feat(register): 完善经销商注册功能并优化邀请机制
- 将登录方法替换为注册方法,实现完整的用户注册流程 - 添加短信验证码发送失败的错误处理和提示 - 实现邀请推荐关系绑定功能,支持注册后自动建立推荐关系 - 优化URL参数解析,支持tenantId和inviter参数的灵活获取 - 添加倒计时清理逻辑,防止内存泄漏 - 更新表单验证规则,移除不必要的字段验证 - 在邀请链接中添加tenantId参数,确保未登录用户能正确识别租户 - 添加注册成功后的自动登录和推荐关系建立流程
This commit is contained in:
@@ -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,10 +311,10 @@
|
||||
return;
|
||||
}
|
||||
codeLoading.value = true;
|
||||
sendSmsCaptcha({ phone: form.phone }).then(() => {
|
||||
sendSmsCaptcha({ phone: form.phone })
|
||||
.then(() => {
|
||||
message.success('短信验证码发送成功, 请注意查收!');
|
||||
visible.value = false;
|
||||
codeLoading.value = false;
|
||||
countdownTime.value = 30;
|
||||
// 开始对按钮进行倒计时
|
||||
countdownTimer = window.setInterval(() => {
|
||||
@@ -352,6 +324,12 @@
|
||||
}
|
||||
countdownTime.value--;
|
||||
}, 1000);
|
||||
})
|
||||
.catch((e: any) => {
|
||||
message.error(e?.message || '短信验证码发送失败');
|
||||
})
|
||||
.finally(() => {
|
||||
codeLoading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
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
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
/* 保存编辑 */
|
||||
@@ -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,
|
||||
() => {
|
||||
|
||||
@@ -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()}`;
|
||||
});
|
||||
|
||||
// 复制链接
|
||||
|
||||
Reference in New Issue
Block a user