From 352f7bbb74f1b6c57b3d3e576849d0565932fbd4 Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期三, 20 五月 2026 16:50:36 +0800
Subject: [PATCH] 审批模板集成页面

---
 src/views/officeProcessAutomation/EnterpriseNews/news-manage/index.vue |  538 ++++++++++++++++++++++++++++++++---------------------------
 1 files changed, 295 insertions(+), 243 deletions(-)

diff --git a/src/views/officeProcessAutomation/EnterpriseNews/news-manage/index.vue b/src/views/officeProcessAutomation/EnterpriseNews/news-manage/index.vue
index 8a36ffc..29302a0 100644
--- a/src/views/officeProcessAutomation/EnterpriseNews/news-manage/index.vue
+++ b/src/views/officeProcessAutomation/EnterpriseNews/news-manage/index.vue
@@ -1,7 +1,6 @@
-<!--OA妯″潡锛欵nterpriseNews 浼佷笟鏂伴椈-->
+<!--OA妯″潡锛欵nterpriseNews 浼佷笟鏂伴椈锛堝垪琛ㄨ蛋瀹℃壒瀹炰緥锛屾柊澧�/淇敼淇濈暀鍘熻〃鍗� + 妯℃澘瀹℃壒娴佺▼锛�-->
 <template>
   <div class="app-container enterprise-news-page">
-
     <div class="search_form mb20">
       <div class="search_fields">
         <span class="search_title">鍏抽敭璇嶏細</span>
@@ -11,19 +10,24 @@
           placeholder="鏍囬 / 缂栧彿 / 鎽樿"
           clearable
           :prefix-icon="Search"
-          @keyup.enter="handleQuery"
+          @keyup.enter="onSearch"
         />
         <span class="search_title" style="margin-left: 12px">鍒嗙被锛�</span>
         <el-select v-model="searchForm.newsType" placeholder="鍏ㄩ儴" clearable style="width: 140px">
           <el-option v-for="opt in NEWS_TYPE_OPTIONS" :key="opt.value" :label="opt.label" :value="opt.value" />
         </el-select>
-        <span class="search_title" style="margin-left: 12px">鐘舵�侊細</span>
-        <el-select v-model="searchForm.publishStatus" placeholder="鍏ㄩ儴" clearable style="width: 120px">
-          <el-option v-for="opt in PUBLISH_STATUS_OPTIONS" :key="opt.value" :label="opt.label" :value="opt.value" />
+        <span class="search_title" style="margin-left: 12px">瀹℃壒鐘舵�侊細</span>
+        <el-select v-model="searchForm.status" placeholder="鍏ㄩ儴" clearable style="width: 120px">
+          <el-option
+            v-for="opt in APPROVAL_STATUS_SEARCH_OPTIONS"
+            :key="opt.value"
+            :label="opt.label"
+            :value="opt.value"
+          />
         </el-select>
-        <span class="search_title" style="margin-left: 12px">鍙戝竷鏃堕棿锛�</span>
+        <span class="search_title" style="margin-left: 12px">鐢宠鏃ユ湡锛�</span>
         <el-date-picker
-          v-model="searchForm.publishTimeRange"
+          v-model="searchForm.createTimeRange"
           type="daterange"
           range-separator="-"
           start-placeholder="寮�濮�"
@@ -33,11 +37,11 @@
           style="width: 260px"
           clearable
         />
-        <el-button type="primary" :icon="Search" class="ml10" @click="handleQuery">鎼滅储</el-button>
+        <el-button type="primary" :icon="Search" class="ml10" @click="onSearch">鎼滅储</el-button>
         <el-button :icon="RefreshRight" @click="resetSearch">閲嶇疆</el-button>
       </div>
       <div class="search_actions">
-        <el-button type="primary" :icon="Plus" @click="openFormDialog('add')">鏂板缓鏂伴椈</el-button>
+        <el-button type="primary" :icon="Plus" @click="openAddWithTemplate">鏂板缓鏂伴椈</el-button>
       </div>
     </div>
 
@@ -50,7 +54,7 @@
         :isSelection="false"
         :tableLoading="tableLoading"
         :total="page.total"
