Parcourir la source

新增任务管理菜单

duy il y a 1 an
Parent
commit
ffa965173b

+ 12 - 0
src/api/modules/other.js

@@ -115,3 +115,15 @@ export const getVersionJson = () => httpApi({
     method: 'get',
 }, false)
 
+//获取项目下的合同段信息
+export const getContractInfo = (form, msg = true) => httpApi({
+    url: '/api/blade-manager/contractInfo/get-contractInfo',
+    method: 'get',
+    params: form,
+}, msg)
+//检查当前审批人是否存在证书
+export const checkFlowUserIsExistPfxFile = (form, msg = true) => httpApi({
+    url: '/api/blade-business/eVisaTaskCheck/checkFlowUserIsExistPfxFile',
+    method: 'get',
+    params: form,
+}, msg)

+ 125 - 92
src/router/modules/base.js

@@ -1,155 +1,155 @@
-import Layout from "~src/layout/index.vue";
+import Layout from '~src/layout/index.vue'
 
 //路由菜单系统内置页面配置
 export default [
     {
         path: '/',
-        redirect: '/using/stats'
+        redirect: '/using/stats',
     },
     {
         path: '/login',
         name: 'login',
-        meta: {title: '登录'},
-        component: () => import('~src/views/login/index.vue')
+        meta: { title: '登录' },
+        component: () => import('~src/views/login/index.vue'),
     },
     {
         path: '/auth',
         name: 'auth',
-        meta: {title: '授权登录'},
-        component: () => import('~src/views/user/auth.vue')
+        meta: { title: '授权登录' },
+        component: () => import('~src/views/user/auth.vue'),
     },
     {
         path: '/home',
         name: 'home',
         redirect: '/home/index',
-        meta: {title: '数据看板'},
+        meta: { title: '数据看板' },
         component: Layout,
         children: [
             {
                 path: '/home/index',
                 name: 'home-index',
-                meta: {title: '数据看板'},
-                component: () => import('~src/views/home/index.vue')
-            }
+                meta: { title: '数据看板' },
+                component: () => import('~src/views/home/index.vue'),
+            },
         ],
     },
     {
         path: '/file',
         name: 'file',
         redirect: '/file/collection',
-        meta: {title: '文件收集'},
+        meta: { title: '文件收集' },
         component: Layout,
         children: [
             {
                 path: '/file/collection',
                 name: 'file-collection',
-                meta: {title: '在线收集'},
-                component: () => import('~src/views/file/collection.vue')
+                meta: { title: '在线收集' },
+                component: () => import('~src/views/file/collection.vue'),
             },
             {
                 path: '/file/records',
                 name: 'file-records',
-                meta: {title: '档案收集'},
-                component: () => import('~src/views/file/records.vue')
-            }
+                meta: { title: '档案收集' },
+                component: () => import('~src/views/file/records.vue'),
+            },
         ],
     },
     {
         path: '/archives',
         name: 'archives',
         redirect: '/archives/bookmark',
-        meta: {title: '档案管理'},
+        meta: { title: '档案管理' },
         component: Layout,
         children: [
             {
                 path: '/archives/bookmark',
                 name: 'archives-bookmark',
-                meta: {title: '档案著录'},
-                component: () => import('~src/views/archives/bookmark.vue')
+                meta: { title: '档案著录' },
+                component: () => import('~src/views/archives/bookmark.vue'),
             },
             {
                 path: '/archives/rolling',
                 name: 'archives-rolling',
-                meta: {title: '自动立卷'},
-                component: () => import('~src/views/archives/rolling.vue')
+                meta: { title: '自动立卷' },
+                component: () => import('~src/views/archives/rolling.vue'),
             },
             {
                 path: '/archives/appraisal',
                 name: 'archives-appraisal',
-                meta: {title: '档案鉴定'},
-                component: () => import('~src/views/archives/appraisal.vue')
+                meta: { title: '档案鉴定' },
+                component: () => import('~src/views/archives/appraisal.vue'),
             },
             {
                 path: '/archives/meta-data',
                 name: 'archives-meta-data',
-                meta: {title: '元数据'},
-                component: () => import('~src/views/archives/meta-data.vue')
+                meta: { title: '元数据' },
+                component: () => import('~src/views/archives/meta-data.vue'),
             },
             {
                 path: '/archives/tuning',
                 name: 'archives-tuning',
-                meta: {title: '档案调整'},
-                component: () => import('~src/views/archives/tuning.vue')
-            }
+                meta: { title: '档案调整' },
+                component: () => import('~src/views/archives/tuning.vue'),
+            },
         ],
     },
     {
         path: '/custody',
         name: 'custody',
         redirect: '/custody/backup',
-        meta: {title: '档案保管'},
+        meta: { title: '档案保管' },
         component: Layout,
         children: [
             {
                 path: '/custody/backup',
                 name: 'custody-backup',
-                meta: {title: '存储备份'},
-                component: () => import('~src/views/custody/backup.vue')
+                meta: { title: '存储备份' },
+                component: () => import('~src/views/custody/backup.vue'),
             },
             {
                 path: '/custody/early',
                 name: 'custody-early',
-                meta: {title: '档案预警'},
-                component: () => import('~src/views/custody/early.vue')
+                meta: { title: '档案预警' },
+                component: () => import('~src/views/custody/early.vue'),
             },
             {
                 path: '/custody/early-admin',
                 name: 'custody-early-admin',
-                meta: {title: '档案预警'},
-                component: () => import('~src/views/custody/early-admin.vue')
+                meta: { title: '档案预警' },
+                component: () => import('~src/views/custody/early-admin.vue'),
             },
             {
                 path: '/custody/testing',
                 name: 'custody-testing',
-                meta: {title: '四性检测'},
-                component: () => import('~src/views/custody/testing.vue')
-            }
+                meta: { title: '四性检测' },
+                component: () => import('~src/views/custody/testing.vue'),
+            },
         ],
     },
     {
         path: '/using',
         name: 'using',
         redirect: '/using/stats',
-        meta: {title: '档案利用'},
+        meta: { title: '档案利用' },
         component: Layout,
         children: [
             {
                 path: '/using/stats',
                 name: 'using-stats',
-                meta: {title: '档案统计'},
-                component: () => import('~src/views/using/stats.vue')
+                meta: { title: '档案统计' },
+                component: () => import('~src/views/using/stats.vue'),
             },
             {
                 path: '/using/bim',
                 name: 'using-bim',
-                meta: {title: 'BIM查询'},
-                component: () => import('~src/views/using/bim.vue')
+                meta: { title: 'BIM查询' },
+                component: () => import('~src/views/using/bim.vue'),
             },
             {
                 path: '/using/query',
                 name: 'using-query',
-                meta: {title: '档案查询'},
-                component: () => import('~src/views/using/query.vue')
+                meta: { title: '档案查询' },
+                component: () => import('~src/views/using/query.vue'),
             },
         ],
     },
