feat:系统UI优化初版

This commit is contained in:
2026-08-07 17:08:19 +08:00
parent 956a76ac63
commit 4cf4e45ea2
16 changed files with 2500 additions and 555 deletions
@@ -84,7 +84,7 @@ public class LearningCourseDisplayController {
/** /**
* 查询课程展示列表。 * 查询课程展示列表。
* pageForm 传分页页码和每页条数;keyword、courseTypeId、recommendFlag 分别用于关键词、分类和推荐标识筛选; * pageForm 传分页页码和每页条数;keyword、courseTypeId、recommendFlag 分别用于关键词、分类和推荐标识筛选;
* sortType 支持 default(综合排序)和 view_count(观看次数倒序)。返回值为 Pagination,list 中包含课程基础字段、课节数、总时长观看次数。 * sortType 支持 default(综合排序)和 view_count(观看次数倒序)。返回值为 Pagination,list 中包含课程基础字段、课节数、总时长观看次数和当前用户学习进度
*/ */
public Result pageData(PageForm pageForm, String keyword, String courseTypeId, String recommendFlag, String sortType) { public Result pageData(PageForm pageForm, String keyword, String courseTypeId, String recommendFlag, String sortType) {
Cnd cnd = Cnd.where("c.status", "=", "published"); Cnd cnd = Cnd.where("c.status", "=", "published");
@@ -112,6 +112,9 @@ public class LearningCourseDisplayController {
SELECT c.id, c.courseName, c.courseIntro, c.startTime, c.endTime, c.openType, c.cover, SELECT c.id, c.courseName, c.courseIntro, c.startTime, c.endTime, c.openType, c.cover,
c.recommendFlags, c.courseTypeId, c.lecturerName, c.sortNum, c.recommendFlags, c.courseTypeId, c.lecturerName, c.sortNum,
COALESCE(c.viewCount, 0) AS viewCount, t.typeName AS courseTypeName, COALESCE(c.viewCount, 0) AS viewCount, t.typeName AS courseTypeName,
COALESCE((SELECT ROUND(AVG(IFNULL(sr.progressPercent, 0)))
FROM learning_study_record sr
WHERE sr.courseId = c.id AND sr.userId = @userId), 0) AS progressPercent,
(SELECT COUNT(1) FROM learning_outline_resource r (SELECT COUNT(1) FROM learning_outline_resource r
WHERE r.courseId = c.id AND r.status = 'enabled') AS resourceCount, WHERE r.courseId = c.id AND r.status = 'enabled') AS resourceCount,
COALESCE((SELECT SUM(r.durationSeconds) FROM learning_outline_resource r COALESCE((SELECT SUM(r.durationSeconds) FROM learning_outline_resource r
@@ -122,6 +125,7 @@ public class LearningCourseDisplayController {
ORDER BY $orderBy ORDER BY $orderBy
"""); """);
listSql.setCondition(cnd); listSql.setCondition(cnd);
listSql.params().set("userId", SecurityUtil.getUserId());
// 排序字段由固定选项映射,禁止将前端参数直接拼接进SQL。 // 排序字段由固定选项映射,禁止将前端参数直接拼接进SQL。
listSql.setVar("orderBy", "view_count".equals(sortType) listSql.setVar("orderBy", "view_count".equals(sortType)
? "COALESCE(c.viewCount, 0) DESC, c.sortNum ASC, c.createdAt DESC" ? "COALESCE(c.viewCount, 0) DESC, c.sortNum ASC, c.createdAt DESC"
@@ -0,0 +1,98 @@
package com.budwk.app.zhgh.spread.h5controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.spread.service.SpreadCategoryService;
import com.budwk.app.zhgh.spread.service.SpreadContentService;
import io.swagger.annotations.ApiOperation;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
/**
* 风采墙 H5 展示控制器,提供栏目、列表和内容详情预览。
*/
@IocBean
@Ok("json:full")
@At("/platform/h5/spread")
public class H5SpreadDisplayController {
@Inject
private SpreadCategoryService categoryService;
@Inject
private SpreadContentService contentService;
/**
* 打开风采墙 H5 预览页面。
*/
@At("")
@Ok("beetl:/platform/zhghh5/spread/display/index.html")
@SaCheckLogin
public void index() {
}
/**
* 查询 H5 端可见栏目树。
*
* @return 按层级组织的可见栏目
*/
@At
@SaCheckLogin
@ApiOperation("H5可见栏目树")
public Result categoryTree() {
return Result.success(categoryService.categoryTree(true));
}
/**
* 查询指定栏目及其子栏目的已发布内容。
*
* @param pageForm 分页参数
* @param categoryId 栏目 ID
* @param title 标题模糊查询条件
* @return 已发布内容分页数据
*/
@At
@SaCheckLogin
@ApiOperation("H5已发布内容分页")
public Result pageData(PageForm pageForm, String categoryId, String title) {
return Result.success(contentService.pagePublished(pageForm, categoryId, title));
}
/**
* 查询 H5 内容详情。
*
* @param id 内容 ID
* @return 内容详情
*/
@At
@SaCheckLogin
@ApiOperation("H5内容详情")
public Result detailData(String id) {
try {
return Result.success(contentService.getContent(id));
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
}
/**
* 登记 H5 内容浏览量。
*
* @param id 内容 ID
* @return 操作结果
*/
@At
@SaCheckLogin
@ApiOperation("H5增加浏览量")
public Result addView(String id) {
try {
contentService.addViewCount(id);
return Result.success();
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
}
}
@@ -16,6 +16,16 @@ public interface SpreadContentService extends BaseService<SpreadContent> {
Pagination<NutMap> pagePublished(PageForm pageForm, String categoryId); Pagination<NutMap> pagePublished(PageForm pageForm, String categoryId);
/**
* 分页查询指定栏目中标题匹配的已发布内容。
*
* @param pageForm 分页参数
* @param categoryId 栏目 ID,查询时包含该栏目的直接子栏目
* @param title 标题模糊查询条件
* @return 已发布内容分页数据
*/
Pagination<NutMap> pagePublished(PageForm pageForm, String categoryId, String title);
SpreadContent getContent(String id); SpreadContent getContent(String id);
void saveContent(SpreadContent content); void saveContent(SpreadContent content);
@@ -62,8 +62,22 @@ public class SpreadContentServiceImpl extends BaseServiceImpl<SpreadContent> imp
@Override @Override
public Pagination<NutMap> pagePublished(PageForm pageForm, String categoryId) { public Pagination<NutMap> pagePublished(PageForm pageForm, String categoryId) {
return pagePublished(pageForm, categoryId, null);
}
/**
* 分页查询指定栏目中标题匹配的已发布内容。
*
* @param pageForm 分页参数
* @param categoryId 栏目 ID,查询时包含该栏目的直接子栏目
* @param title 标题模糊查询条件
* @return 已发布内容分页数据
*/
@Override
public Pagination<NutMap> pagePublished(PageForm pageForm, String categoryId, String title) {
SpreadContentQuery query = new SpreadContentQuery(); SpreadContentQuery query = new SpreadContentQuery();
query.setCategoryId(categoryId); query.setCategoryId(categoryId);
query.setTitle(title);
query.setIsPublished(1); query.setIsPublished(1);
Cnd cnd = buildCondition(query); Cnd cnd = buildCondition(query);
Sql countSql = Sqls.create("SELECT COUNT(1) FROM spread_content c $condition"); Sql countSql = Sqls.create("SELECT COUNT(1) FROM spread_content c $condition");
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 2.0 MiB

@@ -124,11 +124,44 @@ body {
} }
.custom-list-loading { .custom-list-loading {
background-color: unset; min-width: 72px;
padding: 10px 12px 9px;
border-radius: 12px;
color: #7890a8;
background-color: rgba(255, 255, 255, 0.94);
box-shadow: 0 5px 18px rgba(52, 91, 128, 0.12);
} }
.custom-list-loading .van-icon { .custom-list-loading .van-icon {
font-size: 108px; width: 48px;
height: 48px;
font-size: 48px;
transform-origin: center;
animation: smart-union-loading-breathe 1.15s ease-in-out infinite;
}
.custom-list-loading .van-icon__image {
width: 100%;
height: 100%;
object-fit: contain;
}
@keyframes smart-union-loading-breathe {
0%, 100% {
opacity: 0.58;
transform: scale(0.92);
}
50% {
opacity: 1;
transform: scale(1.03);
}
}
.custom-list-loading .van-toast__text {
margin-top: 5px;
color: #7890a8;
font-size: 12px;
line-height: 16px;
} }
.list-card { .list-card {
@@ -149,9 +149,10 @@
}) })
} }
function createListLoading(icon = "https://preview.qiantucdn.com/58pic/35/39/61/62K58PICb88i68HEwVnm5_PIC2018.gif!qt480") { function createListLoading(icon = "/assets/mobile/img/common/smart-union-loading.png") {
return vant.Toast.loading({ return vant.Toast.loading({
icon: icon, icon: icon,
message: "加载中...",
forbidClick: true, forbidClick: true,
loadingType: "spinner", loadingType: "spinner",
duration: 0, duration: 0,
@@ -0,0 +1,297 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style>
body { background: #f5f7fa; }
.spread-h5 { min-height: 100vh; color: #202124; background: #f5f7fa; }
.spread-h5__toolbar { position: sticky; top: 46px; z-index: 15; background: #fff; box-shadow: 0 3px 10px rgba(31,72,122,.06); }
.spread-h5__search { padding: 8px 12px 2px; background: #fff; }
.spread-h5__search .van-search { padding: 0; }
.spread-h5__categories { padding: 2px 12px 10px; background: #fff; }
.spread-h5__tabs { height: 36px; padding: 0; display: flex; gap: 0; overflow-x: auto; overflow-y: hidden; border-bottom: 1px solid #edf1f6; border-radius: 0; background: transparent; box-sizing: border-box; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.spread-h5__tabs::-webkit-scrollbar { display: none; }
.spread-h5__tab { position: relative; min-width: 92px; height: 30px; padding: 0 8px; flex: 1 0 calc((100% - 8px) / 3); display: flex; align-items: center; justify-content: center; color: #7d8797; font-size: 13px; white-space: nowrap; border-radius: 6px; box-sizing: border-box; }
.spread-h5__tab--active { color: #087af4; font-weight: 600; background: transparent; box-shadow: none; }
.spread-h5__tabs:not(.spread-h5__tabs--secondary) .spread-h5__tab--active::after { content: ""; position: absolute; left: 50%; bottom: -1px; width: 26px; height: 2px; border-radius: 2px; background: #087af4; transform: translateX(-50%); }
.spread-h5__tabs--secondary { height: 30px; margin-top: 7px; padding: 1px 0; gap: 6px; border-bottom: 0; border-radius: 0; background: transparent; }
.spread-h5__tabs--secondary .spread-h5__tab { min-width: 84px; height: 26px; padding: 0 10px; flex: 1 0 calc((100% - 12px) / 3); color: #969daa; font-size: 11px; border: 1px solid #e7ecf3; border-radius: 999px; background: #fafbfd; box-shadow: none; }
.spread-h5__tabs--secondary .spread-h5__tab--active { color: #087af4; border-color: #b9d8ff; background: #edf6ff; }
.spread-h5__list { margin: 10px 12px 20px; padding: 0 12px; border-radius: 10px; background: #fff; }
.spread-h5__item { min-height: 82px; padding: 11px 0; display: flex; align-items: center; box-sizing: border-box; }
.spread-h5__item + .spread-h5__item { border-top: 1px solid #f0f2f5; }
.spread-h5__cover { width: 96px; height: 60px; margin-right: 11px; flex-shrink: 0; border-radius: 5px; object-fit: cover; }
.spread-h5__item-content { min-width: 0; flex: 1; }
.spread-h5__item-title { overflow: hidden; color: #202124; font-size: 14px; line-height: 20px; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }
.spread-h5__item-summary { margin-top: 4px; overflow: hidden; color: #7d8797; font-size: 11px; line-height: 16px; text-overflow: ellipsis; white-space: nowrap; }
.spread-h5__item-meta { margin-top: 6px; display: flex; align-items: center; justify-content: space-between; color: #9aa2af; font-size: 10px; line-height: 14px; }
.spread-h5__views { display: inline-flex; align-items: center; }
.spread-h5__views .van-icon { margin-right: 3px; }
.spread-h5__loading { padding: 35px 0; text-align: center; }
.spread-h5__finished { padding: 10px 0 18px; color: #b0b6c0; font-size: 11px; line-height: 16px; text-align: center; }
.spread-h5__empty { padding: 36px 0; color: #9aa2af; font-size: 13px; line-height: 20px; text-align: center; }
.spread-h5__detail { margin: 10px 12px 24px; padding: 18px 15px 24px; border-radius: 10px; background: #fff; }
.spread-h5__detail-title { margin: 0; color: #202124; font-size: 20px; line-height: 29px; text-align: center; word-break: break-word; }
.spread-h5__detail-meta { margin-top: 10px; padding-bottom: 14px; color: #9aa2af; font-size: 11px; line-height: 17px; text-align: center; border-bottom: 1px solid #edf1f6; }
.spread-h5__detail-summary { margin: 15px 0 0; padding: 10px 12px; color: #697386; font-size: 14px; line-height: 22px; border-radius: 6px; background: #f7f9fc; }
.spread-h5__detail-cover { width: 100%; max-height: 240px; margin: 16px 0 0; display: block; border-radius: 6px; object-fit: contain; }
.spread-h5__slider { height: 220px; margin-top: 16px; overflow: hidden; border-radius: 6px; background: #eef2f7; }
.spread-h5__slide { position: relative; height: 220px; }
.spread-h5__slide img { width: 100%; height: 100%; display: block; object-fit: contain; }
.spread-h5__slide-caption { position: absolute; left: 0; right: 0; bottom: 0; padding: 24px 12px 10px; overflow: hidden; color: #fff; font-size: 12px; line-height: 18px; text-overflow: ellipsis; white-space: nowrap; background: linear-gradient(to top, rgba(15,23,42,.72), transparent); box-sizing: border-box; }
.spread-h5__detail-body { margin-top: 16px; overflow: hidden; color: #323233; font-size: 15px; line-height: 1.8; word-break: break-word; }
.spread-h5__detail-body img, .spread-h5__detail-body video { max-width: 100% !important; height: auto !important; }
.spread-h5__detail-body table { max-width: 100% !important; }
</style>
<div id="app" class="spread-h5" v-cloak>
<van-nav-bar :title="detail ? '动态详情' : '工会动态'" left-arrow left-text="返回" fixed placeholder :z-index="20" @click-left="handleBack"></van-nav-bar>
<template v-if="!detail">
<div class="spread-h5__toolbar">
<div class="spread-h5__search">
<van-search v-model.trim="keyword"
shape="round"
placeholder="搜索动态标题"
@search="doSearch"
@clear="doSearch"></van-search>
</div>
<div class="spread-h5__categories" v-if="categories.length">
<div class="spread-h5__tabs">
<div class="spread-h5__tab"
v-for="category in categories"
:key="category.id"
:class="{'spread-h5__tab--active': parentId === category.id}"
@click="selectParent(category)">{{category.name}}</div>
</div>
<div class="spread-h5__tabs spread-h5__tabs--secondary" v-if="currentChildren.length">
<div class="spread-h5__tab"
v-for="category in currentChildren"
:key="category.id"
:class="{'spread-h5__tab--active': categoryId === category.id}"
@click="selectCategory(category)">{{category.name}}</div>
</div>
</div>
</div>
<div v-if="pageData.list.length" class="spread-h5__list">
<div class="spread-h5__item" v-for="item in pageData.list" :key="item.id" @click="openDetail(item)">
<img v-if="getCoverUrl(item.thumbUrl)" class="spread-h5__cover" :src="getCoverUrl(item.thumbUrl)" :alt="item.title">
<div class="spread-h5__item-content">
<div class="spread-h5__item-title">{{item.title}}</div>
<div v-if="item.summary || item.subTitle" class="spread-h5__item-summary">{{item.summary || item.subTitle}}</div>
<div class="spread-h5__item-meta">
<span>{{formatDate(item.publishedAt || item.createdAt)}}</span>
<span class="spread-h5__views"><van-icon name="eye-o"></van-icon>{{item.viewCount || 0}}</span>
</div>
</div>
</div>
</div>
<div v-if="loading" class="spread-h5__loading"><van-loading size="22px">加载中...</van-loading></div>
<div v-if="!loading && finished && pageData.list.length" class="spread-h5__finished">没有更多了</div>
<div v-if="!loading && finished && !pageData.list.length" class="spread-h5__empty">暂无动态</div>
</template>
<article v-else class="spread-h5__detail">
<h1 class="spread-h5__detail-title">{{detail.title}}</h1>
<div class="spread-h5__detail-meta">
{{formatDate(detail.publishedAt || detail.createdAt)}} {{detail.categoryName}} 
<van-icon name="eye-o"></van-icon> {{detail.viewCount || 0}}
</div>
<img v-if="getCoverUrl(detail.thumbUrl)" class="spread-h5__detail-cover" :src="getCoverUrl(detail.thumbUrl)" :alt="detail.title">
<div v-if="detail.summary" class="spread-h5__detail-summary">{{detail.summary}}</div>
<van-swipe v-if="detailSlides.length"
class="spread-h5__slider"
:autoplay="3000"
:show-indicators="detailSlides.length > 1"
indicator-color="#1989fa">
<van-swipe-item v-for="(slide, index) in detailSlides" :key="slide.src + '-' + index">
<div class="spread-h5__slide">
<img :src="slide.src" :alt="slide.title || detail.title">
<div v-if="slide.title" class="spread-h5__slide-caption">{{slide.title}}</div>
</div>
</van-swipe-item>
</van-swipe>
<div class="spread-h5__detail-body" v-html="detail.contentBody"></div>
</article>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
data() {
return {
apiBase: "/platform/h5/spread",
categories: [],
parentId: "",
categoryId: "",
keyword: "",
loading: false,
finished: false,
scrollContainer: null,
requestSeq: 0,
detail: null,
pageData: {pageNumber: 1, pageSize: 10, totalCount: 0, list: []}
}
},
computed: {
currentChildren() {
const parent = this.categories.find(item => item.id === this.parentId)
return parent && parent.children ? parent.children : []
},
detailSlides() {
return this.getSliderEntries(this.detail && this.detail.sliderImages)
}
},
methods: {
loadCategories() {
return this.$axios.post(this.apiBase + "/categoryTree").then(resp => {
if (resp.code !== 0) return
this.categories = resp.data || []
const firstParent = this.categories[0]
if (!firstParent) return
this.parentId = firstParent.id
const children = firstParent.children || []
this.categoryId = children.length ? children[0].id : firstParent.id
})
},
loadPage() {
if (!this.categoryId || this.loading || this.finished) {
return
}
this.loading = true
const requestSeq = ++this.requestSeq
this.$axios.post(this.apiBase + "/pageData", {
categoryId: this.categoryId,
title: this.keyword,
pageNumber: this.pageData.pageNumber,
pageSize: this.pageData.pageSize
}).then(resp => {
if (requestSeq !== this.requestSeq) return
if (resp.code === 0) {
const data = resp.data || {}
const rows = data.list || []
this.pageData.list = this.pageData.list.concat(rows)
this.pageData.totalCount = data.totalCount || 0
this.pageData.pageNumber += 1
this.finished = this.pageData.list.length >= this.pageData.totalCount || rows.length < this.pageData.pageSize
} else {
this.finished = true
}
}).finally(() => {
if (requestSeq === this.requestSeq) this.loading = false
})
},
resetPage() {
this.requestSeq += 1
this.loading = false
this.finished = false
this.pageData = {pageNumber: 1, pageSize: 10, totalCount: 0, list: []}
this.$nextTick(() => this.loadPage())
},
handlePageScroll() {
if (!this.scrollContainer || this.loading || this.finished || this.detail) return
const remaining = this.scrollContainer.scrollHeight
- this.scrollContainer.scrollTop
- this.scrollContainer.clientHeight
if (remaining <= 80) this.loadPage()
},
doSearch() {
this.resetPage()
},
selectParent(category) {
if (this.parentId === category.id) return
this.parentId = category.id
const children = category.children || []
this.categoryId = children.length ? children[0].id : category.id
this.resetPage()
},
selectCategory(category) {
if (this.categoryId === category.id) return
this.categoryId = category.id
this.resetPage()
},
openDetail(item) {
this.loading = true
Promise.all([
this.$axios.post(this.apiBase + "/detailData", {id: item.id}),
this.$axios.post(this.apiBase + "/addView", {id: item.id})
]).then(values => {
if (values[0].code === 0) {
this.detail = values[0].data
this.detail.viewCount = Number(this.detail.viewCount || 0) + 1
}
}).finally(() => this.loading = false)
},
loadRequestedDetail() {
const id = new URLSearchParams(window.location.search).get("id")
if (!id) return false
this.openDetail({id: id})
return true
},
handleBack() {
if (this.detail) {
this.detail = null
if (!this.pageData.list.length) this.resetPage()
return
}
this.$pjaxReplace("/platform/h5/home")
},
getCoverUrl(value) {
if (!value) return ""
let files = value
if (typeof value === "string" && value.trim().startsWith("[")) {
try {
files = JSON.parse(value)
} catch (e) {
files = []
}
}
if (!Array.isArray(files)) files = String(files).split(",")
const first = files[0]
if (typeof first === "string") return first.trim()
return first && (first.src || first.url || first.data || (first.response && first.response.data)) || ""
},
getSliderEntries(value) {
if (!value) return []
let items = value
if (!Array.isArray(items)) {
try {
items = JSON.parse(value)
} catch (e) {
items = String(value).split(",").filter(Boolean)
}
}
if (!Array.isArray(items)) return []
return items.map(item => {
if (typeof item === "string") return {src: item.trim(), title: ""}
return {
src: item.src || item.url || item.data || (item.response && item.response.data) || "",
title: item.title || item.description || ""
}
}).filter(item => item.src)
},
formatDate(value) {
return value ? this.$moment(Number(value)).format("YYYY-MM-DD") : ""
}
},
created() {
this.loadCategories().then(() => {
if (!this.loadRequestedDetail()) this.resetPage()
})
},
mounted() {
this.scrollContainer = document.querySelector("body > .main-page .page-wrapper")
if (this.scrollContainer) {
this.scrollContainer.addEventListener("scroll", this.handlePageScroll, {passive: true})
}
},
beforeDestroy() {
if (this.scrollContainer) {
this.scrollContainer.removeEventListener("scroll", this.handlePageScroll)
}
}
})
</script>
<!--#
}
#-->
@@ -1,6 +1,29 @@
const MOBILE_MEMBER_INFO = { const MOBILE_MEMBER_INFO = {
template: /*language=HTML*/ ` template: /*language=HTML*/ `
<div class="detail-container"> <div v-if="profileMode" class="mine-profile-details">
<section class="mine-detail-card">
<h2>基本信息</h2>
<div class="mine-detail-row"><span>姓名</span><strong>{{ viewData.username || '-' }}</strong></div>
<div class="mine-detail-row"><span>性别</span><strong>{{ viewData.sex || '-' }}</strong></div>
<div class="mine-detail-row"><span>民族</span><strong>{{ viewData.nation || '-' }}</strong></div>
<div class="mine-detail-row"><span>出生日期</span><strong>{{ formatBirthday(viewData.birthday) }}</strong></div>
<div class="mine-detail-row"><span>政治面貌</span><strong>{{ viewData.political || '-' }}</strong></div>
</section>
<section class="mine-detail-card">
<h2>组织信息</h2>
<div class="mine-detail-row"><span>工作单位</span><strong>{{ viewData.unitName || '-' }}</strong></div>
<div class="mine-detail-row"><span>所属工会</span><strong>{{ viewData.unionName || '-' }}</strong></div>
<div class="mine-detail-row"><span>人员类型</span><strong>{{ viewData.personType || '-' }}</strong></div>
</section>
<section class="mine-detail-card">
<h2>联系方式</h2>
<div class="mine-detail-row"><span>联系电话</span><strong>{{ maskMobile(viewData.mobile) }}</strong></div>
<div class="mine-detail-row"><span>电子邮箱</span><strong>{{ viewData.email || '-' }}</strong></div>
</section>
</div>
<div v-else class="detail-container">
<van-cell-group title="基本信息"> <van-cell-group title="基本信息">
<van-cell title="工号">{{ viewData.loginname }}</van-cell> <van-cell title="工号">{{ viewData.loginname }}</van-cell>
<van-cell title="姓名">{{ viewData.username }}</van-cell> <van-cell title="姓名">{{ viewData.username }}</van-cell>
@@ -61,7 +84,8 @@
</div> </div>
`, `,
props: { props: {
userId: { type: String, required: true } userId: { type: String, required: true },
profileMode: { type: Boolean, default: false }
}, },
data() { data() {
return { return {
@@ -77,6 +101,18 @@
} }
}, },
methods: { methods: {
formatBirthday(birthday) {
if (!birthday) {
return "-"
}
return this.$moment(birthday).format("YYYY-MM-DD")
},
maskMobile(mobile) {
if (!mobile) {
return "-"
}
return mobile.replace(/^(\d{3})\d+(\d{4})$/, "$1****$2")
},
findUserInfoByUserId() { findUserInfoByUserId() {
if (!this.userId) { if (!this.userId) {
return return
@@ -6,13 +6,19 @@ const apps = {
<van-sticky offset-top="46px"> <van-sticky offset-top="46px">
<van-search <van-search
v-model="searchKeyword" v-model="searchKeyword"
placeholder="搜索应用" placeholder="搜索应用或服务名称"
show-action
@search="onSearch" @search="onSearch"
@cancel="onCancel" @cancel="onCancel"
></van-search> ></van-search>
</van-sticky> </van-sticky>
<div class="apps-guide-banner">
<div class="apps-guide-copy">
<div class="apps-guide-title">工会服务,一站直达</div>
<div class="apps-guide-desc">常用应用与业务办理便捷入口</div>
</div>
</div>
<div class="custom-tree-select"> <div class="custom-tree-select">
<!-- 左侧导航 --> <!-- 左侧导航 -->
<div class="tree-nav"> <div class="tree-nav">
@@ -22,24 +28,20 @@ const apps = {
:class="['nav-item', { active: activeTab === index }]" :class="['nav-item', { active: activeTab === index }]"
@click="onClickNav(index)" @click="onClickNav(index)"
> >
<div class="nav-icon">
<!--<i v-if="category.icon" :class="category.icon"></i>
<i v-else class="fa fa-book"></i>-->
<img v-if="category.picIcon" :src="category.picIcon" :alt="category.name || ''" style="width: 18px"/>
<i v-else :class="category.icon || 'fa fa-th-large'"></i>
</div>
<div class="nav-text">{{ category.name }}</div> <div class="nav-text">{{ category.name }}</div>
<div class="nav-indicator" v-if="activeTab === index"></div>
</div> </div>
</div> </div>
<!-- 右侧内容 --> <!-- 右侧内容 -->
<div class="tree-content"> <div class="tree-content">
<div class="tree-content-title">
{{ allCategories[activeTab] ? allCategories[activeTab].name : '全部应用' }}
</div>
<div v-if="loading" class="module-loading"> <div v-if="loading" class="module-loading">
<van-loading size="24px">加载中...</van-loading> <van-loading size="24px">加载中...</van-loading>
</div> </div>
<div :class="['app-grid', { 'app-grid-single': applications.length === 1 }]" <div class="app-grid"
v-else-if="isFixedCategory && applications.length > 0"> v-else-if="isFixedCategory && applications.length > 0">
<div v-for="app in applications" <div v-for="app in applications"
:key="app.id" :key="app.id"
@@ -47,21 +49,26 @@ const apps = {
:class="{ 'is-favorite': app.isFavorite }" :class="{ 'is-favorite': app.isFavorite }"
@click="openApp(app)" @click="openApp(app)"
@touchstart="onTouchStart(app)" @touchstart="onTouchStart(app)"
@touchend="onTouchEnd" @touchend="onTouchEnd"
@touchmove="onTouchMove"> @touchmove="onTouchMove">
<div class="app-grid-icon"> <div class="app-grid-icon">
<van-icon v-if="app.picIcon" :name="app.picIcon" size="45"></van-icon> <img v-if="app.picIcon" :src="app.picIcon" :alt="app.name || ''"/>
<van-icon v-else-if="app.icon" :name="app.icon" size="28"></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> <van-icon v-else name="apps-o" size="28"></van-icon>
</div> </div>
<div class="app-grid-name">{{ app.name }}</div> <div class="app-grid-info">
<div class="app-grid-name">{{ app.name }}</div>
<div v-if="app.aliasName || app.note" class="app-grid-desc">{{ app.aliasName || app.note }}</div>
</div>
<van-icon v-if="app.isFavorite" name="star" class="app-grid-favorite"></van-icon>
<van-icon name="arrow" class="app-grid-arrow"></van-icon>
</div> </div>
</div> </div>
<div v-else-if="!isFixedCategory && moduleSections.length > 0" class="module-sections"> <div v-else-if="!isFixedCategory && moduleSections.length > 0" class="module-sections">
<div v-for="section in moduleSections" :key="section.id" class="module-section"> <div v-for="section in moduleSections" :key="section.id" class="module-section">
<div class="module-title">{{ section.name }}</div> <div class="module-title">{{ section.name }}</div>
<div :class="['module-grid', { 'module-grid-single': section.menus.length === 1 }]"> <div class="module-grid">
<div <div
v-for="menu in section.menus" v-for="menu in section.menus"
:key="menu.id" :key="menu.id"
@@ -72,7 +79,11 @@ const apps = {
<div v-else class="module-feature-icon"> <div v-else class="module-feature-icon">
<van-icon :name="menu.icon || 'apps-o'" size="24"></van-icon> <van-icon :name="menu.icon || 'apps-o'" size="24"></van-icon>
</div> </div>
<div class="module-feature-name">{{ menu.name }}</div> <div class="module-feature-info">
<div class="module-feature-name">{{ menu.name }}</div>
<div v-if="menu.aliasName || menu.note" class="module-feature-desc">{{ menu.aliasName || menu.note }}</div>
</div>
<van-icon name="arrow" class="module-feature-arrow"></van-icon>
</div> </div>
</div> </div>
</div> </div>
@@ -85,7 +96,7 @@ const apps = {
</div> </div>
<!-- 应用菜单弹框 --> <!-- 应用菜单弹框 -->
<van-popup v-model="showMenuPopup" position="bottom" :style="{ width: '100%', maxHeight: '60%' }" round> <van-popup v-model="showMenuPopup" position="bottom" :style="{ width: '100%', height: '62%' }" round>
<div class="menu-popup"> <div class="menu-popup">
<div class="popup-header"> <div class="popup-header">
<div class="popup-title">{{ currentApp.name }}</div> <div class="popup-title">{{ currentApp.name }}</div>
@@ -98,12 +109,17 @@ const apps = {
class="grid-item" class="grid-item"
@click="navigateToMenu(menu)" @click="navigateToMenu(menu)"
> >
<img v-if="menu.picIcon" :src="menu.picIcon" :alt="menu.name || ''" style="width: 48px" class="grid-img"/> <div v-if="menu.picIcon" class="grid-img-wrap">
<img :src="menu.picIcon" :alt="menu.name || ''" class="grid-img"/>
</div>
<div v-else class="grid-icon"> <div v-else class="grid-icon">
<van-icon :name="menu.icon || 'apps-o'" size="24"></van-icon> <van-icon :name="menu.icon || 'apps-o'" size="24"></van-icon>
</div> </div>
<div class="grid-name">{{ menu.name }}</div> <div class="grid-info">
<div class="grid-desc" v-if="menu.aliasName">{{ menu.aliasName }}</div> <div class="grid-name">{{ menu.name }}</div>
<div class="grid-desc" v-if="menu.aliasName || menu.note">{{ menu.aliasName || menu.note }}</div>
</div>
<van-icon name="arrow" class="grid-arrow"></van-icon>
</div> </div>
</div> </div>
</div> </div>
@@ -432,6 +448,65 @@ const apps = {
flex-direction: column; flex-direction: column;
height: calc(100vh - 50px); height: calc(100vh - 50px);
overflow: hidden; overflow: hidden;
background: #f5f7fa;
}
.apps-container .van-search {
padding: 8px 12px;
background: #fff;
}
.apps-container .van-search__content {
border-radius: 18px;
background: #f3f5f8;
}
.apps-guide-banner {
position: relative;
flex: 0 0 132px;
margin: 0 12px 10px;
border-radius: 9px;
overflow: hidden;
background: #168cff url('/assets/mobile/img/home/v4/apps-guide-banner.png') right top / cover no-repeat;
}
.apps-guide-copy {
position: relative;
z-index: 1;
width: 58%;
height: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: center;
padding: 0 0 0 16px;
color: #fff;
}
.apps-guide-title {
font-size: 20px;
line-height: 1.35;
font-weight: 700;
}
.apps-guide-desc {
margin-top: 7px;
font-size: 12px;
line-height: 1.4;
opacity: 0.94;
}
.tree-content-title {
position: relative;
display: flex;
align-items: center;
min-height: 38px;
box-sizing: border-box;
padding-left: 16px;
border-bottom: 1px solid #eef1f5;
color: #1677ff;
font-size: 13px;
font-weight: 600;
} }
/* 自定义树形选择组件样式 */ /* 自定义树形选择组件样式 */
@@ -439,7 +514,6 @@ const apps = {
display: flex; display: flex;
min-height: 0; min-height: 0;
flex: 1; flex: 1;
height: calc(100vh - 150px);
background: #fff; background: #fff;
} }
@@ -458,12 +532,12 @@ const apps = {
.nav-item { .nav-item {
position: relative; position: relative;
padding: 12px 8px; min-height: 54px;
padding: 8px 10px;
box-sizing: border-box;
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
border-bottom: 1px solid #ebedf0;
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
@@ -487,30 +561,17 @@ const apps = {
background: #1989fa; background: #1989fa;
} }
.nav-icon {
font-size: 18px;
margin-bottom: 6px;
color: inherit;
}
.nav-text { .nav-text {
font-size: 12px; font-size: 13px;
font-weight: 500; font-weight: 400;
text-align: center; text-align: center;
line-height: 1.2; line-height: 1.35;
word-break: break-all; color: #64748b;
} }
.nav-indicator { .nav-item.active .nav-text {
position: absolute; color: #1677ff;
right: 0; font-weight: 600;
top: 50%;
transform: translateY(-50%);
width: 0;
height: 0;
border-left: 6px solid #1989fa;
border-top: 6px solid transparent;
border-bottom: 6px solid transparent;
} }
.tree-content { .tree-content {
@@ -530,57 +591,43 @@ const apps = {
} }
.module-sections { .module-sections {
padding: 12px 12px 18px; padding: 0 12px 18px;
background: #fff; background: #fff;
} }
.module-section { .module-section {
padding: 0 0 18px; padding: 0;
} }
.module-section + .module-section { .module-section + .module-section {
border-top: 1px solid #f1f3f6; padding-top: 8px;
padding-top: 14px;
} }
.module-title { .module-title {
position: relative; position: relative;
padding-left: 10px; padding: 12px 0 8px;
margin-bottom: 10px;
color: #202733; color: #202733;
font-size: 15px; font-size: 13px;
font-weight: 700; font-weight: 600;
line-height: 1.4; line-height: 1.4;
} }
.module-title::before { .module-title::before {
content: ''; display: none;
position: absolute;
left: 0;
top: 4px;
width: 3px;
height: 14px;
border-radius: 2px;
background: #1989fa;
} }
.module-grid { .module-grid {
display: grid; display: block;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px 8px;
}
.module-grid-single {
grid-template-columns: repeat(3, minmax(0, 1fr));
} }
.module-feature { .module-feature {
min-width: 0; min-width: 0;
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
padding: 8px 2px; min-height: 68px;
border-radius: 8px; padding: 9px 2px;
box-sizing: border-box;
border-bottom: 1px solid #edf1f5;
background: #fff; background: #fff;
} }
@@ -589,67 +636,74 @@ const apps = {
} }
.module-feature-img { .module-feature-img {
flex: 0 0 42px;
width: 42px; width: 42px;
height: 42px; height: 42px;
object-fit: contain; object-fit: contain;
margin-bottom: 6px; margin-right: 10px;
} }
.module-feature-icon { .module-feature-icon {
flex: 0 0 42px;
width: 42px; width: 42px;
height: 42px; height: 42px;
border-radius: 12px; border-radius: 12px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
margin-bottom: 6px; margin-right: 10px;
color: #ffffff; color: #168cff;
background: linear-gradient(135deg, #2f8af5 0%, #49c3dc 100%); background: #eef6ff;
}
.module-feature-info {
flex: 1;
min-width: 0;
} }
.module-feature-name { .module-feature-name {
width: 100%; width: 100%;
color: #323233; color: #172033;
font-size: 13px; font-size: 14px;
line-height: 1.25; line-height: 1.4;
text-align: center;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.module-feature-desc {
margin-top: 3px;
color: #8a94a6;
font-size: 11px;
line-height: 1.35;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.module-feature-arrow,
.app-grid-arrow {
flex: 0 0 auto;
margin-left: 8px;
color: #9eb0c8;
font-size: 14px;
}
.app-grid { .app-grid {
display: grid; display: block;
/* 改为自适应列数:每格最小 80px,自动换行 */ padding: 0 12px 18px;
grid-template-columns: repeat(auto-fit, minmax(60px, 1fr));
justify-items: center; /* 子项内容居中(图标和文字) */
justify-content: start; /* 整体网格靠左对齐,避免居中 */
gap: 12px;
padding: 12px;
background: #fff; background: #fff;
overflow-x: hidden; overflow-x: hidden;
} }
.app-grid-single {
display: flex;
justify-content: start;
padding: 16px 0;
}
.app-grid-single .app-grid-item {
max-width: 80px;
margin: 0 12px;
}
.app-grid-item { .app-grid-item {
position: relative; position: relative;
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
padding: 12px 4px; min-height: 68px;
border-radius: 8px; padding: 9px 2px;
box-sizing: border-box;
border-bottom: 1px solid #edf1f5;
background: #fff; background: #fff;
width: 100%; width: 100%;
max-width: 100%; max-width: 100%;
@@ -665,27 +719,49 @@ const apps = {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
margin-bottom: 6px; margin-right: 10px;
overflow: hidden; overflow: hidden;
/*border: 2px solid transparent;*/ /*border: 2px solid transparent;*/
transition: all 0.2s ease; transition: all 0.2s ease;
} }
.app-grid-icon img {
width: 45px;
height: 45px;
object-fit: contain;
}
.app-grid-info {
flex: 1;
min-width: 0;
}
.app-grid-name { .app-grid-name {
width: 100%; width: 100%;
font-size: 14px; font-size: 14px;
color: #323233; line-height: 1.4;
text-align: center; color: #172033;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.app-grid-item.is-favorite .app-grid-icon, .app-grid-desc {
.app-grid-icon.is-favorite { margin-top: 3px;
border-color: #ff9800; color: #8a94a6;
box-shadow: 0 0 0 2px rgba(255, 152, 0, 0.2); font-size: 11px;
line-height: 1.35;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.app-grid-favorite {
flex: 0 0 auto;
margin-left: 8px;
color: #ffb020;
font-size: 18px;
} }
@@ -722,15 +798,17 @@ const apps = {
/deep/ .menu-grid { /deep/ .menu-grid {
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
display: grid; display: block;
grid-template-columns: repeat(3, 1fr); padding: 0 16px 12px;
} }
/deep/ .grid-item { /deep/ .grid-item {
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
padding: 20px 12px; min-height: 70px;
padding: 10px 0;
box-sizing: border-box;
border-bottom: 1px solid #edf1f5;
background: #fff; background: #fff;
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
@@ -738,7 +816,6 @@ const apps = {
} }
/deep/ .grid-item:active { /deep/ .grid-item:active {
transform: scale(0.95);
background: #f2f3f5; background: #f2f3f5;
} }
@@ -750,28 +827,40 @@ const apps = {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
margin-bottom: 12px; margin-right: 12px;
color: #fff; color: #fff;
} }
/deep/ .grid-img-wrap {
flex: 0 0 56px;
width: 56px;
height: 56px;
padding: 4px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
margin-right: 8px;
overflow: visible;
}
/deep/ .grid-img { /deep/ .grid-img {
width: 48px; width: 48px;
height: 48px; height: 48px;
border-radius: 12px; display: block;
display: flex; object-fit: contain;
align-items: center; }
justify-content: center;
margin-bottom: 12px; /deep/ .grid-info {
color: #fff; flex: 1;
min-width: 0;
} }
/deep/ .grid-name { /deep/ .grid-name {
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 600;
color: #323233; color: #172033;
text-align: center; line-height: 1.4;
margin-bottom: 4px;
line-height: 1.2;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@@ -780,10 +869,20 @@ const apps = {
} }
/deep/ .grid-desc { /deep/ .grid-desc {
margin-top: 3px;
font-size: 12px; font-size: 12px;
color: #969799; color: #969799;
text-align: center; line-height: 1.35;
line-height: 1.2; white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/deep/ .grid-arrow {
flex: 0 0 auto;
margin-left: 8px;
color: #9eb0c8;
font-size: 14px;
} }
/deep/ .menu-name { /deep/ .menu-name {
@@ -1,8 +1,16 @@
const home = { const home = {
template: /*language=HTML*/ ` template: /*language=HTML*/ `
<div class="home-page"> <div class="home-page">
<div class="home-hero"> <div class="home-summary-bar" :class="{'home-summary-bar--visible': showHomeSummary}">
<img class="home-summary-bar__logo"
src="/assets/mobile/img/home/v4/中国地质大学-logo.png"
alt="中国地质大学">
<span class="home-summary-bar__divider"></span>
<span class="home-summary-bar__title">智慧工会</span>
</div>
<div ref="homeHero" class="home-hero">
<img class="home-hero__bg" src="/assets/mobile/img/home/v4/hero-campus.png" alt=""> <img class="home-hero__bg" src="/assets/mobile/img/home/v4/hero-campus.png" alt="">
<div class="home-hero__color-layer" aria-hidden="true"></div>
<div class="home-hero__content"> <div class="home-hero__content">
<div class="home-hero__topbar"> <div class="home-hero__topbar">
<img class="home-hero__brand-logo" <img class="home-hero__brand-logo"
@@ -59,16 +67,92 @@ const home = {
</div> </div>
</div> </div>
<div class="classroom-panel" @click="enterClassroom"> <div class="classroom-section">
<div class="entry-panel__heading"> <div class="classroom-section__head">
<span class="entry-panel__icon entry-panel__icon--classroom"><van-icon name="notes-o"></van-icon></span> <div class="classroom-section__title">我的课堂</div>
<span class="classroom-panel__title">我的课堂</span> <div class="classroom-section__more" @click="enterClassroom">
<span>全部课程</span>
<van-icon name="arrow"></van-icon>
</div>
</div> </div>
<div class="classroom-panel__course">{{ currentClassroomCourseName }}</div> <div v-if="classroomCourses.length" class="classroom-course-scroll">
<div class="classroom-panel__action"> <div class="classroom-course-card"
<span>个人学习</span> v-for="course in classroomCourses"
<van-icon name="arrow"></van-icon> :key="course.id"
@click="enterCourse(course)">
<div class="classroom-course-cover">
<img v-if="getCourseCover(course.cover)" :src="getCourseCover(course.cover)" :alt="course.courseName">
<div v-else class="classroom-course-cover__empty">{{ course.courseTypeName || '课程' }}</div>
<span v-if="getCourseRecommendName(course.recommendFlags)" class="classroom-course-recommend">
{{ getCourseRecommendName(course.recommendFlags) }}
</span>
</div>
<div class="classroom-course-content">
<div class="classroom-course-name">{{ course.courseName || '未命名课程' }}</div>
<div class="classroom-course-tags">
<span v-if="course.courseTypeName" class="classroom-course-tag">{{ course.courseTypeName }}</span>
<span v-if="course.lecturerName" class="classroom-course-lecturer">讲师:{{ course.lecturerName }}</span>
</div>
<div class="classroom-course-stats">
<span><van-icon name="eye-o"></van-icon>{{ course.viewCount || 0 }}次观看</span>
<span>我的进度 {{ course.progressPercent || 0 }}%</span>
</div>
<div class="classroom-course-progress">
<span :style="{width: Math.min(Number(course.progressPercent || 0), 100) + '%'}"></span>
</div>
</div>
</div>
</div> </div>
<div v-else class="classroom-empty">暂无课程</div>
</div>
<div class="union-news-panel">
<div class="union-news-panel__head">
<div class="union-news-panel__title">工会动态</div>
<div class="union-news-panel__more" @click="enterSpreadList">
<span>全部动态</span>
<van-icon name="arrow"></van-icon>
</div>
</div>
<div class="union-news-tabs union-news-tabs--primary">
<div class="union-news-tab"
v-for="category in spreadCategories"
:key="category.id"
:class="{'union-news-tab--active': spreadParentId === category.id}"
@click="changeSpreadParent(category)">{{ category.name }}</div>
</div>
<div class="union-news-tabs union-news-tabs--secondary" v-if="spreadChildren.length">
<div class="union-news-tab union-news-tab--secondary"
v-for="category in spreadChildren"
:key="category.id"
:class="{'union-news-tab--active': spreadCategoryId === category.id}"
@click="changeSpreadCategory(category)">{{ category.name }}</div>
</div>
<div class="union-news-list" v-if="spreadDynamics.length">
<div class="union-news-item"
v-for="item in spreadDynamics"
:key="item.id"
@click="enterSpreadDetail(item)">
<img v-if="getSpreadCover(item.thumbUrl)"
class="union-news-item__cover"
:src="getSpreadCover(item.thumbUrl)"
:alt="item.title">
<div class="union-news-item__content">
<div class="union-news-item__title">{{ item.title }}</div>
<div v-if="item.summary || item.subTitle" class="union-news-item__summary">
{{ item.summary || item.subTitle }}
</div>
<div class="union-news-item__meta">
<span>{{ formatSpreadDate(item.publishedAt || item.createdAt) }}</span>
<span class="union-news-item__views">
<van-icon name="eye-o"></van-icon>
{{ item.viewCount || 0 }}
</span>
</div>
</div>
</div>
</div>
<div v-else-if="!spreadLoading" class="union-news-empty">暂无动态</div>
</div> </div>
<div class="policy-panel"> <div class="policy-panel">
@@ -121,18 +205,16 @@ const home = {
return { return {
activityOptions: [], activityOptions: [],
quickEntries: [], quickEntries: [],
quickEntryIcons: {
"撰写提案": "/assets/mobile/img/home/v4/recommend/撰写提案.png",
"会员入会": "/assets/mobile/img/home/v4/recommend/会员入会.png",
"帮扶申请": "/assets/mobile/img/home/v4/recommend/帮扶申请.png",
"疗休养报名": "/assets/mobile/img/home/v4/recommend/疗休养报名.png",
"社团入会": "/assets/mobile/img/home/v4/recommend/社团入会.png",
"慰问申请": "/assets/mobile/img/home/v4/recommend/慰问申请.png",
"问卷调查": "/assets/mobile/img/home/v4/recommend/问卷调查.png"
},
classroomCourses: [], classroomCourses: [],
classroomCourseIndex: 0, classroomRecommendOptions: [],
classroomCourseTimer: null, showHomeSummary: false,
homeScrollContainer: null,
spreadCategories: [],
spreadParentId: "",
spreadCategoryId: "",
spreadDynamics: [],
spreadLoading: false,
spreadLoadSeq: 0,
showLatestActivity: false, showLatestActivity: false,
featuredEntries: [ featuredEntries: [
{ {
@@ -197,9 +279,9 @@ const home = {
currentBanner() { currentBanner() {
return this.bannerList[this.activeBannerIndex] || this.bannerList[0] || {} return this.bannerList[this.activeBannerIndex] || this.bannerList[0] || {}
}, },
currentClassroomCourseName() { spreadChildren() {
const course = this.classroomCourses[this.classroomCourseIndex] const parent = this.spreadCategories.find(item => item.id === this.spreadParentId)
return course && course.courseName ? course.courseName : "二十大精神" return parent && parent.children ? parent.children : []
} }
}, },
methods: { methods: {
@@ -353,8 +435,6 @@ const home = {
const cached = this.readHomeCache("classroom") const cached = this.readHomeCache("classroom")
if (cached && cached.length) { if (cached && cached.length) {
this.classroomCourses = cached this.classroomCourses = cached
this.classroomCourseIndex = 0
this.startClassroomCourseTimer()
} }
this.$axios.post("/platform/learning/course/display/pageData", { this.$axios.post("/platform/learning/course/display/pageData", {
pageNumber: 1, pageNumber: 1,
@@ -363,34 +443,140 @@ const home = {
if (res.code === 0) { if (res.code === 0) {
const data = res.data || {} const data = res.data || {}
this.classroomCourses = data.list || [] this.classroomCourses = data.list || []
this.classroomCourseIndex = 0
this.writeHomeCache("classroom", this.classroomCourses, this.homeCacheTtl.classroom) this.writeHomeCache("classroom", this.classroomCourses, this.homeCacheTtl.classroom)
this.startClassroomCourseTimer()
} }
}).catch(() => { }).catch(() => {
if (!cached) { if (!cached) {
this.classroomCourses = [] this.classroomCourses = []
this.classroomCourseIndex = 0
} }
}) })
}, },
startClassroomCourseTimer() { listClassroomRecommendOptions() {
if (this.classroomCourseTimer) { this.$axios.post("/platform/learning/course/display/recommendOptions").then((res) => {
clearInterval(this.classroomCourseTimer) if (res.code === 0) {
this.classroomCourseTimer = null this.classroomRecommendOptions = res.data || []
}
}).catch(() => {
this.classroomRecommendOptions = []
})
},
getCourseCover(cover) {
if (!cover) return ""
if (Array.isArray(cover)) {
const first = cover[0]
return first && (first.url || first.data || (first.response && first.response.data)) || ""
} }
if (this.classroomCourses.length <= 1) { if (typeof cover !== "string") return ""
const text = cover.trim()
if (!text) return ""
if (!text.startsWith("[")) return text
try {
const files = JSON.parse(text)
const first = files[0]
return first && (first.url || first.data || (first.response && first.response.data)) || ""
} catch (e) {
return ""
}
},
getCourseRecommendName(flags) {
const code = flags ? flags.split(",").filter(Boolean)[0] : ""
if (!code) return ""
const option = this.classroomRecommendOptions.find(item => item.code === code)
return option ? option.name : code
},
listSpreadCategories() {
this.$axios.post("/platform/h5/spread/categoryTree").then((res) => {
if (res.code !== 0) {
return
}
this.spreadCategories = res.data || []
const firstParent = this.spreadCategories[0]
if (!firstParent) {
this.spreadDynamics = []
return
}
this.spreadParentId = firstParent.id
const children = firstParent.children || []
this.spreadCategoryId = children.length ? children[0].id : firstParent.id
this.listSpreadHomeContent(this.spreadCategoryId)
}).catch(() => {
this.spreadCategories = []
this.spreadDynamics = []
})
},
listSpreadHomeContent(categoryId) {
const loadSeq = ++this.spreadLoadSeq
this.spreadLoading = true
this.$axios.post("/platform/h5/spread/pageData", {
categoryId: categoryId,
pageNumber: 1,
pageSize: 5
}).then((res) => {
if (loadSeq === this.spreadLoadSeq && res.code === 0) {
const data = res.data || {}
this.spreadDynamics = data.list || []
}
}).catch(() => {
if (loadSeq === this.spreadLoadSeq) {
this.spreadDynamics = []
}
}).finally(() => {
if (loadSeq === this.spreadLoadSeq) {
this.spreadLoading = false
}
})
},
changeSpreadParent(category) {
if (this.spreadParentId === category.id) {
return return
} }
this.classroomCourseTimer = setInterval(() => { this.spreadParentId = category.id
this.classroomCourseIndex = (this.classroomCourseIndex + 1) % this.classroomCourses.length const children = category.children || []
}, 3000) this.spreadCategoryId = children.length ? children[0].id : category.id
this.listSpreadHomeContent(this.spreadCategoryId)
},
changeSpreadCategory(category) {
if (this.spreadCategoryId === category.id) {
return
}
this.spreadCategoryId = category.id
this.listSpreadHomeContent(category.id)
},
getSpreadCover(value) {
if (!value) {
return ""
}
let files = value
if (typeof value === "string" && value.trim().startsWith("[")) {
try {
files = JSON.parse(value)
} catch (e) {
files = []
}
}
if (!Array.isArray(files)) {
files = String(files).split(",")
}
const first = files[0]
if (typeof first === "string") {
return first.trim()
}
return first && (first.src || first.url || first.data || (first.response && first.response.data)) || ""
},
formatSpreadDate(value) {
return value ? this.$moment(Number(value)).format("YYYY-MM-DD") : ""
},
enterSpreadDetail(item) {
this.$pjaxReplace("/platform/h5/spread?id=" + encodeURIComponent(item.id))
},
enterSpreadList() {
this.$pjaxReplace("/platform/h5/spread")
}, },
menuClick(item) { menuClick(item) {
this.$pjaxReplace(item.href) this.$pjaxReplace(item.href)
}, },
getQuickEntryIcon(item) { getQuickEntryIcon(item) {
return this.quickEntryIcons[item.name] || item.picIcon || "" return item.picIcon || ""
}, },
featureClick(item) { featureClick(item) {
if (item.href) { if (item.href) {
@@ -400,6 +586,16 @@ const home = {
enterClassroom() { enterClassroom() {
this.$pjaxReplace("/platform/learning/course/h5") this.$pjaxReplace("/platform/learning/course/h5")
}, },
enterCourse(course) {
if (course && course.id) {
this.$pjaxReplace("/platform/learning/course/h5/study?id=" + encodeURIComponent(course.id))
}
},
handleHomeScroll() {
if (!this.homeScrollContainer) return
const heroHeight = this.$refs.homeHero ? this.$refs.homeHero.offsetHeight : 200
this.showHomeSummary = this.homeScrollContainer.scrollTop >= Math.max(heroHeight - 48, 0)
},
bannerChange(index) { bannerChange(index) {
this.activeBannerIndex = index this.activeBannerIndex = index
}, },
@@ -416,43 +612,129 @@ const home = {
this.listHomeBrand() this.listHomeBrand()
this.listRecommendApp() this.listRecommendApp()
this.listClassroomCourses() this.listClassroomCourses()
this.listClassroomRecommendOptions()
this.listSpreadCategories()
if (this.showLatestActivity) { if (this.showLatestActivity) {
this.listActivity() this.listActivity()
} }
}, },
mounted() {
this.homeScrollContainer = document.querySelector("body > .main-page .page-wrapper")
if (this.homeScrollContainer) {
this.homeScrollContainer.addEventListener("scroll", this.handleHomeScroll, {passive: true})
this.handleHomeScroll()
}
},
beforeDestroy() { beforeDestroy() {
if (this.classroomCourseTimer) { if (this.homeScrollContainer) {
clearInterval(this.classroomCourseTimer) this.homeScrollContainer.removeEventListener("scroll", this.handleHomeScroll)
this.classroomCourseTimer = null
} }
}, },
style: /*language=CSS*/ ` style: /*language=CSS*/ `
.home-page {
padding-bottom: 65px;
box-sizing: border-box;
}
.home-summary-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 30;
height: 48px;
padding: 0 16px;
display: flex;
align-items: center;
opacity: 0;
color: #202124;
background-color: rgba(255, 255, 255, .96);
border-bottom: 1px solid rgba(224, 230, 238, .9);
box-shadow: 0 3px 12px rgba(31, 72, 122, .08);
box-sizing: border-box;
transform: translateY(-100%);
transition: opacity .2s ease, transform .2s ease;
pointer-events: none;
}
.home-summary-bar--visible {
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
.home-summary-bar__logo {
width: 118px;
height: 28px;
display: block;
object-fit: contain;
object-position: left center;
}
.home-summary-bar__divider {
width: 1px;
height: 18px;
margin: 0 10px;
background-color: #dce3ec;
}
.home-summary-bar__title {
color: #202124;
font-size: 16px;
line-height: 22px;
font-weight: 600;
white-space: nowrap;
}
.home-hero { .home-hero {
position: relative; position: relative;
height: 200px; height: 200px;
overflow: hidden; overflow: hidden;
color: #fff; color: #fff;
background-color: #0878f7; background: #0878f4;
}
.home-hero__color-layer {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 100%;
z-index: 1;
pointer-events: none;
background: linear-gradient(111deg,
#0561e9 0%,
#0873f0 45%,
rgba(9, 132, 244, .96) 51%,
rgba(10, 150, 247, .72) 58%,
rgba(10, 158, 247, .4) 65%,
rgba(10, 158, 247, .14) 71%,
rgba(10, 158, 247, 0) 75%);
-webkit-clip-path: polygon(0 0, 90% 0, 70% 100%, 0 100%);
clip-path: polygon(0 0, 90% 0, 70% 100%, 0 100%);
} }
.home-hero__bg { .home-hero__bg {
position: absolute; position: absolute;
inset: 0; top: 0;
right: 0;
bottom: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
object-position: center; object-position: 90% center;
display: block; display: block;
} }
.home-hero__content { .home-hero__content {
position: relative; position: relative;
z-index: 1; z-index: 2;
width: 60%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
height: 100%; height: 100%;
padding: 15px 20px 12px; padding: 15px 0 12px 20px;
box-sizing: border-box; box-sizing: border-box;
} }
@@ -464,7 +746,9 @@ const home = {
margin-bottom: 12px; margin-bottom: 12px;
} }
.home-hero__brand-logo { .home-hero__brand-logo {
transform: scale(1.15);
transform-origin: left center;
display: block; display: block;
width: 160px; width: 160px;
height: 36px; height: 36px;
@@ -712,20 +996,192 @@ const home = {
color: #087af4; color: #087af4;
} }
.classroom-panel { .classroom-section {
height: 48px; margin: 10px 12px 7px;
margin: 15px 12px 7px; padding: 13px 0 12px;
padding: 0 12px; overflow: hidden;
display: flex; border-radius: 10px;
align-items: center;
justify-content: space-between;
border-radius: 8px;
background-color: #fff; background-color: #fff;
box-sizing: border-box; box-sizing: border-box;
} }
.classroom-panel:active { .classroom-section__head {
transform: scale(.99); padding: 0 12px 10px;
display: flex;
align-items: center;
justify-content: space-between;
}
.classroom-section__title {
color: #202124;
font-size: 15px;
line-height: 21px;
font-weight: 600;
}
.classroom-section__more {
display: flex;
align-items: center;
color: #969799;
font-size: 12px;
line-height: 18px;
}
.classroom-section__more .van-icon {
margin-left: 4px;
font-size: 13px;
}
.classroom-course-scroll {
padding: 0 12px 4px;
display: flex;
gap: 10px;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: none;
-webkit-overflow-scrolling: touch;
}
.classroom-course-scroll::-webkit-scrollbar {
display: none;
}
.classroom-course-card {
width: 238px;
flex: 0 0 238px;
overflow: hidden;
border: 1px solid #edf1f6;
border-radius: 8px;
background-color: #fff;
box-shadow: 0 3px 10px rgba(34, 76, 122, .07);
}
.classroom-course-card:active {
transform: scale(.985);
}
.classroom-course-cover {
position: relative;
height: 104px;
overflow: hidden;
background-color: #eaf2fb;
}
.classroom-course-cover img {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
.classroom-course-cover__empty {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #6b87a8;
font-size: 13px;
background: linear-gradient(135deg, #e8f2ff, #f3f8ff);
}
.classroom-course-recommend {
position: absolute;
top: 8px;
left: 8px;
max-width: 90px;
padding: 2px 7px;
overflow: hidden;
color: #fff;
font-size: 10px;
line-height: 16px;
text-overflow: ellipsis;
white-space: nowrap;
border-radius: 999px;
background-color: rgba(255, 116, 52, .92);
}
.classroom-course-content {
padding: 9px 10px 10px;
}
.classroom-course-name {
overflow: hidden;
color: #202124;
font-size: 14px;
line-height: 20px;
font-weight: 600;
text-overflow: ellipsis;
white-space: nowrap;
}
.classroom-course-tags {
height: 22px;
margin-top: 5px;
display: flex;
align-items: center;
gap: 6px;
overflow: hidden;
}
.classroom-course-tag {
max-width: 82px;
padding: 1px 6px;
overflow: hidden;
color: #287ff0;
font-size: 10px;
line-height: 16px;
text-overflow: ellipsis;
white-space: nowrap;
border-radius: 3px;
background-color: #edf6ff;
}
.classroom-course-lecturer {
min-width: 0;
overflow: hidden;
color: #7d8797;
font-size: 10px;
line-height: 16px;
text-overflow: ellipsis;
white-space: nowrap;
}
.classroom-course-stats {
margin-top: 7px;
display: flex;
align-items: center;
justify-content: space-between;
color: #969daa;
font-size: 10px;
line-height: 15px;
}
.classroom-course-stats .van-icon {
margin-right: 3px;
vertical-align: -1px;
}
.classroom-course-progress {
height: 3px;
margin-top: 6px;
overflow: hidden;
border-radius: 3px;
background-color: #edf1f6;
}
.classroom-course-progress span {
height: 100%;
display: block;
border-radius: inherit;
background-color: #287ff0;
}
.classroom-empty {
padding: 18px 0 12px;
color: #9aa2af;
font-size: 12px;
line-height: 18px;
text-align: center;
} }
.entry-panel__heading { .entry-panel__heading {
@@ -746,55 +1202,13 @@ const home = {
font-size: 15px; font-size: 15px;
} }
.entry-panel__icon--classroom {
background: linear-gradient(145deg, #0c70f5, #278fff);
}
.entry-panel__icon--policy { .entry-panel__icon--policy {
background: linear-gradient(145deg, #ff424d, #ff6c73); background: linear-gradient(145deg, #ff424d, #ff6c73);
} }
.classroom-panel__title {
flex-shrink: 0;
color: #202124;
font-size: 14px;
line-height: 20px;
font-weight: 600;
}
.classroom-panel__course {
min-width: 0;
flex: 1;
margin: 0 8px;
color: #087af4;
font-size: 12px;
line-height: 17px;
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: 12px;
line-height: 17px;
white-space: nowrap;
}
.classroom-panel__action .van-icon {
flex-shrink: 0;
margin-left: 4px;
font-size: 14px;
}
.policy-panel { .policy-panel {
height: 48px; height: 48px;
margin: 0 12px 65px; margin: 0 12px 7px;
padding: 0 12px; padding: 0 12px;
display: flex; display: flex;
align-items: center; align-items: center;
@@ -825,6 +1239,205 @@ const home = {
font-size: 14px; font-size: 14px;
} }
.union-news-panel {
margin: 15px 12px 7px;
padding: 13px 12px 8px;
border-radius: 10px;
background-color: #fff;
box-sizing: border-box;
}
.union-news-panel__head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.union-news-panel__title {
color: #202124;
font-size: 15px;
line-height: 21px;
font-weight: 600;
}
.union-news-panel__more {
display: flex;
align-items: center;
color: #969799;
font-size: 12px;
line-height: 18px;
}
.union-news-panel__more .van-icon {
margin-left: 4px;
font-size: 13px;
}
.union-news-tabs {
height: 38px;
padding: 0;
display: flex;
overflow-x: auto;
overflow-y: hidden;
gap: 0;
border-bottom: 1px solid #edf1f6;
border-radius: 0;
background-color: transparent;
box-sizing: border-box;
scrollbar-width: none;
-webkit-overflow-scrolling: touch;
}
.union-news-tabs::-webkit-scrollbar {
display: none;
}
.union-news-tabs--secondary {
height: 32px;
margin-top: 8px;
padding: 0;
gap: 6px;
border-bottom: 0;
border-radius: 0;
background-color: transparent;
}
.union-news-tab {
position: relative;
min-width: 96px;
padding: 0 8px;
flex: 1 0 calc((100% - 6px) / 3);
display: flex;
align-items: center;
justify-content: center;
color: #7d8797;
font-size: 12px;
line-height: 18px;
white-space: nowrap;
border-radius: 7px;
box-sizing: border-box;
}
.union-news-tab--secondary {
min-width: 88px;
height: 28px;
padding: 0 12px;
flex: 1 0 calc((100% - 12px) / 3);
color: #969daa;
font-size: 11px;
border: 1px solid #e7ecf3;
border-radius: 999px;
background-color: #fafbfd;
}
.union-news-tab--active {
color: #087af4;
font-weight: 600;
}
.union-news-tabs--primary .union-news-tab--active {
color: #087af4;
background-color: transparent;
background-image: linear-gradient(#087af4, #087af4);
background-repeat: no-repeat;
background-position: center bottom;
background-size: 24px 2px;
box-shadow: none;
}
.union-news-tabs--primary .union-news-tab--active::after {
content: none;
}
.union-news-tabs--secondary .union-news-tab--active {
color: #087af4;
border-color: #b9d8ff;
background-color: #edf6ff;
}
.union-news-list {
margin-top: 7px;
}
.union-news-item {
min-height: 58px;
padding: 8px 0;
display: flex;
align-items: center;
box-sizing: border-box;
}
.union-news-item + .union-news-item {
border-top: 1px solid #f2f4f7;
}
.union-news-item:active {
background-color: #f7faff;
}
.union-news-item__cover {
width: 80px;
height: 48px;
margin-right: 10px;
flex-shrink: 0;
border-radius: 4px;
object-fit: cover;
}
.union-news-item__content {
min-width: 0;
flex: 1;
}
.union-news-item__title {
overflow: hidden;
color: #202124;
font-size: 13px;
line-height: 19px;
font-weight: 600;
text-overflow: ellipsis;
white-space: nowrap;
}
.union-news-item__summary {
margin-top: 2px;
overflow: hidden;
color: #7d8797;
font-size: 10px;
line-height: 15px;
text-overflow: ellipsis;
white-space: nowrap;
}
.union-news-item__meta {
margin-top: 3px;
display: flex;
align-items: center;
justify-content: space-between;
color: #9aa2af;
font-size: 10px;
line-height: 14px;
}
.union-news-item__views {
display: inline-flex;
align-items: center;
}
.union-news-item__views .van-icon {
margin-right: 3px;
font-size: 11px;
}
.union-news-empty {
padding: 18px 0 14px;
color: #9aa2af;
font-size: 12px;
line-height: 18px;
text-align: center;
}
.act-list { .act-list {
background-color: #fff; background-color: #fff;
margin: 10px; margin: 10px;
@@ -15,7 +15,7 @@ layout("/layouts/platform_h5.html"){
<home v-if="homeTabbarActive === 'home'" @module-click="moduleClick"></home> <home v-if="homeTabbarActive === 'home'" @module-click="moduleClick"></home>
<todo v-if="homeTabbarActive === 'todo'"></todo> <todo v-if="homeTabbarActive === 'todo'"></todo>
<work v-if="homeTabbarActive === 'work'"></work> <work v-if="homeTabbarActive === 'work'"></work>
<mine v-if="homeTabbarActive === 'mine'"></mine> <mine v-if="homeTabbarActive === 'mine'" @switch-tab="switchHomeTab"></mine>
<apps v-if="homeTabbarActive === 'apps'"></apps> <apps v-if="homeTabbarActive === 'apps'"></apps>
<msg v-if="homeTabbarActive === 'msg'"></msg> <msg v-if="homeTabbarActive === 'msg'"></msg>
</div> </div>
@@ -60,6 +60,10 @@ layout("/layouts/platform_h5.html"){
homeTabbarChange(val) { homeTabbarChange(val) {
this.$store.commit("setActiveTarBar", val) this.$store.commit("setActiveTarBar", val)
}, },
switchHomeTab(val) {
this.homeTabbarActive = val
this.$store.commit("setActiveTarBar", val)
},
moduleClick(val) { moduleClick(val) {
this.homeTabbarActive = "apps" this.homeTabbarActive = "apps"
this.$store.commit("setActiveTarBar", "apps") this.$store.commit("setActiveTarBar", "apps")
@@ -1,102 +1,148 @@
const mine = { const mine = {
template: /*language=HTML*/ ` template: /*language=HTML*/ `
<div class="home-mine"> <div class="home-mine">
<van-nav-bar title="个人中心" class="nav-bar"></van-nav-bar> <van-nav-bar title="我的" class="mine-nav" placeholder fixed :border="false"></van-nav-bar>
<section class="mine-hero">
<div class="profile-card"> <div class="mine-user-summary">
<div class="profile-header"> <div class="mine-avatar-wrap">
<div class="avatar-section"> <van-image :src="avatar" round width="58" height="58" class="mine-avatar">
<div class="avatar-wrapper"> <van-loading slot="loading" type="spinner" size="18"></van-loading>
<van-image :src="avatar" round width="80" height="80" class="avatar"> </van-image>
<van-loading slot="loading" type="spinner" size="20"></van-loading> <i class="mine-online-dot"></i>
</van-image>
<div class="status-badge">
<div class="status-dot"></div>
<span>{{ $store.state.user.userState }}</span>
</div>
</div>
</div> </div>
<div class="mine-user-text">
<div class="basic-info"> <strong>{{ user.username || '-' }}</strong>
<div class="name-row"> <span>工号:{{ user.loginname || '-' }}</span>
<h2 class="username">{{ $store.state.user.username }}</h2> <span>性别:{{ user.sex || '-' }}</span>
<div class="gender-tag">
<van-icon name="friends-o" class="gender-icon"></van-icon>
<span>{{ $store.state.user.sex }}</span>
</div>
</div>
<div class="employee-id">
工号:
<span class="id-value">{{ $store.state.user.loginname }}</span>
</div>
</div> </div>
</div> </div>
<div class="info-list"> <div class="mine-status-card">
<!-- <div class="info-item"> <div class="mine-status-item">
<van-icon name="user-o"></van-icon> <span>在职状态</span>
<span class="item-label">人员类型</span> <strong :class="employmentStatusClass">{{ user.userState || '-' }}</strong>
<span class="info-value">{{ $store.state.user.personType }}</span>
</div>-->
<div class="info-item">
<van-icon name="user-o"></van-icon>
<span class="item-label">在职状态</span>
<span class="info-value">{{ $store.state.user.userState }}</span>
</div> </div>
<div class="info-item"> <div class="mine-status-item">
<van-icon name="wap-home-o"></van-icon> <span>所属单位</span>
<span class="item-label">所属单位</span> <strong>{{ unitName }}</strong>
<span class="info-value">{{ $store.state.user.unit.name }}</span>
</div> </div>
<div class="info-item"> <div class="mine-status-item">
<van-icon name="wap-home-o"></van-icon> <span>所属工会</span>
<span class="item-label">所属工会</span> <strong>{{ unionName }}</strong>
<span class="info-value">{{ $store.state.user.union.name }}</span>
</div> </div>
</div> </div>
</div> </section>
<div class="action-cards"> <main class="mine-content">
<van-cell-group> <section class="mine-panel mine-business-panel">
<van-cell is-link @click="$pjaxReplace('/platform/h5/indexMine/userInfo')" class="action-cell"> <div class="mine-panel-heading">
<span class="cell-title" slot="title"> <h2>我的业务</h2>
<van-icon name="user-o" class="cell-icon"></van-icon> <button type="button" class="mine-panel-link" @click="switchTab('apps')">
个人信息 全部业务 <van-icon name="arrow"></van-icon>
</span> </button>
</van-cell> </div>
<div class="mine-grid mine-business-grid">
<button type="button" class="mine-grid-item" @click="switchTab('todo')">
<van-icon name="orders-o"></van-icon>
<span>待办事项</span>
</button>
<button type="button" class="mine-grid-item" @click="switchTab('apps')">
<van-icon name="edit"></van-icon>
<span>我的申请</span>
</button>
<button type="button" class="mine-grid-item" @click="$pjaxReplace('/platform/learning/course/h5')">
<van-icon name="notes-o"></van-icon>
<span>我的课程</span>
</button>
<button type="button" class="mine-grid-item" @click="switchTab('msg')">
<van-icon name="envelop-o"></van-icon>
<span>我的消息</span>
</button>
</div>
</section>
<van-cell is-link @click="$pjaxReplace('/platform/signature/h5')" class="action-cell"> <section class="mine-panel mine-tools-panel">
<span class="cell-title" slot="title"> <div class="mine-panel-heading">
<van-icon name="edit" class="cell-icon"></van-icon> <h2>我的工具</h2>
我的签名 </div>
</span> <div class="mine-grid mine-tools-grid">
</van-cell> <button type="button" class="mine-grid-item" @click="$pjaxReplace('/platform/h5/indexMine/userInfo')">
<van-icon name="user-circle-o"></van-icon>
<span>个人信息</span>
</button>
<button type="button" class="mine-grid-item" @click="$pjaxReplace('/platform/signature/h5')">
<van-icon name="edit"></van-icon>
<span>我的签名</span>
</button>
<button type="button" class="mine-grid-item" @click="switchTab('apps')">
<van-icon name="star-o"></van-icon>
<span>我的收藏</span>
</button>
<button type="button" class="mine-grid-item" @click="switchTab('apps')">
<van-icon name="gift-o"></van-icon>
<span>福利中心</span>
</button>
<button type="button" class="mine-grid-item" @click="switchTab('apps')">
<van-icon name="calendar-o"></van-icon>
<span>活动报名</span>
</button>
<button type="button" class="mine-grid-item" @click="switchTab('apps')">
<van-icon name="completed-o"></van-icon>
<span>问卷调查</span>
</button>
<button type="button" class="mine-grid-item" @click="switchTab('apps')">
<van-icon name="setting-o"></van-icon>
<span>设置中心</span>
</button>
<button type="button" class="mine-grid-item" @click="switchTab('apps')">
<van-icon name="question-o"></van-icon>
<span>帮助中心</span>
</button>
</div>
</section>
<!-- <van-cell :value="$store.state.user.totalIntegral" is-link @click="$pjaxReplace('/platform/integral/records/h5')" class="action-cell">--> <div class="mine-logout-wrap">
<!-- <span class="cell-title" slot="title">--> <van-button round block plain color="#bdcbe0" @click="logout" class="mine-logout-btn">退出登录</van-button>
<!-- <van-icon name="points" class="cell-icon"></van-icon>--> </div>
<!-- 我的积分--> </main>
<!-- </span>-->
<!-- </van-cell>-->
</van-cell-group>
</div>
<div class="logout-wrapper">
<van-button round block color="#ee0a24" @click="logout" class="logout-btn">退出登录</van-button>
</div>
</div> </div>
`, `,
computed: { computed: {
user() {
return this.$store.state.user || {}
},
unitName() {
return (this.user.unit && this.user.unit.name) || "-"
},
unionName() {
return (this.user.union && this.user.union.name) || "-"
},
employmentStatusClass() {
const status = this.user.userState || ""
if (/离职|不在职|停职|辞退/.test(status)) {
return "mine-employment-status--inactive"
}
if (/退休|离休/.test(status)) {
return "mine-employment-status--retired"
}
if (/在职/.test(status)) {
return "mine-employment-status--active"
}
return "mine-employment-status--default"
},
avatar() { avatar() {
const sex = this.$store.state.user.sex const sex = this.user.sex
if (sex === "男" || !sex) { if (sex === "男" || !sex) {
return "/assets/platform/img/home/avatar_boy.png" return "/assets/platform/img/home/avatar_boy.png"
} else {
return "/assets/platform/img/home/avatar_girl.png"
} }
return "/assets/platform/img/home/avatar_girl.png"
} }
}, },
methods: { methods: {
switchTab(tab) {
this.$store.commit("setActiveTarBar", tab)
this.$emit("switch-tab", tab)
},
logout() { logout() {
this.$dialog this.$dialog
.confirm({ .confirm({
@@ -112,261 +158,258 @@ const mine = {
style() { style() {
return /*language=CSS*/ ` return /*language=CSS*/ `
.home-mine { .home-mine {
background: #f7f8fa; min-height: 100vh;
padding-bottom: 76px;
box-sizing: border-box;
background: #f3f6fa;
color: #15243a;
} }
.profile-card { .mine-hero {
margin: 10px;
padding: 20px;
background: white;
border-radius: 12px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
animation: slideIn 0.3s ease-out;
}
.profile-header {
display: flex;
gap: 16px;
align-items: flex-start;
margin-bottom: 20px;
animation: slideIn 0.3s ease-out;
}
.avatar-section {
flex-shrink: 0;
width: 80px;
}
.avatar-wrapper {
position: relative; position: relative;
width: 80px; height: 154px;
height: 80px; box-sizing: border-box;
padding: 0 26px;
background: linear-gradient(90deg, #0878f9 0%, #35aff6 100%);
} }
.avatar { /deep/ .mine-nav,
border: 2px solid #f5f5f5; /deep/ .mine-nav .van-nav-bar {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); background: linear-gradient(90deg, #0878f9 0%, #35aff6 100%) !important;
transition: transform 0.3s;
} }
.status-badge { /deep/ .mine-nav .van-nav-bar__title {
color: #fff;
}
.mine-user-summary {
display: flex;
align-items: center;
padding-top: 36px;
}
.mine-avatar-wrap {
position: relative;
flex: 0 0 58px;
width: 58px;
height: 58px;
margin-right: 12px;
}
.mine-avatar {
overflow: hidden;
box-sizing: border-box;
border: 2px solid rgba(255, 255, 255, 0.96);
background: #fff;
}
.mine-online-dot {
position: absolute; position: absolute;
bottom: -2px; right: -1px;
right: -2px; bottom: 2px;
background: rgba(255, 255, 255, 0.9); width: 11px;
color: #34c759; height: 11px;
padding: 2px 8px; border: 2px solid #fff;
border-radius: 10px;
font-size: 12px;
display: flex;
align-items: center;
gap: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
z-index: 1;
animation: fadeInScale 0.3s ease-out 0.2s backwards;
}
.status-dot {
width: 6px;
height: 6px;
background: #34c759;
border-radius: 50%; border-radius: 50%;
background: #3dd0a0;
} }
.basic-info { .mine-user-text {
flex: 1;
padding-top: 4px;
display: flex; display: flex;
min-width: 0;
flex-direction: column; flex-direction: column;
gap: 8px; color: #fff;
line-height: 1.45;
} }
.name-row { .mine-user-text strong {
display: flex; overflow: hidden;
align-items: center; max-width: 240px;
gap: 12px; margin-bottom: 1px;
animation: slideLeft 0.3s ease-out;
}
.username {
margin: 0;
font-size: 20px; font-size: 20px;
font-weight: 500; font-weight: 600;
color: #333; text-overflow: ellipsis;
white-space: nowrap;
} }
.gender-tag { .mine-user-text span {
font-size: 14px;
opacity: 0.96;
}
.mine-status-card {
position: absolute;
z-index: 2;
right: 12px;
bottom: -102px;
left: 12px;
display: grid;
min-height: 112px;
grid-template-columns: repeat(3, minmax(0, 1fr));
align-items: center;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.82);
border-radius: 12px;
background: #fff;
box-shadow: 0 12px 30px rgba(35, 82, 130, 0.13);
}
.mine-status-item {
position: relative;
display: flex;
min-width: 0;
flex-direction: column;
align-items: center;
padding: 0 8px;
text-align: center;
}
.mine-status-item + .mine-status-item::before {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 1px;
background: #e3ebf5;
content: "";
}
.mine-status-item strong {
overflow: hidden;
width: 100%;
margin-top: 18px;
color: #18263a;
font-size: 16px;
font-weight: 600;
line-height: 24px;
text-overflow: ellipsis;
white-space: nowrap;
}
.mine-status-item span {
color: #75849b;
font-size: 13px;
}
.mine-status-item .mine-employment-status--active {
color: #28b985;
}
.mine-status-item .mine-employment-status--retired {
color: #f2a33a;
}
.mine-status-item .mine-employment-status--inactive {
color: #e46a6a;
}
.mine-status-item .mine-employment-status--default {
color: #718198;
}
.mine-content {
padding: 124px 12px 22px;
}
.mine-panel {
margin-bottom: 13px;
padding: 14px 10px 13px;
border: 1px solid rgba(255, 255, 255, 0.82);
border-radius: 12px;
background: #fff;
box-shadow: 0 12px 30px rgba(35, 82, 130, 0.13);
}
.mine-panel-heading {
display: flex;
height: 28px;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #e3ebf5;
}
.mine-panel-heading h2 {
margin: 0;
color: #18263a;
font-size: 17px;
font-weight: 600;
}
.mine-panel-link {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 4px; padding: 0;
background: #f5f5f5; border: 0;
padding: 2px 10px; outline: 0;
border-radius: 20px; background: transparent;
color: #75849b;
font-size: 13px; font-size: 13px;
color: #666;
} }
.employee-id { .mine-panel-link .van-icon {
color: #666; margin-left: 5px;
font-size: 15px; color: #a8b6c8;
animation: slideLeft 0.3s ease-out 0.1s backwards;
} }
.id-value { .mine-grid {
color: #333; display: grid;
font-weight: 500; grid-template-columns: repeat(4, minmax(0, 1fr));
font-size: 16px;
} }
.info-list { .mine-business-grid {
margin-top: 20px; padding-top: 19px;
padding-top: 20px; }
border-top: 1px solid #f5f5f5;
.mine-tools-grid {
padding-top: 18px;
row-gap: 22px;
}
.mine-grid-item {
display: flex; display: flex;
min-width: 0;
flex-direction: column; flex-direction: column;
gap: 0;
}
.info-item {
display: flex;
align-items: center; align-items: center;
gap: 8px; padding: 0 2px;
font-size: 14px; border: 0;
animation: slideLeft 0.3s ease-out; outline: 0;
transition: background-color 0.3s; background: transparent;
padding: 8px; color: #173d68;
border-radius: 8px;
} }
.info-item:active { .mine-grid-item:active {
background-color: #f5f5f5; opacity: 0.65;
} }
.item-label { .mine-grid-item .van-icon {
color: #999; height: 26px;
width: 70px; font-size: 25px;
line-height: 26px;
} }
.info-value { .mine-grid-item span {
color: #333; overflow: hidden;
flex: 1; width: 100%;
margin-top: 9px;
color: #1c2b41;
font-size: 13px;
line-height: 18px;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
} }
.info-item .van-icon { .mine-logout-wrap {
font-size: 16px; padding-top: 4px;
color: #666;
width: 20px;
} }
.action-cards { .mine-logout-btn {
margin: 10px;
animation: slideUp 0.3s ease-out;
}
.action-cell {
padding: 16px !important;
}
.cell-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 15px;
color: #333;
}
.cell-icon {
font-size: 18px;
color: #666;
}
.logout-wrapper {
padding: 16px;
margin-top: 20px;
animation: fadeIn 0.3s ease-out;
}
.logout-btn {
height: 44px; height: 44px;
font-size: 16px; border-color: #bdcbe0 !important;
transition: opacity 0.3s; background: rgba(255, 255, 255, 0.82) !important;
} color: #718198 !important;
font-size: 15px;
.logout-btn:active {
opacity: 0.8;
}
/* 动画定义 */
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideLeft {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeInScale {
from {
opacity: 0;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}
/* 为每个列表项添加延迟动画 */
.info-item:nth-child(1) {
animation-delay: 0.1s;
}
.info-item:nth-child(2) {
animation-delay: 0.2s;
}
.info-item:nth-child(3) {
animation-delay: 0.3s;
}
.info-item:nth-child(4) {
animation-delay: 0.4s;
} }
` `
} }
} }
@@ -1,10 +1,37 @@
const todo = { const todo = {
template: /*language=HTML*/ ` template: /*language=HTML*/ `
<div class="task-center"> <div class="task-center">
<van-nav-bar title="待办中心" placeholder fixed></van-nav-bar> <van-nav-bar class="task-nav" placeholder fixed :border="false" style="background-color: #0879f9;">
<template #title><span class="task-nav-title">待办中心</span></template>
</van-nav-bar>
<div class="task-hero">
<div class="task-statistics">
<div class="statistic-item">
<div class="statistic-icon statistic-icon-todo">
<van-icon name="description"></van-icon>
</div>
<div class="statistic-count">{{ todoCount }}</div>
<div class="statistic-label">待办事项</div>
</div>
<div class="statistic-item">
<div class="statistic-icon statistic-icon-done">
<van-icon name="underway-o"></van-icon>
</div>
<div class="statistic-count">{{ doneCount }}</div>
<div class="statistic-label">已办事项</div>
</div>
<div class="statistic-item">
<div class="statistic-icon statistic-icon-started">
<van-icon name="guide-o"></van-icon>
</div>
<div class="statistic-count">{{ startedCount }}</div>
<div class="statistic-label">我发起的</div>
</div>
</div>
</div>
<!-- 标签页 --> <!-- 标签页 -->
<van-sticky offset-top="46px"> <van-sticky offset-top="46px">
<van-tabs v-model="activeTab" @change="handleTabChange" animated swipeable> <van-tabs v-model="activeTab" class="task-tabs" @change="handleTabChange" animated swipeable>
<van-tab :title="'待办(' + todoCount + ')'" name="todo"></van-tab> <van-tab :title="'待办(' + todoCount + ')'" name="todo"></van-tab>
<van-tab :title="'已办(' + doneCount + ')'" name="done"></van-tab> <van-tab :title="'已办(' + doneCount + ')'" name="done"></van-tab>
<van-tab :title="'发起(' + startedCount + ')'" name="started"></van-tab> <van-tab :title="'发起(' + startedCount + ')'" name="started"></van-tab>
@@ -44,6 +71,15 @@ const todo = {
ref="tableListRef" ref="tableListRef"
@ready="doSearch" @ready="doSearch"
title="variable.instanceName"> title="variable.instanceName">
<template #header="{row}">
<div class="todo-item-header">
<div class="todo-item-title">
<span class="todo-title-icon"><van-icon name="description"></van-icon></span>
<span class="todo-title-text">{{ row.variable?.instanceName || '' }}</span>
</div>
<span class="todo-status">{{ getTaskStatus(row) }}</span>
</div>
</template>
<template v-slot="{index,row}"> <template v-slot="{index,row}">
<table-column label="任务节点">{{row.taskName}}</table-column> <table-column label="任务节点">{{row.taskName}}</table-column>
<table-column label="流程分类">{{row.categoryName}}</table-column> <table-column label="流程分类">{{row.categoryName}}</table-column>
@@ -183,6 +219,13 @@ const todo = {
this.$pjaxReplace(h5FormKey + "?taskId=" + taskId + "bizId=" + businessNo + "taskKey=" + taskKey+ "&tab=" + this.activeTab) this.$pjaxReplace(h5FormKey + "?taskId=" + taskId + "bizId=" + businessNo + "taskKey=" + taskKey+ "&tab=" + this.activeTab)
}, },
// 根据当前列表类型统一展示流程状态,发起列表保留流程实例的实际状态。
getTaskStatus(task) {
if (this.activeTab === "todo") return "进行中";
if (this.activeTab === "done") return "已完成";
return this.processStatusMap[task.state]?.text || "未知状态";
},
// 获取空状态文本 // 获取空状态文本
getEmptyText() { getEmptyText() {
switch (this.activeTab) { switch (this.activeTab) {
@@ -198,6 +241,98 @@ const todo = {
} }
}, },
style: /*language=CSS*/ ` style: /*language=CSS*/ `
.task-center {
min-height: 100vh;
padding-bottom: 72px;
box-sizing: border-box;
background: #f5f7fb;
}
/deep/ .task-nav .van-nav-bar {
background-color: #0879f9 !important;
}
/deep/ .task-nav .van-nav-bar::after {
border-bottom: 0;
}
/deep/ .task-nav .van-nav-bar__title {
color: #fff;
font-size: 16px;
font-weight: 500;
}
.task-nav-title {
color: #fff;
}
.task-hero {
position: relative;
height: 92px;
background-color: #0879f9;
}
.task-statistics {
position: absolute;
top: 0;
right: 12px;
left: 12px;
display: flex;
height: 78px;
padding: 10px 6px 8px;
box-sizing: border-box;
border-radius: 10px;
background: #fff;
box-shadow: 0 6px 18px rgba(45, 91, 145, 0.14);
overflow: hidden;
}
.statistic-item {
display: flex;
flex: 1;
min-width: 0;
flex-direction: column;
align-items: center;
justify-content: center;
}
.statistic-icon {
display: flex;
width: 24px;
height: 24px;
align-items: center;
justify-content: center;
border-radius: 50%;
color: #fff;
font-size: 15px;
}
.statistic-icon-todo {
background: #4d8ff7;
}
.statistic-icon-done {
background: #ffad3d;
}
.statistic-icon-started {
background: #5dce91;
}
.statistic-count {
margin-top: 3px;
color: #26364d;
font-size: 14px;
font-weight: 600;
line-height: 18px;
}
.statistic-label {
color: #8d97a7;
font-size: 10px;
line-height: 15px;
}
/* 标签页样式优化 */ /* 标签页样式优化 */
/deep/ .van-tabs__nav { /deep/ .van-tabs__nav {
background-color: #fff; background-color: #fff;
@@ -213,6 +348,9 @@ const todo = {
} }
/deep/ .van-tabs__line { /deep/ .van-tabs__line {
width: 30px;
height: 3px;
border-radius: 3px;
background-color: var(--color-primary, #1989fa); background-color: var(--color-primary, #1989fa);
} }
@@ -222,14 +360,26 @@ const todo = {
.search-form { .search-form {
display: flex; display: flex;
background: #fff; padding: 8px 12px 4px;
padding: 12px 16px;
border-radius: 8px;
align-items: center; align-items: center;
background: #f5f7fb;
} }
.search-form .van-search{ .search-form .van-search{
width: 100%; width: 100%;
padding: 0;
border-radius: 8px;
overflow: hidden;
background: #fff;
}
/deep/ .search-form .van-search__content {
border-radius: 8px;
background: #fff;
}
/deep/ .van-sticky--fixed {
background: #f5f7fb;
} }
.search-input { .search-input {
@@ -289,5 +439,96 @@ const todo = {
.category-select { .category-select {
margin: 0 12px 12px 0; margin: 0 12px 12px 0;
} }
/deep/ .table-list-container {
margin-top: 8px;
padding: 0 12px;
}
/deep/ .table-list-container .table-list-item {
margin-bottom: 10px;
padding: 12px;
border: 0;
border-radius: 9px;
box-shadow: 0 2px 10px rgba(31, 68, 113, 0.07);
}
/deep/ .todo-item-header {
display: flex;
min-width: 0;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
/deep/ .todo-item-title {
display: flex;
flex: 1;
min-width: 0;
align-items: center;
}
/deep/ .todo-title-icon {
display: flex;
flex: 0 0 24px;
width: 24px;
height: 24px;
margin-right: 8px;
align-items: center;
justify-content: center;
border-radius: 6px;
color: #438df5;
font-size: 15px;
background: #edf5ff;
}
/deep/ .todo-title-text {
overflow: hidden;
color: #26364d;
font-size: 14px;
font-weight: 600;
line-height: 22px;
text-overflow: ellipsis;
white-space: nowrap;
}
/deep/ .todo-status {
flex: 0 0 auto;
margin-left: 8px;
padding: 3px 7px;
border-radius: 4px;
color: #4b92f2;
font-size: 10px;
line-height: 14px;
background: #edf5ff;
}
/deep/ .table-column {
padding: 3px 0;
}
/deep/ .table-column .label,
/deep/ .table-column .value {
color: #637083;
font-size: 11px;
line-height: 17px;
}
/deep/ .table-list-container .table-list-item .item-actions {
margin-top: 8px;
padding-top: 7px;
border-top-color: #edf1f6;
}
/deep/ .table-list-container .table-list-item .action-btn {
padding: 0;
color: #2783ef;
font-size: 11px;
}
/deep/ .table-list-container .table-list-item .action-btn i {
margin-right: 4px;
font-size: 12px;
}
` `
} }
@@ -3,39 +3,301 @@ layout("/layouts/platform_h5.html"){
#--> #-->
<style> <style>
.bgcolor-white { body {
background: #ffffff; background: #f3f6fa;
} }
.van-status__figure {
text-align: center; .signature-page {
line-height: 1.5; min-height: 100vh;
background: #f3f6fa;
color: #15243a;
} }
.van-status__figure img {
width: 130px; .signature-hero {
position: relative;
height: 154px;
box-sizing: border-box;
padding: 0 26px;
background: linear-gradient(90deg, #0878f9 0%, #35aff6 100%);
}
.signature-nav,
.signature-nav .van-nav-bar {
background: linear-gradient(90deg, #0878f9 0%, #35aff6 100%) !important;
}
.signature-nav .van-nav-bar__title,
.signature-nav .van-icon {
color: #fff;
}
.signature-user {
display: flex;
align-items: center;
padding-top: 36px;
}
.signature-avatar-wrap {
position: relative;
flex: 0 0 58px;
width: 58px;
height: 58px;
margin-right: 12px;
}
.signature-avatar {
overflow: hidden;
box-sizing: border-box;
border: 2px solid rgba(255, 255, 255, 0.96);
background: #fff;
}
.signature-online {
position: absolute;
right: -1px;
bottom: 2px;
width: 11px;
height: 11px;
border: 2px solid #fff;
border-radius: 50%;
background: #3dd0a0;
}
.signature-user-text {
display: flex;
min-width: 0;
flex-direction: column;
color: #fff;
font-size: 14px;
line-height: 1.45;
}
.signature-user-text strong {
overflow: hidden;
max-width: 240px;
margin-bottom: 1px;
font-size: 20px;
font-weight: 600;
text-overflow: ellipsis;
white-space: nowrap;
}
.signature-body {
position: relative;
min-height: calc(100vh - 184px);
box-sizing: border-box;
margin-top: -16px;
padding: 24px 16px 40px;
border-radius: 18px 18px 0 0;
background: #f3f6fa;
}
.signature-card {
margin-bottom: 18px;
padding: 17px 16px 19px;
border-radius: 16px;
background: #fff;
box-shadow: 0 8px 22px rgba(31, 72, 113, 0.06);
}
.signature-card-heading {
display: flex;
height: 32px;
align-items: flex-start;
justify-content: space-between;
border-bottom: 1px solid #e3ebf5;
}
.signature-card-heading h2 {
margin: 0;
color: #18263a;
font-size: 17px;
font-weight: 600;
line-height: 24px;
}
.signature-state-tag {
padding: 3px 10px;
border-radius: 14px;
background: #eaf8f3;
color: #30bc8c;
font-size: 12px;
font-weight: 600;
line-height: 18px;
}
.signature-state-tag.unset {
background: #eef3f9;
color: #8292a8;
}
.signature-description {
margin: 20px 0 12px;
color: #718198;
font-size: 14px;
line-height: 20px;
}
.signature-preview {
display: flex;
height: 130px; height: 130px;
align-items: center;
justify-content: center;
overflow: hidden;
box-sizing: border-box;
border: 1px solid #bdcbe0;
border-radius: 10px;
background: #f7f9fc;
}
.signature-preview .van-image,
.signature-preview .van-image__img {
width: 100%;
height: 100%;
object-fit: contain;
}
.signature-empty {
color: #95a4b8;
font-size: 14px;
}
.signature-meta {
margin-top: 20px;
color: #718198;
font-size: 13px;
line-height: 22px;
}
.signature-health {
display: flex;
align-items: center;
}
.signature-health .van-icon {
margin-right: 8px;
color: #42ca99;
font-size: 16px;
}
.signature-action {
height: 42px;
margin-top: 15px;
border: 0;
background: #087cf4;
font-size: 15px;
font-weight: 600;
}
.signature-instructions {
padding-bottom: 20px;
}
.signature-steps {
margin: 17px 0 0;
padding: 0;
list-style: none;
}
.signature-steps li {
display: flex;
align-items: center;
margin-bottom: 14px;
color: #718198;
font-size: 14px;
line-height: 20px;
}
.signature-steps li:last-child {
margin-bottom: 0;
}
.signature-step-number {
display: inline-flex;
width: 20px;
height: 20px;
flex: 0 0 20px;
align-items: center;
justify-content: center;
margin-right: 10px;
border-radius: 50%;
background: #e7f1ff;
color: #087cf4;
font-size: 12px;
font-weight: 600;
}
.signature-editor-card {
overflow: hidden;
border-radius: 16px;
background: #fff;
box-shadow: 0 8px 22px rgba(31, 72, 113, 0.06);
} }
</style> </style>
<div id="app" v-cloak> <div id="app" v-cloak>
<van-nav-bar title="我的签名" @click-left="historyBack" left-arrow left-text="返回" placeholder fixed></van-nav-bar> <div class="signature-page">
<van-nav-bar title="我的签名" class="signature-nav" left-arrow @click-left="historyBack"
<div class="bgcolor-white"> placeholder fixed :border="false"></van-nav-bar>
<div v-if="!showSignature" class="p10"> <header class="signature-hero">
<van-image width="100%" height="200" :src="userSignatureUrl" v-if="userSignatureUrl" style="border: 1px dashed #000"> <div class="signature-user">
<template slot="error">获取签名失败</template> <div class="signature-avatar-wrap">
</van-image> <van-image :src="avatar" round width="58" height="58" class="signature-avatar">
<div v-else class="van-status__figure"> <van-loading slot="loading" type="spinner" size="18"></van-loading>
<img src="/assets/platform/plugins/vant-green/images/figure/fail.png" class="" /> </van-image>
<div class="text-center pt10 pb10">暂无签名</div> <i class="signature-online"></i>
</div>
<div class="signature-user-text">
<strong>{{ user.username || '-' }}</strong>
<span>工号:{{ user.loginname || '-' }}</span>
<span>性别:{{ user.sex || '-' }}</span>
</div>
</div> </div>
<!-- <van-status orient="vertical" :figure="14" title="暂无签名" v-else></van-status>--> </header>
<div class="margin-top10"> <main class="signature-body">
<van-button v-if="!userSignatureUrl" block type="primary" @click="openSignature">去签名</van-button> <template v-if="!showSignature">
<van-button v-else block type="primary" @click="openSignature">修改签名</van-button> <section class="signature-card">
<div class="signature-card-heading">
<h2>当前签名</h2>
<span class="signature-state-tag" :class="{ unset: !userSignatureUrl }">
{{ userSignatureUrl ? '已设置' : '未设置' }}
</span>
</div>
<p class="signature-description">用于工会业务审批和材料签署</p>
<div class="signature-preview">
<van-image v-if="userSignatureUrl" :src="userSignatureUrl">
<template slot="error">获取签名失败</template>
</van-image>
<span v-else class="signature-empty">暂无签名</span>
</div>
<div class="signature-meta">
<div>最近更新:{{ signatureUpdatedAt }}</div>
<div class="signature-health">
<van-icon name="checked"></van-icon>
<span>{{ userSignatureUrl ? '签名状态正常,可用于业务办理' : '请先设置签名后办理相关业务' }}</span>
</div>
</div>
<van-button round block type="primary" class="signature-action" @click="openSignature">
{{ userSignatureUrl ? '重新签名' : '立即签名' }}
</van-button>
</section>
<section class="signature-card signature-instructions">
<div class="signature-card-heading">
<h2>使用说明</h2>
</div>
<ol class="signature-steps">
<li><span class="signature-step-number">1</span><span>在签名区域内完成本人签名</span></li>
<li><span class="signature-step-number">2</span><span>确认清晰完整后保存</span></li>
<li><span class="signature-step-number">3</span><span>签名变更后自动更新业务调用</span></li>
</ol>
</section>
</template>
<div v-else class="signature-editor-card">
<signature @save="saveSignature"></signature>
</div> </div>
</div> </main>
<signature v-if="showSignature" @save="saveSignature"></signature>
</div> </div>
</div> </div>
@@ -49,14 +311,34 @@ layout("/layouts/platform_h5.html"){
data() { data() {
return { return {
showSignature: false, showSignature: false,
userSignatureUrl: null userSignatureUrl: null,
signatureUpdatedTime: null
}
},
computed: {
user() {
return this.$store.state.user || {}
},
avatar() {
if (this.user.sex === "男" || !this.user.sex) {
return "/assets/platform/img/home/avatar_boy.png"
}
return "/assets/platform/img/home/avatar_girl.png"
},
signatureUpdatedAt() {
if (!this.signatureUpdatedTime) {
return "--"
}
return this.$moment(this.signatureUpdatedTime).format("YYYY-MM-DD HH:mm")
} }
}, },
methods: { methods: {
getUserSignature() { getUserSignature() {
this.$axios.post("/platform/signature/get").then((res) => { this.$axios.post("/platform/signature/get").then((res) => {
if (res.code === 0) { if (res.code === 0) {
this.userSignatureUrl = res.data.signature const signatureData = res.data || {}
this.userSignatureUrl = signatureData.signature || null
this.signatureUpdatedTime = signatureData.updatedAt || signatureData.createdAt || null
} }
}) })
}, },
@@ -64,7 +346,6 @@ layout("/layouts/platform_h5.html"){
this.showSignature = true this.showSignature = true
}, },
saveSignature(signature) { saveSignature(signature) {
console.log(signature)
const file = base64ToFile(signature, "signature.png") const file = base64ToFile(signature, "signature.png")
const formData = new FormData() const formData = new FormData()
formData.append("file", file) formData.append("file", file)
@@ -2,9 +2,169 @@
layout("/layouts/platform_h5.html"){ layout("/layouts/platform_h5.html"){
#--> #-->
<style>
body {
background: #f3f6fa;
}
.profile-detail-page {
min-height: 100vh;
background: #f3f6fa;
color: #15243a;
}
.profile-detail-hero {
position: relative;
height: 154px;
box-sizing: border-box;
padding: 0 26px;
background: linear-gradient(90deg, #0878f9 0%, #35aff6 100%);
}
.profile-detail-nav,
.profile-detail-nav .van-nav-bar {
background: linear-gradient(90deg, #0878f9 0%, #35aff6 100%) !important;
}
.profile-detail-nav .van-nav-bar__title,
.profile-detail-nav .van-icon {
color: #fff;
}
.profile-detail-user {
display: flex;
align-items: center;
padding-top: 36px;
}
.profile-detail-avatar-wrap {
position: relative;
flex: 0 0 58px;
width: 58px;
height: 58px;
margin-right: 12px;
}
.profile-detail-avatar {
overflow: hidden;
box-sizing: border-box;
border: 2px solid rgba(255, 255, 255, 0.96);
background: #fff;
}
.profile-detail-online {
position: absolute;
right: -1px;
bottom: 2px;
width: 11px;
height: 11px;
border: 2px solid #fff;
border-radius: 50%;
background: #3dd0a0;
}
.profile-detail-user-text {
display: flex;
min-width: 0;
flex-direction: column;
color: #fff;
font-size: 14px;
line-height: 1.45;
}
.profile-detail-user-text strong {
overflow: hidden;
max-width: 240px;
margin-bottom: 1px;
font-size: 20px;
font-weight: 600;
text-overflow: ellipsis;
white-space: nowrap;
}
.profile-detail-body {
position: relative;
min-height: calc(100vh - 184px);
box-sizing: border-box;
margin-top: -16px;
padding: 24px 16px 40px;
border-radius: 18px 18px 0 0;
background: #f3f6fa;
}
.mine-profile-details {
display: flex;
flex-direction: column;
gap: 14px;
}
.mine-detail-card {
padding: 15px 16px 3px;
border-radius: 16px;
background: #fff;
box-shadow: 0 8px 22px rgba(31, 72, 113, 0.06);
}
.mine-detail-card h2 {
height: 29px;
margin: 0;
border-bottom: 1px solid #e3ebf5;
color: #18263a;
font-size: 17px;
font-weight: 600;
line-height: 24px;
}
.mine-detail-row {
display: flex;
min-height: 42px;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #e3ebf5;
font-size: 14px;
line-height: 20px;
}
.mine-detail-row:last-child {
border-bottom: 0;
}
.mine-detail-row span {
flex: 0 0 82px;
color: #718198;
}
.mine-detail-row strong {
overflow-wrap: anywhere;
color: #1c2b41;
font-weight: 400;
text-align: right;
}
</style>
<div id="app" v-cloak> <div id="app" v-cloak>
<van-nav-bar title="个人信息" @click-left="historyBack" left-arrow left-text="返回" placeholder fixed></van-nav-bar> <div class="profile-detail-page">
<member-info :user-id="userId" ref="memberInfoRef"></member-info> <van-nav-bar title="个人信息" class="profile-detail-nav" left-arrow @click-left="historyBack"
placeholder fixed :border="false"></van-nav-bar>
<header class="profile-detail-hero">
<div class="profile-detail-user">
<div class="profile-detail-avatar-wrap">
<van-image :src="avatar" round width="58" height="58" class="profile-detail-avatar">
<van-loading slot="loading" type="spinner" size="18"></van-loading>
</van-image>
<i class="profile-detail-online"></i>
</div>
<div class="profile-detail-user-text">
<strong>{{ user.username || '-' }}</strong>
<span>工号:{{ user.loginname || '-' }}</span>
<span>性别:{{ user.sex || '-' }}</span>
</div>
</div>
</header>
<main class="profile-detail-body">
<member-info :user-id="userId" :profile-mode="true" ref="memberInfoRef"></member-info>
</main>
</div>
</div> </div>
<script nonce="${cspNonce!}"> <script nonce="${cspNonce!}">
<!--#include("/platform/zhghh5/staffmanage/member/common/mobileMemberInfo.js"){}#--> <!--#include("/platform/zhghh5/staffmanage/member/common/mobileMemberInfo.js"){}#-->
@@ -16,12 +176,23 @@ layout("/layouts/platform_h5.html"){
userId: "" userId: ""
} }
}, },
computed: {
user() {
return this.$store.state.user || {}
},
avatar() {
if (this.user.sex === "男" || !this.user.sex) {
return "/assets/platform/img/home/avatar_boy.png"
}
return "/assets/platform/img/home/avatar_girl.png"
}
},
components: { components: {
"member-info": MOBILE_MEMBER_INFO "member-info": MOBILE_MEMBER_INFO
}, },
methods: { methods: {
viewInfo() { viewInfo() {
this.userId = this.$store.state.user.id this.userId = this.user.id
} }
}, },
created() { created() {