feat: multi-module iteration
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "@dukang/shared-types",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./src/index.ts",
|
||||
"default": "./dist/index.js"
|
||||
},
|
||||
"./user-log": {
|
||||
"types": "./dist/user-log.d.ts",
|
||||
"import": "./src/user-log.ts",
|
||||
"default": "./dist/user-log.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"lint": "eslint src"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
export interface ApiResponse<T = unknown> {
|
||||
code: number;
|
||||
message: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
export interface PaginatedData<T> {
|
||||
list: T[];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
export interface JwtPayload {
|
||||
sub: string;
|
||||
actorType: string;
|
||||
actorId: string;
|
||||
clientApp: string;
|
||||
}
|
||||
|
||||
export interface LoginResult {
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
actorType: string;
|
||||
actorId: string;
|
||||
user?: Record<string, unknown>;
|
||||
store?: Record<string, unknown>;
|
||||
partner?: Record<string, unknown>;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
export interface BenefitCouponDto {
|
||||
id: string;
|
||||
couponNo: string;
|
||||
balance: number;
|
||||
totalAmount: number;
|
||||
status: string;
|
||||
sourceProduct?: string;
|
||||
}
|
||||
|
||||
export interface BenefitSummaryDto {
|
||||
totalBalance: number;
|
||||
couponCount: number;
|
||||
}
|
||||
|
||||
export interface BenefitLedgerDto {
|
||||
id: string;
|
||||
type: string;
|
||||
amount: number;
|
||||
balanceAfter: number;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
/** HQ 手动发放权益 */
|
||||
export interface AdminBenefitGrantRequest {
|
||||
phone: string;
|
||||
amount: number;
|
||||
remark?: string;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
export interface CityDto {
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
province: string;
|
||||
status: string;
|
||||
/** 订单+核销佣金合计上限(小数,默认 0.05) */
|
||||
maxPartnerCommissionRate?: number;
|
||||
}
|
||||
|
||||
export interface ProductDto {
|
||||
id: string;
|
||||
skuCode: string;
|
||||
name: string;
|
||||
subtitle?: string | null;
|
||||
price: number;
|
||||
benefitAmount: number;
|
||||
benefitDisplay?: number;
|
||||
aromaType: string;
|
||||
status: string;
|
||||
/** 封面图(common_resource COVER / cover_resource_id) */
|
||||
mainImageUrl?: string | null;
|
||||
/** 轮播图(bizType=CAROUSEL;无则回退封面) */
|
||||
carouselUrls?: string[];
|
||||
/** 详情长图(bizType=DETAIL 或 detailContent JSON) */
|
||||
detailImageUrls?: string[];
|
||||
detailContent?: ProductDetailContentDto | null;
|
||||
/** 是否允许现场取货下单 */
|
||||
allowOnSitePickup?: boolean;
|
||||
/** 是否允许配送到址(同城线上购买) */
|
||||
allowOnlinePurchase?: boolean;
|
||||
/** 是否允许跨城配送 */
|
||||
allowCrossCityDelivery?: boolean;
|
||||
}
|
||||
|
||||
export interface ProductDetailFeatureDto {
|
||||
icon: string;
|
||||
title: string;
|
||||
desc: string;
|
||||
}
|
||||
|
||||
export interface ProductDetailContentDto {
|
||||
storyTitle?: string;
|
||||
storyText?: string;
|
||||
features?: ProductDetailFeatureDto[];
|
||||
/** 兜底详情图,优先 DETAIL 资源 */
|
||||
images?: string[];
|
||||
}
|
||||
|
||||
export interface ProductListQuery {
|
||||
cityCode?: string;
|
||||
aromaType?: string;
|
||||
}
|
||||
|
||||
export interface ProductDetailTemplateDto {
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
description?: string | null;
|
||||
aromaType?: string | null;
|
||||
storyTitle?: string | null;
|
||||
storyText?: string | null;
|
||||
features?: ProductDetailFeatureDto[];
|
||||
detailImageUrls?: string[];
|
||||
suggestedDetailImageCount: number;
|
||||
sortOrder: number;
|
||||
status: 'ACTIVE' | 'DISABLED';
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import type { CityPartnerScopeType, CityPartnerStatus } from './enums';
|
||||
|
||||
/** @deprecated use PartnerPrimaryAccountDto */
|
||||
export type CityPartnerDto = PartnerPrimaryAccountDto;
|
||||
|
||||
export interface PartnerPrimaryAccountDto {
|
||||
id: string;
|
||||
cityId: string;
|
||||
cityName?: string | null;
|
||||
cityCode?: string | null;
|
||||
companyName: string;
|
||||
contactPhone?: string | null;
|
||||
address?: string | null;
|
||||
phone: string;
|
||||
name: string;
|
||||
scopeType: CityPartnerScopeType;
|
||||
districtCodes: string[] | null;
|
||||
orderCommissionRate: number;
|
||||
redeemCommissionRate: number;
|
||||
bindingStatus: CityPartnerStatus;
|
||||
managedWarehouseId?: string | null;
|
||||
status: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface CreatePartnerPrimaryInput {
|
||||
cityId: string;
|
||||
phone: string;
|
||||
name: string;
|
||||
companyName: string;
|
||||
address: string;
|
||||
contactPhone?: string;
|
||||
scopeType: CityPartnerScopeType;
|
||||
districtCodes?: string[];
|
||||
orderCommissionRate?: number;
|
||||
redeemCommissionRate?: number;
|
||||
bindingStatus?: CityPartnerStatus;
|
||||
managedWarehouseId?: string;
|
||||
contractNo?: string;
|
||||
bankAccountName?: string;
|
||||
bankAccountNo?: string;
|
||||
bankBranch?: string;
|
||||
weeklyStoreTarget?: number;
|
||||
}
|
||||
|
||||
export interface UpdatePartnerPrimaryInput {
|
||||
name?: string;
|
||||
phone?: string;
|
||||
companyName?: string;
|
||||
address?: string;
|
||||
contactPhone?: string;
|
||||
scopeType?: CityPartnerScopeType;
|
||||
districtCodes?: string[];
|
||||
orderCommissionRate?: number;
|
||||
redeemCommissionRate?: number;
|
||||
bindingStatus?: CityPartnerStatus;
|
||||
managedWarehouseId?: string | null;
|
||||
contractNo?: string;
|
||||
bankAccountName?: string;
|
||||
bankAccountNo?: string;
|
||||
bankBranch?: string;
|
||||
weeklyStoreTarget?: number;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
export interface PartnerCityBindingRef {
|
||||
id: string;
|
||||
partnerAccountId: string;
|
||||
scopeType: CityPartnerScopeType;
|
||||
districtCodes: string[] | null;
|
||||
orderCommissionRate: number;
|
||||
redeemCommissionRate: number;
|
||||
bindingStatus: CityPartnerStatus;
|
||||
}
|
||||
|
||||
export const PARTNER_PERMISSION_KEYS = [
|
||||
'warehouse:manage',
|
||||
'store:manage',
|
||||
'store:create',
|
||||
'order:view',
|
||||
] as const;
|
||||
|
||||
export type PartnerPermissionKey = (typeof PARTNER_PERMISSION_KEYS)[number];
|
||||
|
||||
export const PARTNER_PERMISSION_LABELS: Record<PartnerPermissionKey, string> = {
|
||||
'warehouse:manage': '仓库管理',
|
||||
'store:manage': '门店管理',
|
||||
'store:create': '开店管理',
|
||||
'order:view': '订单查看',
|
||||
};
|
||||
|
||||
/** 门店类子账号默认权限:录入、开闭店、维护资料 */
|
||||
export const DEFAULT_PARTNER_STORE_STAFF_PERMISSIONS: PartnerPermissionKey[] = [
|
||||
'store:create',
|
||||
'store:manage',
|
||||
];
|
||||
@@ -0,0 +1,56 @@
|
||||
import type { WarehouseFulfillmentMode, WarehouseManagerType, WarehouseStatus } from './enums';
|
||||
|
||||
export interface CityWarehouseDto {
|
||||
id: string;
|
||||
cityId: string;
|
||||
name: string;
|
||||
address: string;
|
||||
contactName: string;
|
||||
contactPhone: string;
|
||||
managerType: WarehouseManagerType;
|
||||
partnerAccountId: string | null;
|
||||
partnerCompanyName?: string | null;
|
||||
status: WarehouseStatus;
|
||||
fulfillmentMode: WarehouseFulfillmentMode;
|
||||
fulfillmentProviderId: string | null;
|
||||
fulfillmentProviderName?: string | null;
|
||||
fulfillmentProviderCode?: string | null;
|
||||
manualCarrierLabel?: string | null;
|
||||
manualQueryUrlTemplate?: string | null;
|
||||
lng?: number | null;
|
||||
lat?: number | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface CreateCityWarehouseInput {
|
||||
name: string;
|
||||
address: string;
|
||||
contactName: string;
|
||||
contactPhone: string;
|
||||
managerType: WarehouseManagerType;
|
||||
partnerAccountId?: string;
|
||||
status?: WarehouseStatus;
|
||||
fulfillmentMode?: WarehouseFulfillmentMode;
|
||||
fulfillmentProviderId?: string;
|
||||
manualCarrierLabel?: string;
|
||||
manualQueryUrlTemplate?: string;
|
||||
lng?: number;
|
||||
lat?: number;
|
||||
}
|
||||
|
||||
export interface UpdateCityWarehouseInput {
|
||||
name?: string;
|
||||
address?: string;
|
||||
contactName?: string;
|
||||
contactPhone?: string;
|
||||
managerType?: WarehouseManagerType;
|
||||
partnerAccountId?: string | null;
|
||||
status?: WarehouseStatus;
|
||||
fulfillmentMode?: WarehouseFulfillmentMode;
|
||||
fulfillmentProviderId?: string | null;
|
||||
manualCarrierLabel?: string | null;
|
||||
manualQueryUrlTemplate?: string | null;
|
||||
lng?: number | null;
|
||||
lat?: number | null;
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
export interface AppConfig {
|
||||
mockSms: boolean;
|
||||
mockPay: boolean;
|
||||
mockDeliveryAuto: boolean;
|
||||
autoApproveStore: boolean;
|
||||
/** Mock 微信 OAuth/登录/绑定;关闭且凭证齐全时走真实微信 */
|
||||
mockWechat: boolean;
|
||||
/** 前端是否展示微信授权入口(由 mockWechat 或真实凭证推导) */
|
||||
wxAuthorize: boolean;
|
||||
/** 是否可走真实 JSAPI 支付(由 MOCK_PAY 与商户凭证推导) */
|
||||
wechatPayEnabled: boolean;
|
||||
wxAppId: string;
|
||||
/** 小程序 AppID(code2session 与小程序支付;未配置时回退 wxAppId) */
|
||||
wxMiniAppId: string;
|
||||
wxMchId: string;
|
||||
aliyunSmsSignName: string;
|
||||
aliyunSmsTemplateCode: string;
|
||||
/** 核销确认短信模板(REDEEM_PHONE_CONFIRM);env: ALIYUN_SMS_TEMPLATE_CONFIRM_REDEEM */
|
||||
aliyunSmsRedeemConfirmTemplateCode: string;
|
||||
/** 合伙人代下单短信模板(PARTNER_PROXY_ORDER / PARTNER_PROXY_CUSTOMER);env: ALIYUN_SMS_TEMPLATE_PROXY_ORDER */
|
||||
aliyunSmsProxyOrderTemplateCode: string;
|
||||
aliyunSmsAccessKeyId: string;
|
||||
aliyunSmsAccessKeySecret: string;
|
||||
/** 腾讯位置服务 Key(逆地理编码 / 地点搜索) */
|
||||
tencentLbsKey: string;
|
||||
/**
|
||||
* 腾讯位置服务 SecretKey(SK)
|
||||
* 控制台开启 WebServiceAPI 签名校验后生成;仅服务端签名用,勿下发前端
|
||||
*/
|
||||
tencentLbsSecretKey: string;
|
||||
/** C 端 H5 落地页(推广码二维码链接前缀) */
|
||||
userH5Url: string;
|
||||
}
|
||||
|
||||
/** 推广码 / C 端 H5 默认落地页(未配置 USER_H5_URL 时使用) */
|
||||
export const DEFAULT_USER_H5_URL = 'https://user.runxian.top/user';
|
||||
|
||||
/** 品牌 Logo OSS 根路径(改环境时只改此处) */
|
||||
export const BRAND_LOGO_OSS_BASE = 'https://dukang-dev.oss-cn-beijing.aliyuncs.com/logo/';
|
||||
|
||||
/** 方形 Logo(首页等品牌展示;商品列表顶栏仍用文字标题) */
|
||||
export const BRAND_LOGO_URL = `${BRAND_LOGO_OSS_BASE}logo.png`;
|
||||
|
||||
/** 长方形 Logo(含文字,登录等场景) */
|
||||
export const BRAND_LOGO_WIDE_URL = `${BRAND_LOGO_OSS_BASE}logo1.png`;
|
||||
|
||||
/** 仅图标 Logo(默认头像:未微信授权时) */
|
||||
export const BRAND_LOGO_MARK_URL = `${BRAND_LOGO_OSS_BASE}logo2.png`;
|
||||
|
||||
/** 小程序静态资源(资质公示等) */
|
||||
export const MINI_USER_STATIC_OSS_BASE =
|
||||
'https://dukang-dev.oss-cn-beijing.aliyuncs.com/static/mini-user/';
|
||||
|
||||
/** 「我的」页资质公示长图 */
|
||||
export const QUALIFICATION_DISCLOSURE_URL = `${MINI_USER_STATIC_OSS_BASE}qualification-disclosure.png`;
|
||||
|
||||
/** 总部客服电话(C 端联系客服) */
|
||||
export const CUSTOMER_SERVICE_PHONE = '13203801799';
|
||||
|
||||
/**
|
||||
* 企业微信「微信客服」链接(C 端「在线客服」;微信内网页点击后进入原生客服会话)
|
||||
* 可在 h5-user 用 VITE_CS_WECOM_URL 覆盖
|
||||
*/
|
||||
export const CUSTOMER_SERVICE_WECOM_URL =
|
||||
'https://work.weixin.qq.com/kfid/kfc8b88659a1dffa8cd';
|
||||
|
||||
function readEnv(env?: Record<string, string | undefined>) {
|
||||
return (
|
||||
env ??
|
||||
(globalThis as { process?: { env: Record<string, string | undefined> } }).process?.env ??
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
export function hasWechatAuthCredentials(env?: Record<string, string | undefined>): boolean {
|
||||
const e = readEnv(env);
|
||||
return !!(e.WX_APP_ID?.trim() && e.WX_APP_SECRET?.trim());
|
||||
}
|
||||
|
||||
export function hasWechatPayCredentials(env?: Record<string, string | undefined>): boolean {
|
||||
const e = readEnv(env);
|
||||
const appId = e.WX_MINI_APP_ID?.trim() || e.WX_APP_ID?.trim();
|
||||
return !!(
|
||||
appId &&
|
||||
e.WX_MCH_ID?.trim() &&
|
||||
e.WX_MCH_SERIAL_NO?.trim() &&
|
||||
e.WX_MCH_PRIVATE_KEY?.trim() &&
|
||||
e.WX_API_V3_KEY?.trim()
|
||||
);
|
||||
}
|
||||
|
||||
/** 是否应使用真实微信 API(授权或支付任一需要) */
|
||||
export function needsRealWechatApi(cfg: Pick<AppConfig, 'mockWechat' | 'mockPay'>, env?: Record<string, string | undefined>): boolean {
|
||||
return (
|
||||
(!cfg.mockWechat && hasWechatAuthCredentials(env)) ||
|
||||
(!cfg.mockPay && hasWechatPayCredentials(env))
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveWxAuthorize(cfg: Pick<AppConfig, 'mockWechat'>, env?: Record<string, string | undefined>): boolean {
|
||||
return cfg.mockWechat || hasWechatAuthCredentials(env);
|
||||
}
|
||||
|
||||
export function resolveWechatPayEnabled(cfg: Pick<AppConfig, 'mockPay'>, env?: Record<string, string | undefined>): boolean {
|
||||
return !cfg.mockPay && hasWechatPayCredentials(env);
|
||||
}
|
||||
|
||||
export function loadAppConfig(env?: Record<string, string | undefined>): AppConfig {
|
||||
const e = readEnv(env);
|
||||
const mockSms = e.MOCK_SMS !== 'false';
|
||||
const mockPay = e.MOCK_PAY !== 'false';
|
||||
const mockWechat = e.MOCK_WECHAT === 'true';
|
||||
const base = {
|
||||
mockSms,
|
||||
mockPay,
|
||||
mockDeliveryAuto: e.MOCK_DELIVERY_AUTO !== 'false',
|
||||
autoApproveStore: e.AUTO_APPROVE_STORE !== 'false',
|
||||
mockWechat,
|
||||
wxAppId: e.WX_APP_ID ?? '',
|
||||
wxMiniAppId: e.WX_MINI_APP_ID ?? e.WX_APP_ID ?? '',
|
||||
wxMchId: e.WX_MCH_ID ?? '',
|
||||
aliyunSmsSignName: e.ALIYUN_SMS_SIGN_NAME ?? '',
|
||||
aliyunSmsTemplateCode: e.ALIYUN_SMS_TEMPLATE_CODE ?? '',
|
||||
aliyunSmsRedeemConfirmTemplateCode:
|
||||
e.ALIYUN_SMS_TEMPLATE_CONFIRM_REDEEM ??
|
||||
e.ALIYUN_SMS_REDEEM_CONFIRM_TEMPLATE_CODE ??
|
||||
'',
|
||||
aliyunSmsProxyOrderTemplateCode:
|
||||
e.ALIYUN_SMS_TEMPLATE_PROXY_ORDER ??
|
||||
e.ALIYUN_SMS_PROXY_ORDER_TEMPLATE_CODE ??
|
||||
'',
|
||||
aliyunSmsAccessKeyId: e.ALIYUN_SMS_ACCESS_KEY_ID ?? e.OSS_ACCESS_KEY_ID ?? '',
|
||||
aliyunSmsAccessKeySecret: e.ALIYUN_SMS_ACCESS_KEY_SECRET ?? e.OSS_ACCESS_KEY_SECRET ?? '',
|
||||
tencentLbsKey: e.TENCENT_LBS_KEY ?? '',
|
||||
tencentLbsSecretKey: e.TENCENT_LBS_SECRET_KEY ?? '',
|
||||
userH5Url: (e.USER_H5_URL || DEFAULT_USER_H5_URL).replace(/\/$/, ''),
|
||||
};
|
||||
return {
|
||||
...base,
|
||||
wxAuthorize: resolveWxAuthorize(base, e),
|
||||
wechatPayEnabled: resolveWechatPayEnabled(base, e),
|
||||
};
|
||||
}
|
||||
|
||||
/** Mock 短信环境固定验证码 */
|
||||
export const MOCK_SMS_FIXED_CODE = '999888';
|
||||
@@ -0,0 +1,235 @@
|
||||
/** 开发计划任务类型 */
|
||||
|
||||
export type DevPlanTaskTypeDto = 'BUG' | 'REQUIREMENT' | 'OPTIMIZATION';
|
||||
|
||||
|
||||
|
||||
export const DEV_PLAN_TASK_TYPES = ['BUG', 'REQUIREMENT', 'OPTIMIZATION'] as const;
|
||||
|
||||
|
||||
|
||||
export const DEV_PLAN_TASK_TYPE_LABELS: Record<DevPlanTaskTypeDto, string> = {
|
||||
|
||||
BUG: 'BUG',
|
||||
|
||||
REQUIREMENT: '需求',
|
||||
|
||||
OPTIMIZATION: '优化',
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
/** 开发计划任务状态 */
|
||||
|
||||
export type DevPlanTaskStatusDto = 'TODO' | 'DEVELOPED' | 'RELEASED';
|
||||
|
||||
|
||||
|
||||
export const DEV_PLAN_TASK_STATUSES = ['TODO', 'DEVELOPED', 'RELEASED'] as const;
|
||||
|
||||
|
||||
|
||||
export const DEV_PLAN_TASK_STATUS_LABELS: Record<DevPlanTaskStatusDto, string> = {
|
||||
|
||||
TODO: '待开发',
|
||||
|
||||
DEVELOPED: '已开发',
|
||||
|
||||
RELEASED: '已上线',
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
/** 开发计划版本状态 */
|
||||
|
||||
export type DevPlanVersionStatusDto = 'PENDING' | 'IN_PROGRESS' | 'TESTING' | 'RELEASED';
|
||||
|
||||
|
||||
|
||||
export const DEV_PLAN_VERSION_STATUSES = ['PENDING', 'IN_PROGRESS', 'TESTING', 'RELEASED'] as const;
|
||||
|
||||
|
||||
|
||||
export const DEV_PLAN_VERSION_STATUS_LABELS: Record<DevPlanVersionStatusDto, string> = {
|
||||
|
||||
PENDING: '待启动',
|
||||
|
||||
IN_PROGRESS: '开发中',
|
||||
|
||||
TESTING: '测试',
|
||||
|
||||
RELEASED: '已上线',
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
export interface DevPlanTaskDto {
|
||||
|
||||
id: string;
|
||||
|
||||
taskNo: string;
|
||||
|
||||
content: string;
|
||||
|
||||
type: DevPlanTaskTypeDto;
|
||||
|
||||
status: DevPlanTaskStatusDto;
|
||||
|
||||
creatorHqAccountId: string;
|
||||
|
||||
creatorName?: string | null;
|
||||
|
||||
supportTicketId?: string | null;
|
||||
|
||||
supportTicketNo?: string | null;
|
||||
|
||||
lastDispatchedAt?: string | null;
|
||||
|
||||
createdAt: string;
|
||||
|
||||
completedAt?: string | null;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface DevPlanVersionDto {
|
||||
|
||||
id: string;
|
||||
|
||||
versionNo: string;
|
||||
|
||||
content?: string | null;
|
||||
|
||||
status: DevPlanVersionStatusDto;
|
||||
|
||||
assigneeHqAccountId?: string | null;
|
||||
|
||||
assigneeName?: string | null;
|
||||
|
||||
createdAt: string;
|
||||
|
||||
devStartedAt?: string | null;
|
||||
|
||||
devCompletedAt?: string | null;
|
||||
|
||||
releasedAt?: string | null;
|
||||
|
||||
durationMinutes?: number | null;
|
||||
|
||||
tasks?: DevPlanTaskDto[];
|
||||
|
||||
taskIds?: string[];
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface DevPlanSettingsDto {
|
||||
|
||||
reviewAssistantLlmConfigId?: string | null;
|
||||
|
||||
reviewAssistantKnowledgeBaseId?: string | null;
|
||||
|
||||
reviewAssistantPrompt?: string | null;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface CreateDevPlanTaskInput {
|
||||
|
||||
content: string;
|
||||
|
||||
type: DevPlanTaskTypeDto;
|
||||
|
||||
supportTicketId?: string;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface UpdateDevPlanTaskInput {
|
||||
|
||||
content?: string;
|
||||
|
||||
type?: DevPlanTaskTypeDto;
|
||||
|
||||
status?: DevPlanTaskStatusDto;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface CreateDevPlanVersionInput {
|
||||
|
||||
versionNo: string;
|
||||
|
||||
content?: string;
|
||||
|
||||
status?: DevPlanVersionStatusDto;
|
||||
|
||||
taskIds?: string[];
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface UpdateDevPlanVersionInput {
|
||||
|
||||
versionNo?: string;
|
||||
|
||||
content?: string;
|
||||
|
||||
status?: DevPlanVersionStatusDto;
|
||||
|
||||
taskIds?: string[];
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface DevPlanTaskDispatchInput {
|
||||
|
||||
taskIds: string[];
|
||||
|
||||
supplement?: string;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface DevPlanLinkedTaskSummary {
|
||||
|
||||
id: string;
|
||||
|
||||
taskNo: string;
|
||||
|
||||
content: string;
|
||||
|
||||
status: DevPlanTaskStatusDto;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** 技术支持工单类型 → 开发任务类型 */
|
||||
|
||||
export function mapSupportTicketTypeToDevPlanTask(
|
||||
|
||||
ticketType: 'BUG' | 'SUGGESTION' | 'OTHER',
|
||||
|
||||
): DevPlanTaskTypeDto {
|
||||
|
||||
if (ticketType === 'BUG') return 'BUG';
|
||||
|
||||
if (ticketType === 'SUGGESTION') return 'REQUIREMENT';
|
||||
|
||||
return 'OPTIMIZATION';
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
export enum ClientApp {
|
||||
USER_MINI = 'USER_MINI',
|
||||
USER_H5 = 'USER_H5',
|
||||
PARTNER_MINI = 'PARTNER_MINI',
|
||||
PARTNER_H5 = 'PARTNER_H5',
|
||||
HQ_MINI = 'HQ_MINI',
|
||||
HQ_WEB = 'HQ_WEB',
|
||||
SHOP_H5 = 'SHOP_H5',
|
||||
}
|
||||
|
||||
export enum ActorType {
|
||||
USER = 'USER',
|
||||
STORE = 'STORE',
|
||||
PARTNER = 'PARTNER',
|
||||
HQ = 'HQ',
|
||||
}
|
||||
|
||||
/** 用户注册/首次归因来源(对应 user_user.source_type) */
|
||||
export enum UserSourceType {
|
||||
ORGANIC = 'ORGANIC',
|
||||
PROMO_CODE = 'PROMO_CODE',
|
||||
SHARE_LINK = 'SHARE_LINK',
|
||||
FRIEND_REFERRAL = 'FRIEND_REFERRAL',
|
||||
OFFLINE_EVENT = 'OFFLINE_EVENT',
|
||||
PARTNER_PROXY = 'PARTNER_PROXY',
|
||||
OTHER = 'OTHER',
|
||||
}
|
||||
|
||||
export const USER_SOURCE_TYPE_LABELS: Record<UserSourceType, string> = {
|
||||
[UserSourceType.ORGANIC]: '自然流量',
|
||||
[UserSourceType.PROMO_CODE]: '推广码',
|
||||
[UserSourceType.SHARE_LINK]: '分享链接',
|
||||
[UserSourceType.FRIEND_REFERRAL]: '好友推荐',
|
||||
[UserSourceType.OFFLINE_EVENT]: '线下活动',
|
||||
[UserSourceType.PARTNER_PROXY]: '代下单',
|
||||
[UserSourceType.OTHER]: '其他',
|
||||
};
|
||||
|
||||
export enum SmsScene {
|
||||
USER_LOGIN = 'USER_LOGIN',
|
||||
STORE_LOGIN = 'STORE_LOGIN',
|
||||
STORE_ACCOUNT_OPEN = 'STORE_ACCOUNT_OPEN',
|
||||
PARTNER_LOGIN = 'PARTNER_LOGIN',
|
||||
HQ_LOGIN = 'HQ_LOGIN',
|
||||
BIND_PHONE = 'BIND_PHONE',
|
||||
PARTNER_STAFF_ADD = 'PARTNER_STAFF_ADD',
|
||||
/** 门店手机号核销:查询用户权益前验证码(发至用户手机) */
|
||||
REDEEM_PHONE_LOOKUP = 'REDEEM_PHONE_LOOKUP',
|
||||
/** 门店手机号核销:核销确认验证码(阿里云模板「核销确认」) */
|
||||
REDEEM_PHONE_CONFIRM = 'REDEEM_PHONE_CONFIRM',
|
||||
/** 合伙人代下单:客户手机号归属验证码(发至客户手机) */
|
||||
PARTNER_PROXY_CUSTOMER = 'PARTNER_PROXY_CUSTOMER',
|
||||
/** 合伙人代下单:线下确认验证码(发至合伙人手机) */
|
||||
PARTNER_PROXY_ORDER = 'PARTNER_PROXY_ORDER',
|
||||
/** 合伙人录店:门店登录手机号验证码(发至门店负责人手机) */
|
||||
PARTNER_STORE_OPEN = 'PARTNER_STORE_OPEN',
|
||||
}
|
||||
|
||||
export enum OrderType {
|
||||
NORMAL = 'NORMAL',
|
||||
RESHIPMENT = 'RESHIPMENT',
|
||||
PROXY = 'PROXY',
|
||||
}
|
||||
|
||||
export const ORDER_TYPE_LABELS: Record<OrderType, string> = {
|
||||
[OrderType.NORMAL]: '普通订单',
|
||||
[OrderType.RESHIPMENT]: '补发订单',
|
||||
[OrderType.PROXY]: '线下代下单',
|
||||
};
|
||||
|
||||
export enum OrderStatus {
|
||||
PENDING_PAY = 'PENDING_PAY',
|
||||
PENDING_SHIP = 'PENDING_SHIP',
|
||||
OUT_WAREHOUSE = 'OUT_WAREHOUSE',
|
||||
SHIPPING = 'SHIPPING',
|
||||
PENDING_RECEIVE = 'PENDING_RECEIVE',
|
||||
COMPLETED = 'COMPLETED',
|
||||
CANCELLED = 'CANCELLED',
|
||||
REFUNDING = 'REFUNDING',
|
||||
REFUNDED = 'REFUNDED',
|
||||
}
|
||||
|
||||
export enum OrderTab {
|
||||
ALL = 'all',
|
||||
PENDING_PAY = 'pending_pay',
|
||||
/** V3.0 PRD:已付款(含待发货/配送中,内部多状态聚合) */
|
||||
PAID = 'paid',
|
||||
COMPLETED = 'completed',
|
||||
/** @deprecated 兼容旧链接,映射同 PAID */
|
||||
PENDING_SHIP = 'pending_ship',
|
||||
/** @deprecated 兼容旧链接,映射同 PAID */
|
||||
PENDING_RECEIVE = 'pending_receive',
|
||||
}
|
||||
|
||||
export enum DeliveryType {
|
||||
LOCAL = 'LOCAL',
|
||||
CROSS_CITY = 'CROSS_CITY',
|
||||
ON_SITE_PICKUP = 'ON_SITE_PICKUP',
|
||||
}
|
||||
|
||||
export enum AromaType {
|
||||
QINGXIANG = 'QINGXIANG',
|
||||
JIANGXIANG = 'JIANGXIANG',
|
||||
NONGXIANG = 'NONGXIANG',
|
||||
}
|
||||
|
||||
export enum StoreStatus {
|
||||
OPEN = 'OPEN',
|
||||
PAUSED = 'PAUSED',
|
||||
CLOSED = 'CLOSED',
|
||||
}
|
||||
|
||||
export const STORE_STATUS_LABELS: Record<StoreStatus, string> = {
|
||||
[StoreStatus.OPEN]: '营业中',
|
||||
[StoreStatus.PAUSED]: '临时闭店',
|
||||
[StoreStatus.CLOSED]: '永久关闭',
|
||||
};
|
||||
|
||||
/** C 端订单列表展示用(细粒度中文) */
|
||||
export const ORDER_STATUS_LABELS: Record<string, string> = {
|
||||
PENDING_PAY: '待付款',
|
||||
PENDING_SHIP: '待发货',
|
||||
OUT_WAREHOUSE: '已出库',
|
||||
SHIPPING: '配送中',
|
||||
PENDING_RECEIVE: '待收货',
|
||||
COMPLETED: '已完成',
|
||||
CANCELLED: '已取消',
|
||||
REFUNDING: '退款中',
|
||||
REFUNDED: '已退款',
|
||||
};
|
||||
|
||||
export enum StoreAuditStatus {
|
||||
PENDING = 'PENDING',
|
||||
APPROVED = 'APPROVED',
|
||||
REJECTED = 'REJECTED',
|
||||
}
|
||||
|
||||
export const STORE_AUDIT_STATUS_LABELS: Record<StoreAuditStatus, string> = {
|
||||
[StoreAuditStatus.PENDING]: '待审核',
|
||||
[StoreAuditStatus.APPROVED]: '已通过',
|
||||
[StoreAuditStatus.REJECTED]: '已驳回',
|
||||
};
|
||||
|
||||
export enum PartnerStaffRole {
|
||||
PARTNER = 'PARTNER',
|
||||
INTERNAL = 'INTERNAL',
|
||||
PROMOTER = 'PROMOTER',
|
||||
}
|
||||
|
||||
export enum StoreStaffRole {
|
||||
MANAGER = 'MANAGER',
|
||||
CASHIER = 'CASHIER',
|
||||
}
|
||||
|
||||
export enum CityPartnerScopeType {
|
||||
CITY_WIDE = 'CITY_WIDE',
|
||||
DISTRICT = 'DISTRICT',
|
||||
}
|
||||
|
||||
export enum CityPartnerStatus {
|
||||
ACTIVE = 'ACTIVE',
|
||||
PAUSED = 'PAUSED',
|
||||
}
|
||||
|
||||
export enum WarehouseManagerType {
|
||||
HQ = 'HQ',
|
||||
PARTNER = 'PARTNER',
|
||||
}
|
||||
|
||||
export enum WarehouseStatus {
|
||||
ACTIVE = 'ACTIVE',
|
||||
PAUSED = 'PAUSED',
|
||||
}
|
||||
|
||||
export const CITY_PARTNER_SCOPE_LABELS: Record<CityPartnerScopeType, string> = {
|
||||
[CityPartnerScopeType.CITY_WIDE]: '全城合伙人',
|
||||
[CityPartnerScopeType.DISTRICT]: '区域合伙人',
|
||||
};
|
||||
|
||||
export const WAREHOUSE_MANAGER_LABELS: Record<WarehouseManagerType, string> = {
|
||||
[WarehouseManagerType.HQ]: '总部直管',
|
||||
[WarehouseManagerType.PARTNER]: '合伙人管仓',
|
||||
};
|
||||
|
||||
export const CITY_PARTNER_STATUS_LABELS: Record<CityPartnerStatus, string> = {
|
||||
[CityPartnerStatus.ACTIVE]: '启用',
|
||||
[CityPartnerStatus.PAUSED]: '暂停',
|
||||
};
|
||||
|
||||
export const WAREHOUSE_STATUS_LABELS: Record<WarehouseStatus, string> = {
|
||||
[WarehouseStatus.ACTIVE]: '启用',
|
||||
[WarehouseStatus.PAUSED]: '暂停',
|
||||
};
|
||||
|
||||
export enum FulfillmentProviderType {
|
||||
API = 'API',
|
||||
MANUAL = 'MANUAL',
|
||||
}
|
||||
|
||||
export enum FulfillmentProviderStatus {
|
||||
ACTIVE = 'ACTIVE',
|
||||
DISABLED = 'DISABLED',
|
||||
}
|
||||
|
||||
/** 物流承运商结算方式:前期充值,后期挂账月结 */
|
||||
export enum LogisticsSettlementMethod {
|
||||
PREPAID = 'PREPAID',
|
||||
MONTHLY_CREDIT = 'MONTHLY_CREDIT',
|
||||
}
|
||||
|
||||
export const LOGISTICS_SETTLEMENT_METHOD_LABELS: Record<LogisticsSettlementMethod, string> = {
|
||||
[LogisticsSettlementMethod.PREPAID]: '充值扣款',
|
||||
[LogisticsSettlementMethod.MONTHLY_CREDIT]: '挂账月结',
|
||||
};
|
||||
|
||||
export enum WarehouseFulfillmentMode {
|
||||
API_AUTO = 'API_AUTO',
|
||||
MANUAL = 'MANUAL',
|
||||
}
|
||||
|
||||
export const FULFILLMENT_PROVIDER_TYPE_LABELS: Record<FulfillmentProviderType, string> = {
|
||||
[FulfillmentProviderType.API]: 'API 对接',
|
||||
[FulfillmentProviderType.MANUAL]: '自管',
|
||||
};
|
||||
|
||||
export const FULFILLMENT_PROVIDER_STATUS_LABELS: Record<FulfillmentProviderStatus, string> = {
|
||||
[FulfillmentProviderStatus.ACTIVE]: '启用',
|
||||
[FulfillmentProviderStatus.DISABLED]: '停用',
|
||||
};
|
||||
|
||||
export const WAREHOUSE_FULFILLMENT_MODE_LABELS: Record<WarehouseFulfillmentMode, string> = {
|
||||
[WarehouseFulfillmentMode.API_AUTO]: '自动发货',
|
||||
[WarehouseFulfillmentMode.MANUAL]: '关闭(手工填单)',
|
||||
};
|
||||
|
||||
export enum AccountStatus {
|
||||
ACTIVE = 'ACTIVE',
|
||||
DISABLED = 'DISABLED',
|
||||
}
|
||||
|
||||
export enum BenefitCouponStatus {
|
||||
ACTIVE = 'ACTIVE',
|
||||
USED_UP = 'USED_UP',
|
||||
VOID = 'VOID',
|
||||
}
|
||||
|
||||
export const CLIENT_APP_ACTOR_MAP: Record<ClientApp, ActorType> = {
|
||||
[ClientApp.USER_MINI]: ActorType.USER,
|
||||
[ClientApp.USER_H5]: ActorType.USER,
|
||||
[ClientApp.PARTNER_MINI]: ActorType.PARTNER,
|
||||
[ClientApp.PARTNER_H5]: ActorType.PARTNER,
|
||||
[ClientApp.HQ_MINI]: ActorType.HQ,
|
||||
[ClientApp.HQ_WEB]: ActorType.HQ,
|
||||
[ClientApp.SHOP_H5]: ActorType.STORE,
|
||||
};
|
||||
|
||||
export const REDEEM_DIRECT_LIMIT_POLICY = 'TOTAL_ACTIVE_BALANCE';
|
||||
export const REDEEM_DOCUMENT_LIMIT_POLICY = 'DOCUMENT_BALANCE';
|
||||
/** V3.0 PRD:核销码 3 分钟有效 */
|
||||
export const REDEEM_TOKEN_TTL_SECONDS = 180;
|
||||
/** 短信/核销验证码 3 分钟有效(NFR-003) */
|
||||
export const SMS_CODE_TTL_SECONDS = 180;
|
||||
/** 核销成功后供用户端轮询结果,略长于 token TTL */
|
||||
export const REDEEM_RESULT_TTL_SECONDS = 360;
|
||||
/** 手机号核销会话 TTL(查权益 → 选金额 → 确认) */
|
||||
export const REDEEM_PHONE_SESSION_TTL_SECONDS = 600;
|
||||
/** 弱网兜底:preview 快照 TTL(token 失效后仍可提交待处理单) */
|
||||
export const REDEEM_PENDING_SNAPSHOT_TTL_SECONDS = 600;
|
||||
/** 弱网核销:网络类失败阈值 */
|
||||
export const REDEEM_WEAKNET_FAIL_THRESHOLD = 5;
|
||||
@@ -0,0 +1,115 @@
|
||||
import type {
|
||||
FulfillmentProviderStatus,
|
||||
FulfillmentProviderType,
|
||||
LogisticsSettlementMethod,
|
||||
} from './enums';
|
||||
import type { WarehouseFulfillmentMode } from './enums';
|
||||
import type { LogisticsPricingRuleDto } from './settlement';
|
||||
import { DEFAULT_XFX_LOGISTICS_PRICING } from './settlement';
|
||||
|
||||
/** 小飞侠仓配凭证(存 FulfillmentProvider.configJson) */
|
||||
export interface XiaofeixiaProviderConfig {
|
||||
apiUrl: string;
|
||||
mchId: string;
|
||||
apiKey: string;
|
||||
signType?: 'MD5' | 'HMAC-SHA256';
|
||||
appId?: string;
|
||||
}
|
||||
|
||||
/** 回显用(不含明文 apiKey) */
|
||||
export interface XiaofeixiaProviderConfigPublic {
|
||||
apiUrl: string;
|
||||
mchId: string;
|
||||
signType: 'MD5' | 'HMAC-SHA256';
|
||||
appId?: string;
|
||||
hasApiKey: boolean;
|
||||
}
|
||||
|
||||
export interface FulfillmentProviderBankDto {
|
||||
bankAccountName?: string | null;
|
||||
bankName?: string | null;
|
||||
bankBranch?: string | null;
|
||||
bankAccountNo?: string | null;
|
||||
}
|
||||
|
||||
export interface FulfillmentProviderDto {
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
type: FulfillmentProviderType;
|
||||
status: FulfillmentProviderStatus;
|
||||
capabilities?: {
|
||||
createShipment?: boolean;
|
||||
getTrack?: boolean;
|
||||
callback?: boolean;
|
||||
cancel?: boolean;
|
||||
} | null;
|
||||
hasConfig: boolean;
|
||||
/** 小飞侠等承运商结构化配置(脱敏) */
|
||||
xiaofeixiaConfig?: XiaofeixiaProviderConfigPublic | null;
|
||||
/** 结算银行账户 */
|
||||
bankAccountName?: string | null;
|
||||
bankName?: string | null;
|
||||
bankBranch?: string | null;
|
||||
bankAccountNo?: string | null;
|
||||
settlementMethod: LogisticsSettlementMethod;
|
||||
pricingRules?: LogisticsPricingRuleDto | null;
|
||||
prepaidBalance: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface CreateFulfillmentProviderInput {
|
||||
code: string;
|
||||
name: string;
|
||||
type: FulfillmentProviderType;
|
||||
status?: FulfillmentProviderStatus;
|
||||
configJson?: string;
|
||||
capabilitiesJson?: string;
|
||||
/** 结构化小飞侠配置;有则覆盖写入 configJson */
|
||||
xiaofeixiaConfig?: Partial<XiaofeixiaProviderConfig>;
|
||||
bankAccountName?: string | null;
|
||||
bankName?: string | null;
|
||||
bankBranch?: string | null;
|
||||
bankAccountNo?: string | null;
|
||||
settlementMethod?: LogisticsSettlementMethod;
|
||||
pricingRules?: LogisticsPricingRuleDto | null;
|
||||
}
|
||||
|
||||
export interface UpdateFulfillmentProviderInput {
|
||||
name?: string;
|
||||
type?: FulfillmentProviderType;
|
||||
status?: FulfillmentProviderStatus;
|
||||
configJson?: string;
|
||||
capabilitiesJson?: string;
|
||||
xiaofeixiaConfig?: Partial<XiaofeixiaProviderConfig>;
|
||||
bankAccountName?: string | null;
|
||||
bankName?: string | null;
|
||||
bankBranch?: string | null;
|
||||
bankAccountNo?: string | null;
|
||||
settlementMethod?: LogisticsSettlementMethod;
|
||||
pricingRules?: LogisticsPricingRuleDto | null;
|
||||
}
|
||||
|
||||
export { DEFAULT_XFX_LOGISTICS_PRICING };
|
||||
|
||||
export interface ManualShipOrderInput {
|
||||
logisticsCompany: string;
|
||||
trackingNo: string;
|
||||
manualQueryUrl?: string;
|
||||
}
|
||||
|
||||
export interface WarehouseFulfillmentConfig {
|
||||
fulfillmentMode: WarehouseFulfillmentMode;
|
||||
fulfillmentProviderId?: string | null;
|
||||
manualCarrierLabel?: string | null;
|
||||
manualQueryUrlTemplate?: string | null;
|
||||
lng?: number | null;
|
||||
lat?: number | null;
|
||||
}
|
||||
|
||||
export const XFX_PROVIDER_CODES = ['XFX', 'XIAOFEIXIA'] as const;
|
||||
|
||||
export function isXfxProviderCode(code: string): boolean {
|
||||
return (XFX_PROVIDER_CODES as readonly string[]).includes(code.trim().toUpperCase());
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/** HQ 权限目录(权限分配页勾选源) */
|
||||
export const HQ_PERMISSION_CATALOG = [
|
||||
{ key: 'dashboard', label: '概览', group: '业务' },
|
||||
{ key: 'users', label: '用户管理', group: '业务' },
|
||||
{ key: 'wechat_bindings', label: '微信绑定', group: '业务' },
|
||||
{ key: 'products', label: '商品管理', group: '业务' },
|
||||
{ key: 'orders', label: '订单管理', group: '业务' },
|
||||
{ key: 'promo_codes', label: '推广码', group: '业务' },
|
||||
{ key: 'stores', label: '门店管理', group: '业务' },
|
||||
{ key: 'partners', label: '开城管理', group: '业务' },
|
||||
{ key: 'finance', label: '财务', group: '业务' },
|
||||
{ key: 'benefit', label: '好客权益', group: '业务' },
|
||||
{ key: 'deliveries', label: '配送单', group: '业务' },
|
||||
{ key: 'tickets', label: '工单中心', group: '业务' },
|
||||
{ key: 'tech_support', label: '技术支持', group: '业务' },
|
||||
{ key: 'invoices', label: '发票管理', group: '业务' },
|
||||
{ key: 'wecom_bots', label: '企微机器人', group: '业务' },
|
||||
{ key: 'llm_configs', label: '语言模型配置', group: '业务' },
|
||||
{ key: 'knowledge_bases', label: '知识库', group: '业务' },
|
||||
{ key: 'dev_plan', label: '开发计划', group: '业务' },
|
||||
{ key: 'resources', label: 'OSS 资源库', group: '业务' },
|
||||
{ key: 'logs', label: '日志', group: '业务' },
|
||||
{ key: 'users_delete', label: '删除用户', group: '危险操作' },
|
||||
{ key: 'orders_delete', label: '删除订单', group: '危险操作' },
|
||||
{ key: 'cities_delete', label: '删除城市', group: '危险操作' },
|
||||
{ key: 'hq_permissions', label: '权限分配', group: '管理' },
|
||||
{ key: 'hq_accounts', label: 'HQ 账户', group: '管理' },
|
||||
{ key: 'system_settings_feature', label: '功能开关', group: '系统设置' },
|
||||
{ key: 'system_settings_sms', label: '短信', group: '系统设置' },
|
||||
{ key: 'system_settings_wechat', label: '微信', group: '系统设置' },
|
||||
{ key: 'system_settings_wechat_mini', label: '微信小程序', group: '系统设置' },
|
||||
{ key: 'system_settings_oss', label: '对象存储 OSS', group: '系统设置' },
|
||||
{ key: 'system_settings_app', label: '应用链接', group: '系统设置' },
|
||||
{ key: 'system_settings_deploy', label: '发布部署', group: '系统设置' },
|
||||
{ key: 'system_settings_winery_bank', label: '酒厂银行账户', group: '系统设置' },
|
||||
{ key: 'system_settings_finance', label: '财务结算', group: '系统设置' },
|
||||
] as const;
|
||||
|
||||
export type HqPermissionKey = (typeof HQ_PERMISSION_CATALOG)[number]['key'];
|
||||
|
||||
/** 危险操作:非超管角色默认不含,需在权限分配中显式勾选;超管默认拥有 */
|
||||
export const HQ_DANGEROUS_PERMISSION_KEYS = [
|
||||
'users_delete',
|
||||
'orders_delete',
|
||||
'cities_delete',
|
||||
] as const satisfies readonly HqPermissionKey[];
|
||||
|
||||
export function isHqDangerousPermission(key: string): boolean {
|
||||
return (HQ_DANGEROUS_PERMISSION_KEYS as readonly string[]).includes(key);
|
||||
}
|
||||
|
||||
export function hqBasePermissionKeys(): HqPermissionKey[] {
|
||||
return HQ_PERMISSION_CATALOG.map((p) => p.key).filter((k) => !isHqDangerousPermission(k));
|
||||
}
|
||||
|
||||
/** 系统配置 registry group → 权限 key */
|
||||
export const SYSTEM_CONFIG_GROUP_PERMISSION: Record<string, HqPermissionKey> = {
|
||||
feature: 'system_settings_feature',
|
||||
sms: 'system_settings_sms',
|
||||
wechat: 'system_settings_wechat',
|
||||
wechat_mini: 'system_settings_wechat_mini',
|
||||
oss: 'system_settings_oss',
|
||||
app: 'system_settings_app',
|
||||
deploy: 'system_settings_deploy',
|
||||
winery_bank: 'system_settings_winery_bank',
|
||||
finance: 'system_settings_finance',
|
||||
};
|
||||
|
||||
export const SYSTEM_SETTINGS_PERMISSION_KEYS = Object.values(
|
||||
SYSTEM_CONFIG_GROUP_PERMISSION,
|
||||
) as HqPermissionKey[];
|
||||
|
||||
/** 旧版单一 system_settings 权限:视为拥有全部系统设置分组(兼容存量角色配置) */
|
||||
export const LEGACY_SYSTEM_SETTINGS_KEY = 'system_settings';
|
||||
|
||||
export function expandHqPermissionKeys(keys: string[]): HqPermissionKey[] {
|
||||
const set = new Set<string>(keys);
|
||||
if (set.has(LEGACY_SYSTEM_SETTINGS_KEY)) {
|
||||
for (const k of SYSTEM_SETTINGS_PERMISSION_KEYS) set.add(k);
|
||||
set.delete(LEGACY_SYSTEM_SETTINGS_KEY);
|
||||
}
|
||||
return [...set].filter((k): k is HqPermissionKey =>
|
||||
HQ_PERMISSION_CATALOG.some((p) => p.key === k),
|
||||
);
|
||||
}
|
||||
|
||||
export function hasAnySystemSettingsPermission(keys: string[]): boolean {
|
||||
const expanded = expandHqPermissionKeys(keys);
|
||||
return SYSTEM_SETTINGS_PERMISSION_KEYS.some((k) => expanded.includes(k));
|
||||
}
|
||||
|
||||
export const HQ_ADMIN_ROLES = [
|
||||
{ value: 'SUPER_ADMIN', label: '超级管理员' },
|
||||
{ value: 'OPS', label: '运营' },
|
||||
{ value: 'FINANCE', label: '财务' },
|
||||
{ value: 'CUSTOMER_SERVICE', label: '客服' },
|
||||
] as const;
|
||||
|
||||
export const HQ_ROLE_DEFAULT_PERMISSIONS: Record<string, HqPermissionKey[]> = {
|
||||
SUPER_ADMIN: hqBasePermissionKeys(),
|
||||
OPS: [
|
||||
'dashboard',
|
||||
'users',
|
||||
'wechat_bindings',
|
||||
'products',
|
||||
'orders',
|
||||
'promo_codes',
|
||||
'stores',
|
||||
'partners',
|
||||
'benefit',
|
||||
'deliveries',
|
||||
'tickets',
|
||||
'tech_support',
|
||||
'invoices',
|
||||
'wecom_bots',
|
||||
'llm_configs',
|
||||
'knowledge_bases',
|
||||
'dev_plan',
|
||||
'resources',
|
||||
'logs',
|
||||
'system_settings_wechat_mini',
|
||||
],
|
||||
FINANCE: [
|
||||
'dashboard',
|
||||
'orders',
|
||||
'stores',
|
||||
'partners',
|
||||
'finance',
|
||||
'benefit',
|
||||
'tech_support',
|
||||
'invoices',
|
||||
'logs',
|
||||
'system_settings_winery_bank',
|
||||
'system_settings_finance',
|
||||
],
|
||||
CUSTOMER_SERVICE: [
|
||||
'dashboard',
|
||||
'users',
|
||||
'orders',
|
||||
'tickets',
|
||||
'tech_support',
|
||||
'invoices',
|
||||
'logs',
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
export * from './enums';
|
||||
export * from './api';
|
||||
export * from './config';
|
||||
export * from './wechat';
|
||||
export * from './catalog';
|
||||
export * from './trade';
|
||||
export * from './benefit';
|
||||
export * from './redeem';
|
||||
export * from './settlement';
|
||||
export * from './ops';
|
||||
export * from './ticket';
|
||||
export * from './support-ticket';
|
||||
export * from './invoice';
|
||||
export * from './user-log';
|
||||
|
||||
export * from './store-log';
|
||||
export * from './store-package';
|
||||
export * from './partner-log';
|
||||
export * from './promo';
|
||||
export * from './hq-permissions';
|
||||
export * from './partner';
|
||||
export * from './shop';
|
||||
export * from './city-partner';
|
||||
export * from './city-warehouse';
|
||||
export * from './fulfillment-provider';
|
||||
export * from './system-config';
|
||||
export * from './wecom-bot';
|
||||
export * from './wecom-message-push';
|
||||
export * from './llm-config';
|
||||
export * from './knowledge-base';
|
||||
export * from './legal';
|
||||
export * from './dev-plan';
|
||||
@@ -0,0 +1,54 @@
|
||||
export type InvoiceTitleType = 'PERSONAL' | 'ENTERPRISE';
|
||||
export type InvoiceKind = 'NORMAL' | 'SPECIAL';
|
||||
export type InvoiceStatus = 'PENDING' | 'ISSUED' | 'REJECTED';
|
||||
|
||||
export const INVOICE_TITLE_TYPE_LABELS: Record<InvoiceTitleType, string> = {
|
||||
PERSONAL: '个人',
|
||||
ENTERPRISE: '企业',
|
||||
};
|
||||
|
||||
export const INVOICE_KIND_LABELS: Record<InvoiceKind, string> = {
|
||||
NORMAL: '增值税普通发票',
|
||||
SPECIAL: '增值税专用发票',
|
||||
};
|
||||
|
||||
export const INVOICE_STATUS_LABELS: Record<InvoiceStatus, string> = {
|
||||
PENDING: '待开票',
|
||||
ISSUED: '已开票',
|
||||
REJECTED: '已驳回',
|
||||
};
|
||||
|
||||
export interface CreateInvoiceRequest {
|
||||
titleType: InvoiceTitleType;
|
||||
invoiceKind: InvoiceKind;
|
||||
titleName: string;
|
||||
taxNo?: string;
|
||||
addressPhone?: string;
|
||||
bankAccount?: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
export interface InvoiceDto {
|
||||
id: string;
|
||||
invoiceNo: string;
|
||||
orderId: string;
|
||||
userId: string;
|
||||
titleType: InvoiceTitleType;
|
||||
invoiceKind: InvoiceKind;
|
||||
titleName: string;
|
||||
taxNo?: string | null;
|
||||
addressPhone?: string | null;
|
||||
bankAccount?: string | null;
|
||||
email: string;
|
||||
phone: string;
|
||||
status: InvoiceStatus;
|
||||
fileUrl?: string | null;
|
||||
remark?: string | null;
|
||||
issuedAt?: string | null;
|
||||
createdAt: string;
|
||||
orderNo?: string;
|
||||
/** 待开票超过 2 个工作日(总部列表标红用) */
|
||||
overdue?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
export type KnowledgeBaseDto = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
enabled: boolean;
|
||||
documentCount: number;
|
||||
createdByHqAccountId: string;
|
||||
createdByName: string | null;
|
||||
isOwner: boolean;
|
||||
canEditFull: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type CreateKnowledgeBaseRequest = {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
enabled?: boolean;
|
||||
};
|
||||
|
||||
export type UpdateKnowledgeBaseRequest = {
|
||||
name?: string;
|
||||
description?: string | null;
|
||||
enabled?: boolean;
|
||||
};
|
||||
|
||||
export type KnowledgeDocumentDto = {
|
||||
id: string;
|
||||
knowledgeBaseId: string;
|
||||
title: string;
|
||||
fileName: string | null;
|
||||
fileUrl: string | null;
|
||||
mimeType: string | null;
|
||||
sizeBytes: number | null;
|
||||
/** 是否已抽取可检索正文 */
|
||||
hasContent: boolean;
|
||||
status: 'READY' | 'EMPTY' | 'FAILED';
|
||||
errorMessage: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type CreateKnowledgeDocumentRequest = {
|
||||
title: string;
|
||||
/** 直贴正文(优先) */
|
||||
contentText?: string | null;
|
||||
fileName?: string | null;
|
||||
fileUrl?: string | null;
|
||||
mimeType?: string | null;
|
||||
sizeBytes?: number | null;
|
||||
};
|
||||
|
||||
export type KnowledgeBaseOptionDto = {
|
||||
id: string;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
documentCount: number;
|
||||
};
|
||||
@@ -0,0 +1,162 @@
|
||||
import { CUSTOMER_SERVICE_PHONE } from './config';
|
||||
|
||||
export type LegalSection = {
|
||||
heading: string;
|
||||
paragraphs: string[];
|
||||
};
|
||||
|
||||
export type LegalDocument = {
|
||||
id: 'user-agreement' | 'privacy-policy';
|
||||
title: string;
|
||||
updatedAt: string;
|
||||
intro: string;
|
||||
sections: LegalSection[];
|
||||
};
|
||||
|
||||
/**
|
||||
* 杜康好客 · 用户协议 / 隐私政策(多端共用)。
|
||||
* 结构对齐《个人信息保护法》及 App 合规常见披露要点。
|
||||
* 参考:小米开发者「隐私政策不合规修改指引」、FreeBuf APP 隐私合规规范(禁止默认勾选等)。
|
||||
* 正式上线前请法务审定主体名称与联系方式。
|
||||
*/
|
||||
export const USER_AGREEMENT: LegalDocument = {
|
||||
id: 'user-agreement',
|
||||
title: '用户服务协议',
|
||||
updatedAt: '2026-07-21',
|
||||
intro:
|
||||
'欢迎使用「杜康好客」平台(含小程序、移动网页及门店端/合伙人端相关服务,以下统称「本平台」)。请您在注册、登录或以其他方式使用本服务前仔细阅读并充分理解本协议。您须主动勾选同意后继续使用,不得默认强制同意;勾选即视为已阅读并接受本协议全部内容。',
|
||||
sections: [
|
||||
{
|
||||
heading: '一、服务说明',
|
||||
paragraphs: [
|
||||
'杜康好客是杜康酒业 O2O 消费服务平台:用户可在线浏览与购买酒类商品,获得对应「好客权益」并在合作门店核销;门店与城市合伙人可使用管理端完成核销、门店与订单相关履约操作。',
|
||||
'我们有权根据业务需要调整服务内容、功能或规则,并在合理范围内通过平台公告、页面提示等方式通知您。',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: '二、账号注册与安全',
|
||||
paragraphs: [
|
||||
'您可通过手机号快捷登录、手机号验证码等方式注册或登录。您应保证提供的信息真实、准确、完整,并及时更新。',
|
||||
'您应妥善保管账号、验证码及设备。因您自身原因导致的账号被盗用、信息泄露等风险,由您自行承担;如发现异常请立即联系客服。',
|
||||
'您不得利用本平台从事违法违规、侵害他人权益或扰乱平台秩序的行为,否则我们有权限制或终止服务。',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: '三、订单、支付与权益',
|
||||
paragraphs: [
|
||||
'下单、支付、配送及「好客权益」发放/核销规则以平台页面展示及相关业务规则为准。支付成功后产生的权益额度按产品说明计入您的账户。',
|
||||
'核销须在合作门店按规则完成;超出可用余额、已失效或违反使用规则的核销请求将被拒绝。',
|
||||
'如发生退款、补发等售后事宜,将按平台售后规则及客服处理结果执行。',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: '四、用户行为规范',
|
||||
paragraphs: [
|
||||
'您承诺依法使用本平台,不得利用技术手段恶意刷单、伪造核销、攻击系统或传播违法信息。',
|
||||
'您理解并同意:酒类商品及相关服务可能仅面向符合法律法规要求的主体;若您不具备相应资格,请勿使用购买或核销功能。',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: '五、知识产权',
|
||||
paragraphs: [
|
||||
'本平台中的文字、图片、标识、界面设计、软件等知识产权归平台运营方或合法权利人所有。未经许可,您不得擅自复制、传播或用于商业目的。',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: '六、免责与责任限制',
|
||||
paragraphs: [
|
||||
'因不可抗力、网络故障、第三方支付或配送服务异常等非我们可控因素导致的服务中断或延误,我们将在合理范围内协助处理,但不承担因此产生的间接损失。',
|
||||
'在法律允许的范围内,我们对因使用或无法使用本服务所产生的损害责任以您就相关服务实际支付的费用为限(免费服务除外另有约定)。',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: '七、协议变更与终止',
|
||||
paragraphs: [
|
||||
'我们可能适时修订本协议,修订后的协议将在平台公布并自公布之日起生效(法律法规另有要求的除外)。若您继续使用服务,视为接受修订后的协议。',
|
||||
'您可停止使用并申请注销账号;我们亦可在您严重违反本协议时中止或终止向您提供服务。',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: '八、联系我们',
|
||||
paragraphs: [
|
||||
`如对本协议有任何疑问,请通过客服热线 ${CUSTOMER_SERVICE_PHONE}(工作时间 9:00–21:00)与我们联系。`,
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const PRIVACY_POLICY: LegalDocument = {
|
||||
id: 'privacy-policy',
|
||||
title: '隐私政策',
|
||||
updatedAt: '2026-07-21',
|
||||
intro:
|
||||
'杜康好客平台运营方(以下简称「我们」)深知个人信息对您的重要性,将按《中华人民共和国个人信息保护法》等相关法律法规保护您的个人信息。请您在使用服务前仔细阅读本政策。您须主动勾选同意后,我们才会按本政策处理相关个人信息;我们不会默认勾选或强制同意。',
|
||||
sections: [
|
||||
{
|
||||
heading: '一、我们如何收集与使用个人信息',
|
||||
paragraphs: [
|
||||
'为向您提供注册登录、下单支付、配送履约、门店核销、客服支持等核心功能,我们可能在取得您授权同意后收集并使用下列信息:',
|
||||
'1)账号信息:手机号码、验证码、开放平台账号标识(OpenID/UnionID,若您授权)、昵称与头像(若您主动授权);用于注册登录、账号绑定与安全保障。',
|
||||
'2)交易信息:订单内容、收货地址、支付状态、配送状态、权益与核销记录;用于履约、售后与对账。',
|
||||
'3)位置信息:在您授权后获取大致位置或精确位置,用于展示所在城市商品与附近门店;您可拒绝授权,我们将使用默认开城城市兜底。',
|
||||
'4)设备与日志信息:设备型号、操作系统、网络类型、崩溃日志、操作日志等;用于安全风控、故障排查与服务优化。',
|
||||
'5)您主动提供的其他信息:如客服沟通内容、反馈建议等。',
|
||||
'收集目的与方式:仅在实现上述业务功能所必需的范围内,通过您主动填写、授权组件或系统必要日志收集;未征得同意前,我们不会超范围收集与业务无关的个人信息。',
|
||||
'我们不会以默认勾选等方式强制您同意本政策。',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: '二、我们如何共享、转让、公开披露',
|
||||
paragraphs: [
|
||||
'我们不会向第三方出售您的个人信息。仅在以下情形共享:',
|
||||
'1)获得您的明确同意;',
|
||||
'2)为实现支付、短信、配送、地图/定位、账号登录与支付等功能,与必要的服务提供商共享履行服务所必需的信息,并要求其依法保护;',
|
||||
'3)根据法律法规、行政或司法机关要求;',
|
||||
'4)在合并、分立、资产转让等情形下,如涉及个人信息转移,我们将要求新的持有方继续受本政策约束,或重新征得您的同意。',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: '三、我们如何存储与保护',
|
||||
paragraphs: [
|
||||
'您的个人信息存储于中华人民共和国境内。我们仅在实现本政策所述目的所必需的期限内保存;超出期限后将删除或匿名化处理(法律法规另有规定的除外)。',
|
||||
'我们采取合理的技术与管理措施保护信息安全,防止未经授权的访问、披露、篡改或丢失。如发生安全事件,我们将按法规要求及时告知并采取补救措施。',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: '四、第三方 SDK / 服务说明',
|
||||
paragraphs: [
|
||||
'为实现登录、支付、分享、定位等功能,本平台可能接入微信开放平台、支付、短信、地图定位等第三方服务。该类服务会按其自身隐私政策处理相关信息。我们仅在实现功能所必需的范围内启用,并尽量采用最小化授权。',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: '五、您的权利',
|
||||
paragraphs: [
|
||||
'您有权查阅、复制、更正、补充、删除您的个人信息,有权撤回同意、注销账号,以及在符合条件时限制或拒绝我们处理您的个人信息。',
|
||||
'您可通过「我的」相关功能或联系客服行使上述权利。为保障安全,我们可能需要先验证您的身份。我们将在合理期限内答复。',
|
||||
'您撤回同意后,我们将停止基于相应同意的处理活动,但不影响此前基于同意已进行的处理。',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: '六、未成年人保护',
|
||||
paragraphs: [
|
||||
'本平台主要面向成年人。若您为未成年人,请在监护人陪同下阅读本政策,并在监护人同意后使用服务。我们不会主动收集未成年人的个人信息;如发现误收集,将尽快删除。',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: '七、本政策的更新',
|
||||
paragraphs: [
|
||||
'我们可能适时更新本政策,并通过平台公告、弹窗或页面提示等方式告知。重大变更时,我们会提供更显著的通知,并在必要时重新征得您的同意。',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: '八、联系我们',
|
||||
paragraphs: [
|
||||
`个人信息保护相关事宜,请拨打客服热线 ${CUSTOMER_SERVICE_PHONE}(工作时间 9:00–21:00)。我们将尽快处理您的请求。`,
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export function getLegalDocument(id: LegalDocument['id']): LegalDocument {
|
||||
return id === 'privacy-policy' ? PRIVACY_POLICY : USER_AGREEMENT;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/** 语言模型 API 提供商(OpenAI 兼容 Chat Completions) */
|
||||
export const LLM_PROVIDERS = [
|
||||
'DEEPSEEK',
|
||||
'OPENAI',
|
||||
'QWEN',
|
||||
'CUSTOM',
|
||||
] as const;
|
||||
|
||||
export type LlmProvider = (typeof LLM_PROVIDERS)[number];
|
||||
|
||||
export const LLM_PROVIDER_LABELS: Record<LlmProvider, string> = {
|
||||
DEEPSEEK: 'DeepSeek',
|
||||
OPENAI: 'OpenAI',
|
||||
QWEN: '通义千问',
|
||||
CUSTOM: '自定义(OpenAI 兼容)',
|
||||
};
|
||||
|
||||
export const LLM_PROVIDER_PRESETS: Record<
|
||||
LlmProvider,
|
||||
{ defaultBaseUrl: string; defaultModel: string }
|
||||
> = {
|
||||
DEEPSEEK: {
|
||||
defaultBaseUrl: 'https://api.deepseek.com',
|
||||
defaultModel: 'deepseek-chat',
|
||||
},
|
||||
OPENAI: {
|
||||
defaultBaseUrl: 'https://api.openai.com',
|
||||
defaultModel: 'gpt-4o-mini',
|
||||
},
|
||||
QWEN: {
|
||||
defaultBaseUrl: 'https://dashscope.aliyuncs.com/compatible-mode',
|
||||
defaultModel: 'qwen-plus',
|
||||
},
|
||||
CUSTOM: {
|
||||
defaultBaseUrl: '',
|
||||
defaultModel: '',
|
||||
},
|
||||
};
|
||||
|
||||
export type LlmApiConfigDto = {
|
||||
id: string;
|
||||
name: string;
|
||||
provider: LlmProvider;
|
||||
baseUrl: string;
|
||||
modelName: string;
|
||||
/** 列表/详情不回明文 */
|
||||
apiKeyConfigured: boolean;
|
||||
temperature: number | null;
|
||||
maxTokens: number | null;
|
||||
systemPrompt: string | null;
|
||||
enabled: boolean;
|
||||
createdByHqAccountId: string;
|
||||
createdByName: string | null;
|
||||
/** 当前登录账号是否为创建人 */
|
||||
isOwner: boolean;
|
||||
/** 非超管仅可改 enabled;超管可改全部 */
|
||||
canEditFull: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type CreateLlmApiConfigRequest = {
|
||||
name: string;
|
||||
provider: LlmProvider;
|
||||
baseUrl?: string;
|
||||
apiKey: string;
|
||||
modelName?: string;
|
||||
temperature?: number | null;
|
||||
maxTokens?: number | null;
|
||||
systemPrompt?: string | null;
|
||||
enabled?: boolean;
|
||||
};
|
||||
|
||||
export type UpdateLlmApiConfigRequest = {
|
||||
/** 非超管仅允许传 enabled */
|
||||
name?: string;
|
||||
provider?: LlmProvider;
|
||||
baseUrl?: string;
|
||||
apiKey?: string;
|
||||
modelName?: string;
|
||||
temperature?: number | null;
|
||||
maxTokens?: number | null;
|
||||
systemPrompt?: string | null;
|
||||
enabled?: boolean;
|
||||
};
|
||||
|
||||
export type LlmApiConfigOptionDto = {
|
||||
id: string;
|
||||
name: string;
|
||||
provider: LlmProvider;
|
||||
modelName: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
export interface AdminListQuery {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
export interface AdminPageResult<T> {
|
||||
items: T[];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
export interface ExportRequest {
|
||||
format?: 'csv';
|
||||
}
|
||||
|
||||
export interface SystemVersionDto {
|
||||
id: string;
|
||||
gitTag: string | null;
|
||||
commitId: string;
|
||||
commitMessage: string;
|
||||
branch: string | null;
|
||||
deployedBy: string | null;
|
||||
deployedAt: string;
|
||||
}
|
||||
|
||||
export interface DeployTriggerResult {
|
||||
accepted: boolean;
|
||||
started?: boolean;
|
||||
message: string;
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
export type PartnerLogCategory =
|
||||
| 'login'
|
||||
| 'wechat_auth'
|
||||
| 'page_view'
|
||||
| 'account_ops'
|
||||
| 'store_ops'
|
||||
| 'shipping'
|
||||
| 'settlement'
|
||||
| 'warehouse_ops';
|
||||
|
||||
export const PARTNER_LOG_EVENT_CATEGORIES: Record<PartnerLogCategory, readonly string[]> = {
|
||||
login: ['partner_sms_send', 'partner_sms_login', 'partner_sms_verify_fail', 'partner_login_success'],
|
||||
wechat_auth: ['partner_wechat_login', 'partner_wechat_bind'],
|
||||
page_view: [
|
||||
'partner_home_view',
|
||||
'partner_store_list_view',
|
||||
'partner_store_detail_view',
|
||||
'partner_store_create_view',
|
||||
'partner_order_list_view',
|
||||
'partner_order_detail_view',
|
||||
'partner_proxy_order_view',
|
||||
'partner_bills_view',
|
||||
'partner_bill_detail_view',
|
||||
'partner_staff_list_view',
|
||||
'partner_leaderboard_view',
|
||||
'partner_weekly_report_view',
|
||||
],
|
||||
account_ops: [
|
||||
'partner_staff_create',
|
||||
'partner_staff_sms_send',
|
||||
'partner_staff_sms_verify_fail',
|
||||
'partner_staff_update',
|
||||
'partner_staff_delete',
|
||||
'partner_staff_permission_update',
|
||||
],
|
||||
store_ops: [
|
||||
'partner_store_create',
|
||||
'partner_store_status_change',
|
||||
'partner_store_audit_approved',
|
||||
'partner_store_audit_rejected',
|
||||
],
|
||||
shipping: ['partner_order_ship', 'partner_delivery_advance', 'partner_proxy_order_create'],
|
||||
settlement: ['partner_bill_view', 'partner_bill_detail_view', 'partner_bill_confirm'],
|
||||
warehouse_ops: ['partner_warehouse_view', 'partner_warehouse_update'],
|
||||
};
|
||||
|
||||
export const PARTNER_LOG_CATEGORY_OPTIONS: Array<{ value: PartnerLogCategory | ''; label: string }> = [
|
||||
{ value: '', label: '全部' },
|
||||
{ value: 'login', label: '登录' },
|
||||
{ value: 'wechat_auth', label: '微信授权' },
|
||||
{ value: 'page_view', label: '页面浏览' },
|
||||
{ value: 'account_ops', label: '子账号管理' },
|
||||
{ value: 'store_ops', label: '门店操作' },
|
||||
{ value: 'shipping', label: '发货/配送' },
|
||||
{ value: 'settlement', label: '结算' },
|
||||
{ value: 'warehouse_ops', label: '仓库操作' },
|
||||
];
|
||||
|
||||
export const PARTNER_LOG_CATEGORY_LABELS: Record<PartnerLogCategory | '', string> = {
|
||||
'': '全部',
|
||||
login: '登录',
|
||||
wechat_auth: '微信授权',
|
||||
page_view: '页面浏览',
|
||||
account_ops: '子账号管理',
|
||||
store_ops: '门店操作',
|
||||
shipping: '发货/配送',
|
||||
settlement: '结算',
|
||||
warehouse_ops: '仓库操作',
|
||||
};
|
||||
|
||||
export function resolvePartnerLogCategory(eventName: string): PartnerLogCategory | null {
|
||||
for (const [category, events] of Object.entries(PARTNER_LOG_EVENT_CATEGORIES) as Array<
|
||||
[PartnerLogCategory, readonly string[]]
|
||||
>) {
|
||||
if (events.includes(eventName)) return category;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function eventNamesForPartnerLogCategory(category: string): string[] | undefined {
|
||||
if (!category) return undefined;
|
||||
return [...(PARTNER_LOG_EVENT_CATEGORIES[category as PartnerLogCategory] ?? [])];
|
||||
}
|
||||
|
||||
export interface PartnerLogRowDto {
|
||||
id: string;
|
||||
partnerId: string;
|
||||
partnerAccountId: string | null;
|
||||
accountName: string | null;
|
||||
accountPhone: string | null;
|
||||
companyName: string | null;
|
||||
isSubAccount?: boolean;
|
||||
staffRole?: string | null;
|
||||
category: PartnerLogCategory | null;
|
||||
eventName: string;
|
||||
clientApp: string | null;
|
||||
refType: string | null;
|
||||
refId: string | null;
|
||||
extraJson: Record<string, unknown> | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export type PartnerAnalyticsExtra = {
|
||||
sessionId?: string;
|
||||
partnerAccountId?: string;
|
||||
storeId?: string;
|
||||
orderId?: string;
|
||||
billId?: string;
|
||||
cityCode?: string;
|
||||
pagePath?: string;
|
||||
};
|
||||
@@ -0,0 +1,132 @@
|
||||
import type { AccountStatus, PartnerStaffRole } from './enums';
|
||||
|
||||
export interface PartnerMe {
|
||||
id: string;
|
||||
name: string;
|
||||
phone: string;
|
||||
isPrimary: boolean;
|
||||
companyName?: string;
|
||||
hasWechat?: boolean;
|
||||
/** 微信授权后的昵称 */
|
||||
wxNickname?: string | null;
|
||||
/** 微信授权后的头像 URL */
|
||||
wxAvatarUrl?: string | null;
|
||||
staffRole?: PartnerStaffRole;
|
||||
permissions?: string[];
|
||||
primaryAccountId?: string;
|
||||
/** 子账号联系主账号用 */
|
||||
primaryPhone?: string;
|
||||
primaryName?: string;
|
||||
/** 主账号绑定的管仓 ID(有则本城酒单推送至该仓) */
|
||||
managedWarehouseId?: string | null;
|
||||
/** 是否已配置仓库管理(无则不展示/不推送酒订单) */
|
||||
hasWarehouseAccess?: boolean;
|
||||
}
|
||||
|
||||
export interface UpdatePartnerMeRequest {
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface PartnerStaffItem {
|
||||
id: string;
|
||||
name: string;
|
||||
phone: string;
|
||||
staffRole: PartnerStaffRole;
|
||||
permissions?: string[];
|
||||
status: AccountStatus;
|
||||
lastLoginAt?: string;
|
||||
}
|
||||
|
||||
export interface CreatePartnerStaffRequest {
|
||||
phone: string;
|
||||
name: string;
|
||||
smsCode: string;
|
||||
permissions?: string[];
|
||||
}
|
||||
|
||||
export interface UpdatePartnerStaffRequest {
|
||||
name?: string;
|
||||
staffRole?: PartnerStaffRole;
|
||||
permissions?: string[];
|
||||
status?: AccountStatus;
|
||||
}
|
||||
|
||||
export const PARTNER_STAFF_ROLE_LABELS: Record<PartnerStaffRole, string> = {
|
||||
PARTNER: '城市合伙人',
|
||||
INTERNAL: '内部员工',
|
||||
PROMOTER: '推广员',
|
||||
};
|
||||
|
||||
export type PartnerLeaderboardPeriod = 'month' | 'lastMonth' | 'total';
|
||||
|
||||
export interface PartnerLeaderboardEntry {
|
||||
rank: number;
|
||||
accountId: string;
|
||||
name: string;
|
||||
staffRole?: PartnerStaffRole;
|
||||
roleLabel: string;
|
||||
totalStores: number;
|
||||
periodStores: number;
|
||||
isSelf?: boolean;
|
||||
}
|
||||
|
||||
export interface PartnerLeaderboardResponse {
|
||||
period: PartnerLeaderboardPeriod;
|
||||
list: PartnerLeaderboardEntry[];
|
||||
self?: PartnerLeaderboardEntry & { beatPercent?: number };
|
||||
}
|
||||
|
||||
export interface PartnerStorePhoneAvailableResponse {
|
||||
available: boolean;
|
||||
message?: string;
|
||||
/** 该手机号已是主账号时绑定的门店数;>0 时拓店需二次确认 */
|
||||
existingStoreCount?: number;
|
||||
needConfirm?: boolean;
|
||||
}
|
||||
|
||||
/** 合伙人登录前手机号校验 */
|
||||
export interface PartnerPhoneCheckResponse {
|
||||
ok: boolean;
|
||||
maskedPhone: string;
|
||||
name: string;
|
||||
companyName?: string;
|
||||
hasWechat?: boolean;
|
||||
}
|
||||
|
||||
export interface PartnerWeeklyReportPeriod {
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface PartnerWeeklyReportDailyGmv {
|
||||
date: string;
|
||||
weekdayLabel: string;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
export interface PartnerWeeklyReportStoreRank {
|
||||
rank: number;
|
||||
storeId: string;
|
||||
name: string;
|
||||
subtitle?: string;
|
||||
redeemAmount: number;
|
||||
}
|
||||
|
||||
export interface PartnerWeeklyReportResponse {
|
||||
period: PartnerWeeklyReportPeriod;
|
||||
availablePeriods: PartnerWeeklyReportPeriod[];
|
||||
summary: {
|
||||
gmv: number;
|
||||
gmvGrowthPercent: number;
|
||||
activeStoreCount: number;
|
||||
totalStoreCount: number;
|
||||
orderCount: number;
|
||||
newStoreCount: number;
|
||||
newStoreTarget: number;
|
||||
newStoreProgressPercent: number;
|
||||
};
|
||||
dailyGmv: PartnerWeeklyReportDailyGmv[];
|
||||
storeRanking: PartnerWeeklyReportStoreRank[];
|
||||
insight: string;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
export type PromoCodeStatus = 'ACTIVE' | 'DISABLED';
|
||||
|
||||
export type PromoCodeScene =
|
||||
| 'ONLINE_LINK'
|
||||
| 'OFFLINE_PICKUP'
|
||||
| 'PARTNER_CHANNEL'
|
||||
| 'EVENT'
|
||||
| 'OTHER';
|
||||
|
||||
export const PROMO_CODE_SCENE_LABELS: Record<PromoCodeScene, string> = {
|
||||
ONLINE_LINK: '线上链接',
|
||||
OFFLINE_PICKUP: '现场提货',
|
||||
PARTNER_CHANNEL: '合伙人渠道',
|
||||
EVENT: '活动品鉴',
|
||||
OTHER: '其他',
|
||||
};
|
||||
|
||||
export const PROMO_CODE_STATUS_LABELS: Record<PromoCodeStatus, string> = {
|
||||
ACTIVE: '启用',
|
||||
DISABLED: '已关闭',
|
||||
};
|
||||
|
||||
export type PromoCodeOwnerUser = {
|
||||
id: string;
|
||||
userNo?: string | null;
|
||||
nickname?: string | null;
|
||||
phone?: string | null;
|
||||
};
|
||||
|
||||
export type PromoCodeItem = {
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
scene: PromoCodeScene;
|
||||
qrcodeId: string;
|
||||
status: PromoCodeStatus;
|
||||
scanCount: number;
|
||||
orderCount: number;
|
||||
landingUrl: string;
|
||||
qrcodeUrl?: string | null;
|
||||
remark?: string | null;
|
||||
ownerUser?: PromoCodeOwnerUser | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type PromoCodeStats = {
|
||||
/** 扫码进入次数 */
|
||||
scanCount: number;
|
||||
orderCount: number;
|
||||
conversionRate: number;
|
||||
/** 归因用户数(user_promo_attribution) */
|
||||
attributionCount?: number;
|
||||
/** 扫码注册用户数:用户来源标记为本推广码 */
|
||||
sourceMarkedCount?: number;
|
||||
/** @deprecated 同 sourceMarkedCount,兼容旧字段名 */
|
||||
registerCount?: number;
|
||||
};
|
||||
|
||||
export type PromoCodeAttributedUser = {
|
||||
id: string;
|
||||
userNo: string;
|
||||
nickname: string | null;
|
||||
phone: string | null;
|
||||
phoneVerifiedAt: string | null;
|
||||
sourceType: string;
|
||||
sourceRefId: string | null;
|
||||
firstTouchAt: string | null;
|
||||
orderCount: number;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
export type PromoTouchResult = {
|
||||
promoCode: string;
|
||||
promoCodeId: string;
|
||||
channelName: string;
|
||||
/** 是否首次写入 user_promo_attribution */
|
||||
attributed: boolean;
|
||||
/** 是否已将用户来源标记为 PROMO_CODE */
|
||||
sourceApplied: boolean;
|
||||
};
|
||||
|
||||
export function promoConversion(scan: number, orders: number): string {
|
||||
if (scan <= 0) return '0%';
|
||||
return `${Math.round((orders / scan) * 1000) / 10}%`;
|
||||
}
|
||||
|
||||
export function buildPromoLandingUrl(baseUrl: string, code: string, qrcodeId: string): string {
|
||||
const base = baseUrl.replace(/\/$/, '');
|
||||
return `${base}/?promo=${encodeURIComponent(code)}&pid=${encodeURIComponent(qrcodeId)}`;
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
export interface RedeemTokenRequest {
|
||||
amount: number;
|
||||
couponId?: string;
|
||||
storeId?: string;
|
||||
}
|
||||
|
||||
export interface RedeemTokenResult {
|
||||
token: string;
|
||||
expireAt: string;
|
||||
amount: number;
|
||||
boundStoreId?: string | null;
|
||||
}
|
||||
|
||||
export interface RedeemPreviewDto {
|
||||
token: string;
|
||||
amount: number;
|
||||
expireInSeconds: number;
|
||||
redeemType?: 'DIRECT' | 'COUPON';
|
||||
boundStoreId?: string | null;
|
||||
}
|
||||
|
||||
/** 门店核销方式 */
|
||||
export type RedeemChannel = 'SCAN' | 'PHONE';
|
||||
|
||||
export const REDEEM_CHANNEL_LABELS: Record<RedeemChannel, string> = {
|
||||
SCAN: '扫码核销',
|
||||
PHONE: '手机号核销',
|
||||
};
|
||||
|
||||
export interface RedeemRecordDto {
|
||||
id: string;
|
||||
redeemNo: string;
|
||||
amount: number;
|
||||
settleAmount: number;
|
||||
/** 核销方式:扫码 SCAN / 手机号 PHONE */
|
||||
channel?: RedeemChannel;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface RedeemChannelStatsBucket {
|
||||
channel: RedeemChannel;
|
||||
count: number;
|
||||
amount: number;
|
||||
settleAmount: number;
|
||||
}
|
||||
|
||||
export interface RedeemStatsDto {
|
||||
range: 'today' | '7d' | '30d';
|
||||
totalCount: number;
|
||||
totalAmount: number;
|
||||
totalSettleAmount: number;
|
||||
byChannel: RedeemChannelStatsBucket[];
|
||||
}
|
||||
|
||||
export interface RedeemPhoneBalanceDto {
|
||||
sessionId: string;
|
||||
totalBalance: number;
|
||||
maskedPhone: string;
|
||||
user: {
|
||||
id: string;
|
||||
userNo?: string | null;
|
||||
nickname?: string | null;
|
||||
phone?: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
export interface RedeemPhonePrepareDto {
|
||||
sessionId: string;
|
||||
amount: number;
|
||||
expireInSeconds: number;
|
||||
}
|
||||
|
||||
export interface RedeemPhoneDirectPrepareRequest {
|
||||
phone: string;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
export interface RedeemPhoneDirectPrepareResult extends RedeemPhonePrepareDto {
|
||||
totalBalance: number;
|
||||
maskedPhone: string;
|
||||
user: {
|
||||
id: string;
|
||||
userNo?: string | null;
|
||||
nickname?: string | null;
|
||||
phone?: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
export type RedeemPendingStatus = 'PENDING' | 'COMPLETED' | 'REJECTED';
|
||||
|
||||
export const REDEEM_PENDING_STATUS_LABELS: Record<RedeemPendingStatus, string> = {
|
||||
PENDING: '待处理',
|
||||
COMPLETED: '已补核销',
|
||||
REJECTED: '已驳回',
|
||||
};
|
||||
|
||||
export type RedeemErrorClass = 'NETWORK' | 'BUSINESS';
|
||||
|
||||
export type RedeemFailureReportResult = {
|
||||
failCount: number;
|
||||
thresholdReached: boolean;
|
||||
threshold: number;
|
||||
};
|
||||
|
||||
export type RedeemPendingSubmitResult = {
|
||||
pendingId: string;
|
||||
pendingNo: string;
|
||||
redeemToken: string;
|
||||
failCount: number;
|
||||
};
|
||||
|
||||
export type RedeemPendingItem = {
|
||||
id: string;
|
||||
pendingNo: string;
|
||||
redeemToken: string;
|
||||
amount: number;
|
||||
redeemType: string;
|
||||
failCount: number;
|
||||
status: RedeemPendingStatus;
|
||||
remark?: string | null;
|
||||
rejectReason?: string | null;
|
||||
processedAt?: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
store?: { id: string; name: string; cityName?: string } | null;
|
||||
user?: { id: string; userNo?: string | null; phone?: string | null; nickname?: string | null } | null;
|
||||
photoUrl?: string | null;
|
||||
redeemRecordId?: string | null;
|
||||
redeemRecord?: { id: string; redeemNo: string } | null;
|
||||
};
|
||||
@@ -0,0 +1,271 @@
|
||||
export type FinancePayStatus = 'UNPAID' | 'PAID';
|
||||
|
||||
/** 门店未出账提现单日上限默认值(FIN-002,可配置) */
|
||||
export const DEFAULT_STORE_WITHDRAW_DAILY_LIMIT = 5000;
|
||||
|
||||
export type StoreWithdrawStatus = 'PENDING_REVIEW' | 'REJECTED' | 'PAID';
|
||||
|
||||
export const STORE_WITHDRAW_STATUS_LABELS: Record<StoreWithdrawStatus, string> = {
|
||||
PENDING_REVIEW: '待审核',
|
||||
REJECTED: '已驳回',
|
||||
PAID: '已结算',
|
||||
};
|
||||
|
||||
/** 总部「门店账单」统一列表:T+1 出账 / 手动提现 */
|
||||
export type StoreSettlementKind = 'T1_BILL' | 'WITHDRAW';
|
||||
|
||||
export const STORE_SETTLEMENT_KIND_LABELS: Record<StoreSettlementKind, string> = {
|
||||
T1_BILL: 'T+1出账',
|
||||
WITHDRAW: '手动提现',
|
||||
};
|
||||
|
||||
export type StoreSettlementStatus = FinancePayStatus | StoreWithdrawStatus;
|
||||
|
||||
export const STORE_SETTLEMENT_STATUS_LABELS: Record<StoreSettlementStatus, string> = {
|
||||
UNPAID: '未打款',
|
||||
PAID: '已打款',
|
||||
PENDING_REVIEW: '待审核',
|
||||
REJECTED: '已驳回',
|
||||
};
|
||||
|
||||
export interface StoreSettlementRowDto {
|
||||
kind: StoreSettlementKind;
|
||||
id: string;
|
||||
billNo: string;
|
||||
storeId: string;
|
||||
amount: number;
|
||||
status: StoreSettlementStatus;
|
||||
date: string;
|
||||
overdue?: boolean;
|
||||
redeemCount?: number | null;
|
||||
redeemAmount?: number | null;
|
||||
settlementRate?: number | null;
|
||||
payoutCount?: number | null;
|
||||
store?: {
|
||||
id: string;
|
||||
name: string;
|
||||
cityName: string;
|
||||
phone?: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
export interface StoreWithdrawBankAccountDto {
|
||||
bankAccountName?: string | null;
|
||||
bankAccountNo?: string | null;
|
||||
bankBranch?: string | null;
|
||||
}
|
||||
|
||||
export interface StoreWithdrawSummaryDto {
|
||||
availableAmount: number;
|
||||
pendingReviewAmount: number;
|
||||
todayAppliedAmount: number;
|
||||
dailyLimit: number;
|
||||
remainingDailyLimit: number;
|
||||
isPrimary: boolean;
|
||||
hasBankAccount: boolean;
|
||||
hasPendingRequest: boolean;
|
||||
bankAccount?: StoreWithdrawBankAccountDto | null;
|
||||
}
|
||||
|
||||
export interface StoreWithdrawRequestDto {
|
||||
id: string;
|
||||
withdrawNo: string;
|
||||
storeId: string;
|
||||
amount: number;
|
||||
payoutCount: number;
|
||||
status: StoreWithdrawStatus;
|
||||
rejectReason?: string | null;
|
||||
appliedAt: string;
|
||||
reviewedAt?: string | null;
|
||||
paidAt?: string | null;
|
||||
paymentRef?: string | null;
|
||||
}
|
||||
|
||||
export interface StorePayoutDto {
|
||||
id: string;
|
||||
redeemAmount: number;
|
||||
payoutAmount: number;
|
||||
status: 'PENDING' | 'PAID';
|
||||
expectedPayAt: string;
|
||||
paidAt?: string | null;
|
||||
storeBillId?: string | null;
|
||||
}
|
||||
|
||||
export type PartnerBillStatus =
|
||||
| 'PENDING_REVIEW'
|
||||
| 'AWAITING_CONFIRM'
|
||||
| 'UNPAID'
|
||||
| 'PAID'
|
||||
| 'REJECTED';
|
||||
|
||||
export const PARTNER_BILL_STATUS_LABELS: Record<PartnerBillStatus, string> = {
|
||||
PENDING_REVIEW: '待审核',
|
||||
AWAITING_CONFIRM: '待合伙人确认',
|
||||
UNPAID: '未打款',
|
||||
PAID: '已打款',
|
||||
REJECTED: '已驳回',
|
||||
};
|
||||
|
||||
export const FINANCE_PAY_STATUS_LABELS: Record<FinancePayStatus, string> = {
|
||||
UNPAID: '未打款',
|
||||
PAID: '已打款',
|
||||
};
|
||||
|
||||
export function storeSettlementStatusLabel(
|
||||
kind: StoreSettlementKind,
|
||||
status: string,
|
||||
): string {
|
||||
if (kind === 'WITHDRAW' && status in STORE_WITHDRAW_STATUS_LABELS) {
|
||||
return STORE_WITHDRAW_STATUS_LABELS[status as StoreWithdrawStatus];
|
||||
}
|
||||
if (status in FINANCE_PAY_STATUS_LABELS) {
|
||||
return FINANCE_PAY_STATUS_LABELS[status as FinancePayStatus];
|
||||
}
|
||||
return STORE_SETTLEMENT_STATUS_LABELS[status as StoreSettlementStatus] ?? status;
|
||||
}
|
||||
|
||||
export interface PartnerBillDto {
|
||||
id: string;
|
||||
billNo: string;
|
||||
orderCommission: number;
|
||||
redeemCommission: number;
|
||||
totalAmount: number;
|
||||
status: PartnerBillStatus;
|
||||
periodStart: string;
|
||||
periodEnd: string;
|
||||
confirmedAt?: string | null;
|
||||
sentAt?: string | null;
|
||||
paidAt?: string | null;
|
||||
rejectReason?: string | null;
|
||||
}
|
||||
|
||||
export interface PartnerBillDetailDto extends PartnerBillDto {
|
||||
partnerId: string;
|
||||
}
|
||||
|
||||
export interface StoreBillDto {
|
||||
id: string;
|
||||
billNo: string;
|
||||
storeId: string;
|
||||
billDate: string;
|
||||
redeemCount: number;
|
||||
redeemAmount: number;
|
||||
settlementRate: number;
|
||||
payoutAmount: number;
|
||||
status: FinancePayStatus;
|
||||
paidAt?: string | null;
|
||||
}
|
||||
|
||||
export interface WineryBillDto {
|
||||
id: string;
|
||||
billNo: string;
|
||||
billDate: string;
|
||||
orderCount: number;
|
||||
orderAmount: number;
|
||||
wineryRate: number;
|
||||
wineryAmount: number;
|
||||
status: FinancePayStatus;
|
||||
paidAt?: string | null;
|
||||
}
|
||||
|
||||
export interface WineryBillItemDto {
|
||||
id: string;
|
||||
orderId: string;
|
||||
orderNo: string;
|
||||
deliveryType: string;
|
||||
payAmount: number;
|
||||
wineryAmount: number;
|
||||
paidAt: string;
|
||||
}
|
||||
|
||||
/** 门店结算默认比例(核销金额 × 比例) */
|
||||
export const STORE_SETTLEMENT_DEFAULT_RATE = 0.6;
|
||||
|
||||
/** 酒厂账单结算比例(酒单实付 × 比例),暂定 30% */
|
||||
export const WINERY_SETTLEMENT_RATE = 0.3;
|
||||
|
||||
export type { LogisticsSettlementMethod } from './enums';
|
||||
export { LOGISTICS_SETTLEMENT_METHOD_LABELS } from './enums';
|
||||
import type { LogisticsSettlementMethod } from './enums';
|
||||
|
||||
/** 小飞侠默认计价:2瓶6元,加一瓶+2元,6瓶一箱14元 */
|
||||
export const DEFAULT_XFX_LOGISTICS_PRICING = {
|
||||
baseBottles: 2,
|
||||
baseFee: 6,
|
||||
extraBottleFee: 2,
|
||||
boxBottles: 6,
|
||||
boxFee: 14,
|
||||
} as const;
|
||||
|
||||
export type LogisticsPricingRuleDto = {
|
||||
baseBottles: number;
|
||||
baseFee: number;
|
||||
extraBottleFee: number;
|
||||
boxBottles?: number;
|
||||
boxFee?: number;
|
||||
};
|
||||
|
||||
export interface LogisticsBillDto {
|
||||
id: string;
|
||||
billNo: string;
|
||||
fulfillmentProviderId: string;
|
||||
providerCode?: string;
|
||||
providerName?: string;
|
||||
periodStart: string;
|
||||
periodEnd: string;
|
||||
orderCount: number;
|
||||
bottleCount: number;
|
||||
logisticsAmount: number;
|
||||
settlementMethod: LogisticsSettlementMethod;
|
||||
status: FinancePayStatus;
|
||||
paidAt?: string | null;
|
||||
pricingSnapshot?: LogisticsPricingRuleDto | null;
|
||||
}
|
||||
|
||||
export interface LogisticsBillItemDto {
|
||||
id: string;
|
||||
orderId: string;
|
||||
orderNo: string;
|
||||
quantity: number;
|
||||
logisticsAmount: number;
|
||||
shippedAt: string;
|
||||
}
|
||||
|
||||
export type FinanceBillSummary = {
|
||||
count: number;
|
||||
redeemAmount?: number;
|
||||
payoutAmount?: number;
|
||||
orderAmount?: number;
|
||||
orderCommission?: number;
|
||||
redeemCommission?: number;
|
||||
wineryAmount?: number;
|
||||
logisticsAmount?: number;
|
||||
bottleCount?: number;
|
||||
totalAmount: number;
|
||||
};
|
||||
|
||||
export type StoreDailyBillRow = {
|
||||
billDate: string;
|
||||
storeId: string;
|
||||
storeName: string;
|
||||
storePhone?: string;
|
||||
cityName?: string;
|
||||
redeemCount: number;
|
||||
redeemAmount: number;
|
||||
settlementRate: number;
|
||||
payoutAmount: number;
|
||||
pendingCount: number;
|
||||
paidCount: number;
|
||||
};
|
||||
|
||||
export type WineryOrderBillRow = {
|
||||
orderId: string;
|
||||
orderNo: string;
|
||||
deliveryType: string;
|
||||
cityName?: string;
|
||||
payAmount: number;
|
||||
wineryRate: number;
|
||||
wineryAmount: number;
|
||||
paidAt: string;
|
||||
receiverCity?: string;
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
import type { AccountStatus, StoreStaffRole } from './enums';
|
||||
|
||||
export interface ShopStoreOption {
|
||||
storeId: string;
|
||||
name: string;
|
||||
status: string;
|
||||
district?: string;
|
||||
address?: string;
|
||||
}
|
||||
|
||||
export interface ShopAccountMe {
|
||||
id: string;
|
||||
name: string;
|
||||
phone: string;
|
||||
isPrimary: boolean;
|
||||
staffRole?: StoreStaffRole | null;
|
||||
permissions?: string[];
|
||||
primaryAccountId?: string;
|
||||
hasWechat?: boolean;
|
||||
}
|
||||
|
||||
export interface ShopMe {
|
||||
account: ShopAccountMe;
|
||||
store: ShopStoreOption | null;
|
||||
stores: ShopStoreOption[];
|
||||
/** @deprecated use account + store; kept for older H5 clients during rollout */
|
||||
id?: string;
|
||||
storeId?: string;
|
||||
name?: string;
|
||||
phone?: string;
|
||||
}
|
||||
|
||||
export interface ShopLoginResponse {
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
actorType: string;
|
||||
actorId: string;
|
||||
phoneVerified: boolean;
|
||||
account: ShopAccountMe;
|
||||
stores: ShopStoreOption[];
|
||||
store?: ShopStoreOption | null;
|
||||
/** populated after select-store (or auto when single store) */
|
||||
selectedStoreId?: string;
|
||||
}
|
||||
|
||||
export interface SelectShopStoreRequest {
|
||||
storeId: string;
|
||||
}
|
||||
|
||||
export interface ShopStaffItem {
|
||||
id: string;
|
||||
name: string;
|
||||
phone: string;
|
||||
staffRole: StoreStaffRole;
|
||||
permissions?: string[];
|
||||
status: AccountStatus;
|
||||
storeIds: string[];
|
||||
stores: ShopStoreOption[];
|
||||
lastLoginAt?: string;
|
||||
}
|
||||
|
||||
export interface CreateShopStaffRequest {
|
||||
phone: string;
|
||||
name: string;
|
||||
storeIds: string[];
|
||||
staffRole?: StoreStaffRole;
|
||||
permissions?: string[];
|
||||
}
|
||||
|
||||
export interface UpdateShopStaffRequest {
|
||||
name?: string;
|
||||
staffRole?: StoreStaffRole;
|
||||
permissions?: string[];
|
||||
status?: AccountStatus;
|
||||
storeIds?: string[];
|
||||
}
|
||||
|
||||
export const STORE_STAFF_ROLE_LABELS: Record<StoreStaffRole, string> = {
|
||||
MANAGER: '店长',
|
||||
CASHIER: '收银员',
|
||||
};
|
||||
|
||||
/** Default permissions for store sub-accounts (首版). */
|
||||
export const STORE_STAFF_DEFAULT_PERMISSIONS = ['redeem', 'records'] as const;
|
||||
@@ -0,0 +1,116 @@
|
||||
export type StoreLogCategory =
|
||||
| 'login'
|
||||
| 'wechat_auth'
|
||||
| 'page_view'
|
||||
| 'redeem'
|
||||
| 'payout'
|
||||
| 'store_ops'
|
||||
| 'package';
|
||||
|
||||
export const STORE_LOG_EVENT_CATEGORIES: Record<StoreLogCategory, readonly string[]> = {
|
||||
login: [
|
||||
'store_sms_send',
|
||||
'store_sms_login',
|
||||
'store_sms_verify_fail',
|
||||
'store_login_success',
|
||||
'store_select',
|
||||
],
|
||||
wechat_auth: ['store_wechat_login', 'store_wechat_bind'],
|
||||
page_view: [
|
||||
'store_home_view',
|
||||
'store_redeem_entry_view',
|
||||
'store_redeem_confirm_view',
|
||||
'store_phone_redeem_view',
|
||||
'store_records_view',
|
||||
'store_withdraw_view',
|
||||
'store_packages_view',
|
||||
'store_mine_view',
|
||||
'store_staff_view',
|
||||
],
|
||||
redeem: [
|
||||
'store_redeem_preview',
|
||||
'store_redeem_confirm',
|
||||
'store_redeem_confirm_fail',
|
||||
'store_redeem_weaknet_threshold',
|
||||
'store_redeem_pending_submit',
|
||||
'store_redeem_pending_complete',
|
||||
'store_redeem_pending_reject',
|
||||
'store_redeem_phone_lookup_sms',
|
||||
'store_redeem_phone_balance',
|
||||
'store_redeem_phone_prepare',
|
||||
'store_redeem_scan_start',
|
||||
],
|
||||
payout: ['store_payout_created', 'store_payout_paid', 'store_withdraw_applied', 'store_withdraw_paid'],
|
||||
store_ops: [
|
||||
'store_status_change',
|
||||
'store_staff_create',
|
||||
'store_staff_update',
|
||||
'store_staff_delete',
|
||||
'store_staff_permission_update',
|
||||
],
|
||||
package: ['store_package_apply', 'store_package_audit_view'],
|
||||
};
|
||||
|
||||
export const STORE_LOG_CATEGORY_OPTIONS: Array<{ value: StoreLogCategory | ''; label: string }> = [
|
||||
{ value: '', label: '全部' },
|
||||
{ value: 'login', label: '登录' },
|
||||
{ value: 'wechat_auth', label: '授权' },
|
||||
{ value: 'page_view', label: '页面浏览' },
|
||||
{ value: 'redeem', label: '核销' },
|
||||
{ value: 'payout', label: '提现/打款' },
|
||||
{ value: 'store_ops', label: '门店操作' },
|
||||
{ value: 'package', label: '套餐' },
|
||||
];
|
||||
|
||||
export const STORE_LOG_CATEGORY_LABELS: Record<StoreLogCategory | '', string> = {
|
||||
'': '全部',
|
||||
login: '登录',
|
||||
wechat_auth: '授权',
|
||||
page_view: '页面浏览',
|
||||
redeem: '核销',
|
||||
payout: '提现/打款',
|
||||
store_ops: '门店操作',
|
||||
package: '套餐',
|
||||
};
|
||||
|
||||
export function resolveStoreLogCategory(eventName: string): StoreLogCategory | null {
|
||||
for (const [category, events] of Object.entries(STORE_LOG_EVENT_CATEGORIES) as Array<
|
||||
[StoreLogCategory, readonly string[]]
|
||||
>) {
|
||||
if (events.includes(eventName)) return category;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function eventNamesForStoreLogCategory(category: string): string[] | undefined {
|
||||
if (!category) return undefined;
|
||||
return [...(STORE_LOG_EVENT_CATEGORIES[category as StoreLogCategory] ?? [])];
|
||||
}
|
||||
|
||||
export interface StoreLogRowDto {
|
||||
id: string;
|
||||
source: 'analytics' | 'redeem_record' | 'store_payout';
|
||||
storeId: string;
|
||||
storeAccountId: string | null;
|
||||
storeName: string | null;
|
||||
accountName: string | null;
|
||||
accountPhone: string | null;
|
||||
category: StoreLogCategory | null;
|
||||
eventName: string;
|
||||
clientApp: string | null;
|
||||
refType: string | null;
|
||||
refId: string | null;
|
||||
extraJson: Record<string, unknown> | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export type StoreAnalyticsExtra = {
|
||||
sessionId?: string;
|
||||
storeId?: string;
|
||||
storeAccountId?: string;
|
||||
redeemChannel?: 'scan' | 'phone' | 'pending';
|
||||
amount?: number;
|
||||
failReason?: string;
|
||||
durationMs?: number;
|
||||
pagePath?: string;
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
/** 门店套餐(v3.4.10) */
|
||||
export interface StorePackageItemDto {
|
||||
name: string;
|
||||
price: string;
|
||||
dishes: string;
|
||||
usableTime?: string | null;
|
||||
otherNotes?: string | null;
|
||||
sortOrder: number;
|
||||
}
|
||||
|
||||
export interface StorePackageViewDto extends StorePackageItemDto {
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export type StorePackageChangeStatus = 'PENDING' | 'APPROVED' | 'REJECTED';
|
||||
|
||||
export type StorePackageSubmitterType = 'PARTNER' | 'SHOP';
|
||||
|
||||
export const STORE_PACKAGE_CHANGE_STATUS_LABELS: Record<StorePackageChangeStatus, string> = {
|
||||
PENDING: '审核中',
|
||||
APPROVED: '已通过',
|
||||
REJECTED: '已驳回',
|
||||
};
|
||||
|
||||
export const STORE_PACKAGE_MAX_COUNT = 10;
|
||||
|
||||
export interface StorePackagesResponse {
|
||||
live: StorePackageViewDto[];
|
||||
pendingRequest?: {
|
||||
id: string;
|
||||
status: StorePackageChangeStatus;
|
||||
packages: StorePackageItemDto[];
|
||||
rejectReason?: string | null;
|
||||
createdAt: string;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface StorePackageChangeRequestDto {
|
||||
id: string;
|
||||
storeId: string;
|
||||
storeName?: string;
|
||||
status: StorePackageChangeStatus;
|
||||
packages: StorePackageItemDto[];
|
||||
submitterType: StorePackageSubmitterType;
|
||||
submitterId: string;
|
||||
rejectReason?: string | null;
|
||||
reviewedAt?: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
/** 套餐变更审核详情(含线上现行套餐,供对比) */
|
||||
export interface StorePackageAuditDetailDto extends StorePackageChangeRequestDto {
|
||||
livePackages: StorePackageViewDto[];
|
||||
}
|
||||
|
||||
export interface StorePackageAuditAction {
|
||||
action: 'APPROVE' | 'REJECT';
|
||||
rejectReason?: string;
|
||||
}
|
||||
|
||||
export interface CreatePackageDisputeRequest {
|
||||
storeId: string;
|
||||
remark?: string;
|
||||
redeemRecordId?: string;
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/** HQ 技术支持工单类型 */
|
||||
export type SupportTicketTypeDto = 'BUG' | 'SUGGESTION' | 'OTHER';
|
||||
|
||||
export const SUPPORT_TICKET_TYPES = ['BUG', 'SUGGESTION', 'OTHER'] as const;
|
||||
|
||||
export const SUPPORT_TICKET_TYPE_LABELS: Record<SupportTicketTypeDto, string> = {
|
||||
BUG: 'BUG',
|
||||
SUGGESTION: '建议',
|
||||
OTHER: '其他',
|
||||
};
|
||||
|
||||
/** 技术支持工单状态机 */
|
||||
export type SupportTicketStatusDto =
|
||||
| 'PENDING_REVIEW'
|
||||
| 'REJECTED'
|
||||
| 'DEVELOPING'
|
||||
| 'TESTING'
|
||||
| 'PASSED';
|
||||
|
||||
export const SUPPORT_TICKET_STATUSES = [
|
||||
'PENDING_REVIEW',
|
||||
'REJECTED',
|
||||
'DEVELOPING',
|
||||
'TESTING',
|
||||
'PASSED',
|
||||
] as const;
|
||||
|
||||
export const SUPPORT_TICKET_STATUS_LABELS: Record<SupportTicketStatusDto, string> = {
|
||||
PENDING_REVIEW: '待评审',
|
||||
REJECTED: '已驳回',
|
||||
DEVELOPING: '开发',
|
||||
TESTING: '测试',
|
||||
PASSED: '通过',
|
||||
};
|
||||
|
||||
export interface SupportTicketDto {
|
||||
id: string;
|
||||
ticketNo: string;
|
||||
ticketType: SupportTicketTypeDto;
|
||||
status: SupportTicketStatusDto;
|
||||
title: string;
|
||||
content?: string | null;
|
||||
rejectReason?: string | null;
|
||||
creatorId: string;
|
||||
creatorName: string;
|
||||
reviewerId?: string | null;
|
||||
reviewerName?: string | null;
|
||||
reviewedAt?: string | null;
|
||||
remark?: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
completedAt?: string | null;
|
||||
}
|
||||
|
||||
export interface CreateSupportTicketRequest {
|
||||
ticketType: SupportTicketTypeDto;
|
||||
title: string;
|
||||
content?: string;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
export interface RejectSupportTicketRequest {
|
||||
rejectReason: string;
|
||||
}
|
||||
|
||||
export interface CreateDevPlanTaskFromTicketInput {
|
||||
content: string;
|
||||
type: 'BUG' | 'REQUIREMENT' | 'OPTIMIZATION';
|
||||
}
|
||||
|
||||
export interface ReviewSupportTicketRequest {
|
||||
decision: 'APPROVE' | 'REJECT';
|
||||
rejectReason?: string;
|
||||
note?: string;
|
||||
tasks?: CreateDevPlanTaskFromTicketInput[];
|
||||
}
|
||||
|
||||
export interface SupportTicketLinkedTaskDto {
|
||||
id: string;
|
||||
taskNo: string;
|
||||
content: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface BatchReviewPreviewItem {
|
||||
ticketId: string;
|
||||
ticketNo: string;
|
||||
title: string;
|
||||
decision: 'APPROVE' | 'REJECT';
|
||||
rejectReason?: string;
|
||||
note?: string;
|
||||
reportMarkdown: string;
|
||||
suggestedTasks: CreateDevPlanTaskFromTicketInput[];
|
||||
}
|
||||
|
||||
export interface BatchReviewPreviewResponse {
|
||||
items: BatchReviewPreviewItem[];
|
||||
}
|
||||
|
||||
export interface BatchReviewConfirmItem {
|
||||
ticketId: string;
|
||||
decision: 'APPROVE' | 'REJECT';
|
||||
rejectReason?: string;
|
||||
note?: string;
|
||||
tasks?: CreateDevPlanTaskFromTicketInput[];
|
||||
}
|
||||
|
||||
export interface BatchReviewConfirmRequest {
|
||||
items: BatchReviewConfirmItem[];
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
export type SystemConfigFieldType =
|
||||
| 'string'
|
||||
| 'boolean'
|
||||
| 'number'
|
||||
| 'password'
|
||||
| 'textarea'
|
||||
| 'image'
|
||||
| 'imageList';
|
||||
|
||||
export interface SystemConfigFieldMeta {
|
||||
key: string;
|
||||
label: string;
|
||||
group: string;
|
||||
type: SystemConfigFieldType;
|
||||
secret?: boolean;
|
||||
requiresRestart: boolean;
|
||||
description?: string;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export interface SystemConfigGroupMeta {
|
||||
key: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface SystemConfigFormField extends SystemConfigFieldMeta {
|
||||
value: string;
|
||||
/** 密钥类字段:是否已配置(不返回明文) */
|
||||
configured?: boolean;
|
||||
}
|
||||
|
||||
export interface SystemConfigFormResponse {
|
||||
groups: SystemConfigGroupMeta[];
|
||||
fields: SystemConfigFieldMeta[];
|
||||
values: Record<string, string>;
|
||||
configuredSecrets: string[];
|
||||
envFilePath: string;
|
||||
updatedAt: string | null;
|
||||
/** Mock 短信最近生成的验证码(仅 HQ 系统设置展示) */
|
||||
mockSmsCodes: MockSmsCodeItem[];
|
||||
}
|
||||
|
||||
export interface MockSmsCodeItem {
|
||||
id: string;
|
||||
phone: string;
|
||||
scene: string;
|
||||
code: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface SystemConfigUpdateRequest {
|
||||
values: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface SystemConfigSyncResult {
|
||||
envFilePath: string;
|
||||
writtenKeys: number;
|
||||
requiresRestartKeys: string[];
|
||||
message: string;
|
||||
}
|
||||
|
||||
/** 解析小程序首页轮播 JSON(最多 8 张) */
|
||||
export function parseMiniHomeBanners(raw?: string | null): string[] {
|
||||
if (!raw?.trim()) return [];
|
||||
const text = raw.trim();
|
||||
try {
|
||||
const parsed = JSON.parse(text) as unknown;
|
||||
if (!Array.isArray(parsed)) return [];
|
||||
return parsed
|
||||
.filter((u): u is string => typeof u === 'string' && !!u.trim())
|
||||
.map((u) => u.trim())
|
||||
.slice(0, 8);
|
||||
} catch {
|
||||
if (/^https?:\/\//i.test(text)) return [text];
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function serializeMiniHomeBanners(urls: string[]): string {
|
||||
const cleaned = urls.filter((u) => !!u?.trim()).map((u) => u.trim()).slice(0, 8);
|
||||
return JSON.stringify(cleaned);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/** 售后工单类型(PRD 四类型 + 系统 ALERT + 套餐异议) */
|
||||
export type TicketTypeDto =
|
||||
| 'REFUND'
|
||||
| 'RESHIPMENT'
|
||||
| 'ALERT'
|
||||
| 'DAMAGE_RETURN'
|
||||
| 'RETURN_REFUND'
|
||||
| 'PACKAGE_DISPUTE';
|
||||
|
||||
/** 用户可发起的售后类型 */
|
||||
export const AFTER_SALE_TICKET_TYPES = [
|
||||
'REFUND',
|
||||
'RESHIPMENT',
|
||||
'DAMAGE_RETURN',
|
||||
'RETURN_REFUND',
|
||||
'PACKAGE_DISPUTE',
|
||||
] as const;
|
||||
|
||||
export type AfterSaleTicketType = (typeof AFTER_SALE_TICKET_TYPES)[number];
|
||||
|
||||
export const TICKET_TYPE_LABELS: Record<TicketTypeDto, string> = {
|
||||
REFUND: '仅退款',
|
||||
RESHIPMENT: '破损补发',
|
||||
ALERT: '异常',
|
||||
DAMAGE_RETURN: '破损退货',
|
||||
RETURN_REFUND: '退货退款',
|
||||
PACKAGE_DISPUTE: '套餐异议',
|
||||
};
|
||||
|
||||
export interface TicketDto {
|
||||
id: string;
|
||||
ticketNo: string;
|
||||
ticketType: TicketTypeDto;
|
||||
status: string;
|
||||
refType: string;
|
||||
refId: string;
|
||||
remark?: string | null;
|
||||
extraJson?: Record<string, unknown> | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface TicketActionRequest {
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
export interface CreateAfterSaleTicketRequest {
|
||||
ticketType: AfterSaleTicketType;
|
||||
remark?: string;
|
||||
evidenceUrls?: string[];
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
import type { WechatJsapiPrepayParams } from './wechat';
|
||||
|
||||
export interface OrderDto {
|
||||
id: string;
|
||||
orderNo: string;
|
||||
status: string;
|
||||
payStatus: string;
|
||||
payAmount: number;
|
||||
benefitAmount: number;
|
||||
quantity: number;
|
||||
productName: string;
|
||||
createdAt: string;
|
||||
orderType?: string;
|
||||
isProxyOrder?: boolean;
|
||||
proxyPartnerName?: string | null;
|
||||
proxyPartnerPhone?: string | null;
|
||||
}
|
||||
|
||||
export interface OrderPreviewRequest {
|
||||
productId: string;
|
||||
quantity: number;
|
||||
addressId?: string;
|
||||
}
|
||||
|
||||
export interface OrderPreviewResult {
|
||||
productAmount: number;
|
||||
payAmount: number;
|
||||
benefitAmount: number;
|
||||
deliveryType: 'LOCAL' | 'CROSS_CITY';
|
||||
}
|
||||
|
||||
export type ProxyPayMethod = 'NATIVE' | 'JSAPI';
|
||||
|
||||
export interface PayOrderResult {
|
||||
mode?: 'jsapi' | 'mock' | 'native';
|
||||
orderId?: string;
|
||||
codeUrl?: string;
|
||||
prepay?: WechatJsapiPrepayParams;
|
||||
payExpireAt?: string | null;
|
||||
}
|
||||
|
||||
export type ProxyOrderPayRequest = {
|
||||
payMethod: ProxyPayMethod;
|
||||
};
|
||||
|
||||
export type ProxyOrderPayResponse = {
|
||||
mode: 'jsapi' | 'mock' | 'native';
|
||||
orderId: string;
|
||||
orderNo?: string;
|
||||
codeUrl?: string;
|
||||
prepay?: WechatJsapiPrepayParams;
|
||||
payExpireAt?: string | null;
|
||||
};
|
||||
|
||||
export type ProxyOrderCreateResponse = {
|
||||
id: string;
|
||||
orderNo: string;
|
||||
status: string;
|
||||
payStatus: string;
|
||||
payAmount: number;
|
||||
benefitAmount: number;
|
||||
deliveryType: string;
|
||||
payExpireAt: string | null;
|
||||
proxyPartnerName?: string | null;
|
||||
};
|
||||
|
||||
export interface DeliveryDto {
|
||||
provider?: string;
|
||||
shippingAt?: string;
|
||||
deliveredAt?: string;
|
||||
}
|
||||
|
||||
/** 大单拦截原因:≥10 箱不自动推小飞侠 */
|
||||
export const FULFILLMENT_HOLD_REASON_LABELS: Record<string, string> = {
|
||||
LARGE_ORDER_GE_10_BOXES: '大单≥10箱,待总部确认推单/自配送',
|
||||
};
|
||||
|
||||
export type PartnerProxyDeliveryMode = 'ADDRESS' | 'ON_SITE_PICKUP';
|
||||
|
||||
export type PartnerProxyOrderProductOption = {
|
||||
id: string;
|
||||
name: string;
|
||||
spec: string;
|
||||
price: number;
|
||||
benefitAmount: number | null;
|
||||
coverUrl?: string | null;
|
||||
allowOnSitePickup?: boolean;
|
||||
allowOnlinePurchase?: boolean;
|
||||
allowCrossCityDelivery?: boolean;
|
||||
};
|
||||
|
||||
export type PartnerProxyOrderPromoOption = {
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type PartnerProxyOrderStoreOption = {
|
||||
id: string;
|
||||
name: string;
|
||||
address?: string | null;
|
||||
phone?: string | null;
|
||||
province?: string | null;
|
||||
cityName?: string | null;
|
||||
district?: string | null;
|
||||
};
|
||||
|
||||
export type PartnerProxyOrderOptions = {
|
||||
products: PartnerProxyOrderProductOption[];
|
||||
promoCodes: PartnerProxyOrderPromoOption[];
|
||||
stores: PartnerProxyOrderStoreOption[];
|
||||
};
|
||||
|
||||
export type PartnerProxyOrderPreviewRequest = {
|
||||
productId: string;
|
||||
quantity: number;
|
||||
deliveryMode?: PartnerProxyDeliveryMode;
|
||||
storeId?: string;
|
||||
receiverCity?: string;
|
||||
receiverDistrict?: string;
|
||||
};
|
||||
|
||||
export type PartnerProxyOrderPreviewResult = {
|
||||
productAmount: number;
|
||||
payAmount: number;
|
||||
benefitAmount: number;
|
||||
deliveryType: 'LOCAL' | 'CROSS_CITY' | 'ON_SITE_PICKUP';
|
||||
unitPrice: number;
|
||||
};
|
||||
|
||||
export type PartnerProxyOrderCreateRequest = {
|
||||
phone: string;
|
||||
deliveryMode: PartnerProxyDeliveryMode;
|
||||
/** 地址配送时必须为 true */
|
||||
autoReceive?: boolean;
|
||||
storeId?: string;
|
||||
receiverName?: string;
|
||||
province?: string;
|
||||
city?: string;
|
||||
district?: string;
|
||||
addressDetail?: string;
|
||||
productId: string;
|
||||
quantity: number;
|
||||
promoCodeId?: string;
|
||||
};
|
||||
|
||||
/** 合伙人代下单列表项(与 OrderDto 兼容,附带收货信息) */
|
||||
export type PartnerProxyOrderListItem = OrderDto & {
|
||||
receiverName?: string | null;
|
||||
receiverPhone?: string | null;
|
||||
receiverAddress?: string | null;
|
||||
deliveryType?: string | null;
|
||||
productSpec?: string | null;
|
||||
imageResource?: { url?: string } | null;
|
||||
/** 已锁定的支付方式(首次拉起支付后不可切换,须取消后重新下单) */
|
||||
proxyPayMethod?: ProxyPayMethod | null;
|
||||
};
|
||||
|
||||
export type PartnerProxyOrderListResponse = {
|
||||
list: PartnerProxyOrderListItem[];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
};
|
||||
|
||||
/** 总部代下单(在线支付后发权益) */
|
||||
export type HqProxyOrderCreateRequest = {
|
||||
phone: string;
|
||||
deliveryMode: PartnerProxyDeliveryMode;
|
||||
autoReceive?: boolean;
|
||||
receiverName?: string;
|
||||
province?: string;
|
||||
city?: string;
|
||||
district?: string;
|
||||
addressDetail?: string;
|
||||
productId: string;
|
||||
quantity: number;
|
||||
promoCodeId?: string;
|
||||
};
|
||||
@@ -0,0 +1,103 @@
|
||||
export type UserLogCategory =
|
||||
| 'login'
|
||||
| 'browse_product'
|
||||
| 'order'
|
||||
| 'pay'
|
||||
| 'wechat_auth'
|
||||
| 'browse_store'
|
||||
| 'redeem'
|
||||
| 'profile'
|
||||
| 'promo'
|
||||
| 'customer_service'
|
||||
| 'after_sale'
|
||||
| 'error';
|
||||
|
||||
export const USER_LOG_EVENT_CATEGORIES: Record<UserLogCategory, readonly string[]> = {
|
||||
login: ['login_success', 'sms_login', 'sms_send', 'sms_verify_fail', 'session_start'],
|
||||
browse_product: ['home_view', 'product_list_view', 'product_click', 'product_detail_view'],
|
||||
order: ['order_confirm_view', 'order_submit', 'order_list_view', 'order_detail_view', 'order_cancel'],
|
||||
pay: ['pay_page_view', 'pay_success', 'pay_fail'],
|
||||
wechat_auth: [
|
||||
'wechat_login',
|
||||
'wechat_phone',
|
||||
'wechat_phone_login',
|
||||
'wechat_location',
|
||||
'wechat_album',
|
||||
'wechat_bind',
|
||||
],
|
||||
browse_store: ['store_list_view', 'store_detail_view'],
|
||||
redeem: [
|
||||
'benefit_page_view',
|
||||
'benefit_detail_view',
|
||||
'benefit_redeem_start',
|
||||
'benefit_redeem_success',
|
||||
'redeem_code_view',
|
||||
'redeem_success_view',
|
||||
],
|
||||
profile: ['profile_update', 'bind_phone', 'address_list_view', 'address_edit'],
|
||||
promo: ['promo_touch', 'promo_share'],
|
||||
customer_service: ['cs_contact'],
|
||||
after_sale: ['after_sale_list_view', 'after_sale_apply'],
|
||||
error: ['client_error'],
|
||||
};
|
||||
|
||||
export const USER_LOG_CATEGORY_OPTIONS: Array<{ value: UserLogCategory | ''; label: string }> = [
|
||||
{ value: '', label: '全部' },
|
||||
{ value: 'login', label: '登录' },
|
||||
{ value: 'browse_product', label: '浏览商品' },
|
||||
{ value: 'order', label: '下单' },
|
||||
{ value: 'pay', label: '支付' },
|
||||
{ value: 'wechat_auth', label: '微信授权' },
|
||||
{ value: 'browse_store', label: '浏览门店' },
|
||||
{ value: 'redeem', label: '核销' },
|
||||
{ value: 'profile', label: '信息修改' },
|
||||
{ value: 'promo', label: '推广' },
|
||||
{ value: 'customer_service', label: '客服' },
|
||||
{ value: 'after_sale', label: '售后' },
|
||||
{ value: 'error', label: '前端异常' },
|
||||
];
|
||||
|
||||
export function resolveUserLogCategory(eventName: string): UserLogCategory | null {
|
||||
for (const [category, events] of Object.entries(USER_LOG_EVENT_CATEGORIES) as Array<
|
||||
[UserLogCategory, readonly string[]]
|
||||
>) {
|
||||
if (events.includes(eventName)) return category;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function eventNamesForUserLogCategory(category: string): string[] | undefined {
|
||||
if (!category) return undefined;
|
||||
return [...(USER_LOG_EVENT_CATEGORIES[category as UserLogCategory] ?? [])];
|
||||
}
|
||||
|
||||
export interface UserLogRowDto {
|
||||
id: string;
|
||||
userId: string | null;
|
||||
userNo: string | null;
|
||||
phone: string | null;
|
||||
nickname: string | null;
|
||||
category: UserLogCategory | null;
|
||||
eventName: string;
|
||||
clientApp: string | null;
|
||||
refType: string | null;
|
||||
refId: string | null;
|
||||
extraJson: Record<string, unknown> | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
/** C 端埋点 extraJson 推荐字段(画像分析) */
|
||||
export type UserAnalyticsExtra = {
|
||||
sessionId?: string;
|
||||
cityCode?: string;
|
||||
productId?: string;
|
||||
skuId?: string;
|
||||
storeId?: string;
|
||||
orderId?: string;
|
||||
amount?: number;
|
||||
quantity?: number;
|
||||
sourceType?: string;
|
||||
sourceRefId?: string;
|
||||
failReason?: string;
|
||||
pagePath?: string;
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
/** 微信 JSSDK 初始化参数(后端签名下发) */
|
||||
export interface WechatJssdkConfig {
|
||||
appId: string;
|
||||
timestamp: number;
|
||||
nonceStr: string;
|
||||
signature: string;
|
||||
jsApiList: string[];
|
||||
}
|
||||
|
||||
/** JSAPI 调起支付参数 */
|
||||
export interface WechatJsapiPrepayParams {
|
||||
appId: string;
|
||||
timeStamp: string;
|
||||
nonceStr: string;
|
||||
package: string;
|
||||
signType: 'RSA' | 'MD5';
|
||||
paySign: string;
|
||||
}
|
||||
|
||||
export type WechatPayOrderResult =
|
||||
| { mode: 'mock'; externalNo: string; order?: Record<string, unknown> }
|
||||
| { mode: 'jsapi'; prepay: WechatJsapiPrepayParams; orderId: string };
|
||||
|
||||
/** 微信退款回调解密结果 */
|
||||
export type WechatRefundNotifyResult = {
|
||||
outRefundNo: string;
|
||||
refundId: string;
|
||||
status: 'SUCCESS' | 'PROCESSING' | 'ABNORMAL' | 'CLOSED';
|
||||
amountFen: number;
|
||||
outTradeNo?: string;
|
||||
transactionId?: string;
|
||||
};
|
||||
|
||||
/** 业务错误码:微信支付前需完成微信授权 */
|
||||
export const WECHAT_AUTH_REQUIRED = 'WECHAT_AUTH_REQUIRED';
|
||||
|
||||
export type ClientRuntimeConfig = {
|
||||
mockPay: boolean;
|
||||
wechatPayEnabled: boolean;
|
||||
mockSms: boolean;
|
||||
mockWechat?: boolean;
|
||||
/** false 时三端跳过微信 SDK OAuth 授权(由 MOCK_WECHAT 或真实凭证推导) */
|
||||
wxAuthorize?: boolean;
|
||||
/** 腾讯位置服务 Key(地图选点组件,可按域名限制) */
|
||||
tencentLbsKey?: string;
|
||||
/** 小程序首页轮播 / 底部图 */
|
||||
miniHome?: {
|
||||
banners: string[];
|
||||
footerUrl: string | null;
|
||||
};
|
||||
};
|
||||
|
||||
/** 是否展示微信授权入口 */
|
||||
export function isWxAuthorizeEnabled(config?: Pick<ClientRuntimeConfig, 'wxAuthorize'> | null): boolean {
|
||||
return config?.wxAuthorize !== false;
|
||||
}
|
||||
|
||||
export interface WechatLoginResult {
|
||||
accessToken?: string;
|
||||
refreshToken?: string;
|
||||
deviceKey?: string;
|
||||
actorType?: string;
|
||||
actorId?: string;
|
||||
phoneVerified?: boolean;
|
||||
/** 本次登录/绑定触发了账号合并,客户端应强制刷新页面 */
|
||||
accountMerged?: boolean;
|
||||
needBindPhone?: boolean;
|
||||
wxSessionKey?: string;
|
||||
user?: Record<string, unknown>;
|
||||
store?: Record<string, unknown>;
|
||||
partner?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type WechatLoginPlatform = 'h5' | 'mini';
|
||||
|
||||
export interface WechatGpsLocation {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
speed?: number;
|
||||
accuracy?: number;
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
/** 企业微信智能机器人 · 模块化能力权限(v3.4.11 重构) */
|
||||
export const WECOM_BOT_PERMISSIONS = [
|
||||
'order.read',
|
||||
'delivery.read',
|
||||
'store.read',
|
||||
'redeem.read',
|
||||
'ticket.read',
|
||||
'ticket.create',
|
||||
'user.read',
|
||||
'user.read_sms',
|
||||
'finance.store_bill.read',
|
||||
'finance.partner_bill.read',
|
||||
'finance.winery_bill.read',
|
||||
'finance.logistics_bill.read',
|
||||
'finance.payout.read',
|
||||
'finance.withdrawal.read',
|
||||
'support_ticket.read',
|
||||
'support_ticket.create',
|
||||
'support_ticket.review',
|
||||
'support_ticket.approve',
|
||||
'support_ticket.reject',
|
||||
'dev_plan.task.read',
|
||||
'dev_plan.task.create',
|
||||
'dev_plan.task.update_status',
|
||||
'dev_plan.version.read',
|
||||
'dev_plan.version.create',
|
||||
'dev_plan.version.update_status',
|
||||
'dev_plan.version.link_tasks',
|
||||
'server_log.read',
|
||||
'handbook.read',
|
||||
] as const;
|
||||
|
||||
export type WecomBotPermission = (typeof WECOM_BOT_PERMISSIONS)[number];
|
||||
|
||||
export const WECOM_BOT_PERMISSION_LABELS: Record<WecomBotPermission, string> = {
|
||||
'order.read': '查询订单',
|
||||
'delivery.read': '查询配送/物流',
|
||||
'store.read': '查询门店',
|
||||
'redeem.read': '查询核销记录',
|
||||
'ticket.read': '查询售后工单',
|
||||
'ticket.create': '创建售后工单',
|
||||
'user.read': '查询用户(脱敏)',
|
||||
'user.read_sms': '查用户(短信验证)',
|
||||
'finance.store_bill.read': '查询门店账单',
|
||||
'finance.partner_bill.read': '查询合伙人账单',
|
||||
'finance.winery_bill.read': '查询酒厂账单',
|
||||
'finance.logistics_bill.read': '查询物流账单',
|
||||
'finance.payout.read': '查询门店打款',
|
||||
'finance.withdrawal.read': '查询门店提现',
|
||||
'support_ticket.read': '查询技术支持工单',
|
||||
'support_ticket.create': '创建技术支持工单',
|
||||
'support_ticket.review': '审批技术支持工单(通过/驳回)',
|
||||
'support_ticket.approve': '通过技术支持工单',
|
||||
'support_ticket.reject': '驳回技术支持工单',
|
||||
'dev_plan.task.read': '查询开发任务',
|
||||
'dev_plan.task.create': '新建开发任务',
|
||||
'dev_plan.task.update_status': '修改任务状态',
|
||||
'dev_plan.version.read': '查询开发版本',
|
||||
'dev_plan.version.create': '新建开发版本',
|
||||
'dev_plan.version.update_status': '修改版本状态',
|
||||
'dev_plan.version.link_tasks': '关联任务到版本',
|
||||
'server_log.read': '查看服务器日志',
|
||||
'handbook.read': '查询使用手册',
|
||||
};
|
||||
|
||||
/** Admin UI 权限分组 */
|
||||
export const WECOM_BOT_PERMISSION_GROUPS: Array<{
|
||||
key: string;
|
||||
label: string;
|
||||
permissions: WecomBotPermission[];
|
||||
}> = [
|
||||
{
|
||||
key: 'order',
|
||||
label: '订单与配送',
|
||||
permissions: ['order.read', 'delivery.read'],
|
||||
},
|
||||
{
|
||||
key: 'store_redeem',
|
||||
label: '门店与核销',
|
||||
permissions: ['store.read', 'redeem.read'],
|
||||
},
|
||||
{
|
||||
key: 'ticket',
|
||||
label: '售后工单',
|
||||
permissions: ['ticket.read', 'ticket.create'],
|
||||
},
|
||||
{
|
||||
key: 'user',
|
||||
label: '用户',
|
||||
permissions: ['user.read', 'user.read_sms'],
|
||||
},
|
||||
{
|
||||
key: 'finance',
|
||||
label: '财务',
|
||||
permissions: [
|
||||
'finance.store_bill.read',
|
||||
'finance.partner_bill.read',
|
||||
'finance.winery_bill.read',
|
||||
'finance.logistics_bill.read',
|
||||
'finance.payout.read',
|
||||
'finance.withdrawal.read',
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'support',
|
||||
label: '技术支持',
|
||||
permissions: ['support_ticket.read', 'support_ticket.create', 'support_ticket.review', 'support_ticket.approve', 'support_ticket.reject'],
|
||||
},
|
||||
{
|
||||
key: 'dev_plan',
|
||||
label: '开发计划',
|
||||
permissions: ['dev_plan.task.read', 'dev_plan.task.create', 'dev_plan.task.update_status', 'dev_plan.version.read', 'dev_plan.version.create', 'dev_plan.version.update_status', 'dev_plan.version.link_tasks'],
|
||||
},
|
||||
{
|
||||
key: 'ops',
|
||||
label: '运维与手册',
|
||||
permissions: ['server_log.read', 'handbook.read'],
|
||||
},
|
||||
];
|
||||
|
||||
/** 预置机器人角色 */
|
||||
export const WECOM_BOT_ROLES = [
|
||||
'CUSTOMER_SERVICE',
|
||||
'FINANCE',
|
||||
'OPERATIONS',
|
||||
'TECH_SUPPORT',
|
||||
'CUSTOM',
|
||||
] as const;
|
||||
|
||||
export type WecomBotRole = (typeof WECOM_BOT_ROLES)[number];
|
||||
|
||||
/** 历史角色名 → 现行角色 */
|
||||
export function normalizeWecomBotRole(raw: string): WecomBotRole {
|
||||
if (raw === 'TEAM_ASSISTANT') return 'OPERATIONS';
|
||||
if ((WECOM_BOT_ROLES as readonly string[]).includes(raw)) return raw as WecomBotRole;
|
||||
return 'CUSTOM';
|
||||
}
|
||||
|
||||
export const WECOM_BOT_ROLE_LABELS: Record<WecomBotRole, string> = {
|
||||
CUSTOMER_SERVICE: '客服助手',
|
||||
FINANCE: '财务助手',
|
||||
OPERATIONS: '运营助手',
|
||||
TECH_SUPPORT: '技术支持',
|
||||
CUSTOM: '自定义',
|
||||
};
|
||||
|
||||
export const WECOM_BOT_ROLE_DEFAULT_PERMISSIONS: Record<WecomBotRole, WecomBotPermission[]> = {
|
||||
CUSTOMER_SERVICE: [
|
||||
'order.read',
|
||||
'delivery.read',
|
||||
'store.read',
|
||||
'redeem.read',
|
||||
'ticket.read',
|
||||
'ticket.create',
|
||||
'user.read_sms',
|
||||
],
|
||||
FINANCE: [
|
||||
'finance.store_bill.read',
|
||||
'finance.partner_bill.read',
|
||||
'finance.winery_bill.read',
|
||||
'finance.logistics_bill.read',
|
||||
'finance.payout.read',
|
||||
'finance.withdrawal.read',
|
||||
'order.read',
|
||||
],
|
||||
OPERATIONS: [
|
||||
'order.read',
|
||||
'store.read',
|
||||
'user.read',
|
||||
'redeem.read',
|
||||
'delivery.read',
|
||||
'handbook.read',
|
||||
],
|
||||
TECH_SUPPORT: [
|
||||
'support_ticket.read',
|
||||
'support_ticket.create',
|
||||
'support_ticket.review',
|
||||
'dev_plan.task.read',
|
||||
'dev_plan.task.create',
|
||||
'dev_plan.task.update_status',
|
||||
'dev_plan.version.read',
|
||||
'dev_plan.version.create',
|
||||
'dev_plan.version.update_status',
|
||||
'dev_plan.version.link_tasks',
|
||||
'server_log.read',
|
||||
],
|
||||
CUSTOM: [],
|
||||
};
|
||||
|
||||
/** 旧版权限 → 新版权限(读取 DB 时迁移) */
|
||||
const LEGACY_PERMISSION_MAP: Record<string, WecomBotPermission[]> = {
|
||||
'ticket.create': ['ticket.create'],
|
||||
'user.view_sms': ['user.read_sms'],
|
||||
'delivery.view': ['delivery.read'],
|
||||
'support_ticket.create': ['support_ticket.create'],
|
||||
'support_ticket.progress': ['support_ticket.read', 'dev_plan.task.read'],
|
||||
'handbook.query': ['handbook.read'],
|
||||
'server_log.view': ['server_log.read'],
|
||||
'api.query': ['order.read'],
|
||||
'api.read.all': [
|
||||
'order.read',
|
||||
'delivery.read',
|
||||
'store.read',
|
||||
'redeem.read',
|
||||
'support_ticket.read',
|
||||
'dev_plan.task.read',
|
||||
'dev_plan.version.read',
|
||||
'user.read',
|
||||
],
|
||||
'db.read': ['support_ticket.read', 'order.read'],
|
||||
};
|
||||
|
||||
export function migrateWecomBotPermissions(raw: string[]): WecomBotPermission[] {
|
||||
const set = new Set<WecomBotPermission>();
|
||||
const valid = new Set<string>(WECOM_BOT_PERMISSIONS);
|
||||
for (const p of raw) {
|
||||
if (valid.has(p)) {
|
||||
set.add(p as WecomBotPermission);
|
||||
continue;
|
||||
}
|
||||
const mapped = LEGACY_PERMISSION_MAP[p];
|
||||
if (mapped) mapped.forEach((m) => set.add(m));
|
||||
}
|
||||
return [...set];
|
||||
}
|
||||
|
||||
export function parseWecomBotPermissions(
|
||||
raw?: string | string[] | null,
|
||||
): WecomBotPermission[] {
|
||||
const valid = new Set<string>(WECOM_BOT_PERMISSIONS);
|
||||
let list: string[];
|
||||
|
||||
if (Array.isArray(raw)) {
|
||||
list = raw.map(String);
|
||||
} else {
|
||||
const text = String(raw ?? '').trim();
|
||||
if (!text) {
|
||||
list = [];
|
||||
} else if (text.startsWith('[')) {
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(text);
|
||||
list = Array.isArray(parsed) ? parsed.map(String) : [];
|
||||
} catch {
|
||||
list = text
|
||||
.split(/[,,\s]+/)
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
} else {
|
||||
list = text
|
||||
.split(/[,,\s]+/)
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
}
|
||||
|
||||
const migrated = migrateWecomBotPermissions(list);
|
||||
if (migrated.length) return migrated;
|
||||
return [...new Set(list.filter((s): s is WecomBotPermission => valid.has(s)))];
|
||||
}
|
||||
|
||||
export function resolveWecomBotPermissions(
|
||||
role: WecomBotRole,
|
||||
override?: string | string[] | null,
|
||||
): WecomBotPermission[] {
|
||||
const parsed = parseWecomBotPermissions(override);
|
||||
if (parsed.length) return parsed;
|
||||
return [...WECOM_BOT_ROLE_DEFAULT_PERMISSIONS[role]];
|
||||
}
|
||||
|
||||
export function parseWecomUserIdList(raw?: string | string[] | null): string[] {
|
||||
if (Array.isArray(raw)) return [...new Set(raw.map(String).filter(Boolean))];
|
||||
const text = String(raw ?? '').trim();
|
||||
if (!text) return [];
|
||||
if (text.startsWith('[')) {
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(text);
|
||||
return Array.isArray(parsed) ? [...new Set(parsed.map(String).filter(Boolean))] : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
return [...new Set(text.split(/[,,\s]+/).map((s) => s.trim()).filter(Boolean))];
|
||||
}
|
||||
|
||||
export type WecomBotDto = {
|
||||
id: string;
|
||||
name: string;
|
||||
role: WecomBotRole;
|
||||
botId: string;
|
||||
secretConfigured: boolean;
|
||||
avatarUrl: string | null;
|
||||
welcome: string | null;
|
||||
permissions: WecomBotPermission[];
|
||||
/** 可执行 support_ticket.review 的企微 userid 白名单 */
|
||||
reviewSuperAdminWecomUserIds: string[];
|
||||
aiEnabled: boolean;
|
||||
llmConfigId: string | null;
|
||||
llmConfigName: string | null;
|
||||
knowledgeBaseId: string | null;
|
||||
knowledgeBaseName: string | null;
|
||||
enabled: boolean;
|
||||
sortOrder: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type CreateWecomBotRequest = {
|
||||
name: string;
|
||||
role: WecomBotRole;
|
||||
botId: string;
|
||||
secret: string;
|
||||
avatarUrl?: string | null;
|
||||
welcome?: string | null;
|
||||
permissions?: WecomBotPermission[];
|
||||
reviewSuperAdminWecomUserIds?: string[];
|
||||
aiEnabled?: boolean;
|
||||
llmConfigId?: string | null;
|
||||
knowledgeBaseId?: string | null;
|
||||
enabled?: boolean;
|
||||
sortOrder?: number;
|
||||
};
|
||||
|
||||
export type UpdateWecomBotRequest = {
|
||||
name?: string;
|
||||
role?: WecomBotRole;
|
||||
botId?: string;
|
||||
secret?: string;
|
||||
avatarUrl?: string | null;
|
||||
welcome?: string | null;
|
||||
permissions?: WecomBotPermission[];
|
||||
reviewSuperAdminWecomUserIds?: string[];
|
||||
aiEnabled?: boolean;
|
||||
llmConfigId?: string | null;
|
||||
knowledgeBaseId?: string | null;
|
||||
enabled?: boolean;
|
||||
sortOrder?: number;
|
||||
};
|
||||
|
||||
/** 长连接运行时状态(Admin GET /admin/wecom-bots/runtime) */
|
||||
export type WecomAibotRuntimeDto = {
|
||||
masterEnabled: boolean;
|
||||
bots: Array<{
|
||||
id: string;
|
||||
role: string;
|
||||
key: string;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
configured: boolean;
|
||||
connected: boolean;
|
||||
botIdMasked: string | null;
|
||||
avatarUrl: string | null;
|
||||
permissions: WecomBotPermission[];
|
||||
lastError: string | null;
|
||||
}>;
|
||||
};
|
||||
|
||||
/** Admin 表单 permissions 与 UI 分组/catalog 对齐校验 */
|
||||
export function assertWecomBotPermissionCatalogConsistent(): void {
|
||||
const fromGroups = WECOM_BOT_PERMISSION_GROUPS.flatMap((g) => g.permissions);
|
||||
const catalog = new Set<string>(WECOM_BOT_PERMISSIONS);
|
||||
const groupSet = new Set(fromGroups);
|
||||
for (const p of WECOM_BOT_PERMISSIONS) {
|
||||
if (!groupSet.has(p)) throw new Error(`permission missing in UI groups: ${p}`);
|
||||
if (!WECOM_BOT_PERMISSION_LABELS[p]) throw new Error(`permission missing label: ${p}`);
|
||||
}
|
||||
for (const p of fromGroups) {
|
||||
if (!catalog.has(p)) throw new Error(`unknown permission in UI groups: ${p}`);
|
||||
}
|
||||
if (fromGroups.length !== groupSet.size) {
|
||||
throw new Error('duplicate permission in UI groups');
|
||||
}
|
||||
}
|
||||
|
||||
export type WecomBotLogDto = {
|
||||
id: string;
|
||||
botId: string | null;
|
||||
botName: string | null;
|
||||
botKey: string | null;
|
||||
wecomUserId: string;
|
||||
action: string;
|
||||
permission: string | null;
|
||||
inputSummary: string | null;
|
||||
success: boolean;
|
||||
errorMessage: string | null;
|
||||
latencyMs: number | null;
|
||||
createdAt: string;
|
||||
};
|
||||
@@ -0,0 +1,138 @@
|
||||
/** 企微群机器人 Webhook · 推送条件(v3.4.11) */
|
||||
export const WECOM_PUSH_CONDITIONS = [
|
||||
'alert.ops',
|
||||
'support_ticket.created',
|
||||
'alert.pay',
|
||||
'alert.redeem',
|
||||
'alert.system',
|
||||
'alert.settlement',
|
||||
'dev_plan.task_dispatch',
|
||||
] as const;
|
||||
|
||||
export type WecomPushCondition = (typeof WECOM_PUSH_CONDITIONS)[number];
|
||||
|
||||
export const WECOM_PUSH_CONDITION_LABELS: Record<WecomPushCondition, string> = {
|
||||
'alert.ops': '运营告警(售后工单、客户端错误等)',
|
||||
'support_ticket.created': '新建技术支持工单',
|
||||
'alert.pay': '支付异常',
|
||||
'alert.redeem': '核销异常',
|
||||
'alert.system': '系统监控(5xx、回调、定时任务)',
|
||||
'alert.settlement': '结算任务告警',
|
||||
'dev_plan.task_dispatch': '开发任务评审派发',
|
||||
};
|
||||
|
||||
export const WECOM_PUSH_CONDITION_GROUPS: Array<{
|
||||
key: string;
|
||||
label: string;
|
||||
conditions: WecomPushCondition[];
|
||||
}> = [
|
||||
{
|
||||
key: 'ops',
|
||||
label: '工单与运营',
|
||||
conditions: ['alert.ops', 'support_ticket.created'],
|
||||
},
|
||||
{
|
||||
key: 'pay_redeem',
|
||||
label: '支付与核销',
|
||||
conditions: ['alert.pay', 'alert.redeem'],
|
||||
},
|
||||
{
|
||||
key: 'system',
|
||||
label: '系统与结算',
|
||||
conditions: ['alert.system', 'alert.settlement'],
|
||||
},
|
||||
{
|
||||
key: 'dev_plan',
|
||||
label: '开发计划',
|
||||
conditions: ['dev_plan.task_dispatch'],
|
||||
},
|
||||
];
|
||||
|
||||
/** 默认「运营告警」推送条件 */
|
||||
export const WECOM_PUSH_DEFAULT_ALERT_CONDITIONS: WecomPushCondition[] = [
|
||||
'alert.ops',
|
||||
'support_ticket.created',
|
||||
'alert.pay',
|
||||
'alert.redeem',
|
||||
'alert.system',
|
||||
'alert.settlement',
|
||||
];
|
||||
|
||||
/** 默认「开发任务派发」推送条件 */
|
||||
export const WECOM_PUSH_DEFAULT_DEV_DISPATCH_CONDITIONS: WecomPushCondition[] = [
|
||||
'dev_plan.task_dispatch',
|
||||
'support_ticket.created',
|
||||
];
|
||||
|
||||
export function parseWecomPushConditions(
|
||||
raw?: string | string[] | null,
|
||||
): WecomPushCondition[] {
|
||||
const valid = new Set<string>(WECOM_PUSH_CONDITIONS);
|
||||
let list: string[];
|
||||
|
||||
if (Array.isArray(raw)) {
|
||||
list = raw.map(String);
|
||||
} else {
|
||||
const text = String(raw ?? '').trim();
|
||||
if (!text) {
|
||||
list = [];
|
||||
} else if (text.startsWith('[')) {
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(text);
|
||||
list = Array.isArray(parsed) ? parsed.map(String) : [];
|
||||
} catch {
|
||||
list = text
|
||||
.split(/[,,\s]+/)
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
} else {
|
||||
list = text
|
||||
.split(/[,,\s]+/)
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
}
|
||||
|
||||
return [...new Set(list.filter((s): s is WecomPushCondition => valid.has(s)))];
|
||||
}
|
||||
|
||||
export type WecomMessagePushDto = {
|
||||
id: string;
|
||||
name: string;
|
||||
avatarUrl: string | null;
|
||||
webhookUrl: string;
|
||||
webhookUrlMasked: string;
|
||||
enabled: boolean;
|
||||
mentionWecomUserId: string | null;
|
||||
pushConditions: WecomPushCondition[];
|
||||
sortOrder: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type CreateWecomMessagePushRequest = {
|
||||
name: string;
|
||||
avatarUrl?: string | null;
|
||||
webhookUrl: string;
|
||||
enabled?: boolean;
|
||||
mentionWecomUserId?: string | null;
|
||||
pushConditions: WecomPushCondition[];
|
||||
sortOrder?: number;
|
||||
};
|
||||
|
||||
export type UpdateWecomMessagePushRequest = {
|
||||
name?: string;
|
||||
avatarUrl?: string | null;
|
||||
webhookUrl?: string;
|
||||
enabled?: boolean;
|
||||
mentionWecomUserId?: string | null;
|
||||
pushConditions?: WecomPushCondition[];
|
||||
sortOrder?: number;
|
||||
};
|
||||
|
||||
export function maskWecomWebhookUrl(url: string): string {
|
||||
const u = url.trim();
|
||||
if (u.length <= 24) return `${u.slice(0, 8)}…`;
|
||||
return `${u.slice(0, 20)}…${u.slice(-8)}`;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
Reference in New Issue
Block a user