diff --git a/src/api/customer/index.ts b/src/api/customer/index.ts index ba74576..ac2a012 100644 --- a/src/api/customer/index.ts +++ b/src/api/customer/index.ts @@ -144,7 +144,9 @@ export const CustomerService = { assertBusinessOk(r.data) }, - async create(data: Partial & { password?: string; inviteCode?: string }): Promise { + async create( + data: Partial & { password?: string; inviteCode?: string; advisorCode?: string }, + ): Promise { const body = { customerCode: data.customerCode || undefined, customerName: data.customerName, @@ -165,6 +167,7 @@ export const CustomerService = { memberLevelId: data.memberLevelId, priceTierKey: data.priceTierKey, inviteCode: data.inviteCode?.trim() || undefined, + advisorCode: data.advisorCode?.trim() || undefined, } const r = await axiosInstance.post(omsUrl('/customer/create'), body) assertBusinessOk(r.data) diff --git a/src/api/wechat/types.ts b/src/api/wechat/types.ts index 679033d..1bc2449 100644 --- a/src/api/wechat/types.ts +++ b/src/api/wechat/types.ts @@ -49,6 +49,8 @@ export interface WeChatCustomerRegisterInput { password: string; /** 邀请码(来自 ?ref=,注册时绑定引荐人) */ inviteCode?: string; + /** 顾问邀请(来自 ?advisor=,值为员工账号,注册时绑定所属顾问) */ + advisorCode?: string; wechat: WeChatProfileInput; } diff --git a/src/components/EmployeeProfileFields/index.vue b/src/components/EmployeeProfileFields/index.vue index b6517ea..291a85c 100644 --- a/src/components/EmployeeProfileFields/index.vue +++ b/src/components/EmployeeProfileFields/index.vue @@ -145,10 +145,21 @@ field="employeePosition" v-bind="itemColProps" > - + > + + {{ item.label }} + + @@ -157,10 +168,21 @@ field="employeeJobLevel" v-bind="itemColProps" > - + > + + {{ item.label }} + + @@ -298,10 +320,14 @@ diff --git a/src/views/system/dict/dict-batch-import.ts b/src/views/system/dict/dict-batch-import.ts index 7d1e1d2..5353f88 100644 --- a/src/views/system/dict/dict-batch-import.ts +++ b/src/views/system/dict/dict-batch-import.ts @@ -102,6 +102,8 @@ export const OMS_DICT_TYPE_REMARKS: Record = { secret_level: '密级', data_scope: '数据范围', org_scope: '组织范围', + employee_position: '员工岗位', + employee_job_level: '员工职级', } /** 按预置种子中的 dictType 生成类型备注行(含排序) */ @@ -469,6 +471,32 @@ export const OMS_DICT_BATCH_SEED: DictSeedRow[] = [ ['1', '仅本级'], ['2', '本级及下级'], ]), + ...pack('employee_position', [ + ['generalManager', '总经理'], + ['deputyGeneralManager', '副总经理'], + ['logisticsAdvisor', '物流顾问'], + ['csManager', '客服主管'], + ['csSpecialist', '客服专员'], + ['opsManager', '运营经理'], + ['opsSpecialist', '运营专员'], + ['financeManager', '财务经理'], + ['financeSpecialist', '财务专员'], + ['warehouseManager', '仓储主管'], + ['warehouseClerk', '仓管员'], + ['customsSpecialist', '关务专员'], + ['productManager', '产品经理'], + ['techEngineer', '技术工程师'], + ['hrAdmin', '人事行政'], + ['marketingSpecialist', '市场专员'], + ['dispatcher', '调度员'], + ]), + ...pack('employee_job_level', [ + ['1', '一级'], + ['2', '二级'], + ['3', '三级'], + ['4', '四级'], + ['5', '五级'], + ]), ] /** 与库表唯一约束一致:dictType + dictCode */ diff --git a/src/views/system/organization/index.vue b/src/views/system/organization/index.vue index 0eb2f92..4dfc152 100644 --- a/src/views/system/organization/index.vue +++ b/src/views/system/organization/index.vue @@ -174,7 +174,6 @@ {{ t('system.organization.form.type.company') }} {{ t('system.organization.form.type.department') }} {{ t('system.organization.form.type.group') }} - {{ t('system.organization.form.type.position') }} @@ -410,7 +409,6 @@ function getTypeName(type?: number) { 1: t('system.organization.form.type.company'), 2: t('system.organization.form.type.department'), 3: t('system.organization.form.type.group'), - 4: t('system.organization.form.type.position'), } return names[type || 0] || t('system.organization.type.unknown') } @@ -434,7 +432,8 @@ function handleOrgSearch() { } function customCellRenderer(_row: number, col: number, _value: any, rowData: UserDto): string | null { - const dataColIndex = col - 1 + // HandsontablePro 列序:操作 → 序号 → 数据;showActionColumn 时 dataColStart=2 + const dataColIndex = col - 2 if (dataColIndex < 0 || dataColIndex >= hotColumns.value.length) return null const key = hotColumns.value[dataColIndex].key diff --git a/src/views/system/organization/user/index.vue b/src/views/system/organization/user/index.vue index db6093e..040e1f6 100644 --- a/src/views/system/organization/user/index.vue +++ b/src/views/system/organization/user/index.vue @@ -177,7 +177,8 @@ const hotColumns = computed(() => [ ]) function customCellRenderer(_row: number, col: number, _value: any, rowData: UserDto): string | null { - const dataColIndex = col - 1 + // HandsontablePro 列序:操作 → 序号 → 数据;showActionColumn 时 dataColStart=2 + const dataColIndex = col - 2 if (dataColIndex < 0 || dataColIndex >= hotColumns.value.length) return null const key = hotColumns.value[dataColIndex].key diff --git a/src/views/user/customer/index.vue b/src/views/user/customer/index.vue index 3a27f59..378535e 100644 --- a/src/views/user/customer/index.vue +++ b/src/views/user/customer/index.vue @@ -345,8 +345,11 @@ function isCustomerPortalUser(u: any, excludedUserIds?: Set): boolean { } function mapUserToSelectOption(u: any): SelectOption | null { - const label = getUserDisplayName(u) - return label ? { value: label, label } : null + const account = String(u?.account ?? u?.Account ?? '').trim() + if (!account) return null + const name = getUserDisplayName(u) + const label = name && name !== account ? `${name}(${account})` : account + return { value: account, label } } function getUserDisplayName(u: any): string { @@ -362,14 +365,43 @@ function getUserDisplayName(u: any): string { ) } -/** 表单展示/提交用姓名;若历史数据存的是用户 id,则反查姓名 */ -function resolveStaffNameForForm(value: string | number | undefined): string { +/** 表单 value 存员工 Account;兼容历史姓名 / id */ +function resolveStaffAccountForForm(value: string | number | undefined): string { if (value == null || value === '') return '' - const str = String(value) - const byName = userOptions.value.find((o) => o.label === str || String(o.value) === str) - if (byName) return String(byName.value) - const user = userList.value.find((u) => String(u?.id ?? u?.Id) === str) - if (user) return getUserDisplayName(user) + const str = String(value).trim() + const byAccount = userOptions.value.find((o) => String(o.value) === str) + if (byAccount) return String(byAccount.value) + const byLabel = userOptions.value.find((o) => o.label === str || String(o.label).includes(`(${str})`)) + if (byLabel) return String(byLabel.value) + const user = userList.value.find( + (u) => + String(u?.account ?? u?.Account ?? '') === str || + String(u?.id ?? u?.Id ?? '') === str || + getUserDisplayName(u) === str, + ) + if (user) { + const account = String(user?.account ?? user?.Account ?? '').trim() + if (account) return account + } + return str +} + +function formatStaffDisplay(value: string | number | undefined): string { + if (value == null || value === '') return '--' + const str = String(value).trim() + const opt = userOptions.value.find((o) => String(o.value) === str) + if (opt?.label) return String(opt.label) + const user = userList.value.find( + (u) => + String(u?.account ?? u?.Account ?? '') === str || + getUserDisplayName(u) === str, + ) + if (user) { + const account = String(user?.account ?? user?.Account ?? '').trim() + const name = getUserDisplayName(user) + if (account && name && name !== account) return `${name}(${account})` + return name || account || str + } return str } @@ -615,6 +647,12 @@ function customCellRenderer(row: number, col: number, value: any, rowData: any): if (val === undefined || val === null) return '--' return Number(val).toLocaleString() } + if (colKey === 'salesPerson') { + return formatStaffDisplay(rowData?.salesPerson ?? rowData?.salesman) + } + if (colKey === 'customerService') { + return formatStaffDisplay(rowData?.customerService) + } if (colKey === 'createTime') return rowData?.createTime ?? rowData?.createdAt ?? '--' if (colKey === 'updateTime') return rowData?.updateTime ?? '--' if (colKey === 'lastOnlineTime') return rowData?.lastOnlineTime ?? rowData?.lastLoginTime ?? '--' @@ -802,8 +840,8 @@ function handleEdit(record: CustomerRow) { memberLevelId: record.memberLevelId ?? undefined, priceTierKey: record.priceTierKey || '普通客户', remark: record.remark, - salesPerson: resolveStaffNameForForm(record.salesPerson ?? record.salesman), - customerService: resolveStaffNameForForm(record.customerService), + salesPerson: resolveStaffAccountForForm(record.salesPerson ?? record.salesman), + customerService: resolveStaffAccountForForm(record.customerService), }) modalVisible.value = true } @@ -884,14 +922,14 @@ async function handleModalOk(): Promise { } modalLoading.value = true try { - const salesPersonName = resolveStaffNameForForm(formState.salesPerson) - const customerServiceName = resolveStaffNameForForm(formState.customerService) + const salesPersonAccount = resolveStaffAccountForForm(formState.salesPerson) + const customerServiceAccount = resolveStaffAccountForForm(formState.customerService) if (isEdit.value && currentId.value != null && currentId.value !== '') { await CustomerService.update(currentId.value, { ...formState, - salesPerson: salesPersonName, - customerService: customerServiceName, + salesPerson: salesPersonAccount, + customerService: customerServiceAccount, }) Message.success(t('user.customer.message.updateSuccess')) } else { @@ -899,8 +937,8 @@ async function handleModalOk(): Promise { ...formState, customerCode: undefined, password: formState.password, - salesPerson: salesPersonName, - customerService: customerServiceName, + salesPerson: salesPersonAccount, + customerService: customerServiceAccount, }) Message.success(t('user.customer.message.createSuccess')) currentPage.value = 1 diff --git a/src/views/wechat/register/index.vue b/src/views/wechat/register/index.vue index 04aa403..e163d49 100644 --- a/src/views/wechat/register/index.vue +++ b/src/views/wechat/register/index.vue @@ -211,6 +211,11 @@ import { resolveWeChatInviteRef, stashWeChatInviteRef, } from '@/utils/wechatOAuth'; +import { + clearStashedAdvisorInvite, + resolveAdvisorInvite, + stashAdvisorInvite, +} from '@/utils/advisorInviteStorage'; import { buildInviteRegisterUrl } from '@/utils/inviteLink'; import { isTimeoutError, withTimeout } from '@/utils/withTimeout'; import { isWeChatBrowser, hideWeChatBrowserNav } from '@/utils/device'; @@ -295,6 +300,7 @@ const loadingHint = ref(''); const authModalVisible = ref(false); const wxUserInfo = ref(null); const inviteCode = ref(''); +const advisorCode = ref(''); const referrerValidated = ref(null); const referrerInfo = ref<{ name: string; code: string } | null>(null); let loadingSlowTimer: number | undefined; @@ -417,6 +423,7 @@ async function handleSubmit() { customerName: formState.customerName.trim(), password: formState.password, ...(canBindInvite ? { inviteCode: inviteCode.value } : {}), + ...(advisorCode.value ? { advisorCode: advisorCode.value } : {}), wechat: { openid: wxUserInfo.value.openid, nickname: wxUserInfo.value.nickname, @@ -425,6 +432,7 @@ async function handleSubmit() { }, }); clearStashedWeChatInviteRef(); + clearStashedAdvisorInvite(); Message.success(t('wechat.register.success')); const account = String(result?.customerCode || '').trim(); const loggedIn = account @@ -454,6 +462,11 @@ onMounted(() => { if (refCode) { void loadReferrer(refCode); } + const advisor = resolveAdvisorInvite(route.query as Record); + if (advisor) { + advisorCode.value = advisor; + stashAdvisorInvite(advisor); + } const code = route.query.code; if (typeof code !== 'string' || !code) {