| | |
| | | { value: "culture", label: "文化活动", color: "#67c23a" }, |
| | | ]; |
| | | |
| | | /** 发布状态 */ |
| | | /** 企业新闻状态(与后端枚举一致) */ |
| | | export const PUBLISH_STATUS_OPTIONS = [ |
| | | { value: "draft", label: "草稿", tag: "info" }, |
| | | { value: "pending_review", label: "待审核", tag: "warning" }, |
| | | { value: "published", label: "已发布", tag: "success" }, |
| | | { value: "archived", label: "已归档", tag: "" }, |
| | | { value: "DRAFT", label: "草稿", tag: "info" }, |
| | | { value: "PENDING", label: "待审批", tag: "warning" }, |
| | | { value: "PUBLISHED", label: "已发布", tag: "success" }, |
| | | { value: "REJECTED", label: "驳回", tag: "danger" }, |
| | | { value: "OFFLINE", label: "已下线", tag: "info" }, |
| | | ]; |
| | | |
| | | /** 企业新闻列表筛选 */ |
| | | export const ENTERPRISE_NEWS_STATUS_SEARCH_OPTIONS = [...PUBLISH_STATUS_OPTIONS]; |
| | | |
| | | const LEGACY_PUBLISH_STATUS_MAP = { |
| | | draft: "DRAFT", |
| | | pending_review: "PENDING", |
| | | published: "PUBLISHED", |
| | | archived: "OFFLINE", |
| | | }; |
| | | |
| | | /** 统一为后端状态枚举值 */ |
| | | export function normalizeEnterpriseNewsStatus(v) { |
| | | if (v == null || v === "") return "DRAFT"; |
| | | const upper = String(v).trim().toUpperCase(); |
| | | if (upper === "APPROVED") return "PUBLISHED"; |
| | | const hit = PUBLISH_STATUS_OPTIONS.find((x) => x.value === upper); |
| | | if (hit) return hit.value; |
| | | const legacy = LEGACY_PUBLISH_STATUS_MAP[String(v).trim().toLowerCase()]; |
| | | if (legacy) return legacy; |
| | | return upper; |
| | | } |
| | | |
| | | /** 排版模板 */ |
| | | export const LAYOUT_TEMPLATE_OPTIONS = [ |
| | |
| | | { value: "editor", label: "内容编辑" }, |
| | | ]; |
| | | |
| | | export const STORAGE_KEY = "oa_enterprise_news_v1"; |
| | | |
| | | /** 演示用目标受众(后期对接组织架构) */ |
| | | export const MOCK_AUDIENCE = [ |
| | | { userId: "u1", employeeNo: "zhangsan", name: "张三", deptName: "研发部", isManagement: false }, |
| | | { userId: "u2", employeeNo: "lisi", name: "李四", deptName: "研发部", isManagement: false }, |
| | | { userId: "u3", employeeNo: "wangwu", name: "王五", deptName: "行政部", isManagement: false }, |
| | | { userId: "u4", employeeNo: "zhaoliu", name: "赵六", deptName: "销售部", isManagement: false }, |
| | | { userId: "u5", employeeNo: "sunqi", name: "孙七", deptName: "财务部", isManagement: false }, |
| | | { userId: "u6", employeeNo: "zhouba", name: "周八", deptName: "总经办", isManagement: true }, |
| | | { userId: "u7", employeeNo: "wujiu", name: "吴九", deptName: "总经办", isManagement: true }, |
| | | { userId: "u8", employeeNo: "zhengshi", name: "郑十", deptName: "人力资源部", isManagement: false }, |
| | | ]; |
| | | /** 目标受众(对接组织架构 API 前为空) */ |
| | | export const MOCK_AUDIENCE = []; |
| | | |
| | | const DEPT_OPTIONS = [ |
| | | { value: "101", label: "研发部" }, |
| | |
| | | } |
| | | |
| | | export function publishStatusLabel(v) { |
| | | return PUBLISH_STATUS_OPTIONS.find((x) => x.value === v)?.label || v || "—"; |
| | | const key = normalizeEnterpriseNewsStatus(v); |
| | | return PUBLISH_STATUS_OPTIONS.find((x) => x.value === key)?.label || v || "—"; |
| | | } |
| | | |
| | | export function publishStatusTag(v) { |
| | | return PUBLISH_STATUS_OPTIONS.find((x) => x.value === v)?.tag || "info"; |
| | | const key = normalizeEnterpriseNewsStatus(v); |
| | | return PUBLISH_STATUS_OPTIONS.find((x) => x.value === key)?.tag || "info"; |
| | | } |
| | | |
| | | export function layoutTemplateLabel(v) { |
| | |
| | | readScope: "all", |
| | | targetDeptIds: [], |
| | | targetUserIds: [], |
| | | publishStatus: "draft", |
| | | publishStatus: "DRAFT", |
| | | publisherName: "", |
| | | publishTime: "", |
| | | readRecords: [], |
| | |
| | | versions: [], |
| | | versionNo: 1, |
| | | requireReadConfirm: false, |
| | | templateId: null, |
| | | templateName: "", |
| | | }; |
| | | } |
| | | |
| | | function buildReadRecords(readUserIds = []) { |
| | | const set = new Set(readUserIds); |
| | | return MOCK_AUDIENCE.map((u) => ({ |
| | | userId: u.userId, |
| | | employeeNo: u.employeeNo, |
| | | name: u.name, |
| | | deptName: u.deptName, |
| | | readAt: set.has(u.userId) ? dayjs().subtract(2, "day").format("YYYY-MM-DD HH:mm:ss") : "", |
| | | lastRemindAt: "", |
| | | })); |
| | | } |
| | | |
| | | function createVersionSnapshot(row, changeNote = "发布") { |
| | | return { |
| | | versionNo: row.versionNo || 1, |
| | | title: row.title, |
| | | summary: row.summary, |
| | | contentHtml: row.contentHtml, |
| | | newsType: row.newsType, |
| | | publishTime: row.publishTime || dayjs().format("YYYY-MM-DD HH:mm:ss"), |
| | | archivedAt: dayjs().format("YYYY-MM-DD HH:mm:ss"), |
| | | changeNote, |
| | | publisherName: row.publisherName || "系统", |
| | | }; |
| | | } |
| | | |
| | | export function createInitialMockNews() { |
| | | const policyContent = |
| | | "<p><strong>2026 年考勤管理制度(试行)</strong></p><p>一、上班时间 9:00,弹性打卡窗口 8:30–9:30。</p><p>二、请假须提前在 OA 提交审批。</p><p>三、本制度自 2026-06-01 起执行。</p>"; |
| | | const cultureContent = |
| | | "<p>2026 企业年会圆满落幕!感谢每一位同事的参与,以下为精彩瞬间图集。</p>"; |
| | | const strategyContent = |
| | | "<p><strong>2026 下半年战略方向(内部)</strong></p><p>聚焦核心产品线升级与海外市场拓展,具体指标见附件。</p>"; |
| | | |
| | | const policyRow = { |
| | | id: "news_1", |
| | | newsNo: "EN202605150001", |
| | | title: "关于发布新考勤制度的通知", |
| | | summary: "请全体员工认真阅读并确认知悉,自 2026-06-01 起执行。", |
| | | newsType: "policy", |
| | | layoutTemplate: "policy", |
| | | contentHtml: policyContent, |
| | | coverImage: "", |
| | | mediaList: [], |
| | | attachmentList: [{ name: "考勤制度2026.pdf", url: "/mock/attendance-policy.pdf" }], |
| | | editorRole: "hr", |
| | | reviewerRole: "admin", |
| | | readScope: "all", |
| | | targetDeptIds: [], |
| | | targetUserIds: [], |
| | | publishStatus: "published", |
| | | publisherName: "人力资源部", |
| | | publishTime: "2026-05-15 10:00:00", |
| | | readRecords: buildReadRecords(["u6", "u7", "u8"]), |
| | | remindLogs: [], |
| | | likes: [], |
| | | comments: [], |
| | | versions: [ |
| | | { |
| | | versionNo: 1, |
| | | title: "关于发布新考勤制度的通知(征求意见稿)", |
| | | summary: "征求意见稿", |
| | | contentHtml: "<p>征求意见稿:上班时间 9:00……</p>", |
| | | newsType: "policy", |
| | | publishTime: "2026-05-10 09:00:00", |
| | | archivedAt: "2026-05-15 09:55:00", |
| | | changeNote: "定稿发布", |
| | | publisherName: "人力资源部", |
| | | }, |
| | | ], |
| | | versionNo: 2, |
| | | requireReadConfirm: true, |
| | | createTime: "2026-05-10 09:00:00", |
| | | updateTime: "2026-05-15 10:00:00", |
| | | }; |
| | | |
| | | const cultureRow = { |
| | | id: "news_2", |
| | | newsNo: "EN202605200002", |
| | | title: "2026 企业年会精彩瞬间", |
| | | summary: "年会图集上线,欢迎点赞留言,共建企业文化。", |
| | | newsType: "culture", |
| | | layoutTemplate: "gallery", |
| | | contentHtml: cultureContent, |
| | | coverImage: "/mock/annual-cover.jpg", |
| | | mediaList: [ |
| | | { type: "image", name: "开场.jpg", url: "/mock/annual-1.jpg" }, |
| | | { type: "image", name: "颁奖.jpg", url: "/mock/annual-2.jpg" }, |
| | | { type: "video", name: "年会花絮.mp4", url: "/mock/annual.mp4" }, |
| | | ], |
| | | attachmentList: [], |
| | | editorRole: "dept_manager", |
| | | reviewerRole: "admin", |
| | | readScope: "all", |
| | | targetDeptIds: [], |
| | | targetUserIds: [], |
| | | publishStatus: "published", |
| | | publisherName: "行政部", |
| | | publishTime: "2026-05-20 14:30:00", |
| | | readRecords: buildReadRecords(["u1", "u2", "u3", "u4", "u5", "u6", "u7"]), |
| | | remindLogs: [], |
| | | likes: [ |
| | | { userId: "u1", name: "张三", time: "2026-05-20 15:01:00" }, |
| | | { userId: "u2", name: "李四", time: "2026-05-20 15:05:00" }, |
| | | { userId: "u4", name: "赵六", time: "2026-05-20 16:20:00" }, |
| | | ], |
| | | comments: [ |
| | | { id: "c1", userId: "u1", name: "张三", content: "节目太精彩了!", time: "2026-05-20 15:10:00" }, |
| | | { id: "c2", userId: "u3", name: "王五", content: "期待明年再聚!", time: "2026-05-20 17:00:00" }, |
| | | ], |
| | | versions: [], |
| | | versionNo: 1, |
| | | requireReadConfirm: false, |
| | | createTime: "2026-05-20 14:00:00", |
| | | updateTime: "2026-05-20 14:30:00", |
| | | }; |
| | | |
| | | const strategyRow = { |
| | | id: "news_3", |
| | | newsNo: "EN202605220003", |
| | | title: "2026 下半年战略规划要点", |
| | | summary: "仅限管理层阅读,请勿对外传播。", |
| | | newsType: "announcement", |
| | | layoutTemplate: "briefing", |
| | | contentHtml: strategyContent, |
| | | coverImage: "", |
| | | mediaList: [], |
| | | attachmentList: [{ name: "战略指标.pdf", url: "/mock/strategy.pdf" }], |
| | | editorRole: "admin", |
| | | reviewerRole: "admin", |
| | | readScope: "management", |
| | | targetDeptIds: [], |
| | | targetUserIds: [], |
| | | publishStatus: "published", |
| | | publisherName: "总经办", |
| | | publishTime: "2026-05-22 09:00:00", |
| | | readRecords: buildReadRecords(["u6", "u7"]), |
| | | remindLogs: [], |
| | | likes: [], |
| | | comments: [], |
| | | versions: [], |
| | | versionNo: 1, |
| | | requireReadConfirm: false, |
| | | createTime: "2026-05-22 08:30:00", |
| | | updateTime: "2026-05-22 09:00:00", |
| | | }; |
| | | |
| | | const industryDraft = { |
| | | id: "news_4", |
| | | newsNo: "EN202605250004", |
| | | title: "制造业数字化转型趋势简报", |
| | | summary: "行业动态草稿,待管理员审核后发布。", |
| | | newsType: "industry", |
| | | layoutTemplate: "standard", |
| | | contentHtml: "<p>本期简报梳理工业互联网与 AI 质检应用案例……</p>", |
| | | coverImage: "", |
| | | mediaList: [], |
| | | attachmentList: [], |
| | | editorRole: "editor", |
| | | reviewerRole: "admin", |
| | | readScope: "all", |
| | | targetDeptIds: [], |
| | | targetUserIds: [], |
| | | publishStatus: "pending_review", |
| | | publisherName: "市场部", |
| | | publishTime: "", |
| | | readRecords: [], |
| | | remindLogs: [], |
| | | likes: [], |
| | | comments: [], |
| | | versions: [], |
| | | versionNo: 1, |
| | | requireReadConfirm: false, |
| | | createTime: "2026-05-25 11:00:00", |
| | | updateTime: "2026-05-25 11:00:00", |
| | | }; |
| | | |
| | | return [policyRow, cultureRow, strategyRow, industryDraft]; |
| | | } |
| | | |
| | | export function loadStoredNews() { |
| | | try { |
| | | const raw = localStorage.getItem(STORAGE_KEY); |
| | | if (!raw) return null; |
| | | const data = JSON.parse(raw); |
| | | return Array.isArray(data) ? data : null; |
| | | } catch { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | export function saveStoredNews(rows) { |
| | | try { |
| | | localStorage.setItem(STORAGE_KEY, JSON.stringify(rows)); |
| | | } catch { |
| | | /* ignore */ |
| | | } |
| | | } |
| | | |
| | | /** 按阅读范围解析目标受众 */ |
| | |
| | | if (!audience.length) return 0; |
| | | const readCount = (row.readRecords || []).filter((r) => r.readAt).length; |
| | | return Math.round((readCount / audience.length) * 100); |
| | | } |
| | | |
| | | export function nextNewsNo() { |
| | | return `EN${dayjs().format("YYYYMMDD")}${String(Math.floor(Math.random() * 9000) + 1000)}`; |
| | | } |
| | | |
| | | export function pushVersionBeforeUpdate(row, changeNote) { |
| | | const versions = row.versions || []; |
| | | versions.unshift(createVersionSnapshot(row, changeNote)); |
| | | row.versions = versions; |
| | | row.versionNo = (row.versionNo || 1) + 1; |
| | | } |
| | | |
| | | export function validateNewsForm(form) { |