refactor: 用户管理重构完成
This commit is contained in:
@@ -1,174 +0,0 @@
|
|||||||
<template>
|
|
||||||
<wd-picker
|
|
||||||
v-model="selectedValues"
|
|
||||||
:columns="pickerColumns"
|
|
||||||
:display-format="displayFormat"
|
|
||||||
:column-change="handleColumnChange"
|
|
||||||
:label="label"
|
|
||||||
:placeholder="pickerColumns.length ? '请选择' : '加载中...'"
|
|
||||||
:required="required"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { PickerViewInstance } from "wot-design-uni/components/wd-picker-view/types";
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
modelValue: {
|
|
||||||
type: [Number, String],
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
required: true,
|
|
||||||
type: Array as () => OptionType[],
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
type: String,
|
|
||||||
default: "",
|
|
||||||
},
|
|
||||||
required: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const emits = defineEmits(["update:modelValue"]);
|
|
||||||
|
|
||||||
// 定义响应式变量
|
|
||||||
const selectedValues = ref<number[] | string[]>([]);
|
|
||||||
const pickerColumns = ref<Array<Array<{ label: string; value: string | number }>>>([]);
|
|
||||||
|
|
||||||
// 监听 modelValue 的变化,更新 selectedValues
|
|
||||||
watch(
|
|
||||||
() => props.modelValue,
|
|
||||||
(val) => {
|
|
||||||
selectedValues.value = val ? findTreePath(val) : [];
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据节点值查找路径
|
|
||||||
* 示例数据: [{"value":"1","label":"公司","children":[{"value":"2","label":"研发部"}]}]
|
|
||||||
* 查找部门ID为2的路径,返回结果:[1, 2]
|
|
||||||
*/
|
|
||||||
const findTreePath = (value: number | string): number[] | string[] => {
|
|
||||||
const numberPath: number[] = [];
|
|
||||||
const stringPath: string[] = [];
|
|
||||||
const list = props.data;
|
|
||||||
|
|
||||||
const find = (value: number | string, list: OptionType[]): boolean => {
|
|
||||||
for (const item of list) {
|
|
||||||
if (item.value === value) {
|
|
||||||
typeof value === "number" ? numberPath.push(value) : stringPath.push(value);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (item.children?.length) {
|
|
||||||
typeof item.value === "number" ? numberPath.push(item.value) : stringPath.push(item.value);
|
|
||||||
if (find(value, item.children)) return true;
|
|
||||||
typeof item.value === "number" ? numberPath.pop() : stringPath.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
find(value, list);
|
|
||||||
return typeof value === "number" ? numberPath : stringPath;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将树形数据转换为 Picker 所需的 columns 格式
|
|
||||||
*
|
|
||||||
* @param treeData 树形数据
|
|
||||||
* @returns Picker 所需的 columns 格式
|
|
||||||
*/
|
|
||||||
const transformTreeToColumns = (
|
|
||||||
treeData: OptionType[]
|
|
||||||
): Array<Array<{ label: string; value: string | number }>> => {
|
|
||||||
const columns: Array<Array<{ label: string; value: string | number }>> = [];
|
|
||||||
|
|
||||||
for (let depth = 0; depth <= selectedValues.value.length; depth++) {
|
|
||||||
const currentColumn = treeData.map((node) => ({ label: node.label, value: node.value }));
|
|
||||||
if (!currentColumn.length) break;
|
|
||||||
|
|
||||||
const selectedId = selectedValues.value[depth];
|
|
||||||
if (!currentColumn.some((item) => item.value === selectedId)) {
|
|
||||||
selectedValues.value[depth] = currentColumn[0]?.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
columns.push(currentColumn);
|
|
||||||
const selectedNode = treeData.find((node) => node.value == selectedValues.value[depth]);
|
|
||||||
treeData = selectedNode?.children || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return columns;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 监听 data 的变化,更新 pickerColumns
|
|
||||||
watch(
|
|
||||||
() => props.data,
|
|
||||||
(val) => {
|
|
||||||
console.log("监听 data 的变化", val);
|
|
||||||
pickerColumns.value = transformTreeToColumns(val);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
immediate: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理列的变化,动态更新后续列的数据
|
|
||||||
*/
|
|
||||||
function handleColumnChange(
|
|
||||||
pickerView: PickerViewInstance,
|
|
||||||
value: Record<string, any> | Record<string, any>[],
|
|
||||||
columnIndex: number,
|
|
||||||
resolve: () => void
|
|
||||||
) {
|
|
||||||
const selectedValue = selectedValues.value[selectedValues.value.length - 1] || undefined;
|
|
||||||
emits("update:modelValue", selectedValue);
|
|
||||||
|
|
||||||
const item = Array.isArray(value) ? value[columnIndex] : value.value;
|
|
||||||
updatePickerColumns(pickerView, item.value, columnIndex);
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 动态更新所有后续列的数据
|
|
||||||
*/
|
|
||||||
function updatePickerColumns(
|
|
||||||
pickerView: PickerViewInstance,
|
|
||||||
parentId: string | number,
|
|
||||||
columnIndex: number
|
|
||||||
) {
|
|
||||||
const nextColumnIndex = columnIndex + 1;
|
|
||||||
const children = findChildren(parentId, props.data);
|
|
||||||
|
|
||||||
if (children.length > 0 && nextColumnIndex < 3) {
|
|
||||||
pickerView.setColumnData(nextColumnIndex, children);
|
|
||||||
updatePickerColumns(pickerView, children[0].value, nextColumnIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据节点value查找其子节点数据
|
|
||||||
*/
|
|
||||||
function findChildren(
|
|
||||||
parentId: string | number,
|
|
||||||
list: Record<string, any>[]
|
|
||||||
): Record<string, any>[] {
|
|
||||||
for (const item of list) {
|
|
||||||
if (item.value === parentId && item.children) {
|
|
||||||
return item.children;
|
|
||||||
}
|
|
||||||
if (item.children) {
|
|
||||||
const children = findChildren(parentId, item.children);
|
|
||||||
if (children.length) return children;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化显示选中项(显示最后一个子节点的 label)
|
|
||||||
const displayFormat = (items: any) => {
|
|
||||||
return items.length > 0 ? items[items.length - 1].label : "";
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
<wd-card
|
<wd-card
|
||||||
v-for="item in pageData"
|
v-for="item in pageData"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
custom-class="list-card"
|
custom-class="item-card"
|
||||||
@click="openUserDialog(item.id)"
|
@click="openUserDialog(item.id)"
|
||||||
>
|
>
|
||||||
<!-- 主信息行 -->
|
<!-- 主信息行 -->
|
||||||
@@ -127,13 +127,21 @@
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<wd-input v-model="formData.nickname" label="昵称" required />
|
<wd-input v-model="formData.nickname" label="昵称" required />
|
||||||
|
<wd-col-picker
|
||||||
|
v-model="deptSelected"
|
||||||
|
label="部门"
|
||||||
|
:columns="deptColumns"
|
||||||
|
:column-change="handleDeptColumnChange"
|
||||||
|
required
|
||||||
|
:display-format="displayDeptFormat"
|
||||||
|
@confirm="handleDeptConfirm"
|
||||||
|
/>
|
||||||
<wd-select-picker
|
<wd-select-picker
|
||||||
v-model="formData.roleIds"
|
v-model="formData.roleIds"
|
||||||
label="角色"
|
label="角色"
|
||||||
:columns="roleOptions"
|
:columns="roleOptions"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<CuPicker v-model="formData.deptId" v-model:data="deptOptions" label="部门" required />
|
|
||||||
<wd-input v-model="formData.mobile" label="手机号" />
|
<wd-input v-model="formData.mobile" label="手机号" />
|
||||||
<wd-input v-model="formData.email" label="邮箱" />
|
<wd-input v-model="formData.email" label="邮箱" />
|
||||||
<wd-cell title="状态">
|
<wd-cell title="状态">
|
||||||
@@ -151,12 +159,15 @@
|
|||||||
<!-- 浮动新增按钮 -->
|
<!-- 浮动新增按钮 -->
|
||||||
<wd-fab
|
<wd-fab
|
||||||
v-if="hasPermission('sys:user:create') && !dialog.visible"
|
v-if="hasPermission('sys:user:create') && !dialog.visible"
|
||||||
type="primary"
|
|
||||||
position="right-bottom"
|
position="right-bottom"
|
||||||
:expandable="false"
|
:expandable="false"
|
||||||
custom-class="fab--small"
|
>
|
||||||
@click="openUserDialog()"
|
<template #trigger>
|
||||||
/>
|
<wd-button custom-class="fab-btn" type="primary" round @click="openUserDialog()">
|
||||||
|
<wd-icon name="add" />
|
||||||
|
</wd-button>
|
||||||
|
</template>
|
||||||
|
</wd-fab>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -164,13 +175,13 @@
|
|||||||
import { onLoad, onReachBottom } from "@dcloudio/uni-app";
|
import { onLoad, onReachBottom } from "@dcloudio/uni-app";
|
||||||
import { LoadMoreState } from "wot-design-uni/components/wd-loadmore/types";
|
import { LoadMoreState } from "wot-design-uni/components/wd-loadmore/types";
|
||||||
import { FormRules } from "wot-design-uni/components/wd-form/types";
|
import { FormRules } from "wot-design-uni/components/wd-form/types";
|
||||||
import { useMessage, useQueue } from "wot-design-uni";
|
import { useQueue, useToast } from "wot-design-uni";
|
||||||
import UserAPI, { type UserPageQuery, UserPageVO, UserForm } from "@/api/user";
|
import UserAPI, { type UserPageQuery, UserPageVO, UserForm } from "@/api/user";
|
||||||
import RoleAPI from "@/api/role";
|
import RoleAPI from "@/api/role";
|
||||||
import DeptAPI from "@/api/dept";
|
import DeptAPI from "@/api/dept";
|
||||||
import { hasPermission } from "@/utils/permission";
|
import { hasPermission } from "@/utils/permission";
|
||||||
|
|
||||||
const message = useMessage();
|
const toast = useToast();
|
||||||
const { closeOutside } = useQueue();
|
const { closeOutside } = useQueue();
|
||||||
const loadMoreState = ref<LoadMoreState>("loading");
|
const loadMoreState = ref<LoadMoreState>("loading");
|
||||||
const formRef = ref();
|
const formRef = ref();
|
||||||
@@ -203,6 +214,47 @@ const formData = reactive<UserForm>({ ...initialFormData });
|
|||||||
const roleOptions = ref<Record<string, any>[]>([]);
|
const roleOptions = ref<Record<string, any>[]>([]);
|
||||||
const deptOptions = ref<OptionType[]>([]);
|
const deptOptions = ref<OptionType[]>([]);
|
||||||
|
|
||||||
|
// 部门多列选择器数据
|
||||||
|
const deptSelected = ref<(string | number)[]>([]);
|
||||||
|
const deptColumns = ref<Record<string, any>[]>([]);
|
||||||
|
|
||||||
|
// 格式化部门展示
|
||||||
|
const displayDeptFormat = (selectedItems: Record<string, any>[]) => {
|
||||||
|
return selectedItems.map((item) => item.label).join("/");
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查找部门在树中的完整路径
|
||||||
|
function findDeptPath(data: any[], targetId: string, path: string[] = []): string[] | null {
|
||||||
|
for (const item of data) {
|
||||||
|
const currentPath = [...path, item.value];
|
||||||
|
if (item.value === targetId) {
|
||||||
|
return currentPath;
|
||||||
|
}
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
const found = findDeptPath(item.children, targetId, currentPath);
|
||||||
|
if (found) return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 部门列变化(动态加载子部门)
|
||||||
|
const handleDeptColumnChange = ({ selectedItem, resolve, finish }: any) => {
|
||||||
|
const children = selectedItem.children;
|
||||||
|
if (children && children.length > 0) {
|
||||||
|
resolve(children);
|
||||||
|
} else {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 部门确认选择
|
||||||
|
const handleDeptConfirm = ({ value, selectedItems }: any) => {
|
||||||
|
deptSelected.value = value;
|
||||||
|
// 取最后一个选中的部门ID
|
||||||
|
formData.deptId = value[value.length - 1];
|
||||||
|
};
|
||||||
|
|
||||||
const rules: FormRules = {
|
const rules: FormRules = {
|
||||||
username: [{ required: true, message: "请输入用户名" }],
|
username: [{ required: true, message: "请输入用户名" }],
|
||||||
nickname: [{ required: true, message: "请输入昵称" }],
|
nickname: [{ required: true, message: "请输入昵称" }],
|
||||||
@@ -244,7 +296,7 @@ function applyUserFilter() {
|
|||||||
// 重置筛选并刷新
|
// 重置筛选并刷新
|
||||||
function resetUserFilter() {
|
function resetUserFilter() {
|
||||||
queryParams.keywords = "";
|
queryParams.keywords = "";
|
||||||
queryParams.createTime = undefined;
|
delete queryParams.createTime;
|
||||||
queryParams.field = "";
|
queryParams.field = "";
|
||||||
queryParams.direction = "";
|
queryParams.direction = "";
|
||||||
sortValue.value = 0;
|
sortValue.value = 0;
|
||||||
@@ -272,13 +324,41 @@ function fetchUserList() {
|
|||||||
async function openUserDialog(id?: number) {
|
async function openUserDialog(id?: number) {
|
||||||
formRef.value?.reset();
|
formRef.value?.reset();
|
||||||
Object.assign(formData, initialFormData);
|
Object.assign(formData, initialFormData);
|
||||||
|
deptSelected.value = [];
|
||||||
dialog.visible = true;
|
dialog.visible = true;
|
||||||
roleOptions.value = await RoleAPI.getOptions();
|
roleOptions.value = await RoleAPI.getOptions();
|
||||||
deptOptions.value = await DeptAPI.getOptions();
|
const deptData = await DeptAPI.getOptions();
|
||||||
|
deptOptions.value = deptData;
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
formData.id = id;
|
formData.id = id;
|
||||||
const data = await UserAPI.getFormData(id);
|
const data = await UserAPI.getFormData(id);
|
||||||
Object.assign(formData, data, { id });
|
Object.assign(formData, data, { id });
|
||||||
|
// 编辑时回显部门选择(需要完整路径和预加载所有层级数据)
|
||||||
|
if (data.deptId) {
|
||||||
|
const path = findDeptPath(deptData, String(data.deptId));
|
||||||
|
if (path) {
|
||||||
|
deptSelected.value = path;
|
||||||
|
// 预加载所有层级的 columns
|
||||||
|
const columns: any[] = [deptData];
|
||||||
|
let currentLevel = deptData;
|
||||||
|
for (let i = 0; i < path.length - 1; i++) {
|
||||||
|
const found = currentLevel.find((item: any) => item.value === path[i]);
|
||||||
|
if (found && found.children) {
|
||||||
|
columns.push(found.children);
|
||||||
|
currentLevel = found.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deptColumns.value = columns;
|
||||||
|
} else {
|
||||||
|
deptSelected.value = [String(data.deptId)];
|
||||||
|
deptColumns.value = [deptData];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
deptColumns.value = [deptData];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
deptColumns.value = [deptData];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,7 +370,7 @@ function submitUserForm() {
|
|||||||
const action = formData.id ? UserAPI.update(formData.id, formData) : UserAPI.add(formData);
|
const action = formData.id ? UserAPI.update(formData.id, formData) : UserAPI.add(formData);
|
||||||
action
|
action
|
||||||
.then(() => {
|
.then(() => {
|
||||||
message.show(formData.id ? "修改成功" : "添加成功");
|
toast.success("操作成功");
|
||||||
closeUserDialog();
|
closeUserDialog();
|
||||||
loadUserList();
|
loadUserList();
|
||||||
})
|
})
|
||||||
@@ -328,14 +408,14 @@ function showUserActions(item: UserPageVO) {
|
|||||||
});
|
});
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
await UserAPI.deleteByIds(String(item.id));
|
await UserAPI.deleteByIds(String(item.id));
|
||||||
message.show("删除成功");
|
toast.success("删除成功");
|
||||||
loadUserList();
|
loadUserList();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actions.length === 0) {
|
if (actions.length === 0) {
|
||||||
message.show("暂无操作权限");
|
toast.warning("暂无操作权限");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,7 +436,9 @@ onReachBottom(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onLoad(() => loadUserList());
|
onLoad(() => {
|
||||||
|
loadUserList();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@@ -371,5 +453,3 @@ export default { options: { styleIsolation: "shared" } };
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</route>
|
</route>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
|
||||||
|
|||||||
@@ -75,12 +75,9 @@ body,
|
|||||||
// 列表卡片
|
// 列表卡片
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
.list-card {
|
.item-card.wd-card {
|
||||||
margin-bottom: 12rpx;
|
margin: 0 !important;
|
||||||
|
margin-bottom: 12rpx !important;
|
||||||
:deep(.wd-card) {
|
|
||||||
margin: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
@@ -92,7 +89,7 @@ body,
|
|||||||
gap: 16rpx;
|
gap: 16rpx;
|
||||||
margin-top: 24rpx;
|
margin-top: 24rpx;
|
||||||
|
|
||||||
:deep(.wd-button) {
|
.wd-button {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,16 +98,11 @@ body,
|
|||||||
// 浮动按钮
|
// 浮动按钮
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
.fab--small {
|
.wd-button.fab-btn {
|
||||||
|
box-sizing: border-box !important;
|
||||||
width: 80rpx !important;
|
width: 80rpx !important;
|
||||||
|
min-width: 80rpx !important;
|
||||||
height: 80rpx !important;
|
height: 80rpx !important;
|
||||||
|
padding: 0 !important;
|
||||||
:deep(.wd-fab__trigger) {
|
border-radius: 40rpx !important;
|
||||||
width: 80rpx !important;
|
|
||||||
height: 80rpx !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.wd-fab__icon) {
|
|
||||||
font-size: 36rpx !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user