Browse Source

feat: add user manage module

master
AaronWu 2 months ago
parent
commit
11a8971ee9
  1. 16
      src/api/typings.d.ts
  2. 155
      src/api/user/index.ts
  3. 44
      src/api/user/model.d.ts
  4. 9
      src/locales/lang/en-US/routes/system.ts
  5. 1
      src/locales/lang/zh-CN/routes/system.ts
  6. 20
      src/router/staticModules/system.ts
  7. 143
      src/views/system/user/columns.tsx
  8. 14
      src/views/system/user/data.ts
  9. 100
      src/views/system/user/formSchemas.ts
  10. 254
      src/views/system/user/index.vue

16
src/api/typings.d.ts

@ -1,9 +1,15 @@
/*
* @Author: AaronWu 2463371514@qq.com
* @Date: 2025-03-31 15:12:17
* @LastEditors: AaronWu 2463371514@qq.com
* @LastEditTime: 2025-04-01 09:14:02
* @FilePath: /IssueSupportManage/src/api/typings.d.ts
* @Description: ,`customMade`, koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
// @ts-ignore // @ts-ignore
/* eslint-disable */ /* eslint-disable */
declare namespace API { declare namespace API {
/** 查询列表参数 */ /** 查询列表参数 */
type SearchListParams = { type SearchListParams = {
name?: string; name?: string;
@ -19,7 +25,7 @@ declare namespace API {
}; };
/** 查询列表成功结果 */ /** 查询列表成功结果 */
type SearchListResult = any[]
type SearchListResult = any[];
/** 查询分页列表成功结果 */ /** 查询分页列表成功结果 */
type SearchPageListResult = { type SearchPageListResult = {
@ -29,8 +35,8 @@ declare namespace API {
// total:string // total:string
// }; // };
// msg: string; // msg: string;
list:any[]
total:string
list: any[];
total: string;
}; };
/** 全局通过表格查询返回结果 */ /** 全局通过表格查询返回结果 */

155
src/api/user/index.ts

@ -0,0 +1,155 @@
import type { BaseResponse } from '@/utils/request';
import { request } from '@/utils/request';
/**
* @description
* @param {SearchListParams} data
* @returns
*/
export function fetchUserList(data: API.SearchListParams) {
return request<BaseResponse<API.SearchListResult>>(
{
url: 'user/query',
method: 'post',
data,
},
{
isGetDataDirectly: false,
},
);
}
/**
* @description
* @param {SearchPageListParams} data
* @returns
*/
export function fetchTenantBindUsers(data: any) {
return request<BaseResponse<API.SearchPageListResult>>(
{
url: `tenantUser/bindPage?tenantId=${data.tenantId}`,
method: 'post',
data,
},
{
isGetDataDirectly: false,
},
);
}
/**
* @description
* @param {SearchPageListParams} data
* @returns
*/
export function fetchUnTenantBindUsers(data: any) {
return request<BaseResponse<API.SearchPageListResult>>(
{
url: `tenantUser/unBindPage?tenantId=${data.tenantId}`,
method: 'post',
data,
},
{
isGetDataDirectly: false,
},
);
}
/**
* @description
* @param data
* @returns
*/
export function assignTenantAdmin(data: { tenantId?: string; userId?: string; isAdmin?: boolean }) {
return request({
url: `tenantUser/assignTenantAdmin`,
method: 'post',
data,
});
}
/**
* @description
* @param {SearchPageListParams} data
* @returns
*/
export function fetchUserPageList(data: API.SearchPageListParams) {
return request<BaseResponse<API.SearchPageListResult>>(
{
url: 'user/page',
method: 'post',
data,
},
{
isGetDataDirectly: false,
},
);
}
/**
* @description
* @param {UserInfoType} data
* @returns
*/
export function createUser(data: API.UserInfoType) {
return request<BaseResponse<API.SearchListResult>>(
{
url: 'user/create',
method: 'post',
data,
},
{
isGetDataDirectly: false,
},
);
}
/**
* @description
* @param {UserInfoType} data
* @returns
*/
export function updateUser(data: API.UserInfoType) {
return request<BaseResponse<API.SearchListResult>>(
{
url: 'user/update',
method: 'post',
data,
},
{
isGetDataDirectly: false,
},
);
}
/**
* @description
*/
export function findOneById(params: { id: string }) {
return request<API.SearchListResult>({
url: `user/getById`,
method: 'get',
params,
});
}
/**
* @description ?id=${data.id}
*/
export function deleteUserById(params: API.DeleteUserParams) {
return request<API.SearchListResult>({
url: `user/delById`,
method: 'post',
params,
});
}
/**
* @description
*/
export function deleteBatchUserById(data: API.DeleteBatchUserParams) {
return request<API.SearchListResult>({
url: `user/deleteBatch`,
method: 'delete',
data,
});
}

44
src/api/user/model.d.ts

@ -0,0 +1,44 @@
/*
* @Author: AaronWu 2463371514@qq.com
* @Date: 2025-04-01 09:09:04
* @LastEditors: AaronWu 2463371514@qq.com
* @LastEditTime: 2025-04-01 09:14:00
* @FilePath: /IssueSupportManage/src/api/user/model.d.ts
* @Description: ,`customMade`, koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
declare namespace API {
/** 用户信息类型 */
type UserInfoType = {
id?: string;
userId?: string;
pendingStatus?: boolean;
username: string;
realName: any;
mobile: string;
idCard: string;
sex: string;
avatar: string;
state: number;
remark: string;
createTime: string;
isAdmin?: boolean;
};
type CreateUserParams = {
username: string;
realName: any;
mobile: string;
idCard: string;
sex: string;
avatar: string;
state: number;
remark: string;
isAdmin?: boolean;
};
type DeleteUserParams = {
id: string;
};
type DeleteBatchUserParams = string[];
}

9
src/locales/lang/en-US/routes/system.ts

@ -1,4 +1,13 @@
/*
* @Author: AaronWu 2463371514@qq.com
* @Date: 2025-03-31 15:38:04
* @LastEditors: AaronWu 2463371514@qq.com
* @LastEditTime: 2025-04-01 09:11:14
* @FilePath: /IssueSupportManage/src/locales/lang/en-US/routes/system.ts
* @Description: ,`customMade`, koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
export default { export default {
system: 'system', system: 'system',
dictionary: 'Dictionary', dictionary: 'Dictionary',
user: 'User',
}; };

1
src/locales/lang/zh-CN/routes/system.ts

@ -1,4 +1,5 @@
export default { export default {
system: '系统管理', system: '系统管理',
dictionary: '字典维护', dictionary: '字典维护',
user: '用户管理',
}; };

20
src/router/staticModules/system.ts

@ -1,3 +1,11 @@
/*
* @Author: AaronWu 2463371514@qq.com
* @Date: 2025-03-31 15:36:46
* @LastEditors: AaronWu 2463371514@qq.com
* @LastEditTime: 2025-04-01 09:11:18
* @FilePath: /IssueSupportManage/src/router/staticModules/system.ts
* @Description: ,`customMade`, koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import type { RouteRecordRaw } from 'vue-router'; import type { RouteRecordRaw } from 'vue-router';
import RouterView from '@/layout/routerView/index.vue'; import RouterView from '@/layout/routerView/index.vue';
import { t } from '@/hooks/useI18n'; import { t } from '@/hooks/useI18n';
@ -25,7 +33,17 @@ const routes: Array<RouteRecordRaw> = [
component: () => component: () =>
import(/* webpackChunkName: "system-dictionary" */ '@/views/system/dictionary/index.vue'), import(/* webpackChunkName: "system-dictionary" */ '@/views/system/dictionary/index.vue'),
}, },
{
path: 'user',
name: `${moduleName}-user`,
meta: {
title: t('routes.system.user'),
icon: 'icon-yonghu',
keepAlive: true,
},
component: () =>
import(/* webpackChunkName: "tenant-user" */ '@/views/system/user/index.vue'),
},
], ],
}, },
]; ];

