feat: 新增租户管理和子账号管理模块

This commit is contained in:
傅光孟 2026-02-26 23:37:06 +08:00
parent 989a2a20a1
commit 47ced8b554
9 changed files with 1371 additions and 0 deletions

155
src/api/tenant/index.ts Normal file
View File

@ -0,0 +1,155 @@
// 租户/子账号管理
import { id } from 'element-plus/es/locale';
import request from '/@/utils/request';
// 获取租户列表参数
type TenantListDTO = {
accountType: number; // 账号类型
companyType: number; // 企业类型
status: number; // 状态
accountName: string; // 账号名称
page: number; // 页码
size: number; // 页数
orderBy: string; // 排序字段
direction: string; // 排序方式
};
// 获取租户列表
export const getTenantList = (params: Partial<TenantListDTO>) => {
return request({
url: '/tenant/list',
method: 'get',
params,
});
};
// 创建租户参数
type CreateTenantDTO = {
id?: number; // 租户id
companyType: number | string; // 企业类型
companyName: string; // 企业名称
accountName: string; // 账号名称
accountType: number | string; // 账号类型
phone: string; // 手机号
validStart: string; // 有效期开始
validEnd: string; // 有效期结束
accountLimit: number; // 子账号最大数量
};
// 创建租户
export const createTenant = (data: CreateTenantDTO) => {
return request({
url: '/tenant/create',
method: 'post',
data,
});
};
// 修改租户
export const updateTenant = (data: CreateTenantDTO) => {
return request({
url: '/tenant/update',
method: 'post',
data,
});
};
// 租户禁用
export const disableTenant = (data: { id: number }) => {
return request({
url: '/tenant/disable',
method: 'post',
data,
});
};
// 租户启用
export const enableTenant = (data: { id: number }) => {
return request({
url: '/tenant/enable',
method: 'post',
data,
});
};
// 租户导出
export const exportTenant = (params: Partial<TenantListDTO>) => {
return request({
url: '/tenant/export',
method: 'get',
params,
});
};
// 获取子账号列表参数
type TenantUserListDTO = {
phone: string; // 手机号
companyName: string; // 企业名称
accountType: number; // 账号类型
active: number; // 状态
page: number; // 页码
size: number; // 页数
orderBy: string; // 排序字段
direction: string; // 排序方式
};
// 获取子账号列表
export const getTenantUserList = (params: Partial<TenantUserListDTO>) => {
return request({
url: '/tenant/user/list',
method: 'get',
params,
});
};
// 创建租户子账号参数
type CreateTenantUserDTO = {
id?: number; // 子账号id
name: string; // 姓名
mobile: string; // 手机号
companyName: number; // 企业名称
department: string; // 部门
accountType: string; // 账号类型
};
// 创建租户子账号
export const createTenantUser = (data: CreateTenantUserDTO) => {
return request({
url: '/tenant/user/create',
method: 'post',
data,
});
};
// 修改租户子账号
export const updateTenantUser = (data: CreateTenantUserDTO) => {
return request({
url: '/tenant/user/update',
method: 'post',
data,
});
};
// 租户子账号批量导入
export const importTenantUser = (data: any) => {
return request({
url: '/tenant/user/import',
method: 'post',
headers: { 'Content-Type': 'multipart/form-data' },
data,
});
};
// 租户子账号导出
export const exportTenantUser = (params: Partial<TenantUserListDTO>) => {
return request({
url: '/tenant/user/export',
method: 'get',
params,
});
};
// 租户子账号禁用
export const disableTenantUser = (data: { id: number }) => {
return request({
url: '/tenant/disable',
method: 'post',
data,
});
};
// 租户子账号启用
export const enableTenantUser = (data: { id: number }) => {
return request({
url: '/tenant/user/enable',
method: 'post',
data,
});
};

View File

@ -358,6 +358,36 @@ export const dynamicRoutes: Array<RouteRecordRaw> = [
// }, // },
// children: [], // children: [],
// }, // },
{
path: '/tenant',
name: 'tenant',
component: () => import('/@/views/pages/tenant/index.vue'),
meta: {
title: '租户管理',
isLink: '',
isHide: false,
isKeepAlive: true,
isAffix: true,
isIframe: false,
roles: ['common'],
icon: 'ele-UserFilled',
},
},
{
path: '/tenant/user',
name: 'tenantUser',
component: () => import('/@/views/pages/tenantUser/index.vue'),
meta: {
title: '子账号管理',
isLink: '',
isHide: false,
isKeepAlive: true,
isAffix: true,
isIframe: false,
roles: ['common'],
icon: 'ele-User',
},
},
], ],
}, },
]; ];

