feat: 设置模块用户列表功能90%
This commit is contained in:
+113
-19
@@ -13,7 +13,18 @@ export interface DepartmentIdData {
|
||||
departmentId: number | string;
|
||||
}
|
||||
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 {
|
||||
id: number;
|
||||
systemTenantId: number;
|
||||
@@ -41,22 +52,11 @@ export interface DepartmentDetailData {
|
||||
}
|
||||
export type DepartmentDetailRes = HttpResponse<DepartmentDetailData>;
|
||||
|
||||
// 创建部门
|
||||
export function createDepartment(
|
||||
params: DepartmentFormParams
|
||||
): Promise<DepartmentFormRes> {
|
||||
return axios.post('/department/createDepartment', params);
|
||||
}
|
||||
// 获取部门列表
|
||||
export function getDepartmentList(): Promise<DepartmentListRes> {
|
||||
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> {
|
||||
return axios.get(`/department/getDepartment?id=${id}`);
|
||||
}
|
||||
@@ -68,19 +68,73 @@ export interface UserItem {
|
||||
email: string; // 邮箱
|
||||
tel: string; // 手机号
|
||||
status: 1 | 2; // 状态:1 启用,2 禁用
|
||||
role_id: number; // 角色id
|
||||
department_id: number; // 部门id
|
||||
role_name: string; // 角色名称
|
||||
dept_name: string; // 部门名称
|
||||
roleId: number; // 角色id
|
||||
departmentId: number; // 部门id
|
||||
roleName: string; // 角色名称
|
||||
deptName: string; // 部门名称
|
||||
lastLoginTime: string; // 最后登录时间
|
||||
}
|
||||
export type UserList = UserItem[];
|
||||
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);
|
||||
}
|
||||
// 后台-用户列表(所有用户)
|
||||
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> {
|
||||
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> {
|
||||
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) {
|
||||
// return axios.get<any>(`/project/getProject?id=${id}`);
|
||||
|
||||
@@ -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 接口。
|
||||
@@ -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';
|
||||
import Chart from './chart/index.vue';
|
||||
import Breadcrumb from './breadcrumb/index.vue';
|
||||
import DataTable from './common/DataTable.vue';
|
||||
|
||||
// Manually introduce ECharts modules to reduce packing size
|
||||
|
||||
@@ -31,5 +32,6 @@ export default {
|
||||
install(Vue: App) {
|
||||
Vue.component('Chart', Chart);
|
||||
Vue.component('Breadcrumb', Breadcrumb);
|
||||
Vue.component('DataTable', DataTable);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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` 适用于需要等待用户操作后再获取数据的场景
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
+1
@@ -4,6 +4,7 @@ import {
|
||||
DepartmentItem,
|
||||
getDepartmentDetail,
|
||||
} from '@/api/setting';
|
||||
import useLoading from '@/hooks/loading';
|
||||
|
||||
export default function useDepartmentList(autoFetch = true) {
|
||||
const departmentListData = ref<DepartmentItem[]>([]);
|
||||
+25
-1
@@ -35,7 +35,10 @@ export default {
|
||||
'menu.finance': '财务',
|
||||
'menu.property': '物业',
|
||||
'menu.setting': '设置',
|
||||
'search.placeholder': '请输入内容',
|
||||
'please.enter': '请输入{placeholder}',
|
||||
'please.select': '请选择{placeholder}',
|
||||
'please.confirm': '请确认{placeholder}',
|
||||
'content': '内容',
|
||||
'table.filter': '筛选',
|
||||
'table.export': '导出',
|
||||
'watch-detail': '查看详情',
|
||||
@@ -61,6 +64,7 @@ export default {
|
||||
'company-info': '公司信息',
|
||||
'appearance-setting': '外观设置',
|
||||
'add-department': '新建部门',
|
||||
'edit-department': '编辑部门',
|
||||
'department-name': '部门名称',
|
||||
'belongto-department': '所属部门',
|
||||
'department-head': '部门主管',
|
||||
@@ -73,6 +77,26 @@ export default {
|
||||
'delete': '删除',
|
||||
'confirm': '确定',
|
||||
'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,
|
||||
...localeMessageBox,
|
||||
|
||||
@@ -4,43 +4,46 @@
|
||||
<a-card class="general-card">
|
||||
<template #title>
|
||||
<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">
|
||||
<a-space>
|
||||
<a-input-search
|
||||
:style="{ width: '320px' }"
|
||||
:placeholder="$t('search.placeholder')"
|
||||
:placeholder="t('please.enter', { placeholder: t('content') })"
|
||||
/>
|
||||
<a-button>
|
||||
<template #icon>
|
||||
<icon-filter />
|
||||
</template>
|
||||
{{ $t('table.filter') }}
|
||||
{{ t('table.filter') }}
|
||||
</a-button>
|
||||
<a-button>
|
||||
<template #icon>
|
||||
<icon-download />
|
||||
</template>
|
||||
{{ $t('table.export') }}
|
||||
{{ t('table.export') }}
|
||||
</a-button>
|
||||
<a-button type="primary">
|
||||
<template #icon>
|
||||
<icon-plus />
|
||||
</template>
|
||||
{{ $t('add-project') }}
|
||||
{{ t('add-project') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<a-table
|
||||
row-key="id"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:columns="projectColumn"
|
||||
|
||||
<!-- 使用新的 DataTable 组件 -->
|
||||
<DataTable
|
||||
:data="renderData"
|
||||
:bordered="false"
|
||||
:columns="projectColumn"
|
||||
:loading="loading"
|
||||
:total="total"
|
||||
:current="current"
|
||||
:page-size="pageSize"
|
||||
@page-change="onPageChange"
|
||||
@page-size-change="onPageSizeChange"
|
||||
>
|
||||
<template #type="{ record }">
|
||||
<a-tag checkable :color="getTypeColor(record.type)">{{
|
||||
@@ -61,15 +64,16 @@
|
||||
<a-button>更多</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-table>
|
||||
</DataTable>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, reactive, watch, nextTick } from 'vue';
|
||||
import { onMounted } from 'vue';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { Pagination } from '@/types/global';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import type { TableColumnData } from '@arco-design/web-vue';
|
||||
import {
|
||||
ProjectRecord,
|
||||
ProjectType,
|
||||
@@ -77,11 +81,35 @@
|
||||
PROJECT_TYPE_COLORS,
|
||||
PROJECT_TYPE_LABELS,
|
||||
} 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 renderData = ref<ProjectRecord[]>([
|
||||
const { t } = useI18n();
|
||||
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: '星河商务中心',
|
||||
type: PROJECT_TYPE.COMMERCIAL_COMPLEX,
|
||||
size: '1200m²',
|
||||
@@ -91,6 +119,7 @@
|
||||
create_time: '2025-06-24 00:51',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: '南山科学科学院',
|
||||
type: PROJECT_TYPE.OFFICE_BUILDING,
|
||||
size: '120m²',
|
||||
@@ -100,6 +129,7 @@
|
||||
create_time: '2025-06-24 00:58',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: '前海科学科学院',
|
||||
type: PROJECT_TYPE.INDUSTRIAL_PARK,
|
||||
size: '1520m²',
|
||||
@@ -108,24 +138,25 @@
|
||||
count: '120',
|
||||
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 => {
|
||||
@@ -135,21 +166,28 @@
|
||||
const getTypeText = (type: ProjectType): string => {
|
||||
return PROJECT_TYPE_LABELS[type] || '未知';
|
||||
};
|
||||
const onPageChange = (current: number) => {
|
||||
pagination.current = current;
|
||||
|
||||
// 分页事件处理
|
||||
const onPageChange = (page: number) => {
|
||||
setCurrent(page);
|
||||
// TODO: 这里应该调用API获取对应页码的数据
|
||||
};
|
||||
|
||||
const getAllProjectListData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data } = await getAllProjectList();
|
||||
console.log(data, 'getAllProjectList');
|
||||
} catch (err) {
|
||||
// you can report use errorHandler or other
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
const onPageSizeChange = (newPageSize: number) => {
|
||||
setPageSize(newPageSize);
|
||||
// TODO: 这里应该调用API获取对应页码的数据
|
||||
};
|
||||
|
||||
// const getAllProjectListData = async () => {
|
||||
// setLoading(true);
|
||||
// try {
|
||||
// await getAllProjectList();
|
||||
// // 处理数据
|
||||
// } catch (err) {
|
||||
// // you can report use errorHandler or other
|
||||
// } finally {
|
||||
// setLoading(false);
|
||||
// }
|
||||
// };
|
||||
// getAllProjectListData()
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:visible="visible"
|
||||
:title="isEdit ? '编辑部门' : '新增部门'"
|
||||
:title="isEdit ? $t('edit-department') : $t('add-department')"
|
||||
unmount-on-close
|
||||
@ok="handleSubmit"
|
||||
@cancel="handleCancel"
|
||||
@@ -14,36 +14,48 @@
|
||||
layout="vertical"
|
||||
>
|
||||
<!-- 部门名称 -->
|
||||
<a-form-item :label="t('department-name')" field="name" required>
|
||||
<a-input v-model="form.name" placeholder="请输入部门名称" />
|
||||
<a-form-item :label="$t('department-name')" field="name" required>
|
||||
<a-input
|
||||
v-model="form.name"
|
||||
:placeholder="
|
||||
$t('please.enter', { placeholder: $t('department-name') })
|
||||
"
|
||||
/>
|
||||
</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
|
||||
v-model="form.parent_id"
|
||||
:data="departmentListData"
|
||||
:field-names="{ key: 'id', title: 'name', children: 'children' }"
|
||||
placeholder="请选择所属部门"
|
||||
:placeholder="
|
||||
$t('please.select', { placeholder: $t('belongto-department') })
|
||||
"
|
||||
allow-clear
|
||||
/>
|
||||
</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
|
||||
v-model="form.user_id"
|
||||
:options="props.userListData"
|
||||
placeholder="请选择部门主管"
|
||||
:placeholder="
|
||||
$t('please.select', { placeholder: $t('department-head') })
|
||||
"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 备注 -->
|
||||
<a-form-item :label="t('remark')" field="remark">
|
||||
<a-form-item :label="$t('remark')" field="remark">
|
||||
<a-textarea
|
||||
v-model="form.remark"
|
||||
placeholder="请输入备注"
|
||||
:placeholder="$t('please.enter', { placeholder: $t('remark') })"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
@@ -75,17 +87,26 @@
|
||||
user_id?: number;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
interface TreeNode {
|
||||
id: string | number;
|
||||
name: string;
|
||||
children?: TreeNode[];
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
isEdit: boolean;
|
||||
record?: DepartmentItem | null;
|
||||
departmentListData: any[]; // 新增
|
||||
departmentListData: TreeNode[]; // 修复类型
|
||||
userListData: UserList;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', val: boolean): void;
|
||||
(e: 'submitted'): void;
|
||||
}>();
|
||||
|
||||
// 表单数据
|
||||
const formRef = ref<FormInstance>();
|
||||
const form = reactive<DepartmentForm>({
|
||||
@@ -99,7 +120,12 @@
|
||||
const { t } = useI18n();
|
||||
// 校验规则
|
||||
const rules = {
|
||||
name: [{ required: true, message: '请输入部门名称' }],
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: t('please.enter', { placeholder: t('department') }),
|
||||
},
|
||||
],
|
||||
parent_id: [{ required: true, message: '请选择所属部门' }],
|
||||
};
|
||||
|
||||
|
||||
@@ -33,8 +33,9 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
import {
|
||||
IconSettings,
|
||||
IconSafe,
|
||||
@@ -45,7 +46,23 @@
|
||||
import organization from './organization.vue';
|
||||
// import { CreateDepartmentParams, createDepartment, getDepartmentList, DepartmentItem } from '@/api/setting'
|
||||
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 = [
|
||||
{ id: 1, name: t('general-setting'), icon: IconSettings },
|
||||
{ id: 2, name: t('security-setting'), icon: IconSafe },
|
||||
@@ -56,6 +73,8 @@
|
||||
const handleChooseSystemItem = (id: number) => {
|
||||
currentActive.value = id;
|
||||
};
|
||||
|
||||
// watch()
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
@@ -93,9 +93,9 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch, onMounted } from 'vue';
|
||||
import { ref, watch, onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import useDepartmentList from '@/views/setting/composables/useDepartmentList';
|
||||
import useDepartmentList from '@/composables/useDepartmentList';
|
||||
import {
|
||||
DepartmentItem,
|
||||
getAllUserList,
|
||||
@@ -114,9 +114,10 @@
|
||||
getDepartmentListData,
|
||||
getDepartmentDetailData,
|
||||
} = useDepartmentList();
|
||||
const emit = defineEmits(['onSelectTree']);
|
||||
|
||||
const expandedKeys = ref<(string | number)[]>([]);
|
||||
const { t } = useI18n();
|
||||
|
||||
watch(
|
||||
allDepartmentKeys,
|
||||
(val) => {
|
||||
@@ -129,7 +130,14 @@
|
||||
const isEdit = ref<boolean>(false);
|
||||
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) => {
|
||||
node.isShowEditIcon = false;
|
||||
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 { node } = event;
|
||||
if (node.id === currentNode.value?.id) return;
|
||||
currentNode.value = node;
|
||||
// 递归重置所有节点的 isShowEditIcon
|
||||
resetEditIcon(departmentListData.value);
|
||||
// 只设置当前节点
|
||||
if (currentNode.value.level !== 1) {
|
||||
node.isShowEditIcon = true;
|
||||
}
|
||||
departmentDetailInfo.value = await getDepartmentDetailData(
|
||||
currentNode.value.id
|
||||
);
|
||||
}, 300);
|
||||
|
||||
const currentNode = ref<TreeNode | null>(null);
|
||||
|
||||
const onSelect = debounce(
|
||||
async (_: (string | number)[], event: { node: TreeNode }) => {
|
||||
const { node } = event;
|
||||
if (node.id === currentNode.value?.id) return;
|
||||
currentNode.value = node;
|
||||
// 递归重置所有节点的 isShowEditIcon
|
||||
resetEditIcon(departmentListData.value);
|
||||
// 只设置当前节点
|
||||
if (currentNode.value.level !== 1) {
|
||||
node.isShowEditIcon = true;
|
||||
}
|
||||
departmentDetailInfo.value = await getDepartmentDetailData(
|
||||
Number(currentNode.value.id)
|
||||
);
|
||||
},
|
||||
300
|
||||
);
|
||||
|
||||
// 所有用户数据
|
||||
const allUserList = ref<UserList>([]);
|
||||
@@ -162,39 +175,43 @@
|
||||
const { code, data } = await getAllUserList({
|
||||
keyword: userKeyword.value,
|
||||
});
|
||||
if (code === 200 && data?.length) {
|
||||
allUserList.value = data;
|
||||
if (code === 200) {
|
||||
const { user } = data;
|
||||
allUserList.value = Array.isArray(user) ? user : [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取所以用户数据失败:', error);
|
||||
// 处理错误
|
||||
}
|
||||
};
|
||||
|
||||
const handleAdd = () => {
|
||||
showModal.value = true;
|
||||
isEdit.value = false;
|
||||
departmentDetailInfo.value = null;
|
||||
showModal.value = true;
|
||||
};
|
||||
|
||||
const handleEdit = () => {
|
||||
showModal.value = true;
|
||||
isEdit.value = true;
|
||||
};
|
||||
const handleDelete = async (item) => {
|
||||
|
||||
const handleDelete = async (item: { id: string | number }) => {
|
||||
const { id } = item;
|
||||
try {
|
||||
const { code, message } = await delDepartment(id);
|
||||
const { code, message } = await delDepartment(Number(id));
|
||||
if (code === 200) {
|
||||
Message.success(message);
|
||||
getDepartmentListData();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('删除失败:', error);
|
||||
// 处理错误
|
||||
}
|
||||
};
|
||||
|
||||
const handleMoveUp = async (item) => {
|
||||
const handleMoveUp = async (item: { id: string | number }) => {
|
||||
const { id } = item;
|
||||
try {
|
||||
const { code, message } = await departmentSetUp(id);
|
||||
const { code, message } = await departmentSetUp(Number(id));
|
||||
if (code === 200) {
|
||||
Message.success(message);
|
||||
getDepartmentListData();
|
||||
@@ -202,13 +219,14 @@
|
||||
Message.info(message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('向上移动失败:', error);
|
||||
// 处理错误
|
||||
}
|
||||
};
|
||||
const handleMoveDown = async (item) => {
|
||||
|
||||
const handleMoveDown = async (item: { id: string | number }) => {
|
||||
const { id } = item;
|
||||
try {
|
||||
const { code, message } = await departmentSetDown(id);
|
||||
const { code, message } = await departmentSetDown(Number(id));
|
||||
if (code === 200) {
|
||||
Message.success(message);
|
||||
getDepartmentListData();
|
||||
@@ -216,7 +234,7 @@
|
||||
Message.info(message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('向下移动失败:', error);
|
||||
// 处理错误
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
+327
-23
@@ -3,17 +3,21 @@
|
||||
<Breadcrumb
|
||||
: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-card :bordered="false">
|
||||
<a-card class="full_height" :bordered="false">
|
||||
<template #title>
|
||||
<div class="card-title_between">
|
||||
<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>
|
||||
</template>
|
||||
<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"
|
||||
>
|
||||
<template #icon>
|
||||
@@ -23,35 +27,335 @@
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col flex="auto">
|
||||
<a-card title="Arco Card" :bordered="false">
|
||||
<template #extra>
|
||||
<a-link>More</a-link>
|
||||
<a-card class="full_height" title="Arco Card" :bordered="false">
|
||||
<template #title>
|
||||
<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>
|
||||
Card content
|
||||
<br />
|
||||
Card content
|
||||
<!-- 使用新的 DataTable 组件 -->
|
||||
<DataTable
|
||||
: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>
|
||||
<userFormDrawer
|
||||
v-model:visible="showDrawer"
|
||||
:record="userInfo"
|
||||
:is-edit="isEdit"
|
||||
:department-list-data="departmentListData"
|
||||
:role-list="roleList"
|
||||
@submitted="getUserListData"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DepartmentFormParams, createDepartment } from '@/api/setting';
|
||||
import useDepartmentList from '@/views/setting/composables/useDepartmentList';
|
||||
// import { ref } from 'vue'
|
||||
const onClick = async () => {
|
||||
const params: DepartmentFormParams = {
|
||||
name: '冰一下很开心', // 部门名称
|
||||
parent_id: 34, // 上一级id
|
||||
};
|
||||
import { ref, watch, onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import type { TableColumnData } from '@arco-design/web-vue';
|
||||
import {
|
||||
getUserList,
|
||||
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 {
|
||||
const res = await createDepartment(params);
|
||||
console.log(res, 'res');
|
||||
const params = {
|
||||
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) {
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user