diff --git a/.env.development b/.env.development index 743a4a3..393b8c8 100644 --- a/.env.development +++ b/.env.development @@ -2,9 +2,9 @@ ENV = 'development' # base api -VUE_APP_BASE_API='https://cz-hjjc-test.115.langye.net/' +VUE_APP_BASE_API='http://127.0.0.1:8000' #VUE_APP_BASE_API='http://czemc.localhost' -VUE_APP_UPLOAD_API='https://cz-hjjc-test.115.langye.net/api/upload-file' +VUE_APP_UPLOAD_API='http://127.0.0.1:8000/api/upload-file' VUE_APP_PREVIEW=//view.langye.net/preview/onlinePreview VUE_APP_MODULE_NAME=oa VUE_APP_OUTPUT_DIR='../backend/public/oa/' diff --git a/package.json b/package.json index 644fdfe..2c5977f 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "A vue admin template with Element UI & axios & iconfont & permission control & lint", "author": "Pan ", "scripts": { - "dev": "vue-cli-service serve", + "dev": "CHOKIDAR_USEPOLLING=true vue-cli-service serve", "build:prod": "vue-cli-service build", "build:stage": "vue-cli-service build --mode staging", "preview": "node build/index.js --preview", diff --git a/src/api/flow/index.js b/src/api/flow/index.js index 22eb989..58c64ce 100644 --- a/src/api/flow/index.js +++ b/src/api/flow/index.js @@ -86,6 +86,14 @@ export function getNextNodeUsers(params,isLoading=false) { }) } +export function freeAssignPre(flowId, isLoading = false) { + return request({ + method: 'get', + url: `/api/oa/flow/free-assign-pre/${flowId}`, + isLoading + }) +} + //查看列表相关 export function flowList(type,params,isLoading = false) { return request({ @@ -254,6 +262,33 @@ export function updateFlowTime(params,isLoading=true) { }) } +export function updateFlowLogMeta(data, isLoading = true) { + return request({ + method: 'post', + url: '/api/oa/flow/update-flow-log-meta', + data, + isLoading + }) +} + +export function deleteFlowLog(data, isLoading = true) { + return request({ + method: 'post', + url: '/api/oa/flow/delete-flow-log', + data, + isLoading + }) +} + +export function insertHistoryLog(data, isLoading = true) { + return request({ + method: 'post', + url: '/api/oa/flow/insert-history-log', + data, + isLoading + }) +} + export function getOvertimeHoliday(params,isLoading=false) { return request({ diff --git a/src/views/flow/create.vue b/src/views/flow/create.vue index aac1874..8c4b775 100644 --- a/src/views/flow/create.vue +++ b/src/views/flow/create.vue @@ -544,7 +544,16 @@
-
流转记录
+
+
流转记录
+ 新增节点 +
{{ diffTime(row.updated_at, row.created_at) }} + + +
@@ -720,27 +745,203 @@ > - + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 参考记录前 + 参考记录后 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1018,9 +1219,13 @@ import { create, deal, fieldConfig, flowList, + deleteFlowLog, + freeAssignPre, + insertHistoryLog, preConfig, preDeal, updateNodeTime, + updateFlowLogMeta, view, } from "@/api/flow"; import { getPaymentsByFlowId, getPaymentsByContractId, getBudgetContractDetail, getPlannedExpenditure, getPlannedExpenditureTemplatesByCategory, getPaymentCategoryTemplateElements, getDetailTableFields, getOaFlowDetails, getTemplateElementDetail } from "@/api/payment"; @@ -1054,10 +1259,35 @@ export default { isShowTime: false, selectedDateTime: '', selectedDateType: '', + editFlowLogSubmitting: false, + editFlowLogForm: { + id: '', + flow_id: '', + flow_node_id: '', + user_id: '', + status: 1, + created_at: '', + updated_at: '', + sort: 1, + reason: '', + }, isShowUserDialog: false, selectedUserId: '', userList: [], currentRowId: '', + flowLogNodeOptions: [], + isShowInsertHistoryLog: false, + insertHistoryLogSubmitting: false, + insertHistoryLogForm: { + insert_position: 'after', + anchor_log_id: '', + flow_node_id: '', + user_id: '', + created_at: '', + status: 1, + remark: '', + reason: '', + }, info: [], config: {}, writeableFields: [], @@ -2515,6 +2745,7 @@ export default { } } this.form = Object.assign({}, this.form); + await this.loadFlowLogNodeOptions(); loading.close(); // 加载关联的支付信息(详情模式下) @@ -2557,6 +2788,7 @@ export default { this.form = Object.assign({}, this.form); // 新建完成后建立 fill_flow_title watcher(确保 default_json 等已写入后再监听) this.setupFillFlowTitleWatcher(fields); + await this.loadFlowLogNodeOptions(); loading.close(); } catch (err) { console.error(err); @@ -2638,6 +2870,7 @@ export default { } } this.form = Object.assign({}, this.form); + await this.loadFlowLogNodeOptions(); loading.close(); // 加载关联的支付信息(办理模式下) @@ -2795,33 +3028,185 @@ export default { async cellDblclickEvent({ row, column }) { // if(this.$store.state.user.username !== 'admin') return - if(!this.$store.state.user.roles.includes("全局流程监管")) return - if(column.field === 'created_at' || column.field === 'updated_at') { - this.timeId = row.id - this.selectedDateType = column.field - this.selectedDateTime = row[column.field] - this.isShowTime = true + if (!this.canManageFlowLog) return + if (['created_at', 'updated_at', 'user.name', 'node.name', 'status', 'reason'].includes(column.field)) { + this.openEditLogDialog(row) + } + }, + resetInsertHistoryLogForm(defaults = {}) { + const normalizedDefaults = { + ...defaults, + anchor_log_id: defaults.anchor_log_id === '' || defaults.anchor_log_id === undefined || defaults.anchor_log_id === null ? '' : Number(defaults.anchor_log_id), + flow_node_id: defaults.flow_node_id === '' || defaults.flow_node_id === undefined || defaults.flow_node_id === null ? '' : Number(defaults.flow_node_id), + user_id: defaults.user_id === '' || defaults.user_id === undefined || defaults.user_id === null ? '' : Number(defaults.user_id), + status: defaults.status === '' || defaults.status === undefined || defaults.status === null ? 1 : Number(defaults.status), + }; + this.insertHistoryLogSubmitting = false; + this.insertHistoryLogForm = { + insert_position: 'after', + anchor_log_id: normalizedDefaults.anchor_log_id || this.historyLogAnchorOptions[0]?.id || '', + flow_node_id: normalizedDefaults.flow_node_id || this.historyLogNodeOptions[0]?.id || '', + user_id: normalizedDefaults.user_id || this.allUserOptions[0]?.id || '', + created_at: this.$moment().format('YYYY-MM-DD HH:mm:ss'), + status: normalizedDefaults.status, + remark: '', + reason: '', + ...normalizedDefaults, + }; + if (this.$refs.insertHistoryLogFormRef) { + this.$refs.insertHistoryLogFormRef.clearValidate(); } - if(column.field === "user.name"){ - this.currentRowId = row.id - this.selectedUserId = row.user ? row.user.id : '' - this.isShowUserDialog = true - this.getUserList() + }, + openInsertHistoryLogDialog(row = null) { + if (!this.historyLogAnchorOptions.length || !this.historyLogNodeOptions.length) { + this.$message.warning('当前流程缺少可补录的参考记录、节点或承办人员'); + return; } + const openDialog = () => { + this.resetInsertHistoryLogForm({ + anchor_log_id: row?.id || this.historyLogAnchorOptions[0]?.id || '', + }); + this.isShowInsertHistoryLog = true; + }; + if (this.allUserOptions.length) { + openDialog(); + return; + } + this.getUserList().then(openDialog); + }, + resetEditFlowLogForm(defaults = {}) { + const normalizedDefaults = { + ...defaults, + id: defaults.id === '' || defaults.id === undefined || defaults.id === null ? '' : Number(defaults.id), + flow_id: defaults.flow_id === '' || defaults.flow_id === undefined || defaults.flow_id === null ? '' : Number(defaults.flow_id), + flow_node_id: defaults.flow_node_id === '' || defaults.flow_node_id === undefined || defaults.flow_node_id === null ? '' : Number(defaults.flow_node_id), + user_id: defaults.user_id === '' || defaults.user_id === undefined || defaults.user_id === null ? '' : Number(defaults.user_id), + status: defaults.status === '' || defaults.status === undefined || defaults.status === null ? 1 : Number(defaults.status), + sort: defaults.sort === '' || defaults.sort === undefined || defaults.sort === null ? 1 : Number(defaults.sort), + }; + this.editFlowLogSubmitting = false; + this.editFlowLogForm = { + id: '', + flow_id: '', + flow_node_id: '', + user_id: '', + status: 1, + created_at: '', + updated_at: '', + sort: 1, + reason: '', + ...normalizedDefaults, + }; + if (this.$refs.editFlowLogFormRef) { + this.$refs.editFlowLogFormRef.clearValidate(); + } + }, + async openEditLogDialog(row) { + if (!this.userList.length) { + await this.getUserList(); + } + const latestRow = (this.config.logs || []).find((item) => Number(item.id) === Number(row.id)) || row; + this.resetEditFlowLogForm({ + id: latestRow.id, + flow_id: latestRow.flow_id || this.$route.query.flow_id || '', + flow_node_id: latestRow.flow_node_id || '', + user_id: latestRow.user?.id || latestRow.user_id || '', + status: latestRow.status, + created_at: latestRow.created_at, + updated_at: latestRow.updated_at, + sort: latestRow.sort || 1, + reason: latestRow.reason || '', + }); + this.isShowTime = true; }, - updateTime() { - updateNodeTime({ - id: this.timeId, - date: this.selectedDateTime, - date_type: this.selectedDateType - }).then(_ => { - this.$message.success('更新成功') - this.timeId = '' - this.selectedDateTime = '' - this.selectedDateType = '' - this.isShowTime = false - this.getConfig() - }) + async handleDeleteFlowLog(row) { + try { + await this.$confirm('确认删除这条流转记录吗?删除后会重新整理排序。', '提示', { + type: 'warning', + closeOnClickModal: false, + }); + } catch (err) { + return; + } + try { + await deleteFlowLog({ id: row.id }); + this.$message.success('删除成功'); + this.getConfig(); + } catch (err) { + console.error(err); + } + }, + async submitEditFlowLog() { + if (this.editFlowLogSubmitting) return; + try { + await this.$refs.editFlowLogFormRef.validate(); + } catch (err) { + return; + } + + const createdAt = this.$moment(this.editFlowLogForm.created_at); + const updatedAt = this.$moment(this.editFlowLogForm.updated_at); + if (updatedAt.isBefore(createdAt)) { + this.$message.warning('办理时间不能早于流转时间'); + return; + } + + this.editFlowLogSubmitting = true; + try { + const res = await updateFlowLogMeta({ + id: Number(this.editFlowLogForm.id), + flow_id: Number(this.editFlowLogForm.flow_id || this.$route.query.flow_id), + flow_node_id: Number(this.editFlowLogForm.flow_node_id), + user_id: Number(this.editFlowLogForm.user_id), + status: Number(this.editFlowLogForm.status), + created_at: this.editFlowLogForm.created_at, + updated_at: this.editFlowLogForm.updated_at, + sort: Number(this.editFlowLogForm.sort), + reason: this.editFlowLogForm.status < 0 ? (this.editFlowLogForm.reason || '') : '', + }); + if (Array.isArray(res?.logs)) { + this.config = { + ...this.config, + logs: res.logs, + }; + } + this.$message.success('编辑流转记录成功'); + this.isShowTime = false; + await this.getConfig(); + } catch (err) { + console.error(err); + } finally { + this.editFlowLogSubmitting = false; + } + }, + async submitInsertHistoryLog() { + if (this.insertHistoryLogSubmitting) return; + try { + await this.$refs.insertHistoryLogFormRef.validate(); + } catch (err) { + return; + } + this.insertHistoryLogSubmitting = true; + try { + await insertHistoryLog({ + flow_id: Number(this.$route.query.flow_id), + anchor_log_id: Number(this.insertHistoryLogForm.anchor_log_id), + insert_position: this.insertHistoryLogForm.insert_position, + flow_node_id: Number(this.insertHistoryLogForm.flow_node_id), + user_id: Number(this.insertHistoryLogForm.user_id), + created_at: this.insertHistoryLogForm.created_at, + status: Number(this.insertHistoryLogForm.status), + remark: this.insertHistoryLogForm.remark || '', + reason: this.insertHistoryLogForm.status < 0 ? this.insertHistoryLogForm.reason : '', + }); + this.$message.success('插入流转记录成功'); + this.isShowInsertHistoryLog = false; + this.getConfig(); + } catch (err) { + console.error(err); + } finally { + this.insertHistoryLogSubmitting = false; + } }, async getUserList() { try { @@ -2835,6 +3220,31 @@ export default { this.$message.error('获取用户列表失败') } }, + async loadFlowLogNodeOptions() { + if (!this.$route.query.flow_id) { + this.flowLogNodeOptions = []; + return; + } + try { + const res = await freeAssignPre(this.$route.query.flow_id); + const nodes = Array.isArray(res?.nodes) ? res.nodes : []; + this.flowLogNodeOptions = nodes + .map((node) => ({ + id: Number(node.id), + key: node.key === '' || node.key === undefined || node.key === null ? null : Number(node.key), + name: node.name || node.title || `节点${node.id}`, + })) + .filter((node) => !!node.id) + .sort((a, b) => { + const keyA = a.key === null || Number.isNaN(a.key) ? Number.MAX_SAFE_INTEGER : a.key; + const keyB = b.key === null || Number.isNaN(b.key) ? Number.MAX_SAFE_INTEGER : b.key; + return keyA - keyB || a.id - b.id; + }); + } catch (err) { + console.error(err); + this.flowLogNodeOptions = []; + } + }, updateUser() { if (!this.selectedUserId) { this.$message.warning('请选择承办人员') @@ -2853,6 +3263,16 @@ export default { }, }, computed: { + canManageFlowLog() { + const globalRoles = this.$store.state.user.roles || []; + const moduleRoles = this.$store.state.user.moduleRoles?.oa || []; + const adminId = Number(this.$store.state.user.adminId); + const username = this.$store.state.user.username; + return adminId === 1 + || username === 'admin' + || globalRoles.includes('全局流程监管') + || moduleRoles.includes('全局流程监管'); + }, device() { return this.$store.state.app.device; }, @@ -2905,6 +3325,122 @@ export default { isFirstNode() { return this.config?.logs?.length === 0 || this.config?.currentNode?.category === 'start' }, + historyLogAnchorOptions() { + return (this.config.logs || []).map((log) => ({ + id: Number(log.id), + label: `${log.node?.name || '节点已调整'} / ${log.user?.name || '未知人员'} / ${this.$moment(log.created_at).format('YYYY-MM-DD HH:mm:ss')}`, + })); + }, + historyLogNodeOptions() { + const nodeMap = new Map(); + (this.flowLogNodeOptions || []).forEach((node) => { + const id = Number(node.id); + if (!id) return; + nodeMap.set(id, { + id, + name: node.name || `节点${node.id}`, + }); + }); + + // 历史流转记录里可能引用了已删除或已调整的旧节点。 + // 把这些节点补进选项,避免 el-select 只能显示数字 ID。 + (this.config.logs || []).forEach((log) => { + const id = Number(log.flow_node_id); + if (!id || nodeMap.has(id)) return; + nodeMap.set(id, { + id, + key: log.node?.key === '' || log.node?.key === undefined || log.node?.key === null ? null : Number(log.node.key), + name: log.node?.name || `节点${id}`, + }); + }); + + return Array.from(nodeMap.values()).sort((a, b) => { + const keyA = a.key === null || Number.isNaN(a.key) ? Number.MAX_SAFE_INTEGER : a.key; + const keyB = b.key === null || Number.isNaN(b.key) ? Number.MAX_SAFE_INTEGER : b.key; + return keyA - keyB || a.id - b.id; + }); + }, + allUserOptions() { + return (this.userList || []) + .map((user) => ({ + id: Number(user.id), + name: user.name || `用户${user.id}`, + })) + .filter((user) => !!user.id); + }, + flowLogStatusOptions() { + return [ + { value: 1, label: this.myStatus.get(1) }, + { value: 0, label: this.myStatus.get(0) }, + { value: -1, label: this.myStatus.get(-1) }, + { value: -2, label: this.myStatus.get(-2) }, + ]; + }, + editFlowLogStatusOptions() { + return this.flowLogStatusOptions; + }, + editFlowLogUseTime() { + const createdAt = this.editFlowLogForm.created_at; + const updatedAt = this.editFlowLogForm.updated_at; + if (!createdAt || !updatedAt) { + return '-'; + } + const diff = this.$moment(updatedAt).diff(this.$moment(createdAt)); + return this.formatTime(Math.max(0, diff)); + }, + editFlowLogRules() { + return { + flow_node_id: [{ required: true, message: '请选择节点名称', trigger: 'change' }], + status: [{ required: true, message: '请选择办理状态', trigger: 'change' }], + user_id: [{ required: true, message: '请选择承办人', trigger: 'change' }], + created_at: [{ required: true, message: '请选择流转时间', trigger: 'change' }], + updated_at: [ + { required: true, message: '请选择办理时间', trigger: 'change' }, + { + validator: (rule, value, callback) => { + if (!value || !this.editFlowLogForm.created_at) { + callback(); + return; + } + if (this.$moment(value).isBefore(this.$moment(this.editFlowLogForm.created_at))) { + callback(new Error('办理时间不能早于流转时间')); + return; + } + callback(); + }, + trigger: 'change', + }, + ], + sort: [{ required: true, message: '请输入排序', trigger: 'change' }], + reason: [{ + validator: (rule, value, callback) => { + if (this.editFlowLogForm.status >= 0 || (value && String(value).trim())) { + callback(); + return; + } + callback(new Error('请输入退回原因')); + }, + trigger: 'blur', + }], + }; + }, + insertHistoryLogRules() { + return { + insert_position: [{ required: true, message: '请选择插入位置', trigger: 'change' }], + anchor_log_id: [{ required: true, message: '请选择参考记录', trigger: 'change' }], + flow_node_id: [{ required: true, message: '请选择节点名称', trigger: 'change' }], + user_id: [{ required: true, message: '请选择承办人员', trigger: 'change' }], + created_at: [{ required: true, message: '请选择流转时间', trigger: 'change' }], + status: [{ required: true, message: '请选择办理状态', trigger: 'change' }], + reason: [{ validator: (rule, value, callback) => { + if (this.insertHistoryLogForm.status >= 0 || (value && String(value).trim())) { + callback(); + return; + } + callback(new Error('请输入退回原因')); + }, trigger: 'blur' }], + }; + }, isSinglePage() { // 仅当显式传参 isSinglePage=1 时才启用“单页模式” // 不能用 window.self !== window.top 作为判定:被主框架 iframe 引入时也会命中,导致 padding/阴影/背景与独立打开不一致 diff --git a/src/views/flow/detailCommon.vue b/src/views/flow/detailCommon.vue index 3f94bef..5b050aa 100644 --- a/src/views/flow/detailCommon.vue +++ b/src/views/flow/detailCommon.vue @@ -135,6 +135,25 @@ {{ diffTime(row.updated_at, row.created_at) }} + + + @@ -178,13 +197,21 @@