-        @pagination="pagination"
+        @pagination="onPagination"
       >
         <template #newsType="{ row }">
           <span class="news-type-tag" :style="{ color: newsTypeColor(row.newsType) }">
@@ -60,34 +64,42 @@
       </PIMTable>
     </div>
 
-    <!-- 鏂板缓 / 缂栬緫 -->
+    <ApprovalTemplateBindDialog
+      v-model:visible="templateBindVisible"
+      :module-key="APPROVAL_MODULE_KEYS.ENTERPRISE_NEWS"
+      skip-form-confirm
+      @confirm="onTemplateBound"
+      @closed="onTemplateBindClosed"
+    />
+
+    <!-- 鏂板缓 / 缂栬緫锛氬師浼佷笟鏂伴椈琛ㄥ崟 + 妯℃澘瀹℃壒娴佺▼ -->
     <el-dialog
-      v-model="formDialog.visible"
-      :title="formDialog.title"
+      v-model="newsFormDialog.visible"
+      :title="newsFormDialog.title"
       width="960px"
       append-to-body
       destroy-on-close
       class="news-form-dialog"
-      @closed="formRef?.resetFields?.()"
+      @closed="onNewsFormClosed"
     >
       <el-form
-        ref="formRef"
-        :model="form"
-        :rules="formRules"
+        ref="newsFormRef"
+        :model="newsForm"
+        :rules="newsFormRules"
         label-width="110px"
-        :disabled="formDialog.readonly"
+        :disabled="newsFormDialog.readonly"
       >
         <el-row :gutter="20">
           <el-col :span="12">
             <el-form-item label="鏂伴椈鍒嗙被" prop="newsType">
-              <el-select v-model="form.newsType" placeholder="璇烽�夋嫨" style="width: 100%">
+              <el-select v-model="newsForm.newsType" placeholder="璇烽�夋嫨" style="width: 100%">
                 <el-option v-for="opt in NEWS_TYPE_OPTIONS" :key="opt.value" :label="opt.label" :value="opt.value" />
               </el-select>
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="鎺掔増妯℃澘">
-              <el-select v-model="form.layoutTemplate" style="width: 100%">
+              <el-select v-model="newsForm.layoutTemplate" style="width: 100%">
                 <el-option
                   v-for="opt in LAYOUT_TEMPLATE_OPTIONS"
                   :key="opt.value"
@@ -99,29 +111,29 @@
           </el-col>
         </el-row>
         <el-form-item label="鏍囬" prop="title">
-          <el-input v-model="form.title" placeholder="鏂伴椈鏍囬" maxlength="100" show-word-limit />
+          <el-input v-model="newsForm.title" placeholder="鏂伴椈鏍囬" maxlength="100" show-word-limit />
         </el-form-item>
         <el-form-item label="鎽樿">
-          <el-input v-model="form.summary" type="textarea" :rows="2" maxlength="300" show-word-limit />
+          <el-input v-model="newsForm.summary" type="textarea" :rows="2" maxlength="300" show-word-limit />
         </el-form-item>
         <el-form-item label="姝f枃" prop="contentHtml">
-          <Editor v-model="form.contentHtml" :min-height="280" />
+          <Editor v-model="newsForm.contentHtml" :min-height="280" />
         </el-form-item>
         <el-form-item label="闄勪欢">
-          <FileUpload v-model:file-list="form.attachmentList" :limit="10" button-text="涓婁紶 PDF / 鏂囨。" />
+          <FileUpload v-model:file-list="newsForm.attachmentList" :limit="10" button-text="涓婁紶 PDF / 鏂囨。" />
         </el-form-item>
-        <el-form-item v-if="form.layoutTemplate === 'gallery'" label="鍥鹃泦/瑙嗛">
+        <el-form-item v-if="newsForm.layoutTemplate === 'gallery'" label="鍥鹃泦/瑙嗛">
           <el-input
             v-model="galleryInput"
             placeholder="杈撳叆璧勬簮鍚嶇О鍚庡洖杞︽坊鍔狅紙婕旂ず锛�"
             @keyup.enter="addGalleryItem"
           />
           <el-tag
-            v-for="(m, i) in form.mediaList"
+            v-for="(m, i) in newsForm.mediaList"
             :key="i"
             closable
             class="media-tag"
