Merge branch 'dev_NEW_pro' of http://114.132.189.42:9002/r/product-inventory-management into dev_NEW_pro
| | |
| | | "logo": "logo/QXYLogo.png", |
| | | "favicon": "favicon/QXYfavicon.ico" |
| | | }, |
| | | "HQJC": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "华强建材管理系统", |
| | | "VITE_BASE_API": "http://36.134.77.64:9001", |
| | | "VITE_JAVA_API": "http://36.134.77.64:9000" |
| | | }, |
| | | "logo": "logo/HQJCLogo.png", |
| | | "favicon": "favicon/HQJCfavicon.ico" |
| | | }, |
| | | "XCDQ": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "旭晨电器管理系统", |
| | |
| | | import { |
| | | createEmptyNode, |
| | | formatDisplayTime, |
| | | mapNodesFromApi, |
| | | mapSignModeFromApi, |
| | | mapSignModeToApi, |
| | | normalizeFlowNodes, |
| | | nodeSignModeLabel, |
| | | } from "../approve-template/approveTemplateConstants.js"; |
| | | import { createEmptyNode, formatDisplayTime, mapNodesFromApi, mapSignModeFromApi, mapSignModeToApi, normalizeFlowNodes, nodeSignModeLabel } from "../approve-template/approveTemplateConstants.js"; |
| | | import { buildFormPayloadFromFields, parseFormConfigToData } from "../approve-template/formConfigUtils.js"; |
| | | import { |
| | | isDynamicOptionSource, |
| | | resolveSelectDisplayLabel, |
| | | } from "../approve-template/selectOptionSource.js"; |
| | | import { |
| | | appendDotNotationQuery, |
| | | buildApprovalInstanceSearchDto, |
| | | } from "../approve-shared/approvalInstanceListSearch.js"; |
| | | import { isDynamicOptionSource, resolveSelectDisplayLabel } from "../approve-template/selectOptionSource.js"; |
| | | import { appendDotNotationQuery, buildApprovalInstanceSearchDto } from "../approve-shared/approvalInstanceListSearch.js"; |
| | | |
| | | /** 审批类型(与后端字段 approvalType 对齐,后期可同步) */ |
| | | export const APPROVAL_TYPE_OPTIONS = [ |
| | |
| | | if (upper === "APPROVED" || upper === "APPROVE" || upper === "PASS" || upper === "AGREE") { |
| | | return "approved"; |
| | | } |
| | | if ( |
| | | upper === "REJECTED" || |
| | | upper === "REJECT" || |
| | | upper === "REFUSE" || |
| | | upper === "REFUSED" || |
| | | upper === "DENIED" |
| | | ) { |
| | | if (upper === "REJECTED" || upper === "REJECT" || upper === "REFUSE" || upper === "REFUSED" || upper === "DENIED") { |
| | | return "rejected"; |
| | | } |
| | | if (upper === "CANCELLED" || upper === "CANCEL" || upper === "REVOKED") return "cancelled"; |
| | | if ( |
| | | upper === "PENDING" || |
| | | upper === "IN_PROGRESS" || |
| | | upper === "PROCESSING" || |
| | | upper === "RUNNING" || |
| | | upper === "WAIT" || |
| | | upper === "WAITING" |
| | | ) { |
| | | if (upper === "PENDING" || upper === "IN_PROGRESS" || upper === "PROCESSING" || upper === "RUNNING" || upper === "WAIT" || upper === "WAITING") { |
| | | return "pending"; |
| | | } |
| | | if (s.includes("草稿")) return "draft"; |
| | |
| | | } |
| | | const tasks = row.tasks; |
| | | if (Array.isArray(tasks) && tasks.length) { |
| | | const rejected = tasks.some((t) => |
| | | normalizeApprovalStatusKey(t?.status ?? t?.taskStatus) === "rejected" |
| | | ); |
| | | const rejected = tasks.some(t => normalizeApprovalStatusKey(t?.status ?? t?.taskStatus) === "rejected"); |
| | | if (rejected) return "REJECTED"; |
| | | const allApproved = tasks.every((t) => |
| | | normalizeApprovalStatusKey(t?.status ?? t?.taskStatus) === "approved" |
| | | ); |
| | | const allApproved = tasks.every(t => normalizeApprovalStatusKey(t?.status ?? t?.taskStatus) === "approved"); |
| | | if (allApproved) return "APPROVED"; |
| | | } |
| | | return ""; |
| | |
| | | /** 后端 records → 时间线展示结构 */ |
| | | export function mapRecordsFromApi(records) { |
| | | const list = Array.isArray(records) ? records : []; |
| | | return list.map((r) => ({ |
| | | return list.map(r => ({ |
| | | id: r.id, |
| | | operatorName: r.approverName || r.operatorName || r.createUserName || "", |
| | | result: mapRecordResultFromApi(r.approveAction ?? r.action ?? r.status), |
| | |
| | | const list = Array.isArray(tasks) ? tasks : []; |
| | | if (!list.length) return []; |
| | | const byLevel = new Map(); |
| | | list.forEach((t) => { |
| | | list.forEach(t => { |
| | | const level = Number(t.levelNo ?? t.taskLevel ?? t.nodeOrder ?? 1); |
| | | if (!byLevel.has(level)) { |
| | | byLevel.set(level, { |
| | |
| | | node.signMode = mapSignModeFromApi(t.approveType); |
| | | } |
| | | }); |
| | | return [...byLevel.entries()] |
| | | .sort(([a], [b]) => a - b) |
| | | .map(([, node]) => node); |
| | | return [...byLevel.entries()].sort(([a], [b]) => a - b).map(([, node]) => node); |
| | | } |
| | | |
| | | /** 页面 flowNodes → 后端 tasks */ |
| | | export function mapFlowNodesToTasks(flowNodes, { instanceId, templateId } = {}) { |
| | | const nodes = normalizeFlowNodes(flowNodes); |
| | | const tasks = []; |
| | | nodes.forEach((n) => { |
| | | nodes.forEach(n => { |
| | | const levelNo = n.nodeOrder ?? 1; |
| | | const approveType = mapSignModeToApi(n.signMode); |
| | | n.approvers.forEach((a, idx) => { |
| | |
| | | return String(val); |
| | | } |
| | | if (field?.type === "select" && field.options?.length) { |
| | | const hit = field.options.find((o) => String(o.value) === String(val)); |
| | | const hit = field.options.find(o => String(o.value) === String(val)); |
| | | return hit?.label || String(val); |
| | | } |
| | | if (Array.isArray(val)) return val.join(" 至 "); |
| | |
| | | }; |
| | | if (!fields.length && Object.keys(formPayload).length) { |
| | | fields = Object.keys(formPayload) |
| | | .filter((k) => k && k !== "summary") |
| | | .map((k) => ({ |
| | | .filter(k => k && k !== "summary") |
| | | .map(k => ({ |
| | | key: k, |
| | | label: k, |
| | | type: guessFieldTypeFromValue(formPayload[k]), |
| | |
| | | export function buildInstanceDto({ submitForm, activeTemplate, userStore, flowNodes, existingRow }) { |
| | | const payload = submitForm?.formPayload || {}; |
| | | const tpl = activeTemplate || {}; |
| | | const title = |
| | | String(payload.summary || payload.title || "").trim() || |
| | | tpl.label || |
| | | submitForm?.templateName || |
| | | "审批申请"; |
| | | const title = String(payload.summary || payload.title || "").trim() || tpl.label || submitForm?.templateName || "审批申请"; |
| | | const templateId = submitForm?.templateId || tpl.templateId; |
| | | const instanceId = existingRow?.id ?? submitForm?.instanceId; |
| | | const taskList = mapFlowNodesToTasks(flowNodes || submitForm?.flowNodes, { |
| | |
| | | tasks: taskList, |
| | | }; |
| | | |
| | | const attachments = |
| | | (Array.isArray(submitForm?.storageBlobDTOs) && submitForm.storageBlobDTOs.length |
| | | ? submitForm.storageBlobDTOs |
| | | : null) || tpl.storageBlobDTOs; |
| | | const attachments = (Array.isArray(submitForm?.storageBlobDTOs) && submitForm.storageBlobDTOs.length ? submitForm.storageBlobDTOs : null) || tpl.storageBlobDTOs; |
| | | if (attachments?.length) dto.storageBlobDTOs = attachments; |
| | | |
| | | if (isUpdate) { |
| | | dto.id = existingRow?.id ?? submitForm?.instanceId; |
| | | dto.instanceNo = existingRow?.instanceNo ?? submitForm?.instanceNo ?? ""; |
| | | dto.status = |
| | | submitForm?.saveStatusApi || |
| | | existingRow?.statusRaw || |
| | | mapInstanceStatusToApi(existingRow?.approvalStatus) || |
| | | "PENDING"; |
| | | dto.status = submitForm?.saveStatusApi || existingRow?.statusRaw || mapInstanceStatusToApi(existingRow?.approvalStatus) || "PENDING"; |
| | | dto.currentLevel = existingRow?.currentLevel ?? submitForm?.currentLevel ?? 1; |
| | | dto.applicantId = existingRow?.applicantId ?? existingRow?.applicantNo; |
| | | dto.applicantName = existingRow?.applicantName || ""; |
| | |
| | | /** 页面 approvalStatus → 后端 status */ |
| | | export function mapInstanceStatusToApi(approvalStatus) { |
| | | const key = normalizeApprovalStatusKey(approvalStatus); |
| | | const hit = APPROVAL_STATUS_OPTIONS.find((x) => x.value === key); |
| | | const hit = APPROVAL_STATUS_OPTIONS.find(x => x.value === key); |
| | | return hit?.api || "PENDING"; |
| | | } |
| | | |
| | |
| | | const resolved = resolveInstanceFormFields(row); |
| | | const { fields, formPayload, templateSnapshot } = resolved; |
| | | const tasks = Array.isArray(row.tasks) ? row.tasks : []; |
| | | const flowNodes = tasks.length |
| | | ? mapTasksToFlowNodes(tasks) |
| | | : mapNodesFromApi(row.nodes || row.flowNodes); |
| | | const flowNodes = tasks.length ? mapTasksToFlowNodes(tasks) : mapNodesFromApi(row.nodes || row.flowNodes); |
| | | const approvalRecords = mapRecordsFromApi(row.records); |
| | | return { |
| | | id: row.id, |
| | |
| | | templateSnapshot, |
| | | tasks, |
| | | records: Array.isArray(row.records) ? row.records : [], |
| | | storageBlobVOList: row.storageBlobVOList || [], |
| | | storageBlobDTOs: row.storageBlobVOList || row.storageBlobDTOs || [], |
| | | flowNodes, |
| | | approvalFlowNodes: [], |
| | | currentNodeIndex: 0, |
| | | approvalRecords, |
| | | rejectReason: |
| | | approvalRecords.find((r) => r.result === "rejected")?.opinion || "", |
| | | rejectReason: approvalRecords.find(r => r.result === "rejected")?.opinion || "", |
| | | }; |
| | | } |
| | | |
| | |
| | | }; |
| | | } |
| | | |
| | | export function buildApprovalInstanceListParams({ |
| | | page, |
| | | searchForm, |
| | | businessType, |
| | | extraParams, |
| | | }) { |
| | | export function buildApprovalInstanceListParams({ page, searchForm, businessType, extraParams }) { |
| | | const dto = buildApprovalInstanceSearchDto(searchForm, extraParams); |
| | | const bizType = businessType ?? searchForm?.businessType; |
| | | if (bizType != null && bizType !== "") { |
| | |
| | | } |
| | | |
| | | export function approvalTypeLabel(v) { |
| | | return APPROVAL_TYPE_OPTIONS.find((x) => x.value === v)?.label || v || "—"; |
| | | return APPROVAL_TYPE_OPTIONS.find(x => x.value === v)?.label || v || "—"; |
| | | } |
| | | |
| | | export function approvalTypeStyle(v) { |
| | | const hit = APPROVAL_TYPE_OPTIONS.find((x) => x.value === v); |
| | | const hit = APPROVAL_TYPE_OPTIONS.find(x => x.value === v); |
| | | if (!hit) return {}; |
| | | return { |
| | | backgroundColor: hit.cellBg, |
| | |
| | | |
| | | export function approvalStatusLabel(v) { |
| | | const key = normalizeApprovalStatusKey(v); |
| | | return APPROVAL_STATUS_OPTIONS.find((x) => x.value === key)?.label || "—"; |
| | | return APPROVAL_STATUS_OPTIONS.find(x => x.value === key)?.label || "—"; |
| | | } |
| | | |
| | | /** 业务申请页状态文案:PENDING→进行中 APPROVED→已完成 REJECTED→已驳回 */ |
| | |
| | | * 进行中(PENDING)、已完成(APPROVED) 不可修改;已驳回、已撤销等可修改 |
| | | */ |
| | | export function canEditBusinessInstanceRow(row) { |
| | | const key = normalizeApprovalStatusKey( |
| | | row?.approvalStatus ?? row?.statusRaw ?? row?.status |
| | | ); |
| | | const key = normalizeApprovalStatusKey(row?.approvalStatus ?? row?.statusRaw ?? row?.status); |
| | | return key !== "pending" && key !== "approved"; |
| | | } |
| | | |
| | |
| | | /** 列表行 → 编辑表单(仅用行数据回显) */ |
| | | export function buildEditFormFromInstanceRow(row) { |
| | | const { fields, formPayload, templateSnapshot } = resolveInstanceFormFields(row); |
| | | const normalized = normalizeFlowNodes( |
| | | row?.flowNodes?.length ? row.flowNodes : mapTasksToFlowNodes(row?.tasks) |
| | | ); |
| | | const flowNodes = normalized.length |
| | | ? JSON.parse(JSON.stringify(normalized)) |
| | | : [createEmptyNode(1)]; |
| | | const normalized = normalizeFlowNodes(row?.flowNodes?.length ? row.flowNodes : mapTasksToFlowNodes(row?.tasks)); |
| | | const flowNodes = normalized.length ? JSON.parse(JSON.stringify(normalized)) : [createEmptyNode(1)]; |
| | | |
| | | return { |
| | | templateKey: String(row?.templateId || ""), |
| | |
| | | formPayload, |
| | | flowNodes, |
| | | templateAttachments: initTemplateAttachmentsFromSnapshot(templateSnapshot), |
| | | storageBlobDTOs: row?.storageBlobDTOs?.length |
| | | ? JSON.parse(JSON.stringify(row.storageBlobDTOs)) |
| | | : [], |
| | | storageBlobDTOs: (row?.storageBlobDTOs?.length ? row.storageBlobDTOs : row?.storageBlobVOList || []).map(f => JSON.parse(JSON.stringify(f))), |
| | | }; |
| | | } |
| | | |
| | |
| | | const tpl = templateOverride || null; |
| | | const payload = tpl?.fields?.length ? buildFormPayloadFromFields(tpl.fields) : { summary: "" }; |
| | | const normalized = normalizeFlowNodes(flowNodesOverride); |
| | | const flowNodes = normalized.length |
| | | ? JSON.parse(JSON.stringify(normalized)) |
| | | : [createEmptyNode(1)]; |
| | | const flowNodes = normalized.length ? JSON.parse(JSON.stringify(normalized)) : [createEmptyNode(1)]; |
| | | return { |
| | | templateKey: templateKey || "", |
| | | templateId: tpl?.templateId || "", |
| | |
| | | formFieldDefs: tpl?.fields || [], |
| | | formPayload: payload, |
| | | flowNodes, |
| | | templateAttachments: tpl?.storageBlobDTOs |
| | | ? JSON.parse(JSON.stringify(tpl.storageBlobDTOs)) |
| | | : [], |
| | | templateAttachments: tpl?.storageBlobDTOs ? JSON.parse(JSON.stringify(tpl.storageBlobDTOs)) : [], |
| | | storageBlobDTOs: [], |
| | | }; |
| | | } |
| | |
| | | <div class="approve-detail-panel"> |
| | | <div class="detail-block"> |
| | | <div class="detail-block-title">基本信息</div> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions :column="2" |
| | | border> |
| | | <el-descriptions-item label="业务单号">{{ row.bizId || row.id || "—" }}</el-descriptions-item> |
| | | <el-descriptions-item label="审批状态"> |
| | | <el-tag :type="approvalStatusTagType(row.approvalStatus)" size="small" effect="plain"> |
| | | <el-tag :type="approvalStatusTagType(row.approvalStatus)" |
| | | size="small" |
| | | effect="plain"> |
| | | {{ approvalStatusLabel(row.approvalStatus) }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="审批类型"> |
| | | <span class="approve-type-cell" :style="approvalTypeStyle(row.approvalType)"> |
| | | <span class="approve-type-cell" |
| | | :style="approvalTypeStyle(row.approvalType)"> |
| | | {{ approvalTypeLabel(row.approvalType) }} |
| | | </span> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="申请人编号">{{ row.applicantNo || "—" }}</el-descriptions-item> |
| | | <el-descriptions-item label="申请人名称">{{ row.applicantName || "—" }}</el-descriptions-item> |
| | | <el-descriptions-item label="申请摘要">{{ row.summary || "—" }}</el-descriptions-item> |
| | | <el-descriptions-item v-if="row.rejectReason" label="驳回原因" :span="2"> |
| | | <el-descriptions-item v-if="row.rejectReason" |
| | | label="驳回原因" |
| | | :span="2"> |
| | | <span class="reject-text">{{ row.rejectReason }}</span> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="创建时间" :span="2"> |
| | | <el-descriptions-item label="创建时间" |
| | | :span="2"> |
| | | {{ formatDisplayTime(row.createTime) }} |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | </div> |
| | | |
| | | <div class="detail-block"> |
| | | <div class="detail-block-title">填报内容</div> |
| | | <FormPayloadFields |
| | | :fields="formResolved.fields" |
| | | :form-payload="formResolved.formPayload" |
| | | readonly |
| | | /> |
| | | <FormPayloadFields :fields="formResolved.fields" |
| | | :form-payload="formResolved.formPayload" |
| | | readonly /> |
| | | </div> |
| | | <div v-if="attachmentList.length" |
| | | class="detail-block"> |
| | | <div class="detail-block-title">附件列表</div> |
| | | <div class="attachment-list"> |
| | | <div v-for="file in attachmentList" |
| | | :key="file.id" |
| | | class="attachment-item"> |
| | | <el-icon class="file-icon"> |
| | | <Paperclip /> |
| | | </el-icon> |
| | | <span class="file-name" |
| | | :title="file.name || file.originalFilename"> |
| | | {{ file.name || file.originalFilename }} |
| | | </span> |
| | | <div class="file-actions"> |
| | | <el-link v-if="file.previewURL || file.url" |
| | | type="primary" |
| | | :underline="false" |
| | | @click="openFile(file.previewURL || file.url)">预览</el-link> |
| | | <el-divider v-if="(file.previewURL || file.url) && file.downloadURL" |
| | | direction="vertical" /> |
| | | <el-link v-if="file.downloadURL" |
| | | type="primary" |
| | | :underline="false" |
| | | @click="openFile(file.downloadURL)">下载</el-link> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed } from "vue"; |
| | | import { formatDisplayTime } from "../../approve-template/approveTemplateConstants.js"; |
| | | import { |
| | | approvalTypeLabel, |
| | | approvalTypeStyle, |
| | | approvalStatusLabel, |
| | | approvalStatusTagType, |
| | | resolveInstanceFormFields, |
| | | } from "../approveListConstants.js"; |
| | | import FormPayloadFields from "./FormPayloadFields.vue"; |
| | | import { computed } from "vue"; |
| | | import { Paperclip } from "@element-plus/icons-vue"; |
| | | import { formatDisplayTime } from "../../approve-template/approveTemplateConstants.js"; |
| | | import { |
| | | approvalTypeLabel, |
| | | approvalTypeStyle, |
| | | approvalStatusLabel, |
| | | approvalStatusTagType, |
| | | resolveInstanceFormFields, |
| | | } from "../approveListConstants.js"; |
| | | import FormPayloadFields from "./FormPayloadFields.vue"; |
| | | |
| | | const props = defineProps({ |
| | | row: { type: Object, default: () => ({}) }, |
| | | }); |
| | | const props = defineProps({ |
| | | row: { type: Object, default: () => ({}) }, |
| | | }); |
| | | |
| | | const formResolved = computed(() => resolveInstanceFormFields(props.row)); |
| | | const formResolved = computed(() => resolveInstanceFormFields(props.row)); |
| | | |
| | | const attachmentList = computed(() => { |
| | | const list = props.row.storageBlobVOList || props.row.storageBlobDTOs || []; |
| | | return Array.isArray(list) ? list : []; |
| | | }); |
| | | |
| | | function openFile(url) { |
| | | if (!url) return; |
| | | window.open(url, "_blank"); |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .approve-detail-panel { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | .detail-block-title { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: var(--el-text-color-primary); |
| | | margin: 0 0 12px; |
| | | padding-left: 10px; |
| | | border-left: 3px solid var(--el-color-primary); |
| | | line-height: 1.4; |
| | | } |
| | | .approve-type-cell { |
| | | display: inline-block; |
| | | padding: 2px 10px; |
| | | border-radius: 4px; |
| | | font-size: 13px; |
| | | line-height: 1.5; |
| | | } |
| | | .reject-text { |
| | | color: var(--el-color-danger); |
| | | } |
| | | .approve-detail-panel { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | .detail-block-title { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: var(--el-text-color-primary); |
| | | margin: 0 0 12px; |
| | | padding-left: 10px; |
| | | border-left: 3px solid var(--el-color-primary); |
| | | line-height: 1.4; |
| | | } |
| | | .approve-type-cell { |
| | | display: inline-block; |
| | | padding: 2px 10px; |
| | | border-radius: 4px; |
| | | font-size: 13px; |
| | | line-height: 1.5; |
| | | } |
| | | .reject-text { |
| | | color: var(--el-color-danger); |
| | | } |
| | | |
| | | .attachment-list { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); |
| | | gap: 12px; |
| | | } |
| | | .attachment-item { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 10px 12px; |
| | | background-color: var(--el-fill-color-light); |
| | | border-radius: 6px; |
| | | border: 1px solid var(--el-border-color-lighter); |
| | | transition: all 0.3s; |
| | | } |
| | | .attachment-item:hover { |
| | | border-color: var(--el-color-primary-light-5); |
| | | background-color: var(--el-color-primary-light-9); |
| | | } |
| | | .file-icon { |
| | | font-size: 18px; |
| | | color: var(--el-text-color-secondary); |
| | | margin-right: 10px; |
| | | } |
| | | .file-name { |
| | | flex: 1; |
| | | font-size: 13px; |
| | | color: var(--el-text-color-primary); |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | margin-right: 12px; |
| | | } |
| | | .file-actions { |
| | | display: flex; |
| | | align-items: center; |
| | | flex-shrink: 0; |
| | | } |
| | | </style> |
| | |
| | | import { computed } from "vue"; |
| | | import { |
| | | businessApprovalStatusLabel, |
| | | businessApprovalStatusTagType, |
| | | formatFieldDisplayValue, |
| | | resolveInstanceFormFields, |
| | | } from "../approve-list/approveListConstants.js"; |
| | | import { |
| | | INSTANCE_NO_SEARCH_MODULE_KEYS, |
| | | INSTANCE_NO_TABLE_COLUMN, |
| | | } from "./approvalInstanceListSearch.js"; |
| | | import { businessApprovalStatusLabel, businessApprovalStatusTagType, formatFieldDisplayValue, resolveInstanceFormFields } from "../approve-list/approveListConstants.js"; |
| | | import { INSTANCE_NO_SEARCH_MODULE_KEYS, INSTANCE_NO_TABLE_COLUMN } from "./approvalInstanceListSearch.js"; |
| | | |
| | | /** 列表/详情不回显为独立列的填报项 key(避免覆盖实例系统字段) */ |
| | | const DEFAULT_EXCLUDE_KEYS = new Set([ |
| | | "summary", |
| | | "status", |
| | | "approvalStatus", |
| | | "approvalstatus", |
| | | "instanceStatus", |
| | | "publishStatus", |
| | | "newsStatus", |
| | | ]); |
| | | const DEFAULT_EXCLUDE_KEYS = new Set(["summary", "status", "approvalStatus", "approvalstatus", "instanceStatus", "publishStatus", "newsStatus"]); |
| | | |
| | | /** enrich 后必须保留的实例字段(不被 formConfig 铺平覆盖) */ |
| | | const PRESERVE_INSTANCE_FIELDS = [ |
| | |
| | | "unread", |
| | | "currentLevel", |
| | | "newsStatus", |
| | | "storageBlobVOList", |
| | | "storageBlobDTOs", |
| | | ]; |
| | | |
| | | /** |
| | |
| | | if (!f?.key || DEFAULT_EXCLUDE_KEYS.has(f.key)) continue; |
| | | const val = formPayload[f.key]; |
| | | let text = formatFieldDisplayValue(f, val, caches); |
| | | if ( |
| | | text === String(val) && |
| | | row?.applicantName && |
| | | (f.label === "申请人" || f.key === "applicant" || f.key === "applicantName") |
| | | ) { |
| | | const idMatch = |
| | | String(val) === String(row.applicantId) || |
| | | String(val) === String(row.applicantNo); |
| | | if (text === String(val) && row?.applicantName && (f.label === "申请人" || f.key === "applicant" || f.key === "applicantName")) { |
| | | const idMatch = String(val) === String(row.applicantId) || String(val) === String(row.applicantNo); |
| | | if (idMatch) text = row.applicantName; |
| | | } |
| | | formDisplay[f.key] = text; |
| | |
| | | * 从列表首行 formConfig 生成主表动态列(label 取自模板字段 label) |
| | | */ |
| | | export function getFormConfigFieldColumns(firstRow, { excludeKeys = DEFAULT_EXCLUDE_KEYS } = {}) { |
| | | const fields = (firstRow?.formFieldDefs || []).filter( |
| | | (f) => f?.key && !excludeKeys.has(f.key) |
| | | ); |
| | | return fields.map((f) => ({ |
| | | const fields = (firstRow?.formFieldDefs || []).filter(f => f?.key && !excludeKeys.has(f.key)); |
| | | return fields.map(f => ({ |
| | | label: f.label || f.key, |
| | | prop: f.key, |
| | | minWidth: f.type === "textarea" ? 200 : f.type === "datetimerange" ? 160 : 120, |
| | |
| | | * 业务申请主表列:固定列 + formConfig 动态列 + 审批状态 + 操作 |
| | | */ |
| | | export function buildInstanceTableColumns(tableDataRef, buildTableActions, options = {}) { |
| | | const { |
| | | moduleKey, |
| | | excludeKeys = DEFAULT_EXCLUDE_KEYS, |
| | | beforeFormColumns = [], |
| | | extraColumns = [], |
| | | afterFormColumns = [], |
| | | actionWidth = 260, |
| | | } = options; |
| | | const { moduleKey, excludeKeys = DEFAULT_EXCLUDE_KEYS, beforeFormColumns = [], extraColumns = [], afterFormColumns = [], actionWidth = 260 } = options; |
| | | |
| | | const leadingCols = |
| | | moduleKey && INSTANCE_NO_SEARCH_MODULE_KEYS.has(moduleKey) |
| | | ? [INSTANCE_NO_TABLE_COLUMN] |
| | | : []; |
| | | const leadingCols = moduleKey && INSTANCE_NO_SEARCH_MODULE_KEYS.has(moduleKey) ? [INSTANCE_NO_TABLE_COLUMN] : []; |
| | | |
| | | return computed(() => { |
| | | const formCols = getFormConfigFieldColumns(tableDataRef.value?.[0], { excludeKeys }); |
| | |
| | | prop: "approvalStatus", |
| | | width: 110, |
| | | dataType: "tag", |
| | | formatData: (v) => businessApprovalStatusLabel(v), |
| | | formatType: (v) => businessApprovalStatusTagType(v), |
| | | formatData: v => businessApprovalStatusLabel(v), |
| | | formatType: v => businessApprovalStatusTagType(v), |
| | | }, |
| | | { |
| | | dataType: "action", |
| | |
| | | import dayjs from "dayjs"; |
| | | import { APPROVAL_MODULE_KEYS } from "./approvalModuleRegistry.js"; |
| | | |
| | | /** 支持审批单号查询/主表展示的审批申请模块 */ |
| | |
| | | return no ? { instanceNo: no } : {}; |
| | | } |
| | | |
| | | /** 组装 approvalInstanceDto 查询片段(申请人 + 审批单号) */ |
| | | /** 组装 approvalInstanceDto 查询片段(申请人 + 审批单号 + 状态 + 时间范围) */ |
| | | export function buildApprovalInstanceSearchDto(searchForm = {}, extraParams = {}) { |
| | | const dto = { |
| | | ...(extraParams && typeof extraParams === "object" ? extraParams : {}), |
| | | }; |
| | | Object.assign(dto, pickApplicantFromSearchForm(searchForm)); |
| | | Object.assign(dto, pickInstanceNoFromSearchForm(searchForm)); |
| | | |
| | | // 审批状态 |
| | | if (searchForm?.status) { |
| | | dto.status = searchForm.status; |
| | | } |
| | | |
| | | // 创建时间范围 |
| | | const range = searchForm?.createTimeRange; |
| | | if (Array.isArray(range) && range[0]) { |
| | | dto.createTimeStart = range[0] + (range[0].includes(":") ? "" : " 00:00:00"); |
| | | } |
| | | if (Array.isArray(range) && range[1]) { |
| | | dto.createTimeEnd = range[1] + (range[1].includes(":") ? "" : " 23:59:59"); |
| | | } |
| | | |
| | | delete dto.createTime; |
| | | delete dto.createTimeStart; |
| | | delete dto.createTimeEnd; |
| | | return dto; |
| | | } |
| | | |
| | |
| | | function matchApplicantKeyword(row, keyword) { |
| | | const kw = (keyword || "").trim().toLowerCase(); |
| | | if (!kw) return true; |
| | | const parts = [ |
| | | row?.applicantName, |
| | | row?.applicantNo, |
| | | row?.applicantId, |
| | | getRowPayloadValue(row, ["applicant", "applicantName", "applicantId"]), |
| | | ] |
| | | .filter((v) => v != null && v !== "") |
| | | .map((v) => String(v).toLowerCase()); |
| | | return parts.some((p) => p.includes(kw)); |
| | | const parts = [row?.applicantName, row?.applicantNo, row?.applicantId, getRowPayloadValue(row, ["applicant", "applicantName", "applicantId"])] |
| | | .filter(v => v != null && v !== "") |
| | | .map(v => String(v).toLowerCase()); |
| | | return parts.some(p => p.includes(kw)); |
| | | } |
| | | |
| | | function matchApplicantId(row, applicantId) { |
| | | if (applicantId == null || applicantId === "") return true; |
| | | const id = String(applicantId); |
| | | if (row?.applicantId != null && String(row.applicantId) === id) return true; |
| | | const payloadApplicant = getRowPayloadValue(row, [ |
| | | "applicant", |
| | | "applicantId", |
| | | "applicantUserId", |
| | | ]); |
| | | const payloadApplicant = getRowPayloadValue(row, ["applicant", "applicantId", "applicantUserId"]); |
| | | return String(payloadApplicant) === id; |
| | | } |
| | | |
| | |
| | | function matchInstanceNo(row, instanceNo) { |
| | | const kw = (instanceNo || "").trim().toLowerCase(); |
| | | if (!kw) return true; |
| | | const parts = [row?.instanceNo, row?.bizId] |
| | | .filter((v) => v != null && v !== "") |
| | | .map((v) => String(v).toLowerCase()); |
| | | return parts.some((p) => p.includes(kw)); |
| | | const parts = [row?.instanceNo, row?.bizId].filter(v => v != null && v !== "").map(v => String(v).toLowerCase()); |
| | | return parts.some(p => p.includes(kw)); |
| | | } |
| | | |
| | | /** 是否存在列表筛选条件(申请人 / 审批单号) */ |
| | | /** 是否存在列表筛选条件(申请人 / 审批单号 / 状态 / 时间范围) */ |
| | | export function hasActiveModuleSearch(moduleKey, searchForm = {}) { |
| | | const sf = searchForm || {}; |
| | | if ((sf.instanceNo || "").trim()) return true; |
| | | if ((sf.applicantKeyword || "").trim()) return true; |
| | | if ((sf.applicantName || "").trim()) return true; |
| | | return sf.applicantId != null && sf.applicantId !== ""; |
| | | if (sf.applicantId != null && sf.applicantId !== "") return true; |
| | | if (sf.status) return true; |
| | | if (Array.isArray(sf.createTimeRange) && sf.createTimeRange.length === 2) return true; |
| | | return false; |
| | | } |
| | | |
| | | /** 按申请人、审批单号做前端兜底筛选 */ |
| | | /** 按申请人、审批单号、状态、时间范围做前端兜底筛选 */ |
| | | export function filterInstanceRowsByModuleSearch(moduleKey, rows, searchForm = {}) { |
| | | const sf = searchForm || {}; |
| | | const list = Array.isArray(rows) ? rows : []; |
| | | if (!hasActiveModuleSearch(moduleKey, sf)) return list; |
| | | |
| | | return list.filter( |
| | | (row) => |
| | | matchInstanceNo(row, sf.instanceNo) && |
| | | matchApplicantId(row, sf.applicantId) && |
| | | matchApplicantKeyword(row, sf.applicantKeyword || sf.applicantName) |
| | | ); |
| | | return list.filter(row => { |
| | | // 审批单号 |
| | | if (!matchInstanceNo(row, sf.instanceNo)) return false; |
| | | // 申请人 |
| | | if (!matchApplicantId(row, sf.applicantId)) return false; |
| | | if (!matchApplicantKeyword(row, sf.applicantKeyword || sf.applicantName)) return false; |
| | | // 状态 |
| | | if (sf.status && String(row.statusRaw || row.status).toUpperCase() !== String(sf.status).toUpperCase()) { |
| | | return false; |
| | | } |
| | | // 时间范围 |
| | | if (Array.isArray(sf.createTimeRange) && sf.createTimeRange.length === 2) { |
| | | const rowTime = row.createTime || row.applyTime; |
| | | if (rowTime) { |
| | | const t = dayjs(rowTime); |
| | | const start = dayjs(sf.createTimeRange[0] + " 00:00:00"); |
| | | const end = dayjs(sf.createTimeRange[1] + " 23:59:59"); |
| | | if (t.isBefore(start) || t.isAfter(end)) return false; |
| | | } |
| | | } |
| | | return true; |
| | | }); |
| | | } |
| | |
| | | import { matchBusinessTypeValue } from "../approve-list/approveListConstants.js"; |
| | | |
| | | /** |
| | | * 各业务模块与审批模板类型的映射(配置化入口) |
| | | * businessType 与后端 TypeEnums / listPage 约定一致(写死枚举值) |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | title="详情" |
| | | width="70%" |
| | | @close="closeDia" |
| | | > |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :tableLoading="tableLoading" |
| | | height="600" |
| | | ></PIMTable> |
| | | <el-dialog v-model="dialogFormVisible" |
| | | title="详情" |
| | | width="70%" |
| | | @close="closeDia"> |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :tableLoading="tableLoading" |
| | | height="600"></PIMTable> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="closeDia">取消</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <Files ref="filesDia"></Files> |
| | | <FileList v-if="fileDialogVisible" |
| | | v-model:visible="fileDialogVisible" |
| | | :record-type="'staff_contract'" |
| | | :record-id="recordId" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref} from "vue"; |
| | | import {findStaffContractListPage} from "@/api/personnelManagement/staffContract.js"; |
| | | const Files = defineAsyncComponent(() => import( "@/views/personnelManagement/contractManagement/filesDia.vue")); |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close']) |
| | | const filesDia = ref() |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref('') |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "合同年限", |
| | | prop: "contractTerm", |
| | | }, |
| | | { |
| | | label: "合同开始日期", |
| | | prop: "contractStartTime", |
| | | }, |
| | | { |
| | | label: "合同结束日期", |
| | | prop: "contractEndTime", |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "操作", |
| | | align: "center", |
| | | fixed: 'right', |
| | | width: 120, |
| | | operation: [ |
| | | { |
| | | name: "上传附件", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | filesDia.value.openDialog( row,'合同') |
| | | import { ref, defineAsyncComponent, getCurrentInstance } from "vue"; |
| | | import { findStaffContractListPage } from "@/api/personnelManagement/staffContract.js"; |
| | | const FileList = defineAsyncComponent(() => |
| | | import("@/components/Dialog/FileList.vue") |
| | | ); |
| | | const { proxy } = getCurrentInstance(); |
| | | const emit = defineEmits(["close"]); |
| | | const fileDialogVisible = ref(false); |
| | | const recordId = ref(0); |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref(""); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "合同年限", |
| | | prop: "contractTerm", |
| | | }, |
| | | { |
| | | label: "合同开始日期", |
| | | prop: "contractStartTime", |
| | | }, |
| | | { |
| | | label: "合同结束日期", |
| | | prop: "contractEndTime", |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "操作", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 120, |
| | | operation: [ |
| | | { |
| | | name: "附件", |
| | | type: "text", |
| | | clickFun: row => { |
| | | recordId.value = row.id; |
| | | fileDialogVisible.value = true; |
| | | }, |
| | | }, |
| | | } |
| | | ], |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | ], |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | |
| | | // 打开弹框 |
| | | const openDialog = (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | if (operationType.value === 'edit') { |
| | | findStaffContractListPage({staffOnJobId: row.id}).then(res => { |
| | | tableData.value = res.data.records |
| | | }) |
| | | } |
| | | } |
| | | // 打开弹框 |
| | | const openDialog = (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | if (operationType.value === "edit") { |
| | | findStaffContractListPage({ staffOnJobId: row.id }).then(res => { |
| | | tableData.value = res.data.records; |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const openUploadFile = (row) => { |
| | | filesDia.value.open = true |
| | | filesDia.value.row = row |
| | | } |
| | | |
| | | // 关闭弹框 |
| | | const closeDia = () => { |
| | | dialogFormVisible.value = false; |
| | | emit('close') |
| | | }; |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | // 关闭弹框 |
| | | const closeDia = () => { |
| | | dialogFormVisible.value = false; |
| | | emit("close"); |
| | | }; |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | title="详情" |
| | | width="70%" |
| | | @close="closeDia" |
| | | > |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :tableLoading="tableLoading" |
| | | height="600" |
| | | ></PIMTable> |
| | | <el-dialog v-model="dialogFormVisible" |
| | | title="详情" |
| | | width="70%" |
| | | @close="closeDia"> |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :tableLoading="tableLoading" |
| | | height="600"></PIMTable> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="closeDia">取消</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <Files ref="filesDia"></Files> |
| | | <FileList v-if="fileDialogVisible" |
| | | v-model:visible="fileDialogVisible" |
| | | :record-type="'staff_contract'" |
| | | :record-id="recordId" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref} from "vue"; |
| | | import {findStaffContractListPage} from "@/api/personnelManagement/staffContract.js"; |
| | | const Files = defineAsyncComponent(() => import( "@/views/personnelManagement/contractManagement/filesDia.vue")); |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close']) |
| | | const filesDia = ref() |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref('') |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "合同年限", |
| | | prop: "contractTerm", |
| | | }, |
| | | { |
| | | label: "合同开始日期", |
| | | prop: "contractStartTime", |
| | | }, |
| | | { |
| | | label: "合同结束日期", |
| | | prop: "contractEndTime", |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "操作", |
| | | align: "center", |
| | | fixed: 'right', |
| | | width: 120, |
| | | operation: [ |
| | | { |
| | | name: "上传附件", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | filesDia.value.openDialog( row,'合同') |
| | | import { ref, defineAsyncComponent, getCurrentInstance } from "vue"; |
| | | import { findStaffContractListPage } from "@/api/personnelManagement/staffContract.js"; |
| | | const FileList = defineAsyncComponent(() => |
| | | import("@/components/Dialog/FileList.vue") |
| | | ); |
| | | const { proxy } = getCurrentInstance(); |
| | | const emit = defineEmits(["close"]); |
| | | const fileDialogVisible = ref(false); |
| | | const recordId = ref(0); |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref(""); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "合同年限", |
| | | prop: "contractTerm", |
| | | }, |
| | | { |
| | | label: "合同开始日期", |
| | | prop: "contractStartTime", |
| | | }, |
| | | { |
| | | label: "合同结束日期", |
| | | prop: "contractEndTime", |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "操作", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 120, |
| | | operation: [ |
| | | { |
| | | name: "附件", |
| | | type: "text", |
| | | clickFun: row => { |
| | | recordId.value = row.id; |
| | | fileDialogVisible.value = true; |
| | | }, |
| | | }, |
| | | } |
| | | ], |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | ], |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | |
| | | // 打开弹框 |
| | | const openDialog = (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | if (operationType.value === 'edit') { |
| | | findStaffContractListPage({staffOnJobId: row.id}).then(res => { |
| | | tableData.value = res.data.records |
| | | }) |
| | | } |
| | | } |
| | | // 打开弹框 |
| | | const openDialog = (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | if (operationType.value === "edit") { |
| | | findStaffContractListPage({ staffOnJobId: row.id }).then(res => { |
| | | tableData.value = res.data.records; |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const openUploadFile = (row) => { |
| | | filesDia.value.open = true |
| | | filesDia.value.row = row |
| | | } |
| | | |
| | | // 关闭弹框 |
| | | const closeDia = () => { |
| | | dialogFormVisible.value = false; |
| | | emit('close') |
| | | }; |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | // 关闭弹框 |
| | | const closeDia = () => { |
| | | dialogFormVisible.value = false; |
| | | emit("close"); |
| | | }; |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |