feat(shop): 添加邀请注册功能并优化注册流程
- 新增邀请注册弹窗组件,用于生成邀请链接和二维码 - 在注册页面添加邀请信息显示,引导用户通过邀请链接注册 - 实现注册成功后自动建立推荐关系的功能 - 优化注册表单,支持通过邀请链接直接进入注册页面
This commit is contained in:
@@ -35,6 +35,14 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="loginType === 'account'">
|
<template v-if="loginType === 'account'">
|
||||||
|
<!-- 邀请信息显示 -->
|
||||||
|
<div v-if="inviterId" style="margin-bottom: 16px; padding: 12px; background: #f6ffed; border: 1px solid #b7eb8f; border-radius: 4px;">
|
||||||
|
<div style="color: #52c41a; font-size: 14px;">
|
||||||
|
<check-circle-outlined style="margin-right: 4px;" />
|
||||||
|
您正在通过邀请链接注册,注册成功后将自动建立推荐关系
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<a-form-item name="phone">
|
<a-form-item name="phone">
|
||||||
<a-input
|
<a-input
|
||||||
allow-clear
|
allow-clear
|
||||||
@@ -227,12 +235,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {ref, reactive, unref, watch} from 'vue';
|
import {ref, reactive, unref, watch, onMounted} from 'vue';
|
||||||
import {useI18n} from 'vue-i18n';
|
import {useI18n} from 'vue-i18n';
|
||||||
import {useRouter} from 'vue-router';
|
import {useRouter} from 'vue-router';
|
||||||
import {getTenantId} from '@/utils/domain';
|
import {getTenantId} from '@/utils/domain';
|
||||||
import {Form, message} from 'ant-design-vue';
|
import {Form, message} from 'ant-design-vue';
|
||||||
import { AimOutlined } from '@ant-design/icons-vue';
|
import { AimOutlined, CheckCircleOutlined, CloseCircleOutlined } from '@ant-design/icons-vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import {goHomeRoute, cleanPageTabs} from '@/utils/page-tab-util';
|
import {goHomeRoute, cleanPageTabs} from '@/utils/page-tab-util';
|
||||||
import {loginBySms, getCaptcha} from '@/api/passport/login';
|
import {loginBySms, getCaptcha} from '@/api/passport/login';
|
||||||
@@ -248,6 +256,7 @@ import {push} from "@/utils/common";
|
|||||||
import {useThemeStore} from "@/store/modules/theme";
|
import {useThemeStore} from "@/store/modules/theme";
|
||||||
import {CmsWebsite} from "@/api/cms/cmsWebsite/model";
|
import {CmsWebsite} from "@/api/cms/cmsWebsite/model";
|
||||||
import {createCmsWebSite} from "@/api/layout";
|
import {createCmsWebSite} from "@/api/layout";
|
||||||
|
import { bindUserReferee } from '@/api/user/referee';
|
||||||
|
|
||||||
const useForm = Form.useForm;
|
const useForm = Form.useForm;
|
||||||
const {currentRoute} = useRouter();
|
const {currentRoute} = useRouter();
|
||||||
@@ -382,6 +391,8 @@ const countdownTime = ref(0);
|
|||||||
let countdownTimer: number | null = null;
|
let countdownTimer: number | null = null;
|
||||||
// 是否显示地图选择弹窗
|
// 是否显示地图选择弹窗
|
||||||
const showMap = ref(false);
|
const showMap = ref(false);
|
||||||
|
// 邀请人ID
|
||||||
|
const inviterId = ref<number>();
|
||||||
|
|
||||||
// 表格选中数据
|
// 表格选中数据
|
||||||
const formRef = ref<FormInstance | null>(null);
|
const formRef = ref<FormInstance | null>(null);
|
||||||
@@ -548,8 +559,28 @@ const submit = () => {
|
|||||||
.validate()
|
.validate()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
||||||
|
// 普通注册流程
|
||||||
createCmsWebSite(form)
|
createCmsWebSite(form)
|
||||||
.then((msg) => {
|
.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(() => {
|
setTimeout(() => {
|
||||||
// 登录成功
|
// 登录成功
|
||||||
message.success(msg);
|
message.success(msg);
|
||||||
@@ -593,6 +624,15 @@ const changeCaptcha = () => {
|
|||||||
// 首次加载
|
// 首次加载
|
||||||
changeCaptcha();
|
changeCaptcha();
|
||||||
|
|
||||||
|
// 检查URL参数中的邀请人ID
|
||||||
|
onMounted(() => {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const inviterParam = urlParams.get('inviter');
|
||||||
|
if (inviterParam) {
|
||||||
|
inviterId.value = Number(inviterParam);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
currentRoute,
|
currentRoute,
|
||||||
() => {
|
() => {
|
||||||
|
|||||||
146
src/views/shop/shopAdmin/components/invitation-modal.vue
Normal file
146
src/views/shop/shopAdmin/components/invitation-modal.vue
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
:width="500"
|
||||||
|
:visible="visible"
|
||||||
|
:footer="null"
|
||||||
|
title="邀请注册"
|
||||||
|
@update:visible="updateVisible"
|
||||||
|
>
|
||||||
|
<div style="text-align: center">
|
||||||
|
<div style="margin-bottom: 20px">
|
||||||
|
<a-typography-title :level="4">邀请新用户注册</a-typography-title>
|
||||||
|
<a-typography-text type="secondary">
|
||||||
|
分享以下链接或二维码,邀请用户注册并自动建立推荐关系
|
||||||
|
</a-typography-text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 邀请链接 -->
|
||||||
|
<div style="margin-bottom: 20px">
|
||||||
|
<a-input
|
||||||
|
:value="invitationLink"
|
||||||
|
readonly
|
||||||
|
style="margin-bottom: 8px"
|
||||||
|
>
|
||||||
|
<template #addonAfter>
|
||||||
|
<a-button type="link" size="small" @click="copyLink">
|
||||||
|
复制链接
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 二维码 -->
|
||||||
|
<div style="margin-bottom: 20px">
|
||||||
|
<div style="display: inline-block; padding: 10px; background: white; border-radius: 4px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1)">
|
||||||
|
<QrCode
|
||||||
|
:value="invitationLink"
|
||||||
|
:size="200"
|
||||||
|
:margin="10"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 使用说明 -->
|
||||||
|
<div style="text-align: left; background: #f5f5f5; padding: 12px; border-radius: 4px; margin-bottom: 20px">
|
||||||
|
<div style="font-weight: 500; margin-bottom: 8px">使用说明:</div>
|
||||||
|
<div style="font-size: 12px; color: #666; line-height: 1.5">
|
||||||
|
1. 复制邀请链接发送给用户,或让用户扫描二维码<br>
|
||||||
|
2. 用户点击链接进入注册页面<br>
|
||||||
|
3. 用户完成注册后,系统自动建立推荐关系<br>
|
||||||
|
4. 您可以在"推荐关系管理"中查看邀请结果
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<div>
|
||||||
|
<a-space>
|
||||||
|
<a-button @click="downloadQRCode">下载二维码</a-button>
|
||||||
|
<a-button type="primary" @click="copyLink">复制链接</a-button>
|
||||||
|
<a-button @click="goToRefereeManage">查看推荐关系</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, watch } from 'vue';
|
||||||
|
import { message } from 'ant-design-vue/es';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import QrCode from '@/components/QrCode/index.vue';
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:visible', visible: boolean): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
visible: boolean;
|
||||||
|
inviterId?: number; // 邀请人ID(当前登录用户ID)
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// 生成邀请链接
|
||||||
|
const invitationLink = computed(() => {
|
||||||
|
const baseUrl = window.location.origin;
|
||||||
|
const inviterId = props.inviterId || localStorage.getItem('UserId');
|
||||||
|
return `${baseUrl}/register?inviter=${inviterId}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 复制链接
|
||||||
|
const copyLink = async () => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(invitationLink.value);
|
||||||
|
message.success('邀请链接已复制到剪贴板');
|
||||||
|
} catch (e) {
|
||||||
|
// 降级方案
|
||||||
|
const textArea = document.createElement('textarea');
|
||||||
|
textArea.value = invitationLink.value;
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
message.success('邀请链接已复制到剪贴板');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 下载二维码
|
||||||
|
const downloadQRCode = () => {
|
||||||
|
try {
|
||||||
|
// 查找二维码canvas元素
|
||||||
|
const canvas = document.querySelector('.ant-modal-body canvas') as HTMLCanvasElement;
|
||||||
|
if (canvas) {
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.download = `邀请注册二维码.png`;
|
||||||
|
link.href = canvas.toDataURL();
|
||||||
|
link.click();
|
||||||
|
message.success('二维码已下载');
|
||||||
|
} else {
|
||||||
|
message.error('下载失败,请稍后重试');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
message.error('下载失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 跳转到推荐关系管理
|
||||||
|
const goToRefereeManage = () => {
|
||||||
|
router.push('/shop/user-referee');
|
||||||
|
updateVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新visible
|
||||||
|
const updateVisible = (value: boolean) => {
|
||||||
|
emit('update:visible', value);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
:deep(.ant-typography-title) {
|
||||||
|
margin-bottom: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-input-group-addon) {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
>
|
>
|
||||||
<template #toolbar>
|
<template #toolbar>
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-button type="primary" class="ele-btn-icon" @click="openEdit()">
|
<a-button type="primary" class="ele-btn-icon" @click="openInvitation">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<plus-outlined/>
|
<plus-outlined/>
|
||||||
</template>
|
</template>
|
||||||
@@ -105,6 +105,8 @@
|
|||||||
<user-import v-model:visible="showImport" @done="reload"/>
|
<user-import v-model:visible="showImport" @done="reload"/>
|
||||||
<!-- 用户详情 -->
|
<!-- 用户详情 -->
|
||||||
<user-info v-model:visible="showInfo" :data="current" @done="reload"/>
|
<user-info v-model:visible="showInfo" :data="current" @done="reload"/>
|
||||||
|
<!-- 邀请注册弹窗 -->
|
||||||
|
<invitation-modal v-model:visible="showInvitation" :inviter-id="currentUserId"/>
|
||||||
</a-page-header>
|
</a-page-header>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -128,6 +130,7 @@ import {messageLoading, formatNumber} from 'ele-admin-pro/es';
|
|||||||
import UserEdit from './components/user-edit.vue';
|
import UserEdit from './components/user-edit.vue';
|
||||||
import UserImport from './components/user-import.vue';
|
import UserImport from './components/user-import.vue';
|
||||||
import UserInfo from './components/user-info.vue';
|
import UserInfo from './components/user-info.vue';
|
||||||
|
import InvitationModal from './components/invitation-modal.vue';
|
||||||
import {toDateString} from 'ele-admin-pro';
|
import {toDateString} from 'ele-admin-pro';
|
||||||
import {
|
import {
|
||||||
pageUsers,
|
pageUsers,
|
||||||
@@ -165,8 +168,12 @@ const showEdit = ref(false);
|
|||||||
const showInfo = ref(false);
|
const showInfo = ref(false);
|
||||||
// 是否显示用户导入弹窗
|
// 是否显示用户导入弹窗
|
||||||
const showImport = ref(false);
|
const showImport = ref(false);
|
||||||
|
// 是否显示邀请注册弹窗
|
||||||
|
const showInvitation = ref(false);
|
||||||
const userType = ref<number>();
|
const userType = ref<number>();
|
||||||
const searchText = ref('');
|
const searchText = ref('');
|
||||||
|
// 当前用户ID
|
||||||
|
const currentUserId = ref<number>();
|
||||||
|
|
||||||
// 加载角色
|
// 加载角色
|
||||||
const roles = ref<any[]>([]);
|
const roles = ref<any[]>([]);
|
||||||
@@ -410,6 +417,18 @@ const query = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 打开邀请注册弹窗 */
|
||||||
|
const openInvitation = () => {
|
||||||
|
// 获取当前用户ID
|
||||||
|
const userId = localStorage.getItem('UserId');
|
||||||
|
if (userId) {
|
||||||
|
currentUserId.value = Number(userId);
|
||||||
|
showInvitation.value = true;
|
||||||
|
} else {
|
||||||
|
message.error('获取用户信息失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => router.currentRoute.value.query,
|
() => router.currentRoute.value.query,
|
||||||
() => {
|
() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user