代下单功能

This commit is contained in:
2026-07-12 11:40:04 +08:00
parent d949301bc1
commit de01d36cdb
46 changed files with 2657 additions and 253 deletions
+9
View File
@@ -18,12 +18,19 @@ export interface AppConfig {
aliyunSmsTemplateCode: string;
/** 核销确认短信模板(REDEEM_PHONE_CONFIRM);未配置时回退 aliyunSmsTemplateCode */
aliyunSmsRedeemConfirmTemplateCode: string;
/** 合伙人代下单短信模板(PARTNER_PROXY_ORDER / 线下代发货);未配置时回退 aliyunSmsTemplateCode */
aliyunSmsProxyOrderTemplateCode: string;
aliyunSmsAccessKeyId: string;
aliyunSmsAccessKeySecret: string;
/** 腾讯位置服务 Key(逆地理编码) */
tencentLbsKey: string;
/** C 端 H5 落地页(推广码二维码链接前缀) */
userH5Url: string;
}
/** 推广码 / C 端 H5 默认落地页(未配置 USER_H5_URL 时使用) */
export const DEFAULT_USER_H5_URL = 'https://user.runxian.top/user';
/** 总部客服电话(C 端联系客服) */
export const CUSTOMER_SERVICE_PHONE = '400-888-1234';
@@ -48,8 +55,10 @@ export function loadAppConfig(env?: Record<string, string | undefined>): AppConf
aliyunSmsSignName: e.ALIYUN_SMS_SIGN_NAME ?? '',
aliyunSmsTemplateCode: e.ALIYUN_SMS_TEMPLATE_CODE ?? '',
aliyunSmsRedeemConfirmTemplateCode: e.ALIYUN_SMS_REDEEM_CONFIRM_TEMPLATE_CODE ?? '',
aliyunSmsProxyOrderTemplateCode: 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 ?? '',
userH5Url: (e.USER_H5_URL || DEFAULT_USER_H5_URL).replace(/\/$/, ''),
};
}
+33
View File
@@ -15,6 +15,25 @@ export enum ActorType {
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',
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.OTHER]: '其他',
};
export enum SmsScene {
USER_LOGIN = 'USER_LOGIN',
STORE_LOGIN = 'STORE_LOGIN',
@@ -27,8 +46,22 @@ export enum SmsScene {
REDEEM_PHONE_LOOKUP = 'REDEEM_PHONE_LOOKUP',
/** 门店手机号核销:核销确认验证码(阿里云模板「核销确认」) */
REDEEM_PHONE_CONFIRM = 'REDEEM_PHONE_CONFIRM',
/** 合伙人代下单:线下代发货确认验证码(发至用户手机) */
PARTNER_PROXY_ORDER = 'PARTNER_PROXY_ORDER',
}
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',
+63
View File
@@ -1,24 +1,87 @@
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;
attributionCount?: number;
/** 用户表 source_ref_id 指向本推广码的用户数 */
sourceMarkedCount?: 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)}`;
}
+47
View File
@@ -33,3 +33,50 @@ export interface DeliveryDto {
shippingAt?: string;
deliveredAt?: string;
}
export type PartnerProxyOrderProductOption = {
id: string;
name: string;
spec: string;
price: number;
benefitAmount: number | null;
};
export type PartnerProxyOrderPromoOption = {
id: string;
code: string;
name: string;
};
export type PartnerProxyOrderOptions = {
products: PartnerProxyOrderProductOption[];
promoCodes: PartnerProxyOrderPromoOption[];
};
export type PartnerProxyOrderPreviewRequest = {
productId: string;
quantity: number;
receiverCity?: string;
receiverDistrict?: string;
};
export type PartnerProxyOrderPreviewResult = {
productAmount: number;
payAmount: number;
benefitAmount: number;
deliveryType: 'LOCAL' | 'CROSS_CITY';
unitPrice: number;
};
export type PartnerProxyOrderCreateRequest = {
phone: string;
smsCode: string;
receiverName?: string;
province: string;
city: string;
district: string;
addressDetail: string;
productId: string;
quantity: number;
promoCodeId?: string;
};