Files
FastapiAdmin/frontend/src/views/module_example/demo/index.vue
T
zhangtao 0766c8aa7d style: 统一字符串引号为双引号并优化代码格式
将单引号字符串统一改为双引号
调整部分组件代码格式和样式
移除空行和冗余代码
2025-12-03 01:07:45 +08:00

783 lines
24 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 v-show="visible" class="search-container">
<el-form
ref="queryFormRef"
:model="queryFormData"
label-suffix=":"
:inline="true"
@submit.prevent="handleQuery"
>
<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: 170px"
clearable
>
<el-option value="0" label="启用" />
<el-option value="1" label="停用" />
</el-select>
</el-form-item>
<el-form-item v-if="isExpand" prop="created_id" label="创建人">
<UserTableSelect
v-model="queryFormData.created_id"
@confirm-click="handleConfirm"
@clear-click="handleQuery"
/>
</el-form-item>
<el-form-item v-if="isExpand" prop="updated_id" label="更新人">
<UserTableSelect
v-model="queryFormData.updated_id"
@confirm-click="handleConfirm"
@clear-click="handleQuery"
/>
</el-form-item>
<!-- 时间范围,收起状态下隐藏 -->
<el-form-item v-if="isExpand" prop="created_time" label="创建时间">
<DatePicker
v-model="createdDateRange"
@update:model-value="handleCreatedDateRangeChange"
/>
</el-form-item>
<el-form-item v-if="isExpand" prop="updated_time" label="更新时间">
<DatePicker
v-model="updatedDateRange"
@update:model-value="handleUpdatedDateRangeChange"
/>
</el-form-item>
<!-- 查询、重置、展开/收起按钮 -->
<el-form-item>
<el-button
v-hasPerm="['module_example:demo:query']"
type="primary"
icon="search"
@click="handleQuery"
>
查询
</el-button>
<el-button
v-hasPerm="['module_example:demo:query']"
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 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--left">
<el-row :gutter="10">
<el-col :span="1.5">
<el-button
v-hasPerm="['module_example:demo:create']"
type="success"
icon="plus"
@click="handleOpenDialog('create')"
>
新增
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
v-hasPerm="['module_example:demo: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="['module_example:demo: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('0')">
批量启用
</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick('1')">
批量停用
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-col>
</el-row>
</div>
<div class="data-table__toolbar--right">
<el-row :gutter="10">
<el-col :span="1.5">
<el-tooltip content="导入">
<el-button
v-hasPerm="['module_example:demo:import']"
type="success"
icon="upload"
circle
@click="handleOpenImportDialog"
/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-tooltip content="导出">
<el-button
v-hasPerm="['module_example:demo:export']"
type="warning"
icon="download"
circle
@click="handleOpenExportsModal"
/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-tooltip content="搜索显示/隐藏">
<el-button
v-hasPerm="['*:*:*']"
type="info"
icon="search"
circle
@click="visible = !visible"
/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-tooltip content="刷新">
<el-button
v-hasPerm="['module_example:demo:query']"
type="primary"
icon="refresh"
circle
@click="handleRefresh"
/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-popover placement="bottom" trigger="click">
<template #reference>
<el-button type="danger" icon="operation" circle></el-button>
</template>
<el-scrollbar max-height="350px">
<template v-for="column in tableColumns" :key="column.prop">
<el-checkbox v-if="column.prop" v-model="column.show" :label="column.label" />
</template>
</el-scrollbar>
</el-popover>
</el-col>
</el-row>
</div>
</div>
<!-- 表格区域:系统配置列表 -->
<el-table
ref="tableRef"
v-loading="loading"
:data="pageTableData"
highlight-current-row
class="data-table__content"
:height="450"
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"
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
v-if="tableColumns.find((col) => col.prop === 'name')?.show"
label="名称"
prop="name"
min-width="140"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'status')?.show"
label="状态"
prop="status"
min-width="120"
>
<template #default="scope">
<el-tag :type="scope.row.status ? 'success' : 'info'">
{{ scope.row.status ? "启用" : "停用" }}
</el-tag>
</template>
</el-table-column>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'description')?.show"
label="描述"
prop="description"
min-width="140"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'created_time')?.show"
label="创建时间"
prop="created_time"
min-width="180"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'updated_time')?.show"
label="更新时间"
prop="updated_time"
min-width="180"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'created_id')?.show"
label="创建人"
prop="created_id"
min-width="120"
>
<template #default="scope">
<el-tag>{{ scope.row.created_by?.name }}</el-tag>
</template>
</el-table-column>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'operation')?.show"
fixed="right"
label="操作"
align="center"
min-width="180"
>
<template #default="scope">
<el-button
v-hasPerm="['module_example:demo:query']"
type="info"
size="small"
link
icon="document"
@click="handleOpenDialog('detail', scope.row.id)"
>
详情
</el-button>
<el-button
v-hasPerm="['module_example:demo:update']"
type="primary"
size="small"
link
icon="edit"
@click="handleOpenDialog('update', scope.row.id)"
>
编辑
</el-button>
<el-button
v-hasPerm="['module_example:demo: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.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.description }}
</el-descriptions-item>
<el-descriptions-item label="创建人" :span="2">
{{ detailFormData.created_by?.name }}
</el-descriptions-item>
<el-descriptions-item label="更新人" :span="2">
{{ detailFormData.updated_by?.name }}
</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">
{{ detailFormData.created_time }}
</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">
{{ detailFormData.updated_time }}
</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="status">
<el-radio-group v-model="formData.status">
<el-radio value="0">启用</el-radio>
<el-radio value="1">停用</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>
<!-- 导入弹窗 -->
<ImportModal
v-model="importDialogVisible"
:content-config="curdContentConfig"
@upload="handleUpload"
/>
<!-- 导出弹窗 -->
<ExportModal
v-model="exportsDialogVisible"
:content-config="curdContentConfig"
:query-params="queryFormData"
:page-data="pageTableData"
:selection-data="selectionRows"
/>
</div>
</template>
<script setup lang="ts">
defineOptions({
name: "Demo",
inheritAttrs: false,
});
import { ref, reactive, onMounted } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { ResultEnum } from "@/enums/api/result.enum";
import DemoAPI, { DemoTable, DemoForm, DemoPageQuery } from "@/api/module_example/demo";
import ImportModal from "@/components/CURD/ImportModal.vue";
import ExportModal from "@/components/CURD/ExportModal.vue";
import DatePicker from "@/components/DatePicker/index.vue";
import type { IContentConfig } from "@/components/CURD/types";
import { QuestionFilled, ArrowUp, ArrowDown } from "@element-plus/icons-vue";
import { formatToDateTime } from "@/utils/dateUtil";
const visible = ref(true);
const queryFormRef = ref();
const dataFormRef = ref();
const total = ref(0);
const selectIds = ref<number[]>([]);
const selectionRows = ref<DemoTable[]>([]);
const loading = ref(false);
const isExpand = ref(false);
const isExpandable = ref(true);
// 分页表单
const pageTableData = ref<DemoTable[]>([]);
// 表格列配置
const tableColumns = ref([
{ prop: "selection", label: "选择框", show: true },
{ prop: "index", label: "序号", show: true },
{ prop: "name", label: "名称", show: true },
{ prop: "status", label: "状态", show: true },
{ prop: "description", label: "描述", show: true },
{ prop: "created_time", label: "创建时间", show: true },
{ prop: "updated_time", label: "更新时间", show: true },
{ prop: "created_id", label: "创建人", show: true },
{ prop: "updated_id", label: "更新人", show: true },
{ prop: "operation", label: "操作", show: true },
]);
// 仅用于导出字段的列(排除非数据列及嵌套对象列)
const exportColumns = [
{ prop: "name", label: "名称" },
{ prop: "status", label: "状态" },
{ prop: "description", label: "描述" },
{ prop: "created_time", label: "创建时间" },
{ prop: "updated_time", label: "更新时间" },
];
// 导入/导出配置
const curdContentConfig = {
permPrefix: "module_example:demo",
cols: exportColumns as any,
importTemplate: () => DemoAPI.downloadTemplateDemo(),
exportsAction: async (params: any) => {
const query: any = { ...params };
if (typeof query.status === "string") {
query.status = query.status === "true";
}
query.page_no = 1;
query.page_size = 9999;
const all: any[] = [];
while (true) {
const res = await DemoAPI.getDemoList(query);
const items = res.data?.data?.items || [];
const total = res.data?.data?.total || 0;
all.push(...items);
if (all.length >= total || items.length === 0) break;
query.page_no += 1;
}
return all;
},
} as unknown as IContentConfig;
// 详情表单
const detailFormData = ref<DemoTable>({});
// 日期范围临时变量
const createdDateRange = ref<[Date, Date] | []>([]);
// 更新时间范围临时变量
const updatedDateRange = ref<[Date, Date] | []>([]);
// 处理创建时间范围变化
function handleCreatedDateRangeChange(range: [Date, Date]) {
createdDateRange.value = range;
if (range && range.length === 2) {
queryFormData.created_time = [formatToDateTime(range[0]), formatToDateTime(range[1])];
} else {
queryFormData.created_time = undefined;
}
}
// 处理更新时间范围变化
function handleUpdatedDateRangeChange(range: [Date, Date]) {
updatedDateRange.value = range;
if (range && range.length === 2) {
queryFormData.updated_time = [formatToDateTime(range[0]), formatToDateTime(range[1])];
} else {
queryFormData.updated_time = undefined;
}
}
// 分页查询参数
const queryFormData = reactive<DemoPageQuery>({
page_no: 1,
page_size: 10,
name: undefined,
status: undefined,
created_time: undefined,
updated_time: undefined,
created_id: undefined,
updated_id: undefined,
});
// 编辑表单
const formData = reactive<DemoForm>({
id: undefined,
name: "",
status: "0",
description: undefined,
});
// 弹窗状态
const dialogVisible = reactive({
title: "",
visible: false,
type: "create" as "create" | "update" | "detail",
});
// 表单验证规则
const rules = reactive({
name: [{ required: true, message: "请输入名称", trigger: "blur" }],
status: [{ required: true, message: "请选择状态", trigger: "blur" }],
});
// 导入弹窗显示状态
const importDialogVisible = ref(false);
// 导出弹窗显示状态
const exportsDialogVisible = ref(false);
// 打开导入弹窗
function handleOpenImportDialog() {
importDialogVisible.value = true;
}
// 打开导出弹窗
function handleOpenExportsModal() {
exportsDialogVisible.value = true;
}
// 列表刷新
async function handleRefresh() {
await loadingData();
}
// 加载表格数据
async function loadingData() {
loading.value = true;
try {
const response = await DemoAPI.getDemoList(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();
}
// 选择创建人后触发查询
function handleConfirm() {
handleQuery();
}
// 重置查询
async function handleResetQuery() {
queryFormRef.value.resetFields();
queryFormData.page_no = 1;
// 重置日期范围选择器
createdDateRange.value = [];
updatedDateRange.value = [];
queryFormData.created_time = undefined;
queryFormData.updated_time = undefined;
loadingData();
}
// 定义初始表单数据常量
const initialFormData: DemoForm = {
id: undefined,
name: "",
status: "0",
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);
selectionRows.value = selection;
}
// 关闭弹窗
async function handleCloseDialog() {
dialogVisible.visible = false;
resetForm();
}
// 打开弹窗
async function handleOpenDialog(type: "create" | "update" | "detail", id?: number) {
dialogVisible.type = type;
if (id) {
const response = await DemoAPI.getDemoDetail(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;
}
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 DemoAPI.updateDemo(id, { id, ...formData });
dialogVisible.visible = false;
resetForm();
handleCloseDialog();
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
} else {
try {
await DemoAPI.createDemo(formData);
dialogVisible.visible = false;
resetForm();
handleCloseDialog();
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 DemoAPI.deleteDemo(ids);
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
})
.catch(() => {
ElMessageBox.close();
});
}
// 批量启用/停用
async function handleMoreClick(status: string) {
if (selectIds.value.length) {
ElMessageBox.confirm(`确认${status === "0" ? "启用" : "停用"}该项数据?`, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
try {
loading.value = true;
await DemoAPI.batchDemo({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
})
.catch(() => {
ElMessageBox.close();
});
}
}
// 处理上传
const handleUpload = async (formData: FormData) => {
try {
const response = await DemoAPI.importDemo(formData);
if (response.data.code === ResultEnum.SUCCESS) {
ElMessage.success(`${response.data.msg},${response.data.data}`);
importDialogVisible.value = false;
await handleQuery();
}
} catch (error: any) {
console.error(error);
}
};
onMounted(() => {
loadingData();
});
</script>
<style lang="scss" scoped></style>