mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-10-01 16:21:20 +00:00
chore: 清理冗余代码与配置,优化项目结构
1. 删除无用文件与废弃代码:移除locale枚举、element-plus插件、sse路由、api token模块等 2. 简化类型导入与依赖:移除大量未使用的类型导入,统一echarts导入方式 3. 优化配置与样式:调整gitignore、样式引入顺序,新增列表动画样式 4. 修复接口与模型:修正接口返回类型、查询参数配置,更新部门模型字段 5. 优化性能与体验:添加图片懒加载,优化加载逻辑与表格渲染 6. 调整环境配置:新增并更新开发/生产环境配置文件
This commit is contained in:
@@ -2,14 +2,14 @@
|
||||
<template>
|
||||
<div class="data-table__toolbar--left inline-flex flex-wrap items-center gap-2">
|
||||
<template v-if="configButtons && configButtons.length">
|
||||
<template v-for="(btn, index) in configButtons" :key="index">
|
||||
<template v-for="btn in configButtons" :key="btn.name">
|
||||
<ElButton
|
||||
v-hasPerm="btn.perm ?? '*:*:*'"
|
||||
v-bind="btn.attrs"
|
||||
:disabled="btn.name === 'delete' && removeIds.length === 0"
|
||||
@click="$emit('toolbar', btn.name)"
|
||||
>
|
||||
{{ btn.text }}
|
||||
{{ btn.name === "delete" ? batchDeleteText(btn.text ?? "") : btn.text }}
|
||||
</ElButton>
|
||||
</template>
|
||||
</template>
|
||||
@@ -61,7 +61,7 @@
|
||||
@click="$emit('delete')"
|
||||
plain
|
||||
>
|
||||
批量删除
|
||||
{{ batchDeleteText("批量删除") }}
|
||||
</ElButton>
|
||||
<ElDropdown
|
||||
v-if="permPatch"
|
||||
@@ -123,6 +123,8 @@ interface Props {
|
||||
createLoading?: boolean;
|
||||
/** 「更多」下拉项(启用/停用)loading */
|
||||
moreLoading?: boolean;
|
||||
/** 是否全选状态,用于显示 "已选择全部 X 项" */
|
||||
isAllSelected?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -132,6 +134,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
exportLoading: false,
|
||||
createLoading: false,
|
||||
moreLoading: false,
|
||||
isAllSelected: false,
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
@@ -149,4 +152,10 @@ defineEmits<Emits>();
|
||||
const moreDisabled = computed(
|
||||
() => props.removeIds.length === 0 || props.deleteLoading || props.moreLoading
|
||||
);
|
||||
|
||||
/** 生成带选中计数的批量删除按钮文本 */
|
||||
function batchDeleteText(baseText: string): string {
|
||||
const count = props.removeIds.length;
|
||||
return count > 0 ? `${baseText} (${count})` : baseText;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -14,7 +14,11 @@
|
||||
>
|
||||
<div
|
||||
class="button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="search"
|
||||
@keydown.enter.prevent="search"
|
||||
@keydown.space.prevent="search"
|
||||
:class="!showSearchBar ? 'active bg-theme! hover:bg-theme/80!' : ''"
|
||||
>
|
||||
<FaSvgIcon icon="ri:search-line" :class="!showSearchBar ? 'text-white' : 'text-g-700'" />
|
||||
@@ -25,7 +29,11 @@
|
||||
<div
|
||||
v-if="shouldShow('refresh')"
|
||||
class="button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="refresh"
|
||||
@keydown.enter.prevent="refresh"
|
||||
@keydown.space.prevent="refresh"
|
||||
:class="{ loading: loading && isManualRefresh }"
|
||||
>
|
||||
<FaSvgIcon
|
||||
@@ -59,7 +67,15 @@
|
||||
</ElDropdown>
|
||||
|
||||
<!-- 全屏 -->
|
||||
<div v-if="shouldShow('fullscreen')" class="button" @click="toggleFullScreen">
|
||||
<div
|
||||
v-if="shouldShow('fullscreen')"
|
||||
class="button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="toggleFullScreen"
|
||||
@keydown.enter.prevent="toggleFullScreen"
|
||||
@keydown.space.prevent="toggleFullScreen"
|
||||
>
|
||||
<FaSvgIcon :icon="isFullScreen ? 'ri:fullscreen-exit-line' : 'ri:fullscreen-line'" />
|
||||
</div>
|
||||
|
||||
@@ -71,7 +87,11 @@
|
||||
>
|
||||
<div
|
||||
class="button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="toggleRowDrag"
|
||||
@keydown.enter.prevent="toggleRowDrag"
|
||||
@keydown.space.prevent="toggleRowDrag"
|
||||
:class="isRowDrag ? 'active bg-theme! hover:bg-theme/80!' : ''"
|
||||
>
|
||||
<FaSvgIcon icon="ri:drag-move-line" :class="isRowDrag ? 'text-white' : 'text-g-700'" />
|
||||
@@ -156,8 +176,6 @@ import { TableSizeEnum } from "@/enums/formEnum";
|
||||
import { useTableStore } from "@stores";
|
||||
import { VueDraggable } from "vue-draggable-plus";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import type { ColumnOption } from "@/types/component";
|
||||
|
||||
defineOptions({ name: "FaTableHeader" });
|
||||
|
||||
// 显式声明插槽类型
|
||||
|
||||
@@ -39,7 +39,18 @@
|
||||
:disabled="rowDragDisabled"
|
||||
@end="onRowDragEnd"
|
||||
>
|
||||
<ElTable ref="elTableRef" :key="tableKey" v-loading="!!loading" v-bind="mergedTableProps">
|
||||
<ElTable
|
||||
ref="elTableRef"
|
||||
:key="tableKey"
|
||||
v-loading="!!loading"
|
||||
:expand-row-keys="
|
||||
props.rowKey && !hasExplicitTableProp('treeProps')
|
||||
? expandRowKeys.map(String)
|
||||
: undefined
|
||||
"
|
||||
@expand-change="!hasExplicitTableProp('treeProps') ? onExpandChange : undefined"
|
||||
v-bind="mergedTableProps"
|
||||
>
|
||||
<template v-for="col in columns" :key="col.prop || col.type">
|
||||
<ElTableColumn v-if="col.type === 'globalIndex'" v-bind="{ ...col }">
|
||||
<template #default="{ $index }">
|
||||
@@ -110,7 +121,7 @@ import {
|
||||
ref,
|
||||
computed,
|
||||
nextTick,
|
||||
watchEffect,
|
||||
watch,
|
||||
getCurrentInstance,
|
||||
useAttrs,
|
||||
useSlots,
|
||||
@@ -119,9 +130,10 @@ import {
|
||||
defineComponent,
|
||||
type PropType,
|
||||
} from "vue";
|
||||
import type { ElTable, TableProps } from "element-plus";
|
||||
import type { ElTable, TableInstance, TableProps } from "element-plus";
|
||||
import { useRoute } from "vue-router";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { ColumnOption } from "@/types";
|
||||
|
||||
import { useTableStore } from "@stores";
|
||||
import { useCommon } from "@/hooks/core/useCommon";
|
||||
import { useTableHeight } from "@/hooks/core/useTableHeight";
|
||||
@@ -135,7 +147,7 @@ const { width } = useWindowSize();
|
||||
const isMobile = computed(() => width.value < MOBILE_BREAKPOINT);
|
||||
// H5 ↔ 桌面切换时强制重建 ElTable,使列宽 / formatter 重新计算
|
||||
const tableKey = computed(() => (isMobile.value ? "mobile" : "desktop"));
|
||||
const elTableRef = ref<InstanceType<typeof ElTable> | null>(null);
|
||||
const elTableRef = ref<TableInstance | null>(null);
|
||||
const paginationRef = ref<HTMLElement>();
|
||||
const tableHeaderRef = ref<HTMLElement>();
|
||||
const tableStore = useTableStore();
|
||||
@@ -212,6 +224,51 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
});
|
||||
const instance = getCurrentInstance();
|
||||
const attrs = useAttrs();
|
||||
const route = useRoute();
|
||||
|
||||
// ── 树形表格展开状态记忆 ──
|
||||
/** localStorage 存储 key */
|
||||
const expandStorageKey = computed(() => `table-expand-${route.path}`);
|
||||
|
||||
/** 当前展开的行 key 集合 */
|
||||
const expandRowKeys = ref<(string | number)[]>([]);
|
||||
|
||||
/** 保存展开状态到 localStorage */
|
||||
function saveExpandState(keys: (string | number)[]) {
|
||||
try {
|
||||
localStorage.setItem(expandStorageKey.value, JSON.stringify(keys));
|
||||
} catch {
|
||||
// 静默忽略
|
||||
}
|
||||
}
|
||||
|
||||
/** 从 localStorage 恢复展开状态 */
|
||||
function restoreExpandState(): (string | number)[] {
|
||||
try {
|
||||
const raw = localStorage.getItem(expandStorageKey.value);
|
||||
if (!raw) return [];
|
||||
const parsed = JSON.parse(raw);
|
||||
return Array.isArray(parsed) ? parsed : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// 数据刷新后尝试恢复展开状态
|
||||
watch(
|
||||
() => props.data,
|
||||
(newData) => {
|
||||
if (!newData?.length) {
|
||||
expandRowKeys.value = [];
|
||||
return;
|
||||
}
|
||||
const savedKeys = restoreExpandState();
|
||||
if (savedKeys.length > 0) {
|
||||
expandRowKeys.value = savedKeys;
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
/** 仅当调用方显式传入对应 prop 时视为「固定」,否则交由表格 store */
|
||||
const hasExplicitTableProp = (propName: string): boolean => {
|
||||
@@ -329,20 +386,24 @@ const headerCellStyle = computed(() => ({
|
||||
...(props.headerCellStyle || {}), // 合并用户传入的样式
|
||||
}));
|
||||
|
||||
const mergedTableProps = computed(() => ({
|
||||
...attrs,
|
||||
...props,
|
||||
height: height.value,
|
||||
stripe: stripe.value,
|
||||
border: border.value,
|
||||
size: hasExplicitTableProp("size") ? size.value : undefined,
|
||||
headerCellStyle: headerCellStyle.value,
|
||||
highlightCurrentRow: highlightCurrentRow.value,
|
||||
// Element Plus 默认值为 true,未显式传入时不应被 FaTable 覆盖成 false。
|
||||
selectOnIndeterminate: hasExplicitTableProp("selectOnIndeterminate")
|
||||
? props.selectOnIndeterminate
|
||||
: undefined,
|
||||
}));
|
||||
const mergedTableProps = computed(() => {
|
||||
const { expandRowKeys: _ignored, ...restProps } = props;
|
||||
void _ignored;
|
||||
return {
|
||||
...attrs,
|
||||
...restProps,
|
||||
height: height.value,
|
||||
stripe: stripe.value,
|
||||
border: border.value,
|
||||
size: hasExplicitTableProp("size") ? size.value : undefined,
|
||||
headerCellStyle: headerCellStyle.value,
|
||||
highlightCurrentRow: highlightCurrentRow.value,
|
||||
// Element Plus 默认值为 true,未显式传入时不应被 FaTable 覆盖成 false。
|
||||
selectOnIndeterminate: hasExplicitTableProp("selectOnIndeterminate")
|
||||
? props.selectOnIndeterminate
|
||||
: undefined,
|
||||
};
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: "pagination:size-change", val: number): void;
|
||||
@@ -378,6 +439,31 @@ const onRowDragEnd = () => {
|
||||
}
|
||||
};
|
||||
|
||||
/** 树形表格行展开/收起变化时记录状态 */
|
||||
const onExpandChange = (row: Record<string, unknown>, expandedRows: Record<string, unknown>[]) => {
|
||||
const rowKey = (row as Record<string, unknown>)[props.rowKey as string];
|
||||
if (rowKey === undefined || rowKey === null) return;
|
||||
|
||||
const currentKeys = [...expandRowKeys.value];
|
||||
const isExpanded = expandedRows.some(
|
||||
(r) => (r as Record<string, unknown>)[props.rowKey as string] === rowKey
|
||||
);
|
||||
|
||||
if (isExpanded) {
|
||||
if (!currentKeys.includes(rowKey as string | number)) {
|
||||
currentKeys.push(rowKey as string | number);
|
||||
}
|
||||
} else {
|
||||
const idx = currentKeys.indexOf(rowKey as string | number);
|
||||
if (idx > -1) {
|
||||
currentKeys.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
expandRowKeys.value = currentKeys;
|
||||
saveExpandState(currentKeys);
|
||||
};
|
||||
|
||||
// 是否显示分页器
|
||||
const showPagination = computed(() => !!props.pagination);
|
||||
|
||||
@@ -505,23 +591,18 @@ const findTableHeader = () => {
|
||||
}
|
||||
};
|
||||
|
||||
watchEffect(
|
||||
() => {
|
||||
// 访问响应式数据以建立依赖追踪
|
||||
void props.data?.length; // 追踪数据变化
|
||||
const shouldShow = props.showTableHeader;
|
||||
|
||||
// 只有在需要显示表格头部时才查找
|
||||
watch(
|
||||
() => props.showTableHeader,
|
||||
(shouldShow) => {
|
||||
if (shouldShow) {
|
||||
nextTick(() => {
|
||||
findTableHeader();
|
||||
});
|
||||
} else {
|
||||
// 不显示时清空引用
|
||||
tableHeaderRef.value = undefined;
|
||||
}
|
||||
},
|
||||
{ flush: "post" }
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
defineExpose({
|
||||
@@ -618,13 +699,56 @@ defineExpose({
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* 空状态垂直居中 */
|
||||
/* 空状态垂直居中 + 优化间距 */
|
||||
&.is-empty {
|
||||
:deep(.el-table__body-wrapper) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
:deep(.el-table__empty-block) {
|
||||
min-height: 180px;
|
||||
}
|
||||
|
||||
:deep(.el-empty) {
|
||||
.el-empty__image {
|
||||
width: 72px;
|
||||
}
|
||||
|
||||
.el-empty__description {
|
||||
margin-top: 8px;
|
||||
|
||||
p {
|
||||
font-size: 13px;
|
||||
color: var(--fa-gray-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 表格行悬停行高亮(强化) */
|
||||
:deep(.el-table__body tr.el-table__row) {
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover > td.el-table__cell {
|
||||
background-color: var(--fa-hover-color) !important;
|
||||
}
|
||||
|
||||
&.current-row > td.el-table__cell {
|
||||
background-color: color-mix(in srgb, var(--el-color-primary) 8%, transparent) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 斑马纹优化 */
|
||||
:deep(.el-table--striped .el-table__body tr.el-table__row--striped) {
|
||||
td.el-table__cell {
|
||||
background-color: var(--fa-gray-100);
|
||||
}
|
||||
|
||||
&:hover td.el-table__cell {
|
||||
background-color: var(--fa-hover-color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 分页按钮样式已统一由 FaPagination 组件处理 */
|
||||
|
||||
Reference in New Issue
Block a user