6923 lines
278 KiB
JavaScript
6923 lines
278 KiB
JavaScript
(async function () {
|
||
const INITIAL_HASH = location.hash;
|
||
const VERSION = new URLSearchParams(location.search).get("v") || "";
|
||
const ROUTE_HASH = "#/dashboard";
|
||
const STYLE_HREFS = [
|
||
"/static/css/chunk-libs.3dfb7769.css",
|
||
"/static/css/app.89efc722.css"
|
||
];
|
||
|
||
let routeMap = {};
|
||
const HCPOS_DASHBOARD_PRESETS = {
|
||
financeMicrobrain: {
|
||
eyebrow: "业财税银",
|
||
headline: "财务看板",
|
||
summary: "聚合收入、成本、利润与收缴排行,替代原始零值数据页。",
|
||
accent: "#2d76ff",
|
||
accentSoft: "rgba(45, 118, 255, 0.16)",
|
||
metrics: [
|
||
{ label: "本月总收费", value: "¥2,186,000", delta: "较上月 +7.6%" },
|
||
{ label: "累计成本", value: "¥1,428,000", delta: "成本率 65.3%" },
|
||
{ label: "月度利润", value: "¥758,000", delta: "利润率 34.7%" },
|
||
{ label: "风险预警", value: "4", delta: "高风险 1 项" }
|
||
],
|
||
trendLabels: ["1月", "2月", "3月", "4月", "5月", "6月"],
|
||
trendSeries: [
|
||
{ label: "收费额", color: "#2d76ff", values: [142, 156, 168, 182, 194, 218] },
|
||
{ label: "利润额", color: "#2fc1a8", values: [42, 48, 55, 61, 68, 76] }
|
||
],
|
||
segments: [
|
||
{ label: "物业费", value: "46%", tone: "#2d76ff" },
|
||
{ label: "停车费", value: "24%", tone: "#20c997" },
|
||
{ label: "增值服务", value: "18%", tone: "#f59e0b" },
|
||
{ label: "其他收入", value: "12%", tone: "#a78bfa" }
|
||
],
|
||
rankingTitle: "收缴红榜",
|
||
rankingRows: [
|
||
["循环花园一期 1 栋", "¥96,800", "98%"],
|
||
["博万物 A 座", "¥82,400", "95%"],
|
||
["美好花园 3 栋", "¥76,200", "93%"],
|
||
["连城花园 B 区", "¥68,900", "91%"]
|
||
],
|
||
alertsTitle: "财务提示",
|
||
alerts: [
|
||
{ title: "循环花园一期", detail: "本月收费额继续上升,建议同步预算口径。", level: "正常" },
|
||
{ title: "博万物", detail: "存在 2 笔大额费用待核销,需本周复核。", level: "关注" },
|
||
{ title: "连城花园", detail: "收缴完成率低于目标 4.1%,建议补做催收。", level: "预警" }
|
||
]
|
||
},
|
||
cleanMicrobrain: {
|
||
eyebrow: "清洁运营",
|
||
headline: "清洁看板",
|
||
summary: "展示清洁履约、问题点位、物资消耗和班组效率,替代空态页。",
|
||
accent: "#06b6d4",
|
||
accentSoft: "rgba(6, 182, 212, 0.16)",
|
||
metrics: [
|
||
{ label: "清洁任务数", value: "1,328", delta: "完成率 95.1%" },
|
||
{ label: "问题点位", value: "16", delta: "待闭环 4 个" },
|
||
{ label: "物资消耗", value: "¥38,600", delta: "较上月 -3.8%" },
|
||
{ label: "班组评分", value: "4.76", delta: "住户评价稳定" }
|
||
],
|
||
trendLabels: ["1周", "2周", "3周", "4周", "5周", "6周"],
|
||
trendSeries: [
|
||
{ label: "完成率", color: "#06b6d4", values: [84, 86, 89, 91, 93, 95] },
|
||
{ label: "问题闭环率", color: "#2d76ff", values: [68, 71, 76, 81, 84, 88] }
|
||
],
|
||
segments: [
|
||
{ label: "楼道保洁", value: "38%", tone: "#06b6d4" },
|
||
{ label: "园区保洁", value: "27%", tone: "#2d76ff" },
|
||
{ label: "垃圾分类", value: "19%", tone: "#20c997" },
|
||
{ label: "专项清洁", value: "16%", tone: "#f59e0b" }
|
||
],
|
||
rankingTitle: "班组履约排行",
|
||
rankingRows: [
|
||
["循环花园一期 A 组", "4.92", "98%"],
|
||
["博万物 2 组", "4.81", "96%"],
|
||
["美好花园 1 组", "4.74", "94%"],
|
||
["连城花园机动组", "4.68", "92%"]
|
||
],
|
||
alertsTitle: "清洁提示",
|
||
alerts: [
|
||
{ title: "地下车库 B 区", detail: "夜间巡检发现积尘偏高,建议安排专项清洁。", level: "关注" },
|
||
{ title: "循环花园一期", detail: "垃圾分类房周边清洁已连续 7 天达标。", level: "正常" },
|
||
{ title: "博万物", detail: "保洁耗材库存偏低,建议本周补货。", level: "预警" }
|
||
]
|
||
},
|
||
elevatorDimension: {
|
||
eyebrow: "特种设备",
|
||
headline: "电梯看板",
|
||
summary: "汇总电梯运行、维保、困人事件和年检进度,替代空态页。",
|
||
accent: "#7c3aed",
|
||
accentSoft: "rgba(124, 58, 237, 0.16)",
|
||
metrics: [
|
||
{ label: "电梯总数", value: "286", delta: "在线率 98.2%" },
|
||
{ label: "本月维保", value: "82", delta: "完成率 94.6%" },
|
||
{ label: "困人事件", value: "3", delta: "同比下降 2 起" },
|
||
{ label: "年检临期", value: "11", delta: "30 天内到期" }
|
||
],
|
||
trendLabels: ["1周", "2周", "3周", "4周", "5周", "6周"],
|
||
trendSeries: [
|
||
{ label: "运行稳定率", color: "#7c3aed", values: [91, 92, 93, 94, 96, 97] },
|
||
{ label: "维保完成率", color: "#2fc1a8", values: [76, 80, 84, 87, 90, 95] }
|
||
],
|
||
segments: [
|
||
{ label: "住宅梯", value: "54%", tone: "#7c3aed" },
|
||
{ label: "商用梯", value: "21%", tone: "#2d76ff" },
|
||
{ label: "货梯", value: "15%", tone: "#20c997" },
|
||
{ label: "扶梯", value: "10%", tone: "#f59e0b" }
|
||
],
|
||
rankingTitle: "维保单位排行",
|
||
rankingRows: [
|
||
["万嘉电梯维保", "97 分", "98%"],
|
||
["城安机电服务", "93 分", "95%"],
|
||
["迅达联保", "91 分", "93%"],
|
||
["华升特种设备", "88 分", "90%"]
|
||
],
|
||
alertsTitle: "电梯预警",
|
||
alerts: [
|
||
{ title: "循环花园一期 3 栋 2 单元", detail: "年检到期剩余 12 天,请安排检验。", level: "预警" },
|
||
{ title: "博万物 2 号货梯", detail: "运行平稳,近 30 天无故障。", level: "正常" },
|
||
{ title: "美好花园 5 号梯", detail: "本周困人演练需补录签到。", level: "关注" }
|
||
]
|
||
},
|
||
equipmentPortrait: {
|
||
eyebrow: "设备资产",
|
||
headline: "设备看板",
|
||
summary: "展示设备台账、维保、告警与能耗概览,替代空态页。",
|
||
accent: "#00a870",
|
||
accentSoft: "rgba(0, 168, 112, 0.16)",
|
||
metrics: [
|
||
{ label: "设备总台账", value: "1,186", delta: "在线率 97.3%" },
|
||
{ label: "本月维保完成", value: "214", delta: "完成率 92.1%" },
|
||
{ label: "待处理预警", value: "18", delta: "一级预警 3 条" },
|
||
{ label: "本月能耗", value: "18.6万kWh", delta: "较上月 -4.8%" }
|
||
],
|
||
trendLabels: ["1周", "2周", "3周", "4周", "5周", "6周"],
|
||
trendSeries: [
|
||
{ label: "工单关闭量", color: "#00a870", values: [26, 32, 29, 36, 39, 42] },
|
||
{ label: "新增预警", color: "#ff8a00", values: [14, 12, 11, 10, 8, 7] }
|
||
],
|
||
segments: [
|
||
{ label: "电梯", value: "34%", tone: "#00a870" },
|
||
{ label: "消防", value: "28%", tone: "#2d76ff" },
|
||
{ label: "供配电", value: "22%", tone: "#ff8a00" },
|
||
{ label: "给排水", value: "16%", tone: "#a78bfa" }
|
||
],
|
||
rankingTitle: "维保完成排行",
|
||
rankingRows: [
|
||
["循环花园一期", "48 项", "100%"],
|
||
["博万物", "41 项", "97%"],
|
||
["连城花园", "33 项", "94%"],
|
||
["美好花园", "28 项", "91%"]
|
||
],
|
||
alertsTitle: "设备预警",
|
||
alerts: [
|
||
{ title: "电梯系统", detail: "2 台电梯年检临期,需在 7 天内完成复检。", level: "预警" },
|
||
{ title: "消防泵房", detail: "巡检记录缺失 1 次,已通知项目负责人补录。", level: "关注" },
|
||
{ title: "供配电房", detail: "夜间负荷恢复正常,建议继续观察。", level: "正常" }
|
||
]
|
||
},
|
||
greenMicroBrain: {
|
||
eyebrow: "园林养护",
|
||
headline: "绿化看板",
|
||
summary: "展示绿化养护计划、植物健康度和病虫害预警,替代空态页。",
|
||
accent: "#22c55e",
|
||
accentSoft: "rgba(34, 197, 94, 0.16)",
|
||
metrics: [
|
||
{ label: "绿化养护点位", value: "418", delta: "覆盖 36 个片区" },
|
||
{ label: "本月养护完成", value: "362", delta: "完成率 91.6%" },
|
||
{ label: "病虫害预警", value: "8", delta: "需跟进 3 处" },
|
||
{ label: "成活率", value: "97.4%", delta: "春季补植良好" }
|
||
],
|
||
trendLabels: ["1周", "2周", "3周", "4周", "5周", "6周"],
|
||
trendSeries: [
|
||
{ label: "养护完成率", color: "#22c55e", values: [76, 81, 84, 87, 90, 92] },
|
||
{ label: "病害闭环率", color: "#f97316", values: [58, 63, 69, 74, 78, 83] }
|
||
],
|
||
segments: [
|
||
{ label: "乔木养护", value: "36%", tone: "#22c55e" },
|
||
{ label: "灌木修剪", value: "24%", tone: "#2d76ff" },
|
||
{ label: "草坪维护", value: "22%", tone: "#f59e0b" },
|
||
{ label: "节日花卉", value: "18%", tone: "#a78bfa" }
|
||
],
|
||
rankingTitle: "养护片区排行",
|
||
rankingRows: [
|
||
["中央花园片区", "97 分", "96%"],
|
||
["东门景观带", "94 分", "93%"],
|
||
["儿童活动区", "91 分", "90%"],
|
||
["外围绿篱区", "88 分", "87%"]
|
||
],
|
||
alertsTitle: "绿化提示",
|
||
alerts: [
|
||
{ title: "中央花园", detail: "病虫害处理完成,建议三天后复查。", level: "正常" },
|
||
{ title: "东门景观带", detail: "月季补植计划推迟,需协调供应商。", level: "关注" },
|
||
{ title: "南侧草坪区", detail: "连续高温导致草坪发黄,建议启动灌溉预案。", level: "预警" }
|
||
]
|
||
},
|
||
parkingMicroBrain: {
|
||
eyebrow: "车场经营",
|
||
headline: "车场看板",
|
||
summary: "展示车位利用、出入流量、收费表现与异常车牌,替代空态页。",
|
||
accent: "#7c4dff",
|
||
accentSoft: "rgba(124, 77, 255, 0.16)",
|
||
metrics: [
|
||
{ label: "总车位数", value: "2,146", delta: "固定车位 1,382" },
|
||
{ label: "当前占用率", value: "81.4%", delta: "较昨日 +2.6%" },
|
||
{ label: "本月停车收入", value: "¥486,300", delta: "临停车收入占比 38%" },
|
||
{ label: "异常进出记录", value: "12", delta: "黑名单 2 辆" }
|
||
],
|
||
trendLabels: ["08时", "10时", "12时", "14时", "16时", "18时"],
|
||
trendSeries: [
|
||
{ label: "进场车辆", color: "#7c4dff", values: [42, 58, 63, 71, 76, 69] },
|
||
{ label: "离场车辆", color: "#2fc1a8", values: [35, 46, 58, 64, 70, 66] }
|
||
],
|
||
segments: [
|
||
{ label: "固定月租", value: "62%", tone: "#7c4dff" },
|
||
{ label: "临停收费", value: "24%", tone: "#2d76ff" },
|
||
{ label: "新能源充电", value: "9%", tone: "#20c997" },
|
||
{ label: "其他", value: "5%", tone: "#ffb74d" }
|
||
],
|
||
rankingTitle: "项目车场收益排行",
|
||
rankingRows: [
|
||
["循环花园一期", "¥96,800", "88.4%"],
|
||
["博万物", "¥74,200", "82.7%"],
|
||
["美好花园", "¥68,500", "79.3%"],
|
||
["连城花园", "¥61,900", "75.1%"]
|
||
],
|
||
alertsTitle: "车场异常",
|
||
alerts: [
|
||
{ title: "粤B9X2F8", detail: "同一车辆 24 小时内重复进出 5 次,建议复核。", level: "关注" },
|
||
{ title: "循环花园一期", detail: "北门道闸离线 18 分钟,已恢复在线。", level: "预警" },
|
||
{ title: "博万物", detail: "新能源桩周转率较高,可考虑扩容。", level: "正常" }
|
||
]
|
||
},
|
||
securityBrain: {
|
||
eyebrow: "安防运营",
|
||
headline: "安防看板",
|
||
summary: "汇总巡更、监控、事件闭环和应急演练情况,替代空态页。",
|
||
accent: "#ef4444",
|
||
accentSoft: "rgba(239, 68, 68, 0.16)",
|
||
metrics: [
|
||
{ label: "巡更任务", value: "682", delta: "完成率 96.4%" },
|
||
{ label: "异常事件", value: "14", delta: "重大事件 0 起" },
|
||
{ label: "监控在线率", value: "98.9%", delta: "离线摄像头 3 台" },
|
||
{ label: "应急演练", value: "12", delta: "季度计划完成" }
|
||
],
|
||
trendLabels: ["1周", "2周", "3周", "4周", "5周", "6周"],
|
||
trendSeries: [
|
||
{ label: "巡更闭环率", color: "#ef4444", values: [88, 90, 92, 93, 95, 96] },
|
||
{ label: "事件处置率", color: "#2d76ff", values: [72, 76, 81, 84, 88, 91] }
|
||
],
|
||
segments: [
|
||
{ label: "巡更任务", value: "41%", tone: "#ef4444" },
|
||
{ label: "门岗值守", value: "24%", tone: "#2d76ff" },
|
||
{ label: "监控巡检", value: "20%", tone: "#f59e0b" },
|
||
{ label: "应急演练", value: "15%", tone: "#20c997" }
|
||
],
|
||
rankingTitle: "项目安防排行",
|
||
rankingRows: [
|
||
["循环花园一期", "95 分", "97%"],
|
||
["博万物", "92 分", "93%"],
|
||
["美好花园", "90 分", "91%"],
|
||
["连城花园", "88 分", "89%"]
|
||
],
|
||
alertsTitle: "安防提示",
|
||
alerts: [
|
||
{ title: "北门岗亭", detail: "夜班巡更存在 1 次漏巡,需补录。", level: "关注" },
|
||
{ title: "监控中心", detail: "3 台摄像头离线超过 2 小时,建议安排检修。", level: "预警" },
|
||
{ title: "循环花园一期", detail: "消防演练完成率 100%,建议复盘分享。", level: "正常" }
|
||
]
|
||
},
|
||
customerPortrait: {
|
||
eyebrow: "客户运营",
|
||
headline: "客户画像",
|
||
summary: "整合住户结构、活跃度、工单偏好与缴费行为,替代空态页。",
|
||
accent: "#0ea5e9",
|
||
accentSoft: "rgba(14, 165, 233, 0.16)",
|
||
metrics: [
|
||
{ label: "活跃住户", value: "4,286", delta: "月活率 72.4%" },
|
||
{ label: "重点客户", value: "126", delta: "需专人跟进" },
|
||
{ label: "回访完成", value: "318", delta: "本月回访任务" },
|
||
{ label: "满意度均分", value: "4.68", delta: "较上月 +0.05" }
|
||
],
|
||
trendLabels: ["1周", "2周", "3周", "4周", "5周", "6周"],
|
||
trendSeries: [
|
||
{ label: "活跃住户数", color: "#0ea5e9", values: [3200, 3380, 3520, 3680, 3890, 4286] },
|
||
{ label: "回访完成数", color: "#2fc1a8", values: [102, 136, 181, 224, 276, 318] }
|
||
],
|
||
segments: [
|
||
{ label: "高频互动", value: "34%", tone: "#0ea5e9" },
|
||
{ label: "稳定缴费", value: "29%", tone: "#2d76ff" },
|
||
{ label: "投诉敏感", value: "18%", tone: "#f59e0b" },
|
||
{ label: "沉默住户", value: "19%", tone: "#a78bfa" }
|
||
],
|
||
rankingTitle: "项目客户运营排行",
|
||
rankingRows: [
|
||
["循环花园一期", "4.82", "96%"],
|
||
["博万物", "4.76", "93%"],
|
||
["美好花园", "4.69", "91%"],
|
||
["连城花园", "4.58", "88%"]
|
||
],
|
||
alertsTitle: "客户提示",
|
||
alerts: [
|
||
{ title: "循环花园一期", detail: "高频投诉用户 3 位,建议安排专属回访。", level: "关注" },
|
||
{ title: "博万物", detail: "本月客户活跃度显著提升,可继续推动社群运营。", level: "正常" },
|
||
{ title: "美好花园", detail: "沉默住户占比上升,建议补做满意度回访。", level: "预警" }
|
||
]
|
||
},
|
||
energyNotice: {
|
||
eyebrow: "能源管理",
|
||
headline: "能源看板",
|
||
summary: "聚合公共用电、可回收用能和设备能耗等级,替代原始异常文本页。",
|
||
accent: "#14b8a6",
|
||
accentSoft: "rgba(20, 184, 166, 0.16)",
|
||
metrics: [
|
||
{ label: "本月用电量", value: "186.4万kWh", delta: "较上月 -4.3%" },
|
||
{ label: "减支金额", value: "¥62,878", delta: "节能效果稳定" },
|
||
{ label: "增收金额", value: "¥18,620", delta: "可回收能耗贡献" },
|
||
{ label: "异常设备", value: "5", delta: "需安排巡检" }
|
||
],
|
||
trendLabels: ["1周", "2周", "3周", "4周", "5周", "6周"],
|
||
trendSeries: [
|
||
{ label: "公共用电同比减支", color: "#14b8a6", values: [22, 28, 34, 41, 53, 63] },
|
||
{ label: "可回收用电增收", color: "#2d76ff", values: [8, 10, 11, 13, 15, 19] }
|
||
],
|
||
segments: [
|
||
{ label: "公共用电", value: "41%", tone: "#14b8a6" },
|
||
{ label: "可回收用电", value: "27%", tone: "#2d76ff" },
|
||
{ label: "商铺能耗", value: "19%", tone: "#f59e0b" },
|
||
{ label: "多经能耗", value: "13%", tone: "#a78bfa" }
|
||
],
|
||
rankingTitle: "重点能耗项目",
|
||
rankingRows: [
|
||
["循环花园一期", "6.45 kWh/㎡", "92%"],
|
||
["博万物", "6.58 kWh/㎡", "89%"],
|
||
["美好花园", "6.11 kWh/㎡", "91%"],
|
||
["连城花园", "6.37 kWh/㎡", "84%"]
|
||
],
|
||
alertsTitle: "能源提示",
|
||
alerts: [
|
||
{ title: "公共用电", detail: "连续两周维持减支,建议固化节能策略。", level: "正常" },
|
||
{ title: "设备能耗", detail: "5 台高能耗设备需补做专项巡检。", level: "关注" },
|
||
{ title: "商铺能耗", detail: "本月波动偏高,建议核查晚间峰值。", level: "预警" }
|
||
]
|
||
}
|
||
};
|
||
const HCPOS_STATIC_PAGE_PRESETS = {
|
||
childCare: {
|
||
eyebrow: "社区服务",
|
||
title: "社区幼托运营总览",
|
||
summary: "整合托育名额、预约转化、服务满意度与园区联动情况,替代内测提示页。",
|
||
accent: "#f97316",
|
||
accentSoft: "rgba(249, 115, 22, 0.14)",
|
||
cards: [
|
||
{ label: "托育名额", value: "128", detail: "本月已使用 94 个" },
|
||
{ label: "本月预约", value: "216", detail: "到访转化 43%" },
|
||
{ label: "活跃家庭", value: "86", detail: "重复预约率 61%" },
|
||
{ label: "满意度", value: "4.81", detail: "家长反馈稳定" }
|
||
],
|
||
headers: ["项目名称", "服务方案", "开放时段", "本月预约", "到访家庭", "转化率", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "幼托半日班", "09:00-18:00", "72", "31", "43.1%", "何琳"],
|
||
["博万物", "周末托管", "10:00-17:00", "46", "19", "41.3%", "李佑聪"],
|
||
["美好花园", "课后看护", "16:00-20:00", "38", "17", "44.7%", "郭晓"],
|
||
["连城花园", "假期托育", "09:00-17:30", "34", "13", "38.2%", "曾丽娜"],
|
||
["江南世家一期", "亲子陪护", "09:30-18:30", "26", "11", "42.3%", "陈谷先"]
|
||
]
|
||
},
|
||
express: {
|
||
eyebrow: "社区服务",
|
||
title: "快递收发运营总览",
|
||
summary: "聚合收发件量、代收时效与异常包裹,替代内测提示页。",
|
||
accent: "#06b6d4",
|
||
accentSoft: "rgba(6, 182, 212, 0.14)",
|
||
cards: [
|
||
{ label: "本日收件", value: "1,286", detail: "较昨日 +8.2%" },
|
||
{ label: "本日派件", value: "1,104", detail: "签收率 95.6%" },
|
||
{ label: "异常包裹", value: "9", detail: "需人工复核" },
|
||
{ label: "平均停留时长", value: "6.4h", detail: "库位周转正常" }
|
||
],
|
||
headers: ["项目名称", "收件量", "派件量", "异常包裹", "当日签收率", "平均停留时长", "值班人"],
|
||
rows: [
|
||
["循环花园一期", "342", "301", "2", "96.1%", "5.8h", "林婉"],
|
||
["博万物", "286", "244", "1", "95.4%", "6.2h", "何琳"],
|
||
["美好花园", "241", "219", "2", "94.8%", "6.9h", "郭晓"],
|
||
["连城花园", "218", "197", "2", "95.0%", "6.5h", "周宇"],
|
||
["江南世家一期", "199", "143", "2", "94.2%", "6.8h", "陈谷先"]
|
||
]
|
||
},
|
||
house: {
|
||
eyebrow: "社区服务",
|
||
title: "房屋经纪运营总览",
|
||
summary: "展示房源供给、带看转化与签约进展,替代内测提示页。",
|
||
accent: "#7c4dff",
|
||
accentSoft: "rgba(124, 77, 255, 0.14)",
|
||
cards: [
|
||
{ label: "在售房源", value: "86", detail: "新增 14 套" },
|
||
{ label: "本月带看", value: "214", detail: "意向客户 67 位" },
|
||
{ label: "签约套数", value: "12", detail: "转化率 17.9%" },
|
||
{ label: "成交额", value: "¥1,286万", detail: "含租售业务" }
|
||
],
|
||
headers: ["项目名称", "在售房源", "本月带看", "意向客户", "签约套数", "转化率", "经纪人"],
|
||
rows: [
|
||
["循环花园一期", "24", "61", "18", "4", "22.2%", "李佑聪"],
|
||
["博万物", "18", "46", "14", "3", "21.4%", "何琳"],
|
||
["美好花园", "15", "38", "12", "2", "16.7%", "郭晓"],
|
||
["连城花园", "16", "35", "13", "2", "15.4%", "周宇"],
|
||
["江南世家一期", "13", "34", "10", "1", "10.0%", "曾丽娜"]
|
||
]
|
||
},
|
||
housekeeping: {
|
||
eyebrow: "社区服务",
|
||
title: "社区家政运营总览",
|
||
summary: "汇总家政订单、服务评价与排班履约,替代内测提示页。",
|
||
accent: "#22c55e",
|
||
accentSoft: "rgba(34, 197, 94, 0.14)",
|
||
cards: [
|
||
{ label: "本月订单", value: "328", detail: "保洁 / 保姆 / 深度清洁" },
|
||
{ label: "已服务家庭", value: "246", detail: "复购率 48.8%" },
|
||
{ label: "履约率", value: "96.2%", detail: "投诉 3 单" },
|
||
{ label: "服务评分", value: "4.83", detail: "口碑稳定" }
|
||
],
|
||
headers: ["项目名称", "服务类型", "本月订单", "已服务家庭", "履约率", "投诉单", "组长"],
|
||
rows: [
|
||
["循环花园一期", "日常保洁", "92", "71", "97.8%", "1", "陈谷先"],
|
||
["博万物", "深度清洁", "68", "49", "95.1%", "1", "何琳"],
|
||
["美好花园", "收纳整理", "54", "41", "96.3%", "0", "郭晓"],
|
||
["连城花园", "保姆月嫂", "47", "36", "94.7%", "1", "曾丽娜"],
|
||
["江南世家一期", "空房开荒", "41", "29", "96.6%", "0", "周宇"]
|
||
]
|
||
},
|
||
retirement: {
|
||
eyebrow: "社区服务",
|
||
title: "社区养老运营总览",
|
||
summary: "汇总长者服务覆盖、上门关怀和医疗联动,替代内测提示页。",
|
||
accent: "#ef4444",
|
||
accentSoft: "rgba(239, 68, 68, 0.14)",
|
||
cards: [
|
||
{ label: "服务长者", value: "214", detail: "高龄长者 38 人" },
|
||
{ label: "本月上门关怀", value: "486", detail: "医养联动 62 次" },
|
||
{ label: "健康预警", value: "11", detail: "重点跟进对象" },
|
||
{ label: "满意度", value: "4.88", detail: "家属反馈良好" }
|
||
],
|
||
headers: ["项目名称", "服务长者", "本月关怀", "医养联动", "健康预警", "满意度", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "56", "128", "18", "3", "4.92", "郭晓"],
|
||
["博万物", "44", "103", "14", "2", "4.85", "李佑聪"],
|
||
["美好花园", "39", "92", "12", "2", "4.81", "何琳"],
|
||
["连城花园", "37", "86", "10", "2", "4.79", "曾丽娜"],
|
||
["江南世家一期", "38", "77", "8", "2", "4.84", "陈谷先"]
|
||
]
|
||
},
|
||
bankEnterprise: {
|
||
eyebrow: "业财税银",
|
||
title: "银企直联总览",
|
||
summary: "展示银行账户接入、支付通道状态与对账进度,替代空态页。",
|
||
accent: "#0ea5e9",
|
||
accentSoft: "rgba(14, 165, 233, 0.14)",
|
||
cards: [
|
||
{ label: "已接银行账户", value: "18", detail: "覆盖 9 个项目" },
|
||
{ label: "本月支付笔数", value: "1,286", detail: "对账完成 96.8%" },
|
||
{ label: "待处理回单", value: "12", detail: "需财务复核" },
|
||
{ label: "支付成功率", value: "99.2%", detail: "通道状态稳定" }
|
||
],
|
||
headers: ["项目名称", "开户行", "账户类型", "本月支付笔数", "支付金额", "对账完成率", "状态"],
|
||
rows: [
|
||
["循环花园一期", "中国银行", "基本户", "286", "¥684,200", "98.6%", "正常"],
|
||
["博万物", "建设银行", "一般户", "241", "¥512,800", "96.2%", "正常"],
|
||
["美好花园", "工商银行", "基本户", "214", "¥468,600", "95.8%", "正常"],
|
||
["连城花园", "农业银行", "一般户", "198", "¥392,400", "94.7%", "待复核"],
|
||
["江南世家一期", "招商银行", "基本户", "173", "¥318,900", "97.9%", "正常"]
|
||
]
|
||
},
|
||
financialAccount: {
|
||
eyebrow: "业财税银",
|
||
title: "财务核算总览",
|
||
summary: "展示核算主体、账套状态与月度结账进度,替代空态页。",
|
||
accent: "#8b5cf6",
|
||
accentSoft: "rgba(139, 92, 246, 0.14)",
|
||
cards: [
|
||
{ label: "核算主体", value: "11", detail: "账套均在线" },
|
||
{ label: "本月结账率", value: "81.8%", detail: "9/11 主体已完成" },
|
||
{ label: "待审核凭证", value: "42", detail: "需本周内清理" },
|
||
{ label: "异常科目", value: "6", detail: "需复核映射关系" }
|
||
],
|
||
headers: ["项目名称", "核算主体", "账套状态", "本月凭证数", "待审核凭证", "结账进度", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "循环花园物业服务主体", "正常", "128", "6", "100%", "陈谷先"],
|
||
["博万物", "博万物运营主体", "正常", "116", "8", "92%", "何琳"],
|
||
["美好花园", "美好花园服务主体", "正常", "104", "9", "88%", "郭晓"],
|
||
["连城花园", "连城花园管理主体", "关注", "97", "11", "74%", "曾丽娜"],
|
||
["江南世家一期", "江南世家服务主体", "正常", "82", "8", "79%", "周宇"]
|
||
]
|
||
},
|
||
financialVoucher: {
|
||
eyebrow: "业财税银",
|
||
title: "财务凭证总览",
|
||
summary: "汇总月度凭证流转、审核效率与异常凭证,替代空态页。",
|
||
accent: "#f97316",
|
||
accentSoft: "rgba(249, 115, 22, 0.14)",
|
||
cards: [
|
||
{ label: "本月凭证", value: "468", detail: "自动生成占比 62%" },
|
||
{ label: "已审核", value: "426", detail: "审核率 91.0%" },
|
||
{ label: "异常凭证", value: "14", detail: "需人工校正" },
|
||
{ label: "平均审核时长", value: "3.2h", detail: "较上月 -0.6h" }
|
||
],
|
||
headers: ["项目名称", "本月凭证", "自动生成", "已审核", "异常凭证", "审核率", "平均审核时长"],
|
||
rows: [
|
||
["循环花园一期", "112", "74", "106", "2", "94.6%", "2.6h"],
|
||
["博万物", "98", "63", "89", "4", "90.8%", "3.1h"],
|
||
["美好花园", "91", "58", "82", "3", "90.1%", "3.4h"],
|
||
["连城花园", "86", "52", "75", "3", "87.2%", "3.8h"],
|
||
["江南世家一期", "81", "43", "74", "2", "91.4%", "3.0h"]
|
||
]
|
||
},
|
||
taxCoordination: {
|
||
eyebrow: "业财税银",
|
||
title: "税务统筹总览",
|
||
summary: "展示申报进度、风险事项与税负变化,替代空态页。",
|
||
accent: "#ef4444",
|
||
accentSoft: "rgba(239, 68, 68, 0.14)",
|
||
cards: [
|
||
{ label: "本月申报主体", value: "11", detail: "已申报 9 个" },
|
||
{ label: "税负率", value: "5.86%", detail: "较上月 -0.22%" },
|
||
{ label: "风险事项", value: "4", detail: "逾期 0 项" },
|
||
{ label: "待补资料", value: "7", detail: "需市场侧配合" }
|
||
],
|
||
headers: ["项目名称", "申报状态", "本月税额", "税负率", "风险事项", "待补资料", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "已申报", "¥82,400", "5.42%", "0", "1", "陈谷先"],
|
||
["博万物", "已申报", "¥74,800", "5.91%", "1", "2", "何琳"],
|
||
["美好花园", "已申报", "¥68,200", "5.76%", "1", "1", "郭晓"],
|
||
["连城花园", "待复核", "¥63,900", "6.11%", "1", "2", "曾丽娜"],
|
||
["江南世家一期", "已申报", "¥58,700", "6.03%", "1", "1", "周宇"]
|
||
]
|
||
},
|
||
serviceProvider: {
|
||
eyebrow: "社区治理",
|
||
title: "服务商管理总览",
|
||
summary: "汇总服务商覆盖、合作状态与履约评分,替代空态页。",
|
||
accent: "#14b8a6",
|
||
accentSoft: "rgba(20, 184, 166, 0.14)",
|
||
cards: [
|
||
{ label: "合作服务商", value: "42", detail: "有效合作 36 家" },
|
||
{ label: "本月履约率", value: "92.6%", detail: "较上月 +1.7%" },
|
||
{ label: "待签约", value: "5", detail: "需合同跟进" },
|
||
{ label: "平均评分", value: "4.74", detail: "投诉 2 单" }
|
||
],
|
||
headers: ["项目名称", "服务商", "服务类型", "合作状态", "履约评分", "本月工单", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "星河保洁服务有限公司", "保洁服务", "合作中", "4.92", "86", "郭晓"],
|
||
["博万物", "万嘉设备维保有限公司", "设备维保", "合作中", "4.81", "72", "何琳"],
|
||
["美好花园", "城安安防科技有限公司", "安防服务", "合作中", "4.73", "64", "李佑聪"],
|
||
["连城花园", "绿景园林服务有限公司", "园林养护", "待续签", "4.66", "58", "曾丽娜"],
|
||
["江南世家一期", "社邻家政服务有限公司", "社区家政", "待签约", "4.58", "41", "陈谷先"]
|
||
]
|
||
},
|
||
cleanAssessmentTraining: {
|
||
eyebrow: "清洁管理",
|
||
title: "清洁考核培训总览",
|
||
summary: "汇总保洁班组培训计划、考核得分与复训状态,替代空态页。",
|
||
accent: "#06b6d4",
|
||
accentSoft: "rgba(6, 182, 212, 0.14)",
|
||
cards: [
|
||
{ label: "培训计划", value: "42", detail: "本月新增 6 场" },
|
||
{ label: "参训人数", value: "186", detail: "到课率 94.1%" },
|
||
{ label: "平均得分", value: "88.6", detail: "高于目标线" },
|
||
{ label: "待复训", value: "8", detail: "需下周安排" }
|
||
],
|
||
headers: ["项目名称", "培训主题", "参训人数", "考核得分", "复训状态", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "夜间保洁标准化", "36", "92.4", "已完成", "郭晓"],
|
||
["博万物", "地下车库清洁规范", "28", "88.1", "待复训", "何琳"],
|
||
["美好花园", "垃圾分类流程", "31", "87.6", "已完成", "曾丽娜"],
|
||
["连城花园", "专项清洁作业", "24", "84.9", "进行中", "周宇"],
|
||
["江南世家一期", "应急保洁演练", "19", "89.3", "已完成", "陈谷先"]
|
||
]
|
||
},
|
||
cleanSpareParts: {
|
||
eyebrow: "清洁管理",
|
||
title: "清洁备品备件总览",
|
||
summary: "展示清洁耗材库存、补货周期与异常库存,替代空态页。",
|
||
accent: "#22c55e",
|
||
accentSoft: "rgba(34, 197, 94, 0.14)",
|
||
cards: [
|
||
{ label: "SKU 数量", value: "86", detail: "低库存 7 项" },
|
||
{ label: "本月领用", value: "¥38,600", detail: "较上月 -3.8%" },
|
||
{ label: "待补货", value: "12", detail: "3 项紧急" },
|
||
{ label: "周转天数", value: "18.4", detail: "库存结构稳定" }
|
||
],
|
||
headers: ["项目名称", "物料名称", "库存数量", "月消耗", "安全库存", "状态", "库管员"],
|
||
rows: [
|
||
["循环花园一期", "清洁剂", "128", "36", "80", "正常", "郭晓"],
|
||
["博万物", "垃圾袋", "92", "48", "60", "关注", "何琳"],
|
||
["美好花园", "拖布头", "74", "22", "40", "正常", "曾丽娜"],
|
||
["连城花园", "消毒液", "38", "19", "30", "预警", "周宇"],
|
||
["江南世家一期", "尘推布", "56", "18", "32", "正常", "陈谷先"]
|
||
]
|
||
},
|
||
elevatorAssessmentTraining: {
|
||
eyebrow: "电梯管理",
|
||
title: "电梯考核培训总览",
|
||
summary: "汇总维保培训、困人演练与考核得分,替代空态页。",
|
||
accent: "#7c3aed",
|
||
accentSoft: "rgba(124, 58, 237, 0.14)",
|
||
cards: [
|
||
{ label: "培训批次", value: "18", detail: "季度滚动计划" },
|
||
{ label: "参训人数", value: "74", detail: "到课率 96.2%" },
|
||
{ label: "演练通过率", value: "93.4%", detail: "困人演练良好" },
|
||
{ label: "待复训", value: "4", detail: "新员工为主" }
|
||
],
|
||
headers: ["项目名称", "培训主题", "参训人数", "得分", "演练结果", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "困人救援演练", "18", "94.8", "通过", "何琳"],
|
||
["博万物", "年检流程培训", "12", "91.2", "通过", "郭晓"],
|
||
["美好花园", "维保质量复训", "14", "89.6", "待复训", "曾丽娜"],
|
||
["连城花园", "设备安全管理", "11", "87.3", "通过", "周宇"],
|
||
["江南世家一期", "电梯巡检规范", "9", "92.4", "通过", "陈谷先"]
|
||
]
|
||
},
|
||
elevatorSpareParts: {
|
||
eyebrow: "电梯管理",
|
||
title: "电梯备品备件总览",
|
||
summary: "展示电梯备件库存、使用频率与关键缺件,替代空态页。",
|
||
accent: "#f59e0b",
|
||
accentSoft: "rgba(245, 158, 11, 0.14)",
|
||
cards: [
|
||
{ label: "备件种类", value: "54", detail: "关键件 12 类" },
|
||
{ label: "本月领用", value: "32", detail: "较上月 +4" },
|
||
{ label: "低库存", value: "6", detail: "需本周补货" },
|
||
{ label: "完好率", value: "97.1%", detail: "仓储正常" }
|
||
],
|
||
headers: ["项目名称", "备件名称", "库存数量", "月领用", "安全库存", "状态", "库管员"],
|
||
rows: [
|
||
["循环花园一期", "门机皮带", "24", "6", "12", "正常", "何琳"],
|
||
["博万物", "层门锁触点", "18", "4", "10", "正常", "郭晓"],
|
||
["美好花园", "按钮面板", "9", "3", "8", "关注", "曾丽娜"],
|
||
["连城花园", "轿厢风扇", "6", "2", "6", "预警", "周宇"],
|
||
["江南世家一期", "编码器", "11", "1", "5", "正常", "陈谷先"]
|
||
]
|
||
},
|
||
equipmentAssessmentTraining: {
|
||
eyebrow: "设备管理",
|
||
title: "设备考核培训总览",
|
||
summary: "汇总设备维保培训、巡检演练与得分,替代空态页。",
|
||
accent: "#00a870",
|
||
accentSoft: "rgba(0, 168, 112, 0.14)",
|
||
cards: [
|
||
{ label: "培训场次", value: "26", detail: "覆盖 8 类设备" },
|
||
{ label: "参训人数", value: "96", detail: "到课率 95.3%" },
|
||
{ label: "平均得分", value: "90.8", detail: "维保体系稳定" },
|
||
{ label: "待整改项", value: "7", detail: "需复盘" }
|
||
],
|
||
headers: ["项目名称", "培训主题", "参训人数", "得分", "整改项", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "消防系统培训", "22", "93.1", "1", "何琳"],
|
||
["博万物", "供配电巡检培训", "18", "90.4", "2", "郭晓"],
|
||
["美好花园", "泵房维护复训", "17", "89.6", "1", "曾丽娜"],
|
||
["连城花园", "弱电设备培训", "14", "88.8", "2", "周宇"],
|
||
["江南世家一期", "设施保养培训", "13", "92.0", "1", "陈谷先"]
|
||
]
|
||
},
|
||
equipmentSpareParts: {
|
||
eyebrow: "设备管理",
|
||
title: "设备备品备件总览",
|
||
summary: "展示设备备件库存、消耗与缺件预警,替代空态页。",
|
||
accent: "#2d76ff",
|
||
accentSoft: "rgba(45, 118, 255, 0.14)",
|
||
cards: [
|
||
{ label: "备件种类", value: "138", detail: "关键件 28 类" },
|
||
{ label: "本月消耗", value: "¥46,200", detail: "较上月 -2.7%" },
|
||
{ label: "低库存", value: "11", detail: "紧急 3 项" },
|
||
{ label: "完好率", value: "96.4%", detail: "库存状态可控" }
|
||
],
|
||
headers: ["项目名称", "备件名称", "库存数量", "月消耗", "安全库存", "状态", "库管员"],
|
||
rows: [
|
||
["循环花园一期", "消防探测器", "62", "18", "30", "正常", "何琳"],
|
||
["博万物", "压力开关", "41", "12", "20", "正常", "郭晓"],
|
||
["美好花园", "接触器", "24", "8", "12", "关注", "曾丽娜"],
|
||
["连城花园", "轴承组件", "17", "6", "10", "正常", "周宇"],
|
||
["江南世家一期", "信号模块", "9", "4", "8", "预警", "陈谷先"]
|
||
]
|
||
},
|
||
greenAssessmentTraining: {
|
||
eyebrow: "绿化管理",
|
||
title: "绿化考核培训总览",
|
||
summary: "汇总绿化养护培训、病虫害演练与考核得分,替代空态页。",
|
||
accent: "#22c55e",
|
||
accentSoft: "rgba(34, 197, 94, 0.14)",
|
||
cards: [
|
||
{ label: "培训场次", value: "21", detail: "覆盖 6 类养护主题" },
|
||
{ label: "参训人数", value: "84", detail: "到课率 93.8%" },
|
||
{ label: "平均得分", value: "89.2", detail: "高于月目标" },
|
||
{ label: "待复训", value: "5", detail: "多为新员工" }
|
||
],
|
||
headers: ["项目名称", "培训主题", "参训人数", "得分", "复训状态", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "乔木修剪规范", "18", "91.4", "已完成", "郭晓"],
|
||
["博万物", "病虫害识别", "16", "88.9", "待复训", "何琳"],
|
||
["美好花园", "草坪养护标准", "15", "90.2", "已完成", "曾丽娜"],
|
||
["连城花园", "季节性补植培训", "13", "87.3", "进行中", "周宇"],
|
||
["江南世家一期", "灌溉系统操作", "11", "88.1", "已完成", "陈谷先"]
|
||
]
|
||
},
|
||
greenSpareParts: {
|
||
eyebrow: "绿化管理",
|
||
title: "绿化备品备件总览",
|
||
summary: "展示绿化工具、药剂和补植物资库存,替代空态页。",
|
||
accent: "#84cc16",
|
||
accentSoft: "rgba(132, 204, 22, 0.14)",
|
||
cards: [
|
||
{ label: "物资种类", value: "72", detail: "关键项 14 类" },
|
||
{ label: "本月消耗", value: "¥24,800", detail: "较上月 +3.2%" },
|
||
{ label: "低库存", value: "9", detail: "需补货 2 项" },
|
||
{ label: "完好率", value: "95.2%", detail: "仓储正常" }
|
||
],
|
||
headers: ["项目名称", "物资名称", "库存数量", "月消耗", "安全库存", "状态", "库管员"],
|
||
rows: [
|
||
["循环花园一期", "复合肥", "48", "12", "20", "正常", "郭晓"],
|
||
["博万物", "修剪刀", "26", "6", "10", "正常", "何琳"],
|
||
["美好花园", "杀菌剂", "18", "5", "8", "关注", "曾丽娜"],
|
||
["连城花园", "草籽", "12", "4", "6", "预警", "周宇"],
|
||
["江南世家一期", "滴灌接头", "24", "3", "10", "正常", "陈谷先"]
|
||
]
|
||
},
|
||
securityAssessmentTraining: {
|
||
eyebrow: "安防管理",
|
||
title: "安防考核培训总览",
|
||
summary: "汇总巡更、门岗和值守演练培训,替代空态页。",
|
||
accent: "#ef4444",
|
||
accentSoft: "rgba(239, 68, 68, 0.14)",
|
||
cards: [
|
||
{ label: "培训批次", value: "24", detail: "本月滚动培训" },
|
||
{ label: "参训人数", value: "102", detail: "到课率 95.1%" },
|
||
{ label: "平均得分", value: "91.3", detail: "安防体系稳定" },
|
||
{ label: "待复训", value: "6", detail: "夜班岗为主" }
|
||
],
|
||
headers: ["项目名称", "培训主题", "参训人数", "得分", "复训状态", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "门岗值守规范", "22", "93.5", "已完成", "何琳"],
|
||
["博万物", "监控巡检流程", "19", "90.4", "进行中", "郭晓"],
|
||
["美好花园", "消防联动演练", "18", "92.1", "已完成", "曾丽娜"],
|
||
["连城花园", "夜间巡更培训", "16", "88.8", "待复训", "周宇"],
|
||
["江南世家一期", "突发事件上报", "14", "91.7", "已完成", "陈谷先"]
|
||
]
|
||
},
|
||
securitySpareParts: {
|
||
eyebrow: "安防管理",
|
||
title: "安防备品备件总览",
|
||
summary: "展示安防设备备件、耗材和缺件预警,替代空态页。",
|
||
accent: "#f43f5e",
|
||
accentSoft: "rgba(244, 63, 94, 0.14)",
|
||
cards: [
|
||
{ label: "备件种类", value: "66", detail: "摄像头 / 门禁 / 对讲" },
|
||
{ label: "本月消耗", value: "¥31,400", detail: "较上月 +1.6%" },
|
||
{ label: "低库存", value: "8", detail: "紧急 2 项" },
|
||
{ label: "完好率", value: "97.8%", detail: "库存状态可控" }
|
||
],
|
||
headers: ["项目名称", "备件名称", "库存数量", "月消耗", "安全库存", "状态", "库管员"],
|
||
rows: [
|
||
["循环花园一期", "摄像头模组", "34", "8", "12", "正常", "何琳"],
|
||
["博万物", "门禁卡", "186", "42", "80", "正常", "郭晓"],
|
||
["美好花园", "对讲电源", "16", "4", "8", "关注", "曾丽娜"],
|
||
["连城花园", "硬盘录像机盘位", "7", "2", "5", "预警", "周宇"],
|
||
["江南世家一期", "报警探测器", "22", "6", "10", "正常", "陈谷先"]
|
||
]
|
||
},
|
||
parkingAssessmentTraining: {
|
||
eyebrow: "车场管理",
|
||
title: "车场考核培训总览",
|
||
summary: "汇总车场收费、道闸处理和现场演练培训,替代空态页。",
|
||
accent: "#7c4dff",
|
||
accentSoft: "rgba(124, 77, 255, 0.14)",
|
||
cards: [
|
||
{ label: "培训批次", value: "16", detail: "收费岗与巡查岗" },
|
||
{ label: "参训人数", value: "58", detail: "到课率 96.7%" },
|
||
{ label: "平均得分", value: "90.1", detail: "车场运营稳定" },
|
||
{ label: "待复训", value: "3", detail: "新岗补训" }
|
||
],
|
||
headers: ["项目名称", "培训主题", "参训人数", "得分", "复训状态", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "道闸异常处理", "14", "92.6", "已完成", "何琳"],
|
||
["博万物", "收费岗交接培训", "12", "89.7", "已完成", "郭晓"],
|
||
["美好花园", "夜间来访登记", "11", "88.4", "待复训", "曾丽娜"],
|
||
["连城花园", "车牌识别演练", "10", "90.8", "已完成", "周宇"],
|
||
["江南世家一期", "临停收费规范", "11", "89.0", "进行中", "陈谷先"]
|
||
]
|
||
},
|
||
balanceSheet: {
|
||
eyebrow: "财务报表",
|
||
title: "资产负债表总览",
|
||
summary: "基于镜像静态样本重建资产、负债与净资产概览,替代空态页。",
|
||
accent: "#2d76ff",
|
||
accentSoft: "rgba(45, 118, 255, 0.14)",
|
||
cards: [
|
||
{ label: "资产总计", value: "¥8,426,000", detail: "较年初 +6.8%" },
|
||
{ label: "负债合计", value: "¥3,286,000", detail: "负债率 39.0%" },
|
||
{ label: "所有者权益", value: "¥5,140,000", detail: "权益结构稳定" },
|
||
{ label: "流动比率", value: "1.92", detail: "短期偿债正常" }
|
||
],
|
||
headers: ["项目名称", "流动资产", "非流动资产", "资产总计", "流动负债", "非流动负债", "负债合计", "所有者权益"],
|
||
rows: [
|
||
["循环花园一期", "¥1,286,000", "¥842,000", "¥2,128,000", "¥624,000", "¥182,000", "¥806,000", "¥1,322,000"],
|
||
["博万物", "¥1,164,000", "¥736,000", "¥1,900,000", "¥592,000", "¥168,000", "¥760,000", "¥1,140,000"],
|
||
["美好花园", "¥1,082,000", "¥704,000", "¥1,786,000", "¥548,000", "¥154,000", "¥702,000", "¥1,084,000"],
|
||
["连城花园", "¥968,000", "¥642,000", "¥1,610,000", "¥486,000", "¥136,000", "¥622,000", "¥988,000"],
|
||
["江南世家一期", "¥594,000", "¥408,000", "¥1,002,000", "¥284,000", "¥112,000", "¥396,000", "¥606,000"]
|
||
]
|
||
},
|
||
cashFlowStatement: {
|
||
eyebrow: "财务报表",
|
||
title: "现金流量表总览",
|
||
summary: "重建经营、投资、筹资现金流概览,替代空态页。",
|
||
accent: "#14b8a6",
|
||
accentSoft: "rgba(20, 184, 166, 0.14)",
|
||
cards: [
|
||
{ label: "经营净现金流", value: "¥1,286,000", detail: "核心业务稳定" },
|
||
{ label: "投资净现金流", value: "-¥326,000", detail: "设备投入增加" },
|
||
{ label: "筹资净现金流", value: "¥182,000", detail: "贷款偿付平稳" },
|
||
{ label: "现金净增加额", value: "¥1,142,000", detail: "资金安全" }
|
||
],
|
||
headers: ["项目名称", "经营现金流入", "经营现金流出", "经营净现金流", "投资净现金流", "筹资净现金流", "现金净增加额"],
|
||
rows: [
|
||
["循环花园一期", "¥2,486,000", "¥1,618,000", "¥868,000", "-¥126,000", "¥52,000", "¥794,000"],
|
||
["博万物", "¥2,084,000", "¥1,428,000", "¥656,000", "-¥94,000", "¥48,000", "¥610,000"],
|
||
["美好花园", "¥1,876,000", "¥1,294,000", "¥582,000", "-¥72,000", "¥36,000", "¥546,000"],
|
||
["连城花园", "¥1,624,000", "¥1,142,000", "¥482,000", "-¥21,000", "¥28,000", "¥489,000"],
|
||
["江南世家一期", "¥1,208,000", "¥962,000", "¥246,000", "-¥13,000", "¥18,000", "¥251,000"]
|
||
]
|
||
},
|
||
incomeStatement: {
|
||
eyebrow: "财务报表",
|
||
title: "全年收入报表总览",
|
||
summary: "按项目汇总收入、成本、毛利与净利,替代空态页。",
|
||
accent: "#22c55e",
|
||
accentSoft: "rgba(34, 197, 94, 0.14)",
|
||
cards: [
|
||
{ label: "全年收入", value: "¥12,680,000", detail: "较上年 +9.4%" },
|
||
{ label: "营业成本", value: "¥8,940,000", detail: "成本率 70.5%" },
|
||
{ label: "毛利润", value: "¥3,740,000", detail: "毛利率 29.5%" },
|
||
{ label: "净利润", value: "¥2,286,000", detail: "净利率 18.0%" }
|
||
],
|
||
headers: ["项目名称", "全年收入", "营业成本", "毛利润", "管理费用", "净利润", "净利率"],
|
||
rows: [
|
||
["循环花园一期", "¥3,186,000", "¥2,186,000", "¥1,000,000", "¥318,000", "¥642,000", "20.2%"],
|
||
["博万物", "¥2,648,000", "¥1,862,000", "¥786,000", "¥264,000", "¥486,000", "18.4%"],
|
||
["美好花园", "¥2,214,000", "¥1,614,000", "¥600,000", "¥208,000", "¥356,000", "16.1%"],
|
||
["连城花园", "¥1,986,000", "¥1,434,000", "¥552,000", "¥186,000", "¥318,000", "16.0%"],
|
||
["江南世家一期", "¥1,646,000", "¥1,132,000", "¥514,000", "¥148,000", "¥284,000", "17.3%"]
|
||
]
|
||
},
|
||
profitSurface: {
|
||
eyebrow: "财务报表",
|
||
title: "利润表总览",
|
||
summary: "按项目展示利润结构、费用与利润率,替代空态页。",
|
||
accent: "#f97316",
|
||
accentSoft: "rgba(249, 115, 22, 0.14)",
|
||
cards: [
|
||
{ label: "营业利润", value: "¥2,948,000", detail: "较上月 +5.3%" },
|
||
{ label: "利润率", value: "23.2%", detail: "经营状态良好" },
|
||
{ label: "费用率", value: "9.6%", detail: "可继续优化" },
|
||
{ label: "亏损项目", value: "0", detail: "本月无亏损项目" }
|
||
],
|
||
headers: ["项目名称", "营业收入", "营业成本", "销售费用", "管理费用", "营业利润", "利润率"],
|
||
rows: [
|
||
["循环花园一期", "¥1,286,000", "¥824,000", "¥42,000", "¥86,000", "¥334,000", "26.0%"],
|
||
["博万物", "¥1,084,000", "¥712,000", "¥38,000", "¥74,000", "¥260,000", "24.0%"],
|
||
["美好花园", "¥986,000", "¥664,000", "¥32,000", "¥68,000", "¥222,000", "22.5%"],
|
||
["连城花园", "¥842,000", "¥598,000", "¥28,000", "¥61,000", "¥155,000", "18.4%"],
|
||
["江南世家一期", "¥714,000", "¥486,000", "¥21,000", "¥48,000", "¥159,000", "22.3%"]
|
||
]
|
||
},
|
||
projectIncome: {
|
||
eyebrow: "财务报表",
|
||
title: "项目收支表总览",
|
||
summary: "聚合项目收入、支出、预算执行与收支结余,替代空态页。",
|
||
accent: "#8b5cf6",
|
||
accentSoft: "rgba(139, 92, 246, 0.14)",
|
||
cards: [
|
||
{ label: "项目收入", value: "¥8,864,000", detail: "本月累计" },
|
||
{ label: "项目支出", value: "¥6,428,000", detail: "预算执行 91.2%" },
|
||
{ label: "收支结余", value: "¥2,436,000", detail: "结余率 27.5%" },
|
||
{ label: "超预算项目", value: "3", detail: "需专项复盘" }
|
||
],
|
||
headers: ["项目名称", "项目收入", "项目支出", "收支结余", "预算执行率", "本月回款", "超预算项"],
|
||
rows: [
|
||
["循环花园一期", "¥2,186,000", "¥1,548,000", "¥638,000", "92.4%", "¥246,000", "0"],
|
||
["博万物", "¥1,946,000", "¥1,382,000", "¥564,000", "89.8%", "¥214,000", "1"],
|
||
["美好花园", "¥1,782,000", "¥1,296,000", "¥486,000", "90.6%", "¥198,000", "1"],
|
||
["连城花园", "¥1,612,000", "¥1,168,000", "¥444,000", "91.1%", "¥176,000", "0"],
|
||
["江南世家一期", "¥1,338,000", "¥1,034,000", "¥304,000", "93.5%", "¥152,000", "1"]
|
||
]
|
||
},
|
||
laborQuota: {
|
||
eyebrow: "业财税银",
|
||
title: "劳动定额总览",
|
||
summary: "汇总定额岗位、人工配置与执行偏差,替代等待页。",
|
||
accent: "#2d76ff",
|
||
accentSoft: "rgba(45, 118, 255, 0.14)",
|
||
cards: [
|
||
{ label: "定额岗位", value: "28", detail: "覆盖保洁/安防/工程" },
|
||
{ label: "标准工时", value: "4,820h", detail: "月度核定口径" },
|
||
{ label: "执行偏差", value: "3.6%", detail: "较上月收敛" },
|
||
{ label: "超编项目", value: "2", detail: "需复核岗位配置" }
|
||
],
|
||
headers: ["项目名称", "岗位类型", "核定人数", "标准工时", "执行偏差", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "环境巡查", "12", "1,260h", "2.8%", "郭晓"],
|
||
["博万物", "设备运维", "9", "980h", "4.1%", "何琳"],
|
||
["江南世家一期", "安防值守", "8", "860h", "3.3%", "陈谷先"]
|
||
]
|
||
},
|
||
resourceManage: {
|
||
eyebrow: "业财税银",
|
||
title: "资源管理总览",
|
||
summary: "展示资源档案、利用率与收益分布,替代等待页。",
|
||
accent: "#14b8a6",
|
||
accentSoft: "rgba(20, 184, 166, 0.14)",
|
||
cards: [
|
||
{ label: "资源档案", value: "86", detail: "场地/广告位/配套资源" },
|
||
{ label: "利用率", value: "91.4%", detail: "本月稳定提升" },
|
||
{ label: "资源收益", value: "¥268,400", detail: "较上月 +5.2%" },
|
||
{ label: "待上架", value: "6", detail: "均在补资料" }
|
||
],
|
||
headers: ["项目名称", "资源类型", "档案数量", "利用率", "本月收益", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "广告位", "28", "94.2%", "¥82,600", "郭晓"],
|
||
["博万物", "共享场地", "17", "88.6%", "¥64,800", "何琳"],
|
||
["江南世家一期", "便民柜机", "9", "92.1%", "¥28,300", "陈谷先"]
|
||
]
|
||
}
|
||
};
|
||
const HCPOS_FINANCE_REPORT_PRESETS = {
|
||
balanceSheet: {
|
||
eyebrow: "财务报表",
|
||
title: "资产负债表总览",
|
||
summary: "基于镜像静态样本重建资产、负债与净资产概览,替代空态页。",
|
||
accent: "#2d76ff",
|
||
accentSoft: "rgba(45, 118, 255, 0.14)",
|
||
cards: [
|
||
{ label: "资产总计", value: "¥8,426,000", detail: "较年初 +6.8%" },
|
||
{ label: "负债合计", value: "¥3,286,000", detail: "负债率 39.0%" },
|
||
{ label: "所有者权益", value: "¥5,140,000", detail: "权益结构稳定" },
|
||
{ label: "流动比率", value: "1.92", detail: "短期偿债正常" }
|
||
],
|
||
headers: ["项目名称", "流动资产", "非流动资产", "资产总计", "流动负债", "非流动负债", "负债合计", "所有者权益"],
|
||
rows: [
|
||
["循环花园一期", "¥1,286,000", "¥842,000", "¥2,128,000", "¥624,000", "¥182,000", "¥806,000", "¥1,322,000"],
|
||
["博万物", "¥1,164,000", "¥736,000", "¥1,900,000", "¥592,000", "¥168,000", "¥760,000", "¥1,140,000"],
|
||
["美好花园", "¥1,082,000", "¥704,000", "¥1,786,000", "¥548,000", "¥154,000", "¥702,000", "¥1,084,000"],
|
||
["连城花园", "¥968,000", "¥642,000", "¥1,610,000", "¥486,000", "¥136,000", "¥622,000", "¥988,000"],
|
||
["江南世家一期", "¥594,000", "¥408,000", "¥1,002,000", "¥284,000", "¥112,000", "¥396,000", "¥606,000"]
|
||
]
|
||
},
|
||
cashFlowStatement: {
|
||
eyebrow: "财务报表",
|
||
title: "现金流量表总览",
|
||
summary: "重建经营、投资、筹资现金流概览,替代空态页。",
|
||
accent: "#14b8a6",
|
||
accentSoft: "rgba(20, 184, 166, 0.14)",
|
||
cards: [
|
||
{ label: "经营净现金流", value: "¥1,286,000", detail: "核心业务稳定" },
|
||
{ label: "投资净现金流", value: "-¥326,000", detail: "设备投入增加" },
|
||
{ label: "筹资净现金流", value: "¥182,000", detail: "贷款偿付平稳" },
|
||
{ label: "现金净增加额", value: "¥1,142,000", detail: "资金安全" }
|
||
],
|
||
headers: ["项目名称", "经营现金流入", "经营现金流出", "经营净现金流", "投资净现金流", "筹资净现金流", "现金净增加额"],
|
||
rows: [
|
||
["循环花园一期", "¥2,486,000", "¥1,618,000", "¥868,000", "-¥126,000", "¥52,000", "¥794,000"],
|
||
["博万物", "¥2,084,000", "¥1,428,000", "¥656,000", "-¥94,000", "¥48,000", "¥610,000"],
|
||
["美好花园", "¥1,876,000", "¥1,294,000", "¥582,000", "-¥72,000", "¥36,000", "¥546,000"],
|
||
["连城花园", "¥1,624,000", "¥1,142,000", "¥482,000", "-¥21,000", "¥28,000", "¥489,000"],
|
||
["江南世家一期", "¥1,208,000", "¥962,000", "¥246,000", "-¥13,000", "¥18,000", "¥251,000"]
|
||
]
|
||
},
|
||
incomeStatement: {
|
||
eyebrow: "财务报表",
|
||
title: "全年收入报表总览",
|
||
summary: "按项目汇总收入、成本、毛利与净利,替代空态页。",
|
||
accent: "#22c55e",
|
||
accentSoft: "rgba(34, 197, 94, 0.14)",
|
||
cards: [
|
||
{ label: "全年收入", value: "¥12,680,000", detail: "较上年 +9.4%" },
|
||
{ label: "营业成本", value: "¥8,940,000", detail: "成本率 70.5%" },
|
||
{ label: "毛利润", value: "¥3,740,000", detail: "毛利率 29.5%" },
|
||
{ label: "净利润", value: "¥2,286,000", detail: "净利率 18.0%" }
|
||
],
|
||
headers: ["项目名称", "全年收入", "营业成本", "毛利润", "管理费用", "净利润", "净利率"],
|
||
rows: [
|
||
["循环花园一期", "¥3,186,000", "¥2,186,000", "¥1,000,000", "¥318,000", "¥642,000", "20.2%"],
|
||
["博万物", "¥2,648,000", "¥1,862,000", "¥786,000", "¥264,000", "¥486,000", "18.4%"],
|
||
["美好花园", "¥2,214,000", "¥1,614,000", "¥600,000", "¥208,000", "¥356,000", "16.1%"],
|
||
["连城花园", "¥1,986,000", "¥1,434,000", "¥552,000", "¥186,000", "¥318,000", "16.0%"],
|
||
["江南世家一期", "¥1,646,000", "¥1,132,000", "¥514,000", "¥148,000", "¥284,000", "17.3%"]
|
||
]
|
||
},
|
||
profitSurface: {
|
||
eyebrow: "财务报表",
|
||
title: "利润表总览",
|
||
summary: "按项目展示利润结构、费用与利润率,替代空态页。",
|
||
accent: "#f97316",
|
||
accentSoft: "rgba(249, 115, 22, 0.14)",
|
||
cards: [
|
||
{ label: "营业利润", value: "¥2,948,000", detail: "较上月 +5.3%" },
|
||
{ label: "利润率", value: "23.2%", detail: "经营状态良好" },
|
||
{ label: "费用率", value: "9.6%", detail: "可继续优化" },
|
||
{ label: "亏损项目", value: "0", detail: "本月无亏损项目" }
|
||
],
|
||
headers: ["项目名称", "营业收入", "营业成本", "销售费用", "管理费用", "营业利润", "利润率"],
|
||
rows: [
|
||
["循环花园一期", "¥1,286,000", "¥824,000", "¥42,000", "¥86,000", "¥334,000", "26.0%"],
|
||
["博万物", "¥1,084,000", "¥712,000", "¥38,000", "¥74,000", "¥260,000", "24.0%"],
|
||
["美好花园", "¥986,000", "¥664,000", "¥32,000", "¥68,000", "¥222,000", "22.5%"],
|
||
["连城花园", "¥842,000", "¥598,000", "¥28,000", "¥61,000", "¥155,000", "18.4%"],
|
||
["江南世家一期", "¥714,000", "¥486,000", "¥21,000", "¥48,000", "¥159,000", "22.3%"]
|
||
]
|
||
},
|
||
projectIncome: {
|
||
eyebrow: "财务报表",
|
||
title: "项目收支表总览",
|
||
summary: "聚合项目收入、支出、预算执行与收支结余,替代空态页。",
|
||
accent: "#8b5cf6",
|
||
accentSoft: "rgba(139, 92, 246, 0.14)",
|
||
cards: [
|
||
{ label: "项目收入", value: "¥8,864,000", detail: "本月累计" },
|
||
{ label: "项目支出", value: "¥6,428,000", detail: "预算执行 91.2%" },
|
||
{ label: "收支结余", value: "¥2,436,000", detail: "结余率 27.5%" },
|
||
{ label: "超预算项目", value: "3", detail: "需专项复盘" }
|
||
],
|
||
headers: ["项目名称", "项目收入", "项目支出", "收支结余", "预算执行率", "本月回款", "超预算项"],
|
||
rows: [
|
||
["循环花园一期", "¥2,186,000", "¥1,548,000", "¥638,000", "92.4%", "¥246,000", "0"],
|
||
["博万物", "¥1,946,000", "¥1,382,000", "¥564,000", "89.8%", "¥214,000", "1"],
|
||
["美好花园", "¥1,782,000", "¥1,296,000", "¥486,000", "90.6%", "¥198,000", "1"],
|
||
["连城花园", "¥1,612,000", "¥1,168,000", "¥444,000", "91.1%", "¥176,000", "0"],
|
||
["江南世家一期", "¥1,338,000", "¥1,034,000", "¥304,000", "93.5%", "¥152,000", "1"]
|
||
]
|
||
}
|
||
};
|
||
function buildHcPosSoftAccent(hex, alpha) {
|
||
const normalized = (hex || "").replace("#", "");
|
||
const full = normalized.length === 3 ? normalized.split("").map((item) => `${item}${item}`).join("") : normalized;
|
||
const value = Number.parseInt(full || "2d76ff", 16);
|
||
const r = (value >> 16) & 255;
|
||
const g = (value >> 8) & 255;
|
||
const b = value & 255;
|
||
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
||
}
|
||
|
||
function createHcPosStaticPreset(preset) {
|
||
return {
|
||
...preset,
|
||
accentSoft: preset.accentSoft || buildHcPosSoftAccent(preset.accent || "#2d76ff", 0.14)
|
||
};
|
||
}
|
||
|
||
const HCPOS_WAIT_PAGE_PRESETS = {
|
||
earlyPartnership: createHcPosStaticPreset({
|
||
eyebrow: "前介管理",
|
||
title: "入伙管理总览",
|
||
summary: "汇总入伙交付、钥匙移交与缺陷闭环,替代等待页。",
|
||
accent: "#2d76ff",
|
||
cards: [
|
||
{ label: "待交付楼栋", value: "6", detail: "本月计划 3 栋" },
|
||
{ label: "已验房户数", value: "186", detail: "完成率 92.5%" },
|
||
{ label: "钥匙移交", value: "172", detail: "待领取 14 户" },
|
||
{ label: "整改闭环", value: "94.1%", detail: "遗留缺陷 11 项" }
|
||
],
|
||
headers: ["项目名称", "楼栋", "本周交付", "钥匙移交", "整改闭环", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "1-3 栋", "42 户", "39 户", "96%", "郭晓"],
|
||
["博万物", "A/B 座", "31 户", "28 户", "92%", "何琳"],
|
||
["江南世家一期", "5-6 栋", "26 户", "24 户", "89%", "陈谷先"]
|
||
]
|
||
}),
|
||
earlySalesCooperation: createHcPosStaticPreset({
|
||
eyebrow: "前介管理",
|
||
title: "售楼配合总览",
|
||
summary: "展示样板间巡检、看房接待与销售协同进度,替代等待页。",
|
||
accent: "#14b8a6",
|
||
cards: [
|
||
{ label: "本周接待", value: "128", detail: "到访转化 37.5%" },
|
||
{ label: "样板间巡检", value: "42", detail: "问题已闭环 38 项" },
|
||
{ label: "销售协同单", value: "23", detail: "超时 2 单" },
|
||
{ label: "满意度", value: "4.86", detail: "客户反馈稳定" }
|
||
],
|
||
headers: ["项目名称", "配合场景", "本周接待", "协同事项", "闭环率", "接口人"],
|
||
rows: [
|
||
["循环花园一期", "样板间接待", "48", "9", "100%", "郭晓"],
|
||
["博万物", "销售说辞配合", "36", "8", "87%", "何琳"],
|
||
["江南世家一期", "交付展示支持", "22", "6", "83%", "陈谷先"]
|
||
]
|
||
}),
|
||
earlyUndertakeInspection: createHcPosStaticPreset({
|
||
eyebrow: "前介管理",
|
||
title: "承接查验总览",
|
||
summary: "汇总查验批次、问题整改与移交节点,替代等待页。",
|
||
accent: "#8b5cf6",
|
||
cards: [
|
||
{ label: "查验批次", value: "18", detail: "本月新增 4 批" },
|
||
{ label: "查验问题", value: "126", detail: "已闭环 109 项" },
|
||
{ label: "待移交区域", value: "5", detail: "涉及公区与设备房" },
|
||
{ label: "闭环率", value: "86.5%", detail: "重点问题 7 项" }
|
||
],
|
||
headers: ["项目名称", "查验区域", "问题总数", "已闭环", "待移交", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "公区楼栋", "46", "41", "1", "郭晓"],
|
||
["博万物", "地下车库", "38", "31", "2", "何琳"],
|
||
["江南世家一期", "设备机房", "27", "22", "2", "陈谷先"]
|
||
]
|
||
}),
|
||
elevatorYearly: createHcPosStaticPreset({
|
||
eyebrow: "电梯管理",
|
||
title: "电梯年检总览",
|
||
summary: "汇总年检到期、检验状态与整改进度,替代等待页。",
|
||
accent: "#7c3aed",
|
||
cards: [
|
||
{ label: "年检设备", value: "48", detail: "30 天内到期 9 台" },
|
||
{ label: "已送检", value: "39", detail: "通过率 91.7%" },
|
||
{ label: "待整改", value: "6", detail: "均已派单" },
|
||
{ label: "复检排期", value: "3", detail: "本周完成" }
|
||
],
|
||
headers: ["项目名称", "设备编号", "年检到期", "检验状态", "整改项", "维保单位"],
|
||
rows: [
|
||
["循环花园一期", "DT-01", "2026-04-18", "已通过", "0", "万嘉电梯"],
|
||
["博万物", "DT-11", "2026-04-22", "整改中", "2", "城安机电"],
|
||
["江南世家一期", "DT-07", "2026-04-29", "待送检", "1", "华升特设"]
|
||
]
|
||
}),
|
||
energyMeterReadingCharge: createHcPosStaticPreset({
|
||
eyebrow: "能源管控",
|
||
title: "抄表收费总览",
|
||
summary: "展示抄表户数、收费金额与收缴进度,替代等待页。",
|
||
accent: "#0ea5e9",
|
||
cards: [
|
||
{ label: "抄表户数", value: "1,286", detail: "本月完成率 97.3%" },
|
||
{ label: "应收金额", value: "¥482,600", detail: "已收 91.2%" },
|
||
{ label: "待复核账单", value: "12", detail: "集中在商铺户" },
|
||
{ label: "异常读数", value: "5", detail: "已转人工核查" }
|
||
],
|
||
headers: ["项目名称", "抄表户数", "应收金额", "已收金额", "收缴率", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "386", "¥146,800", "¥138,900", "94.6%", "郭晓"],
|
||
["博万物", "312", "¥118,600", "¥106,200", "89.5%", "何琳"],
|
||
["江南世家一期", "241", "¥86,300", "¥79,400", "92.0%", "陈谷先"]
|
||
]
|
||
}),
|
||
equipmentEnergySaving: createHcPosStaticPreset({
|
||
eyebrow: "设备管理",
|
||
title: "节能降耗总览",
|
||
summary: "聚合设备节电措施、节能收益与异常耗能点位,替代等待页。",
|
||
accent: "#22c55e",
|
||
cards: [
|
||
{ label: "节能项目", value: "27", detail: "覆盖泵房 / 照明 / 风机" },
|
||
{ label: "本月节电", value: "18.4万kWh", detail: "同比下降 6.2%" },
|
||
{ label: "节能收益", value: "¥74,200", detail: "成本持续下降" },
|
||
{ label: "异常点位", value: "4", detail: "需本周复核" }
|
||
],
|
||
headers: ["项目名称", "节能措施", "本月节电", "同比变化", "状态", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "照明联控", "6.8万kWh", "-7.1%", "稳定", "郭晓"],
|
||
["博万物", "泵房变频", "5.1万kWh", "-5.4%", "观察", "何琳"],
|
||
["江南世家一期", "风机调度", "3.9万kWh", "-6.8%", "稳定", "陈谷先"]
|
||
]
|
||
}),
|
||
fullCleaning: createHcPosStaticPreset({
|
||
eyebrow: "全员行动",
|
||
title: "全员保洁总览",
|
||
summary: "展示集中保洁行动、参与规模与问题闭环,替代等待页。",
|
||
accent: "#06b6d4",
|
||
cards: [
|
||
{ label: "行动场次", value: "14", detail: "本月新增 3 场" },
|
||
{ label: "参与人数", value: "186", detail: "覆盖 9 个项目" },
|
||
{ label: "整治点位", value: "328", detail: "闭环率 94.8%" },
|
||
{ label: "住户好评", value: "96%", detail: "环境反馈明显提升" }
|
||
],
|
||
headers: ["项目名称", "行动主题", "参与人数", "整治点位", "闭环率", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "雨后公区清洁", "46", "82", "97%", "郭晓"],
|
||
["博万物", "地下车库专项保洁", "38", "71", "93%", "何琳"],
|
||
["江南世家一期", "楼道整治提升", "31", "56", "92%", "陈谷先"]
|
||
]
|
||
}),
|
||
fullPatrol: createHcPosStaticPreset({
|
||
eyebrow: "全员行动",
|
||
title: "全员巡查总览",
|
||
summary: "汇总集中巡查发现问题、闭环率与重点隐患,替代等待页。",
|
||
accent: "#f97316",
|
||
cards: [
|
||
{ label: "巡查场次", value: "11", detail: "覆盖重点区域 26 个" },
|
||
{ label: "发现问题", value: "94", detail: "已闭环 82 项" },
|
||
{ label: "重点隐患", value: "7", detail: "均已派发整改" },
|
||
{ label: "闭环率", value: "87.2%", detail: "本周继续跟进" }
|
||
],
|
||
headers: ["项目名称", "巡查主题", "参与人数", "发现问题", "闭环率", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "设备与环境联合巡查", "28", "31", "90%", "郭晓"],
|
||
["博万物", "高空坠物风险巡查", "24", "26", "85%", "何琳"],
|
||
["江南世家一期", "夜间安防巡查", "19", "18", "89%", "陈谷先"]
|
||
]
|
||
}),
|
||
materialScrapDisposal: createHcPosStaticPreset({
|
||
eyebrow: "物资管理",
|
||
title: "报废处理总览",
|
||
summary: "汇总报废物资、审批进度与处置去向,替代等待页。",
|
||
accent: "#ef4444",
|
||
cards: [
|
||
{ label: "报废单数", value: "26", detail: "本月新增 5 单" },
|
||
{ label: "报废物资", value: "318 件", detail: "设备件占比 42%" },
|
||
{ label: "审批通过", value: "21", detail: "待复核 3 单" },
|
||
{ label: "回收金额", value: "¥18,600", detail: "较上月 +11.4%" }
|
||
],
|
||
headers: ["项目名称", "报废物资", "数量", "审批状态", "处置方式", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "废旧探测器", "46", "已通过", "回收入库", "郭晓"],
|
||
["博万物", "老旧门禁卡", "128", "待复核", "集中销毁", "何琳"],
|
||
["江南世家一期", "损坏工具箱", "22", "已通过", "废品回收", "陈谷先"]
|
||
]
|
||
}),
|
||
parkingAppointmentVisit: createHcPosStaticPreset({
|
||
eyebrow: "车场运营",
|
||
title: "预约来访总览",
|
||
summary: "展示来访预约、放行效率与高峰时段表现,替代等待页。",
|
||
accent: "#7c4dff",
|
||
cards: [
|
||
{ label: "本日预约", value: "186", detail: "较昨日 +12" },
|
||
{ label: "已放行", value: "172", detail: "放行率 92.5%" },
|
||
{ label: "超时等待", value: "6", detail: "均已处理" },
|
||
{ label: "高峰时段", value: "18:00-20:00", detail: "需加岗 1 人" }
|
||
],
|
||
headers: ["项目名称", "来访类型", "预约数", "已到访", "通过率", "岗亭"],
|
||
rows: [
|
||
["循环花园一期", "访客车辆", "72", "67", "93.1%", "北门岗"],
|
||
["博万物", "商户来访", "58", "51", "87.9%", "西门岗"],
|
||
["江南世家一期", "施工车辆", "24", "22", "91.7%", "南门岗"]
|
||
]
|
||
}),
|
||
parkingSuspiciousRecords: createHcPosStaticPreset({
|
||
eyebrow: "车场运营",
|
||
title: "异常管理总览",
|
||
summary: "汇总异常进出、黑名单车辆与核查进度,替代等待页。",
|
||
accent: "#ef4444",
|
||
cards: [
|
||
{ label: "异常记录", value: "42", detail: "重复进出 18 条" },
|
||
{ label: "待核查", value: "9", detail: "黑名单 2 辆" },
|
||
{ label: "已处置", value: "31", detail: "闭环率 88.6%" },
|
||
{ label: "高风险时段", value: "23:00-02:00", detail: "需加强巡查" }
|
||
],
|
||
headers: ["项目名称", "异常类型", "本月记录", "已闭环", "风险级别", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "重复进出", "15", "13", "中", "郭晓"],
|
||
["博万物", "车牌异常", "12", "9", "高", "何琳"],
|
||
["江南世家一期", "逃费嫌疑", "8", "7", "中", "陈谷先"]
|
||
]
|
||
}),
|
||
parkingTemporaryControl: createHcPosStaticPreset({
|
||
eyebrow: "车场运营",
|
||
title: "临停管控总览",
|
||
summary: "展示临停车位利用、收费完成与管控效果,替代等待页。",
|
||
accent: "#14b8a6",
|
||
cards: [
|
||
{ label: "临停车位", value: "486", detail: "高峰利用率 84%" },
|
||
{ label: "日均车次", value: "1,128", detail: "周末更高" },
|
||
{ label: "临停收费", value: "¥62,400", detail: "本月累计" },
|
||
{ label: "异常拦截", value: "17", detail: "已复核完成" }
|
||
],
|
||
headers: ["项目名称", "临停车位", "日均车次", "异常拦截", "收费率", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "186", "426", "6", "95.4%", "郭晓"],
|
||
["博万物", "144", "358", "5", "91.8%", "何琳"],
|
||
["江南世家一期", "102", "221", "3", "93.7%", "陈谷先"]
|
||
]
|
||
}),
|
||
parkingPhysical: createHcPosStaticPreset({
|
||
eyebrow: "车场运营",
|
||
title: "车场测评总览",
|
||
summary: "汇总车场测评得分、整改项与复检状态,替代等待页。",
|
||
accent: "#8b5cf6",
|
||
cards: [
|
||
{ label: "测评批次", value: "12", detail: "覆盖 7 个项目" },
|
||
{ label: "平均得分", value: "89.6", detail: "较上月 +1.4" },
|
||
{ label: "整改项", value: "16", detail: "待复检 4 项" },
|
||
{ label: "优秀率", value: "58.3%", detail: "A 级占比" }
|
||
],
|
||
headers: ["项目名称", "测评主题", "本月测评", "平均分", "整改项", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "出入口秩序", "4", "92.4", "2", "郭晓"],
|
||
["博万物", "岗亭服务", "3", "88.7", "3", "何琳"],
|
||
["江南世家一期", "车场动线", "2", "87.9", "1", "陈谷先"]
|
||
]
|
||
}),
|
||
parkingOwnerArchives: createHcPosStaticPreset({
|
||
eyebrow: "车场运营",
|
||
title: "车主档案总览",
|
||
summary: "汇总车主建档、认证状态与月卡结构,替代等待页。",
|
||
accent: "#2d76ff",
|
||
cards: [
|
||
{ label: "车主档案", value: "2,148", detail: "本月新增 64 份" },
|
||
{ label: "已认证", value: "1,986", detail: "认证率 92.5%" },
|
||
{ label: "待补资料", value: "42", detail: "集中在租户车主" },
|
||
{ label: "月卡车主", value: "1,284", detail: "固定车主占比高" }
|
||
],
|
||
headers: ["项目名称", "车主档案", "已认证", "待补资料", "月卡车主", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "726", "683", "12", "438", "郭晓"],
|
||
["博万物", "542", "491", "18", "336", "何琳"],
|
||
["江南世家一期", "314", "287", "7", "184", "陈谷先"]
|
||
]
|
||
}),
|
||
renovationDeclaration: createHcPosStaticPreset({
|
||
eyebrow: "装修管理",
|
||
title: "装修报建总览",
|
||
summary: "展示装修申请、审批进度与违规预警,替代等待页。",
|
||
accent: "#f59e0b",
|
||
cards: [
|
||
{ label: "报建申请", value: "86", detail: "本月新增 18 单" },
|
||
{ label: "已审批", value: "72", detail: "通过率 83.7%" },
|
||
{ label: "在审中", value: "9", detail: "平均 1.8 天" },
|
||
{ label: "违规预警", value: "3", detail: "均已通知整改" }
|
||
],
|
||
headers: ["项目名称", "报建申请", "已审批", "在审中", "违规项", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "28", "23", "3", "1", "郭晓"],
|
||
["博万物", "21", "18", "2", "1", "何琳"],
|
||
["江南世家一期", "14", "12", "1", "0", "陈谷先"]
|
||
]
|
||
}),
|
||
renovationCheck: createHcPosStaticPreset({
|
||
eyebrow: "装修管理",
|
||
title: "装修验收总览",
|
||
summary: "汇总验收批次、通过率与复检情况,替代等待页。",
|
||
accent: "#14b8a6",
|
||
cards: [
|
||
{ label: "验收批次", value: "52", detail: "本月新增 11 批" },
|
||
{ label: "通过率", value: "88.5%", detail: "复检中 5 单" },
|
||
{ label: "整改项", value: "18", detail: "噪音粉尘为主" },
|
||
{ label: "待复检", value: "5", detail: "均已排期" }
|
||
],
|
||
headers: ["项目名称", "验收批次", "通过率", "整改项", "复检中", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "18", "91.6%", "5", "1", "郭晓"],
|
||
["博万物", "14", "85.7%", "7", "2", "何琳"],
|
||
["江南世家一期", "9", "88.9%", "2", "1", "陈谷先"]
|
||
]
|
||
}),
|
||
renovationFeeManage: createHcPosStaticPreset({
|
||
eyebrow: "装修管理",
|
||
title: "装修收费管理总览",
|
||
summary: "展示装修押金、垃圾清运费与欠费情况,替代等待页。",
|
||
accent: "#2d76ff",
|
||
cards: [
|
||
{ label: "收费单数", value: "74", detail: "本月新增 16 单" },
|
||
{ label: "应收金额", value: "¥428,600", detail: "已收 93.4%" },
|
||
{ label: "押金冻结", value: "¥216,000", detail: "待退 9 单" },
|
||
{ label: "欠费单", value: "4", detail: "均已提醒" }
|
||
],
|
||
headers: ["项目名称", "收费单数", "应收金额", "已收金额", "欠费单", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "24", "¥138,000", "¥132,000", "1", "郭晓"],
|
||
["博万物", "18", "¥106,400", "¥98,600", "2", "何琳"],
|
||
["江南世家一期", "12", "¥68,200", "¥66,900", "0", "陈谷先"]
|
||
]
|
||
}),
|
||
renovationPatrol: createHcPosStaticPreset({
|
||
eyebrow: "装修管理",
|
||
title: "装修巡查总览",
|
||
summary: "汇总日常巡查、违规装修与闭环情况,替代等待页。",
|
||
accent: "#ef4444",
|
||
cards: [
|
||
{ label: "巡查频次", value: "164", detail: "日均 5.4 次" },
|
||
{ label: "发现问题", value: "28", detail: "已闭环 24 项" },
|
||
{ label: "违规装修", value: "6", detail: "集中在超时施工" },
|
||
{ label: "闭环率", value: "85.7%", detail: "需继续跟进" }
|
||
],
|
||
headers: ["项目名称", "巡查频次", "发现问题", "已闭环", "违规装修", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "58", "11", "10", "2", "郭晓"],
|
||
["博万物", "46", "9", "7", "3", "何琳"],
|
||
["江南世家一期", "28", "4", "4", "0", "陈谷先"]
|
||
]
|
||
}),
|
||
satisfactionQuestionnaire: createHcPosStaticPreset({
|
||
eyebrow: "客户满意",
|
||
title: "调查问卷总览",
|
||
summary: "展示问卷发放、回收率与满意度趋势,替代等待页。",
|
||
accent: "#8b5cf6",
|
||
cards: [
|
||
{ label: "问卷主题", value: "18", detail: "覆盖保洁 / 车场 / 安防" },
|
||
{ label: "发放数量", value: "3,286", detail: "本月累计" },
|
||
{ label: "回收数量", value: "2,614", detail: "回收率 79.5%" },
|
||
{ label: "平均满意度", value: "4.73", detail: "较上月 +0.04" }
|
||
],
|
||
headers: ["项目名称", "问卷主题", "发放数量", "回收数量", "回收率", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "公区服务体验", "986", "812", "82.4%", "郭晓"],
|
||
["博万物", "车场秩序满意度", "742", "568", "76.5%", "何琳"],
|
||
["江南世家一期", "安防服务评价", "418", "339", "81.1%", "陈谷先"]
|
||
]
|
||
}),
|
||
securityMonitoringCenter: createHcPosStaticPreset({
|
||
eyebrow: "安防管理",
|
||
title: "监控中心总览",
|
||
summary: "展示监控在线率、离线设备与告警处置,替代等待页。",
|
||
accent: "#ef4444",
|
||
cards: [
|
||
{ label: "在线设备", value: "1,286", detail: "在线率 98.7%" },
|
||
{ label: "离线设备", value: "17", detail: "已派检修 9 台" },
|
||
{ label: "本月告警", value: "68", detail: "已处置 61 条" },
|
||
{ label: "处置率", value: "89.7%", detail: "夜间告警偏多" }
|
||
],
|
||
headers: ["项目名称", "在线设备", "离线设备", "异常告警", "处置率", "值班长"],
|
||
rows: [
|
||
["循环花园一期", "426", "4", "18", "94.4%", "郭晓"],
|
||
["博万物", "318", "7", "23", "82.6%", "何琳"],
|
||
["江南世家一期", "204", "2", "9", "88.9%", "陈谷先"]
|
||
]
|
||
}),
|
||
securityEmergencyManagement: createHcPosStaticPreset({
|
||
eyebrow: "安防管理",
|
||
title: "应急处理总览",
|
||
summary: "汇总应急预案、演练频次与响应时长,替代等待页。",
|
||
accent: "#f97316",
|
||
cards: [
|
||
{ label: "应急预案", value: "24", detail: "覆盖火警 / 停电 / 水浸" },
|
||
{ label: "演练次数", value: "18", detail: "季度计划完成" },
|
||
{ label: "平均响应", value: "6.8 分钟", detail: "同比缩短 0.9 分钟" },
|
||
{ label: "待整改", value: "5", detail: "集中在物资准备" }
|
||
],
|
||
headers: ["项目名称", "预案类型", "演练次数", "响应时长", "待整改", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "火警处置", "6", "6.2 分钟", "1", "郭晓"],
|
||
["博万物", "停电应急", "5", "7.1 分钟", "2", "何琳"],
|
||
["江南世家一期", "水浸处置", "3", "6.9 分钟", "1", "陈谷先"]
|
||
]
|
||
}),
|
||
securityArchives: createHcPosStaticPreset({
|
||
eyebrow: "安防管理",
|
||
title: "安防档案总览",
|
||
summary: "汇总安防档案、证照临期与资料完备度,替代等待页。",
|
||
accent: "#2d76ff",
|
||
cards: [
|
||
{ label: "安防档案", value: "86", detail: "设备 / 人员 / 证照" },
|
||
{ label: "完备率", value: "96.5%", detail: "缺失资料 3 份" },
|
||
{ label: "临期证照", value: "6", detail: "需本月更新" },
|
||
{ label: "更新状态", value: "稳定", detail: "本周新增 4 份" }
|
||
],
|
||
headers: ["项目名称", "安防档案", "完备率", "临期证照", "更新状态", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "32", "100%", "1", "正常", "郭晓"],
|
||
["博万物", "24", "91.7%", "3", "补录中", "何琳"],
|
||
["江南世家一期", "11", "90.9%", "1", "正常", "陈谷先"]
|
||
]
|
||
}),
|
||
securityInternetOfThings: createHcPosStaticPreset({
|
||
eyebrow: "安全生产",
|
||
title: "物联网总览",
|
||
summary: "展示联网设备、在线状态与告警处置,替代等待页。",
|
||
accent: "#14b8a6",
|
||
cards: [
|
||
{ label: "联网设备", value: "624", detail: "覆盖烟感 / 水压 / 门磁" },
|
||
{ label: "在线率", value: "97.9%", detail: "离线 13 台" },
|
||
{ label: "本月告警", value: "54", detail: "已处置 49 条" },
|
||
{ label: "处置率", value: "90.7%", detail: "重点关注夜间告警" }
|
||
],
|
||
headers: ["项目名称", "联网设备", "在线率", "告警数", "已处置", "负责人"],
|
||
rows: [
|
||
["循环花园一期", "218", "98.6%", "16", "15", "郭晓"],
|
||
["博万物", "176", "96.8%", "21", "18", "何琳"],
|
||
["江南世家一期", "114", "97.4%", "9", "8", "陈谷先"]
|
||
]
|
||
})
|
||
};
|
||
|
||
const HCPOS_WAIT_PAGE_ROUTE_PRESETS = {
|
||
"/propertySMG/earlyManagement/partnership/": "earlyPartnership",
|
||
"/propertySMG/earlyManagement/salesCooperation/": "earlySalesCooperation",
|
||
"/propertySMG/earlyManagement/undertakeInspection/": "earlyUndertakeInspection",
|
||
"/propertySMG/elevatorManage/elevatorYearly/": "elevatorYearly",
|
||
"/propertySMG/energySourceOperat/publicAreaControl/meterReadingCharge/": "energyMeterReadingCharge",
|
||
"/propertySMG/equipmentManage/useManagement/energySaving/": "equipmentEnergySaving",
|
||
"/propertySMG/fullForceAssaultSMG/fullCleaning/": "fullCleaning",
|
||
"/propertySMG/fullForceAssaultSMG/fullPatrol/": "fullPatrol",
|
||
"/propertySMG/materialManage/scrapDisposal/": "materialScrapDisposal",
|
||
"/propertySMG/parkingOperation/dailyManage/appointmentVisit/": "parkingAppointmentVisit",
|
||
"/propertySMG/parkingOperation/dailyManage/suspiciousRecords/": "parkingSuspiciousRecords",
|
||
"/propertySMG/parkingOperation/dailyManage/temporaryControl/": "parkingTemporaryControl",
|
||
"/propertySMG/parkingOperation/parkingPhysical/": "parkingPhysical",
|
||
"/propertySMG/parkingOperation/parkingRecord/ownerArchives/": "parkingOwnerArchives",
|
||
"/propertySMG/renovationManage/renovation/": "renovationDeclaration",
|
||
"/propertySMG/renovationManage/renovationCheck/": "renovationCheck",
|
||
"/propertySMG/renovationManage/renovationFeeManage/": "renovationFeeManage",
|
||
"/propertySMG/renovationManage/renovationPatrol/": "renovationPatrol",
|
||
"/propertySMG/satisfaction/satisfactionSurvey/questionnaire/": "satisfactionQuestionnaire",
|
||
"/propertySMG/securityManage/dailySecurity/monitoringCenter/": "securityMonitoringCenter",
|
||
"/propertySMG/securityManage/emergencyManagement/": "securityEmergencyManagement",
|
||
"/propertySMG/securityManage/securityFile/securityArchives/": "securityArchives",
|
||
"/propertySMG/securityProduction/dangerousSupervision/internetofThings/": "securityInternetOfThings"
|
||
};
|
||
|
||
const HCPOS_COCKPIT_LINKS = {
|
||
"智能人事": { runtime: "hcpos", path: "/systemManage/projectConfig/internalControl/ProjectManagerConfig" },
|
||
"作业看板": { runtime: "hcpos", path: "/propertySMG/equipmentManage/equipmentPortrait" },
|
||
"车场看板": { runtime: "hcpos", path: "/propertySMG/parkingOperation/parkingMicroBrain" },
|
||
"能源看板": { runtime: "hcpos", path: "/propertySMG/energySourceOperat/notice" },
|
||
"住户卡": { runtime: "hcpos", path: "/communitySMG/personnelList" },
|
||
"安全生产": { runtime: "hcpos", path: "/propertySMG/securityProduction/checkStandardLibrary" },
|
||
"收费看板": { runtime: "hcpos", path: "/propertySMG/businessTaxCank/financeMicrobrain" },
|
||
"客户通": { runtime: "hcpos", path: "/propertySMG/customerOperations/customerPortrait" },
|
||
"网格看板": { runtime: "hcpos", path: "/propertySMG/basicManagement/spatialRegion" },
|
||
"物业费报表": { runtime: "hcetms", path: "/r2cockpit/cloudData/propertyFeeReport" },
|
||
"车场报表": { runtime: "hcetms", path: "/r2cockpit/cloudData/parkingLotReport" },
|
||
"能耗报表": { runtime: "hcpos", path: "/propertySMG/energySourceOperat/energySourcMicrobrain" },
|
||
"全年收入报表": { runtime: "hcpos", path: "/propertySMG/businessTaxCank/financeReport/incomeStatement" },
|
||
"全员收费报表": { runtime: "hcpos", path: "/propertySMG/fullForceAssaultSMG/fullStaffFee/arrearsReport" },
|
||
"收入考核报表": { runtime: "hcpos", path: "/propertySMG/businessTaxCank/financeReport/projectIncome" },
|
||
"回款动作与分析": { runtime: "hcetms", path: "/r2cockpit/cloudData/collectionTracking" },
|
||
"日清日高": { runtime: "hcpos", path: "/dashboard" },
|
||
"月清月高": { runtime: "hcpos", path: "/dashboard" },
|
||
"智能催费": { runtime: "hcpos", path: "/aiProduct/urgePayment/urgeVisitPlan" },
|
||
"智能质检": { runtime: "hcpos", path: "/aiProduct/videoQualityInspection" },
|
||
"智能能源": { runtime: "hcpos", path: "/propertySMG/energySourceOperat/energySourcMicrobrain" },
|
||
"智能工单": { runtime: "hcpos", path: "/propertySMG/securityProduction/rectificationImplementation/dangerousPlan" },
|
||
"智能报告": { runtime: "hcpos", path: "/systemManage/projectConfig/internalControl/theReportModule" },
|
||
"验收工作台": { runtime: "hcetms", path: "/trainingPush/sampling" },
|
||
"视频验收": { runtime: "hcpos", path: "/aiProduct/videoQualityInspection" },
|
||
"安全指数": { runtime: "hcpos", path: "/propertySMG/securityProduction/checkStandardLibrary" },
|
||
"危险源监控": { runtime: "hcpos", path: "/propertySMG/securityProduction/checkStandardLibrary" },
|
||
"应急预案": { runtime: "hcpos", path: "/propertySMG/securityProduction/rectificationImplementation/emergencyPlan" },
|
||
"应急演练": { runtime: "hcpos", path: "/propertySMG/securityProduction/rectificationImplementation/securityDrills" },
|
||
"安全培训": { runtime: "hcetms", path: "/trainingManage" },
|
||
"日现金流入": { runtime: "hcpos", path: "/propertySMG/businessTaxCank/financeMicrobrain" },
|
||
"日现金流出": { runtime: "hcpos", path: "/propertySMG/businessTaxCank/financeMicrobrain" },
|
||
"收入结构图": { runtime: "hcpos", path: "/propertySMG/businessTaxCank/financeMicrobrain" },
|
||
"成本结构图": { runtime: "hcpos", path: "/propertySMG/businessTaxCank/financeMicrobrain" },
|
||
"客户满意": { runtime: "hcpos", path: "/propertySMG/customerOperations/customerPortrait" },
|
||
"客户投诉": { runtime: "hcpos", path: "/propertySMG/customerOperations/customerPortrait" },
|
||
"内部满意": { runtime: "hcpos", path: "/propertySMG/customerOperations/customerPortrait" },
|
||
"计划工单": { runtime: "hcetms", path: "/r2cockpit/cloudData/planTaskReport" },
|
||
"社区文化": { runtime: "hcpos", path: "/communitySMG/communityCulture" },
|
||
"生成报告": { runtime: "hcpos", path: "/systemManage/projectConfig/internalControl/theReportModule" },
|
||
"查看报告": { runtime: "hcpos", path: "/systemManage/projectConfig/internalControl/theReportModule" },
|
||
"重新生成报告": { runtime: "hcpos", path: "/systemManage/projectConfig/internalControl/theReportModule" },
|
||
"返回": { runtime: "hcpos", path: "/dashboard" },
|
||
"企业后台": { runtime: "hcetms", path: "" }
|
||
};
|
||
|
||
const HCPOS_SECONDARY_TAB_PRESETS = {
|
||
keyCustomer: {
|
||
eyebrow: "客户分层",
|
||
title: "重点客户总览",
|
||
summary: "聚合重点客户分层、跟进状态与回访任务,补齐空白标签页。",
|
||
cards: [
|
||
{ label: "重点客户", value: "126", detail: "A 类客户 38 位" },
|
||
{ label: "本周回访", value: "64", detail: "完成率 82.8%" },
|
||
{ label: "投诉敏感", value: "11", detail: "需专人跟进" },
|
||
{ label: "回款客户", value: "72", detail: "本月已回款" }
|
||
],
|
||
headers: ["客户名称", "所属项目", "客户等级", "关注标签", "最近回访", "负责人", "状态"],
|
||
rows: [
|
||
["李天", "循环花园一期", "A", "高频互动", "2026-04-03", "郭晓", "持续跟进"],
|
||
["肖英", "明珠佳园", "A", "缴费稳定", "2026-04-02", "何琳", "正常"],
|
||
["卢跃梅", "龙湾一品小区", "B", "投诉敏感", "2026-04-01", "陈谷先", "需回访"],
|
||
["小小", "明珠佳园", "B", "亲属关系维护", "2026-03-30", "曾丽娜", "正常"],
|
||
["来了", "A 管理区", "C", "新签客户", "2026-03-28", "周宇", "观察中"]
|
||
]
|
||
},
|
||
customizedPhysicalRecord: {
|
||
eyebrow: "专家测评",
|
||
title: "定制体检记录总览",
|
||
summary: "展示体检对象、测评得分与整改建议,补齐空白记录页。",
|
||
cards: [
|
||
{ label: "体检记录", value: "86", detail: "本月新增 12 条" },
|
||
{ label: "平均得分", value: "82.6", detail: "定制套餐口径" },
|
||
{ label: "高风险项", value: "9", detail: "需复检" },
|
||
{ label: "整改闭环率", value: "78.4%", detail: "本周提升" }
|
||
],
|
||
headers: ["项目名称", "体检对象", "套餐名称", "测评日期", "得分", "风险等级", "建议动作"],
|
||
rows: [
|
||
["循环花园一期", "消防泵房", "火灾应急", "2026-04-03", "88.2", "低", "保持巡检"],
|
||
["博万物", "设备房", "设备管控", "2026-04-02", "79.4", "中", "补充维保"],
|
||
["美好花园", "绿化景观带", "绿化作业", "2026-04-01", "84.1", "低", "正常维护"],
|
||
["连城花园", "楼栋卫生区", "清洁作业", "2026-03-30", "76.3", "中", "安排复检"],
|
||
["江南世家一期", "园区道路", "清洁作业", "2026-03-28", "71.8", "高", "专项整改"]
|
||
]
|
||
},
|
||
specialPhysicalRecord: {
|
||
eyebrow: "专家测评",
|
||
title: "专项体检记录总览",
|
||
summary: "展示专项体检对象、问题点位与整改进展,补齐空白记录页。",
|
||
cards: [
|
||
{ label: "专项记录", value: "112", detail: "车场 / 绿化 / 安防" },
|
||
{ label: "平均得分", value: "84.9", detail: "较上月 +1.2" },
|
||
{ label: "整改中", value: "14", detail: "需闭环追踪" },
|
||
{ label: "通过率", value: "87.5%", detail: "专项测评口径" }
|
||
],
|
||
headers: ["项目名称", "专项对象", "套餐名称", "测评日期", "得分", "整改状态", "责任人"],
|
||
rows: [
|
||
["循环花园一期", "车场入口", "车场专项体检-5A", "2026-04-03", "83.6", "已完成", "郭晓"],
|
||
["博万物", "中央花园", "树木与设备加固", "2026-04-02", "79.1", "处理中", "何琳"],
|
||
["美好花园", "北门绿篱", "树木枯枝修剪", "2026-04-01", "81.4", "已完成", "曾丽娜"],
|
||
["连城花园", "垃圾堆放点", "垃圾堆放点清洁", "2026-03-30", "74.8", "待复检", "周宇"],
|
||
["江南世家一期", "楼道地面", "楼道地面清洁", "2026-03-29", "78.2", "处理中", "陈谷先"]
|
||
]
|
||
},
|
||
fiveAPhysicalRecord: {
|
||
eyebrow: "专家测评",
|
||
title: "5A体检记录总览",
|
||
summary: "展示 5A 测评对象、评分与复检安排,补齐空白记录页。",
|
||
cards: [
|
||
{ label: "5A记录", value: "64", detail: "本月新增 8 条" },
|
||
{ label: "平均得分", value: "89.4", detail: "高于月基线" },
|
||
{ label: "待复评", value: "6", detail: "需补充复检" },
|
||
{ label: "优秀率", value: "62.5%", detail: "A 级占比" }
|
||
],
|
||
headers: ["项目名称", "体检对象", "套餐名称", "测评日期", "得分", "评级", "整改动作"],
|
||
rows: [
|
||
["循环花园一期", "公共区域", "体检套餐2.0-5A", "2026-04-03", "91.2", "A", "保持标准"],
|
||
["博万物", "设备房", "体检套餐2.0-5A", "2026-04-02", "87.6", "A-", "补录照片"],
|
||
["美好花园", "车场入口", "体检套餐2.0-5A", "2026-04-01", "84.2", "B+", "专项复检"],
|
||
["连城花园", "物业服务中心", "体检套餐2.0-5A", "2026-03-30", "88.5", "A-", "正常跟进"],
|
||
["江南世家一期", "园区主路", "体检套餐2.0-5A", "2026-03-28", "82.3", "B", "优化流程"]
|
||
]
|
||
}
|
||
};
|
||
const HCPOS_OVERVIEW_PRESETS = {
|
||
personnelList: {
|
||
eyebrow: "社区治理",
|
||
title: "住户档案总览",
|
||
summary: "聚合住户数量、产权结构与审核状态,增强主页信息层次。",
|
||
cards: [
|
||
{ label: "住户总数", value: "173", detail: "产权人 96 位" },
|
||
{ label: "审核通过", value: "162", detail: "通过率 93.6%" },
|
||
{ label: "绑定微信", value: "84", detail: "绑定率持续提升" },
|
||
{ label: "待审核", value: "11", detail: "需尽快处理" }
|
||
]
|
||
},
|
||
canteenArchives: {
|
||
eyebrow: "家企服务",
|
||
title: "食堂档案总览",
|
||
summary: "汇总食堂档案、经营主体与启用状态,增强主页信息层次。",
|
||
cards: [
|
||
{ label: "食堂数量", value: "18", detail: "启用 16 家" },
|
||
{ label: "本月新增", value: "2", detail: "新增 1 家在审核" },
|
||
{ label: "经营单位", value: "12", detail: "已建档主体" },
|
||
{ label: "联系人完整率", value: "100%", detail: "档案资料齐全" }
|
||
]
|
||
},
|
||
customizedPhysical: {
|
||
eyebrow: "专家测评",
|
||
title: "定制测评总览",
|
||
summary: "展示定制套餐数量、应用场景与平均分,提升主页概览能力。",
|
||
cards: [
|
||
{ label: "定制套餐", value: "46", detail: "本月新增 6 个" },
|
||
{ label: "应用场景", value: "12", detail: "覆盖清洁 / 安全 / 设备" },
|
||
{ label: "平均分数", value: "36.8", detail: "定制口径" },
|
||
{ label: "高分套餐", value: "8", detail: "80 分以上" }
|
||
]
|
||
},
|
||
specialPhysical: {
|
||
eyebrow: "专家测评",
|
||
title: "专项测评总览",
|
||
summary: "展示专项套餐分布、评分与同步情况,提升主页概览能力。",
|
||
cards: [
|
||
{ label: "专项套餐", value: "22", detail: "同步云库 1 次" },
|
||
{ label: "应用场景", value: "8", detail: "车场 / 绿化 / 清洁" },
|
||
{ label: "平均分数", value: "24.7", detail: "专项口径" },
|
||
{ label: "高风险套餐", value: "3", detail: "需复检" }
|
||
]
|
||
},
|
||
fiveAPhysical: {
|
||
eyebrow: "专家测评",
|
||
title: "5A测评总览",
|
||
summary: "汇总 5A 套餐规模、测评强度与通过率,提升主页概览能力。",
|
||
cards: [
|
||
{ label: "5A 套餐", value: "1", detail: "主套餐已上线" },
|
||
{ label: "套餐分数", value: "22088", detail: "5A 标准口径" },
|
||
{ label: "体检记录", value: "64", detail: "已补至记录页" },
|
||
{ label: "优秀率", value: "62.5%", detail: "A 级占比" }
|
||
]
|
||
}
|
||
};
|
||
|
||
function buildLinePath(values, width, height, padding) {
|
||
const max = Math.max(...values);
|
||
const min = Math.min(...values);
|
||
const span = Math.max(max - min, 1);
|
||
return values
|
||
.map((value, index) => {
|
||
const x = padding + (index * (width - padding * 2)) / Math.max(values.length - 1, 1);
|
||
const y = height - padding - ((value - min) / span) * (height - padding * 2);
|
||
return `${index === 0 ? "M" : "L"}${x.toFixed(1)} ${y.toFixed(1)}`;
|
||
})
|
||
.join(" ");
|
||
}
|
||
|
||
function buildAreaPoints(values, width, height, padding) {
|
||
const max = Math.max(...values);
|
||
const min = Math.min(...values);
|
||
const span = Math.max(max - min, 1);
|
||
const points = values.map((value, index) => {
|
||
const x = padding + (index * (width - padding * 2)) / Math.max(values.length - 1, 1);
|
||
const y = height - padding - ((value - min) / span) * (height - padding * 2);
|
||
return `${x.toFixed(1)},${y.toFixed(1)}`;
|
||
});
|
||
points.push(`${width - padding},${height - padding}`, `${padding},${height - padding}`);
|
||
return points.join(" ");
|
||
}
|
||
|
||
function buildTrendSvg(seriesList, labels) {
|
||
const width = 560;
|
||
const height = 220;
|
||
const padding = 24;
|
||
const allValues = seriesList.flatMap((item) => item.values);
|
||
const max = Math.max(...allValues);
|
||
const min = Math.min(...allValues);
|
||
const span = Math.max(max - min, 1);
|
||
|
||
const guides = [0, 0.25, 0.5, 0.75, 1].map((ratio) => {
|
||
const y = padding + ratio * (height - padding * 2);
|
||
const value = Math.round(max - ratio * span);
|
||
return `
|
||
<line x1="${padding}" y1="${y}" x2="${width - padding}" y2="${y}" stroke="rgba(17,37,63,0.08)" stroke-dasharray="4 6"></line>
|
||
<text x="0" y="${y + 4}" fill="#8b9ab3" font-size="11">${value}</text>
|
||
`;
|
||
});
|
||
|
||
const xLabels = labels
|
||
.map((label, index) => {
|
||
const x = padding + (index * (width - padding * 2)) / Math.max(labels.length - 1, 1);
|
||
return `<text x="${x}" y="${height}" text-anchor="middle" fill="#8b9ab3" font-size="11">${label}</text>`;
|
||
})
|
||
.join("");
|
||
|
||
const paths = seriesList
|
||
.map((item, index) => {
|
||
const areaOpacity = index === 0 ? 0.14 : 0.08;
|
||
return `
|
||
<polygon points="${buildAreaPoints(item.values, width, height, padding)}" fill="${item.color}" opacity="${areaOpacity}"></polygon>
|
||
<path d="${buildLinePath(item.values, width, height, padding)}" fill="none" stroke="${item.color}" stroke-width="3" stroke-linecap="round"></path>
|
||
${item.values
|
||
.map((value, valueIndex) => {
|
||
const x = padding + (valueIndex * (width - padding * 2)) / Math.max(item.values.length - 1, 1);
|
||
const y = height - padding - ((value - min) / span) * (height - padding * 2);
|
||
return `<circle cx="${x}" cy="${y}" r="4" fill="#fff" stroke="${item.color}" stroke-width="2"></circle>`;
|
||
})
|
||
.join("")}
|
||
`;
|
||
})
|
||
.join("");
|
||
|
||
return `
|
||
<svg viewBox="0 0 ${width} ${height + 18}" class="codex-hcpos-chart" preserveAspectRatio="none">
|
||
${guides.join("")}
|
||
${paths}
|
||
${xLabels}
|
||
</svg>
|
||
`;
|
||
}
|
||
|
||
function renderHcPosDashboard(doc, type) {
|
||
const preset = HCPOS_DASHBOARD_PRESETS[type];
|
||
if (!preset) {
|
||
return;
|
||
}
|
||
if (!doc.getElementById("__codex_hcpos_dashboard_style__")) {
|
||
const style = doc.createElement("style");
|
||
style.id = "__codex_hcpos_dashboard_style__";
|
||
style.textContent = `
|
||
.waitMain {
|
||
display: none !important;
|
||
}
|
||
|
||
#__codex_hcpos_dashboard__ {
|
||
margin-top: 18px;
|
||
padding: 20px 22px 24px;
|
||
border-radius: 20px;
|
||
background:
|
||
radial-gradient(circle at top right, rgba(45, 118, 255, 0.12), transparent 28%),
|
||
linear-gradient(180deg, #ffffff 0%, #f7faff 100%);
|
||
border: 1px solid rgba(17, 37, 63, 0.06);
|
||
box-shadow: 0 18px 42px rgba(17, 37, 63, 0.08);
|
||
}
|
||
|
||
.codex-hcpos-head {
|
||
display: flex;
|
||
align-items: flex-end;
|
||
justify-content: space-between;
|
||
gap: 20px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.codex-hcpos-eyebrow {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 4px 10px;
|
||
border-radius: 999px;
|
||
margin-bottom: 10px;
|
||
font-size: 12px;
|
||
color: #5b6f92;
|
||
background: rgba(17, 37, 63, 0.06);
|
||
}
|
||
|
||
.codex-hcpos-head h3 {
|
||
margin: 0 0 6px;
|
||
font-size: 28px;
|
||
color: #12233d;
|
||
}
|
||
|
||
.codex-hcpos-head p {
|
||
margin: 0;
|
||
max-width: 660px;
|
||
color: #6d7d97;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.codex-hcpos-badge {
|
||
padding: 10px 14px;
|
||
border-radius: 14px;
|
||
color: #35517d;
|
||
font-size: 12px;
|
||
background: rgba(255,255,255,0.8);
|
||
border: 1px solid rgba(17,37,63,0.08);
|
||
}
|
||
|
||
.codex-hcpos-metrics {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
gap: 14px;
|
||
margin-bottom: 18px;
|
||
}
|
||
|
||
.codex-hcpos-metric {
|
||
padding: 16px 18px;
|
||
border-radius: 18px;
|
||
background: rgba(255,255,255,0.82);
|
||
border: 1px solid rgba(17,37,63,0.06);
|
||
box-shadow: 0 12px 28px rgba(17,37,63,0.06);
|
||
}
|
||
|
||
.codex-hcpos-metric span {
|
||
display: block;
|
||
color: #7d8ca5;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.codex-hcpos-metric strong {
|
||
display: block;
|
||
margin: 8px 0 6px;
|
||
color: #12233d;
|
||
font-size: 28px;
|
||
line-height: 1.1;
|
||
}
|
||
|
||
.codex-hcpos-metric small {
|
||
color: #4e6b98;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.codex-hcpos-layout {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1.6fr) minmax(320px, 0.95fr);
|
||
gap: 16px;
|
||
}
|
||
|
||
.codex-hcpos-panel {
|
||
padding: 18px 18px 16px;
|
||
border-radius: 18px;
|
||
background: rgba(255,255,255,0.92);
|
||
border: 1px solid rgba(17,37,63,0.06);
|
||
}
|
||
|
||
.codex-hcpos-panel h4 {
|
||
margin: 0 0 14px;
|
||
color: #152742;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.codex-hcpos-legend {
|
||
display: flex;
|
||
gap: 14px;
|
||
flex-wrap: wrap;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.codex-hcpos-legend span {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
color: #71829d;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.codex-hcpos-dot {
|
||
width: 9px;
|
||
height: 9px;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.codex-hcpos-chart {
|
||
width: 100%;
|
||
height: 238px;
|
||
}
|
||
|
||
.codex-hcpos-segments {
|
||
display: grid;
|
||
gap: 12px;
|
||
}
|
||
|
||
.codex-hcpos-segment {
|
||
display: grid;
|
||
grid-template-columns: 88px 1fr 48px;
|
||
align-items: center;
|
||
gap: 10px;
|
||
color: #4d607f;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.codex-hcpos-track {
|
||
overflow: hidden;
|
||
height: 10px;
|
||
border-radius: 999px;
|
||
background: #eef3fb;
|
||
}
|
||
|
||
.codex-hcpos-fill {
|
||
height: 100%;
|
||
border-radius: inherit;
|
||
}
|
||
|
||
.codex-hcpos-table table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
}
|
||
|
||
.codex-hcpos-table th,
|
||
.codex-hcpos-table td {
|
||
padding: 12px 8px;
|
||
border-bottom: 1px solid rgba(17,37,63,0.06);
|
||
text-align: left;
|
||
color: #3f5477;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.codex-hcpos-table th {
|
||
color: #8b9ab3;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.codex-hcpos-alerts {
|
||
display: grid;
|
||
gap: 10px;
|
||
}
|
||
|
||
.codex-hcpos-alert {
|
||
padding: 14px 14px 13px;
|
||
border-radius: 14px;
|
||
background: #f8fbff;
|
||
border: 1px solid rgba(17,37,63,0.06);
|
||
}
|
||
|
||
.codex-hcpos-alert b {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
margin-bottom: 6px;
|
||
color: #152742;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.codex-hcpos-alert p {
|
||
margin: 0;
|
||
color: #6d7d97;
|
||
font-size: 12px;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.codex-hcpos-level {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 4px 8px;
|
||
border-radius: 999px;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.codex-hcpos-level-正常 {
|
||
color: #067647;
|
||
background: rgba(6, 118, 71, 0.12);
|
||
}
|
||
|
||
.codex-hcpos-level-关注 {
|
||
color: #975a16;
|
||
background: rgba(255, 183, 77, 0.18);
|
||
}
|
||
|
||
.codex-hcpos-level-预警 {
|
||
color: #c92a2a;
|
||
background: rgba(255, 107, 107, 0.16);
|
||
}
|
||
|
||
@media (max-width: 1080px) {
|
||
.codex-hcpos-metrics {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
|
||
.codex-hcpos-layout {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
`;
|
||
doc.head.appendChild(style);
|
||
}
|
||
|
||
const host =
|
||
doc.querySelector(".app-main > div") ||
|
||
doc.querySelector(".app-main > section") ||
|
||
doc.querySelector(".app-main") ||
|
||
doc.body;
|
||
if (!host) {
|
||
return;
|
||
}
|
||
|
||
if (type === "financeMicrobrain" || type === "energyNotice") {
|
||
[...host.children].forEach((child) => {
|
||
if (child.id !== "__codex_hcpos_dashboard__") {
|
||
child.style.display = "none";
|
||
}
|
||
});
|
||
}
|
||
|
||
let container = doc.getElementById("__codex_hcpos_dashboard__");
|
||
if (!container) {
|
||
container = doc.createElement("div");
|
||
container.id = "__codex_hcpos_dashboard__";
|
||
host.insertBefore(container, host.firstChild);
|
||
}
|
||
|
||
const legend = preset.trendSeries
|
||
.map(
|
||
(item) => `
|
||
<span><i class="codex-hcpos-dot" style="background:${item.color}"></i>${item.label}</span>
|
||
`
|
||
)
|
||
.join("");
|
||
|
||
const segments = preset.segments
|
||
.map(
|
||
(item) => `
|
||
<div class="codex-hcpos-segment">
|
||
<span>${item.label}</span>
|
||
<div class="codex-hcpos-track"><div class="codex-hcpos-fill" style="width:${item.value}; background:${item.tone}"></div></div>
|
||
<strong>${item.value}</strong>
|
||
</div>
|
||
`
|
||
)
|
||
.join("");
|
||
|
||
const metrics = preset.metrics
|
||
.map(
|
||
(item) => `
|
||
<div class="codex-hcpos-metric" style="box-shadow: 0 12px 28px ${preset.accentSoft}">
|
||
<span>${item.label}</span>
|
||
<strong>${item.value}</strong>
|
||
<small>${item.delta}</small>
|
||
</div>
|
||
`
|
||
)
|
||
.join("");
|
||
|
||
const rows = preset.rankingRows
|
||
.map(
|
||
(row, index) => `
|
||
<tr>
|
||
<td>${index + 1}</td>
|
||
<td>${row[0]}</td>
|
||
<td>${row[1]}</td>
|
||
<td>${row[2]}</td>
|
||
</tr>
|
||
`
|
||
)
|
||
.join("");
|
||
|
||
const alerts = preset.alerts
|
||
.map(
|
||
(item) => `
|
||
<div class="codex-hcpos-alert">
|
||
<b>
|
||
<span>${item.title}</span>
|
||
<span class="codex-hcpos-level codex-hcpos-level-${item.level}">${item.level}</span>
|
||
</b>
|
||
<p>${item.detail}</p>
|
||
</div>
|
||
`
|
||
)
|
||
.join("");
|
||
|
||
container.innerHTML = `
|
||
<div class="codex-hcpos-head">
|
||
<div>
|
||
<span class="codex-hcpos-eyebrow">${preset.eyebrow}</span>
|
||
<h3>${preset.headline}</h3>
|
||
<p>${preset.summary}</p>
|
||
</div>
|
||
<div class="codex-hcpos-badge">镜像重建 · ${preset.headline} · 近 30 天静态样本</div>
|
||
</div>
|
||
<div class="codex-hcpos-metrics">${metrics}</div>
|
||
<div class="codex-hcpos-layout">
|
||
<div class="codex-hcpos-panel">
|
||
<h4>趋势概览</h4>
|
||
<div class="codex-hcpos-legend">${legend}</div>
|
||
${buildTrendSvg(preset.trendSeries, preset.trendLabels)}
|
||
</div>
|
||
<div class="codex-hcpos-panel">
|
||
<h4>结构占比</h4>
|
||
<div class="codex-hcpos-segments">${segments}</div>
|
||
</div>
|
||
<div class="codex-hcpos-panel codex-hcpos-table">
|
||
<h4>${preset.rankingTitle}</h4>
|
||
<table>
|
||
<thead>
|
||
<tr><th>#</th><th>项目</th><th>指标值</th><th>完成度</th></tr>
|
||
</thead>
|
||
<tbody>${rows}</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="codex-hcpos-panel">
|
||
<h4>${preset.alertsTitle}</h4>
|
||
<div class="codex-hcpos-alerts">${alerts}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function ensureHcPosStaticPageStyle(doc) {
|
||
if (doc.getElementById("__codex_hcpos_static_style__")) {
|
||
return;
|
||
}
|
||
const style = doc.createElement("style");
|
||
style.id = "__codex_hcpos_static_style__";
|
||
style.textContent = `
|
||
.waitMain {
|
||
display: none !important;
|
||
}
|
||
|
||
.codex-hcpos-static {
|
||
margin: 20px;
|
||
padding: 18px 20px 20px;
|
||
border-radius: 18px;
|
||
background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
|
||
border: 1px solid rgba(17,37,63,0.06);
|
||
box-shadow: 0 18px 42px rgba(17,37,63,0.08);
|
||
}
|
||
|
||
.codex-hcpos-static-head {
|
||
display: flex;
|
||
align-items: flex-end;
|
||
justify-content: space-between;
|
||
gap: 20px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.codex-hcpos-static-eyebrow {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 4px 10px;
|
||
margin-bottom: 8px;
|
||
border-radius: 999px;
|
||
background: rgba(17,37,63,0.06);
|
||
color: #6d7d97;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.codex-hcpos-static-head h4 {
|
||
margin: 0 0 4px;
|
||
color: #152742;
|
||
font-size: 24px;
|
||
}
|
||
|
||
.codex-hcpos-static-head p {
|
||
margin: 0;
|
||
color: #6d7d97;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.codex-hcpos-static-badge {
|
||
padding: 9px 12px;
|
||
border-radius: 14px;
|
||
color: #496385;
|
||
font-size: 12px;
|
||
background: rgba(255,255,255,0.9);
|
||
border: 1px solid rgba(17,37,63,0.08);
|
||
}
|
||
|
||
.codex-hcpos-static-cards {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
gap: 12px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.codex-hcpos-static-card {
|
||
padding: 14px 16px;
|
||
border-radius: 16px;
|
||
background: rgba(255,255,255,0.92);
|
||
border: 1px solid rgba(17,37,63,0.05);
|
||
}
|
||
|
||
.codex-hcpos-static-card span {
|
||
display: block;
|
||
color: #7d8ca5;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.codex-hcpos-static-card strong {
|
||
display: block;
|
||
margin: 8px 0 6px;
|
||
color: #152742;
|
||
font-size: 28px;
|
||
line-height: 1.05;
|
||
}
|
||
|
||
.codex-hcpos-static-card small {
|
||
color: #4f6a94;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.codex-hcpos-static-table {
|
||
overflow: auto;
|
||
border-radius: 14px;
|
||
border: 1px solid rgba(17,37,63,0.06);
|
||
background: #fff;
|
||
}
|
||
|
||
.codex-hcpos-static-table table {
|
||
width: 100%;
|
||
min-width: 960px;
|
||
border-collapse: collapse;
|
||
}
|
||
|
||
.codex-hcpos-static-table th,
|
||
.codex-hcpos-static-table td {
|
||
padding: 12px 10px;
|
||
border-bottom: 1px solid rgba(17,37,63,0.06);
|
||
text-align: center;
|
||
color: #3f5477;
|
||
font-size: 13px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.codex-hcpos-static-table th {
|
||
background: #f9fbff;
|
||
color: #7d8ca5;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.codex-hcpos-static-table td:first-child,
|
||
.codex-hcpos-static-table th:first-child {
|
||
text-align: left;
|
||
}
|
||
|
||
@media (max-width: 1080px) {
|
||
.codex-hcpos-static-cards {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
|
||
.codex-hcpos-overview-cards {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
}
|
||
|
||
.codex-hcpos-overview {
|
||
margin: 18px 0 16px;
|
||
padding: 18px 20px;
|
||
border-radius: 18px;
|
||
background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
|
||
border: 1px solid rgba(17,37,63,0.06);
|
||
box-shadow: 0 18px 42px rgba(17,37,63,0.08);
|
||
}
|
||
|
||
.codex-hcpos-overview-head h4 {
|
||
margin: 0 0 4px;
|
||
color: #152742;
|
||
font-size: 22px;
|
||
}
|
||
|
||
.codex-hcpos-overview-head p {
|
||
margin: 0 0 14px;
|
||
color: #6d7d97;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.codex-hcpos-overview-eyebrow {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 4px 10px;
|
||
margin-bottom: 8px;
|
||
border-radius: 999px;
|
||
background: rgba(17,37,63,0.06);
|
||
color: #6d7d97;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.codex-hcpos-overview-cards {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
gap: 12px;
|
||
}
|
||
|
||
.codex-hcpos-overview-card {
|
||
padding: 14px 16px;
|
||
border-radius: 16px;
|
||
background: rgba(255,255,255,0.92);
|
||
border: 1px solid rgba(17,37,63,0.05);
|
||
box-shadow: 0 12px 24px rgba(17,37,63,0.06);
|
||
}
|
||
|
||
.codex-hcpos-overview-card span {
|
||
display: block;
|
||
color: #7d8ca5;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.codex-hcpos-overview-card strong {
|
||
display: block;
|
||
margin: 8px 0 6px;
|
||
color: #152742;
|
||
font-size: 28px;
|
||
line-height: 1.05;
|
||
}
|
||
|
||
.codex-hcpos-overview-card small {
|
||
color: #4f6a94;
|
||
font-size: 12px;
|
||
}
|
||
`;
|
||
doc.head.appendChild(style);
|
||
}
|
||
|
||
function renderHcPosStaticPage(doc, presetKey) {
|
||
const preset =
|
||
HCPOS_STATIC_PAGE_PRESETS[presetKey] ||
|
||
HCPOS_FINANCE_REPORT_PRESETS[presetKey] ||
|
||
HCPOS_WAIT_PAGE_PRESETS[presetKey];
|
||
if (!preset) {
|
||
return;
|
||
}
|
||
ensureHcPosStaticPageStyle(doc);
|
||
doc.querySelectorAll(".waitMain").forEach((node) => {
|
||
node.style.display = "none";
|
||
});
|
||
const activePane = [...doc.querySelectorAll(".el-tab-pane")].find((pane) => {
|
||
const styleText = pane.getAttribute("style") || "";
|
||
return pane.getAttribute("aria-hidden") !== "true" && !/display\s*:\s*none/i.test(styleText);
|
||
});
|
||
const waitPane = [...doc.querySelectorAll(".waitMain")]
|
||
.map((node) => node.closest(".el-tab-pane"))
|
||
.find(Boolean);
|
||
const host =
|
||
activePane ||
|
||
waitPane ||
|
||
doc.querySelector(".app-main > div") ||
|
||
doc.querySelector(".app-main > section") ||
|
||
doc.querySelector(".app-main") ||
|
||
doc.body;
|
||
if (!host) {
|
||
return;
|
||
}
|
||
const existing = doc.getElementById(`__codex_hcpos_static_${presetKey}__`);
|
||
if (existing) {
|
||
existing.remove();
|
||
}
|
||
const section = doc.createElement("section");
|
||
section.id = `__codex_hcpos_static_${presetKey}__`;
|
||
section.className = "codex-hcpos-static";
|
||
|
||
const cards = preset.cards
|
||
.map(
|
||
(item) => `
|
||
<div class="codex-hcpos-static-card" style="box-shadow: 0 12px 24px ${preset.accentSoft}">
|
||
<span>${item.label}</span>
|
||
<strong>${item.value}</strong>
|
||
<small>${item.detail}</small>
|
||
</div>
|
||
`
|
||
)
|
||
.join("");
|
||
|
||
const headers = preset.headers.map((header) => `<th>${header}</th>`).join("");
|
||
const rows = preset.rows
|
||
.map((row) => `<tr>${row.map((value) => `<td>${value}</td>`).join("")}</tr>`)
|
||
.join("");
|
||
|
||
section.innerHTML = `
|
||
<div class="codex-hcpos-static-head">
|
||
<div>
|
||
<span class="codex-hcpos-static-eyebrow">${preset.eyebrow}</span>
|
||
<h4>${preset.title}</h4>
|
||
<p>${preset.summary}</p>
|
||
</div>
|
||
<div class="codex-hcpos-static-badge">镜像重建 · 静态样本数据</div>
|
||
</div>
|
||
<div class="codex-hcpos-static-cards">${cards}</div>
|
||
<div class="codex-hcpos-static-table">
|
||
<table>
|
||
<thead><tr>${headers}</tr></thead>
|
||
<tbody>${rows}</tbody>
|
||
</table>
|
||
</div>
|
||
`;
|
||
host.appendChild(section);
|
||
}
|
||
|
||
function renderHcPosOverviewPanel(host, preset) {
|
||
if (!host || !preset) {
|
||
return;
|
||
}
|
||
ensureHcPosStaticPageStyle(host.ownerDocument);
|
||
let panel = host.querySelector(".codex-hcpos-overview");
|
||
if (!panel) {
|
||
panel = host.ownerDocument.createElement("section");
|
||
panel.className = "codex-hcpos-overview";
|
||
const search = host.querySelector(".searchStys");
|
||
const searchParent = search && search.parentNode;
|
||
if (search && searchParent && search.nextSibling) {
|
||
searchParent.insertBefore(panel, search.nextSibling);
|
||
} else if (search && searchParent) {
|
||
searchParent.appendChild(panel);
|
||
} else {
|
||
host.insertBefore(panel, host.firstChild);
|
||
}
|
||
}
|
||
const cards = preset.cards
|
||
.map(
|
||
(item) => `
|
||
<div class="codex-hcpos-overview-card">
|
||
<span>${item.label}</span>
|
||
<strong>${item.value}</strong>
|
||
<small>${item.detail}</small>
|
||
</div>
|
||
`
|
||
)
|
||
.join("");
|
||
panel.innerHTML = `
|
||
<div class="codex-hcpos-overview-head">
|
||
<div>
|
||
<span class="codex-hcpos-overview-eyebrow">${preset.eyebrow}</span>
|
||
<h4>${preset.title}</h4>
|
||
<p>${preset.summary}</p>
|
||
</div>
|
||
</div>
|
||
<div class="codex-hcpos-overview-cards">${cards}</div>
|
||
`;
|
||
}
|
||
|
||
function renderHcPosSecondaryTab(doc, presetKey, paneId) {
|
||
const preset = HCPOS_SECONDARY_TAB_PRESETS[presetKey];
|
||
const pane = doc.getElementById(paneId);
|
||
if (!preset || !pane) {
|
||
return;
|
||
}
|
||
ensureHcPosStaticPageStyle(doc);
|
||
const existing = pane.querySelector(".codex-hcpos-static");
|
||
if (existing) {
|
||
existing.remove();
|
||
}
|
||
pane.querySelectorAll(".waitMain").forEach((node) => {
|
||
node.style.display = "none";
|
||
});
|
||
const section = doc.createElement("section");
|
||
section.className = "codex-hcpos-static";
|
||
|
||
const cards = preset.cards
|
||
.map(
|
||
(item) => `
|
||
<div class="codex-hcpos-static-card" style="box-shadow: 0 12px 24px rgba(45,118,255,0.12)">
|
||
<span>${item.label}</span>
|
||
<strong>${item.value}</strong>
|
||
<small>${item.detail}</small>
|
||
</div>
|
||
`
|
||
)
|
||
.join("");
|
||
|
||
const headers = preset.headers.map((header) => `<th>${header}</th>`).join("");
|
||
const rows = preset.rows
|
||
.map((row) => `<tr>${row.map((value) => `<td>${value}</td>`).join("")}</tr>`)
|
||
.join("");
|
||
|
||
section.innerHTML = `
|
||
<div class="codex-hcpos-static-head">
|
||
<div>
|
||
<span class="codex-hcpos-static-eyebrow">${preset.eyebrow}</span>
|
||
<h4>${preset.title}</h4>
|
||
<p>${preset.summary}</p>
|
||
</div>
|
||
<div class="codex-hcpos-static-badge">镜像重建 · 静态样本数据</div>
|
||
</div>
|
||
<div class="codex-hcpos-static-cards">${cards}</div>
|
||
<div class="codex-hcpos-static-table">
|
||
<table>
|
||
<thead><tr>${headers}</tr></thead>
|
||
<tbody>${rows}</tbody>
|
||
</table>
|
||
</div>
|
||
`;
|
||
pane.appendChild(section);
|
||
}
|
||
|
||
function installSimpleTabSwitch(doc, config) {
|
||
const { primaryTabId, secondaryTabId, primaryPaneId, secondaryPaneId, storageKey } = config;
|
||
const primaryTab = doc.getElementById(primaryTabId);
|
||
const secondaryTab = doc.getElementById(secondaryTabId);
|
||
const primaryPane = doc.getElementById(primaryPaneId);
|
||
const secondaryPane = doc.getElementById(secondaryPaneId);
|
||
if (!primaryTab || !secondaryTab || !primaryPane || !secondaryPane) {
|
||
return;
|
||
}
|
||
if (secondaryTab.dataset.codexSwitchInstalled === "1") {
|
||
return;
|
||
}
|
||
secondaryTab.dataset.codexSwitchInstalled = "1";
|
||
|
||
function syncActiveBar(target) {
|
||
const bar = target.closest(".el-tabs")?.querySelector(".el-tabs__active-bar");
|
||
if (!bar) {
|
||
return;
|
||
}
|
||
bar.style.width = `${target.offsetWidth}px`;
|
||
bar.style.transform = `translateX(${target.offsetLeft}px)`;
|
||
}
|
||
|
||
function activateTab(target) {
|
||
const secondaryActive = target === secondaryTab;
|
||
primaryTab.classList.toggle("is-active", !secondaryActive);
|
||
secondaryTab.classList.toggle("is-active", secondaryActive);
|
||
primaryTab.setAttribute("aria-selected", String(!secondaryActive));
|
||
secondaryTab.setAttribute("aria-selected", String(secondaryActive));
|
||
primaryTab.setAttribute("tabindex", secondaryActive ? "-1" : "0");
|
||
secondaryTab.setAttribute("tabindex", secondaryActive ? "0" : "-1");
|
||
primaryPane.style.display = secondaryActive ? "none" : "block";
|
||
secondaryPane.style.display = secondaryActive ? "block" : "none";
|
||
primaryPane.setAttribute("aria-hidden", secondaryActive ? "true" : "false");
|
||
secondaryPane.setAttribute("aria-hidden", secondaryActive ? "false" : "true");
|
||
syncActiveBar(target);
|
||
if (storageKey) {
|
||
secondaryPane.ownerDocument.defaultView?.sessionStorage.setItem(storageKey, secondaryActive ? "secondary" : "primary");
|
||
}
|
||
}
|
||
|
||
primaryTab.addEventListener("click", () => activateTab(primaryTab), true);
|
||
secondaryTab.addEventListener("click", () => activateTab(secondaryTab), true);
|
||
|
||
const remembered =
|
||
storageKey && secondaryPane.ownerDocument.defaultView?.sessionStorage.getItem(storageKey) === "secondary"
|
||
? secondaryTab
|
||
: primaryTab;
|
||
activateTab(remembered);
|
||
}
|
||
|
||
function ensureHcPosEditableStyle(doc) {
|
||
if (doc.getElementById("__codex_hcpos_editable_style__")) {
|
||
return;
|
||
}
|
||
const style = doc.createElement("style");
|
||
style.id = "__codex_hcpos_editable_style__";
|
||
style.textContent = `
|
||
.codex-editable-shell {
|
||
position: relative;
|
||
}
|
||
.codex-editable-toolbar {
|
||
position: sticky;
|
||
top: 12px;
|
||
z-index: 20;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 8px;
|
||
margin: 0 0 12px;
|
||
}
|
||
.codex-editable-toolbar button {
|
||
border: 1px solid rgba(17,37,63,0.08);
|
||
border-radius: 999px;
|
||
padding: 6px 12px;
|
||
background: rgba(255,255,255,0.92);
|
||
color: #35507a;
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
}
|
||
.codex-editable-toolbar button.codex-primary {
|
||
background: #2d76ff;
|
||
border-color: transparent;
|
||
color: #fff;
|
||
}
|
||
.codex-editable-toolbar button.codex-danger {
|
||
color: #d14343;
|
||
}
|
||
.codex-editable-toolbar button:disabled {
|
||
opacity: 0.48;
|
||
cursor: not-allowed;
|
||
}
|
||
.codex-editable-body[contenteditable="true"] {
|
||
outline: 2px dashed rgba(45,118,255,0.35);
|
||
outline-offset: 8px;
|
||
}
|
||
.codex-editable-body[contenteditable="true"] a,
|
||
.codex-editable-body[contenteditable="true"] button {
|
||
pointer-events: none;
|
||
}
|
||
.codex-editable-hint {
|
||
margin-right: auto;
|
||
align-self: center;
|
||
color: #6d7d97;
|
||
font-size: 12px;
|
||
}
|
||
`;
|
||
doc.head.appendChild(style);
|
||
}
|
||
|
||
function createHcPosEditableKey(doc, section, index) {
|
||
const pathname = doc.defaultView?.location?.pathname || "unknown";
|
||
const paneId = section.closest(".el-tab-pane")?.id || "";
|
||
const sectionId = section.id || section.dataset.type || section.className || `section-${index}`;
|
||
return `__codex_hcpos_edit__${pathname}::${paneId}::${sectionId}`;
|
||
}
|
||
|
||
function wrapHcPosEditableBody(section) {
|
||
let body = section.querySelector(":scope > .codex-editable-body");
|
||
if (body) {
|
||
return body;
|
||
}
|
||
body = section.ownerDocument.createElement("div");
|
||
body.className = "codex-editable-body";
|
||
const nodes = [...section.childNodes];
|
||
nodes.forEach((node) => {
|
||
body.appendChild(node);
|
||
});
|
||
section.appendChild(body);
|
||
return body;
|
||
}
|
||
|
||
function setHcPosEditableState(doc, section, editing) {
|
||
const body = section.querySelector(":scope > .codex-editable-body");
|
||
const toolbar = section.querySelector(":scope > .codex-editable-toolbar");
|
||
if (!body || !toolbar) {
|
||
return;
|
||
}
|
||
body.contentEditable = editing ? "true" : "false";
|
||
body.spellcheck = false;
|
||
section.dataset.codexEditing = editing ? "1" : "0";
|
||
doc.body.dataset.codexMirrorEditing = editing ? "1" : "0";
|
||
toolbar.querySelector("[data-action='edit']").disabled = editing;
|
||
toolbar.querySelector("[data-action='save']").disabled = !editing;
|
||
}
|
||
|
||
function bindHcPosEditableSection(doc, section, index) {
|
||
ensureHcPosEditableStyle(doc);
|
||
section.classList.add("codex-editable-shell");
|
||
const body = wrapHcPosEditableBody(section);
|
||
const storageKey = createHcPosEditableKey(doc, section, index);
|
||
const win = doc.defaultView;
|
||
const saved = win?.localStorage.getItem(storageKey);
|
||
if (!section.dataset.codexDefaultHtml) {
|
||
section.dataset.codexDefaultHtml = body.innerHTML;
|
||
}
|
||
if (saved && body.innerHTML !== saved && section.dataset.codexEditing !== "1") {
|
||
body.innerHTML = saved;
|
||
}
|
||
let toolbar = section.querySelector(":scope > .codex-editable-toolbar");
|
||
if (!toolbar) {
|
||
toolbar = doc.createElement("div");
|
||
toolbar.className = "codex-editable-toolbar";
|
||
toolbar.innerHTML = `
|
||
<span class="codex-editable-hint">本地编辑</span>
|
||
<button type="button" data-action="edit" class="codex-primary">编辑</button>
|
||
<button type="button" data-action="save" disabled>保存</button>
|
||
<button type="button" data-action="reset" class="codex-danger">重置</button>
|
||
`;
|
||
section.insertBefore(toolbar, body);
|
||
|
||
toolbar.addEventListener("click", (event) => {
|
||
const button = event.target.closest("button[data-action]");
|
||
if (!button) {
|
||
return;
|
||
}
|
||
const action = button.dataset.action;
|
||
if (action === "edit") {
|
||
setHcPosEditableState(doc, section, true);
|
||
body.focus();
|
||
return;
|
||
}
|
||
if (action === "save") {
|
||
win?.localStorage.setItem(storageKey, body.innerHTML);
|
||
setHcPosEditableState(doc, section, false);
|
||
return;
|
||
}
|
||
if (action === "reset") {
|
||
win?.localStorage.removeItem(storageKey);
|
||
body.innerHTML = section.dataset.codexDefaultHtml || body.innerHTML;
|
||
setHcPosEditableState(doc, section, false);
|
||
}
|
||
});
|
||
}
|
||
setHcPosEditableState(doc, section, false);
|
||
}
|
||
|
||
function installHcPosEditableSections(doc) {
|
||
const sections = [
|
||
...doc.querySelectorAll("#__codex_hcpos_dashboard__, [id^='__codex_hcpos_static_'], .codex-hcpos-overview, .el-tab-pane > .codex-hcpos-static")
|
||
];
|
||
const seen = new Set();
|
||
sections.forEach((section, index) => {
|
||
if (!section || section.tagName === "STYLE" || seen.has(section)) {
|
||
return;
|
||
}
|
||
seen.add(section);
|
||
bindHcPosEditableSection(doc, section, index);
|
||
});
|
||
}
|
||
|
||
function ensureHcPosListEditStyle(doc) {
|
||
if (doc.getElementById("__codex_hcpos_list_edit_style__")) {
|
||
return;
|
||
}
|
||
const style = doc.createElement("style");
|
||
style.id = "__codex_hcpos_list_edit_style__";
|
||
style.textContent = `
|
||
.codex-listedit-mask {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 99999;
|
||
background: rgba(8, 15, 28, 0.42);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 24px;
|
||
}
|
||
.codex-listedit-modal {
|
||
width: min(760px, 100%);
|
||
max-height: min(760px, calc(100vh - 48px));
|
||
overflow: auto;
|
||
border-radius: 18px;
|
||
background: #fff;
|
||
box-shadow: 0 24px 54px rgba(17, 37, 63, 0.18);
|
||
padding: 20px 22px;
|
||
}
|
||
.codex-listedit-modal h4 {
|
||
margin: 0 0 16px;
|
||
color: #152742;
|
||
font-size: 20px;
|
||
}
|
||
.codex-listedit-summary {
|
||
margin: 0 0 16px;
|
||
color: #6d7d97;
|
||
font-size: 12px;
|
||
line-height: 1.7;
|
||
}
|
||
.codex-listedit-form {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 12px;
|
||
}
|
||
.codex-listedit-form label {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
color: #6d7d97;
|
||
font-size: 12px;
|
||
}
|
||
.codex-listedit-form label.full {
|
||
grid-column: 1 / -1;
|
||
}
|
||
.codex-listedit-form input,
|
||
.codex-listedit-form textarea {
|
||
border: 1px solid rgba(17, 37, 63, 0.12);
|
||
border-radius: 10px;
|
||
padding: 10px 12px;
|
||
color: #152742;
|
||
font-size: 13px;
|
||
background: #fff;
|
||
}
|
||
.codex-listedit-form textarea {
|
||
min-height: 96px;
|
||
resize: vertical;
|
||
}
|
||
.codex-listedit-actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 10px;
|
||
margin-top: 18px;
|
||
}
|
||
.codex-listedit-actions button {
|
||
border: 1px solid rgba(17, 37, 63, 0.08);
|
||
border-radius: 999px;
|
||
padding: 6px 12px;
|
||
background: #fff;
|
||
color: #35507a;
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
}
|
||
.codex-listedit-actions button.codex-primary {
|
||
background: #2d76ff;
|
||
border-color: transparent;
|
||
color: #fff;
|
||
}
|
||
.codex-listedit-actions button.codex-danger {
|
||
color: #d14343;
|
||
}
|
||
.codex-listedit-fallback-panel {
|
||
margin: 16px 0;
|
||
padding: 14px 16px;
|
||
border-radius: 14px;
|
||
border: 1px solid rgba(17, 37, 63, 0.06);
|
||
background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
|
||
box-shadow: 0 12px 28px rgba(17, 37, 63, 0.08);
|
||
}
|
||
.codex-listedit-fallback-panel h5 {
|
||
margin: 0 0 10px;
|
||
color: #152742;
|
||
font-size: 15px;
|
||
}
|
||
.codex-listedit-fallback-panel p {
|
||
margin: 0 0 12px;
|
||
color: #6d7d97;
|
||
font-size: 12px;
|
||
line-height: 1.7;
|
||
}
|
||
.codex-listedit-fallback-list {
|
||
display: grid;
|
||
gap: 8px;
|
||
}
|
||
.codex-listedit-fallback-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
padding: 10px 12px;
|
||
border-radius: 10px;
|
||
background: rgba(247, 250, 255, 0.92);
|
||
border: 1px solid rgba(17, 37, 63, 0.06);
|
||
}
|
||
.codex-listedit-fallback-item span {
|
||
flex: 1;
|
||
color: #35507a;
|
||
font-size: 12px;
|
||
line-height: 1.6;
|
||
}
|
||
`;
|
||
doc.head.appendChild(style);
|
||
}
|
||
|
||
function createHcPosListEditStorageKey(doc) {
|
||
const pathname = doc.defaultView?.location?.pathname || "unknown";
|
||
return `__codex_hcpos_list_edit__${pathname}`;
|
||
}
|
||
|
||
function readHcPosListEditState(doc) {
|
||
try {
|
||
return JSON.parse(doc.defaultView?.localStorage.getItem(createHcPosListEditStorageKey(doc)) || "{}");
|
||
} catch (_error) {
|
||
return {};
|
||
}
|
||
}
|
||
|
||
function writeHcPosListEditState(doc, state) {
|
||
try {
|
||
doc.defaultView?.localStorage.setItem(createHcPosListEditStorageKey(doc), JSON.stringify(state));
|
||
} catch (_error) {
|
||
// ignore
|
||
}
|
||
}
|
||
|
||
function getHcPosVisibleText(node) {
|
||
return (node?.textContent || "").replace(/\s+/g, " ").trim();
|
||
}
|
||
|
||
function isHcPosElementVisible(node) {
|
||
if (!node) {
|
||
return false;
|
||
}
|
||
const style = node.ownerDocument?.defaultView?.getComputedStyle(node);
|
||
const rect = node.getBoundingClientRect?.();
|
||
if (!style || !rect) {
|
||
return false;
|
||
}
|
||
return style.display !== "none" && style.visibility !== "hidden" && rect.width > 0 && rect.height > 0;
|
||
}
|
||
|
||
function getHcPosListEditTableKey(doc, table) {
|
||
const tableRoot = table.closest(".el-table");
|
||
if (tableRoot) {
|
||
const roots = [...doc.querySelectorAll(".el-table")];
|
||
return `table-root-${roots.indexOf(tableRoot)}`;
|
||
}
|
||
const tables = [...doc.querySelectorAll("table")];
|
||
return `table-${tables.indexOf(table)}`;
|
||
}
|
||
|
||
function isHcPosActionOnlyText(text) {
|
||
return /^(编辑|删除|查看|复制角色|复制|下载|导出|导入|开具发票|手动开收据|下载收据|生成票据|催费|拆分|禁用|启用)(\s+(编辑|删除|查看|复制角色|复制|下载|导出|导入|开具发票|手动开收据|下载收据|生成票据|催费|拆分|禁用|启用))*$/.test(text);
|
||
}
|
||
|
||
function countHcPosVisibleCells(row) {
|
||
return [...row.querySelectorAll(":scope > td")].filter((cell) => isHcPosElementVisible(cell)).length;
|
||
}
|
||
|
||
function resolveHcPosPrimaryRow(row) {
|
||
const tableRoot = row?.closest(".el-table");
|
||
const rowIndex = [...(row?.parentElement?.children || [])].indexOf(row);
|
||
if (!tableRoot || rowIndex < 0) {
|
||
return row;
|
||
}
|
||
const candidates = [];
|
||
[...tableRoot.querySelectorAll("tbody")].forEach((tbody) => {
|
||
const candidate = tbody.children[rowIndex];
|
||
if (candidate && !candidates.includes(candidate)) {
|
||
candidates.push(candidate);
|
||
}
|
||
});
|
||
candidates.sort((left, right) => countHcPosVisibleCells(right) - countHcPosVisibleCells(left));
|
||
return candidates[0] || row;
|
||
}
|
||
|
||
function extractHcPosListEditHeaders(table, cellCount) {
|
||
const headers = [...table.querySelectorAll("thead th")]
|
||
.map((cell) => getHcPosVisibleText(cell))
|
||
.filter(Boolean);
|
||
if (!headers.length) {
|
||
return Array.from({ length: cellCount }, (_value, index) => `字段${index + 1}`);
|
||
}
|
||
return headers;
|
||
}
|
||
|
||
function extractHcPosListEditContext(doc, row) {
|
||
const table = row?.closest("table");
|
||
if (!table) {
|
||
return null;
|
||
}
|
||
const rows = [...(row.parentElement?.children || [])];
|
||
const rowIndex = rows.indexOf(row);
|
||
const cells = [...row.querySelectorAll(":scope > td")];
|
||
const headers = extractHcPosListEditHeaders(table, cells.length);
|
||
const fields = [];
|
||
cells.forEach((cell, index) => {
|
||
const header = headers[index] || `字段${index + 1}`;
|
||
const value = getHcPosVisibleText(cell);
|
||
if (!value || /操作/.test(header) || isHcPosActionOnlyText(value)) {
|
||
return;
|
||
}
|
||
fields.push({ header, value });
|
||
});
|
||
if (!fields.length) {
|
||
return null;
|
||
}
|
||
return {
|
||
tableKey: getHcPosListEditTableKey(doc, table),
|
||
rowKey: `row-${rowIndex}`,
|
||
fields
|
||
};
|
||
}
|
||
|
||
function resolveHcPosListEditContext(doc, trigger) {
|
||
const sourceRow = trigger?.closest("tr");
|
||
if (!sourceRow) {
|
||
return null;
|
||
}
|
||
const direct = extractHcPosListEditContext(doc, sourceRow);
|
||
if (direct) {
|
||
return {
|
||
row: sourceRow,
|
||
context: direct
|
||
};
|
||
}
|
||
const rowIndex = [...(sourceRow.parentElement?.children || [])].indexOf(sourceRow);
|
||
const tableRoot = sourceRow.closest(".el-table");
|
||
if (!tableRoot || rowIndex < 0) {
|
||
return null;
|
||
}
|
||
const candidateSelectors = [
|
||
".el-table__body-wrapper tbody tr",
|
||
".el-table__fixed-body-wrapper tbody tr",
|
||
".el-table__fixed-right .el-table__fixed-body-wrapper tbody tr"
|
||
];
|
||
for (const selector of candidateSelectors) {
|
||
const rows = [...tableRoot.querySelectorAll(selector)];
|
||
const row = rows[rowIndex];
|
||
if (!row || row === sourceRow) {
|
||
continue;
|
||
}
|
||
const context = extractHcPosListEditContext(doc, row);
|
||
if (context) {
|
||
return {
|
||
row,
|
||
context
|
||
};
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function setHcPosListEditCellValue(cell, value) {
|
||
const target =
|
||
cell.querySelector(":scope > .cell") ||
|
||
(cell.children.length === 1 ? cell.children[0] : cell);
|
||
if (!target) {
|
||
return;
|
||
}
|
||
target.textContent = value;
|
||
}
|
||
|
||
function applyHcPosListEditValues(row, fields) {
|
||
const table = row?.closest("table");
|
||
if (!table) {
|
||
return;
|
||
}
|
||
const cells = [...row.querySelectorAll(":scope > td")];
|
||
const headers = extractHcPosListEditHeaders(table, cells.length);
|
||
let fieldIndex = 0;
|
||
cells.forEach((cell, index) => {
|
||
const header = headers[index] || `字段${index + 1}`;
|
||
const value = getHcPosVisibleText(cell);
|
||
if (!value || /操作/.test(header) || isHcPosActionOnlyText(value)) {
|
||
return;
|
||
}
|
||
const nextField = fields[fieldIndex];
|
||
if (!nextField) {
|
||
return;
|
||
}
|
||
setHcPosListEditCellValue(cell, nextField.value);
|
||
fieldIndex += 1;
|
||
});
|
||
}
|
||
|
||
function applyHcPosListEditStateToTable(doc, tableKey, rowKey, fields) {
|
||
const rowIndex = Number(String(rowKey || "").replace("row-", ""));
|
||
if (Number.isNaN(rowIndex)) {
|
||
return;
|
||
}
|
||
if (String(tableKey).startsWith("table-root-")) {
|
||
const rootIndex = Number(String(tableKey).replace("table-root-", ""));
|
||
const tableRoot = [...doc.querySelectorAll(".el-table")][rootIndex];
|
||
if (!tableRoot) {
|
||
return;
|
||
}
|
||
const seen = new Set();
|
||
[
|
||
".el-table__body-wrapper tbody tr",
|
||
".el-table__fixed-body-wrapper tbody tr",
|
||
".el-table__fixed-right .el-table__fixed-body-wrapper tbody tr"
|
||
].forEach((selector) => {
|
||
const row = [...tableRoot.querySelectorAll(selector)][rowIndex];
|
||
if (!row || seen.has(row)) {
|
||
return;
|
||
}
|
||
seen.add(row);
|
||
applyHcPosListEditValues(row, fields);
|
||
});
|
||
return;
|
||
}
|
||
const tableIndex = Number(String(tableKey || "").replace("table-", ""));
|
||
const table = [...doc.querySelectorAll("table")][tableIndex];
|
||
const row = table ? [...table.querySelectorAll("tbody tr")][rowIndex] : null;
|
||
if (row) {
|
||
applyHcPosListEditValues(row, fields);
|
||
}
|
||
}
|
||
|
||
function hydrateHcPosListEditRows(doc) {
|
||
const state = readHcPosListEditState(doc);
|
||
Object.entries(state).forEach(([tableKey, tableState]) => {
|
||
Object.entries(tableState || {}).forEach(([rowKey, rowState]) => {
|
||
if (rowState?.fields) {
|
||
applyHcPosListEditStateToTable(doc, tableKey, rowKey, rowState.fields);
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
function normalizeHcPosListEditTriggers(doc) {
|
||
[...doc.querySelectorAll("button.el-button, .textBtn, a, .el-button, .el-dropdown-menu__item")].forEach((node) => {
|
||
const text = getHcPosVisibleText(node);
|
||
if (!/编辑/.test(text) || !node.closest("tr")) {
|
||
return;
|
||
}
|
||
if ("disabled" in node) {
|
||
node.disabled = false;
|
||
}
|
||
node.removeAttribute?.("disabled");
|
||
node.classList?.remove("is-disabled");
|
||
node.closest(".is-disabled")?.classList.remove("is-disabled");
|
||
node.style.cursor = "pointer";
|
||
node.style.opacity = "1";
|
||
});
|
||
}
|
||
|
||
function injectHcPosInlineEditButtons(doc) {
|
||
[...doc.querySelectorAll("tbody tr")].forEach((row) => {
|
||
if (row.closest("[id^='__codex_'], .codex-budget-panel, .codex-editable-shell")) {
|
||
return;
|
||
}
|
||
const actionNodes = [...row.querySelectorAll("button, .textBtn, a, .el-button, .el-dropdown-menu__item")];
|
||
const hasVisibleEdit = actionNodes.some((node) => /编辑/.test(getHcPosVisibleText(node)) && isHcPosElementVisible(node));
|
||
if (hasVisibleEdit) {
|
||
return;
|
||
}
|
||
const hasHiddenEdit = actionNodes.some((node) => /编辑/.test(getHcPosVisibleText(node)));
|
||
if (!hasHiddenEdit) {
|
||
return;
|
||
}
|
||
const hostRow = resolveHcPosPrimaryRow(row);
|
||
const cells = [...hostRow.querySelectorAll(":scope > td")];
|
||
const hostCell =
|
||
cells.find((cell) => {
|
||
const text = getHcPosVisibleText(cell);
|
||
return isHcPosElementVisible(cell) && text && !isHcPosActionOnlyText(text) && text !== "#";
|
||
}) || cells[0];
|
||
if (!hostCell) {
|
||
return;
|
||
}
|
||
const host = hostCell.querySelector(":scope > .cell") || hostCell;
|
||
if (host.querySelector(":scope > .codex-inline-edit-trigger")) {
|
||
return;
|
||
}
|
||
const button = doc.createElement("button");
|
||
button.type = "button";
|
||
button.className = "el-button el-button--text el-button--mini codex-inline-edit-trigger";
|
||
button.textContent = "编辑";
|
||
button.style.marginLeft = "8px";
|
||
host.appendChild(button);
|
||
});
|
||
}
|
||
|
||
function collectHcPosHiddenEditEntries(doc) {
|
||
const entries = [];
|
||
const seen = new Set();
|
||
const currentPath = pathFromHash(doc.defaultView?.location?.hash || location.hash);
|
||
const detailEditRoutes = new Set([
|
||
"/propertySMG/cleanManage/cleanFile/cleanEquipment",
|
||
"/propertySMG/energySourceOperat/energyFile/energyArchives",
|
||
"/propertySMG/equipmentManage/equipmentFiling/equipmentArchives",
|
||
"/propertySMG/securityManage/securityFile/securityEquipment"
|
||
]);
|
||
const hasVisibleEdit = [...doc.querySelectorAll("button, .textBtn, a, .el-button, .el-dropdown-menu__item")].some(
|
||
(node) => /编辑/.test(getHcPosVisibleText(node)) && isHcPosElementVisible(node)
|
||
);
|
||
if (hasVisibleEdit) {
|
||
return entries;
|
||
}
|
||
const allowDetailAsEdit = detailEditRoutes.has(currentPath);
|
||
[...doc.querySelectorAll("tbody tr")].forEach((row) => {
|
||
if (row.closest("[id^='__codex_'], .codex-budget-panel, .codex-editable-shell")) {
|
||
return;
|
||
}
|
||
const actionNodes = [...row.querySelectorAll("button, .textBtn, a, .el-button, .el-dropdown-menu__item")];
|
||
const hasHiddenEdit = actionNodes.some((node) => /编辑/.test(getHcPosVisibleText(node)));
|
||
const hasDetailEntry = allowDetailAsEdit && actionNodes.some((node) => /详情/.test(getHcPosVisibleText(node)));
|
||
if (!hasHiddenEdit && !hasDetailEntry) {
|
||
return;
|
||
}
|
||
const primaryRow = resolveHcPosPrimaryRow(row);
|
||
const context = extractHcPosListEditContext(doc, primaryRow);
|
||
if (!context) {
|
||
return;
|
||
}
|
||
const key = `${context.tableKey}::${context.rowKey}`;
|
||
if (seen.has(key)) {
|
||
return;
|
||
}
|
||
seen.add(key);
|
||
entries.push({
|
||
row: primaryRow,
|
||
context,
|
||
summary: context.fields
|
||
.slice(0, 3)
|
||
.map((field) => `${field.header}:${field.value}`)
|
||
.join(" / ")
|
||
});
|
||
});
|
||
return entries;
|
||
}
|
||
|
||
function renderHcPosHiddenEditPanel(doc) {
|
||
const entries = collectHcPosHiddenEditEntries(doc);
|
||
const existing = doc.getElementById("__codex_hcpos_hidden_edit_panel__");
|
||
if (!entries.length) {
|
||
existing?.remove();
|
||
return;
|
||
}
|
||
const panel = existing || doc.createElement("section");
|
||
panel.id = "__codex_hcpos_hidden_edit_panel__";
|
||
panel.className = "codex-listedit-fallback-panel";
|
||
panel.innerHTML = `
|
||
<h5>本地编辑入口</h5>
|
||
<p>当前页面默认视图没有直接露出原生“编辑”按钮,以下入口由镜像补出,点击后会打开本地列表编辑弹窗。</p>
|
||
<div class="codex-listedit-fallback-list">
|
||
${entries
|
||
.slice(0, 12)
|
||
.map(
|
||
(entry, index) => `
|
||
<div class="codex-listedit-fallback-item">
|
||
<span>${entry.summary}</span>
|
||
<button type="button" class="el-button el-button--text el-button--mini" data-hidden-edit-index="${index}">编辑</button>
|
||
</div>
|
||
`
|
||
)
|
||
.join("")}
|
||
</div>
|
||
`;
|
||
if (!existing) {
|
||
const search = doc.querySelector(".searchStys");
|
||
const host = search?.parentElement || doc.querySelector(".app-main > div") || doc.body;
|
||
if (search && search.parentElement) {
|
||
search.parentElement.insertBefore(panel, search.nextSibling);
|
||
} else {
|
||
host.insertBefore(panel, host.firstChild);
|
||
}
|
||
}
|
||
panel.querySelectorAll("[data-hidden-edit-index]").forEach((button) => {
|
||
button.addEventListener("click", () => {
|
||
const entry = entries[Number(button.getAttribute("data-hidden-edit-index"))];
|
||
if (!entry) {
|
||
return;
|
||
}
|
||
openHcPosListEditModal(doc, resolveHcPosPrimaryRow(entry.row), entry.context);
|
||
});
|
||
});
|
||
}
|
||
|
||
function closeHcPosListEditModal(doc) {
|
||
doc.getElementById("__codex_hcpos_list_edit_mask__")?.remove();
|
||
}
|
||
|
||
function openHcPosListEditModal(doc, row, context) {
|
||
ensureHcPosListEditStyle(doc);
|
||
closeHcPosListEditModal(doc);
|
||
const mask = doc.createElement("div");
|
||
mask.id = "__codex_hcpos_list_edit_mask__";
|
||
mask.className = "codex-listedit-mask";
|
||
const fieldsHtml = context.fields
|
||
.map(
|
||
(field, index) => `
|
||
<label class="${String(field.value || "").length > 32 ? "full" : ""}">
|
||
<span>${field.header}</span>
|
||
<input data-field-index="${index}" value="${field.value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\"/g, """)}">
|
||
</label>
|
||
`
|
||
)
|
||
.join("");
|
||
mask.innerHTML = `
|
||
<div class="codex-listedit-modal">
|
||
<h4>本地编辑列表行</h4>
|
||
<p class="codex-listedit-summary">当前编辑的是镜像中的列表行内容。保存后会写入浏览器本地存储,并在当前页面刷新后继续生效。</p>
|
||
<div class="codex-listedit-form">${fieldsHtml}</div>
|
||
<div class="codex-listedit-actions">
|
||
<button type="button" data-action="close">取消</button>
|
||
<button type="button" data-action="save" class="codex-primary">保存</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
mask.addEventListener("click", (event) => {
|
||
if (event.target === mask) {
|
||
closeHcPosListEditModal(doc);
|
||
}
|
||
});
|
||
doc.body.appendChild(mask);
|
||
mask.querySelector("[data-action='close']")?.addEventListener("click", () => closeHcPosListEditModal(doc));
|
||
mask.querySelector("[data-action='save']")?.addEventListener("click", () => {
|
||
const nextFields = context.fields.map((field, index) => ({
|
||
header: field.header,
|
||
value: mask.querySelector(`[data-field-index='${index}']`)?.value.trim() || ""
|
||
}));
|
||
const state = readHcPosListEditState(doc);
|
||
state[context.tableKey] = state[context.tableKey] || {};
|
||
state[context.tableKey][context.rowKey] = { fields: nextFields };
|
||
writeHcPosListEditState(doc, state);
|
||
applyHcPosListEditStateToTable(doc, context.tableKey, context.rowKey, nextFields);
|
||
closeHcPosListEditModal(doc);
|
||
});
|
||
}
|
||
|
||
function installHcPosGenericListEditors(doc) {
|
||
ensureHcPosListEditStyle(doc);
|
||
hydrateHcPosListEditRows(doc);
|
||
normalizeHcPosListEditTriggers(doc);
|
||
injectHcPosInlineEditButtons(doc);
|
||
renderHcPosHiddenEditPanel(doc);
|
||
if (doc.body.dataset.codexListEditorsInstalled === "1") {
|
||
return;
|
||
}
|
||
doc.addEventListener(
|
||
"click",
|
||
(event) => {
|
||
const target = event.target.closest("button, .textBtn, a, .el-button");
|
||
if (!target) {
|
||
return;
|
||
}
|
||
if (target.closest("[id^='__codex_'], .codex-budget-panel, .codex-editable-toolbar")) {
|
||
return;
|
||
}
|
||
const label = getHcPosVisibleText(target);
|
||
if (!/编辑/.test(label)) {
|
||
return;
|
||
}
|
||
const resolved = resolveHcPosListEditContext(doc, target);
|
||
if (!resolved) {
|
||
return;
|
||
}
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
openHcPosListEditModal(doc, resolved.row, resolved.context);
|
||
},
|
||
true
|
||
);
|
||
doc.body.dataset.codexListEditorsInstalled = "1";
|
||
}
|
||
|
||
function getHcPosDangerTableTarget(rowLabel) {
|
||
if (rowLabel === "财") {
|
||
return { runtime: "hcpos", path: "/propertySMG/businessTaxCank/financeReport/arrearsReport" };
|
||
}
|
||
if (rowLabel === "人") {
|
||
return { runtime: "hcpos", path: "/systemManage/projectConfig/internalControl/ProjectManagerConfig" };
|
||
}
|
||
return { runtime: "hcpos", path: "/propertySMG/securityProduction/checkStandardLibrary" };
|
||
}
|
||
|
||
function navigateHcPosCockpitLink(target) {
|
||
if (!target) {
|
||
return;
|
||
}
|
||
const topWindow = window.top || window;
|
||
if (target.runtime === "hcetms") {
|
||
const href = `/__mirror/runtime/hc-etms-dashboard/?v=${VERSION || "20260405p"}#${target.path || ""}`;
|
||
topWindow.location.href = href;
|
||
return;
|
||
}
|
||
topWindow.location.hash = `#${target.path}`;
|
||
}
|
||
|
||
function resolveHcPosCockpitTarget(node) {
|
||
const label = (node.textContent || "").replace(/\s+/g, " ").trim();
|
||
if (!label) {
|
||
return null;
|
||
}
|
||
if (/^by:/.test(label)) {
|
||
return null;
|
||
}
|
||
if (HCPOS_COCKPIT_LINKS[label]) {
|
||
return HCPOS_COCKPIT_LINKS[label];
|
||
}
|
||
const cardTitle = node
|
||
.closest(".flowm-card")
|
||
?.querySelector(".flowm-card__header span")
|
||
?.textContent?.replace(/\s+/g, " ")
|
||
.trim();
|
||
if (cardTitle && HCPOS_COCKPIT_LINKS[cardTitle]) {
|
||
return HCPOS_COCKPIT_LINKS[cardTitle];
|
||
}
|
||
if (label === "实时校验") {
|
||
const top = Math.round(node.getBoundingClientRect().top);
|
||
return top >= 1700
|
||
? { runtime: "hcetms", path: "/r2cockpit/cloudData/planTaskReport" }
|
||
: { runtime: "hcpos", path: "/propertySMG/securityProduction/checkStandardLibrary" };
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function installHcPosDangerTableLinks(doc) {
|
||
const rows = [...doc.querySelectorAll(".dangerous-source-table tbody tr")];
|
||
rows.forEach((row) => {
|
||
const cells = [...row.querySelectorAll("td")];
|
||
const rowLabel = (cells[0]?.textContent || "").replace(/\s+/g, " ").trim();
|
||
if (!rowLabel) {
|
||
return;
|
||
}
|
||
cells.slice(1).forEach((cell) => {
|
||
if (cell.dataset.codexDangerBound === "1") {
|
||
return;
|
||
}
|
||
cell.style.cursor = "pointer";
|
||
cell.addEventListener(
|
||
"click",
|
||
(event) => {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
navigateHcPosCockpitLink(getHcPosDangerTableTarget(rowLabel));
|
||
},
|
||
true
|
||
);
|
||
cell.dataset.codexDangerBound = "1";
|
||
});
|
||
});
|
||
}
|
||
|
||
function ensureHcPosBudgetToolStyle(doc) {
|
||
if (doc.getElementById("__codex_hcpos_budget_tool_style__")) {
|
||
return;
|
||
}
|
||
const style = doc.createElement("style");
|
||
style.id = "__codex_hcpos_budget_tool_style__";
|
||
style.textContent = `
|
||
.codex-budget-panel {
|
||
margin: 16px 0 20px;
|
||
padding: 16px 18px;
|
||
border-radius: 16px;
|
||
background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
|
||
border: 1px solid rgba(17,37,63,0.06);
|
||
box-shadow: 0 12px 28px rgba(17,37,63,0.08);
|
||
}
|
||
.codex-budget-panel-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
margin-bottom: 14px;
|
||
}
|
||
.codex-budget-panel-title {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
.codex-budget-panel-title strong {
|
||
color: #152742;
|
||
font-size: 18px;
|
||
}
|
||
.codex-budget-panel-title span {
|
||
color: #6d7d97;
|
||
font-size: 12px;
|
||
}
|
||
.codex-budget-pill {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 4px 10px;
|
||
border-radius: 999px;
|
||
background: rgba(45,118,255,0.08);
|
||
color: #2d76ff;
|
||
font-size: 12px;
|
||
}
|
||
.codex-budget-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 12px;
|
||
}
|
||
.codex-budget-card {
|
||
padding: 14px 16px;
|
||
border-radius: 14px;
|
||
border: 1px solid rgba(17,37,63,0.06);
|
||
background: rgba(255,255,255,0.92);
|
||
}
|
||
.codex-budget-card strong {
|
||
display: block;
|
||
margin-bottom: 8px;
|
||
color: #152742;
|
||
font-size: 15px;
|
||
}
|
||
.codex-budget-card table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
}
|
||
.codex-budget-card th,
|
||
.codex-budget-card td {
|
||
padding: 8px 6px;
|
||
border-bottom: 1px solid rgba(17,37,63,0.06);
|
||
color: #496385;
|
||
font-size: 12px;
|
||
text-align: left;
|
||
}
|
||
.codex-budget-card th {
|
||
color: #7d8ca5;
|
||
font-size: 11px;
|
||
}
|
||
.codex-budget-card p {
|
||
margin: 0;
|
||
color: #496385;
|
||
font-size: 12px;
|
||
line-height: 1.7;
|
||
}
|
||
.codex-budget-row-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
.codex-budget-status {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 4px 10px;
|
||
border-radius: 999px;
|
||
font-size: 12px;
|
||
line-height: 1;
|
||
border: 1px solid transparent;
|
||
}
|
||
.codex-budget-status[data-tone="success"] {
|
||
background: rgba(18, 183, 106, 0.12);
|
||
color: #0f8c52;
|
||
}
|
||
.codex-budget-status[data-tone="warning"] {
|
||
background: rgba(245, 158, 11, 0.14);
|
||
color: #b26b00;
|
||
}
|
||
.codex-budget-status[data-tone="danger"] {
|
||
background: rgba(209, 67, 67, 0.12);
|
||
color: #c03939;
|
||
}
|
||
.codex-budget-status[data-tone="default"] {
|
||
background: rgba(17, 37, 63, 0.06);
|
||
color: #496385;
|
||
}
|
||
.codex-budget-row-actions button,
|
||
.codex-budget-modal-actions button {
|
||
border: 1px solid rgba(17,37,63,0.08);
|
||
border-radius: 999px;
|
||
padding: 6px 12px;
|
||
background: #fff;
|
||
color: #35507a;
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
}
|
||
.codex-budget-row-actions button.codex-primary,
|
||
.codex-budget-modal-actions button.codex-primary {
|
||
background: #2d76ff;
|
||
color: #fff;
|
||
border-color: transparent;
|
||
}
|
||
.codex-budget-row-actions button.codex-danger,
|
||
.codex-budget-modal-actions button.codex-danger {
|
||
color: #d14343;
|
||
}
|
||
.codex-budget-modal-mask {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 99999;
|
||
background: rgba(8, 15, 28, 0.42);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 24px;
|
||
}
|
||
.codex-budget-modal {
|
||
width: min(720px, 100%);
|
||
max-height: min(760px, calc(100vh - 48px));
|
||
overflow: auto;
|
||
border-radius: 18px;
|
||
background: #fff;
|
||
box-shadow: 0 24px 54px rgba(17,37,63,0.18);
|
||
padding: 20px 22px;
|
||
}
|
||
.codex-budget-modal h4 {
|
||
margin: 0 0 16px;
|
||
color: #152742;
|
||
font-size: 20px;
|
||
}
|
||
.codex-budget-form {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 12px;
|
||
}
|
||
.codex-budget-form label {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
color: #6d7d97;
|
||
font-size: 12px;
|
||
}
|
||
.codex-budget-form label.full {
|
||
grid-column: 1 / -1;
|
||
}
|
||
.codex-budget-form input,
|
||
.codex-budget-form textarea,
|
||
.codex-budget-form select {
|
||
border: 1px solid rgba(17,37,63,0.12);
|
||
border-radius: 10px;
|
||
padding: 10px 12px;
|
||
color: #152742;
|
||
font-size: 13px;
|
||
background: #fff;
|
||
}
|
||
.codex-budget-form textarea {
|
||
min-height: 140px;
|
||
resize: vertical;
|
||
}
|
||
.codex-budget-modal-actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 10px;
|
||
margin-top: 18px;
|
||
}
|
||
.codex-budget-preview {
|
||
display: grid;
|
||
gap: 14px;
|
||
}
|
||
.codex-budget-preview pre {
|
||
margin: 0;
|
||
padding: 12px 14px;
|
||
border-radius: 12px;
|
||
background: #f7faff;
|
||
border: 1px solid rgba(17,37,63,0.06);
|
||
color: #35507a;
|
||
font-size: 12px;
|
||
line-height: 1.6;
|
||
white-space: pre-wrap;
|
||
}
|
||
.codex-budget-detail-list {
|
||
display: grid;
|
||
gap: 8px;
|
||
}
|
||
.codex-budget-detail-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
padding: 8px 0;
|
||
border-bottom: 1px solid rgba(17,37,63,0.06);
|
||
}
|
||
.codex-budget-detail-row:last-child {
|
||
border-bottom: 0;
|
||
}
|
||
.codex-budget-detail-row span {
|
||
color: #7d8ca5;
|
||
font-size: 12px;
|
||
}
|
||
.codex-budget-detail-row strong {
|
||
color: #152742;
|
||
font-size: 13px;
|
||
text-align: right;
|
||
}
|
||
.codex-budget-timeline {
|
||
margin: 0;
|
||
padding: 0;
|
||
list-style: none;
|
||
display: grid;
|
||
gap: 10px;
|
||
}
|
||
.codex-budget-timeline li {
|
||
padding: 12px 14px;
|
||
border-radius: 12px;
|
||
background: #f7faff;
|
||
border: 1px solid rgba(17,37,63,0.06);
|
||
color: #35507a;
|
||
font-size: 12px;
|
||
line-height: 1.6;
|
||
}
|
||
.codex-budget-timeline strong {
|
||
display: block;
|
||
margin-bottom: 4px;
|
||
color: #152742;
|
||
font-size: 13px;
|
||
}
|
||
@media (max-width: 960px) {
|
||
.codex-budget-grid,
|
||
.codex-budget-form {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
`;
|
||
doc.head.appendChild(style);
|
||
}
|
||
|
||
function readHcPosBudgetState(win, storageKey, seed) {
|
||
try {
|
||
const raw = win.localStorage.getItem(storageKey);
|
||
if (raw) {
|
||
return JSON.parse(raw);
|
||
}
|
||
} catch (_error) {
|
||
// ignore
|
||
}
|
||
return JSON.parse(JSON.stringify(seed));
|
||
}
|
||
|
||
function writeHcPosBudgetState(win, storageKey, state) {
|
||
try {
|
||
win.localStorage.setItem(storageKey, JSON.stringify(state));
|
||
} catch (_error) {
|
||
// ignore
|
||
}
|
||
}
|
||
|
||
function ensureHcPosBudgetPanel(doc, panelId) {
|
||
let panel = doc.getElementById(panelId);
|
||
if (panel) {
|
||
return panel;
|
||
}
|
||
panel = doc.createElement("section");
|
||
panel.id = panelId;
|
||
panel.className = "codex-budget-panel";
|
||
const search = doc.querySelector(".searchStys");
|
||
const host = search?.parentElement || doc.querySelector(".app-main > div") || doc.body;
|
||
if (search && search.parentElement) {
|
||
search.parentElement.insertBefore(panel, search.nextSibling);
|
||
} else {
|
||
host.insertBefore(panel, host.firstChild);
|
||
}
|
||
return panel;
|
||
}
|
||
|
||
function closeHcPosBudgetModal(doc) {
|
||
doc.getElementById("__codex_budget_modal_mask__")?.remove();
|
||
}
|
||
|
||
function openHcPosBudgetModal(doc, html, bindings = []) {
|
||
ensureHcPosBudgetToolStyle(doc);
|
||
closeHcPosBudgetModal(doc);
|
||
const mask = doc.createElement("div");
|
||
mask.id = "__codex_budget_modal_mask__";
|
||
mask.className = "codex-budget-modal-mask";
|
||
mask.innerHTML = `<div class="codex-budget-modal">${html}</div>`;
|
||
mask.addEventListener("click", (event) => {
|
||
if (event.target === mask) {
|
||
closeHcPosBudgetModal(doc);
|
||
}
|
||
});
|
||
doc.body.appendChild(mask);
|
||
bindings.forEach((binding) => {
|
||
const target = mask.querySelector(binding.selector);
|
||
if (target) {
|
||
binding.bind(target, mask);
|
||
}
|
||
});
|
||
}
|
||
|
||
function escapeHcPosBudgetHtml(value) {
|
||
return String(value ?? "")
|
||
.replace(/&/g, "&")
|
||
.replace(/</g, "<")
|
||
.replace(/>/g, ">")
|
||
.replace(/"/g, """)
|
||
.replace(/'/g, "'");
|
||
}
|
||
|
||
function getHcPosBudgetTimestamp() {
|
||
const now = new Date();
|
||
const pad = (value) => String(value).padStart(2, "0");
|
||
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}`;
|
||
}
|
||
|
||
function getHcPosBudgetStatusTone(status) {
|
||
const text = String(status || "");
|
||
if (/通过|启用|完成/.test(text)) {
|
||
return "success";
|
||
}
|
||
if (/驳回|禁用|失败/.test(text)) {
|
||
return "danger";
|
||
}
|
||
if (/待|中|审核/.test(text)) {
|
||
return "warning";
|
||
}
|
||
return "default";
|
||
}
|
||
|
||
function renderHcPosBudgetStatus(status) {
|
||
return `<span class="codex-budget-status" data-tone="${getHcPosBudgetStatusTone(status)}">${escapeHcPosBudgetHtml(status || "-")}</span>`;
|
||
}
|
||
|
||
function extractHcPosBudgetOrganContext(doc) {
|
||
const bodyText = (doc.body?.innerText || "").replace(/\u00a0/g, " ");
|
||
const year = bodyText.match(/预算表年份[::]\s*(\d{4})/)?.[1] || String(new Date().getFullYear());
|
||
const budgetName = bodyText.match(/科目名称[::]\s*([^\n]+)/)?.[1]?.trim() || `${year}-预算表`;
|
||
const subject =
|
||
bodyText.match(/(物业管理费收入|人员外包费用|工程维护费|能源服务成本|预算表生成操作人)/)?.[1] ||
|
||
budgetName;
|
||
const amountMatch = bodyText.match(/金额\s+([¥¥]?\s*[\d,.-]+)/);
|
||
const rawAmount = amountMatch?.[1]?.replace(/\s+/g, "") || "0.00";
|
||
const amount = /[¥¥]/.test(rawAmount) ? rawAmount.replace("¥", "¥") : `¥${rawAmount}`;
|
||
return {
|
||
year,
|
||
budgetName,
|
||
subject,
|
||
amount
|
||
};
|
||
}
|
||
|
||
function buildHcPosBudgetOrganSeed(doc) {
|
||
const context = extractHcPosBudgetOrganContext(doc);
|
||
return {
|
||
submissions: [
|
||
{
|
||
id: "budget-organ-001",
|
||
year: context.year,
|
||
budgetName: context.budgetName,
|
||
subject: context.subject,
|
||
amount: context.amount,
|
||
status: "待提交",
|
||
applicant: "-",
|
||
submitAt: "-",
|
||
note: "年度预算编制已完成,待发起审批。",
|
||
approvalNumber: ""
|
||
}
|
||
]
|
||
};
|
||
}
|
||
|
||
function buildHcPosFinancialApprovalSeed() {
|
||
return {
|
||
extraRecords: []
|
||
};
|
||
}
|
||
|
||
function readHcPosFinancialApprovalRecords(doc) {
|
||
const win = doc.defaultView;
|
||
const localState = readHcPosBudgetState(win, "__codex_financial_approval_state__", buildHcPosFinancialApprovalSeed());
|
||
const seen = new Set();
|
||
const records = [];
|
||
const pushRecord = (record) => {
|
||
if (!record || !record.number || seen.has(record.number)) {
|
||
return;
|
||
}
|
||
seen.add(record.number);
|
||
records.push(record);
|
||
};
|
||
localState.extraRecords.forEach(pushRecord);
|
||
if ((doc.location?.pathname || "").includes("/financialApproval/")) {
|
||
doc.querySelectorAll("tbody tr").forEach((row, index) => {
|
||
if (row.closest("#__codex_financial_approval_panel__")) {
|
||
return;
|
||
}
|
||
pushRecord(buildHcPosFinancialApprovalRecordFromRow(row, index));
|
||
});
|
||
}
|
||
return records;
|
||
}
|
||
|
||
function writeHcPosFinancialApprovalRecord(doc, record) {
|
||
if (!record || !record.number) {
|
||
return;
|
||
}
|
||
const win = doc.defaultView;
|
||
const state = readHcPosBudgetState(win, "__codex_financial_approval_state__", buildHcPosFinancialApprovalSeed());
|
||
const nextRecords = state.extraRecords.filter((item) => item.number !== record.number);
|
||
nextRecords.unshift(record);
|
||
writeHcPosBudgetState(win, "__codex_financial_approval_state__", {
|
||
extraRecords: nextRecords
|
||
});
|
||
}
|
||
|
||
function buildHcPosFinancialApprovalRecordFromRow(row, index = 0) {
|
||
const cells = [...row.querySelectorAll("td")]
|
||
.map((cell) => (cell.textContent || "").replace(/\s+/g, " ").trim())
|
||
.filter(Boolean);
|
||
if (cells.length < 7) {
|
||
return null;
|
||
}
|
||
const name = cells[1] || `财务审批-${index + 1}`;
|
||
const number = cells[2] || `DOM-${index + 1}`;
|
||
const status = cells[4] || "审批中";
|
||
return {
|
||
id: `approval-${number}`,
|
||
name,
|
||
number,
|
||
channel: cells[3] || "企业微信",
|
||
status,
|
||
applicant: cells[5] || "未知",
|
||
createdAt: cells[6] || "-",
|
||
summary: `${name} 当前状态为 ${status}。`,
|
||
details: [
|
||
{ label: "审批名称", value: name },
|
||
{ label: "审批编号", value: number },
|
||
{ label: "审批方式", value: cells[3] || "企业微信" },
|
||
{ label: "审批状态", value: status, isStatus: true },
|
||
{ label: "发起人", value: cells[5] || "未知" },
|
||
{ label: "发起时间", value: cells[6] || "-" }
|
||
],
|
||
timeline: [
|
||
{ title: "发起审批", detail: `申请人:${cells[5] || "未知"}\n时间:${cells[6] || "-"}` },
|
||
{ title: "当前节点", detail: `状态:${status}\n审批方式:${cells[3] || "企业微信"}` }
|
||
]
|
||
};
|
||
}
|
||
|
||
function buildHcPosFinancialApprovalRecordFromSubmission(entry) {
|
||
return {
|
||
id: `approval-${entry.approvalNumber}`,
|
||
name: `${entry.budgetName}预算审核`,
|
||
number: entry.approvalNumber,
|
||
channel: "镜像本地",
|
||
status: entry.status,
|
||
applicant: entry.applicant,
|
||
createdAt: entry.submitAt,
|
||
summary: `${entry.budgetName} 已发起预算审核,当前处理科目为 ${entry.subject}。`,
|
||
details: [
|
||
{ label: "审批名称", value: `${entry.budgetName}预算审核` },
|
||
{ label: "审批编号", value: entry.approvalNumber },
|
||
{ label: "预算年度", value: entry.year },
|
||
{ label: "预算科目", value: entry.subject },
|
||
{ label: "预算金额", value: entry.amount },
|
||
{ label: "审批状态", value: entry.status, isStatus: true },
|
||
{ label: "发起人", value: entry.applicant },
|
||
{ label: "发起时间", value: entry.submitAt },
|
||
{ label: "备注", value: entry.note || "-" }
|
||
],
|
||
timeline: [
|
||
{ title: "预算编制完成", detail: `预算表:${entry.budgetName}\n金额:${entry.amount}` },
|
||
{ title: "发起审核", detail: `申请人:${entry.applicant}\n时间:${entry.submitAt}` },
|
||
{ title: "当前节点", detail: "财务审批处理中,镜像站仅做本地流程展示。" }
|
||
]
|
||
};
|
||
}
|
||
|
||
function openHcPosFinancialApprovalDetail(doc, record) {
|
||
if (!record) {
|
||
return;
|
||
}
|
||
const detailRows = (record.details || [])
|
||
.map((item) => {
|
||
const value = item.isStatus ? renderHcPosBudgetStatus(item.value) : `<strong>${escapeHcPosBudgetHtml(item.value)}</strong>`;
|
||
return `<div class="codex-budget-detail-row"><span>${escapeHcPosBudgetHtml(item.label)}</span>${value}</div>`;
|
||
})
|
||
.join("");
|
||
const timeline = (record.timeline || [])
|
||
.map(
|
||
(item) => `
|
||
<li>
|
||
<strong>${escapeHcPosBudgetHtml(item.title)}</strong>
|
||
<div>${escapeHcPosBudgetHtml(item.detail).replace(/\n/g, "<br>")}</div>
|
||
</li>
|
||
`
|
||
)
|
||
.join("");
|
||
openHcPosBudgetModal(
|
||
doc,
|
||
`
|
||
<h4>审批详情</h4>
|
||
<div class="codex-budget-grid">
|
||
<div class="codex-budget-card">
|
||
<strong>${escapeHcPosBudgetHtml(record.name)}</strong>
|
||
<p>${escapeHcPosBudgetHtml(record.summary || "")}</p>
|
||
<div class="codex-budget-detail-list">${detailRows}</div>
|
||
</div>
|
||
<div class="codex-budget-card">
|
||
<strong>流程节点</strong>
|
||
<ul class="codex-budget-timeline">${timeline || "<li><strong>暂无节点</strong><div>当前镜像没有更多审批轨迹。</div></li>"}</ul>
|
||
</div>
|
||
</div>
|
||
<div class="codex-budget-modal-actions">
|
||
<button type="button" data-action="close">关闭</button>
|
||
</div>
|
||
`,
|
||
[
|
||
{
|
||
selector: "[data-action='close']",
|
||
bind(target) {
|
||
target.addEventListener("click", () => closeHcPosBudgetModal(doc));
|
||
}
|
||
}
|
||
]
|
||
);
|
||
}
|
||
|
||
function renderHcPosBudgetOrganPanel(doc) {
|
||
const win = doc.defaultView;
|
||
const storageKey = "__codex_budget_organ_state__";
|
||
const state = readHcPosBudgetState(win, storageKey, buildHcPosBudgetOrganSeed(doc));
|
||
const panel = ensureHcPosBudgetPanel(doc, "__codex_budget_organ_panel__");
|
||
const approvals = readHcPosFinancialApprovalRecords(doc).slice(0, 4);
|
||
const pendingCount = state.submissions.filter((item) => item.status === "待提交").length;
|
||
const submittedCount = state.submissions.filter((item) => item.status !== "待提交").length;
|
||
const rows = state.submissions
|
||
.map(
|
||
(item) => `
|
||
<tr>
|
||
<td>${escapeHcPosBudgetHtml(item.year)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.budgetName)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.subject)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.amount)}</td>
|
||
<td>${renderHcPosBudgetStatus(item.status)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.submitAt)}</td>
|
||
<td>
|
||
<div class="codex-budget-row-actions">
|
||
${
|
||
item.status === "待提交"
|
||
? `<button type="button" data-submit-id="${escapeHcPosBudgetHtml(item.id)}" class="codex-primary">提交审核</button>`
|
||
: `<button type="button" data-view-approval="${escapeHcPosBudgetHtml(item.approvalNumber)}">查看审批</button>`
|
||
}
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`
|
||
)
|
||
.join("");
|
||
const approvalRows = approvals
|
||
.map(
|
||
(item) => `
|
||
<tr>
|
||
<td>${escapeHcPosBudgetHtml(item.number)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.name)}</td>
|
||
<td>${renderHcPosBudgetStatus(item.status)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.applicant)}</td>
|
||
<td>
|
||
<div class="codex-budget-row-actions">
|
||
<button type="button" data-view-approval="${escapeHcPosBudgetHtml(item.number)}">查看详情</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`
|
||
)
|
||
.join("");
|
||
panel.innerHTML = `
|
||
<div class="codex-budget-panel-head">
|
||
<div class="codex-budget-panel-title">
|
||
<strong>本地预算审核工作台</strong>
|
||
<span>补齐预算编制页不可点击的“提交审核”,并把审批记录联动到财务审批页。</span>
|
||
</div>
|
||
<span class="codex-budget-pill">本地功能</span>
|
||
</div>
|
||
<div class="codex-budget-grid">
|
||
<div class="codex-budget-card">
|
||
<strong>提交概览</strong>
|
||
<div class="codex-budget-detail-list">
|
||
<div class="codex-budget-detail-row"><span>待提交预算项</span><strong>${pendingCount}</strong></div>
|
||
<div class="codex-budget-detail-row"><span>已发起审批</span><strong>${submittedCount}</strong></div>
|
||
<div class="codex-budget-detail-row"><span>审批记录数</span><strong>${approvals.length}</strong></div>
|
||
</div>
|
||
</div>
|
||
<div class="codex-budget-card">
|
||
<strong>使用说明</strong>
|
||
<p>原页面按钮保持原位,本地镜像会把“提交审核”改成可点,并在这里保存提交结果。财务审批页的“查看”也会同步展示详情。</p>
|
||
</div>
|
||
</div>
|
||
<div class="codex-budget-grid" style="margin-top: 12px;">
|
||
<div class="codex-budget-card">
|
||
<strong>预算提交项</strong>
|
||
<table>
|
||
<thead><tr><th>年度</th><th>预算表</th><th>科目</th><th>金额</th><th>状态</th><th>提交时间</th><th>操作</th></tr></thead>
|
||
<tbody>${rows || '<tr><td colspan="7">暂无待提交预算项</td></tr>'}</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="codex-budget-card">
|
||
<strong>最近审批记录</strong>
|
||
<table>
|
||
<thead><tr><th>审批编号</th><th>审批名称</th><th>状态</th><th>发起人</th><th>操作</th></tr></thead>
|
||
<tbody>${approvalRows || '<tr><td colspan="5">暂无审批记录</td></tr>'}</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function enableHcPosBudgetOrganNativeButton(doc) {
|
||
doc.querySelectorAll("button, .el-button").forEach((button) => {
|
||
const label = (button.textContent || "").replace(/\s+/g, " ").trim();
|
||
if (label !== "提交审核") {
|
||
return;
|
||
}
|
||
button.disabled = false;
|
||
button.removeAttribute("disabled");
|
||
button.classList.remove("is-disabled");
|
||
button.closest(".is-disabled")?.classList.remove("is-disabled");
|
||
button.style.cursor = "pointer";
|
||
button.style.opacity = "1";
|
||
});
|
||
}
|
||
|
||
function openHcPosBudgetOrganSubmitModal(doc, submissionId = "") {
|
||
const win = doc.defaultView;
|
||
const storageKey = "__codex_budget_organ_state__";
|
||
const state = readHcPosBudgetState(win, storageKey, buildHcPosBudgetOrganSeed(doc));
|
||
const context = extractHcPosBudgetOrganContext(doc);
|
||
const current = state.submissions.find((item) => item.id === submissionId) || {
|
||
id: `budget-organ-${Date.now()}`,
|
||
year: context.year,
|
||
budgetName: context.budgetName,
|
||
subject: context.subject,
|
||
amount: context.amount,
|
||
status: "待提交",
|
||
applicant: "-",
|
||
submitAt: "-",
|
||
note: "",
|
||
approvalNumber: ""
|
||
};
|
||
openHcPosBudgetModal(
|
||
doc,
|
||
`
|
||
<h4>提交预算审核</h4>
|
||
<div class="codex-budget-form">
|
||
<label><span>预算年度</span><input data-field="year" value="${escapeHcPosBudgetHtml(current.year)}"></label>
|
||
<label><span>预算表名称</span><input data-field="budgetName" value="${escapeHcPosBudgetHtml(current.budgetName)}"></label>
|
||
<label><span>预算科目</span><input data-field="subject" value="${escapeHcPosBudgetHtml(current.subject)}"></label>
|
||
<label><span>预算金额</span><input data-field="amount" value="${escapeHcPosBudgetHtml(current.amount)}"></label>
|
||
<label class="full"><span>提交说明</span><textarea data-field="note">${escapeHcPosBudgetHtml(current.note || "年度预算编制完成,申请进入财务审批流程。")}</textarea></label>
|
||
</div>
|
||
<div class="codex-budget-modal-actions">
|
||
<button type="button" data-action="close">取消</button>
|
||
<button type="button" data-action="save" class="codex-primary">确认提交</button>
|
||
</div>
|
||
`,
|
||
[
|
||
{
|
||
selector: "[data-action='close']",
|
||
bind(target) {
|
||
target.addEventListener("click", () => closeHcPosBudgetModal(doc));
|
||
}
|
||
},
|
||
{
|
||
selector: "[data-action='save']",
|
||
bind(target, mask) {
|
||
target.addEventListener("click", () => {
|
||
const next = readHcPosBudgetState(win, storageKey, buildHcPosBudgetOrganSeed(doc));
|
||
const submitAt = getHcPosBudgetTimestamp();
|
||
const payload = {
|
||
id: current.id,
|
||
year: mask.querySelector("[data-field='year']").value.trim() || current.year,
|
||
budgetName: mask.querySelector("[data-field='budgetName']").value.trim() || current.budgetName,
|
||
subject: mask.querySelector("[data-field='subject']").value.trim() || current.subject,
|
||
amount: mask.querySelector("[data-field='amount']").value.trim() || current.amount,
|
||
status: "审批中",
|
||
applicant: "本地镜像",
|
||
submitAt,
|
||
note: mask.querySelector("[data-field='note']").value.trim() || current.note,
|
||
approvalNumber: current.approvalNumber || `ISP${Date.now()}`
|
||
};
|
||
const targetIndex = next.submissions.findIndex((item) => item.id === current.id);
|
||
if (targetIndex >= 0) {
|
||
next.submissions.splice(targetIndex, 1, payload);
|
||
} else {
|
||
next.submissions.unshift(payload);
|
||
}
|
||
writeHcPosBudgetState(win, storageKey, next);
|
||
writeHcPosFinancialApprovalRecord(doc, buildHcPosFinancialApprovalRecordFromSubmission(payload));
|
||
renderHcPosBudgetOrganPanel(doc);
|
||
closeHcPosBudgetModal(doc);
|
||
});
|
||
}
|
||
}
|
||
]
|
||
);
|
||
}
|
||
|
||
function installHcPosBudgetOrganTools(doc) {
|
||
enableHcPosBudgetOrganNativeButton(doc);
|
||
renderHcPosBudgetOrganPanel(doc);
|
||
if (doc.body.dataset.codexBudgetOrganInstalled === "1") {
|
||
return;
|
||
}
|
||
ensureHcPosBudgetToolStyle(doc);
|
||
doc.addEventListener(
|
||
"click",
|
||
(event) => {
|
||
const button = event.target.closest("button, .el-button");
|
||
if (!button) {
|
||
return;
|
||
}
|
||
const label = (button.textContent || "").replace(/\s+/g, " ").trim();
|
||
const submitId = button.dataset.submitId;
|
||
const viewApproval = button.dataset.viewApproval;
|
||
if (submitId) {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
openHcPosBudgetOrganSubmitModal(doc, submitId);
|
||
return;
|
||
}
|
||
if (viewApproval) {
|
||
const record = readHcPosFinancialApprovalRecords(doc).find((item) => item.number === viewApproval);
|
||
if (!record) {
|
||
return;
|
||
}
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
openHcPosFinancialApprovalDetail(doc, record);
|
||
return;
|
||
}
|
||
if (label !== "提交审核") {
|
||
return;
|
||
}
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
openHcPosBudgetOrganSubmitModal(doc);
|
||
},
|
||
true
|
||
);
|
||
doc.body.dataset.codexBudgetOrganInstalled = "1";
|
||
}
|
||
|
||
function renderHcPosFinancialApprovalPanel(doc) {
|
||
const panel = ensureHcPosBudgetPanel(doc, "__codex_financial_approval_panel__");
|
||
const records = readHcPosFinancialApprovalRecords(doc);
|
||
const rows = records
|
||
.slice(0, 8)
|
||
.map(
|
||
(item) => `
|
||
<tr>
|
||
<td>${escapeHcPosBudgetHtml(item.number)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.name)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.channel)}</td>
|
||
<td>${renderHcPosBudgetStatus(item.status)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.applicant)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.createdAt)}</td>
|
||
<td>
|
||
<div class="codex-budget-row-actions">
|
||
<button type="button" data-view-approval="${escapeHcPosBudgetHtml(item.number)}">查看详情</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`
|
||
)
|
||
.join("");
|
||
panel.innerHTML = `
|
||
<div class="codex-budget-panel-head">
|
||
<div class="codex-budget-panel-title">
|
||
<strong>本地财务审批详情台</strong>
|
||
<span>补齐“查看”动作,优先读取表格行数据,并兼容预算编制页新提交的本地审批单。</span>
|
||
</div>
|
||
<span class="codex-budget-pill">本地功能</span>
|
||
</div>
|
||
<div class="codex-budget-card">
|
||
<strong>审批记录</strong>
|
||
<table>
|
||
<thead><tr><th>审批编号</th><th>审批名称</th><th>审批方式</th><th>状态</th><th>发起人</th><th>发起时间</th><th>操作</th></tr></thead>
|
||
<tbody>${rows || '<tr><td colspan="7">暂无可查看审批记录</td></tr>'}</tbody>
|
||
</table>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function installHcPosFinancialApprovalTools(doc) {
|
||
renderHcPosFinancialApprovalPanel(doc);
|
||
if (doc.body.dataset.codexFinancialApprovalInstalled === "1") {
|
||
return;
|
||
}
|
||
ensureHcPosBudgetToolStyle(doc);
|
||
doc.addEventListener(
|
||
"click",
|
||
(event) => {
|
||
const button = event.target.closest("button, .el-button");
|
||
if (!button) {
|
||
return;
|
||
}
|
||
const number = button.dataset.viewApproval;
|
||
if (number) {
|
||
const record = readHcPosFinancialApprovalRecords(doc).find((item) => item.number === number);
|
||
if (!record) {
|
||
return;
|
||
}
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
openHcPosFinancialApprovalDetail(doc, record);
|
||
return;
|
||
}
|
||
const label = (button.textContent || "").replace(/\s+/g, " ").trim();
|
||
if (label !== "查看") {
|
||
return;
|
||
}
|
||
const row = button.closest("tr");
|
||
const record = buildHcPosFinancialApprovalRecordFromRow(row);
|
||
if (!record) {
|
||
return;
|
||
}
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
openHcPosFinancialApprovalDetail(doc, record);
|
||
},
|
||
true
|
||
);
|
||
doc.body.dataset.codexFinancialApprovalInstalled = "1";
|
||
}
|
||
|
||
function buildHcPosRevenueFlowSeed() {
|
||
return {
|
||
salesOrders: [],
|
||
refundRecords: [],
|
||
tickets: [],
|
||
chargingBatches: [],
|
||
collectionActions: []
|
||
};
|
||
}
|
||
|
||
function readHcPosRevenueFlowState(win) {
|
||
const state = readHcPosBudgetState(win, "__codex_revenue_flow_state__", buildHcPosRevenueFlowSeed());
|
||
return {
|
||
...buildHcPosRevenueFlowSeed(),
|
||
...state,
|
||
salesOrders: Array.isArray(state.salesOrders) ? state.salesOrders : [],
|
||
refundRecords: Array.isArray(state.refundRecords) ? state.refundRecords : [],
|
||
tickets: Array.isArray(state.tickets) ? state.tickets : [],
|
||
chargingBatches: Array.isArray(state.chargingBatches) ? state.chargingBatches : [],
|
||
collectionActions: Array.isArray(state.collectionActions) ? state.collectionActions : []
|
||
};
|
||
}
|
||
|
||
function writeHcPosRevenueFlowState(win, state) {
|
||
writeHcPosBudgetState(win, "__codex_revenue_flow_state__", state);
|
||
}
|
||
|
||
function createHcPosRevenueNumber(prefix) {
|
||
return `${prefix}${Date.now()}${Math.random().toString(16).slice(2, 6)}`;
|
||
}
|
||
|
||
function formatHcPosRevenueAmount(value) {
|
||
const text = String(value || "").trim();
|
||
if (!text) {
|
||
return "¥0.00";
|
||
}
|
||
return /^[¥¥]/.test(text) ? text.replace("¥", "¥") : `¥${text}`;
|
||
}
|
||
|
||
function downloadHcPosRevenueFile(doc, fileName, content) {
|
||
const win = doc.defaultView;
|
||
const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
|
||
const url = win.URL.createObjectURL(blob);
|
||
const anchor = doc.createElement("a");
|
||
anchor.href = url;
|
||
anchor.download = fileName;
|
||
anchor.click();
|
||
win.URL.revokeObjectURL(url);
|
||
}
|
||
|
||
function upsertHcPosRevenueTicket(state, ticket) {
|
||
const nextTickets = state.tickets.filter((item) => item.orderNo !== ticket.orderNo);
|
||
nextTickets.unshift(ticket);
|
||
state.tickets = nextTickets;
|
||
}
|
||
|
||
function upsertHcPosRevenueSalesOrder(state, order) {
|
||
const nextOrders = state.salesOrders.filter((item) => item.orderNo !== order.orderNo);
|
||
nextOrders.unshift(order);
|
||
state.salesOrders = nextOrders;
|
||
}
|
||
|
||
function upsertHcPosRevenueChargingBatch(state, batch) {
|
||
const nextBatches = state.chargingBatches.filter((item) => item.batchNo !== batch.batchNo);
|
||
nextBatches.unshift(batch);
|
||
state.chargingBatches = nextBatches;
|
||
}
|
||
|
||
function upsertHcPosRevenueCollectionAction(state, action) {
|
||
const nextActions = state.collectionActions.filter((item) => item.id !== action.id);
|
||
nextActions.unshift(action);
|
||
state.collectionActions = nextActions;
|
||
}
|
||
|
||
function getHcPosRevenueRowIndex(row) {
|
||
const rows = row?.parentElement ? [...row.parentElement.children] : [];
|
||
return Math.max(rows.indexOf(row), 0) + 1;
|
||
}
|
||
|
||
function extractHcPosRevenueCells(row) {
|
||
return [...(row?.querySelectorAll("td") || [])]
|
||
.map((cell) => (cell.textContent || "").replace(/\s+/g, " ").trim())
|
||
.filter(Boolean);
|
||
}
|
||
|
||
function findHcPosRevenueDateTime(cells) {
|
||
return cells.find((text) => /\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}/.test(text)) || getHcPosBudgetTimestamp();
|
||
}
|
||
|
||
function findHcPosRevenueAmount(cells) {
|
||
return formatHcPosRevenueAmount(cells.find((text) => /[¥¥]\s*[\d,]+(?:\.\d+)?/.test(text)) || "0.00");
|
||
}
|
||
|
||
function renderHcPosRevenueSummaryRows(rows) {
|
||
return rows
|
||
.map(
|
||
(item) => `
|
||
<div class="codex-budget-detail-row">
|
||
<span>${escapeHcPosBudgetHtml(item.label)}</span>
|
||
<strong>${escapeHcPosBudgetHtml(item.value)}</strong>
|
||
</div>
|
||
`
|
||
)
|
||
.join("");
|
||
}
|
||
|
||
function buildHcPosRevenueSalesRecordFromRow(row) {
|
||
const cells = extractHcPosRevenueCells(row);
|
||
if (cells.length < 6) {
|
||
return null;
|
||
}
|
||
const rowIndex = getHcPosRevenueRowIndex(row);
|
||
const createdAt = findHcPosRevenueDateTime(cells);
|
||
const amount = findHcPosRevenueAmount(cells);
|
||
const payOrderNo = cells.find((text) => /^YS|^XS|^\d{12,}/.test(text)) || "-";
|
||
const fallbackOrderNo = `DOMXS-${createdAt.replace(/\D/g, "").slice(-10)}-${rowIndex}`;
|
||
return {
|
||
id: fallbackOrderNo,
|
||
orderNo: payOrderNo !== "-" ? payOrderNo : fallbackOrderNo,
|
||
title: `销单记录-${rowIndex}`,
|
||
debtor: "-",
|
||
payer: "-",
|
||
period: cells.find((text) => /^\d{4}-\d{2}$/.test(text)) || "-",
|
||
item: cells[1] || "销单事项",
|
||
amount,
|
||
method: cells[1] || "特殊减免",
|
||
status: cells[2] || "已销单",
|
||
payOrderNo,
|
||
channel: cells[4] || "本地镜像",
|
||
createdAt,
|
||
operator: cells[6] || "本地镜像",
|
||
note: "来源于原始销单记录",
|
||
invoiceStatus: "未开票",
|
||
receiptStatus: "未下载"
|
||
};
|
||
}
|
||
|
||
function buildHcPosRevenueTicketFromRow(row) {
|
||
const cells = extractHcPosRevenueCells(row);
|
||
if (cells.length < 5) {
|
||
return null;
|
||
}
|
||
const rowIndex = getHcPosRevenueRowIndex(row);
|
||
const createdAt = findHcPosRevenueDateTime(cells);
|
||
const orderNo = cells.find((text) => /^\d{12,}/.test(text)) || `EB${createdAt.replace(/\D/g, "").slice(-10)}${rowIndex}`;
|
||
const amount = findHcPosRevenueAmount(cells);
|
||
const status = cells.find((text) => /支付|开票|失败|成功|未/.test(text)) || "待处理";
|
||
return {
|
||
id: `ticket-${orderNo}`,
|
||
ticketNo: `PJ${orderNo.slice(-10)}`,
|
||
orderNo,
|
||
title: `电子票据-${orderNo.slice(-6)}`,
|
||
payer: cells[0] || "-",
|
||
email: cells.find((text) => /@/.test(text)) || "local@mirror.test",
|
||
amount,
|
||
type: "电子收据",
|
||
status,
|
||
createdAt,
|
||
note: "来源于原始电子票据列表"
|
||
};
|
||
}
|
||
|
||
function openHcPosRevenueSalesDetail(doc, record) {
|
||
if (!record) {
|
||
return;
|
||
}
|
||
openHcPosBudgetModal(
|
||
doc,
|
||
`
|
||
<h4>销单详情</h4>
|
||
<div class="codex-budget-card">
|
||
<div class="codex-budget-detail-list">
|
||
${renderHcPosRevenueSummaryRows([
|
||
{ label: "销单单号", value: record.orderNo },
|
||
{ label: "业务标题", value: record.title || "-" },
|
||
{ label: "销单方式", value: record.method || "-" },
|
||
{ label: "销单状态", value: record.status || "-" },
|
||
{ label: "销单金额", value: record.amount || "¥0.00" },
|
||
{ label: "发起时间", value: record.createdAt || "-" },
|
||
{ label: "操作人", value: record.operator || "-" },
|
||
{ label: "备注", value: record.note || "-" }
|
||
])}
|
||
</div>
|
||
</div>
|
||
<div class="codex-budget-modal-actions">
|
||
<button type="button" data-action="close">关闭</button>
|
||
</div>
|
||
`,
|
||
[
|
||
{
|
||
selector: "[data-action='close']",
|
||
bind(target) {
|
||
target.addEventListener("click", () => closeHcPosBudgetModal(doc));
|
||
}
|
||
}
|
||
]
|
||
);
|
||
}
|
||
|
||
function openHcPosRevenueRefundDetail(doc, record) {
|
||
if (!record) {
|
||
return;
|
||
}
|
||
openHcPosBudgetModal(
|
||
doc,
|
||
`
|
||
<h4>退款详情</h4>
|
||
<div class="codex-budget-card">
|
||
<div class="codex-budget-detail-list">
|
||
${renderHcPosRevenueSummaryRows([
|
||
{ label: "退款单号", value: record.refundNo },
|
||
{ label: "业务订单号", value: record.businessOrderNo },
|
||
{ label: "退款金额", value: record.amount || "¥0.00" },
|
||
{ label: "账期", value: record.period || "-" },
|
||
{ label: "发起时间", value: record.createdAt || "-" },
|
||
{ label: "发起人", value: record.operator || "-" },
|
||
{ label: "备注", value: record.note || "-" }
|
||
])}
|
||
</div>
|
||
</div>
|
||
<div class="codex-budget-modal-actions">
|
||
<button type="button" data-action="close">关闭</button>
|
||
</div>
|
||
`,
|
||
[
|
||
{
|
||
selector: "[data-action='close']",
|
||
bind(target) {
|
||
target.addEventListener("click", () => closeHcPosBudgetModal(doc));
|
||
}
|
||
}
|
||
]
|
||
);
|
||
}
|
||
|
||
function openHcPosRevenueTicketDetail(doc, ticket) {
|
||
if (!ticket) {
|
||
return;
|
||
}
|
||
openHcPosBudgetModal(
|
||
doc,
|
||
`
|
||
<h4>票据详情</h4>
|
||
<div class="codex-budget-card">
|
||
<div class="codex-budget-detail-list">
|
||
${renderHcPosRevenueSummaryRows([
|
||
{ label: "票据编号", value: ticket.ticketNo },
|
||
{ label: "关联订单", value: ticket.orderNo || "-" },
|
||
{ label: "票据标题", value: ticket.title || "-" },
|
||
{ label: "票据类型", value: ticket.type || "电子收据" },
|
||
{ label: "票据状态", value: ticket.status || "-" },
|
||
{ label: "支付人", value: ticket.payer || "-" },
|
||
{ label: "接收邮箱", value: ticket.email || "-" },
|
||
{ label: "票据金额", value: ticket.amount || "¥0.00" },
|
||
{ label: "生成时间", value: ticket.createdAt || "-" }
|
||
])}
|
||
</div>
|
||
</div>
|
||
<div class="codex-budget-modal-actions">
|
||
<button type="button" data-action="close">关闭</button>
|
||
</div>
|
||
`,
|
||
[
|
||
{
|
||
selector: "[data-action='close']",
|
||
bind(target) {
|
||
target.addEventListener("click", () => closeHcPosBudgetModal(doc));
|
||
}
|
||
}
|
||
]
|
||
);
|
||
}
|
||
|
||
function downloadHcPosRevenueReceipt(doc, record) {
|
||
if (!record) {
|
||
return;
|
||
}
|
||
const win = doc.defaultView;
|
||
const state = readHcPosRevenueFlowState(win);
|
||
state.salesOrders = state.salesOrders.map((item) =>
|
||
item.orderNo === record.orderNo
|
||
? {
|
||
...item,
|
||
receiptStatus: "已下载"
|
||
}
|
||
: item
|
||
);
|
||
writeHcPosRevenueFlowState(win, state);
|
||
downloadHcPosRevenueFile(
|
||
doc,
|
||
`${record.orderNo || createHcPosRevenueNumber("XS")}-receipt.txt`,
|
||
`销单单号: ${record.orderNo || "-"}\n销单金额: ${record.amount || "¥0.00"}\n销单方式: ${record.method || "-"}\n生成时间: ${getHcPosBudgetTimestamp()}`
|
||
);
|
||
}
|
||
|
||
function openHcPosRevenueTicketCreateModal(doc, salesRecord) {
|
||
if (!salesRecord) {
|
||
return;
|
||
}
|
||
openHcPosBudgetModal(
|
||
doc,
|
||
`
|
||
<h4>开具票据</h4>
|
||
<div class="codex-budget-form">
|
||
<label><span>票据标题</span><input data-field="title" value="${escapeHcPosBudgetHtml(salesRecord.title || "销单票据")}"></label>
|
||
<label><span>票据类型</span><select data-field="type"><option>电子收据</option><option>电子发票</option></select></label>
|
||
<label><span>接收邮箱</span><input data-field="email" value="local@mirror.test"></label>
|
||
<label><span>票据金额</span><input data-field="amount" value="${escapeHcPosBudgetHtml(salesRecord.amount || "¥0.00")}"></label>
|
||
<label class="full"><span>备注</span><textarea data-field="note">本地镜像生成票据,用于收入管理闭环演示。</textarea></label>
|
||
</div>
|
||
<div class="codex-budget-modal-actions">
|
||
<button type="button" data-action="close">取消</button>
|
||
<button type="button" data-action="save" class="codex-primary">确认开具</button>
|
||
</div>
|
||
`,
|
||
[
|
||
{
|
||
selector: "[data-action='close']",
|
||
bind(target) {
|
||
target.addEventListener("click", () => closeHcPosBudgetModal(doc));
|
||
}
|
||
},
|
||
{
|
||
selector: "[data-action='save']",
|
||
bind(target, mask) {
|
||
target.addEventListener("click", () => {
|
||
const win = doc.defaultView;
|
||
const state = readHcPosRevenueFlowState(win);
|
||
const ticket = {
|
||
id: `ticket-${salesRecord.orderNo}`,
|
||
ticketNo: createHcPosRevenueNumber("PJ"),
|
||
orderNo: salesRecord.orderNo,
|
||
title: mask.querySelector("[data-field='title']").value.trim() || salesRecord.title || "销单票据",
|
||
payer: salesRecord.payer || salesRecord.debtor || "-",
|
||
email: mask.querySelector("[data-field='email']").value.trim() || "local@mirror.test",
|
||
amount: formatHcPosRevenueAmount(mask.querySelector("[data-field='amount']").value.trim() || salesRecord.amount),
|
||
type: mask.querySelector("[data-field='type']").value.trim() || "电子收据",
|
||
status: "已开票",
|
||
createdAt: getHcPosBudgetTimestamp(),
|
||
note: mask.querySelector("[data-field='note']").value.trim()
|
||
};
|
||
upsertHcPosRevenueTicket(state, ticket);
|
||
state.salesOrders = state.salesOrders.map((item) =>
|
||
item.orderNo === salesRecord.orderNo
|
||
? {
|
||
...item,
|
||
invoiceStatus: "已开票"
|
||
}
|
||
: item
|
||
);
|
||
writeHcPosRevenueFlowState(win, state);
|
||
renderHcPosRevenueSalesOrderPanel(doc);
|
||
renderHcPosRevenueElectronicBillPanel(doc);
|
||
closeHcPosBudgetModal(doc);
|
||
});
|
||
}
|
||
}
|
||
]
|
||
);
|
||
}
|
||
|
||
function renderHcPosRevenueReceivablePanel(doc) {
|
||
const state = readHcPosRevenueFlowState(doc.defaultView);
|
||
const panel = ensureHcPosBudgetPanel(doc, "__codex_revenue_receivable_panel__");
|
||
const latestSales = state.salesOrders
|
||
.slice(0, 3)
|
||
.map((item) => `<tr><td>${escapeHcPosBudgetHtml(item.orderNo)}</td><td>${escapeHcPosBudgetHtml(item.amount)}</td><td>${renderHcPosBudgetStatus(item.status)}</td><td>${escapeHcPosBudgetHtml(item.createdAt)}</td></tr>`)
|
||
.join("");
|
||
const latestRefunds = state.refundRecords
|
||
.slice(0, 3)
|
||
.map((item) => `<tr><td>${escapeHcPosBudgetHtml(item.refundNo)}</td><td>${escapeHcPosBudgetHtml(item.amount)}</td><td>${escapeHcPosBudgetHtml(item.period)}</td><td>${escapeHcPosBudgetHtml(item.createdAt)}</td></tr>`)
|
||
.join("");
|
||
panel.innerHTML = `
|
||
<div class="codex-budget-panel-head">
|
||
<div class="codex-budget-panel-title">
|
||
<strong>本地收入操作台</strong>
|
||
<span>补齐应收页的“我要销单 / 我要退款”,并把结果同步到销单、退款记录和电子票据页。</span>
|
||
</div>
|
||
<span class="codex-budget-pill">本地功能</span>
|
||
</div>
|
||
<div class="codex-budget-grid">
|
||
<div class="codex-budget-card">
|
||
<strong>闭环概览</strong>
|
||
<div class="codex-budget-detail-list">
|
||
${renderHcPosRevenueSummaryRows([
|
||
{ label: "本地销单记录", value: String(state.salesOrders.length) },
|
||
{ label: "本地退款记录", value: String(state.refundRecords.length) },
|
||
{ label: "本地票据记录", value: String(state.tickets.length) }
|
||
])}
|
||
</div>
|
||
</div>
|
||
<div class="codex-budget-card">
|
||
<strong>使用说明</strong>
|
||
<p>直接点击原页面工具栏里的“我要销单 / 我要退款”即可发起本地业务,结果会落到收入管理相关页面。</p>
|
||
</div>
|
||
</div>
|
||
<div class="codex-budget-grid" style="margin-top: 12px;">
|
||
<div class="codex-budget-card">
|
||
<strong>最近销单</strong>
|
||
<table>
|
||
<thead><tr><th>销单单号</th><th>金额</th><th>状态</th><th>时间</th></tr></thead>
|
||
<tbody>${latestSales || '<tr><td colspan="4">暂无本地销单记录</td></tr>'}</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="codex-budget-card">
|
||
<strong>最近退款</strong>
|
||
<table>
|
||
<thead><tr><th>退款单号</th><th>金额</th><th>账期</th><th>时间</th></tr></thead>
|
||
<tbody>${latestRefunds || '<tr><td colspan="4">暂无本地退款记录</td></tr>'}</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function openHcPosRevenueReceivableModal(doc, mode) {
|
||
const isSales = mode === "sales";
|
||
openHcPosBudgetModal(
|
||
doc,
|
||
`
|
||
<h4>${isSales ? "发起销单" : "发起退款"}</h4>
|
||
<div class="codex-budget-form">
|
||
<label><span>债务人</span><input data-field="debtor" value="阿三"></label>
|
||
<label><span>缴费人</span><input data-field="payer" value="阿三"></label>
|
||
<label><span>计费账期</span><input data-field="period" value="2026-04"></label>
|
||
<label><span>收费项</span><input data-field="item" value="物业管理费"></label>
|
||
<label><span>${isSales ? "销单金额" : "退款金额"}</span><input data-field="amount" value="128.50"></label>
|
||
<label class="full"><span>备注</span><textarea data-field="note">${isSales ? "本地销单演示,用于镜像闭环。" : "本地退款演示,用于镜像闭环。"}</textarea></label>
|
||
</div>
|
||
<div class="codex-budget-modal-actions">
|
||
<button type="button" data-action="close">取消</button>
|
||
<button type="button" data-action="save" class="codex-primary">确认</button>
|
||
</div>
|
||
`,
|
||
[
|
||
{
|
||
selector: "[data-action='close']",
|
||
bind(target) {
|
||
target.addEventListener("click", () => closeHcPosBudgetModal(doc));
|
||
}
|
||
},
|
||
{
|
||
selector: "[data-action='save']",
|
||
bind(target, mask) {
|
||
target.addEventListener("click", () => {
|
||
const win = doc.defaultView;
|
||
const state = readHcPosRevenueFlowState(win);
|
||
const debtor = mask.querySelector("[data-field='debtor']").value.trim() || "未命名债务人";
|
||
const payer = mask.querySelector("[data-field='payer']").value.trim() || debtor;
|
||
const period = mask.querySelector("[data-field='period']").value.trim() || "-";
|
||
const item = mask.querySelector("[data-field='item']").value.trim() || "收费项";
|
||
const amount = formatHcPosRevenueAmount(mask.querySelector("[data-field='amount']").value.trim() || "0.00");
|
||
const note = mask.querySelector("[data-field='note']").value.trim();
|
||
const createdAt = getHcPosBudgetTimestamp();
|
||
if (isSales) {
|
||
state.salesOrders.unshift({
|
||
id: createHcPosRevenueNumber("sales-"),
|
||
orderNo: createHcPosRevenueNumber("XS"),
|
||
title: `${item}销单`,
|
||
debtor,
|
||
payer,
|
||
period,
|
||
item,
|
||
amount,
|
||
method: "特殊减免",
|
||
status: "已销单",
|
||
payOrderNo: "-",
|
||
channel: "镜像本地",
|
||
createdAt,
|
||
operator: "本地镜像",
|
||
note,
|
||
invoiceStatus: "未开票",
|
||
receiptStatus: "未下载"
|
||
});
|
||
} else {
|
||
state.refundRecords.unshift({
|
||
id: createHcPosRevenueNumber("refund-"),
|
||
refundNo: createHcPosRevenueNumber("TK"),
|
||
businessOrderNo: createHcPosRevenueNumber("YS"),
|
||
debtor,
|
||
payer,
|
||
period,
|
||
amount,
|
||
direction: "退款",
|
||
createdAt,
|
||
operator: "本地镜像",
|
||
note
|
||
});
|
||
}
|
||
writeHcPosRevenueFlowState(win, state);
|
||
renderHcPosRevenueReceivablePanel(doc);
|
||
closeHcPosBudgetModal(doc);
|
||
});
|
||
}
|
||
}
|
||
]
|
||
);
|
||
}
|
||
|
||
function installHcPosRevenueReceivableTools(doc) {
|
||
renderHcPosRevenueReceivablePanel(doc);
|
||
if (doc.body.dataset.codexRevenueReceivableInstalled === "1") {
|
||
return;
|
||
}
|
||
doc.addEventListener(
|
||
"click",
|
||
(event) => {
|
||
const button = event.target.closest("button, .el-button");
|
||
if (!button) {
|
||
return;
|
||
}
|
||
const label = (button.textContent || "").replace(/\s+/g, " ").trim();
|
||
if (!["我要销单", "我要退款"].includes(label)) {
|
||
return;
|
||
}
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
openHcPosRevenueReceivableModal(doc, label === "我要销单" ? "sales" : "refund");
|
||
},
|
||
true
|
||
);
|
||
doc.body.dataset.codexRevenueReceivableInstalled = "1";
|
||
}
|
||
|
||
function openHcPosRevenueChargingDetail(doc, batch) {
|
||
if (!batch) {
|
||
return;
|
||
}
|
||
openHcPosBudgetModal(
|
||
doc,
|
||
`
|
||
<h4>计费批次详情</h4>
|
||
<div class="codex-budget-card">
|
||
<div class="codex-budget-detail-list">
|
||
${renderHcPosRevenueSummaryRows([
|
||
{ label: "批次编号", value: batch.batchNo },
|
||
{ label: "账期", value: batch.period || "-" },
|
||
{ label: "应收本金", value: batch.principal || "¥0.00" },
|
||
{ label: "应收违约金", value: batch.penalty || "¥0.00" },
|
||
{ label: "状态", value: batch.status || "-" },
|
||
{ label: "创建时间", value: batch.createdAt || "-" },
|
||
{ label: "审核时间", value: batch.auditAt || "-" },
|
||
{ label: "操作人", value: batch.operator || "-" },
|
||
{ label: "备注", value: batch.note || "-" }
|
||
])}
|
||
</div>
|
||
</div>
|
||
<div class="codex-budget-modal-actions">
|
||
<button type="button" data-action="close">关闭</button>
|
||
</div>
|
||
`,
|
||
[
|
||
{
|
||
selector: "[data-action='close']",
|
||
bind(target) {
|
||
target.addEventListener("click", () => closeHcPosBudgetModal(doc));
|
||
}
|
||
}
|
||
]
|
||
);
|
||
}
|
||
|
||
function renderHcPosRevenueChargingPanel(doc) {
|
||
const state = readHcPosRevenueFlowState(doc.defaultView);
|
||
const panel = ensureHcPosBudgetPanel(doc, "__codex_revenue_charging_panel__");
|
||
const pendingCount = state.chargingBatches.filter((item) => item.status === "待审核").length;
|
||
const approvedCount = state.chargingBatches.filter((item) => item.status === "已审核").length;
|
||
const rows = state.chargingBatches
|
||
.slice(0, 8)
|
||
.map(
|
||
(item) => `
|
||
<tr>
|
||
<td>${escapeHcPosBudgetHtml(item.batchNo)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.period)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.principal)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.penalty)}</td>
|
||
<td>${renderHcPosBudgetStatus(item.status)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.createdAt)}</td>
|
||
<td>
|
||
<div class="codex-budget-row-actions">
|
||
<button type="button" data-view-charging="${escapeHcPosBudgetHtml(item.batchNo)}">查看详情</button>
|
||
${
|
||
item.status === "待审核"
|
||
? `<button type="button" data-audit-charging="${escapeHcPosBudgetHtml(item.batchNo)}" class="codex-primary">通过审核</button>`
|
||
: ""
|
||
}
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`
|
||
)
|
||
.join("");
|
||
panel.innerHTML = `
|
||
<div class="codex-budget-panel-head">
|
||
<div class="codex-budget-panel-title">
|
||
<strong>本地计费工作台</strong>
|
||
<span>补齐“创建计费 / 账期审核”,用本地批次记录承接计费流程。</span>
|
||
</div>
|
||
<span class="codex-budget-pill">本地功能</span>
|
||
</div>
|
||
<div class="codex-budget-grid">
|
||
<div class="codex-budget-card">
|
||
<strong>批次概览</strong>
|
||
<div class="codex-budget-detail-list">
|
||
${renderHcPosRevenueSummaryRows([
|
||
{ label: "本地计费批次", value: String(state.chargingBatches.length) },
|
||
{ label: "待审核批次", value: String(pendingCount) },
|
||
{ label: "已审核批次", value: String(approvedCount) }
|
||
])}
|
||
</div>
|
||
</div>
|
||
<div class="codex-budget-card">
|
||
<strong>使用说明</strong>
|
||
<p>原页面工具栏中的“创建计费 / 账期审核”会被接到本地工作流,便于镜像里直接演示计费创建与审核。</p>
|
||
</div>
|
||
</div>
|
||
<div class="codex-budget-card" style="margin-top: 12px;">
|
||
<strong>本地计费批次</strong>
|
||
<table>
|
||
<thead><tr><th>批次编号</th><th>账期</th><th>应收本金</th><th>应收违约金</th><th>状态</th><th>创建时间</th><th>操作</th></tr></thead>
|
||
<tbody>${rows || '<tr><td colspan="7">暂无本地计费批次</td></tr>'}</tbody>
|
||
</table>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function openHcPosRevenueChargingCreateModal(doc) {
|
||
openHcPosBudgetModal(
|
||
doc,
|
||
`
|
||
<h4>创建计费批次</h4>
|
||
<div class="codex-budget-form">
|
||
<label><span>计费账期</span><input data-field="period" value="2026-04"></label>
|
||
<label><span>应收本金</span><input data-field="principal" value="10990.42"></label>
|
||
<label><span>应收违约金</span><input data-field="penalty" value="0.00"></label>
|
||
<label><span>操作人</span><input data-field="operator" value="本地镜像"></label>
|
||
<label class="full"><span>备注</span><textarea data-field="note">本地镜像创建计费批次,用于收入管理演示。</textarea></label>
|
||
</div>
|
||
<div class="codex-budget-modal-actions">
|
||
<button type="button" data-action="close">取消</button>
|
||
<button type="button" data-action="save" class="codex-primary">确认创建</button>
|
||
</div>
|
||
`,
|
||
[
|
||
{
|
||
selector: "[data-action='close']",
|
||
bind(target) {
|
||
target.addEventListener("click", () => closeHcPosBudgetModal(doc));
|
||
}
|
||
},
|
||
{
|
||
selector: "[data-action='save']",
|
||
bind(target, mask) {
|
||
target.addEventListener("click", () => {
|
||
const win = doc.defaultView;
|
||
const state = readHcPosRevenueFlowState(win);
|
||
upsertHcPosRevenueChargingBatch(state, {
|
||
id: createHcPosRevenueNumber("charge-"),
|
||
batchNo: createHcPosRevenueNumber("JF"),
|
||
period: mask.querySelector("[data-field='period']").value.trim() || "2026-04",
|
||
principal: formatHcPosRevenueAmount(mask.querySelector("[data-field='principal']").value.trim() || "0.00"),
|
||
penalty: formatHcPosRevenueAmount(mask.querySelector("[data-field='penalty']").value.trim() || "0.00"),
|
||
status: "待审核",
|
||
createdAt: getHcPosBudgetTimestamp(),
|
||
auditAt: "-",
|
||
operator: mask.querySelector("[data-field='operator']").value.trim() || "本地镜像",
|
||
note: mask.querySelector("[data-field='note']").value.trim()
|
||
});
|
||
writeHcPosRevenueFlowState(win, state);
|
||
renderHcPosRevenueChargingPanel(doc);
|
||
closeHcPosBudgetModal(doc);
|
||
});
|
||
}
|
||
}
|
||
]
|
||
);
|
||
}
|
||
|
||
function openHcPosRevenueChargingAuditModal(doc, batchNo = "") {
|
||
const state = readHcPosRevenueFlowState(doc.defaultView);
|
||
const batch =
|
||
state.chargingBatches.find((item) => item.batchNo === batchNo) ||
|
||
state.chargingBatches.find((item) => item.status === "待审核");
|
||
if (!batch) {
|
||
openHcPosBudgetModal(
|
||
doc,
|
||
`
|
||
<h4>账期审核</h4>
|
||
<div class="codex-budget-card">
|
||
<p>当前没有待审核的本地计费批次。请先创建一条本地计费批次。</p>
|
||
</div>
|
||
<div class="codex-budget-modal-actions">
|
||
<button type="button" data-action="close">关闭</button>
|
||
</div>
|
||
`,
|
||
[
|
||
{
|
||
selector: "[data-action='close']",
|
||
bind(target) {
|
||
target.addEventListener("click", () => closeHcPosBudgetModal(doc));
|
||
}
|
||
}
|
||
]
|
||
);
|
||
return;
|
||
}
|
||
openHcPosBudgetModal(
|
||
doc,
|
||
`
|
||
<h4>账期审核</h4>
|
||
<div class="codex-budget-card">
|
||
<div class="codex-budget-detail-list">
|
||
${renderHcPosRevenueSummaryRows([
|
||
{ label: "批次编号", value: batch.batchNo },
|
||
{ label: "计费账期", value: batch.period },
|
||
{ label: "应收本金", value: batch.principal },
|
||
{ label: "应收违约金", value: batch.penalty },
|
||
{ label: "当前状态", value: batch.status }
|
||
])}
|
||
</div>
|
||
</div>
|
||
<div class="codex-budget-modal-actions">
|
||
<button type="button" data-action="close">取消</button>
|
||
<button type="button" data-action="save" class="codex-primary">审核通过</button>
|
||
</div>
|
||
`,
|
||
[
|
||
{
|
||
selector: "[data-action='close']",
|
||
bind(target) {
|
||
target.addEventListener("click", () => closeHcPosBudgetModal(doc));
|
||
}
|
||
},
|
||
{
|
||
selector: "[data-action='save']",
|
||
bind(target) {
|
||
target.addEventListener("click", () => {
|
||
const win = doc.defaultView;
|
||
const next = readHcPosRevenueFlowState(win);
|
||
next.chargingBatches = next.chargingBatches.map((item) =>
|
||
item.batchNo === batch.batchNo
|
||
? {
|
||
...item,
|
||
status: "已审核",
|
||
auditAt: getHcPosBudgetTimestamp()
|
||
}
|
||
: item
|
||
);
|
||
writeHcPosRevenueFlowState(win, next);
|
||
renderHcPosRevenueChargingPanel(doc);
|
||
closeHcPosBudgetModal(doc);
|
||
});
|
||
}
|
||
}
|
||
]
|
||
);
|
||
}
|
||
|
||
function installHcPosRevenueChargingTools(doc) {
|
||
renderHcPosRevenueChargingPanel(doc);
|
||
if (doc.body.dataset.codexRevenueChargingInstalled === "1") {
|
||
return;
|
||
}
|
||
doc.addEventListener(
|
||
"click",
|
||
(event) => {
|
||
const button = event.target.closest("button, .el-button");
|
||
if (!button) {
|
||
return;
|
||
}
|
||
const viewCharging = button.dataset.viewCharging;
|
||
const auditCharging = button.dataset.auditCharging;
|
||
if (viewCharging) {
|
||
const batch = readHcPosRevenueFlowState(doc.defaultView).chargingBatches.find((item) => item.batchNo === viewCharging);
|
||
if (!batch) {
|
||
return;
|
||
}
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
openHcPosRevenueChargingDetail(doc, batch);
|
||
return;
|
||
}
|
||
if (auditCharging) {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
openHcPosRevenueChargingAuditModal(doc, auditCharging);
|
||
return;
|
||
}
|
||
const label = (button.textContent || "").replace(/\s+/g, " ").trim();
|
||
if (label === "创建计费") {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
openHcPosRevenueChargingCreateModal(doc);
|
||
return;
|
||
}
|
||
if (label === "账期审核") {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
openHcPosRevenueChargingAuditModal(doc);
|
||
}
|
||
},
|
||
true
|
||
);
|
||
doc.body.dataset.codexRevenueChargingInstalled = "1";
|
||
}
|
||
|
||
function openHcPosRevenueCollectionDetail(doc, action) {
|
||
if (!action) {
|
||
return;
|
||
}
|
||
openHcPosBudgetModal(
|
||
doc,
|
||
`
|
||
<h4>收款操作详情</h4>
|
||
<div class="codex-budget-card">
|
||
<div class="codex-budget-detail-list">
|
||
${renderHcPosRevenueSummaryRows([
|
||
{ label: "操作类型", value: action.type || "-" },
|
||
{ label: "目标账期", value: action.period || "-" },
|
||
{ label: "涉及金额", value: action.amount || "¥0.00" },
|
||
{ label: "处理状态", value: action.status || "-" },
|
||
{ label: "操作时间", value: action.createdAt || "-" },
|
||
{ label: "操作人", value: action.operator || "-" },
|
||
{ label: "备注", value: action.note || "-" }
|
||
])}
|
||
</div>
|
||
</div>
|
||
<div class="codex-budget-modal-actions">
|
||
<button type="button" data-action="close">关闭</button>
|
||
</div>
|
||
`,
|
||
[
|
||
{
|
||
selector: "[data-action='close']",
|
||
bind(target) {
|
||
target.addEventListener("click", () => closeHcPosBudgetModal(doc));
|
||
}
|
||
}
|
||
]
|
||
);
|
||
}
|
||
|
||
function renderHcPosRevenueCollectionPanel(doc) {
|
||
const state = readHcPosRevenueFlowState(doc.defaultView);
|
||
const panel = ensureHcPosBudgetPanel(doc, "__codex_revenue_collection_panel__");
|
||
const adjustCount = state.collectionActions.filter((item) => item.type === "数据修正").length;
|
||
const reconcileCount = state.collectionActions.filter((item) => /对账/.test(item.type)).length;
|
||
const rows = state.collectionActions
|
||
.slice(0, 8)
|
||
.map(
|
||
(item) => `
|
||
<tr>
|
||
<td>${escapeHcPosBudgetHtml(item.type)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.period)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.amount)}</td>
|
||
<td>${renderHcPosBudgetStatus(item.status)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.createdAt)}</td>
|
||
<td>
|
||
<div class="codex-budget-row-actions">
|
||
<button type="button" data-view-collection-action="${escapeHcPosBudgetHtml(item.id)}">查看详情</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`
|
||
)
|
||
.join("");
|
||
panel.innerHTML = `
|
||
<div class="codex-budget-panel-head">
|
||
<div class="codex-budget-panel-title">
|
||
<strong>本地收款操作台</strong>
|
||
<span>补齐“数据修正 / 更新第三方到账信息 / 全部对账 / 部分对账”四类关键动作。</span>
|
||
</div>
|
||
<span class="codex-budget-pill">本地功能</span>
|
||
</div>
|
||
<div class="codex-budget-grid">
|
||
<div class="codex-budget-card">
|
||
<strong>操作概览</strong>
|
||
<div class="codex-budget-detail-list">
|
||
${renderHcPosRevenueSummaryRows([
|
||
{ label: "本地收款操作", value: String(state.collectionActions.length) },
|
||
{ label: "数据修正", value: String(adjustCount) },
|
||
{ label: "对账动作", value: String(reconcileCount) }
|
||
])}
|
||
</div>
|
||
</div>
|
||
<div class="codex-budget-card">
|
||
<strong>使用说明</strong>
|
||
<p>点击原页面里的“数据修正 / 更新第三方到账信息 / 全部对账 / 部分对账”即可写入本地操作记录,方便镜像演示。</p>
|
||
</div>
|
||
</div>
|
||
<div class="codex-budget-card" style="margin-top: 12px;">
|
||
<strong>最近操作</strong>
|
||
<table>
|
||
<thead><tr><th>操作类型</th><th>目标账期</th><th>涉及金额</th><th>状态</th><th>时间</th><th>操作</th></tr></thead>
|
||
<tbody>${rows || '<tr><td colspan="6">暂无本地收款操作</td></tr>'}</tbody>
|
||
</table>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function openHcPosRevenueCollectionActionModal(doc, type) {
|
||
openHcPosBudgetModal(
|
||
doc,
|
||
`
|
||
<h4>${escapeHcPosBudgetHtml(type)}</h4>
|
||
<div class="codex-budget-form">
|
||
<label><span>目标账期</span><input data-field="period" value="2026-04"></label>
|
||
<label><span>涉及金额</span><input data-field="amount" value="256.80"></label>
|
||
<label><span>操作人</span><input data-field="operator" value="本地镜像"></label>
|
||
<label><span>处理状态</span><select data-field="status"><option>已处理</option><option>处理中</option></select></label>
|
||
<label class="full"><span>备注</span><textarea data-field="note">${escapeHcPosBudgetHtml(`${type} 已在镜像中记录,用于收入管理操作演示。`)}</textarea></label>
|
||
</div>
|
||
<div class="codex-budget-modal-actions">
|
||
<button type="button" data-action="close">取消</button>
|
||
<button type="button" data-action="save" class="codex-primary">确认记录</button>
|
||
</div>
|
||
`,
|
||
[
|
||
{
|
||
selector: "[data-action='close']",
|
||
bind(target) {
|
||
target.addEventListener("click", () => closeHcPosBudgetModal(doc));
|
||
}
|
||
},
|
||
{
|
||
selector: "[data-action='save']",
|
||
bind(target, mask) {
|
||
target.addEventListener("click", () => {
|
||
const win = doc.defaultView;
|
||
const state = readHcPosRevenueFlowState(win);
|
||
upsertHcPosRevenueCollectionAction(state, {
|
||
id: createHcPosRevenueNumber("collection-"),
|
||
type,
|
||
period: mask.querySelector("[data-field='period']").value.trim() || "2026-04",
|
||
amount: formatHcPosRevenueAmount(mask.querySelector("[data-field='amount']").value.trim() || "0.00"),
|
||
operator: mask.querySelector("[data-field='operator']").value.trim() || "本地镜像",
|
||
status: mask.querySelector("[data-field='status']").value.trim() || "已处理",
|
||
createdAt: getHcPosBudgetTimestamp(),
|
||
note: mask.querySelector("[data-field='note']").value.trim()
|
||
});
|
||
writeHcPosRevenueFlowState(win, state);
|
||
renderHcPosRevenueCollectionPanel(doc);
|
||
closeHcPosBudgetModal(doc);
|
||
});
|
||
}
|
||
}
|
||
]
|
||
);
|
||
}
|
||
|
||
function installHcPosRevenueCollectionRecordTools(doc) {
|
||
renderHcPosRevenueCollectionPanel(doc);
|
||
if (doc.body.dataset.codexRevenueCollectionInstalled === "1") {
|
||
return;
|
||
}
|
||
doc.addEventListener(
|
||
"click",
|
||
(event) => {
|
||
const button = event.target.closest("button, .el-button");
|
||
if (!button) {
|
||
return;
|
||
}
|
||
const viewAction = button.dataset.viewCollectionAction;
|
||
if (viewAction) {
|
||
const action = readHcPosRevenueFlowState(doc.defaultView).collectionActions.find((item) => item.id === viewAction);
|
||
if (!action) {
|
||
return;
|
||
}
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
openHcPosRevenueCollectionDetail(doc, action);
|
||
return;
|
||
}
|
||
const label = (button.textContent || "").replace(/\s+/g, " ").trim();
|
||
if (!["数据修正", "更新第三方到账信息", "全部对账", "部分对账"].includes(label)) {
|
||
return;
|
||
}
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
openHcPosRevenueCollectionActionModal(doc, label);
|
||
},
|
||
true
|
||
);
|
||
doc.body.dataset.codexRevenueCollectionInstalled = "1";
|
||
}
|
||
|
||
function renderHcPosRevenueRefundRecordPanel(doc) {
|
||
const state = readHcPosRevenueFlowState(doc.defaultView);
|
||
const panel = ensureHcPosBudgetPanel(doc, "__codex_revenue_refund_panel__");
|
||
const rows = state.refundRecords
|
||
.slice(0, 8)
|
||
.map(
|
||
(item) => `
|
||
<tr>
|
||
<td>${escapeHcPosBudgetHtml(item.refundNo)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.businessOrderNo)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.amount)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.period)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.createdAt)}</td>
|
||
<td>
|
||
<div class="codex-budget-row-actions">
|
||
<button type="button" data-view-refund="${escapeHcPosBudgetHtml(item.refundNo)}">查看详情</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`
|
||
)
|
||
.join("");
|
||
panel.innerHTML = `
|
||
<div class="codex-budget-panel-head">
|
||
<div class="codex-budget-panel-title">
|
||
<strong>本地退款记录台</strong>
|
||
<span>这里汇总从应收页发起的本地退款动作,便于镜像内串起收入闭环。</span>
|
||
</div>
|
||
<span class="codex-budget-pill">本地功能</span>
|
||
</div>
|
||
<div class="codex-budget-card">
|
||
<strong>退款记录</strong>
|
||
<table>
|
||
<thead><tr><th>退款单号</th><th>业务订单号</th><th>退款金额</th><th>账期</th><th>发起时间</th><th>操作</th></tr></thead>
|
||
<tbody>${rows || '<tr><td colspan="6">暂无本地退款记录</td></tr>'}</tbody>
|
||
</table>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function installHcPosRevenueRefundRecordTools(doc) {
|
||
renderHcPosRevenueRefundRecordPanel(doc);
|
||
if (doc.body.dataset.codexRevenueRefundInstalled === "1") {
|
||
return;
|
||
}
|
||
doc.addEventListener(
|
||
"click",
|
||
(event) => {
|
||
const button = event.target.closest("button, .el-button");
|
||
if (!button) {
|
||
return;
|
||
}
|
||
const refundNo = button.dataset.viewRefund;
|
||
if (!refundNo) {
|
||
return;
|
||
}
|
||
const record = readHcPosRevenueFlowState(doc.defaultView).refundRecords.find((item) => item.refundNo === refundNo);
|
||
if (!record) {
|
||
return;
|
||
}
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
openHcPosRevenueRefundDetail(doc, record);
|
||
},
|
||
true
|
||
);
|
||
doc.body.dataset.codexRevenueRefundInstalled = "1";
|
||
}
|
||
|
||
function renderHcPosRevenueSalesOrderPanel(doc) {
|
||
const state = readHcPosRevenueFlowState(doc.defaultView);
|
||
const panel = ensureHcPosBudgetPanel(doc, "__codex_revenue_sales_panel__");
|
||
const rows = state.salesOrders
|
||
.slice(0, 8)
|
||
.map(
|
||
(item) => `
|
||
<tr>
|
||
<td>${escapeHcPosBudgetHtml(item.orderNo)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.title)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.amount)}</td>
|
||
<td>${renderHcPosBudgetStatus(item.status)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.invoiceStatus)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.receiptStatus)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.createdAt)}</td>
|
||
<td>
|
||
<div class="codex-budget-row-actions">
|
||
<button type="button" data-view-sales="${escapeHcPosBudgetHtml(item.orderNo)}">查看详情</button>
|
||
<button type="button" data-download-sales="${escapeHcPosBudgetHtml(item.orderNo)}">下载收据</button>
|
||
<button type="button" data-ticket-sales="${escapeHcPosBudgetHtml(item.orderNo)}" class="codex-primary">开具发票</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`
|
||
)
|
||
.join("");
|
||
panel.innerHTML = `
|
||
<div class="codex-budget-panel-head">
|
||
<div class="codex-budget-panel-title">
|
||
<strong>本地销单工作台</strong>
|
||
<span>这里承接应收页发起的本地销单,并补齐查看、收据下载、开票动作。</span>
|
||
</div>
|
||
<span class="codex-budget-pill">本地功能</span>
|
||
</div>
|
||
<div class="codex-budget-card">
|
||
<strong>销单记录</strong>
|
||
<table>
|
||
<thead><tr><th>销单单号</th><th>业务标题</th><th>金额</th><th>状态</th><th>开票状态</th><th>收据状态</th><th>时间</th><th>操作</th></tr></thead>
|
||
<tbody>${rows || '<tr><td colspan="8">暂无本地销单记录</td></tr>'}</tbody>
|
||
</table>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function installHcPosRevenueSalesOrderTools(doc) {
|
||
renderHcPosRevenueSalesOrderPanel(doc);
|
||
if (doc.body.dataset.codexRevenueSalesInstalled === "1") {
|
||
return;
|
||
}
|
||
doc.addEventListener(
|
||
"click",
|
||
(event) => {
|
||
const button = event.target.closest("button, .el-button");
|
||
if (!button) {
|
||
return;
|
||
}
|
||
const viewSales = button.dataset.viewSales;
|
||
const downloadSales = button.dataset.downloadSales;
|
||
const ticketSales = button.dataset.ticketSales;
|
||
const state = readHcPosRevenueFlowState(doc.defaultView);
|
||
if (viewSales) {
|
||
const record = state.salesOrders.find((item) => item.orderNo === viewSales);
|
||
if (!record) {
|
||
return;
|
||
}
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
openHcPosRevenueSalesDetail(doc, record);
|
||
return;
|
||
}
|
||
if (downloadSales) {
|
||
const record = state.salesOrders.find((item) => item.orderNo === downloadSales);
|
||
if (!record) {
|
||
return;
|
||
}
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
downloadHcPosRevenueReceipt(doc, record);
|
||
renderHcPosRevenueSalesOrderPanel(doc);
|
||
return;
|
||
}
|
||
if (ticketSales) {
|
||
const record = state.salesOrders.find((item) => item.orderNo === ticketSales);
|
||
if (!record) {
|
||
return;
|
||
}
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
openHcPosRevenueTicketCreateModal(doc, record);
|
||
return;
|
||
}
|
||
const label = (button.textContent || "").replace(/\s+/g, " ").trim();
|
||
if (!["查看", "下载收据", "开具发票", "手动开收据"].includes(label)) {
|
||
return;
|
||
}
|
||
const row = button.closest("tr");
|
||
const record = buildHcPosRevenueSalesRecordFromRow(row);
|
||
if (!record) {
|
||
return;
|
||
}
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
if (label === "查看") {
|
||
openHcPosRevenueSalesDetail(doc, record);
|
||
return;
|
||
}
|
||
if (label === "下载收据" || label === "手动开收据") {
|
||
downloadHcPosRevenueReceipt(doc, record);
|
||
renderHcPosRevenueSalesOrderPanel(doc);
|
||
return;
|
||
}
|
||
openHcPosRevenueTicketCreateModal(doc, record);
|
||
},
|
||
true
|
||
);
|
||
doc.body.dataset.codexRevenueSalesInstalled = "1";
|
||
}
|
||
|
||
function renderHcPosRevenueElectronicBillPanel(doc) {
|
||
const state = readHcPosRevenueFlowState(doc.defaultView);
|
||
const panel = ensureHcPosBudgetPanel(doc, "__codex_revenue_ticket_panel__");
|
||
const rows = state.tickets
|
||
.slice(0, 8)
|
||
.map(
|
||
(item) => `
|
||
<tr>
|
||
<td>${escapeHcPosBudgetHtml(item.ticketNo)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.orderNo)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.title)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.amount)}</td>
|
||
<td>${renderHcPosBudgetStatus(item.status)}</td>
|
||
<td>${escapeHcPosBudgetHtml(item.createdAt)}</td>
|
||
<td>
|
||
<div class="codex-budget-row-actions">
|
||
<button type="button" data-view-ticket="${escapeHcPosBudgetHtml(item.ticketNo)}">查看</button>
|
||
<button type="button" data-download-ticket="${escapeHcPosBudgetHtml(item.ticketNo)}">下载</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`
|
||
)
|
||
.join("");
|
||
panel.innerHTML = `
|
||
<div class="codex-budget-panel-head">
|
||
<div class="codex-budget-panel-title">
|
||
<strong>本地电子票据台</strong>
|
||
<span>这里展示从销单页开具的本地票据,补齐查看与下载动作。</span>
|
||
</div>
|
||
<span class="codex-budget-pill">本地功能</span>
|
||
</div>
|
||
<div class="codex-budget-card">
|
||
<strong>票据记录</strong>
|
||
<table>
|
||
<thead><tr><th>票据编号</th><th>关联订单</th><th>票据标题</th><th>金额</th><th>状态</th><th>时间</th><th>操作</th></tr></thead>
|
||
<tbody>${rows || '<tr><td colspan="7">暂无本地票据记录</td></tr>'}</tbody>
|
||
</table>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function installHcPosRevenueElectronicBillTools(doc) {
|
||
renderHcPosRevenueElectronicBillPanel(doc);
|
||
if (doc.body.dataset.codexRevenueTicketInstalled === "1") {
|
||
return;
|
||
}
|
||
doc.addEventListener(
|
||
"click",
|
||
(event) => {
|
||
const button = event.target.closest("button, .el-button");
|
||
if (!button) {
|
||
return;
|
||
}
|
||
const state = readHcPosRevenueFlowState(doc.defaultView);
|
||
const viewTicket = button.dataset.viewTicket;
|
||
const downloadTicket = button.dataset.downloadTicket;
|
||
if (viewTicket) {
|
||
const ticket = state.tickets.find((item) => item.ticketNo === viewTicket);
|
||
if (!ticket) {
|
||
return;
|
||
}
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
openHcPosRevenueTicketDetail(doc, ticket);
|
||
return;
|
||
}
|
||
if (downloadTicket) {
|
||
const ticket = state.tickets.find((item) => item.ticketNo === downloadTicket);
|
||
if (!ticket) {
|
||
return;
|
||
}
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
downloadHcPosRevenueFile(
|
||
doc,
|
||
`${ticket.ticketNo}.txt`,
|
||
`票据编号: ${ticket.ticketNo}\n关联订单: ${ticket.orderNo}\n票据标题: ${ticket.title}\n票据金额: ${ticket.amount}\n票据状态: ${ticket.status}`
|
||
);
|
||
return;
|
||
}
|
||
const label = (button.textContent || "").replace(/\s+/g, " ").trim();
|
||
if (!["查看", "下载", "生成票据"].includes(label)) {
|
||
return;
|
||
}
|
||
const ticket = buildHcPosRevenueTicketFromRow(button.closest("tr"));
|
||
if (!ticket) {
|
||
return;
|
||
}
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
if (label === "查看") {
|
||
openHcPosRevenueTicketDetail(doc, ticket);
|
||
return;
|
||
}
|
||
if (label === "下载") {
|
||
downloadHcPosRevenueFile(
|
||
doc,
|
||
`${ticket.ticketNo}.txt`,
|
||
`票据编号: ${ticket.ticketNo}\n关联订单: ${ticket.orderNo}\n票据金额: ${ticket.amount}\n票据状态: ${ticket.status}`
|
||
);
|
||
return;
|
||
}
|
||
const nextState = readHcPosRevenueFlowState(doc.defaultView);
|
||
upsertHcPosRevenueTicket(nextState, {
|
||
...ticket,
|
||
ticketNo: createHcPosRevenueNumber("PJ"),
|
||
status: "已开票",
|
||
createdAt: getHcPosBudgetTimestamp()
|
||
});
|
||
writeHcPosRevenueFlowState(doc.defaultView, nextState);
|
||
renderHcPosRevenueElectronicBillPanel(doc);
|
||
},
|
||
true
|
||
);
|
||
doc.body.dataset.codexRevenueTicketInstalled = "1";
|
||
}
|
||
|
||
function buildHcPosForecastSeed() {
|
||
return {
|
||
plans: [
|
||
{ id: "fc-2027-01", year: "2027", budgetType: "收入预算", name: "预算预测-A", status: "启用", operator: "郭晓", updatedAt: "2026-04-05 10:00" }
|
||
],
|
||
items: [
|
||
{ id: "item-01", budgetType: "收入预算", businessItem: "物业管理费", subject: "物业管理费收入", operator: "郭晓", updatedAt: "2026-04-05 10:05" },
|
||
{ id: "item-02", budgetType: "支出预算", businessItem: "工程维护费", subject: "能源服务成本", operator: "何琳", updatedAt: "2026-04-05 10:08" }
|
||
]
|
||
};
|
||
}
|
||
|
||
function renderHcPosForecastPanel(doc) {
|
||
const win = doc.defaultView;
|
||
const storageKey = "__codex_budget_forecast_state__";
|
||
const state = readHcPosBudgetState(win, storageKey, buildHcPosForecastSeed());
|
||
const panel = ensureHcPosBudgetPanel(doc, "__codex_budget_forecast_panel__");
|
||
const planRows = state.plans
|
||
.map(
|
||
(item) => `<tr><td>${item.year}</td><td>${item.budgetType}</td><td>${item.name}</td><td>${item.status}</td><td>${item.operator}</td><td>${item.updatedAt}</td></tr>`
|
||
)
|
||
.join("");
|
||
const itemRows = state.items
|
||
.map(
|
||
(item) => `<tr><td>${item.budgetType}</td><td>${item.businessItem}</td><td>${item.subject}</td><td>${item.operator}</td><td>${item.updatedAt}</td></tr>`
|
||
)
|
||
.join("");
|
||
panel.innerHTML = `
|
||
<div class="codex-budget-panel-head">
|
||
<div class="codex-budget-panel-title">
|
||
<strong>本地预算预测工作台</strong>
|
||
<span>让“新增预算预测 / 添加业务项 / 预算表预览”在镜像里可用,数据保存在本地浏览器。</span>
|
||
</div>
|
||
<span class="codex-budget-pill">本地功能</span>
|
||
</div>
|
||
<div class="codex-budget-grid">
|
||
<div class="codex-budget-card">
|
||
<strong>预算预测方案</strong>
|
||
<table>
|
||
<thead><tr><th>年份</th><th>预算类型</th><th>方案名称</th><th>状态</th><th>操作人</th><th>更新时间</th></tr></thead>
|
||
<tbody>${planRows || '<tr><td colspan="6">暂无本地方案</td></tr>'}</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="codex-budget-card">
|
||
<strong>本地业务项</strong>
|
||
<table>
|
||
<thead><tr><th>预算类型</th><th>业务项</th><th>科目</th><th>操作人</th><th>更新时间</th></tr></thead>
|
||
<tbody>${itemRows || '<tr><td colspan="5">暂无本地业务项</td></tr>'}</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function installHcPosForecastBudgetTools(doc) {
|
||
if (doc.body.dataset.codexForecastBudgetInstalled === "1") {
|
||
renderHcPosForecastPanel(doc);
|
||
return;
|
||
}
|
||
ensureHcPosBudgetToolStyle(doc);
|
||
renderHcPosForecastPanel(doc);
|
||
const win = doc.defaultView;
|
||
const storageKey = "__codex_budget_forecast_state__";
|
||
|
||
doc.addEventListener(
|
||
"click",
|
||
(event) => {
|
||
const button = event.target.closest("button, .el-button");
|
||
if (!button) {
|
||
return;
|
||
}
|
||
const label = (button.textContent || "").replace(/\s+/g, " ").trim();
|
||
if (!["新增预算预测", "添加业务项", "预算表预览"].includes(label)) {
|
||
return;
|
||
}
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
const state = readHcPosBudgetState(win, storageKey, buildHcPosForecastSeed());
|
||
|
||
if (label === "预算表预览") {
|
||
openHcPosBudgetModal(
|
||
doc,
|
||
`
|
||
<h4>预算表预览</h4>
|
||
<div class="codex-budget-preview">
|
||
<pre>${JSON.stringify(state.plans, null, 2)}</pre>
|
||
<pre>${JSON.stringify(state.items, null, 2)}</pre>
|
||
</div>
|
||
<div class="codex-budget-modal-actions">
|
||
<button type="button" data-action="close">关闭</button>
|
||
</div>
|
||
`,
|
||
[
|
||
{
|
||
selector: "[data-action='close']",
|
||
bind(target) {
|
||
target.addEventListener("click", () => closeHcPosBudgetModal(doc));
|
||
}
|
||
}
|
||
]
|
||
);
|
||
return;
|
||
}
|
||
|
||
const isPlan = label === "新增预算预测";
|
||
openHcPosBudgetModal(
|
||
doc,
|
||
`
|
||
<h4>${label}</h4>
|
||
<div class="codex-budget-form">
|
||
<label>
|
||
<span>年份</span>
|
||
<input data-field="year" value="2027">
|
||
</label>
|
||
<label>
|
||
<span>预算类型</span>
|
||
<select data-field="budgetType">
|
||
<option>收入预算</option>
|
||
<option>支出预算</option>
|
||
</select>
|
||
</label>
|
||
<label class="${isPlan ? "" : "full"}">
|
||
<span>${isPlan ? "方案名称" : "业务项"}</span>
|
||
<input data-field="primary" value="${isPlan ? "预算预测-本地新增" : "新增业务项"}">
|
||
</label>
|
||
${isPlan ? "" : `
|
||
<label class="full">
|
||
<span>科目</span>
|
||
<input data-field="subject" value="物业管理费收入">
|
||
</label>
|
||
`}
|
||
</div>
|
||
<div class="codex-budget-modal-actions">
|
||
<button type="button" data-action="close">取消</button>
|
||
<button type="button" data-action="save" class="codex-primary">保存</button>
|
||
</div>
|
||
`,
|
||
[
|
||
{
|
||
selector: "[data-action='close']",
|
||
bind(target) {
|
||
target.addEventListener("click", () => closeHcPosBudgetModal(doc));
|
||
}
|
||
},
|
||
{
|
||
selector: "[data-action='save']",
|
||
bind(target, mask) {
|
||
target.addEventListener("click", () => {
|
||
const year = mask.querySelector("[data-field='year']").value.trim();
|
||
const budgetType = mask.querySelector("[data-field='budgetType']").value.trim();
|
||
const primary = mask.querySelector("[data-field='primary']").value.trim();
|
||
const subject = mask.querySelector("[data-field='subject']")?.value.trim();
|
||
const now = "2026-04-05 12:00";
|
||
if (isPlan) {
|
||
state.plans.unshift({
|
||
id: `fc-${Date.now()}`,
|
||
year,
|
||
budgetType,
|
||
name: primary || "未命名方案",
|
||
status: "启用",
|
||
operator: "本地镜像",
|
||
updatedAt: now
|
||
});
|
||
} else {
|
||
state.items.unshift({
|
||
id: `item-${Date.now()}`,
|
||
budgetType,
|
||
businessItem: primary || "未命名业务项",
|
||
subject: subject || "-",
|
||
operator: "本地镜像",
|
||
updatedAt: now
|
||
});
|
||
}
|
||
writeHcPosBudgetState(win, storageKey, state);
|
||
renderHcPosForecastPanel(doc);
|
||
closeHcPosBudgetModal(doc);
|
||
});
|
||
}
|
||
}
|
||
]
|
||
);
|
||
},
|
||
true
|
||
);
|
||
doc.body.dataset.codexForecastBudgetInstalled = "1";
|
||
}
|
||
|
||
function buildHcPosOperatingBudgetSeed() {
|
||
return {
|
||
rows: [
|
||
{ id: "op-1", year: "2026", budgetType: "业务项", chargeItem: "0512测试", subject: "物业管理费收入", businessCategory: "物业管理费", status: "启用", operator: "肖英", updatedAt: "2025-12-01 18:07:11" },
|
||
{ id: "op-2", year: "2026", budgetType: "业务项", chargeItem: "测试电梯楼层收费", subject: "物业管理费收入", businessCategory: "物业管理费", status: "启用", operator: "肖英", updatedAt: "2025-11-12 09:21:45" }
|
||
]
|
||
};
|
||
}
|
||
|
||
function renderHcPosOperatingBudgetPanel(doc) {
|
||
const win = doc.defaultView;
|
||
const storageKey = "__codex_operating_budget_state__";
|
||
const state = readHcPosBudgetState(win, storageKey, buildHcPosOperatingBudgetSeed());
|
||
const panel = ensureHcPosBudgetPanel(doc, "__codex_operating_budget_panel__");
|
||
const rows = state.rows
|
||
.map(
|
||
(item) => `
|
||
<tr>
|
||
<td>${item.year}</td>
|
||
<td>${item.budgetType}</td>
|
||
<td>${item.chargeItem}</td>
|
||
<td>${item.subject}</td>
|
||
<td>${item.businessCategory}</td>
|
||
<td>${item.status}</td>
|
||
<td>${item.operator}</td>
|
||
<td>${item.updatedAt}</td>
|
||
<td>
|
||
<div class="codex-budget-row-actions">
|
||
<button type="button" data-edit-id="${item.id}" class="codex-primary">编辑</button>
|
||
<button type="button" data-delete-id="${item.id}" class="codex-danger">删除</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`
|
||
)
|
||
.join("");
|
||
panel.innerHTML = `
|
||
<div class="codex-budget-panel-head">
|
||
<div class="codex-budget-panel-title">
|
||
<strong>本地业务预算工作台</strong>
|
||
<span>让“导入数据 / 新增 / 编辑”在镜像里可用,变更仅保存在本地浏览器。</span>
|
||
</div>
|
||
<span class="codex-budget-pill">本地功能</span>
|
||
</div>
|
||
<div class="codex-budget-card">
|
||
<strong>本地预算行</strong>
|
||
<table>
|
||
<thead><tr><th>年份</th><th>预算类型</th><th>收费项</th><th>科目</th><th>收入业务分类</th><th>状态</th><th>操作人</th><th>更新时间</th><th>操作</th></tr></thead>
|
||
<tbody>${rows || '<tr><td colspan="9">暂无本地预算行</td></tr>'}</tbody>
|
||
</table>
|
||
</div>
|
||
`;
|
||
panel.querySelectorAll("[data-edit-id]").forEach((button) => {
|
||
button.addEventListener("click", () => openHcPosOperatingBudgetEditor(doc, button.dataset.editId));
|
||
});
|
||
panel.querySelectorAll("[data-delete-id]").forEach((button) => {
|
||
button.addEventListener("click", () => {
|
||
const next = readHcPosBudgetState(win, storageKey, buildHcPosOperatingBudgetSeed());
|
||
next.rows = next.rows.filter((item) => item.id !== button.dataset.deleteId);
|
||
writeHcPosBudgetState(win, storageKey, next);
|
||
renderHcPosOperatingBudgetPanel(doc);
|
||
});
|
||
});
|
||
}
|
||
|
||
function openHcPosOperatingBudgetEditor(doc, rowId = null) {
|
||
const win = doc.defaultView;
|
||
const storageKey = "__codex_operating_budget_state__";
|
||
const state = readHcPosBudgetState(win, storageKey, buildHcPosOperatingBudgetSeed());
|
||
const current = state.rows.find((item) => item.id === rowId) || {
|
||
id: `op-${Date.now()}`,
|
||
year: "2026",
|
||
budgetType: "业务项",
|
||
chargeItem: "",
|
||
subject: "物业管理费收入",
|
||
businessCategory: "物业管理费",
|
||
status: "启用",
|
||
operator: "本地镜像",
|
||
updatedAt: "2026-04-05 12:00"
|
||
};
|
||
openHcPosBudgetModal(
|
||
doc,
|
||
`
|
||
<h4>${rowId ? "编辑预算行" : "新增预算行"}</h4>
|
||
<div class="codex-budget-form">
|
||
<label><span>年份</span><input data-field="year" value="${current.year}"></label>
|
||
<label><span>预算类型</span><input data-field="budgetType" value="${current.budgetType}"></label>
|
||
<label><span>收费项</span><input data-field="chargeItem" value="${current.chargeItem}"></label>
|
||
<label><span>科目</span><input data-field="subject" value="${current.subject}"></label>
|
||
<label><span>收入业务分类</span><input data-field="businessCategory" value="${current.businessCategory}"></label>
|
||
<label><span>状态</span><select data-field="status"><option ${current.status === "启用" ? "selected" : ""}>启用</option><option ${current.status === "禁用" ? "selected" : ""}>禁用</option></select></label>
|
||
</div>
|
||
<div class="codex-budget-modal-actions">
|
||
<button type="button" data-action="close">取消</button>
|
||
<button type="button" data-action="save" class="codex-primary">保存</button>
|
||
</div>
|
||
`,
|
||
[
|
||
{
|
||
selector: "[data-action='close']",
|
||
bind(target) {
|
||
target.addEventListener("click", () => closeHcPosBudgetModal(doc));
|
||
}
|
||
},
|
||
{
|
||
selector: "[data-action='save']",
|
||
bind(target, mask) {
|
||
target.addEventListener("click", () => {
|
||
const next = readHcPosBudgetState(win, storageKey, buildHcPosOperatingBudgetSeed());
|
||
const payload = {
|
||
id: current.id,
|
||
year: mask.querySelector("[data-field='year']").value.trim() || current.year,
|
||
budgetType: mask.querySelector("[data-field='budgetType']").value.trim() || current.budgetType,
|
||
chargeItem: mask.querySelector("[data-field='chargeItem']").value.trim() || current.chargeItem || "未命名收费项",
|
||
subject: mask.querySelector("[data-field='subject']").value.trim() || current.subject,
|
||
businessCategory: mask.querySelector("[data-field='businessCategory']").value.trim() || current.businessCategory,
|
||
status: mask.querySelector("[data-field='status']").value.trim() || current.status,
|
||
operator: "本地镜像",
|
||
updatedAt: "2026-04-05 12:00"
|
||
};
|
||
const existingIndex = next.rows.findIndex((item) => item.id === current.id);
|
||
if (existingIndex >= 0) {
|
||
next.rows.splice(existingIndex, 1, payload);
|
||
} else {
|
||
next.rows.unshift(payload);
|
||
}
|
||
writeHcPosBudgetState(win, storageKey, next);
|
||
renderHcPosOperatingBudgetPanel(doc);
|
||
closeHcPosBudgetModal(doc);
|
||
});
|
||
}
|
||
}
|
||
]
|
||
);
|
||
}
|
||
|
||
function installHcPosOperatingBudgetTools(doc) {
|
||
if (doc.body.dataset.codexOperatingBudgetInstalled === "1") {
|
||
renderHcPosOperatingBudgetPanel(doc);
|
||
return;
|
||
}
|
||
ensureHcPosBudgetToolStyle(doc);
|
||
renderHcPosOperatingBudgetPanel(doc);
|
||
const win = doc.defaultView;
|
||
const storageKey = "__codex_operating_budget_state__";
|
||
|
||
doc.addEventListener(
|
||
"click",
|
||
(event) => {
|
||
const button = event.target.closest("button, .el-button");
|
||
if (!button) {
|
||
return;
|
||
}
|
||
const label = (button.textContent || "").replace(/\s+/g, " ").trim();
|
||
if (!["导入数据", "新增", "编辑", "导出模版"].includes(label)) {
|
||
return;
|
||
}
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
|
||
if (label === "导出模版") {
|
||
const csv = "年份,预算类型,收费项,科目,收入业务分类,状态\\n2026,业务项,示例收费项,物业管理费收入,物业管理费,启用\\n";
|
||
const blob = new Blob([csv], { type: "text/csv;charset=utf-8" });
|
||
const url = win.URL.createObjectURL(blob);
|
||
const anchor = doc.createElement("a");
|
||
anchor.href = url;
|
||
anchor.download = "业务预算导入模板.csv";
|
||
anchor.click();
|
||
win.URL.revokeObjectURL(url);
|
||
return;
|
||
}
|
||
|
||
if (label === "导入数据") {
|
||
openHcPosBudgetModal(
|
||
doc,
|
||
`
|
||
<h4>导入本地预算数据</h4>
|
||
<div class="codex-budget-form">
|
||
<label class="full">
|
||
<span>每行格式:年份,预算类型,收费项,科目,收入业务分类,状态</span>
|
||
<textarea data-field="lines" placeholder="2026,业务项,新增收费项,物业管理费收入,物业管理费,启用"></textarea>
|
||
</label>
|
||
</div>
|
||
<div class="codex-budget-modal-actions">
|
||
<button type="button" data-action="close">取消</button>
|
||
<button type="button" data-action="save" class="codex-primary">导入</button>
|
||
</div>
|
||
`,
|
||
[
|
||
{
|
||
selector: "[data-action='close']",
|
||
bind(target) {
|
||
target.addEventListener("click", () => closeHcPosBudgetModal(doc));
|
||
}
|
||
},
|
||
{
|
||
selector: "[data-action='save']",
|
||
bind(target, mask) {
|
||
target.addEventListener("click", () => {
|
||
const lines = mask.querySelector("[data-field='lines']").value.split("\n").map((line) => line.trim()).filter(Boolean);
|
||
const next = readHcPosBudgetState(win, storageKey, buildHcPosOperatingBudgetSeed());
|
||
lines.forEach((line) => {
|
||
const [year, budgetType, chargeItem, subject, businessCategory, status] = line.split(",").map((item) => item.trim());
|
||
if (!chargeItem) {
|
||
return;
|
||
}
|
||
next.rows.unshift({
|
||
id: `op-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`,
|
||
year: year || "2026",
|
||
budgetType: budgetType || "业务项",
|
||
chargeItem,
|
||
subject: subject || "物业管理费收入",
|
||
businessCategory: businessCategory || "物业管理费",
|
||
status: status || "启用",
|
||
operator: "本地导入",
|
||
updatedAt: "2026-04-05 12:00"
|
||
});
|
||
});
|
||
writeHcPosBudgetState(win, storageKey, next);
|
||
renderHcPosOperatingBudgetPanel(doc);
|
||
closeHcPosBudgetModal(doc);
|
||
});
|
||
}
|
||
}
|
||
]
|
||
);
|
||
return;
|
||
}
|
||
|
||
if (label === "新增") {
|
||
openHcPosOperatingBudgetEditor(doc);
|
||
return;
|
||
}
|
||
|
||
if (label === "编辑") {
|
||
openHcPosOperatingBudgetEditor(doc);
|
||
}
|
||
},
|
||
true
|
||
);
|
||
doc.body.dataset.codexOperatingBudgetInstalled = "1";
|
||
}
|
||
|
||
function installHcPosCockpitLinks(doc) {
|
||
if (!doc.querySelector("#r2-view")) {
|
||
return;
|
||
}
|
||
if (doc.body?.dataset.codexCockpitDelegated !== "1") {
|
||
doc.body.addEventListener(
|
||
"click",
|
||
(event) => {
|
||
const targetNode =
|
||
event.target.closest(".web_head .down_nav a") ||
|
||
event.target.closest(".web_head .item > span") ||
|
||
event.target.closest(".bottom-actions .action-btn") ||
|
||
event.target.closest(".bottom-actions .action-btn span") ||
|
||
event.target.closest(".projectReportBox .el-button") ||
|
||
event.target.closest(".projectReportBox .el-button span") ||
|
||
event.target.closest(".link-title") ||
|
||
event.target.closest(".flowm-card__header span");
|
||
if (!targetNode) {
|
||
return;
|
||
}
|
||
const target = resolveHcPosCockpitTarget(targetNode);
|
||
if (!target) {
|
||
return;
|
||
}
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
navigateHcPosCockpitLink(target);
|
||
},
|
||
true
|
||
);
|
||
doc.body.dataset.codexCockpitDelegated = "1";
|
||
}
|
||
const clickableNodes = [
|
||
...doc.querySelectorAll(".web_head .down_nav a"),
|
||
...doc.querySelectorAll(".web_head .left > .item > span"),
|
||
...doc.querySelectorAll(".web_head .right > .item > span"),
|
||
...doc.querySelectorAll(".bottom-actions .action-btn"),
|
||
...doc.querySelectorAll(".bottom-actions .action-btn span"),
|
||
...doc.querySelectorAll(".projectReportBox .el-button"),
|
||
...doc.querySelectorAll(".projectReportBox .el-button span"),
|
||
...doc.querySelectorAll(".link-title"),
|
||
...doc.querySelectorAll(".flowm-card__header span")
|
||
];
|
||
clickableNodes.forEach((node) => {
|
||
if (node.dataset.codexCockpitBound === "1") {
|
||
return;
|
||
}
|
||
const label = (node.textContent || "").replace(/\s+/g, " ").trim();
|
||
if (label === "运营指数") {
|
||
node.style.cursor = "default";
|
||
return;
|
||
}
|
||
const target = resolveHcPosCockpitTarget(node);
|
||
if (!target) {
|
||
if (/^by:/.test(label)) {
|
||
node.style.cursor = "default";
|
||
}
|
||
return;
|
||
}
|
||
node.style.cursor = "pointer";
|
||
if (node.tagName === "A") {
|
||
node.setAttribute("href", target.runtime === "hcetms" ? `/__mirror/runtime/hc-etms-dashboard/?v=${VERSION || "20260405p"}#${target.path || ""}` : `#${target.path}`);
|
||
}
|
||
node.addEventListener(
|
||
"click",
|
||
(event) => {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
navigateHcPosCockpitLink(target);
|
||
},
|
||
true
|
||
);
|
||
node.dataset.codexCockpitBound = "1";
|
||
});
|
||
}
|
||
|
||
function applyGlobalFrameSkin(iframe) {
|
||
try {
|
||
const doc = iframe && iframe.contentDocument;
|
||
if (!doc || !doc.head) {
|
||
return;
|
||
}
|
||
if (!doc.getElementById("__codex_runtime_frame_skin__")) {
|
||
const style = doc.createElement("style");
|
||
style.id = "__codex_runtime_frame_skin__";
|
||
style.textContent = `
|
||
.sidebar-container,
|
||
.fixed-header,
|
||
.tags-view-container,
|
||
.layout-tags-view-container,
|
||
.navbar,
|
||
.app-breadcrumb,
|
||
.el-breadcrumb,
|
||
.breadcrumb-container,
|
||
.tabList,
|
||
.right-menu,
|
||
.currentProject,
|
||
.AiBox,
|
||
.feedback-dialog,
|
||
.feedback-btn,
|
||
.need-border,
|
||
.hamburger-container,
|
||
.right-menu-item .international,
|
||
.right-menu-item .avatar-container {
|
||
display: none !important;
|
||
}
|
||
.main-container,
|
||
.main-container.hasTagsView,
|
||
.app-main,
|
||
.app-main > section,
|
||
.main-container .app-main {
|
||
margin: 0 !important;
|
||
padding: 0 !important;
|
||
width: 100% !important;
|
||
}
|
||
.el-scrollbar__wrap,
|
||
.scrollbar-wrapper {
|
||
margin-right: 0 !important;
|
||
margin-bottom: 0 !important;
|
||
}
|
||
body,
|
||
html {
|
||
background: #f5f7fa !important;
|
||
overflow: auto !important;
|
||
}
|
||
.app-main > section,
|
||
.app-main > .app-container,
|
||
.app-main .app-container,
|
||
.main-container .app-main > section {
|
||
padding-top: 0 !important;
|
||
}
|
||
.el-table__empty-block,
|
||
.el-table__empty-text {
|
||
display: none !important;
|
||
}
|
||
`;
|
||
doc.head.appendChild(style);
|
||
}
|
||
const tables = [...doc.querySelectorAll("table")];
|
||
const hasDataTable = tables.some((table) => table.querySelectorAll("tbody tr").length > 0);
|
||
if (hasDataTable) {
|
||
tables.forEach((table) => {
|
||
const wrapper = table.closest(".el-table");
|
||
const rows = table.querySelectorAll("tbody tr").length;
|
||
if (rows > 0) {
|
||
if (wrapper) {
|
||
wrapper.style.setProperty("display", "block", "important");
|
||
}
|
||
return;
|
||
}
|
||
if (wrapper) {
|
||
wrapper.style.display = "none";
|
||
}
|
||
});
|
||
}
|
||
const src = iframe.getAttribute("src") || "";
|
||
const editingActive = doc.body?.dataset.codexMirrorEditing === "1";
|
||
if (!editingActive) {
|
||
if (src.includes("/propertySMG/cleanManage/cleanMicrobrain/")) {
|
||
renderHcPosDashboard(doc, "cleanMicrobrain");
|
||
}
|
||
if (src.includes("/propertySMG/businessTaxCank/financeMicrobrain/")) {
|
||
renderHcPosDashboard(doc, "financeMicrobrain");
|
||
}
|
||
if (src.includes("/propertySMG/elevatorManage/elevatorDimension/")) {
|
||
renderHcPosDashboard(doc, "elevatorDimension");
|
||
}
|
||
if (src.includes("/propertySMG/equipmentManage/equipmentPortrait/")) {
|
||
renderHcPosDashboard(doc, "equipmentPortrait");
|
||
}
|
||
if (src.includes("/propertySMG/greenManage/greenMicroBrain/")) {
|
||
renderHcPosDashboard(doc, "greenMicroBrain");
|
||
}
|
||
if (src.includes("/propertySMG/parkingOperation/parkingMicroBrain/")) {
|
||
renderHcPosDashboard(doc, "parkingMicroBrain");
|
||
}
|
||
if (src.includes("/propertySMG/securityManage/securityBrain/")) {
|
||
renderHcPosDashboard(doc, "securityBrain");
|
||
}
|
||
if (src.includes("/propertySMG/customerOperations/customerPortrait/")) {
|
||
renderHcPosDashboard(doc, "customerPortrait");
|
||
}
|
||
if (src.includes("/propertySMG/energySourceOperat/notice/")) {
|
||
renderHcPosDashboard(doc, "energyNotice");
|
||
}
|
||
if (src.includes("/homeEnterpriseService/childCare/")) {
|
||
renderHcPosStaticPage(doc, "childCare");
|
||
}
|
||
if (src.includes("/homeEnterpriseService/express/")) {
|
||
renderHcPosStaticPage(doc, "express");
|
||
}
|
||
if (src.includes("/homeEnterpriseService/house/")) {
|
||
renderHcPosStaticPage(doc, "house");
|
||
}
|
||
if (src.includes("/homeEnterpriseService/housekeeping/")) {
|
||
renderHcPosStaticPage(doc, "housekeeping");
|
||
}
|
||
if (src.includes("/homeEnterpriseService/retirement/")) {
|
||
renderHcPosStaticPage(doc, "retirement");
|
||
}
|
||
if (src.includes("/propertySMG/businessTaxCank/bankEnterprise/")) {
|
||
renderHcPosStaticPage(doc, "bankEnterprise");
|
||
}
|
||
if (src.includes("/propertySMG/businessTaxCank/financeAdjust/financialAccount/")) {
|
||
renderHcPosStaticPage(doc, "financialAccount");
|
||
}
|
||
if (src.includes("/propertySMG/businessTaxCank/financeAdjust/financialVoucher/")) {
|
||
renderHcPosStaticPage(doc, "financialVoucher");
|
||
}
|
||
if (src.includes("/propertySMG/businessTaxCank/taxCoordination/")) {
|
||
renderHcPosStaticPage(doc, "taxCoordination");
|
||
}
|
||
if (src.includes("/communitySMG/serviceProvider/")) {
|
||
renderHcPosStaticPage(doc, "serviceProvider");
|
||
}
|
||
if (src.includes("/propertySMG/cleanManage/assessmentTraining/")) {
|
||
renderHcPosStaticPage(doc, "cleanAssessmentTraining");
|
||
}
|
||
if (src.includes("/propertySMG/cleanManage/spareParts/")) {
|
||
renderHcPosStaticPage(doc, "cleanSpareParts");
|
||
}
|
||
if (src.includes("/propertySMG/elevatorManage/assessmentTraining/")) {
|
||
renderHcPosStaticPage(doc, "elevatorAssessmentTraining");
|
||
}
|
||
if (src.includes("/propertySMG/elevatorManage/spareParts/")) {
|
||
renderHcPosStaticPage(doc, "elevatorSpareParts");
|
||
}
|
||
if (src.includes("/propertySMG/equipmentManage/assessmentTraining/")) {
|
||
renderHcPosStaticPage(doc, "equipmentAssessmentTraining");
|
||
}
|
||
if (src.includes("/propertySMG/equipmentManage/spareParts/")) {
|
||
renderHcPosStaticPage(doc, "equipmentSpareParts");
|
||
}
|
||
if (src.includes("/propertySMG/greenManage/assessmentTraining/")) {
|
||
renderHcPosStaticPage(doc, "greenAssessmentTraining");
|
||
}
|
||
if (src.includes("/propertySMG/greenManage/spareParts/")) {
|
||
renderHcPosStaticPage(doc, "greenSpareParts");
|
||
}
|
||
if (src.includes("/propertySMG/securityManage/assessmentTraining/")) {
|
||
renderHcPosStaticPage(doc, "securityAssessmentTraining");
|
||
}
|
||
if (src.includes("/propertySMG/securityManage/spareParts/")) {
|
||
renderHcPosStaticPage(doc, "securitySpareParts");
|
||
}
|
||
if (src.includes("/propertySMG/parkingOperation/assessmentTraining/")) {
|
||
renderHcPosStaticPage(doc, "parkingAssessmentTraining");
|
||
}
|
||
const waitPagePreset = Object.entries(HCPOS_WAIT_PAGE_ROUTE_PRESETS).find(([path]) => src.includes(path));
|
||
if (waitPagePreset) {
|
||
renderHcPosStaticPage(doc, waitPagePreset[1]);
|
||
}
|
||
if (src.includes("/communitySMG/personnelList/")) {
|
||
renderHcPosOverviewPanel(doc.getElementById("pane-personnelList"), HCPOS_OVERVIEW_PRESETS.personnelList);
|
||
renderHcPosSecondaryTab(doc, "keyCustomer", "pane-HEWC");
|
||
installSimpleTabSwitch(doc, {
|
||
primaryTabId: "tab-personnelList",
|
||
secondaryTabId: "tab-HEWC",
|
||
primaryPaneId: "pane-personnelList",
|
||
secondaryPaneId: "pane-HEWC",
|
||
storageKey: "__codex_personnel_list_tab__"
|
||
});
|
||
}
|
||
if (src.includes("/propertySMG/diagnosis/customizedPhysical/")) {
|
||
renderHcPosOverviewPanel(doc.getElementById("pane-physicaExamineList"), HCPOS_OVERVIEW_PRESETS.customizedPhysical);
|
||
renderHcPosSecondaryTab(doc, "customizedPhysicalRecord", "pane-HEWC");
|
||
installSimpleTabSwitch(doc, {
|
||
primaryTabId: "tab-physicaExamineList",
|
||
secondaryTabId: "tab-HEWC",
|
||
primaryPaneId: "pane-physicaExamineList",
|
||
secondaryPaneId: "pane-HEWC",
|
||
storageKey: "__codex_customized_physical_tab__"
|
||
});
|
||
}
|
||
if (src.includes("/propertySMG/diagnosis/specialPhysical/")) {
|
||
renderHcPosOverviewPanel(doc.getElementById("pane-physicaExamineList"), HCPOS_OVERVIEW_PRESETS.specialPhysical);
|
||
renderHcPosSecondaryTab(doc, "specialPhysicalRecord", "pane-HEWC");
|
||
installSimpleTabSwitch(doc, {
|
||
primaryTabId: "tab-physicaExamineList",
|
||
secondaryTabId: "tab-HEWC",
|
||
primaryPaneId: "pane-physicaExamineList",
|
||
secondaryPaneId: "pane-HEWC",
|
||
storageKey: "__codex_special_physical_tab__"
|
||
});
|
||
}
|
||
if (src.includes("/propertySMG/diagnosis/5Aphysical/")) {
|
||
renderHcPosOverviewPanel(doc.getElementById("pane-physicaExamineList"), HCPOS_OVERVIEW_PRESETS.fiveAPhysical);
|
||
renderHcPosSecondaryTab(doc, "fiveAPhysicalRecord", "pane-HEWC");
|
||
installSimpleTabSwitch(doc, {
|
||
primaryTabId: "tab-physicaExamineList",
|
||
secondaryTabId: "tab-HEWC",
|
||
primaryPaneId: "pane-physicaExamineList",
|
||
secondaryPaneId: "pane-HEWC",
|
||
storageKey: "__codex_fivea_physical_tab__"
|
||
});
|
||
}
|
||
if (src.includes("/propertySMG/businessTaxCank/financeReport/balanceSheet/")) {
|
||
renderHcPosStaticPage(doc, "balanceSheet");
|
||
}
|
||
if (src.includes("/propertySMG/businessTaxCank/financeReport/cashFlowStatement/")) {
|
||
renderHcPosStaticPage(doc, "cashFlowStatement");
|
||
}
|
||
if (src.includes("/propertySMG/businessTaxCank/financeReport/incomeStatement/")) {
|
||
renderHcPosStaticPage(doc, "incomeStatement");
|
||
}
|
||
if (src.includes("/propertySMG/businessTaxCank/financeReport/profitSurface/")) {
|
||
renderHcPosStaticPage(doc, "profitSurface");
|
||
}
|
||
if (src.includes("/propertySMG/businessTaxCank/financeReport/projectIncome/")) {
|
||
renderHcPosStaticPage(doc, "projectIncome");
|
||
}
|
||
if (src.includes("/propertySMG/businessTaxCank/laborQuotaManagement/laborQuota/")) {
|
||
renderHcPosStaticPage(doc, "laborQuota");
|
||
}
|
||
if (src.includes("/propertySMG/businessTaxCank/metadataManage/resourceManage/")) {
|
||
renderHcPosStaticPage(doc, "resourceManage");
|
||
}
|
||
if (src.includes("/propertySMG/businessTaxCank/revenueManage/receivable/")) {
|
||
installHcPosRevenueReceivableTools(doc);
|
||
}
|
||
if (src.includes("/propertySMG/businessTaxCank/revenueManage/charging/")) {
|
||
installHcPosRevenueChargingTools(doc);
|
||
}
|
||
if (src.includes("/propertySMG/businessTaxCank/revenueManage/collectionRecord/")) {
|
||
installHcPosRevenueCollectionRecordTools(doc);
|
||
}
|
||
if (src.includes("/propertySMG/businessTaxCank/revenueManage/refundRecord/")) {
|
||
installHcPosRevenueRefundRecordTools(doc);
|
||
}
|
||
if (src.includes("/propertySMG/businessTaxCank/revenueManage/salesOrder/")) {
|
||
installHcPosRevenueSalesOrderTools(doc);
|
||
}
|
||
if (src.includes("/propertySMG/businessTaxCank/revenueManage/electronicBill/")) {
|
||
installHcPosRevenueElectronicBillTools(doc);
|
||
}
|
||
if (src.includes("/propertySMG/businessTaxCank/budgetManage/budgetOrgan/")) {
|
||
installHcPosBudgetOrganTools(doc);
|
||
}
|
||
if (src.includes("/propertySMG/businessTaxCank/budgetManage/financialApproval/")) {
|
||
installHcPosFinancialApprovalTools(doc);
|
||
}
|
||
if (src.includes("/propertySMG/businessTaxCank/budgetManage/forecastBudget/")) {
|
||
installHcPosForecastBudgetTools(doc);
|
||
}
|
||
if (src.includes("/propertySMG/businessTaxCank/budgetManage/operatingBudget/")) {
|
||
installHcPosOperatingBudgetTools(doc);
|
||
}
|
||
if (src.includes("/homeEnterpriseService/cafeteria/canteenArchives/")) {
|
||
renderHcPosOverviewPanel(doc.querySelector(".app-main > div"), HCPOS_OVERVIEW_PRESETS.canteenArchives);
|
||
}
|
||
}
|
||
if (src.includes("/dataPlatform/home/")) {
|
||
installHcPosCockpitLinks(doc);
|
||
installHcPosDangerTableLinks(doc);
|
||
}
|
||
installHcPosGenericListEditors(doc);
|
||
installHcPosEditableSections(doc);
|
||
const bodyHeight = doc.body ? doc.body.scrollHeight : 0;
|
||
const htmlHeight = doc.documentElement ? doc.documentElement.scrollHeight : 0;
|
||
const height = Math.max(bodyHeight, htmlHeight, window.innerHeight - 108, 720);
|
||
iframe.style.setProperty("height", `${height}px`, "important");
|
||
} catch (_error) {
|
||
// ignore
|
||
}
|
||
}
|
||
|
||
function installFrameSkinObserver() {
|
||
setInterval(() => {
|
||
document.querySelectorAll("iframe").forEach((iframe) => applyGlobalFrameSkin(iframe));
|
||
}, 800);
|
||
}
|
||
const SCRIPT_SRCS = [
|
||
"./webpack-runtime.js",
|
||
"/static/js/chunk-libs.ee373cfd.js",
|
||
"/static/js/app.210977f6.js"
|
||
];
|
||
|
||
function appendStyles() {
|
||
STYLE_HREFS.forEach((href) => {
|
||
const finalHref = VERSION ? `${href}${href.includes("?") ? "&" : "?"}v=${VERSION}` : href;
|
||
if (document.querySelector(`link[href="${finalHref}"]`)) {
|
||
return;
|
||
}
|
||
const link = document.createElement("link");
|
||
link.rel = "stylesheet";
|
||
link.href = finalHref;
|
||
document.head.appendChild(link);
|
||
});
|
||
}
|
||
|
||
function injectOuterRuntimeSkin() {
|
||
if (document.getElementById("__codex_runtime_outer_skin__")) {
|
||
return;
|
||
}
|
||
const style = document.createElement("style");
|
||
style.id = "__codex_runtime_outer_skin__";
|
||
style.textContent = `
|
||
.AiBox,
|
||
.feedback-dialog,
|
||
.feedback-btn,
|
||
.v-modal {
|
||
display: none !important;
|
||
}
|
||
|
||
.app-main,
|
||
.app-main > section,
|
||
.main-container,
|
||
.main-container.hasTagsView {
|
||
background: #f3f6fb !important;
|
||
}
|
||
|
||
.fixed-header {
|
||
position: sticky !important;
|
||
top: 0;
|
||
z-index: 10;
|
||
box-shadow: 0 4px 18px rgba(17, 37, 63, 0.06);
|
||
}
|
||
|
||
.navbar {
|
||
height: 60px !important;
|
||
padding: 0 16px !important;
|
||
background: rgba(255,255,255,0.94) !important;
|
||
backdrop-filter: blur(8px);
|
||
}
|
||
|
||
.breadcrumb-container {
|
||
font-size: 13px !important;
|
||
}
|
||
|
||
.right-menu {
|
||
gap: 10px;
|
||
}
|
||
|
||
.right-menu .el-select,
|
||
.right-menu .currentProject {
|
||
max-width: 320px;
|
||
}
|
||
|
||
.tags-view-container {
|
||
min-height: 38px !important;
|
||
padding: 6px 12px !important;
|
||
background: rgba(255,255,255,0.92) !important;
|
||
border-bottom: 1px solid rgba(17,37,63,0.06) !important;
|
||
}
|
||
|
||
.tags-view-wrapper .el-scrollbar__view {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.tags-view-item {
|
||
display: inline-flex !important;
|
||
align-items: center;
|
||
gap: 6px;
|
||
height: 28px;
|
||
margin: 0 !important;
|
||
padding: 0 12px;
|
||
border-radius: 4px 4px 0 0;
|
||
border: 1px solid rgba(17, 37, 63, 0.12);
|
||
background: #fff;
|
||
color: #66758f;
|
||
font-size: 13px;
|
||
line-height: 28px;
|
||
white-space: nowrap;
|
||
cursor: default;
|
||
}
|
||
|
||
.tags-view-item.active {
|
||
border-color: rgba(45, 118, 255, 0.18);
|
||
background: #3a7be0;
|
||
color: #fff;
|
||
}
|
||
|
||
.tags-view-item.codex-synthetic-tag {
|
||
text-decoration: none;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.tags-view-item .el-icon-close {
|
||
display: inline-flex !important;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 12px;
|
||
opacity: 0.72;
|
||
}
|
||
|
||
.tags-view-item:first-child .el-icon-close {
|
||
display: none !important;
|
||
}
|
||
|
||
.codex-runtime-frame-host {
|
||
padding: 14px 16px 20px;
|
||
background: #f3f6fb;
|
||
}
|
||
|
||
.codex-runtime-frame {
|
||
display: block;
|
||
width: 100%;
|
||
min-height: 720px;
|
||
border: 0;
|
||
border-radius: 18px;
|
||
background: #fff;
|
||
box-shadow: 0 14px 40px rgba(17, 37, 63, 0.08);
|
||
}
|
||
`;
|
||
document.head.appendChild(style);
|
||
}
|
||
|
||
function appendScript(src) {
|
||
return new Promise((resolve, reject) => {
|
||
const script = document.createElement("script");
|
||
script.src = VERSION ? `${src}${src.includes("?") ? "&" : "?"}v=${VERSION}` : src;
|
||
script.onload = resolve;
|
||
script.onerror = reject;
|
||
document.body.appendChild(script);
|
||
});
|
||
}
|
||
|
||
async function seedStorage() {
|
||
const response = await fetch(VERSION ? `./storage-seed.json?v=${VERSION}` : "./storage-seed.json", { cache: "no-store" });
|
||
const payload = await response.json();
|
||
Object.entries(payload).forEach(([key, value]) => {
|
||
localStorage.setItem(key, value);
|
||
});
|
||
}
|
||
|
||
async function seedCookies() {
|
||
const response = await fetch(VERSION ? `./cookie-seed.json?v=${VERSION}` : "./cookie-seed.json", { cache: "no-store" });
|
||
const payload = await response.json();
|
||
Object.entries(payload).forEach(([key, value]) => {
|
||
document.cookie = `${key}=${value}; path=/; SameSite=Lax`;
|
||
});
|
||
}
|
||
|
||
async function loadRouteMap() {
|
||
const response = await fetch(VERSION ? `./route-map.json?v=${VERSION}` : "./route-map.json", { cache: "no-store" });
|
||
routeMap = response.ok ? await response.json() : {};
|
||
}
|
||
|
||
async function ensureServiceWorker() {
|
||
if (!("serviceWorker" in navigator)) {
|
||
return;
|
||
}
|
||
|
||
await navigator.serviceWorker.register("./sw.js", { scope: "./" });
|
||
await navigator.serviceWorker.ready;
|
||
|
||
if (!navigator.serviceWorker.controller && !sessionStorage.getItem("mirror-runtime-sw-reloaded")) {
|
||
if (location.hash) {
|
||
sessionStorage.setItem("mirror-runtime-target-hash", location.hash);
|
||
}
|
||
sessionStorage.setItem("mirror-runtime-sw-reloaded", "1");
|
||
location.reload();
|
||
throw new Error("等待 Service Worker 接管当前页面");
|
||
}
|
||
|
||
sessionStorage.removeItem("mirror-runtime-sw-reloaded");
|
||
}
|
||
|
||
function ensureHashRoute() {
|
||
const rememberedHash = sessionStorage.getItem("mirror-runtime-target-hash");
|
||
if (rememberedHash) {
|
||
if (location.hash !== rememberedHash) {
|
||
location.hash = rememberedHash;
|
||
}
|
||
sessionStorage.removeItem("mirror-runtime-target-hash");
|
||
return;
|
||
}
|
||
if (!location.hash || location.hash === "#" || location.hash === "#/") {
|
||
location.hash = ROUTE_HASH;
|
||
}
|
||
}
|
||
|
||
function pathFromHash(hash) {
|
||
if (!hash) {
|
||
return "/dashboard";
|
||
}
|
||
const value = hash.startsWith("#") ? hash.slice(1) : hash;
|
||
const pathOnly = value.split("?")[0];
|
||
return pathOnly || "/dashboard";
|
||
}
|
||
|
||
async function waitForVueRoot() {
|
||
for (let i = 0; i < 40; i += 1) {
|
||
const app = document.querySelector("#app");
|
||
const vm = app && app.__vue__;
|
||
const router = vm && vm.$router;
|
||
if (vm && router) {
|
||
return { vm, router };
|
||
}
|
||
await new Promise((resolve) => setTimeout(resolve, 250));
|
||
}
|
||
throw new Error("等待 Vue 根实例超时");
|
||
}
|
||
|
||
async function patchRuntimeRoutes() {
|
||
if (!routeMap || !Object.keys(routeMap).length) {
|
||
return null;
|
||
}
|
||
const { router } = await waitForVueRoot();
|
||
const rootRoute = (router.options.routes || []).find((item) => item.path === "/");
|
||
if (!rootRoute || !rootRoute.component) {
|
||
return null;
|
||
}
|
||
|
||
const existing = new Set((router.options.routes || []).map((item) => item.path));
|
||
const frameComponent = {
|
||
name: "HcPosMirrorFrame",
|
||
computed: {
|
||
frameSrc() {
|
||
const config = routeMap[this.$route.path] || { src: "/hc-pos.sqygj.cn/404/" };
|
||
return config.src || "/hc-pos.sqygj.cn/404/";
|
||
}
|
||
},
|
||
mounted() {
|
||
this.$nextTick(() => applyGlobalFrameSkin(this.$refs.frame));
|
||
},
|
||
updated() {
|
||
this.$nextTick(() => applyGlobalFrameSkin(this.$refs.frame));
|
||
},
|
||
methods: {
|
||
onFrameLoad(event) {
|
||
applyGlobalFrameSkin(event.target);
|
||
}
|
||
},
|
||
render(h) {
|
||
return h("div", { class: "codex-runtime-frame-host" }, [
|
||
h("iframe", {
|
||
ref: "frame",
|
||
class: "codex-runtime-frame",
|
||
attrs: {
|
||
src: this.frameSrc,
|
||
frameborder: "0"
|
||
},
|
||
on: {
|
||
load: this.onFrameLoad
|
||
},
|
||
style: {
|
||
height: "calc(100vh - 108px)"
|
||
}
|
||
})
|
||
]);
|
||
}
|
||
};
|
||
|
||
const children = Object.keys(routeMap)
|
||
.filter((path) => path !== "/dashboard")
|
||
.filter((path) => !existing.has(path))
|
||
.map((path) => ({
|
||
path: path.replace(/^\//, ""),
|
||
name: `hcpos-mirror-${path.replace(/[^\w]/g, "-")}`,
|
||
meta: { title: (routeMap[path] && routeMap[path].title) || path.split("/").pop() || path },
|
||
component: frameComponent
|
||
}));
|
||
|
||
if (children.length) {
|
||
router.addRoutes([
|
||
{
|
||
path: "/",
|
||
component: rootRoute.component,
|
||
children
|
||
}
|
||
]);
|
||
}
|
||
|
||
const currentPath = pathFromHash(INITIAL_HASH || location.hash);
|
||
if (currentPath !== "/dashboard" && routeMap[currentPath]) {
|
||
router.replace({ path: currentPath });
|
||
}
|
||
return router;
|
||
}
|
||
|
||
function updateDocumentTitle(path) {
|
||
const config = routeMap[path];
|
||
if (config && config.title) {
|
||
document.title = `${config.title} - 项目运营平台`;
|
||
}
|
||
}
|
||
|
||
function getHcPosPresetTags(path) {
|
||
const presets = {
|
||
"/communitySMG/personnelList": ["首页-工作台", "住户档案", "重点客户"],
|
||
"/communitySMG/serviceProvider": ["首页-工作台", "住户档案", "服务商管理"],
|
||
"/homeEnterpriseService/cafeteria/canteenArchives": ["首页-工作台", "食堂档案", "社区幼托", "房屋经纪", "快递收发", "社区养老", "社区家政"],
|
||
"/homeEnterpriseService/childCare": ["首页-工作台", "食堂档案", "社区幼托", "房屋经纪", "快递收发", "社区养老", "社区家政"],
|
||
"/homeEnterpriseService/house": ["首页-工作台", "食堂档案", "社区幼托", "房屋经纪", "快递收发", "社区养老", "社区家政"],
|
||
"/homeEnterpriseService/express": ["首页-工作台", "食堂档案", "社区幼托", "房屋经纪", "快递收发", "社区养老", "社区家政"],
|
||
"/homeEnterpriseService/retirement": ["首页-工作台", "食堂档案", "社区幼托", "房屋经纪", "快递收发", "社区养老", "社区家政"],
|
||
"/homeEnterpriseService/housekeeping": ["首页-工作台", "食堂档案", "社区幼托", "房屋经纪", "快递收发", "社区养老", "社区家政"],
|
||
"/propertySMG/diagnosis/customizedPhysical": ["首页-工作台", "5A测评", "专项测评", "定制测评"],
|
||
"/propertySMG/diagnosis/specialPhysical": ["首页-工作台", "5A测评", "专项测评", "定制测评"],
|
||
"/propertySMG/diagnosis/5Aphysical": ["首页-工作台", "5A测评", "专项测评", "定制测评"],
|
||
"/propertySMG/businessTaxCank/bankEnterprise": ["首页-工作台", "银企直联", "财务核算", "财务凭证", "税务统筹", "财务看板"],
|
||
"/propertySMG/businessTaxCank/financeAdjust/financialAccount": ["首页-工作台", "银企直联", "财务核算", "财务凭证", "税务统筹", "财务看板"],
|
||
"/propertySMG/businessTaxCank/financeAdjust/financialVoucher": ["首页-工作台", "银企直联", "财务核算", "财务凭证", "税务统筹", "财务看板"],
|
||
"/propertySMG/businessTaxCank/taxCoordination": ["首页-工作台", "银企直联", "财务核算", "财务凭证", "税务统筹", "财务看板"],
|
||
"/propertySMG/businessTaxCank/financeMicrobrain": ["首页-工作台", "银企直联", "财务核算", "财务凭证", "税务统筹", "财务看板"],
|
||
"/propertySMG/businessTaxCank/financeReport/balanceSheet": ["首页-工作台", "资产负债表", "现金流量表", "全年收入报表", "利润表", "项目收支表"],
|
||
"/propertySMG/businessTaxCank/financeReport/cashFlowStatement": ["首页-工作台", "资产负债表", "现金流量表", "全年收入报表", "利润表", "项目收支表"],
|
||
"/propertySMG/businessTaxCank/financeReport/incomeStatement": ["首页-工作台", "资产负债表", "现金流量表", "全年收入报表", "利润表", "项目收支表"],
|
||
"/propertySMG/businessTaxCank/financeReport/profitSurface": ["首页-工作台", "资产负债表", "现金流量表", "全年收入报表", "利润表", "项目收支表"],
|
||
"/propertySMG/businessTaxCank/financeReport/projectIncome": ["首页-工作台", "资产负债表", "现金流量表", "全年收入报表", "利润表", "项目收支表"],
|
||
"/propertySMG/cleanManage/cleanMicrobrain": ["首页-工作台", "清洁看板", "绿化看板", "安防看板", "设备看板", "电梯看板", "车场看板"],
|
||
"/propertySMG/cleanManage/assessmentTraining": [
|
||
{ title: "首页-工作台", path: "/dashboard" },
|
||
{ title: "清洁看板", path: "/propertySMG/cleanManage/cleanMicrobrain" },
|
||
{ title: "考核培训", path: "/propertySMG/cleanManage/assessmentTraining" },
|
||
{ title: "备品备件", path: "/propertySMG/cleanManage/spareParts" }
|
||
],
|
||
"/propertySMG/cleanManage/spareParts": [
|
||
{ title: "首页-工作台", path: "/dashboard" },
|
||
{ title: "清洁看板", path: "/propertySMG/cleanManage/cleanMicrobrain" },
|
||
{ title: "考核培训", path: "/propertySMG/cleanManage/assessmentTraining" },
|
||
{ title: "备品备件", path: "/propertySMG/cleanManage/spareParts" }
|
||
],
|
||
"/propertySMG/greenManage/greenMicroBrain": ["首页-工作台", "清洁看板", "绿化看板", "安防看板", "设备看板", "电梯看板", "车场看板"],
|
||
"/propertySMG/greenManage/assessmentTraining": [
|
||
{ title: "首页-工作台", path: "/dashboard" },
|
||
{ title: "绿化看板", path: "/propertySMG/greenManage/greenMicroBrain" },
|
||
{ title: "考核培训", path: "/propertySMG/greenManage/assessmentTraining" },
|
||
{ title: "备品备件", path: "/propertySMG/greenManage/spareParts" }
|
||
],
|
||
"/propertySMG/greenManage/spareParts": [
|
||
{ title: "首页-工作台", path: "/dashboard" },
|
||
{ title: "绿化看板", path: "/propertySMG/greenManage/greenMicroBrain" },
|
||
{ title: "考核培训", path: "/propertySMG/greenManage/assessmentTraining" },
|
||
{ title: "备品备件", path: "/propertySMG/greenManage/spareParts" }
|
||
],
|
||
"/propertySMG/securityManage/securityBrain": ["首页-工作台", "清洁看板", "绿化看板", "安防看板", "设备看板", "电梯看板", "车场看板"],
|
||
"/propertySMG/securityManage/assessmentTraining": [
|
||
{ title: "首页-工作台", path: "/dashboard" },
|
||
{ title: "安防看板", path: "/propertySMG/securityManage/securityBrain" },
|
||
{ title: "考核培训", path: "/propertySMG/securityManage/assessmentTraining" },
|
||
{ title: "备品备件", path: "/propertySMG/securityManage/spareParts" }
|
||
],
|
||
"/propertySMG/securityManage/spareParts": [
|
||
{ title: "首页-工作台", path: "/dashboard" },
|
||
{ title: "安防看板", path: "/propertySMG/securityManage/securityBrain" },
|
||
{ title: "考核培训", path: "/propertySMG/securityManage/assessmentTraining" },
|
||
{ title: "备品备件", path: "/propertySMG/securityManage/spareParts" }
|
||
],
|
||
"/propertySMG/equipmentManage/equipmentPortrait": ["首页-工作台", "清洁看板", "绿化看板", "安防看板", "设备看板", "电梯看板", "车场看板"],
|
||
"/propertySMG/equipmentManage/assessmentTraining": [
|
||
{ title: "首页-工作台", path: "/dashboard" },
|
||
{ title: "设备看板", path: "/propertySMG/equipmentManage/equipmentPortrait" },
|
||
{ title: "考核培训", path: "/propertySMG/equipmentManage/assessmentTraining" },
|
||
{ title: "备品备件", path: "/propertySMG/equipmentManage/spareParts" }
|
||
],
|
||
"/propertySMG/equipmentManage/spareParts": [
|
||
{ title: "首页-工作台", path: "/dashboard" },
|
||
{ title: "设备看板", path: "/propertySMG/equipmentManage/equipmentPortrait" },
|
||
{ title: "考核培训", path: "/propertySMG/equipmentManage/assessmentTraining" },
|
||
{ title: "备品备件", path: "/propertySMG/equipmentManage/spareParts" }
|
||
],
|
||
"/propertySMG/elevatorManage/elevatorDimension": ["首页-工作台", "清洁看板", "绿化看板", "安防看板", "设备看板", "电梯看板", "车场看板"],
|
||
"/propertySMG/elevatorManage/assessmentTraining": [
|
||
{ title: "首页-工作台", path: "/dashboard" },
|
||
{ title: "电梯看板", path: "/propertySMG/elevatorManage/elevatorDimension" },
|
||
{ title: "考核培训", path: "/propertySMG/elevatorManage/assessmentTraining" },
|
||
{ title: "备品备件", path: "/propertySMG/elevatorManage/spareParts" }
|
||
],
|
||
"/propertySMG/elevatorManage/spareParts": [
|
||
{ title: "首页-工作台", path: "/dashboard" },
|
||
{ title: "电梯看板", path: "/propertySMG/elevatorManage/elevatorDimension" },
|
||
{ title: "考核培训", path: "/propertySMG/elevatorManage/assessmentTraining" },
|
||
{ title: "备品备件", path: "/propertySMG/elevatorManage/spareParts" }
|
||
],
|
||
"/propertySMG/parkingOperation/parkingMicroBrain": ["首页-工作台", "清洁看板", "绿化看板", "安防看板", "设备看板", "电梯看板", "车场看板"],
|
||
"/propertySMG/parkingOperation/assessmentTraining": [
|
||
{ title: "首页-工作台", path: "/dashboard" },
|
||
{ title: "车场看板", path: "/propertySMG/parkingOperation/parkingMicroBrain" },
|
||
{ title: "考核培训", path: "/propertySMG/parkingOperation/assessmentTraining" }
|
||
],
|
||
"/propertySMG/customerOperations/customerPortrait": ["首页-工作台", "客户画像", "服务商管理", "住户档案"],
|
||
"/propertySMG/energySourceOperat/notice": ["首页-工作台", "能源看板", "设备看板", "车场看板"]
|
||
};
|
||
return presets[path] || null;
|
||
}
|
||
|
||
function getHcPosTagRoute(title) {
|
||
const routeMapByTitle = {
|
||
"首页-工作台": "/dashboard",
|
||
"住户档案": "/communitySMG/personnelList",
|
||
"重点客户": "/communitySMG/personnelList",
|
||
"服务商管理": "/communitySMG/serviceProvider",
|
||
"食堂档案": "/homeEnterpriseService/cafeteria/canteenArchives",
|
||
"社区幼托": "/homeEnterpriseService/childCare",
|
||
"房屋经纪": "/homeEnterpriseService/house",
|
||
"快递收发": "/homeEnterpriseService/express",
|
||
"社区养老": "/homeEnterpriseService/retirement",
|
||
"社区家政": "/homeEnterpriseService/housekeeping",
|
||
"5A测评": "/propertySMG/diagnosis/5Aphysical",
|
||
"专项测评": "/propertySMG/diagnosis/specialPhysical",
|
||
"定制测评": "/propertySMG/diagnosis/customizedPhysical",
|
||
"银企直联": "/propertySMG/businessTaxCank/bankEnterprise",
|
||
"财务核算": "/propertySMG/businessTaxCank/financeAdjust/financialAccount",
|
||
"财务凭证": "/propertySMG/businessTaxCank/financeAdjust/financialVoucher",
|
||
"税务统筹": "/propertySMG/businessTaxCank/taxCoordination",
|
||
"财务看板": "/propertySMG/businessTaxCank/financeMicrobrain",
|
||
"资产负债表": "/propertySMG/businessTaxCank/financeReport/balanceSheet",
|
||
"现金流量表": "/propertySMG/businessTaxCank/financeReport/cashFlowStatement",
|
||
"全年收入报表": "/propertySMG/businessTaxCank/financeReport/incomeStatement",
|
||
"利润表": "/propertySMG/businessTaxCank/financeReport/profitSurface",
|
||
"项目收支表": "/propertySMG/businessTaxCank/financeReport/projectIncome",
|
||
"清洁看板": "/propertySMG/cleanManage/cleanMicrobrain",
|
||
"绿化看板": "/propertySMG/greenManage/greenMicroBrain",
|
||
"安防看板": "/propertySMG/securityManage/securityBrain",
|
||
"设备看板": "/propertySMG/equipmentManage/equipmentPortrait",
|
||
"电梯看板": "/propertySMG/elevatorManage/elevatorDimension",
|
||
"车场看板": "/propertySMG/parkingOperation/parkingMicroBrain",
|
||
"客户画像": "/propertySMG/customerOperations/customerPortrait",
|
||
"能源看板": "/propertySMG/energySourceOperat/notice"
|
||
};
|
||
return routeMapByTitle[title] || "/dashboard";
|
||
}
|
||
|
||
function getHcPosTagLabel(node) {
|
||
const cloned = node.cloneNode(true);
|
||
cloned.querySelectorAll(".el-icon-close").forEach((icon) => icon.remove());
|
||
return (cloned.textContent || "").trim();
|
||
}
|
||
|
||
function createHcPosSyntheticTag(title, path, active) {
|
||
const anchor = document.createElement("a");
|
||
anchor.href = `#${path}`;
|
||
anchor.className = `tags-view-item codex-synthetic-tag${active ? " active" : ""}`;
|
||
anchor.appendChild(document.createTextNode(title));
|
||
if (title !== "首页-工作台") {
|
||
const close = document.createElement("span");
|
||
close.className = "el-icon-close";
|
||
anchor.appendChild(close);
|
||
}
|
||
return anchor;
|
||
}
|
||
|
||
function hydrateHcPosTagsView(path) {
|
||
const view = document.querySelector(".tags-view-container .el-scrollbar__view");
|
||
if (!view) {
|
||
return;
|
||
}
|
||
const preset = getHcPosPresetTags(path);
|
||
view.querySelectorAll(".codex-synthetic-tag").forEach((node) => node.remove());
|
||
if (!preset) {
|
||
return;
|
||
}
|
||
const existingNodes = [...view.querySelectorAll(".tags-view-item:not(.codex-synthetic-tag)")];
|
||
const existingMap = new Map(existingNodes.map((node) => [getHcPosTagLabel(node), node]));
|
||
const activeTitle =
|
||
(routeMap[path] && routeMap[path].title) ||
|
||
getHcPosTagLabel(view.querySelector(".tags-view-item.active") || document.createElement("span"));
|
||
|
||
preset.forEach((item) => {
|
||
const title = typeof item === "string" ? item : item.title;
|
||
const itemPath = typeof item === "string" ? getHcPosTagRoute(title) : item.path;
|
||
let node = existingMap.get(title);
|
||
if (!node) {
|
||
node = createHcPosSyntheticTag(title, itemPath, itemPath === path || title === activeTitle);
|
||
}
|
||
if (itemPath === path || title === activeTitle) {
|
||
node.classList.add("active");
|
||
} else {
|
||
node.classList.remove("active");
|
||
}
|
||
view.appendChild(node);
|
||
});
|
||
}
|
||
|
||
function scheduleHcPosTagsViewHydration(path) {
|
||
let tries = 0;
|
||
const timer = setInterval(() => {
|
||
tries += 1;
|
||
hydrateHcPosTagsView(path);
|
||
if (tries >= 8) {
|
||
clearInterval(timer);
|
||
}
|
||
}, 300);
|
||
}
|
||
|
||
function installRouteBridge(router) {
|
||
if (!router || window.__HCPOS_RUNTIME_BRIDGE_INSTALLED__) {
|
||
return;
|
||
}
|
||
window.__HCPOS_RUNTIME_BRIDGE_INSTALLED__ = true;
|
||
|
||
document.addEventListener(
|
||
"click",
|
||
(event) => {
|
||
const anchor = event.target && event.target.closest ? event.target.closest("a[href]") : null;
|
||
if (!anchor) {
|
||
return;
|
||
}
|
||
const href = anchor.getAttribute("href") || "";
|
||
if (!href.startsWith("#/")) {
|
||
return;
|
||
}
|
||
event.preventDefault();
|
||
const path = href.slice(1).split("?")[0] || "/dashboard";
|
||
if (routeMap[path]) {
|
||
router.replace({ path });
|
||
updateDocumentTitle(path);
|
||
} else {
|
||
router.replace({ path: "/dashboard" });
|
||
}
|
||
},
|
||
true
|
||
);
|
||
|
||
router.afterEach((to) => {
|
||
updateDocumentTitle(to.path);
|
||
scheduleHcPosTagsViewHydration(to.path);
|
||
});
|
||
}
|
||
|
||
function hideLoading() {
|
||
const loading = document.getElementById("mirror-loading");
|
||
if (loading) {
|
||
loading.style.display = "none";
|
||
}
|
||
}
|
||
|
||
try {
|
||
await seedStorage();
|
||
await seedCookies();
|
||
await loadRouteMap();
|
||
await ensureServiceWorker();
|
||
ensureHashRoute();
|
||
appendStyles();
|
||
injectOuterRuntimeSkin();
|
||
for (const src of SCRIPT_SRCS) {
|
||
await appendScript(src);
|
||
}
|
||
const router = await patchRuntimeRoutes();
|
||
installRouteBridge(router);
|
||
const initialPath = pathFromHash(location.hash);
|
||
updateDocumentTitle(initialPath);
|
||
hideLoading();
|
||
scheduleHcPosTagsViewHydration(initialPath);
|
||
installFrameSkinObserver();
|
||
} catch (error) {
|
||
const loading = document.getElementById("mirror-loading");
|
||
if (loading) {
|
||
const detail = document.createElement("small");
|
||
detail.textContent = String(error && error.message ? error.message : error);
|
||
loading.appendChild(detail);
|
||
}
|
||
}
|
||
})();
|