import { Search } from "@element-plus/icons-vue"; import dayjs from "dayjs"; import { ElMessageBox } from "element-plus"; import { computed, reactive, ref, watch } from "vue"; import { NEWS_TYPE_OPTIONS, PUBLISH_STATUS_OPTIONS, LAYOUT_TEMPLATE_OPTIONS, READ_SCOPE_OPTIONS, PUBLISH_ROLE_OPTIONS, DEPT_OPTIONS, createEmptyForm, createInitialMockNews, loadStoredNews, saveStoredNews, getUnreadEmployees, readRate, nextNewsNo, pushVersionBeforeUpdate, validateNewsForm, newsTypeLabel, publishStatusLabel, } from "./enterpriseNewsUtils.js"; export function useEnterpriseNews() { const stored = loadStoredNews(); const allRows = ref(stored?.length ? stored : createInitialMockNews()); const searchForm = reactive({ keyword: "", newsType: "", publishStatus: "", publishTimeRange: [], }); const tableLoading = ref(false); const page = reactive({ current: 1, size: 10, total: 0 }); const formDialog = reactive({ visible: false, title: "", mode: "add", readonly: false }); const form = reactive(createEmptyForm()); const formRef = ref(); const detailDialog = reactive({ visible: false }); const detailRow = ref({}); const unreadDialog = reactive({ visible: false, row: null }); const unreadSelection = ref([]); const versionDialog = reactive({ visible: false, row: null }); const filteredList = computed(() => { let list = [...allRows.value]; const kw = (searchForm.keyword || "").trim().toLowerCase(); if (kw) { list = list.filter((r) => { const title = (r.title || "").toLowerCase(); const summary = (r.summary || "").toLowerCase(); const no = (r.newsNo || "").toLowerCase(); return title.includes(kw) || summary.includes(kw) || no.includes(kw); }); } if (searchForm.newsType) { list = list.filter((r) => r.newsType === searchForm.newsType); } if (searchForm.publishStatus) { list = list.filter((r) => r.publishStatus === searchForm.publishStatus); } const range = searchForm.publishTimeRange; if (range?.length === 2 && range[0] && range[1]) { const start = dayjs(range[0]).startOf("day"); const end = dayjs(range[1]).endOf("day"); list = list.filter((r) => { if (!r.publishTime) return false; const t = dayjs(r.publishTime); return t.isAfter(start) && t.isBefore(end); }); } return list.sort((a, b) => (String(a.updateTime) < String(b.updateTime) ? 1 : -1)); }); watch( filteredList, (list) => { page.total = list.length; const maxPage = Math.max(1, Math.ceil(list.length / page.size) || 1); if (page.current > maxPage) page.current = maxPage; }, { immediate: true } ); const tableData = computed(() => { const start = (page.current - 1) * page.size; return filteredList.value.slice(start, start + page.size); }); const unreadList = computed(() => { if (!unreadDialog.row) return []; return getUnreadEmployees(unreadDialog.row); }); const formRules = { title: [{ required: true, message: "请输入新闻标题", trigger: "blur" }], newsType: [{ required: true, message: "请选择新闻分类", trigger: "change" }], readScope: [{ required: true, message: "请选择阅读范围", trigger: "change" }], }; const tableColumn = ref([ { label: "编号", prop: "newsNo", width: 150 }, { label: "标题", prop: "title", minWidth: 180, showOverflowTooltip: true }, { label: "分类", prop: "newsType", width: 100, dataType: "slot", slot: "newsType", }, { label: "状态", prop: "publishStatus", width: 90, dataType: "tag", formatData: (v) => publishStatusLabel(v), formatType: (v) => { const hit = PUBLISH_STATUS_OPTIONS.find((x) => x.value === v); return hit?.tag || "info"; }, }, { label: "阅读率", prop: "readRecords", width: 90, align: "center", formatData: (_, row) => `${readRate(row)}%`, }, { label: "未读", prop: "id", width: 70, align: "center", formatData: (_, row) => { if (row.publishStatus !== "published") return "—"; return getUnreadEmployees(row).length; }, }, { label: "发布人", prop: "publisherName", width: 110 }, { label: "发布时间", prop: "publishTime", width: 170 }, { label: "更新时间", prop: "updateTime", width: 170 }, { dataType: "action", label: "操作", align: "center", fixed: "right", width: 280, operation: [ { name: "详情", type: "text", clickFun: (row) => openDetail(row) }, { name: "编辑", type: "text", disabled: (row) => row.publishStatus === "archived", clickFun: (row) => openFormDialog("edit", row), }, { name: "审核", type: "text", disabled: (row) => row.publishStatus !== "pending_review", clickFun: (row) => openReview(row), }, { name: "未读提醒", type: "text", disabled: (row) => row.publishStatus !== "published" || getUnreadEmployees(row).length === 0, clickFun: (row) => openUnreadRemind(row), }, { name: "版本留证", type: "text", clickFun: (row) => openVersionHistory(row) }, ], }, ]); function persist() { saveStoredNews(allRows.value); } function handleQuery() { tableLoading.value = true; page.current = 1; setTimeout(() => { tableLoading.value = false; }, 200); } function resetSearch() { searchForm.keyword = ""; searchForm.newsType = ""; searchForm.publishStatus = ""; searchForm.publishTimeRange = []; handleQuery(); } function pagination({ page: p, limit }) { page.current = p; page.size = limit; } function resetForm(target = createEmptyForm()) { Object.assign(form, createEmptyForm(), target); } function openFormDialog(mode, row) { formDialog.mode = mode; formDialog.readonly = mode === "view"; formDialog.title = mode === "add" ? "新建企业新闻" : mode === "edit" ? "编辑企业新闻" : "查看企业新闻"; if (mode === "add") { resetForm({ publisherName: "当前用户" }); } else { resetForm({ ...JSON.parse(JSON.stringify(row)), targetDeptIds: [...(row.targetDeptIds || [])], targetUserIds: [...(row.targetUserIds || [])], mediaList: [...(row.mediaList || [])], attachmentList: [...(row.attachmentList || [])], }); } formDialog.visible = true; } function openDetail(row) { detailRow.value = { ...row }; detailDialog.visible = true; } function openUnreadRemind(row) { unreadDialog.row = row; unreadSelection.value = []; unreadDialog.visible = true; } function openVersionHistory(row) { versionDialog.row = row; versionDialog.visible = true; } async function openReview(row) { try { await ElMessageBox.confirm( `确认审核通过并发布「${row.title}」?外部/行业类新闻需管理员审核。`, "审核发布", { type: "warning", confirmButtonText: "通过并发布", cancelButtonText: "取消" } ); const hit = allRows.value.find((r) => r.id === row.id); if (!hit) return; hit.publishStatus = "published"; hit.publishTime = dayjs().format("YYYY-MM-DD HH:mm:ss"); hit.updateTime = hit.publishTime; if (!hit.readRecords?.length) { hit.readRecords = []; } persist(); return true; } catch { return false; } } function saveForm(submitAction = "save") { const v = validateNewsForm(form); if (!v.ok) return { ok: false, message: v.message }; const now = dayjs().format("YYYY-MM-DD HH:mm:ss"); const payload = { ...JSON.parse(JSON.stringify(form)), title: v.title, updateTime: now, }; if (formDialog.mode === "add") { payload.id = `news_${Date.now()}`; payload.newsNo = nextNewsNo(); payload.createTime = now; if (submitAction === "submit_review") { payload.publishStatus = "pending_review"; } else if (submitAction === "publish") { payload.publishStatus = "published"; payload.publishTime = now; } else { payload.publishStatus = "draft"; } allRows.value.unshift(payload); } else { const idx = allRows.value.findIndex((r) => r.id === form.id); if (idx < 0) return { ok: false, message: "记录不存在" }; const prev = allRows.value[idx]; if (prev.publishStatus === "published" && submitAction !== "draft") { pushVersionBeforeUpdate(prev, submitAction === "publish" ? "修订发布" : "内容更新"); } if (submitAction === "submit_review") { payload.publishStatus = "pending_review"; } else if (submitAction === "publish") { payload.publishStatus = "published"; payload.publishTime = payload.publishTime || now; } payload.versions = prev.versions || []; payload.versionNo = prev.versionNo || 1; if (prev.publishStatus === "published" && submitAction === "publish") { payload.versionNo = (prev.versionNo || 1) + 1; } allRows.value[idx] = { ...prev, ...payload }; } persist(); formDialog.visible = false; return { ok: true }; } function archiveNews(row) { const hit = allRows.value.find((r) => r.id === row.id); if (hit) { hit.publishStatus = "archived"; hit.updateTime = dayjs().format("YYYY-MM-DD HH:mm:ss"); persist(); } } function sendUnreadRemind(selectedIds) { const row = unreadDialog.row; if (!row || !selectedIds?.length) return { ok: false, message: "请选择要提醒的员工" }; const hit = allRows.value.find((r) => r.id === row.id); if (!hit) return { ok: false }; const now = dayjs().format("YYYY-MM-DD HH:mm:ss"); hit.remindLogs = hit.remindLogs || []; hit.remindLogs.push({ time: now, count: selectedIds.length, operator: "HR", userIds: [...selectedIds], }); const records = hit.readRecords || []; selectedIds.forEach((uid) => { let rec = records.find((r) => r.userId === uid); if (!rec) { const emp = getUnreadEmployees(hit).find((e) => e.userId === uid); rec = { userId: uid, employeeNo: emp?.employeeNo || "", name: emp?.name || "", deptName: emp?.deptName || "", readAt: "", lastRemindAt: now, }; records.push(rec); } else { rec.lastRemindAt = now; } }); hit.readRecords = records; hit.updateTime = now; persist(); unreadDialog.visible = false; return { ok: true, count: selectedIds.length }; } function toggleLike(row, userId = "u1", userName = "张三") { const hit = allRows.value.find((r) => r.id === row.id); if (!hit) return; hit.likes = hit.likes || []; const idx = hit.likes.findIndex((l) => l.userId === userId); if (idx >= 0) { hit.likes.splice(idx, 1); } else { hit.likes.push({ userId, name: userName, time: dayjs().format("YYYY-MM-DD HH:mm:ss") }); } persist(); if (detailRow.value?.id === row.id) { detailRow.value = { ...hit }; } } function addComment(row, content, userId = "u1", userName = "张三") { const text = (content || "").trim(); if (!text) return { ok: false, message: "请输入评论内容" }; const hit = allRows.value.find((r) => r.id === row.id); if (!hit) return { ok: false }; hit.comments = hit.comments || []; hit.comments.push({ id: `c_${Date.now()}`, userId, name: userName, content: text, time: dayjs().format("YYYY-MM-DD HH:mm:ss"), }); persist(); if (detailRow.value?.id === row.id) { detailRow.value = { ...hit }; } return { ok: true }; } return { Search, NEWS_TYPE_OPTIONS, PUBLISH_STATUS_OPTIONS, LAYOUT_TEMPLATE_OPTIONS, READ_SCOPE_OPTIONS, PUBLISH_ROLE_OPTIONS, DEPT_OPTIONS, newsTypeLabel, searchForm, tableLoading, page, tableData, tableColumn, formDialog, form, formRef, formRules, detailDialog, detailRow, unreadDialog, unreadList, unreadSelection, versionDialog, getUnreadEmployees, readRate, handleQuery, resetSearch, pagination, openFormDialog, openDetail, openUnreadRemind, openVersionHistory, openReview, saveForm, archiveNews, sendUnreadRemind, toggleLike, addComment, }; }