| | |
| | | <!--OA模块:EnterpriseNews 企业新闻--> |
| | | <!--OA模块:EnterpriseNews 企业新闻(listPage|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="开始" |
| | |
| | | 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> |
| | | |
| | |
| | | :isSelection="false" |
| | | :tableLoading="tableLoading" |
| | | :total="page.total" |
| | | @pagination="pagination" |
| | | @pagination="onPagination" |
| | | > |
| | | <template #newsType="{ row }"> |
| | | <span class="news-type-tag" :style="{ color: newsTypeColor(row.newsType) }"> |
| | |
| | | </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" |
| | |
| | | </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="正文" 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> |
| | |
| | | <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 { |
| | | applyBindingToForm, |
| | | validateTemplateBinding, |
| | | } from "../../ApproveManage/approve-shared/approvalTemplateBindingUtils.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> |
| | | |
| | |
| | | .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; |
| | |
| | | .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; |