feat(register): 完善经销商注册功能并优化邀请机制

- 将登录方法替换为注册方法,实现完整的用户注册流程
- 添加短信验证码发送失败的错误处理和提示
- 实现邀请推荐关系绑定功能,支持注册后自动建立推荐关系
- 优化URL参数解析,支持tenantId和inviter参数的灵活获取
- 添加倒计时清理逻辑,防止内存泄漏
- 更新表单验证规则,移除不必要的字段验证
- 在邀请链接中添加tenantId参数,确保未登录用户能正确识别租户
- 添加注册成功后的自动登录和推荐关系建立流程
This commit is contained in:
2026-01-22 11:29:15 +08:00
parent 585b2e95fa
commit 49c8d40e75
2 changed files with 162 additions and 133 deletions

View File

@@ -67,7 +67,7 @@
size="large"
:maxlength="6"
allow-clear
@pressEnter="onLoginBySms"
@pressEnter="submit"
/>
<a-button
class="login-captcha"
@@ -120,7 +120,7 @@
size="large"
:maxlength="6"
allow-clear
@pressEnter="onLoginBySms"
@pressEnter="submit"
/>
<a-button
class="login-captcha"
@@ -138,7 +138,7 @@
size="large"
type="primary"
:loading="loading"
@click="onLoginBySms"
@click="submit"
>
{{ loading ? t('login.loading') : t('login.login') }}
</a-button>
@@ -195,7 +195,7 @@
</template>
<script lang="ts" setup>
import { ref, reactive, unref, watch, onMounted } from 'vue';
import { ref, reactive, unref, watch, onMounted, onBeforeUnmount } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { getTenantId } from '@/utils/domain';
@@ -203,22 +203,26 @@
import { CheckCircleOutlined } from '@ant-design/icons-vue';
import { storeToRefs } from 'pinia';
import { goHomeRoute, cleanPageTabs } from '@/utils/page-tab-util';
import { loginBySms, getCaptcha } from '@/api/passport/login';
import { TEMPLATE_ID, THEME_STORE_NAME } from '@/config/setting';
import { sendSmsCaptcha } from '@/api/passport/login';
import {
getCaptcha,
loginBySms,
registerUser,
sendSmsCaptcha
} from '@/api/passport/login';
import { THEME_STORE_NAME } from '@/config/setting';
import useFormData from '@/utils/use-form-data';
import { FormInstance } from 'ant-design-vue/es/form';
import { configWebsiteField } from '@/api/cms/cmsWebsiteField';
import { Config } from '@/api/cms/cmsWebsiteField/model';
import { phoneReg } from 'ele-admin-pro';
import { useThemeStore } from '@/store/modules/theme';
import { CmsWebsite } from '@/api/cms/cmsWebsite/model';
import { addAdminUser } from '@/api/system/user';
import { User } from '@/api/system/user/model';
import { listRoles } from '@/api/system/role';
import { addShopDealerReferee } from '@/api/shop/shopDealerReferee';
import { getToken } from '@/utils/token-util';
const useForm = Form.useForm;
const { currentRoute } = useRouter();
const router = useRouter();
const { currentRoute } = router;
const { t } = useI18n();
const { locale } = useI18n();
@@ -237,7 +241,10 @@
// 配置信息
const { form } = useFormData<User>({
phone: '',
isAdmin: true
code: '',
remember: false, // 这里作为“同意协议”的勾选状态使用
isAdmin: true,
dealerId: undefined
});
// 验证码 base64 数据
@@ -262,22 +269,8 @@
// 表格选中数据
const formRef = ref<FormInstance | null>(null);
// 表单验证规则
// 表单验证规则(只保留本页面实际填写的字段)
const rules = reactive({
companyName: [
{
required: true,
message: '请输入店铺名称',
type: 'string'
}
],
category: [
{
required: true,
message: '请选择行业分类',
type: 'string'
}
],
phone: [
{
pattern: phoneReg,
@@ -287,33 +280,12 @@
trigger: 'blur'
}
],
// address: [
// {
// required: true,
// message: '请选择店铺位置',
// type: 'string'
// }
// ],
password: [
{
required: true,
message: t('login.password'),
type: 'string'
}
],
code: [
{
required: true,
message: t('login.code'),
type: 'string'
}
],
smsCode: [
{
required: true,
message: t('login.code'),
type: 'string'
}
]
});
@@ -339,20 +311,26 @@
return;
}
codeLoading.value = true;
sendSmsCaptcha({ phone: form.phone }).then(() => {
message.success('短信验证码发送成功, 请注意查收!');
visible.value = false;
codeLoading.value = false;
countdownTime.value = 30;
// 开始对按钮进行倒计时
countdownTimer = window.setInterval(() => {
if (countdownTime.value <= 1) {
countdownTimer && clearInterval(countdownTimer);
countdownTimer = null;
}
countdownTime.value--;
}, 1000);
});
sendSmsCaptcha({ phone: form.phone })
.then(() => {
message.success('短信验证码发送成功, 请注意查收!');
visible.value = false;
countdownTime.value = 30;
// 开始对按钮进行倒计时
countdownTimer = window.setInterval(() => {
if (countdownTime.value <= 1) {
countdownTimer && clearInterval(countdownTimer);
countdownTimer = null;
}
countdownTime.value--;
}, 1000);
})
.catch((e: any) => {
message.error(e?.message || '短信验证码发送失败');
})
.finally(() => {
codeLoading.value = false;
});
};
const { resetFields } = useForm(form, rules);
@@ -363,29 +341,30 @@
localStorage.removeItem(THEME_STORE_NAME);
};
const onLoginBySms = () => {
if (!formRef.value) {
// 建立邀请推荐关系(需要登录态;失败不影响主流程)
let bindInviterOnce = false;
const tryBindInviteRelation = async () => {
if (bindInviterOnce) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
form.code = form.smsCode?.toLowerCase();
loginBySms(form)
.then((msg) => {
message.success(msg);
loading.value = false;
resetFields();
cleanPageTabs();
goHome();
})
.catch((e: Error) => {
message.error(e.message);
loading.value = false;
});
})
.catch(() => {});
if (!inviterId.value) {
return;
}
if (!getToken()) {
return;
}
const tenantIdStr = localStorage.getItem('TenantId');
const userIdStr = localStorage.getItem('UserId');
if (!userIdStr) {
return;
}
bindInviterOnce = true;
await addShopDealerReferee({
dealerId: inviterId.value,
userId: Number(userIdStr),
level: 1,
tenantId: tenantIdStr ? Number(tenantIdStr) : form.tenantId
});
};
/* 保存编辑 */
@@ -394,52 +373,53 @@
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
// addShopDealerUser({
// ...form,
// dealerId: Number(dealerId.value),
// }).then((data) => {
// console.log(data);
// });
try {
await formRef.value.validate();
} catch {
return;
}
// createCmsWebSite(form)
// .then(async (msg) => {
// // 如果有邀请人,注册成功后建立推荐关系
// if (inviterId.value) {
// try {
// const userId = localStorage.getItem('UserId');
// if (userId) {
// await bindUserReferee({
// dealerId: inviterId.value,
// userId: Number(userId),
// level: 1
// });
// message.success('注册成功,已建立推荐关系');
// }
// } catch (e) {
// console.error('建立推荐关系失败:', e);
// // 不影响注册流程,只是推荐关系建立失败
// }
// }
//
// setTimeout(() => {
// // 登录成功
// message.success(msg);
// loading.value = false;
// resetFields();
// cleanPageTabs();
// goHome();
// }, 2000)
// })
// .catch(() => {
// message.error('该手机号码已经被注册');
// loading.value = false;
// });
})
.catch(() => {});
loading.value = true;
try {
// 通过短信验证码完成注册inviter 作为推荐人ID传给后端
const msg = await registerUser({
tenantId: form.tenantId,
phone: form.phone,
username: form.phone,
code: form.code?.trim(),
// 后端若用 Java boolean 命名为 `isAdmin`,序列化/反序列化可能会被映射成 `admin`
// 这里双写以兼容不同字段名,避免后端拿到 null 导致 NPE。
isAdmin: true,
...({ admin: true } as any),
dealerId: inviterId.value
});
// 注册成功后自动登录,再建立绑定关系(避免未登录时后端取 role 为空)
await loginBySms({
tenantId: form.tenantId,
phone: form.phone,
code: form.code?.trim(),
remember: !!form.remember
});
// 建立绑定关系(失败不影响主流程)
try {
await tryBindInviteRelation();
} catch (e: any) {
message.warning(
`注册成功,但建立推荐关系失败:${e?.message || '未知错误'}`
);
}
message.success(msg);
resetFields();
cleanPageTabs();
goHome();
} catch (e: any) {
message.error(e?.message || '注册失败');
} finally {
loading.value = false;
}
};
/* 获取图形验证码 */
@@ -469,13 +449,51 @@
// 检查URL参数中的邀请人ID
onMounted(() => {
const urlParams = new URLSearchParams(window.location.search);
const inviterParam = urlParams.get('inviter');
if (inviterParam) {
inviterId.value = Number(inviterParam);
// 优先从链接参数中拿 tenantId新用户未登录时用于后端识别租户/初始化角色)
const tenantIdParam = (unref(currentRoute).query.tenantId ??
new URLSearchParams(window.location.search).get('tenantId')) as
| string
| undefined;
if (tenantIdParam != null && tenantIdParam !== '') {
const tid = Number.parseInt(String(tenantIdParam).trim(), 10);
if (!Number.isNaN(tid) && tid > 0) {
form.tenantId = tid;
// 让 request 拦截器也能带上 TenantId
localStorage.setItem('TenantId', String(tid));
showTenantId.value = false;
}
}
const inviterParam = (unref(currentRoute).query.inviter ??
new URLSearchParams(window.location.search).get('inviter')) as
| string
| undefined;
if (inviterParam != null && inviterParam !== '') {
// 容错:有些场景会把链接后面带上逗号/空格等(例如复制文本),这里提取首段数字
const raw = String(inviterParam).trim();
const id = Number.parseInt(raw.match(/\d+/)?.[0] || '', 10);
if (!Number.isNaN(id) && id > 0) {
inviterId.value = id;
form.dealerId = id;
}
}
// 已登录用户通过邀请链接访问时,补绑一次推荐关系
tryBindInviteRelation();
});
onBeforeUnmount(() => {
if (countdownTimer) {
clearInterval(countdownTimer);
countdownTimer = null;
}
});
// 地图选择回调(本页面不使用地图能力,避免模板引用报错)
const onDone = () => {
showMap.value = false;
};
watch(
currentRoute,
() => {

View File

@@ -169,10 +169,21 @@
return props.inviterId || Number(localStorage.getItem('UserId'));
});
// 邀请链接需要带上 tenantId避免未登录用户打开链接时后端无法识别租户导致角色/权限初始化失败
const tenantId = computed(() => {
const tid = localStorage.getItem('TenantId');
return tid ? Number(tid) : undefined;
});
// 生成邀请链接
const invitationLink = computed(() => {
const baseUrl = window.location.origin;
return `${baseUrl}/dealer/register?inviter=${inviterId.value}`;
const params = new URLSearchParams();
params.set('inviter', String(inviterId.value));
if (tenantId.value) {
params.set('tenantId', String(tenantId.value));
}
return `${baseUrl}/dealer/register?${params.toString()}`;
});
// 复制链接