View File

@ -0,0 +1,191 @@
<template>
<div class="index">
<el-dialog v-model="dialogTableVisible" :title="formData?.id ? '编辑账号' : '创建账号'" width="600" @closed="close">
<el-form :model="formData" label-width="auto" :rules="formRule" ref="formRef">
<el-form-item label="企业类型" prop="companyType">
<el-select v-model="formData.companyType" placeholder="请选择企业类型" clearable>
<el-option v-for="item of COMPANYTYPE_CONST" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="企业名称" prop="companyName">
<el-input v-model="formData.companyName" placeholder="企业名称需唯一" />
</el-form-item>
<el-form-item label="手机号码" prop="phone">
<el-input v-model="formData.phone" placeholder="请输入手机号码" />
</el-form-item>
<el-form-item label="账号名称" prop="accountName">
<el-input v-model="formData.accountName" placeholder="无则默认为手机号" />
</el-form-item>
<el-form-item label="子账号最大数量" prop="accountLimit">
<el-input-number v-model="formData.accountLimit" :min="0" />
</el-form-item>
<el-form-item label="账号类型" prop="accountType">
<el-select v-model="formData.accountType" placeholder="请选择账号类型" clearable>
<el-option v-for="item of ACCOUNTTYPE_CONST" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-row v-if="formData.accountType == 1" style="margin-top: 10px; padding: 10px; border: 1px solid #e7e7e7; border-radius: 5px">
<el-col :span="12" v-for="(item, index) in menu" :key="item.value">
<el-switch v-model="formData.menu[index]" :active-text="item.label" :active-value="item.value" :inactive-value="false" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="账号有效期" prop="daterange">
<el-date-picker
v-model="formData.daterange"
type="daterange"
unlink-panels
range-separator="-"
value-format="YYYY-MM-DD"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="close">取消</el-button>
<el-button type="primary" @click="submit()"> 确认 </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="loginIndex">
import { onMounted, reactive, ref } from 'vue';
import { NextLoading } from '/@/utils/loading';
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
import { createTenant, updateTenant } from '/@/api/tenant';
import { COMPANYTYPE_CONST, ACCOUNTTYPE_CONST } from './constant';
const emit = defineEmits(['close']);
const formRef = ref<FormInstance>();
const formData = reactive({
id: undefined,
companyType: '',
companyName: '',
phone: '',
accountName: '',
accountLimit: 0,
accountType: '',
daterange: [],
menu: ['1','2','3','4','5','6'],
});
const menu = ref([
{
label: '海外独家',
value: '1',
},
{
label: '编辑精选',
value: '2',
},
{
label: '宏观知微',
value: '3',
},
{
label: '智能资讯榜',
value: '4',
},
{
label: '热门行业',
value: '5',
},
{
label: '风口概念',
value: '6',
},
]);
const validatePhone = (rule: any, value: any, callback: any) => {
const phoneRegex = /^1[3456789]\d{9}$/;
if (!value) {
callback(new Error('必须填写手机号码'));
} else if (!phoneRegex.test(value)) {
callback(new Error('请输入有效的手机号'));
} else {
callback();
}
};
const formRule = reactive({
companyType: [{ required: true, message: '必须填写企业类型', trigger: 'change' }],
companyName: [{ required: true, message: '必须填写企业名称', trigger: 'blur' }],
phone: [{ validator: validatePhone, required: true, message: '必须填写手机号码', trigger: 'blur' }],
accountType: [{ required: true, message: '必须填写账号类型', trigger: 'change' }],
daterange: [{ required: true, message: '必须填写有效期', trigger: 'change' }],
accountLimit: [{ required: true, message: '必须填写子账号最大数量', trigger: 'blur' }],
});
const dialogTableVisible = ref(false);
function open(data) {
dialogTableVisible.value = true;
if (data) {
Object.assign(formData, data);
}
}
function close() {
dialogTableVisible.value = false;
formData.id = undefined;
formData.companyType = '';
formData.companyName = '';
formData.phone = '';
formData.accountName = '';
formData.accountType = '';
formData.accountLimit = 0;
formData.daterange = [];
emit('close');
}
async function submit() {
console.log('output >>>>> formData', formData);
await formRef.value?.validate();
const params = {
id: formData?.id || undefined,
companyType: formData.companyType,
companyName: formData.companyName,
accountName: formData.accountName || formData.phone,
accountType: formData.accountType,
phone: formData.phone,
validStart: formData.daterange[0],
validEnd: formData.daterange[1],
accountLimit: formData.accountLimit,
};
console.log('output >>>>> params',params);
if (formData.id) {
updateTenant(params).then((res) => {
if (res.code === 200) {
ElMessage.success('操作成功');
close();
} else {
ElMessage.error(res.msg || '操作失败');
}
});
return;
} else {
createTenant(params).then((res) => {
if (res.code === 200) {
ElMessage.success('操作成功');
close();
} else {
ElMessage.error(res.msg || '操作失败');
}
});
}
}
//
onMounted(() => {
NextLoading.done();
});
defineExpose({
open,
close,
});
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,49 @@
type IValue = {
label: string;
value: number;
};
export const COMPANYTYPE_CONST: Record<string, IValue> = {
'0': {
label: '内部',
value: 0,
},
'1': {
label: '银行',
value: 1,
},
'2': {
label: '券商',
value: 2,
},
'3': {
label: '上市公司',
value: 3,
},
'4': {
label: '其他机构',
value: 4,
},
};
export const ACCOUNTTYPE_CONST: Record<string, IValue> = {
'0': {
label: '试用',
value: 0,
},
'1': {
label: '正式',
value: 1,
},
};
export const STATUS_CONST: Record<string, IValue> = {
'0': {
label: '启用',
value: 0,
},
'1': {
label: '禁用',
value: 1,
},
};

