diff --git a/components/flow/FlowAssigneePickerPopup.vue b/components/flow/FlowAssigneePickerPopup.vue new file mode 100644 index 0000000..d0dfe5b --- /dev/null +++ b/components/flow/FlowAssigneePickerPopup.vue @@ -0,0 +1,228 @@ + + + + + diff --git a/components/flow/FlowAssigneeTree.vue b/components/flow/FlowAssigneeTree.vue new file mode 100644 index 0000000..39a4862 --- /dev/null +++ b/components/flow/FlowAssigneeTree.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/components/flow/flowAssigneeUtils.js b/components/flow/flowAssigneeUtils.js new file mode 100644 index 0000000..b356f71 --- /dev/null +++ b/components/flow/flowAssigneeUtils.js @@ -0,0 +1,71 @@ +export const resolveAssigneeIdentityId = (user) => { + if (!user) return ''; + const id = user.identityId ?? user.userIdentityId ?? user.userId ?? ''; + return id === '' || id == null ? '' : String(id); +}; + +export const getAssigneeItemKey = (user) => { + const identityId = resolveAssigneeIdentityId(user); + if (identityId) return `identity-${identityId}`; + return `user-${user.userId || user.nickName || ''}`; +}; + +export const formatAssigneeDisplayName = (user) => { + if (!user) return ''; + if (user.identityName) { + return `${user.nickName || user.userName || ''}_${user.identityName}`; + } + if (user.postName) { + return `${user.nickName || user.userName || ''}_${user.postName}`; + } + return user.nickName || user.userName || user.name || '未知人员'; +}; + +export const normalizeApproverDeptTree = (data) => { + if (!data) return []; + if (Array.isArray(data)) return data; + if (Array.isArray(data.records)) return data.records; + if (Array.isArray(data.list)) return data.list; + return []; +}; + +export const findAssigneeUserInDeptTree = (depts, identityId) => { + if (!identityId || !Array.isArray(depts)) return null; + for (const dept of depts) { + const user = (dept.users || []).find( + (item) => String(resolveAssigneeIdentityId(item)) === String(identityId) + ); + if (user) return user; + if (dept.children?.length) { + const found = findAssigneeUserInDeptTree(dept.children, identityId); + if (found) return found; + } + } + return null; +}; + +/** 将部门树拍平为可渲染行(部门标题 + 人员),避免小程序递归组件不展示子部门 */ +export const flattenApproverDeptTree = (depts, level = 0) => { + const rows = []; + if (!Array.isArray(depts)) return rows; + for (const dept of depts) { + rows.push({ + type: 'dept', + key: `dept-${dept.deptId ?? dept.deptName ?? level}`, + deptName: dept.deptName || '', + level + }); + for (const user of dept.users || []) { + rows.push({ + type: 'user', + key: getAssigneeItemKey(user), + user, + level: level + 1 + }); + } + if (dept.children?.length) { + rows.push(...flattenApproverDeptTree(dept.children, level + 1)); + } + } + return rows; +}; diff --git a/components/hazard/HazardFormPanel.vue b/components/hazard/HazardFormPanel.vue index 4254bf6..dfe7333 100644 --- a/components/hazard/HazardFormPanel.vue +++ b/components/hazard/HazardFormPanel.vue @@ -173,7 +173,7 @@ 下一步处理人 * - 部门、企业管理员、企业成员 + 管理人员、执行人员 diff --git a/components/hazardDetail/HazardDetailPanel.vue b/components/hazardDetail/HazardDetailPanel.vue index 962c98b..bccfd41 100644 --- a/components/hazardDetail/HazardDetailPanel.vue +++ b/components/hazardDetail/HazardDetailPanel.vue @@ -62,6 +62,10 @@ class="node-section" :class="{ 'node-section--last': index === historyList.length - 1 }" > + @@ -320,6 +324,7 @@ + @@ -624,6 +629,23 @@ const previewImages = (attachments, index) => { margin-bottom: 0; } +.detail-card-shell { + border-radius: 22rpx; + padding: 2rpx; + background: transparent; + transition: background 0.3s ease, box-shadow 0.3s ease; +} + +.detail-card-shell--active { + background: linear-gradient( + 145deg, + rgba(4, 108, 234, 0.48) 0%, + rgba(38, 103, 233, 0.3) 50%, + rgba(33, 88, 200, 0.16) 100% + ); + box-shadow: 0 8rpx 24rpx rgba(38, 103, 233, 0.12); +} + .detail-card { background: #fff; border-radius: 16rpx; diff --git a/components/hazardDetail/HazardDetailPanelV2.vue b/components/hazardDetail/HazardDetailPanelV2.vue index 1437015..005f2b8 100644 --- a/components/hazardDetail/HazardDetailPanelV2.vue +++ b/components/hazardDetail/HazardDetailPanelV2.vue @@ -62,6 +62,10 @@ class="node-section" :class="{ 'node-section--last': index === historyList.length - 1 }" > + @@ -328,6 +332,7 @@ + @@ -552,6 +557,23 @@ const previewImages = (attachments, index) => { margin-bottom: 0; } +.detail-card-shell { + border-radius: 22rpx; + padding: 2rpx; + background: transparent; + transition: background 0.3s ease, box-shadow 0.3s ease; +} + +.detail-card-shell--active { + background: linear-gradient( + 145deg, + rgba(4, 108, 234, 0.48) 0%, + rgba(38, 103, 233, 0.3) 50%, + rgba(33, 88, 200, 0.16) 100% + ); + box-shadow: 0 8rpx 24rpx rgba(38, 103, 233, 0.12); +} + .detail-card { background: #fff; border-radius: 20rpx; diff --git a/components/hazardDetail/HazardProcessChainPanel.vue b/components/hazardDetail/HazardProcessChainPanel.vue new file mode 100644 index 0000000..a5df93d --- /dev/null +++ b/components/hazardDetail/HazardProcessChainPanel.vue @@ -0,0 +1,787 @@ + + + + + diff --git a/components/hazardDetail/processChain.js b/components/hazardDetail/processChain.js new file mode 100644 index 0000000..734a2ed --- /dev/null +++ b/components/hazardDetail/processChain.js @@ -0,0 +1,383 @@ +import { formatNameList } from './hazardDetail.js'; + +export const PENDING_LABEL = '待处理'; + +/** completed 未传时视为已完成(兼容旧数据) */ +export const isNodeCompleted = (node) => node?.completed !== false; + +/** 空值:已完成显示 -,未完成显示待处理 */ +export const resolveFieldDisplay = (value, completed = true) => { + if (value != null && value !== '') return value; + return completed ? '-' : PENDING_LABEL; +}; + +export const PROCESS_NODE_TYPES = { + ADD: 'add', + ASSIGN: 'assign', + RECTIFY: 'rectify', + RECTIFY_ASSIGN: 'rectifyAssign', + VERIFY: 'verify', + VERIFY_SUB: 'verify_sub', + WRITEOFF_APPLY: 'writeoff_apply', + WRITEOFF_APPROVE: 'writeoff_approve', + WRITEOFF_SUB: 'writeoff_sub', + APPROVAL: 'approval' +}; + +const SUB_TYPE_SUFFIX_MAP = { + verify: '验收', + writeoff: '销号' +}; + +const TITLE_PREFIX_MAP = { + add: '提交', + assign: '交办', + rectify: '整改', + rectifyAssign: '交办', + verify: '验收', + verify_sub: '审批', + writeoff_apply: '销号申请', + writeoff_approve: '销号审核', + writeoff_sub: '审批' +}; + +const APPROVAL_TASK_KEY_ICON = { + department_review: 'bumenshenpi', + section_chief_review: 'fenguanshenpi', + supervising_executive_review: 'fenguanshenpi', + supervising_leader_review_1: 'zhuguanshenpi', + supervising_leader_review_2: 'zhuguanshenpi' +}; + +const NODE_TYPE_ICON = { + add: 'tijiao', + assign: 'jiaoban', + rectifyAssign: 'jiaoban', + rectify: 'zhenggai', + verify: 'yanshou', + writeoff_apply: 'xiaohao', + writeoff_approve: 'xiaohao' +}; + +const resolveTaskKey = (node) => { + return node?.taskKey || node?.subProcessApprovalInfo?.taskKey || ''; +}; + +/** null/undefined 等非对象值统一转为 {},避免默认参数对 null 不生效 */ +const toSafeObject = (value) => (value && typeof value === 'object' ? value : {}); + +/** null/undefined/空字符串展示为空 */ +const toDisplayText = (value) => { + if (value == null || value === '') return ''; + return value; +}; + +export const getProcessStepDisplayName = (node) => { + if (!node) return ''; + const nodeType = node.nodeType; + if (nodeType === 'verify_sub' || nodeType === 'writeoff_sub') { + const subType = node.subProcessApprovalInfo?.subType; + const suffix = SUB_TYPE_SUFFIX_MAP[subType]; + return suffix ? `${node.nodeName}(${suffix})` : node.nodeName; + } + return node.nodeName || ''; +}; + +export const getProcessStepIconPath = (item, active = false) => { + const type = item?.type; + const taskKey = item?.taskKey || ''; + const state = active ? 'selected' : 'unselected'; + + if (type === PROCESS_NODE_TYPES.APPROVAL) { + const iconBase = APPROVAL_TASK_KEY_ICON[taskKey] || 'bumenshenpi'; + return `/static/yinhuan_detail/${iconBase}_${state}.png`; + } + + const base = NODE_TYPE_ICON[item?.rawType] || NODE_TYPE_ICON[type] || 'tijiao'; + if (base === 'zhenggai') { + return active + ? '/static/yinhuan_detail/zhenggai_selected.png' + : '/static/yinhuan_detail/zhenggai__unselected.png'; + } + return `/static/yinhuan_detail/${base}__${state}.png`; +}; + +const resolveOperator = (node, nodeType) => { + const completed = isNodeCompleted(node); + const fallback = completed ? '-' : ''; + if (nodeType === PROCESS_NODE_TYPES.ADD) { + return node.hazardInfo?.reporterName || fallback; + } + if (nodeType === PROCESS_NODE_TYPES.ASSIGN) { + return node.assignInfo?.assignerName || fallback; + } + if (nodeType === PROCESS_NODE_TYPES.RECTIFY) { + return node.rectifyInfo?.rectifierName || fallback; + } + if (nodeType === PROCESS_NODE_TYPES.VERIFY || nodeType === PROCESS_NODE_TYPES.WRITEOFF_APPROVE) { + return node.verifyInfo?.verifierName || fallback; + } + if (nodeType === PROCESS_NODE_TYPES.WRITEOFF_APPLY) { + return node.writeOffApplyInfo?.applicantName || fallback; + } + if (nodeType === PROCESS_NODE_TYPES.APPROVAL) { + const info = node.subProcessApprovalInfo || {}; + return info.operatorName || info.assigneeName || fallback; + } + return fallback; +}; + +const resolveTime = (node, nodeType) => { + const completed = isNodeCompleted(node); + const fallback = completed ? '-' : ''; + if (nodeType === PROCESS_NODE_TYPES.ADD) { + return node.hazardInfo?.createdAt || node.completedAt || node.occurredAt || fallback; + } + if (nodeType === PROCESS_NODE_TYPES.ASSIGN) { + return node.assignInfo?.assignTime || node.completedAt || fallback; + } + if (nodeType === PROCESS_NODE_TYPES.RECTIFY) { + return node.rectifyInfo?.rectifyTime || node.completedAt || fallback; + } + if (nodeType === PROCESS_NODE_TYPES.VERIFY || nodeType === PROCESS_NODE_TYPES.WRITEOFF_APPROVE) { + return node.verifyInfo?.verifyTime || node.completedAt || fallback; + } + if (nodeType === PROCESS_NODE_TYPES.WRITEOFF_APPLY) { + return node.writeOffApplyInfo?.applyTime || node.completedAt || fallback; + } + if (nodeType === PROCESS_NODE_TYPES.APPROVAL) { + return node.subProcessApprovalInfo?.endTime || node.completedAt || fallback; + } + return node.completedAt || node.occurredAt || fallback; +}; + +const mapAddContent = (info) => { + const data = toSafeObject(info); + return { + code: toDisplayText(data.code), + title: toDisplayText(data.title), + source: toDisplayText(data.source), + hazardSourceName: toDisplayText(data.hazardSourceName), + areaName: toDisplayText(data.areaName), + address: toDisplayText(data.address), + level: data.level ?? null, + levelName: toDisplayText(data.levelName), + tagName: toDisplayText(data.tagName), + description: toDisplayText(data.description), + attachments: Array.isArray(data.attachments) ? data.attachments : [], + legalBasis: toDisplayText(data.legalBasis) + }; +}; + +const mapAssignContent = (info) => { + const data = toSafeObject(info); + return { + assigneeName: toDisplayText(data.assigneeName), + deadline: toDisplayText(data.deadline), + assignRemark: toDisplayText(data.assignRemark), + assignStatusName: toDisplayText(data.assignStatusName) + }; +}; + +const mapRectifyContent = (info) => { + const data = toSafeObject(info); + return { + rectifyStatusName: toDisplayText(data.rectifyStatusName), + rectifyPlan: toDisplayText(data.rectifyPlan), + rectifyResult: toDisplayText(data.rectifyResult), + rectificationMeasures: toDisplayText(data.rectificationMeasures), + controlMeasures: toDisplayText(data.controlMeasures), + rectifierName: toDisplayText(data.rectifierName), + managerNames: formatNameList(data.managerNames, ''), + memberNames: formatNameList(data.memberNames, ''), + planCost: data.planCost ?? null, + actualCost: data.actualCost ?? null, + attachments: Array.isArray(data.attachments) ? data.attachments : [], + signPath: toDisplayText(data.signPath) + }; +}; + +const mapVerifyContent = (info) => { + const data = toSafeObject(info); + return { + resultName: toDisplayText(data.resultName), + remark: toDisplayText(data.remark), + attachments: Array.isArray(data.attachments) ? data.attachments : [], + signPath: toDisplayText(data.signPath) + }; +}; + +const mapApprovalContent = (info) => { + const data = toSafeObject(info); + return { + approveTypeName: toDisplayText(data.approveTypeName), + pass: data.pass ?? null, + comment: toDisplayText(data.comment), + nextStepName: toDisplayText(data.nextStepName), + nextAssigneeName: toDisplayText(data.nextAssigneeName), + sendMsgFlag: data.sendMsgFlag ?? null, + signPath: toDisplayText(data.signPath) + }; +}; + +const mapWriteoffApplyContent = (info) => { + const data = toSafeObject(info); + return { + rectifyDeadline: toDisplayText(data.rectifyDeadline), + responsibleDeptName: toDisplayText(data.responsibleDeptName), + responsiblePerson: toDisplayText(data.responsiblePerson), + mainTreatmentContent: toDisplayText(data.mainTreatmentContent), + treatmentResult: toDisplayText(data.treatmentResult), + selfVerifyContent: toDisplayText(data.selfVerifyContent), + signPath: toDisplayText(data.signPath) + }; +}; + +const resolveNodeType = (node) => { + const nodeType = node?.nodeType; + if (nodeType === 'verify_sub' || nodeType === 'writeoff_sub') { + return PROCESS_NODE_TYPES.APPROVAL; + } + return nodeType || PROCESS_NODE_TYPES.ADD; +}; + +const hasRectifyData = (rectifyInfo) => { + if (!rectifyInfo || typeof rectifyInfo !== 'object') { + return false; + } + return Boolean( + rectifyInfo.rectifyId + || rectifyInfo.rectifyPlan + || rectifyInfo.rectifyResult + || rectifyInfo.rectifyTime + || rectifyInfo.rectifierName + ); +}; + +const hasAssignData = (assignInfo) => { + if (!assignInfo || typeof assignInfo !== 'object') { + return false; + } + return Boolean( + assignInfo.assignId + || assignInfo.assigneeName + || assignInfo.assignerName + || assignInfo.assignTime + ); +}; + +/** 交办 / 整改转派节点 */ +const isAssignLikeNodeType = (nodeType) => ( + nodeType === PROCESS_NODE_TYPES.ASSIGN + || nodeType === PROCESS_NODE_TYPES.RECTIFY_ASSIGN +); + +/** 交办/整改节点:有整改数据展示整改,否则有交办数据展示交办 */ +export const resolveAssignRectifyDisplayType = (node) => { + if (hasRectifyData(node?.rectifyInfo)) { + return PROCESS_NODE_TYPES.RECTIFY; + } + if (hasAssignData(node?.assignInfo)) { + return PROCESS_NODE_TYPES.ASSIGN; + } + return null; +}; + +const resolveDisplayType = (node, nodeType) => { + // assign:隐患交办 → assignInfo + // rectifyAssign:整改转派 → assignInfo(若同节点附带整改数据则展示整改) + if (isAssignLikeNodeType(nodeType)) { + return resolveAssignRectifyDisplayType(node) || PROCESS_NODE_TYPES.ASSIGN; + } + // rectify:隐患整改 → rectifyInfo;未整改前可能只有转派信息 + if (nodeType === PROCESS_NODE_TYPES.RECTIFY) { + return resolveAssignRectifyDisplayType(node) || PROCESS_NODE_TYPES.RECTIFY; + } + return nodeType; +}; + +const resolveTitlePrefix = (rawType, displayType, node) => { + if (displayType === PROCESS_NODE_TYPES.ASSIGN) { + return TITLE_PREFIX_MAP[rawType] || TITLE_PREFIX_MAP.assign; + } + if (displayType === PROCESS_NODE_TYPES.RECTIFY) { + return TITLE_PREFIX_MAP.rectify; + } + return TITLE_PREFIX_MAP[displayType] || TITLE_PREFIX_MAP[rawType] || node.nodeName || '处理'; +}; + +const mapNodeContent = (node, displayType) => { + switch (displayType) { + case PROCESS_NODE_TYPES.ADD: + return mapAddContent(node.hazardInfo); + case PROCESS_NODE_TYPES.ASSIGN: + return mapAssignContent(node.assignInfo); + case PROCESS_NODE_TYPES.RECTIFY: + return mapRectifyContent(node.rectifyInfo); + case PROCESS_NODE_TYPES.VERIFY: + case PROCESS_NODE_TYPES.WRITEOFF_APPROVE: + return mapVerifyContent(node.verifyInfo); + case PROCESS_NODE_TYPES.WRITEOFF_APPLY: + return mapWriteoffApplyContent(node.writeOffApplyInfo); + case PROCESS_NODE_TYPES.APPROVAL: + return mapApprovalContent(node.subProcessApprovalInfo); + default: + return {}; + } +}; + +export const mapProcessNodeToHistoryItem = (node) => { + if (!node || typeof node !== 'object') { + return { + type: PROCESS_NODE_TYPES.ADD, + rawType: '', + taskKey: '', + nodeName: '', + titlePrefix: '处理', + operator: '', + time: '', + content: {}, + flowTaskId: '', + completed: true + }; + } + + const nodeType = resolveNodeType(node); + const rawType = node.nodeType || nodeType; + const displayType = resolveDisplayType(node, nodeType); + + return { + type: displayType, + rawType, + taskKey: resolveTaskKey(node), + nodeName: getProcessStepDisplayName(node), + titlePrefix: resolveTitlePrefix(rawType, displayType, node), + operator: resolveOperator(node, displayType), + time: resolveTime(node, displayType), + content: mapNodeContent(node, displayType), + flowTaskId: node.flowTaskId || '', + completed: isNodeCompleted(node) + }; +}; + +export const mapProcessChainNodes = (nodes = []) => { + if (!Array.isArray(nodes) || nodes.length === 0) return []; + return nodes.map((node) => mapProcessNodeToHistoryItem(node)); +}; + +export const resolveProcessChainSummary = (data) => { + if (!data) { + return { + statusName: '-', + createdAt: '-' + }; + } + + const addNode = (data.nodes || []).find((item) => item.nodeType === 'add'); + const createdAt = addNode?.hazardInfo?.createdAt || '-'; + + return { + statusName: data.statusName || '-', + createdAt + }; +}; diff --git a/components/hazardDetail/processChainLabels.js b/components/hazardDetail/processChainLabels.js new file mode 100644 index 0000000..b7b6981 --- /dev/null +++ b/components/hazardDetail/processChainLabels.js @@ -0,0 +1,58 @@ +export const CHAIN_LABELS = { + loading: '加载中...', + empty: '暂无流程记录', + personnelSuffix: '人员:', + hazardCode: '隐患编号', + hazardTitle: '隐患标题', + checkSource: '检查形式', + hazardSource: '隐患来源', + hazardArea: '隐患区域', + address: '位置描述', + hazardLevel: '隐患等级', + hazardTag: '隐患标签', + description: '问题描述', + hazardAttachments: '隐患附件', + legalBasis: '参考法规', + assigneeName: '指定整改责任人', + assignDeadline: '指定整改截至日期', + assignStatus: '交办状态', + rectifyStatus: '整改状态', + rectifyPlan: '整改方案', + rectifyResult: '整改结果', + rectifyMeasures: '整改措施', + controlMeasures: '管控措施', + rectifierName: '整改责任人', + managerNames: '管理人员', + memberNames: '整改成员', + planCost: '预计费用', + actualCost: '实际费用', + rectifyAttachments: '整改附件', + rectifySign: '整改签字', + verifyResult: '验收结果', + pass: '通过', + verifyRemark: '验收备注', + verifyAttachments: '验收附件', + verifySign: '验收签字', + writeoffDeadline: '整改时限', + responsibleDept: '治理责任单位', + responsiblePerson: '主要负责人', + mainTreatment: '主要治理内容', + treatmentResult: '治理完成内容', + selfVerify: '自行验收情况', + applySign: '申请签字', + approvalOpinion: '审批意见', + approvalComment: '意见说明', + smsReminder: '短信提醒', + yes: '是', + no: '否', + approvalSign: '审批签字', + attachmentFallback: '附件', + yuan: '元' +}; + +export const LEVEL_NAME_CLASS_MAP = { + 一般: 'level-normal', + 一般隐患: 'level-normal', + 重大: 'level-major', + 重大隐患: 'level-major' +}; diff --git a/components/hazardDetail/useProcessChainScroll.js b/components/hazardDetail/useProcessChainScroll.js new file mode 100644 index 0000000..f77a45d --- /dev/null +++ b/components/hazardDetail/useProcessChainScroll.js @@ -0,0 +1,163 @@ +import { ref, watch, nextTick, getCurrentInstance } from 'vue'; +import { onReady } from '@dcloudio/uni-app'; +import { mapProcessChainNodes } from './processChain.js'; + +/** + * 流程链左右联动滚动(与 HazardDetailPanelV2 保持一致) + */ +export function useProcessChainScroll(chainSource, loadingSource) { + const instance = getCurrentInstance(); + const queryScope = instance?.proxy || instance; + + const historyList = ref([]); + const activeIndex = ref(0); + const sectionOffsets = ref([0]); + const contentViewHeight = ref(0); + const contentScrollIntoView = ref(''); + const stepScrollIntoView = ref(''); + const scrollWithAnimation = ref(true); + const isProgrammaticScroll = ref(false); + let measureLayoutTimer = null; + + const measureLayout = () => { + if (!historyList.value.length) return; + + nextTick(() => { + const query = uni.createSelectorQuery().in(queryScope); + query.select('.content-scroll').boundingClientRect(); + query.select('.content-scroll').scrollOffset(); + query.selectAll('.node-section').boundingClientRect(); + query.exec((res) => { + const containerRect = res?.[0]; + const scrollOffset = res?.[1]; + const sections = res?.[2] || []; + if (!containerRect || !sections.length) return; + + contentViewHeight.value = containerRect.height || 0; + const baseScrollTop = scrollOffset?.scrollTop || 0; + sectionOffsets.value = sections.map( + (section) => section.top - containerRect.top + baseScrollTop + ); + }); + }); + }; + + const scheduleMeasureLayout = () => { + clearTimeout(measureLayoutTimer); + measureLayoutTimer = setTimeout(() => { + measureLayout(); + }, 80); + }; + + const rebuildHistory = (data) => { + const nodes = data?.nodes || []; + historyList.value = mapProcessChainNodes(nodes); + activeIndex.value = 0; + contentScrollIntoView.value = ''; + scheduleMeasureLayout(); + setTimeout(scheduleMeasureLayout, 300); + }; + + const syncActiveIndex = (scrollTop, scrollHeight = 0) => { + const count = historyList.value.length; + if (!count) return; + + if (scrollHeight > 0 && contentViewHeight.value > 0) { + if (scrollTop + contentViewHeight.value >= scrollHeight - 60) { + if (activeIndex.value !== count - 1) { + activeIndex.value = count - 1; + } + return; + } + } + + const offsets = sectionOffsets.value; + if (!offsets.length) return; + + let idx = 0; + for (let i = offsets.length - 1; i >= 0; i--) { + if (scrollTop >= offsets[i] - 80) { + idx = i; + break; + } + } + + if (idx !== activeIndex.value) { + activeIndex.value = idx; + } + }; + + const onContentScroll = (e) => { + if (isProgrammaticScroll.value) return; + const { scrollTop = 0, scrollHeight = 0 } = e.detail || {}; + syncActiveIndex(scrollTop, scrollHeight); + scheduleMeasureLayout(); + }; + + const onScrollToLower = () => { + const count = historyList.value.length; + if (count > 0 && activeIndex.value !== count - 1) { + activeIndex.value = count - 1; + } + }; + + const scrollToNode = (index) => { + if (index < 0 || index >= historyList.value.length) return; + + isProgrammaticScroll.value = true; + scrollWithAnimation.value = true; + activeIndex.value = index; + contentScrollIntoView.value = 'process-node-' + index; + + setTimeout(() => { + contentScrollIntoView.value = ''; + isProgrammaticScroll.value = false; + scheduleMeasureLayout(); + }, 350); + }; + + watch( + () => (typeof loadingSource === 'function' ? loadingSource() : loadingSource?.value), + (loading) => { + if (!loading) { + scheduleMeasureLayout(); + setTimeout(scheduleMeasureLayout, 300); + } + } + ); + + watch( + () => (typeof chainSource === 'function' ? chainSource() : chainSource?.value), + (val) => rebuildHistory(val), + { immediate: true, deep: true } + ); + + watch(activeIndex, (index) => { + stepScrollIntoView.value = 'process-step-' + index; + setTimeout(() => { + stepScrollIntoView.value = ''; + }, 300); + }); + + watch( + () => historyList.value.length, + () => scheduleMeasureLayout() + ); + + onReady(() => { + scheduleMeasureLayout(); + setTimeout(scheduleMeasureLayout, 300); + }); + + return { + historyList, + activeIndex, + contentScrollIntoView, + stepScrollIntoView, + scrollWithAnimation, + onContentScroll, + onScrollToLower, + scrollToNode, + scheduleMeasureLayout + }; +} diff --git a/main.js b/main.js index 295abb7..c318e1b 100644 --- a/main.js +++ b/main.js @@ -69,10 +69,17 @@ uni.addInterceptor('uploadFile', { } }); +import uviewPlus, { setConfig } from '@/uni_modules/uview-plus' + +setConfig({ + config: { + loadFontOnce: true + } +}) + // #ifndef VUE3 import Vue from 'vue' import './uni.promisify.adaptor' -import uviewPlus from '@/uni_modules/uview-plus' Vue.config.productionTip = false Vue.use(uviewPlus) App.mpType = 'app' @@ -84,7 +91,6 @@ app.$mount() // #ifdef VUE3 import { createSSRApp } from 'vue' -import uviewPlus from '@/uni_modules/uview-plus' export function createApp() { const app = createSSRApp(App) app.use(uviewPlus) diff --git a/pages.json b/pages.json index 79d2b32..b5062de 100644 --- a/pages.json +++ b/pages.json @@ -129,6 +129,15 @@ "disableScroll": true } }, + { + "path": "pages/hiddendanger/process-chain", + "style": { + "navigationBarTitleText": "隐患详情", + "navigationStyle": "custom", + "navigationBarTextStyle": "white", + "disableScroll": true + } + }, { "path":"pages/hiddendanger/rectification", "style": { @@ -141,6 +150,12 @@ "navigationBarTitleText": "隐患验收" } }, + { + "path":"pages/hiddendanger/acceptance-approval", + "style": { + "navigationBarTitleText": "验收审批" + } + }, { "path":"pages/hiddendanger/assignment", "style": { @@ -153,6 +168,24 @@ "navigationBarTitleText": "销号申请" } }, + { + "path":"pages/closeout/apply", + "style": { + "navigationBarTitleText": "新增销号申请" + } + }, + { + "path":"pages/closeout/approval", + "style": { + "navigationBarTitleText": "销号审批" + } + }, + { + "path":"pages/closeout/leader-approval", + "style": { + "navigationBarTitleText": "领导审批" + } + }, { "path":"pages/closeout/editor", "style": { @@ -218,6 +251,12 @@ } }, + { + "path": "pages/personalcenter/identity", + "style": { + "navigationBarTitleText": "切换身份" + } + }, { "path" : "pages/login/login", "style" : diff --git a/pages/Inspectionresult/detail.vue b/pages/Inspectionresult/detail.vue index 35ca3d5..8826f78 100644 --- a/pages/Inspectionresult/detail.vue +++ b/pages/Inspectionresult/detail.vue @@ -345,7 +345,7 @@ const viewHazardDetail = (item) => { return; } uni.navigateTo({ - url: `/pages/hiddendanger/detail2?hazardId=${item.hazardId}&assignId=${item.assignId || ''}` + url: `/pages/hiddendanger/process-chain?hazardId=${item.hazardId}` }); }; diff --git a/pages/area/management.vue b/pages/area/management.vue index 0cb5e02..534ec3e 100644 --- a/pages/area/management.vue +++ b/pages/area/management.vue @@ -26,7 +26,7 @@ - + 创建时间: - {{item.createdAt}} @@ -27,306 +26,32 @@ - - - - - - 新增销号申请 - × - - - - 隐患 - * - - - {{ selectedHazard || '请选择隐患' }} - - - - - 整改时限 - - - {{ formData.rectifyDeadline || '请先选择隐患' }} - - 隐患治理责任单位 - - {{ selectedDeptName || '请先选择隐患' }} - - 主要负责人 - - {{ formData.responsiblePerson || '请先选择隐患' }} - - - - - 主要治理内容 - - 隐患治理完成内容 - - 隐患治理责任单位自行验收的情况 - - - - 下一步流程 - * - - 隐患销号审批 - - - 下一步处理人 - * - - 部门、企业管理员 - - - 短信提醒 - * - - - - - - - - - - - - - - - - + @@ -397,120 +120,4 @@ background: #F5F5F5; color: #8C8C8C; } - - .popup-content { - width: 600rpx; - background: #fff; - border-radius: 20rpx; - overflow: hidden; - } - - .popup-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 30rpx; - border-bottom: 1rpx solid #eee; - - .popup-title { - font-size: 32rpx; - color: #333; - } - - .popup-close { - font-size: 40rpx; - color: #999; - line-height: 1; - } - } - - .popup-body { - padding: 30rpx; - } - - .popup-footer { - display: flex; - border-top: 1rpx solid #eee; - - button { - flex: 1; - height: 90rpx; - line-height: 90rpx; - border-radius: 0; - margin: 0 !important; - padding: 0 !important; - font-size: 30rpx; - - &::after { - border: none; - } - } - - .btn-cancel { - background: #fff; - color: #666; - } - - .btn-confirm { - color: #fff; - } - } - .ai-btn-wrapper { - display: flex; - justify-content: flex-end; - } - - .ai-analyze-btn { - display: flex; - align-items: center; - justify-content: center; - height: 72rpx; - padding: 0 32rpx; - font-size: 28rpx; - color: #fff; - background: linear-gradient(135deg, #4facfe 0%, #2668EA 100%); - border-radius: 36rpx; - border: none; - - &::after { - border: none; - } - - .ai-btn-icon { - margin-right: 8rpx; - font-size: 30rpx; - } - - &[disabled] { - opacity: 0.7; - } - } - - .picker-input { - background: #fff; - border-radius: 8rpx; - padding: 24rpx 20rpx; - margin-bottom: 20rpx; - border: 1rpx solid #eee; - - text { - font-size: 28rpx; - } - - &.readonly { - background: #f5f5f5; - color: #666; - } - } - - .static-field { - background: #fff; - border: 1rpx solid #dcdfe6; - border-radius: 8rpx; - padding: 20rpx 24rpx; - margin-bottom: 20rpx; - font-size: 28rpx; - color: #333; - line-height: 1.5; - } - \ No newline at end of file + diff --git a/pages/closeout/apply.vue b/pages/closeout/apply.vue new file mode 100644 index 0000000..95ffacf --- /dev/null +++ b/pages/closeout/apply.vue @@ -0,0 +1,778 @@ + + + + + diff --git a/pages/closeout/approval.vue b/pages/closeout/approval.vue new file mode 100644 index 0000000..689a2af --- /dev/null +++ b/pages/closeout/approval.vue @@ -0,0 +1,1232 @@ + + + + + diff --git a/pages/closeout/leader-approval.vue b/pages/closeout/leader-approval.vue new file mode 100644 index 0000000..d5db76b --- /dev/null +++ b/pages/closeout/leader-approval.vue @@ -0,0 +1,1218 @@ + + + + + diff --git a/pages/editchecklist/editchecklist.vue b/pages/editchecklist/editchecklist.vue index 8282719..5a9de86 100644 --- a/pages/editchecklist/editchecklist.vue +++ b/pages/editchecklist/editchecklist.vue @@ -16,21 +16,12 @@ 分派单位 * - + {{ formData.deptName || '请选择分派单位' }} - @@ -377,6 +368,42 @@ + + + + + 选择分派单位 + × + + + + + 暂无部门数据 + + + + + + + + + @@ -413,9 +440,8 @@ diff --git a/pages/hiddendanger/acceptance-逻辑说明.md b/pages/hiddendanger/acceptance-逻辑说明.md new file mode 100644 index 0000000..44ce59b --- /dev/null +++ b/pages/hiddendanger/acceptance-逻辑说明.md @@ -0,0 +1,312 @@ +# 隐患验收页逻辑说明 + +> 对应文件:`pages/hiddendanger/acceptance.vue` +> 最后整理:2026-07-15 + +--- + +## 1. 页面职责 + +验收页用于对**已提交的整改记录**进行验收,主要能力: + +1. 只读展示整改记录(方案、措施、人员、附件等) +2. 填写验收表单(结果、备注、验收附件、签名) +3. 根据验收选择**预览下一步流程** +4. 选择或通过只读展示**下一步处理人** +5. 提交到 `POST /frontend/hazard/verify` + +--- + +## 2. 页面入参(URL Query) + +从首页 / 巡检列表跳转,由 `utils/hazardNav.js` → `buildAcceptanceUrl` 构建: + +| 参数 | 是否必需 | 说明 | +|------|----------|------| +| `rectifyId` | **推荐必带** | 整改记录 ID,有则只调整改详情接口 | +| `hazardId` | 列表通常会带 | 隐患 ID,无 `rectifyId` 时用于兜底拉取 | +| `assignId` | 可选 | 指派 ID,用于从隐患详情中定位正确 assign | +| `taskId` | **流程预览依赖** | 工作流任务 ID,用于「下一步流程」接口 | + +示例: + +``` +/pages/hiddendanger/acceptance?hazardId=184&assignId=158&rectifyId=150&taskId=xxx +``` + +> **注意**:`rectify/detail` 响应里通常**没有** `taskId`,下一步流程主要依赖 URL 传入的 `taskId`。若列表未带且详情也解析不到,「下一步流程」会显示「暂无下一步流程」。 + +--- + +## 3. 页面加载流程 + +``` +onLoad + ├─ 解析 URL 参数(rectifyId / hazardId / assignId / taskId) + ├─ loadPageData() + │ ├─ 有 rectifyId → fetchRectifyDetail() // 只调一次 + │ ├─ 无 rectifyId、有 hazardId → fetchDetail() + │ └─ fetchNextStep() + └─ restoreDraft() // 恢复本地草稿(若有) +``` + +### 3.1 数据接口选择(重要) + +**不要两个详情接口都调**,当前规则: + +| 条件 | 调用接口 | 说明 | +|------|----------|------| +| 有 `rectifyId` | `GET /frontend/hazard/rectify/detail` | **唯一数据源** | +| 无 `rectifyId`、有 `hazardId` | `GET /frontend/hazard/detail` | 从 `assigns[].rectify` 取整改记录 | + +### 3.2 两个详情接口的区别 + +| 对比项 | `rectify/detail` | `hazard/detail` 内嵌 `rectify` | +|--------|------------------|-------------------------------| +| 数据范围 | 单条整改记录 | 整条隐患 + 指派 + 整改 | +| 人员结构 | `members` / `managers` 对象数组 | `memberNames` / `managerNames` 字符串数组 | +| 整改人 | 有 `rectifierName` | 有 `rectifierName` | +| 状态字段 | `statusName` | `rectifyStatusName` | +| 适用场景 | 有 `rectifyId` 时优先 | 仅无 `rectifyId` 时兜底 | + +--- + +## 4. 整改记录字段映射(`applyRectifyData`) + +接口返回结构不统一,统一在 `applyRectifyData` 中做映射: + +| 页面展示字段 | 映射规则 | +|--------------|----------| +| 整改方案/措施/管控/完成情况/费用 | 同名字段直取 | +| 安全管理人员 | 有 `managers[]` → 取 `nickName` 去重;否则用 `managerNames` | +| 整改责任人 | 有 `members[]` → 取 `nickName` 去重;否则用 `memberNames` | +| 完成情况 | `rectifyStatusName` 或 `statusName` | +| 整改附件 | `attachments` | +| 整改人(不通过时展示) | `rectifierName` | + +### 人员名显示规则(易踩坑) + +`rectify/detail` **同时可能返回**: + +- `memberNames` / `managerNames`(身份名,如:阎勇、user1) +- `members` / `managers`(对象,含 `nickName`,如:xiaomi、duoduo) + +**当前规则:有 `members` / `managers` 数组时,优先用其中的 `nickName`,不用 `memberNames`。** + +--- + +## 5. 验收表单交互 + +### 5.1 验收结果 `formData.result` + +| 值 | 含义 | +|----|------| +| `1` | 通过(默认) | +| `2` | 不通过 | + +切换时触发 `onResultChange` → 重新请求下一步流程 `fetchNextStep()`。 + +- 切到「不通过」:清空已选下一步处理人 +- 切到「通过」:若未选快速审批,默认 `quickApproveRadio = 'yes'` + +### 5.2 是否快速审批 `formData.quickApproveRadio` + +- **仅验收通过时显示**,必选 +- `yes` = 快速审批;`no` = 不快速审批 +- 切换时触发 `onQuickApproveChange` → `fetchNextStep()` + +### 5.3 下一步流程(只读预览) + +接口:`POST /flow/task/next-nodes` + +**三种情况都必须传:** + +```json +{ + "taskId": "当前任务ID", + "includeSubProcess": true, + "previewVariables": { ... } +} +``` + +`previewVariables` 按验收选择变化: + +| 场景 | previewVariables | +|------|------------------| +| 不通过 | `{ "pass": false }` | +| 通过 + 快速审批 | `{ "pass": true, "quickApprove": true }` | +| 通过 + 不快速审批 | `{ "pass": true, "quickApprove": false }` | + +展示逻辑:取返回 `branches` 中 `matched === true` 的分支(没有则取第一个)的 `nextNode.taskName`。 + +### 5.4 下一步处理人 + +| 验收结果 | UI | 数据来源 | +|----------|-----|----------| +| 不通过 | 只读文本 | `rectify/detail` 的 **`rectifierName`**(整改人) | +| 通过 | 底部弹窗单选 | `GET /admin/user/dept/users/{deptId}` | + +通过时选人说明: + +- `deptId` 来自本地 `userInfo.userIdentity.deptId`(无则取 `userInfo.deptId`) +- 部门人员接口返回 `{ userId, nickName }`,可能没有 `identityId` +- 选人 ID 解析:`identityId` → `userIdentityId` → `userId`(兜底) +- 提交字段名是 `assigneeIdentityId`,无 `identityId` 时实际传的是 `userId` + +### 5.5 电子签名 + +- 必填,提交前先上传云端得到 `signPath` +- 打开「选择下一步处理人」弹窗时,**卸载签名 Canvas**(`v-if="showCanvas && !showAssigneePopup"`),避免微信小程序原生 canvas 层级穿透盖住弹窗 + +### 5.6 验收图片/视频 + +- 使用 `up-upload` + 水印 canvas +- 提交时只取 `status === 'success'` 的文件,经 `buildAttachmentItem` 转成附件对象 + +--- + +## 6. 提交验收 + +接口:`POST /frontend/hazard/verify`(`acceptanceRectification`) + +### 6.1 提交前校验 + +1. 必须有 `rectifyId` +2. 通过时:必须选「是否快速审批」 +3. 通过 + 不快速审批:必须选下一步处理人 +4. 必须有电子签名 + +### 6.2 请求体 + +**通用字段(通过/不通过都传):** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `rectifyId` | number | 整改记录 ID | +| `result` | number | `1` 通过 / `2` 不通过 | +| `verifyRemark` | string | 验收备注,可为空 | +| `attachments` | array | 验收附件 `[{ fileName, filePath, fileType, fileSize }]` | +| `signPath` | string | 电子签名服务器路径 | + +**仅通过时额外传:** + +| 字段 | 条件 | 说明 | +|------|------|------| +| `quickApprove` | `result === 1` | boolean | +| `assigneeIdentityId` | `quickApprove === false` | 下一步处理人 ID | + +**不提交的内容:** + +- 整改记录只读区所有字段 +- 下一步流程名称(仅预览) +- 不通过时的 `rectifierName`(仅展示,后端按流程自行处理) +- 快速审批为「是」时的下一步处理人 + +### 6.3 提交示例 + +**不通过:** +```json +{ + "rectifyId": 150, + "result": 2, + "verifyRemark": "整改不到位", + "attachments": [], + "signPath": "https://oss.../sign.png" +} +``` + +**通过 + 快速审批:** +```json +{ + "rectifyId": 150, + "result": 1, + "verifyRemark": "", + "attachments": [], + "signPath": "https://oss.../sign.png", + "quickApprove": true +} +``` + +**通过 + 不快速审批:** +```json +{ + "rectifyId": 150, + "result": 1, + "verifyRemark": "", + "attachments": [], + "signPath": "https://oss.../sign.png", + "quickApprove": false, + "assigneeIdentityId": "44" +} +``` + +--- + +## 7. 草稿缓存 + +使用 `useDraftCache`,命名空间 `DRAFT_NS.ACCEPT`,key 基于 `rectifyId`。 + +**会缓存:** + +- 验收结果、备注、快速审批选择 +- 下一步处理人选择 +- 验收上传附件列表 +- 签名相关状态 + +**不会缓存:** + +- 整改记录只读区(每次进页重新拉接口) + +恢复草稿后会再次调用 `fetchNextStep()`,保证下一步流程与当前验收选择一致。 + +--- + +## 8. 关键函数索引 + +| 函数 | 作用 | +|------|------| +| `loadPageData` | 页面数据加载入口 | +| `fetchRectifyDetail` | 调整改详情,有 `rectifyId` 时用 | +| `fetchDetail` | 调隐患详情,无 `rectifyId` 时兜底 | +| `applyRectifyData` | 统一映射整改记录到页面 | +| `resolveManagerNames` / `resolveMemberNames` | 人员名映射(优先 members/managers 的 nickName) | +| `buildPreviewVariables` | 构建下一步流程预览变量 | +| `fetchNextStep` | 请求下一步流程名称 | +| `onResultChange` / `onQuickApproveChange` | 切换验收选项后刷新流程预览 | +| `openAssigneePopup` / `fetchAssigneeList` | 通过时选择下一步处理人 | +| `validateFormBeforeSubmit` | 提交前表单校验 | +| `handleSubmit` / `executeSubmit` | 签名处理 + 提交验收 | + +--- + +## 9. 关联文件 + +| 文件 | 关系 | +|------|------| +| `utils/hazardNav.js` | 构建跳转 URL(含 taskId) | +| `request/api.js` | 接口定义 | +| `utils/upload.js` | 附件上传与 `buildAttachmentItem` | +| `utils/draftCache.js` / `utils/useDraftCache.js` | 草稿 | +| `pages/hiddendanger/rectification.vue` | 签名 canvas 穿透弹窗的同类处理参考 | + +--- + +## 10. 常见问题速查 + +| 现象 | 可能原因 | +|------|----------| +| 下一步流程显示「暂无」 | URL 未带 `taskId`,且详情接口也解析不到 | +| 人员显示身份名而非 nickName | `members` 数组为空,走了 `memberNames` 兜底 | +| 不通过时处理人不对 | 应检查 `rectify/detail` 的 `rectifierName` 是否有值 | +| 选人弹窗被白块挡住 | 签名 canvas 未在弹窗打开时卸载 | +| 选人列表为空 | `userInfo.userIdentity.deptId` 缺失,或部门无人员 | +| 提交了但后端报处理人错误 | `assigneeIdentityId` 传的是 `userId`,需确认后端是否接受 | + +--- + +## 11. 维护建议 + +1. **有 `rectifyId` 就不要再调 `hazard/detail` 做展示**,避免重复请求和数据覆盖混乱。 +2. 改人员展示逻辑时,优先看 `resolveManagerNames` / `resolveMemberNames`,不要直接改模板。 +3. 改流程预览时,确认 `taskId`、`previewVariables`、`includeSubProcess: true` 三者始终齐全。 +4. 新增提交字段时,同步更新本文档第 6 节。 diff --git a/pages/hiddendanger/acceptance.vue b/pages/hiddendanger/acceptance.vue index 7c5503f..894ce9b 100644 --- a/pages/hiddendanger/acceptance.vue +++ b/pages/hiddendanger/acceptance.vue @@ -52,8 +52,8 @@ * - - + + 验收备注 @@ -63,6 +63,15 @@ 验收图片/视频 + + + 是否快速审批 + * + + + + + @@ -71,13 +80,48 @@ 下一步流程 * - 申请隐患销号 + {{ nextStepDisplay }} 下一步处理人 * - 部门、企业管理员 + {{ rectifierDisplayName }} + + + {{ selectedAssigneeName || '请选择下一步处理人' }} + + + + + + + + 选择下一步处理人 + × + + + 加载中... + 暂无人员数据 + + + + + + + + - + + + + diff --git a/pages/hiddendanger/rectification.vue b/pages/hiddendanger/rectification.vue index 8622a79..4f7b437 100644 --- a/pages/hiddendanger/rectification.vue +++ b/pages/hiddendanger/rectification.vue @@ -100,8 +100,8 @@ 选择安全管理人员 × - - 已选 {{ managerPickerSelectedIds.length }} 人: + + 已选 {{ managerPickerSelectedIdentityIds.length }} 人: {{ managerPickerSelectedText }} @@ -119,14 +119,14 @@ 该部门暂无人员 - + @@ -146,8 +146,8 @@ 选择整改责任人 × - - 已选 {{ userPickerSelectedIds.length }} 人: + + 已选 {{ userPickerSelectedIdentityIds.length }} 人: {{ userPickerSelectedText }} @@ -165,15 +165,15 @@ 该部门暂无人员 - + @@ -212,13 +212,13 @@ 下一步流程 * - 隐患验收 + {{ nextStepDisplay }} 下一步处理人 * - 企业管理员 + 管理人员 短信提醒 @@ -289,11 +289,13 @@ import {onLoad, onHide} from '@dcloudio/uni-app' import { submitRectification, + updateRectification, // getDepartmentPersonUsers, // getDeptUsersWithSubordinates, getRelatedDeptUsers, getRectifyDetail, getHiddenDangerDetail, + getFlowNextNodes, generateRectifyPlan } from '@/request/api.js' import { buildDraftKey, buildDraftKeyCompact, DRAFT_NS } from '@/utils/draftCache.js'; @@ -309,9 +311,91 @@ // 从页面参数获取的ID const hazardId = ref(''); const assignId = ref(''); + const taskId = ref(''); const rectifyId = ref(''); // 整改ID(编辑模式时使用) const isEdit = ref(false); // 是否为编辑模式 + // 下一步流程(编辑模式写死展示,新建整改走接口) + const EDIT_NEXT_STEP_NAME = '隐患验收'; + const nextStepName = ref(''); + const nextStepLoading = ref(false); + + const nextStepDisplay = computed(() => { + if (isEdit.value) return EDIT_NEXT_STEP_NAME; + if (nextStepLoading.value) return '加载中...'; + return nextStepName.value || '暂无下一步流程'; + }); + + const resolveTaskIdFromDetail = (data) => { + if (!data) return ''; + if (data.taskId) return String(data.taskId); + if (data.flowTaskId) return String(data.flowTaskId); + if (data.currentTaskId) return String(data.currentTaskId); + const assigns = data.assigns || []; + for (const assign of assigns) { + if (assign?.taskId) return String(assign.taskId); + } + return ''; + }; + + const resolveNextTaskName = (data) => { + if (!data) return ''; + const branches = data.branches || []; + const matchedBranch = branches.find((item) => item.matched) || branches[0]; + return matchedBranch?.nextNode?.taskName || ''; + }; + + const fetchNextStep = async () => { + nextStepLoading.value = true; + try { + let currentTaskId = taskId.value; + if (!currentTaskId && rectifyId.value) { + const rectifyRes = await getRectifyDetail({ rectifyId: rectifyId.value }); + if (rectifyRes.code === 0 && rectifyRes.data) { + const rectifyData = rectifyRes.data; + currentTaskId = resolveTaskIdFromDetail(rectifyData); + if (!hazardId.value && rectifyData.hazardId) { + hazardId.value = rectifyData.hazardId; + } + if (!assignId.value && rectifyData.assignId) { + assignId.value = rectifyData.assignId; + } + if (currentTaskId) { + taskId.value = currentTaskId; + } + } + } + if (!currentTaskId && hazardId.value) { + const detailRes = await getHiddenDangerDetail({ + hazardId: hazardId.value, + assignId: assignId.value + }); + if (detailRes.code === 0 && detailRes.data) { + currentTaskId = resolveTaskIdFromDetail(detailRes.data); + if (currentTaskId) { + taskId.value = currentTaskId; + } + } + } + if (!currentTaskId) { + nextStepName.value = ''; + return; + } + const res = await getFlowNextNodes({ taskId: currentTaskId }); + if (res.code === 0) { + const payload = res.data && typeof res.data === 'object' ? res.data : res; + nextStepName.value = resolveNextTaskName(payload); + } else { + nextStepName.value = ''; + } + } catch (error) { + console.error('获取下一步流程失败:', error); + nextStepName.value = ''; + } finally { + nextStepLoading.value = false; + } + }; + // 防作弊时间戳水印 Canvas 大小配置 const canvasWidth = ref(300); const canvasHeight = ref(300); @@ -492,31 +576,32 @@ } }; - const applyAssigneeFromOptions = (assigneeId, assigneeName) => { - if (!assigneeId) return; - const id = String(assigneeId); + const applyAssigneeFromOptions = (assigneeId, assigneeName, assigneeIdentityId) => { + if (!assigneeIdentityId) return; + const identityId = String(assigneeIdentityId); const name = assigneeName ? decodeURIComponent(String(assigneeName)).trim() : ''; - const exists = detailPersonPool.value.some((user) => String(user.userId) === id); + const exists = detailPersonPool.value.some((user) => String(user.identityId) === identityId); if (!exists) { detailPersonPool.value.push({ - userId: assigneeId, + userId: assigneeId || '', + identityId: assigneeIdentityId, nickName: name, deptName: '' }); } - if (!lockedUserIds.value.includes(id)) { - lockedUserIds.value = [...lockedUserIds.value, id]; + if (!lockedIdentityIds.value.includes(identityId)) { + lockedIdentityIds.value = [...lockedIdentityIds.value, identityId]; } - selectedUserIds.value = mergeLockedUserIds([id]); - syncSelectedUsersFromIds(selectedUserIds.value); + selectedIdentityIds.value = mergeLockedIdentityIds([identityId]); + syncSelectedMembersFromIdentityIds(selectedIdentityIds.value); }; - const setLockedDefaultUsers = (ids) => { + const setLockedDefaultIdentities = (ids) => { const normalized = parseIdList(ids); if (!normalized.length) return; - lockedUserIds.value = normalized; - selectedUserIds.value = mergeLockedUserIds(selectedUserIds.value); - syncSelectedUsersFromIds(selectedUserIds.value); + lockedIdentityIds.value = normalized; + selectedIdentityIds.value = mergeLockedIdentityIds(selectedIdentityIds.value); + syncSelectedMembersFromIdentityIds(selectedIdentityIds.value); }; const parseIdList = (raw) => { @@ -527,42 +612,40 @@ return String(raw).split(',').map((id) => String(id).trim()).filter(Boolean); }; - const resolveManagerIdsFromDetail = (data) => { - const ids = parseIdList(data.manageIds ?? data.managerIds); + const resolveManagerIdentityIdsFromDetail = (data) => { + const ids = parseIdList(data.managerIds ?? data.manageIds ?? data.manageIdentityIds ?? data.managerIdentityIds); if (ids.length > 0) return ids; if (Array.isArray(data.managers) && data.managers.length > 0) { - return data.managers.map((item) => String(item.userId)).filter(Boolean); + return data.managers + .map((item) => String(item.identityId ?? item.userId)) + .filter(Boolean); } return []; }; - const buildUserItemFromDetail = (user) => ({ - id: String(user.userId), - name: formatUserDisplayName(user), - deptName: user.deptName || '' - }); - - const getUsersByIdsFromTree = (ids, tree) => { - const userMap = new Map(); + const getMembersByIdentityIdsFromTree = (ids, tree) => { + const memberMap = new Map(); (tree || []).forEach((dept) => { (dept.users || []).forEach((user) => { - userMap.set(String(user.userId), buildUserItem(user, dept)); + if (user.identityId != null && user.identityId !== '') { + memberMap.set(String(user.identityId), buildMemberItem(user, dept)); + } }); }); - return ids.map((id) => userMap.get(String(id))).filter(Boolean); + return ids.map((id) => memberMap.get(String(id))).filter(Boolean); }; - const mergeUsersFromDetailPool = (ids, resolvedUsers) => { - const userMap = new Map(resolvedUsers.map((user) => [user.id, user])); + const mergeMembersFromDetailPool = (ids, resolvedMembers) => { + const memberMap = new Map(resolvedMembers.map((member) => [member.id, member])); ids.forEach((id) => { const key = String(id); - if (userMap.has(key)) return; - const found = detailPersonPool.value.find((user) => String(user.userId) === key); + if (memberMap.has(key)) return; + const found = detailPersonPool.value.find((user) => String(user.identityId) === key); if (found) { - userMap.set(key, buildUserItemFromDetail(found)); + memberMap.set(key, buildMemberItemFromDetail(found)); } }); - return ids.map((id) => userMap.get(String(id))).filter(Boolean); + return ids.map((id) => memberMap.get(String(id))).filter(Boolean); }; const applyRectifyTimeValue = (timeStr) => { @@ -591,45 +674,55 @@ const deptList = ref([]) const detailPersonPool = ref([]) const showManagerPopup = ref(false) - const selectedManagerIds = ref([]) + const selectedManagerIdentityIds = ref([]) const selectedManagers = ref([]) const activeManagerDeptIndex = ref(0) - const managerPickerSelectedIds = ref([]) + const managerPickerSelectedIdentityIds = ref([]) const showUserPopup = ref(false) - const selectedUserIds = ref([]) + const selectedIdentityIds = ref([]) const selectedUsers = ref([]) - const lockedUserIds = ref([]) + const lockedIdentityIds = ref([]) const activeDeptIndex = ref(0) - const userPickerSelectedIds = ref([]) + const userPickerSelectedIdentityIds = ref([]) - const isLockedUser = (userId) => lockedUserIds.value.includes(String(userId)); + const isLockedIdentity = (identityId) => lockedIdentityIds.value.includes(String(identityId)); - const mergeLockedUserIds = (ids) => { + const mergeLockedIdentityIds = (ids) => { const merged = new Set([ ...(ids || []).map((id) => String(id)), - ...lockedUserIds.value.map((id) => String(id)) + ...lockedIdentityIds.value.map((id) => String(id)) ]); return [...merged]; }; - const formatUserPickerLabel = (user) => { - const name = formatUserDisplayName(user); - return isLockedUser(user.userId) ? `${name}(默认)` : name; - }; - - const formatUserDisplayName = (user) => { + const formatMemberDisplayName = (user) => { + if (user.identityName) { + return `${user.nickName}_${user.identityName}`; + } if (user.postName) { return `${user.nickName}_${user.postName}`; } return user.nickName || ''; }; - const buildUserItem = (user, dept) => ({ - id: String(user.userId), - name: formatUserDisplayName(user), + const formatUserPickerLabel = (user) => { + const name = formatMemberDisplayName(user); + return isLockedIdentity(user.identityId) ? `${name}(默认)` : name; + }; + + const buildMemberItem = (user, dept) => ({ + id: String(user.identityId), + userId: user.userId, + name: formatMemberDisplayName(user), deptName: dept.deptName }); + const buildMemberItemFromDetail = (user) => ({ + id: String(user.identityId || user.userId), + name: formatMemberDisplayName(user), + deptName: user.deptName || '' + }); + const buildSelectedPersonText = (users) => { if (users.length === 0) return ''; if (users.length <= 2) { @@ -647,12 +740,12 @@ }); const managerPickerSelectedText = computed(() => { - const users = getManagerUsersByIds(managerPickerSelectedIds.value); + const users = getManagersByIdentityIds(managerPickerSelectedIdentityIds.value); return buildSelectedPersonText(users); }); const managerPickerSelectedSet = computed(() => { - return new Set(managerPickerSelectedIds.value.map((id) => String(id))); + return new Set(managerPickerSelectedIdentityIds.value.map((id) => String(id))); }); const currentDeptUsers = computed(() => { @@ -661,80 +754,80 @@ }); const userPickerSelectedText = computed(() => { - const users = getUsersByIds(userPickerSelectedIds.value); + const users = getMembersByIdentityIds(userPickerSelectedIdentityIds.value); return buildSelectedPersonText(users); }); const userPickerSelectedSet = computed(() => { - return new Set(userPickerSelectedIds.value.map((id) => String(id))); + return new Set(userPickerSelectedIdentityIds.value.map((id) => String(id))); }); - const getManagerUsersByIds = (ids) => { - let users = getUsersByIdsFromTree(ids, managerDeptList.value); - if (users.length < ids.length) { - const userDeptUsers = getUsersByIdsFromTree(ids, deptList.value); - const userMap = new Map(users.map((user) => [user.id, user])); - userDeptUsers.forEach((user) => { - if (!userMap.has(user.id)) userMap.set(user.id, user); + const getManagersByIdentityIds = (ids) => { + let members = getMembersByIdentityIdsFromTree(ids, managerDeptList.value); + if (members.length < ids.length) { + const deptMembers = getMembersByIdentityIdsFromTree(ids, deptList.value); + const memberMap = new Map(members.map((member) => [member.id, member])); + deptMembers.forEach((member) => { + if (!memberMap.has(member.id)) memberMap.set(member.id, member); }); - users = ids.map((id) => userMap.get(String(id))).filter(Boolean); + members = ids.map((id) => memberMap.get(String(id))).filter(Boolean); } - return mergeUsersFromDetailPool(ids, users); + return mergeMembersFromDetailPool(ids, members); }; - const getUsersByIds = (ids) => { - let users = getUsersByIdsFromTree(ids, deptList.value); - return mergeUsersFromDetailPool(ids, users); + const getMembersByIdentityIds = (ids) => { + let members = getMembersByIdentityIdsFromTree(ids, deptList.value); + return mergeMembersFromDetailPool(ids, members); }; - const syncSelectedManagersFromIds = (ids) => { - selectedManagers.value = getManagerUsersByIds(ids); + const syncSelectedManagersFromIdentityIds = (ids) => { + selectedManagers.value = getManagersByIdentityIds(ids); }; - const syncSelectedUsersFromIds = (ids) => { - selectedUsers.value = getUsersByIds(ids); + const syncSelectedMembersFromIdentityIds = (ids) => { + selectedUsers.value = getMembersByIdentityIds(ids); }; const getManagerDeptSelectedCount = (dept) => { if (!dept.users?.length) return 0; - const selectedSet = new Set(managerPickerSelectedIds.value.map(String)); - return dept.users.filter((user) => selectedSet.has(String(user.userId))).length; + const selectedSet = new Set(managerPickerSelectedIdentityIds.value.map(String)); + return dept.users.filter((user) => selectedSet.has(String(user.identityId))).length; }; const getDeptSelectedCount = (dept) => { if (!dept.users?.length) return 0; - const selectedSet = new Set(userPickerSelectedIds.value.map(String)); - return dept.users.filter((user) => selectedSet.has(String(user.userId))).length; + const selectedSet = new Set(userPickerSelectedIdentityIds.value.map(String)); + return dept.users.filter((user) => selectedSet.has(String(user.identityId))).length; }; - function onManagerCheckChange(userId, checked) { - const id = String(userId); + function onManagerCheckChange(identityId, checked) { + const id = String(identityId); if (checked) { if (!managerPickerSelectedSet.value.has(id)) { - managerPickerSelectedIds.value = [...managerPickerSelectedIds.value, id]; + managerPickerSelectedIdentityIds.value = [...managerPickerSelectedIdentityIds.value, id]; } return; } - managerPickerSelectedIds.value = managerPickerSelectedIds.value.filter((item) => String(item) !== id); + managerPickerSelectedIdentityIds.value = managerPickerSelectedIdentityIds.value.filter((item) => String(item) !== id); } - function onUserCheckChange(userId, checked) { - const id = String(userId); - if (!checked && isLockedUser(id)) { + function onUserCheckChange(identityId, checked) { + const id = String(identityId); + if (!checked && isLockedIdentity(id)) { uni.showToast({ title: '默认整改责任人不可取消', icon: 'none' }); return; } if (checked) { if (!userPickerSelectedSet.value.has(id)) { - userPickerSelectedIds.value = [...userPickerSelectedIds.value, id]; + userPickerSelectedIdentityIds.value = [...userPickerSelectedIdentityIds.value, id]; } return; } - userPickerSelectedIds.value = userPickerSelectedIds.value.filter((item) => String(item) !== id); + userPickerSelectedIdentityIds.value = userPickerSelectedIdentityIds.value.filter((item) => String(item) !== id); } const openManagerPopup = () => { - managerPickerSelectedIds.value = [...selectedManagerIds.value]; + managerPickerSelectedIdentityIds.value = [...selectedManagerIdentityIds.value]; const firstDeptWithUsers = managerDeptList.value.findIndex((dept) => dept.users?.length > 0); activeManagerDeptIndex.value = firstDeptWithUsers >= 0 ? firstDeptWithUsers : 0; showManagerPopup.value = true; @@ -745,7 +838,7 @@ }; const openUserPopup = () => { - userPickerSelectedIds.value = mergeLockedUserIds(selectedUserIds.value); + userPickerSelectedIdentityIds.value = mergeLockedIdentityIds(selectedIdentityIds.value); const firstDeptWithUsers = deptList.value.findIndex((dept) => dept.users?.length > 0); activeDeptIndex.value = firstDeptWithUsers >= 0 ? firstDeptWithUsers : 0; showUserPopup.value = true; @@ -757,16 +850,16 @@ // 确认选择安全管理人员 const confirmManagerSelect = () => { - selectedManagerIds.value = managerPickerSelectedIds.value.map((id) => String(id)); - syncSelectedManagersFromIds(selectedManagerIds.value); + selectedManagerIdentityIds.value = managerPickerSelectedIdentityIds.value.map((id) => String(id)); + syncSelectedManagersFromIdentityIds(selectedManagerIdentityIds.value); showManagerPopup.value = false; }; // 确认选择整改责任人 const confirmUserSelect = () => { - userPickerSelectedIds.value = mergeLockedUserIds(userPickerSelectedIds.value); - selectedUserIds.value = userPickerSelectedIds.value.map((id) => String(id)); - syncSelectedUsersFromIds(selectedUserIds.value); + userPickerSelectedIdentityIds.value = mergeLockedIdentityIds(userPickerSelectedIdentityIds.value); + selectedIdentityIds.value = userPickerSelectedIdentityIds.value.map((id) => String(id)); + syncSelectedMembersFromIdentityIds(selectedIdentityIds.value); showUserPopup.value = false; }; @@ -777,11 +870,11 @@ if (res.code === 0 && res.data) { managerDeptList.value = res.data; deptList.value = res.data; - if (selectedManagerIds.value.length > 0) { - syncSelectedManagersFromIds(selectedManagerIds.value); + if (selectedManagerIdentityIds.value.length > 0) { + syncSelectedManagersFromIdentityIds(selectedManagerIdentityIds.value); } - if (selectedUserIds.value.length > 0) { - syncSelectedUsersFromIds(selectedUserIds.value); + if (selectedIdentityIds.value.length > 0) { + syncSelectedMembersFromIdentityIds(selectedIdentityIds.value); } } } catch (error) { @@ -823,8 +916,8 @@ const fetchPersonnelLists = async () => { await fetchRelatedDeptUsers(); - if (selectedManagerIds.value.length > 0) { - syncSelectedManagersFromIds(selectedManagerIds.value); + if (selectedManagerIdentityIds.value.length > 0) { + syncSelectedManagersFromIdentityIds(selectedManagerIdentityIds.value); } }; @@ -839,38 +932,53 @@ } }); - // 提交整改 - // 真正的提交接口请求 - const executeSubmit = async () => { - // 构建附件列表 - const attachments = fileList1.value - .filter((f) => f.status === 'success') - .map((file) => buildAttachmentItem(file)); - + // 构建提交/保存共用的整改业务字段 + const buildSharedRectifyParams = (attachments) => { const params = { - hazardId: hazardId.value, - assignId: assignId.value, rectifyPlan: formData.rectifyPlan, rectificationMeasures: formData.rectificationMeasures, controlMeasures: formData.controlMeasures, rectifyResult: formData.rectifyResult, planCost: Number(formData.planCost) || 0, actualCost: Number(formData.actualCost) || 0, - attachments: attachments, - manageIds: selectedManagerIds.value.map((id) => Number(id)), - memberIds: selectedUserIds.value.map((id) => Number(id)), + attachments, + managerIds: selectedManagerIdentityIds.value.map((id) => Number(id)), + memberIds: selectedIdentityIds.value.map((id) => Number(id)), rectifyTime: selectedRectifyTime.value || formatDateValue(rectifyTimeValue.value), signPath: signatureServerPath.value || '', sendMsgFlag: sendMsgFlag.value }; - - // 编辑模式需要传递rectifyId - if (rectifyId.value) { - params.rectifyId = rectifyId.value; + if (selectedDeadlineDate.value) { + params.deadline = selectedDeadlineDate.value; } + return params; + }; + + // 真正的提交/保存接口请求 + const executeSubmit = async () => { + const attachments = fileList1.value + .filter((f) => f.status === 'success') + .map((file) => buildAttachmentItem(file)); try { - const res = await submitRectification(params); + let res; + if (isEdit.value) { + const updateParams = { + rectifyId: Number(rectifyId.value), + hazardId: hazardId.value, + assignId: assignId.value, + ...buildSharedRectifyParams(attachments) + }; + res = await updateRectification(updateParams); + } else { + const params = { + hazardId: hazardId.value, + assignId: assignId.value, + ...buildSharedRectifyParams(attachments) + }; + res = await submitRectification(params); + } + uni.hideLoading(); if (res.code === 0) { clearDraft(false); @@ -889,7 +997,7 @@ } } catch (error) { uni.hideLoading(); - console.error('提交整改失败:', error); + console.error(isEdit.value ? '保存整改失败:' : '提交整改失败:', error); uni.showToast({ title: '操作失败', icon: 'none' @@ -1051,7 +1159,7 @@ planCost: Number(formData.planCost) || 0, actualCost: Number(formData.actualCost) || 0, attachments: attachments, - memberIds: selectedUserIds.value.map(id => Number(id)) + memberIds: selectedIdentityIds.value.map(id => Number(id)) }; // 编辑模式需要传递rectifyId @@ -1119,26 +1227,30 @@ // 保存hazardId和assignId hazardId.value = data.hazardId || ''; assignId.value = data.assignId || ''; + const resolvedTaskId = resolveTaskIdFromDetail(data); + if (resolvedTaskId) { + taskId.value = resolvedTaskId; + } detailPersonPool.value = [ ...(Array.isArray(data.managers) ? data.managers : []), ...(Array.isArray(data.members) ? data.members : []) ]; // 先解析人员 ID,再拉取候选列表并回显 - const managerIdArr = resolveManagerIdsFromDetail(data); - const memberIdArr = parseIdList(data.memberIds); - if (managerIdArr.length > 0) { - selectedManagerIds.value = managerIdArr; + const managerIdentityIdArr = resolveManagerIdentityIdsFromDetail(data); + if (managerIdentityIdArr.length > 0) { + selectedManagerIdentityIds.value = managerIdentityIdArr; } - if (memberIdArr.length > 0) { - selectedUserIds.value = memberIdArr; - } else if (data.rectifierId) { - selectedUserIds.value = [String(data.rectifierId)]; + const memberIdentityIdArr = parseIdList(data.memberIds ?? data.memberIdentityIds); + if (memberIdentityIdArr.length > 0) { + selectedIdentityIds.value = memberIdentityIdArr; + } else if (data.rectifierIdentityId) { + selectedIdentityIds.value = [String(data.rectifierIdentityId)]; } - if (data.assigneeId) { - setLockedDefaultUsers(data.assigneeId); - } else if (data.rectifierId) { - setLockedDefaultUsers(data.rectifierId); + if (data.assigneeIdentityId) { + setLockedDefaultIdentities(data.assigneeIdentityId); + } else if (data.rectifierIdentityId) { + setLockedDefaultIdentities(data.rectifierIdentityId); } await fetchPersonnelLists(); @@ -1335,9 +1447,12 @@ if (options.assignId) { assignId.value = options.assignId; } + if (options.taskId) { + taskId.value = options.taskId; + } - if (!options.rectifyId && options.assigneeId) { - applyAssigneeFromOptions(options.assigneeId, options.assigneeName); + if (!options.rectifyId && options.assigneeIdentityId) { + applyAssigneeFromOptions(options.assigneeId, options.assigneeName, options.assigneeIdentityId); } // 在hazardId赋值后调用,确保有值 @@ -1358,6 +1473,10 @@ if (options.deadline) { applyDeadlineFromOptions(options.deadline); } + + if (!options.rectifyId) { + fetchNextStep(); + } }); diff --git a/pages/index/index.vue b/pages/index/index.vue index 761389c..cf02d05 100644 --- a/pages/index/index.vue +++ b/pages/index/index.vue @@ -25,17 +25,18 @@ - + - - + + @@ -44,7 +45,7 @@ - + 我的检查计划 @@ -121,8 +122,8 @@ 加载更多 - - + + 我的隐患排查 @@ -140,11 +141,11 @@ - + 暂无隐患数据 - + @@ -183,10 +184,74 @@ class="round cu-btn bg-blue" @click.stop="goRectification(item)">立即整改 - + + + + + + + + + + + 我的工作台 + + + {{ tab.label }} + + 加载中... + + 暂无数据 + + + + + + {{ item.title }} + + {{ item.levelName }} + + {{ item.address }} + + + 隐患来源: + {{ item.source }} + + + 隐患状态: + {{ item.statusName }} + + + 发现时间: + {{ item.createdAt }} + + + + @@ -197,10 +262,24 @@ diff --git a/request/api.js b/request/api.js index 93c128a..453f857 100644 --- a/request/api.js +++ b/request/api.js @@ -118,6 +118,15 @@ export function getHazardDetail(hazardId) { method: 'GET' }); } + +/** 隐患流程链(动态 nodes 时间线) */ +export function getHazardProcessChain(hazardId) { + return requestAPI({ + url: '/frontend/hazard/process-chain', + method: 'GET', + data: { hazardId } + }); +} //获取隐患排查列表 export function getHiddenDangerList(params) { return requestAPI({ @@ -267,6 +276,33 @@ export function getWriteOffApplyDetail(applyId) { method: 'GET' }); } +//获取销号审批表单数据(按隐患ID) +export function getWriteoffForm(hazardId) { + return requestAPI({ + url: '/frontend/hazard/writeoff/form', + method: 'GET', + data: { hazardId } + }); +} +//销号审核通过 +export function writeoffApprove(params) { + return requestAPI({ + url: '/admin/hazard/writeoff/approve', + method: 'POST', + data: { + ...params, + type: 'agree' + } + }); +} +//销号驳回 +export function writeoffReject(params) { + return requestAPI({ + url: '/admin/hazard/writeoff/reject', + method: 'POST', + data: params + }); +} //验收整改 export function acceptanceRectification(params) { @@ -426,14 +462,32 @@ export function getCheckItemListDetail(params) { }); } -// 根据部门id获取用户列表 -export function getDeptUsers(deptId) { +// 根据部门id获取用户列表;可选 params.type:1=部门全部用户(默认),2=部门 manage 角色用户,3=非 manage 角色用户(验收页非快速审批用) +export function getDeptUsers(deptId, params = {}) { return requestAPI({ url: deptId ? `/admin/user/dept/users/${deptId}` : '/admin/user/dept/users', + method: 'GET', + data: params + }); +} + +/** 上报时获取上级范围内可选处理人(deptId 取当前身份 userIdentity.deptId) */ +export function getDeptSuperiorScope(deptId) { + return requestAPI({ + url: `/admin/user/dept/${deptId}/superior-scope`, method: 'GET' }); } +/** 流程审批:获取下一步处理人候选(部门树 + users) */ +export function getFlowApproverCandidates(taskId) { + return requestAPI({ + url: '/admin/user/flow/approver-candidates', + method: 'GET', + data: { taskId } + }); +} + // 获取子级部门列表(从当前到最后一层) export function getDeptChildren() { return requestAPI({ @@ -487,4 +541,42 @@ export function generateWriteoffContent(params) { data: params, loadingText: 'AI生成销号方案中' }); +} + +// 预览流程下一节点 +export function getFlowNextNodes(params) { + return requestAPI({ + url: '/flow/task/next-nodes', + method: 'POST', + data: params, + loadingText: false + }); +} + +// 流程任务审批(同意/驳回/上报/重新整改等) +export function flowTaskApprove(params) { + return requestAPI({ + url: '/flow/task/approve', + method: 'POST', + data: params, + loadingText: false + }); +} + +// 查询当前身份的 Flowable 待办(候选组 + assignee 并集) +export function getFlowTodoList(params) { + return requestAPI({ + url: '/frontend/flow/todo', + method: 'GET', + data: params + }); +} + +// 查询当前用户/身份的已办(办结人匹配用户 ID 或身份 ID) +export function getFlowDoneList(params) { + return requestAPI({ + url: '/frontend/flow/done', + method: 'GET', + data: params + }); } \ No newline at end of file diff --git a/request/identity.js b/request/identity.js new file mode 100644 index 0000000..af1a295 --- /dev/null +++ b/request/identity.js @@ -0,0 +1,26 @@ +import { requestAPI } from './request.js'; + +/** 获取当前用户身份列表及当前生效身份 */ +export function getMyIdentity() { + return requestAPI({ + url: '/system/identity/my', + method: 'GET' + }); +} + +/** 切换当前用户身份 */ +export function switchIdentity(identityId) { + return requestAPI({ + url: '/system/identity/switch', + method: 'POST', + data: { identityId } + }); +} + +/** 设置默认身份 */ +export function setDefaultIdentity(identityId) { + return requestAPI({ + url: `/system/identity/setDefault/${identityId}`, + method: 'PUT' + }); +} diff --git a/request/request.js b/request/request.js index f609c00..8ca31a5 100644 --- a/request/request.js +++ b/request/request.js @@ -2,10 +2,10 @@ import Request from './luch-request/index.js'; // 基础的url -// const baseUrl = 'https://yingji.hexieapi.com/prod-api'; +const baseUrl = 'https://yingji.hexieapi.com/prod-api'; // const baseUrl = 'http://192.168.1.168:5004'; //廖哥本地 - const baseUrl = 'http://192.168.1.140:5004'; //超哥本地 - // const baseUrl = 'http://192.168.1.158:7003/prod-api'; //测试环境 + // const baseUrl = 'http://192.168.1.140:5004'; //超哥本地 +// const baseUrl = 'http://192.168.1.158:7003/prod-api'; //测试环境 // 图片/文件资源域名:去掉 /prod-api,便于 / previewImage / downloadFile 直接访问 diff --git a/static/qrcode_safecheck_433125.png b/static/qrcode_safecheck_433125.png new file mode 100644 index 0000000..6171455 Binary files /dev/null and b/static/qrcode_safecheck_433125.png differ diff --git a/static/yinhuan_detail/bumenshenpi_selected.png b/static/yinhuan_detail/bumenshenpi_selected.png new file mode 100644 index 0000000..8b5dda6 Binary files /dev/null and b/static/yinhuan_detail/bumenshenpi_selected.png differ diff --git a/static/yinhuan_detail/bumenshenpi_unselected.png b/static/yinhuan_detail/bumenshenpi_unselected.png new file mode 100644 index 0000000..2d0482c Binary files /dev/null and b/static/yinhuan_detail/bumenshenpi_unselected.png differ diff --git a/static/yinhuan_detail/fenguanshenpi_selected.png b/static/yinhuan_detail/fenguanshenpi_selected.png new file mode 100644 index 0000000..90b6e17 Binary files /dev/null and b/static/yinhuan_detail/fenguanshenpi_selected.png differ diff --git a/static/yinhuan_detail/fenguanshenpi_unselected.png b/static/yinhuan_detail/fenguanshenpi_unselected.png new file mode 100644 index 0000000..7de1259 Binary files /dev/null and b/static/yinhuan_detail/fenguanshenpi_unselected.png differ diff --git a/static/yinhuan_detail/zhuguanshenpi_selected.png b/static/yinhuan_detail/zhuguanshenpi_selected.png new file mode 100644 index 0000000..5ef9b5c Binary files /dev/null and b/static/yinhuan_detail/zhuguanshenpi_selected.png differ diff --git a/static/yinhuan_detail/zhuguanshenpi_unselected.png b/static/yinhuan_detail/zhuguanshenpi_unselected.png new file mode 100644 index 0000000..17ec469 Binary files /dev/null and b/static/yinhuan_detail/zhuguanshenpi_unselected.png differ diff --git a/uni_modules/uview-plus/components/u-tree/tree-node.vue b/uni_modules/uview-plus/components/u-tree/tree-node.vue index f82d9e3..ec5aa1f 100644 --- a/uni_modules/uview-plus/components/u-tree/tree-node.vue +++ b/uni_modules/uview-plus/components/u-tree/tree-node.vue @@ -1,27 +1,35 @@