mirror of
http://183.237.82.114:10086/kilo/leaser_front.git
synced 2026-10-23 20:36:02 +00:00
feat: 设置模块用户列表功能90%
This commit is contained in:
parent
1cfa0b6e4c
commit
293beb9f5d
17 changed files with 1758 additions and 147 deletions
|
|
@ -67,5 +67,7 @@ module.exports = {
|
||||||
'no-param-reassign': 0,
|
'no-param-reassign': 0,
|
||||||
'prefer-regex-literals': 0,
|
'prefer-regex-literals': 0,
|
||||||
'import/no-extraneous-dependencies': 0,
|
'import/no-extraneous-dependencies': 0,
|
||||||
|
// 禁用 prefer-destructuring 规则,因为它对对象解构和数组解构都产生错误警告
|
||||||
|
'prefer-destructuring': 0,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@arco-design/web-vue": "^2.44.7",
|
"@arco-design/web-vue": "^2.57.0",
|
||||||
"@vueuse/core": "^9.3.0",
|
"@vueuse/core": "^9.3.0",
|
||||||
"arco-design-pro-vue": "^2.7.3",
|
"arco-design-pro-vue": "^2.7.3",
|
||||||
"axios": "^0.24.0",
|
"axios": "^0.24.0",
|
||||||
|
|
@ -49,7 +49,6 @@
|
||||||
"vue-router": "^4.0.14"
|
"vue-router": "^4.0.14"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@arco-design/web-vue": "^2.0.0-beta.7",
|
|
||||||
"@arco-plugins/vite-vue": "^1.4.5",
|
"@arco-plugins/vite-vue": "^1.4.5",
|
||||||
"@commitlint/cli": "^17.1.2",
|
"@commitlint/cli": "^17.1.2",
|
||||||
"@commitlint/config-conventional": "^17.1.0",
|
"@commitlint/config-conventional": "^17.1.0",
|
||||||
|
|
@ -102,8 +101,5 @@
|
||||||
"bin-wrapper": "npm:bin-wrapper-china",
|
"bin-wrapper": "npm:bin-wrapper-china",
|
||||||
"rollup": "^2.56.3",
|
"rollup": "^2.56.3",
|
||||||
"gifsicle": "5.2.0"
|
"gifsicle": "5.2.0"
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@arco-design/web-vue": ">=2.0.0-beta.7"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
22
pnpm-lock.yaml
generated
22
pnpm-lock.yaml
generated
|
|
@ -14,7 +14,7 @@ importers:
|
||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@arco-design/web-vue':
|
'@arco-design/web-vue':
|
||||||
specifier: ^2.44.7
|
specifier: ^2.57.0
|
||||||
version: 2.57.0(vue@3.5.17(typescript@4.9.5))
|
version: 2.57.0(vue@3.5.17(typescript@4.9.5))
|
||||||
'@vueuse/core':
|
'@vueuse/core':
|
||||||
specifier: ^9.3.0
|
specifier: ^9.3.0
|
||||||
|
|
@ -115,7 +115,7 @@ importers:
|
||||||
version: 8.57.1
|
version: 8.57.1
|
||||||
eslint-config-airbnb-base:
|
eslint-config-airbnb-base:
|
||||||
specifier: ^15.0.0
|
specifier: ^15.0.0
|
||||||
version: 15.0.0(eslint-plugin-import@2.32.0)(eslint@8.57.1)
|
version: 15.0.0(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1))(eslint@8.57.1)
|
||||||
eslint-config-prettier:
|
eslint-config-prettier:
|
||||||
specifier: ^8.5.0
|
specifier: ^8.5.0
|
||||||
version: 8.10.0(eslint@8.57.1)
|
version: 8.10.0(eslint@8.57.1)
|
||||||
|
|
@ -770,41 +770,49 @@ packages:
|
||||||
resolution: {integrity: sha512-UYA0MA8ajkEDCFRQdng/FVx3F6szBvk3EPnkTTQuuO9lV1kPGuTB+V9TmbDxy5ikaEgyWKxa4CI3ySjklZ9lFA==}
|
resolution: {integrity: sha512-UYA0MA8ajkEDCFRQdng/FVx3F6szBvk3EPnkTTQuuO9lV1kPGuTB+V9TmbDxy5ikaEgyWKxa4CI3ySjklZ9lFA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-arm64-musl@1.9.2':
|
'@unrs/resolver-binding-linux-arm64-musl@1.9.2':
|
||||||
resolution: {integrity: sha512-P/CO3ODU9YJIHFqAkHbquKtFst0COxdphc8TKGL5yCX75GOiVpGqd1d15ahpqu8xXVsqP4MGFP2C3LRZnnL5MA==}
|
resolution: {integrity: sha512-P/CO3ODU9YJIHFqAkHbquKtFst0COxdphc8TKGL5yCX75GOiVpGqd1d15ahpqu8xXVsqP4MGFP2C3LRZnnL5MA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-ppc64-gnu@1.9.2':
|
'@unrs/resolver-binding-linux-ppc64-gnu@1.9.2':
|
||||||
resolution: {integrity: sha512-uKStFlOELBxBum2s1hODPtgJhY4NxYJE9pAeyBgNEzHgTqTiVBPjfTlPFJkfxyTjQEuxZbbJlJnMCrRgD7ubzw==}
|
resolution: {integrity: sha512-uKStFlOELBxBum2s1hODPtgJhY4NxYJE9pAeyBgNEzHgTqTiVBPjfTlPFJkfxyTjQEuxZbbJlJnMCrRgD7ubzw==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-riscv64-gnu@1.9.2':
|
'@unrs/resolver-binding-linux-riscv64-gnu@1.9.2':
|
||||||
resolution: {integrity: sha512-LkbNnZlhINfY9gK30AHs26IIVEZ9PEl9qOScYdmY2o81imJYI4IMnJiW0vJVtXaDHvBvxeAgEy5CflwJFIl3tQ==}
|
resolution: {integrity: sha512-LkbNnZlhINfY9gK30AHs26IIVEZ9PEl9qOScYdmY2o81imJYI4IMnJiW0vJVtXaDHvBvxeAgEy5CflwJFIl3tQ==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-riscv64-musl@1.9.2':
|
'@unrs/resolver-binding-linux-riscv64-musl@1.9.2':
|
||||||
resolution: {integrity: sha512-vI+e6FzLyZHSLFNomPi+nT+qUWN4YSj8pFtQZSFTtmgFoxqB6NyjxSjAxEC1m93qn6hUXhIsh8WMp+fGgxCoRg==}
|
resolution: {integrity: sha512-vI+e6FzLyZHSLFNomPi+nT+qUWN4YSj8pFtQZSFTtmgFoxqB6NyjxSjAxEC1m93qn6hUXhIsh8WMp+fGgxCoRg==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-s390x-gnu@1.9.2':
|
'@unrs/resolver-binding-linux-s390x-gnu@1.9.2':
|
||||||
resolution: {integrity: sha512-sSO4AlAYhSM2RAzBsRpahcJB1msc6uYLAtP6pesPbZtptF8OU/CbCPhSRW6cnYOGuVmEmWVW5xVboAqCnWTeHQ==}
|
resolution: {integrity: sha512-sSO4AlAYhSM2RAzBsRpahcJB1msc6uYLAtP6pesPbZtptF8OU/CbCPhSRW6cnYOGuVmEmWVW5xVboAqCnWTeHQ==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-x64-gnu@1.9.2':
|
'@unrs/resolver-binding-linux-x64-gnu@1.9.2':
|
||||||
resolution: {integrity: sha512-jkSkwch0uPFva20Mdu8orbQjv2A3G88NExTN2oPTI1AJ+7mZfYW3cDCTyoH6OnctBKbBVeJCEqh0U02lTkqD5w==}
|
resolution: {integrity: sha512-jkSkwch0uPFva20Mdu8orbQjv2A3G88NExTN2oPTI1AJ+7mZfYW3cDCTyoH6OnctBKbBVeJCEqh0U02lTkqD5w==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-x64-musl@1.9.2':
|
'@unrs/resolver-binding-linux-x64-musl@1.9.2':
|
||||||
resolution: {integrity: sha512-Uk64NoiTpQbkpl+bXsbeyOPRpUoMdcUqa+hDC1KhMW7aN1lfW8PBlBH4mJ3n3Y47dYE8qi0XTxy1mBACruYBaw==}
|
resolution: {integrity: sha512-Uk64NoiTpQbkpl+bXsbeyOPRpUoMdcUqa+hDC1KhMW7aN1lfW8PBlBH4mJ3n3Y47dYE8qi0XTxy1mBACruYBaw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@unrs/resolver-binding-wasm32-wasi@1.9.2':
|
'@unrs/resolver-binding-wasm32-wasi@1.9.2':
|
||||||
resolution: {integrity: sha512-EpBGwkcjDicjR/ybC0g8wO5adPNdVuMrNalVgYcWi+gYtC1XYNuxe3rufcO7dA76OHGeVabcO6cSkPJKVcbCXQ==}
|
resolution: {integrity: sha512-EpBGwkcjDicjR/ybC0g8wO5adPNdVuMrNalVgYcWi+gYtC1XYNuxe3rufcO7dA76OHGeVabcO6cSkPJKVcbCXQ==}
|
||||||
|
|
@ -4753,7 +4761,7 @@ snapshots:
|
||||||
'@types/node': 20.5.1
|
'@types/node': 20.5.1
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
cosmiconfig: 8.3.6(typescript@4.9.5)
|
cosmiconfig: 8.3.6(typescript@4.9.5)
|
||||||
cosmiconfig-typescript-loader: 4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6(typescript@4.9.5))(ts-node@10.9.2(@types/node@24.0.3)(typescript@4.9.5))(typescript@4.9.5)
|
cosmiconfig-typescript-loader: 4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6(typescript@4.9.5))(ts-node@10.9.2(@types/node@20.5.1)(typescript@4.9.5))(typescript@4.9.5)
|
||||||
lodash.isplainobject: 4.0.6
|
lodash.isplainobject: 4.0.6
|
||||||
lodash.merge: 4.6.2
|
lodash.merge: 4.6.2
|
||||||
lodash.uniq: 4.5.0
|
lodash.uniq: 4.5.0
|
||||||
|
|
@ -5839,7 +5847,7 @@ snapshots:
|
||||||
|
|
||||||
core-util-is@1.0.3: {}
|
core-util-is@1.0.3: {}
|
||||||
|
|
||||||
cosmiconfig-typescript-loader@4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6(typescript@4.9.5))(ts-node@10.9.2(@types/node@24.0.3)(typescript@4.9.5))(typescript@4.9.5):
|
cosmiconfig-typescript-loader@4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6(typescript@4.9.5))(ts-node@10.9.2(@types/node@20.5.1)(typescript@4.9.5))(typescript@4.9.5):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.5.1
|
'@types/node': 20.5.1
|
||||||
cosmiconfig: 8.3.6(typescript@4.9.5)
|
cosmiconfig: 8.3.6(typescript@4.9.5)
|
||||||
|
|
@ -6424,7 +6432,7 @@ snapshots:
|
||||||
|
|
||||||
escape-string-regexp@5.0.0: {}
|
escape-string-regexp@5.0.0: {}
|
||||||
|
|
||||||
eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.32.0)(eslint@8.57.1):
|
eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1))(eslint@8.57.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
confusing-browser-globals: 1.0.11
|
confusing-browser-globals: 1.0.11
|
||||||
eslint: 8.57.1
|
eslint: 8.57.1
|
||||||
|
|
@ -6460,7 +6468,7 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1):
|
eslint-module-utils@2.12.1(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1))(eslint@8.57.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 3.2.7
|
debug: 3.2.7
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
|
|
@ -6482,7 +6490,7 @@ snapshots:
|
||||||
doctrine: 2.1.0
|
doctrine: 2.1.0
|
||||||
eslint: 8.57.1
|
eslint: 8.57.1
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
|
eslint-module-utils: 2.12.1(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1))(eslint@8.57.1)
|
||||||
hasown: 2.0.2
|
hasown: 2.0.2
|
||||||
is-core-module: 2.16.1
|
is-core-module: 2.16.1
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,18 @@ export interface DepartmentIdData {
|
||||||
departmentId: number | string;
|
departmentId: number | string;
|
||||||
}
|
}
|
||||||
type DepartmentFormRes = HttpResponse<DepartmentIdData>;
|
type DepartmentFormRes = HttpResponse<DepartmentIdData>;
|
||||||
|
// 创建部门
|
||||||
|
export function createDepartment(
|
||||||
|
params: DepartmentFormParams
|
||||||
|
): Promise<DepartmentFormRes> {
|
||||||
|
return axios.post('/department/createDepartment', params);
|
||||||
|
}
|
||||||
|
// 更新(编辑)部门
|
||||||
|
export function updateDepartment(
|
||||||
|
params: DepartmentFormParams
|
||||||
|
): Promise<DepartmentFormRes> {
|
||||||
|
return axios.post('/department/updateDepartment', params);
|
||||||
|
}
|
||||||
export interface DepartmentItem {
|
export interface DepartmentItem {
|
||||||
id: number;
|
id: number;
|
||||||
systemTenantId: number;
|
systemTenantId: number;
|
||||||
|
|
@ -41,22 +52,11 @@ export interface DepartmentDetailData {
|
||||||
}
|
}
|
||||||
export type DepartmentDetailRes = HttpResponse<DepartmentDetailData>;
|
export type DepartmentDetailRes = HttpResponse<DepartmentDetailData>;
|
||||||
|
|
||||||
// 创建部门
|
|
||||||
export function createDepartment(
|
|
||||||
params: DepartmentFormParams
|
|
||||||
): Promise<DepartmentFormRes> {
|
|
||||||
return axios.post('/department/createDepartment', params);
|
|
||||||
}
|
|
||||||
// 获取部门列表
|
// 获取部门列表
|
||||||
export function getDepartmentList(): Promise<DepartmentListRes> {
|
export function getDepartmentList(): Promise<DepartmentListRes> {
|
||||||
return axios.get('/department/getDepartmentList');
|
return axios.get('/department/getDepartmentList');
|
||||||
}
|
}
|
||||||
// 更新(编辑)部门
|
|
||||||
export function updateDepartment(
|
|
||||||
params: DepartmentFormParams
|
|
||||||
): Promise<DepartmentFormRes> {
|
|
||||||
return axios.post('/department/updateDepartment', params);
|
|
||||||
}
|
|
||||||
export function getDepartmentDetail(id: number): Promise<DepartmentDetailRes> {
|
export function getDepartmentDetail(id: number): Promise<DepartmentDetailRes> {
|
||||||
return axios.get(`/department/getDepartment?id=${id}`);
|
return axios.get(`/department/getDepartment?id=${id}`);
|
||||||
}
|
}
|
||||||
|
|
@ -68,19 +68,73 @@ export interface UserItem {
|
||||||
email: string; // 邮箱
|
email: string; // 邮箱
|
||||||
tel: string; // 手机号
|
tel: string; // 手机号
|
||||||
status: 1 | 2; // 状态:1 启用,2 禁用
|
status: 1 | 2; // 状态:1 启用,2 禁用
|
||||||
role_id: number; // 角色id
|
roleId: number; // 角色id
|
||||||
department_id: number; // 部门id
|
departmentId: number; // 部门id
|
||||||
role_name: string; // 角色名称
|
roleName: string; // 角色名称
|
||||||
dept_name: string; // 部门名称
|
deptName: string; // 部门名称
|
||||||
lastLoginTime: string; // 最后登录时间
|
lastLoginTime: string; // 最后登录时间
|
||||||
}
|
}
|
||||||
export type UserList = UserItem[];
|
export type UserList = UserItem[];
|
||||||
export type UserListRes = HttpResponse<UserList>;
|
export type UserListRes = HttpResponse<UserList>;
|
||||||
|
export interface Pagination {
|
||||||
|
page: number;
|
||||||
|
page_size: number;
|
||||||
|
total?: number;
|
||||||
|
total_pages?: number;
|
||||||
|
}
|
||||||
|
export interface AllUserList {
|
||||||
|
page: Pagination;
|
||||||
|
user: UserListRes;
|
||||||
|
}
|
||||||
|
export type AllUserListRes = HttpResponse<AllUserList>;
|
||||||
// 后台-用户列表(除主管外所有用户)
|
// 后台-用户列表(除主管外所有用户)
|
||||||
export function getAllUserList(params): Promise<UserListRes> {
|
export function getAllUserList(params): Promise<AllUserListRes> {
|
||||||
return axios.post('/user/getAllUserList', params);
|
return axios.post('/user/getAllUserList', params);
|
||||||
}
|
}
|
||||||
|
// 后台-用户列表(所有用户)
|
||||||
|
export function getUserList(params): Promise<AllUserListRes> {
|
||||||
|
return axios.post('/user/getUserList', params);
|
||||||
|
}
|
||||||
|
export interface FormUserItem {
|
||||||
|
username: string; // 用户名
|
||||||
|
realname: string; // 姓名
|
||||||
|
tel: string | number; // 手机号
|
||||||
|
password: string; // 密码
|
||||||
|
repassword: string; // 确认密码
|
||||||
|
role_id: number | null; // 角色id
|
||||||
|
department_id: number | null; // 部门id
|
||||||
|
email: string; // 邮件
|
||||||
|
is_manager: 1 | 2; // 是否为主管:1是,2否
|
||||||
|
}
|
||||||
|
// 创建用户
|
||||||
|
export function createUser(params: FormUserItem): Promise<HttpResponse> {
|
||||||
|
return axios.post('/user/createUser', params);
|
||||||
|
}
|
||||||
|
// 更新用户信息
|
||||||
|
export function updateUser(params: FormUserItem): Promise<HttpResponse> {
|
||||||
|
return axios.post('/user/updateUser', params);
|
||||||
|
}
|
||||||
|
interface UserInfo {
|
||||||
|
user: FormUserItem;
|
||||||
|
}
|
||||||
|
export type UserInfoRES = HttpResponse<UserInfo>;
|
||||||
|
// 获取用户信息
|
||||||
|
export function getUserInfo(id: number): Promise<UserInfoRES> {
|
||||||
|
return axios.get(`/user/getUser?id=${id}`);
|
||||||
|
}
|
||||||
|
// 删除用户
|
||||||
|
export function delUser(id: number): Promise<HttpResponse> {
|
||||||
|
return axios.get(`/user/delUser?id=${id}`);
|
||||||
|
}
|
||||||
|
export interface DisableUserParam {
|
||||||
|
id: number;
|
||||||
|
status: number;
|
||||||
|
}
|
||||||
|
// 禁用启用用户
|
||||||
|
export function disableUser(params: DisableUserParam): Promise<HttpResponse> {
|
||||||
|
return axios.post('/user/disableUser', params);
|
||||||
|
}
|
||||||
|
|
||||||
// 部门-向上移动
|
// 部门-向上移动
|
||||||
export function departmentSetUp(id: number): Promise<HttpResponse> {
|
export function departmentSetUp(id: number): Promise<HttpResponse> {
|
||||||
return axios.get(`/department/setUp?id=${id}`);
|
return axios.get(`/department/setUp?id=${id}`);
|
||||||
|
|
@ -93,6 +147,46 @@ export function departmentSetDown(id: number): Promise<HttpResponse> {
|
||||||
export function delDepartment(id: number): Promise<HttpResponse> {
|
export function delDepartment(id: number): Promise<HttpResponse> {
|
||||||
return axios.get(`/department/delDepartment?id=${id}`);
|
return axios.get(`/department/delDepartment?id=${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RoleItem {
|
||||||
|
id: number; // 角色id
|
||||||
|
systemTenantId: number; // 租户名称
|
||||||
|
name: string; // 角色名称
|
||||||
|
createUserId?: number; // 创建人id
|
||||||
|
isDelete?: number; // 是否删除
|
||||||
|
remark?: string;
|
||||||
|
dataType: number; // 数据权限类型:1全部数据权限,2自定义数据权限,3仅本人数据
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
// 角色列表
|
||||||
|
export interface RoleList {
|
||||||
|
rolesList: RoleItem[];
|
||||||
|
}
|
||||||
|
export type RoleListRes = HttpResponse<RoleList>;
|
||||||
|
// 角色列表
|
||||||
|
export function getRoleList(): Promise<RoleListRes> {
|
||||||
|
return axios.get('/role/getRoleList');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建角色
|
||||||
|
export interface RoleFormParam {
|
||||||
|
id?: number; // 角色id
|
||||||
|
name: string; // 姓名
|
||||||
|
remark?: string; // 备注
|
||||||
|
}
|
||||||
|
export function createRole(params: RoleFormParam): Promise<HttpResponse> {
|
||||||
|
return axios.post('/role/createRole', params);
|
||||||
|
}
|
||||||
|
// 更新角色
|
||||||
|
export function updateRole(params: RoleFormParam): Promise<HttpResponse> {
|
||||||
|
return axios.post('/role/updateRole', params);
|
||||||
|
}
|
||||||
|
// 删除角色
|
||||||
|
export function delRole(id: number): Promise<HttpResponse> {
|
||||||
|
return axios.get(`/role/delRole?id=${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
// // 项目信息
|
// // 项目信息
|
||||||
// export function getProjectDetail(id: string) {
|
// export function getProjectDetail(id: string) {
|
||||||
// return axios.get<any>(`/project/getProject?id=${id}`);
|
// return axios.get<any>(`/project/getProject?id=${id}`);
|
||||||
|
|
|
||||||
236
src/components/common/DataTable.md
Normal file
236
src/components/common/DataTable.md
Normal file
|
|
@ -0,0 +1,236 @@
|
||||||
|
# DataTable 组件使用指南
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
`DataTable` 是一个基于 Arco Design Vue 的 `a-table` 组件封装,提供了统一的分页配置和事件处理,可以在多个页面中复用。
|
||||||
|
|
||||||
|
## 特性
|
||||||
|
|
||||||
|
- 🚀 统一的分页配置
|
||||||
|
- 📊 支持自定义列配置
|
||||||
|
- 🔄 内置加载状态管理
|
||||||
|
- 🎯 支持排序和筛选
|
||||||
|
- 📱 响应式设计
|
||||||
|
- 🎨 可自定义样式
|
||||||
|
|
||||||
|
## 基础用法
|
||||||
|
|
||||||
|
### 1. 使用 DataTable 组件
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<DataTable
|
||||||
|
:data="tableData"
|
||||||
|
:columns="columns"
|
||||||
|
:loading="loading"
|
||||||
|
:total="total"
|
||||||
|
:current="current"
|
||||||
|
:page-size="pageSize"
|
||||||
|
@page-change="handlePageChange"
|
||||||
|
@page-size-change="handlePageSizeChange"
|
||||||
|
>
|
||||||
|
<!-- 自定义列插槽 -->
|
||||||
|
<template #status="{ record }">
|
||||||
|
<a-tag :color="record.status === 'active' ? 'green' : 'red'">
|
||||||
|
{{ record.status }}
|
||||||
|
</a-tag>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #operate="{ record }">
|
||||||
|
<a-space>
|
||||||
|
<a-button size="small">查看</a-button>
|
||||||
|
<a-button size="small" type="primary">编辑</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</DataTable>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import type { TableColumnData, TableData } from '@arco-design/web-vue';
|
||||||
|
import useDataTable from '@/composables/useDataTable';
|
||||||
|
|
||||||
|
// 使用 DataTable composable
|
||||||
|
const {
|
||||||
|
data: tableData,
|
||||||
|
columns,
|
||||||
|
loading,
|
||||||
|
total,
|
||||||
|
current,
|
||||||
|
pageSize,
|
||||||
|
setData,
|
||||||
|
setColumns,
|
||||||
|
setLoading,
|
||||||
|
setTotal,
|
||||||
|
setCurrent,
|
||||||
|
setPageSize,
|
||||||
|
} = useDataTable({
|
||||||
|
initialPage: 1,
|
||||||
|
initialPageSize: 10,
|
||||||
|
pageSizeOptions: [10, 20, 50, 100],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 列配置
|
||||||
|
const columnConfig: TableColumnData[] = [
|
||||||
|
{ title: '姓名', dataIndex: 'name', key: 'name' },
|
||||||
|
{ title: '年龄', dataIndex: 'age', key: 'age' },
|
||||||
|
{ title: '状态', dataIndex: 'status', key: 'status', slotName: 'status' },
|
||||||
|
{ title: '操作', key: 'operate', slotName: 'operate', width: 200 },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 模拟数据
|
||||||
|
const mockData: TableData[] = [
|
||||||
|
{ id: 1, name: '张三', age: 25, status: 'active' },
|
||||||
|
{ id: 2, name: '李四', age: 30, status: 'inactive' },
|
||||||
|
];
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setColumns(columnConfig);
|
||||||
|
setData(mockData);
|
||||||
|
setTotal(mockData.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 分页事件处理
|
||||||
|
const handlePageChange = (page: number) => {
|
||||||
|
setCurrent(page);
|
||||||
|
// 调用 API 获取数据
|
||||||
|
fetchData(page, pageSize.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePageSizeChange = (pageSize: number) => {
|
||||||
|
setPageSize(pageSize);
|
||||||
|
// 调用 API 获取数据
|
||||||
|
fetchData(1, pageSize);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchData = async (page: number, size: number) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
// 调用 API
|
||||||
|
// const { data, total } = await api.getList({ page, size });
|
||||||
|
// setData(data);
|
||||||
|
// setTotal(total);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 高级配置
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<DataTable
|
||||||
|
:data="tableData"
|
||||||
|
:columns="columns"
|
||||||
|
:loading="loading"
|
||||||
|
:total="total"
|
||||||
|
:current="current"
|
||||||
|
:page-size="pageSize"
|
||||||
|
:bordered="true"
|
||||||
|
:stripe="true"
|
||||||
|
:size="'small'"
|
||||||
|
:scroll="{ x: 1200, y: 400 }"
|
||||||
|
@page-change="handlePageChange"
|
||||||
|
@page-size-change="handlePageSizeChange"
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
|
@sorter-change="handleSorterChange"
|
||||||
|
@filter-change="handleFilterChange"
|
||||||
|
>
|
||||||
|
<!-- 插槽内容 -->
|
||||||
|
</DataTable>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Props 说明
|
||||||
|
|
||||||
|
| 属性 | 类型 | 默认值 | 说明 |
|
||||||
|
|------|------|--------|------|
|
||||||
|
| data | TableData[] | [] | 表格数据 |
|
||||||
|
| columns | TableColumnData[] | [] | 列配置 |
|
||||||
|
| loading | boolean | false | 加载状态 |
|
||||||
|
| rowKey | string | 'id' | 行键 |
|
||||||
|
| pagination | boolean \| PaginationProps | true | 分页配置 |
|
||||||
|
| bordered | boolean | false | 是否显示边框 |
|
||||||
|
| size | 'mini' \| 'small' \| 'medium' \| 'large' | 'medium' | 表格大小 |
|
||||||
|
| stripe | boolean | false | 是否显示斑马纹 |
|
||||||
|
| tableLayout | 'auto' \| 'fixed' | 'auto' | 表格布局 |
|
||||||
|
| scroll | object | - | 滚动配置 |
|
||||||
|
| current | number | 1 | 当前页码 |
|
||||||
|
| pageSize | number | 10 | 每页条数 |
|
||||||
|
| total | number | 0 | 总条数 |
|
||||||
|
| showTotal | boolean | true | 是否显示总数 |
|
||||||
|
| pageSizeOptions | number[] | [10, 20, 50, 100] | 每页条数选项 |
|
||||||
|
| showJumper | boolean | true | 是否显示快速跳转 |
|
||||||
|
| showPageSize | boolean | true | 是否显示每页条数选择器 |
|
||||||
|
|
||||||
|
## Events 说明
|
||||||
|
|
||||||
|
| 事件名 | 参数 | 说明 |
|
||||||
|
|--------|------|------|
|
||||||
|
| page-change | (page: number) | 页码变化 |
|
||||||
|
| page-size-change | (pageSize: number) | 每页条数变化 |
|
||||||
|
| selection-change | (selection: TableData[]) | 选择变化 |
|
||||||
|
| sorter-change | (sorter: any) | 排序变化 |
|
||||||
|
| filter-change | (filters: any) | 筛选变化 |
|
||||||
|
|
||||||
|
## useDataTable Composable
|
||||||
|
|
||||||
|
`useDataTable` 是一个用于管理表格状态的 composable,提供了以下功能:
|
||||||
|
|
||||||
|
### 返回值
|
||||||
|
|
||||||
|
| 属性 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| data | TableData[] | 表格数据 |
|
||||||
|
| columns | TableColumnData[] | 列配置 |
|
||||||
|
| loading | boolean | 加载状态 |
|
||||||
|
| pagination | PaginationProps | 分页配置 |
|
||||||
|
| current | number | 当前页码 |
|
||||||
|
| pageSize | number | 每页条数 |
|
||||||
|
| total | number | 总条数 |
|
||||||
|
| setData | (data: TableData[]) => void | 设置数据 |
|
||||||
|
| setColumns | (columns: TableColumnData[]) => void | 设置列配置 |
|
||||||
|
| setLoading | (loading: boolean) => void | 设置加载状态 |
|
||||||
|
| setTotal | (total: number) => void | 设置总条数 |
|
||||||
|
| setCurrent | (current: number) => void | 设置当前页码 |
|
||||||
|
| setPageSize | (pageSize: number) => void | 设置每页条数 |
|
||||||
|
| resetPagination | () => void | 重置分页 |
|
||||||
|
|
||||||
|
### 配置选项
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface UseDataTableOptions {
|
||||||
|
initialPage?: number; // 初始页码
|
||||||
|
initialPageSize?: number; // 初始每页条数
|
||||||
|
pageSizeOptions?: number[]; // 每页条数选项
|
||||||
|
showTotal?: boolean; // 是否显示总数
|
||||||
|
showJumper?: boolean; // 是否显示快速跳转
|
||||||
|
showPageSize?: boolean; // 是否显示每页条数选择器
|
||||||
|
rowKey?: string; // 行键
|
||||||
|
bordered?: boolean; // 是否显示边框
|
||||||
|
size?: 'mini' | 'small' | 'medium' | 'large'; // 表格大小
|
||||||
|
stripe?: boolean; // 是否显示斑马纹
|
||||||
|
tableLayout?: 'auto' | 'fixed'; // 表格布局
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
1. **统一使用 DataTable 组件**:在项目中统一使用 DataTable 组件,避免重复配置分页逻辑。
|
||||||
|
|
||||||
|
2. **使用 useDataTable composable**:利用 composable 管理表格状态,提高代码复用性。
|
||||||
|
|
||||||
|
3. **合理配置列插槽**:对于需要自定义渲染的列,使用插槽进行自定义。
|
||||||
|
|
||||||
|
4. **处理加载状态**:在数据请求时正确设置 loading 状态。
|
||||||
|
|
||||||
|
5. **分页事件处理**:在分页事件中调用 API 获取对应页的数据。
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. 确保在 `main.ts` 中注册了 `DataTable` 组件。
|
||||||
|
2. 列配置中的 `slotName` 要与模板中的插槽名称一致。
|
||||||
|
3. 数据源需要包含 `rowKey` 指定的字段。
|
||||||
|
4. 分页事件处理中应该调用实际的 API 接口。
|
||||||
175
src/components/common/DataTable.vue
Normal file
175
src/components/common/DataTable.vue
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
<template>
|
||||||
|
<a-table
|
||||||
|
:row-key="rowKey"
|
||||||
|
:loading="loading"
|
||||||
|
:pagination="paginationConfig"
|
||||||
|
:columns="columns"
|
||||||
|
:data="data"
|
||||||
|
:bordered="bordered"
|
||||||
|
:scroll="scroll"
|
||||||
|
:size="size"
|
||||||
|
:stripe="stripe"
|
||||||
|
:table-layout="tableLayout"
|
||||||
|
@page-change="handlePageChange"
|
||||||
|
@page-size-change="handlePageSizeChange"
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
|
@sorter-change="handleSorterChange"
|
||||||
|
@filter-change="handleFilterChange"
|
||||||
|
>
|
||||||
|
<template v-for="(_, name) in $slots" #[name]="slotData">
|
||||||
|
<slot :name="name" v-bind="slotData"></slot>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
import type {
|
||||||
|
TableColumnData,
|
||||||
|
TableData,
|
||||||
|
PaginationProps,
|
||||||
|
} from '@arco-design/web-vue';
|
||||||
|
|
||||||
|
interface DataTableProps {
|
||||||
|
// 表格数据
|
||||||
|
data: TableData[];
|
||||||
|
// 列配置
|
||||||
|
columns: TableColumnData[];
|
||||||
|
// 加载状态
|
||||||
|
loading?: boolean;
|
||||||
|
// 行键
|
||||||
|
rowKey?: string;
|
||||||
|
// 分页配置
|
||||||
|
pagination?: boolean | PaginationProps;
|
||||||
|
// 是否显示边框
|
||||||
|
bordered?: boolean;
|
||||||
|
// 表格大小
|
||||||
|
size?: 'mini' | 'small' | 'medium' | 'large';
|
||||||
|
// 是否显示斑马纹
|
||||||
|
stripe?: boolean;
|
||||||
|
// 表格布局
|
||||||
|
tableLayout?: 'auto' | 'fixed';
|
||||||
|
// 滚动配置
|
||||||
|
scroll?: {
|
||||||
|
x?: number | string;
|
||||||
|
y?: number | string;
|
||||||
|
};
|
||||||
|
// 当前页码
|
||||||
|
current?: number;
|
||||||
|
// 每页条数
|
||||||
|
pageSize?: number;
|
||||||
|
// 总条数
|
||||||
|
total?: number;
|
||||||
|
// 是否显示总数
|
||||||
|
showTotal?: boolean;
|
||||||
|
// 每页条数选项
|
||||||
|
pageSizeOptions?: number[];
|
||||||
|
// 是否显示快速跳转
|
||||||
|
showJumper?: boolean;
|
||||||
|
// 是否显示每页条数选择器
|
||||||
|
showPageSize?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SorterInfo {
|
||||||
|
field: string;
|
||||||
|
direction: 'ascend' | 'descend';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FilterInfo {
|
||||||
|
[key: string]: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DataTableEmits {
|
||||||
|
(e: 'pageChange', page: number): void;
|
||||||
|
(e: 'pageSizeChange', pageSize: number): void;
|
||||||
|
(e: 'selectionChange', selection: TableData[]): void;
|
||||||
|
(e: 'sorterChange', sorter: SorterInfo): void;
|
||||||
|
(e: 'filterChange', filters: FilterInfo): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<DataTableProps>(), {
|
||||||
|
loading: false,
|
||||||
|
rowKey: 'id',
|
||||||
|
bordered: false,
|
||||||
|
size: 'medium',
|
||||||
|
stripe: false,
|
||||||
|
tableLayout: 'auto',
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0,
|
||||||
|
showTotal: true,
|
||||||
|
pageSizeOptions: () => [10, 20, 50, 100],
|
||||||
|
showJumper: true,
|
||||||
|
showPageSize: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<DataTableEmits>();
|
||||||
|
|
||||||
|
// 内部分页状态
|
||||||
|
const internalCurrent = ref(props.current);
|
||||||
|
const internalPageSize = ref(props.pageSize);
|
||||||
|
|
||||||
|
// 监听外部传入的分页参数变化
|
||||||
|
watch(
|
||||||
|
() => props.current,
|
||||||
|
(newVal) => {
|
||||||
|
internalCurrent.value = newVal;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.pageSize,
|
||||||
|
(newVal) => {
|
||||||
|
internalPageSize.value = newVal;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 计算分页配置
|
||||||
|
const paginationConfig = computed(() => {
|
||||||
|
if (props.pagination === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultConfig: PaginationProps = {
|
||||||
|
current: internalCurrent.value,
|
||||||
|
pageSize: internalPageSize.value,
|
||||||
|
total: props.total,
|
||||||
|
showTotal: props.showTotal,
|
||||||
|
showJumper: props.showJumper,
|
||||||
|
showPageSize: props.showPageSize,
|
||||||
|
pageSizeOptions: props.pageSizeOptions,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof props.pagination === 'object') {
|
||||||
|
return { ...defaultConfig, ...props.pagination };
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultConfig;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 事件处理函数
|
||||||
|
const handlePageChange = (page: number) => {
|
||||||
|
internalCurrent.value = page;
|
||||||
|
emit('pageChange', page);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePageSizeChange = (pageSize: number) => {
|
||||||
|
internalPageSize.value = pageSize;
|
||||||
|
internalCurrent.value = 1; // 切换每页条数时重置到第一页
|
||||||
|
emit('pageSizeChange', pageSize);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectionChange = (selection: TableData[]) => {
|
||||||
|
emit('selectionChange', selection);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSorterChange = (sorter: SorterInfo) => {
|
||||||
|
emit('sorterChange', sorter);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFilterChange = (filters: FilterInfo) => {
|
||||||
|
emit('filterChange', filters);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
|
|
@ -11,6 +11,7 @@ import {
|
||||||
} from 'echarts/components';
|
} from 'echarts/components';
|
||||||
import Chart from './chart/index.vue';
|
import Chart from './chart/index.vue';
|
||||||
import Breadcrumb from './breadcrumb/index.vue';
|
import Breadcrumb from './breadcrumb/index.vue';
|
||||||
|
import DataTable from './common/DataTable.vue';
|
||||||
|
|
||||||
// Manually introduce ECharts modules to reduce packing size
|
// Manually introduce ECharts modules to reduce packing size
|
||||||
|
|
||||||
|
|
@ -31,5 +32,6 @@ export default {
|
||||||
install(Vue: App) {
|
install(Vue: App) {
|
||||||
Vue.component('Chart', Chart);
|
Vue.component('Chart', Chart);
|
||||||
Vue.component('Breadcrumb', Breadcrumb);
|
Vue.component('Breadcrumb', Breadcrumb);
|
||||||
|
Vue.component('DataTable', DataTable);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
196
src/composables/README.md
Normal file
196
src/composables/README.md
Normal file
|
|
@ -0,0 +1,196 @@
|
||||||
|
# 分页处理 Composable 使用指南
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
本项目提供了两个用于处理表格分页的 composable:
|
||||||
|
|
||||||
|
1. `usePagination` - 基础分页事件处理
|
||||||
|
2. `useTableWithPagination` - 完整的分页和数据获取集成
|
||||||
|
|
||||||
|
## usePagination
|
||||||
|
|
||||||
|
### 基础用法
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import usePagination from '@/composables/usePagination';
|
||||||
|
|
||||||
|
const { handlePageChange, handlePageSizeChange } = usePagination({
|
||||||
|
onPageChange: () => getUserListData(),
|
||||||
|
onPageSizeChange: () => getUserListData(),
|
||||||
|
resetToFirstPage: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 在分页事件中使用
|
||||||
|
const onPageChange = (page: number) => {
|
||||||
|
handlePageChange(page, setCurrent);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPageSizeChange = (pageSize: number) => {
|
||||||
|
handlePageSizeChange(pageSize, setPageSize, setCurrent);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置选项
|
||||||
|
|
||||||
|
| 选项 | 类型 | 默认值 | 说明 |
|
||||||
|
|------|------|--------|------|
|
||||||
|
| onPageChange | Function | - | 页码变化时的回调函数 |
|
||||||
|
| onPageSizeChange | Function | - | 每页条数变化时的回调函数 |
|
||||||
|
| resetToFirstPage | boolean | true | 切换每页条数时是否重置到第一页 |
|
||||||
|
|
||||||
|
## useTableWithPagination
|
||||||
|
|
||||||
|
### 完整用法
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import useTableWithPagination from '@/composables/useTableWithPagination';
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: renderData,
|
||||||
|
columns: projectColumn,
|
||||||
|
loading,
|
||||||
|
total,
|
||||||
|
current,
|
||||||
|
pageSize,
|
||||||
|
setColumns,
|
||||||
|
onPageChange,
|
||||||
|
onPageSizeChange,
|
||||||
|
refresh,
|
||||||
|
updateParams,
|
||||||
|
} = useTableWithPagination({
|
||||||
|
// 数据获取函数
|
||||||
|
fetchData: async (params) => {
|
||||||
|
const { code, data } = await getUserList({
|
||||||
|
dept_id: selectedKeys.value[0],
|
||||||
|
keyword: searchKeyWord.value,
|
||||||
|
page: params.page,
|
||||||
|
page_size: params.pageSize,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (code === 200) {
|
||||||
|
const { page, user } = data;
|
||||||
|
return {
|
||||||
|
data: Array.isArray(user) ? user : [],
|
||||||
|
total: typeof page.total === 'number' ? page.total : 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { data: [], total: 0 };
|
||||||
|
},
|
||||||
|
initialPage: 1,
|
||||||
|
initialPageSize: 10,
|
||||||
|
pageSizeOptions: [10, 20, 50, 100],
|
||||||
|
autoFetch: false, // 不自动获取,等选中部门后再获取
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置选项
|
||||||
|
|
||||||
|
| 选项 | 类型 | 默认值 | 说明 |
|
||||||
|
|------|------|--------|------|
|
||||||
|
| fetchData | Function | - | 数据获取函数,必须返回 { data, total } |
|
||||||
|
| initialPage | number | 1 | 初始页码 |
|
||||||
|
| initialPageSize | number | 10 | 初始每页条数 |
|
||||||
|
| pageSizeOptions | number[] | [10, 20, 50, 100] | 每页条数选项 |
|
||||||
|
| extraParams | object | {} | 额外的查询参数 |
|
||||||
|
| autoFetch | boolean | true | 是否自动获取数据 |
|
||||||
|
| resetToFirstPage | boolean | true | 切换每页条数时是否重置到第一页 |
|
||||||
|
|
||||||
|
### 返回值
|
||||||
|
|
||||||
|
| 属性 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| data | Ref<TableData[]> | 表格数据 |
|
||||||
|
| columns | Ref<TableColumnData[]> | 列配置 |
|
||||||
|
| loading | Ref<boolean> | 加载状态 |
|
||||||
|
| total | Ref<number> | 总条数 |
|
||||||
|
| current | Ref<number> | 当前页码 |
|
||||||
|
| pageSize | Ref<number> | 每页条数 |
|
||||||
|
| setColumns | Function | 设置列配置 |
|
||||||
|
| onPageChange | Function | 页码变化处理 |
|
||||||
|
| onPageSizeChange | Function | 每页条数变化处理 |
|
||||||
|
| refresh | Function | 刷新数据 |
|
||||||
|
| updateParams | Function | 更新查询参数 |
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
### 1. 使用 usePagination(推荐用于简单场景)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 在现有的 useDataTable 基础上添加分页处理
|
||||||
|
const { handlePageChange, handlePageSizeChange } = usePagination({
|
||||||
|
onPageChange: () => getUserListData(),
|
||||||
|
onPageSizeChange: () => getUserListData(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const onPageChange = (page: number) => {
|
||||||
|
handlePageChange(page, setCurrent);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPageSizeChange = (pageSize: number) => {
|
||||||
|
handlePageSizeChange(pageSize, setPageSize, setCurrent);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 使用 useTableWithPagination(推荐用于新页面)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 完整的分页和数据获取集成
|
||||||
|
const {
|
||||||
|
data: renderData,
|
||||||
|
columns: projectColumn,
|
||||||
|
loading,
|
||||||
|
total,
|
||||||
|
current,
|
||||||
|
pageSize,
|
||||||
|
onPageChange,
|
||||||
|
onPageSizeChange,
|
||||||
|
refresh,
|
||||||
|
} = useTableWithPagination({
|
||||||
|
fetchData: async (params) => {
|
||||||
|
// 你的数据获取逻辑
|
||||||
|
return { data: [], total: 0 };
|
||||||
|
},
|
||||||
|
autoFetch: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 搜索时刷新数据
|
||||||
|
const handleSearch = () => {
|
||||||
|
refresh();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除后刷新数据
|
||||||
|
const handleDelete = async (id: number) => {
|
||||||
|
await deleteItem(id);
|
||||||
|
refresh();
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 处理搜索和筛选
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 使用 updateParams 更新查询参数
|
||||||
|
const handleSearch = () => {
|
||||||
|
updateParams({ keyword: searchKeyWord.value });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 或者使用 refresh 直接刷新
|
||||||
|
const handleSearch = () => {
|
||||||
|
refresh();
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 优势
|
||||||
|
|
||||||
|
1. **代码复用**:避免在每个页面重复编写分页逻辑
|
||||||
|
2. **统一处理**:确保所有页面的分页行为一致
|
||||||
|
3. **类型安全**:完整的 TypeScript 类型支持
|
||||||
|
4. **易于维护**:分页逻辑集中管理,便于修改和扩展
|
||||||
|
5. **自动优化**:自动处理页码重置、加载状态等细节
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. `fetchData` 函数必须返回 `{ data, total }` 格式
|
||||||
|
2. 使用 `useTableWithPagination` 时,确保 `fetchData` 函数能正确处理错误
|
||||||
|
3. 如果需要自定义分页行为,可以使用 `usePagination` 进行更精细的控制
|
||||||
|
4. `autoFetch: false` 适用于需要等待用户操作后再获取数据的场景
|
||||||
172
src/composables/useDataTable.ts
Normal file
172
src/composables/useDataTable.ts
Normal file
|
|
@ -0,0 +1,172 @@
|
||||||
|
import { ref, reactive, computed, Ref, ComputedRef } from 'vue';
|
||||||
|
import type {
|
||||||
|
TableColumnData,
|
||||||
|
TableData,
|
||||||
|
PaginationProps,
|
||||||
|
} from '@arco-design/web-vue';
|
||||||
|
|
||||||
|
interface UseDataTableOptions {
|
||||||
|
// 初始页码
|
||||||
|
initialPage?: number;
|
||||||
|
// 初始每页条数
|
||||||
|
initialPageSize?: number;
|
||||||
|
// 每页条数选项
|
||||||
|
pageSizeOptions?: number[];
|
||||||
|
// 是否显示总数
|
||||||
|
showTotal?: boolean;
|
||||||
|
// 是否显示快速跳转
|
||||||
|
showJumper?: boolean;
|
||||||
|
// 是否显示每页条数选择器
|
||||||
|
showPageSize?: boolean;
|
||||||
|
// 行键
|
||||||
|
rowKey?: string;
|
||||||
|
// 是否显示边框
|
||||||
|
bordered?: boolean;
|
||||||
|
// 表格大小
|
||||||
|
size?: 'mini' | 'small' | 'medium' | 'large';
|
||||||
|
// 是否显示斑马纹
|
||||||
|
stripe?: boolean;
|
||||||
|
// 表格布局
|
||||||
|
tableLayout?: 'auto' | 'fixed';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UseDataTableReturn {
|
||||||
|
// 表格数据
|
||||||
|
data: Ref<TableData[]>;
|
||||||
|
// 列配置
|
||||||
|
columns: Ref<TableColumnData[]>;
|
||||||
|
// 加载状态
|
||||||
|
loading: Ref<boolean>;
|
||||||
|
// 分页配置
|
||||||
|
pagination: ComputedRef<PaginationProps>;
|
||||||
|
// 当前页码
|
||||||
|
current: Ref<number>;
|
||||||
|
// 每页条数
|
||||||
|
pageSize: Ref<number>;
|
||||||
|
// 总条数
|
||||||
|
total: Ref<number>;
|
||||||
|
// 设置数据
|
||||||
|
setData: (data: TableData[]) => void;
|
||||||
|
// 设置列配置
|
||||||
|
setColumns: (columns: TableColumnData[]) => void;
|
||||||
|
// 设置加载状态
|
||||||
|
setLoading: (loading: boolean) => void;
|
||||||
|
// 设置总条数
|
||||||
|
setTotal: (total: number) => void;
|
||||||
|
// 设置当前页码
|
||||||
|
setCurrent: (current: number) => void;
|
||||||
|
// 设置每页条数
|
||||||
|
setPageSize: (pageSize: number) => void;
|
||||||
|
// 重置分页
|
||||||
|
resetPagination: () => void;
|
||||||
|
// 表格配置
|
||||||
|
tableConfig: {
|
||||||
|
rowKey: string;
|
||||||
|
bordered: boolean;
|
||||||
|
size: 'mini' | 'small' | 'medium' | 'large';
|
||||||
|
stripe: boolean;
|
||||||
|
tableLayout: 'auto' | 'fixed';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useDataTable(
|
||||||
|
options: UseDataTableOptions = {}
|
||||||
|
): UseDataTableReturn {
|
||||||
|
const {
|
||||||
|
initialPage = 1,
|
||||||
|
initialPageSize = 10,
|
||||||
|
pageSizeOptions = [10, 20, 50, 100],
|
||||||
|
showTotal = true,
|
||||||
|
showJumper = true,
|
||||||
|
showPageSize = true,
|
||||||
|
rowKey = 'id',
|
||||||
|
bordered = false,
|
||||||
|
size = 'medium',
|
||||||
|
stripe = false,
|
||||||
|
tableLayout = 'auto',
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const data = ref<TableData[]>([]);
|
||||||
|
const columns = ref<TableColumnData[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const current = ref(initialPage);
|
||||||
|
const pageSize = ref(initialPageSize);
|
||||||
|
const total = ref(0);
|
||||||
|
|
||||||
|
// 分页配置
|
||||||
|
const pagination = computed<PaginationProps>(() => ({
|
||||||
|
current: current.value,
|
||||||
|
pageSize: pageSize.value,
|
||||||
|
total: total.value,
|
||||||
|
showTotal,
|
||||||
|
showJumper,
|
||||||
|
showPageSize,
|
||||||
|
pageSizeOptions,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 表格配置
|
||||||
|
const tableConfig = reactive({
|
||||||
|
rowKey,
|
||||||
|
bordered,
|
||||||
|
size,
|
||||||
|
stripe,
|
||||||
|
tableLayout,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 设置数据
|
||||||
|
const setData = (newData: TableData[]) => {
|
||||||
|
data.value = newData;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 设置列配置
|
||||||
|
const setColumns = (newColumns: TableColumnData[]) => {
|
||||||
|
columns.value = newColumns;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 设置加载状态
|
||||||
|
const setLoading = (newLoading: boolean) => {
|
||||||
|
loading.value = newLoading;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 设置总条数
|
||||||
|
const setTotal = (newTotal: number) => {
|
||||||
|
total.value = newTotal;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 设置当前页码
|
||||||
|
const setCurrent = (newCurrent: number) => {
|
||||||
|
current.value = newCurrent;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 设置每页条数
|
||||||
|
const setPageSize = (newPageSize: number) => {
|
||||||
|
pageSize.value = newPageSize;
|
||||||
|
// 切换每页条数时重置到第一页
|
||||||
|
current.value = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重置分页
|
||||||
|
const resetPagination = () => {
|
||||||
|
current.value = initialPage;
|
||||||
|
pageSize.value = initialPageSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
columns,
|
||||||
|
loading,
|
||||||
|
pagination,
|
||||||
|
current,
|
||||||
|
pageSize,
|
||||||
|
total,
|
||||||
|
setData,
|
||||||
|
setColumns,
|
||||||
|
setLoading,
|
||||||
|
setTotal,
|
||||||
|
setCurrent,
|
||||||
|
setPageSize,
|
||||||
|
resetPagination,
|
||||||
|
tableConfig,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import {
|
||||||
DepartmentItem,
|
DepartmentItem,
|
||||||
getDepartmentDetail,
|
getDepartmentDetail,
|
||||||
} from '@/api/setting';
|
} from '@/api/setting';
|
||||||
|
import useLoading from '@/hooks/loading';
|
||||||
|
|
||||||
export default function useDepartmentList(autoFetch = true) {
|
export default function useDepartmentList(autoFetch = true) {
|
||||||
const departmentListData = ref<DepartmentItem[]>([]);
|
const departmentListData = ref<DepartmentItem[]>([]);
|
||||||
|
|
@ -35,7 +35,10 @@ export default {
|
||||||
'menu.finance': '财务',
|
'menu.finance': '财务',
|
||||||
'menu.property': '物业',
|
'menu.property': '物业',
|
||||||
'menu.setting': '设置',
|
'menu.setting': '设置',
|
||||||
'search.placeholder': '请输入内容',
|
'please.enter': '请输入{placeholder}',
|
||||||
|
'please.select': '请选择{placeholder}',
|
||||||
|
'please.confirm': '请确认{placeholder}',
|
||||||
|
'content': '内容',
|
||||||
'table.filter': '筛选',
|
'table.filter': '筛选',
|
||||||
'table.export': '导出',
|
'table.export': '导出',
|
||||||
'watch-detail': '查看详情',
|
'watch-detail': '查看详情',
|
||||||
|
|
@ -61,6 +64,7 @@ export default {
|
||||||
'company-info': '公司信息',
|
'company-info': '公司信息',
|
||||||
'appearance-setting': '外观设置',
|
'appearance-setting': '外观设置',
|
||||||
'add-department': '新建部门',
|
'add-department': '新建部门',
|
||||||
|
'edit-department': '编辑部门',
|
||||||
'department-name': '部门名称',
|
'department-name': '部门名称',
|
||||||
'belongto-department': '所属部门',
|
'belongto-department': '所属部门',
|
||||||
'department-head': '部门主管',
|
'department-head': '部门主管',
|
||||||
|
|
@ -73,6 +77,26 @@ export default {
|
||||||
'delete': '删除',
|
'delete': '删除',
|
||||||
'confirm': '确定',
|
'confirm': '确定',
|
||||||
'cancel': '取消',
|
'cancel': '取消',
|
||||||
|
'search.username.name': '搜索用户名、姓名',
|
||||||
|
'add-user': '新建用户',
|
||||||
|
'edit-user': '编辑用户',
|
||||||
|
'username': '用户名',
|
||||||
|
'name': '姓名',
|
||||||
|
'role': '角色',
|
||||||
|
'status': '状态',
|
||||||
|
'last-login': '最后登录',
|
||||||
|
'operate': '操作',
|
||||||
|
'password': '密码',
|
||||||
|
'confirm.password': '确认密码',
|
||||||
|
'tel': '手机号',
|
||||||
|
'email': '邮箱',
|
||||||
|
'lastLogin': '最后登录',
|
||||||
|
'please.enter.correct': '请输入正确的{field}',
|
||||||
|
'enable': '启用',
|
||||||
|
'disable': '禁用',
|
||||||
|
'edit': '编辑',
|
||||||
|
'more': '更多',
|
||||||
|
'user': '用户',
|
||||||
|
|
||||||
...localeSettings,
|
...localeSettings,
|
||||||
...localeMessageBox,
|
...localeMessageBox,
|
||||||
|
|
|
||||||
|
|
@ -4,43 +4,46 @@
|
||||||
<a-card class="general-card">
|
<a-card class="general-card">
|
||||||
<template #title>
|
<template #title>
|
||||||
<div class="card-title_between">
|
<div class="card-title_between">
|
||||||
<p class="title">{{ $t('menu.business.project') }}</p>
|
<p class="title">{{ t('menu.business.project') }}</p>
|
||||||
<div class="flex_center_between">
|
<div class="flex_center_between">
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-input-search
|
<a-input-search
|
||||||
:style="{ width: '320px' }"
|
:style="{ width: '320px' }"
|
||||||
:placeholder="$t('search.placeholder')"
|
:placeholder="t('please.enter', { placeholder: t('content') })"
|
||||||
/>
|
/>
|
||||||
<a-button>
|
<a-button>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-filter />
|
<icon-filter />
|
||||||
</template>
|
</template>
|
||||||
{{ $t('table.filter') }}
|
{{ t('table.filter') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button>
|
<a-button>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-download />
|
<icon-download />
|
||||||
</template>
|
</template>
|
||||||
{{ $t('table.export') }}
|
{{ t('table.export') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button type="primary">
|
<a-button type="primary">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-plus />
|
<icon-plus />
|
||||||
</template>
|
</template>
|
||||||
{{ $t('add-project') }}
|
{{ t('add-project') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<a-table
|
|
||||||
row-key="id"
|
<!-- 使用新的 DataTable 组件 -->
|
||||||
:loading="loading"
|
<DataTable
|
||||||
:pagination="pagination"
|
|
||||||
:columns="projectColumn"
|
|
||||||
:data="renderData"
|
:data="renderData"
|
||||||
:bordered="false"
|
:columns="projectColumn"
|
||||||
|
:loading="loading"
|
||||||
|
:total="total"
|
||||||
|
:current="current"
|
||||||
|
:page-size="pageSize"
|
||||||
@page-change="onPageChange"
|
@page-change="onPageChange"
|
||||||
|
@page-size-change="onPageSizeChange"
|
||||||
>
|
>
|
||||||
<template #type="{ record }">
|
<template #type="{ record }">
|
||||||
<a-tag checkable :color="getTypeColor(record.type)">{{
|
<a-tag checkable :color="getTypeColor(record.type)">{{
|
||||||
|
|
@ -61,15 +64,16 @@
|
||||||
<a-button>更多</a-button>
|
<a-button>更多</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</DataTable>
|
||||||
</a-card>
|
</a-card>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref, reactive, watch, nextTick } from 'vue';
|
import { onMounted } from 'vue';
|
||||||
import useLoading from '@/hooks/loading';
|
import useLoading from '@/hooks/loading';
|
||||||
import { Pagination } from '@/types/global';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import type { TableColumnData } from '@arco-design/web-vue';
|
||||||
import {
|
import {
|
||||||
ProjectRecord,
|
ProjectRecord,
|
||||||
ProjectType,
|
ProjectType,
|
||||||
|
|
@ -77,11 +81,35 @@
|
||||||
PROJECT_TYPE_COLORS,
|
PROJECT_TYPE_COLORS,
|
||||||
PROJECT_TYPE_LABELS,
|
PROJECT_TYPE_LABELS,
|
||||||
} from '@/types/business';
|
} from '@/types/business';
|
||||||
import { ProjectParams, getAllProjectList } from '@/api/business';
|
// import { getAllProjectList } from '@/api/business';
|
||||||
|
import useDataTable from '@/composables/useDataTable';
|
||||||
|
|
||||||
const { loading, setLoading } = useLoading(false);
|
const { t } = useI18n();
|
||||||
const renderData = ref<ProjectRecord[]>([
|
const { loading } = useLoading(false);
|
||||||
|
// setLoading
|
||||||
|
|
||||||
|
// 使用 DataTable composable
|
||||||
|
const {
|
||||||
|
data: renderData,
|
||||||
|
columns: projectColumn,
|
||||||
|
total,
|
||||||
|
current,
|
||||||
|
pageSize,
|
||||||
|
setData,
|
||||||
|
setColumns,
|
||||||
|
setTotal,
|
||||||
|
setCurrent,
|
||||||
|
setPageSize,
|
||||||
|
} = useDataTable({
|
||||||
|
initialPage: 1,
|
||||||
|
initialPageSize: 10,
|
||||||
|
pageSizeOptions: [10, 20, 50, 100],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 模拟数据
|
||||||
|
const mockData: ProjectRecord[] = [
|
||||||
{
|
{
|
||||||
|
id: '1',
|
||||||
name: '星河商务中心',
|
name: '星河商务中心',
|
||||||
type: PROJECT_TYPE.COMMERCIAL_COMPLEX,
|
type: PROJECT_TYPE.COMMERCIAL_COMPLEX,
|
||||||
size: '1200m²',
|
size: '1200m²',
|
||||||
|
|
@ -91,6 +119,7 @@
|
||||||
create_time: '2025-06-24 00:51',
|
create_time: '2025-06-24 00:51',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: '2',
|
||||||
name: '南山科学科学院',
|
name: '南山科学科学院',
|
||||||
type: PROJECT_TYPE.OFFICE_BUILDING,
|
type: PROJECT_TYPE.OFFICE_BUILDING,
|
||||||
size: '120m²',
|
size: '120m²',
|
||||||
|
|
@ -100,6 +129,7 @@
|
||||||
create_time: '2025-06-24 00:58',
|
create_time: '2025-06-24 00:58',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: '3',
|
||||||
name: '前海科学科学院',
|
name: '前海科学科学院',
|
||||||
type: PROJECT_TYPE.INDUSTRIAL_PARK,
|
type: PROJECT_TYPE.INDUSTRIAL_PARK,
|
||||||
size: '1520m²',
|
size: '1520m²',
|
||||||
|
|
@ -108,24 +138,25 @@
|
||||||
count: '120',
|
count: '120',
|
||||||
create_time: '2025-06-24 01:16',
|
create_time: '2025-06-24 01:16',
|
||||||
},
|
},
|
||||||
]);
|
|
||||||
const basePagination: Pagination = {
|
|
||||||
current: 1,
|
|
||||||
pageSize: 20,
|
|
||||||
total: 3,
|
|
||||||
};
|
|
||||||
const projectColumn = [
|
|
||||||
{ title: '项目信息', dataIndex: 'name' },
|
|
||||||
{ title: '项目类型', dataIndex: 'type', slotName: 'type' },
|
|
||||||
{ title: '管理面积', dataIndex: 'size' },
|
|
||||||
{ title: '可租面积', dataIndex: 'kezu' },
|
|
||||||
{ title: '出租率', dataIndex: 'rate', slotName: 'rate' },
|
|
||||||
{ title: '房源数量', dataIndex: 'count' },
|
|
||||||
{ title: '创建时间', dataIndex: 'create_time' },
|
|
||||||
{ title: '操作', dataIndex: 'operate', slotName: 'operate' },
|
|
||||||
];
|
];
|
||||||
const pagination = reactive({
|
|
||||||
...basePagination,
|
// 列配置
|
||||||
|
const columns: TableColumnData[] = [
|
||||||
|
{ title: '项目信息', dataIndex: 'name', key: 'name' },
|
||||||
|
{ title: '项目类型', dataIndex: 'type', key: 'type', slotName: 'type' },
|
||||||
|
{ title: '管理面积', dataIndex: 'size', key: 'size' },
|
||||||
|
{ title: '可租面积', dataIndex: 'kezu', key: 'kezu' },
|
||||||
|
{ title: '出租率', dataIndex: 'rate', key: 'rate', slotName: 'rate' },
|
||||||
|
{ title: '房源数量', dataIndex: 'count', key: 'count' },
|
||||||
|
{ title: '创建时间', dataIndex: 'create_time', key: 'create_time' },
|
||||||
|
{ title: '操作', key: 'operate', slotName: 'operate', width: 200 },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 初始化数据
|
||||||
|
onMounted(() => {
|
||||||
|
setColumns(columns);
|
||||||
|
setData(mockData);
|
||||||
|
setTotal(mockData.length);
|
||||||
});
|
});
|
||||||
|
|
||||||
const getTypeColor = (type: ProjectType): string => {
|
const getTypeColor = (type: ProjectType): string => {
|
||||||
|
|
@ -135,21 +166,28 @@
|
||||||
const getTypeText = (type: ProjectType): string => {
|
const getTypeText = (type: ProjectType): string => {
|
||||||
return PROJECT_TYPE_LABELS[type] || '未知';
|
return PROJECT_TYPE_LABELS[type] || '未知';
|
||||||
};
|
};
|
||||||
const onPageChange = (current: number) => {
|
|
||||||
pagination.current = current;
|
// 分页事件处理
|
||||||
|
const onPageChange = (page: number) => {
|
||||||
|
setCurrent(page);
|
||||||
// TODO: 这里应该调用API获取对应页码的数据
|
// TODO: 这里应该调用API获取对应页码的数据
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAllProjectListData = async () => {
|
const onPageSizeChange = (newPageSize: number) => {
|
||||||
setLoading(true);
|
setPageSize(newPageSize);
|
||||||
try {
|
// TODO: 这里应该调用API获取对应页码的数据
|
||||||
const { data } = await getAllProjectList();
|
|
||||||
console.log(data, 'getAllProjectList');
|
|
||||||
} catch (err) {
|
|
||||||
// you can report use errorHandler or other
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// const getAllProjectListData = async () => {
|
||||||
|
// setLoading(true);
|
||||||
|
// try {
|
||||||
|
// await getAllProjectList();
|
||||||
|
// // 处理数据
|
||||||
|
// } catch (err) {
|
||||||
|
// // you can report use errorHandler or other
|
||||||
|
// } finally {
|
||||||
|
// setLoading(false);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
// getAllProjectListData()
|
// getAllProjectListData()
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<a-modal
|
<a-modal
|
||||||
:visible="visible"
|
:visible="visible"
|
||||||
:title="isEdit ? '编辑部门' : '新增部门'"
|
:title="isEdit ? $t('edit-department') : $t('add-department')"
|
||||||
unmount-on-close
|
unmount-on-close
|
||||||
@ok="handleSubmit"
|
@ok="handleSubmit"
|
||||||
@cancel="handleCancel"
|
@cancel="handleCancel"
|
||||||
|
|
@ -14,36 +14,48 @@
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
>
|
>
|
||||||
<!-- 部门名称 -->
|
<!-- 部门名称 -->
|
||||||
<a-form-item :label="t('department-name')" field="name" required>
|
<a-form-item :label="$t('department-name')" field="name" required>
|
||||||
<a-input v-model="form.name" placeholder="请输入部门名称" />
|
<a-input
|
||||||
|
v-model="form.name"
|
||||||
|
:placeholder="
|
||||||
|
$t('please.enter', { placeholder: $t('department-name') })
|
||||||
|
"
|
||||||
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<!-- 所属部门 -->
|
<!-- 所属部门 -->
|
||||||
<a-form-item :label="t('belongto-department')" field="parent_id" required>
|
<a-form-item
|
||||||
|
:label="$t('belongto-department')"
|
||||||
|
field="parent_id"
|
||||||
|
required
|
||||||
|
>
|
||||||
<a-tree-select
|
<a-tree-select
|
||||||
v-model="form.parent_id"
|
v-model="form.parent_id"
|
||||||
:data="departmentListData"
|
:data="departmentListData"
|
||||||
:field-names="{ key: 'id', title: 'name', children: 'children' }"
|
:field-names="{ key: 'id', title: 'name', children: 'children' }"
|
||||||
placeholder="请选择所属部门"
|
:placeholder="
|
||||||
|
$t('please.select', { placeholder: $t('belongto-department') })
|
||||||
|
"
|
||||||
allow-clear
|
allow-clear
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<!-- 部门主管 -->
|
<!-- 部门主管 -->
|
||||||
<a-form-item :label="t('department-head')" field="user_id">
|
<a-form-item :label="$t('department-head')" field="user_id">
|
||||||
<a-select
|
<a-select
|
||||||
v-model="form.user_id"
|
v-model="form.user_id"
|
||||||
:options="props.userListData"
|
:options="props.userListData"
|
||||||
placeholder="请选择部门主管"
|
:placeholder="
|
||||||
|
$t('please.select', { placeholder: $t('department-head') })
|
||||||
|
"
|
||||||
allow-clear
|
allow-clear
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<!-- 备注 -->
|
<!-- 备注 -->
|
||||||
<a-form-item :label="t('remark')" field="remark">
|
<a-form-item :label="$t('remark')" field="remark">
|
||||||
<a-textarea
|
<a-textarea
|
||||||
v-model="form.remark"
|
v-model="form.remark"
|
||||||
placeholder="请输入备注"
|
:placeholder="$t('please.enter', { placeholder: $t('remark') })"
|
||||||
allow-clear
|
allow-clear
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
@ -75,17 +87,26 @@
|
||||||
user_id?: number;
|
user_id?: number;
|
||||||
remark?: string;
|
remark?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface TreeNode {
|
||||||
|
id: string | number;
|
||||||
|
name: string;
|
||||||
|
children?: TreeNode[];
|
||||||
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
isEdit: boolean;
|
isEdit: boolean;
|
||||||
record?: DepartmentItem | null;
|
record?: DepartmentItem | null;
|
||||||
departmentListData: any[]; // 新增
|
departmentListData: TreeNode[]; // 修复类型
|
||||||
userListData: UserList;
|
userListData: UserList;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'update:visible', val: boolean): void;
|
(e: 'update:visible', val: boolean): void;
|
||||||
(e: 'submitted'): void;
|
(e: 'submitted'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
const formRef = ref<FormInstance>();
|
const formRef = ref<FormInstance>();
|
||||||
const form = reactive<DepartmentForm>({
|
const form = reactive<DepartmentForm>({
|
||||||
|
|
@ -99,7 +120,12 @@
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
// 校验规则
|
// 校验规则
|
||||||
const rules = {
|
const rules = {
|
||||||
name: [{ required: true, message: '请输入部门名称' }],
|
name: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('please.enter', { placeholder: t('department') }),
|
||||||
|
},
|
||||||
|
],
|
||||||
parent_id: [{ required: true, message: '请选择所属部门' }],
|
parent_id: [{ required: true, message: '请选择所属部门' }],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
import {
|
import {
|
||||||
IconSettings,
|
IconSettings,
|
||||||
IconSafe,
|
IconSafe,
|
||||||
|
|
@ -45,7 +46,23 @@
|
||||||
import organization from './organization.vue';
|
import organization from './organization.vue';
|
||||||
// import { CreateDepartmentParams, createDepartment, getDepartmentList, DepartmentItem } from '@/api/setting'
|
// import { CreateDepartmentParams, createDepartment, getDepartmentList, DepartmentItem } from '@/api/setting'
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const currentActive = ref(4);
|
const route = useRoute();
|
||||||
|
const currentActive = ref(1);
|
||||||
|
|
||||||
|
// 监听路由query参数中的active参数
|
||||||
|
watch(
|
||||||
|
() => route.query.active,
|
||||||
|
(newActive) => {
|
||||||
|
if (newActive) {
|
||||||
|
const activeValue = parseInt(newActive as string, 10);
|
||||||
|
if (!Number.isNaN(activeValue)) {
|
||||||
|
currentActive.value = activeValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
const systemSettingList = [
|
const systemSettingList = [
|
||||||
{ id: 1, name: t('general-setting'), icon: IconSettings },
|
{ id: 1, name: t('general-setting'), icon: IconSettings },
|
||||||
{ id: 2, name: t('security-setting'), icon: IconSafe },
|
{ id: 2, name: t('security-setting'), icon: IconSafe },
|
||||||
|
|
@ -56,6 +73,8 @@
|
||||||
const handleChooseSystemItem = (id: number) => {
|
const handleChooseSystemItem = (id: number) => {
|
||||||
currentActive.value = id;
|
currentActive.value = id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// watch()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
|
||||||
|
|
@ -93,9 +93,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive, ref, watch, onMounted } from 'vue';
|
import { ref, watch, onMounted } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import useDepartmentList from '@/views/setting/composables/useDepartmentList';
|
import useDepartmentList from '@/composables/useDepartmentList';
|
||||||
import {
|
import {
|
||||||
DepartmentItem,
|
DepartmentItem,
|
||||||
getAllUserList,
|
getAllUserList,
|
||||||
|
|
@ -114,9 +114,10 @@
|
||||||
getDepartmentListData,
|
getDepartmentListData,
|
||||||
getDepartmentDetailData,
|
getDepartmentDetailData,
|
||||||
} = useDepartmentList();
|
} = useDepartmentList();
|
||||||
const emit = defineEmits(['onSelectTree']);
|
|
||||||
const expandedKeys = ref<(string | number)[]>([]);
|
const expandedKeys = ref<(string | number)[]>([]);
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
allDepartmentKeys,
|
allDepartmentKeys,
|
||||||
(val) => {
|
(val) => {
|
||||||
|
|
@ -129,7 +130,14 @@
|
||||||
const isEdit = ref<boolean>(false);
|
const isEdit = ref<boolean>(false);
|
||||||
const departmentDetailInfo = ref<DepartmentItem | null>(null);
|
const departmentDetailInfo = ref<DepartmentItem | null>(null);
|
||||||
|
|
||||||
const resetEditIcon = (nodes: any[]) => {
|
interface TreeNode {
|
||||||
|
id: string | number;
|
||||||
|
isShowEditIcon?: boolean;
|
||||||
|
children?: TreeNode[];
|
||||||
|
level?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetEditIcon = (nodes: TreeNode[]) => {
|
||||||
nodes.forEach((node) => {
|
nodes.forEach((node) => {
|
||||||
node.isShowEditIcon = false;
|
node.isShowEditIcon = false;
|
||||||
if (node.children && node.children.length > 0) {
|
if (node.children && node.children.length > 0) {
|
||||||
|
|
@ -137,21 +145,26 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const currentNode = ref<any>(null);
|
|
||||||
const onSelect = debounce(async (_: (string | number)[], event: any) => {
|
const currentNode = ref<TreeNode | null>(null);
|
||||||
const { node } = event;
|
|
||||||
if (node.id === currentNode.value?.id) return;
|
const onSelect = debounce(
|
||||||
currentNode.value = node;
|
async (_: (string | number)[], event: { node: TreeNode }) => {
|
||||||
// 递归重置所有节点的 isShowEditIcon
|
const { node } = event;
|
||||||
resetEditIcon(departmentListData.value);
|
if (node.id === currentNode.value?.id) return;
|
||||||
// 只设置当前节点
|
currentNode.value = node;
|
||||||
if (currentNode.value.level !== 1) {
|
// 递归重置所有节点的 isShowEditIcon
|
||||||
node.isShowEditIcon = true;
|
resetEditIcon(departmentListData.value);
|
||||||
}
|
// 只设置当前节点
|
||||||
departmentDetailInfo.value = await getDepartmentDetailData(
|
if (currentNode.value.level !== 1) {
|
||||||
currentNode.value.id
|
node.isShowEditIcon = true;
|
||||||
);
|
}
|
||||||
}, 300);
|
departmentDetailInfo.value = await getDepartmentDetailData(
|
||||||
|
Number(currentNode.value.id)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
300
|
||||||
|
);
|
||||||
|
|
||||||
// 所有用户数据
|
// 所有用户数据
|
||||||
const allUserList = ref<UserList>([]);
|
const allUserList = ref<UserList>([]);
|
||||||
|
|
@ -162,39 +175,43 @@
|
||||||
const { code, data } = await getAllUserList({
|
const { code, data } = await getAllUserList({
|
||||||
keyword: userKeyword.value,
|
keyword: userKeyword.value,
|
||||||
});
|
});
|
||||||
if (code === 200 && data?.length) {
|
if (code === 200) {
|
||||||
allUserList.value = data;
|
const { user } = data;
|
||||||
|
allUserList.value = Array.isArray(user) ? user : [];
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取所以用户数据失败:', error);
|
// 处理错误
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
showModal.value = true;
|
|
||||||
isEdit.value = false;
|
isEdit.value = false;
|
||||||
|
departmentDetailInfo.value = null;
|
||||||
|
showModal.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEdit = () => {
|
const handleEdit = () => {
|
||||||
showModal.value = true;
|
showModal.value = true;
|
||||||
isEdit.value = true;
|
isEdit.value = true;
|
||||||
};
|
};
|
||||||
const handleDelete = async (item) => {
|
|
||||||
|
const handleDelete = async (item: { id: string | number }) => {
|
||||||
const { id } = item;
|
const { id } = item;
|
||||||
try {
|
try {
|
||||||
const { code, message } = await delDepartment(id);
|
const { code, message } = await delDepartment(Number(id));
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
Message.success(message);
|
Message.success(message);
|
||||||
getDepartmentListData();
|
getDepartmentListData();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('删除失败:', error);
|
// 处理错误
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMoveUp = async (item) => {
|
const handleMoveUp = async (item: { id: string | number }) => {
|
||||||
const { id } = item;
|
const { id } = item;
|
||||||
try {
|
try {
|
||||||
const { code, message } = await departmentSetUp(id);
|
const { code, message } = await departmentSetUp(Number(id));
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
Message.success(message);
|
Message.success(message);
|
||||||
getDepartmentListData();
|
getDepartmentListData();
|
||||||
|
|
@ -202,13 +219,14 @@
|
||||||
Message.info(message);
|
Message.info(message);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('向上移动失败:', error);
|
// 处理错误
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const handleMoveDown = async (item) => {
|
|
||||||
|
const handleMoveDown = async (item: { id: string | number }) => {
|
||||||
const { id } = item;
|
const { id } = item;
|
||||||
try {
|
try {
|
||||||
const { code, message } = await departmentSetDown(id);
|
const { code, message } = await departmentSetDown(Number(id));
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
Message.success(message);
|
Message.success(message);
|
||||||
getDepartmentListData();
|
getDepartmentListData();
|
||||||
|
|
@ -216,7 +234,7 @@
|
||||||
Message.info(message);
|
Message.info(message);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('向下移动失败:', error);
|
// 处理错误
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
300
src/views/setting/user/components/user-form-drawer.vue
Normal file
300
src/views/setting/user/components/user-form-drawer.vue
Normal file
|
|
@ -0,0 +1,300 @@
|
||||||
|
<template>
|
||||||
|
<a-drawer
|
||||||
|
:width="340"
|
||||||
|
:visible="visible"
|
||||||
|
:title="isEdit ? $t('edit-user') : $t('add-user')"
|
||||||
|
unmount-on-close
|
||||||
|
@ok="handleOk"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
>
|
||||||
|
<a-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="form"
|
||||||
|
:rules="rules"
|
||||||
|
auto-label-width
|
||||||
|
layout="vertical"
|
||||||
|
>
|
||||||
|
<!-- 用户名 -->
|
||||||
|
<a-form-item :label="$t('username')" field="username" required>
|
||||||
|
<a-input
|
||||||
|
v-model="form.username"
|
||||||
|
:placeholder="$t('please.enter', { placeholder: $t('username') })"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<!-- 姓名 -->
|
||||||
|
<a-form-item :label="$t('name')" field="realname" required>
|
||||||
|
<a-input
|
||||||
|
v-model="form.realname"
|
||||||
|
:placeholder="$t('please.enter', { placeholder: $t('name') })"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item field="password" :label="$t('password')" class="form-item">
|
||||||
|
<a-input-password
|
||||||
|
v-model="form.password"
|
||||||
|
:disabled="isEdit"
|
||||||
|
:placeholder="$t('please.enter', { placeholder: $t('password') })"
|
||||||
|
allow-clear
|
||||||
|
class="input-field"
|
||||||
|
>
|
||||||
|
</a-input-password>
|
||||||
|
</a-form-item>
|
||||||
|
<!-- 确认密码 -->
|
||||||
|
<a-form-item
|
||||||
|
field="repassword"
|
||||||
|
:label="$t('confirm.password')"
|
||||||
|
class="form-item"
|
||||||
|
>
|
||||||
|
<a-input-password
|
||||||
|
v-model="form.repassword"
|
||||||
|
:disabled="isEdit"
|
||||||
|
:placeholder="$t('please.confirm', { placeholder: $t('password') })"
|
||||||
|
allow-clear
|
||||||
|
class="input-field"
|
||||||
|
>
|
||||||
|
</a-input-password>
|
||||||
|
</a-form-item>
|
||||||
|
<!-- 手机号 -->
|
||||||
|
<a-form-item :label="$t('tel')" field="tel" required>
|
||||||
|
<a-input
|
||||||
|
v-model="form.tel"
|
||||||
|
:placeholder="$t('please.enter', { placeholder: $t('tel') })"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<!-- 邮箱 -->
|
||||||
|
<a-form-item :label="$t('email')" field="email">
|
||||||
|
<a-input
|
||||||
|
v-model="form.email"
|
||||||
|
:placeholder="$t('please.enter', { placeholder: $t('email') })"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<!-- 角色 -->
|
||||||
|
<a-form-item :label="$t('role')" field="role_id" required>
|
||||||
|
<a-select
|
||||||
|
v-model="form.role_id"
|
||||||
|
:options="props.roleList"
|
||||||
|
:field-names="{ value: 'id', label: 'name' }"
|
||||||
|
:placeholder="$t('please.select', { placeholder: $t('role') })"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<!-- 部门主管 -->
|
||||||
|
<a-form-item field="department_id">
|
||||||
|
<template #label>
|
||||||
|
<div class="label_wrap">
|
||||||
|
<div class="left">
|
||||||
|
<span class="required">*</span>
|
||||||
|
{{ $t('department') }}
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<a-checkbox
|
||||||
|
:default-checked="form.is_manager === 1"
|
||||||
|
:checked="form.is_manager === 1"
|
||||||
|
@change="form.is_manager = $event ? 1 : 2"
|
||||||
|
>
|
||||||
|
{{ $t('department-head') }}
|
||||||
|
</a-checkbox>
|
||||||
|
<icon-exclamation-circle-fill />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<a-tree-select
|
||||||
|
v-model="form.department_id"
|
||||||
|
:data="props.departmentListData"
|
||||||
|
:field-names="{ key: 'id', title: 'name', children: 'children' }"
|
||||||
|
:placeholder="$t('please.select', { placeholder: $t('department') })"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch, reactive } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import type { FormInstance } from '@arco-design/web-vue';
|
||||||
|
import {
|
||||||
|
DepartmentItem,
|
||||||
|
RoleItem,
|
||||||
|
FormUserItem,
|
||||||
|
createUser,
|
||||||
|
updateUser,
|
||||||
|
} from '@/api/setting';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
visible: boolean;
|
||||||
|
isEdit: boolean;
|
||||||
|
record?: FormUserItem | null;
|
||||||
|
departmentListData?: DepartmentItem[];
|
||||||
|
roleList?: RoleItem[];
|
||||||
|
}>();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:visible', val: boolean): void;
|
||||||
|
(e: 'submitted'): void;
|
||||||
|
}>();
|
||||||
|
// 表单数据
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
const defaultForm: FormUserItem = {
|
||||||
|
username: '',
|
||||||
|
realname: '',
|
||||||
|
tel: '',
|
||||||
|
password: '',
|
||||||
|
repassword: '',
|
||||||
|
role_id: null,
|
||||||
|
department_id: null,
|
||||||
|
email: '',
|
||||||
|
is_manager: 1,
|
||||||
|
};
|
||||||
|
const form = reactive<FormUserItem>({ ...defaultForm });
|
||||||
|
const { t } = useI18n();
|
||||||
|
// 校验规则
|
||||||
|
const rules = reactive({
|
||||||
|
username: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('please.enter', { placeholder: t('username') }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
realname: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('please.enter', { placeholder: t('name') }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
password: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('please.enter', { placeholder: t('password') }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
repassword: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('please.confirm', { placeholder: t('password') }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
validator: (value, cb) => {
|
||||||
|
if (value !== form.password) {
|
||||||
|
cb('two passwords do not match');
|
||||||
|
} else {
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tel: [
|
||||||
|
{ required: true, message: t('please.enter', { placeholder: t('tel') }) },
|
||||||
|
{
|
||||||
|
validator: (value, cb) => {
|
||||||
|
if (!/^1\d{10}$/.test(String(value))) {
|
||||||
|
cb(t('please.enter.correct', { field: t('tel') }));
|
||||||
|
} else {
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
email: [
|
||||||
|
{
|
||||||
|
validator: (value, cb) => {
|
||||||
|
if (!value) {
|
||||||
|
cb();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const emailReg = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/;
|
||||||
|
if (!emailReg.test(value)) {
|
||||||
|
cb(t('please.enter.correct', { field: t('email') }));
|
||||||
|
} else {
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
role_id: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('please.select', { placeholder: t('role') }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
department_id: [
|
||||||
|
{ message: t('please.select', { placeholder: t('department-head') }) },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
(val) => {
|
||||||
|
if (val) {
|
||||||
|
if (props.isEdit && props.record) {
|
||||||
|
const record = props.record as FormUserItem & {
|
||||||
|
roleId?: number;
|
||||||
|
departmentId?: number;
|
||||||
|
isManager?: number;
|
||||||
|
};
|
||||||
|
Object.assign(form, {
|
||||||
|
...record,
|
||||||
|
});
|
||||||
|
form.role_id = record?.roleId ?? null;
|
||||||
|
form.department_id = record?.departmentId ?? null;
|
||||||
|
form.password = record?.password;
|
||||||
|
form.repassword = record?.password;
|
||||||
|
form.is_manager =
|
||||||
|
record?.isManager === 1 || record?.isManager === 2
|
||||||
|
? record.isManager
|
||||||
|
: 1;
|
||||||
|
} else {
|
||||||
|
Object.assign(form, defaultForm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
const handleOk = async () => {
|
||||||
|
try {
|
||||||
|
const valid = await formRef.value.validate();
|
||||||
|
if (valid) return;
|
||||||
|
const params = { ...form };
|
||||||
|
if (props.isEdit) {
|
||||||
|
await updateUser(params);
|
||||||
|
} else {
|
||||||
|
await createUser(params);
|
||||||
|
}
|
||||||
|
emit('submitted');
|
||||||
|
emit('update:visible', false);
|
||||||
|
} catch (err) {
|
||||||
|
// 处理错误
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('update:visible', false);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
:deep(label.arco-form-item-label) {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label_wrap {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.required {
|
||||||
|
color: #f53f3f;
|
||||||
|
margin-right: 4px;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -3,17 +3,21 @@
|
||||||
<Breadcrumb
|
<Breadcrumb
|
||||||
:items="['menu.setting', 'menu.setting.user', 'menu.setting.user-list']"
|
:items="['menu.setting', 'menu.setting.user', 'menu.setting.user-list']"
|
||||||
/>
|
/>
|
||||||
<a-row class="" :gutter="12">
|
<a-row :gutter="12" :wrap="false">
|
||||||
<a-col flex="310px">
|
<a-col flex="310px">
|
||||||
<a-card :bordered="false">
|
<a-card class="full_height" :bordered="false">
|
||||||
<template #title>
|
<template #title>
|
||||||
<div class="card-title_between">
|
<div class="card-title_between">
|
||||||
<p class="title">{{ $t('organization') }}</p>
|
<p class="title">{{ $t('organization') }}</p>
|
||||||
<a-button @click="onClick"> {{ $t('watch-detail') }} </a-button>
|
<a-button @click="toOrganization">
|
||||||
|
{{ $t('watch-detail') }}
|
||||||
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<a-tree
|
<a-tree
|
||||||
:field-names="{ key: 'ID', title: 'Name' }"
|
v-model:expanded-keys="expandedKeys"
|
||||||
|
v-model:selected-keys="selectedKeys"
|
||||||
|
:field-names="{ key: 'id', title: 'name' }"
|
||||||
:data="departmentListData"
|
:data="departmentListData"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
|
|
@ -23,35 +27,335 @@
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col flex="auto">
|
<a-col flex="auto">
|
||||||
<a-card title="Arco Card" :bordered="false">
|
<a-card class="full_height" title="Arco Card" :bordered="false">
|
||||||
<template #extra>
|
<template #title>
|
||||||
<a-link>More</a-link>
|
<div class="card-title_between">
|
||||||
|
<p class="title">{{ $t('menu.business.project') }}</p>
|
||||||
|
<div class="flex_center_between">
|
||||||
|
<a-space>
|
||||||
|
<a-input-search
|
||||||
|
v-model="searchKeyWord"
|
||||||
|
allow-clear
|
||||||
|
:style="{ width: '320px' }"
|
||||||
|
:placeholder="$t('search.username.name')"
|
||||||
|
@clear="handleSearchUser"
|
||||||
|
@search="handleSearchUser"
|
||||||
|
@press-enter="handleSearchUser"
|
||||||
|
/>
|
||||||
|
<a-button>
|
||||||
|
<template #icon>
|
||||||
|
<icon-filter />
|
||||||
|
</template>
|
||||||
|
{{ $t('table.filter') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button type="primary" @click="handleAddUser">
|
||||||
|
<template #icon>
|
||||||
|
<icon-plus />
|
||||||
|
</template>
|
||||||
|
{{ $t('add-user') }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
Card content
|
<!-- 使用新的 DataTable 组件 -->
|
||||||
<br />
|
<DataTable
|
||||||
Card content
|
:data="renderData"
|
||||||
|
:columns="projectColumn"
|
||||||
|
:loading="loading"
|
||||||
|
:total="total"
|
||||||
|
:current="current"
|
||||||
|
:page-size="pageSize"
|
||||||
|
pagination
|
||||||
|
@page-change="onPageChange"
|
||||||
|
@page-size-change="onPageSizeChange"
|
||||||
|
>
|
||||||
|
<template #realname="{ record }">
|
||||||
|
<span>{{ record.realname }}</span>
|
||||||
|
<icon-idcard v-if="record.isManager === 1" />
|
||||||
|
</template>
|
||||||
|
<template #status="{ record }">
|
||||||
|
<a-tag :color="record.status == 1 ? 'green' : 'red'">
|
||||||
|
{{ record.status == 1 ? $t('enable') : $t('disable') }}</a-tag
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
<template #operate="{ record }">
|
||||||
|
<a-space>
|
||||||
|
<!-- <a-button>查看</a-button> -->
|
||||||
|
<a-button type="primary" @click="handleEditUser(record.id)">{{
|
||||||
|
$t('edit')
|
||||||
|
}}</a-button>
|
||||||
|
<a-popover trigger="click" position="bottom">
|
||||||
|
<a-button>{{ $t('more') }}</a-button>
|
||||||
|
<template #content>
|
||||||
|
<a-space direction="vertical">
|
||||||
|
<a-button
|
||||||
|
@click="handleDisableUser(record.id, record.status)"
|
||||||
|
>
|
||||||
|
{{ record.status == 1 ? $t('disable') : $t('enable') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
status="danger"
|
||||||
|
@click="handleDelUser(record.id)"
|
||||||
|
>{{ $t('delete') }}</a-button
|
||||||
|
>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</a-popover>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</DataTable>
|
||||||
</a-card>
|
</a-card>
|
||||||
|
<userFormDrawer
|
||||||
|
v-model:visible="showDrawer"
|
||||||
|
:record="userInfo"
|
||||||
|
:is-edit="isEdit"
|
||||||
|
:department-list-data="departmentListData"
|
||||||
|
:role-list="roleList"
|
||||||
|
@submitted="getUserListData"
|
||||||
|
/>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DepartmentFormParams, createDepartment } from '@/api/setting';
|
import { ref, watch, onMounted } from 'vue';
|
||||||
import useDepartmentList from '@/views/setting/composables/useDepartmentList';
|
import { useI18n } from 'vue-i18n';
|
||||||
// import { ref } from 'vue'
|
import { useRouter } from 'vue-router';
|
||||||
const onClick = async () => {
|
import type { TableColumnData } from '@arco-design/web-vue';
|
||||||
const params: DepartmentFormParams = {
|
import {
|
||||||
name: '冰一下很开心', // 部门名称
|
getUserList,
|
||||||
parent_id: 34, // 上一级id
|
UserList,
|
||||||
};
|
getRoleList,
|
||||||
|
RoleItem,
|
||||||
|
getUserInfo,
|
||||||
|
FormUserItem,
|
||||||
|
disableUser,
|
||||||
|
delUser,
|
||||||
|
} from '@/api/setting';
|
||||||
|
import useDepartmentList from '@/composables/useDepartmentList';
|
||||||
|
import useDataTable from '@/composables/useDataTable';
|
||||||
|
import { Message } from '@arco-design/web-vue';
|
||||||
|
import userFormDrawer from './components/user-form-drawer.vue';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const { departmentListData, allDepartmentKeys } = useDepartmentList();
|
||||||
|
|
||||||
|
const expandedKeys = ref<(string | number)[]>([]);
|
||||||
|
const selectedKeys = ref<(string | number)[]>([]);
|
||||||
|
const showDrawer = ref<boolean>(false);
|
||||||
|
const isEdit = ref<boolean>(false);
|
||||||
|
const userInfo = ref<FormUserItem | null>(null);
|
||||||
|
const roleList = ref<RoleItem[]>([]);
|
||||||
|
const roleListLoaded = ref(false);
|
||||||
|
// 使用 DataTable composable
|
||||||
|
const {
|
||||||
|
data: renderData,
|
||||||
|
columns: projectColumn,
|
||||||
|
loading,
|
||||||
|
total,
|
||||||
|
current,
|
||||||
|
pageSize,
|
||||||
|
setData,
|
||||||
|
setColumns,
|
||||||
|
setLoading,
|
||||||
|
setTotal,
|
||||||
|
setCurrent,
|
||||||
|
setPageSize,
|
||||||
|
} = useDataTable({
|
||||||
|
initialPage: 1,
|
||||||
|
initialPageSize: 10,
|
||||||
|
pageSizeOptions: [10, 20, 50, 100],
|
||||||
|
});
|
||||||
|
|
||||||
|
const userListData = ref<UserList>([]);
|
||||||
|
// 列配置
|
||||||
|
const columns: TableColumnData[] = [
|
||||||
|
{
|
||||||
|
title: t('username'),
|
||||||
|
dataIndex: 'username',
|
||||||
|
key: 'username',
|
||||||
|
width: 120,
|
||||||
|
ellipsis: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('name'),
|
||||||
|
dataIndex: 'realname',
|
||||||
|
key: 'realname',
|
||||||
|
width: 150,
|
||||||
|
ellipsis: true,
|
||||||
|
slotName: 'realname',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('department'),
|
||||||
|
dataIndex: 'deptName',
|
||||||
|
key: 'departmentId',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('role'),
|
||||||
|
dataIndex: 'roleName',
|
||||||
|
key: 'roleName',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('status'),
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
slotName: 'status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('lastLogin'),
|
||||||
|
dataIndex: 'lastLoginTime',
|
||||||
|
key: 'lastLoginTime',
|
||||||
|
render: ({ record }) => record.lastLoginTime || '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'operate',
|
||||||
|
slotName: 'operate',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
watch(
|
||||||
|
allDepartmentKeys,
|
||||||
|
(val) => {
|
||||||
|
expandedKeys.value = val;
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
const toOrganization = () => {
|
||||||
|
router.push({ path: '/setting/system', query: { active: 4 } });
|
||||||
|
};
|
||||||
|
const searchKeyWord = ref<string>('');
|
||||||
|
// const pageNo = ref<number>(1)
|
||||||
|
const getUserListData = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await createDepartment(params);
|
const params = {
|
||||||
console.log(res, 'res');
|
dept_id: selectedKeys.value[0],
|
||||||
|
keyword: searchKeyWord.value,
|
||||||
|
page: current.value,
|
||||||
|
page_size: pageSize.value,
|
||||||
|
};
|
||||||
|
setLoading(true);
|
||||||
|
const { code, data } = await getUserList(params);
|
||||||
|
if (code === 200) {
|
||||||
|
const { page, user } = data;
|
||||||
|
userListData.value = Array.isArray(user) ? user : [];
|
||||||
|
setData(userListData.value);
|
||||||
|
setTotal(typeof page.total === 'number' ? page.total : 0);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error, 'res');
|
// 处理错误
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const { departmentListData, getDepartmentListData } = useDepartmentList();
|
|
||||||
getDepartmentListData();
|
const openDrawer = async () => {
|
||||||
|
if (!roleListLoaded.value) {
|
||||||
|
try {
|
||||||
|
const { code, data } = await getRoleList();
|
||||||
|
if (code === 200) {
|
||||||
|
roleList.value = Array.isArray(data?.rolesList) ? data.rolesList : [];
|
||||||
|
roleListLoaded.value = true;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 处理错误
|
||||||
|
}
|
||||||
|
}
|
||||||
|
showDrawer.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
departmentListData,
|
||||||
|
(val) => {
|
||||||
|
if (Array.isArray(val) && val.length > 0) {
|
||||||
|
const [firstRoot] = val;
|
||||||
|
if (firstRoot.children && firstRoot.children.length > 0) {
|
||||||
|
selectedKeys.value = [firstRoot.children[0].id];
|
||||||
|
} else {
|
||||||
|
selectedKeys.value = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true }
|
||||||
|
);
|
||||||
|
let lastSelectedKey: string | number = '';
|
||||||
|
watch(selectedKeys, (val) => {
|
||||||
|
// 只处理有选中项的情况
|
||||||
|
if (val.length > 0) {
|
||||||
|
// 判断新旧 key 是否相同
|
||||||
|
if (val[0] !== lastSelectedKey) {
|
||||||
|
lastSelectedKey = val[0];
|
||||||
|
getUserListData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 分页事件处理
|
||||||
|
const onPageChange = (page: number) => {
|
||||||
|
setCurrent(page);
|
||||||
|
current.value = page;
|
||||||
|
getUserListData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPageSizeChange = (size: number) => {
|
||||||
|
setPageSize(size);
|
||||||
|
pageSize.value = size;
|
||||||
|
getUserListData();
|
||||||
|
};
|
||||||
|
const handleSearchUser = () => {
|
||||||
|
getUserListData();
|
||||||
|
};
|
||||||
|
const handleAddUser = async () => {
|
||||||
|
isEdit.value = false;
|
||||||
|
userInfo.value = null;
|
||||||
|
openDrawer();
|
||||||
|
};
|
||||||
|
const getUserInfoData = async (id: number) => {
|
||||||
|
try {
|
||||||
|
const { code, data } = await getUserInfo(id);
|
||||||
|
if (code === 200) userInfo.value = data?.user;
|
||||||
|
} catch (error) {
|
||||||
|
// 处理错误
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleEditUser = async (id: number) => {
|
||||||
|
isEdit.value = true;
|
||||||
|
userInfo.value = null;
|
||||||
|
await getUserInfoData(id);
|
||||||
|
openDrawer();
|
||||||
|
};
|
||||||
|
const handleDisableUser = async (id: number, currentStatus: number) => {
|
||||||
|
try {
|
||||||
|
const param = { id, status: currentStatus === 1 ? 2 : 1 };
|
||||||
|
const { code, message } = await disableUser(param);
|
||||||
|
if (code === 200) {
|
||||||
|
Message.success(message);
|
||||||
|
getUserListData();
|
||||||
|
} else {
|
||||||
|
Message.error(message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 处理错误
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleDelUser = async (id: number) => {
|
||||||
|
try {
|
||||||
|
const { code, message } = await delUser(id);
|
||||||
|
if (code === 200) {
|
||||||
|
Message.success(message);
|
||||||
|
getUserListData();
|
||||||
|
} else {
|
||||||
|
Message.error(message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 处理错误
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 初始化数据
|
||||||
|
onMounted(() => {
|
||||||
|
setColumns(columns);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue