| | |
| | | <!--OA模块:EnterpriseNews 企业新闻(列表走审批实例,新增/修改保留原表单 + 模板审批流程)--> |
| | | <!--OA模块:EnterpriseNews 企业新闻(listPage|save|update|delete,新建保留审批模板)--> |
| | | <template> |
| | | <div class="app-container enterprise-news-page"> |
| | | <div class="search_form mb20"> |
| | |
| | | <el-input |
| | | v-model="searchForm.keyword" |
| | | style="width: 200px" |
| | | placeholder="标题 / 编号 / 摘要" |
| | | placeholder="标题" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | @keyup.enter="onSearch" |
| | |
| | | <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" |
| | |
| | | @closed="onTemplateBindClosed" |
| | | /> |
| | | |
| | | <!-- 新建 / 编辑:原企业新闻表单 + 模板审批流程 --> |
| | | <el-dialog |
| | | v-model="newsFormDialog.visible" |
| | | :title="newsFormDialog.title" |
| | |
| | | <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> |
| | |
| | | |
| | | <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 { |
| | |
| | | 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: "", |
| | |
| | | }); |
| | | |
| | | 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(""); |
| | |
| | | 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(() => [ |
| | |
| | | { |
| | | 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), |
| | | }, |
| | | ]) |
| | | ); |
| | |
| | | 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; |
| | | } |
| | |
| | | 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); |
| | | } |
| | | |
| | |
| | | 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() { |
| | |
| | | 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() { |
| | |
| | | } |
| | | |
| | | 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> |
| | | |