yyb
12 小时以前 6c324a234060820d031014ea657af5aa0b0d478e
src/views/officeProcessAutomation/ApproveManage/approve-template/index.vue
@@ -1,4 +1,4 @@
<!--OA模块:审批模板(系统常用 + 自定义多节点流程)-->
<!--OA模块:审批模板-->
<template>
  <div class="app-container approve-template-page">
    <el-tabs v-model="activeTab" class="template-tabs">
@@ -9,7 +9,8 @@
            以下为 OA 模块内置的常用审批类型,填报字段与默认审批方式由系统维护;提交审批时可直接选用。
          </template>
        </el-alert>
        <div class="builtin-grid">
        <div v-loading="builtinLoading" class="builtin-grid">
          <template v-if="builtinTemplates.length">
          <div v-for="item in builtinTemplates" :key="item.key" class="builtin-card">
            <span class="builtin-label">{{ item.label }}</span>
            <p class="builtin-summary">{{ item.summary }}</p>
@@ -21,6 +22,8 @@
              <el-tag size="small" type="info" effect="plain">只读</el-tag>
            </div>
          </div>
          </template>
          <el-empty v-else-if="!builtinLoading" description="暂无系统常用审批模板" :image-size="80" />
        </div>
      </el-tab-pane>
@@ -66,7 +69,7 @@
    <el-dialog
      v-model="formDialog.visible"
      :title="formDialog.title"
      width="960px"
      width="1020px"
      append-to-body
      destroy-on-close
      class="template-form-dialog"
@@ -74,12 +77,24 @@
    >
      <el-form ref="formRef" :model="form" :rules="formRules" label-width="100px">
        <el-row :gutter="20">
          <el-col :span="12">
          <el-col :span="8">
            <el-form-item label="模板名称" prop="templateName">
              <el-input v-model="form.templateName" placeholder="如:项目立项审批" maxlength="50" show-word-limit />
            </el-form-item>
          </el-col>
          <el-col :span="12">
          <el-col :span="8">
            <el-form-item label="模板类型" prop="templateType">
              <el-select v-model="form.templateType" placeholder="请选择" style="width: 100%">
                <el-option
                  v-for="opt in TEMPLATE_TYPE_OPTIONS"
                  :key="opt.value"
                  :label="opt.label"
                  :value="opt.value"
                />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="启用状态">
              <el-switch v-model="form.enabled" active-text="启用" inactive-text="停用" />
            </el-form-item>
@@ -94,6 +109,10 @@
            maxlength="200"
            show-word-limit
          />
        </el-form-item>
        <el-form-item label="填报配置">
          <FormConfigEditor v-model="form.formConfigData" />
          <p class="flow-tip">配置提交审批时需填写的表单项,保存后写入 formConfig(JSON)。</p>
        </el-form-item>
        <el-form-item label="审批流程" required>
          <TemplateFlowEditor v-model="form.flowNodes" :user-options="flowUserOptions" />
