feat: 用户管理-前端

This commit is contained in:
baiqi 2023-06-19 18:05:14 +08:00 committed by rubylliu
parent 87f7b3265d
commit 6cb7e97a5d
16 changed files with 1104 additions and 44 deletions

View File

@ -1,9 +1,9 @@
import MSR from '@/api/http/index';
import { GetApiTestList, GetApiTestListUrl } from '@/api/requrls/api-test';
import { QueryParams } from '@/models/common';
import { TableQueryParams } from '@/models/common';
import { CommonList } from '@/models/api-test';
export function getTableList(params: QueryParams) {
export function getTableList(params: TableQueryParams) {
const { current, pageSize, sort, filter, keyword } = params;
return MSR.post<CommonList>({
url: GetApiTestList,

View File

@ -0,0 +1,16 @@
import MSR from '@/api/http/index';
import { GetUserListUrl, CreateUserUrl, UpdateUserUrl } from '@/api/requrls/system';
import type { UserListItem, CreateUserParams } from '@/models/system/user';
import type { TableQueryParams } from '@/models/common';
export function getUserList(data: TableQueryParams) {
return MSR.post<UserListItem[]>({ url: GetUserListUrl, data });
}
export function batchCreateUser(data: CreateUserParams) {
return MSR.post({ url: CreateUserUrl, data });
}
export function updateUserInfo(data: UserListItem) {
return MSR.post({ url: UpdateUserUrl, data });
}

View File

@ -0,0 +1,3 @@
export const GetUserListUrl = '/user/page';
export const CreateUserUrl = '/user/add';
export const UpdateUserUrl = '/user/update';

View File

@ -13,10 +13,9 @@
}
}
.ms-table-row-disabled {
td:not(.arco-table-col-fixed-right),
td:not(.arco-table-col-fixed-right, .arco-table-checkbox),
&:hover,
td:not(.arco-table-col-fixed-right)::before,
.arco-checkbox-icon,
td:not(.arco-table-col-fixed-right, .arco-table-checkbox)::before,
.arco-tag {
color: var(--color-text-4);
background-color: var(--color-text-n8) !important;
@ -121,7 +120,7 @@
background-color: rgb(var(--danger-2)) !important;
}
}
.arco-btn-secondary {
.arco-btn-secondary:not(.arco-btn-disabled) {
color: var(--clolor-text-1) !important;
background-color: var(--color-text-n8) !important;
&:not(:disabled):hover {
@ -275,3 +274,64 @@
.arco-popover-content {
font-size: 12px;
}
/** 链接 **/
.arco-link-status-normal {
color: rgb(var(--primary-5)) !important;
&:not(:disabled):hover {
color: rgb(var(--primary-4)) !important;
background-color: rgb(var(--color-text-n9));
}
&:not(:disabled):active {
color: rgb(var(--primary-7)) !important;
}
&:disabled {
color: rgb(var(--primary-3)) !important;
}
}
/** 穿梭框 **/
.arco-transfer {
@apply grid;
grid-template-columns: 4fr 1fr 4fr;
.arco-transfer-view {
@apply w-auto;
height: 370px;
.arco-transfer-view-header {
@apply bg-white;
}
}
.arco-transfer-operations {
.arco-btn-secondary {
border-color: rgb(var(--primary-5));
border-radius: var(--border-radius-small);
background-color: rgb(var(--primary-1)) !important;
.arco-btn-icon {
color: rgb(var(--primary-5));
}
&:disabled {
border-color: var(--color-text-input-border) !important;
background-color: var(--color-text-n8) !important;
.arco-btn-icon {
color: var(--color-text-4);
}
}
&:not(:disabled):hover {
border-color: rgb(var(--primary-4)) !important;
background-color: rgb(var(--primary-1)) !important;
.arco-btn-icon {
color: rgb(var(--primary-7));
}
}
&:not(:disabled):active {
border-color: rgb(var(--primary-7)) !important;
background-color: rgb(var(--primary-9)) !important;
.arco-btn-icon {
color: rgb(var(--primary-7));
}
}
}
}
}

View File

@ -74,3 +74,7 @@ body {
.split-line {
border-color: rgb(var(--gray-2));
}
.sub-text {
font-size: 12px;
color: rgb(var(--color-text-4));
}

View File

@ -3,3 +3,5 @@
@border-radius-small: 4px;
@border-radius-medium: 6px;
@border-radius-large: 12px;
@color-white: #fff;

View File

@ -129,7 +129,7 @@
};
function getRowClass(record: TableData) {
if (record.disabled) {
if (!record.raw.enable) {
return 'ms-table-row-disabled';
}
}

View File

@ -3,6 +3,7 @@ import Mock from 'mockjs';
import './user';
import './message-box';
import './api-test';
import './system/user';
Mock.setup({
timeout: '600-1000',

View File

@ -0,0 +1,259 @@
import Mock from 'mockjs';
import setupMock, { successTableResponseWrap } from '@/utils/setup-mock';
const getUserList = () => {
return [
{
id: '103423',
name: '大家的',
email: 'dehihu@kds.sd',
enable: true,
createTime: 1686905750716,
updateTime: 0,
lastOrganizationId: 'string',
phone: '18473647583',
source: 'string',
lastProjectId: 'string',
createUser: 'string',
updateUser: 'string',
organizationList: [
{
id: 'string',
num: 0,
name: '组织 1',
description: 'blabla',
createTime: 0,
updateTime: 0,
createUser: 'string',
updateUser: 'string',
deleted: true,
deleteUser: 'string',
deleteTime: 0,
enable: true,
},
{
id: 'string',
num: 0,
name: '组织 2',
description: 'blabla',
createTime: 0,
updateTime: 0,
createUser: 'string',
updateUser: 'string',
deleted: true,
deleteUser: 'string',
deleteTime: 0,
enable: true,
},
{
id: 'string',
num: 0,
name: '组织 3',
description: 'blabla',
createTime: 0,
updateTime: 0,
createUser: 'string',
updateUser: 'string',
deleted: true,
deleteUser: 'string',
deleteTime: 0,
enable: true,
},
{
id: 'string',
num: 0,
name: '组织 4',
description: 'blabla',
createTime: 0,
updateTime: 0,
createUser: 'string',
updateUser: 'string',
deleted: true,
deleteUser: 'string',
deleteTime: 0,
enable: true,
},
],
userRoleList: [
{
id: 'string',
name: '角色 1',
description: 'sdadasd',
internal: true,
type: 'string',
createTime: 0,
updateTime: 0,
createUser: 'string',
scopeId: 'string',
pos: 0,
},
{
id: 'string',
name: '角色 2',
description: 'sdadasd',
internal: true,
type: 'string',
createTime: 0,
updateTime: 0,
createUser: 'string',
scopeId: 'string',
pos: 0,
},
{
id: 'string',
name: '角色 3',
description: 'sdadasd',
internal: true,
type: 'string',
createTime: 0,
updateTime: 0,
createUser: 'string',
scopeId: 'string',
pos: 0,
},
{
id: 'string',
name: '角色 4',
description: 'sdadasd',
internal: true,
type: 'string',
createTime: 0,
updateTime: 0,
createUser: 'string',
scopeId: 'string',
pos: 0,
},
],
},
{
id: '103233',
name: '发热发热',
email: 'dehihu@kds.sd',
enable: false,
createTime: 1686905750716,
updateTime: 0,
lastOrganizationId: 'string',
phone: '18473647583',
source: 'string',
lastProjectId: 'string',
createUser: 'string',
updateUser: 'string',
organizationList: [
{
id: 'string',
num: 0,
name: '组织 1',
description: 'blabla',
createTime: 0,
updateTime: 0,
createUser: 'string',
updateUser: 'string',
deleted: true,
deleteUser: 'string',
deleteTime: 0,
enable: true,
},
{
id: 'string',
num: 0,
name: '组织 2',
description: 'blabla',
createTime: 0,
updateTime: 0,
createUser: 'string',
updateUser: 'string',
deleted: true,
deleteUser: 'string',
deleteTime: 0,
enable: true,
},
{
id: 'string',
num: 0,
name: '组织 3',
description: 'blabla',
createTime: 0,
updateTime: 0,
createUser: 'string',
updateUser: 'string',
deleted: true,
deleteUser: 'string',
deleteTime: 0,
enable: true,
},
{
id: 'string',
num: 0,
name: '组织 4',
description: 'blabla',
createTime: 0,
updateTime: 0,
createUser: 'string',
updateUser: 'string',
deleted: true,
deleteUser: 'string',
deleteTime: 0,
enable: true,
},
],
userRoleList: [
{
id: 'string',
name: '角色 1',
description: 'sdadasd',
internal: true,
type: 'string',
createTime: 0,
updateTime: 0,
createUser: 'string',
scopeId: 'string',
pos: 0,
},
{
id: 'string',
name: '角色 2',
description: 'sdadasd',
internal: true,
type: 'string',
createTime: 0,
updateTime: 0,
createUser: 'string',
scopeId: 'string',
pos: 0,
},
{
id: 'string',
name: '角色 3',
description: 'sdadasd',
internal: true,
type: 'string',
createTime: 0,
updateTime: 0,
createUser: 'string',
scopeId: 'string',
pos: 0,
},
{
id: 'string',
name: '角色 4',
description: 'sdadasd',
internal: true,
type: 'string',
createTime: 0,
updateTime: 0,
createUser: 'string',
scopeId: 'string',
pos: 0,
},
],
},
];
};
setupMock({
setup: () => {
Mock.mock(new RegExp('/user/page'), () => {
return successTableResponseWrap(getUserList());
});
},
});

View File

@ -7,16 +7,26 @@ export default interface CommonReponse<T> {
}
// 表格查询
export interface QueryParams {
export interface TableQueryParams {
// 当前页
current: number;
// 每页条数
pageSize: number;
// 排序仅针对单个字段
sort?: object;
// 排序仅针对单个字段
sortString?: string;
// 表头筛选
filter?: object;
// 查询条件
keyword?: string;
[key: string]: any;
}
export interface TableResult<T> {
[x: string]: any;
pageSize: number;
total: number;
current: number;
list: T[];
}

View File

@ -0,0 +1,66 @@
export interface UserRoleListItem {
id: string;
name: string;
description: string;
internal: boolean;
type: string;
createTime: number;
updateTime: number;
createUser: string;
scopeId: string;
pos: number;
}
export interface OrganizationList {
id: string;
num: number;
name: string;
description: string;
createTime: number;
updateTime: number;
createUser: string;
updateUser: string;
deleted: boolean;
deleteUser: string;
deleteTime: number;
enable: boolean;
}
export interface UserListItem {
id: string;
name: string;
email: string;
password: string;
enable: boolean;
createTime: number;
updateTime: number;
language: string;
lastOrganizationId: string;
phone: string;
source: string;
lastProjectId: string;
createUser: string;
updateUser: string;
organizationList: OrganizationList[];
userRoleList: UserRoleListItem[];
}
export interface Filter {
[key: string]: any;
}
export interface Sort {
[key: string]: any;
}
// 创建用户模型
export interface CreateUserInfo {
name: string;
email: string;
phone?: string;
}
export interface CreateUserParams {
userInfoList: CreateUserInfo[];
userRoleIdList: string[];
}

View File

@ -18,6 +18,20 @@ export const successResponseWrap = (data: unknown) => {
};
};
/**
* mock-
* @param data mock
* @returns
*/
export const successTableResponseWrap = (data: unknown) => {
return {
data: { list: data },
status: 'ok',
message: '请求成功',
code: 0,
};
};
/**
* mock-
* @param data mock

View File

@ -42,6 +42,9 @@ export default {
'system.user.resetPassword': 'Reset PSW',
'system.user.disable': 'Disable',
'system.user.delete': 'Delete',
'system.user.enable': 'Enable',
'system.user.tableEnable': 'Enabled',
'system.user.tableDisable': 'Disabled',
'system.user.deleteUserStart': 'Are you sure to delete the user',
'system.user.deleteUserEnd': '?',
'system.user.deleteUserContent': "Only delete user information, does not handle the user's system data",
@ -54,6 +57,12 @@ export default {
'system.user.disableUserConfirm': 'Disabled',
'system.user.disableUserCancel': 'Cancel',
'system.user.disableUserSuccess': 'Disabled successful',
'system.user.enableUserStart': 'Are you sure to enable the user',
'system.user.enableUserEnd': '?',
'system.user.enableUserContent': 'Enabled users can log in to the system',
'system.user.enableUserConfirm': 'Enable',
'system.user.enableUserCancel': 'Cancel',
'system.user.enableUserSuccess': 'Enable successful',
'system.user.resetPswStart': 'Reset the password of',
'system.user.resetPswEnd': 'to its initial password?',
'system.user.resetPswContent': "The initial password is the user's mailbox, which will take effect at the next login",
@ -95,4 +104,31 @@ export default {
'system.user.importModalCancel': 'Cancel',
'system.user.importModalConfirm': 'Import',
'system.user.importModalUploading': 'File is uploading...',
'system.user.importSuccessTitle': 'Import user',
'system.user.importSuccess': 'Import succeeded',
'system.user.importFailTitle': 'Some user information failed to import',
'system.user.importAllfailTitle': 'User information import failed',
'system.user.importResultContentStart': 'Successfully imported user',
'system.user.importResultContentCenter': 'items, failed to import',
'system.user.importResultContentEnd': 'items;',
'system.user.importResultContentSubStart': 'You can',
'system.user.importResultContentDownload': 'Download bug report',
'system.user.importResultContentSubEnd': ',re-import after modification',
'system.user.importResultReturn': 'Return list',
'system.user.importResultContinue': 'Continue importing',
'system.user.importErrorFile': 'error report',
'system.user.batchActionAddProject': 'Add to project',
'system.user.batchActionAddUsergroup': 'Add to usergroup',
'system.user.batchActionAddOrgnization': 'Add to org',
'system.user.batchModalTip': 'Add project member usergroup as member by default',
'system.user.batchModalSubTitleStart': 'Selected',
'system.user.batchModalSubTitleEnd': 'users',
'system.user.batchModalCancel': 'Cancel',
'system.user.batchModalConfirm': 'Add',
'system.user.batchModalSuccess': 'Successfully added',
'system.user.batchAddProject': 'Batch add to project',
'system.user.batchAddUsergroup': 'Batch add to user group',
'system.user.batchAddOrgnization': 'Batch add to organization',
'system.user.batchOptional': 'Optional',
'system.user.batchChosen': 'Chosen',
};

View File

@ -42,6 +42,9 @@ export default {
'system.user.resetPassword': '重置密码',
'system.user.disable': '禁用',
'system.user.delete': '删除',
'system.user.enable': '启用',
'system.user.tableEnable': '正常',
'system.user.tableDisable': '禁用',
'system.user.deleteUserStart': '确认删除',
'system.user.deleteUserEnd': '这个用户吗?',
'system.user.deleteUserContent': '仅删除用户信息,不处理该用户的系统数据',
@ -54,6 +57,12 @@ export default {
'system.user.disableUserConfirm': '确认禁用',
'system.user.disableUserCancel': '取消',
'system.user.disableUserSuccess': '禁用成功',
'system.user.enableUserStart': '确认启用',
'system.user.enableUserEnd': '这个用户吗?',
'system.user.enableUserContent': '启用后用户可以登录系统',
'system.user.enableUserConfirm': '确认启用',
'system.user.enableUserCancel': '取消',
'system.user.enableUserSuccess': '启用成功',
'system.user.resetPswStart': '是否将',
'system.user.resetPswEnd': '的密码重置为初始密码?',
'system.user.resetPswContent': '初始的密码为用户邮箱,下次登录时生效',
@ -96,4 +105,31 @@ export default {
'system.user.importModalCancel': '取消',
'system.user.importModalConfirm': '导入',
'system.user.importModalUploading': '文件上传中...',
'system.user.importSuccessTitle': '导入用户',
'system.user.importSuccess': '导入成功',
'system.user.importFailTitle': '部分用户信息导入失败',
'system.user.importAllfailTitle': '用户信息导入失败',
'system.user.importResultContentStart': '成功导入用户',
'system.user.importResultContentCenter': '条,导入失败',
'system.user.importResultContentEnd': '条',
'system.user.importResultContentSubStart': '可',
'system.user.importResultContentDownload': '下载错误报告',
'system.user.importResultContentSubEnd': ',修改后重新导入',
'system.user.importResultReturn': '返回列表',
'system.user.importResultContinue': '继续导入',
'system.user.importErrorFile': '错误报告',
'system.user.batchActionAddProject': '添加至项目',
'system.user.batchActionAddUsergroup': '添加至用户组',
'system.user.batchActionAddOrgnization': '添加至组织',
'system.user.batchModalTip': '默认为成员添加项目成员用户组',
'system.user.batchModalSubTitleStart': '(已选',
'system.user.batchModalSubTitleEnd': '个用户)',
'system.user.batchModalCancel': '取消',
'system.user.batchModalConfirm': '添加',
'system.user.batchModalSuccess': '添加成功',
'system.user.batchAddProject': '批量添加至项目',
'system.user.batchAddUsergroup': '批量添加至用户组',
'system.user.batchAddOrgnization': '批量添加至组织',
'system.user.batchOptional': '可选',
'system.user.batchChosen': '已选',
};

View File

@ -0,0 +1,200 @@
<template>
<a-modal
v-model:visible="showBatchModal"
title-align="start"
class="ms-modal-upload ms-modal-medium"
:loading="batchLoading"
>
<template #title>
{{ batchTitle }}
<div class="text-[var(--color-text-4)]">
{{
`${t('system.user.batchModalSubTitleStart')} ${props.tableSelected.length} ${t(
'system.user.batchModalSubTitleEnd'
)}`
}}
</div>
</template>
<a-alert v-if="batchModalMode === 'project'" class="mb-[16px]">
{{ t('system.user.batchModalTip') }}
</a-alert>
<a-transfer
v-model="target"
:title="[t('system.user.batchOptional'), t('system.user.batchChosen')]"
:data="transferData"
show-search
>
<template #source="{ data, selectedKeys, onSelect }">
<a-tree
:checkable="true"
checked-strategy="child"
:checked-keys="selectedKeys"
:data="getTreeData(data)"
block-node
@check="onSelect"
/>
</template>
</a-transfer>
<template #footer>
<a-button type="secondary" @click="cancelBatch">{{ t('system.user.batchModalCancel') }}</a-button>
<a-button type="primary" @click="confirmBatch">
{{ t('system.user.batchModalConfirm') }}
</a-button>
</template>
</a-modal>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import { useI18n } from '@/hooks/useI18n';
import { Message } from '@arco-design/web-vue';
const { t } = useI18n();
interface TreeDataItem {
key: string;
title: string;
children?: TreeDataItem[];
}
interface TransferDataItem {
value: string;
label: string;
disabled: boolean;
}
const props = withDefaults(
defineProps<{
tableSelected: (string | number)[];
visible: boolean;
action: string;
treeData: TreeDataItem[];
}>(),
{
visible: false,
}
);
const emit = defineEmits(['update:visible']);
const showBatchModal = ref(false);
const batchLoading = ref(false);
const batchTitle = ref('');
const target = ref<string[]>([]);
const batchModalMode = ref<'project' | 'usergroup' | 'organization'>('project');
function handelTableBatch(action: string) {
switch (action) {
case 'batchAddProject':
batchModalMode.value = 'project';
batchTitle.value = t('system.user.batchAddProject');
break;
case 'batchAddUsergroup':
batchModalMode.value = 'usergroup';
batchTitle.value = t('system.user.batchAddUsergroup');
break;
case 'batchAddOrgnization':
batchModalMode.value = 'organization';
batchTitle.value = t('system.user.batchAddOrgnization');
break;
default:
break;
}
showBatchModal.value = true;
}
watch(
() => props.visible,
(val) => {
if (val) {
handelTableBatch(props.action);
}
},
{
immediate: true,
}
);
watch(
() => showBatchModal.value,
(val) => {
emit('update:visible', val);
}
);
/**
* 获取穿梭框数据根据树结构获取
* @param _treeData 树结构
* @param transferDataSource 穿梭框数组
*/
const getTransferData = (_treeData: TreeDataItem[], transferDataSource: TransferDataItem[]) => {
_treeData.forEach((item) => {
if (item.children) getTransferData(item.children, transferDataSource);
else transferDataSource.push({ label: item.title, value: item.key, disabled: false });
});
return transferDataSource;
};
/**
* 获取树结构数据根据穿梭框过滤的数据获取
*/
const getTreeData = (data: TransferDataItem[]) => {
const values = data.map((item) => item.value);
const travel = (_treeData: TreeDataItem[]) => {
const treeDataSource: TreeDataItem[] = [];
_treeData.forEach((item) => {
// push 穿
const allSelected = item.children?.every((child) => target.value.includes(child.key));
if (!allSelected && (item.children || values.includes(item.key))) {
treeDataSource.push({
title: item.title,
key: item.key,
children: item.children ? travel(item.children) : [],
});
}
});
return treeDataSource;
};
return travel(props.treeData);
};
const transferData = getTransferData(props.treeData, []);
function cancelBatch() {
showBatchModal.value = false;
target.value = [];
}
async function batchAddProject() {}
async function batchAddUsergroup() {}
async function batchAddOrgnization() {}
async function confirmBatch() {
batchLoading.value = true;
try {
switch (batchModalMode.value) {
case 'project':
await batchAddProject();
break;
case 'usergroup':
await batchAddUsergroup();
break;
case 'organization':
await batchAddOrgnization();
break;
default:
break;
}
Message.success(t('system.user.batchModalSuccess'));
showBatchModal.value = false;
} catch (error) {
console.log(error);
} finally {
batchLoading.value = false;
}
}
</script>
<style lang="less" scoped></style>

View File

@ -2,7 +2,7 @@
<div class="p-6">
<div class="mb-4 flex items-center justify-between">
<div>
<a-button class="mr-3" type="primary" @click="showUserModal('batch')">{{
<a-button class="mr-3" type="primary" @click="showUserModal('create')">{{
t('system.user.createUser')
}}</a-button>
<a-button class="mr-3" type="outline" @click="showEmailInviteModal">{{
@ -10,18 +10,68 @@
}}</a-button>
<a-button class="mr-3" type="outline" @click="showImportModal">{{ t('system.user.importUser') }}</a-button>
</div>
<div>
<a-input-search
:placeholder="t('system.user.searchUser')"
class="w-[230px]"
@search="searchUser"
></a-input-search>
</div>
<a-input-search
:placeholder="t('system.user.searchUser')"
class="w-[230px]"
@search="searchUser"
></a-input-search>
</div>
<ms-base-table v-bind="propsRes" v-on="propsEvent">
<ms-base-table
v-bind="propsRes"
:action-config="tableBatchActions"
v-on="propsEvent"
@selected-change="handleTableSelect"
@batch-action="handelTableBatch"
>
<template #organization="{ record }">
<a-tag
v-for="org of record.organizationList.slice(0, 2)"
:key="org.id"
class="mr-[4px] bg-transparent"
bordered
>
{{ org.name }}
</a-tag>
<a-tag v-show="record.organizationList.length > 2" class="mr-[4px] bg-transparent" bordered>
+{{ record.organizationList.length - 2 }}
</a-tag>
</template>
<template #userRole="{ record }">
<a-tag
v-for="org of record.userRoleList.slice(0, 2)"
:key="org.id"
class="mr-[4px] border-[rgb(var(--primary-5))] bg-transparent !text-[rgb(var(--primary-5))]"
bordered
>
{{ org.name }}
</a-tag>
<a-tag
v-show="record.organizationList.length > 2"
class="mr-[4px] border-[rgb(var(--primary-5))] bg-transparent !text-[rgb(var(--primary-5))]"
bordered
>
+{{ record.organizationList.length - 2 }}
</a-tag>
</template>
<template #enable="{ record }">
<div v-if="record.enable" class="flex items-center">
<icon-check-circle-fill class="mr-[2px] text-[rgb(var(--success-6))]" />
{{ t('system.user.tableEnable') }}
</div>
<div v-else class="flex items-center text-[var(--color-text-4)]">
<icon-stop class="mr-[2px]" />
{{ t('system.user.tableDisable') }}
</div>
</template>
<template #action="{ record }">
<MsButton @click="showUserModal('edit')">{{ t('system.user.editUser') }}</MsButton>
<MsTableMoreAction :list="tableActions" @select="handleSelect($event, record)"></MsTableMoreAction>
<template v-if="!record.enable">
<MsButton @click="enableUser(record)">{{ t('system.user.enable') }}</MsButton>
<MsButton @click="deleteUser(record)">{{ t('system.user.delete') }}</MsButton>
</template>
<template v-else>
<MsButton @click="showUserModal('edit', record)">{{ t('system.user.editUser') }}</MsButton>
<MsTableMoreAction :list="tableActions" @select="handleSelect($event, record)"></MsTableMoreAction>
</template>
</template>
</ms-base-table>
<a-modal
@ -102,7 +152,7 @@
</template>
</div>
</a-scrollbar>
<div class="w-full">
<div v-if="userFormMode === 'create'" class="w-full">
<a-button class="px-0" type="text" @click="addUserField">
<template #icon>
<icon-plus class="text-[14px]" />
@ -124,7 +174,7 @@
</a-form>
<template #footer>
<a-button type="secondary" @click="cancelCreate">{{ t('system.user.editUserModalCancelCreate') }}</a-button>
<a-button v-if="userFormMode === 'batch'" type="secondary" @click="saveAndContinue">{{
<a-button v-if="userFormMode === 'create'" type="secondary" @click="saveAndContinue">{{
t('system.user.editUserModalSaveAndContinue')
}}</a-button>
<a-button type="primary" @click="beforeCreateUser">{{
@ -187,26 +237,90 @@
</div>
</template>
</a-upload>
<template #footer>
<a-button type="secondary" @click="cancelImport">{{ t('system.user.importModalCancel') }}</a-button>
<a-button type="primary" @click="importUser">
{{ t('system.user.importModalConfirm') }}
</a-button>
</template>
</a-modal>
<a-modal v-model:visible="importResultVisible" title-align="start" class="ms-modal-upload">
<template #title>
<icon-exclamation-circle-fill
v-if="importResult === 'fail'"
class="mr-[8px] text-[20px] text-[rgb(var(--warning-6))]"
/>
<icon-close-circle-fill
v-if="importResult === 'allFail'"
class="mr-[8px] text-[20px] text-[rgb(var(--danger-6))]"
/>
{{ importResultTitle }}
</template>
<div v-if="importResult === 'success'" class="flex flex-col items-center justify-center">
<icon-check-circle-fill class="text-[32px] text-[rgb(var(--success-6))]" />
<div class="mb-[8px] mt-[16px] text-[16px] font-medium text-[var(--color-text-000)]">{{
t('system.user.importSuccess')
}}</div>
<div class="sub-text">{{
`${t('system.user.importResultContentStart')} ${importSuccessCount} ${t(
'system.user.importResultContentEnd'
)}`
}}</div>
</div>
<template v-else>
<div>{{
`${t('system.user.importResultContentStart')} ${importSuccessCount} ${t(
'system.user.importResultContentCenter'
)} ${importFailCount} ${t('system.user.importResultContentEnd')};`
}}</div>
<div
>{{ t('system.user.importResultContentSubStart')
}}<a-link
class="text-[rgb(var(--primary-5))]"
:href="importErrorFileUrl"
:download="`${t('system.user.importErrorFile')}.pdf`"
>{{ t('system.user.importResultContentDownload') }}</a-link
>{{ t('system.user.importResultContentSubEnd') }}</div
>
</template>
<template #footer>
<a-button type="text" class="!text-[var(--color-text-1)]" @click="cancelImport">{{
t('system.user.importResultReturn')
}}</a-button>
<a-button type="text" @click="continueImport">
{{ t('system.user.importResultContinue') }}
</a-button>
</template>
</a-modal>
<batchModal
v-model:visible="showBatchModal"
:table-selected="tableSelected"
:action="batchAction"
:tree-data="treeData"
></batchModal>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { Message } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es';
import useModal from '@/hooks/useModal';
import { useI18n } from '@/hooks/useI18n';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import useTable from '@/components/pure/ms-table/useTable';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import { getTableList } from '@/api/modules/api-test/index';
import { getUserList, batchCreateUser, updateUserInfo } from '@/api/modules/system/user';
import { validateEmail, validatePhone } from '@/utils/validate';
import { cloneDeep } from 'lodash-es';
import batchModal from './components/batchModal.vue';
import type { FormInstance, ValidatedError } from '@arco-design/web-vue';
import type { MsTableColumn } from '@/components/pure/ms-table/type';
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import type { UserListItem } from '@/models/system/user';
const { t } = useI18n();
const columns: MsTableColumn = [
{
@ -224,25 +338,45 @@
},
{
title: '组织',
dataIndex: 'organization',
slotName: 'organization',
dataIndex: 'organizationList',
},
{
title: '用户组',
dataIndex: 'userGroup',
slotName: 'userRole',
dataIndex: 'userRoleList',
},
{
title: '状态',
dataIndex: 'status',
slotName: 'enable',
dataIndex: 'enable',
},
{
title: '操作',
slotName: 'action',
fixed: 'right',
width: 80,
width: 90,
},
];
const { t } = useI18n();
const { propsRes, propsEvent, loadList, setKeyword } = useTable(getUserList, {
columns,
scroll: { y: 'auto' },
selectable: true,
});
const keyword = ref('');
onMounted(async () => {
setKeyword(keyword.value);
await loadList();
});
async function searchUser() {
setKeyword(keyword.value);
await loadList();
}
const tableActions: ActionsItem[] = [
{
label: 'system.user.resetPassword',
@ -262,8 +396,96 @@
},
];
const tableBatchActions = {
baseAction: [
{
label: 'system.user.batchActionAddProject',
eventTag: 'batchAddProject',
},
{
label: 'system.user.batchActionAddUsergroup',
eventTag: 'batchAddUsergroup',
},
{
label: 'system.user.batchActionAddOrgnization',
eventTag: 'batchAddOrgnization',
},
],
moreAction: [
{
label: 'system.user.disable',
eventTag: 'disabled',
},
{
label: 'system.user.enable',
eventTag: 'enable',
},
{
isDivider: true,
},
{
label: 'system.user.delete',
eventTag: 'delete',
danger: true,
},
],
};
const showBatchModal = ref(false);
const batchAction = ref('');
const treeData = ref([
{
title: 'Trunk 0-3',
key: '0-3',
},
{
title: 'Trunk 0-0',
key: '0-0',
children: [
{
title: 'Leaf 0-0-1',
key: '0-0-1',
},
{
title: 'Branch 0-0-2',
key: '0-0-2',
},
],
},
{
title: 'Trunk 0-1',
key: '0-1',
children: [
{
title: 'Branch 0-1-1',
key: '0-1-1',
},
{
title: 'Leaf 0-1-2',
key: '0-1-2',
},
],
},
]);
function handelTableBatch() {
batchAction.value = 'batchAddProject';
showBatchModal.value = true;
}
const tableSelected = ref<(string | number)[]>([]);
/**
* 处理表格选中
*/
function handleTableSelect(arr: (string | number)[]) {
tableSelected.value = arr;
}
const { openModal } = useModal();
/**
* 重置密码
*/
function resetPassword(record: any) {
openModal({
type: 'warning',
@ -284,6 +506,9 @@
});
}
/**
* 禁用用户
*/
function disabledUser(record: any) {
openModal({
type: 'warning',
@ -291,6 +516,9 @@
content: t('system.user.disableUserContent'),
okText: t('system.user.disableUserConfirm'),
cancelText: t('system.user.disableUserCancel'),
okButtonProps: {
status: 'danger',
},
onBeforeOk: async () => {
try {
Message.success(t('system.user.disableUserSuccess'));
@ -304,6 +532,32 @@
});
}
/**
* 启用用户
*/
function enableUser(record: any) {
openModal({
type: 'info',
title: `${t('system.user.enableUserStart')} '${record.name}' ${t('system.user.enableUserEnd')}`,
content: t('system.user.enableUserContent'),
okText: t('system.user.enableUserConfirm'),
cancelText: t('system.user.enableUserCancel'),
onBeforeOk: async () => {
try {
Message.success(t('system.user.enableUserSuccess'));
return true;
} catch (error) {
console.log(error);
return false;
}
},
hideCancel: false,
});
}
/**
* 删除用户
*/
function deleteUser(record: any) {
openModal({
type: 'warning',
@ -327,6 +581,10 @@
});
}
/**
* 处理表格更多按钮事件
* @param item
*/
function handleSelect(item: ActionsItem, record: any) {
switch (item.eventTag) {
case 'resetPassword':
@ -343,16 +601,7 @@
}
}
const { propsRes, propsEvent, loadList } = useTable(getTableList, {
columns,
selectable: true,
});
onMounted(async () => {
await loadList();
});
type UserModalMode = 'create' | 'edit' | 'batch';
type UserModalMode = 'create' | 'edit';
const visible = ref(false);
const loading = ref(false);
const userFormMode = ref<UserModalMode>('create');
@ -387,6 +636,10 @@
},
]);
/**
* 触发创建用户表单校验
* @param cb 校验通过后执行回调
*/
function userFormValidate(cb: () => Promise<any>) {
userFormRef.value?.validate(async (errors: undefined | Record<string, ValidatedError>) => {
if (errors) {
@ -403,17 +656,25 @@
});
}
/**
* 添加用户表单项
*/
function addUserField() {
userFormValidate(async () => {
const lastIndex = userForm.value.list.length - 1;
const lastOrder = userForm.value.list[lastIndex] + 1;
userForm.value.list.push(lastOrder); //
userForm.value.list.push(lastOrder); //
userForm.value[`username${lastOrder}`] = '';
userForm.value[`email${lastOrder}`] = '';
userForm.value[`phone${lastOrder}`] = '';
});
}
/**
* 移除用户表单项
* @param index 表单项的序号
* @param i 表单项对应 list 的下标
*/
function removeUserField(index: number, i: number) {
delete userForm.value[`username${index}`];
delete userForm.value[`email${index}`];
@ -421,6 +682,11 @@
userForm.value.list.splice(i, 1);
}
/**
* 校验用户姓名
* @param value 输入的值
* @param callback 失败回调入参是提示信息
*/
function checkUerName(value: string | undefined, callback: (error?: string) => void) {
if (value === '' || value === undefined) {
callback(t('system.user.createUserNameNotNull'));
@ -429,6 +695,12 @@
}
}
/**
* 校验用户邮箱
* @param value 输入的值
* @param callback 失败回调入参是提示信息
* @param index 当前输入的表单项对应 list 的下标用于校验重复输入的时候排除自身
*/
function checkUerEmail(value: string | undefined, callback: (error?: string) => void, index: number) {
if (value === '' || value === undefined) {
callback(t('system.user.createUserEmailNotNull'));
@ -447,12 +719,20 @@
}
}
/**
* 校验用户手机号
* @param value 输入的值
* @param callback 失败回调入参是提示信息
*/
function checkUerPhone(value: string | undefined, callback: (error?: string) => void) {
if (value !== '' && value !== undefined && !validatePhone(value)) {
callback(t('system.user.createUserPhoneErr'));
}
}
/**
* 取消创建重置用户表单
*/
function cancelCreate() {
visible.value = false;
userFormRef.value?.resetFields();
@ -460,13 +740,36 @@
}
async function updateUser() {
console.log('updateUser');
const params = {
userInfoList: [
{
name: userForm.value.username0,
email: userForm.value.email0,
phone: userForm.value.phone0,
},
],
userRoleIdList: userForm.value.userGroup,
};
await updateUserInfo(params);
}
async function createUser() {
console.log('createUser');
const params = {
userInfoList: userForm.value.list.map((item: number) => {
return {
name: userForm.value[`username${item}`],
email: userForm.value[`email${item}`],
phone: userForm.value[`phone${item}`],
};
}),
userRoleIdList: [],
};
await batchCreateUser(params);
}
/**
* 创建前的校验
*/
function beforeCreateUser() {
if (userFormMode.value === 'create') {
userFormValidate(createUser);
@ -475,6 +778,9 @@
}
}
/**
* 保存并继续创建重置用户表单
*/
function saveAndContinue() {
userFormValidate(async () => {
await createUser();
@ -483,9 +789,15 @@
});
}
function showUserModal(mode: UserModalMode) {
function showUserModal(mode: UserModalMode, record?: UserListItem) {
visible.value = true;
userFormMode.value = mode;
if (mode === 'edit' && record) {
userForm.value.username0 = record.name;
userForm.value.email0 = record.email;
userForm.value.phone0 = record.phone;
userForm.value.userGroup = record.userRoleList.map((e) => e.name);
}
}
const inviteVisible = ref(false);
@ -513,14 +825,55 @@
const importVisible = ref(false);
const importLoading = ref(false);
const importResultVisible = ref(false);
const importSuccessCount = ref(0);
const importFailCount = ref(0);
const importErrorFileUrl = ref('');
const importResult = ref<'success' | 'allFail' | 'fail'>('success');
const importResultTitle = ref(t('system.user.importSuccessTitle'));
function showImportModal() {
importVisible.value = true;
}
function importUser() {}
function cancelImport() {
importVisible.value = false;
importResultVisible.value = false;
}
async function searchUser() {}
/**
* 根据导入结果展示结果弹窗
*/
function showImportResult() {
importLoading.value = false;
importVisible.value = false;
switch (importResult.value) {
case 'success':
importResultTitle.value = t('system.user.importSuccessTitle');
break;
case 'allFail':
importResultTitle.value = t('system.user.importAllfailTitle');
break;
case 'fail':
importResultTitle.value = t('system.user.importFailTitle');
break;
default:
break;
}
importResultVisible.value = true;
}
function continueImport() {
importResultVisible.value = false;
importVisible.value = true;
}
function importUser() {
importResult.value = 'fail';
importSuccessCount.value = 100;
importFailCount.value = 20;
showImportResult();
}
</script>
<style lang="less" scoped>