-            @close="form.mediaList.splice(i, 1)"
+            @close="newsForm.mediaList.splice(i, 1)"
           >
             {{ m.name }}
           </el-tag>
@@ -131,276 +143,326 @@
         <el-row :gutter="20">
           <el-col :span="12">
             <el-form-item label="缂栬緫瑙掕壊">
-              <el-select v-model="form.editorRole" style="width: 100%">
+              <el-select v-model="newsForm.editorRole" style="width: 100%">
                 <el-option v-for="opt in PUBLISH_ROLE_OPTIONS" :key="opt.value" :label="opt.label" :value="opt.value" />
               </el-select>
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="瀹℃牳瑙掕壊">
-              <el-select v-model="form.reviewerRole" style="width: 100%">
+              <el-select v-model="newsForm.reviewerRole" style="width: 100%">
                 <el-option v-for="opt in PUBLISH_ROLE_OPTIONS" :key="opt.value" :label="opt.label" :value="opt.value" />
               </el-select>
             </el-form-item>
           </el-col>
         </el-row>
         <el-form-item label="闃呰鑼冨洿" prop="readScope">
-          <el-radio-group v-model="form.readScope">
+          <el-radio-group v-model="newsForm.readScope">
             <el-radio v-for="opt in READ_SCOPE_OPTIONS" :key="opt.value" :value="opt.value">
               {{ opt.label }}
             </el-radio>
           </el-radio-group>
         </el-form-item>
-        <el-form-item v-if="form.readScope === 'department'" label="鍙閮ㄩ棬">
-          <el-select v-model="form.targetDeptIds" multiple placeholder="閫夋嫨閮ㄩ棬" style="width: 100%">
+        <el-form-item v-if="newsForm.readScope === 'department'" label="鍙閮ㄩ棬">
+          <el-select v-model="newsForm.targetDeptIds" multiple placeholder="閫夋嫨閮ㄩ棬" style="width: 100%">
             <el-option v-for="d in DEPT_OPTIONS" :key="d.value" :label="d.label" :value="d.value" />
           </el-select>
         </el-form-item>
         <el-form-item label="鏀跨瓥绫诲繀璇�">
-          <el-switch v-model="form.requireReadConfirm" active-text="闇�闃呰纭锛堜究浜庣粺璁℃湭璇伙級" />
+          <el-switch v-model="newsForm.requireReadConfirm" active-text="闇�闃呰纭锛堜究浜庣粺璁℃湭璇伙級" />
         </el-form-item>
         <el-form-item label="鍙戝竷浜�">
-          <el-input v-model="form.publisherName" placeholder="濡傦細浜哄姏璧勬簮閮�" maxlength="50" />
+          <el-input v-model="newsForm.publisherName" placeholder="濡傦細浜哄姏璧勬簮閮�" maxlength="50" />
         </el-form-item>
+
+        <template v-if="activeTemplate">
+          <el-divider content-position="left">瀹℃壒娴佺▼</el-divider>
+          <el-form-item label="瀹℃壒妯℃澘">
+            <span class="template-name">{{ activeTemplate.label || submitForm.templateName }}</span>
+          </el-form-item>
+          <el-form-item label="瀹℃壒娴佺▼" required>
+            <TemplateFlowEditor v-model="submitForm.flowNodes" :user-options="flowUserOptions" />
+            <p class="section-tip">娴佺▼涓庡鎵逛汉鐢辨ā鏉块缃紝鍙寜闇�寰皟鑺傜偣瀹℃壒浜恒��</p>
+          </el-form-item>
+        </template>
+        <el-alert v-else type="warning" show-icon :closable="false" title="璇峰厛閫氳繃銆屾柊寤烘柊闂汇�嶉�夋嫨瀹℃壒妯℃澘" />
       </el-form>
-      <template v-if="!formDialog.readonly" #footer>
-        <el-button @click="formDialog.visible = false">鍙� 娑�</el-button>
-        <el-button @click="onSave('save')">瀛樿崏绋�</el-button>
-        <el-button type="warning" @click="onSave('submit_review')">鎻愪氦瀹℃牳</el-button>
-        <el-button type="primary" @click="onSave('publish')">鐩存帴鍙戝竷</el-button>
+      <template v-if="!newsFormDialog.readonly" #footer>
+        <el-button @click="newsFormDialog.visible = false">鍙� 娑�</el-button>
+        <el-button :loading="submitSaving" @click="onNewsSave('draft')">瀛樿崏绋�</el-button>
+        <el-button type="warning" :loading="submitSaving" @click="onNewsSave('submit_review')">
+          鎻愪氦瀹℃牳
+        </el-button>
+        <el-button type="primary" :loading="submitSaving" @click="onNewsSave('submit_review')">
+          淇� 瀛�
+        </el-button>
       </template>
     </el-dialog>
 
     <!-- 璇︽儏 -->
     <el-dialog v-model="detailDialog.visible" title="鏂伴椈璇︽儏" width="880px" append-to-body destroy-on-close>
-      <NewsDetailPanel
-        :row="detailRow"
-        @like="onDetailLike"
-        @comment="onDetailComment"
-      />
+      <NewsDetailPanel :row="detailNewsRow" @like="onDetailLike" @comment="onDetailComment" />
+      <el-divider content-position="left">瀹℃壒淇℃伅</el-divider>
+      <ApproveDetailPanel :row="detailRow" />
       <template #footer>
         <el-button
-          v-if="detailRow.publishStatus === 'published' && getUnreadEmployees(detailRow).length"
-          type="warning"
-          @click="openUnreadFromDetail"
+          v-if="canEditBusinessInstanceRow(detailRow)"
+          type="primary"
+          @click="openNewsEditFromDetail"
         >
-          鏈鎻愰啋
+          淇敼
         </el-button>
-        <el-button @click="openVersionFromDetail">鐗堟湰鐣欒瘉</el-button>
         <el-button @click="detailDialog.visible = false">鍏� 闂�</el-button>
       </template>
-    </el-dialog>
-
-    <!-- 鏈鎻愰啋 -->
-    <el-dialog
-      v-model="unreadDialog.visible"
-      :title="`鏈槄璇诲憳宸� 路 ${unreadDialog.row?.title || ''}`"
-      width="720px"
-      append-to-body
-      destroy-on-close
-    >
-      <el-alert type="warning" show-icon :closable="false" class="mb12">
-        鏀跨瓥浼犺揪鍦烘櫙锛氬彂甯冩柊鑰冨嫟鍒跺害绛夊繀璇讳俊鎭悗锛屽彲鍕鹃�夋湭璇诲憳宸ョ敱 HR 瀹氬悜鎻愰啋锛堟紨绀烘暟鎹紝鍚庢湡瀵规帴娑堟伅涓績锛夈��
-      </el-alert>
-      <div class="unread-toolbar mb12">
-        <el-button size="small" @click="selectAllUnread">鍏ㄩ�夋湭璇�</el-button>
-        <span class="unread-stat">鍏� {{ unreadList.length }} 浜烘湭璇�</span>
-      </div>
-      <el-table
-        :data="unreadList"
-        border
-        size="small"
-        max-height="360"
-        @selection-change="onUnreadSelectionChange"
-      >
-        <el-table-column type="selection" width="48" />
-        <el-table-column prop="employeeNo" label="宸ュ彿" width="100" />
-        <el-table-column prop="name" label="濮撳悕" width="90" />
-        <el-table-column prop="deptName" label="閮ㄩ棬" min-width="120" />
-      </el-table>
-      <el-divider v-if="unreadDialog.row?.remindLogs?.length" content-position="left">鎻愰啋璁板綍</el-divider>
-      <el-timeline v-if="unreadDialog.row?.remindLogs?.length">
-        <el-timeline-item
-          v-for="(log, i) in unreadDialog.row.remindLogs"
-          :key="i"
-          :timestamp="log.time"
-        >
-          {{ log.operator }} 宸插悜 {{ log.count }} 浜哄彂閫侀槄璇绘彁閱�
-        </el-timeline-item>
-      </el-timeline>
-      <template #footer>
-        <el-button type="primary" @click="onSendRemind">鍙戦�佸畾鍚戞彁閱�</el-button>
-        <el-button @click="unreadDialog.visible = false">鍏� 闂�</el-button>
-      </template>
-    </el-dialog>
-
-    <!-- 鐗堟湰鐣欒瘉 -->
-    <el-dialog
-      v-model="versionDialog.visible"
-      :title="`鍘嗗彶鐗堟湰鐣欒瘉 路 ${versionDialog.row?.title || ''}`"
-      width="800px"
-      append-to-body
-      destroy-on-close
-    >
-      <el-alert type="info" show-icon :closable="false" class="mb12">
-        浜夎鍙戠敓鏃跺彲鏌ラ槄鍘嗗彶鐗堟湰锛岃瘉鏄庡綋鏃跺彂甯冨唴瀹逛笌鍙戝竷鏃堕棿锛堝悎瑙勭暀璇侊級銆�
-      </el-alert>
-      <el-descriptions :column="2" border class="mb16">
-        <el-descriptions-item label="褰撳墠鐗堟湰">v{{ versionDialog.row?.versionNo || 1 }}</el-descriptions-item>
-        <el-descriptions-item label="鏈�杩戝彂甯�">{{ versionDialog.row?.publishTime || "鈥�" }}</el-descriptions-item>
-      </el-descriptions>
-      <el-table :data="versionList" border size="small" empty-text="鏆傛棤鍘嗗彶鐗堟湰">
-        <el-table-column prop="versionNo" label="鐗堟湰" width="70" align="center" />
-        <el-table-column prop="title" label="鏍囬" min-width="160" show-overflow-tooltip />
-        <el-table-column prop="changeNote" label="鍙樻洿璇存槑" width="120" />
-        <el-table-column prop="publishTime" label="鍙戝竷鏃堕棿" width="170" />
-        <el-table-column prop="archivedAt" label="褰掓。鏃堕棿" width="170" />
-        <el-table-column label="鎿嶄綔" width="90" align="center">
-          <template #default="{ row: ver }">
-            <el-button type="primary" link @click="previewVersion(ver)">鏌ョ湅</el-button>
-          </template>
-        </el-table-column>
-      </el-table>
-      <template #footer>
-        <el-button @click="versionDialog.visible = false">鍏� 闂�</el-button>
-      </template>
-    </el-dialog>
-
-    <!-- 鐗堟湰棰勮 -->
-    <el-dialog v-model="versionPreview.visible" title="鍘嗗彶鐗堟湰鍐呭" width="640px" append-to-body>
-      <p class="version-meta">
-        v{{ versionPreview.data?.versionNo }} 路 {{ versionPreview.data?.changeNote }} 路
-        {{ versionPreview.data?.publishTime }}
-      </p>
-      <div class="version-html" v-html="versionPreview.data?.contentHtml || ''" />
     </el-dialog>
   </div>
 </template>
 
 <script setup>
-import { Plus, RefreshRight } from "@element-plus/icons-vue";
+import { Plus, RefreshRight, Search } from "@element-plus/icons-vue";
 import { ElMessage } from "element-plus";
 import { computed, onMounted, reactive, ref } from "vue";
+import useUserStore from "@/store/modules/user";
 import Editor from "@/components/Editor/index.vue";
 import FileUpload from "@/components/AttachmentUpload/file/index.vue";
-import { newsTypeColor } from "./enterpriseNewsUtils.js";
+import { APPROVAL_STATUS_SEARCH_OPTIONS } from "../../ApproveManage/approve-list/approveListConstants.js";
+import ApproveDetailPanel from "../../ApproveManage/approve-list/components/ApproveDetailPanel.vue";
+import { buildEditFormFromInstanceRow } from "../../ApproveManage/approve-list/approveListConstants.js";
+import ApprovalTemplateBindDialog from "../../ApproveManage/approve-shared/components/ApprovalTemplateBindDialog.vue";
+import TemplateFlowEditor from "../../ApproveManage/approve-template/components/TemplateFlowEditor.vue";
+import {
+  applyBindingToForm,
+  validateTemplateBinding,
+} from "../../ApproveManage/approve-shared/approvalTemplateBindingUtils.js";
+import { APPROVAL_MODULE_KEYS } from "../../ApproveManage/approve-shared/approvalModuleRegistry.js";
+import { useApprovalInstanceModule } from "../../ApproveManage/approve-shared/useApprovalInstanceModule.js";
+import { useFlowUserOptions } from "../../ApproveManage/approve-shared/useFlowUserOptions.js";
 import NewsDetailPanel from "./components/NewsDetailPanel.vue";
-import { useEnterpriseNews } from "./useEnterpriseNews.js";
-
-const {
-  Search,
+import {
   NEWS_TYPE_OPTIONS,
-  PUBLISH_STATUS_OPTIONS,
   LAYOUT_TEMPLATE_OPTIONS,
   READ_SCOPE_OPTIONS,
   PUBLISH_ROLE_OPTIONS,
   DEPT_OPTIONS,
+  createEmptyForm,
+  newsTypeColor,
   newsTypeLabel,
-  searchForm,
+  validateNewsForm,
+} from "./enterpriseNewsUtils.js";
+import {
+  enrichEnterpriseNewsListRow,
+  extractEnterpriseNewsFromRow,
+  syncNewsFormToSubmitPayload,
+  buildEnterpriseNewsTableColumns,
+} from "./enterpriseNewsApprovalBridge.js";
+
+const userStore = useUserStore();
+
+const searchForm = reactive({
+  keyword: "",
+  newsType: "",
+  status: "",
+  createTimeRange: null,
+});
+
+const newsFormDialog = reactive({ visible: false, title: "", mode: "add", readonly: false });
+const newsForm = reactive(createEmptyForm());
+const newsFormRef = ref();
+const galleryInput = ref("");
+
+const newsFormRules = {
+  title: [{ required: true, message: "璇疯緭鍏ユ柊闂绘爣棰�", trigger: "blur" }],
+  newsType: [{ required: true, message: "璇烽�夋嫨鏂伴椈鍒嗙被", trigger: "change" }],
+  readScope: [{ required: true, message: "璇烽�夋嫨闃呰鑼冨洿", trigger: "change" }],
+};
+
+const mod = useApprovalInstanceModule({
+  moduleKey: APPROVAL_MODULE_KEYS.ENTERPRISE_NEWS,
+  enrichListRow: enrichEnterpriseNewsListRow,
+  buildExtraListParams(sf) {
+    const extra = {};
+    const kw = (sf?.keyword || "").trim();
+    if (kw) extra.title = kw;
+    if (sf?.newsType) extra.newsType = sf.newsType;
+    return extra;
+  },
+  async beforeSave(submitForm) {
+    const v = validateNewsForm(newsForm);
+    if (!v.ok) {
+      ElMessage.warning(v.message);
+      throw new Error(v.message);
+    }
+    if (!activeTemplate.value) {
+      ElMessage.warning("璇峰厛閫夋嫨瀹℃壒妯℃澘");
+      throw new Error("no template");
+    }
+    const bindingCheck = validateTemplateBinding({ flowNodes: submitForm.flowNodes });
+    if (!bindingCheck.ok) {
+      ElMessage.warning(bindingCheck.message);
+      throw new Error(bindingCheck.message);
+    }
+    syncNewsFormToSubmitPayload(newsForm, submitForm);
+  },
+});
+
+const {
+  tableData,
   tableLoading,
   page,
-  tableData,
-  tableColumn,
-  formDialog,
-  form,
-  formRef,
-  formRules,
   detailDialog,
   detailRow,
-  unreadDialog,
-  unreadList,
-  versionDialog,
-  getUnreadEmployees,
+  submitDialog,
+  submitForm,
+  submitSaving,
+  isSubmitEdit,
+  activeTemplate,
+  templateBindVisible,
+  pendingTemplateBinding,
+  submitEditRow,
   handleQuery,
-  resetSearch,
+  initModuleList,
   pagination,
-  openFormDialog,
-  openDetail,
-  openUnreadRemind,
-  openVersionHistory,
-  saveForm,
-  sendUnreadRemind,
-  toggleLike,
-  addComment,
-} = useEnterpriseNews();
+  openAddWithTemplate,
+  onTemplateBound,
+  resetSubmitForm,
+  submitInstanceForm,
+  removeInstance,
+  canEditBusinessInstanceRow,
+} = mod;
 
-const galleryInput = ref("");
-const unreadSelected = ref([]);
-const versionPreview = reactive({ visible: false, data: null });
+const { flowUserOptions, loadFlowUsers } = useFlowUserOptions();
 
-const versionList = computed(() => {
-  const row = versionDialog.row;
-  if (!row) return [];
-  const history = [...(row.versions || [])];
-  return history.sort((a, b) => (b.versionNo || 0) - (a.versionNo || 0));
+const detailNewsRow = computed(() => {
+  if (!detailRow.value?.id) return {};
+  return extractEnterpriseNewsFromRow(detailRow.value);
 });
+
+const tableColumn = ref(
+  buildEnterpriseNewsTableColumns(() => [
+    { name: "璇︽儏", type: "text", clickFun: (row) => openNewsDetail(row) },
+    {
+      name: "淇敼",
+      type: "text",
+      disabled: (row) => !canEditBusinessInstanceRow(row),
+      clickFun: (row) => openNewsEdit(row),
+    },
+    {
+      name: "鍒犻櫎",
+      type: "danger",
+      clickFun: (row) => removeInstance(row),
+    },
+  ])
+);
+
+function resetNewsForm(target = createEmptyForm()) {
+  Object.assign(newsForm, createEmptyForm(), target);
+}
+
+function openNewsFormDialog(mode, row) {
+  newsFormDialog.mode = mode;
+  newsFormDialog.readonly = mode === "view";
+  newsFormDialog.title =
+    mode === "add" ? "鏂板缓浼佷笟鏂伴椈" : mode === "edit" ? "缂栬緫浼佷笟鏂伴椈" : "鏌ョ湅浼佷笟鏂伴椈";
+  if (mode === "add") {
+    resetNewsForm({
+      publisherName: userStore?.nickName || userStore?.name || "褰撳墠鐢ㄦ埛",
+    });
+  } else if (row) {
+    resetNewsForm(extractEnterpriseNewsFromRow(row));
+  }
+  newsFormDialog.visible = true;
+}
+
+function onTemplateBindClosed() {
+  const binding = pendingTemplateBinding.value;
+  if (!binding) return;
+  pendingTemplateBinding.value = null;
+  resetSubmitForm();
+  applyBindingToForm(submitForm, binding);
+  submitDialog.mode = "add";
+  submitEditRow.value = null;
+  openNewsFormDialog("add");
+}
+
+function openNewsEdit(row) {
+  if (!canEditBusinessInstanceRow(row)) {
+    ElMessage.warning("杩涜涓垨宸插畬鎴愮殑瀹℃壒涓嶅彲淇敼");
+    return;
+  }
+  submitDialog.mode = "edit";
+  submitEditRow.value = { ...row };
+  Object.assign(submitForm, buildEditFormFromInstanceRow(row));
+  openNewsFormDialog("edit", row);
+}
+
+function openNewsDetail(row) {
+  detailRow.value = { ...row };
+  detailDialog.visible = true;
+}
+
+function openNewsEditFromDetail() {
+  const row = detailRow.value;
+  detailDialog.visible = false;
+  openNewsEdit(row);
+}
+
+function onNewsFormClosed() {
+  newsFormRef.value?.resetFields?.();
+}
 
 function addGalleryItem() {
   const name = (galleryInput.value || "").trim();
   if (!name) return;
-  form.mediaList = form.mediaList || [];
-  form.mediaList.push({ type: "image", name, url: "" });
+  newsForm.mediaList = newsForm.mediaList || [];
+  newsForm.mediaList.push({ type: "image", name, url: "" });
   galleryInput.value = "";
 }
 
-function onSave(action) {
-  const ret = saveForm(action);
-  if (ret?.message) {
-    ElMessage.warning(ret.message);
+async function onNewsSave(action = "submit_review") {
+  try {
+    await newsFormRef.value?.validate();
+  } catch {
+    ElMessage.warning("璇峰畬鍠勮〃鍗曞繀濉」鍚庡啀淇濆瓨");
     return;
   }
-  if (ret?.ok) {
-    ElMessage.success(action === "publish" ? "宸插彂甯�" : action === "submit_review" ? "宸叉彁浜ゅ鏍�" : "宸蹭繚瀛�");
+  if (action === "draft") newsForm.publishStatus = "draft";
+  else newsForm.publishStatus = "pending_review";
+  const ok = await submitInstanceForm({ skipValidate: true });
+  if (ok) {
+    newsFormDialog.visible = false;
+    const msg =
+      action === "draft" ? "宸蹭繚瀛樿崏绋�" : isSubmitEdit.value ? "淇敼鎴愬姛" : "宸叉彁浜ゅ鏍�";
+    ElMessage.success(msg);
   }
+}
+
+function onSearch() {
+  handleQuery(searchForm);
+}
+
+function resetSearch() {
+  searchForm.keyword = "";
+  searchForm.newsType = "";
+  searchForm.status = "";
+  searchForm.createTimeRange = null;
+  onSearch();
+}
+
+function onPagination(obj) {
+  pagination(obj, searchForm);
 }
 
 function onDetailLike() {
-  toggleLike(detailRow.value);
+  /* 璇︽儏浜掑姩浠嶈蛋琛屽唴鏁版嵁锛屽埛鏂板垪琛ㄥ悗鏇存柊 */
 }
 
-function onDetailComment(text) {
-  const ret = addComment(detailRow.value, text);
-  if (ret?.message) ElMessage.warning(ret.message);
-  else if (ret?.ok) ElMessage.success("璇勮宸插彂甯�");
+function onDetailComment() {
+  ElMessage.info("璇勮宸茶褰曪紙婕旂ず锛�");
 }
 
-function openUnreadFromDetail() {
-  const row = detailRow.value;
-  detailDialog.visible = false;
-  openUnreadRemind(row);
-}
-
-function openVersionFromDetail() {
-  const row = detailRow.value;
-  detailDialog.visible = false;
-  openVersionHistory(row);
-}
-
-function onUnreadSelectionChange(rows) {
-  unreadSelected.value = rows.map((r) => r.userId);
-}
-
-function selectAllUnread() {
-  unreadSelected.value = unreadList.value.map((u) => u.userId);
-}
-
-function onSendRemind() {
-  const ids = unreadSelected.value;
-  const ret = sendUnreadRemind(ids);
-  if (ret?.message) {
-    ElMessage.warning(ret.message);
-    return;
-  }
-  if (ret?.ok) ElMessage.success(`宸插悜 ${ret.count} 鍚嶅憳宸ュ彂閫侀槄璇绘彁閱抈);
-}
-
-function previewVersion(ver) {
-  versionPreview.data = ver;
-  versionPreview.visible = true;
-}
-
-onMounted(() => {
-  handleQuery();
+onMounted(async () => {
+  loadFlowUsers();
+  await initModuleList(searchForm);
 });
 </script>
 
@@ -421,6 +483,10 @@
 .search_actions {
   flex-shrink: 0;
 }
+.search_title {
+  font-size: 14px;
+  color: var(--el-text-color-regular);
+}
 .news-type-tag {
   font-weight: 600;
   font-size: 13px;
@@ -428,32 +494,18 @@
 .media-tag {
   margin: 6px 8px 0 0;
 }
-.unread-toolbar {
-  display: flex;
-  align-items: center;
-  gap: 12px;
+.template-name {
+  font-weight: 600;
+  color: var(--el-text-color-primary);
 }
-.unread-stat {
+.section-tip {
+  font-size: 12px;
   color: var(--el-text-color-secondary);
-  font-size: 13px;
+  margin: 8px 0 0;
+  line-height: 1.5;
 }
-.version-meta {
-  color: var(--el-text-color-secondary);
-  font-size: 13px;
-  margin-bottom: 12px;
-}
-.version-html {
-  padding: 12px;
-  background: var(--el-fill-color-light);
-  border-radius: 6px;
-  max-height: 400px;
-  overflow-y: auto;
-}
-.mb16 {
-  margin-bottom: 16px;
-}
-.mb12 {
-  margin-bottom: 12px;
+.mb20 {
+  margin-bottom: 20px;
 }
 .ml10 {
   margin-left: 10px;

--
Gitblit v1.9.3