From f58a3b2cafdbd6870ff1b496d53c9792cfc47b92 Mon Sep 17 00:00:00 2001 From: zhouhefeng Date: Mon, 12 Oct 2026 20:37:51 +0800 Subject: [PATCH] commit --- .../app/sys/controller/SysHomeController.java | 11 + .../sys/services/impl/SysUserServiceImpl.java | 10 +- src/main/resources/logback.xml | 69 +- .../static/assets/platform/css/h5.css | 72 +- .../assets/platform/js/util/commonUtil.js | 2 + .../assets/platform/js/util/h5PopupHistory.js | 230 ++- .../components/plugins/h5/TableColumn.vue | 28 +- .../components/plugins/h5/TableList.vue | 145 +- .../plugins/sysFilePreview/H5Index.vue | 128 +- .../plugins/sysSignature/h5Index.vue | 178 +- .../components/plugins/sysSignature/index.vue | 11 +- .../plugins/vantMore/yearVanDropDownItem.vue | 10 +- .../resources/views/layouts/platform_h5.html | 189 +- .../layouts/platform_tour_signup_h5.html | 89 +- .../zhghh5/activity/culture/applyUser.html | 627 +++++-- .../zhghh5/activity/culture/signUp.html | 881 +++++++--- .../zhghh5/activity/family/apply/index.html | 655 ++++++- .../zhghh5/activity/family/common/times.js | 98 +- .../zhghh5/activity/family/list/applyForm.js | 265 ++- .../zhghh5/activity/family/list/index.html | 1027 ++++++++++- .../zhghh5/activity/family/mine/index.html | 679 ++++--- .../zhghh5/activity/sports/applyUser.html | 809 ++++++++- .../zhghh5/activity/sports/eventList.html | 863 ++++++++- .../activity/sports/eventNotification.js | 243 ++- .../activity/trainSignUp/apply/index.html | 747 +++++++- .../activity/trainSignUp/common/times.js | 160 +- .../activity/trainSignUp/list/applyForm.js | 209 ++- .../activity/trainSignUp/list/index.html | 1114 +++++++++++- .../activity/trainSignUp/mine/index.html | 552 +++++- .../activity/workscollection/mine/index.html | 651 ++++++- .../activity/workscollection/mine/info.js | 223 ++- .../activity/workscollection/read/index.html | 722 ++++++-- .../workscollection/upload/applyForm.js | 698 ++++++-- .../workscollection/upload/index.html | 862 ++++++++- .../platform/zhghh5/club/apply/index.html | 546 +++--- .../zhghh5/club/clubApproval/index.html | 379 ++-- .../zhghh5/club/common/clubUserJoin.js | 234 ++- .../platform/zhghh5/club/mine/index.html | 207 +-- .../platform/zhghh5/club/mineclub/index.html | 168 +- .../schoolAudit/index.html | 6 +- .../unionAudit/index.html | 6 +- .../meeting/delegationApproval/index.html | 82 +- .../meeting/schoolUnionApproval/index.html | 82 +- .../outlayReimburse/clubAudit/index.html | 6 +- .../outlayReimburse/clubZxAudit/index.html | 6 +- .../outlayReimburse/schoolCnAudit/index.html | 6 +- .../outlayReimburse/schoolKjAudit/index.html | 19 +- .../outlayReimburse/schoolZxAudit/index.html | 6 +- .../outlayReimburse/unionAudit/index.html | 6 +- .../zhghh5/dayofficework/qsv/vote/index.html | 1368 +++++++++++++- .../site/schoolUnionAudit/index.html | 82 +- .../siteCug/schoolUnionAudit/index.html | 92 +- .../dayofficework/tour/mysignup/index.html | 848 ++++++++- .../tour/schoolUnionApproval/index.html | 457 ++++- .../dayofficework/tour/signup/apply.html | 434 ++++- .../dayofficework/tour/signup/index.html | 131 +- .../dayofficework/tour/signup/lineInfo.html | 110 +- .../dayofficework/tour/signup/list.html | 388 +++- .../tour/unionApproval/index.html | 455 ++++- .../unionReimburse/apply/index.html | 580 +++--- .../certifierUserSign/index.html | 4 +- .../unionReimburse/collect/index.html | 155 +- .../dayofficework/unionReimburse/info.js | 351 ++-- .../unionReimburse/mine/index.html | 148 +- .../unionReimburse/review/index.html | 213 ++- .../zhghh5/democratic/proposal/common/info.js | 542 +++--- .../proposal/query/comprehensive/index.html | 74 +- .../proposal/transact/commissioner/index.html | 333 ++-- .../transact/committeeFiling/index.html | 64 +- .../transact/committeeFilingUnit/index.html | 74 +- .../proposal/transact/delegation/index.html | 490 ++++-- .../transact/feedbackEvaluation/index.html | 565 +++--- .../proposal/transact/mine/index.html | 776 +++++++- .../proposal/transact/mine/inviteSeconder.js | 283 +-- .../transact/schoolLeaderApproval/index.html | 261 +-- .../proposal/transact/seconded/index.html | 714 ++++++-- .../proposal/transact/suggestion/index.html | 192 +- .../proposal/transact/unitReply/index.html | 766 +++++--- .../proposal/transact/unitReply/transfer.js | 214 +-- .../transact/viceDelegation/index.html | 125 +- .../proposal/transact/write/index.html | 799 ++++++--- .../tc/transact/delegation/index.html | 8 +- .../tc/transact/seconded/index.html | 6 +- .../wc/transact/delegation/index.html | 8 +- .../wc/transact/seconded/index.html | 6 +- .../democratic/suggestionBox/xgh/index.html | 53 +- .../teachercongress/delegate/read/index.html | 636 ++++++- .../guildhall/selectSeat/index.html | 1564 +++++++++++++---- .../platform/zhghh5/inclusive/detail.html | 18 +- .../platform/zhghh5/inclusive/index.html | 28 +- .../zhghh5/learning/course/index.html | 19 +- .../staffbenefit/condolence/apply/index.html | 182 +- .../condolence/branchUnionApproval/index.html | 317 +++- .../staffbenefit/condolence/common/info.js | 160 +- .../staffbenefit/condolence/mine/index.html | 378 +++- .../schoolPrincipalApproval/index.html | 317 +++- .../condolence/schoolUnionApproval/index.html | 317 +++- .../difficulthelp/apply/index.html | 749 ++++++-- .../branchunionapproval/index.html | 353 ++-- .../staffbenefit/difficulthelp/common/info.js | 436 +++-- .../difficulthelp/mine/index.html | 461 ++++- .../schoolunionapproval/index.html | 456 +++-- .../unionLeaderApproval/index.html | 430 +++-- .../dsznfmtx/schoolAudit/index.html | 6 +- .../dsznfmtx/unionAudit/index.html | 6 +- .../maternityLeave/schoolAudit/index.html | 6 +- .../maternityLeave/unionAudit/index.html | 6 +- .../aidFund/foundationAudit/index.html | 107 +- .../aidFund/retirementAudit/index.html | 53 +- .../aidFund/unionAudit/index.html | 53 +- .../zhghh5/staffmanage/birthday/index.html | 2 +- .../apply/branchunionapproval/index.html | 429 ++++- .../staffmanage/member/apply/common/info.js | 102 +- .../staffmanage/member/apply/mine/index.html | 491 +++++- .../apply/schoolunionapproval/index.html | 427 ++++- .../member/apply/submit/index.html | 428 +++-- .../apply/uniongroupapproval/index.html | 66 +- .../member/common/mobileMemberInfo.js | 70 +- .../staffmanage/member/info/board/index.html | 820 ++++----- .../staffmanage/member/info/group/index.html | 480 ++++- .../views/platform/zhghh5/sys/home/apps.js | 691 ++++++-- .../views/platform/zhghh5/sys/home/home.js | 1414 ++++++++++----- .../views/platform/zhghh5/sys/home/index.html | 98 +- .../views/platform/zhghh5/sys/home/mine.js | 632 ++++--- .../views/platform/zhghh5/sys/home/msg.js | 1120 +++++++----- .../views/platform/zhghh5/sys/home/todo.js | 565 +++++- .../platform/zhghh5/sys/signature/index.html | 333 +++- .../platform/zhghh5/sys/userInfo/index.html | 177 +- .../platform/zhghh5/welfare/address/list.html | 156 +- .../platform/zhghh5/welfare/mine/list.html | 909 ++++++++-- .../zhghh5/welfare/mine/selectView.js | 356 +++- .../platform/zhghh5/welfare/select/index.html | 1410 +++++++++++---- .../platform/zhghh5/welfare/select/list.html | 639 ++++++- .../zhghh5/welfare/select/success.html | 5 + .../zhghh5/welfare/userChoose/index.html | 227 +-- .../zhghh5/welfare/userChoose/list.html | 13 +- 136 files changed, 35450 insertions(+), 10853 deletions(-) diff --git a/src/main/java/com/budwk/app/sys/controller/SysHomeController.java b/src/main/java/com/budwk/app/sys/controller/SysHomeController.java index 65800bb..1e8a23b 100644 --- a/src/main/java/com/budwk/app/sys/controller/SysHomeController.java +++ b/src/main/java/com/budwk/app/sys/controller/SysHomeController.java @@ -128,6 +128,17 @@ public class SysHomeController { public void index() { } + /** + * 兼容旧手机端首页地址,无需请求参数。 + * 未登录时沿用现有登录拦截;登录后重定向到标准 H5 首页,使底部导航按首页地址正常显示。 + * 方法返回 void,无响应数据字段,由重定向视图跳转到 /platform/h5/home。 + */ + @At(value = "/mobile/index", top = true) + @Ok(">>:/platform/h5/home") + @SaCheckLogin + public void mobileIndex() { + } + @At(value = "/platform/h5", top = true) @Ok("re") @SaCheckLogin diff --git a/src/main/java/com/budwk/app/sys/services/impl/SysUserServiceImpl.java b/src/main/java/com/budwk/app/sys/services/impl/SysUserServiceImpl.java index 4d031c9..829f0cf 100644 --- a/src/main/java/com/budwk/app/sys/services/impl/SysUserServiceImpl.java +++ b/src/main/java/com/budwk/app/sys/services/impl/SysUserServiceImpl.java @@ -69,7 +69,7 @@ public class SysUserServiceImpl extends BaseServiceImpl implements Sys * sys_user_role 仅用于确认用户具有目标角色,组织范围统一取 sys_user.unitId, * 避免依赖未维护的 sys_user_role.unitId 字段导致流程无人可分派。 * - * @param unitId 单位 ID + * @param unitId 单位 ID * @param roleCode 角色编码 * @return 对应单位下有效且具有目标角色的用户 ID */ @@ -104,7 +104,7 @@ public class SysUserServiceImpl extends BaseServiceImpl implements Sys * 角色用户通过 sys_user.unitId 关联 sys_unit,再以 sys_unit.unionId 确定 * 二级党组织范围,不读取未维护的 sys_user_role.unionId 字段。 * - * @param unionId 分工会 ID + * @param unionId 分工会 ID * @param roleCode 角色编码 * @return 对应二级党组织下有效且具有目标角色的用户 ID */ @@ -381,9 +381,9 @@ public class SysUserServiceImpl extends BaseServiceImpl implements Sys String decodePwd = Base64Decoder.decodeStr(passowrd); String hashedPassword = PwdUtil.getPassword(decodePwd, user.getSalt()); if (!Strings.sNull(hashedPassword).equalsIgnoreCase(user.getPassword())) { - // if (Globals.sso) { - throw new BaseException("用户名或者密码不正确"); - // } + if (Globals.sso) { + throw new BaseException("用户名或者密码不正确"); + } } user = this.fetchLinks(user, "unit"); if (Lang.isNotEmpty(user.getUnit()) && StrUtil.isNotBlank(user.getUnit().getUnionId())) { diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 4a00be9..53662f2 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -1,59 +1,60 @@ - + - + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - ${LOG_HOME}/info.log + + + + + + ${LOG_HOME}/app.log - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - INFO - ACCEPT - DENY - - ${LOG_HOME}/info-%d{yyyy-MM-dd}.log - 30 + ${LOG_HOME}/app-%d{yyyy-MM-dd}.log + 300 - - - ${LOG_HOME}/error.log - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - ERROR - ACCEPT - DENY - - - ${LOG_HOME}/error-%d{yyyy-MM-dd}.log - 30 - - - - + - + - - + + + + + + + + + + diff --git a/src/main/resources/static/assets/platform/css/h5.css b/src/main/resources/static/assets/platform/css/h5.css index f129def..dcda4d0 100644 --- a/src/main/resources/static/assets/platform/css/h5.css +++ b/src/main/resources/static/assets/platform/css/h5.css @@ -124,11 +124,71 @@ body { } .custom-list-loading { - background-color: unset; + min-width: 132px; + padding: 15px 12px 12px; + border-radius: 14px; + color: #666; + background-color: rgba(255, 255, 255, 0.98); + box-shadow: 0 6px 22px rgba(128, 72, 72, 0.08); } .custom-list-loading .van-icon { - font-size: 108px; + position: relative; + width: 88px; + height: 88px; + overflow: visible; + font-size: 88px; +} + +.custom-list-loading .van-icon::before { + position: absolute; + inset: 6px; + border: 1px solid rgba(255, 112, 103, 0.24); + border-radius: 50%; + content: ""; +} + +.custom-list-loading .van-icon::after { + position: absolute; + top: 2px; + left: 41px; + width: 6px; + height: 6px; + border-radius: 50%; + background: #ff8c83; + box-shadow: 33px 37px 0 #ff6f68, -35px 21px 0 #ffb0a9; + content: ""; + transform-origin: 3px 42px; + animation: smart-union-loading-orbit 1.8s linear infinite; +} + +.custom-list-loading .van-icon__image { + position: absolute; + top: 50%; + left: 50%; + box-sizing: border-box; + width: 54px; + height: 54px; + padding: 5px; + border-radius: 50%; + background: rgba(255, 238, 236, 0.92); + object-fit: contain; + transform: translate(-50%, -50%); +} + +@keyframes smart-union-loading-orbit { + to { + transform: rotate(360deg); + } +} + +.custom-list-loading .van-toast__text { + margin-top: 4px; + color: #707070; + font-size: 11px; + font-weight: 500; + line-height: 18px; + white-space: pre-line; } .list-card { @@ -308,3 +368,11 @@ body { text-align: left; } + +/* 西财独有的幸福工荟与立案分派页面沿用迁移后的卡片间距和手机安全区。 */ +.swufe-mobile-page { min-height:100vh; background:#f3f7fd; padding-bottom:calc(20px + env(safe-area-inset-bottom)); } +.swufe-mobile-page .van-search { margin:10px 12px; padding:0; border-radius:12px; overflow:hidden; } +.swufe-mobile-page .van-search__content { background:#fff; } +.swufe-mobile-page .van-nav-bar .van-icon, .swufe-mobile-page .van-nav-bar__text { color:#1989fa; } +.swufe-mobile-page .van-cell-group { border-radius:12px; overflow:hidden; } +.swufe-mobile-page img { max-width:100%; } diff --git a/src/main/resources/static/assets/platform/js/util/commonUtil.js b/src/main/resources/static/assets/platform/js/util/commonUtil.js index 02b9bf4..81a20ef 100644 --- a/src/main/resources/static/assets/platform/js/util/commonUtil.js +++ b/src/main/resources/static/assets/platform/js/util/commonUtil.js @@ -8,6 +8,8 @@ const commonUtil = { "x-requested-with": "XMLHttpRequest" } }) + // H5 布局提供加载状态适配;PC 布局未提供此方法时不改变其请求行为。 + if (window.bindH5AxiosLoading) window.bindH5AxiosLoading(axiosService) //axios拦截器 axiosService.interceptors.response.use( (response) => { diff --git a/src/main/resources/static/assets/platform/js/util/h5PopupHistory.js b/src/main/resources/static/assets/platform/js/util/h5PopupHistory.js index 674cd82..2a0e9d1 100644 --- a/src/main/resources/static/assets/platform/js/util/h5PopupHistory.js +++ b/src/main/resources/static/assets/platform/js/util/h5PopupHistory.js @@ -1,79 +1,197 @@ /** - * H5 弹框浏览器历史栈管理器。 - * 页面只注册关闭回调;本文件全局统一监听 popstate,避免每个页面重复绑定返回事件。 + * H5 弹框统一历史栈。两种页面 API 和 Vant 弹框适配共用唯一的 popstate 监听。 + * 历史状态保留 PJAX 页面信息;关闭弹框只消费本页增加的记录,不触发业务页面返回。 */ ;((global) => { + if (global.h5PopupHistory && global.h5PopupHistory.unified) return const callbacks = new Map() - const popupStack = [] - let tokenSeed = 0 - let stateSeed = 0 - let pendingClear = null - - const invokeClose = (entry) => { + const entries = [] + const subscribers = new Set() + const pageInstances = new Set() + const sessionId = Date.now().toString(36) + let sequence = 0 + let pending = null + let notifying = false + let pageKey = "" + let pageCallback = null + const names = () => entries.map((entry) => entry.popupKey) + const notify = () => { + notifying = true + try { + if (pageCallback) pageCallback(names()) + subscribers.forEach((callback) => callback(names())) + } finally { notifying = false } + } + /** entry 为栈记录;同步业务回调与 Vant input 事件,不再创建或回退历史。 */ + const dismiss = (entry) => { const callback = callbacks.get(entry.token) if (callback) callback(entry.popupKey) + if (entry.component && !entry.component._isDestroyed) { + const component = entry.component + if ((component.$options.name || "").replace(/-/g, "").toLowerCase() === "vandialog") { + // 返回键等同取消确认,结束 Dialog.confirm 的 Promise,避免后续操作一直等待。 + component.$emit("cancel") + component.onClose("cancel") + } else component.$emit("input", false) + } + } + /** token 为注册标识,popupKey 为弹框名称;返回栈记录,重复打开同一弹框时复用记录。 */ + const open = (token, popupKey, component = null) => { + const existing = entries.find((entry) => entry.token === token && entry.popupKey === popupKey) + if (existing) return existing + if (pending) { + pending.promise.then(() => { + if (!component || (!component._isDestroyed && component.value)) open(token, popupKey, component) + }) + return null + } + const entry = {id: ++sequence, token: token, popupKey: popupKey, component: component, claimable: !component} + entries.push(entry) + global.history.pushState(Object.assign({}, global.history.state || {}, { + __h5PopupHistory: {session: sessionId, ids: entries.map((item) => item.id)} + }), global.document.title, global.location.href) + notify() + // 命名弹层触发本轮 Vue 渲染后不再允许其他弹框占用其记录。 + if (global.Vue) global.Vue.nextTick(() => { entry.claimable = false }) + return entry + } + /** index 为需要关闭的最外层弹框位置;Promise 在浏览器完成回退后才结束。 */ + const closeFrom = (index) => { + if (index < 0 || index >= entries.length) return Promise.resolve() + if (pending) { + // 重复关闭只追踪本次请求时的弹框,避免异步回退结束后误关新打开的弹框。 + const closingIds = entries.slice(index).map((entry) => entry.id) + return pending.promise.then(() => closeFrom(entries.findIndex((entry) => closingIds.includes(entry.id)))) + } + let resolveClose + const promise = new Promise((resolve) => { resolveClose = resolve }) + pending = {promise: promise, resolve: resolveClose} + global.history.go(-(entries.length - index)) + return promise } - global.addEventListener("popstate", (event) => { - if (pendingClear) { - event.stopImmediatePropagation() - const clear = pendingClear - pendingClear = null - clear.resolve() + const marker = event.state && event.state.__h5PopupHistory + if (!entries.length && !pending) { + // 已关闭的弹框不因浏览器前进而重新显示,也不把旧弹框记录交给 PJAX。 + if (marker && marker.session === sessionId) event.stopImmediatePropagation() return } - if (!popupStack.length) return - - // 有弹框历史时消费本次返回事件,阻止 PJAX 将页面直接切回上一个业务页面。 event.stopImmediatePropagation() - const entry = popupStack.pop() - invokeClose(entry) + const targetIds = marker && marker.session === sessionId ? marker.ids : [] + const removed = [] + while (entries.length && !targetIds.includes(entries[entries.length - 1].id)) removed.push(entries.pop()) + notifying = true + try { removed.forEach(dismiss) } finally { notifying = false } + notify() + const finished = pending + pending = null + if (finished) finished.resolve() }, true) - global.h5PopupHistory = { + unified: true, register(closeCallback) { - tokenSeed += 1 - const token = "h5-popup-page-" + tokenSeed + const token = "h5-popup-" + (++sequence) callbacks.set(token, closeCallback) return token }, - unregister(token) { - callbacks.delete(token) - for (let index = popupStack.length - 1; index >= 0; index -= 1) { - if (popupStack[index].token === token) popupStack.splice(index, 1) - } - }, - open(token, popupKey) { - if (!callbacks.has(token)) return - const current = popupStack[popupStack.length - 1] - if (current && current.token === token && current.popupKey === popupKey) return - - stateSeed += 1 - const entry = {token:token,popupKey:popupKey,stateId:stateSeed} - popupStack.push(entry) - const state = Object.assign({}, global.history.state || {}, { - __h5PopupHistory: {token:token,popupKey:popupKey,stateId:stateSeed} - }) - global.history.pushState(state, global.document.title, global.location.href) - }, + unregister(token) { this.clear(token).then(() => callbacks.delete(token)) }, + open(token, popupKey) { if (callbacks.has(token)) open(token, popupKey) }, close(token, popupKey) { - const current = popupStack[popupStack.length - 1] - if (!current || current.token !== token || current.popupKey !== popupKey) return false - global.history.back() + if (notifying) return false + const index = entries.findIndex((entry) => entry.token === token && entry.popupKey === popupKey) + if (index < 0) return false + closeFrom(index) return true }, - clear(token) { - const entries = popupStack.filter((entry) => entry.token === token) - if (!entries.length) return Promise.resolve() - - entries.forEach((entry) => invokeClose(entry)) - for (let index = popupStack.length - 1; index >= 0; index -= 1) { - if (popupStack[index].token === token) popupStack.splice(index, 1) - } - return new Promise((resolve) => { - pendingClear = {resolve:resolve} - global.history.go(-entries.length) - }) + clear(token) { return closeFrom(entries.findIndex((entry) => entry.token === token)) }, + clearAll() { return closeFrom(0) }, + hasOpen() { return entries.length > 0 || !!pending }, + // PJAX 替换 DOM 前销毁旧页面实例,使业务组件的 beforeDestroy 能正常注销监听。 + destroyPage() { + pageInstances.forEach((instance) => { if (!instance._isDestroyed) instance.$destroy() }) + pageInstances.clear() } } + // 命名弹层 API 适配到同一历史栈,不另行监听 popstate。 + global.h5HistoryLayerManager = { + get stack() { return names() }, + get pageKey() { return pageKey }, + register(key, onChange) { + pageKey = key + pageCallback = typeof onChange === "function" ? onChange : null + notify() + }, + ensureRegistered(key) { if (!pageKey) pageKey = key; return true }, + subscribe(callback) { + subscribers.add(callback) + callback(names()) + return () => subscribers.delete(callback) + }, + unregister(key) { if (!key || key === pageKey) this.reset() }, + open(name) { if (!name) return false; open("named-layer", name); return true }, + close(name, afterClose = null) { + if (notifying) return false + const index = name ? entries.findIndex((entry) => entry.popupKey === name) : entries.length - 1 + if (index < 0) { if (afterClose) afterClose(); return false } + closeFrom(index).then(() => { if (afterClose) afterClose() }) + return true + }, + closeAll(afterClose = null) { + const hadEntries = entries.length > 0 + closeFrom(0).then(() => { if (afterClose) afterClose() }) + return hadEntries + }, + /** url 为业务页面地址,data 为可选 PJAX 参数;返回 true 表示已接管导航。 */ + closeAllAndNavigate(url, data = null) { + if (!url) return false + closeFrom(0).then(() => global.pjaxReplace(url, data)) + return true + }, + reset() { + pageCallback = null + pageKey = "" + subscribers.clear() + return closeFrom(0) + } + } + /** + * Vant Popup/Dialog 统一适配已有 v-model 页面,包括 ActionSheet 内部的 Popup。 + * 已登记弹层复用记录;未登记弹层自动注册,组件销毁时注销 watcher 和回调。 + */ + if (global.Vue) global.Vue.mixin({ + beforeCreate() { + const props = this.$options.props || {} + if (props.closeOnPopstate) { + // 禁用 Vant 的组件级返回监听,由统一栈按后进先出顺序处理。 + this.$options.propsData = Object.assign({}, this.$options.propsData, {closeOnPopstate: false}) + } + }, + mounted() { + if (this.$el && this.$el.id === "app" && this.$root === this) pageInstances.add(this) + const name = (this.$options.name || "").toLowerCase().replace(/-/g, "") + if (name !== "vanpopup" && name !== "vandialog") return + this._h5PopupToken = global.h5PopupHistory.register(() => this.$emit("input", false)) + const sync = (visible) => { + if (notifying) return + if (visible) { + const top = entries[entries.length - 1] + if (top && !top.component && top.claimable) top.component = this + else open(this._h5PopupToken, "vant-popup-" + this._uid, this) + } else { + const index = entries.findIndex((entry) => entry.component === this) + if (index >= 0) closeFrom(index) + } + } + this._h5PopupUnwatch = this.$watch("value", sync, {immediate: true}) + }, + beforeDestroy() { + pageInstances.delete(this) + if (this._h5PopupUnwatch) this._h5PopupUnwatch() + if (this._h5PopupToken) { + const index = entries.findIndex((entry) => entry.component === this) + if (index >= 0) closeFrom(index) + global.h5PopupHistory.unregister(this._h5PopupToken) + } + } + }) })(window) diff --git a/src/main/resources/static/components/plugins/h5/TableColumn.vue b/src/main/resources/static/components/plugins/h5/TableColumn.vue index d486646..3fff8e3 100644 --- a/src/main/resources/static/components/plugins/h5/TableColumn.vue +++ b/src/main/resources/static/components/plugins/h5/TableColumn.vue @@ -37,26 +37,32 @@ module.exports = { diff --git a/src/main/resources/static/components/plugins/h5/TableList.vue b/src/main/resources/static/components/plugins/h5/TableList.vue index 1de0240..bff7961 100644 --- a/src/main/resources/static/components/plugins/h5/TableList.vue +++ b/src/main/resources/static/components/plugins/h5/TableList.vue @@ -1,7 +1,9 @@ @@ -39,7 +37,7 @@ const applyForm = {
- 提交 + 提交
@@ -51,6 +49,7 @@ const applyForm = { activity: {}, dynamicColumnsData: [], visible: false, + formLoading: false, formData: {}, showCoursePicker: false, courseTimeSelectList: [], @@ -60,13 +59,16 @@ const applyForm = { "train-dynamic-form": httpVueLoader("/components/module/trainSignUp/TrainDynamicForm.vue") }, methods: { - async onOpen(row, courseType) { - this.row = row - this.init(row, courseType) - if(row.courseIsLimitApply) { - await this.getCourseTimeSelectList(row) - } - this.visible = true + onOpen(row, courseType) { + this.row = row; + this.init(row, courseType); + if (row.courseIsLimitApply) { + return this.getCourseTimeSelectList(row).then(() => { + this.visible = true; + }); + } else { + this.visible = true; + } }, init(row, courseType) { this.$set(this.formData, 'username', this.$store.state.user.username) @@ -88,72 +90,157 @@ const applyForm = { this.$set(this.formData, "courseTimeName", val.text.substring(0, 12)) this.showCoursePicker = false }, - async getCourseTimeSelectList(o) { - const resp = await this.$axios.post('/platform/trainSignUp/apply/getCourseTimeSelectList',{courseId: o.id}) + getCourseTimeSelectList(o) { + return this.$axios.post('/platform/trainSignUp/apply/getCourseTimeSelectList', { + courseId: o.id + }).then(resp => { if (resp.code === 0) { - this.courseTimeSelectList = resp.data + this.courseTimeSelectList = resp.data; } else { - this.$toast.fail("获取时段信息失败,请联系管理员") + this.$toast.fail("获取时段信息失败,请联系管理员"); } + }); }, - async validateSignUp() { - // 验证自定义表单 - await this.$refs.dynamicForm.$refs.form.validate() + validateSignUp() { + return this.$refs.dynamicForm.$refs.form.validate().then(() => { // 获取家属人数 let familyCount = this.dynamicColumnsData.filter(o => o.columnCode === 'xdqsrs').reduce((sum, item) => { - return sum + (Number(item.columnValue) || 0) - }, 0) - - const res = await this.$axios.post("/platform/trainSignUp/apply/validateSignUp", { - courseId: this.formData.courseId, - currentFamilyNumber: familyCount - }) - return res.code === 0 + return sum + (Number(item.columnValue) || 0); + }, 0); + return this.$axios.post("/platform/trainSignUp/apply/validateSignUp", { + courseId: this.formData.courseId, + currentFamilyNumber: familyCount + }).then(res => { + return res.code === 0; + }); + }); }, - async validateCourseTime() { - const res = await this.$axios.post('/platform/trainSignUp/apply/validateSourceSignUp', { - activityCourseId: this.formData.activityCourseId, - courseId: this.row.id - }) - return res.code === 0 + validateCourseTime() { + return this.$axios.post('/platform/trainSignUp/apply/validateSourceSignUp', { + activityCourseId: this.formData.activityCourseId, + courseId: this.row.id + }).then(res => { + return res.code === 0; + }); }, - async onSubmit() { - if(!await this.validateSignUp()) return + onSubmit() { + // 保留原有报名资格、动态表单和限额时段校验;确认后才发送报名数据。 + if (this.formLoading) return - this.$refs.formRef.validate().then(() => { - this.$dialog.confirm({ - title: "提示", - message: "您确定要提交吗?" - }).then(async () => { - if (this.row.courseIsLimitApply) { - if(!await this.validateCourseTime()) return - } - - const mobileColumnsValue = this.dynamicColumnsData.map((v) => { - return { - columnName: v.columnName, - columnValue: v.columnValue, - columnCode: v.columnCode, - columnFormType: v.columnFormType - } - }) - this.formData.mobileColumnsValue = JSON.stringify(mobileColumnsValue) - this.$axios.post("/platform/trainSignUp/apply/doSignUp", this.formData).then(res => { + return this.validateSignUp().then((valid) => { + if (!valid) return + return this.$refs.formRef.validate().then(() => { + return this.$dialog.confirm({title: "提示", message: "您确定要提交吗?"}) + }).then(() => { + return this.row.courseIsLimitApply ? this.validateCourseTime() : true + }).then((validTime) => { + if (!validTime) return + this.$set(this, "formLoading", true) + const formData = clone(this.formData) + formData.mobileColumnsValue = JSON.stringify(this.dynamicColumnsData.map((item) => ({ + columnName: item.columnName, columnValue: item.columnValue, + columnCode: item.columnCode, columnFormType: item.columnFormType + }))) + return this.$axios.post("/platform/trainSignUp/apply/doSignUp", formData).then((res) => { if (res.code === 0) { this.$toast(res.msg) - this.visible = false - this.$emit('refresh') + this.$set(this, "visible", false) + this.$emit("refresh") } + }).finally(() => { + this.$set(this, "formLoading", false) }) + }).catch(() => { + // 取消确认或校验未通过时保留表单,网络错误由公共请求拦截器提示。 }) }) }, }, style: /*language=CSS*/ ` - /deep/ .button { - position: fixed; + .brand-course-form-sheet { + overflow: hidden; + border-radius: 16px 16px 0 0; + background-color: #f3f7fd; + } + + .brand-course-form-sheet /deep/ .van-action-sheet__header { + height: 48px; + color: #1f2f43; + background-color: #fff; + font-size: 16px; + line-height: 48px; + } + + .brand-course-form-sheet /deep/ .van-action-sheet__close { + top: 13px; + color: #b5bdc9; + font-size: 20px; + } + + .brand-course-form-sheet /deep/ .form-container { + height: calc(100% - 48px); + padding: 10px 12px 72px; + overflow-y: auto; + background-color: #f3f7fd; + box-sizing: border-box; + } + + .brand-course-form-sheet /deep/ .form-section { + margin-bottom: 10px; + overflow: hidden; + border-radius: 10px; + background-color: #fff; + box-shadow: 0 6px 18px rgba(55, 85, 126, .07); + } + + .brand-course-form-sheet /deep/ .van-cell-group__title { + padding: 12px 14px 8px; + color: #263548; + background-color: #fff; + font-size: 14px; + line-height: 20px; + font-weight: 600; + } + + .brand-course-form-sheet /deep/ .van-cell-group__title::before { + width: 3px; + top: 12px; + bottom: 8px; + border-radius: 2px; + background-color: #1989fa; + } + + .brand-course-form-sheet /deep/ .van-cell { + padding: 11px 14px; + color: #263548; + font-size: 13px; + } + + .brand-course-form-sheet /deep/ .van-field__label { + color: #66758a; + font-size: 13px; + } + + .brand-course-form-sheet /deep/ .button { + position: absolute; + right: 0; bottom: 0; - width: 100%; + left: 0; + z-index: 2; + padding: 10px 12px calc(10px + env(safe-area-inset-bottom)); + border-top: 1px solid #edf1f6; + background-color: rgba(255, 255, 255, .96); + box-sizing: border-box; + } + + .brand-course-form-sheet /deep/ .button .van-button { + height: 42px; + border: 0; + border-radius: 21px; + color: #fff; + background-color: #1989fa; + font-size: 15px; + box-shadow: 0 6px 16px rgba(25, 137, 250, .22); } ` } diff --git a/src/main/resources/views/platform/zhghh5/activity/trainSignUp/list/index.html b/src/main/resources/views/platform/zhghh5/activity/trainSignUp/list/index.html index 8e3d5ae..ff98323 100644 --- a/src/main/resources/views/platform/zhghh5/activity/trainSignUp/list/index.html +++ b/src/main/resources/views/platform/zhghh5/activity/trainSignUp/list/index.html @@ -2,82 +2,995 @@ layout("/layouts/platform_h5.html"){ #--> - -
+
+
+
+
+ +
+
+
品牌活动项目
+
选择适合的课程,参与品牌活动
+
+
+ - - - +
+ +
+ + + + +
+
+ +
共 {{ pageForm.totalCount }} 项
+
+
- - - - - +
+ +
+
- - + +
+ +
- +
diff --git a/src/main/resources/views/platform/zhghh5/activity/workscollection/mine/index.html b/src/main/resources/views/platform/zhghh5/activity/workscollection/mine/index.html index e79c038..65afa20 100644 --- a/src/main/resources/views/platform/zhghh5/activity/workscollection/mine/index.html +++ b/src/main/resources/views/platform/zhghh5/activity/workscollection/mine/index.html @@ -1,47 +1,474 @@ -
- + + +
+
+ + +
+
+ 我的作品插画 +
+
我的作品
+
作品征集 · 提交记录
+
+
+ + +
+ + + + + +
+
+ +
+
+ +
+ +
+
+ +
+
+
+ + +
+
+ +
+ + {{hasCover(row) ? '图片加载失败' : '暂无图片'}} +
+
+
{{row.name}}
+
所属活动:{{row.activityName || '暂无活动信息'}}
+
+ 提交时间:{{$moment(row.createdAt).format('YYYY-MM-DD HH:mm')}} +
+
+ + 主题{{row.subjectName}} + + + 类型{{row.worksName}} + +
+
+
+ + + +
+
+
+
+
+ 作品记录空状态插画 +
暂无作品记录
+
当前筛选条件下暂时没有已提交作品
+
+
+
+
+ + + + -
@@ -51,22 +478,59 @@ layout("/layouts/platform_h5.html"){ new Vue({ el: "#app", store, + components: { + 'apply-form': applyForm, + 'info': INFO, + }, data() { return { + tableData: [], + loading: false, + skeletonLoading: true, + finished: false, + refreshing: false, + imageLoadErrors: {}, + filterOpened: false, + deleteConfirmVisible: false, + deleteLoading: false, + pendingDeleteRow: {}, pageForm: { pageNumber: 1, pageSize: 10, totalCount: 0, searchKeyword: '', year: new Date().getFullYear(), + activityType: 1, }, + typeOptions: [ + {text: '全部', value: 1}, + {text: '征集中', value: 2}, + {text: '已结束', value: 3}, + ], + historyLayerPageKey: 'works-collection-mine-h5', + historyLayerUnsubscribe: null, } }, - components: { - 'apply-form': applyForm, - 'info': INFO, - }, methods: { + hasCover(row) { + return !!(row && row.cover && String(row.cover).trim()) + }, + handleImageError(row) { + this.$set(this.imageLoadErrors, row.id, true) + }, + onFilterOpen() { + this.$set(this, 'filterOpened', true) + }, + onFilterClose() { + this.$set(this, 'filterOpened', false) + }, + handlePageBack() { + if (window.h5HistoryLayerManager.stack.length) { + window.h5HistoryLayerManager.close() + return + } + this.historyBack() + }, onView(row) { this.$refs.infoRef.onOpen(row) }, @@ -77,31 +541,94 @@ layout("/layouts/platform_h5.html"){ } this.$refs.applyFormRef.onOpenEdit(row) }, + // 编辑提交成功后留在我的作品页面,仅刷新当前列表数据。 + handleEditSubmitSuccess() { + this.loadWorks(true) + }, onDelete(row) { - this.$dialog.confirm({ - title: '温馨提示', - message: '您确定要删除吗?', - }).then(() => { - this.$axios.post("/platform/activity/worksCollection/upload/delete", {id:row.id}).then((res) => { - if (res.code === 0) { - this.$toast.success(res.msg) - this.doSearch() - } - }) - }).catch(() => { - // on cancel - }); + this.$set(this, 'pendingDeleteRow', row) + window.h5HistoryLayerManager.open('works-collection-mine-delete-confirm') }, - onReady() { - this.doSearch() + closeDeleteConfirm() { + window.h5HistoryLayerManager.close('works-collection-mine-delete-confirm') }, - doSearch() { - this.$nextTick(() => { - this.pageForm.pageNumber = 1 - this.pageForm.totalCount = 0 - this.$refs.tableListRef.doSearch() + confirmDelete() { + const row = this.pendingDeleteRow + window.h5HistoryLayerManager.close('works-collection-mine-delete-confirm', () => { + this.deleteRow(row) }) }, + // 删除接口接收作品 id,成功后重置分页列表;接口返回 Result JSON。 + deleteRow(row) { + if (!row || !row.id || this.deleteLoading) return + this.$set(this, 'deleteLoading', true) + this.$axios.post('/platform/activity/worksCollection/upload/delete', {id: row.id}).then((res) => { + if (res.code === 0) { + this.$toast.success(res.msg || '删除成功') + this.loadWorks(true) + } + }).finally(() => { + this.$set(this, 'deleteLoading', false) + }) + }, + onLoad() { + if (!this.finished) this.loadWorks(false) + }, + onRefresh() { + this.loadWorks(true) + }, + /** + * 加载当前用户作品分页数据。 + * reset 为 true 时从第一页重新查询;接口返回 Pagination,list 为作品数组、totalCount 为总数。 + */ + loadWorks(reset) { + const showSkeleton = !!reset + if (reset) { + this.$set(this, 'skeletonLoading', true) + this.$set(this.pageForm, 'pageNumber', 1) + this.$set(this.pageForm, 'totalCount', 0) + this.$set(this, 'tableData', []) + this.$set(this, 'finished', false) + this.$set(this, 'imageLoadErrors', {}) + } + this.$set(this, 'loading', true) + this.$axios.post('/platform/activity/worksCollection/upload/pageData', this.pageForm, { + h5SkeletonLoading: showSkeleton + }).then((res) => { + if (res.code === 0) { + const list = res.data && res.data.list ? res.data.list : [] + this.$set(this, 'tableData', this.pageForm.pageNumber === 1 ? list : this.tableData.concat(list)) + this.$set(this.pageForm, 'totalCount', res.data && res.data.totalCount ? res.data.totalCount : 0) + this.$set(this, 'finished', this.tableData.length >= this.pageForm.totalCount) + if (!this.finished) { + this.$set(this.pageForm, 'pageNumber', this.pageForm.pageNumber + 1) + } + } + }).finally(() => { + this.$set(this, 'loading', false) + this.$set(this, 'refreshing', false) + if (showSkeleton) this.$set(this, 'skeletonLoading', false) + }) + }, + doSearch() { + this.loadWorks(true) + }, + syncHistoryLayers(stack) { + this.$set(this, 'deleteConfirmVisible', stack.includes('works-collection-mine-delete-confirm')) + } + }, + created() { + window.h5HistoryLayerManager.register(this.historyLayerPageKey, (stack) => { + this.syncHistoryLayers(stack) + }) + this.$set(this, 'historyLayerUnsubscribe', window.h5HistoryLayerManager.subscribe((stack) => { + this.syncHistoryLayers(stack) + })) + this.loadWorks(true) + }, + beforeDestroy() { + if (this.historyLayerUnsubscribe) this.historyLayerUnsubscribe() + window.h5HistoryLayerManager.unregister(this.historyLayerPageKey) } }) diff --git a/src/main/resources/views/platform/zhghh5/activity/workscollection/mine/info.js b/src/main/resources/views/platform/zhghh5/activity/workscollection/mine/info.js index 93cda6d..8dc4af3 100644 --- a/src/main/resources/views/platform/zhghh5/activity/workscollection/mine/info.js +++ b/src/main/resources/views/platform/zhghh5/activity/workscollection/mine/info.js @@ -1,64 +1,197 @@ const INFO = { - template: /*language=HTML*/ - ` + template: /*language=HTML*/ `
- -
- - - {{ viewData.userName }} - - - {{ viewData.loginName }} - - - {{ viewData.unitName }} - - - {{ viewData.unionName }} - - - {{ viewData.name }} - - - {{ viewData.activityName }} - - - {{ viewData.subjectName }} - - - {{ viewData.worksName }} - - - {{ viewData.description }} - - - - - + +
+ + + +
+
+

基础信息

+ + + + + + +
+ +
+

作品信息

+ + + + + + +
+ +
+

作品描述

+
{{viewData.description || '暂无作品描述'}}
+
+ +
+

作品附件

+
+ +
+
暂无作品附件
+
+
- -
+ + +
`, + style: /*language=CSS*/ ` + /deep/ .works-detail-popup, + /deep/ .works-detail-page { + background-color: #f3f7fd; + } + + /deep/ .works-detail-page { + min-height: 100vh; + } + + /deep/ .works-detail-page .van-nav-bar__title { + color: #1f2937; + font-size: 16px; + } + + /deep/ .works-detail-page .van-nav-bar .van-icon, + /deep/ .works-detail-page .van-nav-bar__text { + color: #397fbd; + } + + /deep/ .works-detail-scroll { + height: calc(100vh - 46px); + padding: 12px 12px calc(24px + env(safe-area-inset-bottom)); + overflow-x: hidden; + overflow-y: auto; + box-sizing: border-box; + } + + /deep/ .works-detail-card { + margin-top: 12px; + overflow: hidden; + border: 1px solid #e9eff7; + border-radius: 12px; + background-color: #fff; + box-shadow: 0 7px 20px rgba(55, 85, 126, .08); + } + + /deep/ .works-detail-scroll > .works-detail-card:first-child { + margin-top: 0; + } + + /deep/ .works-detail-card__title { + position: relative; + margin: 0; + padding: 13px 14px 9px 22px; + color: #354255; + font-size: 15px; + line-height: 21px; + font-weight: 600; + } + + /deep/ .works-detail-card__title::before { + position: absolute; + top: 16px; + bottom: 12px; + left: 12px; + width: 2px; + border-radius: 2px; + background-color: #1989fa; + content: ""; + } + + /deep/ .works-detail-card .van-cell { + padding: 10px 14px; + color: #596779; + } + + /deep/ .works-detail-card .van-cell__title { + flex: 0 0 82px; + font-size: 14px; + } + + /deep/ .works-detail-card .van-cell__value { + color: #4f5d70; + font-size: 14px; + word-break: break-word; + } + + /deep/ .works-detail-description, + /deep/ .works-detail-files, + /deep/ .works-detail-empty { + padding: 4px 14px 16px; + color: #536174; + font-size: 14px; + line-height: 1.8; + white-space: pre-wrap; + word-break: break-word; + } + + /deep/ .works-detail-empty { + color: #9aa7b8; + } + `, data() { return { viewData: {}, visible: false, + historyLayerUnsubscribe: null, } }, methods: { + /** + * 查询并打开作品详情。 + * row 需传作品 id;接口返回作品、活动、主题、类型及附件信息的 Result JSON。 + */ onOpen(row) { + this.$set(this, "viewData", Object.assign({}, row, {files: this.normalizeFiles(row.files)})) + window.h5HistoryLayerManager.open("works-collection-mine-info") + this.resetScrollPosition() this.$axios.post("/platform/activity/worksCollection/common/findOne", {id: row.id}).then((res) => { if (res.code === 0) { - this.viewData = res.data - try { - this.viewData.files = JSON.parse(this.viewData.files) - } catch (err) { - } + const viewData = res.data || {} + viewData.files = this.normalizeFiles(viewData.files) + this.$set(this, "viewData", viewData) } }) - this.visible = true }, - + // 弹窗组件会被复用,每次打开后需清除上一次查看时保留的滚动位置。 + resetScrollPosition() { + this.$nextTick(() => { + if (this.$refs.detailScrollRef) this.$refs.detailScrollRef.scrollTop = 0 + }) + }, + normalizeFiles(files) { + if (Array.isArray(files)) return files + if (!files) return [] + try { + const result = JSON.parse(files) + return Array.isArray(result) ? result : [] + } catch (err) { + return [] + } + }, + closeInfo() { + window.h5HistoryLayerManager.close("works-collection-mine-info") + }, + syncHistoryLayers(stack) { + this.$set(this, "visible", stack.includes("works-collection-mine-info")) + } + }, + created() { + this.$set(this, "historyLayerUnsubscribe", window.h5HistoryLayerManager.subscribe((stack) => { + this.syncHistoryLayers(stack) + })) + }, + beforeDestroy() { + if (this.historyLayerUnsubscribe) this.historyLayerUnsubscribe() } } diff --git a/src/main/resources/views/platform/zhghh5/activity/workscollection/read/index.html b/src/main/resources/views/platform/zhghh5/activity/workscollection/read/index.html index 82ded3e..a363362 100644 --- a/src/main/resources/views/platform/zhghh5/activity/workscollection/read/index.html +++ b/src/main/resources/views/platform/zhghh5/activity/workscollection/read/index.html @@ -1,66 +1,502 @@ -
- + + +
+
+ + + +
+ + + + +
- - - + + + + + +
+ +
+
@@ -69,6 +505,9 @@ layout("/layouts/platform_h5.html"){ new Vue({ el: "#app", store, + components: { + 'info': INFO, + }, data() { return { pageForm: { @@ -77,79 +516,148 @@ layout("/layouts/platform_h5.html"){ totalCount: 0, searchKeyword: '', pageOrderName: 'createdAt', - pageOrderNameText: '', activityId: '', year: new Date().getFullYear(), }, - activityOptions: [] + activityOptions: [], + orderOptions: [ + {text: '按上传时间排序', value: 'createdAt'}, + {text: '按点赞数量排序', value: 'num'}, + ], + filterOpened: false, + likeLoading: false, + likeHeartEffects: {}, + likeHeartEffectKeys: {}, + likeHeartEffectTimers: {}, + skeletonLoading: true, + historyLayerPageKey: 'works-collection-read-h5', } }, - components: { - 'info': INFO, - }, methods: { + // state.initial 表示查询前列表为空,仅在首次或重置查询时显示整组卡片骨架。 + handleListLoadingChange(state) { + if (state.initial) this.$set(this, 'skeletonLoading', state.loading) + }, + // 判断列表附件是否包含有效记录;files 支持上传组件保存的 JSON 字符串或数组。 + hasListFiles(files) { + if (Array.isArray(files)) return files.length > 0 + if (!files) return false + try { + const fileList = JSON.parse(files) + return Array.isArray(fileList) && fileList.length > 0 + } catch (err) { + return false + } + }, + // 获取当前作品的全部附件数量;files 支持 JSON 字符串或数组,返回 Number 类型。 + getAttachmentCount(files) { + if (Array.isArray(files)) return files.length + if (!files) return 0 + try { + const fileList = JSON.parse(files) + return Array.isArray(fileList) ? fileList.length : 0 + } catch (err) { + return 0 + } + }, onLike(row) { - this.$dialog.confirm({ - title: '温馨提示', - message: '您确定要点赞吗?', - }).then(() => { - this.$axios.post("/platform/activity/worksCollection/read/h5/doLike", {uploadId: row.id}).then((res) => { - if (res.code === 0) { - row.num = row.num + 1 - row.isThisLike = true - this.$toast.success(res.msg) - } - }) - }).catch(() => { - // on cancel - }); + this.executeLikeAction(row, 'like') }, onDeleteLike(row) { - this.$dialog.confirm({ - title: '温馨提示', - message: '您确定要取消点赞吗?', - }).then(() => { - this.$axios.post("/platform/activity/worksCollection/read/h5/doDeleteLike", {uploadId: row.id}).then((res) => { - if (res.code === 0) { - row.num = row.num - 1 - row.isThisLike = false - this.$toast.success(res.msg) - } - }) - }).catch(() => { - // on cancel - }); + this.executeLikeAction(row, 'cancel') + }, + /** + * 执行作品点赞或取消点赞。 + * row 需包含作品 id,action 传 like 或 cancel;接口返回 Result JSON,方法无返回值。 + */ + executeLikeAction(row, action) { + if (!row || !row.id || this.likeLoading) return + const isCancel = action === 'cancel' + const url = isCancel + ? '/platform/activity/worksCollection/read/h5/doDeleteLike' + : '/platform/activity/worksCollection/read/h5/doLike' + this.$set(this, 'likeLoading', true) + this.$axios.post(url, {uploadId: row.id}).then((res) => { + if (res.code === 0) { + const currentNum = Number(row.num) || 0 + this.$set(row, 'num', isCancel ? Math.max(currentNum - 1, 0) : currentNum + 1) + this.$set(row, 'isThisLike', !isCancel) + if (!isCancel) this.playLikeHeart(row.id) + } + }).finally(() => { + this.$set(this, 'likeLoading', false) + }) + }, + // 点赞成功后为当前作品播放多颗红色爱心错峰上浮动画,重复触发时重新开始计时。 + playLikeHeart(rowId) { + if (!rowId) return + if (this.likeHeartEffectTimers[rowId]) { + clearTimeout(this.likeHeartEffectTimers[rowId]) + } + this.$set(this.likeHeartEffects, rowId, false) + this.$set(this.likeHeartEffectKeys, rowId, (this.likeHeartEffectKeys[rowId] || 0) + 1) + this.$nextTick(() => { + this.$set(this.likeHeartEffects, rowId, true) + const timer = setTimeout(() => { + this.$set(this.likeHeartEffects, rowId, false) + this.$delete(this.likeHeartEffectTimers, rowId) + }, 1400) + this.$set(this.likeHeartEffectTimers, rowId, timer) + }) }, onView(row) { this.$refs.infoRef.onOpen(row) }, - async listActivity() { - const res = await this.$axios.post("/platform/activity/worksCollection/common/listActivity") - if (res.code === 0) { - res.data.forEach(v => { - v.text = v.name - v.value = v.id - }) - this.activityOptions = res.data - if (this.activityOptions.length > 0) { - this.pageForm.activityId = this.activityOptions[0].id - } - } + onFilterOpen() { + this.$set(this, 'filterOpened', true) }, - async onReady() { - await this.listActivity() - this.doSearch() + onFilterClose() { + this.$set(this, 'filterOpened', false) + }, + handlePageBack() { + if (window.h5HistoryLayerManager.stack.length) { + window.h5HistoryLayerManager.close() + return + } + this.historyBack() + }, + // 活动接口接收年度,返回 id、name 组成的活动列表;完成后触发首次作品查询。 + listActivity() { + return this.$axios.post('/platform/activity/worksCollection/common/listActivity', { + year: this.pageForm.year + }, { + h5SkeletonLoading: true + }).then((res) => { + if (res.code === 0) { + const options = (res.data || []).map((item) => { + return {text: item.name, value: item.id} + }) + this.$set(this, 'activityOptions', options) + this.$set(this.pageForm, 'activityId', options.length > 0 ? options[0].value : '') + } + }) + }, + onReady() { + this.listActivity().then(() => { + this.doSearch() + }) }, doSearch() { this.$nextTick(() => { - this.pageForm.pageNumber = 1 - this.pageForm.totalCount = 0 + this.$set(this.pageForm, 'pageNumber', 1) + this.$set(this.pageForm, 'totalCount', 0) this.$refs.tableListRef.doSearch() }) - }, + } }, created() { - + window.h5HistoryLayerManager.register(this.historyLayerPageKey, null) + }, + beforeDestroy() { + Object.keys(this.likeHeartEffectTimers).forEach((rowId) => { + clearTimeout(this.likeHeartEffectTimers[rowId]) + }) + window.h5HistoryLayerManager.unregister(this.historyLayerPageKey) } }) diff --git a/src/main/resources/views/platform/zhghh5/activity/workscollection/upload/applyForm.js b/src/main/resources/views/platform/zhghh5/activity/workscollection/upload/applyForm.js index df0a511..7ee236e 100644 --- a/src/main/resources/views/platform/zhghh5/activity/workscollection/upload/applyForm.js +++ b/src/main/resources/views/platform/zhghh5/activity/workscollection/upload/applyForm.js @@ -1,191 +1,557 @@ const applyForm = { - template: - /*language=HTML*/ - ` + template: /*language=HTML*/ `
- - - - - - - - - - - - - - - + +
+ + + + +
+
+

基础信息

+ + + + + + + +
- - - - - +
+

作品信息

+ + + + + + +
- - - - - - - - +
+

作品附件

+
附件说明:请根据所选作品类型上传符合要求的文件
+ + + + + +
+
+ +
+
+
-
- 提交 -
-
- -
+ +
+ +
{{optionPopupTitle}}
+ +
+
+
暂无可选内容
+ +
+
+
`, + style: /*language=CSS*/ ` + /deep/ .works-apply-popup { + background-color: #f3f7fd; + } + + /deep/ .works-apply-page { + position: relative; + min-height: 100vh; + background-color: #f3f7fd; + } + + /deep/ .works-apply-page .van-nav-bar { + background-color: #fff; + } + + /deep/ .works-apply-page .van-nav-bar__title { + color: #1f2937; + font-size: 16px; + } + + /deep/ .works-apply-page .van-nav-bar .van-icon, + /deep/ .works-apply-page .van-nav-bar__text { + color: #397fbd; + } + + /deep/ .works-apply-form.form-container { + min-height: 100%; + padding: 0; + box-sizing: border-box; + background: #f3f7fc; + } + + /deep/ .works-apply-scroll { + height: calc(100vh - 46px); + padding: 12px 15px calc(84px + env(safe-area-inset-bottom)); + overflow-x: hidden; + overflow-y: auto; + box-sizing: border-box; + background: #f3f7fc; + } + + /deep/ .works-apply-section { + margin-bottom: 11px; + overflow: hidden; + border: 1px solid #e9eff7; + border-radius: 10px; + background: #fff; + box-shadow: 0 3px 11px rgba(51, 87, 126, .08); + } + + /deep/ .works-apply-section__title { + position: relative; + margin: 0; + padding: 12px 14px 8px 21px; + color: #354255; + font-size: 15px; + line-height: 20px; + font-weight: 600; + background: transparent; + } + + /deep/ .works-apply-section__title::before { + position: absolute; + top: 15px; + bottom: 11px; + left: 12px; + width: 2px; + border-radius: 2px; + background: #1989fa; + content: ""; + } + + /deep/ .works-apply-section__hint { + padding: 0 14px 8px 21px; + color: orangered; + font-size: 14px; + line-height: 18px; + font-weight: normal; + } + + /deep/ .works-apply-section .van-cell-group { + margin: 0; + background: transparent; + } + + /deep/ .works-apply-section .van-cell { + min-height: 44px; + padding: 10px 14px; + background: transparent; + } + + /deep/ .works-apply-section .van-cell:not(:last-child)::after { + right: 14px; + left: 14px; + border-color: #eef2f7; + } + + /deep/ .works-apply-section .van-field__label { + width: 84px; + color: #596779; + font-size: 14px; + line-height: 24px; + } + + /deep/ .works-apply-section .van-field__value, + /deep/ .works-apply-section .van-field__control { + color: #4f5d70; + font-size: 14px; + line-height: 24px; + } + + /deep/ .works-apply-section .van-field__control::placeholder { + color: #b1bac6; + } + + /deep/ .works-apply-section .van-cell--required::before { + left: 6px; + color: #f06464; + } + + /deep/ .works-apply-upload-field .van-field__value { + overflow: visible; + } + + /deep/ .works-apply-footer { + position: fixed; + z-index: 100; + right: 0; + bottom: 0; + left: 0; + display: flex; + padding: 10px 18px calc(10px + env(safe-area-inset-bottom)); + border-top: 1px solid #edf1f6; + background: rgba(255, 255, 255, .98); + box-shadow: 0 -4px 14px rgba(50, 78, 112, .06); + } + + /deep/ .works-apply-footer .van-button { + flex: 1; + height: 40px; + margin: 0; + border-color: #1989fa; + border-radius: 14px !important; + font-size: 16px; + background: #1989fa; + } + + /deep/ .works-option-popup { + display: flex; + height: 50%; + padding-bottom: env(safe-area-inset-bottom); + flex-direction: column; + box-sizing: border-box; + background: #f5f7fa; + } + + /deep/ .works-option-popup__header { + display: flex; + min-height: 52px; + padding: 0 16px; + align-items: center; + justify-content: space-between; + flex: none; + border-bottom: 1px solid #edf1f6; + background: #fff; + } + + /deep/ .works-option-popup__title { + color: #263548; + font-size: 17px; + line-height: 24px; + font-weight: 600; + } + + /deep/ .works-option-popup__action { + min-width: 48px; + padding: 8px 0; + border: 0; + color: #1989fa; + font-size: 15px; + line-height: 22px; + text-align: left; + background: transparent; + } + + /deep/ .works-option-popup__action:last-child { + text-align: right; + } + + /deep/ .works-option-popup__action:disabled { + color: #b7c0cc; + } + + /deep/ .works-option-popup__list { + min-height: 0; + padding: 12px; + overflow-y: auto; + flex: 1; + -webkit-overflow-scrolling: touch; + } + + /deep/ .works-option-popup__empty { + padding: 44px 18px; + color: #98a4b3; + font-size: 14px; + line-height: 22px; + text-align: center; + } + + /deep/ .works-option-popup__option { + display: flex; + width: 100%; + min-height: 58px; + margin-bottom: 10px; + padding: 12px 14px; + align-items: center; + border: 1px solid #e3e9f1; + border-radius: 10px; + color: #536174; + text-align: left; + background: #fff; + } + + /deep/ .works-option-popup__option:last-child { + margin-bottom: 0; + } + + /deep/ .works-option-popup__option.is-selected { + border-color: #1989fa; + color: #176fbd; + background: #eef7ff; + box-shadow: 0 3px 10px rgba(25, 137, 250, .1); + } + + /deep/ .works-option-popup__option-text { + min-width: 0; + flex: 1; + font-size: 15px; + line-height: 23px; + word-break: break-word; + overflow-wrap: anywhere; + } + + /deep/ .works-apply-confirm .van-dialog__header { + color: #263548; + font-size: 17px; + font-weight: 600; + } + + /deep/ .works-apply-confirm .van-dialog__message { + padding: 24px 20px; + color: #536174; + font-size: 15px; + line-height: 24px; + text-align: center; + } + `, data() { return { - row: {}, - visible: false, - formData: {}, - subjectTypesOptions: [], - showSubjectPicker: false, - - worksTypeOptions: [], - showWorksPicker: false, - - fileAccept: null, - chooseWorksType: {} + row: {}, visible: false, formData: {}, formLoading: false, + subjectTypesOptions: [], worksTypeOptions: [], + showOptionPicker: false, optionPopupType: "", optionPopupTitle: "", + optionPopupOptions: [], pendingOptionValue: "", + submitConfirmVisible: false, fileAccept: null, chooseWorksType: {}, + submitSuccessMode: "navigate", + historyLayerPageKey: "works-collection-upload-h5", + historyLayerUnsubscribe: null, ownsHistoryRegistration: false + } + }, + computed: { + descriptionMaxLength() { + return this.chooseWorksType && this.chooseWorksType.maxWordCount ? this.chooseWorksType.maxWordCount : 150 } }, methods: { - async onOpen(row) { - this.$set(this.formData, 'username', this.$store.state.user.username) - this.$set(this.formData, 'loginname', this.$store.state.user.loginname) - this.$set(this.formData, 'unionName', this.$store.state.user.union.name) - this.$set(this.formData, 'unitName', this.$store.state.user.unit.name) - this.$set(this.formData, 'sex', this.$store.state.user.sex) - this.$set(this.formData, 'activityId', row.id) - await this.activityChange(row.id) - this.row = row - this.visible = true + resetFormData() { + const user = this.$store.state.user || {} + const union = user.union || {} + const unit = user.unit || {} + this.$set(this, "formData", {}) + this.$set(this.formData, "username", user.username) + this.$set(this.formData, "loginname", user.loginname) + this.$set(this.formData, "unionName", union.name) + this.$set(this.formData, "unitName", unit.name) + this.$set(this.formData, "sex", user.sex) + this.$set(this.formData, "files", []) + this.$set(this, "subjectTypesOptions", []) + this.$set(this, "worksTypeOptions", []) + this.$set(this, "chooseWorksType", {}) + this.$set(this, "fileAccept", null) }, - async onOpenEdit(row) { + onOpen(row) { + this.resetFormData() + this.$set(this, "submitSuccessMode", "navigate") + this.$set(this, "row", row) + this.$set(this.formData, "activityId", row.id) + this.activityChange(row.id).then(() => { + window.h5HistoryLayerManager.open("works-collection-apply") + this.resetScrollPosition() + }) + }, + onOpenEdit(row) { const formData = clone(row) - formData.files = JSON.parse(formData.files) - await this.activityChange(formData.activityId) - await this.subjectChange(formData.subjectId) - this.row = formData - this.formData = formData - this.$set(this.formData, 'username', this.$store.state.user.username) - this.$set(this.formData, 'loginname', this.$store.state.user.loginname) - this.$set(this.formData, 'sex', this.$store.state.user.sex) - this.chooseWorksType = this.worksTypeOptions.find((o) => o.id === formData.worksId) - if (this.chooseWorksType.allowFileTypes && this.chooseWorksType.allowFileTypes.length > 0) { - this.fileAccept = this.chooseWorksType.allowFileTypes.map((item) => "." + item).join(",") - } - this.visible = true + formData.files = this.normalizeFiles(formData.files) + this.resetFormData() + this.$set(this, "submitSuccessMode", "close") + this.$set(this, "row", formData) + this.$set(this, "formData", formData) + const user = this.$store.state.user || {} + this.$set(this.formData, "username", user.username) + this.$set(this.formData, "loginname", user.loginname) + this.$set(this.formData, "sex", user.sex) + this.activityChange(formData.activityId).then(() => { + // 编辑时以主题主键匹配当前活动配置,避免列表未携带主题名称时无法回显。 + const subjectType = this.subjectTypesOptions.find((item) => item.id === formData.subjectId) + if (subjectType) this.$set(this.formData, "subjectName", subjectType.typeName) + return this.subjectChange(formData.subjectId) + }).then(() => { + this.setSelectedWorksType(formData.worksId) + window.h5HistoryLayerManager.open("works-collection-apply") + this.resetScrollPosition() + }) }, - onWorksConfirm(value, index) { - this.$set(this.formData, 'worksName', value) - this.$set(this.formData, 'worksId', this.worksTypeOptions[index].id) - this.showWorksPicker = false - this.chooseWorksType = this.worksTypeOptions.find((o) => o.id === this.worksTypeOptions[index].id) - if (this.chooseWorksType.allowFileTypes && this.chooseWorksType.allowFileTypes.length > 0) { - this.fileAccept = this.chooseWorksType.allowFileTypes.map((item) => "." + item).join(",") + // 表单弹窗会被复用,每次打开后需清除上一次编辑或提交时保留的滚动位置。 + resetScrollPosition() { + this.$nextTick(() => { + if (this.$refs.applyScrollRef) this.$refs.applyScrollRef.scrollTop = 0 + }) + }, + // 附件可能由列表 SQL 返回 JSON 字符串,也可能已被序列化为数组;统一转换为上传组件需要的数组。 + normalizeFiles(files) { + if (Array.isArray(files)) return files + if (!files) return [] + try { + const result = JSON.parse(files) + return Array.isArray(result) ? result : [] + } catch (err) { + return [] } }, - async onSubjectConfirm(value, index) { - this.$set(this.formData, 'subjectName', value) - this.$set(this.formData, 'subjectId', this.subjectTypesOptions[index].id) - await this.subjectChange(this.subjectTypesOptions[index].id) - this.showSubjectPicker = false + closeApply() { window.h5HistoryLayerManager.close("works-collection-apply") }, + // 打开统一选择弹框:主题选项由活动配置提供,确认后再写入表单。 + openSubjectPicker() { + this.$set(this, "optionPopupType", "subject") + this.$set(this, "optionPopupTitle", "选择主题类型") + this.$set(this, "optionPopupOptions", this.subjectTypesOptions.map((item) => { + return {value: item.id, text: item.typeName, source: item} + })) + this.$set(this, "pendingOptionValue", this.formData.subjectId || "") + window.h5HistoryLayerManager.open("works-collection-option-picker") + }, + openWorksPicker() { + if (!this.formData.subjectId) { + this.$toast("请先选择主题类型") + return + } + this.$set(this, "optionPopupType", "works") + this.$set(this, "optionPopupTitle", "选择作品类型") + this.$set(this, "optionPopupOptions", this.worksTypeOptions.map((item) => { + return {value: item.id, text: item.worksTypeName, source: item} + })) + this.$set(this, "pendingOptionValue", this.formData.worksId || "") + window.h5HistoryLayerManager.open("works-collection-option-picker") + }, + cancelOptionPopup() { window.h5HistoryLayerManager.close("works-collection-option-picker") }, + selectOptionPopupItem(option) { this.$set(this, "pendingOptionValue", option.value) }, + /** + * 确认类型选项并回填作品表单。 + * optionPopupType 为 subject 或 works,pendingOptionValue 为对应业务主键;方法无返回值。 + */ + confirmOptionPopup() { + const selectedOption = this.optionPopupOptions.find((item) => { + return item.value === this.pendingOptionValue + }) + if (!selectedOption) return + if (this.optionPopupType === "subject") { + this.onSubjectConfirm(selectedOption) + return + } + this.onWorksConfirm(selectedOption) + }, + onWorksConfirm(selectedOption) { + this.$set(this.formData, "worksName", selectedOption.text) + this.$set(this.formData, "worksId", selectedOption.value) + this.setSelectedWorksType(selectedOption.value) + this.cancelOptionPopup() + }, + onSubjectConfirm(selectedOption) { + this.$set(this.formData, "subjectName", selectedOption.text) + this.$set(this.formData, "subjectId", selectedOption.value) + this.$set(this.formData, "worksName", "") + this.$set(this.formData, "worksId", "") + this.$set(this.formData, "files", []) + this.$set(this, "chooseWorksType", {}) + this.$set(this, "fileAccept", null) + this.subjectChange(selectedOption.value).then(() => { this.cancelOptionPopup() }) + }, + setSelectedWorksType(worksId) { + const worksType = this.worksTypeOptions.find((item) => item.id === worksId) || {} + this.$set(this, "chooseWorksType", worksType) + if (worksType.allowFileTypes && worksType.allowFileTypes.length > 0) { + this.$set(this, "fileAccept", worksType.allowFileTypes.map((item) => "." + item).join(",")) + return + } + this.$set(this, "fileAccept", null) }, onSubmit() { this.$refs.formRef.validate().then(() => { - this.$dialog.confirm({ - title: "提示", - message: "您确定要提交吗?" - }).then(() => { - this.$toast.loading({ - message: '提交中...', - forbidClick: true, - }); - this.$axios.post("/platform/activity/worksCollection/upload" + (this.formData.id ? "/update" : "/insert"), {data: JSON.stringify(this.formData)}).then((res) => { - if (res.code === 0) { - this.$toast.clear(); - this.$toast.success("提交成功") - pjaxReplace("/platform/activity/worksCollection/mine/h5") - } - }) - }) - }).catch(); + window.h5HistoryLayerManager.open("works-collection-submit-confirm") + }).catch(() => { + // 表单校验失败时由字段规则直接展示提示,不进入提交确认。 + }) }, - async activityChange(value) { - const resp = await this.$axios.post("/platform/activity/worksCollection/common/getSubjectTypes", {activityId: value}) - if (resp.code === 0) { - this.subjectTypesOptions = resp.data - } + closeSubmitConfirm() { window.h5HistoryLayerManager.close("works-collection-submit-confirm") }, + confirmSubmit() { + window.h5HistoryLayerManager.close("works-collection-submit-confirm", () => { this.submitForm() }) }, - async subjectChange(value) { - const resp = await this.$axios.post("/platform/activity/worksCollection/common/getWorksTypes", {subjectId: value}) - if (resp.code === 0) { - this.worksTypeOptions = resp.data - } + submitForm() { + this.$set(this, "formLoading", true) + this.$axios.post("/platform/activity/worksCollection/upload" + (this.formData.id ? "/update" : "/insert"), { + data: JSON.stringify(this.formData) + }).then((res) => { + if (res.code === 0) { + this.$toast.success("提交成功") + if (this.submitSuccessMode === "close") { + // 我的作品页使用弹窗编辑,成功后只关闭当前表单并通知父页面刷新列表。 + window.h5HistoryLayerManager.close("works-collection-apply", () => { + this.$emit("submit-success") + }) + return + } + window.h5HistoryLayerManager.closeAllAndNavigate("/platform/activity/worksCollection/mine/h5") + } + }).finally(() => { this.$set(this, "formLoading", false) }) + }, + activityChange(value) { + return this.$axios.post("/platform/activity/worksCollection/common/getSubjectTypes", {activityId: value}).then((res) => { + if (res.code === 0) this.$set(this, "subjectTypesOptions", res.data || []) + }) + }, + subjectChange(value) { + return this.$axios.post("/platform/activity/worksCollection/common/getWorksTypes", {subjectId: value}).then((res) => { + if (res.code === 0) this.$set(this, "worksTypeOptions", res.data || []) + }) + }, + syncHistoryLayers(stack) { + this.$set(this, "visible", stack.includes("works-collection-apply")) + this.$set(this, "showOptionPicker", stack.includes("works-collection-option-picker")) + this.$set(this, "submitConfirmVisible", stack.includes("works-collection-submit-confirm")) } + }, + created() { + if (!window.h5HistoryLayerManager.pageKey) { + window.h5HistoryLayerManager.ensureRegistered(this.historyLayerPageKey) + this.$set(this, "ownsHistoryRegistration", true) + } + this.$set(this, "historyLayerUnsubscribe", window.h5HistoryLayerManager.subscribe((stack) => { + this.syncHistoryLayers(stack) + })) + }, + beforeDestroy() { + if (this.historyLayerUnsubscribe) this.historyLayerUnsubscribe() + if (this.ownsHistoryRegistration) window.h5HistoryLayerManager.unregister(this.historyLayerPageKey) } } diff --git a/src/main/resources/views/platform/zhghh5/activity/workscollection/upload/index.html b/src/main/resources/views/platform/zhghh5/activity/workscollection/upload/index.html index 555f4ed..7a10e8a 100644 --- a/src/main/resources/views/platform/zhghh5/activity/workscollection/upload/index.html +++ b/src/main/resources/views/platform/zhghh5/activity/workscollection/upload/index.html @@ -1,108 +1,690 @@ - -
- - +
+
+ - - - - - - +
+
+ 作品征集插画 +
+
作品征集
+
汇聚创意 · 展示风采
+
+
+ +
+ + + + + +
+
- - - - - - -
- -
{{infoRow.name}}
- + + +
+
+ +
+ + {{ hasCover(row) ? '图片加载失败' : '暂无图片' }} +
+
+
{{ row.name }}
+
+ 征集时间:{{ $moment(row.startDateTime).format('MM/DD HH:mm') }}~{{ $moment(row.endDateTime).format('MM/DD HH:mm') }} +
+
+ + 面向对象{{ row.activityGroupName }} + +
+
+ +
+
+
+
+ 作品征集空状态插画 +
暂无作品征集活动
+
当前筛选条件下暂时没有可展示的活动
+
+
-
-
+
- - - 去报名 - - + +
+ + + +
+
+
+ +
+
+
作品征集须知
+
请仔细阅读征集内容与作品要求
+
+
+
+
+
+
+ + 暂无征集介绍 +
+
+
- +
- -
- - diff --git a/src/main/resources/views/platform/zhghh5/club/apply/index.html b/src/main/resources/views/platform/zhghh5/club/apply/index.html index b77e375..035c4d3 100644 --- a/src/main/resources/views/platform/zhghh5/club/apply/index.html +++ b/src/main/resources/views/platform/zhghh5/club/apply/index.html @@ -3,140 +3,106 @@ layout("/layouts/platform_h5.html"){ #--> -
- - +
+ - - - - - - - - - + +
+

基本信息

+ + + + + + + + + + + + + + + +
- - - - - - - - - +
+

其他信息

+ + + + +
- - - - - - - - - +
+

照片

+ + + + + +
- - - - - +
+

电子签名

+ + + + + +
- - - - - - - - - - - - - - - - - - 注:本人已仔细阅读并愿意遵守所参加本校教职工文体社团的章程和规定,自愿加入所报名社团。 - - - - -
- 保存申请 - 提交申请 - 提交申请 -
+ +
+
注:本人已仔细阅读并愿意遵守所参加本校教职工文体社团的章程和规定,自愿加入所报名社团。
+
+ 保存申请 + 提交申请 + 提交申请 +
+
+ + + + + +
diff --git a/src/main/resources/views/platform/zhghh5/club/clubApproval/index.html b/src/main/resources/views/platform/zhghh5/club/clubApproval/index.html index 77888f5..b18a401 100644 --- a/src/main/resources/views/platform/zhghh5/club/clubApproval/index.html +++ b/src/main/resources/views/platform/zhghh5/club/clubApproval/index.html @@ -2,108 +2,136 @@ layout("/layouts/platform_h5.html"){ #--> - -
- + +
+ - - +
+ + - - + + + - - - - +
- -