All checks were successful
Build and Deploy Front / deploy (push) Successful in 6m30s
运单列表/工作台/返佣四态展示,注册归因与客户表单改绑代理,侧栏与占位邮箱优化。 Co-authored-by: Cursor <cursoragent@cursor.com>
331 lines
10 KiB
TypeScript
331 lines
10 KiB
TypeScript
/**
|
||
* 客户管理 API(文档:GET/POST /customer/*,admin 同源)
|
||
*/
|
||
import { axiosInstance } from '@/api/rbac'
|
||
import { assertBusinessOk, omsUrl, parseListPayload, unwrapData } from '@/api/oms/common'
|
||
|
||
export type CustomerStatus = 0 | 1 | 2
|
||
|
||
export type CustomerGroup = 0 | 1 | 2 | 3 | 4 | 5
|
||
|
||
export interface CustomerDto {
|
||
id?: string | number
|
||
parentCustomerId?: number | null
|
||
userId?: number
|
||
customerCode?: string
|
||
customerName?: string
|
||
wechatAvatar?: string
|
||
wechatAvatarUrl?: string
|
||
wechatNickname?: string
|
||
email?: string
|
||
phone?: string
|
||
balance?: number
|
||
accountBalance?: number
|
||
totalQuota?: number
|
||
usedQuota?: number
|
||
status?: CustomerStatus
|
||
remark?: string
|
||
createTime?: string
|
||
createdAt?: string
|
||
createBy?: string
|
||
createdBy?: string
|
||
updateTime?: string
|
||
updateBy?: string
|
||
salesman?: string | number
|
||
salesPerson?: string | number
|
||
customerService?: string | number
|
||
agentId?: number | null
|
||
agentCode?: string
|
||
agentName?: string
|
||
customerGroup?: CustomerGroup
|
||
memberLevelId?: number | null
|
||
memberLevelName?: string
|
||
priceTierKey?: string | null
|
||
lastLoginIp?: string
|
||
lastOnlineIp?: string
|
||
lastLoginTime?: string
|
||
lastOnlineTime?: string
|
||
role?: string | number
|
||
}
|
||
|
||
export interface CustomerListParams {
|
||
page?: number
|
||
pageSize?: number
|
||
keyword?: string
|
||
status?: number
|
||
customerGroup?: number
|
||
salesman?: string
|
||
customerService?: string
|
||
agentId?: number
|
||
sortField?: string
|
||
sortOrder?: string
|
||
}
|
||
|
||
export interface CustomerPagedResult {
|
||
items: CustomerDto[]
|
||
total: number
|
||
}
|
||
|
||
export interface CustomerProfileCompleteness {
|
||
phoneFilled: boolean
|
||
emailFilled: boolean
|
||
needContact: boolean
|
||
customerCode: string
|
||
phone?: string
|
||
email?: string
|
||
}
|
||
|
||
function mapUpdateBody(id: string | number, data: Partial<CustomerDto>) {
|
||
const body: Record<string, unknown> = {
|
||
id,
|
||
customerCode: data.customerCode,
|
||
customerName: data.customerName,
|
||
wechatAvatar: data.wechatAvatarUrl ?? data.wechatAvatar,
|
||
wechatNickname: data.wechatNickname,
|
||
email: data.email,
|
||
phone: data.phone,
|
||
balance: data.accountBalance ?? data.balance,
|
||
totalQuota: data.totalQuota,
|
||
usedQuota: data.usedQuota,
|
||
status: data.status,
|
||
role: data.role,
|
||
remark: data.remark,
|
||
salesman: data.salesman ?? data.salesPerson,
|
||
customerService: data.customerService,
|
||
// 空串表示解绑;有值则按编号绑定
|
||
agentCode: data.agentCode !== undefined ? String(data.agentCode ?? '').trim() : undefined,
|
||
customerGroup: data.customerGroup,
|
||
memberLevelId: data.memberLevelId,
|
||
priceTierKey: data.priceTierKey,
|
||
}
|
||
// 去掉 undefined,避免局部字段被序列化成 null 后把后端原值冲掉
|
||
return Object.fromEntries(Object.entries(body).filter(([, v]) => v !== undefined))
|
||
}
|
||
|
||
export interface CustomerServiceAdvisorDto {
|
||
name?: string
|
||
wechatId?: string
|
||
wechatQrCode?: string
|
||
wecomKfUrl?: string
|
||
}
|
||
|
||
/** 客户 OpenAPI 机机凭证 */
|
||
export interface CustomerOpenApiCredentialDto {
|
||
id?: number
|
||
customerId?: number
|
||
appKey?: string
|
||
appSecret?: string
|
||
enabled?: boolean
|
||
hasCredential?: boolean
|
||
secretRevealed?: boolean
|
||
lastUsedAt?: string | null
|
||
lastUsedIp?: string | null
|
||
createTime?: string | null
|
||
updateTime?: string | null
|
||
}
|
||
|
||
export const CustomerService = {
|
||
async getList(params: CustomerListParams): Promise<CustomerPagedResult> {
|
||
const res = await axiosInstance.get(omsUrl('/customer/list'), { params })
|
||
return parseListPayload(res.data)
|
||
},
|
||
|
||
async getById(id: string | number): Promise<CustomerDto> {
|
||
const res = await axiosInstance.get(omsUrl('/customer/get'), { params: { id } })
|
||
return unwrapData<CustomerDto>(res.data)
|
||
},
|
||
|
||
async getServiceAdvisor(): Promise<CustomerServiceAdvisorDto> {
|
||
const res = await axiosInstance.get(omsUrl('/customer/service-advisor'))
|
||
return unwrapData<CustomerServiceAdvisorDto>(res.data)
|
||
},
|
||
|
||
async createSub(
|
||
data: Partial<CustomerDto> & { password?: string; permissions?: string[] }
|
||
): Promise<void> {
|
||
const body = {
|
||
customerCode: data.customerCode,
|
||
customerName: data.customerName,
|
||
wechatAvatar: data.wechatAvatarUrl ?? data.wechatAvatar,
|
||
wechatNickname: data.wechatNickname,
|
||
phone: data.phone,
|
||
email: data.email,
|
||
password: data.password,
|
||
balance: data.accountBalance ?? data.balance ?? 0,
|
||
totalQuota: data.totalQuota ?? 0,
|
||
usedQuota: data.usedQuota ?? 0,
|
||
status: data.status ?? 1,
|
||
remark: data.remark,
|
||
salesman: data.salesman ?? data.salesPerson,
|
||
customerService: data.customerService,
|
||
customerGroup: data.customerGroup ?? 0,
|
||
memberLevelId: data.memberLevelId,
|
||
priceTierKey: data.priceTierKey,
|
||
permissions: data.permissions ?? [],
|
||
}
|
||
const r = await axiosInstance.post(omsUrl('/customer/sub/create'), body)
|
||
assertBusinessOk(r.data)
|
||
},
|
||
|
||
async create(
|
||
data: Partial<CustomerDto> & {
|
||
password?: string
|
||
inviteCode?: string
|
||
advisorCode?: string
|
||
agentCode?: string
|
||
},
|
||
): Promise<void> {
|
||
const body = {
|
||
customerCode: data.customerCode || undefined,
|
||
customerName: data.customerName,
|
||
wechatAvatar: data.wechatAvatarUrl ?? data.wechatAvatar,
|
||
wechatNickname: data.wechatNickname,
|
||
phone: data.phone,
|
||
email: data.email,
|
||
password: data.password,
|
||
balance: data.accountBalance ?? data.balance,
|
||
totalQuota: data.totalQuota,
|
||
usedQuota: data.usedQuota,
|
||
status: data.status ?? 1,
|
||
role: data.role,
|
||
remark: data.remark,
|
||
salesman: data.salesman ?? data.salesPerson,
|
||
customerService: data.customerService,
|
||
customerGroup: data.customerGroup ?? 0,
|
||
memberLevelId: data.memberLevelId,
|
||
priceTierKey: data.priceTierKey,
|
||
inviteCode: data.inviteCode?.trim() || undefined,
|
||
advisorCode: data.advisorCode?.trim() || undefined,
|
||
agentCode: data.agentCode?.trim() || undefined,
|
||
}
|
||
const r = await axiosInstance.post(omsUrl('/customer/create'), body)
|
||
assertBusinessOk(r.data)
|
||
},
|
||
|
||
async update(id: string | number, data: Partial<CustomerDto>): Promise<void> {
|
||
const r = await axiosInstance.post(omsUrl('/customer/update'), mapUpdateBody(id, data))
|
||
assertBusinessOk(r.data)
|
||
},
|
||
|
||
async delete(id: string | number): Promise<void> {
|
||
const r = await axiosInstance.post(omsUrl('/customer/delete'), { id })
|
||
assertBusinessOk(r.data)
|
||
},
|
||
|
||
async resetPassword(id: string | number, newPassword: string): Promise<void> {
|
||
const r = await axiosInstance.post(omsUrl('/customer/reset-password'), { id, newPassword })
|
||
assertBusinessOk(r.data)
|
||
},
|
||
|
||
async getProfileCompleteness(): Promise<CustomerProfileCompleteness> {
|
||
const r = await axiosInstance.get(omsUrl('/customer/profile-completeness'))
|
||
const data = unwrapData<Record<string, unknown>>(r.data) || (r.data as Record<string, unknown>)
|
||
return {
|
||
phoneFilled: Boolean(data.phoneFilled ?? data.PhoneFilled),
|
||
emailFilled: Boolean(data.emailFilled ?? data.EmailFilled),
|
||
needContact: Boolean(data.needContact ?? data.NeedContact),
|
||
customerCode: String(data.customerCode ?? data.CustomerCode ?? ''),
|
||
phone: (data.phone ?? data.Phone) as string | undefined,
|
||
email: (data.email ?? data.Email) as string | undefined,
|
||
}
|
||
},
|
||
|
||
async completeContact(input: { phone: string; email: string }): Promise<void> {
|
||
const r = await axiosInstance.post(omsUrl('/customer/complete-contact'), {
|
||
phone: input.phone.trim(),
|
||
email: input.email.trim(),
|
||
})
|
||
assertBusinessOk(r.data)
|
||
},
|
||
|
||
async omsLogin(customerId: string | number): Promise<{
|
||
accessToken: string
|
||
refreshToken?: string | null
|
||
expiresIn?: number
|
||
}> {
|
||
const r = await axiosInstance.post(omsUrl('/rbac/auth/customer-oms-login'), {
|
||
customerId: Number(customerId),
|
||
})
|
||
const data = unwrapData<any>(r.data)
|
||
return {
|
||
accessToken: data.accessToken ?? data.AccessToken ?? '',
|
||
refreshToken: data.refreshToken ?? data.RefreshToken ?? null,
|
||
expiresIn: data.expiresIn ?? data.ExpiresIn,
|
||
}
|
||
},
|
||
|
||
buildCustomerSsoUrl(result: {
|
||
accessToken: string
|
||
refreshToken?: string | null
|
||
}): string {
|
||
const params = new URLSearchParams({
|
||
sso: '1',
|
||
accessToken: result.accessToken,
|
||
})
|
||
if (result.refreshToken) {
|
||
params.set('refreshToken', result.refreshToken)
|
||
}
|
||
return `${window.location.origin}/?${params.toString()}`
|
||
},
|
||
|
||
async getOpenApiCredential(): Promise<CustomerOpenApiCredentialDto> {
|
||
const r = await axiosInstance.get(omsUrl('/customer/openapi-credential'))
|
||
return unwrapData<CustomerOpenApiCredentialDto>(r.data)
|
||
},
|
||
|
||
async ensureOpenApiCredential(): Promise<CustomerOpenApiCredentialDto> {
|
||
const r = await axiosInstance.post(omsUrl('/customer/openapi-credential/ensure'))
|
||
return unwrapData<CustomerOpenApiCredentialDto>(r.data)
|
||
},
|
||
|
||
async resetOpenApiSecret(): Promise<CustomerOpenApiCredentialDto> {
|
||
const r = await axiosInstance.post(omsUrl('/customer/openapi-credential/reset-secret'))
|
||
return unwrapData<CustomerOpenApiCredentialDto>(r.data)
|
||
},
|
||
|
||
async setOpenApiEnabled(enabled: boolean): Promise<CustomerOpenApiCredentialDto> {
|
||
const r = await axiosInstance.post(omsUrl('/customer/openapi-credential/set-enabled'), {
|
||
enabled,
|
||
})
|
||
return unwrapData<CustomerOpenApiCredentialDto>(r.data)
|
||
},
|
||
|
||
async adminGetOpenApiCredential(
|
||
customerId: string | number,
|
||
): Promise<CustomerOpenApiCredentialDto> {
|
||
const r = await axiosInstance.get(omsUrl('/customer/openapi-credential/admin/get'), {
|
||
params: { customerId: Number(customerId) },
|
||
})
|
||
return unwrapData<CustomerOpenApiCredentialDto>(r.data)
|
||
},
|
||
|
||
async adminEnsureOpenApiCredential(
|
||
customerId: string | number,
|
||
): Promise<CustomerOpenApiCredentialDto> {
|
||
const r = await axiosInstance.post(omsUrl('/customer/openapi-credential/admin/ensure'), {
|
||
customerId: Number(customerId),
|
||
})
|
||
return unwrapData<CustomerOpenApiCredentialDto>(r.data)
|
||
},
|
||
|
||
async adminResetOpenApiSecret(
|
||
customerId: string | number,
|
||
): Promise<CustomerOpenApiCredentialDto> {
|
||
const r = await axiosInstance.post(omsUrl('/customer/openapi-credential/admin/reset-secret'), {
|
||
customerId: Number(customerId),
|
||
})
|
||
return unwrapData<CustomerOpenApiCredentialDto>(r.data)
|
||
},
|
||
|
||
async adminSetOpenApiEnabled(
|
||
customerId: string | number,
|
||
enabled: boolean,
|
||
): Promise<CustomerOpenApiCredentialDto> {
|
||
const r = await axiosInstance.post(omsUrl('/customer/openapi-credential/admin/set-enabled'), {
|
||
customerId: Number(customerId),
|
||
enabled,
|
||
})
|
||
return unwrapData<CustomerOpenApiCredentialDto>(r.data)
|
||
},
|
||
}
|