From 1160de5142cd2bc08ebc61c247a4857f0c4ab7f1 Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期五, 22 五月 2026 10:45:11 +0800
Subject: [PATCH] 特色功能:丰富报销清单并添加审批流程详情——新增功能以丰富报销清单行内容,为费用和差旅报销提供审批流程详情。——引入新的实用函数来处理审批流程节点和汇总信息。——更新组件以利用丰富后的审批流程数据,从而更好地展示审批进度。

---
 src/views/officeProcessAutomation/EnterpriseNews/news-manage/index.vue |  619 ++++++++++++++++++++++++++++++++-----------------------
 1 files changed, 362 insertions(+), 257 deletions(-)

diff --git a/src/views/officeProcessAutomation/EnterpriseNews/news-manage/index.vue b/src/views/officeProcessAutomation/EnterpriseNews/news-manage/index.vue
index a8b743a..f263a41 100644
--- a/src/views/officeProcessAutomation/EnterpriseNews/news-manage/index.vue
+++ b/src/views/officeProcessAutomation/EnterpriseNews/news-manage/index.vue
@@ -1,29 +1,33 @@
-<!--OA妯″潡锛欵nterpriseNews 浼佷笟鏂伴椈-->
+<!--OA妯″潡锛欵nterpriseNews 浼佷笟鏂伴椈锛坙istPage|save|update|delete锛屾柊寤轰繚鐣欏鎵规ā鏉匡級-->
 <template>
   <div class="app-container enterprise-news-page">
-
     <div class="search_form mb20">
       <div class="search_fields">
         <span class="search_title">鍏抽敭璇嶏細</span>
         <el-input
           v-model="searchForm.keyword"
           style="width: 200px"
-          placeholder="鏍囬 / 缂栧彿 / 鎽樿"
+          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" />
+        <el-select v-model="searchForm.status" placeholder="鍏ㄩ儴" clearable style="width: 120px">
+          <el-option
+            v-for="opt in ENTERPRISE_NEWS_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,41 @@
       </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 +110,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 +142,380 @@
         <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-form-item>
+
+        <template v-if="hasApprovalTemplate">
+          <el-divider content-position="left">瀹℃壒娴佺▼</el-divider>
+          <el-form-item label="瀹℃壒妯℃澘">
+            <span class="template-name">{{ approvalTemplateLabel }}</span>
+          </el-form-item>
+          <el-form-item v-if="activeTemplate" label="瀹℃壒娴佺▼" required>
+            <TemplateFlowEditor v-model="submitForm.flowNodes" :user-options="flowUserOptions" />
+            <p class="section-tip">娴佺▼涓庡鎵逛汉鐢辨ā鏉块缃紝鍙寜闇�寰皟鑺傜偣瀹℃壒浜恒��</p>
+          </el-form-item>
+        </template>
+        <el-alert
+          v-else-if="!isNewsEdit"
+          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="newsSaving" @click="onNewsSave('draft')">瀛樿崏绋�</el-button>
+        <el-button type="warning" :loading="newsSaving" @click="onNewsSave('submit_review')">
+          鎻愪氦瀹℃牳
+        </el-button>
+        <el-button type="primary" :loading="newsSaving" @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" />
       <template #footer>
-        <el-button
-          v-if="detailRow.publishStatus === 'published' && getUnreadEmployees(detailRow).length"
-          type="warning"
-          @click="openUnreadFromDetail"
-        >
-          鏈鎻愰啋
+        <el-button v-if="canEditEnterpriseNewsRow(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 { ElMessage } from "element-plus";
+import { Plus, RefreshRight, Search } from "@element-plus/icons-vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import {
+  deleteEnterpriseNews,
+  saveEnterpriseNews,
+  updateEnterpriseNews,
+} from "@/api/officeProcessAutomation/enterpriseNews.js";
 import { computed, onMounted, reactive, ref } from "vue";
 import Editor from "@/components/Editor/index.vue";
 import FileUpload from "@/components/AttachmentUpload/file/index.vue";
-import { newsTypeColor } from "./enterpriseNewsUtils.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 { createEmptySubmitForm } from "../../ApproveManage/approve-list/approveListConstants.js";
+import { APPROVAL_MODULE_KEYS } from "../../ApproveManage/approve-shared/approvalModuleRegistry.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,
+  ENTERPRISE_NEWS_STATUS_SEARCH_OPTIONS,
+  newsTypeColor,
   newsTypeLabel,
