276 lines
17 KiB
HTML
276 lines
17 KiB
HTML
<!--# layout("/mobile/platform.html"){ #-->
|
||
<style>
|
||
/* 日期和每个时间段保持完整,同日连续场次由后端统一合并展示。 */
|
||
.site-audit .reservation-times { text-align:left; font-variant-numeric:tabular-nums; }
|
||
.site-audit .reservation-time-day { display:flex; align-items:baseline; flex-wrap:wrap; gap:4px 12px; padding:3px 0; line-height:22px; }
|
||
.site-audit .reservation-time-date { flex:0 0 88px; white-space:nowrap; font-weight:500; }
|
||
.site-audit .reservation-time-ranges { display:flex; flex-wrap:wrap; gap:2px 12px; }
|
||
.site-audit .reservation-time-range { white-space:nowrap; }
|
||
.site-audit .reservation-times { padding:4px 0 6px; font-size:14px; overflow-wrap:normal; }
|
||
.site-audit .reservation-time-day { display:block; padding:4px 0; }
|
||
.site-audit .reservation-time-date { display:block; color:#606266; }
|
||
|
||
.site-audit .van-doc-card { margin:14px; padding:12px; background:#fff; border-radius:10px; box-shadow:0 8px 12px #ebedf0; line-height:20px; font-size:15px; position:relative; }
|
||
.site-audit .card-title { font-size:18px; font-weight:bold; color:#1867b0; padding-left:4px; }
|
||
.site-audit .title-mark { position:absolute; left:0; top:12px; color:#1867b0; font-weight:bold; }
|
||
.site-audit .card-fields { margin-top:4px; line-height:30px; overflow-wrap:anywhere; }
|
||
.site-audit .field-label { color:grey; }
|
||
.site-audit .van-divider { margin:6px 0; }
|
||
.site-audit .card-footer { display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:8px; }
|
||
.site-audit .card-footer .van-button { border-radius:8px; height:26px; font-size:16px; margin-left:4px; }
|
||
.site-audit .process-title { color:#1867b0; font-size:15px; font-weight:600; margin:10px 7px; padding:10px; }
|
||
.site-audit .audit-actions { display:flex; gap:10px; padding:10px; }
|
||
.site-audit .site-audit-section { color:#1867b0; font-weight:bold; font-size:16px; }
|
||
.site-audit .site-audit-loading { padding:24px; text-align:center; }
|
||
.site-audit .site-audit-text .van-cell__label { white-space:pre-wrap; overflow-wrap:anywhere; }
|
||
.site-audit .detail-popup { background:#f7f8fa; }
|
||
</style>
|
||
<div id="app" class="site-audit" v-cloak>
|
||
<van-sticky>
|
||
<van-nav-bar title="${reviewTitle}" left-text="返回" left-arrow @click-left="goBack"></van-nav-bar>
|
||
<van-search v-model="keyword" placeholder="请输入场地名称或申请人" @search="search" @clear="search"></van-search>
|
||
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
|
||
<van-dropdown-item ref="auditFilter" v-model="isAudit" :options="auditOptions" @change="search"
|
||
@open="filterOpened" @close="filterClosed"></van-dropdown-item>
|
||
</van-dropdown-menu>
|
||
</van-sticky>
|
||
<van-list v-model="loading" :finished="finished" :error.sync="listError" error-text="加载失败,点击重试"
|
||
:immediate-check="false" :finished-text="rows.length ? '没有更多了' : ''" @load="loadMore">
|
||
<div v-for="row in rows" :key="row.id" class="van-doc-card">
|
||
<div class="card-title van-ellipsis"><span class="title-mark">|</span>{{row.reserve_person}}({{row.loginname}})</div>
|
||
<div class="card-fields">
|
||
<div><span class="field-label">场地名称:</span>{{row.site_name}}</div>
|
||
<div><span class="field-label">所属单位:</span>{{row.reserve_person_unit || '—'}}</div>
|
||
<div><span class="field-label">预约类型:</span>{{typeName(row.reserve_type)}}</div>
|
||
<div v-if="row.club_name"><span class="field-label">所属协会:</span>{{row.club_name}}</div>
|
||
<div><span class="field-label">预约时段:</span><div class="reservation-times">
|
||
<div v-for="group in row.reservationTimeGroups" :key="group.date" class="reservation-time-day">
|
||
<span class="reservation-time-date">{{group.date}}</span>
|
||
<div class="reservation-time-ranges"><span v-for="(range,index) in group.ranges" :key="index" class="reservation-time-range">{{range.start}}–{{range.end}}</span></div>
|
||
</div>
|
||
<span v-if="!row.reservationTimeGroups || !row.reservationTimeGroups.length">—</span>
|
||
</div></div>
|
||
</div>
|
||
<van-divider></van-divider>
|
||
<div class="card-footer">
|
||
<!-- 仅状态内容绑定配置颜色,保留字段标签原有灰色。 -->
|
||
<div><span class="field-label">当前状态:</span><span :style="{color: row.state_color || null}">{{row.state_name}}</span></div>
|
||
<div>
|
||
<van-button size="small" type="info" @click="openDetail(row,false)">查看</van-button>
|
||
<van-button v-if="canReview(row)" size="small" type="primary" @click="openDetail(row,true)">审核</van-button>
|
||
<van-button v-if="isAudit===true" size="small" type="danger" :disabled="!row.canRevoke || formLoading"
|
||
:loading="formLoading && revokeId===row.id" @click="openRevoke(row)">撤回</van-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</van-list>
|
||
<van-empty v-if="!loading && !listError && !rows.length" image="/none.svg" description="暂无数据"></van-empty>
|
||
<van-popup v-model="detailShow" position="right" :style="{height:'100%',width:'100%'}" class="detail-popup" safe-area-inset-bottom>
|
||
<van-sticky>
|
||
<van-nav-bar title="申请详情" left-text="返回" left-arrow @click-left="goBack"></van-nav-bar>
|
||
</van-sticky>
|
||
<site-audit-info ref="infoRef" :review-api="reviewApi" @loaded="onDetailLoaded"></site-audit-info>
|
||
<div v-if="showApprovalForm">
|
||
<div class="process-title">${reviewTitle}</div>
|
||
<van-form @submit="openConfirm(true)">
|
||
<van-field label="审核人员" readonly v-model="formData.username"></van-field>
|
||
<van-field label="当前时间" readonly v-model="formData.auditTime"></van-field>
|
||
<van-field label="审核意见" type="textarea" maxlength="500" show-word-limit required
|
||
v-model="formData.auditOpinion" placeholder="请输入审核意见"></van-field>
|
||
</van-form>
|
||
<div class="audit-actions">
|
||
<van-button block type="default" :disabled="formLoading" @click="goBack">取消</van-button>
|
||
<van-button block type="danger" :disabled="!auditReady || formLoading" :loading="formLoading && !pendingPass" @click="openConfirm(false)">拒绝</van-button>
|
||
<van-button block type="primary" :disabled="!auditReady || formLoading" :loading="formLoading && pendingPass" @click="openConfirm(true)">通过</van-button>
|
||
</div>
|
||
</div>
|
||
</van-popup>
|
||
<van-dialog v-model="confirmShow" title="提示" show-cancel-button :before-close="beforeConfirmClose"
|
||
:message="pendingPass ? '确定通过该申请吗?' : '确定拒绝该申请吗?拒绝后流程结束。'">
|
||
</van-dialog>
|
||
<van-dialog v-model="revokeShow" title="撤回审核" show-cancel-button :before-close="beforeRevokeClose"
|
||
message="确定撤回本次审核吗?撤回后申请恢复到当前节点待审核。"></van-dialog>
|
||
</div>
|
||
<script>
|
||
(() => {
|
||
// PJAX 先执行内联脚本;捕获本次页面,依赖加载完成后才允许创建 Vue。
|
||
const pageRoot = document.getElementById('app')
|
||
const startPage = () => {
|
||
if (document.getElementById('app') !== pageRoot || !pageRoot.isConnected) return
|
||
<!--# include('./common/info.js'){} #-->
|
||
new Vue({
|
||
el: '#app',
|
||
mixins: [window.popupHistoryMixin],
|
||
components: {'site-audit-info': SITE_AUDIT_INFO},
|
||
data() {
|
||
return {
|
||
historyPopupKeys: ['filterShow','detailShow','confirmShow','revokeShow'],
|
||
revokeShow:false, revokeId:'',
|
||
reviewApi: '${reviewApi}',
|
||
auditOptions: [{text:'已审核',value:true},{text:'未审核',value:false}],
|
||
isAudit: false, keyword: '', rows: [], page: 1,
|
||
loading: false, finished: false, listError: false, requestVersion: 0, requesting: false,
|
||
filterShow: false, detailShow: false, confirmShow: false,
|
||
showApprovalForm: false, auditReady: false, pendingPass: false, formLoading: false,
|
||
formData: {id:'',username:'',auditTime:'',auditOpinion:''}
|
||
}
|
||
},
|
||
watch: {
|
||
// DropdownItem 没有弹层 v-model,通过全局历史状态同步关闭内部下拉层。
|
||
filterShow(value) { if (!value && this.$refs.auditFilter) this.$refs.auditFilter.toggle(false) }
|
||
},
|
||
methods: {
|
||
openRevoke(row) {
|
||
if (this.formLoading || !row.canRevoke) return
|
||
this.$set(this, 'revokeId', row.id)
|
||
this.$set(this, 'revokeShow', true)
|
||
},
|
||
// 确认框也进入历史栈;失败保留弹框,请求结束复位 Vant 内部 loading。
|
||
beforeRevokeClose(action, done) {
|
||
if (this.formLoading) { done(false); return }
|
||
if (action!=='confirm') { done(false); this.goBack(); return }
|
||
this.$set(this, 'formLoading', true)
|
||
$.post(this.reviewApi + '/doRevoke', {id:this.revokeId}).then((res) => {
|
||
if (res && res.code===0) {
|
||
this.clearPopupHistory(() => { this.$toast.success('已撤回审核'); this.search() })
|
||
} else { this.$toast.fail(res && res.msg || '响应异常,请刷新核对申请状态') }
|
||
}, () => { this.$toast.fail('请求失败,请刷新核对申请状态') }).always(() => {
|
||
this.$set(this, 'formLoading', false)
|
||
done(false)
|
||
})
|
||
},
|
||
// 菜单采用 PJAX replace 进入,列表返回应回手机首页,不能依赖上一条浏览器历史。
|
||
// 弹框存在时通过统一栈关闭最上层,重复点击也不会连续退过业务页面。
|
||
goBack() {
|
||
if (this.formLoading) return
|
||
const key = this.revokeShow ? 'revokeShow' : this.confirmShow ? 'confirmShow' : this.detailShow ? 'detailShow' : this.filterShow ? 'filterShow' : null
|
||
if (key) {
|
||
window.popupHistory.close(this._popupOwner, key)
|
||
} else {
|
||
this.clearPopupHistory(() => pjaxReplace('/mobile/index'))
|
||
}
|
||
},
|
||
typeName(type) { return {1:'个人预约',2:'分工会预约',3:'协会预约'}[type] || '—' },
|
||
filterOpened() { this.$set(this, 'filterShow', true) },
|
||
filterClosed() { this.$set(this, 'filterShow', false) },
|
||
canReview(row) {
|
||
const stage = this.reviewApi.substring(this.reviewApi.lastIndexOf('/') + 1)
|
||
return Number(row.reserve_state) === {union:4000,club:4010,school:4020}[stage]
|
||
},
|
||
search() {
|
||
// 筛选变更使旧响应失效,避免未审核和已审核数据混入同一列表。
|
||
this.$set(this, 'requestVersion', this.requestVersion + 1)
|
||
this.$set(this, 'requesting', false)
|
||
this.$set(this, 'rows', [])
|
||
this.$set(this, 'page', 1)
|
||
this.$set(this, 'finished', false)
|
||
this.$set(this, 'listError', false)
|
||
this.loadMore()
|
||
},
|
||
// isAudit 为审核状态,pageNumber/pageSize 为分页;响应 data 含 list 和 totalCount。
|
||
loadMore() {
|
||
if (this.requesting || this.finished) return
|
||
const version = this.requestVersion
|
||
this.$set(this, 'requesting', true)
|
||
this.$set(this, 'loading', true)
|
||
$.post(this.reviewApi + '/pageData', {
|
||
isAudit: this.isAudit, searchKeyword: this.keyword, pageNumber: this.page, pageSize: 10
|
||
}).then((res) => {
|
||
if (version !== this.requestVersion) return
|
||
if (res.code === 0) {
|
||
this.$set(this, 'rows', this.rows.concat(res.data.list))
|
||
this.$set(this, 'page', this.page + 1)
|
||
this.$set(this, 'finished', this.rows.length >= res.data.totalCount)
|
||
} else {
|
||
this.$set(this, 'listError', true)
|
||
this.$toast.fail(res.msg)
|
||
}
|
||
}).fail(() => {
|
||
if (version === this.requestVersion) this.$set(this, 'listError', true)
|
||
}).always(() => {
|
||
if (version === this.requestVersion) {
|
||
this.$set(this, 'loading', false)
|
||
this.$set(this, 'requesting', false)
|
||
}
|
||
})
|
||
},
|
||
// 进入审核时默认同意;仅查看时不填意见,切换申请不保留上一条输入。
|
||
openDetail(row, approval) {
|
||
this.$set(this, 'auditReady', false)
|
||
this.$set(this, 'showApprovalForm', approval)
|
||
this.$set(this, 'formData', {
|
||
id: row.id, username: "${@shiro.getPrincipalProperty('username')}",
|
||
auditTime: moment().format('YYYY-MM-DD HH:mm:ss'), auditOpinion: approval ? '同意' : ''
|
||
})
|
||
this.$set(this, 'detailShow', true)
|
||
this.$nextTick(() => this.$refs.infoRef.onOpen(row.id))
|
||
},
|
||
onDetailLoaded(detail) {
|
||
this.$set(this, 'auditReady', detail.id === this.formData.id && this.canReview(detail))
|
||
},
|
||
openConfirm(isPass) {
|
||
if (this.formLoading || !this.auditReady) return
|
||
if (!this.formData.auditOpinion.trim()) { this.$toast.fail('请填写审核意见'); return }
|
||
this.$set(this, 'pendingPass', isPass)
|
||
this.$set(this, 'confirmShow', true)
|
||
},
|
||
// 确认时保留 Dialog,请求收尾通过 done(false) 复位其内部 loading;取消只退一层历史。
|
||
beforeConfirmClose(action, done) {
|
||
if (this.formLoading) { done(false); return }
|
||
if (action === 'confirm') this.review(done)
|
||
else { done(false); this.goBack() }
|
||
},
|
||
// ids 为预约 ID 数组,isPass 为通过/拒绝;响应应为统一 code/msg,空响应不能视为成功。
|
||
review(dialogDone = () => {}) {
|
||
if (this.formLoading || !this.auditReady) { dialogDone(false); return }
|
||
const opinion = (this.formData.auditOpinion || '').trim()
|
||
if (!opinion) { dialogDone(false); this.$toast.fail('请填写审核意见'); return }
|
||
this.$set(this, 'formLoading', true)
|
||
$.post(this.reviewApi + '/doReview', {
|
||
ids: JSON.stringify([this.formData.id]), isPass: this.pendingPass,
|
||
auditOpinion: opinion
|
||
}).then((res) => {
|
||
// jQuery 1.x 的 then 回调异常可能中断后续链,先检查空或非标准响应再读取字段。
|
||
if (!res || typeof res !== 'object' || typeof res.code !== 'number') {
|
||
this.$toast.fail('审核响应异常,请刷新核对申请状态')
|
||
return
|
||
}
|
||
if (res.code === 0) {
|
||
this.$set(this, 'auditReady', false)
|
||
this.clearPopupHistory(() => { this.$toast.success('审核成功'); this.search() })
|
||
} else { this.$toast.fail(res.msg || '审核失败,请刷新核对申请状态') }
|
||
}).fail(() => { this.$toast.fail('提交失败,请刷新核对申请状态') }).always(() => {
|
||
// 同时复位表单按钮及 Vant Dialog 内部按钮,失败时保留意见供核对。
|
||
this.$set(this, 'formLoading', false)
|
||
dialogDone(false)
|
||
})
|
||
}
|
||
},
|
||
created() { this.search() },
|
||
mounted() {
|
||
// PJAX 替换 DOM 不会自动销毁 Vue,离开时注销弹框历史同步及本页事件。
|
||
this._siteAuditDispose = () => this.$destroy()
|
||
$(document).one('pjax:beforeReplace.siteAudit', this._siteAuditDispose)
|
||
},
|
||
beforeDestroy() {
|
||
$(document).off('pjax:beforeReplace.siteAudit', this._siteAuditDispose)
|
||
this.$set(this, 'requestVersion', this.requestVersion + 1)
|
||
}
|
||
})
|
||
}
|
||
if (window.popupHistoryMixin) {
|
||
startPage()
|
||
} else {
|
||
// 共用一次加载请求;失败后允许重新进入重试,不能在缺少 mixin 时继续挂载。
|
||
if (!window.siteAuditHistoryLoading) {
|
||
window.siteAuditHistoryLoading = $.getScript('/assets/mobile/js/popupHistory.js')
|
||
.fail(() => { window.siteAuditHistoryLoading = null })
|
||
}
|
||
window.siteAuditHistoryLoading.then(() => startPage()).fail(() => {
|
||
if (document.getElementById('app') === pageRoot) vant.Toast.fail('页面组件加载失败,请重新进入')
|
||
})
|
||
}
|
||
})()
|
||
</script>
|
||
<!--# } #-->
|