View File

@ -0,0 +1,147 @@
<template>
<div class="index">
<el-dialog v-model="dialogTableVisible" title="批量导入" width="600" @closed="close">
<div style="display: flex; flex-direction: column">
<el-button @click="downloadTemplate" style="width: 120px">下载标准模版</el-button>
<div v-if="errorMsg" class="error-message">{{ errorMsg }}</div>
</div>
<!-- :auto-upload="false"
:on-change="handleFileChange" -->
<el-upload
ref="uploadRef"
class="upload"
drag
:action="baseUrl + '/jnh/accounts/import'"
accept=".xlsx,.xls"
:headers="uploadHeader"
:on-success="handleSuccess"
>
<div class="upload_text">点击或拖拽文件至此处导入</div>
<div class="upload_tips">支持格式.xlsx/.xls文件大小10MB</div>
</el-upload>
<template #footer>
<div class="dialog-footer">
<el-button @click="close">取消</el-button>
<el-button type="primary" @click="submit()"> 确认 </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="loginIndex">
import { onMounted, reactive, ref } from 'vue';
import { NextLoading } from '/@/utils/loading';
import { ElMessage, ElMessageBox } from 'element-plus';
import { getUploadUrl } from '/@/api/jnh';
import { Session } from '/@/utils/storage';
const baseUrl = ref(import.meta.env.VITE_API_UR);
const emit = defineEmits(['close']);
const rules = reactive({
name: [{ required: true, message: '必须填写姓名', trigger: 'blur' }],
mobile: [{ required: true, message: '必须填写手机号码', trigger: 'blur' }],
});
const form = ref({});
const dialogTableVisible = ref(false);
const uploadRef = ref(null);
const selectedFile = ref(null);
const uploadHeader = ref({
'auth-token': `${Session.get('token')}`,
});
function open(data) {
dialogTableVisible.value = true;
if (data) {
form.value = data;
}
}
function close() {
//
if (uploadRef.value) {
uploadRef.value.clearFiles();
}
//
selectedFile.value = null;
errorMsg.value = '';
dialogTableVisible.value = false;
emit('close');
}
//
function handleFileChange(file, fileList) {
selectedFile.value = file;
//
errorMsg.value = '';
}
//
function submit() {
// if (!selectedFile.value) {
// errorMsg.value = '';
// return;
// }
close();
// //
// if (uploadRef.value) {
// uploadRef.value.submit();
// }
}
function downloadTemplate() {
window.open('https://cankao.obs.cn-east-3.myhuaweicloud.com/%E6%89%B9%E9%87%8F%E5%AF%BC%E5%85%A5%E6%A8%A1%E6%9D%BF.xlsx');
}
const errorMsg = ref('');
function handleSuccess(res) {
console.log('🚀 ~ handleSuccess ~ res:', res);
if (res.code === 200) {
ElMessage.success('导入成功');
errorMsg.value = res.data;
//
// dialogTableVisible.value = false;
emit('close');
} else {
errorMsg.value = res.msg || '导入失败';
}
}
//
onMounted(() => {
NextLoading.done();
baseUrl.value = import.meta.env.VITE_API_URL;
});
defineExpose({
open,
close,
});
</script>
<style scoped lang="scss">
.upload {
margin-top: 20px;
}
.upload_text {
font-size: 14px;
color: #303133;
}
.upload_tips {
font-size: 12px;
color: #606266;
margin-top: 10px;
}
.error-message {
color: red;
margin-top: 10px;
white-space: pre-line;
font-size: 14px;
}
</style>

