1
This commit is contained in:
@@ -9,6 +9,8 @@ import com.budwk.app.sys.models.Sys_config;
|
||||
import com.budwk.app.sys.services.SysConfigService;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.integration.jedis.pubsub.PubSubService;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
@@ -32,6 +34,8 @@ public class SysConfController {
|
||||
private SysConfigService sysConfigService;
|
||||
@Inject
|
||||
private PubSubService pubSubService;
|
||||
@Inject
|
||||
private Dao dao;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/sys/conf/index.html")
|
||||
@@ -40,6 +44,24 @@ public class SysConfController {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动补齐首页图片配置项,保证新版移动端页面配置入口可直接维护。
|
||||
*
|
||||
* @param configKey 配置键。
|
||||
* @param note 配置说明。
|
||||
*/
|
||||
private void ensureAppImageConfig(String configKey, String note) {
|
||||
if (sysConfigService.fetch(configKey) != null) {
|
||||
return;
|
||||
}
|
||||
Sys_config conf = new Sys_config();
|
||||
conf.setConfigKey(configKey);
|
||||
conf.setConfigValue("");
|
||||
conf.setNote(note);
|
||||
conf.setCreatedBy(SecurityUtil.getUserId());
|
||||
sysConfigService.insert(conf);
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("json")
|
||||
@SaCheckPermission("sys.manager.conf.add")
|
||||
@@ -73,13 +95,38 @@ public class SysConfController {
|
||||
@SLog(tag = "修改参数", msg = "${conf.configKey}:${conf.configValue}")
|
||||
public Object editDo(@Param("..") Sys_config conf) {
|
||||
try {
|
||||
// 首页图片可能保存多个文件地址,更新前确保 MySQL 字段容量足够。
|
||||
ensureConfigValueColumn(conf);
|
||||
conf.setUpdatedBy(SecurityUtil.getUserId());
|
||||
if (sysConfigService.updateIgnoreNull(conf) > 0) {
|
||||
pubSubService.fire(RedisConstant.PLATFORM_REDIS_PREFIX + "web:platform", "sys_config");
|
||||
}
|
||||
return Result.success();
|
||||
} catch (Exception e) {
|
||||
return Result.error();
|
||||
log.error("Update sys config failed: " + (conf == null ? "" : conf.getConfigKey()), e);
|
||||
return Result.error("save failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片配置保存多个 URL 时会超过原有短文本长度,MySQL 环境下自动扩容配置值字段。
|
||||
*
|
||||
* @param conf 当前保存的系统配置。
|
||||
*/
|
||||
private void ensureConfigValueColumn(Sys_config conf) {
|
||||
if (conf == null || (!"AppHomeImg".equals(conf.getConfigKey())
|
||||
&& !"H5AppHomeImg".equals(conf.getConfigKey())
|
||||
&& !"AppFeaturedActivityImg".equals(conf.getConfigKey())
|
||||
&& !"AppFestivalBenefitImg".equals(conf.getConfigKey()))) {
|
||||
return;
|
||||
}
|
||||
String configValue = Strings.sNull(conf.getConfigValue());
|
||||
if (configValue.length() <= 100) {
|
||||
return;
|
||||
}
|
||||
String dbType = Strings.sNull(dao.getJdbcExpert().getDatabaseType()).toLowerCase();
|
||||
if (dbType.contains("mysql")) {
|
||||
dao.execute(Sqls.create("ALTER TABLE `sys_config` MODIFY COLUMN `configValue` TEXT"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +153,11 @@ public class SysConfController {
|
||||
@SaCheckPermission("sys.manager.conf")
|
||||
public Object data(@Param("pageNumber") int pageNumber, @Param("pageSize") int pageSize, @Param("pageOrderName") String pageOrderName, @Param("pageOrderBy") String pageOrderBy) {
|
||||
try {
|
||||
// 新版首页使用多图配置,进入配置列表时自动创建缺失配置项。
|
||||
ensureAppImageConfig("AppHomeImg", "PC首页轮播图");
|
||||
ensureAppImageConfig("H5AppHomeImg", "移动端首页轮播图");
|
||||
ensureAppImageConfig("AppFeaturedActivityImg", "精彩活动页顶部图片");
|
||||
ensureAppImageConfig("AppFestivalBenefitImg", "节日福利页顶部图片");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
if (Strings.isNotBlank(pageOrderName) && Strings.isNotBlank(pageOrderBy)) {
|
||||
cnd.orderBy(pageOrderName, PageUtil.getOrder(pageOrderBy));
|
||||
|
||||
@@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.sys.models.Sys_home_activity;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.activity.basic.models.ActivityUserScope;
|
||||
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
|
||||
import io.swagger.annotations.Api;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
@@ -32,7 +32,12 @@ public class SysH5IndexController {
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
@Inject
|
||||
private ActivityBasicScopeService activityBasicScopeService;
|
||||
|
||||
/**
|
||||
* 移动端首页主页面。
|
||||
*/
|
||||
@At
|
||||
@Ok("beetl:/platform/zhghh5/sys/home/index.html")
|
||||
@SaCheckLogin
|
||||
@@ -40,6 +45,29 @@ public class SysH5IndexController {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动端精彩活动专题页。
|
||||
*/
|
||||
@At
|
||||
@Ok("beetl:/platform/zhghh5/sys/home/featuredActivity.html")
|
||||
@SaCheckLogin
|
||||
public void featuredActivity() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动端节日福利专题页。
|
||||
*/
|
||||
@At
|
||||
@Ok("beetl:/platform/zhghh5/sys/home/festivalBenefit.html")
|
||||
@SaCheckLogin
|
||||
public void festivalBenefit() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动端个人中心页面。
|
||||
*/
|
||||
@At
|
||||
@Ok("beetl:/platform/zhghh5/index/mine/index.html")
|
||||
@SaCheckLogin
|
||||
@@ -47,6 +75,9 @@ public class SysH5IndexController {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动端消息页面。
|
||||
*/
|
||||
@At
|
||||
@Ok("beetl:/platform/zhghh5/index/msg/index.html")
|
||||
@SaCheckLogin
|
||||
@@ -54,6 +85,9 @@ public class SysH5IndexController {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动端工作台页面。
|
||||
*/
|
||||
@At
|
||||
@Ok("beetl:/platform/zhghh5/index/work/index.html")
|
||||
@SaCheckLogin
|
||||
@@ -61,6 +95,11 @@ public class SysH5IndexController {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前用户可见的移动端首页活动。
|
||||
*
|
||||
* @return 当前登录用户有权限访问且处于有效期内的首页活动列表。
|
||||
*/
|
||||
@At
|
||||
@SaCheckLogin
|
||||
@Ok("json")
|
||||
@@ -88,16 +127,19 @@ public class SysH5IndexController {
|
||||
}
|
||||
|
||||
if (allowUserGroupId != null) {
|
||||
int count = dao.count(ActivityUserScope.class, Cnd.where("groupId", "=", allowUserGroupId).and("userId", "=", userId));
|
||||
if (count > 0) {
|
||||
// 用户组范围判断统一交给活动基础服务处理,保持与其他活动功能的成员范围逻辑一致。
|
||||
if (activityBasicScopeService.isUserInGroup(allowUserGroupId, userId)) {
|
||||
allowActivityList.add(activity);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (StrUtil.isNotBlank(allowUserSql)){
|
||||
// 自定义 SQL 范围仅返回 userId 时视为匹配,执行后清理敏感 SQL 字段避免前端暴露。
|
||||
Sql sql = Sqls.create(allowUserSql).setParam("userId", userId);
|
||||
NutMap nutMap = (NutMap) Daos.query(dao, sql.toString(), Sqls.callback.map());
|
||||
sql.setCallback(Sqls.callback.map());
|
||||
dao.execute(sql);
|
||||
NutMap nutMap = (NutMap) sql.getResult();
|
||||
if (Lang.isNotEmpty(nutMap) && StrUtil.isNotBlank(nutMap.getString("userId"))) {
|
||||
allowActivityList.add(activity);
|
||||
}
|
||||
|
||||
@@ -330,16 +330,21 @@ public class SysUserController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取子应用菜单
|
||||
* 获取子应用菜单。
|
||||
*
|
||||
* @param appId
|
||||
* @return
|
||||
* @param appId 子应用菜单 ID。
|
||||
* @param platform 菜单所属端类型,传入 H5 时仅返回移动端菜单,不传时保持原有全量菜单逻辑。
|
||||
* @return 当前用户在指定子应用下有权限访问的菜单树。
|
||||
*/
|
||||
@At
|
||||
@Ok("json")
|
||||
@SaCheckLogin
|
||||
public Result subAppMenus(@Param("appId") String appId) {
|
||||
public Result subAppMenus(@Param("appId") String appId, @Param("platform") String platform) {
|
||||
List<Sys_menu> menus = sysUserService.getMenus(SecurityUtil.getUserId());
|
||||
// 新版移动端应用页按平台展示功能菜单,避免 PC 菜单混入 H5 分类。
|
||||
if (StrUtil.isNotBlank(platform)) {
|
||||
menus = menus.stream().filter(menu -> platform.equals(menu.getPlatform())).toList();
|
||||
}
|
||||
List<Sys_menu> list = SysMenuUtil.createTreeMenus(menus, appId);
|
||||
if (ObjectUtil.isEmpty(list)) {
|
||||
List<Sys_menu> self = menus.stream().filter(menu -> menu.getId().equals(appId)).toList();
|
||||
|
||||
@@ -148,7 +148,8 @@ public class CommonController {
|
||||
@ApiOperation("系统配置")
|
||||
public Result getConfigKey(String key) {
|
||||
Sys_config configKey = sysDictService.dao().fetch(Sys_config.class, Cnd.where("configKey", "=", key));
|
||||
return Result.success().addData(configKey.getConfigValue());
|
||||
// 新版移动端首页会读取可选图片配置,配置未初始化时返回空值让前端使用默认图兜底。
|
||||
return Result.success().addData(configKey == null ? "" : configKey.getConfigValue());
|
||||
}
|
||||
|
||||
@At
|
||||
|
||||
@@ -75,8 +75,9 @@ layout("/layouts/platform.html"){
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="configValue" label="参数值">
|
||||
<template v-if="formData.configKey === 'AppLogo' || formData.configKey === 'AppHomeImg'">
|
||||
<template v-if="formData.configKey === 'AppLogo'">
|
||||
<file-upload
|
||||
key="AppLogo"
|
||||
style="--upload-width: 214px;--upload-height:64px"
|
||||
:value.sync="formData.configValue"
|
||||
:upload_number="1"
|
||||
@@ -84,8 +85,55 @@ layout("/layouts/platform.html"){
|
||||
upload_result_category="interval"
|
||||
upload_result_type="url"
|
||||
></file-upload>
|
||||
<span v-if="formData.configKey === 'AppLogo'">最佳分辨率:428 x 128</span>
|
||||
<span v-else-if="formData.configKey === 'AppHomeImg'">最佳分辨率:2756 x 732</span>
|
||||
<span>最佳分辨率:428 x 128</span>
|
||||
</template>
|
||||
<template v-else-if="formData.configKey === 'AppHomeImg'">
|
||||
<file-upload
|
||||
key="AppHomeImg"
|
||||
style="--upload-width: 214px;--upload-height:64px"
|
||||
:value.sync="formData.configValue"
|
||||
:upload_number="5"
|
||||
upload_mode="image"
|
||||
upload_result_category="interval"
|
||||
upload_result_type="url"
|
||||
></file-upload>
|
||||
<span>PC 首页轮播图,最佳分辨率:2756 x 732,最多可上传 5 张,首页按上传顺序轮播</span>
|
||||
</template>
|
||||
<template v-else-if="formData.configKey === 'H5AppHomeImg'">
|
||||
<file-upload
|
||||
key="H5AppHomeImg"
|
||||
style="--upload-width: 214px;--upload-height:110px"
|
||||
:value.sync="formData.configValue"
|
||||
:upload_number="5"
|
||||
upload_mode="image"
|
||||
upload_result_category="interval"
|
||||
upload_result_type="url"
|
||||
></file-upload>
|
||||
<span>移动端首页轮播图,建议分辨率:750 x 386,最多可上传 5 张,首页按上传顺序轮播</span>
|
||||
</template>
|
||||
<template v-else-if="formData.configKey === 'AppFeaturedActivityImg'">
|
||||
<file-upload
|
||||
key="AppFeaturedActivityImg"
|
||||
style="--upload-width: 214px;--upload-height:96px"
|
||||
:value.sync="formData.configValue"
|
||||
:upload_number="1"
|
||||
upload_mode="image"
|
||||
upload_result_category="interval"
|
||||
upload_result_type="url"
|
||||
></file-upload>
|
||||
<span>最佳分辨率:750 x 280,精彩活动页顶部图片</span>
|
||||
</template>
|
||||
<template v-else-if="formData.configKey === 'AppFestivalBenefitImg'">
|
||||
<file-upload
|
||||
key="AppFestivalBenefitImg"
|
||||
style="--upload-width: 214px;--upload-height:96px"
|
||||
:value.sync="formData.configValue"
|
||||
:upload_number="1"
|
||||
upload_mode="image"
|
||||
upload_result_category="interval"
|
||||
upload_result_type="url"
|
||||
></file-upload>
|
||||
<span>最佳分辨率:750 x 280,节日福利页顶部图片</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-if="'true'===formData.configValue||'false'===formData.configValue">
|
||||
|
||||
@@ -35,9 +35,12 @@ const apps = {
|
||||
|
||||
<!-- 右侧内容 -->
|
||||
<div class="tree-content">
|
||||
<!-- 应用列表 -->
|
||||
<div v-if="loading" class="module-loading">
|
||||
<van-loading size="24px">加载中...</van-loading>
|
||||
</div>
|
||||
|
||||
<div :class="['app-grid', { 'app-grid-single': applications.length === 1 }]"
|
||||
v-if="applications.length > 0">
|
||||
v-else-if="isFixedCategory && applications.length > 0">
|
||||
<div v-for="app in applications"
|
||||
:key="app.id"
|
||||
class="app-grid-item"
|
||||
@@ -46,17 +49,35 @@ const apps = {
|
||||
@touchstart="onTouchStart(app)"
|
||||
@touchend="onTouchEnd"
|
||||
@touchmove="onTouchMove">
|
||||
<!-- 图标 -->
|
||||
<div class="app-grid-icon">
|
||||
<van-icon v-if="app.picIcon" :name="app.picIcon" size="45"></van-icon>
|
||||
<van-icon v-else-if="app.icon" :name="app.icon" size="28"></van-icon>
|
||||
<van-icon v-else name="apps-o" size="28"></van-icon>
|
||||
</div>
|
||||
<!-- 名称 -->
|
||||
<div class="app-grid-name">{{ app.name }}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div v-else-if="!isFixedCategory && moduleSections.length > 0" class="module-sections">
|
||||
<div v-for="section in moduleSections" :key="section.id" class="module-section">
|
||||
<div class="module-title">{{ section.name }}</div>
|
||||
<div :class="['module-grid', { 'module-grid-single': section.menus.length === 1 }]">
|
||||
<div
|
||||
v-for="menu in section.menus"
|
||||
:key="menu.id"
|
||||
class="module-feature"
|
||||
@click="navigateToMenu(menu)"
|
||||
>
|
||||
<img v-if="menu.picIcon" :src="menu.picIcon" :alt="menu.name || ''" class="module-feature-img"/>
|
||||
<div v-else class="module-feature-icon">
|
||||
<van-icon :name="menu.icon || 'apps-o'" size="24"></van-icon>
|
||||
</div>
|
||||
<div class="module-feature-name">{{ menu.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<van-empty v-else description="暂无应用"></van-empty>
|
||||
|
||||
@@ -93,6 +114,7 @@ const apps = {
|
||||
return {
|
||||
// 应用列表
|
||||
applications: [],
|
||||
moduleSections: [],
|
||||
// 固定分类列表
|
||||
categories: [
|
||||
{id: "all", name: "全部应用", icon: "fa fa-th-large"},
|
||||
@@ -111,6 +133,7 @@ const apps = {
|
||||
showMenuPopup: false,
|
||||
currentApp: {},
|
||||
currentMenus: [],
|
||||
requestSeq: 0,
|
||||
|
||||
touchTimer: null,
|
||||
isTouchMoved: false,
|
||||
@@ -124,6 +147,9 @@ const apps = {
|
||||
// 当前选中的分类ID
|
||||
currentCategoryId() {
|
||||
return this.allCategories[this.activeTab]?.id || "all"
|
||||
},
|
||||
isFixedCategory() {
|
||||
return ["all", "favorites", "recommended"].includes(this.currentCategoryId)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -145,7 +171,9 @@ const apps = {
|
||||
|
||||
// 加载应用数据
|
||||
loadApps() {
|
||||
const requestSeq = ++this.requestSeq
|
||||
this.loading = true
|
||||
this.moduleSections = []
|
||||
|
||||
// 构建请求参数
|
||||
const params = {
|
||||
@@ -172,12 +200,14 @@ const apps = {
|
||||
// 如果是"推荐应用"分类
|
||||
if (this.currentCategoryId === "recommended") {
|
||||
this.$axios.post("/platform/home/listRecommendApp", { platform: "H5" }).then((result) => {
|
||||
if (requestSeq !== this.requestSeq) return
|
||||
if (result.code === 0) {
|
||||
this.applications = result.data || []
|
||||
this.finishLoadApps(requestSeq)
|
||||
} else {
|
||||
console.error("获取推荐应用失败:", result.msg)
|
||||
this.loading = false
|
||||
}
|
||||
this.loading = false
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -185,28 +215,105 @@ const apps = {
|
||||
// 发送请求获取普通应用列表
|
||||
this.$axios.post("/platform/v4/apps/list?" + queryString)
|
||||
.then((result) => {
|
||||
if (requestSeq !== this.requestSeq) return
|
||||
if (result.code === 0) {
|
||||
this.applications = result.data || []
|
||||
this.finishLoadApps(requestSeq)
|
||||
} else {
|
||||
console.error("获取应用列表失败:", result.msg)
|
||||
this.loading = false
|
||||
}
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
// 加载收藏的应用
|
||||
loadFavorites() {
|
||||
const requestSeq = this.requestSeq
|
||||
this.$axios.post("/platform/v4/apps/favorite", {platform: 'H5'})
|
||||
.then((result) => {
|
||||
if (requestSeq !== this.requestSeq) return
|
||||
if (result.code === 0) {
|
||||
this.applications = result.data || []
|
||||
this.finishLoadApps(requestSeq)
|
||||
} else {
|
||||
console.error("获取收藏应用失败:", result.msg)
|
||||
this.loading = false
|
||||
}
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
finishLoadApps(requestSeq) {
|
||||
if (requestSeq !== this.requestSeq) return
|
||||
if (this.isFixedCategory) {
|
||||
this.moduleSections = []
|
||||
this.loading = false
|
||||
return
|
||||
}
|
||||
this.loadModuleSections(this.applications, requestSeq)
|
||||
},
|
||||
|
||||
// 加载每个模块下的功能菜单
|
||||
loadModuleSections(apps, requestSeq) {
|
||||
const list = apps || []
|
||||
if (!list.length) {
|
||||
this.moduleSections = []
|
||||
this.loading = false
|
||||
return
|
||||
}
|
||||
Promise.all(list.map(app => {
|
||||
return this.$axios.post("/platform/sys/user/subAppMenus", {appId: app.id, platform: "H5"}).then((res) => {
|
||||
const menus = res.code === 0 ? (res.data || []) : []
|
||||
return {
|
||||
id: app.id,
|
||||
name: app.name,
|
||||
app: app,
|
||||
menus: this.resolveModuleMenus(app, menus)
|
||||
}
|
||||
}).catch(() => {
|
||||
return {
|
||||
id: app.id,
|
||||
name: app.name,
|
||||
app: app,
|
||||
menus: this.resolveModuleMenus(app, [])
|
||||
}
|
||||
})
|
||||
})).then(sections => {
|
||||
if (requestSeq !== this.requestSeq) return
|
||||
this.moduleSections = sections.filter(section => section.menus.length > 0)
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
resolveModuleMenus(app, menus) {
|
||||
const items = this.flattenMenus(menus || [])
|
||||
if (items.length > 0) {
|
||||
return items
|
||||
}
|
||||
if (app && app.href) {
|
||||
return [{
|
||||
id: app.id,
|
||||
name: app.name,
|
||||
href: app.href,
|
||||
icon: app.icon,
|
||||
picIcon: app.picIcon,
|
||||
aliasName: app.aliasName
|
||||
}]
|
||||
}
|
||||
return []
|
||||
},
|
||||
|
||||
flattenMenus(menus) {
|
||||
const result = []
|
||||
;(menus || []).forEach(menu => {
|
||||
if (menu.children && menu.children.length > 0) {
|
||||
result.push.apply(result, this.flattenMenus(menu.children))
|
||||
} else if (menu.href) {
|
||||
result.push(menu)
|
||||
}
|
||||
})
|
||||
return result
|
||||
},
|
||||
|
||||
// 切换应用收藏状态
|
||||
toggleFavorite(appId) {
|
||||
const app = this.applications.find((a) => a.id === appId)
|
||||
@@ -254,6 +361,7 @@ const apps = {
|
||||
// 搜索
|
||||
onSearch() {
|
||||
this.applications = []
|
||||
this.moduleSections = []
|
||||
this.loadApps()
|
||||
},
|
||||
|
||||
@@ -267,12 +375,13 @@ const apps = {
|
||||
onClickNav(index) {
|
||||
this.activeTab = index
|
||||
this.applications = []
|
||||
this.moduleSections = []
|
||||
this.loadApps()
|
||||
},
|
||||
|
||||
// 打开应用
|
||||
openApp(app) {
|
||||
this.$axios.post("/platform/sys/user/subAppMenus", {appId: app.id}).then((res) => {
|
||||
this.$axios.post("/platform/sys/user/subAppMenus", {appId: app.id, platform: "H5"}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.showAppMenus(app, res.data)
|
||||
}
|
||||
@@ -321,12 +430,15 @@ const apps = {
|
||||
.apps-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
height: calc(100vh - 50px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 自定义树形选择组件样式 */
|
||||
.custom-tree-select {
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
height: calc(100vh - 150px);
|
||||
background: #fff;
|
||||
}
|
||||
@@ -403,10 +515,109 @@ const apps = {
|
||||
|
||||
.tree-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.module-loading {
|
||||
min-height: 180px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #8a94a6;
|
||||
}
|
||||
|
||||
.module-sections {
|
||||
padding: 12px 12px 18px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.module-section {
|
||||
padding: 0 0 18px;
|
||||
}
|
||||
|
||||
.module-section + .module-section {
|
||||
border-top: 1px solid #f1f3f6;
|
||||
padding-top: 14px;
|
||||
}
|
||||
|
||||
.module-title {
|
||||
position: relative;
|
||||
padding-left: 10px;
|
||||
margin-bottom: 10px;
|
||||
color: #202733;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.module-title::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 4px;
|
||||
width: 3px;
|
||||
height: 14px;
|
||||
border-radius: 2px;
|
||||
background: #1989fa;
|
||||
}
|
||||
|
||||
.module-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 10px 8px;
|
||||
}
|
||||
|
||||
.module-grid-single {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.module-feature {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 8px 2px;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.module-feature:active {
|
||||
background: #f2f7ff;
|
||||
}
|
||||
|
||||
.module-feature-img {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
object-fit: contain;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.module-feature-icon {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 6px;
|
||||
color: #ffffff;
|
||||
background: linear-gradient(135deg, #2f8af5 0%, #49c3dc 100%);
|
||||
}
|
||||
|
||||
.module-feature-name {
|
||||
width: 100%;
|
||||
color: #323233;
|
||||
font-size: 13px;
|
||||
line-height: 1.25;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.app-grid {
|
||||
display: grid;
|
||||
/* 改为自适应列数:每格最小 80px,自动换行 */
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<div class="featured-page">
|
||||
<van-nav-bar
|
||||
title="精彩活动"
|
||||
left-text="返回"
|
||||
left-arrow
|
||||
fixed
|
||||
placeholder
|
||||
@click-left="goBack"
|
||||
></van-nav-bar>
|
||||
|
||||
<div class="featured-banner">
|
||||
<img :src="topImage" alt="">
|
||||
<div class="featured-banner__shade"></div>
|
||||
<div class="featured-banner__text">
|
||||
<div class="featured-banner__title">精彩活动</div>
|
||||
<div class="featured-banner__sub">发现更多校园工会活动</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="featured-list">
|
||||
<template v-if="activityOptions && activityOptions.length > 0">
|
||||
<div v-for="item in activityOptions" :key="item.id" class="act-item" @click="enterAct(item)">
|
||||
<van-tag class="act-item-wrapper-tag" v-if="$moment().unix() > $moment(item.endDate).unix()">
|
||||
已结束
|
||||
</van-tag>
|
||||
|
||||
<van-tag class="act-item-wrapper-tag" type="success"
|
||||
v-else-if="$moment().unix() > $moment(item.startDate).unix() && $moment().unix() < $moment(item.endDate).unix()">
|
||||
进行中
|
||||
</van-tag>
|
||||
|
||||
<van-tag class="act-item-wrapper-tag" type="success" v-else-if="$moment().unix() < $moment(item.startDate).unix()">
|
||||
即将开始
|
||||
</van-tag>
|
||||
|
||||
<div class="act-item-cover">
|
||||
<van-image v-if="item.cover" width="120" height="84" :src="item.cover"></van-image>
|
||||
</div>
|
||||
<div class="act-item-wrapper">
|
||||
<div class="content-title">{{ item.name }}</div>
|
||||
<div class="content-text">
|
||||
<div>开始时间:{{ $moment(item.startDate).format('MM-DD HH:mm') }}</div>
|
||||
<div class="mt5">结束时间:{{ $moment(item.endDate).format('MM-DD HH:mm') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<van-empty v-if="!activityOptions || activityOptions.length === 0" description="暂无精彩活动"></van-empty>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data: function () {
|
||||
return {
|
||||
topImage: "/assets/mobile/img/home/home1.png",
|
||||
activityOptions: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 顶部导航返回入口,统一复用返回首页逻辑。
|
||||
goBack: function () {
|
||||
this.returnHome()
|
||||
},
|
||||
// 返回移动端首页并恢复首页 tab,PJAX 不可用时退回整页跳转。
|
||||
returnHome: function () {
|
||||
if (window.store && typeof window.store.commit === "function") {
|
||||
window.store.commit("setActiveTarBar", "home")
|
||||
}
|
||||
if (!window.$ || !$.support || !$.support.pjax) {
|
||||
window.location.replace("/platform/h5/home")
|
||||
return
|
||||
}
|
||||
var fallbackTimer = window.setTimeout(function () {
|
||||
window.location.replace("/platform/h5/home")
|
||||
}, 1200)
|
||||
$(document).one("pjax:complete", function () {
|
||||
window.clearTimeout(fallbackTimer)
|
||||
})
|
||||
$(document).one("pjax:error pjax:timeout", function () {
|
||||
window.clearTimeout(fallbackTimer)
|
||||
window.location.replace("/platform/h5/home")
|
||||
})
|
||||
$.pjax({
|
||||
url: "/platform/h5/home",
|
||||
container: "#container",
|
||||
fragment: "#container",
|
||||
push: false,
|
||||
replace: true,
|
||||
timeout: 8000
|
||||
})
|
||||
},
|
||||
// 加载精彩活动页顶部图片,未配置时使用默认首页图兜底。
|
||||
loadTopImage: function () {
|
||||
var self = this
|
||||
this.$axios.post("/open/common/getConfigKey", { key: "AppFeaturedActivityImg" }).then(function (res) {
|
||||
if (res.code === 0 && res.data) {
|
||||
self.topImage = String(res.data).split(",")[0]
|
||||
}
|
||||
})
|
||||
},
|
||||
// 查询首页活动并排除福利类活动,形成精彩活动列表。
|
||||
listActivity: function () {
|
||||
var self = this
|
||||
this.$axios.post("/platform/home/listHomeActivity").then(function (resp) {
|
||||
if (resp.code === 0) {
|
||||
self.activityOptions = (resp.data || []).filter(function (item) {
|
||||
return !self.isWelfareItem(item)
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
// 根据活动地址识别福利类活动,用于与节日福利页拆分展示。
|
||||
isWelfareItem: function (item) {
|
||||
var url = ((item && item.url) || "") + "," + ((item && item.h5Url) || "")
|
||||
return url.indexOf("/welfare/") > -1
|
||||
},
|
||||
// 点击活动时先校验开始时间,未开始活动只提示不跳转。
|
||||
enterAct: function (item) {
|
||||
if (this.$moment().unix() < this.$moment(item.startDate).unix()) {
|
||||
this.$toast("活动即将开始")
|
||||
return
|
||||
}
|
||||
this.$pjaxReplace(item.h5Url)
|
||||
}
|
||||
},
|
||||
created: function () {
|
||||
this.loadTopImage()
|
||||
this.listActivity()
|
||||
},
|
||||
style: /*language=CSS*/ `
|
||||
body {
|
||||
background-color: #f4f7fb;
|
||||
}
|
||||
|
||||
.featured-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f4f7fb;
|
||||
padding-bottom: 18px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.featured-page /deep/ .van-nav-bar {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.featured-page /deep/ .van-nav-bar__title {
|
||||
color: #1f2937;
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.featured-page /deep/ .van-nav-bar .van-icon,
|
||||
.featured-page /deep/ .van-nav-bar__text {
|
||||
color: #1989fa;
|
||||
}
|
||||
|
||||
.featured-banner {
|
||||
position: relative;
|
||||
height: 140px;
|
||||
margin: 12px 12px 10px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
background-color: #e9eef6;
|
||||
box-shadow: 0 8px 22px rgba(31, 41, 55, .1);
|
||||
}
|
||||
|
||||
.featured-banner img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.featured-banner__shade {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(90deg, rgba(0, 0, 0, .42), rgba(0, 0, 0, .08));
|
||||
}
|
||||
|
||||
.featured-banner__text {
|
||||
position: absolute;
|
||||
left: 18px;
|
||||
bottom: 18px;
|
||||
color: #fff;
|
||||
text-shadow: 0 2px 10px rgba(0, 0, 0, .26);
|
||||
}
|
||||
|
||||
.featured-banner__title {
|
||||
font-size: 24px;
|
||||
line-height: 30px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.featured-banner__sub {
|
||||
margin-top: 5px;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
opacity: .92;
|
||||
}
|
||||
|
||||
.featured-list {
|
||||
margin: 0 10px;
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
padding: 8px 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.act-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
padding: 10px 12px;
|
||||
background-color: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.act-item + .act-item {
|
||||
border-top: 1px solid #f0f2f5;
|
||||
}
|
||||
|
||||
.featured-list /deep/ .act-item-wrapper-tag {
|
||||
position: absolute !important;
|
||||
top: 10px !important;
|
||||
left: 12px !important;
|
||||
z-index: 2;
|
||||
width: auto !important;
|
||||
min-width: 42px;
|
||||
height: 20px !important;
|
||||
flex: none !important;
|
||||
padding: 0 6px !important;
|
||||
margin: 0 !important;
|
||||
border: 0 !important;
|
||||
border-radius: 2px !important;
|
||||
display: inline-flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff !important;
|
||||
background-color: #1f73b7 !important;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
white-space: nowrap !important;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.featured-list /deep/ .act-item-wrapper-tag.van-tag--success {
|
||||
background-color: #1f73b7 !important;
|
||||
}
|
||||
|
||||
.act-item-cover {
|
||||
width: 120px;
|
||||
height: 84px;
|
||||
flex: 0 0 120px;
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
background-color: #f2f3f5;
|
||||
}
|
||||
|
||||
.act-item-cover /deep/ .van-image,
|
||||
.act-item-cover /deep/ img {
|
||||
display: block;
|
||||
width: 120px;
|
||||
height: 84px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.act-item-wrapper {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
padding-left: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.content-title {
|
||||
color: #111827;
|
||||
font-size: 15px;
|
||||
line-height: 21px;
|
||||
font-weight: 600;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.content-text {
|
||||
margin-top: 9px;
|
||||
color: #6b7280;
|
||||
font-size: 12px;
|
||||
line-height: 17px;
|
||||
}
|
||||
|
||||
.mt5 {
|
||||
margin-top: 5px;
|
||||
}
|
||||
`
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,309 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<div class="festival-page">
|
||||
<van-nav-bar
|
||||
title="节日福利"
|
||||
left-text="返回"
|
||||
left-arrow
|
||||
fixed
|
||||
placeholder
|
||||
@click-left="goBack"
|
||||
></van-nav-bar>
|
||||
|
||||
<div class="festival-banner">
|
||||
<img :src="topImage" alt="">
|
||||
<div class="festival-banner__shade"></div>
|
||||
<div class="festival-banner__text">
|
||||
<div class="festival-banner__title">节日福利</div>
|
||||
<div class="festival-banner__sub">查看可参与的节日福利活动</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="festival-list">
|
||||
<template v-if="activityOptions && activityOptions.length > 0">
|
||||
<div v-for="item in activityOptions" :key="item.id" class="act-item" @click="enterAct(item)">
|
||||
<van-tag class="act-item-wrapper-tag" v-if="$moment().unix() > $moment(item.endDate).unix()">
|
||||
已结束
|
||||
</van-tag>
|
||||
|
||||
<van-tag class="act-item-wrapper-tag" type="success"
|
||||
v-else-if="$moment().unix() > $moment(item.startDate).unix() && $moment().unix() < $moment(item.endDate).unix()">
|
||||
进行中
|
||||
</van-tag>
|
||||
|
||||
<van-tag class="act-item-wrapper-tag" type="success" v-else-if="$moment().unix() < $moment(item.startDate).unix()">
|
||||
即将开始
|
||||
</van-tag>
|
||||
|
||||
<div class="act-item-cover">
|
||||
<van-image v-if="item.cover" width="120" height="84" :src="item.cover"></van-image>
|
||||
</div>
|
||||
<div class="act-item-wrapper">
|
||||
<div class="content-title">{{ item.name }}</div>
|
||||
<div class="content-text">
|
||||
<div>开始时间:{{ $moment(item.startDate).format('MM-DD HH:mm') }}</div>
|
||||
<div class="mt5">结束时间:{{ $moment(item.endDate).format('MM-DD HH:mm') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<van-empty v-if="!activityOptions || activityOptions.length === 0" description="暂无节日福利"></van-empty>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data: function () {
|
||||
return {
|
||||
topImage: "/assets/mobile/img/home/home2.png",
|
||||
activityOptions: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 顶部导航返回入口,统一复用返回首页逻辑。
|
||||
goBack: function () {
|
||||
this.returnHome()
|
||||
},
|
||||
// 返回移动端首页并恢复首页 tab,PJAX 不可用时退回整页跳转。
|
||||
returnHome: function () {
|
||||
if (window.store && typeof window.store.commit === "function") {
|
||||
window.store.commit("setActiveTarBar", "home")
|
||||
}
|
||||
if (!window.$ || !$.support || !$.support.pjax) {
|
||||
window.location.replace("/platform/h5/home")
|
||||
return
|
||||
}
|
||||
var fallbackTimer = window.setTimeout(function () {
|
||||
window.location.replace("/platform/h5/home")
|
||||
}, 1200)
|
||||
$(document).one("pjax:complete", function () {
|
||||
window.clearTimeout(fallbackTimer)
|
||||
})
|
||||
$(document).one("pjax:error pjax:timeout", function () {
|
||||
window.clearTimeout(fallbackTimer)
|
||||
window.location.replace("/platform/h5/home")
|
||||
})
|
||||
$.pjax({
|
||||
url: "/platform/h5/home",
|
||||
container: "#container",
|
||||
fragment: "#container",
|
||||
push: false,
|
||||
replace: true,
|
||||
timeout: 8000
|
||||
})
|
||||
},
|
||||
// 加载节日福利页顶部图片,未配置时使用默认首页图兜底。
|
||||
loadTopImage: function () {
|
||||
var self = this
|
||||
this.$axios.post("/open/common/getConfigKey", { key: "AppFestivalBenefitImg" }).then(function (res) {
|
||||
if (res.code === 0 && res.data) {
|
||||
self.topImage = String(res.data).split(",")[0]
|
||||
}
|
||||
})
|
||||
},
|
||||
// 查询首页活动并仅保留福利类活动,形成节日福利列表。
|
||||
listActivity: function () {
|
||||
var self = this
|
||||
this.$axios.post("/platform/home/listHomeActivity").then(function (resp) {
|
||||
if (resp.code === 0) {
|
||||
self.activityOptions = (resp.data || []).filter(function (item) {
|
||||
return self.isWelfareItem(item)
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
// 根据活动地址识别福利类活动,用于与精彩活动页拆分展示。
|
||||
isWelfareItem: function (item) {
|
||||
var url = ((item && item.url) || "") + "," + ((item && item.h5Url) || "")
|
||||
return url.indexOf("/welfare/") > -1
|
||||
},
|
||||
// 点击活动时先校验开始时间,未开始活动只提示不跳转。
|
||||
enterAct: function (item) {
|
||||
if (this.$moment().unix() < this.$moment(item.startDate).unix()) {
|
||||
this.$toast("活动即将开始")
|
||||
return
|
||||
}
|
||||
this.$pjaxReplace(item.h5Url)
|
||||
}
|
||||
},
|
||||
created: function () {
|
||||
this.loadTopImage()
|
||||
this.listActivity()
|
||||
},
|
||||
style: /*language=CSS*/ `
|
||||
body {
|
||||
background-color: #f4f7fb;
|
||||
}
|
||||
|
||||
.festival-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f4f7fb;
|
||||
padding-bottom: 18px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.festival-page /deep/ .van-nav-bar {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.festival-page /deep/ .van-nav-bar__title {
|
||||
color: #1f2937;
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.festival-page /deep/ .van-nav-bar .van-icon,
|
||||
.festival-page /deep/ .van-nav-bar__text {
|
||||
color: #1989fa;
|
||||
}
|
||||
|
||||
.festival-banner {
|
||||
position: relative;
|
||||
height: 140px;
|
||||
margin: 12px 12px 10px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
background-color: #e9eef6;
|
||||
box-shadow: 0 8px 22px rgba(31, 41, 55, .1);
|
||||
}
|
||||
|
||||
.festival-banner img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.festival-banner__shade {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(90deg, rgba(0, 0, 0, .42), rgba(0, 0, 0, .08));
|
||||
}
|
||||
|
||||
.festival-banner__text {
|
||||
position: absolute;
|
||||
left: 18px;
|
||||
bottom: 18px;
|
||||
color: #fff;
|
||||
text-shadow: 0 2px 10px rgba(0, 0, 0, .26);
|
||||
}
|
||||
|
||||
.festival-banner__title {
|
||||
font-size: 24px;
|
||||
line-height: 30px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.festival-banner__sub {
|
||||
margin-top: 5px;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
opacity: .92;
|
||||
}
|
||||
|
||||
.festival-list {
|
||||
margin: 0 10px;
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
padding: 8px 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.act-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
padding: 10px 12px;
|
||||
background-color: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.act-item + .act-item {
|
||||
border-top: 1px solid #f0f2f5;
|
||||
}
|
||||
|
||||
.festival-list /deep/ .act-item-wrapper-tag {
|
||||
position: absolute !important;
|
||||
top: 10px !important;
|
||||
left: 12px !important;
|
||||
z-index: 2;
|
||||
width: auto !important;
|
||||
min-width: 42px;
|
||||
height: 20px !important;
|
||||
flex: none !important;
|
||||
padding: 0 6px !important;
|
||||
margin: 0 !important;
|
||||
border: 0 !important;
|
||||
border-radius: 2px !important;
|
||||
display: inline-flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff !important;
|
||||
background-color: #1f73b7 !important;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
white-space: nowrap !important;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.festival-list /deep/ .act-item-wrapper-tag.van-tag--success {
|
||||
background-color: #1f73b7 !important;
|
||||
}
|
||||
|
||||
.act-item-cover {
|
||||
width: 120px;
|
||||
height: 84px;
|
||||
flex: 0 0 120px;
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
background-color: #f2f3f5;
|
||||
}
|
||||
|
||||
.act-item-cover /deep/ .van-image,
|
||||
.act-item-cover /deep/ img {
|
||||
display: block;
|
||||
width: 120px;
|
||||
height: 84px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.act-item-wrapper {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
padding-left: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.content-title {
|
||||
color: #111827;
|
||||
font-size: 15px;
|
||||
line-height: 21px;
|
||||
font-weight: 600;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.content-text {
|
||||
margin-top: 9px;
|
||||
color: #6b7280;
|
||||
font-size: 12px;
|
||||
line-height: 17px;
|
||||
}
|
||||
|
||||
.mt5 {
|
||||
margin-top: 5px;
|
||||
}
|
||||
`
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -1,37 +1,88 @@
|
||||
const home = {
|
||||
template: /*language=HTML*/ `
|
||||
<div>
|
||||
<van-nav-bar title="首页" class="nav-bar" placeholder fixed></van-nav-bar>
|
||||
<van-swipe :autoplay="100000" :height="200" class="home__swipe">
|
||||
<van-swipe-item>
|
||||
<img src="/assets/mobile/img/home/home1.png" alt="">
|
||||
</van-swipe-item>
|
||||
<van-swipe-item>
|
||||
<img src="/assets/mobile/img/home/home2.png" alt="">
|
||||
</van-swipe-item>
|
||||
<van-swipe-item>
|
||||
<img src="/assets/mobile/img/home/home3.png" alt="">
|
||||
</van-swipe-item>
|
||||
</van-swipe>
|
||||
<div class="home-hero">
|
||||
<img class="home-hero__bg" :src="currentBanner.src" alt="">
|
||||
<div class="home-hero__mask"></div>
|
||||
<div class="home-hero__logo">
|
||||
<img v-if="appLogo" class="home-hero__logo-img" :src="appLogo" alt="">
|
||||
<div v-else class="home-hero__logo-mark"></div>
|
||||
<div class="home-hero__logo-title" :class="{ 'is-placeholder': !appName }">
|
||||
<span v-if="appName">{{ appName }}</span>
|
||||
<template v-else>
|
||||
<i></i>
|
||||
<i></i>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<van-swipe
|
||||
:autoplay="4000"
|
||||
:height="193"
|
||||
class="home__swipe"
|
||||
indicator-color="#e13b4c"
|
||||
@change="bannerChange">
|
||||
<van-swipe-item v-for="banner in bannerList" :key="banner.src">
|
||||
<div class="home__swipe-card">
|
||||
<img :src="banner.src" alt="">
|
||||
</div>
|
||||
</van-swipe-item>
|
||||
</van-swipe>
|
||||
</div>
|
||||
|
||||
<!--快捷入口-->
|
||||
<div class="quick-grid">
|
||||
<div class="section-title">推荐服务</div>
|
||||
<van-grid :column-num="4" icon-size="46" square clickable :border="false">
|
||||
<van-grid-item v-for="item in quickEntries"
|
||||
@click="menuClick(item)"
|
||||
:key="item.id"
|
||||
:text="item.name">
|
||||
<img :src="item.picIcon"
|
||||
slot="icon"
|
||||
style="width: 40px; height: 40px"/>
|
||||
</van-grid-item>
|
||||
</van-grid>
|
||||
<div class="quick-scroll">
|
||||
<div class="quick-scroll-grid">
|
||||
<div class="quick-entry-item"
|
||||
v-for="item in quickEntries"
|
||||
@click="menuClick(item)"
|
||||
:key="item.id">
|
||||
<div class="quick-entry-icon">
|
||||
<img v-if="item.picIcon" :src="item.picIcon" alt="">
|
||||
<van-icon v-else name="apps-o" size="28"></van-icon>
|
||||
</div>
|
||||
<div class="quick-entry-name">{{ item.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--活动列表-->
|
||||
<div class="act-list">
|
||||
<div class="section-title">最新活动</div>
|
||||
<!--专题入口-->
|
||||
<div class="feature-panel">
|
||||
<div
|
||||
class="feature-card"
|
||||
:class="[item.className, { 'feature-card--large': item.large }]"
|
||||
v-for="item in featuredEntries"
|
||||
:key="item.name"
|
||||
@click="featureClick(item)">
|
||||
<div class="feature-card__text">
|
||||
<div class="feature-card__title">{{ item.name }}</div>
|
||||
<div class="feature-card__desc">{{ item.desc }}</div>
|
||||
</div>
|
||||
<van-icon class="feature-card__icon" :name="item.icon"></van-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="classroom-panel" v-if="classroomCourses.length > 0" @click="enterClassroom">
|
||||
<div class="classroom-panel__title">我的课堂</div>
|
||||
<div class="classroom-panel__course">{{ currentClassroomCourseName }}</div>
|
||||
<div class="classroom-panel__action">
|
||||
<span>个人学习</span>
|
||||
<van-icon name="arrow"></van-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="policy-panel">
|
||||
<div class="policy-panel__title">政策文件</div>
|
||||
<div class="policy-panel__action">
|
||||
<span>更多</span>
|
||||
<van-icon name="arrow"></van-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="act-list" v-if="showLatestActivity">
|
||||
<div class="section-title section-title--hidden">最新活动</div>
|
||||
<template v-if="activityOptions && activityOptions.length > 0">
|
||||
<div v-for="item in activityOptions" :key="item.id" class="act-item" @click="enterAct(item)">
|
||||
<van-tag class="act-item-wrapper-tag" v-if="$moment().unix() > $moment(item.endDate).unix()">
|
||||
@@ -68,61 +119,773 @@ const home = {
|
||||
data() {
|
||||
return {
|
||||
activityOptions: [],
|
||||
quickEntries: []
|
||||
quickEntries: [],
|
||||
classroomCourses: [],
|
||||
classroomCourseIndex: 0,
|
||||
classroomCourseTimer: null,
|
||||
showLatestActivity: false,
|
||||
featuredEntries: [
|
||||
{
|
||||
name: "精彩活动",
|
||||
desc: "更多精彩活动",
|
||||
icon: "fire-o",
|
||||
className: "feature-card--activity",
|
||||
large: true,
|
||||
href: "/platform/h5/featuredActivity"
|
||||
},
|
||||
{
|
||||
name: "节日福利",
|
||||
desc: "领取节日福利",
|
||||
icon: "gift-o",
|
||||
className: "feature-card--benefit",
|
||||
href: "/platform/h5/festivalBenefit"
|
||||
},
|
||||
{
|
||||
name: "劳模先进",
|
||||
desc: "先进风采展示",
|
||||
icon: "medal-o",
|
||||
className: "feature-card--model",
|
||||
href: ""
|
||||
},
|
||||
{
|
||||
name: "普惠信息",
|
||||
desc: "普惠服务信息",
|
||||
icon: "bullhorn-o",
|
||||
className: "feature-card--inclusive",
|
||||
href: ""
|
||||
}
|
||||
],
|
||||
appLogo: "",
|
||||
appName: "",
|
||||
activeBannerIndex: 0,
|
||||
bannerLoadSeq: 0,
|
||||
bannerImageCache: {},
|
||||
defaultBannerList: [
|
||||
{ src: "/assets/mobile/img/home/home1.png" },
|
||||
{ src: "/assets/mobile/img/home/home2.png" },
|
||||
{ src: "/assets/mobile/img/home/home3.png" }
|
||||
],
|
||||
bannerList: [
|
||||
{ src: "/assets/mobile/img/home/home1.png" },
|
||||
{ src: "/assets/mobile/img/home/home2.png" },
|
||||
{ src: "/assets/mobile/img/home/home3.png" }
|
||||
],
|
||||
homeCachePrefix: "zhgh:h5:home:",
|
||||
homeCacheTtl: {
|
||||
banner: 30 * 60 * 1000,
|
||||
brand: 30 * 60 * 1000,
|
||||
recommend: 10 * 60 * 1000,
|
||||
classroom: 5 * 60 * 1000,
|
||||
activity: 2 * 60 * 1000
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentBanner() {
|
||||
return this.bannerList[this.activeBannerIndex] || this.bannerList[0] || {}
|
||||
},
|
||||
currentClassroomCourseName() {
|
||||
const course = this.classroomCourses[this.classroomCourseIndex]
|
||||
return course && course.courseName ? course.courseName : ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 根据登录用户隔离首页缓存,避免多人共用浏览器时读取到其他用户的数据。
|
||||
getCacheKey(key) {
|
||||
const userId = this.$store && this.$store.state.user ? (this.$store.state.user.id || "anonymous") : "anonymous"
|
||||
return this.homeCachePrefix + userId + ":" + key
|
||||
},
|
||||
// 读取首页缓存并校验过期时间,缓存失效时主动清理本地存储。
|
||||
readHomeCache(key) {
|
||||
try {
|
||||
const raw = window.localStorage.getItem(this.getCacheKey(key))
|
||||
if (!raw) {
|
||||
return null
|
||||
}
|
||||
const cache = JSON.parse(raw)
|
||||
if (!cache || !cache.expireAt || cache.expireAt < Date.now()) {
|
||||
window.localStorage.removeItem(this.getCacheKey(key))
|
||||
return null
|
||||
}
|
||||
return cache.value
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
},
|
||||
// 写入首页缓存,缓存异常不影响首页主流程加载。
|
||||
writeHomeCache(key, value, ttl) {
|
||||
try {
|
||||
window.localStorage.setItem(this.getCacheKey(key), JSON.stringify({
|
||||
expireAt: Date.now() + ttl,
|
||||
value: value
|
||||
}))
|
||||
} catch (e) {
|
||||
// Ignore cache quota and private mode errors.
|
||||
}
|
||||
},
|
||||
// 将后台配置的逗号分隔图片地址转换为轮播图数据。
|
||||
parseBannerList(value) {
|
||||
if (!value || typeof value !== "string") {
|
||||
return []
|
||||
}
|
||||
return value.split(",")
|
||||
.map(item => item.trim())
|
||||
.filter(item => item)
|
||||
.map(src => ({ src }))
|
||||
},
|
||||
// 加载移动端首页轮播图,优先展示缓存,再用后台配置刷新。
|
||||
listHomeBanner() {
|
||||
const cached = this.readHomeCache("h5Banner")
|
||||
if (cached && cached.length) {
|
||||
this.bannerList = cached
|
||||
this.activeBannerIndex = 0
|
||||
}
|
||||
this.$axios.post("/open/common/getConfigKey", { key: "H5AppHomeImg" }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
const configBannerList = this.parseBannerList(res.data)
|
||||
const nextBannerList = configBannerList.length > 0 ? configBannerList : this.defaultBannerList
|
||||
this.bannerList = nextBannerList
|
||||
this.activeBannerIndex = 0
|
||||
this.writeHomeCache("h5Banner", nextBannerList, this.homeCacheTtl.banner)
|
||||
this.cacheBannerImages(nextBannerList)
|
||||
}
|
||||
}).catch(() => {
|
||||
if (!cached) {
|
||||
this.bannerList = this.defaultBannerList
|
||||
}
|
||||
})
|
||||
},
|
||||
shouldInlineBanner(src) {
|
||||
return typeof src === "string" && src.indexOf("/platform/sys/file/download") !== -1
|
||||
},
|
||||
// 将需要鉴权下载的轮播图片转为内联图片,提升移动端切换稳定性。
|
||||
cacheBannerImages(list) {
|
||||
const seq = ++this.bannerLoadSeq
|
||||
Promise.all(list.map((banner) => {
|
||||
return this.inlineBannerImage(banner.src).then((src) => Object.assign({}, banner, { src }))
|
||||
})).then((cachedList) => {
|
||||
if (seq === this.bannerLoadSeq) {
|
||||
this.bannerList = cachedList
|
||||
this.activeBannerIndex = 0
|
||||
}
|
||||
})
|
||||
},
|
||||
// 下载平台文件图片并转为 dataURL,普通静态图片保持原地址。
|
||||
inlineBannerImage(src) {
|
||||
if (!this.shouldInlineBanner(src)) {
|
||||
return Promise.resolve(src)
|
||||
}
|
||||
if (this.bannerImageCache[src]) {
|
||||
return Promise.resolve(this.bannerImageCache[src])
|
||||
}
|
||||
return fetch(src, {
|
||||
credentials: "same-origin",
|
||||
cache: "force-cache"
|
||||
}).then((resp) => {
|
||||
if (!resp.ok) {
|
||||
throw new Error("load image failed")
|
||||
}
|
||||
return resp.blob()
|
||||
}).then((blob) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = () => resolve(reader.result)
|
||||
reader.onerror = reject
|
||||
reader.readAsDataURL(blob)
|
||||
})
|
||||
}).then((dataUrl) => {
|
||||
this.bannerImageCache[src] = dataUrl
|
||||
return dataUrl
|
||||
}).catch(() => src)
|
||||
},
|
||||
// 按配置键读取系统配置值,读取失败时返回空值用于页面降级展示。
|
||||
getConfigValue(key) {
|
||||
return this.$axios.post("/open/common/getConfigKey", { key }).then((res) => {
|
||||
return res.code === 0 ? (res.data || "") : ""
|
||||
}).catch(() => "")
|
||||
},
|
||||
// 加载移动端首页展示的系统标识和系统名称。
|
||||
listHomeBrand() {
|
||||
const cached = this.readHomeCache("brand")
|
||||
if (cached) {
|
||||
this.appLogo = cached.appLogo || ""
|
||||
this.appName = cached.appName || ""
|
||||
}
|
||||
Promise.all([
|
||||
this.getConfigValue("AppLogo"),
|
||||
this.getConfigValue("AppName")
|
||||
]).then(([appLogo, appName]) => {
|
||||
this.appLogo = appLogo
|
||||
this.appName = appName
|
||||
this.writeHomeCache("brand", { appLogo, appName }, this.homeCacheTtl.brand)
|
||||
})
|
||||
},
|
||||
listRecommendApp() {
|
||||
const cached = this.readHomeCache("recommend")
|
||||
if (cached && cached.length) {
|
||||
this.quickEntries = cached
|
||||
}
|
||||
this.$axios.post("/platform/home/listRecommendService", { platform: "H5" }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.quickEntries = res.data
|
||||
this.writeHomeCache("recommend", res.data || [], this.homeCacheTtl.recommend)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
listActivity() {
|
||||
const cached = this.readHomeCache("activity")
|
||||
if (cached && cached.length) {
|
||||
this.activityOptions = cached
|
||||
}
|
||||
this.$axios.post("/platform/home/listHomeActivity").then((resp) => {
|
||||
if (resp.code === 0) {
|
||||
this.activityOptions = resp.data
|
||||
this.writeHomeCache("activity", resp.data || [], this.homeCacheTtl.activity)
|
||||
}
|
||||
})
|
||||
},
|
||||
// 加载课堂课程摘要,用于首页“我的课堂”区域轮播展示课程名称。
|
||||
listClassroomCourses() {
|
||||
const cached = this.readHomeCache("classroom")
|
||||
if (cached && cached.length) {
|
||||
this.classroomCourses = cached
|
||||
this.classroomCourseIndex = 0
|
||||
this.startClassroomCourseTimer()
|
||||
}
|
||||
this.$axios.post("/platform/learning/course/display/pageData", {
|
||||
pageNumber: 1,
|
||||
pageSize: 20
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
const data = res.data || {}
|
||||
this.classroomCourses = data.list || []
|
||||
this.classroomCourseIndex = 0
|
||||
this.writeHomeCache("classroom", this.classroomCourses, this.homeCacheTtl.classroom)
|
||||
this.startClassroomCourseTimer()
|
||||
}
|
||||
}).catch(() => {
|
||||
if (!cached) {
|
||||
this.classroomCourses = []
|
||||
this.classroomCourseIndex = 0
|
||||
}
|
||||
})
|
||||
},
|
||||
// 启动课堂课程名称轮播,重新加载课程时先清理旧定时器。
|
||||
startClassroomCourseTimer() {
|
||||
if (this.classroomCourseTimer) {
|
||||
clearInterval(this.classroomCourseTimer)
|
||||
this.classroomCourseTimer = null
|
||||
}
|
||||
if (this.classroomCourses.length <= 1) {
|
||||
return
|
||||
}
|
||||
this.classroomCourseTimer = setInterval(() => {
|
||||
this.classroomCourseIndex = (this.classroomCourseIndex + 1) % this.classroomCourses.length
|
||||
}, 3000)
|
||||
},
|
||||
menuClick(item) {
|
||||
this.$pjaxReplace(item.href)
|
||||
},
|
||||
//点击进入活动
|
||||
// 处理专题入口跳转,未配置地址的入口只展示不跳转。
|
||||
featureClick(item) {
|
||||
if (item.href) {
|
||||
this.$pjaxReplace(item.href)
|
||||
}
|
||||
},
|
||||
// 进入移动端课堂列表页。
|
||||
enterClassroom() {
|
||||
this.$pjaxReplace("/platform/learning/course/h5")
|
||||
},
|
||||
// 同步当前轮播索引,供背景模糊图跟随主图切换。
|
||||
bannerChange(index) {
|
||||
this.activeBannerIndex = index
|
||||
},
|
||||
// 点击进入活动详情,未开始的活动只提示不跳转。
|
||||
enterAct(item) {
|
||||
if(this.$moment().unix() < this.$moment(item.startDate).unix()) {
|
||||
this.$toast('活动即将开始')
|
||||
this.$toast("活动即将开始")
|
||||
return
|
||||
}
|
||||
this.$pjaxReplace(item.h5Url)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.listHomeBanner()
|
||||
this.listHomeBrand()
|
||||
this.listRecommendApp()
|
||||
this.listActivity()
|
||||
this.listClassroomCourses()
|
||||
if (this.showLatestActivity) {
|
||||
this.listActivity()
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.classroomCourseTimer) {
|
||||
clearInterval(this.classroomCourseTimer)
|
||||
this.classroomCourseTimer = null
|
||||
}
|
||||
},
|
||||
style: /*language=CSS*/ `
|
||||
.home__swipe .van-swipe-item img {
|
||||
.home-hero {
|
||||
position: relative;
|
||||
min-height: 281px;
|
||||
overflow: hidden;
|
||||
padding: 26px 16px 0;
|
||||
background-color: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.home-hero::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 187px;
|
||||
z-index: 1;
|
||||
width: 180%;
|
||||
height: 96px;
|
||||
transform: translateX(-50%);
|
||||
background-color: #fff;
|
||||
border-radius: 50% 50% 0 0 / 100% 100% 0 0;
|
||||
}
|
||||
|
||||
.home-hero__bg {
|
||||
position: absolute;
|
||||
left: -24px;
|
||||
top: -24px;
|
||||
width: calc(100% + 48px);
|
||||
height: 239px;
|
||||
object-fit: cover;
|
||||
filter: blur(20px);
|
||||
transform: scale(1.12);
|
||||
opacity: .88;
|
||||
transition: opacity .25s ease;
|
||||
}
|
||||
|
||||
.home-hero__mask {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 239px;
|
||||
background: rgba(0, 0, 0, .06);
|
||||
}
|
||||
|
||||
.home-hero__logo {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 36px;
|
||||
margin-bottom: 12px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.home-hero__logo-mark {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border: 1px solid rgba(255, 255, 255, .72);
|
||||
border-radius: 50%;
|
||||
box-sizing: border-box;
|
||||
background: rgba(255, 255, 255, .2);
|
||||
backdrop-filter: blur(2px);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.home-hero__logo-img {
|
||||
display: block;
|
||||
max-width: 210px;
|
||||
max-height: 36px;
|
||||
object-fit: contain;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.home-hero__logo-title {
|
||||
min-width: 0;
|
||||
margin-left: 10px;
|
||||
color: #fff;
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
text-shadow: 0 1px 8px rgba(0, 0, 0, .24);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.home-hero__logo-title.is-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
width: 154px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.home-hero__logo-title i {
|
||||
display: block;
|
||||
height: 10px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, .72);
|
||||
}
|
||||
|
||||
.home-hero__logo-title i + i {
|
||||
width: 66%;
|
||||
height: 7px;
|
||||
opacity: .75;
|
||||
}
|
||||
|
||||
.home__swipe {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
overflow: visible;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.home__swipe-card {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border-radius: 12px;
|
||||
background-color: rgba(255, 255, 255, .26);
|
||||
box-shadow: 0 10px 28px rgba(0, 0, 0, .1);
|
||||
}
|
||||
|
||||
.home__swipe-card img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
.home__swipe /deep/ .van-swipe__track,
|
||||
.home__swipe /deep/ .van-swipe-item {
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.home__swipe /deep/ .van-swipe__indicators {
|
||||
bottom: -14px;
|
||||
}
|
||||
|
||||
.home__swipe /deep/ .van-swipe__indicator {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
background-color: rgba(225, 59, 76, .28);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.home__swipe /deep/ .van-swipe__indicator--active {
|
||||
background-color: #e13b4c;
|
||||
}
|
||||
|
||||
.quick-grid {
|
||||
background-color: #fff;
|
||||
margin: 10px;
|
||||
margin: 10px 10px 10px;
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.quick-grid .van-grid-item .van-grid-item__text {
|
||||
.quick-scroll {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.quick-scroll::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.quick-scroll-grid {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-template-rows: repeat(2, 82px);
|
||||
grid-auto-columns: 25%;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.quick-entry-item {
|
||||
min-width: 0;
|
||||
height: 82px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px 2px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.quick-entry-item:active {
|
||||
background-color: #f2f7ff;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.quick-entry-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 7px;
|
||||
color: #1989fa;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.quick-entry-icon img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.quick-entry-name {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 85%;
|
||||
width: 90%;
|
||||
text-align: center;
|
||||
color: #323233;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.feature-panel {
|
||||
margin: 10px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
grid-auto-rows: 82px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 10px;
|
||||
padding: 14px 12px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
min-width: 0;
|
||||
box-shadow: 0 6px 18px rgba(42, 65, 92, .08);
|
||||
}
|
||||
|
||||
.feature-card:active {
|
||||
transform: scale(.985);
|
||||
}
|
||||
|
||||
.feature-card--large {
|
||||
grid-column: span 2;
|
||||
grid-row: span 2;
|
||||
}
|
||||
|
||||
.feature-card__text {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.feature-card__title {
|
||||
font-size: 17px;
|
||||
line-height: 22px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 5px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.feature-card__desc {
|
||||
color: rgba(55, 65, 81, .64);
|
||||
font-size: 12px;
|
||||
line-height: 17px;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.feature-card__icon {
|
||||
position: absolute;
|
||||
right: 14px;
|
||||
bottom: 12px;
|
||||
font-size: 36px;
|
||||
opacity: .78;
|
||||
}
|
||||
|
||||
.feature-card--large .feature-card__icon {
|
||||
right: 18px;
|
||||
bottom: 18px;
|
||||
font-size: 56px;
|
||||
}
|
||||
|
||||
.feature-card--benefit,
|
||||
.feature-card--model {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.feature-card--benefit .feature-card__desc,
|
||||
.feature-card--model .feature-card__desc {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.feature-card--benefit .feature-card__title,
|
||||
.feature-card--model .feature-card__title {
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
margin-bottom: 0;
|
||||
overflow: visible;
|
||||
text-overflow: clip;
|
||||
}
|
||||
|
||||
.feature-card--benefit .feature-card__icon,
|
||||
.feature-card--model .feature-card__icon {
|
||||
position: static;
|
||||
display: block;
|
||||
margin: 8px auto 0;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.feature-card--inclusive .feature-card__icon {
|
||||
font-size: 44px;
|
||||
}
|
||||
|
||||
.feature-card--activity {
|
||||
background: linear-gradient(145deg, #fff2e5 0%, #ffe2c8 100%);
|
||||
color: #ff7a25;
|
||||
}
|
||||
|
||||
.feature-card--benefit {
|
||||
background: linear-gradient(145deg, #fff1f4 0%, #ffd5dc 100%);
|
||||
color: #f35f74;
|
||||
}
|
||||
|
||||
.feature-card--model {
|
||||
background: linear-gradient(145deg, #eef5ff 0%, #d9e8ff 100%);
|
||||
color: #5b83d7;
|
||||
}
|
||||
|
||||
.feature-card--inclusive {
|
||||
grid-column: span 2;
|
||||
background: linear-gradient(145deg, #f2eeff 0%, #ded4ff 100%);
|
||||
color: #8063dc;
|
||||
}
|
||||
|
||||
.classroom-panel {
|
||||
height: 66px;
|
||||
margin: 10px;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-radius: 10px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 8px 22px rgba(42, 65, 92, .07);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.classroom-panel:active {
|
||||
transform: scale(.99);
|
||||
}
|
||||
|
||||
.classroom-panel__title {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
color: #202124;
|
||||
font-size: 15px;
|
||||
line-height: 21px;
|
||||
}
|
||||
|
||||
.classroom-panel__title::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -5px;
|
||||
width: 22px;
|
||||
height: 2px;
|
||||
border-radius: 999px;
|
||||
background-color: #e13b4c;
|
||||
}
|
||||
|
||||
.classroom-panel__course {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
margin: 0 12px;
|
||||
color: #1989fa;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.classroom-panel__action {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
color: #969799;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.classroom-panel__action .van-icon {
|
||||
flex-shrink: 0;
|
||||
margin-left: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.policy-panel {
|
||||
height: 66px;
|
||||
margin: 10px;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-radius: 10px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 8px 22px rgba(42, 65, 92, .07);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.policy-panel__title {
|
||||
position: relative;
|
||||
color: #202124;
|
||||
font-size: 15px;
|
||||
line-height: 21px;
|
||||
}
|
||||
|
||||
.policy-panel__title::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -5px;
|
||||
width: 22px;
|
||||
height: 2px;
|
||||
border-radius: 999px;
|
||||
background-color: #e13b4c;
|
||||
}
|
||||
|
||||
.policy-panel__action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #969799;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.policy-panel__action .van-icon {
|
||||
margin-left: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.act-list {
|
||||
@@ -190,10 +953,15 @@ const home = {
|
||||
padding: 0 0 5px 5px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.section-title--hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/deep/ .home__swipe img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
/deep/ .act-item-cover img {
|
||||
|
||||
@@ -22,10 +22,10 @@ layout("/layouts/platform_h5.html"){
|
||||
|
||||
<div id="page-tarbar" class="page-tarbar">
|
||||
<van-tabbar v-model="homeTabbarActive" @change="homeTabbarChange">
|
||||
<van-tabbar-item name="home" icon="wap-home-o">首页</van-tabbar-item>
|
||||
<van-tabbar-item name="apps" icon="apps-o">应用</van-tabbar-item>
|
||||
<!-- <van-tabbar-item name="work" icon="gem">工作台</van-tabbar-item>-->
|
||||
<van-tabbar-item name="todo" icon="records-o">待办</van-tabbar-item>
|
||||
<van-tabbar-item name="home" icon="wap-home-o">首页</van-tabbar-item>
|
||||
<van-tabbar-item name="msg" icon="envelop-o">消息</van-tabbar-item>
|
||||
<van-tabbar-item name="mine" icon="user-o">我的</van-tabbar-item>
|
||||
</van-tabbar>
|
||||
|
||||
@@ -165,7 +165,8 @@ const msg= {
|
||||
// 加载统计数据
|
||||
async loadStats() {
|
||||
try {
|
||||
const resp = await this.$axios.get('/platform/v4/msg/stats')
|
||||
const statsRequest = this.$axios.get('/platform/v4/msg/stats')
|
||||
const resp = await statsRequest
|
||||
if (resp.code === 0) {
|
||||
this.stats = resp.data
|
||||
}
|
||||
@@ -184,7 +185,8 @@ const msg= {
|
||||
|
||||
this.loading = true
|
||||
try {
|
||||
const resp = await this.$axios.post('/platform/v4/msg/pageData', this.pageForm)
|
||||
const pageRequest = this.$axios.post('/platform/v4/msg/pageData', this.pageForm)
|
||||
const resp = await pageRequest
|
||||
if (resp.code === 0) {
|
||||
const newMessages = resp.data.list || []
|
||||
if (isRefresh) {
|
||||
@@ -213,7 +215,8 @@ const msg= {
|
||||
// 查看消息详情
|
||||
async viewMessage(message) {
|
||||
try {
|
||||
const resp = await this.$axios.post(`/platform/v4/msg/detail/` + message.id)
|
||||
const detailRequest = this.$axios.post('/platform/v4/msg/detail/' + message.id)
|
||||
const resp = await detailRequest
|
||||
if (resp.code === 0) {
|
||||
this.currentMessage = resp.data
|
||||
this.detailVisible = true
|
||||
@@ -233,7 +236,8 @@ const msg= {
|
||||
// 标记单条消息为已读
|
||||
async markAsRead(messageId) {
|
||||
try {
|
||||
await this.$axios.post(`/platform/v4/msg/read/` + messageId)
|
||||
const readRequest = this.$axios.post('/platform/v4/msg/read/' + messageId)
|
||||
await readRequest
|
||||
} catch (error) {
|
||||
console.error('标记已读失败:', error)
|
||||
}
|
||||
@@ -247,7 +251,8 @@ const msg= {
|
||||
}).then(async () => {
|
||||
this.loading = true
|
||||
try {
|
||||
const resp = await this.$axios.post('/platform/v4/msg/read/all')
|
||||
const readAllRequest = this.$axios.post('/platform/v4/msg/read/all')
|
||||
const resp = await readAllRequest
|
||||
if (resp.code === 0) {
|
||||
this.$toast.success('操作成功')
|
||||
this.loadMessages(true)
|
||||
|
||||
@@ -115,7 +115,9 @@ const todo = {
|
||||
// 获取统计数据
|
||||
async getStatistics() {
|
||||
try {
|
||||
const {code, data, msg} = await this.$axios.post("/flow/todoCenter/statistics");
|
||||
// 统计请求先创建 Promise 再等待,遵守移动端页面 axios 调用规范。
|
||||
const statisticsRequest = this.$axios.post("/flow/todoCenter/statistics");
|
||||
const {code, data, msg} = await statisticsRequest;
|
||||
if (code === 0) {
|
||||
const {todoCount, doneCount, startedCount} = data
|
||||
this.todoCount = todoCount;
|
||||
|
||||
Reference in New Issue
Block a user