@@ -157,68 +157,101 @@ export default [
         path: '/transfer',
         name: 'transfer',
         redirect: '/transfer/inspects',
-        meta: {title: '档案移交'},
+        meta: { title: '档案移交' },
         component: Layout,
         children: [
             {
                 path: '/transfer/hand-over',
                 name: 'transfer-hand-over',
-                meta: {title: '档案移交'},
-                component: () => import('~src/views/transfer/hand-over.vue')
+                meta: { title: '档案移交' },
+                component: () => import('~src/views/transfer/hand-over.vue'),
             },
             {
                 path: '/transfer/inspects',
                 name: 'transfer-inspects',
-                meta: {title: '档案巡检'},
-                component: () => import('~src/views/transfer/inspects.vue')
+                meta: { title: '档案巡检' },
+                component: () => import('~src/views/transfer/inspects.vue'),
             },
             {
                 path: '/transfer/initial',
                 name: 'transfer-initial',
-                meta: {title: '档案初验'},
-                component: () => import('~src/views/transfer/initial.vue')
+                meta: { title: '档案初验' },
+                component: () => import('~src/views/transfer/initial.vue'),
             },
             {
                 path: '/transfer/initial-expert',
                 name: 'transfer-initial-expert',
-                meta: {title: '档案初验(专家)'},
-                component: () => import('~src/views/transfer/initial-expert.vue')
+                meta: { title: '档案初验(专家)' },
+                component: () => import('~src/views/transfer/initial-expert.vue'),
             },
             {
                 path: '/transfer/initial-submit-report',
                 name: 'transfer-initial-submit-report',
-                meta: {title: '提交验收报告'},
-                component: () => import('~src/views/transfer/submit-report.vue')
+                meta: { title: '提交验收报告' },
+                component: () => import('~src/views/transfer/submit-report.vue'),
             },
             {
                 path: '/transfer/entry-sampling',
                 name: 'transfer-entry-sampling',
-                meta: {title: '分配验收范围'},
-                component: () => import('~src/views/transfer/entry-sampling.vue')
+                meta: { title: '分配验收范围' },
+                component: () => import('~src/views/transfer/entry-sampling.vue'),
             },
             {
                 path: '/transfer/leader-sampling',
                 name: 'transfer-leader-sampling',
-                meta: {title: '专家组长抽检'},
-                component: () => import('~src/views/transfer/leader-sampling.vue')
+                meta: { title: '专家组长抽检' },
+                component: () => import('~src/views/transfer/leader-sampling.vue'),
             },
             {
                 path: '/transfer/preliminary-examination',
                 name: 'transfer-preliminary-examination',
-                meta: {title: '档案初验'},
-                component: () => import('~src/views/transfer/preliminary-examination.vue')
+                meta: { title: '档案初验' },
+                component: () => import('~src/views/transfer/preliminary-examination.vue'),
             },
             {
                 path: '/transfer/writing-conclusion',
                 name: 'transfer-writing-conclusion',
-                meta: {title: '编写结论'},
-                component: () => import('~src/views/transfer/writing-conclusion.vue')
+                meta: { title: '编写结论' },
+                component: () => import('~src/views/transfer/writing-conclusion.vue'),
             },
             {
                 path: '/transfer/write-report',
                 name: 'transfer-write-report',
-                meta: {title: '编写报告'},
-                component: () => import('~src/views/transfer/write-report.vue')
+                meta: { title: '编写报告' },
+                component: () => import('~src/views/transfer/write-report.vue'),
+            },
+        ],
+    },
+    {
+        path: '/tasks',
+        name: 'tasks',
+        redirect: '/tasks/data',
+        meta: { title: '任务管理' },
+        component: Layout,
+        children: [
+            {
+                path: '/tasks/hc-data',
+                name: 'tasks-data',
+                meta: { title: '待办任务、已办任务、任务查看' },
+                component: () => import('~src/views/tasks/hc-data.vue'),
+            },
+            {
+                path: '/tasks/flow',
+                name: 'tasks-flow',
+                meta: { title: '任务流程设置' },
+                component: () => import('~src/views/tasks/flow.vue'),
+            },
+            {
+                path: '/tasks/sign-admin',
+                name: 'sign-admin',
+                meta: { title: '电签管理员' },
+                component: () => import('~src/views/tasks/sign-admin.vue'),
+            },
+            {
+                path: '/tasks/message-data',
+                name: 'message-data',
+                meta: { title: '消息提醒' },
+                component: () => import('~src/views/tasks/message-data.vue'),
             },
         ],
     },
@@ -226,85 +259,85 @@ export default [
         path: '/config',
         name: 'config',
         redirect: '/config/theme',
-        meta: {title: '系统设置'},
+        meta: { title: '系统设置' },
         component: Layout,
         children: [
             {
                 path: '/config/theme',
                 name: 'config-theme',
-                meta: {title: '主题配置'},
-                component: () => import('~src/views/config/index.vue')
+                meta: { title: '主题配置' },
+                component: () => import('~src/views/config/index.vue'),
             },
             {
                 path: '/config/certified',
                 name: 'config-certified',
-                meta: {title: '认证管理'},
-                component: () => import('~src/views/config/certified.vue')
+                meta: { title: '认证管理' },
+                component: () => import('~src/views/config/certified.vue'),
             },
             {
                 path: '/config/parameter',
                 name: 'config-parameter',
-                meta: {title: '参数设置'},
-                component: () => import('~src/views/config/parameter.vue')
-            }
+                meta: { title: '参数设置' },
+                component: () => import('~src/views/config/parameter.vue'),
+            },
         ],
     },
     {
         path: '/other',
         name: 'other',
         redirect: '/other/order-index',
-        meta: {title: '其他页面'},
+        meta: { title: '其他页面' },
         component: Layout,
         children: [
             {
                 path: '/user/index',
                 name: 'user-index',
-                meta: {title: '个人中心'},
-                component: () => import('~src/views/user/index.vue')
+                meta: { title: '个人中心' },
+                component: () => import('~src/views/user/index.vue'),
             },
             {
                 path: '/user/project',
                 name: 'user-project',
-                meta: {title: '项目档案汇总'},
-                component: () => import('~src/views/user/project.vue')
-            }
+                meta: { title: '项目档案汇总' },
+                component: () => import('~src/views/user/project.vue'),
+            },
         ],
     },
     {
         path: '/hc-test',
         name: 'hc-test',
         redirect: '/test/index',
-        meta: {title: '测试页面'},
+        meta: { title: '测试页面' },
         component: Layout,
         children: [
             {
                 path: '/test/index',
                 name: 'test-index',
-                meta: {title: '测试'},
-                component: () => import('~src/test/index.vue')
-            }
+                meta: { title: '测试' },
+                component: () => import('~src/test/index.vue'),
+            },
         ],
     },
     {
         path: '/403',
         name: '403',
-        meta: {title: '403'},
-        component: () => import('~src/views/error/403.vue')
+        meta: { title: '403' },
+        component: () => import('~src/views/error/403.vue'),
     },
     {
         path: '/404',
         name: '404',
-        meta: {title: '404'},
-        component: () => import('~src/views/error/404.vue')
+        meta: { title: '404' },
+        component: () => import('~src/views/error/404.vue'),
     },
     {
         path: '/500',
         name: '500',
-        meta: {title: '500'},
-        component: () => import('~src/views/error/500.vue')
+        meta: { title: '500' },
+        component: () => import('~src/views/error/500.vue'),
     },
     {
         path: '/:path(.*)*',
-        redirect: '/404'
-    }
+        redirect: '/404',
+    },
 ]

+ 68 - 0
src/styles/tasks/hc-data.scss

@@ -0,0 +1,68 @@
+.hc-layout-box {
+    position: relative;
+    height: 100%;
+    .hc-content-box {
+        position: relative;
+        height: calc(100% - 45px);
+    }
+}
+
+.obj-item-cell {
+    position: relative;
+    display: flex;
+    align-items: center;
+    margin-top: 24px;
+    .label {
+        margin-right: 20px;
+        width: 126px;
+    }
+}
+
+.hc-card-body-flex {
+    position: relative;
+    display: flex;
+    width: 100%;
+    height: 100%;
+    .flex-iframe {
+        flex: 1;
+        position: relative;
+        height: 100%;
+        border:1px solid #777;
+        overflow: hidden;
+        iframe {
+            height: 100%;
+        }
+        &.hc-no-table-form {
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            .table-form-no {
+                position: relative;
+                img {
+                    width: 350px;
+                }
+                .desc {
+                    text-align: center;
+                    font-size: 20px;
+                    color: #aaa;
+                }
+            }
+        }
+    }
+    .flex-table {
+        position: relative;
+        margin-left: 24px;
+        width: 40%;
+        height: 100%;
+        .data-table {
+            position: relative;
+            height: calc(100% - 125px);
+        }
+        .radio-group-box {
+            margin: 10px 0;
+        }
+        &.vh .data-table {
+            height: 100%;
+        }
+    }
+}

+ 18 - 0
src/styles/tasks/message.scss

@@ -0,0 +1,18 @@
+.hc-layout-box {
+    position: relative;
+    height: 100%;
+    display: flex;
+    .hc-layout-left-box {
+        width: 260px;
+        position: relative;
+        background: #f1f5f8;
+        border-radius: 10px;
+        box-shadow: -2px 0 10px 0 rgba(32,37,50,0.03), 0 10px 21px 20px rgba(32,37,50,0.03);
+    }
+    .hc-layout-content-box {
+        flex: 1;
+        display: inline-grid;
+        position: relative;
+        margin-left: 24px;
+    }
+}

+ 359 - 0
src/views/tasks/components/TableCard.vue

@@ -0,0 +1,359 @@
+<template>
+    <HcCard>
+        <template #header>
+            <div class="w-32">
+                <el-select v-model="searchForm.taskType" block clearable placeholder="任务类型" size="large">
+                    <el-option v-for="item in tasksType" :label="item.dictValue" :value="item.dictKey" />
+                </el-select>
+            </div>
+            <div class="w-32 ml-3">
+                <el-select v-model="searchForm.taskStatus" block clearable placeholder="任务状态" size="large">
+                    <el-option v-for="item in tasksStatus" :label="item.dictValue" :value="item.dictKey" />
+                </el-select>
+            </div>
+            <div class="w-32 ml-3">
+                <el-select
+                    v-model="searchForm.contractId" block clearable placeholder="合同段" size="large"
+                    @change="ContractIdChange"
+                >
+                    <el-option v-for="item in contractList" :label="item.contractName" :value="item.id" />
+                </el-select>
+            </div>
+            <div class="w-32 ml-3">
+                <el-select v-model="searchForm.batch" block clearable placeholder="上报批次" size="large">
+                    <el-option v-for="item in reportBatch" :label="item.batch" :value="item.batch" />
+                </el-select>
+            </div>
+            <div class="w-64 ml-3">
+                <HcDatePicker :dates="betweenTime" clearable size="large" @change="betweenTimeUpdate" />
+            </div>
+            <div class="w-56 ml-3">
+                <el-input
+                    v-model="searchForm.queryValue" block clearable placeholder="请输入名称关键词检索"
+                    size="large" @keyup="keyUpEvent"
+                />
+            </div>
+            <div class="ml-2">
+                <el-button size="large" type="primary" @click="searchClick">
+                    <HcIcon name="search-2" />
+                    <span>搜索</span>
+                </el-button>
+            </div>
+        </template>
+        <template #extra>
+            <HcTooltip keys="tasks_data_set_sign_rules">
+                <el-button hc-btn @click="setSignRulesClick">
+                    <HcIcon name="settings" />
+                    <span>设置重签规则</span>
+                </el-button>
+            </HcTooltip>
+            <HcTooltip v-if="isTableKey === 'key1'" keys="tasks_data_batch_review">
+                <el-button
+                    :disabled="tableCheckedKeys.length <= 0" hc-btn type="primary"
+                    @click="batchApprovalTaskClick"
+                >
+                    <HcIcon name="check-double" />
+                    <span>批量审批</span>
+                </el-button>
+            </HcTooltip>
+        </template>
+        <HcTable
+            ref="tableListRef" :column="tableListColumn" :datas="tableListData" :loading="tableLoading" is-check
+            @selection-change="tableSelectionChange"
+        >
+            <template #taskName="{ row }">
+                <span class="text-link" @click="rowTaskName(row)">{{ row?.taskName }}</span>
+            </template>
+            <template #taskStatus="{ row }">
+                <el-tag
+                    v-if="row?.taskStatus?.statusValue"
+                    :type="`${row.taskStatus.status === 2 ? 'success' : row.taskStatus.status === 3 ? 'warning' : 'info'}`" class="mx-1" effect="dark"
+                >
+                    {{ row.taskStatus.statusValue }}
+                </el-tag>
+            </template>
+            <template #waitingUserList="{ row }">
+                <template v-for="item in row.waitingUserList">
+                    <el-tag
+                        v-if="item.waitingUserName"
+                        :type="`${item.status === 2 ? 'success' : item.status === 3 ? 'warning' : item.status === 999 ? 'danger' : 'info'}`" class="mx-1" effect="dark"
+                    >
+                        {{ item.waitingUserName }}
+                    </el-tag>
+                </template>
+            </template>
+        </HcTable>
+        <template #action>
+            <div class="lr-dialog-footer">
+                <div class="left">
+                    <span class="text-success">审批人员中:</span>
+                    <el-tag class="mx-1" effect="dark" type="info">未签字</el-tag>
+                    <el-tag class="mx-1" effect="dark" type="success">已签字</el-tag>
+                    <el-tag class="mx-1" effect="dark" type="warning">已废除</el-tag>
+                    <el-tag class="mx-1" effect="dark" type="danger">签字异常</el-tag>
+                </div>
+                <div class="right">
+                    <HcPages :pages="searchForm" :sizes="[10, 20, 30, 40, 50, 200]" @change="pageChange" />
+                </div>
+            </div>
+        </template>
+    </HcCard>
+</template>
+
+<script setup>
+import { nextTick, ref, watch } from 'vue'
+import { getArrValue } from 'js-fast-way'
+import tasksApi from '~api/tasks/data'
+import { useAppStore } from '~src/store'
+//参数
+const props = defineProps({
+    projectId: {
+        type: [String, Number],
+        default: '',
+    },
+    contractId: {
+        type: [String, Number],
+        default: '',
+    },
+    contractList: {
+        type: Array,
+        default: () => ([]),
+    },
+    tableKey: {
+        type: String,
+        default: '',
+    },
+})
+//事件
+const emit = defineEmits(['rowTaskName', 'signRules', 'batchApproval'])
+//初始变量
+const useAppState = useAppStore()
+//变量
+const projectId = ref(props.projectId)
+const contractId = ref(props.contractId)
+const isTableKey = ref(props.tableKey)
+const currentContractId = ref(useAppState.getContractId)
+
+//监听
+watch(() => [
+    props.tableKey,
+], ([Key]) => {
+    isTableKey.value = Key
+    setQueryData()
+})
+
+//渲染完成
+nextTick(() => {
+    setQueryData()
+})
+
+//获取相关数据
+const setQueryData = () => {
+    searchForm.value.contractId = contractId.value
+    queryTaskType()
+    queryTaskStatus()
+    searchClick()
+    queryBatchList()
+}
+
+//获取任务类型
+const tasksType = ref([])
+const queryTaskType = async () => {
+    const { error, code, data } = await tasksApi.queryTaskTypeStatus({
+        typeOrStatus: 'task_type',
+    })
+    if (!error && code === 200) {
+        tasksType.value = getArrValue(data)
+    } else {
+        tasksType.value = []
+    }
+}
+
+//获取任务状态
+const tasksStatus = ref([])
+const queryTaskStatus = async () => {
+    const { error, code, data } = await tasksApi.queryTaskTypeStatus({
+        typeOrStatus: 'task_status',
+    })
+    if (!error && code === 200) {
+        tasksStatus.value = getArrValue(data)
+    } else {
+        tasksStatus.value = []
+    }
+}
+
+//合同段
+const ContractIdChange = () => {
+    searchForm.value.batch = null
+    queryBatchList()
+    queryUserStartFlow()
+    getTableData()
+}
+
+//获取上报批次
+const reportBatch = ref([])
+const queryBatchList = async () => {
+    const { contractId } = searchForm.value
+    if (contractId) {
+        const { error, code, data } = await tasksApi.queryBatchList({
+            projectId: projectId.value,
+            contractId: contractId || '',
+        })
+        if (!error && code === 200) {
+            reportBatch.value = getArrValue(data)
+        } else {
+            reportBatch.value = []
+        }
+    } else {
+        reportBatch.value = []
+    }
+}
+
+//搜索表单
+const searchForm = ref({
+    queryValue: null, taskType: null, taskStatus: null, batch: null, startTime: null, endTime: null,
+    current: 1, size: 200, total: 0,
+})
+
+//日期时间被选择
+const betweenTime = ref(null)
+const betweenTimeUpdate = ({ val, arr }) => {
+    betweenTime.value = arr
+    searchForm.value.startTime = val['start']
+    searchForm.value.endTime = val['end']
+}
+
+//回车搜索
+const keyUpEvent = (e) => {
+    if (e.key === 'Enter') {
+        searchForm.value.current = 1
+        getTableData()
+    }
+}
+
+//搜索
+const searchClick = () => {
+    searchForm.value.current = 1
+    getTableData()
+}
+
+//分页被点击
+const pageChange = ({ current, size }) => {
+    searchForm.value.current = current
+    searchForm.value.size = size
+    getTableData()
+}
+
+//获取数据
+const tableLoading = ref(false)
+const tableListColumn = ref([
+    { key: 'taskName', name: '任务名称' },
+    { key: 'typeValue', name: '任务类型', width: '120' },
+    { key: 'taskStatus', name: '任务状态', width: '160' },
+    { key: 'startTime', name: '开始时间', width: '180' },
+    { key: 'endTime', name: '限定时间', width: '180' },
+    { key: 'taskContent', name: '任务描述' },
+    { key: 'reportUserName', name: '上报人', width: '120' },
+    { key: 'waitingUserList', name: '签字人员' },
+    { key: 'evisaContent', name: '电签状态' },
+])
+const tableListData = ref([])
+const getTableData = () => {
+    const key = isTableKey.value
+    tableListRef.value?.clearSelection()
+    tableCheckedKeys.value = []
+    if (key === 'key1') {
+        queryUserToDoTaskList()
+    } else if (key === 'key2') {
+        queryUserDoneTaskList()
+    } else if (key === 'key3') {
+        queryUserStartFlow()
+    }
+}
+
+//待办任务列表
+const queryUserToDoTaskList = async () => {
+    tableLoading.value = true
+    const { error, code, data } = await tasksApi.queryUserToDoTaskList({
+        ...searchForm.value,
+        projectId: projectId.value,
+        currentContractId:currentContractId.value,
+    })
+    //处理数据
+    tableLoading.value = false
+    if (!error && code === 200) {
+        tableListData.value = getArrValue(data['records'])
+        searchForm.value.total = data.total || 0
+    } else {
+        tableListData.value = []
+        searchForm.value.total = 0
+    }
+}
+//获取已办任务
+const queryUserDoneTaskList = async () => {
+    tableLoading.value = true
+    const { error, code, data } = await tasksApi.queryUserDoneTaskList({
+        ...searchForm.value,
+        projectId: projectId.value,
+    })
+    //处理数据
+    tableLoading.value = false
+    if (!error && code === 200) {
+        tableListData.value = getArrValue(data['records'])
+        searchForm.value.total = data.total || 0
+    } else {
+        tableListData.value = []
+        searchForm.value.total = 0
+    }
+}
+//获取我发起的
+const queryUserStartFlow = async () => {
+    tableLoading.value = true
+    const { error, code, data } = await tasksApi.queryUserStartFlow({
+        ...searchForm.value,
+        projectId: projectId.value,
+    })
+    //处理数据
+    tableLoading.value = false
+    if (!error && code === 200) {
+        tableListData.value = getArrValue(data?.records) || []
+        searchForm.value.total = data?.total || 0
+    } else {
+        tableListData.value = []
+        searchForm.value.total = 0
+    }
+}
+
+//多选
+const tableListRef = ref(null)
+const tableCheckedKeys = ref([])
+const tableSelectionChange = (rows) => {
+    tableCheckedKeys.value = rows.filter((item) => {
+        return (item ?? '') !== ''
+    })
+}
+
+//任务审核
+const rowTaskName = (row) => {
+    emit('rowTaskName', row)
+}
+
+//设置重签规则
+const setSignRulesClick = () => {
+    emit('signRules', {})
+}
+
+//批量审批
+const batchApprovalTaskClick = () => {
+    const rows = tableCheckedKeys.value
+    if (rows.length > 0) {
+        emit('batchApproval', rows)
+    } else {
+        window?.$message?.warning('请先勾选需要审批的数据')
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 234 - 0
src/views/tasks/flow.vue

@@ -0,0 +1,234 @@
+<template>
+    <div class="hc-layout-box">
+        <HcCard :scrollbar="false" action-size="lg">
+            <template #header>
+                <HcTooltip keys="tasks_flow_add">
+                    <el-button hc-btn type="primary" @click="addFlowData">
+                        <HcIcon name="add-circle" />
+                        <span>新建流程</span>
+                    </el-button>
+                </HcTooltip>
+            </template>
+            <template #extra>
+                <el-alert
+                    :closable="false"
+                    title="同一合同段内,只需要设置重复岗位的流程即可,其他任务岗位,系统将自动推送,无需创建更多任务流" type="error"
+                />
+            </template>
+            <HcTable :column="tableListColumn" :datas="tableListData" :loading="tableLoading">
+                <template #action="{ row }">
+                    <HcTooltip keys="tasks_flow_edit">
+                        <el-button plain size="small" type="primary" @click="handleTableEdit(row)">编辑</el-button>
+                    </HcTooltip>
+                    <HcTooltip keys="tasks_flow_del">
+                        <el-button plain size="small" type="danger" @click="handleTableDel(row)">删除</el-button>
+                    </HcTooltip>
+                </template>
+            </HcTable>
+            <template #action>
+                <HcPages :pages="searchForm" @change="pageChange" />
+            </template>
+        </HcCard>
+        <!-- 新增/编辑流程 弹框 -->
+        <el-dialog
+            v-model="showEditModal" :title="`${flowFormData.id ? '编辑' : '新增'}流程`" class="hc-modal-border"
+            width="47rem"
+        >
+            <el-form ref="formFlowRef" :model="flowFormData" :rules="formFlowRules" label-width="auto" size="large">
+                <el-form-item label="流程名称" prop="fixedFlowName">
+                    <el-input v-model="flowFormData.fixedFlowName" placeholder="请输入流程名称" />
+                </el-form-item>
+                <el-form-item label="任务人" prop="linkUserJoinString">
+                    <HcTasksUser
+                        :contract-id="contractId" :project-id="projectId" :users="flowFormData.linkUserJoinString"
+                        ui="w-full" @change="tasksUserChange"
+                    />
+                </el-form-item>
+            </el-form>
+            <template #footer>
+                <div class="dialog-footer">
+                    <el-button size="large" @click="showEditModal = false">取消</el-button>
+                    <el-button :loading="sevaLoading" hc-btn type="primary" @click="saveFormClick">保存</el-button>
+                </div>
+            </template>
+        </el-dialog>
+    </div>
+</template>
+
+<script setup>
+import { onMounted, ref } from 'vue'
+import { useAppStore } from '~src/store'
+import { getArrValue, getObjValue } from 'js-fast-way'
+import tasksFlowApi from '~api/tasks/flow'
+import { delMessageV2 } from '~com/message/index.js'
+//变量
+const useAppState = useAppStore()
+const projectId = ref(useAppState.getProjectId)
+const contractId = ref(useAppState.getContractId)
+
+//渲染完成
+onMounted(() => {
+    getTableData()
+})
+
+//搜索表单
+const searchForm = ref({ current: 1, size: 20, total: 0 })
+
+//分页被点击
+const pageChange = ({ current, size }) => {
+    searchForm.value.current = current
+    searchForm.value.size = size
+    getTableData()
+}
+
+//获取数据
+const tableLoading = ref(false)
+const tableListData = ref([])
+
+const tableListColumn = ref([
+    { key: 'fixedFlowName', name: '流程名称' },
+    { key: 'linkUserJoinString', name: '流程详情' },
+    { key: 'action', name: '操作', width: '200', align: 'center' },
+])
+
+const getTableData = async () => {
+    tableLoading.value = true
+    const { error, code, data } = await tasksFlowApi.getPageData({
+        projectId: projectId.value,
+        contractId: contractId.value,
+        ...searchForm.value,
+    })
+    //处理数据
+    tableLoading.value = false
+    if (!error && code === 200) {
+        tableListData.value = getArrValue(data['records'])
+        searchForm.value.total = data.total || 0
+    } else {
+        tableListData.value = []
+        searchForm.value.total = 0
+    }
+}
+
+//新增编辑数据
+const showEditModal = ref(false)
+const formFlowRef = ref(null)
+const flowFormData = ref({ id: '', fixedFlowName: '', linkUserJoinString: '' })
+const formFlowRules = {
+    fixedFlowName: {
+        required: true,
+        trigger: 'blur',
+        message: '请输入流程名称',
+    },
+    linkUserJoinString: {
+        required: true,
+        trigger: 'blur',
+        message: '请选择任务人',
+    },
+}
+
+//任务人选择改变
+const tasksUserChange = (a, b, users) => {
+    flowFormData.value.linkUserJoinString = users
+}
+
+//新建流程
+const addFlowData = () => {
+    flowFormData.value = { id: '', fixedFlowName: '', linkUserJoinString: '' }
+    showEditModal.value = true
+}
+
+//编辑流程
+const handleTableEdit = async (row) => {
+    flowFormData.value = { id: '', fixedFlowName: '', linkUserJoinString: '' }
+    showEditModal.value = true
+    const { error, code, data } = await tasksFlowApi.queryFixedFlowDetail({
+        id: row?.id,
+    })
+    if (!error && code === 200) {
+        let users = '', res = getObjValue(data)
+        const list = getArrValue(res['fixedFlowLinkList'])
+        for (let i = 0; i < list.length; i++) {
+            const item = getObjValue(list[i])
+            if (users) {
+                users += `,${item['fixedFlowLinkUserName']}-${item['fixedFlowLinkUser']}`
+            } else {
+                users = `${item['fixedFlowLinkUserName']}-${item['fixedFlowLinkUser']}`
+            }
+        }
+        flowFormData.value = { id: res.id, fixedFlowName: res.fixedFlowName, linkUserJoinString: users }
+    } else {
+        flowFormData.value = { id: '', fixedFlowName: '', linkUserJoinString: '' }
+    }
+}
+
+//提交保存
+const sevaLoading = ref(false)
+const saveFormClick = async () => {
+    const form = flowFormData.value
+    if (!form.id) {
+        sevaLoading.value = true
+        const { error, code } = await tasksFlowApi.addFixedFlowData({
+            ...form,
+            projectId: projectId.value,
+            contractId: contractId.value,
+        })
+        //处理数据
+        sevaLoading.value = false
+        if (!error && code === 200) {
+            showEditModal.value = false
+            window?.$message?.success('保存成功')
+            getTableData()
+        }
+    } else {
+        sevaLoading.value = true
+        const { error, code } = await tasksFlowApi.updateFixedFlowData({
+            ...form,
+            projectId: projectId.value,
+            contractId: contractId.value,
+        })
+        //处理数据
+        sevaLoading.value = false
+        if (!error && code === 200) {
+            showEditModal.value = false
+            window?.$message?.success('保存成功')
+            getTableData()
+        }
+    }
+}
+
+//删除
+const handleTableDel = (row) => {
+    delMessageV2(async (action, instance, done) => {
+            if (action === 'confirm') {
+                instance.confirmButtonLoading = true
+                removeFixedFlowData(row)
+                instance.confirmButtonLoading = false
+                done()
+            } else {
+                done()
+            }
+    })
+}
+
+//确认删除
+const removeFixedFlowData = async (row) => {
+    const { error, code } = await tasksFlowApi.removeFixedFlowData({
+        ids: row?.id || '',
+        projectId: projectId.value,
+        contractId: contractId.value,
+    })
+    //处理数据
+    if (!error && code === 200) {
+        window.$message?.success('删除成功')
+        getTableData()
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+.hc-layout-box {
+    position: relative;
+    height: 100%;
+}
+</style>
+

+ 443 - 0
src/views/tasks/hc-data.vue

@@ -0,0 +1,443 @@
+<template>
+    <div class="hc-layout-box">
+        <HcTabsSimple :no-drop-shadow="showTaskReviewModal" :cur="sbTableKey" :datas="sbTableData" @tabClick="sbTableClick">
+            <template #tab-key1>
+                <TableCard
+                    v-if="sbTableKey === 'key1'" :contract-id="contractId"
+                    :contract-list="contractList"
+                    :project-id="projectId" :table-key="sbTableKey"
+                    @batchApproval="batchApprovalTaskClick" @rowTaskName="rowTaskName"
+                    @signRules="setSignRulesClick"
+                />
+            </template>
+            <template #tab-key2>
+                <TableCard
+                    v-if="sbTableKey === 'key2'" :contract-id="contractId"
+                    :contract-list="contractList"
+                    :project-id="projectId" :table-key="sbTableKey"
+                    @batchApproval="batchApprovalTaskClick" @rowTaskName="rowTaskName"
+                    @signRules="setSignRulesClick"
+                />
+            </template>
+            <template #tab-key3>
+                <TableCard
+                    v-if="sbTableKey === 'key3'" :contract-id="contractId"
+                    :contract-list="contractList"
+                    :project-id="projectId" :table-key="sbTableKey"
+                    @batchApproval="batchApprovalTaskClick" @rowTaskName="rowTaskName"
+                    @signRules="setSignRulesClick"
+                />
+            </template>
+        </HcTabsSimple>
+
+        <!-- 任务审核 -->
+        <el-dialog
+            v-model="showTaskReviewModal" class="hc-modal-border hc-modal-table" destroy-on-close draggable
+            width="80vw"
+        >
+            <template #header="{ titleId, titleClass }">
+                <div class="hc-card-header flex items-center">
+                    <div :id="titleId" :class="titleClass">
+                        任务审核 【已开启电签】
+                    </div>
+                    <div v-if="taskReviewType === '1'" class="ml-6 font-bold text-main">
+                        任务名称:{{ taskReviewInfo.taskName }}
+                    </div>
+                </div>
+            </template>
+            <div class="hc-card-body-flex">
+                <div v-if="batchPdfUrl" class="flex-iframe">
+                    <hc-pdf :src="batchPdfUrl" />
+                </div>
+                <div v-else class="flex-iframe hc-no-table-form">
+                    <div class="table-form-no">
+                        <img :src="notableform" alt="">
+                        <div class="desc">
+                            暂无 PDF 数据
+                        </div>
+                    </div>
+                </div>
+                <div :class="sbTableKey === 'key1' ? '' : 'vh'" class="flex-table">
+                    <div v-if="taskReviewType === '1'" class="data-table taskReviewData">
+                        <HcTable :column="taskReviewColumns" :datas="taskReviewData" @row-click="rowTaskReviewClick" />
+                    </div>
+                    <div v-if="taskReviewType === '2'" class="data-table checkedRowsRef">
+                        <HcTable :column="checkedRowsColumns" :datas="checkedRowsRef" @row-click="rowTaskReviewClick" />
+                    </div>
+                    <div v-if="sbTableKey === 'key1'" class="radio-group-box">
+                        <span class="label">审批操作:</span>
+                        <el-radio-group v-model="taskReviewForm.flag">
+                            <el-radio label="OK">
+                                同意
+                            </el-radio>
+                            <el-radio label="NO">
+                                废除任务
+                            </el-radio>
+                        </el-radio-group>
+                    </div>
+                    <div v-if="sbTableKey === 'key1'" class="textarea-box">
+                        <el-input
+                            v-model="taskReviewForm.comment" :autosize="{ minRows: 3, maxRows: 5 }"
+                            placeholder="请输入审核意见"
+                            type="textarea"
+                        />
+                    </div>
+                </div>
+            </div>
+            <template v-if="sbTableKey === 'key1'" #footer>
+                <div class="dialog-footer">
+                    <el-button size="large" @click="showTaskReviewModal = false">
+                        取消
+                    </el-button>
+                    <el-button :loading="SMSAuthLoading" hc-btn type="primary" @click="ConfirmApprovalClick">
+                        确认审批
+                    </el-button>
+                </div>
+            </template>
+        </el-dialog>
+
+        <!-- 设置重签规则 -->
+        <el-dialog
+            v-model="showSetSignRulesModal" class="hc-modal-border" destroy-on-close draggable
+            title="设置重签规则"
+            width="38rem"
+        >
+            <div class="text-orange mb-10">
+                <span
+                    class="mr-4"
+                >提示:设置默认时长,在任务被废除需要重签的时候,规定的重签上报时间提示时间段内,系统提示用户重签信息,但是超过处理时间,系统可默认自动授权重签</span>
+                <el-checkbox v-model="setPactVal" label="Option 1" size="large">
+                    <span class="text-main">《授权系统自动电签协议》</span>
+                </el-checkbox>
+            </div>
+            <div class="obj-item-cell">
+                <div class="label">
+                    默认处理时间(时)
+                </div>
+                <HcCounter v-model="formReport.date" @update:modelValue="dateUpdateValue" />
+            </div>
+            <div class="obj-item-cell">
+                <div class="label">
+                    开启系统自动电签
+                </div>
+                <el-switch v-model="formReport.active" />
+            </div>
+            <template #footer>
+                <div class="dialog-footer">
+                    <el-button size="large" @click="showSetSignRulesModal = false">
+                        取消
+                    </el-button>
+                    <el-button hc-btn type="primary">
+                        保存
+                    </el-button>
+                </div>
+            </template>
+        </el-dialog>
+
+        <!-- 短信认证 -->
+        <HcSmsAuth :loading="SMSAuthLoading" :show="SMSAuthShow" @cancel="SMSAuthCancel" @confirm="SMSAuthConfirm" />
+    </div>
+</template>
+
+<script setup>
+import { onMounted, ref } from 'vue'
+import { useAppStore } from '~src/store'
+import { useRoute, useRouter } from 'vue-router'
+import TableCard from './components/TableCard.vue'
+import notableform from '~src/assets/view/notableform.svg'
+import { checkFlowUserIsExistPfxFile, getContractInfo } from '~api/other'
+import { arrToKey, getArrValue, isString } from 'js-fast-way'
+import tasksApi from '~api/tasks/data'
+import dayjs from 'dayjs'
+
+//初始变量
+const router = useRouter()
+const useRoutes = useRoute()
+const useAppState = useAppStore()
+
+//路由参数
+const routerQuery = useRoutes?.query
+const activeName = routerQuery?.active || 'key1'
+
+//全局变量
+const projectId = ref(useAppState.getProjectId)
+const contractId = ref(useAppState.getContractId)
+const projectInfo = ref(useAppState.getProjectInfo)
+
+//渲染完成
+onMounted(() => {
+    checkSmsCode()
+    getContractInfoList()
+})
+//合同段信息
+const contractList = ref([])
+
+const getContractInfoList = async ()=>{
+    const { error, code, data } = await getContractInfo({
+            pid: projectId.value,
+     })
+     if (!error && code === 200) {
+        contractList.value = getArrValue(data)
+     } else {
+        contractList.value = []
+     }
+}
+//类型处理
+const sbTableKey = ref(activeName)
+const sbTableData = ref([
+    { icon: 'time', label: '待办任务', key: 'key1' },
+    { icon: 'calendar-check', label: '已办任务', key: 'key2' },
+    { icon: 'user-shared', label: '我发起的', key: 'key3' },
+])
+const sbTableClick = (key) => {
+    sbTableKey.value = key
+    router.push({
+        path: useRoutes.path,
+        query: { active: key },
+    })
+}
+
+//审批页详情
+const showTaskReviewModal = ref(false)
+const taskReviewType = ref('1')
+const taskReviewInfo = ref({})
+const taskReviewData = ref([])
+const batchPdfUrl = ref('')
+const taskReviewForm = ref({ flag: 'OK', comment: '' })
+const taskReviewColumns = ref([
+    { key: 'fileName', name: '文件名称' },
+])
+//任务审核
+const rowTaskName = async (row) => {
+    if (row.formDataId) {
+        taskReviewInfo.value = row
+        const { error, code, data } = await tasksApi.queryApprovalParameter({
+            parallelProcessInstanceId: row['parallelProcessInstanceId'],
+            formDataId: row.formDataId,
+            approvalType: row.approvalType,
+        })
+        if (!error && code === 200) {
+            const approvalFileList = getArrValue(data['approvalFileList'])
+            if (approvalFileList.length > 0 && approvalFileList[approvalFileList.length - 1].fileName === '') {
+                approvalFileList.pop()
+            }
+            taskReviewData.value = approvalFileList
+            if (approvalFileList.length > 0) {
+                batchPdfUrl.value = approvalFileList[0].fileUrl
+            }
+            taskReviewType.value = '1'
+            showTaskReviewModal.value = true
+        } else {
+            taskReviewData.value = []
+            batchPdfUrl.value = ''
+        }
+    } else {
+        taskReviewInfo.value = {}
+        taskReviewData.value = []
+        batchPdfUrl.value = ''
+        window?.$message?.warning('此数据异常')
+    }
+}
+
+//批量审批
+const checkedRowsColumns = ref([
+    { key: 'taskName', name: '任务名称' },
+])
+const checkedRowsRef = ref([])
+
+const batchApprovalTaskClick = (rows) => {
+    taskReviewType.value = '2'
+    showTaskReviewModal.value = true
+    checkedRowsRef.value = rows
+    let taskids = []
+    rows.forEach((item) => {
+        taskids.push(item.formDataId)
+    })
+    taskids = taskids.join()
+    queryTaskInfo(rows[0], taskids)
+}
+
+//行被点击
+const rowTaskReviewClick = async ({ row }) => {
+    const type = taskReviewType.value
+    if (type === '1') {
+        batchPdfUrl.value = row.fileUrl
+    } else if (row['hc_batchPdfUrl']) {
+        batchPdfUrl.value = row['hc_batchPdfUrl']
+    } else {
+        queryTaskInfo(row)
+    }
+}
+
+//获取PDF数据
+const queryTaskInfo = async (row, taskids) => {
+    const { error, code, data } = await tasksApi.queryTaskInfo({
+        // formDataId: row['formDataId'] || '',
+        formDataId: taskids && taskids.length > 0 ? taskids : row['formDataId'],
+        approvalType: row['approvalType'],
+    })
+    //处理数据
+    if (!error && code === 200) {
+        // const approvalFileList = getArrValue(data['approvalFileList'])
+        // if (approvalFileList.length > 0) {
+        //     batchPdfUrl.value = approvalFileList[0].fileUrl
+        //     row['hc_batchPdfUrl'] = approvalFileList[0].fileUrl
+        // } else {
+        //     batchPdfUrl.value = ''
+        //     row['hc_batchPdfUrl'] = ''
+        //     window?.$message?.warning('PDF获取异常')
+        // }
+        const alldata = getArrValue(data)
+        let approvalFileList = []
+        alldata.forEach((item) => {
+            let innerfilist = item?.approvalFileList
+            innerfilist.forEach((item1) => {
+                approvalFileList.push(item1)
+            })
+        })
+        if (approvalFileList.length > 0) {
+            batchPdfUrl.value = approvalFileList[0].fileUrl
+            row['hc_batchPdfUrl'] = approvalFileList[0].fileUrl
+        } else {
+            batchPdfUrl.value = ''
+            row['hc_batchPdfUrl'] = ''
+            window?.$message?.warning('PDF获取异常')
+        }
+    } else {
+        batchPdfUrl.value = ''
+        row['hc_batchPdfUrl'] = ''
+        window?.$message?.warning(data.msg || 'PDF异常')
+    }
+}
+
+//确认审批
+const ConfirmApprovalClick = async () => {
+    const formData = taskReviewForm.value
+    if (formData.flag === 'NO' && !formData.comment) {
+        window?.$message?.warning('请先输入审核意见')
+    } else {
+        SMSAuthLoading.value = true
+        const { error, code, msg, data } = await checkFlowUserIsExistPfxFile({}, false)
+        //判断数据
+        SMSAuthLoading.value = false
+        if (!error && code === 200 && data === true) {
+            const ShowAuth = isCheckSmsCodeTime()
+            SMSAuthShow.value = ShowAuth
+            //免短信验证
+            if (!ShowAuth) {
+                SMSAuthConfirm()
+            }
+        } else {
+            window.$message?.warning(msg)
+        }
+    }
+}
+
+//短信验证有效期
+const smsCodeTime = ref('')
+const checkSmsCode = async () => {
+    const { error, code, data } = await tasksApi.checkSmsCode()
+    //处理数据
+    if (!error && code === 200) {
+        smsCodeTime.value = isString(data) ? data : ''
+    } else {
+        smsCodeTime.value = ''
+    }
+}
+
+//验证短信有效期
+const isCheckSmsCodeTime = () => {
+    const smsTime = smsCodeTime.value
+    if (!smsTime) {
+        return true
+    } else {
+        const toDayTime = dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss')
+        return dayjs(smsTime).isBefore(toDayTime)
+    }
+}
+
+//短信验证
+const SMSAuthLoading = ref(false)
+const SMSAuthShow = ref(false)
+const SMSAuthConfirm = () => {
+    const type = taskReviewType.value
+    if (type === '1') {
+        saveCompleteApprovalTask()
+    } else {
+        batchCompleteApprovalTask()
+    }
+    checkSmsCode()
+}
+const SMSAuthCancel = () => {
+    SMSAuthShow.value = false
+}
+
+//单个审批
+const saveCompleteApprovalTask = async () => {
+    const DataInfo = taskReviewInfo.value
+    SMSAuthLoading.value = true
+    const { error, code } = await tasksApi.saveCompleteApprovalTask({
+        ...taskReviewForm.value,
+        taskId: DataInfo['taskId'] || '',
+        parallelProcessInstanceId: DataInfo['parallelProcessInstanceId'] || '',
+        formDataId: DataInfo['formDataId'] || '',
+        approvalType: DataInfo['approvalType'],
+    }, false)
+    //处理数据
+    SMSAuthLoading.value = false
+    if (!error && code === 200) {
+        SMSAuthShow.value = false
+        showTaskReviewModal.value = false
+        window?.$message?.success('审批成功')
+        setTimeout(() => {
+            window?.location?.reload() //刷新页面
+        }, 3000)
+    } else {
+        window?.$message?.warning('审批异常')
+    }
+}
+
+//批量审批
+const batchCompleteApprovalTask = async () => {
+    const rows = checkedRowsRef.value
+    SMSAuthLoading.value = true
+    let taskIds = arrToKey(rows, 'taskId', ',')
+    let approvalType = arrToKey(rows, 'approvalType', ',')
+    let formDataId = arrToKey(rows, 'formDataId', ',')
+    let parallelProcessInstanceIds = arrToKey(rows, 'parallelProcessInstanceId', ',')
+    const { error, code } = await tasksApi.batchCompleteApprovalTask({
+        ...taskReviewForm.value,
+        taskIds,
+        approvalType,
+        formDataId,
+        parallelProcessInstanceIds,
+    }, false)
+    //处理数据
+    SMSAuthLoading.value = false
+    if (!error && code === 200) {
+        SMSAuthShow.value = false
+        showTaskReviewModal.value = false
+        window?.$message?.success('审批成功')
+        setTimeout(() => {
+            window?.location?.reload() //刷新页面
+        }, 3000)
+    } else {
+        window?.$message?.warning('审批出错')
+    }
+}
+
+//设置重签规则
+const showSetSignRulesModal = ref(false)
+const setPactVal = ref(true)
+const formReport = ref({ date: 1, active: true })
+const setSignRulesClick = () => {
+    showSetSignRulesModal.value = true
+}
+const dateUpdateValue = (val) => {
+    formReport.value.date = val
+}
+</script>
+
+<style lang="scss" scoped>
+@import '../../styles/tasks/hc-data.scss';
+</style>

+ 318 - 0
src/views/tasks/message-data.vue

@@ -0,0 +1,318 @@
+<template>
+    <div class="hc-layout-box">
+        <div class="hc-layout-left-box">
+            <el-scrollbar>
+                <HcMenuSimple :datas="menuOptions" :keys="menuKey" @change="handleMenuValue" />
+            </el-scrollbar>
+        </div>
+        <div class="hc-layout-content-box">
+            <HcCard action-size="lg">
+                <template #header>
+                    <div class="w-32 ml-2">
+                        <el-select v-model="searchForm.smsType" clearable placeholder="消息类型" size="large">
+                            <el-option v-for="item in smsTypeData" :label="item.label" :value="item.value" />
+                        </el-select>
+                    </div>
+                    <div class="w-64 ml-2">
+                        <HcDatePicker :dates="betweenTime" clearable size="large" @change="betweenTimeUpdate" />
+                    </div>
+                    <div class="ml-2">
+                        <el-button size="large" type="primary" @click="searchClick">
+                            <HcIcon name="search-2" />
+                            <span>搜索</span>
+                        </el-button>
+                    </div>
+                </template>
+                <template #extra>
+                    <el-button :disabled="tableCheckedKeys.length <= 0" :loading="delLoading" hc-btn @click="delClick">
+                        <HcIcon name="delete-bin-3" />
+                        <span>删除消息</span>
+                    </el-button>
+                    <el-button
+                        :disabled="tableCheckedKeys.length <= 0" :loading="markReadLoading" hc-btn
+                        @click="markReadClick"
+                    >
+                        <HcIcon name="bookmark" />
+                        <span>标记已读</span>
+                    </el-button>
+                </template>
+                <HcTable
+                    ref="tableListRef" :column="tableListColumn" :datas="tableListData" :loading="tableLoading"
+                    is-check @selection-change="tableSelectionChange"
+                >
+                    <template #content="{ row }">
+                        <div class="text-link text-cut" @click="tableContent(row)">{{ row.content }}</div>
+                    </template>
+                </HcTable>
+                <template #action>
+                    <HcPages :pages="searchForm" @change="pageChange" />
+                </template>
+            </HcCard>
+        </div>
+        <!-- 日志内容 -->
+        <el-dialog v-model="operationContentModal" class="hc-modal-border" title="消息内容" width="38rem">
+            {{ operationContent }}
+        </el-dialog>
+    </div>
+</template>
+
+<script setup>
+import { onMounted, ref, watch } from 'vue'
+import { useAppStore } from '~src/store'
+import { useRoute, useRouter } from 'vue-router'
+import messageApi from '~api/tasks/message'
+import { arrToId, getArrValue } from 'js-fast-way'
+import website from '~src/config/index'
+import { delMessageV2 } from '~com/message/index.js'
+
+//消息数量
+const props = defineProps(
+    {
+        msgCount: {
+            type: Object,
+            default: () => ({
+                allCount: 0,
+                taskCount: 0,
+                messageCount: 0,
+                messageCount_1: 0,
+                messageCount_2: 0,
+                messageCount_3: 0,
+                messageCount_4: 0,
+                messageCount_5: 0,
+            }),
+        },
+    },
+)
+//变量
+const router = useRouter()
+const useRoutes = useRoute()
+const useAppState = useAppStore()
+const projectId = ref(useAppState.getProjectId)
+const contractId = ref(useAppState.getContractId)
+const userInfo = ref(useAppState.getUserInfo)
+
+//路由参数数据
+const routerQuery = useRoutes?.query
+let MenuType = routerQuery?.MenuType || '1'
+const menumsgCount = ref(props.msgCount)
+//监听
+//监听
+watch(() => [
+        props.msgCount,
+    ], ([val]) => {
+        menumsgCount.value = val
+        if (val) {
+            queryCurrentUserMessageCount()
+        }
+
+    },
+)
+//渲染完成
+onMounted(() => {
+    searchForm.value.type = MenuType
+    searchForm.value.current = 1
+    searchClick()
+})
+
+//左侧菜单
+const menuKey = ref(MenuType)
+const menuOptions = ref([
+    { key: '1', label: '任务催办', icon: 'alarm-warning', badge: 0 },
+    { key: '2', label: '监测预警', icon: 'eye', badge: 0 },
+    { key: '3', label: '废除通知', icon: 'delete-bin-3', badge: 0 },
+    { key: '4', label: '工单反馈', icon: 'question-answer', badge: 0 },
+    { key: '5', label: '系统消息', icon: 'chat-settings', badge: 0 },
+])
+
+const handleMenuValue = (item) => {
+    searchForm.value.type = item.key
+    searchForm.value.current = 1
+    menuKey.value = item.key
+    searchClick()
+    router.push({
+        path: useRoutes.path,
+        query: { MenuType: item.key },
+    })
+}
+
+//获取消息数量
+const queryCurrentUserMessageCount = async () => {
+    const typeArr = ['messageCount_1', 'messageCount_2', 'messageCount_3', 'messageCount_4', 'messageCount_5']
+    for (let i = 0; i < typeArr.length; i++) {
+        menuOptions.value[i].badge = menumsgCount.value[typeArr[i]] ?? 0
+    }
+}
+//消息类型
+const smsTypeData = ref([{ label: '已读消息', value: '1' }, { label: '未读消息', value: '2' }])
+
+//搜索和分页数据
+const searchForm = ref({
+    isRead: null, startTime: null, endTime: null, type: 1,
+    current: 1, size: 20, total: 0,
+})
+
+//日期时间被选择
+const betweenTime = ref(null)
+const betweenTimeUpdate = ({ val, arr }) => {
+    betweenTime.value = arr
+    if (val && val.length > 0) {
+        searchForm.value.startTime = val['start']
+        searchForm.value.endTime = val['end']
+    } else {
+        searchForm.value.startTime = null
+        searchForm.value.endTime = null
+    }
+}
+
+//搜索
+const searchClick = () => {
+    searchForm.value.current = 1
+    getTableData()
+}
+
+//分页被点击
+const pageChange = ({ current, size }) => {
+    searchForm.value.current = current
+    searchForm.value.size = size
+    getTableData()
+}
+
+//设置表头数据
+const tableListRef = ref(null)
+const tableListColumn = ref([
+    { key: 'typeValue', name: '类型', width: '120' },
+    { key: 'time', name: '日期时间', width: '180' },
+    { key: 'content', name: '内容' },
+])
+const tableListData = ref([])
+
+//获取数据
+const tableLoading = ref(false)
+const getTableData = async () => {
+    //处理初始数据
+    tableLoading.value = true
+    tableListData.value = []
+    tableListRef.value?.clearSelection()
+    tableCheckedKeys.value = []
+    //获取数据
+    const { error, code, data } = await messageApi.getPageData({
+        ...searchForm.value,
+        projectId: projectId.value,
+        contractId: contractId.value,
+    })
+    //处理返回数据
+    tableLoading.value = false
+    if (!error && code === 200) {
+        tableListData.value = getArrValue(data['records'])
+        searchForm.value.total = data['total'] ?? 0
+    } else {
+        tableListData.value = []
+        searchForm.value.total = 0
+    }
+}
+
+//多选
+const tableCheckedKeys = ref([])
+const tableSelectionChange = (rows) => {
+    tableCheckedKeys.value = rows.filter((item) => {
+        return (item ?? '') !== ''
+    })
+}
+
+//查看内容
+const operationContentModal = ref(false)
+const operationContent = ref('')
+const tableContent = (row) => {
+    operationContent.value = row['content'] ?? ''
+    operationContentModal.value = true
+    setMessageWarningReadApi(row['id'], false)
+}
+
+//删除消息
+const delClick = () => {
+    const rows = tableCheckedKeys.value
+    if (rows.length > 0) {
+        delMessageV2(async (action, instance, done) => {
+            if (action === 'confirm') {
+                instance.confirmButtonLoading = true
+                removeData(rows)
+                instance.confirmButtonLoading = false
+                done()
+            } else {
+                done()
+            }
+        })
+    } else {
+        window.$message?.warning('请先勾选要删除的消息')
+    }
+}
+
+//确认删除消息
+const delLoading = ref(false)
+const removeData = async (rows) => {
+    delLoading.value = true
+    const ids = arrToId(rows)
+    //请求数据
+    const { error, code } = await messageApi.removeData({
+        ids: ids,
+    }, false)
+    //处理返回数据
+    delLoading.value = false
+    if (!error && code === 200) {
+        tableCheckedKeys.value = []
+        window.$message?.success('消息删除成功')
+        queryCurrentUserMessageCount()
+        searchClick()
+    } else {
+        window.$message?.error('消息删除失败')
+    }
+}
+
+//标记已读
+const markReadClick = () => {
+    const rows = tableCheckedKeys.value
+    if (rows.length > 0) {
+        const ids = arrToId(rows)
+        window?.$messageBox?.alert('勾选的消息是否标记为已读?', '确认操作', {
+            showCancelButton: true,
+            confirmButtonText: '确定标记',
+            cancelButtonText: '取消',
+            callback: (action) => {
+                if (action === 'confirm') {
+                    setMessageWarningReadApi(ids)
+                }
+            },
+        })
+    } else {
+        window.$message?.warning('请先勾选要标记的消息')
+    }
+}
+
+//确认标记
+const markReadLoading = ref(false)
+const setMessageWarningReadApi = async (ids, getTable = true) => {
+    markReadLoading.value = true
+    //请求数据
+    const { error, code } = await messageApi.setMessageWarningRead({
+        ids: ids,
+    }, false)
+    //处理返回数据
+    markReadLoading.value = false
+    if (!error && code === 200) {
+        if (getTable) {
+            tableCheckedKeys.value = []
+            window.$message?.success('消息标记为已读成功')
+            queryCurrentUserMessageCount()
+            getTableData()
+        } else {
+            queryCurrentUserMessageCount()
+        }
+    } else {
+        window.$message?.error('消息标记为已读失败')
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+@import "../../styles/tasks/message.scss";
+</style>

+ 207 - 0
src/views/tasks/sign-admin.vue

@@ -0,0 +1,207 @@
+<template>
+    <div class="hc-layout-box">
+        <HcCard :scrollbar="false" actionSize="lg">
+            <template #header>
+                <div class="w-32">
+                    <el-select v-model="searchForm.tasks" block clearable placeholder="电签任务人" size="large">
+                        <el-option v-for="item in tasksData" :label="item.dictValue" :value="item.dictKey"/>
+                    </el-select>
+                </div>
+                <div class="w-32 ml-3">
+                    <el-select v-model="searchForm.contract" block clearable placeholder="合同段" size="large"
+                               @change="ContractIdChange">
+                        <el-option v-for="item in contractList" :label="item.name" :value="item.id"/>
+                    </el-select>
+                </div>
+                <div class="w-32 ml-3">
+                    <el-select v-model="searchForm.status" block clearable placeholder="电签状态" size="large">
+                        <el-option v-for="item in statusData" :label="item.dictValue" :value="item.dictKey"/>
+                    </el-select>
+                </div>
+                <div class="w-64 ml-3">
+                    <HcDatePicker :dates="betweenTime" clearable size="large" @change="betweenTimeUpdate"/>
+                </div>
+                <div class="w-56 ml-3">
+                    <el-input v-model="searchForm.queryValue" block clearable placeholder="请输入名称关键词检索"
+                              size="large" @keyup="keyUpEvent"/>
+                </div>
+                <div class="ml-2">
+                    <el-button size="large" type="primary" @click="searchClick">
+                        <HcIcon name="search-2"/>
+                        <span>搜索</span>
+                    </el-button>
+                </div>
+            </template>
+            <template #extra>
+                <HcTooltip keys="tasks_sign_key_renewal">
+                    <el-button hc-btn type="primary">
+                        <HcIcon name="restart"/>
+                        <span>一键重签</span>
+                    </el-button>
+                </HcTooltip>
+            </template>
+            <HcTable ref="tableListRef" :column="tableListColumn" :datas="tableData" :loading="tableLoading" isCheck
+                     @selection-change="tableSelectionChange"/>
+            <template #action>
+                <HcPages :pages="searchForm" @change="pageChange"/>
+            </template>
+        </HcCard>
+    </div>
+</template>
+
+<script setup>
+import {ref, onMounted} from "vue";
+import {useAppStore} from "~src/store";
+import {getObjValue, getArrValue} from "js-fast-way"
+
+//变量
+const useAppState = useAppStore()
+const projectId = ref(useAppState.getProjectId);
+const contractId = ref(useAppState.getContractId);
+const projectInfo = ref(useAppState.getProjectInfo);
+
+//渲染完成
+onMounted(() => {
+    const project = getObjValue(projectInfo.value)
+    contractList.value = getArrValue(project['contractInfoList'])
+    getTableData()
+})
+
+const tasksData = ref([])
+const statusData = ref([])
+
+//合同段
+const contractList = ref([])
+const ContractIdChange = () => {
+
+}
+
+//日期时间被选择
+const betweenTime = ref(null)
+const betweenTimeUpdate = ({val, arr}) => {
+    betweenTime.value = arr
+    searchForm.value.startTime = val['start']
+    searchForm.value.endTime = val['end']
+}
+
+//搜索表单
+const searchForm = ref({
+    queryValue: null, tasks: null, contract: null, status: null, startTime: null, endTime: null,
+    current: 1, size: 20, total: 0
+})
+
+//回车搜索
+const keyUpEvent = (e) => {
+    if (e.key === "Enter") {
+        searchForm.value.current = 1;
+        getTableData()
+    }
+}
+
+//重新搜索数据
+const searchClick = () => {
+    searchForm.value.current = 1
+    getTableData()
+}
+
+//分页被点击
+const pageChange = ({current, size}) => {
+    searchForm.value.current = current
+    searchForm.value.size = size
+    getTableData()
+}
+
+//获取数据
+const tableLoading = ref(false)
+const tableListData = ref([])
+const getTableData = async () => {
+
+}
+
+//多选
+const tableListRef = ref(null)
+const tableCheckedKeys = ref([]);
+const tableSelectionChange = (rows) => {
+    tableCheckedKeys.value = rows.filter((item) => {
+        return (item ?? '') !== '';
+    })
+}
+const tableListColumn = ref([
+    {key: 'name', name: '流程名称'},
+    {key: 'date', name: '任务状态'},
+    {key: 'status', name: '电签状态'},
+    {key: 'batch', name: '审批时间'},
+    {key: 'person', name: '电签失败原因'},
+    {key: 'tesk', name: '上报人'},
+    {key: 'tesk1', name: '电签任务人'}
+])
+const tableData = ref([
+    {
+        num: 1,
+        name: "test1",
+        date: "已审批-电签失败",
+        status: "电签失败",
+        batch: "2022-05-16 13:35",
+        person: "xxxxx原因",
+        tesk: "李四",
+        tesk1: '张三'
+    },
+    {
+        num: 2,
+        name: "test2",
+        date: "已审批-电签中",
+        status: "电签中",
+        batch: "2022-05-16 13:35",
+        person: "xxxxx原因",
+        tesk: "李四",
+        tesk1: '张三'
+    },
+    {
+        num: 3,
+        name: "test3",
+        date: "已审批完成",
+        status: "电签完成",
+        batch: "2022-05-16 13:35",
+        person: "xxxxx原因",
+        tesk: "李四",
+        tesk1: '张三'
+    },
+    {
+        num: 4,
+        name: "test4",
+        date: "已审批-电签中",
+        status: "等待电签",
+        batch: "2022-05-16 13:35",
+        person: "xxxxx原因",
+        tesk: "李四",
+        tesk1: '张三'
+    },
+    {
+        num: 5,
+        name: "test5",
+        date: "xxxxxxxxxx",
+        status: "xxxx",
+        batch: "2022-05-16 13:35",
+        person: "xxxxx原因",
+        tesk: "李四",
+        tesk1: '张三'
+    },
+    {
+        num: 6,
+        name: "test6",
+        date: "xxxxxxxxxx",
+        status: "xxxxxx",
+        batch: "2022-05-16 13:35",
+        person: "xxxxx原因",
+        tesk: "李四",
+        tesk1: '张三'
+    },
+]);
+</script>
+
+<style lang="scss" scoped>
+.hc-layout-box {
+    position: relative;
+    height: 100%;
+}
+</style>