feat: 新增可配置通知中心,支持扫描式/埋点式通知规则与手动群发

新增通知规则实体、扫描任务、API与管理界面,重构站内通知支持用户/电工双类型,移除硬编码的证照到期提醒,新增工单派单、电工审核结果等通知触发场景,调整菜单与数据库结构适配新通知体系。
This commit is contained in:
andyesfly committed 2026-10-06 11:53:02 +08:00
1 parent 91aa3c06f0
commit 6cf6a3d5b9
36 files changed
+3309 -246

No files matched your search

-19
View File
@@ -1,19 +0,0 @@
import { Inject } from "@midwayjs/core";
import { Job, IJob } from "@midwayjs/cron";
import { NoticeService } from "../service/notice.service";
/**
* 电工资质证照到期提醒定时任务
* 每天 08:00 扫描 worker_cert 中 30 天内到期或已过期的证照,
* 为每个 ADMIN 生成一条站内通知(幂等,已存在的提醒不会重复生成)。
*/
@Job({ cronTime: "0 0 8 * * *", start: true, timeZone: "Asia/Shanghai" })
export class CertExpireTask implements IJob {
@Inject()
noticeService!: NoticeService;
async onTick() {
const result = await this.noticeService.scanCertExpireAndNotify();
console.log(`[cert-expire] 证照到期扫描完成:扫描 ${result.scanned} 条,新增通知 ${result.created} 条`);
}
}
+4 -1
View File
@@ -1,5 +1,6 @@
import { demoHeartbeatJob } from "./demo_heartbeat.task";
import { logCleanJob } from "./log_clean.task";
import { noticeScanJob } from "./notice_scan.task";
export type JobParamType = "number" | "string";
@@ -26,6 +27,8 @@ export interface JobRuntime {
cleanOperationLogs(keepDays: number): Promise<number>;
/** 清理 sys_job_log 中早于 keepDays 天的记录,返回删除条数 */
cleanJobLogs(keepDays: number): Promise<number>;
/** 执行全部启用的扫描式通知规则,返回本次执行摘要 */
runNoticeRules(): Promise<string>;
}
export interface JobHandlerContext {
@@ -52,7 +55,7 @@ export interface BuiltinJob {
handler: JobHandler;
}
export const BUILTIN_JOBS: BuiltinJob[] = [demoHeartbeatJob, logCleanJob];
export const BUILTIN_JOBS: BuiltinJob[] = [demoHeartbeatJob, logCleanJob, noticeScanJob];
export function getBuiltinJob(jobKey: string): BuiltinJob | undefined {
return BUILTIN_JOBS.find(job => job.jobKey === jobKey);
+110
View File
@@ -0,0 +1,110 @@
/**
* 通知事件注册表(元数据)
*
* 事件分两类:
* - 扫描式(triggerMode=1):由内置任务 notice_scan 定时扫描,扫描实现位于 NoticeRuleService
* - 埋点式(triggerMode=2):业务代码在关键动作处调用 NoticeRuleService.runForEvent()
*
* 本文件只维护「事件元数据」(供 Web「通知管理」下拉与模板变量提示),
* 具体扫描逻辑与业务埋点实现分别位于 NoticeRuleService 与各业务 service。
*/
/** 模板可用变量({变量} 占位符) */
export interface NoticeEventVariable {
key: string;
label: string;
}
/** 事件定义 */
export interface NoticeEventDefinition {
eventKey: string;
eventName: string;
/** 1扫描式 2埋点式 */
triggerMode: 1 | 2;
variables: NoticeEventVariable[];
}
/** 扫描式事件单条命中结果 */
export interface NoticeScanItem {
bizType: string;
bizId: number;
/** workers.mode=event 时作为事件关联电工 */
workerId?: number | null;
vars: Record<string, any>;
}
export const NOTICE_EVENTS: NoticeEventDefinition[] = [
{
eventKey: "cert_expire",
eventName: "电工资质证照到期提醒",
triggerMode: 1,
variables: [
{ key: "workerName", label: "电工姓名" },
{ key: "certType", label: "证书类型" },
{ key: "certNo", label: "证书编号" },
{ key: "issueAuthority", label: "发证机关" },
{ key: "expireDate", label: "有效期至" },
{ key: "remainDays", label: "剩余天数" },
{ key: "expireState", label: "到期状态(即将到期/已过期)" },
{ key: "remainDaysText", label: "剩余天数文案" }
]
},
{
eventKey: "contract_expire",
eventName: "客户合同到期提醒",
triggerMode: 1,
variables: [
{ key: "customerName", label: "客户名称" },
{ key: "contractNo", label: "合同编号" },
{ key: "contractName", label: "合同名称" },
{ key: "endDate", label: "到期日" },
{ key: "expireState", label: "到期状态(即将到期/已过期)" },
{ key: "remainDaysText", label: "剩余天数文案" }
]
},
{
eventKey: "visit_plan",
eventName: "客户拜访计划提醒",
triggerMode: 1,
variables: [
{ key: "customerName", label: "客户名称" },
{ key: "planDate", label: "计划拜访日期" },
{ key: "remainDaysText", label: "到期文案" }
]
},
{
eventKey: "order_timeout",
eventName: "工单超时未接单提醒",
triggerMode: 1,
variables: [
{ key: "orderTypeName", label: "工单类型" },
{ key: "orderNo", label: "工单编号" },
{ key: "customerName", label: "客户名称" },
{ key: "staleDays", label: "滞留天数" }
]
},
{
eventKey: "worker_audit_result",
eventName: "电工入驻审核结果通知",
triggerMode: 2,
variables: [
{ key: "auditState", label: "审核结果(已通过/已驳回)" },
{ key: "auditRemarkText", label: "驳回理由文案" }
]
},
{
eventKey: "dispatch_assign",
eventName: "工单派单通知",
triggerMode: 2,
variables: [
{ key: "orderTypeName", label: "工单类型" },
{ key: "orderNo", label: "工单编号" },
{ key: "customerName", label: "客户名称" },
{ key: "detailText", label: "附加说明" }
]
}
];
export function getNoticeEvent(eventKey: string): NoticeEventDefinition | undefined {
return NOTICE_EVENTS.find(e => e.eventKey === eventKey);
}
+18
View File
@@ -0,0 +1,18 @@
import type { BuiltinJob } from "./job_registry";
/**
* 通知规则扫描任务:遍历启用的扫描式通知规则,
* 扫描业务数据并按模板 + 接收对象生成站内通知(幂等)。
* 默认每日 08:00 执行,可在「定时任务」页调整 cron / 启停 / 立即执行。
*/
export const noticeScanJob: BuiltinJob = {
jobKey: "notice_scan",
jobName: "通知规则扫描",
description: "遍历启用的扫描式通知规则,按模板与接收对象生成站内通知(幂等)",
defaultCron: "0 0 8 * * *",
defaultParams: {},
paramFields: [],
handler: async (_params, ctx) => {
return ctx.runtime.runNoticeRules();
}
};