# Conflicts:
#	src/main/resources/static/components/sign/mobileSign.vue
This commit is contained in:
2026-10-22 08:49:06 +08:00
13 changed files with 711 additions and 219 deletions
@@ -101,7 +101,7 @@ layout("/mobile/platform.html"){
left-arrow
placeholder
fixed
@click-left="pjaxReplace('/mobile/index')"
@click-left="leavePage"
safe-area-inset-top
></van-nav-bar>
@@ -135,7 +135,7 @@ layout("/mobile/platform.html"){
@load="onLoad">
<div class="van-doc-card" v-for="o in tableData">
<div @click="openView(o.id)">
<div @click="openView(o)">
<div style="display: flex; justify-content: space-between">
<div style="width: 74%">
@@ -167,7 +167,7 @@ layout("/mobile/platform.html"){
<van-row>
<van-col span="24">
<span style="color: grey">所属单位:</span>
<span>{{o.unitname}}</span>
<span>{{o.applicantunitname}}</span>
</van-col>
</van-row>
<van-row>
@@ -185,7 +185,7 @@ layout("/mobile/platform.html"){
style="margin-right: 8px; width: auto;">
查看
</van-button>
<van-button @click.stop="openAudit(o)"
<van-button v-if="o.status == 1" @click.stop="openAudit(o)"
type="primary" size="small" color="#1867b0"
style="margin-right: 8px; width: auto;">
审核
@@ -203,16 +203,16 @@ layout("/mobile/platform.html"){
></van-empty>
</div>
<van-popup v-model="infoShow" position="right" :style="{width: '100%', height: '100%' }">
<van-popup :value="infoShow" :close-on-popstate="false" :close-on-click-overlay="false" position="right" :style="{width: '100%', height: '100%' }">
<van-nav-bar
title="详细信息"
left-arrow
placeholder
fixed
@click-left="infoShow = false"
@click-left="closePopup"
safe-area-inset-top
></van-nav-bar>
<club-user-apply-info ref="applyInfo" :handle="handle">
<club-user-apply-info v-if="infoShow" :key="applyId" :apply-id="applyId" :handle="handle">
<template #handle>
<div class="van-cell-group__title">
<div class="van-sidebar-item van-sidebar-item--select">
@@ -243,20 +243,17 @@ layout("/mobile/platform.html"){
<div class="mb10">审核签字</div>
</template>
<template #input>
<mobile-sign ref="signature" :my_sign.sync="formData.auditSign"></mobile-sign>
<mobile-sign :path-mode="true" ref="signature" v-model="formData.auditSign"
:popup-visible="signatureShow" :disabled="formLoading"
@open="openSignature" @close="closePopup"></mobile-sign>
</template>
</van-field>
<van-row gutter="20" style="margin-top: 30px;padding: 0 25px">
<van-col span="12">
<van-button :color="themeColor" plain block @click="doReview(false)">拒 绝
</van-button>
</van-col>
<van-col span="12">
<van-button :color="themeColor" block @click="doReview(true)">通 过
</van-button>
</van-col>
</van-row>
<!-- 与慰问分工会审核页保持一致:红色拒绝、绿色通过。 -->
<div style="display: flex; justify-content: space-between; column-gap: 10px; padding: 10px">
<van-button type="danger" block :loading="formLoading" native-type="button" @click="doReview(false)">拒绝</van-button>
<van-button type="primary" block :loading="formLoading" native-type="button" @click="doReview(true)">通过</van-button>
</div>
</van-form>
</template>
@@ -272,6 +269,8 @@ layout("/mobile/platform.html"){
data() {
return {
handle: false,
applyId: '',
formLoading: false,
clubOptions: [],
userTypeOptions: [],
auditOptions: [
@@ -283,52 +282,71 @@ layout("/mobile/platform.html"){
radioType: 3
},
infoShow: false,
signatureShow: false,
formData: {},
}
},
components: {
'club-user-apply-info': httpVueLoader('/components/mobile/club/mobileClubUserApplyInfo.vue?v=1.0.1'),
'mobile-sign': httpVueLoader('/components/sign/mobileSign.vue'),
'club-user-apply-info': httpVueLoader('/components/mobile/club/mobileClubUserApplyInfo.vue?v=1.0.4'),
'mobile-sign': httpVueLoader('/components/sign/mobileSign.vue?v=1.0.2'),
},
methods: {
// 使用远端统一历史会话;签字层和详情层由同一回调驱动,避免重复压栈。
syncPopup(layers) {
this.$set(this, 'infoShow', layers.includes('info'))
this.$set(this, 'signatureShow', layers.includes('signature'))
},
closePopup() {
return this.$options.popupSession.close()
},
openSignature() {
this.$options.popupSession.open('signature')
},
leavePage() {
if (this.infoShow || this.signatureShow) return this.closePopup()
this.disposePopup()
pjaxReplace('/mobile/index')
},
disposePopup() {
if (this.$options.popupSession) this.$options.popupSession.dispose()
$(document).off('pjax:beforeSend.clubAudit')
$(document).off('pjax:beforeReplace.clubAudit')
},
openAudit(row) {
this.formData = {
this.$set(this, 'formData', {
id: row.id,
auditTime: moment().format('YYYY-MM-DD'),
username: "${@shiro.getPrincipalProperty('username')}",
auditOpinion: null,
auditSign: ""
}
this.infoShow = true
this.handle = true
this.$nextTick(() => {
this.$refs.applyInfo.getInfo(row.id)
auditSign: ''
})
this.$set(this, 'applyId', row.id)
this.$set(this, 'handle', true)
this.$options.popupSession.open('info')
},
openView(row) {
this.infoShow = true
this.handle = false
this.$nextTick(() => {
this.$refs.applyInfo.getInfo(row.id)
})
this.$set(this, 'applyId', row.id)
this.$set(this, 'handle', false)
this.$options.popupSession.open('info')
},
async doReview(flag) {
if (!this.formData.auditOpinion) {
vant.Toast('请填写审核意见');
// id 为申请记录,flag 为是否通过;响应 code 为 0 时清理弹框历史并刷新列表。
doReview(flag) {
if (this.formLoading) return
if (!this.formData.auditOpinion || !this.formData.auditOpinion.trim()) {
vant.Toast('请填写审核意见')
return
}
if (!this.formData.auditSign) {
vant.Toast('请签字')
return
}
// this.$refs.signature.submitSign()
this.formData.flag = flag
const resp = await $.post("/platform/sys/club/sh/check", this.formData)
if (resp.code === 0) {
this.doSearch()
this.infoShow = false
}
vant.Toast(resp.msg)
this.$set(this, 'formLoading', true)
const params = Object.assign({}, this.formData, {flag})
$.post('/platform/sys/club/sh/check', params).then(resp => {
if (resp.code === 0) this.$options.popupSession.clear().then(() => this.doSearch())
vant.Toast(resp.msg)
}).fail(() => vant.Toast('审核提交失败,请重试'))
.always(() => { this.$set(this, 'formLoading', false) })
},
doSearch() {
this.pageForm.pageNumber = 1
@@ -336,7 +354,7 @@ layout("/mobile/platform.html"){
this.finished = false
this.onLoad()
},
async onLoad() {
onLoad() {
this.loading = true
$.post('/platform/sys/club/sh/pageData', this.pageForm).then(res => {
if (res.code === 0) {
@@ -347,26 +365,51 @@ layout("/mobile/platform.html"){
this.pageForm.pageNumber++
}
}
this.loading = false
})
}).always(() => { this.$set(this, 'loading', false) })
},
async getMyMangeClub() {
const {data} = await $.get("/platform/sys/club/users/getMyMangeClub")
return data
getMyMangeClub() {
return $.get('/platform/sys/club/users/getMyMangeClub').then(res => res.data || [])
},
},
async created() {
const data = await this.getMyMangeClub()
data.forEach(item => {
this.clubOptions.push({text: item.name, value: item.id})
created() {
this.$options.popupSession = window.mobilePopupHistory.register('clubAudit', this.syncPopup)
// PJAX 离开前清理本页历史,再执行原跳转,防止返回到已关闭弹框。
$(document).on('pjax:beforeSend.clubAudit', (event, xhr, options) => {
if (this.infoShow || this.signatureShow) {
event.preventDefault()
this.$options.popupSession.clear().then(() => {
this.disposePopup()
pjaxReplace(options.url)
})
return false
}
this.disposePopup()
})
this.clubOptions.unshift({text: '全部社团'})
const userType = await getDictOptions('UserType')
userType.forEach(item => {
this.userTypeOptions.push({text: item.name, value: item.code})
// PJAX 不会自动销毁旧页面实例,替换容器前释放本页会话及事件。
$(document).one('pjax:beforeReplace.clubAudit', () => this.$destroy())
this.getMyMangeClub().then(data => {
this.$set(this, 'clubOptions', [{text: '全部社团'}].concat(
data.map(item => ({text: item.name, value: item.id}))))
})
this.userTypeOptions.unshift({text: '人员类型'})
getDictOptions('UserType').then(data => {
this.$set(this, 'userTypeOptions', [{text: '人员类型'}].concat(
data.map(item => ({text: item.name, value: item.code}))))
})
// 首页已办传 audit=1、applyId;复用现有详情弹框和全局历史栈,只读展示审核结果。
if (GetQueryString('audit') === '1') {
this.$set(this.pageForm, 'radioType', 2)
const applyId = GetQueryString('applyId')
if (applyId) this.openView({id: applyId})
}
this.onLoad()
},
beforeDestroy() {
// 空栈同步注销,防止下一页注册时旧会话尚未释放。
if (this.infoShow || this.signatureShow) {
this.$options.popupSession.clear().then(() => this.disposePopup())
} else {
this.disposePopup()
}
}
})
</script>
+55 -6
View File
@@ -353,7 +353,7 @@ layout("/mobile/platform.html"){
description="暂无待办"
image="https://img01.yzcdn.cn/vant/custom-empty-image.png"
style="margin-top: 44px"
v-if="needItems.length === 0"
v-if="needItems.length === 0 && !clubTodoLoading"
></van-empty>
</div>
</van-tab>
@@ -376,7 +376,7 @@ layout("/mobile/platform.html"){
description="暂无已办"
image="https://img01.yzcdn.cn/vant/custom-empty-image.png"
style="margin-top: 44px"
v-if="completedItems.length === 0"
v-if="completedItems.length === 0 && !clubCompletedLoading"
></van-empty>
</div>
</van-tab>
@@ -510,6 +510,10 @@ layout("/mobile/platform.html"){
active: 0,
needCount: 0,
needItems: [],
clubTodoLoading: false,
clubTodoVersion: 0,
clubCompletedLoading: false,
clubCompletedVersion: 0,
activeName: '0',
completedItems: [],
bigPictureList: [],
@@ -555,14 +559,18 @@ layout("/mobile/platform.html"){
tabbrChange(o) {
console.log(o)
sessionStorage.setItem("zhgh-mobile-home-active",o)
this.needItems = []
this.initData()
},
toUrl(o) {
pjaxReplace(o.mobileHref)
},
toHref(o) {
pjaxReplace(o.mobileUrl + '?audit=1');
// 协会已办携带申请 ID,直接以查看模式打开该申请,不进入待审核列表。
if (o.businessType === 'clubApply') {
pjaxReplace(o.mobileUrl + '?audit=1&applyId=' + encodeURIComponent(o.applyId))
return
}
pjaxReplace(o.mobileUrl + '?audit=1');
},
async getNeedItems() {
const {data, code} = await $.get('/platform/needItems/getNeedItems')
@@ -599,14 +607,55 @@ layout("/mobile/platform.html"){
this.needItems = this.needItems.concat(data.filter(v => v.count > 0))
}
},
async initData() {
// 无参数;data 返回协会个人待办卡片。每次替换协会卡片,保留其他业务待办。
getClubTodo() {
const version = this.clubTodoVersion + 1
this.$set(this, 'clubTodoVersion', version)
this.$set(this, 'clubTodoLoading', true)
return $.post('/platform/sys/club/sh/mobileTodo', {}).then(res => {
if (version !== this.clubTodoVersion) return
if (res.code === 0) {
const otherItems = this.needItems.filter(item => item.mobileHref !== '/platform/sys/club/sh/mobile')
this.$set(this, 'needItems', otherItems.concat(res.data || []))
this.$set(this, 'needCount', this.needItems.reduce((total, item) => total + (Number(item.count) || 0), 0))
} else {
vant.Toast(res.msg || '协会待办加载失败,请重试')
}
}).fail(() => {
if (version === this.clubTodoVersion) vant.Toast('协会待办加载失败,请重试')
}).always(() => {
if (version === this.clubTodoVersion) this.$set(this, 'clubTodoLoading', false)
})
},
// 已办按实际审核人查询,刷新替换而非追加,避免页签切换产生重复卡片。
getClubCompleted() {
const version = this.clubCompletedVersion + 1
this.$set(this, 'clubCompletedVersion', version)
this.$set(this, 'clubCompletedLoading', true)
return $.post('/platform/sys/club/sh/mobileCompleted', {}).then(res => {
if (version !== this.clubCompletedVersion) return
if (res.code === 0) {
const otherItems = this.completedItems.filter(item => item.businessType !== 'clubApply')
this.$set(this, 'completedItems', otherItems.concat(res.data || []))
} else {
vant.Toast(res.msg || '协会已办加载失败,请重试')
}
}).fail(() => {
if (version === this.clubCompletedVersion) vant.Toast('协会已办加载失败,请重试')
}).always(() => {
if (version === this.clubCompletedVersion) this.$set(this, 'clubCompletedLoading', false)
})
},
initData() {
// await this.getNeedItems();
// await this.getMemberCheckSelfAgenda()
// await this.getWelfareCheckSelfAgenda()
// await this.getCompletes();
// await this.getMsg()
this.listActivity();
this.needCount = this.needItems.length
// 首页首次进入、办理页签切换及 pageshow 返回均走此入口,刷新已处理数量。
this.getClubTodo()
this.getClubCompleted()
},
async setNeedItems() {
const {data, code} = await $.get('/platform/needItems/getNeedItems')
@@ -114,7 +114,7 @@ layout("/layouts/platform.html"){
prop="rylx"></el-table-column>
<el-table-column width="100" align="center" header-align="center" label="在职状态"
prop="userstate"></el-table-column>-->
<el-table-column align="center" header-align="center" label="所属单位" prop="unitname"
<el-table-column align="center" header-align="center" label="所属单位" prop="applicantunitname"
show-overflow-tooltip
sortable></el-table-column>
<el-table-column align="center" header-align="center" label="申请时间" prop="join_time"
@@ -141,7 +141,7 @@ layout("/layouts/platform.html"){
<template scope="{row}">
<el-button @click="openView(row)" size="mini" type="primary">查看</el-button>
<el-button v-if="pageForm.radioType == 3" @click="openAudit(row)" size="mini" type="primary">审核</el-button>
<el-button type="primary" size="mini" @click="doExportApply(row)">导出申请表</el-button>
<!-- <el-button type="primary" size="mini" @click="doExportApply(row)">导出申请表</el-button> -->
</template>
</el-table-column>
@@ -190,14 +190,14 @@ layout("/layouts/platform.html"){
</el-form-item>
<el-form-item prop="auditSign" label="签&emsp;&emsp;字">
<sign prefix="club" :qz.sync="formData.auditSign"></sign>
<sign :is_value_base64="false" prefix="club" :qz.sync="formData.auditSign"></sign>
</el-form-item>
</el-form>
<div style="float: right;margin: 20px 0">
<el-button @click="doReview(false)">拒绝</el-button>
<el-button type="primary" @click="doReview(true)">通过</el-button>
<el-button :loading="formLoading" @click="doReview(false)">拒绝</el-button>
<el-button :loading="formLoading" type="primary" @click="doReview(true)">通过</el-button>
</div>
</template>
@@ -219,6 +219,7 @@ layout("/layouts/platform.html"){
tabKey: '',
clubOptions: [],
tabloading: false,
formLoading: false,
formData: {
sign: ''
},
@@ -242,7 +243,7 @@ layout("/layouts/platform.html"){
}
},
components: {
'club-user-apply-info': httpVueLoader('/components/club/clubUserApplyInfo.vue?v=1.0.1'),
'club-user-apply-info': httpVueLoader('/components/club/clubUserApplyInfo.vue?v=1.0.2'),
'dict-select': httpVueLoader('/components/plugins/DictSelect.vue?v=1.0.0'),
},
methods: {
@@ -273,18 +274,39 @@ layout("/layouts/platform.html"){
}
this.$refs.guava.edit()
},
async doReview(flag) {
const valid = await this.$refs.form.validate()
if (!valid) return
this.formData.flag = flag
const resp = await $.post(base + "/platform/sys/club/sh/check", this.formData)
if(resp.code === 0) {
this.doSearch()
this.$refs.guava.index()
this.$message({message: resp.msg, type: 'success'})
} else {
this.$message({message: resp.msg, type: 'warning'})
}
doReview(flag) {
if (this.formLoading) return
this.$refs.form.validate((valid) => {
if (!valid || this.formLoading) return
this.$set(this, 'formLoading', true)
this.$set(this.formData, 'flag', flag)
// 公共签字组件可能读取当前用户的个人 Base64 签名,先上传为路径再提交审核。
// uploadSignData 的 data 参数为 PNG Base64,成功响应的 data 为文件路径。
const sign = this.formData.auditSign
const signRequest = sign.indexOf('data:image/png;base64,') === 0
? $.post('/signature/uploadSignData', {data: sign}).then((res) => {
return res.code === 0 && res.data
? $.Deferred().resolve(res.data).promise()
: $.Deferred().reject(res.msg || '签字上传失败').promise()
})
: $.Deferred().resolve(sign).promise()
signRequest.then((path) => {
this.$set(this.formData, 'auditSign', path)
return $.post(base + '/platform/sys/club/sh/check', this.formData)
}).then((resp) => {
if (resp.code === 0) {
this.doSearch()
this.$refs.guava.index()
this.$message({message: resp.msg, type: 'success'})
} else {
this.$message({message: resp.msg, type: 'warning'})
}
}).fail((error) => {
this.$message.error(typeof error === 'string' ? error : '审核提交失败,请重试')
}).always(() => {
this.$set(this, 'formLoading', false)
})
})
},
doSearch() {
this.pageForm.pageNumber = 1