diff --git a/src/modules/account/views/AccountProfileView.test.mjs b/src/modules/account/views/AccountProfileView.test.mjs index 959f702..5ae5610 100644 --- a/src/modules/account/views/AccountProfileView.test.mjs +++ b/src/modules/account/views/AccountProfileView.test.mjs @@ -23,6 +23,8 @@ test('implements profile view, edit, save and cancel states', () => { assert.match(viewSource, /function cancelEditing\(\)/) assert.match(viewSource, /async function saveProfile\(\)/) assert.match(viewSource, /请在“登录标识”中修改/) + assert.match(viewSource, /
登录手机号<\/dt>
\{\{ auth\.profile\.phone/) + assert.doesNotMatch(viewSource, /
联系电话<\/dt>
\{\{ auth\.profile\.phone/) assert.match(viewSource, /企业归属由平台审核关系确定/) assert.match(authSource, /async function savePersonalProfile\(/) assert.match(viewSource, /await auth\.savePersonalProfile/) @@ -38,6 +40,8 @@ test('changes username and mobile with current-password verification then clears assert.match(viewSource, /error\.code === 'A0401'/) assert.match(viewSource, /auth\.clearSession\(\)/) assert.match(viewSource, /reason: 'identifiers-updated'/) + assert.match(viewSource, /label="登录手机号" prop="phone"/) + assert.match(viewSource, /全部登录会话都会立即退出/) assert.doesNotMatch(viewSource, /auth\.logout\(\)[\s\S]*identifiers-updated/) }) diff --git a/src/modules/account/views/AccountProfileView.vue b/src/modules/account/views/AccountProfileView.vue index a495a2f..9b2d4e1 100644 --- a/src/modules/account/views/AccountProfileView.vue +++ b/src/modules/account/views/AccountProfileView.vue @@ -193,7 +193,7 @@ async function changeLoginIdentifiers() { await router.replace({ path: '/login', query: { account: username, reason: 'identifiers-updated' } }) } catch (error) { identifiersForm.currentPassword = '' - if (error instanceof ApiError && error.code === 'A0409') identifiersError.value = '用户名或手机号已被使用,请更换后重试' + if (error instanceof ApiError && error.code === 'A0409') identifiersError.value = '用户名或登录手机号已被使用,请更换后重试' else if (error instanceof ApiError && error.code === 'A0401') identifiersError.value = '当前密码错误' else if (error instanceof ApiError && error.code === 'A0403') identifiersError.value = '当前会话不允许修改登录标识,请退出代登录后重试' else identifiersError.value = error instanceof Error ? error.message : '登录标识修改失败,请重试' @@ -267,7 +267,7 @@ async function changePassword() {
用户名
{{ auth.profile.username }}
姓名
{{ auth.profile.realName }}
-
联系电话
{{ auth.profile.phone || '未填写' }}
+
登录手机号
{{ auth.profile.phone || '未填写' }}
邮箱
{{ auth.profile.email || '未填写' }}
所属公司
{{ auth.profile.tenantName }}
@@ -277,9 +277,9 @@ async function changePassword() {
- + - +
重置保存并重新登录
diff --git a/src/modules/auth/views/AuthViews.test.mjs b/src/modules/auth/views/AuthViews.test.mjs index 00167c9..a41ff01 100644 --- a/src/modules/auth/views/AuthViews.test.mjs +++ b/src/modules/auth/views/AuthViews.test.mjs @@ -53,6 +53,8 @@ test('implements every backend registration field and real submission', () => { for (const field of ['username', 'companyName', 'displayName', 'legalPerson', 'legalPhone', 'contactEmail', 'countryCode', 'address', 'password', 'agreement']) { assert.match(registerSource, new RegExp(`prop="${field}"`)) } + assert.match(registerSource, /label="登录手机号" prop="legalPhone"/) + assert.match(registerSource, /注册时同时初始化公司联系电话;注册后两者可分别修改,互不联动/) assert.match(registerSource, /registerMerchant\(/) assert.match(registerSource, /4–64 位/) assert.match(registerSource, /8–72 位/) diff --git a/src/modules/auth/views/RegisterView.vue b/src/modules/auth/views/RegisterView.vue index 3b07e2b..3e683e0 100644 --- a/src/modules/auth/views/RegisterView.vue +++ b/src/modules/auth/views/RegisterView.vue @@ -131,7 +131,7 @@ async function submit() {

联系信息

- + 注册时同时初始化公司联系电话;注册后两者可分别修改,互不联动。
diff --git a/src/modules/tenant-management/api/merchantIam.test.mjs b/src/modules/tenant-management/api/merchantIam.test.mjs index db73402..bb0cb5f 100644 --- a/src/modules/tenant-management/api/merchantIam.test.mjs +++ b/src/modules/tenant-management/api/merchantIam.test.mjs @@ -78,6 +78,8 @@ test('starts empty and only loads server role and member records', () => { assert.match(storeSource, /await resetMerchantMemberPassword/) assert.match(source, /username: string/) assert.match(source, /lastLoginAt: string \| null/) + assert.match(source, /phone: string \| null/) + assert.match(storeSource, /phone: payload\.phone\.trim\(\) \|\| null/) assert.match(source, /memberCount: number/) assert.match(storeSource, /account: member\.username/) assert.match(storeSource, /lastLoginAt: formatLastLoginAt\(member\.lastLoginAt\)/) diff --git a/src/modules/tenant-management/api/merchantIam.ts b/src/modules/tenant-management/api/merchantIam.ts index 2a06f58..6a1b2cc 100644 --- a/src/modules/tenant-management/api/merchantIam.ts +++ b/src/modules/tenant-management/api/merchantIam.ts @@ -129,8 +129,8 @@ export interface CreateMerchantMemberRequest { username: string password: string displayName: string - phone: string - email: string + phone: string | null + email: string | null memberType: 'EMPLOYEE' memberNo: string roleIds: Array @@ -138,8 +138,8 @@ export interface CreateMerchantMemberRequest { export interface UpdateMerchantMemberRequest { displayName: string - phone: string - email: string + phone: string | null + email: string | null memberType: string memberNo: string roleIds: Array diff --git a/src/modules/tenant-management/store/tenantAdministration.ts b/src/modules/tenant-management/store/tenantAdministration.ts index c1e17e1..5331a45 100644 --- a/src/modules/tenant-management/store/tenantAdministration.ts +++ b/src/modules/tenant-management/store/tenantAdministration.ts @@ -234,8 +234,8 @@ export const useTenantAdministrationStore = defineStore('tenant-administration', username: payload.account.trim(), password: payload.password, displayName: payload.name.trim(), - phone: payload.phone.trim(), - email: payload.email.trim(), + phone: payload.phone.trim() || null, + email: payload.email.trim() || null, memberType: 'EMPLOYEE', memberNo: payload.memberNo.trim(), roleIds: payload.roleIds, @@ -248,8 +248,8 @@ export const useTenantAdministrationStore = defineStore('tenant-administration', if (!member) throw new Error('成员不存在') const updated = await updateMerchantMember(id, { displayName: payload.name.trim(), - phone: payload.phone.trim(), - email: payload.email.trim(), + phone: payload.phone.trim() || null, + email: payload.email.trim() || null, memberType: member.isAdministrator ? 'OWNER' : 'EMPLOYEE', memberNo: payload.memberNo.trim(), roleIds: payload.roleIds, diff --git a/src/modules/tenant-management/views/CompanyProfileView.vue b/src/modules/tenant-management/views/CompanyProfileView.vue index f49f27c..a1a1227 100644 --- a/src/modules/tenant-management/views/CompanyProfileView.vue +++ b/src/modules/tenant-management/views/CompanyProfileView.vue @@ -150,7 +150,7 @@ const profileFormRules: FormRules = { companyName: [{ required: true, whitespace: true, message: '请输入公司名称', trigger: 'blur' }], legalRepresentative: [{ required: true, whitespace: true, message: '请输入法人姓名', trigger: 'blur' }], contactName: [{ required: true, whitespace: true, message: '请输入联系人姓名', trigger: 'blur' }], - contactPhone: [{ required: true, whitespace: true, message: '请输入联系电话', trigger: 'blur' }], + contactPhone: [{ required: true, whitespace: true, message: '请输入公司联系电话', trigger: 'blur' }], contactEmail: [{ type: 'email', message: '请输入正确的联系邮箱', trigger: 'blur' }], } const qualificationColumns: DataTableColumn[] = [ @@ -516,7 +516,7 @@ function closeQualificationPreview() {

联系信息

联系人
{{ store.company.contactName || '-' }}
-
联系电话
{{ store.company.contactPhone || '-' }}
+
公司联系电话
{{ store.company.contactPhone || '-' }}
联系邮箱
{{ store.company.contactEmail || '-' }}
联系微信
{{ store.company.contactWechat || '-' }}
@@ -571,7 +571,7 @@ function closeQualificationPreview() {

联系信息

- + 仅用于公司业务联系,不会修改任何成员的登录手机号。
@@ -684,6 +684,7 @@ function closeQualificationPreview() { .mobile-qualification-list { display: none; } .profile-form h3 { margin: 4px 0 18px; padding-bottom: 10px; border-bottom: 1px solid var(--border); font-size: 15px; } .profile-form h3:not(:first-child) { margin-top: 22px; } +.profile-field-hint { display: block; margin-top: 5px; color: var(--muted); font-size: 10px; line-height: 1.5; } .form-grid-two { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 16px; } .form-grid-two .full-row { grid-column: 1 / -1; } .drawer-actions { display: flex; justify-content: flex-end; gap: 8px; } diff --git a/src/modules/tenant-management/views/MembersView.vue b/src/modules/tenant-management/views/MembersView.vue index 4dc0154..4d09626 100644 --- a/src/modules/tenant-management/views/MembersView.vue +++ b/src/modules/tenant-management/views/MembersView.vue @@ -30,19 +30,19 @@ const memberRules: FormRules = { { required: true, message: '请输入初始密码', trigger: 'blur' }, { pattern: /^(?=.*[A-Za-z])(?=.*\d).{8,64}$/, message: '密码需为 8–64 位,并同时包含字母和数字', trigger: 'blur' }, ], - phone: [{ max: 32, message: '联系电话不能超过 32 个字符', trigger: 'blur' }], + phone: [{ pattern: /^1[3-9]\d{9}$/, message: '请输入有效的中国大陆登录手机号', trigger: 'blur' }], email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }], roleIds: [{ type: 'array', required: true, min: 1, message: '请至少选择一个成员角色', trigger: 'change' }], } const memberQueryFields: QueryField[] = [ - { key: 'keyword', type: 'input', placeholder: '姓名、账号、手机或邮箱' }, + { key: 'keyword', type: 'input', placeholder: '姓名、账号、登录手机号或邮箱' }, { key: 'status', type: 'select', placeholder: '全部状态', options: [{ label: '正常', value: 'ACTIVE' }, { label: '已停用', value: 'INACTIVE' }] }, ] const memberTableColumns: DataTableColumn[] = [ { key: 'actions', label: '操作', fixed: 'left', width: 230, slotName: 'actions' }, { key: 'member', label: '成员', minWidth: 170, slotName: 'member' }, - { key: 'contact', label: '联系方式', minWidth: 200, slotName: 'contact' }, + { key: 'contact', label: '登录手机号 / 邮箱', minWidth: 200, slotName: 'contact' }, { key: 'roles', label: '角色', minWidth: 130, slotName: 'roles' }, { key: 'lastLoginAt', prop: 'lastLoginAt', label: '登录记录', minWidth: 130 }, { key: 'status', label: '状态', width: 90, slotName: 'status' }, @@ -176,7 +176,7 @@ async function resetPassword(member: TenantMember) { - + @@ -186,7 +186,7 @@ async function resetPassword(member: TenantMember) {
{{ member.name.slice(0, 1) }}
{{ member.name }}{{ member.account }}
{{ member.status === 'ACTIVE' ? '正常' : '已停用' }}
-
手机
{{ member.phone }}
最近登录
{{ member.lastLoginAt }}
角色
{{ store.roleNames(member.roleIds).join('、') }}
+
登录手机号
{{ member.phone || '-' }}
最近登录
{{ member.lastLoginAt }}
角色
{{ store.roleNames(member.roleIds).join('、') }}
编辑重置密码{{ member.status === 'ACTIVE' ? '停用' : '启用' }}仅查看
@@ -199,7 +199,7 @@ async function resetPassword(member: TenantMember) { -
+
用于该成员手机号登录;修改后会同步认证账号。
请通过安全渠道将初始密码交给成员;前端不会保存或再次展示该密码。
@@ -225,6 +225,7 @@ async function resetPassword(member: TenantMember) { .mobile-member-list, .mobile-member-empty { display: none; } .member-form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; } .member-form-tip { display: flex; gap: 10px; padding: 13px; border: 1px solid #cfe1dc; background: #f2f7f6; color: var(--muted); font-size: 11px; line-height: 1.6; } +.member-field-help { margin-top: 5px; color: var(--muted); font-size: 10px; line-height: 1.5; } .member-form-tip svg { flex: 0 0 17px; color: var(--primary); } .drawer-actions { display: flex; justify-content: flex-end; gap: 8px; } .members-page :deep(.el-form-item__error) { line-height: 1.45; overflow-wrap: anywhere; } diff --git a/src/modules/tenant-management/views/TenantAdministrationViews.test.mjs b/src/modules/tenant-management/views/TenantAdministrationViews.test.mjs index c7815e4..a0d53f1 100644 --- a/src/modules/tenant-management/views/TenantAdministrationViews.test.mjs +++ b/src/modules/tenant-management/views/TenantAdministrationViews.test.mjs @@ -94,6 +94,10 @@ test('implements company details, contact information and qualification workflow assert.match(profileSource, /
成立日期<\/dt>
\{\{ store\.company\.establishedDate/) assert.match(profileSource, /
注册资本<\/dt>
\{\{ store\.company\.registeredCapital/) assert.match(profileSource, /
联系微信<\/dt>
\{\{ store\.company\.contactWechat/) + assert.match(profileSource, /
公司联系电话<\/dt>
\{\{ store\.company\.contactPhone/) + assert.match(profileSource, /label="公司联系电话" prop="contactPhone" required/) + assert.match(profileSource, /不会修改任何成员的登录手机号/) + assert.doesNotMatch(profileSource, /
联系电话<\/dt>
\{\{ store\.company\.contactPhone/) assert.match(storeSource, /expiresAt: item\.validTo \|\| '长期有效'/) }) @@ -135,6 +139,11 @@ test('implements member list, role assignment, password reset and status control assert.match(membersSource, /canChangeMemberStatus.*company:member:status/) assert.doesNotMatch(membersSource, /company:member:write/) assert.doesNotMatch(membersSource, /member-metrics/) + assert.match(membersSource, /label="登录手机号" prop="phone"/) + assert.match(membersSource, /登录手机号 \/ 邮箱/) + assert.match(membersSource, /用于该成员手机号登录;修改后会同步认证账号/) + assert.match(membersSource, /phone: \[\{ pattern: \/\^1\[3-9\]\\d\{9\}\$\//) + assert.doesNotMatch(membersSource, /label="联系电话" prop="phone"/) }) test('protects built-in roles and exposes role permission assignment', () => {