feat: 设置模块用户列表功能90%
This commit is contained in:
@@ -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,78 +0,0 @@
|
||||
import { ref, onMounted } from 'vue';
|
||||
import {
|
||||
getDepartmentList,
|
||||
DepartmentItem,
|
||||
getDepartmentDetail,
|
||||
} from '@/api/setting';
|
||||
|
||||
export default function useDepartmentList(autoFetch = true) {
|
||||
const departmentListData = ref<DepartmentItem[]>([]);
|
||||
const allDepartmentKeys = ref<number[]>([]);
|
||||
function markNonLevel1Departments(list: DepartmentItem[]): DepartmentItem[] {
|
||||
const traverse = (items: DepartmentItem[]) => {
|
||||
return items.map((item) => {
|
||||
const newItem: DepartmentItem = {
|
||||
...item,
|
||||
isShowEditIcon: false,
|
||||
children: [],
|
||||
};
|
||||
|
||||
// 收集 ID
|
||||
allDepartmentKeys.value.push(item.id);
|
||||
|
||||
if (item.children && item.children.length > 0) {
|
||||
newItem.children = traverse(item.children);
|
||||
}
|
||||
|
||||
return newItem;
|
||||
});
|
||||
};
|
||||
|
||||
return traverse(list);
|
||||
}
|
||||
|
||||
const getDepartmentListData = async () => {
|
||||
try {
|
||||
const { code, data } = await getDepartmentList();
|
||||
if (code === 200) {
|
||||
if (data?.departmentList?.length === 0) return;
|
||||
allDepartmentKeys.value = []; // 清空再收集
|
||||
|
||||
const fetchDepartmentList = markNonLevel1Departments(
|
||||
data?.departmentList
|
||||
);
|
||||
departmentListData.value = fetchDepartmentList;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取部门列表失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const getDepartmentDetailData = async (
|
||||
id: number
|
||||
): Promise<DepartmentItem | null> => {
|
||||
try {
|
||||
const { code, data } = await getDepartmentDetail(id);
|
||||
if (code === 200 && data?.department) {
|
||||
return data.department;
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('获取部门详情失败:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
if (autoFetch) {
|
||||
onMounted(() => {
|
||||
getDepartmentListData();
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
departmentListData,
|
||||
allDepartmentKeys,
|
||||
getDepartmentListData,
|
||||
getDepartmentDetailData,
|
||||
};
|
||||
}
|
||||
@@ -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