Files
FastapiAdmin/frontend/src/views/system/dept/index.vue
T
zhangtao 0de4ff2905 refactor(system): 改进表单重置逻辑,使用初始数据常量
统一各系统模块的表单重置逻辑,通过定义初始表单数据常量并使用Object.assign完全重置表单状态,替代之前仅重置id字段的方式
2025-07-31 00:56:45 +08:00

433 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 部门配置 -->
<template>
<div class="app-container">
<!-- 搜索区域 -->
<div class="search-container">
<el-form ref="queryFormRef" :model="queryFormData" :inline="true" label-suffix=":" >
<el-form-item prop="name" label="部门名称">
<el-input v-model="queryFormData.name" placeholder="请输入部门名称" clearable />
</el-form-item>
<el-form-item prop="status" label="状态">
<el-select v-model="queryFormData.status" placeholder="请选择状态" style="width: 167.5px" clearable>
<el-option value="true" label="启用" />
<el-option value="false" label="停用" />
</el-select>
</el-form-item>
<!-- 时间范围收起状态下隐藏 -->
<el-form-item v-if="isExpand" prop="start_time" label="创建时间">
<el-date-picker v-model="queryFormData.start_time" type="daterange" value-format="yyyy-MM-dd" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" />
</el-form-item>
<!-- 查询重置展开/收起按钮 -->
<el-form-item class="search-buttons">
<el-button type="primary" icon="search" @click="handleQuery">查询</el-button>
<el-button icon="refresh" @click="handleResetQuery">重置</el-button>
<!-- 展开/收起 -->
<template v-if="isExpandable">
<el-link class="ml-3" type="primary" underline="never" @click="isExpand = !isExpand">
{{ isExpand ? "收起" : "展开" }}
<el-icon>
<template v-if="isExpand">
<ArrowUp />
</template>
<template v-else>
<ArrowDown />
</template>
</el-icon>
</el-link>
</template>
</el-form-item>
</el-form>
</div>
<!-- 内容区域 -->
<el-card shadow="hover" class="data-table">
<template #header>
<div class="card-header">
<span>
<el-tooltip content="部门管理维护部门信息。">
<QuestionFilled class="w-4 h-4 mx-1" />
</el-tooltip>
部门列表
</span>
</div>
</template>
<!-- 功能区域 -->
<div class="data-table__toolbar">
<div class="data-table__toolbar--actions">
<el-button type="success" icon="plus" @click="handleOpenDialog('create')">新增</el-button>
<el-button type="danger" icon="delete" :disabled="selectIds.length === 0" @click="handleDelete(selectIds)">批量删除</el-button>
<el-dropdown trigger="click">
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">更多</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">批量启用</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">批量停用</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<div class="data-table__toolbar--tools">
<el-tooltip content="刷新">
<el-button type="primary" icon="refresh" circle @click="handleRefresh" />
</el-tooltip>
<el-tooltip content="列表筛选">
<el-dropdown trigger="click">
<el-button type="default" icon="operation" circle />
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="column in tableColumns" :key="column.prop" :command="column">
<el-checkbox v-model="column.show">{{ column.label }}</el-checkbox>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-tooltip>
</div>
</div>
<!-- 表格区域系统配置列表 -->
<el-table ref="dataTableRef" v-loading="loading" row-key="id" :data="pageTableData" :tree-props="{children: 'children',hasChildren: 'hasChildren'}" highlight-current-row class="data-table__content" height="540" border stripe @selection-change="handleSelectionChange">
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" type="selection" min-width="55" align="center" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'index')?.show" type="index" fixed label="序号" min-width="60" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'name')?.show" key="name" label="部门名称" prop="name" min-width="120" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'status')?.show" key="status" label="状态" prop="status" min-width="60">
<template #default="scope">
<el-tag :type="scope.row.status === true ? 'success' : 'danger'">
{{ scope.row.status ? "启用" : "停用" }}
</el-tag>
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'order')?.show" key="order" label="排序" prop="order" min-width="60" show-overflow-tooltip />
<el-table-column v-if="tableColumns.find(col => col.prop === 'description')?.show" key="description" label="描述" prop="description" min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'created_at')?.show" key="created_at" label="创建时间" prop="created_at" min-width="120" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'updated_at')?.show" key="updated_at" label="更新时间" prop="updated_at" min-width="120" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'operation')?.show" fixed="right" label="操作" align="center" min-width="200">
<template #default="scope">
<el-button type="success" size="small" link icon="plus" @click="handleOpenDialog('create', undefined, scope.row.id)">新增</el-button>
<el-button type="info" size="small" link icon="document" @click="handleOpenDialog('detail', scope.row.id)">详情</el-button>
<el-button type="primary" size="small" link icon="edit" @click="handleOpenDialog('update', scope.row.id)">编辑</el-button>
<el-button type="danger" size="small" link icon="delete" @click="handleDelete([scope.row.id])">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 弹窗区域 -->
<el-dialog v-model="dialogVisible.visible" :title="dialogVisible.title" @close="handleCloseDialog">
<!-- 详情 -->
<template v-if="dialogVisible.type === 'detail'">
<el-descriptions :column="4" border>
<el-descriptions-item label="部门名称" :span="2">{{ detailFormData.name }}</el-descriptions-item>
<el-descriptions-item label="上级部门" :span="2">{{ detailFormData.parent_name }}</el-descriptions-item>
<el-descriptions-item label="状态" :span="2">
<el-tag :type="detailFormData.status ? 'success' : 'danger'">
{{ detailFormData.status ? '启用' : '停用' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="排序" :span="2">{{ detailFormData.order }}</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">{{ detailFormData.created_at }}</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">{{ detailFormData.updated_at }}</el-descriptions-item>
<el-descriptions-item label="描述" :span="4">{{ detailFormData.description }}</el-descriptions-item>
</el-descriptions>
</template>
<!-- 新增编辑表单 -->
<template v-else>
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-suffix=":" label-width="auto" label-position="right">
<el-form-item label="部门名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入部门名称" :maxlength="50" />
</el-form-item>
<el-form-item label="上级部门" prop="parent_id">
<el-tree-select v-model="formData.parent_id" placeholder="请选择上级部门" :data="deptOptions" filterable check-strictly :render-after-expand="false" />
</el-form-item>
<el-form-item label="排序" prop="order">
<el-input-number v-model="formData.order" controls-position="right" :min="1" :max="999" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio :value="true">启用</el-radio>
<el-radio :value="false">停用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="formData.description" :rows="4" :maxlength="100" show-word-limit type="textarea" placeholder="请输入描述" />
</el-form-item>
</el-form>
</template>
<template #footer>
<div class="dialog-footer">
<!-- 详情弹窗不需要确定按钮的提交逻辑 -->
<el-button @click="handleCloseDialog">取消</el-button>
<el-button v-if="dialogVisible.type !== 'detail'" type="primary" @click="handleSubmit">确定</el-button>
<el-button v-else type="primary" @click="handleCloseDialog">确定</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
defineOptions({
name: "Dept",
inheritAttrs: false,
});
import DeptAPI, { DeptTable, DeptForm, DeptPageQuery } from "@/api/system/dept";
import { ElMessage, ElMessageBox } from "element-plus";
import { listToTree, formatTree } from "@/utils/common";
const queryFormRef = ref();
const dataFormRef = ref();
const total = ref(0);
const selectIds = ref<number[]>([]);
const loading = ref(false);
const isExpand = ref(false);
const isExpandable = ref(true);
// 分页表单
const pageTableData = ref<DeptTable[]>([]);
// 顶级菜单下拉选项
const deptOptions = ref<OptionType[]>([]);
// 表格列配置
const tableColumns = ref([
{ prop: 'selection', label: '选择框', show: true },
{ prop: 'index', label: '序号', show: true },
{ prop: 'name', label: '部门名称', show: true },
{ prop: 'order', label: '排序', show: true },
{ prop: 'status', label: '状态', show: true },
{ prop: 'description', label: '描述', show: true },
{ prop: 'created_at', label: '创建时间', show: true },
{ prop: 'updated_at', label: '更新时间', show: true },
{ prop: 'operation', label: '操作', show: true }
])
// 详情表单
const detailFormData = ref<DeptTable>({});
// 分页查询参数
const queryFormData = reactive<DeptPageQuery>({
name: undefined,
status: undefined,
start_time: undefined,
end_time: undefined,
});
// 编辑表单
const formData = reactive<DeptForm>({
id: undefined,
name: undefined,
order: 1,
parent_id: undefined,
status: true,
description: undefined,
})
// 弹窗状态
const dialogVisible = reactive({
title: "",
visible: false,
type: 'create' as 'create' | 'update' | 'detail',
});
// 表单验证规则
const rules = reactive({
name: [{ required: true, message: "请输入部门名称", trigger: "blur" }],
order: [{ required: true, message: "请输入排序", trigger: "blur" }],
status: [{ required: true, message: "请选择状态", trigger: "blur" }],
});
// 列表刷新
async function handleRefresh () {
loading.value = true;
try {
const response = await DeptAPI.getDeptList(queryFormData);
const treeData = listToTree(response.data.data.items);
pageTableData.value = treeData;
total.value = response.data.data.total;
} catch (error: any) {
ElMessage.error(error.message);
} finally {
loading.value = false;
}
};
// 加载表格数据
async function loadingData() {
loading.value = true;
try {
const response = await DeptAPI.getDeptList(queryFormData);
const treeData = listToTree(response.data.data.items);
pageTableData.value = treeData;
total.value = response.data.data.total;
// 加载部门选项
deptOptions.value = formatTree(treeData);
}
catch (error: any) {
ElMessage.error(error.message);
}
finally {
loading.value = false;
}
}
// 查询(重置页码后获取数据)
async function handleQuery() {
loadingData();
}
// 重置查询
async function handleResetQuery() {
queryFormRef.value.resetFields();
loadingData();
}
// 定义初始表单数据常量
const initialFormData: DeptForm = {
id: undefined,
name: undefined,
order: 1,
parent_id: undefined,
status: true,
description: undefined,
}
// 重置表单
async function resetForm() {
if (dataFormRef.value) {
dataFormRef.value.resetFields();
dataFormRef.value.clearValidate();
}
// 完全重置 formData 为初始状态
Object.assign(formData, initialFormData);
}
// 行复选框选中项变化
async function handleSelectionChange(selection: any) {
selectIds.value = selection.map((item: any) => item.id);
}
// 关闭弹窗
async function handleCloseDialog() {
dialogVisible.visible = false;
resetForm();
}
// 打开系统配置弹窗
async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: number, parentId?: number) {
dialogVisible.type = type;
if (id) {
const response = await DeptAPI.getDeptDetail(id);
if (type === 'detail') {
dialogVisible.title = "部门详情";
Object.assign(detailFormData.value, response.data.data);
} else if (type === 'update') {
dialogVisible.title = "修改部门";
Object.assign(formData, response.data.data);
}
} else {
dialogVisible.title = "新增部门";
formData.id = undefined;
// 设置父级部门
if (parentId) {
formData.parent_id = parentId;
}
}
dialogVisible.visible = true;
}
// 新增、编辑弹窗处理
async function handleSubmit() {
// 表单校验
dataFormRef.value.validate(async (valid: any) => {
if (valid) {
loading.value = true;
// 根据弹窗传入的参数(deatil\create\update)判断走什么逻辑
const id = formData.id;
if (id) {
try {
await DeptAPI.updateDept({ id, ...formData })
dialogVisible.visible = false;
resetForm();
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
} finally {
loading.value = false;
}
} else {
try {
await DeptAPI.createDept(formData)
dialogVisible.visible = false;
resetForm();
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
} finally {
loading.value = false;
}
}
}
});
}
// 删除、批量删除
async function handleDelete(ids: number[]) {
ElMessageBox.confirm("确认删除该项数据?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await DeptAPI.deleteDept(ids);
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessageBox.close();
});
}
// 批量启用/停用
async function handleMoreClick(status: boolean) {
if (selectIds.value.length) {
ElMessageBox.confirm(`确认${status ? '启用' : '停用'}该项数据?`, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await DeptAPI.batchAvailableDept({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
ElMessage.error(error.message);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessageBox.close();
});
}
}
onMounted(() => {
loadingData();
});
</script>
<style lang="scss" scoped></style>