From a1a9521e1f537d742c4f3ebada9b102bfefa6583 Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期二, 19 五月 2026 16:21:13 +0800
Subject: [PATCH] 审批列表
---
src/views/officeProcessAutomation/ApproveManage/approve-list/index.vue | 310 +++++++++++++++++++++++++++++++--------------------
1 files changed, 188 insertions(+), 122 deletions(-)
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-list/index.vue b/src/views/officeProcessAutomation/ApproveManage/approve-list/index.vue
index 774b322..cdae763 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-list/index.vue
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-list/index.vue
@@ -63,119 +63,85 @@
{{ approvalTypeLabel(row.approvalType) }}
</span>
</template>
- <template #approvalMethod="{ row }">
- <span class="approval-method-text">{{ approvalModeLabel(row.approvalMode) }}</span>
- </template>
</PIMTable>
</div>
<!-- 鎻愪氦瀹℃壒锛堟寜妯℃澘锛� -->
<el-dialog
v-model="submitDialog.visible"
- :title="submitDialog.step === 1 ? '閫夋嫨瀹℃壒妯℃澘' : `鎻愪氦${activeTemplate?.label || '瀹℃壒'}`"
+ :title="submitDialogTitle"
width="720px"
append-to-body
destroy-on-close
class="approve-submit-dialog"
- @closed="submitDialog.step = 1"
+ @closed="resetSubmitDialogState"
>
- <template v-if="submitDialog.step === 1">
- <p class="template-hint">璇烽�夋嫨瑕佹彁浜ょ殑瀹℃壒绫诲瀷锛岀郴缁熷皢鎸夊搴旀ā鏉垮紩瀵煎~鎶ワ紙瀛楁鍚庢湡涓庡悗绔悓姝ワ級銆�</p>
- <div class="template-grid">
+ <template v-if="submitDialog.step === 1 && !isSubmitEdit">
+ <p class="template-hint">璇烽�夋嫨宸插惎鐢ㄧ殑瀹℃壒妯℃澘锛岀郴缁熷皢鎸夋ā鏉块厤缃紩瀵煎~鎶ャ��</p>
+ <div v-loading="submitTemplatesLoading" class="template-grid">
<div
- v-for="(tpl, key) in SUBMIT_TEMPLATES"
- :key="key"
+ v-for="card in submitTemplateCards"
+ :key="card.key"
class="template-card"
- @click="onTemplatePick(key)"
+ @click="onTemplatePick(card)"
>
- <span class="template-card-type" :style="approvalTypeStyle(tpl.approvalType)">
- {{ tpl.label }}
+ <span class="template-card-type" :style="approvalTypeStyle(card.approvalType)">
+ {{ card.label }}
</span>
- <span class="template-card-desc">{{ tpl.summaryPlaceholder || "鐐瑰嚮濉啓骞舵彁浜�" }}</span>
+ <span class="template-card-desc">{{ card.summaryPlaceholder }}</span>
</div>
+ <el-empty
+ v-if="!submitTemplatesLoading && !submitTemplateCards.length"
+ description="鏆傛棤鍙敤瀹℃壒妯℃澘"
+ :image-size="80"
+ class="template-empty"
+ />
</div>
</template>
<template v-else>
+ <div v-loading="submitTemplatesLoading && !isSubmitEdit">
<el-form ref="submitFormRef" :model="submitForm" :rules="submitFormRules" label-width="120px">
<el-form-item label="瀹℃壒绫诲瀷">
<span class="approve-type-cell" :style="approvalTypeStyle(activeTemplate.approvalType)">
{{ activeTemplate.label }}
</span>
- <el-button type="primary" link class="ml12" @click="backToTemplatePick">鏇存崲妯℃澘</el-button>
+ <el-button
+ v-if="!isSubmitEdit"
+ type="primary"
+ link
+ class="ml12"
+ @click="backToTemplatePick"
+ >
+ 鏇存崲妯℃澘
+ </el-button>
</el-form-item>
- <el-form-item label="瀹℃壒鏂瑰紡" prop="approvalMode">
- <el-radio-group v-model="submitForm.approvalMode">
- <el-radio value="parallel">涓庣</el-radio>
- <el-radio value="or_sign">鎴栫</el-radio>
- </el-radio-group>
- </el-form-item>
- <template v-for="field in activeTemplate.fields" :key="field.key">
- <el-form-item :label="field.label" :prop="`formPayload.${field.key}`">
- <el-input
- v-if="field.type === 'text'"
- v-model="submitForm.formPayload[field.key]"
- :placeholder="`璇疯緭鍏�${field.label}`"
- maxlength="200"
- />
- <el-input
- v-else-if="field.type === 'textarea'"
- v-model="submitForm.formPayload[field.key]"
- type="textarea"
- :rows="field.rows || 3"
- :placeholder="`璇峰~鍐�${field.label}`"
- maxlength="2000"
- show-word-limit
- />
- <el-input-number
- v-else-if="field.type === 'number'"
- v-model="submitForm.formPayload[field.key]"
- :min="field.min ?? 0"
- :precision="field.precision ?? 0"
- controls-position="right"
- style="width: 100%"
- />
- <el-date-picker
- v-else-if="field.type === 'date'"
- v-model="submitForm.formPayload[field.key]"
- type="date"
- :placeholder="`璇烽�夋嫨${field.label}`"
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
- style="width: 100%"
- />
- <el-date-picker
- v-else-if="field.type === 'datetimerange'"
- v-model="submitForm.formPayload[field.key]"
- type="datetimerange"
- range-separator="鑷�"
- start-placeholder="寮�濮嬫椂闂�"
- end-placeholder="缁撴潫鏃堕棿"
- format="YYYY-MM-DD HH:mm:ss"
- value-format="YYYY-MM-DD HH:mm:ss"
- style="width: 100%"
- />
- <el-select
- v-else-if="field.type === 'select'"
- v-model="submitForm.formPayload[field.key]"
- :placeholder="`璇烽�夋嫨${field.label}`"
- style="width: 100%"
- clearable
- >
- <el-option v-for="o in field.options" :key="o.value" :label="o.label" :value="o.value" />
- </el-select>
- </el-form-item>
- </template>
- <el-form-item label="瀹℃壒娴佺▼">
- <ApprovalFlowEditor v-model="submitForm.approvalFlowNodes" :user-options="flowUserOptions" />
- <p class="flow-tip">鑷冲皯淇濈暀涓�涓鎵硅妭鐐癸紱鎻愪氦鍚庤繘鍏ャ�屽鏍镐腑銆嶇姸鎬併��</p>
+ <FormPayloadFields
+ :fields="submitFormFields"
+ :form-payload="submitForm.formPayload"
+ />
+ <el-form-item label="瀹℃壒娴佺▼" required>
+ <TemplateFlowEditor v-model="submitForm.flowNodes" :user-options="flowUserOptions" />
+ <p class="flow-tip">
+ 鎸夐『搴忔祦杞細鍙负姣忎釜鑺傜偣娣诲姞澶氬悕瀹℃壒浜猴紱浼氱闇�鍏ㄩ儴閫氳繃锛屾垨绛句换涓�浜洪�氳繃鍗冲彲杩涘叆涓嬩竴鑺傜偣銆�
+ </p>
</el-form-item>
</el-form>
+ </div>
</template>
<template #footer>
- <el-button v-if="submitDialog.step === 2" type="primary" @click="onSubmitNew">鎻� 浜�</el-button>
- <el-button @click="submitDialog.visible = false">{{ submitDialog.step === 1 ? "鍙� 娑�" : "鍏� 闂�" }}</el-button>
+ <el-button
+ v-if="submitDialog.step === 2 || isSubmitEdit"
+ type="primary"
+ :loading="submitSaving"
+ @click="onSubmitInstance"
+ >
+ {{ isSubmitEdit ? "淇� 瀛�" : "鎻� 浜�" }}
+ </el-button>
+ <el-button @click="submitDialog.visible = false">
+ {{ submitDialog.step === 1 && !isSubmitEdit ? "鍙� 娑�" : "鍏� 闂�" }}
+ </el-button>
</template>
</el-dialog>
@@ -186,28 +152,51 @@
width="920px"
append-to-body
destroy-on-close
+ class="approve-detail-dialog"
>
- <ApproveDetailPanel :row="detailRow" />
- <el-divider content-position="left">瀹℃壒娴佺▼</el-divider>
- <ApprovalFlowProgress
- :nodes="detailRow.approvalFlowNodes"
- :current-index="detailRow.currentNodeIndex ?? 0"
- />
- <el-divider content-position="left">瀹℃壒璁板綍</el-divider>
- <el-timeline v-if="detailRow.approvalRecords?.length">
- <el-timeline-item
- v-for="(rec, i) in detailRow.approvalRecords"
- :key="i"
- :type="rec.result === 'approved' ? 'success' : rec.result === 'rejected' ? 'danger' : 'primary'"
- :timestamp="rec.time"
- >
- {{ rec.operatorName }} 鈥� {{ approvalActionLabel(rec.result) }}锛歿{ rec.opinion || "鏃犳剰瑙�" }}
- </el-timeline-item>
- </el-timeline>
- <el-empty v-else description="鏆傛棤瀹℃壒璁板綍" :image-size="60" />
+ <div class="approve-detail-body">
+ <ApproveDetailPanel :row="detailRow" />
+ <div class="detail-block">
+ <div class="detail-block-title">
+ 瀹℃壒娴佺▼锛坽{ detailRow.tasks?.length || detailRow.flowNodes?.length || 0 }} 椤癸級
+ </div>
+ <InstanceFlowDisplay :tasks="detailRow.tasks" :nodes="detailRow.flowNodes" />
+ </div>
+ <div class="detail-block">
+ <div class="detail-block-title">瀹℃壒璁板綍</div>
+ <el-timeline v-if="detailRow.approvalRecords?.length" class="approve-record-timeline">
+ <el-timeline-item
+ v-for="(rec, i) in detailRow.approvalRecords"
+ :key="rec.id ?? i"
+ :type="rec.result === 'approved' ? 'success' : rec.result === 'rejected' ? 'danger' : 'primary'"
+ :timestamp="formatRecordTime(rec.time)"
+ placement="top"
+ >
+ <div class="record-item">
+ <span class="record-operator">{{ rec.operatorName || "鈥�" }}</span>
+ <el-tag
+ size="small"
+ :type="rec.result === 'approved' ? 'success' : rec.result === 'rejected' ? 'danger' : 'info'"
+ effect="plain"
+ >
+ {{ approvalActionLabel(rec.result) }}
+ </el-tag>
+ <p class="record-opinion">{{ rec.opinion || "鏃犳剰瑙�" }}</p>
+ </div>
+ </el-timeline-item>
+ </el-timeline>
+ <el-empty v-else description="鏆傛棤瀹℃壒璁板綍" :image-size="48" />
+ </div>
+ </div>
<template #footer>
<el-button
v-if="detailRow.approvalStatus === 'pending'"
+ @click="openEditFromDetail"
+ >
+ 淇� 鏀�
+ </el-button>
+ <el-button
+ v-if="detailRow.approvalStatus === 'pending' && detailRow.isApprove"
type="primary"
@click="openApproveFromDetail"
>
@@ -227,11 +216,12 @@
@closed="approveOpinion = ''"
>
<ApproveDetailPanel :row="approveDialog.row" />
- <el-divider content-position="left">娴佺▼杩涘害</el-divider>
- <ApprovalFlowProgress
- :nodes="approveDialog.row?.approvalFlowNodes"
- :current-index="approveDialog.row?.currentNodeIndex ?? 0"
- />
+ <div class="detail-block mt16">
+ <div class="detail-block-title">
+ 瀹℃壒娴佺▼锛坽{ approveDialog.row?.tasks?.length || approveDialog.row?.flowNodes?.length || 0 }} 椤癸級
+ </div>
+ <InstanceFlowDisplay :tasks="approveDialog.row?.tasks" :nodes="approveDialog.row?.flowNodes" />
+ </div>
<el-form label-width="100px" class="mt16">
<el-form-item label="瀹℃壒鎰忚" required>
<el-input
@@ -245,9 +235,23 @@
</el-form-item>
</el-form>
<template #footer>
- <el-button type="success" @click="onApprove('approved')">閫� 杩�</el-button>
- <el-button type="danger" @click="onApprove('rejected')">椹� 鍥�</el-button>
- <el-button @click="approveDialog.visible = false">鍙� 娑�</el-button>
+ <el-button
+ type="success"
+ :loading="approveSubmitting"
+ @click="onApprove('approved')"
+ >
+ 閫� 杩�
+ </el-button>
+ <el-button
+ type="danger"
+ :loading="approveSubmitting"
+ @click="onApprove('rejected')"
+ >
+ 椹� 鍥�
+ </el-button>
+ <el-button :disabled="approveSubmitting" @click="approveDialog.visible = false">
+ 鍙� 娑�
+ </el-button>
</template>
</el-dialog>
</div>
@@ -258,19 +262,21 @@
import { ElMessage } from "element-plus";
import { onMounted, ref } from "vue";
import { userListNoPageByTenantId } from "@/api/system/user.js";
-import ApprovalFlowEditor from "@/views/officeProcessAutomation/AttendManage/overtime-apply/components/ApprovalFlowEditor.vue";
-import ApprovalFlowProgress from "@/views/officeProcessAutomation/ReimburseManage/travel-reimburse/components/ApprovalFlowProgress.vue";
+import TemplateFlowEditor from "../approve-template/components/TemplateFlowEditor.vue";
+import FormPayloadFields from "./components/FormPayloadFields.vue";
+import { formatDisplayTime } from "../approve-template/approveTemplateConstants.js";
import { approvalTypeStyle } from "./approveListConstants.js";
import ApproveDetailPanel from "./components/ApproveDetailPanel.vue";
+import InstanceFlowDisplay from "./components/InstanceFlowDisplay.vue";
import { useApproveList } from "./useApproveList.js";
const al = useApproveList();
const {
Search,
APPROVAL_TYPE_OPTIONS,
- SUBMIT_TEMPLATES,
+ submitTemplateCards,
+ submitTemplatesLoading,
approvalTypeLabel,
- approvalModeLabel,
approvalActionLabel,
searchForm,
tableLoading,
@@ -281,18 +287,25 @@
detailRow,
approveDialog,
approveOpinion,
+ approveSubmitting,
submitDialog,
+ isSubmitEdit,
+ submitDialogTitle,
submitForm,
submitFormRef,
+ submitSaving,
activeTemplate,
+ submitFormFields,
submitFormRules,
handleQuery,
resetSearch,
pagination,
+ resetSubmitDialogState,
openSubmitDialog,
+ openEditDialog,
onTemplatePick,
backToTemplatePick,
- submitNewApproval,
+ submitInstanceForm,
submitApprove,
openDetail,
openApprove,
@@ -322,13 +335,13 @@
}
}
-async function onSubmitNew() {
- const ok = await submitNewApproval();
- if (ok) ElMessage.success("瀹℃壒宸叉彁浜�");
+async function onSubmitInstance() {
+ const ok = await submitInstanceForm();
+ if (ok) ElMessage.success(isSubmitEdit.value ? "淇敼鎴愬姛" : "瀹℃壒宸叉彁浜�");
}
-function onApprove(result) {
- const ret = submitApprove(result);
+async function onApprove(result) {
+ const ret = await submitApprove(result);
if (ret?.needOpinion) {
ElMessage.warning("椹冲洖鏃惰濉啓瀹℃壒鎰忚");
return;
@@ -338,10 +351,20 @@
}
}
+function formatRecordTime(time) {
+ return formatDisplayTime(time) || "鈥�";
+}
+
function openApproveFromDetail() {
const row = detailRow.value;
detailDialog.visible = false;
openApprove(row);
+}
+
+function openEditFromDetail() {
+ const row = detailRow.value;
+ detailDialog.visible = false;
+ openEditDialog(row);
}
onMounted(() => {
@@ -385,10 +408,6 @@
font-size: 13px;
line-height: 1.5;
}
-.approval-method-text {
- color: var(--el-color-danger);
- font-weight: 500;
-}
.template-hint {
font-size: 13px;
color: var(--el-text-color-secondary);
@@ -398,6 +417,10 @@
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 12px;
+ min-height: 120px;
+}
+.template-empty {
+ grid-column: 1 / -1;
}
.template-card {
padding: 14px 16px;
@@ -433,4 +456,47 @@
.approve-submit-dialog :deep(.el-dialog__body) {
padding-top: 12px;
}
+.approve-detail-dialog :deep(.el-dialog__body) {
+ padding-top: 16px;
+ max-height: 70vh;
+ overflow-y: auto;
+}
+.approve-detail-body .detail-block {
+ margin-top: 20px;
+}
+.approve-detail-body .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-record-timeline {
+ padding-left: 4px;
+}
+.record-item {
+ padding: 4px 0 2px;
+}
+.record-operator {
+ font-weight: 600;
+ margin-right: 8px;
+ color: var(--el-text-color-primary);
+}
+.record-opinion {
+ margin: 8px 0 0;
+ font-size: 13px;
+ color: var(--el-text-color-regular);
+ line-height: 1.5;
+}
+.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;
+}
</style>
--
Gitblit v1.9.3