143
src/views/system/user/columns.tsx

@ -0,0 +1,143 @@
import { debounce } from 'lodash-es';
import type { TableColumn } from '@/components/core/dynamic-table';
import { formatToDateTime } from '@/utils/dateUtil';
import { updateUser } from '@/api/user';
import { h } from 'vue';
import { Switch } from 'ant-design-vue';
import { sexTypeList } from './data';
export type TableListItem = API.UserInfoType;
export type TableColumnItem = TableColumn<TableListItem>;
// 数据项类型
// export type ListItemType = typeof tableData[number];
// 使用TableColumn<ListItemType> 将会限制dataIndex的类型,但换来的是dataIndex有类型提示
export const baseColumns: TableColumnItem[] = [
// {
// title: '用户名',
// align: 'center',
// dataIndex: 'username',
// // sorter: true,
// width: 150,
// resizable: true,
// formItemProps: {
// defaultValue: '',
// required: false,
// },
// },
{
title: '姓名',
align: 'center',
dataIndex: 'realName',
width: 150,
resizable: true,
formItemProps: {
defaultValue: '',
required: false,
},
},
{
title: '手机号码',
align: 'center',
dataIndex: 'mobile',
width: 150,
formItemProps: {
defaultValue: '',
required: false,
},
},
// {
// title: '身份证',
// align: 'center',
// dataIndex: 'idCard',
// width: 150,
// formItemProps: {
// defaultValue: '',
// required: false,
// },
// },
{
title: '性别',
align: 'center',
dataIndex: 'sex',
width: 100,
formItemProps: {
defaultValue: '',
required: false,
component: 'Select',
label: '性别',
componentProps: {
mode: 'single',
request: async () => {
// const data = await getSexList();
return sexTypeList;
},
},
},
customRender: ({ record }) => {
const text = sexTypeList.find((e) => e.value === record.sex)?.label;
return <div>{text}</div>;
},
},
{
title: '帐号状态',
align: 'center',
width: 100,
dataIndex: 'state',
hideInSearch: true,
formItemProps: {
component: 'Select',
componentProps: {
options: [
{
label: '启用',
value: 1,
},
{
label: '禁用',
value: 0,
},
],
},
},
customRender: ({ record }) => {
const onChange = (checked: boolean) => {
console.log('checked: ', checked);
record.pendingStatus = true;
const newState = checked ? 1 : 0;
record.state = newState;
updateUser(record)
.then(() => {
record.state = newState;
})
.catch(() => {})
.finally(() => {
record.pendingStatus = false;
});
};
// return (
// <a-switch
// v-model:checked={record.state}
// v-model:loading={record.pendingStatus}
// onChange={onChange}
// ></a-switch>
// );
// 渲染函数写法
return h(Switch, {
checked: record.state === 1 ? true : false,
loading: record.pendingStatus,
onChange,
});
},
},
{
title: '创建时间',
align: 'center',
width: 200,
dataIndex: 'createTime',
formItemProps: {
defaultValue: '',
required: false,
component: 'RangePicker',
},
},
];

14
src/views/system/user/data.ts

@ -0,0 +1,14 @@
export const sexTypeList:any = [
{
label: '男',
value: 1,
},
{
label: '女',
value: 2,
},
{
label: '未知',
value: 3,
},
];

100
src/views/system/user/formSchemas.ts

@ -0,0 +1,100 @@
import type { FormSchema } from '@/components/core/schema-form/';
import { sexTypeList } from './data';
export const userSchemas: FormSchema<API.CreateUserParams>[] = [
// {
// field: 'username',
// component: 'Input',
// label: '用户名',
// colProps: {
// span: 12,
// },
// rules: [{ required: true }],
// },
{
field: 'realName',
component: 'Input',
label: '姓名',
colProps: {
span: 12,
},
rules: [{ required: true }],
},
{
field: 'mobile',
component: 'Input',
label: '手机号码',
colProps: {
span: 12,
},
rules: [
{
required: true,
message: '请输入正确格式的电话号码',
pattern: /^1(3[0-9]|4[01456879]|5[0-3,5-9]|6[2567]|7[0-8]|8[0-9]|9[0-3,5-9])\d{8}$/,
},
],
},
// {
// field: 'idCard',
// component: 'Input',
// label: '身份证',
// colProps: {
// span: 12,
// },
// },
{
field: 'sex',
component: 'Select',
label: '性别',
colProps: {
span: 12,
},
componentProps: {
mode: 'single',
request: async () => {
// const data = await getSexList();
return sexTypeList;
},
},
},
{
field: 'avatar',
component: 'Input',
colProps: {
span: 12,
},
label: '头像',
},
{
field: 'state',
component: 'RadioGroup',
label: '帐号状态',
defaultValue: true,
colProps: {
span: 12,
},
componentProps: {
options: [
{
label: '启用',
value: 1,
},
{
label: '禁用',
value: 0,
},
],
},
// required:true,
rules: [{ required: true, type: 'number' }],
},
{
field: 'remark',
component: 'InputTextArea',
label: '备注',
},
];

254
src/views/system/user/index.vue

