yyb
8 小时以前 a1df9699594b0a0e46d26a0394eafb1eb030c68b
src/views/officeProcessAutomation/EnterpriseNews/news-manage/index.vue
@@ -1,4 +1,4 @@
<!--OA模块:EnterpriseNews 企业新闻(列表走审批实例,新增/修改保留原表单 + 模板审批流程)-->
<!--OA模块:EnterpriseNews 企业新闻(listPage|save|update|delete,新建保留审批模板)-->
<template>
  <div class="app-container enterprise-news-page">
    <div class="search_form mb20">
@@ -7,7 +7,7 @@
        <el-input
          v-model="searchForm.keyword"
          style="width: 200px"
          placeholder="标题 / 编号 / 摘要"
          placeholder="标题"
          clearable
          :prefix-icon="Search"
          @keyup.enter="onSearch"
@@ -16,16 +16,16 @@
        <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>
        <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"
            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.createTimeRange"
          type="daterange"
@@ -72,7 +72,6 @@
      @closed="onTemplateBindClosed"
    />
    <!-- 新建 / 编辑:原企业新闻表单 + 模板审批流程 -->
    <el-dialog
      v-model="newsFormDialog.visible"
      :title="newsFormDialog.title"
@@ -171,45 +170,41 @@
        <el-form-item label="政策类必读">
          <el-switch v-model="newsForm.requireReadConfirm" active-text="需阅读确认(便于统计未读)" />
        </el-form-item>
        <el-form-item label="发布人">
          <el-input v-model="newsForm.publisherName" placeholder="如:人力资源部" maxlength="50" />
        </el-form-item>
        <template v-if="activeTemplate">
        <template v-if="hasApprovalTemplate">
          <el-divider content-position="left">审批流程</el-divider>
          <el-form-item label="审批模板">
            <span class="template-name">{{ activeTemplate.label || submitForm.templateName }}</span>
            <span class="template-name">{{ approvalTemplateLabel }}</span>
          </el-form-item>
          <el-form-item label="审批流程" required>
          <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 type="warning" show-icon :closable="false" title="请先通过「新建新闻」选择审批模板" />
        <el-alert
          v-else-if="!isNewsEdit"
          type="warning"
          show-icon
          :closable="false"
          title="请先通过「新建新闻」选择审批模板"
        />
      </el-form>
      <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 :loading="newsSaving" @click="onNewsSave('draft')">存草稿</el-button>
        <el-button type="warning" :loading="newsSaving" @click="onNewsSave('submit_review')">
          提交审核
        </el-button>
        <el-button type="primary" :loading="submitSaving" @click="onNewsSave('submit_review')">
        <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="detailNewsRow" />
      <el-divider content-position="left">审批信息</el-divider>
      <ApproveDetailPanel :row="detailRow" />
      <template #footer>
        <el-button
          v-if="canEditBusinessInstanceRow(detailRow)"
          type="primary"
          @click="openNewsEditFromDetail"
        >
        <el-button v-if="canEditEnterpriseNewsRow(detailRow)" type="primary" @click="openNewsEditFromDetail">
          修改
        </el-button>
        <el-button @click="detailDialog.visible = false">关 闭</el-button>
@@ -220,22 +215,27 @@
<script setup>
import { Plus, RefreshRight, Search } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
import { ElMessage, ElMessageBox } from "element-plus";
import {
  deleteEnterpriseNews,
  saveEnterpriseNews,
  updateEnterpriseNews,
} from "@/api/officeProcessAutomation/enterpriseNews.js";
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 { 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 { createEmptySubmitForm } from "../../ApproveManage/approve-list/approveListConstants.js";
import { APPROVAL_MODULE_KEYS } from "../../ApproveManage/approve-shared/approvalModuleRegistry.js";
import { useApprovalInstanceModule } from "../../ApproveManage/approve-shared/useApprovalInstanceModule.js";
import {
  applyBindingToForm,
  validateTemplateBinding,
} from "../../ApproveManage/approve-shared/approvalTemplateBindingUtils.js";
import { useFlowUserOptions } from "../../ApproveManage/approve-shared/useFlowUserOptions.js";
import NewsDetailPanel from "./components/NewsDetailPanel.vue";
import {
@@ -245,18 +245,18 @@
  PUBLISH_ROLE_OPTIONS,
  DEPT_OPTIONS,
  createEmptyForm,
  ENTERPRISE_NEWS_STATUS_SEARCH_OPTIONS,
  newsTypeColor,
  newsTypeLabel,
  validateNewsForm,
} from "./enterpriseNewsUtils.js";
import {
  enrichEnterpriseNewsListRow,
  extractEnterpriseNewsFromRow,
  syncNewsFormToSubmitPayload,
  buildEnterpriseNewsSaveDto,
  buildEnterpriseNewsTableColumns,
} from "./enterpriseNewsApprovalBridge.js";
const userStore = useUserStore();
  canEditEnterpriseNewsRow,
  mapApiRowToNewsForm,
} from "./enterpriseNewsMappers.js";
import { useEnterpriseNewsList } from "./useEnterpriseNewsList.js";
const searchForm = reactive({
  keyword: "",
@@ -266,6 +266,8 @@
});
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("");
@@ -276,66 +278,44 @@
  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 newsList = useEnterpriseNewsList();