-  searchForm,
-  tableLoading,
-  page,
-  tableData,
-  tableColumn,
-  formDialog,
-  form,
-  formRef,
-  formRules,
-  detailDialog,
-  detailRow,
-  unreadDialog,
-  unreadList,
-  versionDialog,
-  getUnreadEmployees,
-  handleQuery,
-  resetSearch,
-  pagination,
-  openFormDialog,
-  openDetail,
-  openUnreadRemind,
-  openVersionHistory,
-  saveForm,
-  sendUnreadRemind,
-  toggleLike,
-  addComment,
-} = useEnterpriseNews();
+  validateNewsForm,
+} from "./enterpriseNewsUtils.js";
+import {
+  buildEnterpriseNewsSaveDto,
+  buildEnterpriseNewsTableColumns,
+  canEditEnterpriseNewsRow,
+  mapApiRowToNewsForm,
+} from "./enterpriseNewsMappers.js";
+import { useEnterpriseNewsList } from "./useEnterpriseNewsList.js";
 
-const galleryInput = ref("");
-const unreadSelected = ref([]);
-const versionPreview = reactive({ visible: false, data: null });
-
-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 searchForm = reactive({
+  keyword: "",
+  newsType: "",
+  status: "",
+  createTimeRange: null,
 });
+
+const newsFormDialog = reactive({ visible: false, title: "", mode: "add", readonly: false });
+const detailDialog = reactive({ visible: false });
+const detailRow = ref({});
+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 newsList = useEnterpriseNewsList();
+const { tableData, tableLoading, page, handleQuery: fetchNewsList, pagination: paginateNewsList } =
+  newsList;
+
+const submitForm = reactive(createEmptySubmitForm(""));
+const templateBindVisible = ref(false);
+const pendingTemplateBinding = ref(null);
+const newsSaving = ref(false);
+
+const isNewsEdit = computed(() => newsFormDialog.mode === "edit");
+const activeTemplate = computed(() => submitForm.templateSnapshot || null);
+const hasApprovalTemplate = computed(
+  () => Boolean(activeTemplate.value || newsForm.templateId)
+);
+const approvalTemplateLabel = computed(
+  () =>
+    activeTemplate.value?.label ||
+    newsForm.templateName ||
+    submitForm.templateName ||
+    "鈥�"
+);
+
+const { flowUserOptions, loadFlowUsers } = useFlowUserOptions();
+
+function openAddWithTemplate() {
+  pendingTemplateBinding.value = null;
+  templateBindVisible.value = true;
+}
+
+function onTemplateBound(binding) {
+  pendingTemplateBinding.value = binding;
+}
+
+function resetSubmitForm() {
+  Object.assign(submitForm, createEmptySubmitForm(""));
+}
+
+const detailNewsRow = computed(() => mapApiRowToNewsForm(detailRow.value));
+
+const tableColumn = ref(
+  buildEnterpriseNewsTableColumns(() => [
+    { name: "璇︽儏", type: "text", clickFun: (row) => openNewsDetail(row) },
+    {
+      name: "淇敼",
+      type: "text",
+      disabled: (row) => !canEditEnterpriseNewsRow(row),
+      clickFun: (row) => openNewsEdit(row),
+    },
+    {
+      name: "鍒犻櫎",
+      type: "danger",
+      disabled: (row) => !canEditEnterpriseNewsRow(row),
+      clickFun: (row) => handleNewsDelete(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();
+  } else if (row) {
+    resetNewsForm(mapApiRowToNewsForm(row));
+  }
+  newsFormDialog.visible = true;
+}
+
+function onTemplateBindClosed() {
+  const binding = pendingTemplateBinding.value;
+  if (!binding) return;
+  pendingTemplateBinding.value = null;
+  resetSubmitForm();
+  applyBindingToForm(submitForm, binding);
+  if (binding.templateId) {
+    newsForm.templateId = binding.templateId;
+    newsForm.templateName = binding.templateName || "";
+  }
+  openNewsFormDialog("add");
+}
+
+function openNewsEdit(row) {
+  if (!canEditEnterpriseNewsRow(row)) {
+    ElMessage.warning("褰撳墠鐘舵�佷笉鍙慨鏀�");
+    return;
+  }
+  resetSubmitForm();
+  if (row?.templateId != null) {
+    submitForm.templateId = row.templateId;
+    submitForm.templateName = row.templateName || "";
+  }
+  openNewsFormDialog("edit", row);
+}
+
+function openNewsDetail(row) {
+  detailRow.value = { ...row };
+  detailDialog.visible = true;
+}
+
+function openNewsEditFromDetail() {
+  const row = detailRow.value;
+  detailDialog.visible = false;
+  openNewsEdit(row);
+}
+
+async function handleNewsDelete(row) {
+  if (!canEditEnterpriseNewsRow(row)) {
+    ElMessage.warning("褰撳墠鐘舵�佷笉鍙垹闄�");
+    return;
+  }
+  if (row?.id == null || row.id === "") {
+    ElMessage.warning("鏃犳硶鍒犻櫎锛氱己灏戞柊闂� ID");
+    return;
+  }
+  const title = (row.title || "").trim() || "璇ユ潯鏂伴椈";
+  try {
+    await ElMessageBox.confirm(
+      `纭畾瑕佸垹闄ゃ��${title}銆嶅悧锛熷垹闄ゅ悗涓嶅彲鎭㈠銆俙,
+      "鍒犻櫎纭",
+      {
+        type: "warning",
+        confirmButtonText: "纭畾鍒犻櫎",
+        cancelButtonText: "鍙栨秷",
+        distinguishCancelAndClose: true,
+        autofocus: false,
+      }
+    );
+  } catch {
+    return;
+  }
+  try {
+    await deleteEnterpriseNews([row.id]);
+    ElMessage.success("鍒犻櫎鎴愬姛");
+    await fetchNewsList(searchForm);
+  } catch {
+    /* 閿欒鐢辫姹傛嫤鎴櫒鎻愮ず */
+  }
+}
+
+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: `/mock/${name}` });
+  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" ? "宸叉彁浜ゅ鏍�" : "宸蹭繚瀛�");
-  }
-}
-
-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 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);
+  const v = validateNewsForm(newsForm);
+  if (!v.ok) {
+    ElMessage.warning(v.message);
     return;
   }
-  if (ret?.ok) ElMessage.success(`宸插悜 ${ret.count} 鍚嶅憳宸ュ彂閫侀槄璇绘彁閱抈);
+  const status = action === "draft" ? "DRAFT" : "PENDING";
+  newsForm.publishStatus = status;
+
+  if (!isNewsEdit.value) {
+    const templateId = newsForm.templateId || submitForm.templateId;
+    if (!templateId) {
+      ElMessage.warning("璇峰厛閫夋嫨瀹℃壒妯℃澘");
+      return;
+    }
+    if (!newsForm.templateId) newsForm.templateId = templateId;
+    if (!newsForm.templateName && submitForm.templateName) {
+      newsForm.templateName = submitForm.templateName;
+    }
+    if (action !== "draft") {
+      const bindingCheck = validateTemplateBinding({ flowNodes: submitForm.flowNodes });
+      if (!bindingCheck.ok) {
+        ElMessage.warning(bindingCheck.message);
+        return;
+      }
+    }
+  } else if (!newsForm.templateId && submitForm.templateId) {
+    newsForm.templateId = submitForm.templateId;
+    newsForm.templateName = submitForm.templateName || newsForm.templateName;
+  }
+
+  const dto = buildEnterpriseNewsSaveDto(newsForm, { status });
+  if (isNewsEdit.value) {
+    if (dto.id == null) {
+      ElMessage.warning("鏃犳硶淇敼锛氱己灏戞柊闂� ID");
+      return;
+    }
+  }
+
+  if (newsSaving.value) return;
+  newsSaving.value = true;
+  try {
+    if (isNewsEdit.value) {
+      await updateEnterpriseNews(dto);
+    } else {
+      await saveEnterpriseNews(dto);
+    }
+    newsFormDialog.visible = false;
+    const msg =
+      action === "draft" ? "宸蹭繚瀛樿崏绋�" : isNewsEdit.value ? "淇敼鎴愬姛" : "宸叉彁浜ゅ鏍�";
+    ElMessage.success(msg);
+    if (!isNewsEdit.value) page.current = 1;
+    await fetchNewsList(searchForm);
+  } catch {
+    /* 閿欒鐢辫姹傛嫤鎴櫒鎻愮ず */
+  } finally {
+    newsSaving.value = false;
+  }
 }
 
-function previewVersion(ver) {
-  versionPreview.data = ver;
-  versionPreview.visible = true;
+function onSearch() {
+  fetchNewsList(searchForm);
+}
+
+function resetSearch() {
+  searchForm.keyword = "";
+  searchForm.newsType = "";
+  searchForm.status = "";
+  searchForm.createTimeRange = null;
+  onSearch();
+}
+
+function onPagination(obj) {
+  paginateNewsList(obj, searchForm);
 }
 
 onMounted(() => {
-  handleQuery();
+  loadFlowUsers();
+  fetchNewsList(searchForm);
 });
 </script>
 
@@ -421,6 +536,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 +547,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