10 changed files with 773 additions and 29 deletions
@ -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, |
|||
}); |
|||
} |
@ -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[]; |
|||
} |
@ -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 { |
|||
system: 'system', |
|||
dictionary: 'Dictionary', |
|||
user: 'User', |
|||
}; |
|||
|
@ -1,4 +1,5 @@ |
|||
export default { |
|||
system: '系统管理', |
|||
dictionary: '字典维护', |
|||
user: '用户管理', |
|||
}; |
|||
|
@ -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', |
|||
}, |
|||
}, |
|||
]; |
@ -0,0 +1,14 @@ |
|||
export const sexTypeList:any = [ |
|||
{ |
|||
label: '男', |
|||
value: 1, |
|||
}, |
|||
{ |
|||
label: '女', |
|||
value: 2, |
|||
}, |
|||
{ |
|||
label: '未知', |
|||
value: 3, |
|||
}, |
|||
]; |
@ -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: '备注', |
|||
}, |
|||
]; |
@ -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…
Reference in new issue