feat(enterprise): 添加子账号管理功能

- 新增子账号管理页面,包括子账号列表、添加子账号和删除子账号功能- 实现获取子账号列表、生成企业邀请码和移除子账号的 API 接口
- 添加删除确认模态框组件- 优化企业信息展示页面布局
This commit is contained in:
2025-06-20 11:24:33 +08:00
parent 7969167c70
commit 7d47dadc6d
5 changed files with 348 additions and 0 deletions

View File

@ -140,3 +140,13 @@ export const fetchSubAccountPage = (params: any) => {
export const fetchImageUploadFile = (params: any) => {
return Http.get(`/v1/oss/image-pre-signed-url`, params);
};
// 移除企业子账号
export const removeEnterpriseAccount = (userId: number) => {
return Http.delete(`/v1/enterprises/users/${userId}`, { headers: { 'enterprise-id': 1 } });
};
// 获取企业邀请码
export const getEnterpriseInviteCode = () => {
return Http.get(`/v1/enterprises/invite-code`, {}, { headers: { 'enterprise-id': 1 } });
};

3
src/assets/warning.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.66675 9.99984C1.66675 5.39746 5.39771 1.6665 10.0001 1.6665C14.6025 1.6665 18.3334 5.39746 18.3334 9.99984C18.3334 14.6022 14.6025 18.3332 10.0001 18.3332C5.39771 18.3332 1.66675 14.6022 1.66675 9.99984ZM9.16675 12.4998V14.1665H10.8334V12.4998H9.16675ZM10.8334 11.6665L10.8334 5.83317L9.16675 5.83317L9.16675 11.6665H10.8334Z" fill="#FFAE00"/>
</svg>

After

Width:  |  Height:  |  Size: 499 B

View File

@ -0,0 +1,58 @@
<template>
<a-modal modal-class="delete-modal" body-class="body" cancel-text="返回" ok-text="确定删除" v-bind="$attrs">
<h2 class="delete-modal-title flex item-center">
<img src="@/assets/warning.svg" alt="" />
{{ $attrs.title }}
</h2>
<slot></slot>
</a-modal>
</template>
<script setup lang="ts">
</script>
<style lang="less">
:deep(.arco-btn-status-danger) {
background-color: red !important;
width: 1000px !important;
}
.delete-modal {
.arco-modal-header {
display: none;
}
.delete-modal-title {
margin-top: 24px;
font-family: Alibaba PuHuiTi, serif;
font-weight: 400;
font-size: 14px;
color: var(--Text-1, rgba(33, 31, 36, 1));
img {
width: 20px;
height: 20px;
margin-right: 12px;
}
}
.arco-modal-footer {
border-top: none;
:first-child {
border: 1px solid var(--BG-500, rgba(177, 178, 181, 1));
border-radius: 4px;
padding: 7px 20px;
font-family: Alibaba PuHuiTi, serif;
font-weight: 400;
font-size: 14px;
}
:last-child {
border-radius: 4px;
padding: 7px 20px;
font-family: Alibaba PuHuiTi, serif;
font-weight: 400;
font-size: 14px;
margin-left: 16px;
border-color: var(--Functional-Danger-6, rgba(246, 75, 49, 1)) !important;
background-color: var(--Functional-Danger-6, rgba(246, 75, 49, 1)) !important;
}
}
}
.body {
padding: 0 24px;
}
</style>

View File

@ -0,0 +1,276 @@
<template>
<Container title="账号信息" class="container mt-24px">
<template #header>
<a-button type="outline" class="add-account-button" @click="handleAddAccount">添加子账号</a-button>
</template>
<a-table
:columns="columns"
:data="data"
:pagination="pagination"
class="mt-16px"
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
>
<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">
<div v-if="canAddAccount" class="add-account-container">
<h2 class="add-account-title">生成企业专属链接成员通过访问即可注册并加入企业账号</h2>
<p class="add-account-subtitle">子账号可独立登录权限继承主账号配置</p>
<div class="add-account-body">
<p>用该链接加入企业吧</p>
<p>{{ inviteUrl }}</p>
</div>
</div>
<div v-else class="add-account-container">
<h2 class="cannot-add-account-title flex item-center">
<img src="@/assets/warning.svg" alt="">
当前可用子账号数为0
</h2>
<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>
</DeleteModal>
</Container>
</template>
<script setup lang="ts">
import Container from '@/components/container.vue';
import { ref, onMounted, reactive, computed } from 'vue';
import { fetchSubAccountPage, removeEnterpriseAccount, getEnterpriseInviteCode } from '@/api/all';
import Modal from '@/components/modal.vue';
import DeleteModal from '@/components/delete-modal.vue';
import CustomerServiceModal from '@/components/customer-service-modal.vue';
import { useClipboard } from '@vueuse/core'
const columns = [
{
title: '手机号',
slotName: 'mobile',
},
{
title: '操作',
slotName: 'action',
},
];
const data = ref([]);
const pagination = reactive({
total: 0,
showPageSize: true,
showTotal: true,
defaultCurrent: 1,
defaultPageSize: 10,
});
const params = reactive({
page_size: 10,
page: 1,
});
const inviteUrl = ref('');
const addAccountVisible = ref(false);
const deleteVisible = ref(false);
const deleteTitle = ref('');
const enterpriseInfo = reactive({
name: '123321',
sub_account_quota: 2,
used_sub_account_count: 1,
});
const okText = computed(() => {
if (!canAddAccount.value) {
return '联系客服';
}
return '复制邀请链接';
});
const customerServiceVisible = ref(false);
const canAddAccount = computed(() => {
return enterpriseInfo.sub_account_quota > enterpriseInfo.used_sub_account_count;
});
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;
getSubAccount();
}
async function getSubAccount() {
const res = await fetchSubAccountPage(params);
pagination.total = res.total;
data.value = res.data;
}
async function handleAddAccount() {
if (canAddAccount.value) {
const res = await getEnterpriseInviteCode();
const port = window.location.port === '' ? '' : ':' + window.location.port;
const domain = window.location.protocol + '//' + window.location.hostname + port;
inviteUrl.value = domain + '?invite_code=' + res.invite_code;
}
addAccountVisible.value = true;
}
function handleOk() {
if (!canAddAccount.value) {
customerServiceVisible.value = true;
return;
}
if (!isSupported) {
AMessage.error('您的浏览器不支持复制,请手动复制!');
}
copy(inviteUrl.value);
if (!copied) {
AMessage.error('复制失败,请手动复制!');
}
AMessage.success('复制成功!');
}
function openDeleteModal(record: { id: number; mobile: string }) {
currentSelectAccount.value = record;
deleteTitle.value = `确认删除“${record.mobile}”子账号吗?`;
deleteVisible.value = true;
}
async function handleDelete() {
await removeEnterpriseAccount(currentSelectAccount.value.id);
AMessage.success('移除成功!');
}
onMounted(() => {
getSubAccount();
});
</script>
<style scoped lang="less">
.primary-account {
border-radius: 2px;
padding-right: 8px;
padding-left: 8px;
background: var(--Brand-Brand-1, rgba(240, 237, 255, 1));
font-family: Alibaba PuHuiTi, serif;
font-weight: 400;
font-size: 12px;
line-height: 20px;
color: var(--Brand-Brand-6, rgba(109, 76, 254, 1));
}
.sub-account {
border-radius: 2px;
padding-right: 8px;
padding-left: 8px;
background: var(--Functional-Warning-1, rgba(255, 245, 222, 1));
font-family: Alibaba PuHuiTi, serif;
font-weight: 400;
font-size: 12px;
color: var(--Functional-Warning-6, rgba(255, 174, 0, 1));
}
.delete-button {
border-radius: 4px;
padding: 2px 12px;
border: 1px solid var(--Functional-Danger-6, rgba(246, 75, 49, 1));
font-family: Alibaba PuHuiTi, serif;
font-weight: 400;
font-size: 12px;
line-height: 20px;
color: var(--Functional-Danger-6, rgba(246, 75, 49, 1));
}
.add-account-button {
border-radius: 4px;
padding: 5px 16px;
border: 1px solid var(--Brand-Brand-6, rgba(109, 76, 254, 1));
font-family: Alibaba PuHuiTi, serif;
font-weight: 400;
font-size: 14px;
color: var(--Brand-Brand-6, rgba(109, 76, 254, 1));
}
.add-account-container {
margin-top: 13px;
margin-bottom: 12px;
.add-account-title {
margin: 0;
font-family: Alibaba PuHuiTi, serif;
font-weight: 400;
font-size: 14px;
color: var(--Text-1, rgba(33, 31, 36, 1));
}
.add-account-subtitle {
margin: 4px 0 0 0;
font-family: Alibaba PuHuiTi, serif;
font-weight: 400;
font-size: 12px;
color: var(--Text-2, rgba(60, 64, 67, 1));
}
.add-account-body {
margin-top: 16px;
width: 432px;
height: 84px;
border-radius: 4px;
padding: 12px 16px;
background: var(--BG-200, rgba(242, 243, 245, 1));
font-family: Alibaba PuHuiTi, serif;
font-weight: 400;
font-size: 12px;
color: var(--Text-2, rgba(60, 64, 67, 1));
p {
padding: 0;
margin: 0;
}
}
.cannot-add-account-title {
margin: 0;
font-family: Alibaba PuHuiTi, serif;
font-weight: 400;
font-size: 14px;
color: var(--Text-1, rgba(33, 31, 36, 1));
img {
width: 20px;
height: 20px;
margin-right: 12px;
}
}
.cannot-add-account-subtitle {
margin: 16px 0 0 0;
padding-left: 32px;
font-family: Alibaba PuHuiTi, serif;
font-weight: 400;
font-size: 12px;
color: var(--Text-2, rgba(60, 64, 67, 1));
}
}
.delete-modal-content {
margin-left: 34px;
margin-top: 16px;
font-family: Alibaba PuHuiTi, serif;
font-weight: 400;
font-size: 12px;
color: var(--Text-2, rgba(60, 64, 67, 1));
}
</style>

View File

@ -82,6 +82,7 @@ function handleUpdate() {
async function handleOk() {
if (!canUpdate.value) {
customerServiceVisible.value = true;
return;
}
await updateEnterpriseName({ name: form.name });
AMessage.success('修改成功!');