commit
This commit is contained in:
+21
-2
@@ -1,6 +1,7 @@
|
||||
package com.budwk.app.zhgh.democratic.proposal.controller.transact;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.annotation.SaMode;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.ExportTableColumns;
|
||||
@@ -55,8 +56,26 @@ public class ProposalCaseCheckController {
|
||||
public void index() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开提案委员会审查手机端列表页。
|
||||
*/
|
||||
@At("/h5")
|
||||
@Ok("beetl:/platform/zhghh5/democratic/proposal/transact/caseCheck/index.html")
|
||||
@SaCheckPermission("h5.proposal.caseCheck")
|
||||
public void h5Index() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开提案委员会审查手机端详情及审核页。
|
||||
*/
|
||||
@At("/info/h5")
|
||||
@Ok("beetl:/platform/zhghh5/democratic/proposal/transact/caseCheck/info.html")
|
||||
@SaCheckPermission("h5.proposal.caseCheck")
|
||||
public void h5Info() {
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("proposal.caseCheck")
|
||||
@SaCheckPermission(value = {"proposal.caseCheck", "h5.proposal.caseCheck"}, mode = SaMode.OR)
|
||||
@ApiOperation("分页列表")
|
||||
public Result pageData(@Valid ProposalSearchParam pageForm, boolean approval) {
|
||||
Sql sql = Sqls.create("""
|
||||
@@ -129,7 +148,7 @@ public class ProposalCaseCheckController {
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("proposal.caseCheck")
|
||||
@SaCheckPermission(value = {"proposal.caseCheck", "h5.proposal.caseCheck"}, mode = SaMode.OR)
|
||||
@ApiOperation("查询当前提案承办单位")
|
||||
public Result listReplyUnits(String proposalId) {
|
||||
return Result.success(proposalCaseCheckService.listReplyUnits(proposalId));
|
||||
|
||||
+6
@@ -49,6 +49,12 @@ public class ProposalCommissionerController {
|
||||
public void h5Index() {
|
||||
}
|
||||
|
||||
@At("/info/h5")
|
||||
@Ok("beetl:/platform/zhghh5/democratic/proposal/transact/commissioner/info.html")
|
||||
@SaCheckPermission("h5.proposal.commissioner")
|
||||
public void h5Info() {
|
||||
}
|
||||
|
||||
|
||||
@At
|
||||
@SaCheckPermission("proposal.commissioner")
|
||||
|
||||
+6
@@ -67,6 +67,12 @@ public class ProposalDelegationController {
|
||||
public void h5Index() {
|
||||
}
|
||||
|
||||
@At("/info/h5")
|
||||
@Ok("beetl:/platform/zhghh5/democratic/proposal/transact/delegation/info.html")
|
||||
@SaCheckPermission("h5.proposal.delegation")
|
||||
public void h5Info() {
|
||||
}
|
||||
|
||||
|
||||
@At
|
||||
@SaCheckPermission("proposal.delegation")
|
||||
|
||||
+6
@@ -54,6 +54,12 @@ public class ProposalFeedbackEvaluationController {
|
||||
public void h5Index() {
|
||||
}
|
||||
|
||||
@At("/info/h5")
|
||||
@Ok("beetl:/platform/zhghh5/democratic/proposal/transact/feedbackEvaluation/info.html")
|
||||
@SaCheckPermission("h5.proposal.feedbackEvaluation")
|
||||
public void h5Info() {
|
||||
}
|
||||
|
||||
|
||||
@At
|
||||
@SaCheckPermission("proposal.feedbackEvaluation")
|
||||
|
||||
+6
@@ -107,6 +107,12 @@ public class ProposalMineController {
|
||||
public void h5Index() {
|
||||
}
|
||||
|
||||
@At("/info/h5")
|
||||
@Ok("beetl:/platform/zhghh5/democratic/proposal/transact/mine/info.html")
|
||||
@SaCheckPermission("h5.proposal.mine")
|
||||
public void h5Info() {
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("proposal.mine")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
|
||||
+12
@@ -52,6 +52,18 @@ public class ProposalPreAuditController {
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At("/h5")
|
||||
@Ok("beetl:/platform/zhghh5/democratic/proposal/transact/preAudit/index.html")
|
||||
@SaCheckPermission("proposal.preAudit")
|
||||
public void h5Index() {
|
||||
}
|
||||
|
||||
@At("/info/h5")
|
||||
@Ok("beetl:/platform/zhghh5/democratic/proposal/transact/preAudit/info.html")
|
||||
@SaCheckPermission("h5.proposal.preAudit")
|
||||
public void h5Info() {
|
||||
}
|
||||
|
||||
|
||||
@At
|
||||
@SaCheckPermission("proposal.preAudit")
|
||||
|
||||
+6
@@ -74,6 +74,12 @@ public class ProposalSchoolLeaderApprovalController {
|
||||
public void h5Index() {
|
||||
}
|
||||
|
||||
@At("/info/h5")
|
||||
@Ok("beetl:/platform/zhghh5/democratic/proposal/transact/schoolLeaderApproval/info.html")
|
||||
@SaCheckPermission("h5.proposal.schoolLeaderApproval")
|
||||
public void h5Info() {
|
||||
}
|
||||
|
||||
|
||||
@At
|
||||
@SaCheckPermission("proposal.schoolLeaderApproval")
|
||||
|
||||
+6
@@ -88,6 +88,12 @@ public class ProposalSecondedController {
|
||||
public void h5Index() {
|
||||
}
|
||||
|
||||
@At("/info/h5")
|
||||
@Ok("beetl:/platform/zhghh5/democratic/proposal/transact/seconded/info.html")
|
||||
@SaCheckPermission("h5.proposal.seconded")
|
||||
public void h5Info() {
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("proposal.seconded")
|
||||
public Result pageData(@Valid ProposalSearchParam pageForm, boolean approval) {
|
||||
|
||||
+6
@@ -76,6 +76,12 @@ public class ProposalUnderTakeReplyController {
|
||||
public void h5Index() {
|
||||
}
|
||||
|
||||
@At("/info/h5")
|
||||
@Ok("beetl:/platform/zhghh5/democratic/proposal/transact/unitReply/info.html")
|
||||
@SaCheckPermission("h5.proposal.unitReply")
|
||||
public void h5Info() {
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("proposal.unitReply")
|
||||
@ApiOperation("分页列表")
|
||||
|
||||
+6
@@ -62,6 +62,12 @@ public class ProposalUndertakeSuggestionController {
|
||||
public void h5Index() {
|
||||
}
|
||||
|
||||
@At("/info/h5")
|
||||
@Ok("beetl:/platform/zhghh5/democratic/proposal/transact/suggestion/info.html")
|
||||
@SaCheckPermission("h5.proposal.suggestion")
|
||||
public void h5Info() {
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission(value = {"proposal.suggestion", "h5.proposal.suggestion"}, mode = SaMode.OR)
|
||||
@ApiOperation("分页列表")
|
||||
|
||||
+6
@@ -42,6 +42,12 @@ public class ProposalViceDelegationController {
|
||||
public void h5Index() {
|
||||
}
|
||||
|
||||
@At("/info/h5")
|
||||
@Ok("beetl:/platform/zhghh5/democratic/proposal/transact/viceDelegation/info.html")
|
||||
@SaCheckPermission("h5.proposal.vice.delegation")
|
||||
public void h5Info() {
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("proposal.vice.delegation")
|
||||
@ApiOperation("分页列表")
|
||||
|
||||
+6
@@ -19,4 +19,10 @@ public class H5ProposalQueryComprehensiveController {
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At("/info")
|
||||
@Ok("beetl:/platform/zhghh5/democratic/proposal/query/comprehensive/info.html")
|
||||
@SaCheckPermission("h5.proposal.query.comprehensive")
|
||||
public void info() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
@@ -9,7 +9,8 @@
|
||||
finished-text="没有更多了"
|
||||
@load="onLoad">
|
||||
<div class="table-list-container">
|
||||
<div v-for="(row, index) in tableData" :key="index" class="table-list-item">
|
||||
<div v-for="(row, index) in tableData" :key="index" class="table-list-item"
|
||||
:data-row-id="row && row.id ? row.id : ''">
|
||||
<slot name="header" :index="index" :row="row">
|
||||
<div class="item-header">
|
||||
<div class="item-title">{{ calcTitle(row) }}</div>
|
||||
@@ -91,6 +92,33 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 获取当前列表快照。
|
||||
* 返回值包含已加载记录、分页参数和加载完成状态,供业务页面在进入详情前暂存列表现场。
|
||||
*/
|
||||
getListState() {
|
||||
return {
|
||||
tableData: JSON.parse(JSON.stringify(this.tableData)),
|
||||
localPageForm: JSON.parse(JSON.stringify(this.localPageForm)),
|
||||
tableFinished: this.tableFinished
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 恢复列表快照。
|
||||
* state 应包含 tableData、localPageForm 和 tableFinished;返回 true 表示恢复成功。
|
||||
*/
|
||||
restoreListState(state) {
|
||||
if (!state || !Array.isArray(state.tableData)) return false
|
||||
this.$set(this, "tableData", state.tableData)
|
||||
this.$set(this, "localPageForm", {...this.localPageForm, ...(state.localPageForm || {})})
|
||||
this.$set(this, "tableFinished", !!state.tableFinished)
|
||||
this.$set(this, "tableLoading", false)
|
||||
this.$set(this, "tableRefreshing", false)
|
||||
this.$emit("update:page_form", {...this.localPageForm})
|
||||
return true
|
||||
},
|
||||
|
||||
calcTitle(row) {
|
||||
if (!this.title) return ""
|
||||
// 处理key1.key2.key3 这样的形式
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const PROPOSAL_INFO = {
|
||||
name: "ProposalInfo",
|
||||
template: /*language=HTML*/ `
|
||||
<van-action-sheet v-model="visible" title="查看详情">
|
||||
<div class="proposal-info-content">
|
||||
<div class="detail-container">
|
||||
<van-cell-group title="基本信息">
|
||||
<van-cell title="提案名称">
|
||||
@@ -178,35 +178,41 @@ const PROPOSAL_INFO = {
|
||||
</div>
|
||||
|
||||
<slot></slot>
|
||||
</van-action-sheet>
|
||||
</div>
|
||||
`,
|
||||
props: {
|
||||
// bizId 为提案主键;组件根据该参数加载提案详情和对应的已办流程记录。
|
||||
bizId: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
dicts: ["PROPOSAL_TYPE", "PROPOSAL_CASE_FILING_RESULT", "PROPOSAL_CASE_FILING_TYPE", "PROCESS_TASK_SUBMIT_TYPE", "PROPOSAL_FEEDBACK"],
|
||||
data() {
|
||||
return {
|
||||
visible:false,
|
||||
viewData: {},
|
||||
doneTasks: [],
|
||||
row: null,
|
||||
originalRow: null, // 用于保存原始数据,支持返回功能
|
||||
currentBizId: ""
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
bizId: {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
if (!value) return
|
||||
this.currentBizId = value
|
||||
this.getInfo()
|
||||
this.getDoneTasks()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 打开
|
||||
onOpen(row) {
|
||||
this.row = row
|
||||
this.visible = true
|
||||
this.getInfo()
|
||||
this.getDoneTasks()
|
||||
},
|
||||
|
||||
// 关闭
|
||||
onClose(){
|
||||
this.visible = false
|
||||
},
|
||||
|
||||
// 获取申请信息
|
||||
/**
|
||||
* 使用 currentBizId 查询提案详情。
|
||||
* 接口参数 id 为提案主键;成功响应的 data 为详情对象并写入 viewData。
|
||||
*/
|
||||
getInfo() {
|
||||
this.$axios.post("/platform/proposal/common/proposalInfo", {id: this.row.id}).then((res) => {
|
||||
this.$axios.post("/platform/proposal/common/proposalInfo", {id: this.currentBizId}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.viewData = res.data
|
||||
}
|
||||
@@ -215,31 +221,19 @@ const PROPOSAL_INFO = {
|
||||
|
||||
// 查看提案
|
||||
openView(row) {
|
||||
// 如果传入的是并案对象,则打开新的详情页面
|
||||
if (row && typeof row === 'object' && row.id) {
|
||||
// 创建一个新的提案详情组件实例来显示并案详情
|
||||
this.$nextTick(() => {
|
||||
// 可以通过事件总线或者其他方式来打开新的详情页面
|
||||
// 这里暂时使用简单的方式,直接在当前组件中显示
|
||||
const currentRow = this.row;
|
||||
this.row = row;
|
||||
this.getInfo();
|
||||
// 保存原来的数据,以便返回时恢复
|
||||
this.originalRow = currentRow;
|
||||
})
|
||||
} else {
|
||||
// 原有的逻辑,通过ID查看
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.infoDialogRef) {
|
||||
this.$refs.infoDialogRef.onOpen(row)
|
||||
}
|
||||
})
|
||||
this.currentBizId = row.id
|
||||
this.getInfo()
|
||||
this.getDoneTasks()
|
||||
}
|
||||
},
|
||||
|
||||
// 获取已办任务审批记录
|
||||
/**
|
||||
* 使用 currentBizId 查询已办流程记录。
|
||||
* 接口参数 bizId 为提案主键;成功响应的 data 为流程任务数组并写入 doneTasks。
|
||||
*/
|
||||
getDoneTasks() {
|
||||
this.$axios.post("/flow/common/doneTasks", {bizId: this.row.id}).then((res) => {
|
||||
this.$axios.post("/flow/common/doneTasks", {bizId: this.currentBizId}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.doneTasks = res.data
|
||||
}
|
||||
|
||||
+2
-18
@@ -57,17 +57,8 @@ layout("/layouts/platform_h5.html"){
|
||||
<van-empty v-else image="/assets/platform/plugins/vant-green/images/nodata/nodata.png" description="暂无数据"></van-empty>
|
||||
</div>
|
||||
|
||||
<van-popup :style="{ height: '100%', width: '100%' }" position="right" safe-area-inset-bottom v-model="infoShow">
|
||||
<van-sticky>
|
||||
<van-nav-bar @click-left="infoShow=false" left-arrow left-text="返回" title="提案信息"></van-nav-bar>
|
||||
</van-sticky>
|
||||
<div style="height: calc(100vh - 44px); overflow-y: auto">
|
||||
<info ref="infoRef"></info>
|
||||
</div>
|
||||
</van-popup>
|
||||
</div>
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include("../../common/info.js"){}#-->
|
||||
const vue = new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
@@ -98,13 +89,9 @@ layout("/layouts/platform_h5.html"){
|
||||
unitOptions: [],
|
||||
moreOptions: {},
|
||||
id: "",
|
||||
formValidSuccess: false,
|
||||
infoShow: false
|
||||
formValidSuccess: false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
info: H5_PROPOSAL_INFO_COMPONENT
|
||||
},
|
||||
methods: {
|
||||
historyBack,
|
||||
sessionChange(val) {
|
||||
@@ -161,10 +148,7 @@ layout("/layouts/platform_h5.html"){
|
||||
},
|
||||
|
||||
openView(row) {
|
||||
this.infoShow = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.infoRef.onOpen(row.id)
|
||||
})
|
||||
this.$pjaxReplace("/platform/h5/proposal/query/comprehensive/info?bizId=" + encodeURIComponent(row.id))
|
||||
},
|
||||
|
||||
pageData() {
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="提案信息" placeholder fixed></van-nav-bar>
|
||||
<proposal-info v-if="bizId" :biz-id="bizId"></proposal-info>
|
||||
</div>
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include("../../common/info.js"){}#-->
|
||||
const vue = new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
components: {
|
||||
"proposal-info": PROPOSAL_INFO
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
bizId: ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
historyBack
|
||||
},
|
||||
created() {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
this.bizId = params.get("bizId") || ""
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
+678
@@ -0,0 +1,678 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
<style>
|
||||
body {
|
||||
background-color: #f3f7fd;
|
||||
}
|
||||
|
||||
.proposal-case-check-page {
|
||||
min-height: 100vh;
|
||||
padding-bottom: calc(24px + env(safe-area-inset-bottom));
|
||||
box-sizing: border-box;
|
||||
background-color: #f3f7fd;
|
||||
}
|
||||
|
||||
.proposal-case-check-page .van-nav-bar {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.proposal-case-check-page .van-nav-bar__title {
|
||||
color: #172033;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.proposal-case-check-page .van-nav-bar .van-icon,
|
||||
.proposal-case-check-page .van-nav-bar__text {
|
||||
color: #1989fa;
|
||||
}
|
||||
|
||||
.proposal-case-check-content {
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.proposal-case-check-sticky {
|
||||
padding: 22px 12px 1px;
|
||||
box-sizing: border-box;
|
||||
background-color: #f3f7fd;
|
||||
}
|
||||
|
||||
.proposal-case-check-search {
|
||||
margin: 0 0 8px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
border-radius: 12px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 5px 16px rgba(43, 73, 112, .1);
|
||||
}
|
||||
|
||||
.proposal-case-check-search .van-search__content {
|
||||
height: 42px;
|
||||
padding-left: 12px;
|
||||
align-items: center;
|
||||
border-radius: 12px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.proposal-case-check-search .van-field__control {
|
||||
color: #263548;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.proposal-case-check-search .van-field__control::placeholder {
|
||||
color: #a7b0bf;
|
||||
}
|
||||
|
||||
.proposal-case-check-filter {
|
||||
margin: 0 0 10px;
|
||||
overflow: hidden;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
|
||||
transition: border-radius .18s ease;
|
||||
}
|
||||
|
||||
.proposal-case-check-filter.is-open {
|
||||
border-radius: 10px 10px 0 0;
|
||||
}
|
||||
|
||||
.proposal-case-check-filter .van-dropdown-menu__bar,
|
||||
.proposal-case-check-filter .van-dropdown-menu__item,
|
||||
.proposal-case-check-filter .van-dropdown-menu__title {
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.proposal-case-check-filter .van-dropdown-menu__bar {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.proposal-case-check-filter .van-dropdown-menu__item,
|
||||
.proposal-case-check-filter .van-dropdown-menu__title {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.proposal-case-check-filter .van-dropdown-menu__item {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.proposal-case-check-filter .van-dropdown-menu__title {
|
||||
display: inline-flex;
|
||||
color: #66758a;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.proposal-case-check-filter .van-dropdown-menu__title--active,
|
||||
.proposal-case-check-filter .van-dropdown-item__option--active,
|
||||
.proposal-case-check-filter .van-dropdown-item__option--active .van-dropdown-item__icon {
|
||||
color: #1989fa;
|
||||
}
|
||||
|
||||
.proposal-case-check-filter .van-dropdown-menu__title--down::after {
|
||||
border-color: transparent transparent #1989fa #1989fa;
|
||||
}
|
||||
|
||||
.proposal-case-check-filter .van-dropdown-item__content {
|
||||
width: calc(100% - 24px);
|
||||
margin: 0 12px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
border-radius: 0 0 12px 12px;
|
||||
box-shadow: 0 10px 24px rgba(31, 65, 108, .14);
|
||||
}
|
||||
|
||||
.proposal-case-check-filter .van-dropdown-item__option {
|
||||
display: flex;
|
||||
min-height: 48px;
|
||||
padding: 0 16px;
|
||||
align-items: center;
|
||||
color: #46566c;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.proposal-case-check-filter .van-cell::after {
|
||||
right: 16px;
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
.proposal-case-check-list .empty-state {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.proposal-case-check-empty {
|
||||
display: flex;
|
||||
min-height: 280px;
|
||||
padding: 46px 18px 36px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.proposal-case-check-empty img {
|
||||
display: block;
|
||||
width: 86%;
|
||||
max-width: 290px;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.proposal-case-check-empty__title {
|
||||
margin-top: 14px;
|
||||
color: #50627a;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.proposal-case-check-empty__hint {
|
||||
margin-top: 5px;
|
||||
color: #9aa7b8;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.proposal-case-check-card-header {
|
||||
display: flex;
|
||||
margin-bottom: 7px;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.proposal-case-check-card-heading {
|
||||
min-width: 0;
|
||||
padding-right: 10px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.proposal-case-check-card-title {
|
||||
overflow: hidden;
|
||||
color: #253247;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
font-weight: 600;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.proposal-case-check-card-code {
|
||||
margin-top: 1px;
|
||||
color: #9ba6b6;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.proposal-case-check-status {
|
||||
display: inline-flex;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.proposal-case-check-status .van-tag {
|
||||
display: inline-flex;
|
||||
min-height: 24px;
|
||||
padding: 3px 9px;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
border: 0;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media (max-width: 350px) {
|
||||
.proposal-case-check-content,
|
||||
.proposal-case-check-sticky {
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div id="app" v-cloak>
|
||||
<div class="proposal-case-check-page">
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="提案委员会审查" placeholder fixed></van-nav-bar>
|
||||
<van-sticky :offset-top="46">
|
||||
<div class="proposal-case-check-sticky">
|
||||
<van-search
|
||||
v-model="pageForm.name"
|
||||
class="proposal-case-check-search"
|
||||
placeholder="请输入提案名称搜索"
|
||||
shape="round"
|
||||
clearable
|
||||
@search="doSearch"
|
||||
@clear="doSearch"
|
||||
></van-search>
|
||||
<van-dropdown-menu
|
||||
class="proposal-case-check-filter"
|
||||
:class="{'is-open': filterOpened}"
|
||||
active-color="#1989fa"
|
||||
>
|
||||
<van-dropdown-item
|
||||
v-model="pageForm.sessionId"
|
||||
:options="sessionOptions"
|
||||
:multiple="false"
|
||||
@change="doSearch"
|
||||
@open="onFilterOpen"
|
||||
@close="onFilterClose"
|
||||
></van-dropdown-item>
|
||||
<van-dropdown-item
|
||||
v-model="pageForm.approvalText"
|
||||
:options="approvalOptions"
|
||||
:multiple="false"
|
||||
@change="approvalChange"
|
||||
@open="onFilterOpen"
|
||||
@close="onFilterClose"
|
||||
></van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
</div>
|
||||
</van-sticky>
|
||||
|
||||
<main class="proposal-case-check-content">
|
||||
<div class="proposal-case-check-list">
|
||||
<!-- 列表无记录时使用提案 H5 统一空状态。 -->
|
||||
<div v-if="pageForm.totalCount === 0" class="proposal-case-check-empty">
|
||||
<img src="/assets/mobile/img/proposal/proposal-empty.png" alt="暂无提案记录插画">
|
||||
<div class="proposal-case-check-empty__title">暂无提案记录</div>
|
||||
<div class="proposal-case-check-empty__hint">当前筛选条件下暂时没有提案</div>
|
||||
</div>
|
||||
<table-list api="/platform/proposal/case/check/pageData" :page_form.sync="pageForm" @ready="onReady"
|
||||
ref="tableListRef"
|
||||
title="name">
|
||||
<template #header="{index,row}">
|
||||
<div class="proposal-case-check-card-header">
|
||||
<div class="proposal-case-check-card-heading">
|
||||
<div class="proposal-case-check-card-title">{{row.name}}</div>
|
||||
<div class="proposal-case-check-card-code">编号:{{row.code || '--'}}</div>
|
||||
</div>
|
||||
<enum-tag
|
||||
class="proposal-case-check-status"
|
||||
v-if="row.instanceState"
|
||||
:value="row.instanceState"
|
||||
name="ProcessInstanceStateEnum"
|
||||
label_key="message"
|
||||
size="small"
|
||||
></enum-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="立案编号">{{row.caseFilingCode || '--'}}</table-column>
|
||||
<table-column label="提案类别">{{row.typeName || '--'}}</table-column>
|
||||
<table-column label="提案人">{{row.createUserName || '--'}}</table-column>
|
||||
<table-column label="教代会">{{row.sessionName || '--'}}</table-column>
|
||||
<table-column label="代表团">{{row.delegationName || '--'}}</table-column>
|
||||
<table-column label="当前节点">{{row.curTaskName || '--'}}</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row,index)">
|
||||
<i class="fa fa-eye"></i>
|
||||
<span>查看</span>
|
||||
</div>
|
||||
<div class="action-btn" v-if="row.taskState === 10" @click="onApproval(row,index)">
|
||||
<i class="fa fa-edit"></i>
|
||||
<span>审核</span>
|
||||
</div>
|
||||
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)">
|
||||
<i class="fa fa-reply"></i>
|
||||
<span>撤回</span>
|
||||
</div>
|
||||
</template>
|
||||
</table-list>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
data() {
|
||||
return {
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
pageOrderName: "",
|
||||
pageOrderBy: "",
|
||||
name: null,
|
||||
sessionId: null,
|
||||
approvalText: "0",
|
||||
approval: false
|
||||
},
|
||||
sessionOptions: [],
|
||||
filterOpened: false,
|
||||
approvalOptions: [
|
||||
{text: "未审核", value: "0"},
|
||||
{text: "已审核", value: "1"}
|
||||
],
|
||||
pendingListCache: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onReady() {
|
||||
if (this.pendingListCache) {
|
||||
this.restoreListCache()
|
||||
this.listSession(true)
|
||||
return
|
||||
}
|
||||
this.listSession(false)
|
||||
},
|
||||
|
||||
// 审核状态下拉框使用字符串值展示,查询接口使用布尔值区分已审核和未审核。
|
||||
approvalChange(value) {
|
||||
this.$set(this.pageForm, "approval", value === "1")
|
||||
this.doSearch()
|
||||
},
|
||||
|
||||
onFilterOpen() {
|
||||
this.$set(this, "filterOpened", true)
|
||||
},
|
||||
|
||||
onFilterClose() {
|
||||
this.$set(this, "filterOpened", false)
|
||||
},
|
||||
|
||||
// 缓存恢复时只加载教代会选项,不覆盖离开列表前的筛选条件和列表快照。
|
||||
listSession(skipDefaultSearch) {
|
||||
this.$axios.post("/platform/proposal/common/listSession").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$set(this, "sessionOptions", [{text: "全部教代会", value: null}]
|
||||
.concat(res.data.map((item) => ({text: item.fullName, value: item.id}))))
|
||||
if (skipDefaultSearch) return
|
||||
this.$set(this.pageForm, "sessionId", res.data.length > 0 ? res.data[0].id : null)
|
||||
this.doSearch()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onView(row, index) {
|
||||
this.saveListCache(row, index)
|
||||
this.$pjaxReplace("/platform/proposal/case/check/info/h5?bizId=" + encodeURIComponent(row.id) + "&action=view")
|
||||
},
|
||||
|
||||
onApproval(row, index) {
|
||||
this.saveListCache(row, index)
|
||||
const params = new URLSearchParams()
|
||||
params.set("bizId", row.id)
|
||||
params.set("action", "approval")
|
||||
params.set("taskId", row.taskId)
|
||||
params.set("taskName", row.curTaskName || row.taskName || "提案委员会审查")
|
||||
this.$pjaxReplace("/platform/proposal/case/check/info/h5?" + params.toString())
|
||||
},
|
||||
|
||||
getListCacheKey() {
|
||||
return "proposal:case-check:h5:list-state"
|
||||
},
|
||||
|
||||
getListCacheTokenParam() {
|
||||
return "caseCheckListCacheToken"
|
||||
},
|
||||
|
||||
getCurrentListCacheToken() {
|
||||
return new URLSearchParams(window.location.search).get(this.getListCacheTokenParam()) || ""
|
||||
},
|
||||
|
||||
/**
|
||||
* 将委员会审查列表缓存标识绑定到当前历史记录。
|
||||
* PJAX 返回时仅恢复当前历史记录对应的筛选条件、分页数据和滚动快照。
|
||||
*/
|
||||
bindListCacheToken(token) {
|
||||
const currentUrl = new URL(window.location.href)
|
||||
currentUrl.searchParams.set(this.getListCacheTokenParam(), token)
|
||||
const currentState = window.history.state ? {...window.history.state} : null
|
||||
if (currentState && currentState.url) {
|
||||
currentState.url = currentUrl.toString()
|
||||
}
|
||||
window.history.replaceState(currentState, document.title, currentUrl.toString())
|
||||
if ($.pjax && $.pjax.state) {
|
||||
$.pjax.state.url = currentUrl.toString()
|
||||
}
|
||||
},
|
||||
|
||||
// 用户主动搜索、切换教代会或审核状态时使旧返回缓存失效。
|
||||
clearListCache() {
|
||||
const token = this.getCurrentListCacheToken()
|
||||
if (!token) return
|
||||
const currentUrl = new URL(window.location.href)
|
||||
currentUrl.searchParams.delete(this.getListCacheTokenParam())
|
||||
const currentState = window.history.state ? {...window.history.state} : null
|
||||
if (currentState && currentState.url) {
|
||||
currentState.url = currentUrl.toString()
|
||||
}
|
||||
window.history.replaceState(currentState, document.title, currentUrl.toString())
|
||||
if ($.pjax && $.pjax.state) {
|
||||
$.pjax.state.url = currentUrl.toString()
|
||||
}
|
||||
sessionStorage.removeItem(this.getListCacheKey())
|
||||
},
|
||||
|
||||
// platform_h5 的实际滚动区域是 page-wrapper,列表位置必须基于该容器保存和恢复。
|
||||
getListScrollContainer() {
|
||||
return document.querySelector(".page-wrapper") || document.scrollingElement || document.documentElement
|
||||
},
|
||||
|
||||
/**
|
||||
* 进入查看或审核详情前保存列表现场。
|
||||
* row 为当前 WF 任务,index 为已加载列表索引;当前任务和相邻锚点均使用唯一 taskId。
|
||||
*/
|
||||
saveListCache(row, index) {
|
||||
const tableList = this.$refs.tableListRef
|
||||
if (!tableList) return
|
||||
const cacheToken = Date.now() + "-" + Math.random().toString(36).slice(2)
|
||||
const tableState = typeof tableList.getListState === "function"
|
||||
? tableList.getListState()
|
||||
: {
|
||||
tableData: JSON.parse(JSON.stringify(tableList.tableData || [])),
|
||||
localPageForm: JSON.parse(JSON.stringify(tableList.localPageForm || this.pageForm)),
|
||||
tableFinished: !!tableList.tableFinished
|
||||
}
|
||||
const cards = document.querySelectorAll(".proposal-case-check-list .table-list-item")
|
||||
const currentCard = cards[index]
|
||||
const scrollContainer = this.getListScrollContainer()
|
||||
const cache = {
|
||||
time: Date.now(),
|
||||
token: cacheToken,
|
||||
pageForm: JSON.parse(JSON.stringify(this.pageForm)),
|
||||
tableState: tableState,
|
||||
completedTaskId: "",
|
||||
anchor: {
|
||||
taskId: row.taskId,
|
||||
previousTaskId: index > 0 && tableState.tableData[index - 1]
|
||||
? tableState.tableData[index - 1].taskId
|
||||
: "",
|
||||
nextTaskId: tableState.tableData[index + 1] ? tableState.tableData[index + 1].taskId : "",
|
||||
viewportTop: currentCard ? currentCard.getBoundingClientRect().top : 0,
|
||||
scrollTop: scrollContainer ? scrollContainer.scrollTop : 0
|
||||
}
|
||||
}
|
||||
try {
|
||||
sessionStorage.setItem(this.getListCacheKey(), JSON.stringify(cache))
|
||||
this.bindListCacheToken(cacheToken)
|
||||
} catch (error) {
|
||||
sessionStorage.removeItem(this.getListCacheKey())
|
||||
}
|
||||
},
|
||||
|
||||
// 仅恢复当前历史记录对应且十分钟内有效的委员会审查列表缓存。
|
||||
prepareListCache() {
|
||||
try {
|
||||
const cacheToken = this.getCurrentListCacheToken()
|
||||
if (!cacheToken) return
|
||||
const cacheText = sessionStorage.getItem(this.getListCacheKey())
|
||||
if (!cacheText) return
|
||||
const cache = JSON.parse(cacheText)
|
||||
if (!cache || cache.token !== cacheToken || !cache.tableState) return
|
||||
if (Date.now() - cache.time > 10 * 60 * 1000) {
|
||||
this.clearListCache()
|
||||
return
|
||||
}
|
||||
Object.keys(cache.pageForm || {}).forEach((key) => {
|
||||
this.$set(this.pageForm, key, cache.pageForm[key])
|
||||
})
|
||||
this.$set(this, "pendingListCache", cache)
|
||||
} catch (error) {
|
||||
sessionStorage.removeItem(this.getListCacheKey())
|
||||
}
|
||||
},
|
||||
|
||||
// 普通查看返回恢复旧快照;审核完成返回重新查询,避免未审核列表继续显示已处理任务。
|
||||
restoreListCache() {
|
||||
const cache = this.pendingListCache
|
||||
const tableList = this.$refs.tableListRef
|
||||
if (!cache || !tableList) return
|
||||
const anchor = cache.anchor || {}
|
||||
if (cache.completedTaskId) {
|
||||
this.refreshCompletedListCache(cache)
|
||||
return
|
||||
}
|
||||
this.applyListCache(cache.tableState, anchor.taskId, anchor)
|
||||
},
|
||||
|
||||
/**
|
||||
* 按离开前已加载页数重新获取审核后的列表。
|
||||
* 返回值转换为 TableList 状态,并优先定位下一任务,没有下一任务时定位上一任务。
|
||||
*/
|
||||
refreshCompletedListCache(cache) {
|
||||
const originalPageSize = Number(cache.pageForm.pageSize || 10)
|
||||
const loadedCount = Math.max(cache.tableState.tableData.length, originalPageSize)
|
||||
const loadedPageCount = Math.max(1, Math.ceil(loadedCount / originalPageSize))
|
||||
const requestForm = {
|
||||
...cache.pageForm,
|
||||
pageNumber: 1,
|
||||
pageSize: loadedPageCount * originalPageSize
|
||||
}
|
||||
const loading = createListLoading()
|
||||
this.$axios.post("/platform/proposal/case/check/pageData", requestForm, {h5SkeletonLoading: true})
|
||||
.then((res) => {
|
||||
if (res.code !== 0) {
|
||||
this.applyCompletedCacheFallback(cache)
|
||||
return
|
||||
}
|
||||
const freshState = {
|
||||
tableData: res.data.list || [],
|
||||
localPageForm: {
|
||||
...cache.pageForm,
|
||||
pageNumber: loadedPageCount,
|
||||
pageSize: originalPageSize,
|
||||
totalCount: res.data.totalCount
|
||||
},
|
||||
tableFinished: (res.data.list || []).length >= res.data.totalCount
|
||||
}
|
||||
const anchor = cache.anchor || {}
|
||||
this.applyListCache(freshState, anchor.nextTaskId || anchor.previousTaskId, anchor)
|
||||
})
|
||||
.catch(() => {
|
||||
this.applyCompletedCacheFallback(cache)
|
||||
})
|
||||
.finally(() => {
|
||||
loading.close()
|
||||
})
|
||||
},
|
||||
|
||||
// 审核后刷新失败时只处理已完成 taskId,避免误改其他流程记录。
|
||||
applyCompletedCacheFallback(cache) {
|
||||
const tableState = cache.tableState
|
||||
const completedIndex = tableState.tableData.findIndex((item) => String(item.taskId) === String(cache.completedTaskId))
|
||||
if (completedIndex >= 0 && cache.pageForm.approval === false) {
|
||||
tableState.tableData.splice(completedIndex, 1)
|
||||
if (tableState.localPageForm && tableState.localPageForm.totalCount > 0) {
|
||||
tableState.localPageForm.totalCount--
|
||||
}
|
||||
} else if (completedIndex >= 0) {
|
||||
this.$set(tableState.tableData[completedIndex], "taskState", 20)
|
||||
}
|
||||
const anchor = cache.anchor || {}
|
||||
this.applyListCache(tableState, anchor.nextTaskId || anchor.previousTaskId, anchor)
|
||||
},
|
||||
|
||||
/**
|
||||
* 恢复 TableList 数据并将目标任务卡片放回离开页面前的视口位置。
|
||||
* sticky 吸顶会改变一次布局,因此首次滚动后再执行一次位置校正。
|
||||
*/
|
||||
applyListCache(tableState, targetTaskId, anchor) {
|
||||
const tableList = this.$refs.tableListRef
|
||||
if (!tableList) return
|
||||
if (typeof tableList.restoreListState === "function") {
|
||||
if (!tableList.restoreListState(tableState)) return
|
||||
} else {
|
||||
this.$set(tableList, "tableData", tableState.tableData || [])
|
||||
this.$set(tableList, "localPageForm", {...tableList.localPageForm, ...(tableState.localPageForm || {})})
|
||||
this.$set(tableList, "tableFinished", !!tableState.tableFinished)
|
||||
this.$set(tableList, "tableLoading", false)
|
||||
this.$set(tableList, "tableRefreshing", false)
|
||||
tableList.$emit("update:page_form", {...tableList.localPageForm})
|
||||
}
|
||||
this.$set(this, "pendingListCache", null)
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
const cards = Array.from(document.querySelectorAll(".proposal-case-check-list .table-list-item"))
|
||||
const scrollContainer = this.getListScrollContainer()
|
||||
if (!scrollContainer) return
|
||||
const candidateTaskIds = [targetTaskId, anchor.nextTaskId, anchor.previousTaskId]
|
||||
.filter((taskId) => taskId)
|
||||
.map((taskId) => String(taskId))
|
||||
.filter((taskId, index, values) => values.indexOf(taskId) === index)
|
||||
let targetIndex = -1
|
||||
candidateTaskIds.some((candidateTaskId) => {
|
||||
targetIndex = (tableState.tableData || [])
|
||||
.findIndex((item) => String(item.taskId) === candidateTaskId)
|
||||
return targetIndex >= 0
|
||||
})
|
||||
const targetCard = targetIndex >= 0 ? cards[targetIndex] : null
|
||||
if (targetCard) {
|
||||
const restoreTargetCard = () => {
|
||||
if (!targetCard.isConnected) return
|
||||
const targetTop = scrollContainer.scrollTop
|
||||
+ targetCard.getBoundingClientRect().top
|
||||
- Number(anchor.viewportTop || 0)
|
||||
scrollContainer.scrollTo(0, Math.max(0, targetTop))
|
||||
}
|
||||
restoreTargetCard()
|
||||
setTimeout(restoreTargetCard, 100)
|
||||
return
|
||||
}
|
||||
scrollContainer.scrollTo(0, Number(anchor.scrollTop || 0))
|
||||
}, 0)
|
||||
})
|
||||
},
|
||||
|
||||
onRevoke(row) {
|
||||
this.$dialog.confirm({
|
||||
title: "提示",
|
||||
message: "您确定要撤回吗?"
|
||||
}).then(() => {
|
||||
this.$axios.post("/flow/common/revokeTask", {taskId: row.taskId}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$toast.success(res.msg)
|
||||
this.doSearch()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
doSearch() {
|
||||
this.clearListCache()
|
||||
this.$nextTick(() => {
|
||||
this.$set(this.pageForm, "pageNumber", 1)
|
||||
this.$set(this.pageForm, "totalCount", 0)
|
||||
this.$refs.tableListRef.doSearch()
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.prepareListCache()
|
||||
if (this.pendingListCache) return
|
||||
const tab = new URLSearchParams(window.location.search).get("tab")
|
||||
if (tab === "done") {
|
||||
this.$set(this.pageForm, "approval", true)
|
||||
this.$set(this.pageForm, "approvalText", "1")
|
||||
} else if (tab === "todo") {
|
||||
this.$set(this.pageForm, "approval", false)
|
||||
this.$set(this.pageForm, "approvalText", "0")
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
+360
@@ -0,0 +1,360 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
<style>
|
||||
.case-check-tip {
|
||||
margin: 0 16px 10px;
|
||||
padding: 10px 12px;
|
||||
color: #8c6a28;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
background: #fff8e8;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.case-check-actions {
|
||||
display: grid;
|
||||
grid-template-columns: 88px 1fr;
|
||||
gap: 12px;
|
||||
padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.case-check-actions .van-button {
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.reply-unit-popup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.reply-unit-toolbar {
|
||||
display: grid;
|
||||
grid-template-columns: 64px 1fr 64px;
|
||||
align-items: center;
|
||||
min-height: 48px;
|
||||
border-bottom: 1px solid #eeeeee;
|
||||
}
|
||||
|
||||
.reply-unit-toolbar button {
|
||||
height: 48px;
|
||||
padding: 0;
|
||||
color: #666666;
|
||||
font-size: 14px;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.reply-unit-toolbar button:last-child {
|
||||
color: #1989fa;
|
||||
}
|
||||
|
||||
.reply-unit-toolbar-title {
|
||||
color: #222222;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.reply-unit-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.reply-unit-empty {
|
||||
padding: 48px 16px;
|
||||
color: #999999;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" :title="pageTitle" placeholder fixed></van-nav-bar>
|
||||
<proposal-info v-if="bizId" :biz-id="bizId">
|
||||
<div v-if="isApproval">
|
||||
<div class="process-title">{{formData.taskName}}</div>
|
||||
<van-form ref="formRef">
|
||||
<van-field :value="formData.tf_username" label="审核人" readonly></van-field>
|
||||
<van-field :value="formData.tf_auditTime" label="审核时间" readonly></van-field>
|
||||
<van-field
|
||||
:value="formData.tf_caseCheckNeedSecondReply"
|
||||
name="tf_caseCheckNeedSecondReply"
|
||||
label="二次答复"
|
||||
required
|
||||
:rules="[{ validator: validateSecondReply, message: '请选择是否需要二次答复' }]"
|
||||
>
|
||||
<template #input>
|
||||
<van-radio-group
|
||||
v-model="formData.tf_caseCheckNeedSecondReply"
|
||||
direction="horizontal"
|
||||
@change="onSecondReplyChange"
|
||||
>
|
||||
<van-radio :name="0">无需二次答复</van-radio>
|
||||
<van-radio :name="1">需要二次答复</van-radio>
|
||||
</van-radio-group>
|
||||
</template>
|
||||
</van-field>
|
||||
<div class="case-check-tip" v-if="formData.tf_caseCheckNeedSecondReply === 1">
|
||||
请选择需要进入二次答复的办理单位,可多选。
|
||||
</div>
|
||||
<van-field
|
||||
v-if="formData.tf_caseCheckNeedSecondReply === 1"
|
||||
:value="selectedReplyUnitNames"
|
||||
name="tf_secondReplyUnitIds"
|
||||
label="答复单位"
|
||||
placeholder="请选择办理单位"
|
||||
readonly
|
||||
required
|
||||
is-link
|
||||
:rules="[{ validator: validateSecondReplyUnits, message: '请选择需要二次答复的办理单位' }]"
|
||||
@click="openReplyUnitPicker"
|
||||
></van-field>
|
||||
<van-field
|
||||
v-model="formData.tf_opinion"
|
||||
name="tf_opinion"
|
||||
label="审批意见"
|
||||
type="textarea"
|
||||
rows="3"
|
||||
autosize
|
||||
placeholder="请输入审批意见"
|
||||
required
|
||||
maxlength="100"
|
||||
show-word-limit
|
||||
:rules="[{ required: true, message: '请填写审批意见' }]"
|
||||
></van-field>
|
||||
</van-form>
|
||||
<div class="case-check-actions">
|
||||
<van-button plain @click="historyBack">取消</van-button>
|
||||
<van-button type="primary" :loading="formLoading" @click="handleTaskAction">同意</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</proposal-info>
|
||||
|
||||
<van-popup v-model="showReplyUnitPicker" position="bottom" round :style="{ height: '70%' }">
|
||||
<div class="reply-unit-popup">
|
||||
<div class="reply-unit-toolbar">
|
||||
<button type="button" @click="cancelReplyUnitPicker">取消</button>
|
||||
<div class="reply-unit-toolbar-title">选择答复单位</div>
|
||||
<button type="button" @click="confirmReplyUnitPicker">确定</button>
|
||||
</div>
|
||||
<div class="reply-unit-list" v-if="replyUnitOptions.length">
|
||||
<van-checkbox-group v-model="pendingReplyUnitIds">
|
||||
<van-cell
|
||||
v-for="item in replyUnitOptions"
|
||||
:key="item.unitId"
|
||||
clickable
|
||||
:title="item.unitName"
|
||||
:label="item.isMaster ? '主办单位' : '协办单位'"
|
||||
@click="toggleReplyUnit(item)"
|
||||
>
|
||||
<template #right-icon>
|
||||
<van-checkbox :name="item.unitId" @click.stop></van-checkbox>
|
||||
</template>
|
||||
</van-cell>
|
||||
</van-checkbox-group>
|
||||
</div>
|
||||
<div class="reply-unit-empty" v-else>暂无可选办理单位</div>
|
||||
</div>
|
||||
</van-popup>
|
||||
</div>
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include("../../common/info.js"){}#-->
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
components: {
|
||||
"proposal-info": PROPOSAL_INFO
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
bizId: "",
|
||||
action: "view",
|
||||
formData: {},
|
||||
formLoading: false,
|
||||
replyUnitOptions: [],
|
||||
pendingReplyUnitIds: [],
|
||||
showReplyUnitPicker: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isApproval() {
|
||||
return this.action === "approval"
|
||||
},
|
||||
pageTitle() {
|
||||
return this.isApproval ? "提案委员会审查" : "提案详情"
|
||||
},
|
||||
selectedReplyUnitNames() {
|
||||
const selectedIds = this.formData.tf_secondReplyUnitIds || []
|
||||
return this.replyUnitOptions
|
||||
.filter((item) => selectedIds.indexOf(item.unitId) > -1)
|
||||
.map((item) => item.unitName + "(" + (item.isMaster ? "主办" : "协办") + ")")
|
||||
.join("、")
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 二次答复使用数字 0/1,与 PC 端和流程拦截器读取的字段保持一致。
|
||||
validateSecondReply(value) {
|
||||
return value === 0 || value === 1
|
||||
},
|
||||
|
||||
// 选择需要二次答复时,至少需要选择一个当前提案的办理单位。
|
||||
validateSecondReplyUnits() {
|
||||
return this.formData.tf_caseCheckNeedSecondReply !== 1
|
||||
|| (this.formData.tf_secondReplyUnitIds || []).length > 0
|
||||
},
|
||||
|
||||
onSecondReplyChange(value) {
|
||||
if (value !== 1) {
|
||||
this.$set(this.formData, "tf_secondReplyUnitIds", [])
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 查询当前提案的承办单位。
|
||||
* proposalId 为提案主键;接口返回 ProposalReplyUnit 数组,包含 unitId、unitName、isMaster。
|
||||
*/
|
||||
loadReplyUnits(proposalId) {
|
||||
this.$axios.post("/platform/proposal/case/check/listReplyUnits", {proposalId: proposalId})
|
||||
.then((res) => {
|
||||
if (res && res.code === 0) {
|
||||
this.$set(this, "replyUnitOptions", res.data || [])
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
openReplyUnitPicker() {
|
||||
this.$set(this, "pendingReplyUnitIds", [...(this.formData.tf_secondReplyUnitIds || [])])
|
||||
this.$set(this, "showReplyUnitPicker", true)
|
||||
},
|
||||
|
||||
toggleReplyUnit(item) {
|
||||
const ids = [...this.pendingReplyUnitIds]
|
||||
const index = ids.indexOf(item.unitId)
|
||||
if (index > -1) {
|
||||
ids.splice(index, 1)
|
||||
} else {
|
||||
ids.push(item.unitId)
|
||||
}
|
||||
this.$set(this, "pendingReplyUnitIds", ids)
|
||||
},
|
||||
|
||||
cancelReplyUnitPicker() {
|
||||
this.$set(this, "showReplyUnitPicker", false)
|
||||
},
|
||||
|
||||
confirmReplyUnitPicker() {
|
||||
this.$set(this.formData, "tf_secondReplyUnitIds", [...this.pendingReplyUnitIds])
|
||||
this.$set(this, "showReplyUnitPicker", false)
|
||||
},
|
||||
|
||||
/**
|
||||
* 组装流程提交参数。
|
||||
* 根据选中的 ProposalReplyUnit 拆分主办和协办单位,返回 flow/common/executeTask 所需对象。
|
||||
*/
|
||||
buildSubmitData() {
|
||||
const submitData = {...this.formData}
|
||||
if (submitData.tf_caseCheckNeedSecondReply === 1) {
|
||||
const secondReplyUnits = this.replyUnitOptions.filter((item) => {
|
||||
return submitData.tf_secondReplyUnitIds.indexOf(item.unitId) > -1
|
||||
})
|
||||
const masterUnits = secondReplyUnits.filter((item) => item.isMaster)
|
||||
const slaveUnits = secondReplyUnits.filter((item) => !item.isMaster)
|
||||
const masterUnitIds = masterUnits.map((item) => item.unitId)
|
||||
const masterUnitNames = masterUnits.map((item) => item.unitName)
|
||||
const slaveUnitIds = slaveUnits.map((item) => item.unitId)
|
||||
const slaveUnitNames = slaveUnits.map((item) => item.unitName)
|
||||
submitData.tf_masterUnitIds = masterUnitIds
|
||||
submitData.tf_masterUnitId = masterUnitIds.length ? masterUnitIds[0] : ""
|
||||
submitData.tf_masterUnitName = masterUnitNames.length ? masterUnitNames[0] : ""
|
||||
submitData.tf_masterUnitNames = masterUnitNames
|
||||
submitData.tf_masterUnitNameStr = masterUnitNames.join(",")
|
||||
submitData.tf_slaveUnitIds = slaveUnitIds
|
||||
submitData.tf_slaveUnitNames = slaveUnitNames
|
||||
submitData.tf_slaveUnitNameStr = slaveUnitNames.join(",")
|
||||
} else {
|
||||
submitData.tf_secondReplyUnitIds = []
|
||||
submitData.tf_masterUnitIds = []
|
||||
submitData.tf_masterUnitId = ""
|
||||
submitData.tf_masterUnitName = ""
|
||||
submitData.tf_masterUnitNames = []
|
||||
submitData.tf_masterUnitNameStr = ""
|
||||
submitData.tf_slaveUnitIds = []
|
||||
submitData.tf_slaveUnitNames = []
|
||||
submitData.tf_slaveUnitNameStr = ""
|
||||
}
|
||||
return submitData
|
||||
},
|
||||
|
||||
// 完成审批后记录任务 ID,列表返回缓存只移除该条记录,不影响其他查看位置。
|
||||
markListCacheCompleted() {
|
||||
const cacheKey = "proposal:case-check:h5:list-state"
|
||||
const cacheText = sessionStorage.getItem(cacheKey)
|
||||
if (!cacheText) return
|
||||
try {
|
||||
const cache = JSON.parse(cacheText)
|
||||
cache.completedTaskId = this.formData.processTaskId || ""
|
||||
sessionStorage.setItem(cacheKey, JSON.stringify(cache))
|
||||
} catch (e) {
|
||||
sessionStorage.removeItem(cacheKey)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 提交提案委员会审查结果。
|
||||
* 参数包含 taskId 对应的 processTaskId、proposalId、审批意见和二次答复单位;Result.code 为 0 表示成功。
|
||||
*/
|
||||
handleTaskAction() {
|
||||
this.$refs.formRef.validate()
|
||||
.then(() => this.$dialog.confirm({title: "提示", message: "您确定要提交吗?"}))
|
||||
.then(() => {
|
||||
this.$set(this, "formLoading", true)
|
||||
const submitData = this.buildSubmitData()
|
||||
return this.$axios.post("/flow/common/executeTask", {
|
||||
data: JSON.stringify({
|
||||
...submitData,
|
||||
submitType: 1
|
||||
})
|
||||
})
|
||||
})
|
||||
.then((res) => {
|
||||
if (res && res.code === 0) {
|
||||
this.markListCacheCompleted()
|
||||
this.$toast.success(res.msg)
|
||||
this.historyBack()
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
this.$set(this, "formLoading", false)
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
// bizId 为提案主键;taskId 和 taskName 用于执行当前 wf 流程任务。
|
||||
this.$set(this, "bizId", params.get("bizId") || "")
|
||||
this.$set(this, "action", params.get("action") || "view")
|
||||
if (this.isApproval) {
|
||||
this.$set(this.formData, "processTaskId", params.get("taskId") || "")
|
||||
this.$set(this.formData, "taskName", params.get("taskName") || "提案委员会审查")
|
||||
this.$set(this.formData, "proposalId", this.bizId)
|
||||
this.$set(this.formData, "tf_auditTime", moment().format("YYYY-MM-DD"))
|
||||
this.$set(this.formData, "tf_username", (this.$store.state.user && this.$store.state.user.username) || "")
|
||||
this.$set(this.formData, "tf_caseCheckNeedSecondReply", 0)
|
||||
this.$set(this.formData, "tf_secondReplyUnitIds", [])
|
||||
this.loadReplyUnits(this.bizId)
|
||||
}
|
||||
if (!this.bizId) {
|
||||
this.$toast.fail("缺少提案参数")
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
+3
-120
@@ -51,76 +51,11 @@ layout("/layouts/platform_h5.html"){
|
||||
</template>
|
||||
</table-list>
|
||||
|
||||
<proposal-info ref="proposalInfoRef">
|
||||
<div v-if="showApprovalForm">
|
||||
<div class="process-title">提案委员会成员意见</div>
|
||||
<van-form ref="formRef">
|
||||
<van-field
|
||||
v-model="formData.caseFilingResultName"
|
||||
name="caseFilingResultName"
|
||||
label="立案结果"
|
||||
placeholder="请选择立案结果"
|
||||
:rules="[{ required: true, message: '请选择立案结果' }]"
|
||||
required
|
||||
is-link
|
||||
@click="showCaseFilingResultPicker = true"
|
||||
></van-field>
|
||||
<van-popup v-model="showCaseFilingResultPicker" position="bottom">
|
||||
<van-picker
|
||||
show-toolbar
|
||||
:columns="dict.type.PROPOSAL_CASE_FILING_RESULT.map(item => ({text: item.label, value: item.code}))"
|
||||
@confirm="onCaseFilingResultConfirm"
|
||||
@cancel="showCaseFilingResultPicker = false"
|
||||
></van-picker>
|
||||
</van-popup>
|
||||
|
||||
<template v-if="['CONFIRM_FILING'].includes(formData.caseFilingResult)">
|
||||
<van-field
|
||||
v-model="formData.caseFilingTypeName"
|
||||
name="caseFilingTypeName"
|
||||
label="立案结果"
|
||||
placeholder="请选择立案类型"
|
||||
:rules="[{ required: true, message: '请选择立案类型' }]"
|
||||
required
|
||||
is-link
|
||||
@click="showCaseFilingTypePicker = true"
|
||||
></van-field>
|
||||
<van-popup v-model="showCaseFilingTypePicker" position="bottom">
|
||||
<van-picker
|
||||
show-toolbar
|
||||
:columns="dict.type.PROPOSAL_CASE_FILING_TYPE.map(item => ({text: item.label, value: item.code}))"
|
||||
@confirm="onCaseFilingTypeConfirm"
|
||||
@cancel="showCaseFilingTypePicker = false"
|
||||
></van-picker>
|
||||
</van-popup>
|
||||
</template>
|
||||
|
||||
<van-field
|
||||
v-model="formData.opinion"
|
||||
name="opinion"
|
||||
label="审批意见"
|
||||
placeholder="请输入审批意见"
|
||||
:rules="[{ required: true, message: '请填写审批意见' }]"
|
||||
required
|
||||
maxlength="100"
|
||||
show-word-limit
|
||||
></van-field>
|
||||
</van-form>
|
||||
<div style="display: flex; justify-content: space-between; column-gap: 10px; padding: 10px">
|
||||
<van-button block @click="$refs.proposalInfoRef.onClose()">取消</van-button>
|
||||
<van-button type="primary" block @click="doSubmit">提交</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</proposal-info>
|
||||
|
||||
</div>
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include("../../common/info.js"){}#-->
|
||||
|
||||
const vue = new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
dicts: ["PROPOSAL_CASE_FILING_RESULT", "PROPOSAL_CASE_FILING_TYPE"],
|
||||
data() {
|
||||
return {
|
||||
pageForm: {
|
||||
@@ -133,17 +68,9 @@ layout("/layouts/platform_h5.html"){
|
||||
approval: false
|
||||
},
|
||||
|
||||
sessionOptions: [],
|
||||
formData: {},
|
||||
showApprovalForm: false,
|
||||
|
||||
showCaseFilingResultPicker: false,
|
||||
showCaseFilingTypePicker: false
|
||||
sessionOptions: []
|
||||
}
|
||||
},
|
||||
components: {
|
||||
"proposal-info": PROPOSAL_INFO
|
||||
},
|
||||
methods: {
|
||||
onReady() {
|
||||
this.listSession()
|
||||
@@ -166,55 +93,11 @@ layout("/layouts/platform_h5.html"){
|
||||
},
|
||||
|
||||
onView(row) {
|
||||
this.showApprovalForm = false
|
||||
this.$refs.proposalInfoRef.onOpen(row)
|
||||
},
|
||||
|
||||
// 确定立案类型
|
||||
onCaseFilingResultConfirm(value) {
|
||||
this.$set(this.formData, "caseFilingResultName", value.text)
|
||||
this.$set(this.formData, "caseFilingResult", value.value)
|
||||
this.showCaseFilingResultPicker = false
|
||||
},
|
||||
|
||||
// 确定立案类型
|
||||
onCaseFilingTypeConfirm(value) {
|
||||
this.$set(this.formData, "caseFilingTypeName", value.text)
|
||||
this.$set(this.formData, "caseFilingType", value.value)
|
||||
this.showCaseFilingTypePicker = false
|
||||
this.$pjaxReplace("/platform/proposal/commissioner/info/h5?bizId=" + encodeURIComponent(row.id) + "&action=view")
|
||||
},
|
||||
|
||||
onApproval(row) {
|
||||
this.showApprovalForm = true
|
||||
this.$refs.proposalInfoRef.onOpen(row)
|
||||
this.formData = {
|
||||
proposalId: row.id,
|
||||
caseFilingResult: null,
|
||||
caseFilingType: null,
|
||||
opinion: null
|
||||
}
|
||||
},
|
||||
|
||||
async doSubmit() {
|
||||
try {
|
||||
await this.$refs.formRef.validate();
|
||||
this.$dialog.confirm({
|
||||
title: '提示',
|
||||
message: "您确定要提交吗?",
|
||||
}).then(() => {
|
||||
this.$axios.post('/platform/proposal/commissioner/doApproval', this.formData).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.$refs.proposalInfoRef.onClose()
|
||||
this.$toast.success(res.msg)
|
||||
this.doSearch()
|
||||
}
|
||||
})
|
||||
}).catch(() => {
|
||||
|
||||
})
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
this.$pjaxReplace("/platform/proposal/commissioner/info/h5?bizId=" + encodeURIComponent(row.id) + "&action=approval")
|
||||
},
|
||||
|
||||
doSearch() {
|
||||
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" :title="pageTitle" placeholder fixed></van-nav-bar>
|
||||
<proposal-info v-if="bizId" :biz-id="bizId">
|
||||
<div v-if="isApproval">
|
||||
<div class="process-title">提案委员会成员意见</div>
|
||||
<van-form ref="formRef">
|
||||
<van-field
|
||||
v-model="formData.caseFilingResultName"
|
||||
name="caseFilingResultName"
|
||||
label="立案结果"
|
||||
placeholder="请选择立案结果"
|
||||
:rules="[{ required: true, message: '请选择立案结果' }]"
|
||||
required
|
||||
is-link
|
||||
@click="showCaseFilingResultPicker = true"
|
||||
></van-field>
|
||||
<van-popup v-model="showCaseFilingResultPicker" position="bottom">
|
||||
<van-picker
|
||||
show-toolbar
|
||||
:columns="dict.type.PROPOSAL_CASE_FILING_RESULT.map(item => ({text: item.label, value: item.code}))"
|
||||
@confirm="onCaseFilingResultConfirm"
|
||||
@cancel="showCaseFilingResultPicker = false"
|
||||
></van-picker>
|
||||
</van-popup>
|
||||
|
||||
<template v-if="formData.caseFilingResult === 'CONFIRM_FILING'">
|
||||
<van-field
|
||||
v-model="formData.caseFilingTypeName"
|
||||
name="caseFilingTypeName"
|
||||
label="立案类型"
|
||||
placeholder="请选择立案类型"
|
||||
:rules="[{ required: true, message: '请选择立案类型' }]"
|
||||
required
|
||||
is-link
|
||||
@click="showCaseFilingTypePicker = true"
|
||||
></van-field>
|
||||
<van-popup v-model="showCaseFilingTypePicker" position="bottom">
|
||||
<van-picker
|
||||
show-toolbar
|
||||
:columns="dict.type.PROPOSAL_CASE_FILING_TYPE.map(item => ({text: item.label, value: item.code}))"
|
||||
@confirm="onCaseFilingTypeConfirm"
|
||||
@cancel="showCaseFilingTypePicker = false"
|
||||
></van-picker>
|
||||
</van-popup>
|
||||
</template>
|
||||
|
||||
<van-field
|
||||
v-model="formData.opinion"
|
||||
name="opinion"
|
||||
label="审批意见"
|
||||
placeholder="请输入审批意见"
|
||||
:rules="[{ required: true, message: '请填写审批意见' }]"
|
||||
required
|
||||
maxlength="100"
|
||||
show-word-limit
|
||||
></van-field>
|
||||
</van-form>
|
||||
<div style="display: flex; justify-content: space-between; column-gap: 10px; padding: 10px">
|
||||
<van-button block :disabled="formLoading" @click="historyBack">取消</van-button>
|
||||
<van-button type="primary" block :loading="formLoading" @click="doSubmit">提交</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</proposal-info>
|
||||
</div>
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include("../../common/info.js"){}#-->
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
dicts: ["PROPOSAL_CASE_FILING_RESULT", "PROPOSAL_CASE_FILING_TYPE"],
|
||||
components: {
|
||||
"proposal-info": PROPOSAL_INFO
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
bizId: "",
|
||||
action: "view",
|
||||
formData: {},
|
||||
formLoading: false,
|
||||
showCaseFilingResultPicker: false,
|
||||
showCaseFilingTypePicker: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isApproval() {
|
||||
return this.action === "approval"
|
||||
},
|
||||
pageTitle() {
|
||||
return this.isApproval ? "委员会成员意见" : "提案详情"
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onCaseFilingResultConfirm(value) {
|
||||
this.$set(this.formData, "caseFilingResultName", value.text)
|
||||
this.$set(this.formData, "caseFilingResult", value.value)
|
||||
this.showCaseFilingResultPicker = false
|
||||
},
|
||||
onCaseFilingTypeConfirm(value) {
|
||||
this.$set(this.formData, "caseFilingTypeName", value.text)
|
||||
this.$set(this.formData, "caseFilingType", value.value)
|
||||
this.showCaseFilingTypePicker = false
|
||||
},
|
||||
/**
|
||||
* 提交委员会成员意见。
|
||||
* 参数包含 proposalId、立案结果、立案类型和意见;接口返回 Result。
|
||||
*/
|
||||
doSubmit() {
|
||||
this.$refs.formRef.validate()
|
||||
.then(() => this.$dialog.confirm({title: "提示", message: "您确定要提交吗?"}))
|
||||
.then(() => {
|
||||
this.formLoading = true
|
||||
return this.$axios.post("/platform/proposal/commissioner/doApproval", this.formData)
|
||||
})
|
||||
.then((res) => {
|
||||
if (res && res.code === 0) {
|
||||
this.$toast.success(res.msg)
|
||||
this.historyBack()
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
this.formLoading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
// bizId 为详情查询和委员会意见提交共同使用的提案主键。
|
||||
this.bizId = params.get("bizId") || ""
|
||||
this.action = params.get("action") || "view"
|
||||
if (this.isApproval) {
|
||||
this.$set(this.formData, "proposalId", this.bizId)
|
||||
this.$set(this.formData, "caseFilingResult", null)
|
||||
this.$set(this.formData, "caseFilingType", null)
|
||||
this.$set(this.formData, "opinion", null)
|
||||
}
|
||||
if (!this.bizId) {
|
||||
this.$toast.fail("缺少提案参数")
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
+590
-118
@@ -1,82 +1,328 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
<style>
|
||||
body {
|
||||
background-color: #f3f7fd;
|
||||
}
|
||||
|
||||
.proposal-delegation-page {
|
||||
min-height: 100vh;
|
||||
padding-bottom: calc(24px + env(safe-area-inset-bottom));
|
||||
box-sizing: border-box;
|
||||
background-color: #f3f7fd;
|
||||
}
|
||||
|
||||
.proposal-delegation-page .van-nav-bar {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.proposal-delegation-page .van-nav-bar__title {
|
||||
color: #172033;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.proposal-delegation-page .van-nav-bar .van-icon,
|
||||
.proposal-delegation-page .van-nav-bar__text {
|
||||
color: #1989fa;
|
||||
}
|
||||
|
||||
.proposal-delegation-content {
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.proposal-delegation-sticky {
|
||||
padding: 22px 12px 1px;
|
||||
box-sizing: border-box;
|
||||
background-color: #f3f7fd;
|
||||
}
|
||||
|
||||
.proposal-delegation-search {
|
||||
margin: 0 0 8px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
border-radius: 12px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 5px 16px rgba(43, 73, 112, .1);
|
||||
}
|
||||
|
||||
.proposal-delegation-search .van-search__content {
|
||||
height: 42px;
|
||||
padding-left: 12px;
|
||||
align-items: center;
|
||||
border-radius: 12px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.proposal-delegation-search .van-field__control {
|
||||
color: #263548;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.proposal-delegation-search .van-field__control::placeholder {
|
||||
color: #a7b0bf;
|
||||
}
|
||||
|
||||
.proposal-delegation-filter {
|
||||
margin: 0 0 10px;
|
||||
overflow: hidden;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
|
||||
transition: border-radius .18s ease;
|
||||
}
|
||||
|
||||
.proposal-delegation-filter.is-open {
|
||||
border-radius: 10px 10px 0 0;
|
||||
}
|
||||
|
||||
.proposal-delegation-filter .van-dropdown-menu__bar,
|
||||
.proposal-delegation-filter .van-dropdown-menu__item,
|
||||
.proposal-delegation-filter .van-dropdown-menu__title {
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.proposal-delegation-filter .van-dropdown-menu__bar {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.proposal-delegation-filter .van-dropdown-menu__item,
|
||||
.proposal-delegation-filter .van-dropdown-menu__title {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.proposal-delegation-filter .van-dropdown-menu__item {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.proposal-delegation-filter .van-dropdown-menu__title {
|
||||
display: inline-flex;
|
||||
color: #66758a;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.proposal-delegation-filter .van-dropdown-menu__title--active,
|
||||
.proposal-delegation-filter .van-dropdown-item__option--active,
|
||||
.proposal-delegation-filter .van-dropdown-item__option--active .van-dropdown-item__icon {
|
||||
color: #1989fa;
|
||||
}
|
||||
|
||||
.proposal-delegation-filter .van-dropdown-menu__title--down::after {
|
||||
border-color: transparent transparent #1989fa #1989fa;
|
||||
}
|
||||
|
||||
.proposal-delegation-filter .van-dropdown-item__content {
|
||||
width: calc(100% - 24px);
|
||||
margin: 0 12px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
border-radius: 0 0 12px 12px;
|
||||
box-shadow: 0 10px 24px rgba(31, 65, 108, .14);
|
||||
}
|
||||
|
||||
.proposal-delegation-filter .van-dropdown-item__option {
|
||||
display: flex;
|
||||
min-height: 48px;
|
||||
padding: 0 16px;
|
||||
align-items: center;
|
||||
color: #46566c;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.proposal-delegation-filter .van-cell::after {
|
||||
right: 16px;
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
.proposal-delegation-list .empty-state {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.proposal-delegation-empty {
|
||||
display: flex;
|
||||
min-height: 280px;
|
||||
padding: 46px 18px 36px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.proposal-delegation-empty img {
|
||||
display: block;
|
||||
width: 86%;
|
||||
max-width: 290px;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.proposal-delegation-empty__title {
|
||||
margin-top: 14px;
|
||||
color: #50627a;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.proposal-delegation-empty__hint {
|
||||
margin-top: 5px;
|
||||
color: #9aa7b8;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.proposal-delegation-card-header {
|
||||
display: flex;
|
||||
margin-bottom: 7px;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.proposal-delegation-card-heading {
|
||||
min-width: 0;
|
||||
padding-right: 10px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.proposal-delegation-card-title {
|
||||
overflow: hidden;
|
||||
color: #253247;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
font-weight: 600;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.proposal-delegation-card-code {
|
||||
margin-top: 1px;
|
||||
color: #9ba6b6;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.proposal-delegation-status {
|
||||
display: inline-flex;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.proposal-delegation-status .van-tag {
|
||||
display: inline-flex;
|
||||
min-height: 24px;
|
||||
padding: 3px 9px;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
border: 0;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media (max-width: 350px) {
|
||||
.proposal-delegation-content,
|
||||
.proposal-delegation-sticky {
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="团长审核" placeholder fixed></van-nav-bar>
|
||||
<van-sticky offset-top="46px">
|
||||
<van-search
|
||||
v-model="pageForm.name"
|
||||
:show-action="false"
|
||||
:reverse-color="false"
|
||||
input-align="left"
|
||||
placeholder="请输入提案名称搜索"
|
||||
@search="doSearch"
|
||||
></van-search>
|
||||
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
|
||||
<van-dropdown-item v-model="pageForm.sessionId" :options="sessionOptions" :multiple="false"
|
||||
@change="doSearch"></van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
<van-tabs v-model="pageForm.approvalText"
|
||||
@change="(val)=>{this.pageForm.approval = val==='1';this.doSearch();}">
|
||||
<van-tab title="已审核" name="1"></van-tab>
|
||||
<van-tab title="未审核" name="0"></van-tab>
|
||||
</van-tabs>
|
||||
</van-sticky>
|
||||
<div class="proposal-delegation-page">
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="团长审核" placeholder fixed></van-nav-bar>
|
||||
<van-sticky :offset-top="46">
|
||||
<div class="proposal-delegation-sticky">
|
||||
<van-search
|
||||
v-model="pageForm.name"
|
||||
class="proposal-delegation-search"
|
||||
placeholder="请输入提案名称搜索"
|
||||
shape="round"
|
||||
clearable
|
||||
@search="doSearch"
|
||||
@clear="doSearch"
|
||||
></van-search>
|
||||
<van-dropdown-menu
|
||||
class="proposal-delegation-filter"
|
||||
:class="{'is-open': filterOpened}"
|
||||
active-color="#1989fa"
|
||||
>
|
||||
<van-dropdown-item
|
||||
v-model="pageForm.sessionId"
|
||||
:options="sessionOptions"
|
||||
:multiple="false"
|
||||
@change="doSearch"
|
||||
@open="filterOpened = true"
|
||||
@close="filterOpened = false"
|
||||
></van-dropdown-item>
|
||||
<van-dropdown-item
|
||||
v-model="pageForm.approvalText"
|
||||
:options="approvalOptions"
|
||||
:multiple="false"
|
||||
@change="approvalChange"
|
||||
@open="filterOpened = true"
|
||||
@close="filterOpened = false"
|
||||
></van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
</div>
|
||||
</van-sticky>
|
||||
|
||||
<table-list api="/platform/proposal/delegation/pageData" :page_form.sync="pageForm" @ready="onReady"
|
||||
ref="tableListRef"
|
||||
title="name">
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="提案编号">{{row.code}}</table-column>
|
||||
<table-column label="提案类别">{{row.typeName}}</table-column>
|
||||
<table-column label="提案人">{{row.createUserName}}</table-column>
|
||||
<table-column label="代表团">{{row.delegationName}}</table-column>
|
||||
<table-column label="当前节点">{{row.curTaskName}}</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row)">
|
||||
<i class="fa fa-eye"></i>
|
||||
<span>查看</span>
|
||||
<main class="proposal-delegation-content">
|
||||
<div class="proposal-delegation-list">
|
||||
<!-- 列表无记录时使用与“我的提案”一致的提案专用空状态。 -->
|
||||
<div v-if="pageForm.totalCount === 0" class="proposal-delegation-empty">
|
||||
<img src="/assets/mobile/img/proposal/proposal-empty.png" alt="暂无提案记录插画">
|
||||
<div class="proposal-delegation-empty__title">暂无提案记录</div>
|
||||
<div class="proposal-delegation-empty__hint">当前筛选条件下暂时没有提案</div>
|
||||
</div>
|
||||
<table-list api="/platform/proposal/delegation/pageData" :page_form.sync="pageForm" @ready="onReady"
|
||||
ref="tableListRef"
|
||||
title="name">
|
||||
<template #header="{index,row}">
|
||||
<div class="proposal-delegation-card-header">
|
||||
<div class="proposal-delegation-card-heading">
|
||||
<div class="proposal-delegation-card-title">{{row.name}}</div>
|
||||
<div class="proposal-delegation-card-code">编号:{{row.code || '--'}}</div>
|
||||
</div>
|
||||
<enum-tag
|
||||
class="proposal-delegation-status"
|
||||
v-if="row.instanceState"
|
||||
:value="row.instanceState"
|
||||
name="ProcessInstanceStateEnum"
|
||||
label_key="message"
|
||||
size="small"
|
||||
></enum-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="提案类别">{{row.typeName || '--'}}</table-column>
|
||||
<table-column label="提案人">{{row.createUserName || '--'}}</table-column>
|
||||
<table-column label="代表团">{{row.delegationName || '--'}}</table-column>
|
||||
<table-column label="当前节点">{{row.curTaskName || '--'}}</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row,index)">
|
||||
<i class="fa fa-eye"></i>
|
||||
<span>查看</span>
|
||||
</div>
|
||||
<div class="action-btn" v-if="row.taskState === 10" @click="onApproval(row,index)">
|
||||
<i class="fa fa-edit"></i>
|
||||
<span>审核</span>
|
||||
</div>
|
||||
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)">
|
||||
<i class="fa fa-reply"></i>
|
||||
<span>撤回</span>
|
||||
</div>
|
||||
</template>
|
||||
</table-list>
|
||||
</div>
|
||||
<div class="action-btn" v-if="row.taskState === 10" @click="onApproval(row)">
|
||||
<i class="fa fa-edit"></i>
|
||||
<span>审核</span>
|
||||
</div>
|
||||
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)">
|
||||
<i class="fa fa-reply"></i>
|
||||
<span>撤回</span>
|
||||
</div>
|
||||
</template>
|
||||
</table-list>
|
||||
|
||||
<proposal-info ref="proposalInfoRef">
|
||||
<div v-if="showApprovalForm">
|
||||
<div class="process-title">{{formData.taskName}}</div>
|
||||
<van-form ref="formRef">
|
||||
<van-field
|
||||
v-model="formData.tf_opinion"
|
||||
name="tf_opinion"
|
||||
label="审批意见"
|
||||
placeholder="请输入审批意见"
|
||||
:rules="[{ required: true, message: '请填写审批意见' }]"
|
||||
required
|
||||
maxlength="100"
|
||||
show-word-limit
|
||||
></van-field>
|
||||
</van-form>
|
||||
<div style="display: flex; justify-content: space-between; column-gap: 10px; padding: 10px">
|
||||
<van-button type="danger" block @click="handleTaskAction(6)">退回</van-button>
|
||||
<van-button type="danger" block @click="handleTaskAction(2)">不同意</van-button>
|
||||
<van-button type="primary" block @click="handleTaskAction(1)">同意</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</proposal-info>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include("../../common/info.js"){}#-->
|
||||
|
||||
const vue = new Vue({
|
||||
new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
data() {
|
||||
@@ -85,6 +331,8 @@ layout("/layouts/platform_h5.html"){
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
pageOrderName: "",
|
||||
pageOrderBy: "",
|
||||
name: null,
|
||||
sessionId: null,
|
||||
approvalText: "0",
|
||||
@@ -92,75 +340,286 @@ layout("/layouts/platform_h5.html"){
|
||||
},
|
||||
|
||||
sessionOptions: [],
|
||||
formData: {},
|
||||
showApprovalForm: false
|
||||
filterOpened: false,
|
||||
approvalOptions: [
|
||||
{text: "未审核", value: "0"},
|
||||
{text: "已审核", value: "1"}
|
||||
],
|
||||
pendingListCache: null
|
||||
}
|
||||
},
|
||||
components: {
|
||||
"proposal-info": PROPOSAL_INFO
|
||||
},
|
||||
methods: {
|
||||
onReady() {
|
||||
this.listSession()
|
||||
if (this.pendingListCache) {
|
||||
this.restoreListCache()
|
||||
this.listSession(true)
|
||||
return
|
||||
}
|
||||
this.listSession(false)
|
||||
},
|
||||
listSession() {
|
||||
// 审核状态下拉框使用字符串值展示,查询接口使用布尔值区分已审核和未审核。
|
||||
approvalChange(value) {
|
||||
this.$set(this.pageForm, "approval", value === "1")
|
||||
this.doSearch()
|
||||
},
|
||||
listSession(skipDefaultSearch) {
|
||||
this.$axios.post("/platform/proposal/common/listSession").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.sessionOptions = [
|
||||
{
|
||||
text: "全部届次",
|
||||
value: null
|
||||
}
|
||||
].concat(res.data.map((v) => ({text: v.fullName, value: v.id})))
|
||||
if (this.sessionOptions.length > 0) {
|
||||
this.pageForm.sessionId = this.sessionOptions[0].value
|
||||
this.doSearch()
|
||||
}
|
||||
this.sessionOptions = [{text: "全部届次", value: null}]
|
||||
.concat(res.data.map((v) => ({text: v.fullName, value: v.id})))
|
||||
if (skipDefaultSearch) return
|
||||
this.$set(this.pageForm, "sessionId", res.data.length > 0 ? res.data[0].id : null)
|
||||
this.doSearch()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onView(row) {
|
||||
this.showApprovalForm = false
|
||||
this.$refs.proposalInfoRef.onOpen(row)
|
||||
onView(row, index) {
|
||||
this.saveListCache(row, index)
|
||||
this.$pjaxReplace("/platform/proposal/delegation/info/h5?bizId=" + encodeURIComponent(row.id) + "&action=view")
|
||||
},
|
||||
|
||||
onApproval(row) {
|
||||
this.showApprovalForm = true
|
||||
this.$refs.proposalInfoRef.onOpen(row)
|
||||
this.formData = {
|
||||
processTaskId: row.taskId,
|
||||
taskName: row.curTaskName
|
||||
onApproval(row, index) {
|
||||
this.saveListCache(row, index)
|
||||
const params = new URLSearchParams()
|
||||
params.set("bizId", row.id)
|
||||
params.set("action", "approval")
|
||||
params.set("taskId", row.taskId)
|
||||
params.set("taskName", row.curTaskName || row.taskName || "团长审核")
|
||||
this.$pjaxReplace("/platform/proposal/delegation/info/h5?" + params.toString())
|
||||
},
|
||||
|
||||
getListCacheKey() {
|
||||
return "proposal:delegation:h5:list-state"
|
||||
},
|
||||
|
||||
getListCacheTokenParam() {
|
||||
return "delegationListCacheToken"
|
||||
},
|
||||
|
||||
getCurrentListCacheToken() {
|
||||
return new URLSearchParams(window.location.search).get(this.getListCacheTokenParam()) || ""
|
||||
},
|
||||
|
||||
/**
|
||||
* 将团长审核列表缓存标识绑定到当前历史记录。
|
||||
* PJAX 重复初始化时,每个列表实例都通过该标识恢复同一份筛选、分页和滚动快照。
|
||||
*/
|
||||
bindListCacheToken(token) {
|
||||
const currentUrl = new URL(window.location.href)
|
||||
currentUrl.searchParams.set(this.getListCacheTokenParam(), token)
|
||||
const currentState = window.history.state ? {...window.history.state} : null
|
||||
if (currentState && currentState.url) {
|
||||
currentState.url = currentUrl.toString()
|
||||
}
|
||||
window.history.replaceState(currentState, document.title, currentUrl.toString())
|
||||
if ($.pjax && $.pjax.state) {
|
||||
$.pjax.state.url = currentUrl.toString()
|
||||
}
|
||||
},
|
||||
|
||||
async handleTaskAction(val) {
|
||||
// 用户主动查询时清除详情返回标识,避免重新进入页面后误用旧列表现场。
|
||||
clearListCache() {
|
||||
const token = this.getCurrentListCacheToken()
|
||||
if (!token) return
|
||||
const currentUrl = new URL(window.location.href)
|
||||
currentUrl.searchParams.delete(this.getListCacheTokenParam())
|
||||
const currentState = window.history.state ? {...window.history.state} : null
|
||||
if (currentState && currentState.url) {
|
||||
currentState.url = currentUrl.toString()
|
||||
}
|
||||
window.history.replaceState(currentState, document.title, currentUrl.toString())
|
||||
if ($.pjax && $.pjax.state) {
|
||||
$.pjax.state.url = currentUrl.toString()
|
||||
}
|
||||
sessionStorage.removeItem(this.getListCacheKey())
|
||||
},
|
||||
|
||||
// platform_h5 的实际滚动区域是 page-wrapper,列表返回位置必须基于该容器保存和恢复。
|
||||
getListScrollContainer() {
|
||||
return document.querySelector(".page-wrapper") || document.scrollingElement || document.documentElement
|
||||
},
|
||||
|
||||
/**
|
||||
* 进入查看或审核详情前保存列表现场。
|
||||
* row 为当前提案,index 为已加载列表索引;缓存包含查询条件、分页数据和相邻记录锚点。
|
||||
*/
|
||||
saveListCache(row, index) {
|
||||
const tableList = this.$refs.tableListRef
|
||||
if (!tableList) return
|
||||
const cacheToken = Date.now() + "-" + Math.random().toString(36).slice(2)
|
||||
const tableState = typeof tableList.getListState === "function"
|
||||
? tableList.getListState()
|
||||
: {
|
||||
tableData: JSON.parse(JSON.stringify(tableList.tableData || [])),
|
||||
localPageForm: JSON.parse(JSON.stringify(tableList.localPageForm || this.pageForm)),
|
||||
tableFinished: !!tableList.tableFinished
|
||||
}
|
||||
const cards = document.querySelectorAll(".proposal-delegation-list .table-list-item")
|
||||
const currentCard = cards[index]
|
||||
const scrollContainer = this.getListScrollContainer()
|
||||
const cache = {
|
||||
time: Date.now(),
|
||||
token: cacheToken,
|
||||
pageForm: JSON.parse(JSON.stringify(this.pageForm)),
|
||||
tableState: tableState,
|
||||
completedBizId: "",
|
||||
anchor: {
|
||||
rowId: row.id,
|
||||
previousRowId: index > 0 && tableState.tableData[index - 1] ? tableState.tableData[index - 1].id : "",
|
||||
nextRowId: tableState.tableData[index + 1] ? tableState.tableData[index + 1].id : "",
|
||||
viewportTop: currentCard ? currentCard.getBoundingClientRect().top : 0,
|
||||
scrollTop: scrollContainer ? scrollContainer.scrollTop : 0
|
||||
}
|
||||
}
|
||||
try {
|
||||
await this.$refs.formRef.validate();
|
||||
this.$dialog.confirm({
|
||||
title: "提示",
|
||||
message: "您确定要提交吗?"
|
||||
}).then(() => {
|
||||
this.$axios.post("/flow/common/executeTask", {
|
||||
data: JSON.stringify({
|
||||
...this.formData,
|
||||
submitType: val
|
||||
})
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$refs.proposalInfoRef.onClose()
|
||||
this.$toast.success(res.msg)
|
||||
this.doSearch()
|
||||
}
|
||||
})
|
||||
}).catch(() => {
|
||||
|
||||
})
|
||||
sessionStorage.setItem(this.getListCacheKey(), JSON.stringify(cache))
|
||||
this.bindListCacheToken(cacheToken)
|
||||
} catch (error) {
|
||||
|
||||
sessionStorage.removeItem(this.getListCacheKey())
|
||||
}
|
||||
},
|
||||
|
||||
// 仅恢复当前历史记录对应且十分钟内有效的团长审核列表缓存。
|
||||
prepareListCache() {
|
||||
try {
|
||||
const cacheToken = this.getCurrentListCacheToken()
|
||||
if (!cacheToken) return
|
||||
const cacheText = sessionStorage.getItem(this.getListCacheKey())
|
||||
if (!cacheText) return
|
||||
const cache = JSON.parse(cacheText)
|
||||
if (!cache || cache.token !== cacheToken || !cache.tableState) return
|
||||
if (Date.now() - cache.time > 10 * 60 * 1000) {
|
||||
this.clearListCache()
|
||||
return
|
||||
}
|
||||
Object.keys(cache.pageForm || {}).forEach((key) => {
|
||||
this.$set(this.pageForm, key, cache.pageForm[key])
|
||||
})
|
||||
this.$set(this, "pendingListCache", cache)
|
||||
} catch (error) {
|
||||
sessionStorage.removeItem(this.getListCacheKey())
|
||||
}
|
||||
},
|
||||
|
||||
// 普通查看返回恢复旧快照;审核完成返回重新查询,避免未审核列表继续显示已处理记录。
|
||||
restoreListCache() {
|
||||
const cache = this.pendingListCache
|
||||
const tableList = this.$refs.tableListRef
|
||||
if (!cache || !tableList) return
|
||||
const anchor = cache.anchor || {}
|
||||
if (cache.completedBizId) {
|
||||
this.refreshCompletedListCache(cache)
|
||||
return
|
||||
}
|
||||
this.applyListCache(cache.tableState, anchor.rowId, anchor)
|
||||
},
|
||||
|
||||
/**
|
||||
* 按离开前已加载页数重新获取审核后的列表。
|
||||
* 返回值仍转换为 TableList 状态,并定位下一条;没有下一条时定位上一条。
|
||||
*/
|
||||
refreshCompletedListCache(cache) {
|
||||
const originalPageSize = Number(cache.pageForm.pageSize || 10)
|
||||
const loadedCount = Math.max(cache.tableState.tableData.length, originalPageSize)
|
||||
const loadedPageCount = Math.max(1, Math.ceil(loadedCount / originalPageSize))
|
||||
const requestForm = {
|
||||
...cache.pageForm,
|
||||
pageNumber: 1,
|
||||
pageSize: loadedPageCount * originalPageSize
|
||||
}
|
||||
const loading = createListLoading()
|
||||
this.$axios.post("/platform/proposal/delegation/pageData", requestForm, {h5SkeletonLoading: true})
|
||||
.then((res) => {
|
||||
if (res.code !== 0) {
|
||||
this.applyCompletedCacheFallback(cache)
|
||||
return
|
||||
}
|
||||
const freshState = {
|
||||
tableData: res.data.list || [],
|
||||
localPageForm: {
|
||||
...cache.pageForm,
|
||||
pageNumber: loadedPageCount,
|
||||
pageSize: originalPageSize,
|
||||
totalCount: res.data.totalCount
|
||||
},
|
||||
tableFinished: (res.data.list || []).length >= res.data.totalCount
|
||||
}
|
||||
const anchor = cache.anchor || {}
|
||||
this.applyListCache(freshState, anchor.nextRowId || anchor.previousRowId, anchor)
|
||||
})
|
||||
.catch(() => {
|
||||
this.applyCompletedCacheFallback(cache)
|
||||
})
|
||||
.finally(() => {
|
||||
loading.close()
|
||||
})
|
||||
},
|
||||
|
||||
// 审核后刷新失败时从旧快照移除已处理记录,保证未审核列表不会保留脏数据。
|
||||
applyCompletedCacheFallback(cache) {
|
||||
const tableState = cache.tableState
|
||||
const completedIndex = tableState.tableData.findIndex((item) => String(item.id) === String(cache.completedBizId))
|
||||
if (completedIndex >= 0 && cache.pageForm.approval === false) {
|
||||
tableState.tableData.splice(completedIndex, 1)
|
||||
if (tableState.localPageForm && tableState.localPageForm.totalCount > 0) {
|
||||
tableState.localPageForm.totalCount--
|
||||
}
|
||||
} else if (completedIndex >= 0) {
|
||||
this.$set(tableState.tableData[completedIndex], "taskState", 20)
|
||||
}
|
||||
const anchor = cache.anchor || {}
|
||||
this.applyListCache(tableState, anchor.nextRowId || anchor.previousRowId, anchor)
|
||||
},
|
||||
|
||||
/**
|
||||
* 恢复 TableList 数据并将目标卡片放回离开页面前的视口位置。
|
||||
* sticky 吸顶会改变一次布局,因此首次滚动后再执行一次位置校正。
|
||||
*/
|
||||
applyListCache(tableState, targetRowId, anchor) {
|
||||
const tableList = this.$refs.tableListRef
|
||||
if (!tableList) return
|
||||
if (typeof tableList.restoreListState === "function") {
|
||||
if (!tableList.restoreListState(tableState)) return
|
||||
} else {
|
||||
this.$set(tableList, "tableData", tableState.tableData || [])
|
||||
this.$set(tableList, "localPageForm", {...tableList.localPageForm, ...(tableState.localPageForm || {})})
|
||||
this.$set(tableList, "tableFinished", !!tableState.tableFinished)
|
||||
this.$set(tableList, "tableLoading", false)
|
||||
this.$set(tableList, "tableRefreshing", false)
|
||||
tableList.$emit("update:page_form", {...tableList.localPageForm})
|
||||
}
|
||||
this.$set(this, "pendingListCache", null)
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
const cards = Array.from(document.querySelectorAll(".proposal-delegation-list .table-list-item"))
|
||||
const scrollContainer = this.getListScrollContainer()
|
||||
if (!scrollContainer) return
|
||||
const candidateIds = [targetRowId, anchor.nextRowId, anchor.previousRowId]
|
||||
.filter((id) => id)
|
||||
.map((id) => String(id))
|
||||
.filter((id, index, values) => values.indexOf(id) === index)
|
||||
let targetIndex = -1
|
||||
candidateIds.some((candidateId) => {
|
||||
targetIndex = (tableState.tableData || []).findIndex((item) => String(item.id) === candidateId)
|
||||
return targetIndex >= 0
|
||||
})
|
||||
const targetCard = targetIndex >= 0 ? cards[targetIndex] : null
|
||||
if (targetCard) {
|
||||
const restoreTargetCard = () => {
|
||||
if (!targetCard.isConnected) return
|
||||
const targetTop = scrollContainer.scrollTop + targetCard.getBoundingClientRect().top - Number(anchor.viewportTop || 0)
|
||||
scrollContainer.scrollTo(0, Math.max(0, targetTop))
|
||||
}
|
||||
restoreTargetCard()
|
||||
setTimeout(restoreTargetCard, 100)
|
||||
return
|
||||
}
|
||||
scrollContainer.scrollTo(0, Number(anchor.scrollTop || 0))
|
||||
}, 0)
|
||||
})
|
||||
},
|
||||
|
||||
onRevoke(row){
|
||||
this.$dialog.confirm({
|
||||
title: "提示",
|
||||
@@ -176,12 +635,25 @@ layout("/layouts/platform_h5.html"){
|
||||
},
|
||||
|
||||
doSearch() {
|
||||
this.clearListCache()
|
||||
this.$nextTick(() => {
|
||||
this.pageForm.pageNumber = 1
|
||||
this.pageForm.totalCount = 0
|
||||
this.$refs.tableListRef.doSearch()
|
||||
})
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.prepareListCache()
|
||||
if (this.pendingListCache) return
|
||||
const tab = new URLSearchParams(window.location.search).get("tab")
|
||||
if (tab === "done") {
|
||||
this.$set(this.pageForm, "approval", true)
|
||||
this.$set(this.pageForm, "approvalText", "1")
|
||||
} else if (tab === "todo") {
|
||||
this.$set(this.pageForm, "approval", false)
|
||||
this.$set(this.pageForm, "approvalText", "0")
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" :title="pageTitle" placeholder fixed></van-nav-bar>
|
||||
<proposal-info v-if="bizId" :biz-id="bizId">
|
||||
<div v-if="isApproval">
|
||||
<div class="process-title">{{formData.taskName}}</div>
|
||||
<van-form ref="formRef">
|
||||
<van-field
|
||||
v-model="formData.tf_opinion"
|
||||
name="tf_opinion"
|
||||
label="审批意见"
|
||||
placeholder="请输入审批意见"
|
||||
:rules="[{ required: true, message: '请填写审批意见' }]"
|
||||
required
|
||||
maxlength="100"
|
||||
show-word-limit
|
||||
></van-field>
|
||||
</van-form>
|
||||
<div style="display: flex; justify-content: space-between; column-gap: 10px; padding: 10px">
|
||||
<van-button type="danger" block :loading="formLoading" @click="handleTaskAction(6)">退回</van-button>
|
||||
<van-button type="danger" block :loading="formLoading" @click="handleTaskAction(2)">不同意</van-button>
|
||||
<van-button type="primary" block :loading="formLoading" @click="handleTaskAction(1)">同意</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</proposal-info>
|
||||
</div>
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include("../../common/info.js"){}#-->
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
components: {
|
||||
"proposal-info": PROPOSAL_INFO
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
bizId: "",
|
||||
action: "view",
|
||||
formData: {},
|
||||
formLoading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isApproval() {
|
||||
return this.action === "approval"
|
||||
},
|
||||
pageTitle() {
|
||||
return this.isApproval ? "团长审核" : "提案详情"
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 提交团长审核结果。
|
||||
* val 为 WF 提交类型:1 同意、2 不同意、6 退回;接口返回 Result。
|
||||
*/
|
||||
handleTaskAction(val) {
|
||||
this.$refs.formRef.validate()
|
||||
.then(() => this.$dialog.confirm({title: "提示", message: "您确定要提交吗?"}))
|
||||
.then(() => {
|
||||
this.formLoading = true
|
||||
return this.$axios.post("/flow/common/executeTask", {
|
||||
data: JSON.stringify({
|
||||
...this.formData,
|
||||
submitType: val
|
||||
})
|
||||
})
|
||||
})
|
||||
.then((res) => {
|
||||
if (res && res.code === 0) {
|
||||
this.markListCacheCompleted()
|
||||
this.$toast.success(res.msg)
|
||||
this.historyBack()
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
this.formLoading = false
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 团长审核成功后标记已处理提案主键。
|
||||
* 返回未审核列表时根据该标记重新查询原浏览范围,并定位到相邻记录。
|
||||
*/
|
||||
markListCacheCompleted() {
|
||||
const cacheKey = "proposal:delegation:h5:list-state"
|
||||
try {
|
||||
const cacheText = sessionStorage.getItem(cacheKey)
|
||||
if (!cacheText) return
|
||||
const cache = JSON.parse(cacheText)
|
||||
cache.completedBizId = this.bizId
|
||||
cache.time = Date.now()
|
||||
sessionStorage.setItem(cacheKey, JSON.stringify(cache))
|
||||
} catch (error) {
|
||||
sessionStorage.removeItem(cacheKey)
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
// bizId 用于查询详情,taskId 和 taskName 用于构造 WF 审核提交数据。
|
||||
this.bizId = params.get("bizId") || ""
|
||||
this.action = params.get("action") || "view"
|
||||
if (this.isApproval) {
|
||||
this.$set(this.formData, "processTaskId", params.get("taskId") || "")
|
||||
this.$set(this.formData, "taskName", params.get("taskName") || "团长审核")
|
||||
}
|
||||
if (!this.bizId) {
|
||||
this.$toast.fail("缺少提案参数")
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
+595
-191
@@ -1,134 +1,329 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
<style>
|
||||
body {
|
||||
background-color: #f3f7fd;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-page {
|
||||
min-height: 100vh;
|
||||
padding-bottom: calc(24px + env(safe-area-inset-bottom));
|
||||
box-sizing: border-box;
|
||||
background-color: #f3f7fd;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-page .van-nav-bar {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-page .van-nav-bar__title {
|
||||
color: #172033;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-page .van-nav-bar .van-icon,
|
||||
.proposal-feedback-evaluation-page .van-nav-bar__text {
|
||||
color: #1989fa;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-content {
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-sticky {
|
||||
padding: 22px 12px 1px;
|
||||
box-sizing: border-box;
|
||||
background-color: #f3f7fd;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-search {
|
||||
margin: 0 0 8px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
border-radius: 12px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 5px 16px rgba(43, 73, 112, .1);
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-search .van-search__content {
|
||||
height: 42px;
|
||||
padding-left: 12px;
|
||||
align-items: center;
|
||||
border-radius: 12px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-search .van-field__control {
|
||||
color: #263548;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-search .van-field__control::placeholder {
|
||||
color: #a7b0bf;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-filter {
|
||||
margin: 0 0 10px;
|
||||
overflow: hidden;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
|
||||
transition: border-radius .18s ease;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-filter.is-open {
|
||||
border-radius: 10px 10px 0 0;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-filter .van-dropdown-menu__bar,
|
||||
.proposal-feedback-evaluation-filter .van-dropdown-menu__item,
|
||||
.proposal-feedback-evaluation-filter .van-dropdown-menu__title {
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-filter .van-dropdown-menu__bar {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-filter .van-dropdown-menu__item,
|
||||
.proposal-feedback-evaluation-filter .van-dropdown-menu__title {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-filter .van-dropdown-menu__item {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-filter .van-dropdown-menu__title {
|
||||
display: inline-flex;
|
||||
color: #66758a;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-filter .van-dropdown-menu__title--active,
|
||||
.proposal-feedback-evaluation-filter .van-dropdown-item__option--active,
|
||||
.proposal-feedback-evaluation-filter .van-dropdown-item__option--active .van-dropdown-item__icon {
|
||||
color: #1989fa;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-filter .van-dropdown-menu__title--down::after {
|
||||
border-color: transparent transparent #1989fa #1989fa;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-filter .van-dropdown-item__content {
|
||||
width: calc(100% - 24px);
|
||||
margin: 0 12px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
border-radius: 0 0 12px 12px;
|
||||
box-shadow: 0 10px 24px rgba(31, 65, 108, .14);
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-filter .van-dropdown-item__option {
|
||||
display: flex;
|
||||
min-height: 48px;
|
||||
padding: 0 16px;
|
||||
align-items: center;
|
||||
color: #46566c;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-filter .van-cell::after {
|
||||
right: 16px;
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-list .empty-state {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-empty {
|
||||
display: flex;
|
||||
min-height: 280px;
|
||||
padding: 46px 18px 36px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-empty img {
|
||||
display: block;
|
||||
width: 86%;
|
||||
max-width: 290px;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-empty__title {
|
||||
margin-top: 14px;
|
||||
color: #50627a;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-empty__hint {
|
||||
margin-top: 5px;
|
||||
color: #9aa7b8;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-card-header {
|
||||
display: flex;
|
||||
margin-bottom: 7px;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-card-heading {
|
||||
min-width: 0;
|
||||
padding-right: 10px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-card-title {
|
||||
overflow: hidden;
|
||||
color: #253247;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
font-weight: 600;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-card-code {
|
||||
margin-top: 1px;
|
||||
color: #9ba6b6;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-status {
|
||||
display: inline-flex;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.proposal-feedback-evaluation-status .van-tag {
|
||||
display: inline-flex;
|
||||
min-height: 24px;
|
||||
padding: 3px 9px;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
border: 0;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media (max-width: 350px) {
|
||||
.proposal-feedback-evaluation-content,
|
||||
.proposal-feedback-evaluation-sticky {
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="反馈评价" placeholder fixed></van-nav-bar>
|
||||
<van-sticky offset-top="46px">
|
||||
<van-search
|
||||
v-model="pageForm.name"
|
||||
:show-action="false"
|
||||
:reverse-color="false"
|
||||
input-align="left"
|
||||
placeholder="请输入提案名称搜索"
|
||||
@search="doSearch"
|
||||
></van-search>
|
||||
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
|
||||
<van-dropdown-item v-model="pageForm.sessionId" :options="sessionOptions" :multiple="false"
|
||||
@change="doSearch"></van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
<van-tabs v-model="pageForm.approvalText"
|
||||
@change="(val)=>{this.pageForm.approval = val==='1';this.doSearch();}">
|
||||
<van-tab title="已评价" name="1"></van-tab>
|
||||
<van-tab title="未评价" name="0"></van-tab>
|
||||
</van-tabs>
|
||||
</van-sticky>
|
||||
|
||||
<table-list api="/platform/proposal/feedbackEvaluation/pageData" :page_form.sync="pageForm" @ready="onReady"
|
||||
ref="tableListRef"
|
||||
title="name">
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="提案编号">{{row.code}}</table-column>
|
||||
<table-column label="提案类别">{{row.typeName}}</table-column>
|
||||
<table-column label="提案人">{{row.createUserName}}</table-column>
|
||||
<table-column label="代表团">{{row.delegationName}}</table-column>
|
||||
<table-column label="当前节点">{{row.curTaskName}}</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row)">
|
||||
<i class="fa fa-eye"></i>
|
||||
<span>查看</span>
|
||||
<div class="proposal-feedback-evaluation-page">
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="反馈评价" placeholder fixed></van-nav-bar>
|
||||
<van-sticky :offset-top="46">
|
||||
<div class="proposal-feedback-evaluation-sticky">
|
||||
<van-search
|
||||
v-model="pageForm.name"
|
||||
class="proposal-feedback-evaluation-search"
|
||||
placeholder="请输入提案名称搜索"
|
||||
shape="round"
|
||||
clearable
|
||||
@search="doSearch"
|
||||
@clear="doSearch"
|
||||
></van-search>
|
||||
<van-dropdown-menu
|
||||
class="proposal-feedback-evaluation-filter"
|
||||
:class="{'is-open': filterOpened}"
|
||||
active-color="#1989fa"
|
||||
>
|
||||
<van-dropdown-item
|
||||
v-model="pageForm.sessionId"
|
||||
:options="sessionOptions"
|
||||
:multiple="false"
|
||||
@change="doSearch"
|
||||
@open="filterOpened = true"
|
||||
@close="filterOpened = false"
|
||||
></van-dropdown-item>
|
||||
<van-dropdown-item
|
||||
v-model="pageForm.approvalText"
|
||||
:options="approvalOptions"
|
||||
:multiple="false"
|
||||
@change="approvalChange"
|
||||
@open="filterOpened = true"
|
||||
@close="filterOpened = false"
|
||||
></van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
</div>
|
||||
<div class="action-btn" v-if="row.taskState === 10" @click="onApproval(row)">
|
||||
<i class="fa fa-edit"></i>
|
||||
<span>评价</span>
|
||||
</div>
|
||||
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)">
|
||||
<i class="fa fa-reply"></i>
|
||||
<span>撤回</span>
|
||||
</div>
|
||||
</template>
|
||||
</table-list>
|
||||
</van-sticky>
|
||||
|
||||
<proposal-info ref="proposalInfoRef">
|
||||
<div v-if="showApprovalForm">
|
||||
<div class="process-title">{{formData.taskName}}</div>
|
||||
<van-form ref="formRef">
|
||||
<van-field
|
||||
v-model="formData.tf_feedback_name"
|
||||
name="tf_feedback_name"
|
||||
label="满意度"
|
||||
placeholder="请选择满意度"
|
||||
:rules="[{ required: true, message: '请选择满意度' }]"
|
||||
required
|
||||
is-link
|
||||
@click="showFeedBackPicker = true"
|
||||
></van-field>
|
||||
<van-popup v-model="showFeedBackPicker" position="bottom">
|
||||
<van-picker
|
||||
show-toolbar
|
||||
:columns="dict.type.PROPOSAL_FEEDBACK.map(item => ({text: item.label, value: item.code}))"
|
||||
@confirm="onFeedBackConfirm"
|
||||
@cancel="showFeedBackPicker = false"
|
||||
></van-picker>
|
||||
</van-popup>
|
||||
|
||||
<van-field
|
||||
v-model="formData.tf_opinion"
|
||||
name="tf_opinion"
|
||||
label="审批意见"
|
||||
placeholder="请输入审批意见"
|
||||
:rules="[{ required: true, message: '请填写审批意见' }]"
|
||||
required
|
||||
maxlength="100"
|
||||
show-word-limit
|
||||
></van-field>
|
||||
|
||||
<template v-if="['SATISFIED','FAIRLY_SATISFIED'].includes(formData.tf_feedback)">
|
||||
<van-field
|
||||
v-model="formData.tf_address"
|
||||
name="tf_address"
|
||||
label="通讯地址"
|
||||
placeholder="请输入通讯地址"
|
||||
:rules="[{ required: true, message: '请输入通讯地址' }]"
|
||||
required
|
||||
show-word-limit
|
||||
></van-field>
|
||||
<!--联系电话-->
|
||||
<van-field
|
||||
v-model="formData.tf_phone"
|
||||
name="tf_phone"
|
||||
label="联系电话"
|
||||
placeholder="请输入联系电话"
|
||||
:rules="[{ required: true, message: '请输入联系电话' }]"
|
||||
required
|
||||
show-word-limit
|
||||
></van-field>
|
||||
<!--邮政编码-->
|
||||
<van-field
|
||||
v-model="formData.tf_postcode"
|
||||
name="tf_postcode"
|
||||
label="邮政编码"
|
||||
placeholder="请输入邮政编码"
|
||||
:rules="[{ required: true, message: '请输入邮政编码' }]"
|
||||
required
|
||||
show-word-limit
|
||||
></van-field>
|
||||
</template>
|
||||
</van-form>
|
||||
<div style="display: flex; justify-content: space-between; column-gap: 10px; padding: 10px">
|
||||
<van-button type="danger" block @click="$refs.proposalInfoRef.onClose()">取消</van-button>
|
||||
<van-button type="primary" block @click="handleTaskAction(1)">提交</van-button>
|
||||
<main class="proposal-feedback-evaluation-content">
|
||||
<div class="proposal-feedback-evaluation-list">
|
||||
<!-- 列表无记录时使用与团长审核一致的提案专用空状态。 -->
|
||||
<div v-if="pageForm.totalCount === 0" class="proposal-feedback-evaluation-empty">
|
||||
<img src="/assets/mobile/img/proposal/proposal-empty.png" alt="暂无提案记录插画">
|
||||
<div class="proposal-feedback-evaluation-empty__title">暂无提案记录</div>
|
||||
<div class="proposal-feedback-evaluation-empty__hint">当前筛选条件下暂时没有提案</div>
|
||||
</div>
|
||||
<table-list api="/platform/proposal/feedbackEvaluation/pageData" :page_form.sync="pageForm" @ready="onReady"
|
||||
ref="tableListRef"
|
||||
title="name">
|
||||
<template #header="{index,row}">
|
||||
<div class="proposal-feedback-evaluation-card-header">
|
||||
<div class="proposal-feedback-evaluation-card-heading">
|
||||
<div class="proposal-feedback-evaluation-card-title">{{row.name}}</div>
|
||||
<div class="proposal-feedback-evaluation-card-code">编号:{{row.code || '--'}}</div>
|
||||
</div>
|
||||
<enum-tag
|
||||
class="proposal-feedback-evaluation-status"
|
||||
v-if="row.instanceState"
|
||||
:value="row.instanceState"
|
||||
name="ProcessInstanceStateEnum"
|
||||
label_key="message"
|
||||
size="small"
|
||||
></enum-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="提案类别">{{row.typeName || '--'}}</table-column>
|
||||
<table-column label="提案人">{{row.createUserName || '--'}}</table-column>
|
||||
<table-column label="代表团">{{row.delegationName || '--'}}</table-column>
|
||||
<table-column label="当前节点">{{row.curTaskName || '--'}}</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row,index)">
|
||||
<i class="fa fa-eye"></i>
|
||||
<span>查看</span>
|
||||
</div>
|
||||
<div class="action-btn" v-if="row.taskState === 10" @click="onApproval(row,index)">
|
||||
<i class="fa fa-edit"></i>
|
||||
<span>评价</span>
|
||||
</div>
|
||||
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)">
|
||||
<i class="fa fa-reply"></i>
|
||||
<span>撤回</span>
|
||||
</div>
|
||||
</template>
|
||||
</table-list>
|
||||
</div>
|
||||
</div>
|
||||
</proposal-info>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include("../../common/info.js"){}#-->
|
||||
|
||||
const vue = new Vue({
|
||||
el: "#app",
|
||||
dicts: ["PROPOSAL_FEEDBACK"],
|
||||
store,
|
||||
data() {
|
||||
return {
|
||||
@@ -136,108 +331,304 @@ layout("/layouts/platform_h5.html"){
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
pageOrderName: "",
|
||||
pageOrderBy: "",
|
||||
name: null,
|
||||
sessionId: null,
|
||||
approvalText: "0",
|
||||
approval: false
|
||||
},
|
||||
|
||||
sessionOptions: [],
|
||||
formData: {},
|
||||
showApprovalForm: false,
|
||||
|
||||
showFeedBackPicker: false
|
||||
filterOpened: false,
|
||||
approvalOptions: [
|
||||
{text: "未评价", value: "0"},
|
||||
{text: "已评价", value: "1"}
|
||||
],
|
||||
pendingListCache: null
|
||||
}
|
||||
},
|
||||
components: {
|
||||
"proposal-info": PROPOSAL_INFO
|
||||
},
|
||||
methods: {
|
||||
onReady() {
|
||||
this.listSession()
|
||||
if (this.pendingListCache) {
|
||||
this.restoreListCache()
|
||||
this.listSession(true)
|
||||
return
|
||||
}
|
||||
this.listSession(false)
|
||||
},
|
||||
listSession() {
|
||||
|
||||
// 评价状态下拉框使用字符串值展示,查询接口使用布尔值区分已评价和未评价。
|
||||
approvalChange(value) {
|
||||
this.$set(this.pageForm, "approval", value === "1")
|
||||
this.doSearch()
|
||||
},
|
||||
|
||||
// 缓存恢复时只加载届次选项,不覆盖离开列表前的筛选条件和列表快照。
|
||||
listSession(skipDefaultSearch) {
|
||||
this.$axios.post("/platform/proposal/common/listSession").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.sessionOptions = [
|
||||
{
|
||||
text: "全部届次",
|
||||
value: null
|
||||
}
|
||||
].concat(res.data.map((v) => ({text: v.fullName, value: v.id})))
|
||||
if (this.sessionOptions.length > 0) {
|
||||
this.pageForm.sessionId = this.sessionOptions[0].value
|
||||
this.doSearch()
|
||||
}
|
||||
this.sessionOptions = [{text: "全部届次", value: null}]
|
||||
.concat(res.data.map((item) => ({text: item.fullName, value: item.id})))
|
||||
if (skipDefaultSearch) return
|
||||
// 保留反馈评价原有默认口径:首次进入查询全部届次。
|
||||
this.$set(this.pageForm, "sessionId", null)
|
||||
this.doSearch()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onView(row) {
|
||||
this.showApprovalForm = false
|
||||
this.$refs.proposalInfoRef.onOpen(row)
|
||||
onView(row, index) {
|
||||
this.saveListCache(row, index)
|
||||
this.$pjaxReplace("/platform/proposal/feedbackEvaluation/info/h5?bizId=" + encodeURIComponent(row.id) + "&action=view")
|
||||
},
|
||||
|
||||
onApproval(row) {
|
||||
this.showApprovalForm = true
|
||||
this.$refs.proposalInfoRef.onOpen(row)
|
||||
this.formData = {
|
||||
processTaskId: row.taskId,
|
||||
taskName: row.curTaskName,
|
||||
instanceId: row.instanceId
|
||||
onApproval(row, index) {
|
||||
this.saveListCache(row, index)
|
||||
const params = new URLSearchParams()
|
||||
params.set("bizId", row.id)
|
||||
params.set("action", "approval")
|
||||
params.set("taskId", row.taskId)
|
||||
params.set("taskName", row.curTaskName || "反馈评价")
|
||||
params.set("instanceId", row.instanceId)
|
||||
this.$pjaxReplace("/platform/proposal/feedbackEvaluation/info/h5?" + params.toString())
|
||||
},
|
||||
|
||||
getListCacheKey() {
|
||||
return "proposal:feedback-evaluation:h5:list-state"
|
||||
},
|
||||
|
||||
getListCacheTokenParam() {
|
||||
return "feedbackEvaluationListCacheToken"
|
||||
},
|
||||
|
||||
getCurrentListCacheToken() {
|
||||
return new URLSearchParams(window.location.search).get(this.getListCacheTokenParam()) || ""
|
||||
},
|
||||
|
||||
/**
|
||||
* 将反馈评价列表缓存标识绑定到当前历史记录。
|
||||
* PJAX 返回时仅恢复当前历史记录对应的筛选条件、分页数据和滚动快照。
|
||||
*/
|
||||
bindListCacheToken(token) {
|
||||
const currentUrl = new URL(window.location.href)
|
||||
currentUrl.searchParams.set(this.getListCacheTokenParam(), token)
|
||||
const currentState = window.history.state ? {...window.history.state} : null
|
||||
if (currentState && currentState.url) {
|
||||
currentState.url = currentUrl.toString()
|
||||
}
|
||||
window.history.replaceState(currentState, document.title, currentUrl.toString())
|
||||
if ($.pjax && $.pjax.state) {
|
||||
$.pjax.state.url = currentUrl.toString()
|
||||
}
|
||||
},
|
||||
|
||||
onFeedBackConfirm({value, text}) {
|
||||
this.formData.tf_feedback_name = text
|
||||
this.formData.tf_feedback = value
|
||||
this.showFeedBackPicker = false
|
||||
// 用户主动搜索、切换届次或评价状态时使旧返回缓存失效。
|
||||
clearListCache() {
|
||||
const token = this.getCurrentListCacheToken()
|
||||
if (!token) return
|
||||
const currentUrl = new URL(window.location.href)
|
||||
currentUrl.searchParams.delete(this.getListCacheTokenParam())
|
||||
const currentState = window.history.state ? {...window.history.state} : null
|
||||
if (currentState && currentState.url) {
|
||||
currentState.url = currentUrl.toString()
|
||||
}
|
||||
window.history.replaceState(currentState, document.title, currentUrl.toString())
|
||||
if ($.pjax && $.pjax.state) {
|
||||
$.pjax.state.url = currentUrl.toString()
|
||||
}
|
||||
sessionStorage.removeItem(this.getListCacheKey())
|
||||
},
|
||||
|
||||
async handleTaskAction(val) {
|
||||
// platform_h5 的实际滚动区域是 page-wrapper,列表位置必须基于该容器保存和恢复。
|
||||
getListScrollContainer() {
|
||||
return document.querySelector(".page-wrapper") || document.scrollingElement || document.documentElement
|
||||
},
|
||||
|
||||
/**
|
||||
* 进入查看或评价详情前保存列表现场。
|
||||
* row 为当前反馈任务,index 为已加载列表索引;任务和相邻锚点统一使用唯一 taskId。
|
||||
*/
|
||||
saveListCache(row, index) {
|
||||
const tableList = this.$refs.tableListRef
|
||||
if (!tableList) return
|
||||
const cacheToken = Date.now() + "-" + Math.random().toString(36).slice(2)
|
||||
const tableState = typeof tableList.getListState === "function"
|
||||
? tableList.getListState()
|
||||
: {
|
||||
tableData: JSON.parse(JSON.stringify(tableList.tableData || [])),
|
||||
localPageForm: JSON.parse(JSON.stringify(tableList.localPageForm || this.pageForm)),
|
||||
tableFinished: !!tableList.tableFinished
|
||||
}
|
||||
const cards = document.querySelectorAll(".proposal-feedback-evaluation-list .table-list-item")
|
||||
const currentCard = cards[index]
|
||||
const scrollContainer = this.getListScrollContainer()
|
||||
const cache = {
|
||||
time: Date.now(),
|
||||
token: cacheToken,
|
||||
pageForm: JSON.parse(JSON.stringify(this.pageForm)),
|
||||
tableState: tableState,
|
||||
completedTaskId: "",
|
||||
anchor: {
|
||||
taskId: row.taskId,
|
||||
previousTaskId: index > 0 && tableState.tableData[index - 1]
|
||||
? tableState.tableData[index - 1].taskId
|
||||
: "",
|
||||
nextTaskId: tableState.tableData[index + 1] ? tableState.tableData[index + 1].taskId : "",
|
||||
viewportTop: currentCard ? currentCard.getBoundingClientRect().top : 0,
|
||||
scrollTop: scrollContainer ? scrollContainer.scrollTop : 0
|
||||
}
|
||||
}
|
||||
try {
|
||||
await this.$refs.formRef.validate();
|
||||
this.$dialog.confirm({
|
||||
title: "提示",
|
||||
message: "您确定要提交吗?"
|
||||
}).then(async () => {
|
||||
debugger
|
||||
const formData = {
|
||||
...this.formData,
|
||||
submitType: val
|
||||
}
|
||||
// 不满意
|
||||
if (this.formData.tf_feedback === 'DISSATISFIED') {
|
||||
const caseInfo = await this.getCaseInfo(this.formData.instanceId)
|
||||
formData.tf_masterUnitId = caseInfo.tf_masterUnitId
|
||||
}
|
||||
|
||||
this.$axios.post("/flow/common/executeTask", {
|
||||
data: JSON.stringify(formData)
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$refs.proposalInfoRef.onClose()
|
||||
this.$toast.success(res.msg)
|
||||
this.doSearch()
|
||||
}
|
||||
})
|
||||
}).catch(() => {
|
||||
|
||||
})
|
||||
sessionStorage.setItem(this.getListCacheKey(), JSON.stringify(cache))
|
||||
this.bindListCacheToken(cacheToken)
|
||||
} catch (error) {
|
||||
sessionStorage.removeItem(this.getListCacheKey())
|
||||
}
|
||||
},
|
||||
|
||||
// 仅恢复当前历史记录对应且十分钟内有效的反馈评价列表缓存。
|
||||
prepareListCache() {
|
||||
try {
|
||||
const cacheToken = this.getCurrentListCacheToken()
|
||||
if (!cacheToken) return
|
||||
const cacheText = sessionStorage.getItem(this.getListCacheKey())
|
||||
if (!cacheText) return
|
||||
const cache = JSON.parse(cacheText)
|
||||
if (!cache || cache.token !== cacheToken || !cache.tableState) return
|
||||
if (Date.now() - cache.time > 10 * 60 * 1000) {
|
||||
this.clearListCache()
|
||||
return
|
||||
}
|
||||
Object.keys(cache.pageForm || {}).forEach((key) => {
|
||||
this.$set(this.pageForm, key, cache.pageForm[key])
|
||||
})
|
||||
this.$set(this, "pendingListCache", cache)
|
||||
} catch (error) {
|
||||
sessionStorage.removeItem(this.getListCacheKey())
|
||||
}
|
||||
},
|
||||
// 获取立案信息
|
||||
async getCaseInfo(instId) {
|
||||
const {
|
||||
data,
|
||||
code
|
||||
} = await this.$axios.post('/platform/proposal/feedbackEvaluation/caseInfo', {instId})
|
||||
if (code === 0) {
|
||||
return data
|
||||
|
||||
// 普通查看返回恢复旧快照;评价完成返回重新查询,避免未评价列表继续显示已处理任务。
|
||||
restoreListCache() {
|
||||
const cache = this.pendingListCache
|
||||
const tableList = this.$refs.tableListRef
|
||||
if (!cache || !tableList) return
|
||||
const anchor = cache.anchor || {}
|
||||
if (cache.completedTaskId) {
|
||||
this.refreshCompletedListCache(cache)
|
||||
return
|
||||
}
|
||||
this.applyListCache(cache.tableState, anchor.taskId, anchor)
|
||||
},
|
||||
|
||||
/**
|
||||
* 按离开前已加载页数重新获取评价后的列表。
|
||||
* 返回值转换为 TableList 状态,并优先定位下一任务,没有下一任务时定位上一任务。
|
||||
*/
|
||||
refreshCompletedListCache(cache) {
|
||||
const originalPageSize = Number(cache.pageForm.pageSize || 10)
|
||||
const loadedCount = Math.max(cache.tableState.tableData.length, originalPageSize)
|
||||
const loadedPageCount = Math.max(1, Math.ceil(loadedCount / originalPageSize))
|
||||
const requestForm = {
|
||||
...cache.pageForm,
|
||||
pageNumber: 1,
|
||||
pageSize: loadedPageCount * originalPageSize
|
||||
}
|
||||
const loading = createListLoading()
|
||||
this.$axios.post("/platform/proposal/feedbackEvaluation/pageData", requestForm, {h5SkeletonLoading: true})
|
||||
.then((res) => {
|
||||
if (res.code !== 0) {
|
||||
this.applyCompletedCacheFallback(cache)
|
||||
return
|
||||
}
|
||||
const freshState = {
|
||||
tableData: res.data.list || [],
|
||||
localPageForm: {
|
||||
...cache.pageForm,
|
||||
pageNumber: loadedPageCount,
|
||||
pageSize: originalPageSize,
|
||||
totalCount: res.data.totalCount
|
||||
},
|
||||
tableFinished: (res.data.list || []).length >= res.data.totalCount
|
||||
}
|
||||
const anchor = cache.anchor || {}
|
||||
this.applyListCache(freshState, anchor.nextTaskId || anchor.previousTaskId, anchor)
|
||||
})
|
||||
.catch(() => {
|
||||
this.applyCompletedCacheFallback(cache)
|
||||
})
|
||||
.finally(() => {
|
||||
loading.close()
|
||||
})
|
||||
},
|
||||
|
||||
// 评价后刷新失败时只处理已完成 taskId,避免误改同一提案的其他流程记录。
|
||||
applyCompletedCacheFallback(cache) {
|
||||
const tableState = cache.tableState
|
||||
const completedIndex = tableState.tableData.findIndex((item) => String(item.taskId) === String(cache.completedTaskId))
|
||||
if (completedIndex >= 0 && cache.pageForm.approval === false) {
|
||||
tableState.tableData.splice(completedIndex, 1)
|
||||
if (tableState.localPageForm && tableState.localPageForm.totalCount > 0) {
|
||||
tableState.localPageForm.totalCount--
|
||||
}
|
||||
} else if (completedIndex >= 0) {
|
||||
this.$set(tableState.tableData[completedIndex], "taskState", 20)
|
||||
}
|
||||
const anchor = cache.anchor || {}
|
||||
this.applyListCache(tableState, anchor.nextTaskId || anchor.previousTaskId, anchor)
|
||||
},
|
||||
|
||||
/**
|
||||
* 恢复 TableList 数据并将目标任务卡片放回离开页面前的视口位置。
|
||||
* sticky 吸顶会改变一次布局,因此首次滚动后再执行一次位置校正。
|
||||
*/
|
||||
applyListCache(tableState, targetTaskId, anchor) {
|
||||
const tableList = this.$refs.tableListRef
|
||||
if (!tableList) return
|
||||
if (typeof tableList.restoreListState === "function") {
|
||||
if (!tableList.restoreListState(tableState)) return
|
||||
} else {
|
||||
this.$set(tableList, "tableData", tableState.tableData || [])
|
||||
this.$set(tableList, "localPageForm", {...tableList.localPageForm, ...(tableState.localPageForm || {})})
|
||||
this.$set(tableList, "tableFinished", !!tableState.tableFinished)
|
||||
this.$set(tableList, "tableLoading", false)
|
||||
this.$set(tableList, "tableRefreshing", false)
|
||||
tableList.$emit("update:page_form", {...tableList.localPageForm})
|
||||
}
|
||||
this.$set(this, "pendingListCache", null)
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
const cards = Array.from(document.querySelectorAll(".proposal-feedback-evaluation-list .table-list-item"))
|
||||
const scrollContainer = this.getListScrollContainer()
|
||||
if (!scrollContainer) return
|
||||
const candidateTaskIds = [targetTaskId, anchor.nextTaskId, anchor.previousTaskId]
|
||||
.filter((taskId) => taskId)
|
||||
.map((taskId) => String(taskId))
|
||||
.filter((taskId, index, values) => values.indexOf(taskId) === index)
|
||||
let targetIndex = -1
|
||||
candidateTaskIds.some((candidateTaskId) => {
|
||||
targetIndex = (tableState.tableData || [])
|
||||
.findIndex((item) => String(item.taskId) === candidateTaskId)
|
||||
return targetIndex >= 0
|
||||
})
|
||||
const targetCard = targetIndex >= 0 ? cards[targetIndex] : null
|
||||
if (targetCard) {
|
||||
const restoreTargetCard = () => {
|
||||
if (!targetCard.isConnected) return
|
||||
const targetTop = scrollContainer.scrollTop
|
||||
+ targetCard.getBoundingClientRect().top
|
||||
- Number(anchor.viewportTop || 0)
|
||||
scrollContainer.scrollTo(0, Math.max(0, targetTop))
|
||||
}
|
||||
restoreTargetCard()
|
||||
setTimeout(restoreTargetCard, 100)
|
||||
return
|
||||
}
|
||||
scrollContainer.scrollTo(0, Number(anchor.scrollTop || 0))
|
||||
}, 0)
|
||||
})
|
||||
},
|
||||
|
||||
onRevoke(row) {
|
||||
this.$dialog.confirm({
|
||||
title: "提示",
|
||||
@@ -253,12 +644,25 @@ layout("/layouts/platform_h5.html"){
|
||||
},
|
||||
|
||||
doSearch() {
|
||||
this.clearListCache()
|
||||
this.$nextTick(() => {
|
||||
this.pageForm.pageNumber = 1
|
||||
this.pageForm.totalCount = 0
|
||||
this.$refs.tableListRef.doSearch()
|
||||
})
|
||||
},
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.prepareListCache()
|
||||
if (this.pendingListCache) return
|
||||
const tab = new URLSearchParams(window.location.search).get("tab")
|
||||
if (tab === "done") {
|
||||
this.$set(this.pageForm, "approval", true)
|
||||
this.$set(this.pageForm, "approvalText", "1")
|
||||
} else if (tab === "todo") {
|
||||
this.$set(this.pageForm, "approval", false)
|
||||
this.$set(this.pageForm, "approvalText", "0")
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" :title="pageTitle" placeholder fixed></van-nav-bar>
|
||||
<proposal-info v-if="bizId" :biz-id="bizId">
|
||||
<div v-if="isApproval">
|
||||
<div class="process-title">{{formData.taskName}}</div>
|
||||
<van-form ref="formRef">
|
||||
<van-field
|
||||
v-model="formData.tf_feedback_name"
|
||||
name="tf_feedback_name"
|
||||
label="满意度"
|
||||
placeholder="请选择满意度"
|
||||
:rules="[{ required: true, message: '请选择满意度' }]"
|
||||
required
|
||||
is-link
|
||||
@click="showFeedBackPicker = true"
|
||||
></van-field>
|
||||
<van-popup v-model="showFeedBackPicker" position="bottom">
|
||||
<van-picker
|
||||
show-toolbar
|
||||
:columns="dict.type.PROPOSAL_FEEDBACK.map(item => ({text: item.label, value: item.code}))"
|
||||
@confirm="onFeedBackConfirm"
|
||||
@cancel="showFeedBackPicker = false"
|
||||
></van-picker>
|
||||
</van-popup>
|
||||
<van-field
|
||||
v-model="formData.tf_opinion"
|
||||
name="tf_opinion"
|
||||
label="审批意见"
|
||||
placeholder="请输入审批意见"
|
||||
:rules="[{ required: true, message: '请填写审批意见' }]"
|
||||
required
|
||||
maxlength="100"
|
||||
show-word-limit
|
||||
></van-field>
|
||||
<!-- <template v-if="['SATISFIED','FAIRLY_SATISFIED'].includes(formData.tf_feedback)">
|
||||
<van-field
|
||||
v-model="formData.tf_address"
|
||||
name="tf_address"
|
||||
label="通讯地址"
|
||||
placeholder="请输入通讯地址"
|
||||
:rules="[{ required: true, message: '请输入通讯地址' }]"
|
||||
required
|
||||
></van-field>
|
||||
<van-field
|
||||
v-model="formData.tf_phone"
|
||||
name="tf_phone"
|
||||
label="联系电话"
|
||||
placeholder="请输入联系电话"
|
||||
:rules="[{ required: true, message: '请输入联系电话' }]"
|
||||
required
|
||||
></van-field>
|
||||
<van-field
|
||||
v-model="formData.tf_postcode"
|
||||
name="tf_postcode"
|
||||
label="邮政编码"
|
||||
placeholder="请输入邮政编码"
|
||||
:rules="[{ required: true, message: '请输入邮政编码' }]"
|
||||
required
|
||||
></van-field>
|
||||
</template>-->
|
||||
</van-form>
|
||||
<div style="display: flex; justify-content: space-between; column-gap: 10px; padding: 10px">
|
||||
<van-button type="danger" block :disabled="formLoading" @click="historyBack">取消</van-button>
|
||||
<van-button type="primary" block :loading="formLoading" @click="handleTaskAction">提交</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</proposal-info>
|
||||
</div>
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include("../../common/info.js"){}#-->
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
dicts: ["PROPOSAL_FEEDBACK"],
|
||||
components: {
|
||||
"proposal-info": PROPOSAL_INFO
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
bizId: "",
|
||||
action: "view",
|
||||
formData: {},
|
||||
formLoading: false,
|
||||
showFeedBackPicker: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isApproval() {
|
||||
return this.action === "approval"
|
||||
},
|
||||
pageTitle() {
|
||||
return this.isApproval ? "反馈评价" : "提案详情"
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onFeedBackConfirm(value) {
|
||||
this.$set(this.formData, "tf_feedback_name", value.text)
|
||||
this.$set(this.formData, "tf_feedback", value.value)
|
||||
this.showFeedBackPicker = false
|
||||
},
|
||||
/**
|
||||
* 提交反馈评价;不满意时先用 instanceId 查询立案主办单位,再提交完整 WF 表单数据。
|
||||
* 两个接口均返回 Result,code 为 0 表示成功。
|
||||
*/
|
||||
handleTaskAction() {
|
||||
this.$refs.formRef.validate()
|
||||
.then(() => this.$dialog.confirm({title: "提示", message: "您确定要提交吗?"}))
|
||||
.then(() => {
|
||||
this.formLoading = true
|
||||
const submitData = {...this.formData, submitType: 1}
|
||||
if (this.formData.tf_feedback !== "DISSATISFIED") {
|
||||
return submitData
|
||||
}
|
||||
return this.$axios.post("/platform/proposal/feedbackEvaluation/caseInfo", {
|
||||
instId: this.formData.instanceId
|
||||
}).then((res) => {
|
||||
if (res.code !== 0) {
|
||||
return Promise.reject(new Error(res.msg || "查询立案信息失败"))
|
||||
}
|
||||
submitData.tf_masterUnitId = res.data.tf_masterUnitId
|
||||
return submitData
|
||||
})
|
||||
})
|
||||
.then((submitData) => this.$axios.post("/flow/common/executeTask", {
|
||||
data: JSON.stringify(submitData)
|
||||
}))
|
||||
.then((res) => {
|
||||
if (res && res.code === 0) {
|
||||
this.markListCacheCompleted()
|
||||
this.$toast.success(res.msg)
|
||||
this.historyBack()
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error && error.message && error.message !== "cancel") {
|
||||
this.$toast.fail(error.message)
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.formLoading = false
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 评价成功后记录已处理 WF 任务主键。
|
||||
* 返回列表时按原筛选和已加载范围刷新数据,并定位到相邻评价任务。
|
||||
*/
|
||||
markListCacheCompleted() {
|
||||
const cacheKey = "proposal:feedback-evaluation:h5:list-state"
|
||||
try {
|
||||
const cacheText = sessionStorage.getItem(cacheKey)
|
||||
if (!cacheText) return
|
||||
const cache = JSON.parse(cacheText)
|
||||
cache.completedTaskId = this.formData.processTaskId || ""
|
||||
cache.time = Date.now()
|
||||
sessionStorage.setItem(cacheKey, JSON.stringify(cache))
|
||||
} catch (error) {
|
||||
sessionStorage.removeItem(cacheKey)
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
// bizId 用于详情查询,taskId、taskName、instanceId 用于反馈评价的 WF 提交与立案信息查询。
|
||||
this.bizId = params.get("bizId") || ""
|
||||
this.action = params.get("action") || "view"
|
||||
if (this.isApproval) {
|
||||
this.$set(this.formData, "processTaskId", params.get("taskId") || "")
|
||||
this.$set(this.formData, "taskName", params.get("taskName") || "反馈评价")
|
||||
this.$set(this.formData, "instanceId", params.get("instanceId") || "")
|
||||
}
|
||||
if (!this.bizId) {
|
||||
this.$toast.fail("缺少提案参数")
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
+374
-66
@@ -1,75 +1,384 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
<style>
|
||||
body {
|
||||
background-color: #f3f7fd;
|
||||
}
|
||||
|
||||
.proposal-mine-page {
|
||||
min-height: 100vh;
|
||||
padding-bottom: calc(24px + env(safe-area-inset-bottom));
|
||||
box-sizing: border-box;
|
||||
background-color: #f3f7fd;
|
||||
}
|
||||
|
||||
.proposal-mine-page .van-nav-bar {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.proposal-mine-page .van-nav-bar__title {
|
||||
color: #172033;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.proposal-mine-page .van-nav-bar .van-icon,
|
||||
.proposal-mine-page .van-nav-bar__text {
|
||||
color: #1989fa;
|
||||
}
|
||||
|
||||
.proposal-mine-content {
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.proposal-mine-sticky {
|
||||
padding: 22px 12px 1px;
|
||||
box-sizing: border-box;
|
||||
background-color: #f3f7fd;
|
||||
}
|
||||
|
||||
.proposal-mine-search {
|
||||
margin: 0 0 8px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
border-radius: 12px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 5px 16px rgba(43, 73, 112, .1);
|
||||
}
|
||||
|
||||
.proposal-mine-search .van-search__content {
|
||||
height: 42px;
|
||||
padding-left: 12px;
|
||||
align-items: center;
|
||||
border-radius: 12px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.proposal-mine-search .van-field__control {
|
||||
color: #263548;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.proposal-mine-search .van-field__control::placeholder {
|
||||
color: #a7b0bf;
|
||||
}
|
||||
|
||||
.proposal-mine-filter {
|
||||
margin: 0 0 10px;
|
||||
overflow: hidden;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
|
||||
transition: border-radius .18s ease;
|
||||
}
|
||||
|
||||
.proposal-mine-filter.is-open {
|
||||
border-radius: 10px 10px 0 0;
|
||||
}
|
||||
|
||||
.proposal-mine-filter .van-dropdown-menu__bar {
|
||||
height: 44px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.proposal-mine-filter .van-dropdown-menu__item {
|
||||
height: 44px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.proposal-mine-filter .van-dropdown-menu__title {
|
||||
display: inline-flex;
|
||||
height: 44px;
|
||||
align-items: center;
|
||||
line-height: 20px;
|
||||
color: #66758a;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.proposal-mine-filter .van-dropdown-menu__title--active,
|
||||
.proposal-mine-filter .van-dropdown-item__option--active,
|
||||
.proposal-mine-filter .van-dropdown-item__option--active .van-dropdown-item__icon {
|
||||
color: #1989fa;
|
||||
}
|
||||
|
||||
.proposal-mine-filter .van-dropdown-item__content {
|
||||
width: calc(100% - 24px);
|
||||
margin: 0 12px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
border-radius: 0 0 12px 12px;
|
||||
box-shadow: 0 10px 24px rgba(31, 65, 108, .14);
|
||||
}
|
||||
|
||||
.proposal-mine-filter .van-dropdown-item__option {
|
||||
display: flex;
|
||||
min-height: 48px;
|
||||
padding: 0 16px;
|
||||
align-items: center;
|
||||
color: #46566c;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.proposal-mine-filter .van-dropdown-item__option .van-cell__title {
|
||||
display: flex;
|
||||
min-height: 20px;
|
||||
align-items: center;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.proposal-mine-list {
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.proposal-mine-list .empty-state {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.proposal-mine-empty {
|
||||
display: flex;
|
||||
min-height: 280px;
|
||||
padding: 46px 18px 36px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.proposal-mine-empty img {
|
||||
display: block;
|
||||
width: 86%;
|
||||
max-width: 290px;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.proposal-mine-empty__title {
|
||||
margin-top: 14px;
|
||||
color: #50627a;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.proposal-mine-empty__hint {
|
||||
margin-top: 5px;
|
||||
color: #9aa7b8;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.proposal-invite-empty {
|
||||
display: flex;
|
||||
min-height: 300px;
|
||||
padding: 40px 18px 36px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.proposal-invite-empty img {
|
||||
display: block;
|
||||
width: 82%;
|
||||
max-width: 270px;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.proposal-invite-empty__title {
|
||||
margin-top: 14px;
|
||||
color: #50627a;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.proposal-invite-empty__hint {
|
||||
margin-top: 5px;
|
||||
color: #9aa7b8;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.proposal-card-header {
|
||||
display: flex;
|
||||
margin-bottom: 7px;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.proposal-card-heading {
|
||||
min-width: 0;
|
||||
padding-right: 10px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.proposal-card-title {
|
||||
overflow: hidden;
|
||||
color: #253247;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
font-weight: 600;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.proposal-card-code {
|
||||
margin-top: 1px;
|
||||
color: #9ba6b6;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.proposal-card-status {
|
||||
display: inline-flex;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.proposal-card-status .van-tag,
|
||||
.proposal-card-status--plain {
|
||||
display: inline-flex;
|
||||
min-height: 24px;
|
||||
padding: 3px 9px;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
border: 0;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.proposal-card-status--plain {
|
||||
color: #1989fa;
|
||||
background-color: #eaf4ff;
|
||||
}
|
||||
|
||||
@media (max-width: 350px) {
|
||||
.proposal-mine-content {
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.proposal-mine-sticky {
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="我的提案" placeholder fixed></van-nav-bar>
|
||||
<van-sticky offset-top="46px">
|
||||
<van-search
|
||||
:reverse-color="false"
|
||||
:show-action="false"
|
||||
@search="doSearch"
|
||||
input-align="left"
|
||||
placeholder="请输入提案名称搜索"
|
||||
v-model="pageForm.name"
|
||||
></van-search>
|
||||
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
|
||||
<van-dropdown-item :multiple="false" :options="sessionOptions" @change="doSearch"
|
||||
v-model="pageForm.sessionId"></van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
</van-sticky>
|
||||
<div class="proposal-mine-page">
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="我的提案" placeholder fixed></van-nav-bar>
|
||||
|
||||
<table-list api="/platform/proposal/mine/pageData" :page_form.sync="pageForm" @ready="onReady" ref="tableListRef"
|
||||
title="name">
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="提案编号">{{row.code}}</table-column>
|
||||
<table-column label="提案类别">{{row.typeName}}</table-column>
|
||||
<table-column label="提案人">{{row.createUserName}}</table-column>
|
||||
<table-column label="代表团">{{row.delegationName}}</table-column>
|
||||
<table-column label="当前节点">{{ (row.startTaskId) ? row.taskName : '待提交' }}</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row)">
|
||||
<i class="fa fa-eye"></i>
|
||||
<span>查看</span>
|
||||
<van-sticky :offset-top="46">
|
||||
<div class="proposal-mine-sticky">
|
||||
<van-search
|
||||
v-model="pageForm.name"
|
||||
class="proposal-mine-search"
|
||||
placeholder="请输入提案名称搜索"
|
||||
shape="round"
|
||||
clearable
|
||||
@search="doSearch"
|
||||
@clear="doSearch"
|
||||
></van-search>
|
||||
<van-dropdown-menu
|
||||
class="proposal-mine-filter"
|
||||
:class="{'is-open': filterOpened}"
|
||||
active-color="#1989fa"
|
||||
:close-on-click-outside="false"
|
||||
:close-on-click-overlay="false"
|
||||
>
|
||||
<van-dropdown-item
|
||||
v-model="pageForm.sessionId"
|
||||
:multiple="false"
|
||||
:options="sessionOptions"
|
||||
@change="doSearch"
|
||||
@open="filterOpened = true"
|
||||
@close="filterOpened = false"
|
||||
></van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
</div>
|
||||
<div class="action-btn" v-if="row.taskKey === 'startTask' || !row.instanceId"
|
||||
@click="onEdit(row)">
|
||||
<i class="fa fa-edit"></i>
|
||||
<span>编辑</span>
|
||||
</div>
|
||||
<div class="action-btn"
|
||||
v-if="row.taskKey === 'invite' || row.taskKey === 'second'"
|
||||
@click="onInvite(row)">
|
||||
<i class="fa fa-user-plus"></i>
|
||||
<span>邀请附议人</span>
|
||||
</div>
|
||||
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)">
|
||||
<i class="fa fa-undo"></i>
|
||||
<span>撤回</span>
|
||||
</div>
|
||||
<div class="action-btn delete" v-if="row.taskKey === 'startTask' || !row.instanceId"
|
||||
@click="onDelete(row)">
|
||||
<i class="fa fa-trash"></i>
|
||||
<span>删除</span>
|
||||
</div>
|
||||
</template>
|
||||
</table-list>
|
||||
</van-sticky>
|
||||
|
||||
<invite-seconder @refresh="doSearch" ref="inviteSeconderRef"></invite-seconder>
|
||||
<main class="proposal-mine-content">
|
||||
<div class="proposal-mine-list">
|
||||
<div v-if="pageForm.totalCount === 0" class="proposal-mine-empty">
|
||||
<img src="/assets/mobile/img/proposal/proposal-empty.png" alt="暂无提案记录插画">
|
||||
<div class="proposal-mine-empty__title">暂无提案记录</div>
|
||||
<div class="proposal-mine-empty__hint">当前筛选条件下暂时没有提案</div>
|
||||
</div>
|
||||
<table-list api="/platform/proposal/mine/pageData" :page_form.sync="pageForm" @ready="onReady" ref="tableListRef"
|
||||
title="name">
|
||||
<template #header="{index,row}">
|
||||
<div class="proposal-card-header">
|
||||
<div class="proposal-card-heading">
|
||||
<div class="proposal-card-title">{{row.name}}</div>
|
||||
<div class="proposal-card-code">编号:{{row.code || '--'}}</div>
|
||||
</div>
|
||||
<!-- 流程已启动时显示真实实例状态,尚未启动的提案统一标记为待提交。 -->
|
||||
<enum-tag
|
||||
class="proposal-card-status"
|
||||
v-if="row.instanceState"
|
||||
:value="row.instanceState"
|
||||
name="ProcessInstanceStateEnum"
|
||||
label_key="message"
|
||||
size="small"
|
||||
></enum-tag>
|
||||
<span v-else class="proposal-card-status proposal-card-status--plain">待提交</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="提案类别">{{row.typeName || '--'}}</table-column>
|
||||
<table-column label="提案人">{{row.createUserName || '--'}}</table-column>
|
||||
<table-column label="代表团">{{row.delegationName || '--'}}</table-column>
|
||||
<table-column label="当前节点">{{ (row.startTaskId) ? row.taskName : '待提交' }}</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row)">
|
||||
<i class="fa fa-eye"></i>
|
||||
<span>查看</span>
|
||||
</div>
|
||||
<div class="action-btn" v-if="row.taskKey === 'startTask' || !row.instanceId"
|
||||
@click="onEdit(row)">
|
||||
<i class="fa fa-edit"></i>
|
||||
<span>编辑</span>
|
||||
</div>
|
||||
<div class="action-btn"
|
||||
v-if="row.taskKey === 'invite' || row.taskKey === 'second'"
|
||||
@click="onInvite(row)">
|
||||
<i class="fa fa-user-plus"></i>
|
||||
<span>邀请附议人</span>
|
||||
</div>
|
||||
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)">
|
||||
<i class="fa fa-undo"></i>
|
||||
<span>撤回</span>
|
||||
</div>
|
||||
<div class="action-btn delete" v-if="row.taskKey === 'startTask' || !row.instanceId"
|
||||
@click="onDelete(row)">
|
||||
<i class="fa fa-trash"></i>
|
||||
<span>删除</span>
|
||||
</div>
|
||||
</template>
|
||||
</table-list>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<proposal-info ref="proposalInfoRef"></proposal-info>
|
||||
<invite-seconder @refresh="doSearch" ref="inviteSeconderRef"></invite-seconder>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include("../../common/info.js"){}#-->
|
||||
<!--#include("inviteSeconder.js"){}#-->
|
||||
|
||||
const vue = new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
components: {
|
||||
"invite-seconder": PROPOSAL_INVITE_SECONDER_COMPONENT,
|
||||
"proposal-info": PROPOSAL_INFO
|
||||
"invite-seconder": PROPOSAL_INVITE_SECONDER_COMPONENT
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -78,34 +387,33 @@ layout("/layouts/platform_h5.html"){
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
searchKeyword: "",
|
||||
pageOrderName: "",
|
||||
pageOrderBy: "",
|
||||
sessionId: null
|
||||
},
|
||||
|
||||
sessionOptions: [],
|
||||
infoShow: false
|
||||
filterOpened: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
listSession() {
|
||||
this.$axios.post("/platform/proposal/common/listSession").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.sessionOptions = res.data
|
||||
.map((v) => ({
|
||||
this.sessionOptions = [{text: "全部届次", value: null}]
|
||||
.concat(res.data.map((v) => ({
|
||||
text: v.fullName,
|
||||
value: v.id
|
||||
}))
|
||||
.concat([{text: "全部届次", value: null}])
|
||||
if (this.sessionOptions.length > 0) {
|
||||
this.pageForm.sessionId = this.sessionOptions[0].value
|
||||
this.doSearch()
|
||||
}
|
||||
})))
|
||||
// “全部届次”固定展示在首位,但首次查询仍默认使用接口倒序返回的最新届次。
|
||||
this.$set(this.pageForm, "sessionId", res.data.length > 0 ? res.data[0].id : null)
|
||||
this.doSearch()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onView(row) {
|
||||
this.showApprovalForm = false
|
||||
this.$refs.proposalInfoRef.onOpen(row)
|
||||
this.$pjaxReplace("/platform/proposal/mine/info/h5?bizId=" + encodeURIComponent(row.id))
|
||||
},
|
||||
|
||||
onEdit(row) {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="提案详情" placeholder fixed></van-nav-bar>
|
||||
<proposal-info v-if="bizId" :biz-id="bizId"></proposal-info>
|
||||
</div>
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include("../../common/info.js"){}#-->
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
components: {
|
||||
"proposal-info": PROPOSAL_INFO
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
bizId: ""
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// bizId 为提案主键,公共详情组件根据该参数返回提案详情对象和流程记录数组。
|
||||
this.bizId = new URLSearchParams(window.location.search).get("bizId") || ""
|
||||
if (!this.bizId) {
|
||||
this.$toast.fail("缺少提案参数")
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
+10
-1
@@ -44,7 +44,14 @@ const PROPOSAL_INVITE_SECONDER_COMPONENT = {
|
||||
<van-button @click="invite" type="primary" block :disabled="pageForm.isInvite" class="mt10">邀请</van-button>
|
||||
</div>
|
||||
</van-checkbox-group>
|
||||
<van-empty v-else :figure="2"></van-empty>
|
||||
<!-- 空状态提示随“已邀请 / 可邀请”标签切换,便于用户判断当前无数据的具体范围。 -->
|
||||
<div v-else class="proposal-invite-empty">
|
||||
<img src="/assets/mobile/img/proposal/proposal-empty.png" alt="暂无附议人插画">
|
||||
<div class="proposal-invite-empty__title">暂无附议人</div>
|
||||
<div class="proposal-invite-empty__hint">
|
||||
{{ isInvite === "1" ? "当前暂无已邀请的附议人" : "当前暂无可邀请的附议人" }}
|
||||
</div>
|
||||
</div>
|
||||
</van-action-sheet>
|
||||
`,
|
||||
data() {
|
||||
@@ -57,6 +64,8 @@ const PROPOSAL_INVITE_SECONDER_COMPONENT = {
|
||||
pageNumber: 1,
|
||||
pageSize: 5,
|
||||
totalCount: 0,
|
||||
pageOrderName: "",
|
||||
pageOrderBy: "",
|
||||
isInvite: false,
|
||||
searchKeyword: null
|
||||
},
|
||||
|
||||
+656
@@ -0,0 +1,656 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
<style>
|
||||
body {
|
||||
background-color: #f3f7fd;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-page {
|
||||
min-height: 100vh;
|
||||
padding-bottom: calc(24px + env(safe-area-inset-bottom));
|
||||
box-sizing: border-box;
|
||||
background-color: #f3f7fd;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-page .van-nav-bar {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-page .van-nav-bar__title {
|
||||
color: #172033;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-page .van-nav-bar .van-icon,
|
||||
.proposal-pre-audit-page .van-nav-bar__text {
|
||||
color: #1989fa;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-content {
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-sticky {
|
||||
padding: 22px 12px 1px;
|
||||
box-sizing: border-box;
|
||||
background-color: #f3f7fd;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-search {
|
||||
margin: 0 0 8px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
border-radius: 12px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 5px 16px rgba(43, 73, 112, .1);
|
||||
}
|
||||
|
||||
.proposal-pre-audit-search .van-search__content {
|
||||
height: 42px;
|
||||
padding-left: 12px;
|
||||
align-items: center;
|
||||
border-radius: 12px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-search .van-field__control {
|
||||
color: #263548;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-search .van-field__control::placeholder {
|
||||
color: #a7b0bf;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-filter {
|
||||
margin: 0 0 10px;
|
||||
overflow: hidden;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
|
||||
transition: border-radius .18s ease;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-filter.is-open {
|
||||
border-radius: 10px 10px 0 0;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-filter .van-dropdown-menu__bar,
|
||||
.proposal-pre-audit-filter .van-dropdown-menu__item,
|
||||
.proposal-pre-audit-filter .van-dropdown-menu__title {
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-filter .van-dropdown-menu__bar {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-filter .van-dropdown-menu__title {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: #66758a;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-filter .van-dropdown-menu__title--active,
|
||||
.proposal-pre-audit-filter .van-dropdown-item__option--active,
|
||||
.proposal-pre-audit-filter .van-dropdown-item__option--active .van-dropdown-item__icon {
|
||||
color: #1989fa;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-filter .van-dropdown-item__content {
|
||||
width: calc(100% - 24px);
|
||||
margin: 0 12px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
border-radius: 0 0 12px 12px;
|
||||
box-shadow: 0 10px 24px rgba(31, 65, 108, .14);
|
||||
}
|
||||
|
||||
.proposal-pre-audit-filter .van-dropdown-item__option {
|
||||
display: flex;
|
||||
min-height: 48px;
|
||||
padding: 0 16px;
|
||||
align-items: center;
|
||||
color: #46566c;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-list .empty-state {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-empty {
|
||||
display: flex;
|
||||
min-height: 280px;
|
||||
padding: 46px 18px 36px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-empty img {
|
||||
display: block;
|
||||
width: 86%;
|
||||
max-width: 290px;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-empty__title {
|
||||
margin-top: 14px;
|
||||
color: #50627a;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-empty__hint {
|
||||
margin-top: 5px;
|
||||
color: #9aa7b8;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-card-header {
|
||||
display: flex;
|
||||
margin-bottom: 7px;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-card-heading {
|
||||
min-width: 0;
|
||||
padding-right: 10px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-card-title {
|
||||
overflow: hidden;
|
||||
color: #253247;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
font-weight: 600;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-card-code {
|
||||
margin-top: 1px;
|
||||
color: #9ba6b6;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-status {
|
||||
display: inline-flex;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.proposal-pre-audit-status .van-tag {
|
||||
display: inline-flex;
|
||||
min-height: 24px;
|
||||
padding: 3px 9px;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
border: 0;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media (max-width: 350px) {
|
||||
.proposal-pre-audit-content,
|
||||
.proposal-pre-audit-sticky {
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div id="app" v-cloak>
|
||||
<div class="proposal-pre-audit-page">
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="校工会预审核" placeholder fixed></van-nav-bar>
|
||||
<van-sticky :offset-top="46">
|
||||
<div class="proposal-pre-audit-sticky">
|
||||
<van-search
|
||||
v-model="pageForm.name"
|
||||
class="proposal-pre-audit-search"
|
||||
placeholder="请输入提案名称搜索"
|
||||
shape="round"
|
||||
clearable
|
||||
@search="doSearch"
|
||||
@clear="doSearch"
|
||||
></van-search>
|
||||
<van-dropdown-menu
|
||||
class="proposal-pre-audit-filter"
|
||||
:class="{'is-open': filterOpened}"
|
||||
active-color="#1989fa"
|
||||
>
|
||||
<van-dropdown-item
|
||||
v-model="pageForm.sessionId"
|
||||
:options="sessionOptions"
|
||||
:multiple="false"
|
||||
@change="doSearch"
|
||||
@open="onFilterOpen"
|
||||
@close="onFilterClose"
|
||||
></van-dropdown-item>
|
||||
<van-dropdown-item
|
||||
v-model="pageForm.approvalText"
|
||||
:options="approvalOptions"
|
||||
:multiple="false"
|
||||
@change="approvalChange"
|
||||
@open="onFilterOpen"
|
||||
@close="onFilterClose"
|
||||
></van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
</div>
|
||||
</van-sticky>
|
||||
|
||||
<main class="proposal-pre-audit-content">
|
||||
<div class="proposal-pre-audit-list">
|
||||
<!-- 列表无记录时使用与“我的提案”一致的提案专用空状态。 -->
|
||||
<div v-if="pageForm.totalCount === 0" class="proposal-pre-audit-empty">
|
||||
<img src="/assets/mobile/img/proposal/proposal-empty.png" alt="暂无提案记录插画">
|
||||
<div class="proposal-pre-audit-empty__title">暂无提案记录</div>
|
||||
<div class="proposal-pre-audit-empty__hint">当前筛选条件下暂时没有提案</div>
|
||||
</div>
|
||||
<table-list api="/platform/proposal/pre/audit/pageData" :page_form.sync="pageForm" @ready="onReady"
|
||||
ref="tableListRef" title="name">
|
||||
<template #header="{index,row}">
|
||||
<div class="proposal-pre-audit-card-header">
|
||||
<div class="proposal-pre-audit-card-heading">
|
||||
<div class="proposal-pre-audit-card-title">{{row.name}}</div>
|
||||
<div class="proposal-pre-audit-card-code">编号:{{row.code || '--'}}</div>
|
||||
</div>
|
||||
<enum-tag
|
||||
class="proposal-pre-audit-status"
|
||||
v-if="row.instanceState"
|
||||
:value="row.instanceState"
|
||||
name="ProcessInstanceStateEnum"
|
||||
label_key="message"
|
||||
size="small"
|
||||
></enum-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="立案编号">{{row.caseFilingCode || '--'}}</table-column>
|
||||
<table-column label="提案类别">{{row.typeName || '--'}}</table-column>
|
||||
<table-column label="提案人">{{row.createUserName || '--'}}</table-column>
|
||||
<table-column label="教代会">{{row.sessionName || '--'}}</table-column>
|
||||
<table-column label="代表团">{{row.delegationName || '--'}}</table-column>
|
||||
<table-column label="当前节点">{{row.curTaskName || '--'}}</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row,index)">
|
||||
<i class="fa fa-eye"></i>
|
||||
<span>查看</span>
|
||||
</div>
|
||||
<div class="action-btn" v-if="row.taskState === 10" @click="onApproval(row,index)">
|
||||
<i class="fa fa-edit"></i>
|
||||
<span>审核</span>
|
||||
</div>
|
||||
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)">
|
||||
<i class="fa fa-reply"></i>
|
||||
<span>撤回</span>
|
||||
</div>
|
||||
</template>
|
||||
</table-list>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
data() {
|
||||
return {
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
pageOrderName: "",
|
||||
pageOrderBy: "",
|
||||
name: null,
|
||||
sessionId: null,
|
||||
approvalText: "0",
|
||||
approval: false
|
||||
},
|
||||
sessionOptions: [],
|
||||
filterOpened: false,
|
||||
approvalOptions: [
|
||||
{text: "未审核", value: "0"},
|
||||
{text: "已审核", value: "1"}
|
||||
],
|
||||
pendingListCache: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onReady() {
|
||||
if (this.pendingListCache) {
|
||||
this.restoreListCache()
|
||||
this.listSession(true)
|
||||
return
|
||||
}
|
||||
this.listSession(false)
|
||||
},
|
||||
|
||||
onFilterOpen() {
|
||||
this.filterOpened = true
|
||||
},
|
||||
|
||||
onFilterClose() {
|
||||
this.filterOpened = false
|
||||
},
|
||||
|
||||
// 审核状态下拉使用字符串展示,查询接口使用布尔值区分已审核和未审核。
|
||||
approvalChange(value) {
|
||||
this.$set(this.pageForm, "approval", value === "1")
|
||||
this.doSearch()
|
||||
},
|
||||
|
||||
// 与其他提案 H5 页面一致加载全部教代会;首次进入默认选中最新一届,缓存恢复时不覆盖原筛选条件。
|
||||
listSession(skipDefaultSearch) {
|
||||
this.$axios.post("/platform/proposal/common/listSession").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.sessionOptions = [{text: "全部教代会", value: null}]
|
||||
.concat(res.data.map((item) => ({text: item.fullName, value: item.id})))
|
||||
if (skipDefaultSearch) return
|
||||
this.$set(this.pageForm, "sessionId", res.data.length > 0 ? res.data[0].id : null)
|
||||
this.doSearch()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onView(row, index) {
|
||||
this.saveListCache(row, index)
|
||||
this.$pjaxReplace("/platform/proposal/pre/audit/info/h5?bizId=" + encodeURIComponent(row.id) + "&action=view")
|
||||
},
|
||||
|
||||
onApproval(row, index) {
|
||||
this.saveListCache(row, index)
|
||||
const params = new URLSearchParams()
|
||||
params.set("bizId", row.id)
|
||||
params.set("action", "approval")
|
||||
params.set("taskId", row.taskId)
|
||||
params.set("taskKey", row.taskKey || "preAudit")
|
||||
params.set("taskName", row.curTaskName || row.taskName || "校工会预审核")
|
||||
this.$pjaxReplace("/platform/proposal/pre/audit/info/h5?" + params.toString())
|
||||
},
|
||||
|
||||
getListCacheKey() {
|
||||
return "proposal:pre-audit:h5:list-state"
|
||||
},
|
||||
|
||||
getListCacheTokenParam() {
|
||||
return "preAuditListCacheToken"
|
||||
},
|
||||
|
||||
getCurrentListCacheToken() {
|
||||
return new URLSearchParams(window.location.search).get(this.getListCacheTokenParam()) || ""
|
||||
},
|
||||
|
||||
/**
|
||||
* 将预审核列表缓存标识绑定到当前历史记录。
|
||||
* PJAX 返回时仅恢复当前历史记录对应的筛选、分页数据和滚动快照。
|
||||
*/
|
||||
bindListCacheToken(token) {
|
||||
const currentUrl = new URL(window.location.href)
|
||||
currentUrl.searchParams.set(this.getListCacheTokenParam(), token)
|
||||
const currentState = window.history.state ? {...window.history.state} : null
|
||||
if (currentState && currentState.url) {
|
||||
currentState.url = currentUrl.toString()
|
||||
}
|
||||
window.history.replaceState(currentState, document.title, currentUrl.toString())
|
||||
if ($.pjax && $.pjax.state) {
|
||||
$.pjax.state.url = currentUrl.toString()
|
||||
}
|
||||
},
|
||||
|
||||
// 用户主动搜索或切换筛选时使旧返回缓存失效。
|
||||
clearListCache() {
|
||||
const token = this.getCurrentListCacheToken()
|
||||
if (!token) return
|
||||
const currentUrl = new URL(window.location.href)
|
||||
currentUrl.searchParams.delete(this.getListCacheTokenParam())
|
||||
const currentState = window.history.state ? {...window.history.state} : null
|
||||
if (currentState && currentState.url) {
|
||||
currentState.url = currentUrl.toString()
|
||||
}
|
||||
window.history.replaceState(currentState, document.title, currentUrl.toString())
|
||||
if ($.pjax && $.pjax.state) {
|
||||
$.pjax.state.url = currentUrl.toString()
|
||||
}
|
||||
sessionStorage.removeItem(this.getListCacheKey())
|
||||
},
|
||||
|
||||
// platform_h5 的实际滚动区域是 page-wrapper,列表位置必须基于该容器保存和恢复。
|
||||
getListScrollContainer() {
|
||||
return document.querySelector(".page-wrapper") || document.scrollingElement || document.documentElement
|
||||
},
|
||||
|
||||
/**
|
||||
* 进入查看或审核详情前保存列表现场。
|
||||
* row 为当前提案,index 为已加载列表索引;缓存同时保存相邻记录作为审核后的定位候选。
|
||||
*/
|
||||
saveListCache(row, index) {
|
||||
const tableList = this.$refs.tableListRef
|
||||
if (!tableList) return
|
||||
const cacheToken = Date.now() + "-" + Math.random().toString(36).slice(2)
|
||||
const tableState = typeof tableList.getListState === "function"
|
||||
? tableList.getListState()
|
||||
: {
|
||||
tableData: JSON.parse(JSON.stringify(tableList.tableData || [])),
|
||||
localPageForm: JSON.parse(JSON.stringify(tableList.localPageForm || this.pageForm)),
|
||||
tableFinished: !!tableList.tableFinished
|
||||
}
|
||||
const cards = document.querySelectorAll(".proposal-pre-audit-list .table-list-item")
|
||||
const currentCard = cards[index]
|
||||
const scrollContainer = this.getListScrollContainer()
|
||||
const cache = {
|
||||
time: Date.now(),
|
||||
token: cacheToken,
|
||||
pageForm: JSON.parse(JSON.stringify(this.pageForm)),
|
||||
tableState: tableState,
|
||||
completedBizId: "",
|
||||
anchor: {
|
||||
rowId: row.id,
|
||||
previousRowId: index > 0 && tableState.tableData[index - 1] ? tableState.tableData[index - 1].id : "",
|
||||
nextRowId: tableState.tableData[index + 1] ? tableState.tableData[index + 1].id : "",
|
||||
viewportTop: currentCard ? currentCard.getBoundingClientRect().top : 0,
|
||||
scrollTop: scrollContainer ? scrollContainer.scrollTop : 0
|
||||
}
|
||||
}
|
||||
try {
|
||||
sessionStorage.setItem(this.getListCacheKey(), JSON.stringify(cache))
|
||||
this.bindListCacheToken(cacheToken)
|
||||
} catch (error) {
|
||||
sessionStorage.removeItem(this.getListCacheKey())
|
||||
}
|
||||
},
|
||||
|
||||
// 仅恢复当前历史记录对应且十分钟内有效的校工会预审核列表缓存。
|
||||
prepareListCache() {
|
||||
try {
|
||||
const cacheToken = this.getCurrentListCacheToken()
|
||||
if (!cacheToken) return
|
||||
const cacheText = sessionStorage.getItem(this.getListCacheKey())
|
||||
if (!cacheText) return
|
||||
const cache = JSON.parse(cacheText)
|
||||
if (!cache || cache.token !== cacheToken || !cache.tableState) return
|
||||
if (Date.now() - cache.time > 10 * 60 * 1000) {
|
||||
this.clearListCache()
|
||||
return
|
||||
}
|
||||
Object.keys(cache.pageForm || {}).forEach((key) => {
|
||||
this.$set(this.pageForm, key, cache.pageForm[key])
|
||||
})
|
||||
this.$set(this, "pendingListCache", cache)
|
||||
} catch (error) {
|
||||
sessionStorage.removeItem(this.getListCacheKey())
|
||||
}
|
||||
},
|
||||
|
||||
// 普通查看返回恢复原快照;审核完成返回重新查询,避免未审核列表保留已处理记录。
|
||||
restoreListCache() {
|
||||
const cache = this.pendingListCache
|
||||
const tableList = this.$refs.tableListRef
|
||||
if (!cache || !tableList) return
|
||||
const anchor = cache.anchor || {}
|
||||
if (cache.completedBizId) {
|
||||
this.refreshCompletedListCache(cache)
|
||||
return
|
||||
}
|
||||
this.applyListCache(cache.tableState, anchor.rowId, anchor)
|
||||
},
|
||||
|
||||
/**
|
||||
* 按离开前已加载页数重新查询审核后的列表。
|
||||
* 查询结果转换为 TableList 状态,并优先定位原记录的下一条、其次定位上一条。
|
||||
*/
|
||||
refreshCompletedListCache(cache) {
|
||||
const originalPageSize = Number(cache.pageForm.pageSize || 10)
|
||||
const loadedCount = Math.max(cache.tableState.tableData.length, originalPageSize)
|
||||
const loadedPageCount = Math.max(1, Math.ceil(loadedCount / originalPageSize))
|
||||
const requestForm = {
|
||||
...cache.pageForm,
|
||||
pageNumber: 1,
|
||||
pageSize: loadedPageCount * originalPageSize
|
||||
}
|
||||
const loading = createListLoading()
|
||||
this.$axios.post("/platform/proposal/pre/audit/pageData", requestForm, {h5SkeletonLoading: true})
|
||||
.then((res) => {
|
||||
if (res.code !== 0) {
|
||||
this.applyCompletedCacheFallback(cache)
|
||||
return
|
||||
}
|
||||
const freshState = {
|
||||
tableData: res.data.list || [],
|
||||
localPageForm: {
|
||||
...cache.pageForm,
|
||||
pageNumber: loadedPageCount,
|
||||
pageSize: originalPageSize,
|
||||
totalCount: res.data.totalCount
|
||||
},
|
||||
tableFinished: (res.data.list || []).length >= res.data.totalCount
|
||||
}
|
||||
const anchor = cache.anchor || {}
|
||||
this.applyListCache(freshState, anchor.nextRowId || anchor.previousRowId, anchor)
|
||||
})
|
||||
.catch(() => {
|
||||
this.applyCompletedCacheFallback(cache)
|
||||
})
|
||||
.finally(() => {
|
||||
loading.close()
|
||||
})
|
||||
},
|
||||
|
||||
// 审核后刷新失败时从旧快照移除已处理记录,避免未审核列表保留脏数据。
|
||||
applyCompletedCacheFallback(cache) {
|
||||
const tableState = cache.tableState
|
||||
const completedIndex = tableState.tableData.findIndex((item) => String(item.id) === String(cache.completedBizId))
|
||||
if (completedIndex >= 0 && cache.pageForm.approval === false) {
|
||||
tableState.tableData.splice(completedIndex, 1)
|
||||
if (tableState.localPageForm && tableState.localPageForm.totalCount > 0) {
|
||||
tableState.localPageForm.totalCount--
|
||||
}
|
||||
} else if (completedIndex >= 0) {
|
||||
this.$set(tableState.tableData[completedIndex], "taskState", 20)
|
||||
}
|
||||
const anchor = cache.anchor || {}
|
||||
this.applyListCache(tableState, anchor.nextRowId || anchor.previousRowId, anchor)
|
||||
},
|
||||
|
||||
/**
|
||||
* 恢复 TableList 数据并将目标卡片放回离开页面前的视口位置。
|
||||
* sticky 吸顶会改变一次布局,因此首次滚动后再执行一次位置校正。
|
||||
*/
|
||||
applyListCache(tableState, targetRowId, anchor) {
|
||||
const tableList = this.$refs.tableListRef
|
||||
if (!tableList) return
|
||||
if (typeof tableList.restoreListState === "function") {
|
||||
if (!tableList.restoreListState(tableState)) return
|
||||
} else {
|
||||
this.$set(tableList, "tableData", tableState.tableData || [])
|
||||
this.$set(tableList, "localPageForm", {...tableList.localPageForm, ...(tableState.localPageForm || {})})
|
||||
this.$set(tableList, "tableFinished", !!tableState.tableFinished)
|
||||
this.$set(tableList, "tableLoading", false)
|
||||
this.$set(tableList, "tableRefreshing", false)
|
||||
tableList.$emit("update:page_form", {...tableList.localPageForm})
|
||||
}
|
||||
this.$set(this, "pendingListCache", null)
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
const cards = Array.from(document.querySelectorAll(".proposal-pre-audit-list .table-list-item"))
|
||||
const scrollContainer = this.getListScrollContainer()
|
||||
if (!scrollContainer) return
|
||||
const candidateIds = [targetRowId, anchor.nextRowId, anchor.previousRowId]
|
||||
.filter((id) => id)
|
||||
.map((id) => String(id))
|
||||
.filter((id, index, values) => values.indexOf(id) === index)
|
||||
let targetIndex = -1
|
||||
candidateIds.some((candidateId) => {
|
||||
targetIndex = (tableState.tableData || []).findIndex((item) => String(item.id) === candidateId)
|
||||
return targetIndex >= 0
|
||||
})
|
||||
const targetCard = targetIndex >= 0 ? cards[targetIndex] : null
|
||||
if (targetCard) {
|
||||
const restoreTargetCard = () => {
|
||||
if (!targetCard.isConnected) return
|
||||
const targetTop = scrollContainer.scrollTop + targetCard.getBoundingClientRect().top - Number(anchor.viewportTop || 0)
|
||||
scrollContainer.scrollTo(0, Math.max(0, targetTop))
|
||||
}
|
||||
restoreTargetCard()
|
||||
setTimeout(restoreTargetCard, 100)
|
||||
return
|
||||
}
|
||||
scrollContainer.scrollTo(0, Number(anchor.scrollTop || 0))
|
||||
}, 0)
|
||||
})
|
||||
},
|
||||
|
||||
onRevoke(row) {
|
||||
this.$dialog.confirm({
|
||||
title: "提示",
|
||||
message: "您确定要撤回吗?"
|
||||
}).then(() => {
|
||||
this.$axios.post("/flow/common/revokeTask", {taskId: row.taskId}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$toast.success(res.msg)
|
||||
this.doSearch()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
doSearch() {
|
||||
this.clearListCache()
|
||||
this.$nextTick(() => {
|
||||
this.pageForm.pageNumber = 1
|
||||
this.pageForm.totalCount = 0
|
||||
this.$refs.tableListRef.doSearch()
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.prepareListCache()
|
||||
if (this.pendingListCache) return
|
||||
const tab = new URLSearchParams(window.location.search).get("tab")
|
||||
if (tab === "done") {
|
||||
this.$set(this.pageForm, "approval", true)
|
||||
this.$set(this.pageForm, "approvalText", "1")
|
||||
} else if (tab === "todo") {
|
||||
this.$set(this.pageForm, "approval", false)
|
||||
this.$set(this.pageForm, "approvalText", "0")
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
<style>
|
||||
.proposal-pre-audit-actions {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
padding: 10px 12px calc(12px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.proposal-pre-audit-actions .van-button {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" :title="pageTitle" placeholder fixed></van-nav-bar>
|
||||
<proposal-info v-if="bizId" :biz-id="bizId">
|
||||
<div v-if="isApproval">
|
||||
<div class="process-title">{{formData.taskDisplayName}}</div>
|
||||
<van-form ref="formRef">
|
||||
<van-field
|
||||
v-model="formData.tf_opinion"
|
||||
name="tf_opinion"
|
||||
label="审批意见"
|
||||
placeholder="请输入审批意见"
|
||||
:rules="[{ required: true, message: '请填写审批意见' }]"
|
||||
required
|
||||
maxlength="100"
|
||||
show-word-limit
|
||||
></van-field>
|
||||
</van-form>
|
||||
<div class="proposal-pre-audit-actions">
|
||||
<van-button type="warning" :loading="formLoading" @click="handleTaskAction(4)">退回(不审核)</van-button>
|
||||
<van-button type="warning" :loading="formLoading" @click="handleTaskAction(6)">退回(审核)</van-button>
|
||||
<van-button type="danger" :loading="formLoading" @click="handleTaskAction(2)">不同意</van-button>
|
||||
<van-button type="primary" :loading="formLoading" @click="handleTaskAction(1)">同意</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</proposal-info>
|
||||
</div>
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include("../../common/info.js"){}#-->
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
components: {
|
||||
"proposal-info": PROPOSAL_INFO
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
bizId: "",
|
||||
action: "view",
|
||||
formData: {},
|
||||
formLoading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isApproval() {
|
||||
return this.action === "approval"
|
||||
},
|
||||
pageTitle() {
|
||||
return this.isApproval ? "校工会预审核" : "提案详情"
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 提交校工会预审核结果。
|
||||
* val 为 WF 提交类型:1 同意、2 不同意、4 退回且不重复审核、6 退回并重新审核;接口返回 Result。
|
||||
*/
|
||||
handleTaskAction(val) {
|
||||
this.$refs.formRef.validate()
|
||||
.then(() => this.$dialog.confirm({title: "提示", message: "您确定要提交吗?"}))
|
||||
.then(() => {
|
||||
const params = {
|
||||
...this.formData,
|
||||
submitType: val,
|
||||
taskName: "startTask"
|
||||
}
|
||||
if (val === 4) {
|
||||
params.tf_sourceTaskKey = this.formData.taskKey
|
||||
params.tf_audit = false
|
||||
}
|
||||
this.formLoading = true
|
||||
return this.$axios.post("/flow/common/executeTask", {
|
||||
data: JSON.stringify(params)
|
||||
})
|
||||
})
|
||||
.then((res) => {
|
||||
if (res && res.code === 0) {
|
||||
this.markListCacheCompleted()
|
||||
this.$toast.success(res.msg)
|
||||
this.historyBack()
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
this.formLoading = false
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 审核成功后记录已处理提案主键。
|
||||
* 返回列表时按原筛选和已加载范围刷新数据,并定位到相邻记录。
|
||||
*/
|
||||
markListCacheCompleted() {
|
||||
const cacheKey = "proposal:pre-audit:h5:list-state"
|
||||
try {
|
||||
const cacheText = sessionStorage.getItem(cacheKey)
|
||||
if (!cacheText) return
|
||||
const cache = JSON.parse(cacheText)
|
||||
cache.completedBizId = this.bizId
|
||||
cache.time = Date.now()
|
||||
sessionStorage.setItem(cacheKey, JSON.stringify(cache))
|
||||
} catch (error) {
|
||||
sessionStorage.removeItem(cacheKey)
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
// bizId 查询提案详情;taskId、taskKey 和 taskName 构造 WF 预审核提交参数。
|
||||
this.bizId = params.get("bizId") || ""
|
||||
this.action = params.get("action") || "view"
|
||||
if (this.isApproval) {
|
||||
this.$set(this.formData, "processTaskId", params.get("taskId") || "")
|
||||
this.$set(this.formData, "taskKey", params.get("taskKey") || "preAudit")
|
||||
this.$set(this.formData, "taskDisplayName", params.get("taskName") || "校工会预审核")
|
||||
}
|
||||
if (!this.bizId) {
|
||||
this.$toast.fail("缺少提案参数")
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
+9
-73
@@ -49,32 +49,8 @@ layout("/layouts/platform_h5.html"){
|
||||
</template>
|
||||
</table-list>
|
||||
|
||||
<proposal-info ref="proposalInfoRef">
|
||||
<div v-if="showApprovalForm">
|
||||
<div class="process-title">{{formData.taskName}}</div>
|
||||
<van-form ref="formRef">
|
||||
<van-field
|
||||
v-model="formData.tf_opinion"
|
||||
name="tf_opinion"
|
||||
label="审批意见"
|
||||
placeholder="请输入审批意见"
|
||||
:rules="[{ required: true, message: '请填写审批意见' }]"
|
||||
required
|
||||
maxlength="100"
|
||||
show-word-limit
|
||||
></van-field>
|
||||
</van-form>
|
||||
<div style="display: flex; justify-content: space-between; column-gap: 10px; padding: 10px">
|
||||
<van-button type="danger" block @click="handleTaskAction('back')">退回答复</van-button>
|
||||
<van-button type="primary" block @click="handleTaskAction('agree')">同意答复</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</proposal-info>
|
||||
|
||||
</div>
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include("../../common/info.js"){}#-->
|
||||
|
||||
const vue = new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
@@ -90,14 +66,9 @@ layout("/layouts/platform_h5.html"){
|
||||
approval: false
|
||||
},
|
||||
|
||||
sessionOptions: [],
|
||||
formData: {},
|
||||
showApprovalForm: false
|
||||
sessionOptions: []
|
||||
}
|
||||
},
|
||||
components: {
|
||||
"proposal-info": PROPOSAL_INFO
|
||||
},
|
||||
methods: {
|
||||
onReady() {
|
||||
this.listSession()
|
||||
@@ -120,52 +91,17 @@ layout("/layouts/platform_h5.html"){
|
||||
},
|
||||
|
||||
onView(row) {
|
||||
this.showApprovalForm = false
|
||||
this.$refs.proposalInfoRef.onOpen(row)
|
||||
this.$pjaxReplace("/platform/proposal/schoolLeaderApproval/info/h5?bizId=" + encodeURIComponent(row.id) + "&action=view")
|
||||
},
|
||||
|
||||
onApproval(row) {
|
||||
this.showApprovalForm = true
|
||||
this.$refs.proposalInfoRef.onOpen(row)
|
||||
this.formData = {
|
||||
processTaskId: row.taskId,
|
||||
taskKey: row.taskKey,
|
||||
taskName: row.taskName,
|
||||
proposalId: row.id
|
||||
}
|
||||
},
|
||||
|
||||
async handleTaskAction(val) {
|
||||
try {
|
||||
await this.$refs.formRef.validate();
|
||||
this.$dialog.confirm({
|
||||
title: "提示",
|
||||
message: "您确定要提交吗?"
|
||||
}).then(() => {
|
||||
|
||||
const formData = {
|
||||
...this.formData,
|
||||
tf_approval: val
|
||||
}
|
||||
delete formData.taskKey
|
||||
delete formData.taskName
|
||||
|
||||
|
||||
this.$axios.post("/platform/proposal/schoolLeaderApproval/executeTask", {
|
||||
data: JSON.stringify(formData)
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$refs.proposalInfoRef.onClose()
|
||||
this.$toast.success(res.msg)
|
||||
this.doSearch()
|
||||
}
|
||||
})
|
||||
}).catch(() => {
|
||||
|
||||
})
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
const params = new URLSearchParams()
|
||||
params.set("bizId", row.id)
|
||||
params.set("action", "approval")
|
||||
params.set("taskId", row.taskId)
|
||||
params.set("taskKey", row.taskKey || "")
|
||||
params.set("taskName", row.taskName || "分管校领导审批")
|
||||
this.$pjaxReplace("/platform/proposal/schoolLeaderApproval/info/h5?" + params.toString())
|
||||
},
|
||||
|
||||
onRevoke(row){
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" :title="pageTitle" placeholder fixed></van-nav-bar>
|
||||
<proposal-info v-if="bizId" :biz-id="bizId">
|
||||
<div v-if="isApproval">
|
||||
<div class="process-title">{{formData.taskName}}</div>
|
||||
<van-form ref="formRef">
|
||||
<van-field
|
||||
v-model="formData.tf_opinion"
|
||||
name="tf_opinion"
|
||||
label="审批意见"
|
||||
placeholder="请输入审批意见"
|
||||
:rules="[{ required: true, message: '请填写审批意见' }]"
|
||||
required
|
||||
maxlength="100"
|
||||
show-word-limit
|
||||
></van-field>
|
||||
</van-form>
|
||||
<div style="display: flex; justify-content: space-between; column-gap: 10px; padding: 10px">
|
||||
<van-button type="danger" block :loading="formLoading" @click="handleTaskAction('back')">退回答复</van-button>
|
||||
<van-button type="primary" block :loading="formLoading" @click="handleTaskAction('agree')">同意答复</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</proposal-info>
|
||||
</div>
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include("../../common/info.js"){}#-->
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
components: {
|
||||
"proposal-info": PROPOSAL_INFO
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
bizId: "",
|
||||
action: "view",
|
||||
formData: {},
|
||||
formLoading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isApproval() {
|
||||
return this.action === "approval"
|
||||
},
|
||||
pageTitle() {
|
||||
return this.isApproval ? "分管校领导审批" : "提案详情"
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 提交分管校领导审批结果。
|
||||
* val 为 back 或 agree;接口返回 Result,code 为 0 表示处理成功。
|
||||
*/
|
||||
handleTaskAction(val) {
|
||||
this.$refs.formRef.validate()
|
||||
.then(() => this.$dialog.confirm({title: "提示", message: "您确定要提交吗?"}))
|
||||
.then(() => {
|
||||
const submitData = {
|
||||
...this.formData,
|
||||
tf_approval: val
|
||||
}
|
||||
delete submitData.taskKey
|
||||
delete submitData.taskName
|
||||
this.formLoading = true
|
||||
return this.$axios.post("/platform/proposal/schoolLeaderApproval/executeTask", {
|
||||
data: JSON.stringify(submitData)
|
||||
})
|
||||
})
|
||||
.then((res) => {
|
||||
if (res && res.code === 0) {
|
||||
this.$toast.success(res.msg)
|
||||
this.historyBack()
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
this.formLoading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
// bizId 用于详情查询;taskId、taskKey、taskName 和 proposalId 构成校领导审批接口参数。
|
||||
this.bizId = params.get("bizId") || ""
|
||||
this.action = params.get("action") || "view"
|
||||
if (this.isApproval) {
|
||||
this.$set(this.formData, "processTaskId", params.get("taskId") || "")
|
||||
this.$set(this.formData, "taskKey", params.get("taskKey") || "")
|
||||
this.$set(this.formData, "taskName", params.get("taskName") || "分管校领导审批")
|
||||
this.$set(this.formData, "proposalId", this.bizId)
|
||||
}
|
||||
if (!this.bizId) {
|
||||
this.$toast.fail("缺少提案参数")
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
+595
-151
@@ -1,90 +1,336 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
<style>
|
||||
body {
|
||||
background-color: #f3f7fd;
|
||||
}
|
||||
|
||||
.proposal-seconded-page {
|
||||
min-height: 100vh;
|
||||
padding-bottom: calc(24px + env(safe-area-inset-bottom));
|
||||
box-sizing: border-box;
|
||||
background-color: #f3f7fd;
|
||||
}
|
||||
|
||||
.proposal-seconded-page .van-nav-bar {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.proposal-seconded-page .van-nav-bar__title {
|
||||
color: #172033;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.proposal-seconded-page .van-nav-bar .van-icon,
|
||||
.proposal-seconded-page .van-nav-bar__text {
|
||||
color: #1989fa;
|
||||
}
|
||||
|
||||
.proposal-seconded-content {
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.proposal-seconded-sticky {
|
||||
padding: 22px 12px 1px;
|
||||
box-sizing: border-box;
|
||||
background-color: #f3f7fd;
|
||||
}
|
||||
|
||||
.proposal-seconded-search {
|
||||
margin: 0 0 8px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
border-radius: 12px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 5px 16px rgba(43, 73, 112, .1);
|
||||
}
|
||||
|
||||
.proposal-seconded-search .van-search__content {
|
||||
height: 42px;
|
||||
padding-left: 12px;
|
||||
align-items: center;
|
||||
border-radius: 12px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.proposal-seconded-search .van-field__control {
|
||||
color: #263548;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.proposal-seconded-search .van-field__control::placeholder {
|
||||
color: #a7b0bf;
|
||||
}
|
||||
|
||||
.proposal-seconded-filter {
|
||||
margin: 0 0 10px;
|
||||
overflow: hidden;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
|
||||
transition: border-radius .18s ease;
|
||||
}
|
||||
|
||||
.proposal-seconded-filter.is-open {
|
||||
border-radius: 10px 10px 0 0;
|
||||
}
|
||||
|
||||
.proposal-seconded-filter .van-dropdown-menu__bar {
|
||||
height: 44px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.proposal-seconded-filter .van-dropdown-menu__item {
|
||||
height: 44px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.proposal-seconded-filter .van-dropdown-menu__title {
|
||||
display: inline-flex;
|
||||
height: 44px;
|
||||
align-items: center;
|
||||
line-height: 20px;
|
||||
color: #66758a;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.proposal-seconded-filter .van-dropdown-menu__title--active,
|
||||
.proposal-seconded-filter .van-dropdown-item__option--active,
|
||||
.proposal-seconded-filter .van-dropdown-item__option--active .van-dropdown-item__icon {
|
||||
color: #1989fa;
|
||||
}
|
||||
|
||||
.proposal-seconded-filter .van-dropdown-menu__title--down::after {
|
||||
border-color: transparent transparent #1989fa #1989fa;
|
||||
}
|
||||
|
||||
.proposal-seconded-filter .van-dropdown-item__content {
|
||||
width: calc(100% - 24px);
|
||||
margin: 0 12px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
border-radius: 0 0 12px 12px;
|
||||
box-shadow: 0 10px 24px rgba(31, 65, 108, .14);
|
||||
}
|
||||
|
||||
.proposal-seconded-filter .van-dropdown-item__option {
|
||||
display: flex;
|
||||
min-height: 48px;
|
||||
padding: 0 16px;
|
||||
align-items: center;
|
||||
color: #46566c;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.proposal-seconded-filter .van-dropdown-item__option .van-cell__title {
|
||||
display: flex;
|
||||
min-height: 20px;
|
||||
align-items: center;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.proposal-seconded-filter .van-cell::after {
|
||||
right: 16px;
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
.proposal-seconded-list .empty-state {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.proposal-seconded-empty {
|
||||
display: flex;
|
||||
min-height: 280px;
|
||||
padding: 46px 18px 36px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.proposal-seconded-empty img {
|
||||
display: block;
|
||||
width: 86%;
|
||||
max-width: 290px;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.proposal-seconded-empty__title {
|
||||
margin-top: 14px;
|
||||
color: #50627a;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.proposal-seconded-empty__hint {
|
||||
margin-top: 5px;
|
||||
color: #9aa7b8;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.proposal-seconded-card-header {
|
||||
display: flex;
|
||||
margin-bottom: 7px;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.proposal-seconded-card-heading {
|
||||
min-width: 0;
|
||||
padding-right: 10px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.proposal-seconded-card-title {
|
||||
overflow: hidden;
|
||||
color: #253247;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
font-weight: 600;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.proposal-seconded-card-code {
|
||||
margin-top: 1px;
|
||||
color: #9ba6b6;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.proposal-seconded-status {
|
||||
display: inline-flex;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.proposal-seconded-status .van-tag {
|
||||
display: inline-flex;
|
||||
min-height: 24px;
|
||||
padding: 3px 9px;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
border: 0;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media (max-width: 350px) {
|
||||
.proposal-seconded-content {
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.proposal-seconded-sticky {
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="附议提案" placeholder fixed></van-nav-bar>
|
||||
<van-sticky offset-top="46px">
|
||||
<van-search
|
||||
v-model="pageForm.name"
|
||||
:show-action="false"
|
||||
:reverse-color="false"
|
||||
input-align="left"
|
||||
placeholder="请输入提案名称搜索"
|
||||
@search="doSearch"
|
||||
></van-search>
|
||||
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
|
||||
<van-dropdown-item v-model="pageForm.sessionId" :options="sessionOptions" :multiple="false"
|
||||
@change="doSearch"></van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
<van-tabs v-model="pageForm.approvalText"
|
||||
@change="(val)=>{this.pageForm.approval = val==='1';this.doSearch();}">
|
||||
<van-tab title="已附议" name="1"></van-tab>
|
||||
<van-tab title="未附议" name="0"></van-tab>
|
||||
</van-tabs>
|
||||
</van-sticky>
|
||||
<div class="proposal-seconded-page">
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="附议提案" placeholder fixed></van-nav-bar>
|
||||
|
||||
<table-list api="/platform/proposal/seconded/pageData" :page_form.sync="pageForm" @ready="onReady"
|
||||
ref="tableListRef"
|
||||
title="name">
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="提案编号">{{row.code}}</table-column>
|
||||
<table-column label="提案类别">{{row.typeName}}</table-column>
|
||||
<table-column label="提案人">{{row.createUserName}}</table-column>
|
||||
<table-column label="代表团">{{row.delegationName}}</table-column>
|
||||
<table-column label="当前节点">{{row.curTaskName}}</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row)">
|
||||
<i class="fa fa-eye"></i>
|
||||
<span>查看</span>
|
||||
<van-sticky :offset-top="46">
|
||||
<div class="proposal-seconded-sticky">
|
||||
<van-search
|
||||
v-model="pageForm.name"
|
||||
class="proposal-seconded-search"
|
||||
placeholder="请输入提案名称搜索"
|
||||
shape="round"
|
||||
clearable
|
||||
@search="doSearch"
|
||||
@clear="doSearch"
|
||||
></van-search>
|
||||
<van-dropdown-menu
|
||||
class="proposal-seconded-filter"
|
||||
:class="{'is-open': filterOpened}"
|
||||
active-color="#1989fa"
|
||||
>
|
||||
<van-dropdown-item
|
||||
v-model="pageForm.sessionId"
|
||||
:options="sessionOptions"
|
||||
:multiple="false"
|
||||
@change="doSearch"
|
||||
@open="filterOpened = true"
|
||||
@close="filterOpened = false"
|
||||
></van-dropdown-item>
|
||||
<van-dropdown-item
|
||||
v-model="pageForm.approvalText"
|
||||
:options="approvalOptions"
|
||||
:multiple="false"
|
||||
@change="approvalChange"
|
||||
@open="filterOpened = true"
|
||||
@close="filterOpened = false"
|
||||
></van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
</div>
|
||||
<div class="action-btn" v-if="showSecondAction(row)" @click="onApproval(row)">
|
||||
<i class="fa fa-edit"></i>
|
||||
<span>附议</span>
|
||||
</div>
|
||||
<!-- <div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)">-->
|
||||
<!-- <i class="fa fa-reply"></i>-->
|
||||
<!-- <span>撤回</span>-->
|
||||
<!-- </div>-->
|
||||
</template>
|
||||
</table-list>
|
||||
</van-sticky>
|
||||
|
||||
<proposal-info ref="proposalInfoRef">
|
||||
<div v-if="showApprovalForm">
|
||||
<div class="process-title">提案附议</div>
|
||||
<!-- <div class="process-title">{{formData.taskName}}</div>-->
|
||||
<van-form ref="formRef">
|
||||
<van-field
|
||||
v-model="formData.tf_opinion"
|
||||
name="tf_opinion"
|
||||
label="审批意见"
|
||||
placeholder="请输入审批意见"
|
||||
:rules="[{ required: true, message: '请填写审批意见' }]"
|
||||
required
|
||||
maxlength="100"
|
||||
show-word-limit
|
||||
></van-field>
|
||||
<!-- <van-field label="签字" name="approvalSignature" v-model="formData.signature" required class="direction-column-field">-->
|
||||
<!-- <h5-signature v-model="formData.approvalSignature" slot="input"></h5-signature>-->
|
||||
<!-- </van-field>-->
|
||||
</van-form>
|
||||
<div v-if="row && row.curTaskCode === 'second'"
|
||||
style="display: flex; justify-content: space-between; column-gap: 10px; padding: 10px">
|
||||
<van-button type="danger" block @click="handleTaskAction(20)">不同意</van-button>
|
||||
<van-button type="primary" block @click="handleTaskAction(1)">同意</van-button>
|
||||
<main class="proposal-seconded-content">
|
||||
<div class="proposal-seconded-list">
|
||||
<!-- 列表无记录时使用与“我的提案”一致的提案专用空状态。 -->
|
||||
<div v-if="pageForm.totalCount === 0" class="proposal-seconded-empty">
|
||||
<img src="/assets/mobile/img/proposal/proposal-empty.png" alt="暂无提案记录插画">
|
||||
<div class="proposal-seconded-empty__title">暂无提案记录</div>
|
||||
<div class="proposal-seconded-empty__hint">当前筛选条件下暂时没有提案</div>
|
||||
</div>
|
||||
<table-list api="/platform/proposal/seconded/pageData" :page_form.sync="pageForm" @ready="onReady"
|
||||
ref="tableListRef"
|
||||
title="name">
|
||||
<template #header="{index,row}">
|
||||
<div class="proposal-seconded-card-header">
|
||||
<div class="proposal-seconded-card-heading">
|
||||
<div class="proposal-seconded-card-title">{{row.name}}</div>
|
||||
<div class="proposal-seconded-card-code">编号:{{row.code || '--'}}</div>
|
||||
</div>
|
||||
<!-- 流程状态固定显示在卡片右上角,并使用胶囊形标签突出当前进度。 -->
|
||||
<enum-tag
|
||||
class="proposal-seconded-status"
|
||||
v-if="row.instanceState"
|
||||
:value="row.instanceState"
|
||||
name="ProcessInstanceStateEnum"
|
||||
label_key="message"
|
||||
size="small"
|
||||
></enum-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="提案类别">{{row.typeName || '--'}}</table-column>
|
||||
<table-column label="提案人">{{row.createUserName || '--'}}</table-column>
|
||||
<table-column label="代表团">{{row.delegationName || '--'}}</table-column>
|
||||
<table-column label="当前节点">{{row.curTaskName || '--'}}</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row,index)">
|
||||
<i class="fa fa-eye"></i>
|
||||
<span>查看</span>
|
||||
</div>
|
||||
<div class="action-btn" v-if="showSecondAction(row)" @click="onApproval(row,index)">
|
||||
<i class="fa fa-edit"></i>
|
||||
<span>附议</span>
|
||||
</div>
|
||||
<!-- <div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)">-->
|
||||
<!-- <i class="fa fa-reply"></i>-->
|
||||
<!-- <span>撤回</span>-->
|
||||
<!-- </div>-->
|
||||
</template>
|
||||
</table-list>
|
||||
</div>
|
||||
<div v-else style="display: flex; justify-content: space-between; column-gap: 10px; padding: 10px">
|
||||
<van-button type="danger" block @click="updateSecond(2)">不同意</van-button>
|
||||
<van-button type="primary" block @click="updateSecond(1)">同意</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</proposal-info>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include("../../common/info.js"){}#-->
|
||||
|
||||
const vue = new Vue({
|
||||
new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
data() {
|
||||
@@ -93,6 +339,9 @@ layout("/layouts/platform_h5.html"){
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
// 排序字段和方向为空时,后端使用任务创建时间倒序返回分页数据。
|
||||
pageOrderName: "",
|
||||
pageOrderBy: "",
|
||||
name: null,
|
||||
sessionId: null,
|
||||
approvalText: "0",
|
||||
@@ -100,17 +349,15 @@ layout("/layouts/platform_h5.html"){
|
||||
},
|
||||
|
||||
sessionOptions: [],
|
||||
row: null,
|
||||
formData: {},
|
||||
showApprovalForm: false,
|
||||
rules: {
|
||||
approvalSignature: [{required: true, message: "请签字"}]
|
||||
}
|
||||
// 任一筛选项展开时,将筛选栏切换为与下拉面板衔接的上圆角样式。
|
||||
filterOpened: false,
|
||||
approvalOptions: [
|
||||
{text: "未附议", value: "0"},
|
||||
{text: "已附议", value: "1"}
|
||||
],
|
||||
pendingListCache: null
|
||||
}
|
||||
},
|
||||
components: {
|
||||
"proposal-info": PROPOSAL_INFO
|
||||
},
|
||||
methods: {
|
||||
// 附议按钮显示规则:
|
||||
// 1. 当前任务进行中(taskState=10)时显示
|
||||
@@ -119,101 +366,298 @@ layout("/layouts/platform_h5.html"){
|
||||
return row.taskState === 10 || (row.taskState === 99 && ["invite", "second", "preAudit", "delegation", "committee"].includes(row.taskKey))
|
||||
},
|
||||
onReady() {
|
||||
this.listSession()
|
||||
if (this.pendingListCache) {
|
||||
this.restoreListCache()
|
||||
this.listSession(true)
|
||||
return
|
||||
}
|
||||
this.listSession(false)
|
||||
},
|
||||
listSession() {
|
||||
// 附议状态下拉框使用字符串值展示,查询接口使用布尔值区分已附议和未附议。
|
||||
approvalChange(value) {
|
||||
this.$set(this.pageForm, "approval", value === "1")
|
||||
this.doSearch()
|
||||
},
|
||||
listSession(skipDefaultSearch) {
|
||||
this.$axios.post("/platform/proposal/common/listSession").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.sessionOptions = [
|
||||
{
|
||||
text: "全部届次",
|
||||
value: null
|
||||
}
|
||||
].concat(res.data.map((v) => ({text: v.fullName, value: v.id})))
|
||||
if (this.sessionOptions.length > 0) {
|
||||
this.pageForm.sessionId = this.sessionOptions[0].value
|
||||
this.doSearch()
|
||||
}
|
||||
this.sessionOptions = [{text: "全部届次", value: null}]
|
||||
.concat(res.data.map((v) => ({text: v.fullName, value: v.id})))
|
||||
if (skipDefaultSearch) return
|
||||
// “全部届次”固定展示在首位,但首次查询仍默认使用接口倒序返回的最新届次。
|
||||
this.$set(this.pageForm, "sessionId", res.data.length > 0 ? res.data[0].id : null)
|
||||
this.doSearch()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onView(row) {
|
||||
this.showApprovalForm = false
|
||||
this.$refs.proposalInfoRef.onOpen(row)
|
||||
onView(row, index) {
|
||||
this.saveListCache(row, index)
|
||||
this.$pjaxReplace("/platform/proposal/seconded/info/h5?bizId=" + encodeURIComponent(row.id) + "&action=view")
|
||||
},
|
||||
|
||||
onApproval(row) {
|
||||
this.row = row
|
||||
this.showApprovalForm = true
|
||||
this.$refs.proposalInfoRef.onOpen(row)
|
||||
this.formData = {
|
||||
processTaskId: row.taskId,
|
||||
taskId: row.taskId,
|
||||
proposalId: row.id,
|
||||
taskActorUserId: row.taskActorUserId,
|
||||
taskName: row.curTaskName,
|
||||
tf_opinion: "同意该提案"
|
||||
onApproval(row, index) {
|
||||
this.saveListCache(row, index)
|
||||
const params = new URLSearchParams()
|
||||
params.set("bizId", row.id)
|
||||
params.set("action", "approval")
|
||||
params.set("taskId", row.taskId)
|
||||
params.set("taskActorUserId", row.taskActorUserId)
|
||||
params.set("curTaskCode", row.curTaskCode || "")
|
||||
params.set("taskName", row.curTaskName || "提案附议")
|
||||
this.$pjaxReplace("/platform/proposal/seconded/info/h5?" + params.toString())
|
||||
},
|
||||
|
||||
getListCacheKey() {
|
||||
return "proposal:seconded:h5:list-state"
|
||||
},
|
||||
|
||||
getListCacheTokenParam() {
|
||||
return "secondedListCacheToken"
|
||||
},
|
||||
|
||||
getCurrentListCacheToken() {
|
||||
return new URLSearchParams(window.location.search).get(this.getListCacheTokenParam()) || ""
|
||||
},
|
||||
|
||||
/**
|
||||
* 将本次列表缓存标识绑定到当前历史记录。
|
||||
* PJAX 返回时可能重复初始化列表,两个实例通过同一标识读取同一份快照,避免后一次初始化覆盖恢复结果。
|
||||
*/
|
||||
bindListCacheToken(token) {
|
||||
const currentUrl = new URL(window.location.href)
|
||||
currentUrl.searchParams.set(this.getListCacheTokenParam(), token)
|
||||
const currentState = window.history.state ? {...window.history.state} : null
|
||||
if (currentState && currentState.url) {
|
||||
currentState.url = currentUrl.toString()
|
||||
}
|
||||
window.history.replaceState(currentState, document.title, currentUrl.toString())
|
||||
if ($.pjax && $.pjax.state) {
|
||||
$.pjax.state.url = currentUrl.toString()
|
||||
}
|
||||
},
|
||||
|
||||
async handleTaskAction(val) {
|
||||
/**
|
||||
* 用户主动重新查询时清除详情返回标识,防止后续重新进入列表时误用旧筛选和旧锚点。
|
||||
*/
|
||||
clearListCache() {
|
||||
const token = this.getCurrentListCacheToken()
|
||||
if (!token) return
|
||||
const currentUrl = new URL(window.location.href)
|
||||
currentUrl.searchParams.delete(this.getListCacheTokenParam())
|
||||
const currentState = window.history.state ? {...window.history.state} : null
|
||||
if (currentState && currentState.url) {
|
||||
currentState.url = currentUrl.toString()
|
||||
}
|
||||
window.history.replaceState(currentState, document.title, currentUrl.toString())
|
||||
if ($.pjax && $.pjax.state) {
|
||||
$.pjax.state.url = currentUrl.toString()
|
||||
}
|
||||
sessionStorage.removeItem(this.getListCacheKey())
|
||||
},
|
||||
|
||||
// H5 页面由 page-wrapper 承担滚动,缓存和恢复位置时不能使用 window 的滚动值。
|
||||
getListScrollContainer() {
|
||||
return document.querySelector(".page-wrapper") || document.scrollingElement || document.documentElement
|
||||
},
|
||||
|
||||
/**
|
||||
* 进入详情前保存查询条件、已加载分页和点击卡片锚点。
|
||||
* row/index 分别为当前记录和它在已加载列表中的位置;缓存仅在当前标签页保留。
|
||||
*/
|
||||
saveListCache(row, index) {
|
||||
const tableList = this.$refs.tableListRef
|
||||
if (!tableList) return
|
||||
const cacheToken = Date.now() + "-" + Math.random().toString(36).slice(2)
|
||||
const tableState = typeof tableList.getListState === "function"
|
||||
? tableList.getListState()
|
||||
: {
|
||||
tableData: JSON.parse(JSON.stringify(tableList.tableData || [])),
|
||||
localPageForm: JSON.parse(JSON.stringify(tableList.localPageForm || this.pageForm)),
|
||||
tableFinished: !!tableList.tableFinished
|
||||
}
|
||||
const cards = document.querySelectorAll(".proposal-seconded-list .table-list-item")
|
||||
const currentCard = cards[index]
|
||||
const scrollContainer = this.getListScrollContainer()
|
||||
const cache = {
|
||||
time: Date.now(),
|
||||
token: cacheToken,
|
||||
pageForm: JSON.parse(JSON.stringify(this.pageForm)),
|
||||
tableState: tableState,
|
||||
completedBizId: "",
|
||||
anchor: {
|
||||
rowId: row.id,
|
||||
previousRowId: index > 0 && tableState.tableData[index - 1] ? tableState.tableData[index - 1].id : "",
|
||||
nextRowId: tableState.tableData[index + 1] ? tableState.tableData[index + 1].id : "",
|
||||
viewportTop: currentCard ? currentCard.getBoundingClientRect().top : 0,
|
||||
scrollTop: scrollContainer ? scrollContainer.scrollTop : 0
|
||||
}
|
||||
}
|
||||
try {
|
||||
await this.$refs.formRef.validate();
|
||||
this.$dialog.confirm({
|
||||
title: "提示",
|
||||
message: "您确定要提交吗?"
|
||||
}).then(() => {
|
||||
this.$axios.post("/platform/proposal/seconded/executeTask", {
|
||||
data: JSON.stringify({
|
||||
...this.formData,
|
||||
submitType: val
|
||||
})
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$refs.proposalInfoRef.onClose()
|
||||
this.$toast.success(res.msg)
|
||||
this.doSearch()
|
||||
}
|
||||
})
|
||||
}).catch(() => {
|
||||
|
||||
})
|
||||
sessionStorage.setItem(this.getListCacheKey(), JSON.stringify(cache))
|
||||
this.bindListCacheToken(cacheToken)
|
||||
} catch (error) {
|
||||
|
||||
sessionStorage.removeItem(this.getListCacheKey())
|
||||
}
|
||||
},
|
||||
|
||||
updateSecond(submitType) {
|
||||
this.$dialog.confirm({
|
||||
title: "提示",
|
||||
message: "您确定要提交吗?"
|
||||
}).then(() => {
|
||||
this.$axios.post("/platform/proposal/seconded/updateInfo", {
|
||||
proposalId: this.formData.proposalId,
|
||||
taskId: this.formData.taskId,
|
||||
taskActorUserId: this.formData.taskActorUserId,
|
||||
opinion: this.formData.tf_opinion,
|
||||
submitType: submitType,
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$refs.proposalInfoRef.onClose()
|
||||
this.$toast.success(res.msg)
|
||||
this.doSearch()
|
||||
}
|
||||
/**
|
||||
* 读取十分钟内的列表缓存,并先恢复查询参数,保证 TableList 创建时使用原查询条件。
|
||||
*/
|
||||
prepareListCache() {
|
||||
try {
|
||||
const cacheToken = this.getCurrentListCacheToken()
|
||||
if (!cacheToken) return
|
||||
const cacheText = sessionStorage.getItem(this.getListCacheKey())
|
||||
if (!cacheText) return
|
||||
const cache = JSON.parse(cacheText)
|
||||
if (!cache || cache.token !== cacheToken || !cache.tableState) return
|
||||
if (Date.now() - cache.time > 10 * 60 * 1000) {
|
||||
this.clearListCache()
|
||||
return
|
||||
}
|
||||
Object.keys(cache.pageForm || {}).forEach((key) => {
|
||||
this.$set(this.pageForm, key, cache.pageForm[key])
|
||||
})
|
||||
}).catch(() => {
|
||||
this.$set(this, "pendingListCache", cache)
|
||||
} catch (error) {
|
||||
sessionStorage.removeItem(this.getListCacheKey())
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 恢复列表并按业务结果选择定位锚点。
|
||||
* 未提交时定位原记录;附议成功且原筛选为未附议时移除原记录并定位下一条,下一条不存在则定位上一条。
|
||||
*/
|
||||
restoreListCache() {
|
||||
const cache = this.pendingListCache
|
||||
const tableList = this.$refs.tableListRef
|
||||
if (!cache || !tableList) return
|
||||
const anchor = cache.anchor || {}
|
||||
if (cache.completedBizId) {
|
||||
this.refreshCompletedListCache(cache)
|
||||
return
|
||||
}
|
||||
this.applyListCache(cache.tableState, anchor.rowId, anchor)
|
||||
},
|
||||
|
||||
/**
|
||||
* 附议成功后按原来已加载的页数重新查询最新数据。
|
||||
* 请求参数沿用原筛选条件,并临时扩大 pageSize,一次取回原浏览范围,避免删除记录造成后续分页漏数。
|
||||
*/
|
||||
refreshCompletedListCache(cache) {
|
||||
const originalPageSize = Number(cache.pageForm.pageSize || 10)
|
||||
const loadedCount = Math.max(cache.tableState.tableData.length, originalPageSize)
|
||||
const loadedPageCount = Math.max(1, Math.ceil(loadedCount / originalPageSize))
|
||||
const requestForm = {
|
||||
...cache.pageForm,
|
||||
pageNumber: 1,
|
||||
pageSize: loadedPageCount * originalPageSize
|
||||
}
|
||||
const loading = createListLoading()
|
||||
this.$axios.post("/platform/proposal/seconded/pageData", requestForm, {h5SkeletonLoading: true})
|
||||
.then((res) => {
|
||||
if (res.code !== 0) {
|
||||
this.applyCompletedCacheFallback(cache)
|
||||
return
|
||||
}
|
||||
const freshState = {
|
||||
tableData: res.data.list || [],
|
||||
localPageForm: {
|
||||
...cache.pageForm,
|
||||
pageNumber: loadedPageCount,
|
||||
pageSize: originalPageSize,
|
||||
totalCount: res.data.totalCount
|
||||
},
|
||||
tableFinished: (res.data.list || []).length >= res.data.totalCount
|
||||
}
|
||||
const anchor = cache.anchor || {}
|
||||
this.applyListCache(freshState, anchor.nextRowId || anchor.previousRowId, anchor)
|
||||
})
|
||||
.catch(() => {
|
||||
this.applyCompletedCacheFallback(cache)
|
||||
})
|
||||
.finally(() => {
|
||||
loading.close()
|
||||
})
|
||||
},
|
||||
|
||||
// 最新列表查询失败时,仅从旧快照移除已处理记录,仍保证用户能够回到原浏览区域。
|
||||
applyCompletedCacheFallback(cache) {
|
||||
const tableState = cache.tableState
|
||||
const completedIndex = tableState.tableData.findIndex((item) => String(item.id) === String(cache.completedBizId))
|
||||
if (completedIndex >= 0 && cache.pageForm.approval === false) {
|
||||
tableState.tableData.splice(completedIndex, 1)
|
||||
if (tableState.localPageForm && tableState.localPageForm.totalCount > 0) {
|
||||
tableState.localPageForm.totalCount--
|
||||
}
|
||||
} else if (completedIndex >= 0) {
|
||||
this.$set(tableState.tableData[completedIndex], "taskState", 20)
|
||||
}
|
||||
const anchor = cache.anchor || {}
|
||||
this.applyListCache(tableState, anchor.nextRowId || anchor.previousRowId, anchor)
|
||||
},
|
||||
|
||||
/**
|
||||
* 将列表状态写回 TableList,并把目标卡片恢复到离开页面前的屏幕位置。
|
||||
*/
|
||||
applyListCache(tableState, targetRowId, anchor) {
|
||||
const tableList = this.$refs.tableListRef
|
||||
if (!tableList) return
|
||||
if (typeof tableList.restoreListState === "function") {
|
||||
if (!tableList.restoreListState(tableState)) return
|
||||
} else {
|
||||
this.$set(tableList, "tableData", tableState.tableData || [])
|
||||
this.$set(tableList, "localPageForm", {...tableList.localPageForm, ...(tableState.localPageForm || {})})
|
||||
this.$set(tableList, "tableFinished", !!tableState.tableFinished)
|
||||
this.$set(tableList, "tableLoading", false)
|
||||
this.$set(tableList, "tableRefreshing", false)
|
||||
tableList.$emit("update:page_form", {...tableList.localPageForm})
|
||||
}
|
||||
this.$set(this, "pendingListCache", null)
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
const cards = Array.from(document.querySelectorAll(".proposal-seconded-list .table-list-item"))
|
||||
const scrollContainer = this.getListScrollContainer()
|
||||
if (!scrollContainer) return
|
||||
const candidateIds = [targetRowId, anchor.nextRowId, anchor.previousRowId]
|
||||
.filter((id) => id)
|
||||
.map((id) => String(id))
|
||||
.filter((id, index, values) => values.indexOf(id) === index)
|
||||
// 严格按当前记录、下一条、上一条的顺序选择锚点,避免列表中靠前的上一条被提前命中。
|
||||
let targetIndex = -1
|
||||
candidateIds.some((candidateId) => {
|
||||
targetIndex = (tableState.tableData || []).findIndex((item) => String(item.id) === candidateId)
|
||||
return targetIndex >= 0
|
||||
})
|
||||
const targetCard = targetIndex >= 0 ? cards[targetIndex] : null
|
||||
if (targetCard) {
|
||||
// 首次滚动会触发筛选栏吸顶,再次校正可消除 sticky 布局切换造成的位置偏差。
|
||||
const restoreTargetCard = () => {
|
||||
if (!targetCard.isConnected) return
|
||||
const targetTop = scrollContainer.scrollTop + targetCard.getBoundingClientRect().top - Number(anchor.viewportTop || 0)
|
||||
scrollContainer.scrollTo(0, Math.max(0, targetTop))
|
||||
}
|
||||
restoreTargetCard()
|
||||
setTimeout(restoreTargetCard, 100)
|
||||
return
|
||||
}
|
||||
scrollContainer.scrollTo(0, Number(anchor.scrollTop || 0))
|
||||
}, 0)
|
||||
})
|
||||
},
|
||||
|
||||
doSearch() {
|
||||
this.clearListCache()
|
||||
this.$nextTick(() => {
|
||||
this.pageForm.pageNumber = 1
|
||||
this.pageForm.totalCount = 0
|
||||
this.$refs.tableListRef.doSearch()
|
||||
})
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.prepareListCache()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" :title="pageTitle" placeholder fixed></van-nav-bar>
|
||||
<proposal-info v-if="bizId" :biz-id="bizId">
|
||||
<div v-if="isApproval">
|
||||
<div class="process-title">提案附议</div>
|
||||
<van-form ref="formRef">
|
||||
<van-field
|
||||
v-model="formData.tf_opinion"
|
||||
name="tf_opinion"
|
||||
label="审批意见"
|
||||
placeholder="请输入审批意见"
|
||||
:rules="[{ required: true, message: '请填写审批意见' }]"
|
||||
required
|
||||
maxlength="100"
|
||||
show-word-limit
|
||||
></van-field>
|
||||
</van-form>
|
||||
<div style="display: flex; justify-content: space-between; column-gap: 10px; padding: 10px">
|
||||
<van-button type="danger" block :loading="formLoading" @click="submitSeconded(false)">不同意</van-button>
|
||||
<van-button type="primary" block :loading="formLoading" @click="submitSeconded(true)">同意</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</proposal-info>
|
||||
</div>
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include("../../common/info.js"){}#-->
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
components: {
|
||||
"proposal-info": PROPOSAL_INFO
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
bizId: "",
|
||||
action: "view",
|
||||
curTaskCode: "",
|
||||
formData: {},
|
||||
formLoading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isApproval() {
|
||||
return this.action === "approval"
|
||||
},
|
||||
pageTitle() {
|
||||
return this.isApproval ? "提案附议" : "提案详情"
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 提交附议结果。
|
||||
* isAgree 为 true 表示同意、false 表示不同意;接口返回 Result,code 为 0 表示提交成功。
|
||||
*/
|
||||
submitSeconded(isAgree) {
|
||||
this.$refs.formRef.validate()
|
||||
.then(() => this.$dialog.confirm({title: "提示", message: "您确定要提交吗?"}))
|
||||
.then(() => {
|
||||
this.formLoading = true
|
||||
if (this.curTaskCode === "second") {
|
||||
return this.$axios.post("/platform/proposal/seconded/executeTask", {
|
||||
data: JSON.stringify({
|
||||
...this.formData,
|
||||
submitType: isAgree ? 1 : 20
|
||||
})
|
||||
})
|
||||
}
|
||||
return this.$axios.post("/platform/proposal/seconded/updateInfo", {
|
||||
proposalId: this.formData.proposalId,
|
||||
taskId: this.formData.taskId,
|
||||
taskActorUserId: this.formData.taskActorUserId,
|
||||
opinion: this.formData.tf_opinion,
|
||||
submitType: isAgree ? 1 : 2
|
||||
})
|
||||
})
|
||||
.then((res) => {
|
||||
if (res && res.code === 0) {
|
||||
this.markListCacheCompleted()
|
||||
this.$toast.success(res.msg)
|
||||
this.historyBack()
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
this.formLoading = false
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 附议成功后标记已处理的提案主键。
|
||||
* 返回未附议列表时据此移除原记录,并改用相邻卡片恢复浏览位置。
|
||||
*/
|
||||
markListCacheCompleted() {
|
||||
const cacheKey = "proposal:seconded:h5:list-state"
|
||||
try {
|
||||
const cacheText = sessionStorage.getItem(cacheKey)
|
||||
if (!cacheText) return
|
||||
const cache = JSON.parse(cacheText)
|
||||
cache.completedBizId = this.bizId
|
||||
cache.time = Date.now()
|
||||
sessionStorage.setItem(cacheKey, JSON.stringify(cache))
|
||||
} catch (error) {
|
||||
sessionStorage.removeItem(cacheKey)
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
// bizId、taskId、taskActorUserId 和 curTaskCode 分别用于详情查询、任务定位、附议人校验和提交链路选择。
|
||||
this.bizId = params.get("bizId") || ""
|
||||
this.action = params.get("action") || "view"
|
||||
this.curTaskCode = params.get("curTaskCode") || ""
|
||||
if (this.isApproval) {
|
||||
this.$set(this.formData, "processTaskId", params.get("taskId") || "")
|
||||
this.$set(this.formData, "taskId", params.get("taskId") || "")
|
||||
this.$set(this.formData, "proposalId", this.bizId)
|
||||
this.$set(this.formData, "taskActorUserId", params.get("taskActorUserId") || "")
|
||||
this.$set(this.formData, "taskName", params.get("taskName") || "提案附议")
|
||||
this.$set(this.formData, "tf_opinion", "同意该提案")
|
||||
}
|
||||
if (!this.bizId) {
|
||||
this.$toast.fail("缺少提案参数")
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
+2
-73
@@ -57,45 +57,6 @@ layout("/layouts/platform_h5.html"){
|
||||
</template>
|
||||
</table-list>
|
||||
|
||||
<proposal-info ref="proposalInfoRef">
|
||||
<div v-if="showApprovalForm">
|
||||
<div class="process-title">承办单位意见</div>
|
||||
<van-form ref="formRef">
|
||||
<van-field
|
||||
v-model="formData.result"
|
||||
name="result"
|
||||
label="承办意向"
|
||||
placeholder="请选择承办意向"
|
||||
:rules="[{ required: true, message: '必填' }]"
|
||||
required
|
||||
is-link
|
||||
@click="showPicker = true"
|
||||
></van-field>
|
||||
<van-popup v-model="showPicker" position="bottom">
|
||||
<van-picker
|
||||
show-toolbar
|
||||
:columns="dict.type.UNDERTAKE_SUGGESTION.map(item => item.code)"
|
||||
@confirm="(val) => {formData.result = val; showPicker = false}"
|
||||
@cancel="showPicker = false"
|
||||
></van-picker>
|
||||
</van-popup>
|
||||
<van-field
|
||||
v-model="formData.opinion"
|
||||
name="opinion"
|
||||
label="反馈意见"
|
||||
placeholder="请输入反馈意见"
|
||||
:rules="[{ required: true, message: '必填' }]"
|
||||
required
|
||||
show-word-limit
|
||||
></van-field>
|
||||
</van-form>
|
||||
<div style="display: flex; justify-content: space-between; column-gap: 10px; padding: 10px">
|
||||
<van-button block @click="">取消</van-button>
|
||||
<van-button type="primary" block @click="doSubmit">提交</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</proposal-info>
|
||||
|
||||
<van-action-sheet v-model="visible" title="意见信息">
|
||||
<div class="detail-container">
|
||||
<van-cell-group :key="index" v-for="item,index in suggestionList">
|
||||
@@ -120,14 +81,9 @@ layout("/layouts/platform_h5.html"){
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include("../../common/info.js"){}#-->
|
||||
new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
dicts: ["UNDERTAKE_SUGGESTION"],
|
||||
components: {
|
||||
"proposal-info": PROPOSAL_INFO
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pageForm: {
|
||||
@@ -141,10 +97,6 @@ layout("/layouts/platform_h5.html"){
|
||||
},
|
||||
|
||||
sessionOptions: [],
|
||||
formData: {},
|
||||
showApprovalForm: false,
|
||||
|
||||
showPicker: false,
|
||||
suggestionList: [],
|
||||
visible: false,
|
||||
}
|
||||
@@ -203,33 +155,10 @@ layout("/layouts/platform_h5.html"){
|
||||
})
|
||||
},
|
||||
onAudit(row) {
|
||||
this.showApprovalForm = true
|
||||
this.$refs.proposalInfoRef.onOpen(row)
|
||||
this.formData = {
|
||||
proposalId: row.id,
|
||||
}
|
||||
},
|
||||
async doSubmit() {
|
||||
try {
|
||||
await this.$refs.formRef.validate()
|
||||
this.$dialog.confirm({
|
||||
title: "提示",
|
||||
message: "您确定要提交吗?"
|
||||
}).then(() => {
|
||||
this.$axios.post("/platform/proposal/suggestion/suggest", this.formData)
|
||||
.then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$refs.proposalInfoRef.onClose()
|
||||
this.$toast.success(res.msg)
|
||||
this.doSearch()
|
||||
}
|
||||
})
|
||||
}).catch(() => {})
|
||||
} catch (error) {}
|
||||
this.$pjaxReplace("/platform/proposal/suggestion/info/h5?bizId=" + encodeURIComponent(row.id) + "&action=approval")
|
||||
},
|
||||
onView(row) {
|
||||
this.showApprovalForm = false
|
||||
this.$refs.proposalInfoRef.onOpen(row)
|
||||
this.$pjaxReplace("/platform/proposal/suggestion/info/h5?bizId=" + encodeURIComponent(row.id) + "&action=view")
|
||||
},
|
||||
doSearch() {
|
||||
this.$nextTick(() => {
|
||||
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" :title="pageTitle" placeholder fixed></van-nav-bar>
|
||||
<proposal-info v-if="bizId" :biz-id="bizId">
|
||||
<div v-if="isApproval">
|
||||
<div class="process-title">承办单位意见</div>
|
||||
<van-form ref="formRef">
|
||||
<van-field
|
||||
v-model="formData.result"
|
||||
name="result"
|
||||
label="承办意向"
|
||||
placeholder="请选择承办意向"
|
||||
:rules="[{ required: true, message: '请选择承办意向' }]"
|
||||
required
|
||||
is-link
|
||||
@click="showPicker = true"
|
||||
></van-field>
|
||||
<van-popup v-model="showPicker" position="bottom">
|
||||
<van-picker
|
||||
show-toolbar
|
||||
:columns="dict.type.UNDERTAKE_SUGGESTION.map(item => item.code)"
|
||||
@confirm="onResultConfirm"
|
||||
@cancel="showPicker = false"
|
||||
></van-picker>
|
||||
</van-popup>
|
||||
<van-field
|
||||
v-model="formData.opinion"
|
||||
name="opinion"
|
||||
label="反馈意见"
|
||||
placeholder="请输入反馈意见"
|
||||
:rules="[{ required: true, message: '请输入反馈意见' }]"
|
||||
required
|
||||
show-word-limit
|
||||
></van-field>
|
||||
</van-form>
|
||||
<div style="display: flex; justify-content: space-between; column-gap: 10px; padding: 10px">
|
||||
<van-button block :disabled="formLoading" @click="historyBack">取消</van-button>
|
||||
<van-button type="primary" block :loading="formLoading" @click="doSubmit">提交</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</proposal-info>
|
||||
</div>
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include("../../common/info.js"){}#-->
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
dicts: ["UNDERTAKE_SUGGESTION"],
|
||||
components: {
|
||||
"proposal-info": PROPOSAL_INFO
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
bizId: "",
|
||||
action: "view",
|
||||
formData: {},
|
||||
formLoading: false,
|
||||
showPicker: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isApproval() {
|
||||
return this.action === "approval"
|
||||
},
|
||||
pageTitle() {
|
||||
return this.isApproval ? "承办单位意见" : "提案详情"
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onResultConfirm(value) {
|
||||
this.$set(this.formData, "result", value)
|
||||
this.showPicker = false
|
||||
},
|
||||
/**
|
||||
* 提交承办单位意见;参数包含 proposalId、result 和 opinion,接口返回 Result。
|
||||
*/
|
||||
doSubmit() {
|
||||
this.$refs.formRef.validate()
|
||||
.then(() => this.$dialog.confirm({title: "提示", message: "您确定要提交吗?"}))
|
||||
.then(() => {
|
||||
this.formLoading = true
|
||||
return this.$axios.post("/platform/proposal/suggestion/suggest", this.formData)
|
||||
})
|
||||
.then((res) => {
|
||||
if (res && res.code === 0) {
|
||||
this.$toast.success(res.msg)
|
||||
this.historyBack()
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
this.formLoading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
// bizId 为提案详情查询和承办单位意见保存共同使用的提案主键。
|
||||
this.bizId = params.get("bizId") || ""
|
||||
this.action = params.get("action") || "view"
|
||||
if (this.isApproval) {
|
||||
this.$set(this.formData, "proposalId", this.bizId)
|
||||
}
|
||||
if (!this.bizId) {
|
||||
this.$toast.fail("缺少提案参数")
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
+610
-207
@@ -1,260 +1,650 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
<style>
|
||||
body {
|
||||
background-color: #f3f7fd;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-page {
|
||||
min-height: 100vh;
|
||||
padding-bottom: calc(24px + env(safe-area-inset-bottom));
|
||||
box-sizing: border-box;
|
||||
background-color: #f3f7fd;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-page .van-nav-bar {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-page .van-nav-bar__title {
|
||||
color: #172033;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-page .van-nav-bar .van-icon,
|
||||
.proposal-unit-reply-page .van-nav-bar__text {
|
||||
color: #1989fa;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-content {
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-sticky {
|
||||
padding: 22px 12px 1px;
|
||||
box-sizing: border-box;
|
||||
background-color: #f3f7fd;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-search {
|
||||
margin: 0 0 8px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
border-radius: 12px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 5px 16px rgba(43, 73, 112, .1);
|
||||
}
|
||||
|
||||
.proposal-unit-reply-search .van-search__content {
|
||||
height: 42px;
|
||||
padding-left: 12px;
|
||||
align-items: center;
|
||||
border-radius: 12px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-search .van-field__control {
|
||||
color: #263548;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-search .van-field__control::placeholder {
|
||||
color: #a7b0bf;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-filter {
|
||||
margin: 0 0 10px;
|
||||
overflow: hidden;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
|
||||
transition: border-radius .18s ease;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-filter.is-open {
|
||||
border-radius: 10px 10px 0 0;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-filter .van-dropdown-menu__bar,
|
||||
.proposal-unit-reply-filter .van-dropdown-menu__item,
|
||||
.proposal-unit-reply-filter .van-dropdown-menu__title {
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-filter .van-dropdown-menu__bar {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-filter .van-dropdown-menu__item,
|
||||
.proposal-unit-reply-filter .van-dropdown-menu__title {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-filter .van-dropdown-menu__item {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-filter .van-dropdown-menu__title {
|
||||
display: inline-flex;
|
||||
color: #66758a;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-filter .van-dropdown-menu__title--active,
|
||||
.proposal-unit-reply-filter .van-dropdown-item__option--active,
|
||||
.proposal-unit-reply-filter .van-dropdown-item__option--active .van-dropdown-item__icon {
|
||||
color: #1989fa;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-filter .van-dropdown-menu__title--down::after {
|
||||
border-color: transparent transparent #1989fa #1989fa;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-filter .van-dropdown-item__content {
|
||||
width: calc(100% - 24px);
|
||||
margin: 0 12px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
border-radius: 0 0 12px 12px;
|
||||
box-shadow: 0 10px 24px rgba(31, 65, 108, .14);
|
||||
}
|
||||
|
||||
.proposal-unit-reply-filter .van-dropdown-item__option {
|
||||
display: flex;
|
||||
min-height: 48px;
|
||||
padding: 0 16px;
|
||||
align-items: center;
|
||||
color: #46566c;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-filter .van-cell::after {
|
||||
right: 16px;
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-list .empty-state {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-empty {
|
||||
display: flex;
|
||||
min-height: 280px;
|
||||
padding: 46px 18px 36px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-empty img {
|
||||
display: block;
|
||||
width: 86%;
|
||||
max-width: 290px;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-empty__title {
|
||||
margin-top: 14px;
|
||||
color: #50627a;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-empty__hint {
|
||||
margin-top: 5px;
|
||||
color: #9aa7b8;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-card-header {
|
||||
display: flex;
|
||||
margin-bottom: 7px;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-card-heading {
|
||||
min-width: 0;
|
||||
padding-right: 10px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-card-title {
|
||||
overflow: hidden;
|
||||
color: #253247;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
font-weight: 600;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-card-code {
|
||||
margin-top: 1px;
|
||||
color: #9ba6b6;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-status {
|
||||
display: inline-flex;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.proposal-unit-reply-status .van-tag {
|
||||
display: inline-flex;
|
||||
min-height: 24px;
|
||||
padding: 3px 9px;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
border: 0;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media (max-width: 350px) {
|
||||
.proposal-unit-reply-content,
|
||||
.proposal-unit-reply-sticky {
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="承办单位答复" placeholder
|
||||
fixed></van-nav-bar>
|
||||
<van-sticky offset-top="46px">
|
||||
<van-search
|
||||
v-model="pageForm.name"
|
||||
:show-action="false"
|
||||
:reverse-color="false"
|
||||
input-align="left"
|
||||
placeholder="请输入提案名称搜索"
|
||||
@search="doSearch"
|
||||
></van-search>
|
||||
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
|
||||
<van-dropdown-item v-model="pageForm.sessionId" :options="sessionOptions" :multiple="false"
|
||||
@change="doSearch"></van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
<van-tabs v-model="pageForm.approvalText"
|
||||
@change="(val)=>{this.pageForm.approval = val==='1';this.doSearch();}">
|
||||
<van-tab title="已审核" name="1"></van-tab>
|
||||
<van-tab title="未审核" name="0"></van-tab>
|
||||
</van-tabs>
|
||||
</van-sticky>
|
||||
|
||||
<table-list api="/platform/proposal/unitReply/pageData" :page_form.sync="pageForm" @ready="onReady"
|
||||
ref="tableListRef"
|
||||
title="name">
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="提案编号">{{row.code}}</table-column>
|
||||
<table-column label="提案类别">{{row.typeName}}</table-column>
|
||||
<table-column label="提案人">{{row.createUserName}}</table-column>
|
||||
<table-column label="代表团">{{row.delegationName}}</table-column>
|
||||
<table-column label="承办单位">{{row.underTakeName}}({{row.underTakeIsMaster ? '主办' : '协办'}})
|
||||
</table-column>
|
||||
<table-column label="当前节点">{{row.curTaskName}}</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row)">
|
||||
<i class="fa fa-eye"></i>
|
||||
<span>查看</span>
|
||||
<div class="proposal-unit-reply-page">
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="承办单位答复" placeholder fixed></van-nav-bar>
|
||||
<van-sticky :offset-top="46">
|
||||
<div class="proposal-unit-reply-sticky">
|
||||
<van-search
|
||||
v-model="pageForm.name"
|
||||
class="proposal-unit-reply-search"
|
||||
placeholder="请输入提案名称搜索"
|
||||
shape="round"
|
||||
clearable
|
||||
@search="doSearch"
|
||||
@clear="doSearch"
|
||||
></van-search>
|
||||
<van-dropdown-menu
|
||||
class="proposal-unit-reply-filter"
|
||||
:class="{'is-open': filterOpened}"
|
||||
active-color="#1989fa"
|
||||
>
|
||||
<van-dropdown-item
|
||||
v-model="pageForm.sessionId"
|
||||
:options="sessionOptions"
|
||||
:multiple="false"
|
||||
@change="doSearch"
|
||||
@open="filterOpened = true"
|
||||
@close="filterOpened = false"
|
||||
></van-dropdown-item>
|
||||
<van-dropdown-item
|
||||
v-model="pageForm.approvalText"
|
||||
:options="approvalOptions"
|
||||
:multiple="false"
|
||||
@change="approvalChange"
|
||||
@open="filterOpened = true"
|
||||
@close="filterOpened = false"
|
||||
></van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
</div>
|
||||
<div class="action-btn" v-if="row.taskState === 10" @click="onApproval(row)">
|
||||
<i class="fa fa-edit"></i>
|
||||
<span>答复</span>
|
||||
</van-sticky>
|
||||
|
||||
<main class="proposal-unit-reply-content">
|
||||
<div class="proposal-unit-reply-list">
|
||||
<!-- 列表无记录时使用与团长审核一致的提案专用空状态。 -->
|
||||
<div v-if="pageForm.totalCount === 0" class="proposal-unit-reply-empty">
|
||||
<img src="/assets/mobile/img/proposal/proposal-empty.png" alt="暂无提案记录插画">
|
||||
<div class="proposal-unit-reply-empty__title">暂无提案记录</div>
|
||||
<div class="proposal-unit-reply-empty__hint">当前筛选条件下暂时没有提案</div>
|
||||
</div>
|
||||
<table-list api="/platform/proposal/unitReply/pageData" :page_form.sync="pageForm" @ready="onReady"
|
||||
ref="tableListRef"
|
||||
title="name">
|
||||
<template #header="{index,row}">
|
||||
<div class="proposal-unit-reply-card-header">
|
||||
<div class="proposal-unit-reply-card-heading">
|
||||
<div class="proposal-unit-reply-card-title">{{row.name}}</div>
|
||||
<div class="proposal-unit-reply-card-code">编号:{{row.code || '--'}}</div>
|
||||
</div>
|
||||
<enum-tag
|
||||
class="proposal-unit-reply-status"
|
||||
v-if="row.instanceState"
|
||||
:value="row.instanceState"
|
||||
name="ProcessInstanceStateEnum"
|
||||
label_key="message"
|
||||
size="small"
|
||||
></enum-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="提案类别">{{row.typeName || '--'}}</table-column>
|
||||
<table-column label="提案人">{{row.createUserName || '--'}}</table-column>
|
||||
<table-column label="代表团">{{row.delegationName || '--'}}</table-column>
|
||||
<table-column label="承办单位">{{row.underTakeName || '--'}}({{row.underTakeIsMaster ? '主办' : '协办'}})</table-column>
|
||||
<table-column label="当前节点">{{row.curTaskName || '--'}}</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row,index)">
|
||||
<i class="fa fa-eye"></i>
|
||||
<span>查看</span>
|
||||
</div>
|
||||
<div class="action-btn" v-if="row.taskState === 10" @click="onApproval(row,index)">
|
||||
<i class="fa fa-edit"></i>
|
||||
<span>答复</span>
|
||||
</div>
|
||||
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)">
|
||||
<i class="fa fa-reply"></i>
|
||||
<span>撤回</span>
|
||||
</div>
|
||||
|
||||
<!-- 承办单位领导、超级管理员才能转办。 -->
|
||||
<div class="action-btn" v-if="!pageForm.approval && $auth.hasRoleOr('SYSADMIN,PROPOSAL_UNIT_LEADER')"
|
||||
@click="openTransfer(row)">
|
||||
<i class="fa fa-arrow-right"></i>
|
||||
<span>转交</span>
|
||||
</div>
|
||||
</template>
|
||||
</table-list>
|
||||
</div>
|
||||
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)">
|
||||
<i class="fa fa-reply"></i>
|
||||
<span>撤回</span>
|
||||
</div>
|
||||
|
||||
<!--承办单位领导、超级管理员才能转办-->
|
||||
<div class="action-btn" v-if="!pageForm.approval && $auth.hasRoleOr('SYSADMIN,PROPOSAL_UNIT_LEADER')"
|
||||
@click="openTransfer(row)">
|
||||
<i class="fa fa-arrow-right"></i>
|
||||
<span>转交</span>
|
||||
</div>
|
||||
</template>
|
||||
</table-list>
|
||||
|
||||
<proposal-info ref="proposalInfoRef">
|
||||
<div v-if="showApprovalForm">
|
||||
<div class="process-title">{{formData.taskName}}</div>
|
||||
<van-form ref="formRef">
|
||||
<van-field :value="formData.underTakeName"
|
||||
label="承办单位"
|
||||
readonly></van-field>
|
||||
|
||||
<van-field
|
||||
v-model="formData.tf_implementStateName"
|
||||
name="tf_implementStateName"
|
||||
label="落实情况"
|
||||
placeholder="请选择落实情况"
|
||||
:rules="[{ required: true, message: '请选择落实情况' }]"
|
||||
required
|
||||
is-link
|
||||
@click="showImplementPicker = true"
|
||||
></van-field>
|
||||
<van-popup v-model="showImplementPicker" position="bottom">
|
||||
<van-picker
|
||||
show-toolbar
|
||||
:columns="dict.type.PROPOSAL_REPLY_IMPLEMENT.map(item => ({text: item.label, value: item.code}))"
|
||||
@confirm="onImplementConfirm"
|
||||
@cancel="showImplementPicker = false"
|
||||
></van-picker>
|
||||
</van-popup>
|
||||
|
||||
<van-field
|
||||
v-model="formData.tf_opinion"
|
||||
name="tf_opinion"
|
||||
label="答复内容"
|
||||
placeholder="请输入答复内容"
|
||||
:rules="[{ required: true, message: '请填写答复内容' }]"
|
||||
required
|
||||
maxlength="100"
|
||||
show-word-limit
|
||||
></van-field>
|
||||
|
||||
<van-field
|
||||
v-if="supportCandidate && formData.underTakeIsMaster"
|
||||
v-model="formData.tf_nextNodeOperatorName"
|
||||
name="tf_nextNodeOperatorName"
|
||||
label="校领导"
|
||||
placeholder="校领导"
|
||||
:rules="[{ required: true, message: '请选择校领导' }]"
|
||||
required
|
||||
is-link
|
||||
@click="showCandidatesPicker = true"
|
||||
></van-field>
|
||||
<van-popup v-model="showCandidatesPicker" position="bottom">
|
||||
<van-picker
|
||||
show-toolbar
|
||||
:columns="candidates.map(item => ({text: item.userName, value: item.userId}))"
|
||||
@confirm="onCandidatesConfirm"
|
||||
@cancel="showCandidatesPicker = false"
|
||||
></van-picker>
|
||||
</van-popup>
|
||||
|
||||
</van-form>
|
||||
<div style="display: flex; justify-content: space-between; column-gap: 10px; padding: 10px">
|
||||
<van-button block @click="$refs.proposalInfoRef.onClose()">取消</van-button>
|
||||
<van-button type="primary" block @click="handleTaskAction(1)">提交</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</proposal-info>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<transfer ref="transferRef"></transfer>
|
||||
</div>
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include("../../common/info.js"){}#-->
|
||||
<!--#include("transfer.js"){}#-->
|
||||
|
||||
const vue = new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
dicts: ['PROPOSAL_REPLY_IMPLEMENT'],
|
||||
data() {
|
||||
return {
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
pageOrderName: "",
|
||||
pageOrderBy: "",
|
||||
name: null,
|
||||
sessionId: null,
|
||||
approvalText: "0",
|
||||
approval: false
|
||||
},
|
||||
|
||||
sessionOptions: [],
|
||||
formData: {},
|
||||
showApprovalForm: false,
|
||||
|
||||
showImplementPicker: false,
|
||||
|
||||
supportCandidate: false,
|
||||
candidates: [],
|
||||
showCandidatesPicker: false,
|
||||
|
||||
filterOpened: false,
|
||||
approvalOptions: [
|
||||
{text: "未审核", value: "0"},
|
||||
{text: "已审核", value: "1"}
|
||||
],
|
||||
pendingListCache: null
|
||||
}
|
||||
},
|
||||
components: {
|
||||
"proposal-info": PROPOSAL_INFO,
|
||||
"transfer": transfer
|
||||
},
|
||||
methods: {
|
||||
onReady() {
|
||||
this.listSession()
|
||||
if (this.pendingListCache) {
|
||||
this.restoreListCache()
|
||||
this.listSession(true)
|
||||
return
|
||||
}
|
||||
this.listSession(false)
|
||||
},
|
||||
listSession() {
|
||||
|
||||
// 审核状态下拉框使用字符串值展示,查询接口使用布尔值区分已审核和未审核。
|
||||
approvalChange(value) {
|
||||
this.$set(this.pageForm, "approval", value === "1")
|
||||
this.doSearch()
|
||||
},
|
||||
|
||||
// 缓存恢复时只加载届次选项,不覆盖离开列表前的筛选条件和列表快照。
|
||||
listSession(skipDefaultSearch) {
|
||||
this.$axios.post("/platform/proposal/common/listSession").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.sessionOptions = [
|
||||
{
|
||||
text: "全部届次",
|
||||
value: null
|
||||
}
|
||||
].concat(res.data.map((v) => ({text: v.fullName, value: v.id})))
|
||||
if (this.sessionOptions.length > 0) {
|
||||
this.pageForm.sessionId = this.sessionOptions[0].value
|
||||
this.doSearch()
|
||||
}
|
||||
this.sessionOptions = [{text: "全部届次", value: null}]
|
||||
.concat(res.data.map((item) => ({text: item.fullName, value: item.id})))
|
||||
if (skipDefaultSearch) return
|
||||
// 保留承办单位答复原有默认口径:首次进入查询全部届次。
|
||||
this.$set(this.pageForm, "sessionId", null)
|
||||
this.doSearch()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onView(row) {
|
||||
this.showApprovalForm = false
|
||||
this.$refs.proposalInfoRef.onOpen(row)
|
||||
onView(row, index) {
|
||||
this.saveListCache(row, index)
|
||||
this.$pjaxReplace("/platform/proposal/unitReply/info/h5?bizId=" + encodeURIComponent(row.id) + "&action=view")
|
||||
},
|
||||
|
||||
onApproval(row) {
|
||||
this.loadCandidates(row.taskId)
|
||||
onApproval(row, index) {
|
||||
this.saveListCache(row, index)
|
||||
const params = new URLSearchParams()
|
||||
params.set("bizId", row.id)
|
||||
params.set("action", "approval")
|
||||
params.set("taskId", row.taskId)
|
||||
params.set("taskKey", row.taskKey || "")
|
||||
params.set("taskName", row.taskName || "承办单位答复")
|
||||
params.set("underTakeName", row.underTakeName || "")
|
||||
params.set("underTakeIsMaster", row.underTakeIsMaster ? "1" : "0")
|
||||
this.$pjaxReplace("/platform/proposal/unitReply/info/h5?" + params.toString())
|
||||
},
|
||||
|
||||
this.showApprovalForm = true
|
||||
this.$refs.proposalInfoRef.onOpen(row)
|
||||
this.formData = {
|
||||
processTaskId: row.taskId,
|
||||
taskKey: row.taskKey,
|
||||
taskName: row.taskName,
|
||||
proposalId: row.id,
|
||||
underTakeName: row.underTakeName,
|
||||
underTakeIsMaster: row.underTakeIsMaster
|
||||
getListCacheKey() {
|
||||
return "proposal:unit-reply:h5:list-state"
|
||||
},
|
||||
|
||||
getListCacheTokenParam() {
|
||||
return "unitReplyListCacheToken"
|
||||
},
|
||||
|
||||
getCurrentListCacheToken() {
|
||||
return new URLSearchParams(window.location.search).get(this.getListCacheTokenParam()) || ""
|
||||
},
|
||||
|
||||
/**
|
||||
* 将承办单位答复列表缓存标识绑定到当前历史记录。
|
||||
* PJAX 返回时仅恢复当前历史记录对应的筛选条件、分页数据和滚动快照。
|
||||
*/
|
||||
bindListCacheToken(token) {
|
||||
const currentUrl = new URL(window.location.href)
|
||||
currentUrl.searchParams.set(this.getListCacheTokenParam(), token)
|
||||
const currentState = window.history.state ? {...window.history.state} : null
|
||||
if (currentState && currentState.url) {
|
||||
currentState.url = currentUrl.toString()
|
||||
}
|
||||
window.history.replaceState(currentState, document.title, currentUrl.toString())
|
||||
if ($.pjax && $.pjax.state) {
|
||||
$.pjax.state.url = currentUrl.toString()
|
||||
}
|
||||
},
|
||||
|
||||
// 加载候选人列表
|
||||
loadCandidates(taskId) {
|
||||
$.get("/flow/common/candidate", {taskId}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.candidates = res.data.candidates
|
||||
this.supportCandidate = res.data.support
|
||||
// 用户主动搜索、切换届次或审核状态时使旧返回缓存失效。
|
||||
clearListCache() {
|
||||
const token = this.getCurrentListCacheToken()
|
||||
if (!token) return
|
||||
const currentUrl = new URL(window.location.href)
|
||||
currentUrl.searchParams.delete(this.getListCacheTokenParam())
|
||||
const currentState = window.history.state ? {...window.history.state} : null
|
||||
if (currentState && currentState.url) {
|
||||
currentState.url = currentUrl.toString()
|
||||
}
|
||||
window.history.replaceState(currentState, document.title, currentUrl.toString())
|
||||
if ($.pjax && $.pjax.state) {
|
||||
$.pjax.state.url = currentUrl.toString()
|
||||
}
|
||||
sessionStorage.removeItem(this.getListCacheKey())
|
||||
},
|
||||
|
||||
// platform_h5 的实际滚动区域是 page-wrapper,列表位置必须基于该容器保存和恢复。
|
||||
getListScrollContainer() {
|
||||
return document.querySelector(".page-wrapper") || document.scrollingElement || document.documentElement
|
||||
},
|
||||
|
||||
/**
|
||||
* 进入查看或答复详情前保存列表现场。
|
||||
* 承办答复可能一案多任务,因此 row 和相邻锚点均使用唯一 taskId,不能使用重复的提案 id。
|
||||
*/
|
||||
saveListCache(row, index) {
|
||||
const tableList = this.$refs.tableListRef
|
||||
if (!tableList) return
|
||||
const cacheToken = Date.now() + "-" + Math.random().toString(36).slice(2)
|
||||
const tableState = typeof tableList.getListState === "function"
|
||||
? tableList.getListState()
|
||||
: {
|
||||
tableData: JSON.parse(JSON.stringify(tableList.tableData || [])),
|
||||
localPageForm: JSON.parse(JSON.stringify(tableList.localPageForm || this.pageForm)),
|
||||
tableFinished: !!tableList.tableFinished
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onImplementConfirm(value) {
|
||||
this.$set(this.formData, "tf_implementStateName", value.text)
|
||||
this.$set(this.formData, "tf_implementState", value.value)
|
||||
this.showImplementPicker = false
|
||||
},
|
||||
|
||||
onCandidatesConfirm(value) {
|
||||
this.$set(this.formData, "tf_nextNodeOperatorName", value.text)
|
||||
this.$set(this.formData, "tf_nextNodeOperator", value.value)
|
||||
this.showCandidatesPicker = false
|
||||
},
|
||||
|
||||
async handleTaskAction(val) {
|
||||
const cards = document.querySelectorAll(".proposal-unit-reply-list .table-list-item")
|
||||
const currentCard = cards[index]
|
||||
const scrollContainer = this.getListScrollContainer()
|
||||
const cache = {
|
||||
time: Date.now(),
|
||||
token: cacheToken,
|
||||
pageForm: JSON.parse(JSON.stringify(this.pageForm)),
|
||||
tableState: tableState,
|
||||
completedTaskId: "",
|
||||
anchor: {
|
||||
taskId: row.taskId,
|
||||
previousTaskId: index > 0 && tableState.tableData[index - 1]
|
||||
? tableState.tableData[index - 1].taskId
|
||||
: "",
|
||||
nextTaskId: tableState.tableData[index + 1] ? tableState.tableData[index + 1].taskId : "",
|
||||
viewportTop: currentCard ? currentCard.getBoundingClientRect().top : 0,
|
||||
scrollTop: scrollContainer ? scrollContainer.scrollTop : 0
|
||||
}
|
||||
}
|
||||
try {
|
||||
await this.$refs.formRef.validate();
|
||||
this.$dialog.confirm({
|
||||
title: "提示",
|
||||
message: "您确定要提交吗?"
|
||||
}).then(() => {
|
||||
delete this.formData.underTakeIsMaster
|
||||
delete this.formData.underTakeName
|
||||
this.$axios.post("/flow/common/executeTask", {
|
||||
data: JSON.stringify({
|
||||
...this.formData,
|
||||
submitType: val
|
||||
})
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$refs.proposalInfoRef.onClose()
|
||||
this.$toast.success(res.msg)
|
||||
this.doSearch()
|
||||
}
|
||||
})
|
||||
}).catch(() => {
|
||||
|
||||
})
|
||||
sessionStorage.setItem(this.getListCacheKey(), JSON.stringify(cache))
|
||||
this.bindListCacheToken(cacheToken)
|
||||
} catch (error) {
|
||||
|
||||
sessionStorage.removeItem(this.getListCacheKey())
|
||||
}
|
||||
},
|
||||
|
||||
// 仅恢复当前历史记录对应且十分钟内有效的承办单位答复列表缓存。
|
||||
prepareListCache() {
|
||||
try {
|
||||
const cacheToken = this.getCurrentListCacheToken()
|
||||
if (!cacheToken) return
|
||||
const cacheText = sessionStorage.getItem(this.getListCacheKey())
|
||||
if (!cacheText) return
|
||||
const cache = JSON.parse(cacheText)
|
||||
if (!cache || cache.token !== cacheToken || !cache.tableState) return
|
||||
if (Date.now() - cache.time > 10 * 60 * 1000) {
|
||||
this.clearListCache()
|
||||
return
|
||||
}
|
||||
Object.keys(cache.pageForm || {}).forEach((key) => {
|
||||
this.$set(this.pageForm, key, cache.pageForm[key])
|
||||
})
|
||||
this.$set(this, "pendingListCache", cache)
|
||||
} catch (error) {
|
||||
sessionStorage.removeItem(this.getListCacheKey())
|
||||
}
|
||||
},
|
||||
|
||||
// 普通查看返回恢复旧快照;答复完成返回重新查询,避免未审核列表继续显示已处理任务。
|
||||
restoreListCache() {
|
||||
const cache = this.pendingListCache
|
||||
const tableList = this.$refs.tableListRef
|
||||
if (!cache || !tableList) return
|
||||
const anchor = cache.anchor || {}
|
||||
if (cache.completedTaskId) {
|
||||
this.refreshCompletedListCache(cache)
|
||||
return
|
||||
}
|
||||
this.applyListCache(cache.tableState, anchor.taskId, anchor)
|
||||
},
|
||||
|
||||
/**
|
||||
* 按离开前已加载页数重新获取答复后的列表。
|
||||
* 返回值转换为 TableList 状态,并优先定位下一任务,没有下一任务时定位上一任务。
|
||||
*/
|
||||
refreshCompletedListCache(cache) {
|
||||
const originalPageSize = Number(cache.pageForm.pageSize || 10)
|
||||
const loadedCount = Math.max(cache.tableState.tableData.length, originalPageSize)
|
||||
const loadedPageCount = Math.max(1, Math.ceil(loadedCount / originalPageSize))
|
||||
const requestForm = {
|
||||
...cache.pageForm,
|
||||
pageNumber: 1,
|
||||
pageSize: loadedPageCount * originalPageSize
|
||||
}
|
||||
const loading = createListLoading()
|
||||
this.$axios.post("/platform/proposal/unitReply/pageData", requestForm, {h5SkeletonLoading: true})
|
||||
.then((res) => {
|
||||
if (res.code !== 0) {
|
||||
this.applyCompletedCacheFallback(cache)
|
||||
return
|
||||
}
|
||||
const freshState = {
|
||||
tableData: res.data.list || [],
|
||||
localPageForm: {
|
||||
...cache.pageForm,
|
||||
pageNumber: loadedPageCount,
|
||||
pageSize: originalPageSize,
|
||||
totalCount: res.data.totalCount
|
||||
},
|
||||
tableFinished: (res.data.list || []).length >= res.data.totalCount
|
||||
}
|
||||
const anchor = cache.anchor || {}
|
||||
this.applyListCache(freshState, anchor.nextTaskId || anchor.previousTaskId, anchor)
|
||||
})
|
||||
.catch(() => {
|
||||
this.applyCompletedCacheFallback(cache)
|
||||
})
|
||||
.finally(() => {
|
||||
loading.close()
|
||||
})
|
||||
},
|
||||
|
||||
// 答复后刷新失败时只处理已完成 taskId,避免误删同一提案下的其他承办任务。
|
||||
applyCompletedCacheFallback(cache) {
|
||||
const tableState = cache.tableState
|
||||
const completedIndex = tableState.tableData.findIndex((item) => String(item.taskId) === String(cache.completedTaskId))
|
||||
if (completedIndex >= 0 && cache.pageForm.approval === false) {
|
||||
tableState.tableData.splice(completedIndex, 1)
|
||||
if (tableState.localPageForm && tableState.localPageForm.totalCount > 0) {
|
||||
tableState.localPageForm.totalCount--
|
||||
}
|
||||
} else if (completedIndex >= 0) {
|
||||
this.$set(tableState.tableData[completedIndex], "taskState", 20)
|
||||
}
|
||||
const anchor = cache.anchor || {}
|
||||
this.applyListCache(tableState, anchor.nextTaskId || anchor.previousTaskId, anchor)
|
||||
},
|
||||
|
||||
/**
|
||||
* 恢复 TableList 数据并将目标任务卡片放回离开页面前的视口位置。
|
||||
* sticky 吸顶会改变一次布局,因此首次滚动后再执行一次位置校正。
|
||||
*/
|
||||
applyListCache(tableState, targetTaskId, anchor) {
|
||||
const tableList = this.$refs.tableListRef
|
||||
if (!tableList) return
|
||||
if (typeof tableList.restoreListState === "function") {
|
||||
if (!tableList.restoreListState(tableState)) return
|
||||
} else {
|
||||
this.$set(tableList, "tableData", tableState.tableData || [])
|
||||
this.$set(tableList, "localPageForm", {...tableList.localPageForm, ...(tableState.localPageForm || {})})
|
||||
this.$set(tableList, "tableFinished", !!tableState.tableFinished)
|
||||
this.$set(tableList, "tableLoading", false)
|
||||
this.$set(tableList, "tableRefreshing", false)
|
||||
tableList.$emit("update:page_form", {...tableList.localPageForm})
|
||||
}
|
||||
this.$set(this, "pendingListCache", null)
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
const cards = Array.from(document.querySelectorAll(".proposal-unit-reply-list .table-list-item"))
|
||||
const scrollContainer = this.getListScrollContainer()
|
||||
if (!scrollContainer) return
|
||||
const candidateTaskIds = [targetTaskId, anchor.nextTaskId, anchor.previousTaskId]
|
||||
.filter((taskId) => taskId)
|
||||
.map((taskId) => String(taskId))
|
||||
.filter((taskId, index, values) => values.indexOf(taskId) === index)
|
||||
let targetIndex = -1
|
||||
candidateTaskIds.some((candidateTaskId) => {
|
||||
targetIndex = (tableState.tableData || [])
|
||||
.findIndex((item) => String(item.taskId) === candidateTaskId)
|
||||
return targetIndex >= 0
|
||||
})
|
||||
const targetCard = targetIndex >= 0 ? cards[targetIndex] : null
|
||||
if (targetCard) {
|
||||
const restoreTargetCard = () => {
|
||||
if (!targetCard.isConnected) return
|
||||
const targetTop = scrollContainer.scrollTop
|
||||
+ targetCard.getBoundingClientRect().top
|
||||
- Number(anchor.viewportTop || 0)
|
||||
scrollContainer.scrollTo(0, Math.max(0, targetTop))
|
||||
}
|
||||
restoreTargetCard()
|
||||
setTimeout(restoreTargetCard, 100)
|
||||
return
|
||||
}
|
||||
scrollContainer.scrollTo(0, Number(anchor.scrollTop || 0))
|
||||
}, 0)
|
||||
})
|
||||
},
|
||||
|
||||
onRevoke(row) {
|
||||
this.$dialog.confirm({
|
||||
title: "提示",
|
||||
@@ -269,18 +659,31 @@ layout("/layouts/platform_h5.html"){
|
||||
})
|
||||
},
|
||||
|
||||
// 打开转办
|
||||
openTransfer(row){
|
||||
// 打开转办。
|
||||
openTransfer(row) {
|
||||
this.$refs.transferRef.openTransfer(row)
|
||||
},
|
||||
|
||||
doSearch() {
|
||||
this.clearListCache()
|
||||
this.$nextTick(() => {
|
||||
this.pageForm.pageNumber = 1
|
||||
this.pageForm.totalCount = 0
|
||||
this.$refs.tableListRef.doSearch()
|
||||
})
|
||||
},
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.prepareListCache()
|
||||
if (this.pendingListCache) return
|
||||
const tab = new URLSearchParams(window.location.search).get("tab")
|
||||
if (tab === "done") {
|
||||
this.$set(this.pageForm, "approval", true)
|
||||
this.$set(this.pageForm, "approvalText", "1")
|
||||
} else if (tab === "todo") {
|
||||
this.$set(this.pageForm, "approval", false)
|
||||
this.$set(this.pageForm, "approvalText", "0")
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" :title="pageTitle" placeholder fixed></van-nav-bar>
|
||||
<proposal-info v-if="bizId" :biz-id="bizId">
|
||||
<div v-if="isApproval">
|
||||
<div class="process-title">{{formData.taskName}}</div>
|
||||
<van-form ref="formRef">
|
||||
<van-field :value="formData.underTakeName" label="承办单位" readonly></van-field>
|
||||
<van-field
|
||||
v-model="formData.tf_implementStateName"
|
||||
name="tf_implementStateName"
|
||||
label="落实情况"
|
||||
placeholder="请选择落实情况"
|
||||
:rules="[{ required: true, message: '请选择落实情况' }]"
|
||||
required
|
||||
is-link
|
||||
@click="showImplementPicker = true"
|
||||
></van-field>
|
||||
<van-popup v-model="showImplementPicker" position="bottom">
|
||||
<van-picker
|
||||
show-toolbar
|
||||
:columns="dict.type.PROPOSAL_REPLY_IMPLEMENT.map(item => ({text: item.label, value: item.code}))"
|
||||
@confirm="onImplementConfirm"
|
||||
@cancel="showImplementPicker = false"
|
||||
></van-picker>
|
||||
</van-popup>
|
||||
<van-field
|
||||
v-model="formData.tf_opinion"
|
||||
name="tf_opinion"
|
||||
label="答复内容"
|
||||
placeholder="请输入答复内容"
|
||||
:rules="[{ required: true, message: '请填写答复内容' }]"
|
||||
required
|
||||
maxlength="100"
|
||||
show-word-limit
|
||||
></van-field>
|
||||
<van-field
|
||||
v-if="supportCandidate && formData.underTakeIsMaster"
|
||||
v-model="formData.tf_nextNodeOperatorName"
|
||||
name="tf_nextNodeOperatorName"
|
||||
label="校领导"
|
||||
placeholder="请选择校领导"
|
||||
:rules="[{ required: true, message: '请选择校领导' }]"
|
||||
required
|
||||
is-link
|
||||
@click="showCandidatesPicker = true"
|
||||
></van-field>
|
||||
<van-popup v-model="showCandidatesPicker" position="bottom">
|
||||
<van-picker
|
||||
show-toolbar
|
||||
:columns="candidates.map(item => ({text: item.userName, value: item.userId}))"
|
||||
@confirm="onCandidatesConfirm"
|
||||
@cancel="showCandidatesPicker = false"
|
||||
></van-picker>
|
||||
</van-popup>
|
||||
</van-form>
|
||||
<div style="display: flex; justify-content: space-between; column-gap: 10px; padding: 10px">
|
||||
<van-button block :disabled="formLoading" @click="historyBack">取消</van-button>
|
||||
<van-button type="primary" block :loading="formLoading" @click="handleTaskAction">提交</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</proposal-info>
|
||||
</div>
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include("../../common/info.js"){}#-->
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
dicts: ["PROPOSAL_REPLY_IMPLEMENT"],
|
||||
components: {
|
||||
"proposal-info": PROPOSAL_INFO
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
bizId: "",
|
||||
action: "view",
|
||||
formData: {},
|
||||
formLoading: false,
|
||||
showImplementPicker: false,
|
||||
supportCandidate: false,
|
||||
candidates: [],
|
||||
showCandidatesPicker: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isApproval() {
|
||||
return this.action === "approval"
|
||||
},
|
||||
pageTitle() {
|
||||
return this.isApproval ? "承办单位答复" : "提案详情"
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* taskId 为当前 WF 任务主键;接口返回 candidates 候选人数组和 support 是否需要选择候选人。
|
||||
*/
|
||||
loadCandidates(taskId) {
|
||||
this.$axios.post("/flow/common/candidate", {taskId: taskId}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.candidates = res.data.candidates
|
||||
this.supportCandidate = res.data.support
|
||||
}
|
||||
})
|
||||
},
|
||||
onImplementConfirm(value) {
|
||||
this.$set(this.formData, "tf_implementStateName", value.text)
|
||||
this.$set(this.formData, "tf_implementState", value.value)
|
||||
this.showImplementPicker = false
|
||||
},
|
||||
onCandidatesConfirm(value) {
|
||||
this.$set(this.formData, "tf_nextNodeOperatorName", value.text)
|
||||
this.$set(this.formData, "tf_nextNodeOperator", value.value)
|
||||
this.showCandidatesPicker = false
|
||||
},
|
||||
/**
|
||||
* 提交承办单位答复;请求 data 包含任务、落实情况、答复内容及可选校领导,返回 Result。
|
||||
*/
|
||||
handleTaskAction() {
|
||||
this.$refs.formRef.validate()
|
||||
.then(() => this.$dialog.confirm({title: "提示", message: "您确定要提交吗?"}))
|
||||
.then(() => {
|
||||
const submitData = {...this.formData, submitType: 1}
|
||||
delete submitData.underTakeIsMaster
|
||||
delete submitData.underTakeName
|
||||
this.formLoading = true
|
||||
return this.$axios.post("/flow/common/executeTask", {data: JSON.stringify(submitData)})
|
||||
})
|
||||
.then((res) => {
|
||||
if (res && res.code === 0) {
|
||||
this.markListCacheCompleted()
|
||||
this.$toast.success(res.msg)
|
||||
this.historyBack()
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
this.formLoading = false
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 答复成功后记录已处理 WF 任务主键。
|
||||
* 返回列表时按原筛选和已加载范围刷新数据,只移除本次完成的承办任务。
|
||||
*/
|
||||
markListCacheCompleted() {
|
||||
const cacheKey = "proposal:unit-reply:h5:list-state"
|
||||
try {
|
||||
const cacheText = sessionStorage.getItem(cacheKey)
|
||||
if (!cacheText) return
|
||||
const cache = JSON.parse(cacheText)
|
||||
cache.completedTaskId = this.formData.processTaskId || ""
|
||||
cache.time = Date.now()
|
||||
sessionStorage.setItem(cacheKey, JSON.stringify(cache))
|
||||
} catch (error) {
|
||||
sessionStorage.removeItem(cacheKey)
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
// bizId 用于详情查询,其余参数用于恢复当前承办答复任务的表单上下文。
|
||||
this.bizId = params.get("bizId") || ""
|
||||
this.action = params.get("action") || "view"
|
||||
if (this.isApproval) {
|
||||
const taskId = params.get("taskId") || ""
|
||||
this.$set(this.formData, "processTaskId", taskId)
|
||||
this.$set(this.formData, "taskKey", params.get("taskKey") || "")
|
||||
this.$set(this.formData, "taskName", params.get("taskName") || "承办单位答复")
|
||||
this.$set(this.formData, "proposalId", this.bizId)
|
||||
this.$set(this.formData, "underTakeName", params.get("underTakeName") || "")
|
||||
this.$set(this.formData, "underTakeIsMaster", params.get("underTakeIsMaster") === "1")
|
||||
this.loadCandidates(taskId)
|
||||
}
|
||||
if (!this.bizId) {
|
||||
this.$toast.fail("缺少提案参数")
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
+1
-9
@@ -41,12 +41,8 @@ layout("/layouts/platform_h5.html"){
|
||||
</template>
|
||||
</table-list>
|
||||
|
||||
<proposal-info ref="proposalInfoRef"></proposal-info>
|
||||
|
||||
</div>
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include("../../common/info.js"){}#-->
|
||||
|
||||
const vue = new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
@@ -67,9 +63,6 @@ layout("/layouts/platform_h5.html"){
|
||||
showApprovalForm: false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
"proposal-info": PROPOSAL_INFO
|
||||
},
|
||||
methods: {
|
||||
onReady() {
|
||||
this.listSession()
|
||||
@@ -92,8 +85,7 @@ layout("/layouts/platform_h5.html"){
|
||||
},
|
||||
|
||||
onView(row) {
|
||||
this.showApprovalForm = false
|
||||
this.$refs.proposalInfoRef.onOpen(row)
|
||||
this.$pjaxReplace("/platform/proposal/vice/delegation/info/h5?bizId=" + encodeURIComponent(row.id))
|
||||
},
|
||||
|
||||
doSearch() {
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="提案详情" placeholder fixed></van-nav-bar>
|
||||
<proposal-info v-if="bizId" :biz-id="bizId"></proposal-info>
|
||||
</div>
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include("../../common/info.js"){}#-->
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
components: {
|
||||
"proposal-info": PROPOSAL_INFO
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
bizId: ""
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// bizId 为提案主键,公共详情组件根据该参数返回提案详情对象和流程记录数组。
|
||||
this.bizId = new URLSearchParams(window.location.search).get("bizId") || ""
|
||||
if (!this.bizId) {
|
||||
this.$toast.fail("缺少提案参数")
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
+5
-5
@@ -409,7 +409,7 @@ layout("/layouts/platform_h5.html"){
|
||||
@click="setVisibleState('showSessionPicker', true)"
|
||||
:rules="[{ required: true, message: '请选择所属教代会' }]"
|
||||
></van-field>
|
||||
<van-popup v-model="showSessionPicker" position="bottom">
|
||||
<van-popup v-model="showSessionPicker" position="bottom" round get-container="#app">
|
||||
<van-picker
|
||||
show-toolbar
|
||||
:columns="sessionOptions.map(item => ({text: item.fullName, value: item.id}))"
|
||||
@@ -436,7 +436,7 @@ layout("/layouts/platform_h5.html"){
|
||||
@click="setVisibleState('showCommitteePicker', true)"
|
||||
:rules="[{ required: true, message: '请选择所属委员会' }]"
|
||||
></van-field>
|
||||
<van-popup v-model="showCommitteePicker" position="bottom">
|
||||
<van-popup v-model="showCommitteePicker" position="bottom" round get-container="#app">
|
||||
<van-picker
|
||||
show-toolbar
|
||||
:columns="committeeOptions.map(item => ({text: item.name, value: item.id}))"
|
||||
@@ -489,7 +489,7 @@ layout("/layouts/platform_h5.html"){
|
||||
:rules="[{ required: true, message: '请选择提案方式' }]"
|
||||
required
|
||||
></van-field>
|
||||
<van-popup v-model="showSourcePicker" position="bottom">
|
||||
<van-popup v-model="showSourcePicker" position="bottom" round get-container="#app">
|
||||
<van-picker
|
||||
show-toolbar
|
||||
:columns="sourceOptions.map(item => ({text: item.name, value: item.code}))"
|
||||
@@ -555,7 +555,7 @@ layout("/layouts/platform_h5.html"){
|
||||
</section>
|
||||
|
||||
<!-- 各选择器沿用原始数据源与回显逻辑。 -->
|
||||
<van-popup v-model="showTypePicker" position="bottom">
|
||||
<van-popup v-model="showTypePicker" position="bottom" round get-container="#app">
|
||||
<van-picker
|
||||
show-toolbar
|
||||
:columns="typeOptions.map(item => ({text: item.name, value: item.id}))"
|
||||
@@ -564,7 +564,7 @@ layout("/layouts/platform_h5.html"){
|
||||
></van-picker>
|
||||
</van-popup>
|
||||
|
||||
<van-popup v-model="showSuggestionUnitPicker" position="bottom" :style="{ height: '70%' }">
|
||||
<van-popup v-model="showSuggestionUnitPicker" position="bottom" :style="{ height: '70%' }" round get-container="#app">
|
||||
<div class="suggestion-unit-picker">
|
||||
<div class="picker-toolbar">
|
||||
<button type="button" class="picker-cancel" @click="onSuggestionUnitCancel">取消</button>
|
||||
|
||||
Reference in New Issue
Block a user