View File

@ -0,0 +1,264 @@
<template>
<div class="index">
<div class="form">
<div class="form_button">
<el-button type="primary" :icon="Plus" @click="addOpen">创建账号</el-button>
<el-button @click="exportExcel">导出</el-button>
<el-button @click="clearForm">清空条件</el-button>
</div>
<el-form class="form_content">
<el-form-item>
<el-input v-model="formData.accountName" placeholder="企业名称/企业简称/证券代码/账号名称" style="width: 330px" @keydown.enter="getData">
<template #append>
<el-button icon="Search" @click="getData" />
</template>
</el-input>
</el-form-item>
<el-form-item>
<el-select v-model="formData.companyType" placeholder="企业类型(全部)" class="input" clearable @change="getData">
<el-option v-for="item of COMPANYTYPE_CONST" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-select v-model="formData.accountType" placeholder="账号类型(全部)" class="input" clearable @change="getData">
<el-option v-for="item of ACCOUNTTYPE_CONST" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-select v-model="formData.status" placeholder="账号状态(全部)" class="input" clearable @change="getData">
<el-option v-for="item of STATUS_CONST" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-form>
</div>
<tableComponents :tableData="tableData" :tableLoading="loading" :hadExpand="false">
<el-table-column prop="companyType" label="企业类型" align="center">
<template #default="scope">
{{ COMPANYTYPE_CONST[scope.row.companyType]?.label }}
</template>
</el-table-column>
<el-table-column prop="companyName" label="企业名称" align="center" />
<el-table-column prop="accountType" label="账号类型" align="center">
<template #default="scope">
{{ ACCOUNTTYPE_CONST[scope.row.accountType]?.label }}
</template>
</el-table-column>
<el-table-column prop="accountLimit" label="子账号限制" align="center" />
<el-table-column prop="phone" label="手机号" align="center" />
<el-table-column prop="accountName" label="账号名称" align="center" />
<el-table-column prop="validStart" label="账号有效期" align="center" width="180">
<template #default="scope"> {{ scope.row.validStart }} - {{ scope.row.validEnd }} </template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" align="center" />
<el-table-column prop="status" label="账号状态" align="center">
<template #default="scope">
<div style="display: flex; align-items: center; justify-content: center">
<div class="r_point" v-if="scope.row.status === 0">
<div class="point" style="background-color: #2dc74c"></div>
<text style="color: #2dc74c; white-space: nowrap">{{ STATUS_CONST[scope.row.status]?.label }}</text>
</div>
<div class="r_point" v-else>
<div class="point" style="background-color: #eb1c5d"></div>
<text style="color: #eb1c5d; white-space: nowrap">{{ STATUS_CONST[scope.row.status]?.label }}</text>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button type="text" @click="addOpen(scope.row)">编辑</el-button>
<el-button type="text" v-if="scope.row.status === 1" @click="doUpdateStatus(scope.row)">启用</el-button>
<el-button type="text" v-else @click="doUpdateStatus(scope.row)">禁用</el-button>
</template>
</el-table-column>
</tableComponents>
<addDialog ref="addDialogRef" @close="getData"></addDialog>
<importDialog ref="importDialogRef" @close="getData"></importDialog>
</div>
</template>
<script setup lang="ts" name="tenant">
import { onMounted, ref, reactive } from 'vue';
import { NextLoading } from '/@/utils/loading';
import tableComponents from '/@/components/tableComponents/index.vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import addDialog from './add.vue';
import importDialog from './import.vue';
import { Plus } from '@element-plus/icons-vue';
import { disableTenant, enableTenant, getTenantList, exportTenant } from '/@/api/tenant';
import { COMPANYTYPE_CONST, ACCOUNTTYPE_CONST, STATUS_CONST } from './constant';
type FormData = {
accountName: string;
companyType: number | undefined;
accountType: number | undefined;
status: number | undefined;
};
type IData = {
id: number;
companyType: number;
companyName: string;
accountType: number;
subAccountLimit: number;
phone: string;
name: string;
validStart: string;
validEnd: string;
createTime: string;
status: number;
};
type TableData = {
data: IData[];
total: number;
page: number;
size: number;
};
const formData = reactive<FormData>({
accountName: '',
companyType: undefined,
accountType: undefined,
status: undefined,
});
const tableData = reactive<TableData>({
data: [],
total: 0,
page: 1,
size: 20,
});
const loading = ref(false);
//
async function getData() {
loading.value = true;
let { code, data, total } = await getTenantList({
page: tableData.page,
size: tableData.size,
...formData,
});
loading.value = false;
if (code == 200) {
tableData.data = data;
tableData.total = total;
}
}
//
async function doUpdateStatus(row: IData) {
ElMessageBox.confirm('确定' + (row.status === 1 ? '启用' : '禁用') + '该账号吗?', '提示', {
type: 'warning',
}).then(async () => {
//
const updateStatus = row.status === 1 ? enableTenant : disableTenant;
let { code, data } = await updateStatus({
id: row.id,
});
if (code == 200) {
ElMessage.success('操作成功');
getData();
}
});
}
const addDialogRef = ref();
//
function addOpen(data?: IData) {
addDialogRef.value.open(data);
}
const importDialogRef = ref();
function addImport(data?: any) {
importDialogRef.value.open(data);
}
function clearForm() {
formData.accountName = '';
formData.companyType = undefined;
formData.accountType = undefined;
formData.status = undefined;
getData();
}
//
function exportExcel() {
ElMessageBox.confirm('确定导出吗?', '提示', {
type: 'warning',
}).then(async () => {
//
let { code, data } = await exportTenant({
page: tableData.page,
size: tableData.size,
...formData,
});
console.log('output >>>>> data', data);
if (code == 200) {
ElMessage.success('导出成功');
}
});
}
//
onMounted(() => {
NextLoading.done();
getData();
});
</script>
<style scoped lang="scss">
.index {
background-color: white;
padding: 30px;
border-radius: 10px;
}
.form {
// display: flex;
// justify-content: space-between;
margin-bottom: 30px;
.input {
width: 200px;
}
.form_content {
display: flex;
gap: 20px;
align-items: center;
padding: 20px;
border: 1px solid #f0f0f0;
border-radius: 4px;
background-color: #fafafa;
}
:deep(.el-form-item--large) {
display: flex;
align-items: center;
margin-bottom: 0;
}
:deep(.el-form-item--label-right .el-form-item__label) {
font-weight: bold;
color: #3e3e3e;
}
.form_button {
display: flex;
margin-bottom: 20px;
}
}
.r_point {
display: flex;
align-items: center;
gap: 3px;
}
.point {
width: 8px;
height: 8px;
border-radius: 100px;
// color: #ffaa48;
}
</style>

View File

@ -0,0 +1,134 @@
<template>
<div class="index">
<el-dialog v-model="dialogTableVisible" :title="form?.id ? '账号编辑' : '账号创建'" width="600" @closed="close">
<el-form :model="form" label-width="auto" :rules="rules" ref="ruleFormRef">
<el-form-item label="姓名" prop="name">
<el-input v-model="form.name" placeholder="请输入姓名"> </el-input>
</el-form-item>
<el-form-item label="手机号码" prop="mobile">
<el-input v-model="form.mobile" placeholder="请输入手机号码" />
</el-form-item>
<el-form-item label="企业名称" prop="companyName">
<el-select v-model="form.companyName" placeholder="请选择企业名称" value-key="id" clearable @change="handleChange">
<el-option v-for="item of companyData" :key="item.id" :label="item.companyName" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="账号类型" prop="accountType">
<el-select v-model="form.accountType" placeholder="自动关联企业类型" disabled>
<el-option v-for="item of ACCOUNTTYPE_CONST" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="部门名称" prop="department">
<el-input v-model="form.department" placeholder="请输入部门名称" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="close">取消</el-button>
<el-button type="primary" @click="submit()"> 确认 </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="loginIndex">
import { onMounted, reactive, ref } from 'vue';
import { NextLoading } from '/@/utils/loading';
import { ElMessage, ElMessageBox } from 'element-plus';
import { createTenantUser, getTenantList, updateTenantUser } from '/@/api/tenant';
import { ACCOUNTTYPE_CONST } from '../tenant/constant';
const emit = defineEmits(['close']);
const rules = reactive({
name: [{ required: true, message: '必须填写姓名', trigger: 'blur' }],
mobile: [{ required: true, message: '必须填写手机号码', trigger: 'blur' }],
});
const form = reactive({});
const dialogTableVisible = ref(false);
const loading = ref(false);
const companyData = ref([]);
//
async function getData() {
loading.value = true;
let { code, data, total } = await getTenantList({
page: 1,
size: 9999,
});
loading.value = false;
if (code == 200) {
companyData.value = data;
}
}
function handleChange(val) {
form.accountType = val?.accountType ?? '';
}
function open(data) {
getData();
dialogTableVisible.value = true;
if (data) {
Object.assign(form, data);
}
}
function close() {
dialogTableVisible.value = false;
form.name = '';
form.mobile = '';
form.companyName = '';
form.accountType = '';
form.department = '';
emit('close');
}
const ruleFormRef = ref(null);
async function submit() {
await ruleFormRef.value.validate();
const params = {
id: form?.id || undefined,
name: form.name,
mobile: form.mobile,
companyName: form.companyName?.companyName,
department: form.department,
accountType: form.accountType,
};
console.log('output >>>>> params', params);
if (form.id) {
updateTenantUser(params).then((res) => {
if (res.code === 200) {
ElMessage.success('操作成功');
close();
} else {
ElMessage.error(res.msg || '操作失败');
}
});
return;
} else {
createTenantUser(params).then((res) => {
if (res.code === 200) {
ElMessage.success('操作成功');
close();
} else {
ElMessage.error(res.msg || '操作失败');
}
});
}
}
//
onMounted(() => {
NextLoading.done();
});
defineExpose({
open,
close,
});
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,146 @@
<template>
<div class="index">
<el-dialog v-model="dialogTableVisible" title="批量导入" width="600" @closed="close">
<div style="display: flex; flex-direction: column">
<el-button @click="downloadTemplate" style="width: 120px">下载标准模版</el-button>
<div v-if="errorMsg" class="error-message">{{ errorMsg }}</div>
</div>
<!-- :auto-upload="false"
:on-change="handleFileChange" -->
<el-upload
ref="uploadRef"
class="upload"
drag
:action="baseUrl + '/tenant/user/import'"
accept=".xlsx,.xls"
:headers="uploadHeader"
:on-success="handleSuccess"
>
<div class="upload_text">点击或拖拽文件至此处导入</div>
<div class="upload_tips">支持格式.xlsx/.xls文件大小10MB</div>
</el-upload>
<template #footer>
<div class="dialog-footer">
<el-button @click="close">取消</el-button>
<el-button type="primary" @click="submit()"> 确认 </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="loginIndex">
import { onMounted, reactive, ref } from 'vue';
import { NextLoading } from '/@/utils/loading';
import { ElMessage, ElMessageBox } from 'element-plus';
import { Session } from '/@/utils/storage';
const baseUrl = ref(import.meta.env.VITE_API_UR);
const emit = defineEmits(['close']);
const rules = reactive({
name: [{ required: true, message: '必须填写姓名', trigger: 'blur' }],
mobile: [{ required: true, message: '必须填写手机号码', trigger: 'blur' }],
});
const form = ref({});
const dialogTableVisible = ref(false);
const uploadRef = ref(null);
const selectedFile = ref(null);
const uploadHeader = ref({
'auth-token': `${Session.get('token')}`,
});
function open(data) {
dialogTableVisible.value = true;
if (data) {
form.value = data;
}
}
function close() {
//
if (uploadRef.value) {
uploadRef.value.clearFiles();
}
//
selectedFile.value = null;
errorMsg.value = '';
dialogTableVisible.value = false;
emit('close');
}
//
function handleFileChange(file, fileList) {
selectedFile.value = file;
//
errorMsg.value = '';
}
//
function submit() {
// if (!selectedFile.value) {
// errorMsg.value = '';
// return;
// }
close();
// //
// if (uploadRef.value) {
// uploadRef.value.submit();
// }
}
function downloadTemplate() {
window.open('https://cankao.obs.cn-east-3.myhuaweicloud.com/%E6%89%B9%E9%87%8F%E5%AF%BC%E5%85%A5%E6%A8%A1%E6%9D%BF.xlsx');
}
const errorMsg = ref('');
function handleSuccess(res) {
console.log('🚀 ~ handleSuccess ~ res:', res);
if (res.code === 200) {
ElMessage.success('导入成功');
errorMsg.value = res.data;
//
// dialogTableVisible.value = false;
emit('close');
} else {
errorMsg.value = res.msg || '导入失败';
}
}
//
onMounted(() => {
NextLoading.done();
baseUrl.value = import.meta.env.VITE_API_URL;
});
defineExpose({
open,
close,
});
</script>
<style scoped lang="scss">
.upload {
margin-top: 20px;
}
.upload_text {
font-size: 14px;
color: #303133;
}
.upload_tips {
font-size: 12px;
color: #606266;
margin-top: 10px;
}
.error-message {
color: red;
margin-top: 10px;
white-space: pre-line;
font-size: 14px;
}
</style>

View File

@ -0,0 +1,255 @@
<template>
<div class="index">
<div class="form">
<div class="form_button">
<el-button type="primary" :icon="Plus" @click="addOpen">创建子账号</el-button>
<el-button @click="addImport">批量导入</el-button>
<el-button @click="exportExcel">导出</el-button>
<el-button @click="clearForm">清空条件</el-button>
</div>
<el-form class="form_content"
><el-form-item>
<el-input v-model="formData.phone" placeholder="请输入账号手机号" @keydown.enter="getData">
<template #append>
<el-button icon="Search" @click="getData" />
</template>
</el-input>
</el-form-item>
<el-form-item>
<el-input v-model="formData.companyName" placeholder="请输入企业名称" @keydown.enter="getData">
<template #append>
<el-button icon="Search" @click="getData" />
</template>
</el-input>
</el-form-item>
<el-form-item>
<el-select v-model="formData.accountType" placeholder="账号类型(全部)" class="input" clearable @change="getData">
<el-option v-for="item of ACCOUNTTYPE_CONST" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-select v-model="formData.active" placeholder="账号状态(全部)" class="input" clearable @change="getData">
<el-option v-for="item of STATUS_CONST" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-form>
</div>
<tableComponents :tableData="tableData" :tableLoading="loading" :hadExpand="false">
<el-table-column prop="name" label="姓名" align="center" />
<el-table-column prop="phone" label="手机号" align="center" />
<el-table-column prop="companyName" label="企业名称" align="center" />
<el-table-column prop="accountType" label="账号类型" align="center">
<template #default="scope">
{{ ACCOUNTTYPE_CONST[scope.row.accountType]?.label }}
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" align="center" />
<el-table-column prop="updateTime" label="变更时间" align="center" />
<el-table-column prop="active" label="账号状态" align="center">
<template #default="scope">
<div style="display: flex; align-items: center; justify-content: center">
<div class="r_point" v-if="scope.row.active === 1">
<div class="point" style="background-color: #2dc74c"></div>
<text style="color: #2dc74c; white-space: nowrap">{{ STATUS_CONST[scope.row.active]?.label }}</text>
</div>
<div class="r_point" v-else>
<div class="point" style="background-color: #eb1c5d"></div>
<text style="color: #eb1c5d; white-space: nowrap">{{ STATUS_CONST[scope.row.active]?.label }}</text>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button type="text" @click="addOpen(scope.row)">编辑</el-button>
<el-button type="text" v-if="scope.row.active === 1" @click="doUpdateStatus(scope.row)">启用</el-button>
<el-button type="text" v-else @click="doUpdateStatus(scope.row)">禁用</el-button>
</template>
</el-table-column>
</tableComponents>
<addDialog ref="addDialogRef" @close="getData"></addDialog>
<importDialog ref="importDialogRef" @close="getData"></importDialog>
</div>
</template>
<script setup lang="ts" name="tenant">
import { onMounted, ref, reactive } from 'vue';
import { NextLoading } from '/@/utils/loading';
import tableComponents from '/@/components/tableComponents/index.vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import addDialog from './add.vue';
import importDialog from './import.vue';
import { Plus } from '@element-plus/icons-vue';
import { disableTenantUser, enableTenantUser, exportTenantUser, getTenantUserList } from '/@/api/tenant';
import { ACCOUNTTYPE_CONST, STATUS_CONST } from '/@/views/pages/tenant/constant';
type FormData = {
phone: string;
companyName: string;
accountType: number | undefined;
active: number | undefined;
};
type IData = {
id: number;
companyName: string;
accountType: number;
phone: string;
name: string;
createTime: string;
updateTime: string;
active: number;
};
type TableData = {
data: IData[];
total: number;
page: number;
size: number;
};
const formData = reactive<FormData>({
phone: '',
companyName: '',
accountType: undefined,
active: undefined,
});
const tableData = reactive<TableData>({
data: [],
total: 0,
page: 1,
size: 20,
});
const loading = ref(false);
//
async function getData() {
loading.value = true;
let { code, data, total } = await getTenantUserList({
page: tableData.page,
size: tableData.size,
...formData,
});
loading.value = false;
if (code == 200) {
tableData.data = data;
tableData.total = total;
}
}
//
async function doUpdateStatus(row: IData) {
ElMessageBox.confirm('确定' + (row.active === 0 ? '启用' : '禁用') + '该账号吗?', '提示', {
type: 'warning',
}).then(async () => {
//
const updateStatus = row.active === 0 ? enableTenantUser : disableTenantUser;
let { code, data } = await updateStatus({
id: row.id,
});
if (code == 200) {
ElMessage.success('操作成功');
getData();
}
});
}
const addDialogRef = ref();
//
function addOpen(data?: IData) {
addDialogRef.value.open(data);
}
const importDialogRef = ref();
function addImport(data?: any) {
importDialogRef.value.open(data);
}
//
function exportExcel() {
ElMessageBox.confirm('确定导出吗?', '提示', {
type: 'warning',
}).then(async () => {
//
let { code, data } = await exportTenantUser({
page: tableData.page,
size: tableData.size,
...formData,
});
console.log('output >>>>> data', data);
if (code == 200) {
ElMessage.success('导出成功');
}
});
}
function clearForm() {
formData.phone = '';
formData.companyName = '';
formData.accountType = undefined;
formData.active = undefined;
getData();
}
//
onMounted(() => {
NextLoading.done();
getData();
});
</script>
<style scoped lang="scss">
.index {
background-color: white;
padding: 30px;
border-radius: 10px;
}
.form {
// display: flex;
// justify-content: space-between;
margin-bottom: 30px;
.input {
width: 200px;
}
.form_content {
display: flex;
gap: 20px;
align-items: center;
padding: 20px;
border: 1px solid #f0f0f0;
border-radius: 4px;
background-color: #fafafa;
}
:deep(.el-form-item--large) {
display: flex;
align-items: center;
margin-bottom: 0;
}
:deep(.el-form-item--label-right .el-form-item__label) {
font-weight: bold;
color: #3e3e3e;
}
.form_button {
display: flex;
margin-bottom: 20px;
}
}
.r_point {
display: flex;
align-items: center;
gap: 3px;
}
.point {
width: 8px;
height: 8px;
border-radius: 100px;
// color: #ffaa48;
}
</style>