const { tableData, tableLoading, page, handleQuery: fetchNewsList, pagination: paginateNewsList } =
  newsList;
const {
  tableData,
  tableLoading,
  page,
  detailDialog,
  detailRow,
  submitDialog,
  submitForm,
  submitSaving,
  isSubmitEdit,
  activeTemplate,
  templateBindVisible,
  pendingTemplateBinding,
  submitEditRow,
  handleQuery,
  initModuleList,
  pagination,
  openAddWithTemplate,
  onTemplateBound,
  resetSubmitForm,
  submitInstanceForm,
  removeInstance,
  canEditBusinessInstanceRow,
} = mod;
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();
const detailNewsRow = computed(() => {
  if (!detailRow.value?.id) return {};
  return extractEnterpriseNewsFromRow(detailRow.value);
});
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(() => [
@@ -343,13 +323,14 @@
    {
      name: "修改",
      type: "text",
      disabled: (row) => !canEditBusinessInstanceRow(row),
      disabled: (row) => !canEditEnterpriseNewsRow(row),
      clickFun: (row) => openNewsEdit(row),
    },
    {
      name: "删除",
      type: "danger",
      clickFun: (row) => removeInstance(row),
      disabled: (row) => !canEditEnterpriseNewsRow(row),
      clickFun: (row) => handleNewsDelete(row),
    },
  ])
);
@@ -364,11 +345,9 @@
  newsFormDialog.title =
    mode === "add" ? "新建企业新闻" : mode === "edit" ? "编辑企业新闻" : "查看企业新闻";
  if (mode === "add") {
    resetNewsForm({
      publisherName: userStore?.nickName || userStore?.name || "当前用户",
    });
    resetNewsForm();
  } else if (row) {
    resetNewsForm(extractEnterpriseNewsFromRow(row));
    resetNewsForm(mapApiRowToNewsForm(row));
  }
  newsFormDialog.visible = true;
}
@@ -379,19 +358,23 @@
  pendingTemplateBinding.value = null;
  resetSubmitForm();
  applyBindingToForm(submitForm, binding);
  submitDialog.mode = "add";
  submitEditRow.value = null;
  if (binding.templateId) {
    newsForm.templateId = binding.templateId;
    newsForm.templateName = binding.templateName || "";
  }
  openNewsFormDialog("add");
}
function openNewsEdit(row) {
  if (!canEditBusinessInstanceRow(row)) {
    ElMessage.warning("进行中或已完成的审批不可修改");
  if (!canEditEnterpriseNewsRow(row)) {
    ElMessage.warning("当前状态不可修改");
    return;
  }
  submitDialog.mode = "edit";
  submitEditRow.value = { ...row };
  Object.assign(submitForm, buildEditFormFromInstanceRow(row));
  resetSubmitForm();
  if (row?.templateId != null) {
    submitForm.templateId = row.templateId;
    submitForm.templateName = row.templateName || "";
  }
  openNewsFormDialog("edit", row);
}
@@ -404,6 +387,40 @@
  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() {
@@ -425,19 +442,67 @@
    ElMessage.warning("请完善表单必填项后再保存");
    return;
  }
  if (action === "draft") newsForm.publishStatus = "draft";
  else newsForm.publishStatus = "pending_review";
  const ok = await submitInstanceForm({ skipValidate: true });
  if (ok) {
  const v = validateNewsForm(newsForm);
  if (!v.ok) {
    ElMessage.warning(v.message);
    return;
  }
  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" ? "已保存草稿" : isSubmitEdit.value ? "修改成功" : "已提交审核";
      action === "draft" ? "已保存草稿" : isNewsEdit.value ? "修改成功" : "已提交审核";
    ElMessage.success(msg);
    if (!isNewsEdit.value) page.current = 1;
    await fetchNewsList(searchForm);
  } catch {
    /* 错误由请求拦截器提示 */
  } finally {
    newsSaving.value = false;
  }
}
function onSearch() {
  handleQuery(searchForm);
  fetchNewsList(searchForm);
}
function resetSearch() {
@@ -449,17 +514,12 @@
}
function onPagination(obj) {
  pagination(obj, searchForm);
  paginateNewsList(obj, searchForm);
}
onMounted(async () => {
  try {
    localStorage.removeItem("oa_enterprise_news_v1");
  } catch {
    /* 清除历史本地演示缓存 */
  }
onMounted(() => {
  loadFlowUsers();
  await initModuleList(searchForm);
  fetchNewsList(searchForm);
});
</script>