Files
FastapiAdmin/frontend/src/views/system/dict/components/DataDrawer.vue
T
zhangtao 2918c0aebb refactor(frontend): 统一工具栏按钮布局为el-row/el-col结构
重构多个视图组件中的工具栏按钮布局,使用el-row和el-col组件统一管理按钮间距和排列。调整权限指令v-hasPerm的位置使其更规范。优化表格查询表单的样式和结构。

style(frontend): 规范v-hasPerm指令的使用位置

将v-hasPerm指令统一调整到按钮属性最前面,保持代码风格一致。移除不必要的showSearch变量,简化分页组件逻辑。

fix(gencode): 修正生成代码页面的查询重置功能

修复生成代码页面中重置按钮调用错误方法的问题,将resetQuery改为handleRefresh以保持功能一致。调整表单字段命名规范,统一使用下划线风格。
2025-10-06 00:29:38 +08:00

548 lines
20 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>
<el-drawer v-model="drawerVisible" :title="'【' + props.dictLabel + '】字典数据'" :size="drawerSize">
<!-- 搜索区域 -->
<div class="search-container">
<el-form ref="queryFormRef" :model="queryFormData" :inline="true" label-suffix=":" >
<el-form-item prop="dict_label" label="字典数据标签">
<el-input v-model="queryFormData.dict_label" 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="创建时间">
<DatePicker
v-model="dateRange"
@update:model-value="handleDateRangeChange"
/>
</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-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPerm="['system:dict_data:create']" type="success" icon="plus" @click="handleOpenDialog('create')">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPerm="['system:dict_data:delete']" type="danger" icon="delete" :disabled="selectIds.length === 0" @click="handleDelete(selectIds)">批量删除</el-button>
</el-col>
<el-col :span="1.5">
<el-dropdown v-hasPerm="['system:dict_data:patch']" 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>
</el-col>
</el-row>
</div>
<div class="data-table__toolbar--tools">
<el-row :gutter="10">
<el-col :span="1.5">
<el-tooltip content="导出">
<el-button v-hasPerm="['system:dict_data:export']" type="warning" icon="download" circle @click="handleExport"/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-tooltip content="刷新">
<el-button type="default" icon="refresh" circle @click="handleRefresh" />
</el-tooltip>
</el-col>
</el-row>
</div>
</div>
<!-- 表格区域:系统配置列表 -->
<el-table ref="dataTableRef" v-loading="loading" :data="pageTableData" highlight-current-row class="data-table__content" height="460" border stripe @selection-change="handleSelectionChange">
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
<el-table-column type="selection" min-width="55" align="center" />
<el-table-column type="index" fixed label="序号" min-width="60">
<template #default="scope">
{{ (queryFormData.page_no - 1) * queryFormData.page_size + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column label="标签" prop="dict_label" min-width="150" show-overflow-tooltip />
<el-table-column label="状态" prop="status" min-width="100" show-overflow-tooltip>
<template #default="scope">
<el-tag :type="scope.row.status === true ? 'success' : 'danger'">
{{ scope.row.status ? "启用" : "停用" }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="类型" prop="dict_type" min-width="180" show-overflow-tooltip>
<template #default="scope">
<el-tag type="primary">{{ scope.row.dict_type }}</el-tag>
</template>
</el-table-column>
<el-table-column label="值" prop="dict_value" min-width="100" show-overflow-tooltip />
<el-table-column label="样式属性" prop="css_class" min-width="100" show-overflow-tooltip />
<el-table-column label="列表类样式" prop="list_class" min-width="100" show-overflow-tooltip />
<el-table-column label="排序" prop="dict_sort" min-width="60" />
<el-table-column label="是否默认" prop="is_default" min-width="100">
<template #default="scope">
<el-tag v-if="scope.row.is_default" type="success">是</el-tag>
<el-tag v-else type="danger">否</el-tag>
</template>
</el-table-column>
<el-table-column label="描述" prop="description" min-width="100" show-overflow-tooltip/>
<el-table-column label="创建时间" prop="created_at" min-width="200" sortable />
<el-table-column label="更新时间" prop="updated_at" min-width="200" sortable />
<el-table-column label="创建人" prop="creator" min-width="100">
<template #default="scope">
{{ scope.row.creator?.name }}
</template>
</el-table-column>
<el-table-column fixed="right" label="操作" align="center" min-width="200">
<template #default="scope">
<el-button type="info" size="small" link icon="document" @click="handleOpenDialog('detail', scope.row.id)">详情</el-button>
<el-button v-hasPerm="['system:dict_data:update']" type="primary" size="small" link icon="edit" @click="handleOpenDialog('update', scope.row.id)">编辑</el-button>
<el-button v-hasPerm="['system:dict_data:delete']" type="danger" size="small" link icon="delete" @click="handleDelete([scope.row.id])">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页区域 -->
<template #footer>
<pagination v-model:total="total" v-model:page="queryFormData.page_no" v-model:limit="queryFormData.page_size" @pagination="loadingData" />
</template>
</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.dict_label }}</el-descriptions-item>
<el-descriptions-item label="数据类型" :span="2">{{ detailFormData.dict_type }}</el-descriptions-item>
<el-descriptions-item label="数据值" :span="2">{{ detailFormData.dict_value }}</el-descriptions-item>
<el-descriptions-item label="样式属性" :span="2">{{ detailFormData.css_class }}</el-descriptions-item>
<el-descriptions-item label="列表样式属性" :span="2">{{ detailFormData.list_class }}</el-descriptions-item>
<el-descriptions-item label="是否默认" :span="2">
<el-tag v-if="detailFormData.is_default" type="success">是</el-tag>
<el-tag v-else type="danger">否</el-tag>
</el-descriptions-item>
<el-descriptions-item label="状态" :span="2">
<el-tag v-if="detailFormData.status" type="success">启用</el-tag>
<el-tag v-else type="danger">停用</el-tag>
</el-descriptions-item>
<el-descriptions-item label="排序" :span="2">{{ detailFormData.dict_sort }}</el-descriptions-item>
<el-descriptions-item label="描述" :span="2">{{ detailFormData.description }}</el-descriptions-item>
<el-descriptions-item label="创建人" :span="2">{{ detailFormData.creator?.name }}</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>
</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="dict_type">
<el-input v-model="formData.dict_type" placeholder="请输入数据类型" :maxlength="50" :disabled="true" />
</el-form-item>
<el-form-item label="数据标签" prop="dict_label">
<el-input v-model="formData.dict_label" placeholder="请输入数据标签" :maxlength="50" />
</el-form-item>
<el-form-item label="数据值" prop="dict_value">
<el-input v-model="formData.dict_value" placeholder="请输入数据值" :maxlength="50" />
</el-form-item>
<el-form-item label="样式属性" prop="css_class">
<el-input v-model="formData.css_class" placeholder="请输入样式属性" :maxlength="50" />
</el-form-item>
<el-form-item label="列表类样式" prop="list_class">
<el-select v-model="formData.list_class" placeholder="请选择列表类样式" clearable>
<el-option value="default" label="默认(default)" />
<el-option value="primary" label="主要(primary)" />
<el-option value="success" label="成功(success)" />
<el-option value="warning" label="警告(warning)" />
<el-option value="danger" label="危险(danger)" />
<el-option value="info" label="信息(info)" />
</el-select>
</el-form-item>
<el-form-item label="是否默认" prop="is_default">
<el-radio-group v-model="formData.is_default">
<el-radio :value="true">是</el-radio>
<el-radio :value="false">否</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="排序" prop="dict_sort">
<el-input-number v-model="formData.dict_sort" controls-position="right" :min="1" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-switch v-model="formData.status" inline-prompt active-value="true" inactive-value="false" />
</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>
</el-drawer>
</template>
<script setup lang="ts">
// 添加 props 来接收 dictType
const props = defineProps({
dictType: {
type: String,
required: true
},
dictLabel: {
type: String,
required: true
}
})
import DictAPI, { DictDataTable, DictDataForm, DictDataPageQuery } from "@/api/system/dict";
import { useAppStore } from "@/store/modules/app.store";
import { DeviceEnum } from "@/enums/settings/device.enum";
const appStore = useAppStore();
const drawerSize = computed(() => (appStore.device === DeviceEnum.DESKTOP ? "80%" : "60%"));
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 drawerVisible = ref<boolean>(false);
// 分页表单
const pageTableData = ref<DictDataTable[]>([]);
// 详情表单
const detailFormData = ref<DictDataTable>({});
// 分页查询参数
const queryFormData = reactive<DictDataPageQuery>({
page_no: 1,
page_size: 10,
dict_label: undefined,
dict_type: props.dictType,
status: undefined,
start_time: undefined,
end_time: undefined,
});
// 编辑表单
const formData = reactive<DictDataForm>({
id: undefined,
dict_sort: 1,
dict_label: '',
dict_value: '',
dict_type: '',
css_class: '',
list_class: undefined,
is_default: false,
status: true,
description: ''
})
// 弹窗状态
const dialogVisible = reactive({
title: "",
visible: false,
type: 'create' as 'create' | 'update' | 'detail',
});
// 表单验证规则
const rules = reactive({
dict_label: [{ required: true, message: "请输入字典标签", trigger: "blur" }],
dict_type: [{ required: true, message: "请输入字典类型", trigger: "blur" }],
dict_value: [{ required: true, message: "请输入字典键值", trigger: "blur" }],
status: [{ required: true, message: "请选择状态", trigger: "blur" }],
dict_sort: [{ required: true, message: "请输入排序", trigger: "blur" }],
css_class: [{ required: true, message: "请输入样式类名", trigger: "blur" }],
list_class: [{ required: true, message: "请输入列表类名", trigger: "blur" }],
is_default: [{ required: true, message: "请选择是否默认", trigger: "blur" }],
});
// 日期范围临时变量
const dateRange = ref<[Date, Date] | []>([]);
// 处理日期范围变化
function handleDateRangeChange(range: [Date, Date]) {
dateRange.value = range;
if (range && range.length === 2) {
queryFormData.start_time = range[0].toISOString();
queryFormData.end_time = range[1].toISOString();
} else {
queryFormData.start_time = undefined;
queryFormData.end_time = undefined;
}
}
// 列表刷新
async function handleRefresh () {
await loadingData();
};
// 加载表格数据
async function loadingData() {
loading.value = true;
try {
// 在查询参数中添加 dictType
const response = await DictAPI.getDictDataList(queryFormData);
pageTableData.value = response.data.data.items;
total.value = response.data.data.total;
}
catch (error: any) {
console.error(error);
}
finally {
loading.value = false;
}
}
// 查询(重置页码后获取数据)
async function handleQuery() {
queryFormData.page_no = 1;
loadingData();
}
// 重置查询
async function handleResetQuery() {
queryFormRef.value.resetFields();
queryFormData.page_no = 1;
loadingData();
}
// 定义初始表单数据常量
const initialFormData: DictDataForm = {
id: undefined,
dict_sort: 1,
dict_label: '',
dict_value: '',
dict_type: '',
css_class: '',
list_class: undefined,
is_default: false,
status: true,
description: ''
}
// 重置表单
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) {
dialogVisible.type = type;
if (id) {
const response = await DictAPI.getDictDataDetail(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;
formData.dict_type = props.dictType;
}
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 DictAPI.updateDictData(id, { id, ...formData })
dialogVisible.visible = false;
resetForm();
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
} else {
try {
await DictAPI.createDictData(formData)
dialogVisible.visible = false;
resetForm();
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}
}
});
}
// 删除、批量删除
async function handleDelete(ids: number[]) {
ElMessageBox.confirm("确认删除该项数据?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await DictAPI.deleteDictData(ids);
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessageBox.close();
});
}
// 导出
async function handleExport() {
ElMessageBox.confirm('是否确认导出当前系统配置?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
let downloadUrl = '';
try {
loading.value = true;
const response = await DictAPI.exportDictData(queryFormData);
const fileData = response.data;
const fileName = decodeURI(response.headers["content-disposition"].split(";")[1].split("=")[1]);
const fileType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
const blob = new Blob([fileData], { type: fileType });
downloadUrl = window.URL.createObjectURL(blob);
const downloadLink = document.createElement("a");
downloadLink.href = downloadUrl;
downloadLink.download = fileName;
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
} catch (error: any) {
// 错误信息已经在响应拦截器中处理并显示
console.error('导出失败:', error);
} finally {
if (downloadUrl) {
window.URL.revokeObjectURL(downloadUrl);
}
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 DictAPI.batchAvailableDictData({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessageBox.close();
});
}
}
onMounted(() => {
loadingData();
});
</script>
<style lang="scss" scoped></style>