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,
|
};
|
}
|