mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-27 14:52:56 +00:00
refactor: 大规模代码整理与功能优化
1. 重构后端API路由、CRUD与模块结构,整合日志管理,移除废弃demo代码 2. 优化前端组件类型定义、样式与路由配置,修复权限判断逻辑 3. 调整默认排序规则、滚动条样式与工具类函数,更新依赖与配置文件 4. 修复多处类型不匹配与默认值问题,完善表单与菜单验证逻辑
This commit is contained in:
@@ -76,8 +76,8 @@
|
||||
:items="demoDetailItems"
|
||||
max-height="70vh"
|
||||
>
|
||||
<template #i="{ row }">
|
||||
<FaJsonPretty v-if="row?.i != null" :value="row?.i" height="140px" />
|
||||
<template #json_val="{ row }">
|
||||
<FaJsonPretty v-if="row?.json_val != null" :value="row?.json_val" height="140px" />
|
||||
</template>
|
||||
</FaDescriptions>
|
||||
</template>
|
||||
@@ -99,7 +99,7 @@
|
||||
:show-submit="false"
|
||||
class="crud-dialog-art-form"
|
||||
>
|
||||
<template #i>
|
||||
<template #json_val>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div
|
||||
v-for="(item, index) in metadataList"
|
||||
@@ -205,8 +205,8 @@ const showSearchBar = ref(true);
|
||||
const searchBarRef = ref<{ validate: () => Promise<boolean> } | null>(null);
|
||||
const searchBarRules: Record<string, unknown> = {};
|
||||
const statusOptions = ref([
|
||||
{ label: "启用", value: "0" },
|
||||
{ label: "停用", value: "1" },
|
||||
{ label: "启用", value: 0 },
|
||||
{ label: "停用", value: 1 },
|
||||
]);
|
||||
/** 名称、状态;创建人/更新人/时间由 FaSearchBarWithAudit 追加 */
|
||||
const demoBusinessSearchItems = computed(() => [
|
||||
@@ -269,34 +269,36 @@ const {
|
||||
label: "状态",
|
||||
width: 88,
|
||||
formatter: (row: DemoTable) => {
|
||||
const ok = row.status === "0";
|
||||
const ok = row.status === 0;
|
||||
const cfg = ok
|
||||
? { type: "success" as const, text: "启用" }
|
||||
: { type: "info" as const, text: "停用" };
|
||||
return h(ElTag, { type: cfg.type }, () => cfg.text);
|
||||
},
|
||||
},
|
||||
{ prop: "a", label: "整数", minWidth: 88, showOverflowTooltip: true },
|
||||
{ prop: "b", label: "大整数", minWidth: 100, showOverflowTooltip: true },
|
||||
{ prop: "c", label: "浮点数", minWidth: 88, showOverflowTooltip: true },
|
||||
{ prop: "int_val", label: "整数", minWidth: 88, showOverflowTooltip: true },
|
||||
{ prop: "bigint_val", label: "大整数", minWidth: 100, showOverflowTooltip: true },
|
||||
{ prop: "float_val", label: "浮点数", minWidth: 88, showOverflowTooltip: true },
|
||||
{
|
||||
prop: "d",
|
||||
prop: "bool_val",
|
||||
label: "布尔",
|
||||
width: 80,
|
||||
formatter: (row: DemoTable) =>
|
||||
h(ElTag, { type: row.d ? "success" : "danger" }, () => (row.d ? "是" : "否")),
|
||||
h(ElTag, { type: row.bool_val ? "success" : "danger" }, () =>
|
||||
row.bool_val ? "是" : "否"
|
||||
),
|
||||
},
|
||||
{ prop: "e", label: "日期", minWidth: 112, showOverflowTooltip: true },
|
||||
{ prop: "f", label: "时间", minWidth: 96, showOverflowTooltip: true },
|
||||
{ prop: "g", label: "日期时间", minWidth: 168, showOverflowTooltip: true },
|
||||
{ prop: "h", label: "长文本", minWidth: 120, showOverflowTooltip: true },
|
||||
{ prop: "date_val", label: "日期", minWidth: 112, showOverflowTooltip: true },
|
||||
{ prop: "time_val", label: "时间", minWidth: 96, showOverflowTooltip: true },
|
||||
{ prop: "datetime_val", label: "日期时间", minWidth: 168, showOverflowTooltip: true },
|
||||
{ prop: "text_val", label: "长文本", minWidth: 120, showOverflowTooltip: true },
|
||||
{
|
||||
prop: "i",
|
||||
prop: "json_val",
|
||||
label: "元数据",
|
||||
minWidth: 160,
|
||||
formatter: (row: DemoTable) => {
|
||||
if (row.i == null) return h("span", { class: "text-g-500" }, "—");
|
||||
return h(FaJsonPretty, { value: row.i, height: "120px" });
|
||||
if (row.json_val == null) return h("span", { class: "text-g-500" }, "—");
|
||||
return h(FaJsonPretty, { value: row.json_val, height: "120px" });
|
||||
},
|
||||
},
|
||||
{ prop: "description", label: "描述", minWidth: 120, showOverflowTooltip: true },
|
||||
@@ -379,21 +381,21 @@ const demoDetailItems: import("@/components/others/fa-descriptions/index.vue").D
|
||||
map: { "0": { type: "success", text: "启用" }, "1": { type: "danger", text: "停用" } },
|
||||
},
|
||||
},
|
||||
{ label: "整数", prop: "a" },
|
||||
{ label: "大整数", prop: "b" },
|
||||
{ label: "浮点数", prop: "c" },
|
||||
{ label: "整数", prop: "int_val" },
|
||||
{ label: "大整数", prop: "bigint_val" },
|
||||
{ label: "浮点数", prop: "float_val" },
|
||||
{
|
||||
label: "布尔值",
|
||||
prop: "d",
|
||||
prop: "bool_val",
|
||||
tag: {
|
||||
map: { true: { type: "success", text: "是" }, false: { type: "danger", text: "否" } },
|
||||
},
|
||||
},
|
||||
{ label: "日期", prop: "e" },
|
||||
{ label: "时间", prop: "f" },
|
||||
{ label: "日期时间", prop: "g" },
|
||||
{ label: "长文本", prop: "h" },
|
||||
{ label: "元数据", prop: "i", slot: "i" },
|
||||
{ label: "日期", prop: "date_val" },
|
||||
{ label: "时间", prop: "time_val" },
|
||||
{ label: "日期时间", prop: "datetime_val" },
|
||||
{ label: "长文本", prop: "text_val" },
|
||||
{ label: "元数据", prop: "json_val", slot: "json_val" },
|
||||
{ label: "描述", prop: "description" },
|
||||
{ label: "创建人", prop: "created_by.name" },
|
||||
{ label: "更新人", prop: "updated_by.name" },
|
||||
@@ -415,34 +417,34 @@ const demoDialogFormItems: FormItem[] = [
|
||||
type: "radiogroup",
|
||||
props: {
|
||||
options: [
|
||||
{ label: "启用", value: "0" },
|
||||
{ label: "停用", value: "1" },
|
||||
{ label: "启用", value: 0 },
|
||||
{ label: "停用", value: 1 },
|
||||
],
|
||||
},
|
||||
},
|
||||
{ key: "a", label: "整数", type: "number", props: { placeholder: "请输入整数" } },
|
||||
{ key: "b", label: "大整数", type: "number", props: { placeholder: "请输入大整数" } },
|
||||
{ key: "int_val", label: "整数", type: "number", props: { placeholder: "请输入整数" } },
|
||||
{ key: "bigint_val", label: "大整数", type: "number", props: { placeholder: "请输入大整数" } },
|
||||
{
|
||||
key: "c",
|
||||
key: "float_val",
|
||||
label: "浮点数",
|
||||
type: "number",
|
||||
props: { placeholder: "请输入浮点数", step: 0.01, precision: 2 },
|
||||
},
|
||||
{ key: "d", label: "布尔值", type: "switch" },
|
||||
{ key: "bool_val", label: "布尔值", type: "switch" },
|
||||
{
|
||||
key: "e",
|
||||
key: "date_val",
|
||||
label: "日期",
|
||||
type: "date",
|
||||
props: { placeholder: "请选择日期", valueFormat: "YYYY-MM-DD", style: "width: 100%" },
|
||||
},
|
||||
{
|
||||
key: "f",
|
||||
key: "time_val",
|
||||
label: "时间",
|
||||
type: "timepicker",
|
||||
props: { placeholder: "请选择时间", valueFormat: "HH:mm:ss", style: "width: 100%" },
|
||||
},
|
||||
{
|
||||
key: "g",
|
||||
key: "datetime_val",
|
||||
label: "日期时间",
|
||||
type: "datetime",
|
||||
props: {
|
||||
@@ -452,7 +454,7 @@ const demoDialogFormItems: FormItem[] = [
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "h",
|
||||
key: "text_val",
|
||||
label: "长文本",
|
||||
type: "input",
|
||||
props: { type: "textarea", rows: 4, placeholder: "请输入长文本" },
|
||||
@@ -469,23 +471,23 @@ const demoDialogFormItems: FormItem[] = [
|
||||
placeholder: "请输入描述",
|
||||
},
|
||||
},
|
||||
{ key: "i", label: "元数据", type: "input" /* 由 #i 插槽接管 */ },
|
||||
{ key: "json_val", label: "元数据", type: "input" /* 由 #json_val 插槽接管 */ },
|
||||
];
|
||||
|
||||
const formData = ref<DemoForm>({
|
||||
id: undefined,
|
||||
name: "",
|
||||
status: "0",
|
||||
status: 0,
|
||||
description: undefined,
|
||||
a: undefined,
|
||||
b: undefined,
|
||||
c: undefined,
|
||||
d: true,
|
||||
e: undefined,
|
||||
f: undefined,
|
||||
g: undefined,
|
||||
h: undefined,
|
||||
i: undefined,
|
||||
int_val: undefined,
|
||||
bigint_val: undefined,
|
||||
float_val: undefined,
|
||||
bool_val: true,
|
||||
date_val: undefined,
|
||||
time_val: undefined,
|
||||
datetime_val: undefined,
|
||||
text_val: undefined,
|
||||
json_val: undefined,
|
||||
});
|
||||
|
||||
const rules = reactive({
|
||||
@@ -507,17 +509,17 @@ const { importVisible, exportVisible, openImport, openExport } = useImportExport
|
||||
const initialFormData: DemoForm = {
|
||||
id: undefined,
|
||||
name: "",
|
||||
status: "0",
|
||||
status: 0,
|
||||
description: undefined,
|
||||
a: undefined,
|
||||
b: undefined,
|
||||
c: undefined,
|
||||
d: true,
|
||||
e: undefined,
|
||||
f: undefined,
|
||||
g: undefined,
|
||||
h: undefined,
|
||||
i: undefined,
|
||||
int_val: undefined,
|
||||
bigint_val: undefined,
|
||||
float_val: undefined,
|
||||
bool_val: true,
|
||||
date_val: undefined,
|
||||
time_val: undefined,
|
||||
datetime_val: undefined,
|
||||
text_val: undefined,
|
||||
json_val: undefined,
|
||||
};
|
||||
|
||||
const handleSearch = async (params: DemoSearchFormParams) => {
|
||||
@@ -557,7 +559,6 @@ function buildDemoRowActions(row: DemoTable): TableOperationAction[] {
|
||||
key: "detail",
|
||||
label: "详情",
|
||||
artType: "view",
|
||||
icon: "ri:file-list-3-line",
|
||||
perm: "module_example:demo:detail",
|
||||
run: () => void openDetailDialog(row),
|
||||
},
|
||||
@@ -606,10 +607,11 @@ async function openEditDialog(type: "add" | "edit", row?: DemoTable) {
|
||||
demoFormRenderKey.value += 1;
|
||||
} else if (row?.id) {
|
||||
dialogVisible.title = "修改";
|
||||
demoFormRenderKey.value += 1;
|
||||
const response = await DemoAPI.getDemoDetail(row.id);
|
||||
Object.assign(formData.value, response.data.data);
|
||||
if (formData.value.i && typeof formData.value.i === "object") {
|
||||
metadataList.value = Object.entries(formData.value.i).map(([key, value]) => ({
|
||||
if (formData.value.json_val && typeof formData.value.json_val === "object") {
|
||||
metadataList.value = Object.entries(formData.value.json_val).map(([key, value]) => ({
|
||||
key,
|
||||
value: String(value),
|
||||
}));
|
||||
@@ -645,9 +647,9 @@ async function handleSubmit() {
|
||||
metadataObj[item.key.trim()] = item.value;
|
||||
}
|
||||
});
|
||||
submitData.i = Object.keys(metadataObj).length > 0 ? metadataObj : undefined;
|
||||
submitData.json_val = Object.keys(metadataObj).length > 0 ? metadataObj : undefined;
|
||||
} else {
|
||||
submitData.i = undefined;
|
||||
submitData.json_val = undefined;
|
||||
}
|
||||
const id = formData.value.id;
|
||||
try {
|
||||
@@ -671,7 +673,6 @@ const deleteDemoRow = async (row: DemoTable) => {
|
||||
try {
|
||||
await confirmDelete(`确定删除「${row.name ?? row.id}」吗?此操作不可恢复!`);
|
||||
await DemoAPI.deleteDemo([row.id!]);
|
||||
ElMessage.success("删除成功");
|
||||
faTableRef.value?.elTableRef?.clearSelection();
|
||||
await refreshRemove();
|
||||
} catch {
|
||||
@@ -686,7 +687,6 @@ async function handleBatchDelete() {
|
||||
await confirmBatchDelete(ids.length);
|
||||
batchDeleting.value = true;
|
||||
await DemoAPI.deleteDemo(ids);
|
||||
ElMessage.success("删除成功");
|
||||
faTableRef.value?.elTableRef?.clearSelection();
|
||||
await refreshRemove();
|
||||
} catch {
|
||||
|
||||
@@ -1,588 +0,0 @@
|
||||
<!-- 示例01 CRUD:与 demo 同一套 Fa 布局;权限与接口为 module_example:demo01 / Demo01API -->
|
||||
<template>
|
||||
<div class="fa-full-height">
|
||||
<FaSearchBarWithAudit
|
||||
v-show="showSearchBar"
|
||||
ref="searchBarRef"
|
||||
v-model="searchForm"
|
||||
:items="demo01BusinessSearchItems"
|
||||
:rules="searchBarRules"
|
||||
:is-expand="false"
|
||||
:show-expand="true"
|
||||
:show-reset="true"
|
||||
:show-search="true"
|
||||
:disabled-search="false"
|
||||
:default-expanded="false"
|
||||
@search="handleSearch"
|
||||
@reset="onResetSearch"
|
||||
/>
|
||||
|
||||
<ElCard
|
||||
shadow="hover"
|
||||
class="fa-table-card"
|
||||
:style="{ 'margin-top': showSearchBar ? '12px' : '0' }"
|
||||
>
|
||||
<FaTableHeader
|
||||
v-model:columns="columnChecks"
|
||||
v-model:showSearchBar="showSearchBar"
|
||||
:loading="loading"
|
||||
@refresh="refreshData"
|
||||
>
|
||||
<template #left>
|
||||
<FaTableHeaderLeft
|
||||
:remove-ids="selectedIds"
|
||||
:perm-create="['module_example:demo01:create']"
|
||||
:perm-import="['module_example:demo01:import']"
|
||||
:perm-export="['module_example:demo01:export']"
|
||||
:perm-delete="['module_example:demo01:delete']"
|
||||
:perm-patch="['module_example:demo01:patch']"
|
||||
:delete-loading="batchDeleting"
|
||||
@add="openEditDialog('add')"
|
||||
@import="openImport"
|
||||
@export="openExport"
|
||||
@delete="handleBatchDelete"
|
||||
@more="runBatchStatus"
|
||||
/>
|
||||
</template>
|
||||
</FaTableHeader>
|
||||
|
||||
<FaTable
|
||||
ref="faTableRef"
|
||||
:loading="loading"
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
:pagination="pagination"
|
||||
@selection-change="onTableSelectionChange"
|
||||
@pagination:size-change="handleSizeChange"
|
||||
@pagination:current-change="handleCurrentChange"
|
||||
/>
|
||||
</ElCard>
|
||||
|
||||
<FaDialog
|
||||
v-model="dialogVisible.visible"
|
||||
:title="dialogVisible.title"
|
||||
width="768px"
|
||||
dialog-class="crud-embed-dialog"
|
||||
modal-class="crud-embed-dialog"
|
||||
:form-mode="dialogVisible.type"
|
||||
:confirm-loading="submitLoading"
|
||||
@cancel="handleCloseDialog"
|
||||
@confirm="dialogVisible.type === 'detail' ? handleCloseDialog() : handleSubmit()"
|
||||
>
|
||||
<template v-if="dialogVisible.type === 'detail'">
|
||||
<FaDescriptions
|
||||
:column="2"
|
||||
:data="detailFormData"
|
||||
:items="demo01DetailItems"
|
||||
max-height="70vh"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<FaForm
|
||||
:key="demo01FormRenderKey"
|
||||
scrollbar
|
||||
max-height="70vh"
|
||||
ref="dataFormRef"
|
||||
v-model="formData"
|
||||
:items="demo01DialogFormItems"
|
||||
:rules="rules"
|
||||
label-suffix=":"
|
||||
:label-width="100"
|
||||
label-position="right"
|
||||
:span="24"
|
||||
:gutter="16"
|
||||
:show-reset="false"
|
||||
:show-submit="false"
|
||||
class="crud-dialog-art-form"
|
||||
>
|
||||
<template #status>
|
||||
<ElRadioGroup v-model="formData.status">
|
||||
<ElRadio value="0">正常</ElRadio>
|
||||
<ElRadio value="1">停用</ElRadio>
|
||||
</ElRadioGroup>
|
||||
</template>
|
||||
</FaForm>
|
||||
</template>
|
||||
</FaDialog>
|
||||
|
||||
<FaImportDialog
|
||||
v-model="importVisible"
|
||||
:content-config="demo01ImportContentConfig"
|
||||
default-template-file-name="demo01_import_template.xlsx"
|
||||
@upload="handleCrudImportUpload"
|
||||
/>
|
||||
|
||||
<FaExportDialog
|
||||
v-model="exportVisible"
|
||||
:content-config="demo01ExportContentConfig"
|
||||
:query-params="exportQueryParams"
|
||||
:page-data="data"
|
||||
:selection-data="selectedRows"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAuth } from "@/hooks/core/useAuth";
|
||||
import { renderTableOperationCell, type TableOperationAction } from "@utils";
|
||||
import { useTable } from "@/hooks/core/useTable";
|
||||
import { useImportExport } from "@/hooks/core/useImportExport";
|
||||
import { useCrudDialog } from "@/hooks/core/useCrudDialog";
|
||||
import { useTableSelection } from "@/hooks/core/useTableSelection";
|
||||
import { confirmDelete, confirmBatchDelete, confirmAction } from "@/hooks/core/useConfirm";
|
||||
import { cleanEmptyArrayParams, stripPaginationParams } from "@/utils/query";
|
||||
import type { IContentConfig, IObject } from "@/components/modal/types";
|
||||
import type { AuditSearchFormParams } from "@/components/forms/fa-search-bar/auditSearchFormItems";
|
||||
import FaSearchBarWithAudit from "@/components/forms/fa-search-bar/FaSearchBarWithAudit.vue";
|
||||
import FaForm from "@/components/forms/fa-form/index.vue";
|
||||
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||
import type { ColumnOption } from "@/types/component";
|
||||
import Demo01API, {
|
||||
type Demo01Form,
|
||||
type Demo01PageQuery,
|
||||
type Demo01Table,
|
||||
} from "@/api/module_example/demo01";
|
||||
import { ResultEnum } from "@/enums/api/result.enum";
|
||||
import { ElTag, ElMessage } from "element-plus";
|
||||
|
||||
defineOptions({
|
||||
name: "ModuleExampleDemo01",
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const { hasAuth } = useAuth();
|
||||
|
||||
type Demo01SearchFormParams = {
|
||||
name?: string;
|
||||
description?: string;
|
||||
status?: string;
|
||||
} & AuditSearchFormParams;
|
||||
|
||||
function normalizeDemo01Query(params: Record<string, unknown>): Demo01PageQuery {
|
||||
return cleanEmptyArrayParams({ ...params }, [
|
||||
"created_time",
|
||||
"updated_time",
|
||||
]) as unknown as Demo01PageQuery;
|
||||
}
|
||||
|
||||
const searchForm = ref<Demo01SearchFormParams>({
|
||||
name: undefined,
|
||||
description: undefined,
|
||||
status: undefined,
|
||||
created_id: undefined,
|
||||
updated_id: undefined,
|
||||
created_time: [],
|
||||
updated_time: [],
|
||||
});
|
||||
|
||||
const showSearchBar = ref(true);
|
||||
|
||||
const searchBarRef = ref<InstanceType<typeof FaSearchBarWithAudit> | null>(null);
|
||||
const searchBarRules: Record<string, unknown> = {};
|
||||
|
||||
const statusOptions = ref([
|
||||
{ label: "正常", value: "0" },
|
||||
{ label: "停用", value: "1" },
|
||||
]);
|
||||
|
||||
const demo01BusinessSearchItems = computed(() => [
|
||||
{
|
||||
label: "名称",
|
||||
key: "name",
|
||||
type: "input",
|
||||
placeholder: "请输入名称",
|
||||
clearable: true,
|
||||
span: 6,
|
||||
},
|
||||
{
|
||||
label: "描述",
|
||||
key: "description",
|
||||
type: "input",
|
||||
placeholder: "请输入描述",
|
||||
clearable: true,
|
||||
span: 6,
|
||||
},
|
||||
{
|
||||
label: "状态",
|
||||
key: "status",
|
||||
type: "select",
|
||||
props: {
|
||||
placeholder: "请选择状态",
|
||||
options: statusOptions.value,
|
||||
clearable: true,
|
||||
},
|
||||
span: 6,
|
||||
},
|
||||
]);
|
||||
|
||||
const faTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
||||
const { selectedRows, selectedIds, batchDeleting, onTableSelectionChange } =
|
||||
useTableSelection<Demo01Table>();
|
||||
|
||||
const {
|
||||
columns,
|
||||
columnChecks,
|
||||
data,
|
||||
loading,
|
||||
pagination,
|
||||
searchParams,
|
||||
getData,
|
||||
replaceSearchParams,
|
||||
resetSearchParams,
|
||||
handleSizeChange,
|
||||
handleCurrentChange,
|
||||
refreshData,
|
||||
refreshCreate,
|
||||
refreshUpdate,
|
||||
refreshRemove,
|
||||
} = useTable({
|
||||
core: {
|
||||
apiFn: Demo01API.getDemo01List,
|
||||
apiParams: {
|
||||
page_no: 1,
|
||||
page_size: 10,
|
||||
},
|
||||
columnsFactory: (): ColumnOption<Demo01Table>[] => [
|
||||
{ type: "globalIndex", width: 56, label: "序号" },
|
||||
{ type: "selection", width: 48, fixed: "left" },
|
||||
{ prop: "name", label: "名称", minWidth: 120, showOverflowTooltip: true },
|
||||
{ prop: "uuid", label: "UUID", minWidth: 168, showOverflowTooltip: true },
|
||||
{
|
||||
prop: "status",
|
||||
label: "状态",
|
||||
width: 88,
|
||||
formatter: (row: Demo01Table) => {
|
||||
const ok = row.status === "0";
|
||||
const cfg = ok
|
||||
? { type: "success" as const, text: "正常" }
|
||||
: { type: "info" as const, text: "停用" };
|
||||
return h(ElTag, { type: cfg.type }, () => cfg.text);
|
||||
},
|
||||
},
|
||||
{ prop: "description", label: "描述", minWidth: 140, showOverflowTooltip: true },
|
||||
{ prop: "created_time", label: "创建时间", width: 168, showOverflowTooltip: true },
|
||||
{ prop: "updated_time", label: "更新时间", width: 168, showOverflowTooltip: true },
|
||||
{
|
||||
prop: "created_by",
|
||||
label: "创建人",
|
||||
minWidth: 100,
|
||||
formatter: (row: Demo01Table) => row.created_by?.name ?? "—",
|
||||
},
|
||||
{
|
||||
prop: "updated_by",
|
||||
label: "更新人",
|
||||
minWidth: 100,
|
||||
formatter: (row: Demo01Table) => row.updated_by?.name ?? "—",
|
||||
},
|
||||
{
|
||||
prop: "operation",
|
||||
label: "操作",
|
||||
width: 220,
|
||||
fixed: "right",
|
||||
align: "right",
|
||||
formatter: (row: Demo01Table) => formatDemo01OperationCell(row),
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const demo01CrudCols = computed(() =>
|
||||
columns.value.map((c: ColumnOption<Demo01Table>) => {
|
||||
const t = (c as { type?: string }).type;
|
||||
return {
|
||||
prop: c.prop,
|
||||
label: c.label,
|
||||
type: t === "selection" ? ("selection" as const) : ("default" as const),
|
||||
show: true,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const exportQueryParams = computed(() => {
|
||||
const sp = stripPaginationParams(searchParams as Record<string, unknown>);
|
||||
return normalizeDemo01Query(sp);
|
||||
});
|
||||
|
||||
const demo01ImportContentConfig = computed<IContentConfig>(() => ({
|
||||
permPrefix: "module_example:demo01",
|
||||
cols: demo01CrudCols.value,
|
||||
indexAction: async () => ({}) as Demo01Table[],
|
||||
importTemplate: () => Demo01API.downloadDemo01Template(),
|
||||
}));
|
||||
|
||||
const demo01ExportContentConfig = computed(() => ({
|
||||
permPrefix: "module_example:demo01",
|
||||
cols: demo01CrudCols.value,
|
||||
exportsBlobAction: async (params: IObject) => {
|
||||
const merged = normalizeDemo01Query({
|
||||
...(exportQueryParams.value as unknown as Record<string, unknown>),
|
||||
...params,
|
||||
} as Record<string, unknown>);
|
||||
const res = await Demo01API.exportDemo01(merged);
|
||||
return res.data as Blob;
|
||||
},
|
||||
}));
|
||||
|
||||
const { dialogVisible } = useCrudDialog();
|
||||
|
||||
const detailFormData = ref<Demo01Table>({});
|
||||
|
||||
const demo01DetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] =
|
||||
[
|
||||
{ label: "名称", prop: "name" },
|
||||
{ label: "UUID", prop: "uuid" },
|
||||
{
|
||||
label: "状态",
|
||||
prop: "status",
|
||||
tag: {
|
||||
map: { "0": { type: "success", text: "正常" }, "1": { type: "danger", text: "停用" } },
|
||||
},
|
||||
},
|
||||
{ label: "描述", prop: "description" },
|
||||
{ label: "创建人", prop: "created_by.name" },
|
||||
{ label: "更新人", prop: "updated_by.name" },
|
||||
{ label: "创建时间", prop: "created_time" },
|
||||
{ label: "更新时间", prop: "updated_time" },
|
||||
];
|
||||
|
||||
const formData = ref<Demo01Form>({
|
||||
id: undefined,
|
||||
name: "",
|
||||
status: "0",
|
||||
description: "",
|
||||
});
|
||||
|
||||
const rules = reactive({
|
||||
name: [{ required: true, message: "请输入名称", trigger: "blur" }],
|
||||
status: [{ required: true, message: "请选择状态", trigger: "blur" }],
|
||||
});
|
||||
|
||||
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
||||
const submitLoading = ref(false);
|
||||
const demo01FormRenderKey = ref(0);
|
||||
|
||||
const demo01DialogFormItems = computed<FormItem[]>(() => [
|
||||
{
|
||||
label: "名称",
|
||||
key: "name",
|
||||
type: "input",
|
||||
span: 24,
|
||||
props: { placeholder: "请输入名称", clearable: true },
|
||||
},
|
||||
{
|
||||
label: "状态",
|
||||
key: "status",
|
||||
type: "input",
|
||||
span: 24,
|
||||
placeholder: "",
|
||||
},
|
||||
{
|
||||
label: "描述",
|
||||
key: "description",
|
||||
type: "input",
|
||||
span: 24,
|
||||
props: { type: "textarea", rows: 4, placeholder: "请输入描述" },
|
||||
},
|
||||
]);
|
||||
|
||||
const { importVisible, exportVisible, openImport, openExport } = useImportExport();
|
||||
|
||||
const initialFormData: Demo01Form = {
|
||||
id: undefined,
|
||||
name: "",
|
||||
status: "0",
|
||||
description: "",
|
||||
};
|
||||
|
||||
const handleSearch = async (params: Demo01SearchFormParams) => {
|
||||
await searchBarRef.value?.validate();
|
||||
replaceSearchParams({
|
||||
name: params.name,
|
||||
description: params.description,
|
||||
status: params.status,
|
||||
created_id: params.created_id ?? undefined,
|
||||
updated_id: params.updated_id ?? undefined,
|
||||
created_time:
|
||||
Array.isArray(params.created_time) && params.created_time.length === 2
|
||||
? params.created_time
|
||||
: undefined,
|
||||
updated_time:
|
||||
Array.isArray(params.updated_time) && params.updated_time.length === 2
|
||||
? params.updated_time
|
||||
: undefined,
|
||||
} as Record<string, unknown>);
|
||||
getData();
|
||||
};
|
||||
|
||||
const onResetSearch = async () => {
|
||||
searchForm.value = {
|
||||
name: undefined,
|
||||
description: undefined,
|
||||
status: undefined,
|
||||
created_id: undefined,
|
||||
updated_id: undefined,
|
||||
created_time: [],
|
||||
updated_time: [],
|
||||
};
|
||||
await resetSearchParams();
|
||||
};
|
||||
|
||||
function buildDemo01RowActions(row: Demo01Table): TableOperationAction[] {
|
||||
const all: TableOperationAction[] = [
|
||||
{
|
||||
key: "detail",
|
||||
label: "详情",
|
||||
artType: "view",
|
||||
icon: "ri:file-list-3-line",
|
||||
perm: "module_example:demo01:detail",
|
||||
run: () => void openDetailDialog(row),
|
||||
},
|
||||
{
|
||||
key: "edit",
|
||||
label: "编辑",
|
||||
artType: "edit",
|
||||
icon: "ri:edit-2-line",
|
||||
perm: "module_example:demo01:update",
|
||||
run: () => void openEditDialog("edit", row),
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
label: "删除",
|
||||
artType: "delete",
|
||||
icon: "ri:delete-bin-4-line",
|
||||
perm: "module_example:demo01:delete",
|
||||
run: () => deleteDemo01Row(row),
|
||||
},
|
||||
];
|
||||
return all.filter((a) => a.perm != null && hasAuth(a.perm));
|
||||
}
|
||||
|
||||
function formatDemo01OperationCell(row: Demo01Table) {
|
||||
return renderTableOperationCell(buildDemo01RowActions(row), {
|
||||
wrapperClass: "inline-flex flex-wrap items-center justify-end gap-1 demo01-table-actions",
|
||||
});
|
||||
}
|
||||
|
||||
async function openDetailDialog(row: Demo01Table) {
|
||||
if (!row.id) return;
|
||||
const response = await Demo01API.getDemo01Detail(row.id);
|
||||
dialogVisible.type = "detail";
|
||||
dialogVisible.title = "详情";
|
||||
detailFormData.value = response.data.data ?? { ...row };
|
||||
dialogVisible.visible = true;
|
||||
}
|
||||
|
||||
async function openEditDialog(type: "add" | "edit", row?: Demo01Table) {
|
||||
dialogVisible.type = type === "add" ? "create" : "update";
|
||||
if (type === "add") {
|
||||
dialogVisible.title = "新增示例01";
|
||||
Object.assign(formData.value, initialFormData);
|
||||
formData.value.id = undefined;
|
||||
} else if (row?.id) {
|
||||
dialogVisible.title = "修改";
|
||||
const response = await Demo01API.getDemo01Detail(row.id);
|
||||
Object.assign(formData.value, response.data.data);
|
||||
}
|
||||
demo01FormRenderKey.value += 1;
|
||||
dialogVisible.visible = true;
|
||||
}
|
||||
|
||||
async function resetForm() {
|
||||
dataFormRef.value?.resetFields();
|
||||
dataFormRef.value?.clearValidate();
|
||||
Object.assign(formData.value, initialFormData);
|
||||
}
|
||||
|
||||
async function handleCloseDialog() {
|
||||
dialogVisible.visible = false;
|
||||
await resetForm();
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
dataFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (!valid) return;
|
||||
const id = formData.value.id;
|
||||
try {
|
||||
if (id) {
|
||||
await Demo01API.updateDemo01(id, { id, ...formData.value });
|
||||
await refreshUpdate();
|
||||
} else {
|
||||
await Demo01API.createDemo01(formData.value);
|
||||
await refreshCreate();
|
||||
}
|
||||
dialogVisible.visible = false;
|
||||
await resetForm();
|
||||
} catch (error: unknown) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const deleteDemo01Row = async (row: Demo01Table) => {
|
||||
if (!row.id) return;
|
||||
try {
|
||||
await confirmDelete(`确定删除「${row.name ?? row.id}」吗?此操作不可恢复!`);
|
||||
await Demo01API.deleteDemo01([row.id!]);
|
||||
ElMessage.success("删除成功");
|
||||
faTableRef.value?.elTableRef?.clearSelection();
|
||||
await refreshRemove();
|
||||
} catch {
|
||||
// 用户取消
|
||||
}
|
||||
};
|
||||
|
||||
async function handleBatchDelete() {
|
||||
const ids = selectedIds.value;
|
||||
if (ids.length === 0) return;
|
||||
try {
|
||||
await confirmBatchDelete(ids.length);
|
||||
batchDeleting.value = true;
|
||||
await Demo01API.deleteDemo01(ids);
|
||||
ElMessage.success("删除成功");
|
||||
faTableRef.value?.elTableRef?.clearSelection();
|
||||
await refreshRemove();
|
||||
} catch {
|
||||
// 用户取消
|
||||
} finally {
|
||||
batchDeleting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function runBatchStatus(status: string) {
|
||||
const ids = selectedIds.value;
|
||||
if (ids.length === 0) {
|
||||
ElMessage.warning("请先在列表中勾选数据");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await confirmAction(
|
||||
`确认对选中的 ${ids.length} 条数据${status === "0" ? "启用" : "停用"}?`,
|
||||
"批量设置"
|
||||
);
|
||||
await Demo01API.batchDemo01({ ids, status });
|
||||
ElMessage.success("操作成功");
|
||||
faTableRef.value?.elTableRef?.clearSelection();
|
||||
await refreshData();
|
||||
} catch {
|
||||
// 用户取消
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCrudImportUpload(formDataUpload: FormData) {
|
||||
try {
|
||||
const res = await Demo01API.importDemo01(formDataUpload);
|
||||
if (res.data.code !== ResultEnum.SUCCESS) {
|
||||
ElMessage.error(res.data.msg || "导入失败");
|
||||
return;
|
||||
}
|
||||
ElMessage.success(res.data.msg || "导入成功");
|
||||
importVisible.value = false;
|
||||
await refreshData();
|
||||
} catch (error) {
|
||||
console.error("[Import]", error);
|
||||
ElMessage.error("导入失败");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
Reference in New Issue
Block a user