Merge remote-tracking branch 'origin/main' into feature/0905_登录注册流程重构

# Conflicts:
#	src/App.vue
#	src/views/components/login/index.vue
#	src/views/components/management/person/index.vue
#	src/views/login/style.scss
This commit is contained in:
rd
2025-09-10 16:16:34 +08:00
233 changed files with 5564 additions and 6102 deletions

View File

@ -2,40 +2,44 @@
<div class="bg-#fff rounded-8px w-100% py-0 px-20px pb-24px">
<div class="title-row">
<span class="title">账号管理</span>
<a-button type="outline" class="add-account-button" @click="handleAddAccount">添加子账号</a-button>
<Button type="primary" ghost class="add-account-button" @click="handleAddAccount">添加子账号</Button>
</div>
<a-table
:columns="columns"
:data="dataSource"
<Table
:dataSource="dataSource"
:pagination="pagination"
:showSorterTooltip="false"
class="mt-8px"
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
@change="handleTableChange"
>
<template #empty>
<Table.Column title="手机号" dataIndex="mobile">
<template #customRender="{ record }">
<div class="flex item-center pt-13px pb-13px">
<span class="mr-4px">{{ record.mobile }}</span>
<Tag v-if="record.type === 0" class="primary-account">主账号</Tag>
<Tag v-else class="sub-account">子账号</Tag>
</div>
</template>
</Table.Column>
<Table.Column title="操作" dataIndex="action" width="120">
<template #customRender="{ record }">
<Button
v-if="record.type !== 0"
class="delete-button"
size="small"
type="primary"
ghost
danger
@click="openDeleteModal(record)"
>
删除
</Button>
</template>
</Table.Column>
<template #emptyText>
<NoData />
</template>
<template #mobile="{ record }">
<div class="flex item-center pt-13px pb-13px">
<span class="mr-4px">{{ record.mobile }}</span>
<a-tag v-if="record.type === 0" class="primary-account">主账号</a-tag>
<a-tag v-else class="sub-account">子账号</a-tag>
</div>
</template>
<template #action="{ record }">
<a-button
v-if="record.type !== 0"
class="delete-button"
size="mini"
type="outline"
status="danger"
@click="openDeleteModal(record)"
>
删除
</a-button>
</template>
</a-table>
<Modal v-model:visible="addAccountVisible" width="480px" title="添加子账号" :okText="okText" @ok="handleOk">
</Table>
<Modal v-model:open="addAccountVisible" centered width="480px" title="添加子账号" :okText="okText" @ok="handleOk" >
<div v-if="canAddAccount" class="add-account-container">
<h2 class="add-account-title">生成企业专属链接成员通过访问即可注册并加入企业账号</h2>
<p class="add-account-subtitle">子账号可独立登录权限继承主账号配置</p>
@ -52,15 +56,15 @@
<p class="cannot-add-account-subtitle">如需添加更多子账号您可联系销售人员进行购买和权限扩展</p>
</div>
</Modal>
<CustomerServiceModal v-model:visible="customerServiceVisible" />
<DeleteModal v-model:visible="deleteVisible" :title="deleteTitle" @ok="handleDelete">
<p class="delete-modal-content">删除后该账号将无法登录您的企业</p>
<CustomerServiceModal v-model:open="customerServiceVisible" centered/>
<DeleteModal v-model:open="deleteVisible" centered :content="deleteTitle" @ok="handleDelete" @cancel="deleteVisible = false">
</DeleteModal>
</div>
</template>
<script setup lang="ts">
import Container from '@/components/container.vue';
import { ref, onMounted, reactive, computed } from 'vue';
import { Button, Table, message, Tag } from 'ant-design-vue';
import { fetchSubAccountPage, removeEnterpriseAccount, getEnterpriseInviteCode } from '@/api/all';
import Modal from '@/components/modal.vue';
import DeleteModal from '@/components/delete-modal.vue';
@ -82,10 +86,10 @@ const columns = [
const dataSource = ref([]);
const pagination = reactive({
total: 0,
showPageSize: true,
showTotal: true,
defaultCurrent: 1,
defaultPageSize: 10,
showSizeChanger: true,
showTotal: (total: number, range: [number, number]) => `${total} 条记录`,
current: 1,
pageSize: 10,
});
const params = reactive({
@ -120,13 +124,12 @@ const currentSelectAccount = ref();
const { copy, copied, isSupported } = useClipboard({ source: inviteUrl });
function handlePageChange(current: number) {
params.page = current;
getSubAccount();
}
function handlePageSizeChange(pageSize: number) {
params.page_size = pageSize;
function handleTableChange(paginationInfo: any, filters: any, sorter: any) {
params.page = paginationInfo.current;
params.page_size = paginationInfo.pageSize;
// 更新分页状态
pagination.current = paginationInfo.current;
pagination.pageSize = paginationInfo.pageSize;
getSubAccount();
}
@ -155,13 +158,13 @@ function handleOk() {
return;
}
if (!isSupported) {
AMessage.error('您的浏览器不支持复制,请手动复制!');
message.error('您的浏览器不支持复制,请手动复制!');
}
copy(inviteUrl.value);
if (!copied) {
AMessage.error('复制失败,请手动复制!');
message.error('复制失败,请手动复制!');
}
AMessage.success('复制成功!');
message.success('复制成功!');
}
function openDeleteModal(record: { id: number; mobile: string }) {
@ -172,7 +175,7 @@ function openDeleteModal(record: { id: number; mobile: string }) {
async function handleDelete() {
await removeEnterpriseAccount(currentSelectAccount.value.id);
AMessage.success('移除成功!');
message.success('移除成功!');
}
onMounted(() => {
@ -275,14 +278,7 @@ onMounted(() => {
color: var(--Text-2, rgba(60, 64, 67, 1));
}
}
.delete-modal-content {
margin-left: 34px;
margin-top: 16px;
font-family: $font-family-medium;
font-weight: 400;
font-size: 12px;
color: var(--Text-2, rgba(60, 64, 67, 1));
}
.title-row {
display: flex;
height: 64px;

View File

@ -3,39 +3,53 @@
<div class="title-row">
<span class="title">企业信息</span>
</div>
<a-table :columns="columns" :data="dataSource" :pagination="false" class="mt-8px">
<template #empty>
<Table :dataSource="dataSource" :pagination="false" :showSorterTooltip="false" class="mt-8px">
<Table.Column title="企业信息" dataIndex="info">
<template #customRender="{ record }">
{{ record.name }}
</template>
</Table.Column>
<Table.Column title="操作" dataIndex="action" width="120">
<template #customRender>
<Button class="edit-button" size="small" type="primary" ghost @click="handleUpdate">修改</Button>
</template>
</Table.Column>
<template #emptyText>
<NoData />
</template>
<template #info="{ record }">
{{ record.name }}
</template>
<template #action>
<a-button class="edit-button" size="mini" type="outline" @click="handleUpdate">修改</a-button>
</template>
</a-table>
<Modal v-model:visible="infoVisible" width="480px" title="修改企业名称" :okText="okText" @ok="handleOk">
</Table>
<Modal
v-model:open="infoVisible"
width="480px"
centered
title="修改企业名称"
:okText="okText"
@ok="handleOk"
cancelText="取消"
>
<p class="tips">
企业名称只能修改2次请谨慎操作<span
>剩余{{ enterpriseInfo!.update_name_quota - enterpriseInfo!.used_update_name_count }}
</span>
</p>
<a-form
<Form
:model="form"
:rules="rules"
class="form"
:label-col-props="{ span: 6, offset: 0 }"
:wrapper-col-props="{ span: 18, offset: 0 }"
label-align="left"
>
<a-form-item required field="name" label="新企业名称">
<a-input v-model.trim="form.name" size="small" :disabled="!canUpdate" placeholder="请输入新企业名称" />
</a-form-item>
</a-form>
<FormItem required name="name" label="新企业名称">
<Input v-model:value.trim="form.name" size="small" :disabled="!canUpdate" placeholder="请输入新企业名称" />
</FormItem>
</Form>
</Modal>
<CustomerServiceModal v-model:visible="customerServiceVisible" />
<CustomerServiceModal v-model:open="customerServiceVisible" centered />
</div>
</template>
<script setup lang="ts">
import { Button, Form, FormItem, Input, Table, message } from 'ant-design-vue';
import Container from '@/components/container.vue';
import Modal from '@/components/modal.vue';
import { ref, reactive, computed } from 'vue';
@ -48,6 +62,16 @@ const form = reactive({
name: '',
});
const rules = {
name: [
{
required: true,
message: '请输入新企业名称',
trigger: ['blur'],
},
],
};
const enterpriseInfo = computed(() => {
return store.enterpriseInfo ?? {};
});
@ -98,7 +122,7 @@ async function handleOk() {
await updateEnterpriseName({ name: form.name });
store.setEnterpriseName(form.name);
store.incUsedUpdateNameCount();
AMessage.success('修改成功!');
message.success('修改成功!');
}
</script>

View File

@ -1,32 +1,37 @@
<template>
<div class="bg-#fff rounded-16px w-100% p-36px">
<p class="title mb-32px">个人信息</p>
<a-table :columns="columns" :data="dataSource" :pagination="false" class="mt-8px">
<template #empty>
<Table :dataSource="dataSource" :pagination="false" :showSorterTooltip="false" class="mt-8px">
<Table.Column title="用户信息" dataIndex="info">
<template #customRender="{ record }">
<div class="pt-3px pb-3px">
<Avatar :src="record.head_image" :size="32" />
{{ record.name || '-' }}
<icon-edit size="13" class="ml-8px" @click="openEditInfoModal" />
</div>
</template>
</Table.Column>
<Table.Column title="手机号" dataIndex="mobile">
<template #customRender="{ record }">
{{ record.mobile.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') }}
<icon-edit size="13" class="ml-8px" @click="openEditMobileModal" />
</template>
</Table.Column>
<template #emptyText>
<NoData />
</template>
<template #info="{ record }">
<div class="pt-3px pb-3px">
<a-avatar :image-url="record.head_image" :size="32" />
{{ record.name || '-' }}
<icon-edit size="13" class="ml-8px" @click="openEditInfoModal" />
</div>
</template>
<template #mobile="{ record }">
{{ record.mobile.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') }}
<icon-edit size="13" class="ml-8px" @click="openEditMobileModal" />
</template>
</a-table>
<Modal v-model:visible="infoVisible" title="修改用户信息" @ok="handleSubmitUserInfo">
<a-form
</Table>
<Modal v-model:open="infoVisible" centered title="修改用户信息" @ok="handleSubmitUserInfo">
<Form
class="form"
:rules="rules"
:model="userInfoForm"
:label-col-props="{ span: 3, offset: 0 }"
:wrapper-col-props="{ span: 21, offset: 0 }"
>
<a-form-item field="head_image" label="头像">
<FormItem name="head_image" label="头像">
<div class="flex items-center">
<a-avatar :image-url="userInfoForm.file_url" :size="48" />
<Avatar :src="userInfoForm.file_url" :size="48" />
<span class="upload-button" @click="triggerFileInput">
<input
ref="uploadInputRef"
@ -35,40 +40,40 @@
style="display: none"
@change="handleFileChange"
/>
<a-button><icon-upload />上传新头像</a-button>
<Button><icon-upload />上传新头像</Button>
</span>
</div>
</a-form-item>
<a-form-item field="name" label="昵称">
<a-input v-model.trim="userInfoForm.name" placeholder="请输入昵称" />
</a-form-item>
</a-form>
</FormItem>
<FormItem name="name" label="昵称">
<Input v-model:value="userInfoForm.name" placeholder="请输入昵称" />
</FormItem>
</Form>
</Modal>
<Modal v-model:visible="imageVisible" title="头像裁剪">
<Modal v-model:open="imageVisible" centered title="头像裁剪">
<VueCropper></VueCropper>
</Modal>
<Modal v-model:visible="mobileVisible" title="修改手机号" @ok="handleUpdateMobile">
<a-form
<Modal v-model:open="mobileVisible" centered title="修改手机号" @ok="handleUpdateMobile">
<Form
ref="formRef"
:model="form"
class="form"
:rules="formRules"
:label-col-props="{ span: 5, offset: 0 }"
:wrapper-col-props="{ span: 19, offset: 0 }"
label-align="left"
labelAlign="right"
:labelCol="{ span: 4 }"
:wrapperCol="{ span: 20 }"
>
<a-form-item required field="mobile" label="新手机号">
<a-input v-model.trim="form.mobile" size="small" placeholder="请输入新的手机号" />
</a-form-item>
<a-form-item required field="captcha" label="获取验证码">
<a-input v-model.trim="form.captcha" size="small" placeholder="请输入验证码">
<FormItem required name="mobile" label="新手机号">
<Input v-model:value="form.mobile" size="small" placeholder="请输入新的手机号" />
</FormItem>
<FormItem required name="captcha" label="获取验证码">
<Input v-model:value="form.captcha" size="small" placeholder="请输入验证码">
<template #suffix>
<span v-if="countdown <= 0" @click="sendCaptcha">发送验证码</span>
<span v-else>{{ countdown }}s</span>
</template>
</a-input>
</a-form-item>
</a-form>
</Input>
</FormItem>
</Form>
<PuzzleVerification
:show="verificationVisible"
@submit="handleVerificationSubmit"
@ -78,6 +83,7 @@
</div>
</template>
<script setup lang="ts">
import { Button, Form, FormItem, Input, Table, message, Avatar } from 'ant-design-vue';
import Container from '@/components/container.vue';
import Modal from '@/components/modal.vue';
import PuzzleVerification from '@/views/login/components/PuzzleVerification.vue';
@ -122,35 +128,32 @@ const dataSource = computed(() => {
const formRules = {
mobile: [
{
required: true,
message: '请填写手机号',
trigger: ['blur', 'change'],
},
{
validator: (value: string, callback: (error?: string) => void) => {
if (!/^1[3-9]\d{9}$/.test(value)) {
callback('手机号格式不正确');
} else {
callback();
validator: (_rule: any, value: string) => {
if (!value) {
return Promise.reject('请填写手机号');
}
if (!/^1[3-9]\d{9}$/.test(value)) {
return Promise.reject('手机号格式不正确');
}
return Promise.resolve();
},
required: true,
trigger: ['blur', 'change'],
},
],
captcha: [
{
required: true,
message: '请填写验证码',
trigger: ['blur', 'change'],
},
{
validator: (value: string, callback: (error?: string) => void) => {
if (!/^\d{6}$/.test(value)) {
callback('验证码必须是6位数字');
} else {
callback();
validator: (rule, value) => {
if (!value) {
return Promise.reject('请填写验证码');
}
if (!/^\d{6}$/.test(value)) {
return Promise.reject('验证码必须是6位数字');
}
return Promise.resolve();
},
trigger: ['blur', 'change'],
},
],
@ -203,7 +206,7 @@ function openEditMobileModal() {
async function handleSubmitUserInfo() {
await updateMyInfo(userInfoForm);
AMessage.success('修改成功!');
message.success('修改成功!');
}
async function sendCaptcha() {
@ -213,7 +216,7 @@ async function sendCaptcha() {
verificationVisible.value = true;
isSendCaptcha.value = true;
}
AMessage.error('请填写正确的手机号!');
message.error('请填写正确的手机号!');
} catch (error) {
console.log('手机号验证失败:', error);
}
@ -231,7 +234,7 @@ function beginCountdown() {
async function handleVerificationSubmit() {
await sendUpdateMobileCaptcha({ mobile: form.mobile });
AMessage.success('发送成功');
message.success('发送成功');
verificationVisible.value = false;
countdown.value = 60;
beginCountdown();
@ -239,13 +242,13 @@ async function handleVerificationSubmit() {
async function handleUpdateMobile() {
if (!isSendCaptcha.value) {
AMessage.error('请先获取验证码!');
message.error('请先获取验证码!');
return false;
}
const res = await formRef.value.validate();
if (res === true || res === undefined) {
await updateMobile(form);
AMessage.success('修改成功!');
message.success('修改成功!');
}
}