@@ -1,18 +1,31 @@
|
||||
import { BadRequestException, Injectable, Logger, OnModuleInit } from '@nestjs/common';
|
||||
import {
|
||||
WECOM_BIZ_TODO_PUSH_NAME,
|
||||
WECOM_DEAL_BROADCAST_PUSH_NAME,
|
||||
WECOM_PUSH_CONDITIONS,
|
||||
WECOM_PUSH_DEFAULT_ALERT_CONDITIONS,
|
||||
WECOM_PUSH_DEFAULT_BIZ_TODO_CONDITIONS,
|
||||
WECOM_PUSH_DEFAULT_DEAL_BROADCAST_CONDITIONS,
|
||||
WECOM_PUSH_DEFAULT_DEV_DISPATCH_CONDITIONS,
|
||||
WECOM_PUSH_DEFAULT_STORE_AUDIT_CONDITIONS,
|
||||
WECOM_STORE_AUDIT_PLACEHOLDER_WEBHOOK,
|
||||
WECOM_STORE_AUDIT_PUSH_NAME,
|
||||
WECOM_TEMPLATE_EVENT_KEYS,
|
||||
maskWecomWebhookUrl,
|
||||
parseWecomPushConditions,
|
||||
type WecomMessagePushDto,
|
||||
type WecomPushCondition,
|
||||
type WecomPushTemplateDto,
|
||||
type WecomTemplateEventKey,
|
||||
} from '@dukang/shared-types';
|
||||
import { PrismaService } from '../../common/prisma/prisma.module';
|
||||
import { applyWecomAtMentionInContent } from '../../modules/dev-plan/dev-plan-wecom-mention.util';
|
||||
import {
|
||||
WECOM_PUSH_TEMPLATE_DEFAULTS,
|
||||
buildHqHandleUrl,
|
||||
getDefaultTemplate,
|
||||
renderWecomTemplate,
|
||||
} from './wecom-push-template.defaults';
|
||||
|
||||
type PushRow = {
|
||||
id: bigint;
|
||||
@@ -27,6 +40,16 @@ type PushRow = {
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
type TemplateRow = {
|
||||
id: bigint;
|
||||
eventKey: string;
|
||||
title: string;
|
||||
body: string;
|
||||
handleLabel: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class WecomMessagePushService implements OnModuleInit {
|
||||
private readonly logger = new Logger(WecomMessagePushService.name);
|
||||
@@ -43,7 +66,7 @@ export class WecomMessagePushService implements OnModuleInit {
|
||||
}
|
||||
}
|
||||
|
||||
/** 表空时从 .env / 旧 dev_plan_settings 迁移默认推送;并按名称 upsert「门店审核通知群」(v3.5.3) */
|
||||
/** 表空时从 .env / 旧设置迁移;并确保门店审核 / 业务待办 / 成交播报 / 模板缺行 */
|
||||
async ensureDefaults(): Promise<void> {
|
||||
const count = await this.prisma.wecomMessagePush.count();
|
||||
if (count === 0) {
|
||||
@@ -100,45 +123,81 @@ export class WecomMessagePushService implements OnModuleInit {
|
||||
}
|
||||
}
|
||||
|
||||
await this.ensureStoreAuditPush();
|
||||
await this.ensureNamedPush(
|
||||
WECOM_STORE_AUDIT_PUSH_NAME,
|
||||
WECOM_PUSH_DEFAULT_STORE_AUDIT_CONDITIONS,
|
||||
20,
|
||||
process.env.WECOM_STORE_AUDIT_WEBHOOK_URL,
|
||||
);
|
||||
await this.ensureNamedPush(
|
||||
WECOM_BIZ_TODO_PUSH_NAME,
|
||||
WECOM_PUSH_DEFAULT_BIZ_TODO_CONDITIONS,
|
||||
25,
|
||||
process.env.WECOM_BIZ_TODO_WEBHOOK_URL,
|
||||
);
|
||||
await this.ensureNamedPush(
|
||||
WECOM_DEAL_BROADCAST_PUSH_NAME,
|
||||
WECOM_PUSH_DEFAULT_DEAL_BROADCAST_CONDITIONS,
|
||||
30,
|
||||
process.env.WECOM_DEAL_BROADCAST_WEBHOOK_URL,
|
||||
);
|
||||
await this.ensureTemplates();
|
||||
}
|
||||
|
||||
/** 按名称 upsert「门店审核通知群」:已有行保留 webhook;无行则 env 或占位 URL */
|
||||
private async ensureStoreAuditPush(): Promise<void> {
|
||||
const existing = await this.prisma.wecomMessagePush.findFirst({
|
||||
where: { name: WECOM_STORE_AUDIT_PUSH_NAME },
|
||||
});
|
||||
private async ensureNamedPush(
|
||||
name: string,
|
||||
conditions: WecomPushCondition[],
|
||||
sortOrder: number,
|
||||
envUrl?: string,
|
||||
): Promise<void> {
|
||||
const existing = await this.prisma.wecomMessagePush.findFirst({ where: { name } });
|
||||
if (existing) return;
|
||||
|
||||
const conditionsJson = JSON.stringify(WECOM_PUSH_DEFAULT_STORE_AUDIT_CONDITIONS);
|
||||
const envUrl = (process.env.WECOM_STORE_AUDIT_WEBHOOK_URL || '').trim();
|
||||
|
||||
if (envUrl) {
|
||||
const conditionsJson = JSON.stringify(conditions);
|
||||
const url = (envUrl || '').trim();
|
||||
if (url) {
|
||||
await this.prisma.wecomMessagePush.create({
|
||||
data: {
|
||||
name: WECOM_STORE_AUDIT_PUSH_NAME,
|
||||
webhookUrl: envUrl,
|
||||
name,
|
||||
webhookUrl: url,
|
||||
enabled: true,
|
||||
pushConditions: conditionsJson,
|
||||
sortOrder: 20,
|
||||
sortOrder,
|
||||
},
|
||||
});
|
||||
this.logger.log(`seeded wecom message push: ${WECOM_STORE_AUDIT_PUSH_NAME} (from env)`);
|
||||
this.logger.log(`seeded wecom message push: ${name} (from env)`);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.prisma.wecomMessagePush.create({
|
||||
data: {
|
||||
name: WECOM_STORE_AUDIT_PUSH_NAME,
|
||||
name,
|
||||
webhookUrl: WECOM_STORE_AUDIT_PLACEHOLDER_WEBHOOK,
|
||||
enabled: false,
|
||||
pushConditions: conditionsJson,
|
||||
sortOrder: 20,
|
||||
sortOrder,
|
||||
},
|
||||
});
|
||||
this.logger.log(
|
||||
`seeded wecom message push: ${WECOM_STORE_AUDIT_PUSH_NAME} (placeholder, disabled)`,
|
||||
);
|
||||
this.logger.log(`seeded wecom message push: ${name} (placeholder, disabled)`);
|
||||
}
|
||||
|
||||
/** 仅插入缺失 eventKey,不覆盖已有文案 */
|
||||
async ensureTemplates(): Promise<void> {
|
||||
for (const def of WECOM_PUSH_TEMPLATE_DEFAULTS) {
|
||||
const existing = await this.prisma.wecomPushTemplate.findUnique({
|
||||
where: { eventKey: def.eventKey },
|
||||
});
|
||||
if (existing) continue;
|
||||
await this.prisma.wecomPushTemplate.create({
|
||||
data: {
|
||||
eventKey: def.eventKey,
|
||||
title: def.title,
|
||||
body: def.body,
|
||||
handleLabel: def.handleLabel,
|
||||
},
|
||||
});
|
||||
this.logger.log(`seeded wecom push template: ${def.eventKey}`);
|
||||
}
|
||||
}
|
||||
|
||||
async listMatchingPushes(eventKey: WecomPushCondition): Promise<PushRow[]> {
|
||||
@@ -154,6 +213,58 @@ export class WecomMessagePushService implements OnModuleInit {
|
||||
return pushes.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读 HQ 模板 → 插值 → 补快链 → 按条件路由推送。
|
||||
* 失败只打日志,不抛给业务。
|
||||
*/
|
||||
async dispatchEvent(
|
||||
eventKey: WecomTemplateEventKey,
|
||||
vars: Record<string, string | number | null | undefined>,
|
||||
options?: { applyMention?: boolean; handlePath?: string },
|
||||
): Promise<number> {
|
||||
try {
|
||||
const content = await this.renderEventContent(eventKey, vars, options?.handlePath);
|
||||
return await this.dispatchMarkdown(eventKey, content, {
|
||||
applyMention: options?.applyMention ?? false,
|
||||
});
|
||||
} catch (e) {
|
||||
this.logger.warn(
|
||||
`dispatchEvent(${eventKey}) failed: ${e instanceof Error ? e.message : String(e)}`,
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
async renderEventContent(
|
||||
eventKey: WecomTemplateEventKey,
|
||||
vars: Record<string, string | number | null | undefined>,
|
||||
handlePath?: string,
|
||||
): Promise<string> {
|
||||
const row = await this.prisma.wecomPushTemplate.findUnique({ where: { eventKey } });
|
||||
const def = getDefaultTemplate(eventKey);
|
||||
const body = row?.body || def?.body || `**${eventKey}**`;
|
||||
const handleLabel = row?.handleLabel || def?.handleLabel || '去处理';
|
||||
|
||||
const merged: Record<string, string | number | null | undefined> = {
|
||||
...vars,
|
||||
handleLabel: vars.handleLabel ?? handleLabel,
|
||||
time:
|
||||
vars.time ??
|
||||
new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }),
|
||||
};
|
||||
|
||||
if (handlePath && !merged.handleUrl) {
|
||||
merged.handleUrl = buildHqHandleUrl(handlePath);
|
||||
}
|
||||
|
||||
let content = renderWecomTemplate(body, merged).trim();
|
||||
const url = String(merged.handleUrl || '').trim();
|
||||
if (url && !content.includes(url) && !/\{\{handleUrl\}\}/.test(body)) {
|
||||
content = `${content}\n[${handleLabel}](${url})`;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
/** 向所有匹配 eventKey 的启用推送发送 markdown;返回成功发送数 */
|
||||
async dispatchMarkdown(
|
||||
eventKey: WecomPushCondition,
|
||||
@@ -239,6 +350,188 @@ export class WecomMessagePushService implements OnModuleInit {
|
||||
: { ok: false, message: 'Webhook 调用失败,请检查 URL 或 API 日志' };
|
||||
}
|
||||
|
||||
// ── templates CRUD ──
|
||||
|
||||
async listTemplates(): Promise<WecomPushTemplateDto[]> {
|
||||
await this.ensureTemplates();
|
||||
const rows = await this.prisma.wecomPushTemplate.findMany({
|
||||
orderBy: { eventKey: 'asc' },
|
||||
});
|
||||
return rows.map((r) => this.templateToDto(r));
|
||||
}
|
||||
|
||||
async getTemplate(eventKey: string): Promise<WecomPushTemplateDto> {
|
||||
this.assertTemplateKey(eventKey);
|
||||
await this.ensureTemplates();
|
||||
const row = await this.prisma.wecomPushTemplate.findUnique({ where: { eventKey } });
|
||||
if (!row) throw new BadRequestException('模板不存在');
|
||||
return this.templateToDto(row);
|
||||
}
|
||||
|
||||
async updateTemplate(
|
||||
eventKey: string,
|
||||
data: { title?: string; body?: string; handleLabel?: string },
|
||||
): Promise<WecomPushTemplateDto> {
|
||||
this.assertTemplateKey(eventKey);
|
||||
await this.ensureTemplates();
|
||||
const existing = await this.prisma.wecomPushTemplate.findUnique({ where: { eventKey } });
|
||||
if (!existing) throw new BadRequestException('模板不存在');
|
||||
|
||||
const title = data.title != null ? String(data.title).trim() : undefined;
|
||||
const body = data.body != null ? String(data.body).trim() : undefined;
|
||||
const handleLabel =
|
||||
data.handleLabel != null ? String(data.handleLabel).trim() || '去处理' : undefined;
|
||||
if (title !== undefined && !title) throw new BadRequestException('标题不能为空');
|
||||
if (body !== undefined && !body) throw new BadRequestException('正文不能为空');
|
||||
|
||||
const row = await this.prisma.wecomPushTemplate.update({
|
||||
where: { eventKey },
|
||||
data: {
|
||||
...(title !== undefined ? { title } : {}),
|
||||
...(body !== undefined ? { body } : {}),
|
||||
...(handleLabel !== undefined ? { handleLabel } : {}),
|
||||
},
|
||||
});
|
||||
return this.templateToDto(row);
|
||||
}
|
||||
|
||||
async resetTemplate(eventKey: string): Promise<WecomPushTemplateDto> {
|
||||
this.assertTemplateKey(eventKey);
|
||||
const def = getDefaultTemplate(eventKey);
|
||||
if (!def) throw new BadRequestException('无默认模板');
|
||||
await this.ensureTemplates();
|
||||
const row = await this.prisma.wecomPushTemplate.upsert({
|
||||
where: { eventKey },
|
||||
create: {
|
||||
eventKey: def.eventKey,
|
||||
title: def.title,
|
||||
body: def.body,
|
||||
handleLabel: def.handleLabel,
|
||||
},
|
||||
update: {
|
||||
title: def.title,
|
||||
body: def.body,
|
||||
handleLabel: def.handleLabel,
|
||||
},
|
||||
});
|
||||
return this.templateToDto(row);
|
||||
}
|
||||
|
||||
/** 用示例变量渲染并推到勾选了该事件的启用群 */
|
||||
async testTemplate(eventKey: string): Promise<{ ok: boolean; message: string; preview: string }> {
|
||||
this.assertTemplateKey(eventKey);
|
||||
const sample = this.sampleVars(eventKey);
|
||||
const preview = await this.renderEventContent(
|
||||
eventKey,
|
||||
sample.vars,
|
||||
sample.handlePath,
|
||||
);
|
||||
const sent = await this.dispatchMarkdown(eventKey, preview, { applyMention: false });
|
||||
if (sent === 0) {
|
||||
return {
|
||||
ok: false,
|
||||
message: '没有已启用且勾选该事件的消息推送,请先配置 webhook',
|
||||
preview,
|
||||
};
|
||||
}
|
||||
return { ok: true, message: `已发送至 ${sent} 个推送`, preview };
|
||||
}
|
||||
|
||||
private sampleVars(eventKey: WecomTemplateEventKey): {
|
||||
vars: Record<string, string>;
|
||||
handlePath: string;
|
||||
} {
|
||||
const samples: Record<WecomTemplateEventKey, { vars: Record<string, string>; handlePath: string }> = {
|
||||
'order.paid': {
|
||||
vars: {
|
||||
orderNo: 'DK202608200001',
|
||||
payAmount: '199.00',
|
||||
cityName: '郑州',
|
||||
skuSummary: '杜康原浆 ×2',
|
||||
phoneMasked: '138****8000',
|
||||
},
|
||||
handlePath: '/orders?orderNo=DK202608200001',
|
||||
},
|
||||
'redeem.success': {
|
||||
vars: {
|
||||
redeemNo: 'RD202608200001',
|
||||
amount: '88.00',
|
||||
storeName: '示例门店',
|
||||
channel: '扫码',
|
||||
},
|
||||
handlePath: '/redeem-records?redeemNo=RD202608200001',
|
||||
},
|
||||
'store.audit_pending': {
|
||||
vars: {
|
||||
storeName: '示例门店',
|
||||
cityName: '郑州',
|
||||
partnerLabel: '示例合伙人',
|
||||
action: '新建',
|
||||
storeId: '1',
|
||||
},
|
||||
handlePath: '/stores?auditStatus=PENDING&storeId=1',
|
||||
},
|
||||
'store.package_audit_pending': {
|
||||
vars: {
|
||||
storeName: '示例门店',
|
||||
cityName: '郑州',
|
||||
submitter: '合伙人',
|
||||
packageCount: '3',
|
||||
requestId: '1',
|
||||
},
|
||||
handlePath: '/store-package-audits?requestId=1',
|
||||
},
|
||||
'store.info_change_pending': {
|
||||
vars: {
|
||||
storeName: '示例门店',
|
||||
cityName: '郑州',
|
||||
submitter: '门店',
|
||||
changedFields: '门店名称、详细地址',
|
||||
requestId: '1',
|
||||
},
|
||||
handlePath: '/store-package-audits?tab=info&infoRequestId=1',
|
||||
},
|
||||
'store.withdraw_pending': {
|
||||
vars: {
|
||||
storeName: '示例门店',
|
||||
withdrawNo: 'SW202608200001',
|
||||
amount: '500.00',
|
||||
payoutCount: '5',
|
||||
storeId: '1',
|
||||
},
|
||||
handlePath: '/finance/store-bills?kind=WITHDRAW&status=PENDING_REVIEW&storeId=1',
|
||||
},
|
||||
'invoice.pending': {
|
||||
vars: {
|
||||
invoiceNo: 'IV202608200001',
|
||||
orderNo: 'DK202608200001',
|
||||
payAmount: '199.00',
|
||||
titleName: '示例公司',
|
||||
phoneMasked: '138****8000',
|
||||
},
|
||||
handlePath: '/invoices?status=PENDING&invoiceNo=IV202608200001',
|
||||
},
|
||||
};
|
||||
return samples[eventKey];
|
||||
}
|
||||
|
||||
private assertTemplateKey(eventKey: string): asserts eventKey is WecomTemplateEventKey {
|
||||
if (!(WECOM_TEMPLATE_EVENT_KEYS as readonly string[]).includes(eventKey)) {
|
||||
throw new BadRequestException(`无效模板事件:${eventKey}`);
|
||||
}
|
||||
}
|
||||
|
||||
templateToDto(row: TemplateRow): WecomPushTemplateDto {
|
||||
return {
|
||||
id: row.id.toString(),
|
||||
eventKey: row.eventKey as WecomTemplateEventKey,
|
||||
title: row.title,
|
||||
body: row.body,
|
||||
handleLabel: row.handleLabel,
|
||||
updatedAt: row.updatedAt.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
toDto(row: PushRow): WecomMessagePushDto {
|
||||
return {
|
||||
id: row.id.toString(),
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
import type { WecomTemplateEventKey } from '@dukang/shared-types';
|
||||
|
||||
export type WecomTemplateDefault = {
|
||||
eventKey: WecomTemplateEventKey;
|
||||
title: string;
|
||||
body: string;
|
||||
handleLabel: string;
|
||||
};
|
||||
|
||||
/** 代码内默认文案;ensureDefaults 仅在库中无行时写入,不覆盖 HQ 已改 */
|
||||
export const WECOM_PUSH_TEMPLATE_DEFAULTS: WecomTemplateDefault[] = [
|
||||
{
|
||||
eventKey: 'order.paid',
|
||||
title: '订单支付成功',
|
||||
body: [
|
||||
'**订单支付成功**',
|
||||
'订单号:{{orderNo}}',
|
||||
'实付:¥{{payAmount}}',
|
||||
'城市:{{cityName}}',
|
||||
'商品:{{skuSummary}}',
|
||||
'用户:{{phoneMasked}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
handleLabel: '去处理',
|
||||
},
|
||||
{
|
||||
eventKey: 'redeem.success',
|
||||
title: '门店核销成功',
|
||||
body: [
|
||||
'**门店核销成功**',
|
||||
'核销单号:{{redeemNo}}',
|
||||
'金额:¥{{amount}}',
|
||||
'门店:{{storeName}}',
|
||||
'渠道:{{channel}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
handleLabel: '去处理',
|
||||
},
|
||||
{
|
||||
eventKey: 'store.audit_pending',
|
||||
title: '门店审核待处理',
|
||||
body: [
|
||||
'**门店审核待处理 · {{action}}**',
|
||||
'门店:{{storeName}}',
|
||||
'城市:{{cityName}}',
|
||||
'合伙人:{{partnerLabel}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
handleLabel: '去处理',
|
||||
},
|
||||
{
|
||||
eventKey: 'store.package_audit_pending',
|
||||
title: '套餐变更待审核',
|
||||
body: [
|
||||
'**套餐变更待审核**',
|
||||
'门店:{{storeName}}',
|
||||
'城市:{{cityName}}',
|
||||
'提交端:{{submitter}}',
|
||||
'套餐条数:{{packageCount}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
handleLabel: '去处理',
|
||||
},
|
||||
{
|
||||
eventKey: 'store.info_change_pending',
|
||||
title: '门店信息变更待审',
|
||||
body: [
|
||||
'**门店信息变更待审**',
|
||||
'门店:{{storeName}}',
|
||||
'城市:{{cityName}}',
|
||||
'提交端:{{submitter}}',
|
||||
'变更字段:{{changedFields}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
handleLabel: '去处理',
|
||||
},
|
||||
{
|
||||
eventKey: 'store.withdraw_pending',
|
||||
title: '门店提现待审',
|
||||
body: [
|
||||
'**门店提现待审**',
|
||||
'门店:{{storeName}}',
|
||||
'提现单号:{{withdrawNo}}',
|
||||
'金额:¥{{amount}}',
|
||||
'明细笔数:{{payoutCount}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
handleLabel: '去处理',
|
||||
},
|
||||
{
|
||||
eventKey: 'invoice.pending',
|
||||
title: '发票申请待开票',
|
||||
body: [
|
||||
'**发票申请待开票**',
|
||||
'申请单号:{{invoiceNo}}',
|
||||
'订单号:{{orderNo}}',
|
||||
'金额:¥{{payAmount}}',
|
||||
'抬头:{{titleName}}',
|
||||
'用户:{{phoneMasked}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
handleLabel: '去处理',
|
||||
},
|
||||
];
|
||||
|
||||
export function getDefaultTemplate(eventKey: string): WecomTemplateDefault | undefined {
|
||||
return WECOM_PUSH_TEMPLATE_DEFAULTS.find((t) => t.eventKey === eventKey);
|
||||
}
|
||||
|
||||
/** 将 body 中的 {{key}} 替换为 vars;缺失置空 */
|
||||
export function renderWecomTemplate(
|
||||
body: string,
|
||||
vars: Record<string, string | number | null | undefined>,
|
||||
): string {
|
||||
return body.replace(/\{\{(\w+)\}\}/g, (_m, key: string) => {
|
||||
const v = vars[key];
|
||||
if (v == null) return '';
|
||||
return String(v);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* HQ 后台公网根地址(无尾斜杠)。
|
||||
* 优先 HQ_ADMIN_PUBLIC_URL;未配时按 WECOM_ALERT_ENV_LABEL / NODE_ENV 回退,
|
||||
* 避免相对路径被企微解析成 http://orders/... 这类无效链接。
|
||||
*/
|
||||
export function resolveHqAdminPublicBase(): string {
|
||||
const configured = (process.env.HQ_ADMIN_PUBLIC_URL || '').trim().replace(/\/$/, '');
|
||||
if (configured) return configured;
|
||||
|
||||
const label = (process.env.WECOM_ALERT_ENV_LABEL || process.env.NODE_ENV || '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
if (label === 'production' || label === 'prod') {
|
||||
return 'https://admin.dukanghaoke.com';
|
||||
}
|
||||
if (label === 'staging' || label === 'test' || label === 'testing') {
|
||||
return 'https://admin-test.dukanghaoke.com';
|
||||
}
|
||||
// local / development / 未识别:本机 admin-web 默认端口
|
||||
return 'http://localhost:5175';
|
||||
}
|
||||
|
||||
export function buildHqHandleUrl(pathWithQuery: string): string {
|
||||
const base = resolveHqAdminPublicBase();
|
||||
let path = (pathWithQuery || '').trim();
|
||||
if (!path) return base;
|
||||
if (!path.startsWith('/')) path = `/${path}`;
|
||||
return `${base}${path}`;
|
||||
}
|
||||
Reference in New Issue
Block a user