@ -0,0 +1,254 @@
<!--
功能功能描述
作者Aaron.Wu
时间2023年05月25日 17:00:26
版本v1.0
修改记录
修改内容
修改人员
修改时间
-->
<template>
<DynamicTable
size="small"
showIndex
headerTitle="用户列表"
titleTooltip=""
:data-request="loadData"
:columns="columns"
row-key="id"
@resize-column="handleResizeColumn"
:row-selection="rowSelection"
:scroll="{ x: '100vw' }"
>
<template v-if="isCheckRows" #title>
<Alert class="w-full" type="info" show-icon>
<template #message>
已选 {{ isCheckRows }}
<a-button type="link" @click="rowSelection.selectedRowKeys = []">取消选择</a-button>
</template>
</Alert>
</template>
<template #toolbar>
<a-button type="primary" @click="openUserModal({})">新增</a-button>
<a-button
type="danger"
:disabled="!isCheckRows"
@click="delRowConfirm(rowSelection.selectedRowKeys)"
>
<DeleteOutlined /> 删除
</a-button>
</template>
</DynamicTable>
</template>
<script lang="tsx" setup>
import { type TableListItem, baseColumns } from './columns';
import { useTable, type OnChangeCallbackParams } from '@/components/core/dynamic-table';
import {
fetchUserList,
fetchUserPageList,
createUser,
updateUser,
findOneById,
deleteUserById,
deleteBatchUserById,
} from '@/api/user';
import { computed, reactive, ref, toRaw } from 'vue';
import { Modal, message, Alert } from 'ant-design-vue';
import { useFormModal } from '@/hooks/useModal/index';
import { userSchemas } from './formSchemas';
import { ExclamationCircleOutlined, DeleteOutlined } from '@ant-design/icons-vue';
defineOptions({
name: 'tanant-user',
});
const [showModal] = useFormModal();
const columns = [
...baseColumns,
{
title: '操作',
width: 200,
dataIndex: 'ACTION',
hideInSearch: true,
align: 'center',
fixed: 'right',
actions: ({ record }) => [
{
icon: 'searchoutlined',
color: '#3b82f6',
label: '查看',
// auth: {
// perm: 'sys.menu.update',
// effect: 'disable',
// },
onClick: () => handleShow(record),
},
{
icon: 'edit',
color: '#3b82f6',
size: '15',
label: '修改',
// auth: {
// perm: 'sys.menu.update',
// effect: 'disable',
// },
onClick: () => handleEdit(record),
},
{
icon: 'delete',
color: '#ec6f6f',
label: '删除',
popConfirm: {
title: '确定要删除吗?',
onConfirm: () => handleDelete(record.id),
},
},
],
},
];
/**
* @description 打开用户弹窗
*/
const openUserModal = async (record: Partial<TableListItem> = {}, isReadOnly = false) => {
const [formRef] = await showModal<any>({
modalProps: {
title: `${isReadOnly ? '查看' : record.id ? '编辑' : '新增'}用户`,
width: 700,
onFinish: async (values) => {
console.log('新增/编辑用户', values);
values.id = record.id;
await (record.id ? updateUser : createUser)(values);
message.success(`${record.id ? '编辑' : '新增'}成功`);
dynamicTableInstance?.reload();
},
footer: isReadOnly ? null : undefined,
},
formProps: {
labelWidth: 100,
schemas: userSchemas,
autoSubmitOnEnter: true,
disabled: isReadOnly,
},
});
formRef?.setFieldsValue(record);
// if (record?.id) {
// const { roles } = await getUserInfo({ userId: record.id });
// formRef?.setFieldsValue({ roles });
// }
};
const handleEdit = (record: TableListItem) => {
openUserModal(record);
};
const handleShow = (record: TableListItem) => {
openUserModal(record, true);
};
const handleDelete = (id: string) => {
delRowConfirm(id);
};
/**
* @description 表格删除行
*/
const delRowConfirm = async (id: string | string[]) => {
Modal.confirm({
title: '确定要删除所选的用户吗?',
icon: <ExclamationCircleOutlined />,
centered: true,
onOk: async () => {
if (Array.isArray(id)) {
//
await deleteBatchUserById(id).finally(dynamicTableInstance?.reload);
} else {
await deleteUserById({ id }).finally(dynamicTableInstance?.reload);
}
},
});
};
const [DynamicTable, dynamicTableInstance] = useTable();
const rowSelection = ref<any>({
selectedRowKeys: [] as string[],
onChange: (selectedRowKeys: string[], selectedRows: TableListItem[]) => {
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
rowSelection.value.selectedRowKeys = selectedRowKeys;
},
});
//
const isCheckRows = computed(() => rowSelection.value.selectedRowKeys.length);
const loadData = async (
params,
onChangeParams?: OnChangeCallbackParams,
): Promise<API.TableListResult> => {
console.log('params', params);
console.log('onChangeParams', onChangeParams);
//
// dynamicTableInstance?.getQueryFormRef()?.updateSchema?.([
// {
// field: 'price',
// componentProps: {
// options: [
// {
// label: '0-199',
// value: '0-199',
// },
// {
// label: '200-999',
// value: '200-999',
// },
// ],
// },
// },
// ]);
return new Promise((resolve) => {
setTimeout(() => {
resolve({
list: [
{
id: '1',
name: '1',
},
],
...params,
});
}, 500);
});
const res = await fetchUserPageList({
model: {
...params,
},
current: params.page,
size: params.limit,
});
console.log('res: ', res);
rowSelection.value.selectedRowKeys = [];
return {
list: res.data,
pagination: {
total: Number(res.totalCount),
...params,
},
};
};
const handleResizeColumn = (w, col) => {
// console.log('w', w, col);
col.width = w;
};
</script>
<style lang="less" scoped>
.action-divider {
margin: 0 5px;
}
</style>
Loading…
Cancel
Save