@@ -110,17 +129,44 @@
    <!-- 详情 -->
    <el-dialog v-model="detailDialog.visible" title="模板详情" width="880px" append-to-body destroy-on-close>
      <div v-loading="detailLoading" class="detail-dialog-body">
      <el-descriptions :column="2" border>
        <el-descriptions-item label="模板名称">{{ detailRow.templateName }}</el-descriptions-item>
        <el-descriptions-item label="模板类型">{{ templateTypeLabel(detailRow.templateType) }}</el-descriptions-item>
        <el-descriptions-item label="状态">
          <el-tag :type="detailRow.enabled !== false ? 'success' : 'info'" size="small">
            {{ detailRow.enabled !== false ? "启用" : "停用" }}
          </el-tag>
        </el-descriptions-item>
        <el-descriptions-item label="说明" :span="2">{{ detailRow.description || "—" }}</el-descriptions-item>
        <el-descriptions-item label="创建时间">{{ detailRow.createTime || "—" }}</el-descriptions-item>
        <el-descriptions-item label="更新时间">{{ detailRow.updateTime || "—" }}</el-descriptions-item>
        <el-descriptions-item label="填报提示" :span="2">
          {{ detailFormConfig.summaryPlaceholder || "—" }}
        </el-descriptions-item>
        <el-descriptions-item label="创建人">{{ detailRow.createdUserName || "—" }}</el-descriptions-item>
        <el-descriptions-item label="创建时间">{{ formatDisplayTime(detailRow.createdTime) }}</el-descriptions-item>
        <el-descriptions-item label="更新时间">{{ formatDisplayTime(detailRow.updatedTime) }}</el-descriptions-item>
      </el-descriptions>
      <el-divider content-position="left">填报项({{ detailFormConfig.fields?.length || 0 }} 项)</el-divider>
      <el-table
        v-if="detailFormConfig.fields?.length"
        :data="detailFormConfig.fields"
        border
        size="small"
        class="mb16"
      >
        <el-table-column prop="label" label="显示名称" min-width="120" />
        <el-table-column prop="key" label="字段标识" min-width="100" />
        <el-table-column label="类型" width="100">
          <template #default="{ row }">{{ formFieldTypeLabel(row.type) }}</template>
        </el-table-column>
        <el-table-column label="必填" width="70" align="center">
          <template #default="{ row }">{{ row.required !== false ? "是" : "否" }}</template>
        </el-table-column>
        <el-table-column label="默认值" min-width="120" show-overflow-tooltip>
          <template #default="{ row }">{{ formatDefaultValueDisplay(row) }}</template>
        </el-table-column>
      </el-table>
      <el-empty v-else description="未配置填报项" :image-size="48" class="mb16" />
      <el-divider content-position="left">审批流程({{ detailRow.flowNodes?.length || 0 }} 个节点)</el-divider>
      <div v-if="detailRow.flowNodes?.length" class="detail-flow">
        <div v-for="(node, index) in detailRow.flowNodes" :key="index" class="detail-node">
@@ -145,6 +191,7 @@
        </div>
      </div>
      <el-empty v-else description="暂无流程节点" :image-size="60" />
      </div>
      <template #footer>
        <el-button @click="detailDialog.visible = false">关 闭</el-button>
        <el-button type="primary" @click="editFromDetail">编 辑</el-button>
@@ -156,16 +203,23 @@
<script setup>
import { ArrowRight, Document, Plus, RefreshRight } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
import { onMounted, ref } from "vue";
import { computed, onMounted, ref } from "vue";
import { userListNoPageByTenantId } from "@/api/system/user.js";
import FormConfigEditor from "./components/FormConfigEditor.vue";
import TemplateFlowEditor from "./components/TemplateFlowEditor.vue";
import { formatDisplayTime } from "./approveTemplateConstants.js";
import { formatDefaultValueDisplay, formFieldTypeLabel, parseFormConfigToData } from "./formConfigUtils.js";
import { useApproveTemplate } from "./useApproveTemplate.js";
const at = useApproveTemplate();
const {
  Search,
  TEMPLATE_TYPE_OPTIONS,
  templateTypeLabel,
  activeTab,
  builtinTemplates,
  builtinLoading,
  loadBuiltinTemplates,
  nodeSignModeLabel,
  searchForm,
  tableLoading,
@@ -178,6 +232,8 @@
  formRules,
  detailDialog,
  detailRow,
  detailLoading,
  fetchTemplateList,
  handleQuery,
  resetSearch,
  pagination,
@@ -187,6 +243,10 @@
} = at;
const flowUserOptions = ref([]);
const detailFormConfig = computed(() =>
  parseFormConfigToData(detailRow.value?.formConfigData ?? detailRow.value?.formConfig)
);
function unwrapArray(payload) {
  if (Array.isArray(payload)) return payload;
@@ -227,7 +287,8 @@
onMounted(() => {
  loadUsers();
  handleQuery();
  loadBuiltinTemplates();
  fetchTemplateList();
});
</script>
@@ -237,6 +298,9 @@
}
.mb16 {
  margin-bottom: 16px;
}
.mb16.el-empty {
  padding: 8px 0;
}
.ml10 {
  margin-left: 10px;
@@ -355,6 +419,9 @@
  transform: translateY(-50%);
  color: var(--el-text-color-placeholder);
}
.detail-dialog-body {
  min-height: 120px;
}
.text-muted {
  font-size: 12px;
  color: var(--el-text-color-placeholder);