mirror of https://github.com/certd/certd
perf: 通知管理
parent
131ed13df1
commit
d9a00eeaf7
|
@ -48,4 +48,4 @@ admin/123456
|
||||||
|
|
||||||
## 五、备份恢复
|
## 五、备份恢复
|
||||||
|
|
||||||
将备份的`db.sqlite`覆盖到原来的位置即可
|
将备份的`db.sqlite`覆盖到原来的位置,重启certd即可
|
||||||
|
|
|
@ -81,4 +81,4 @@ services:
|
||||||
|
|
||||||
## 五、备份恢复
|
## 五、备份恢复
|
||||||
|
|
||||||
将备份的`db.sqlite`覆盖到原来的位置即可
|
将备份的`db.sqlite`覆盖到原来的位置,重启certd即可
|
||||||
|
|
|
@ -71,4 +71,4 @@ docker compose up -d
|
||||||
|
|
||||||
## 四、备份恢复
|
## 四、备份恢复
|
||||||
|
|
||||||
将备份的`db.sqlite`覆盖到原来的位置即可
|
将备份的`db.sqlite`覆盖到原来的位置,重启certd即可
|
|
@ -42,4 +42,4 @@ kill -9 $(lsof -t -i:7001)
|
||||||
|
|
||||||
## 四、备份恢复
|
## 四、备份恢复
|
||||||
|
|
||||||
将备份的`db.sqlite`覆盖到原来的位置即可
|
将备份的`db.sqlite`覆盖到原来的位置,重启certd即可
|
||||||
|
|
|
@ -27,4 +27,4 @@
|
||||||
|
|
||||||
## 三、备份恢复
|
## 三、备份恢复
|
||||||
|
|
||||||
将备份的`db.sqlite`覆盖到原来的位置即可
|
将备份的`db.sqlite`覆盖到原来的位置,重启certd即可
|
|
@ -2,7 +2,15 @@ import { Registrable } from "../registry/index.js";
|
||||||
import { FormItemProps } from "../dt/index.js";
|
import { FormItemProps } from "../dt/index.js";
|
||||||
import { HttpClient, ILogger, utils } from "@certd/basic";
|
import { HttpClient, ILogger, utils } from "@certd/basic";
|
||||||
import * as _ from "lodash-es";
|
import * as _ from "lodash-es";
|
||||||
import { AccessRequestHandleReq } from "../core";
|
import { PluginRequestHandleReq } from "../plugin/index.js";
|
||||||
|
|
||||||
|
export type AccessRequestHandleReqInput<T = any> = {
|
||||||
|
id?: number;
|
||||||
|
title?: string;
|
||||||
|
access: T;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AccessRequestHandleReq<T = any> = PluginRequestHandleReq<AccessRequestHandleReqInput<T>>;
|
||||||
|
|
||||||
export type AccessInputDefine = FormItemProps & {
|
export type AccessInputDefine = FormItemProps & {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
import { HttpClient, ILogger, utils } from "@certd/basic";
|
|
||||||
|
|
||||||
export type PluginRequestHandleReq<T = any> = {
|
|
||||||
typeName: string;
|
|
||||||
action: string;
|
|
||||||
input: T;
|
|
||||||
data: any;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type AccessRequestHandleReqInput<T = any> = {
|
|
||||||
id?: number;
|
|
||||||
title?: string;
|
|
||||||
access: T;
|
|
||||||
};
|
|
||||||
export type AccessRequestHandleContext = {
|
|
||||||
http: HttpClient;
|
|
||||||
logger: ILogger;
|
|
||||||
utils: typeof utils;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type AccessRequestHandleReq<T = any> = PluginRequestHandleReq<AccessRequestHandleReqInput<T>>;
|
|
|
@ -3,5 +3,4 @@ export * from "./run-history.js";
|
||||||
export * from "./context.js";
|
export * from "./context.js";
|
||||||
export * from "./storage.js";
|
export * from "./storage.js";
|
||||||
export * from "./file-store.js";
|
export * from "./file-store.js";
|
||||||
export * from "./handler.js";
|
|
||||||
export * from "./exceptions.js";
|
export * from "./exceptions.js";
|
||||||
|
|
|
@ -6,3 +6,4 @@ export * from "./plugin/index.js";
|
||||||
export * from "./context/index.js";
|
export * from "./context/index.js";
|
||||||
export * from "./decorator/index.js";
|
export * from "./decorator/index.js";
|
||||||
export * from "./service/index.js";
|
export * from "./service/index.js";
|
||||||
|
export * from "./notification/index.js";
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { PluginRequestHandleReq } from "../plugin";
|
||||||
|
import { Registrable } from "../registry/index.js";
|
||||||
|
import { FormItemProps } from "../dt/index.js";
|
||||||
|
import { HttpClient, ILogger, utils } from "@certd/basic";
|
||||||
|
import * as _ from "lodash-es";
|
||||||
|
import { IEmailService } from "../service";
|
||||||
|
|
||||||
|
export type NotificationBody = {
|
||||||
|
userId: number;
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
pipelineId: number;
|
||||||
|
historyId: number;
|
||||||
|
url: string;
|
||||||
|
extra?: any;
|
||||||
|
options?: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NotificationRequestHandleReqInput<T = any> = {
|
||||||
|
id?: number;
|
||||||
|
title?: string;
|
||||||
|
access: T;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NotificationRequestHandleReq<T = any> = PluginRequestHandleReq<NotificationRequestHandleReqInput<T>>;
|
||||||
|
|
||||||
|
export type NotificationInputDefine = FormItemProps & {
|
||||||
|
title: string;
|
||||||
|
required?: boolean;
|
||||||
|
encrypt?: boolean;
|
||||||
|
};
|
||||||
|
export type NotificationDefine = Registrable & {
|
||||||
|
input?: {
|
||||||
|
[key: string]: NotificationInputDefine;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export interface INotificationService {
|
||||||
|
send(body: NotificationBody): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INotification extends INotificationService {
|
||||||
|
ctx: NotificationContext;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NotificationContext = {
|
||||||
|
http: HttpClient;
|
||||||
|
logger: ILogger;
|
||||||
|
utils: typeof utils;
|
||||||
|
emailService: IEmailService;
|
||||||
|
};
|
||||||
|
|
||||||
|
export abstract class BaseNotification implements INotification {
|
||||||
|
ctx!: NotificationContext;
|
||||||
|
http!: HttpClient;
|
||||||
|
logger!: ILogger;
|
||||||
|
abstract send(body: NotificationBody): Promise<void>;
|
||||||
|
setCtx(ctx: NotificationContext) {
|
||||||
|
this.ctx = ctx;
|
||||||
|
this.http = ctx.http;
|
||||||
|
this.logger = ctx.logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
async onRequest(req: NotificationRequestHandleReq) {
|
||||||
|
if (!req.action) {
|
||||||
|
throw new Error("action is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
let methodName = req.action;
|
||||||
|
if (!req.action.startsWith("on")) {
|
||||||
|
methodName = `on${_.upperFirst(req.action)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const method = this[methodName];
|
||||||
|
if (method) {
|
||||||
|
// @ts-ignore
|
||||||
|
return await this[methodName](req.data);
|
||||||
|
}
|
||||||
|
throw new Error(`action ${req.action} not found`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
// src/decorator/memoryCache.decorator.ts
|
||||||
|
import { Decorator } from "../decorator/index.js";
|
||||||
|
import * as _ from "lodash-es";
|
||||||
|
import { notificationRegistry } from "./registry.js";
|
||||||
|
import { http, logger, utils } from "@certd/basic";
|
||||||
|
import { NotificationContext, NotificationDefine, NotificationInputDefine } from "./api.js";
|
||||||
|
|
||||||
|
// 提供一个唯一 key
|
||||||
|
export const NOTIFICATION_CLASS_KEY = "pipeline:notification";
|
||||||
|
export const NOTIFICATION_INPUT_KEY = "pipeline:notification:input";
|
||||||
|
|
||||||
|
export function IsNotification(define: NotificationDefine): ClassDecorator {
|
||||||
|
return (target: any) => {
|
||||||
|
target = Decorator.target(target);
|
||||||
|
|
||||||
|
const inputs: any = {};
|
||||||
|
const properties = Decorator.getClassProperties(target);
|
||||||
|
for (const property in properties) {
|
||||||
|
const input = Reflect.getMetadata(NOTIFICATION_INPUT_KEY, target, property);
|
||||||
|
if (input) {
|
||||||
|
inputs[property] = input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_.merge(define, { input: inputs });
|
||||||
|
Reflect.defineMetadata(NOTIFICATION_CLASS_KEY, define, target);
|
||||||
|
target.define = define;
|
||||||
|
notificationRegistry.register(define.name, {
|
||||||
|
define,
|
||||||
|
target,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NotificationInput(input?: NotificationInputDefine): PropertyDecorator {
|
||||||
|
return (target, propertyKey) => {
|
||||||
|
target = Decorator.target(target, propertyKey);
|
||||||
|
// const _type = Reflect.getMetadata("design:type", target, propertyKey);
|
||||||
|
Reflect.defineMetadata(NOTIFICATION_INPUT_KEY, input, target, propertyKey);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function newNotification(type: string, input: any, ctx?: NotificationContext) {
|
||||||
|
const register = notificationRegistry.get(type);
|
||||||
|
if (register == null) {
|
||||||
|
throw new Error(`notification ${type} not found`);
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
const access = new register.target();
|
||||||
|
for (const key in input) {
|
||||||
|
access[key] = input[key];
|
||||||
|
}
|
||||||
|
if (!ctx) {
|
||||||
|
ctx = {
|
||||||
|
http,
|
||||||
|
logger,
|
||||||
|
utils,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
access.ctx = ctx;
|
||||||
|
return access;
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from "./api.js";
|
||||||
|
export * from "./registry.js";
|
||||||
|
export * from "./decorator.js";
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { createRegistry } from "../registry/index.js";
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
export const notificationRegistry = createRegistry("notification");
|
|
@ -3,12 +3,20 @@ import { FileItem, FormItemProps, Pipeline, Runnable, Step } from "../dt/index.j
|
||||||
import { FileStore } from "../core/file-store.js";
|
import { FileStore } from "../core/file-store.js";
|
||||||
import { IAccessService } from "../access/index.js";
|
import { IAccessService } from "../access/index.js";
|
||||||
import { ICnameProxyService, IEmailService } from "../service/index.js";
|
import { ICnameProxyService, IEmailService } from "../service/index.js";
|
||||||
import { CancelError, IContext, PluginRequestHandleReq, RunnableCollection } from "../core/index.js";
|
import { CancelError, IContext, RunnableCollection } from "../core/index.js";
|
||||||
import { HttpRequestConfig, ILogger, logger, utils } from "@certd/basic";
|
import { HttpRequestConfig, ILogger, logger, utils } from "@certd/basic";
|
||||||
import { HttpClient } from "@certd/basic";
|
import { HttpClient } from "@certd/basic";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { IPluginConfigService } from "../service/config";
|
import { IPluginConfigService } from "../service/config";
|
||||||
import { upperFirst } from "lodash-es";
|
import { upperFirst } from "lodash-es";
|
||||||
|
|
||||||
|
export type PluginRequestHandleReq<T = any> = {
|
||||||
|
typeName: string;
|
||||||
|
action: string;
|
||||||
|
input: T;
|
||||||
|
data: any;
|
||||||
|
};
|
||||||
|
|
||||||
export type UserInfo = {
|
export type UserInfo = {
|
||||||
role: "admin" | "user";
|
role: "admin" | "user";
|
||||||
id: any;
|
id: any;
|
||||||
|
|
|
@ -49,6 +49,17 @@ export const certdResources = [
|
||||||
cache: true
|
cache: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "通知设置",
|
||||||
|
name: "NotificationManager",
|
||||||
|
path: "/certd/notification",
|
||||||
|
component: "/certd/notification/index.vue",
|
||||||
|
meta: {
|
||||||
|
icon: "ion:disc-outline",
|
||||||
|
auth: true,
|
||||||
|
cache: true
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "CNAME记录管理",
|
title: "CNAME记录管理",
|
||||||
name: "CnameRecord",
|
name: "CnameRecord",
|
||||||
|
|
|
@ -115,7 +115,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cnameProviderId: {
|
cnameProviderId: {
|
||||||
title: "CNAME提供者",
|
title: "CNAME服务",
|
||||||
type: "dict-select",
|
type: "dict-select",
|
||||||
dict: dict({
|
dict: dict({
|
||||||
url: "/cname/provider/list",
|
url: "/cname/provider/list",
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { request } from "/src/api/service";
|
||||||
|
|
||||||
|
export function createApi() {
|
||||||
|
const apiPrefix = "/pi/notification";
|
||||||
|
return {
|
||||||
|
async GetList(query: any) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/page",
|
||||||
|
method: "post",
|
||||||
|
data: query
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async AddObj(obj: any) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/add",
|
||||||
|
method: "post",
|
||||||
|
data: obj
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async UpdateObj(obj: any) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/update",
|
||||||
|
method: "post",
|
||||||
|
data: obj
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async DelObj(id: number) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/delete",
|
||||||
|
method: "post",
|
||||||
|
params: { id }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async GetObj(id: number) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/info",
|
||||||
|
method: "post",
|
||||||
|
params: { id }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async GetSimpleInfo(id: number) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/simpleInfo",
|
||||||
|
method: "post",
|
||||||
|
params: { id }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async GetProviderDefine(type: string) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/define",
|
||||||
|
method: "post",
|
||||||
|
params: { type }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async GetProviderDefineByType(type: string) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/defineByType",
|
||||||
|
method: "post",
|
||||||
|
params: { type }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
import { ColumnCompositionProps, dict } from "@fast-crud/fast-crud";
|
||||||
|
import { computed, provide, ref, toRef } from "vue";
|
||||||
|
import { useReference } from "/@/use/use-refrence";
|
||||||
|
import { forEach, get, merge, set } from "lodash-es";
|
||||||
|
|
||||||
|
export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
|
||||||
|
provide("notificationApi", api);
|
||||||
|
const notificationTypeDictRef = dict({
|
||||||
|
url: "/pi/notification/getTypeDict"
|
||||||
|
});
|
||||||
|
const defaultPluginConfig = {
|
||||||
|
component: {
|
||||||
|
name: "a-input",
|
||||||
|
vModel: "value"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function buildDefineFields(define: any, form: any, mode: string) {
|
||||||
|
const formWrapperRef = crudExpose.getFormWrapperRef();
|
||||||
|
const columnsRef = toRef(formWrapperRef.formOptions, "columns");
|
||||||
|
|
||||||
|
for (const key in columnsRef.value) {
|
||||||
|
if (key.indexOf(".") >= 0) {
|
||||||
|
delete columnsRef.value[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('crudBinding.value[mode + "Form"].columns', columnsRef.value);
|
||||||
|
forEach(define.input, (value: any, mapKey: any) => {
|
||||||
|
const key = "body." + mapKey;
|
||||||
|
const field = {
|
||||||
|
...value,
|
||||||
|
key
|
||||||
|
};
|
||||||
|
const column = merge({ title: key }, defaultPluginConfig, field);
|
||||||
|
//eval
|
||||||
|
useReference(column);
|
||||||
|
|
||||||
|
//设置默认值
|
||||||
|
if (column.value != null && get(form, key) == null) {
|
||||||
|
set(form, key, column.value);
|
||||||
|
}
|
||||||
|
//字段配置赋值
|
||||||
|
columnsRef.value[key] = column;
|
||||||
|
console.log("form", columnsRef.value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentDefine = ref();
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: {
|
||||||
|
title: "ID",
|
||||||
|
key: "id",
|
||||||
|
type: "number",
|
||||||
|
column: {
|
||||||
|
width: 100
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
title: "通知名称",
|
||||||
|
search: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
type: ["text"],
|
||||||
|
form: {
|
||||||
|
rules: [{ required: true, message: "请填写名称" }],
|
||||||
|
helper: "随便填,当多个相同类型的通知时,便于区分"
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 200
|
||||||
|
}
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
title: "类型",
|
||||||
|
type: "dict-select",
|
||||||
|
dict: notificationTypeDictRef,
|
||||||
|
search: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 200,
|
||||||
|
component: {
|
||||||
|
color: "auto"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
component: {
|
||||||
|
disabled: false,
|
||||||
|
showSearch: true,
|
||||||
|
filterOption: (input: string, option: any) => {
|
||||||
|
input = input?.toLowerCase();
|
||||||
|
return option.value.toLowerCase().indexOf(input) >= 0 || option.label.toLowerCase().indexOf(input) >= 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rules: [{ required: true, message: "请选择类型" }],
|
||||||
|
valueChange: {
|
||||||
|
immediate: true,
|
||||||
|
async handle({ value, mode, form, immediate }) {
|
||||||
|
if (value == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const define = await api.GetProviderDefine(value);
|
||||||
|
currentDefine.value = define;
|
||||||
|
console.log("define", define);
|
||||||
|
if (!immediate) {
|
||||||
|
form.body = {};
|
||||||
|
}
|
||||||
|
buildDefineFields(define, form, mode);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
helper: computed(() => {
|
||||||
|
const define = currentDefine.value;
|
||||||
|
if (define == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return define.desc;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
addForm: {
|
||||||
|
value: typeRef
|
||||||
|
}
|
||||||
|
} as ColumnCompositionProps,
|
||||||
|
setting: {
|
||||||
|
column: { show: false },
|
||||||
|
form: {
|
||||||
|
show: false,
|
||||||
|
valueBuilder({ value, form }) {
|
||||||
|
form.body = {};
|
||||||
|
if (!value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const setting = JSON.parse(value);
|
||||||
|
for (const key in setting) {
|
||||||
|
form.body[key] = setting[key];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
valueResolve({ form }) {
|
||||||
|
const setting = form.body;
|
||||||
|
form.setting = JSON.stringify(setting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as ColumnCompositionProps
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
// @ts-ignore
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { getCommonColumnDefine } from "./common";
|
||||||
|
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||||
|
|
||||||
|
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
|
const { t } = useI18n();
|
||||||
|
const api = context.api;
|
||||||
|
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||||
|
return await api.GetList(query);
|
||||||
|
};
|
||||||
|
const editRequest = async (req: EditReq) => {
|
||||||
|
const { form, row } = req;
|
||||||
|
form.id = row.id;
|
||||||
|
const res = await api.UpdateObj(form);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
const delRequest = async (req: DelReq) => {
|
||||||
|
const { row } = req;
|
||||||
|
return await api.DelObj(row.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addRequest = async (req: AddReq) => {
|
||||||
|
const { form } = req;
|
||||||
|
const res = await api.AddObj(form);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
const typeRef = ref();
|
||||||
|
const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef, api);
|
||||||
|
return {
|
||||||
|
crudOptions: {
|
||||||
|
request: {
|
||||||
|
pageRequest,
|
||||||
|
addRequest,
|
||||||
|
editRequest,
|
||||||
|
delRequest
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
labelCol: {
|
||||||
|
span: 6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rowHandle: {
|
||||||
|
width: 200
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
...commonColumnsDefine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
<template>
|
||||||
|
<fs-page>
|
||||||
|
<template #header>
|
||||||
|
<div class="title">
|
||||||
|
通知管理
|
||||||
|
<span class="sub">管理通知配置</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||||
|
</fs-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, onActivated, onMounted } from "vue";
|
||||||
|
import { useFs } from "@fast-crud/fast-crud";
|
||||||
|
import createCrudOptions from "./crud";
|
||||||
|
import { createApi } from "./api";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "NotificationManager",
|
||||||
|
setup() {
|
||||||
|
const api = createApi();
|
||||||
|
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { api } });
|
||||||
|
|
||||||
|
// 页面打开后获取列表数据
|
||||||
|
onMounted(() => {
|
||||||
|
crudExpose.doRefresh();
|
||||||
|
});
|
||||||
|
onActivated(() => {
|
||||||
|
crudExpose.doRefresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
crudBinding,
|
||||||
|
crudRef
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,139 @@
|
||||||
|
<template>
|
||||||
|
<div class="notification-selector">
|
||||||
|
<span v-if="target.name" class="mr-5 cd-flex-inline">
|
||||||
|
<a-tag class="mr-5" color="green">{{ target.name }}</a-tag>
|
||||||
|
<fs-icon class="cd-icon-button" icon="ion:close-circle-outline" @click="clear"></fs-icon>
|
||||||
|
</span>
|
||||||
|
<span v-else class="mlr-5 text-gray">{{ placeholder }}</span>
|
||||||
|
<a-button class="ml-5" :size="size" @click="chooseForm.open">选择</a-button>
|
||||||
|
<a-form-item-rest v-if="chooseForm.show">
|
||||||
|
<a-modal v-model:open="chooseForm.show" title="选择通知" width="900px" @ok="chooseForm.ok">
|
||||||
|
<div style="height: 400px; position: relative">
|
||||||
|
<cert-notification-modal v-model="selectedId" :type="type" :from="from"></cert-notification-modal>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</a-form-item-rest>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent, reactive, ref, watch, inject } from "vue";
|
||||||
|
import CertNotificationModal from "./modal/index.vue";
|
||||||
|
import { createApi } from "../api";
|
||||||
|
import { message } from "ant-design-vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "NotificationSelector",
|
||||||
|
components: { CertNotificationModal },
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: "请选择"
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: "middle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ["update:modelValue"],
|
||||||
|
setup(props, ctx) {
|
||||||
|
const api = createApi();
|
||||||
|
|
||||||
|
const target = ref({});
|
||||||
|
const selectedId = ref();
|
||||||
|
async function refreshTarget(value) {
|
||||||
|
selectedId.value = value;
|
||||||
|
if (value > 0) {
|
||||||
|
target.value = await api.GetSimpleInfo(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear() {
|
||||||
|
if (pipeline && pipeline.userId !== target.value.userId) {
|
||||||
|
message.error("对不起,您不能修改他人流水线的通知");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selectedId.value = "";
|
||||||
|
target.value = null;
|
||||||
|
ctx.emit("update:modelValue", selectedId.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => {
|
||||||
|
return props.modelValue;
|
||||||
|
},
|
||||||
|
async (value) => {
|
||||||
|
selectedId.value = null;
|
||||||
|
target.value = {};
|
||||||
|
if (value == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await refreshTarget(value);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const providerDefine = ref({});
|
||||||
|
|
||||||
|
async function refreshProviderDefine(type) {
|
||||||
|
providerDefine.value = await api.GetProviderDefine(type);
|
||||||
|
}
|
||||||
|
watch(
|
||||||
|
() => {
|
||||||
|
return props.type;
|
||||||
|
},
|
||||||
|
async (value) => {
|
||||||
|
await refreshProviderDefine(value);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
//当不在pipeline中编辑时,可能为空
|
||||||
|
const pipeline = inject("pipeline", null);
|
||||||
|
|
||||||
|
const chooseForm = reactive({
|
||||||
|
show: false,
|
||||||
|
open() {
|
||||||
|
chooseForm.show = true;
|
||||||
|
},
|
||||||
|
ok: () => {
|
||||||
|
chooseForm.show = false;
|
||||||
|
console.log("choose ok:", selectedId.value);
|
||||||
|
refreshTarget(selectedId.value);
|
||||||
|
|
||||||
|
if (pipeline && pipeline.userId !== target.value.userId) {
|
||||||
|
message.error("对不起,您不能修改他人流水线的授权");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.emit("change", selectedId.value);
|
||||||
|
ctx.emit("update:modelValue", selectedId.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
clear,
|
||||||
|
target,
|
||||||
|
selectedId,
|
||||||
|
providerDefine,
|
||||||
|
chooseForm
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="less">
|
||||||
|
.notification-selector {
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,90 @@
|
||||||
|
// @ts-ignore
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { getCommonColumnDefine } from "../../common";
|
||||||
|
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||||
|
|
||||||
|
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
|
const { crudBinding } = crudExpose;
|
||||||
|
const { props, ctx, api } = context;
|
||||||
|
const lastResRef = ref();
|
||||||
|
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||||
|
return await context.api.GetList(query);
|
||||||
|
};
|
||||||
|
const editRequest = async (req: EditReq) => {
|
||||||
|
const { form, row } = req;
|
||||||
|
form.id = row.id;
|
||||||
|
form.type = props.type;
|
||||||
|
const res = await context.api.UpdateObj(form);
|
||||||
|
lastResRef.value = res;
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
const delRequest = async (req: DelReq) => {
|
||||||
|
const { row } = req;
|
||||||
|
return await context.api.DelObj(row.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addRequest = async (req: AddReq) => {
|
||||||
|
const { form } = req;
|
||||||
|
form.type = props.type;
|
||||||
|
const res = await context.api.AddObj(form);
|
||||||
|
lastResRef.value = res;
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectedRowKey = ref([props.modelValue]);
|
||||||
|
|
||||||
|
const onSelectChange = (changed: any) => {
|
||||||
|
selectedRowKey.value = changed;
|
||||||
|
ctx.emit("update:modelValue", changed[0]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const typeRef = ref("");
|
||||||
|
context.typeRef = typeRef;
|
||||||
|
const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef, api);
|
||||||
|
commonColumnsDefine.type.form.component.disabled = true;
|
||||||
|
return {
|
||||||
|
typeRef,
|
||||||
|
crudOptions: {
|
||||||
|
request: {
|
||||||
|
pageRequest,
|
||||||
|
addRequest,
|
||||||
|
editRequest,
|
||||||
|
delRequest
|
||||||
|
},
|
||||||
|
toolbar: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
wrapper: {
|
||||||
|
width: "1050px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rowHandle: {
|
||||||
|
width: 200
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
scroll: {
|
||||||
|
x: 800
|
||||||
|
},
|
||||||
|
rowSelection: {
|
||||||
|
type: "radio",
|
||||||
|
selectedRowKeys: selectedRowKey,
|
||||||
|
onChange: onSelectChange
|
||||||
|
},
|
||||||
|
customRow: (record: any) => {
|
||||||
|
return {
|
||||||
|
onClick: () => {
|
||||||
|
onSelectChange([record.id]);
|
||||||
|
} // 点击行
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
...commonColumnsDefine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
<template>
|
||||||
|
<fs-page class="page-cert-notification-modal">
|
||||||
|
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||||
|
</fs-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, onMounted, watch } from "vue";
|
||||||
|
import { useFs } from "@fast-crud/fast-crud";
|
||||||
|
import createCrudOptions from "./crud";
|
||||||
|
import { createApi } from "../../api";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "CertNotificationModal",
|
||||||
|
props: {
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
modelValue: {}
|
||||||
|
},
|
||||||
|
emits: ["update:modelValue"],
|
||||||
|
setup(props, ctx) {
|
||||||
|
const api = createApi();
|
||||||
|
const context: any = { props, ctx, api };
|
||||||
|
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context });
|
||||||
|
|
||||||
|
// 你可以调用此方法,重新初始化crud配置
|
||||||
|
function onTypeChanged(value: any) {
|
||||||
|
context.typeRef.value = value;
|
||||||
|
crudExpose.setSearchFormData({ form: { type: value }, mergeForm: true });
|
||||||
|
crudExpose.doRefresh();
|
||||||
|
}
|
||||||
|
watch(
|
||||||
|
() => {
|
||||||
|
return props.type;
|
||||||
|
},
|
||||||
|
(value) => {
|
||||||
|
console.log("access type changed:", value);
|
||||||
|
onTypeChanged(value);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// 页面打开后获取列表数据
|
||||||
|
onMounted(() => {
|
||||||
|
onTypeChanged(props.type);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
crudBinding,
|
||||||
|
crudRef
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="less">
|
||||||
|
.page-cert-notification {
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
CREATE TABLE "pi_notification" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "user_id" integer NOT NULL, "name" varchar(100) NOT NULL, "type" varchar(100) NOT NULL, "setting" text, "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP));
|
|
@ -0,0 +1,91 @@
|
||||||
|
import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
|
||||||
|
import { Constants, CrudController } from '@certd/lib-server';
|
||||||
|
import { NotificationService } from '../../modules/pipeline/service/notification-service.js';
|
||||||
|
import { AuthService } from '../../modules/sys/authority/service/auth-service.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知
|
||||||
|
*/
|
||||||
|
@Provide()
|
||||||
|
@Controller('/api/pi/notification')
|
||||||
|
export class NotificationController extends CrudController<NotificationService> {
|
||||||
|
@Inject()
|
||||||
|
service: NotificationService;
|
||||||
|
@Inject()
|
||||||
|
authService: AuthService;
|
||||||
|
|
||||||
|
getService(): NotificationService {
|
||||||
|
return this.service;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/page', { summary: Constants.per.authOnly })
|
||||||
|
async page(@Body(ALL) body) {
|
||||||
|
body.query = body.query ?? {};
|
||||||
|
delete body.query.userId;
|
||||||
|
const buildQuery = qb => {
|
||||||
|
qb.andWhere('user_id = :userId', { userId: this.getUserId() });
|
||||||
|
};
|
||||||
|
const res = await this.service.page({
|
||||||
|
query: body.query,
|
||||||
|
page: body.page,
|
||||||
|
sort: body.sort,
|
||||||
|
buildQuery,
|
||||||
|
});
|
||||||
|
return this.ok(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/list', { summary: Constants.per.authOnly })
|
||||||
|
async list(@Body(ALL) body) {
|
||||||
|
body.userId = this.getUserId();
|
||||||
|
return super.list(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/add', { summary: Constants.per.authOnly })
|
||||||
|
async add(@Body(ALL) bean) {
|
||||||
|
bean.userId = this.getUserId();
|
||||||
|
return super.add(bean);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/update', { summary: Constants.per.authOnly })
|
||||||
|
async update(@Body(ALL) bean) {
|
||||||
|
await this.service.checkUserId(bean.id, this.getUserId());
|
||||||
|
return super.update(bean);
|
||||||
|
}
|
||||||
|
@Post('/info', { summary: Constants.per.authOnly })
|
||||||
|
async info(@Query('id') id: number) {
|
||||||
|
await this.service.checkUserId(id, this.getUserId());
|
||||||
|
return super.info(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/delete', { summary: Constants.per.authOnly })
|
||||||
|
async delete(@Query('id') id: number) {
|
||||||
|
await this.service.checkUserId(id, this.getUserId());
|
||||||
|
return super.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/define', { summary: Constants.per.authOnly })
|
||||||
|
async define(@Query('type') type: string) {
|
||||||
|
const notification = this.service.getDefineByType(type);
|
||||||
|
return this.ok(notification);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/getTypeDict', { summary: Constants.per.authOnly })
|
||||||
|
async getTypeDict() {
|
||||||
|
const list = this.service.getDefineList();
|
||||||
|
const dict = [];
|
||||||
|
for (const item of list) {
|
||||||
|
dict.push({
|
||||||
|
value: item.name,
|
||||||
|
label: item.title,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this.ok(dict);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/simpleInfo', { summary: Constants.per.authOnly })
|
||||||
|
async simpleInfo(@Query('id') id: number) {
|
||||||
|
await this.authService.checkEntityUserId(this.ctx, this.service, id);
|
||||||
|
const res = await this.service.getSimpleInfo(id);
|
||||||
|
return this.ok(res);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||||
|
|
||||||
|
@Entity('pi_notification')
|
||||||
|
export class NotificationEntity {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column({ name: 'user_id', comment: 'UserId' })
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'type', comment: '通知类型' })
|
||||||
|
type: string;
|
||||||
|
|
||||||
|
@Column({ name: 'name', comment: '名称' })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column({ name: 'setting', comment: '通知配置', length: 10240 })
|
||||||
|
setting: string;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'create_time',
|
||||||
|
comment: '创建时间',
|
||||||
|
default: () => 'CURRENT_TIMESTAMP',
|
||||||
|
})
|
||||||
|
createTime: Date;
|
||||||
|
@Column({
|
||||||
|
name: 'update_time',
|
||||||
|
comment: '修改时间',
|
||||||
|
default: () => 'CURRENT_TIMESTAMP',
|
||||||
|
})
|
||||||
|
updateTime: Date;
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||||
|
import { BaseService, ValidateException } from '@certd/lib-server';
|
||||||
|
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { NotificationEntity } from '../entity/notification.js';
|
||||||
|
import { notificationRegistry } from '@certd/pipeline';
|
||||||
|
|
||||||
|
@Provide()
|
||||||
|
@Scope(ScopeEnum.Singleton)
|
||||||
|
export class NotificationService extends BaseService<NotificationEntity> {
|
||||||
|
@InjectEntityModel(NotificationEntity)
|
||||||
|
repository: Repository<NotificationEntity>;
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
getRepository() {
|
||||||
|
return this.repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSimpleInfo(id: number) {
|
||||||
|
const entity = await this.info(id);
|
||||||
|
if (entity == null) {
|
||||||
|
throw new ValidateException('该通知配置不存在,请确认是否已被删除');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: entity.id,
|
||||||
|
name: entity.name,
|
||||||
|
userId: entity.userId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefineList() {
|
||||||
|
return notificationRegistry.getDefineList();
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefineByType(type: string) {
|
||||||
|
return notificationRegistry.getDefine(type);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { BaseNotification, IsNotification, NotificationBody, NotificationInput } from '@certd/pipeline';
|
||||||
|
|
||||||
|
@IsNotification({
|
||||||
|
name: 'email',
|
||||||
|
title: '电子邮件',
|
||||||
|
desc: '电子邮件通知',
|
||||||
|
})
|
||||||
|
export class EmailNotification extends BaseNotification {
|
||||||
|
@NotificationInput({
|
||||||
|
title: '收件人邮箱',
|
||||||
|
component: {
|
||||||
|
name: 'a-select',
|
||||||
|
vModel: 'value',
|
||||||
|
mode: 'tags',
|
||||||
|
open: false,
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
helper: '可以填写多个,填写一个按回车键再填写下一个',
|
||||||
|
})
|
||||||
|
receivers!: string[];
|
||||||
|
|
||||||
|
async send(body: NotificationBody) {
|
||||||
|
await this.ctx.emailService.send({
|
||||||
|
userId: body.userId,
|
||||||
|
subject: body.title,
|
||||||
|
content: body.content,
|
||||||
|
receivers: this.receivers,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './qywx/index.js';
|
||||||
|
export * from './email/index.js';
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { BaseNotification, IsNotification, NotificationBody, NotificationInput } from '@certd/pipeline';
|
||||||
|
|
||||||
|
@IsNotification({
|
||||||
|
name: 'qywx',
|
||||||
|
title: '企业微信通知',
|
||||||
|
desc: '企业微信群聊机器人通知',
|
||||||
|
})
|
||||||
|
export class QywxNotification extends BaseNotification {
|
||||||
|
@NotificationInput({
|
||||||
|
title: 'webhook地址',
|
||||||
|
component: {
|
||||||
|
placeholder: 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxxx',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
webhook = '';
|
||||||
|
|
||||||
|
@NotificationInput({
|
||||||
|
title: '提醒指定成员',
|
||||||
|
component: {
|
||||||
|
name: 'a-select',
|
||||||
|
vModel: 'value',
|
||||||
|
mode: 'tags',
|
||||||
|
open: false,
|
||||||
|
},
|
||||||
|
required: false,
|
||||||
|
helper: '填写成员名字,@all 为提醒所有人',
|
||||||
|
})
|
||||||
|
mentionedList!: string[];
|
||||||
|
|
||||||
|
async send(body: NotificationBody) {
|
||||||
|
console.log('send qywx');
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* "msgtype": "text",
|
||||||
|
* "text": {
|
||||||
|
* "content": "hello world"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
|
||||||
|
await this.http.request({
|
||||||
|
url: this.webhook,
|
||||||
|
data: {
|
||||||
|
msgtype: 'markdown',
|
||||||
|
text: {
|
||||||
|
content: `# ${body.title}\n\n${body.content}\n[查看详情](${body.url})`,
|
||||||
|
mentioned_list: this.mentionedList,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue