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 |  504 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 497 insertions(+), 7 deletions(-)

diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-list/index.vue b/src/views/officeProcessAutomation/ApproveManage/approve-list/index.vue
index d4ff149..cdae763 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-list/index.vue
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-list/index.vue
@@ -1,12 +1,502 @@
-<!--
-  妯″潡涓枃鍚嶏細瀹℃壒鍒楄〃
-  鐩綍鏍囪瘑锛欰pproveManage/approve-list锛坅pprove-list 鈫� 涓枃锛氬鎵瑰垪琛級
-  澶嶇敤椤甸潰锛欯/views/procurementManagement/procurementLedger/index.vue锛堥噰璐彴璐︼紱鏂囦欢鍚� index.vue 鈫� 鍏ュ彛椤碉級
--->
+<!--OA妯″潡锛氬鎵瑰垪琛�-->
 <template>
-  <ProcurementLedger />
+  <div class="app-container">
+    <div class="search_form mb20">
+      <div class="search_fields">
+        <span class="search_title">瀹℃壒绫诲瀷锛�</span>
+        <el-select
+          v-model="searchForm.approvalType"
+          placeholder="璇烽�夋嫨瀹℃壒绫诲瀷"
+          clearable
+          filterable
+          style="width: 200px"
+        >
+          <el-option
+            v-for="opt in APPROVAL_TYPE_OPTIONS"
+            :key="opt.value"
+            :label="opt.label"
+            :value="opt.value"
+          />
+        </el-select>
+        <span class="search_title" style="margin-left: 12px">鐢宠浜哄悕绉帮細</span>
+        <el-input
+          v-model="searchForm.applicantKeyword"
+          style="width: 200px"
+          placeholder="璇疯緭鍏ョ敵璇蜂汉鍚嶇О"
+          clearable
+          :prefix-icon="Search"
+          @keyup.enter="handleQuery"
+        />
+        <span class="search_title" style="margin-left: 12px">鍒涘缓鏃堕棿锛�</span>
+        <el-date-picker
+          v-model="searchForm.createTimeRange"
+          type="daterange"
+          range-separator="-"
+          start-placeholder="寮�濮嬫棩鏈�"
+          end-placeholder="缁撴潫鏃ユ湡"
+          format="YYYY-MM-DD"
+          value-format="YYYY-MM-DD"
+          style="width: 260px"
+          clearable
+        />
+        <el-button type="primary" :icon="Search" style="margin-left: 10px" @click="handleQuery">鎼滅储</el-button>
+        <el-button :icon="RefreshRight" @click="resetSearch">閲嶇疆</el-button>
+      </div>
+      <div class="search_actions">
+        <el-button type="primary" :icon="Plus" @click="openSubmitDialog">鎻愪氦瀹℃壒</el-button>
+      </div>
+    </div>
+
+    <div class="table_list">
+      <PIMTable
+        rowKey="id"
+        :column="tableColumn"
+        :tableData="tableData"
+        :page="page"
+        :isSelection="false"
+        :tableLoading="tableLoading"
+        :total="page.total"
+        @pagination="pagination"
+      >
+        <template #approveType="{ row }">
+          <span class="approve-type-cell" :style="approvalTypeStyle(row.approvalType)">
+            {{ approvalTypeLabel(row.approvalType) }}
+          </span>
+        </template>
+      </PIMTable>
+    </div>
+
+    <!-- 鎻愪氦瀹℃壒锛堟寜妯℃澘锛� -->
+    <el-dialog
+      v-model="submitDialog.visible"
+      :title="submitDialogTitle"
+      width="720px"
+      append-to-body
+      destroy-on-close
+      class="approve-submit-dialog"
+      @closed="resetSubmitDialogState"
+    >
+      <template v-if="submitDialog.step === 1 && !isSubmitEdit">
+        <p class="template-hint">璇烽�夋嫨宸插惎鐢ㄧ殑瀹℃壒妯℃澘锛岀郴缁熷皢鎸夋ā鏉块厤缃紩瀵煎~鎶ャ��</p>
+        <div v-loading="submitTemplatesLoading" class="template-grid">
+          <div
+            v-for="card in submitTemplateCards"
+            :key="card.key"
+            class="template-card"
+            @click="onTemplatePick(card)"
+          >
+            <span class="template-card-type" :style="approvalTypeStyle(card.approvalType)">
+              {{ card.label }}
+            </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
+              v-if="!isSubmitEdit"
+              type="primary"
+              link
+              class="ml12"
+              @click="backToTemplatePick"
+            >
+              鏇存崲妯℃澘
+            </el-button>
+          </el-form-item>
+          <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 || 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>
+
+    <!-- 璇︽儏 -->
+    <el-dialog
+      v-model="detailDialog.visible"
+      title="瀹℃壒璇︽儏"
+      width="920px"
+      append-to-body
+      destroy-on-close
+      class="approve-detail-dialog"
+    >
+      <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"
+        >
+          鍘诲鎵�
+        </el-button>
+        <el-button @click="detailDialog.visible = false">鍏� 闂�</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 瀹℃壒鎿嶄綔 -->
+    <el-dialog
+      v-model="approveDialog.visible"
+      title="瀹℃壒澶勭悊"
+      width="960px"
+      append-to-body
+      destroy-on-close
+      @closed="approveOpinion = ''"
+    >
+      <ApproveDetailPanel :row="approveDialog.row" />
+      <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
+            v-model="approveOpinion"
+            type="textarea"
+            :rows="3"
+            maxlength="500"
+            show-word-limit
+            placeholder="閫氳繃鍙暀绌猴紱椹冲洖璇峰~鍐欏叿浣撳師鍥�"
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <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>
 </template>
 
 <script setup>
