| | |
| | | <!--OA模块:EnterpriseNews 企业新闻--> |
| | | <!--OA模块:EnterpriseNews 企业新闻(列表走审批实例,新增/修改保留原表单 + 模板审批流程)--> |
| | | <template> |
| | | <div class="app-container enterprise-news-page"> |
| | | |
| | | <div class="search_form mb20"> |
| | | <div class="search_fields"> |
| | | <span class="search_title">关键词:</span> |
| | |
| | | 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="开始" |
| | |
| | | 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-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> |
| | | |
| | |
| | | .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; |