-import ProcurementLedger from '@/views/procurementManagement/procurementLedger/index.vue'
+import { Plus, RefreshRight } from "@element-plus/icons-vue";
+import { ElMessage } from "element-plus";
+import { onMounted, ref } from "vue";
+import { userListNoPageByTenantId } from "@/api/system/user.js";
+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,
+  submitTemplateCards,
+  submitTemplatesLoading,
+  approvalTypeLabel,
+  approvalActionLabel,
+  searchForm,
+  tableLoading,
+  page,
+  tableData,
+  tableColumn,
+  detailDialog,
+  detailRow,
+  approveDialog,
+  approveOpinion,
+  approveSubmitting,
+  submitDialog,
+  isSubmitEdit,
+  submitDialogTitle,
+  submitForm,
+  submitFormRef,
+  submitSaving,
+  activeTemplate,
+  submitFormFields,
+  submitFormRules,
+  handleQuery,
+  resetSearch,
+  pagination,
+  resetSubmitDialogState,
+  openSubmitDialog,
+  openEditDialog,
+  onTemplatePick,
+  backToTemplatePick,
+  submitInstanceForm,
+  submitApprove,
+  openDetail,
+  openApprove,
+} = al;
+
+const flowUserOptions = ref([]);
+
+function unwrapArray(payload) {
+  if (Array.isArray(payload)) return payload;
+  if (payload?.data && Array.isArray(payload.data)) return payload.data;
+  if (payload?.rows && Array.isArray(payload.rows)) return payload.rows;
+  return [];
+}
+
+function isActiveUser(u) {
+  if (u.delFlag === "2" || u.delFlag === 2) return false;
+  if (u.status == null) return true;
+  return String(u.status) === "0";
+}
+
+async function loadUsers() {
+  try {
+    const res = await userListNoPageByTenantId();
+    flowUserOptions.value = unwrapArray(res).filter(isActiveUser);
+  } catch {
+    flowUserOptions.value = [];
+  }
+}
+
+async function onSubmitInstance() {
+  const ok = await submitInstanceForm();
+  if (ok) ElMessage.success(isSubmitEdit.value ? "淇敼鎴愬姛" : "瀹℃壒宸叉彁浜�");
+}
+
+async function onApprove(result) {
+  const ret = await submitApprove(result);
+  if (ret?.needOpinion) {
+    ElMessage.warning("椹冲洖鏃惰濉啓瀹℃壒鎰忚");
+    return;
+  }
+  if (ret?.ok) {
+    ElMessage.success(result === "approved" ? "宸查�氳繃" : "宸查┏鍥�");
+  }
+}
+
+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(() => {
+  loadUsers();
+  handleQuery();
+});
 </script>
+
+<style scoped>
+.mb20 {
+  margin-bottom: 20px;
+}
+.ml12 {
+  margin-left: 12px;
+}
+.mt16 {
+  margin-top: 16px;
+}
+.search_form {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  justify-content: space-between;
+  gap: 12px;
+}
+.search_fields {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 4px;
+}
+.search_actions {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+}
+.approve-type-cell {
+  display: inline-block;
+  padding: 2px 10px;
+  border-radius: 4px;
+  font-size: 13px;
+  line-height: 1.5;
+}
+.template-hint {
+  font-size: 13px;
+  color: var(--el-text-color-secondary);
+  margin: 0 0 16px;
+}
+.template-grid {
+  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;
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: var(--radius-md, 8px);
+  cursor: pointer;
+  transition: border-color 0.2s, box-shadow 0.2s;
+  background: var(--el-fill-color-blank);
+}
+.template-card:hover {
+  border-color: var(--el-color-primary);
+  box-shadow: var(--shadow-sm, 0 2px 8px rgba(0, 0, 0, 0.06));
+}
+.template-card-type {
+  display: inline-block;
+  padding: 2px 8px;
+  border-radius: 4px;
+  font-size: 13px;
+  font-weight: 600;
+  margin-bottom: 8px;
+}
+.template-card-desc {
+  display: block;
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  line-height: 1.5;
+}
+.flow-tip {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  margin-top: 8px;
+}
+.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