mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-10-04 01:10:36 +00:00
feat: 更新前端配置和组件逻辑
- 在tsconfig.json中排除特定示例文件夹 - 移除不再使用的路由别名清理逻辑,更新为手动修改提示 - 统一组件中对utils路径的引用,简化导入 - 优化多个组件的样式和布局,确保一致性 - 更新国际化文件,调整语言选项格式
This commit is contained in:
@@ -120,7 +120,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import ArtSvgIcon from "@/components/Core/base/art-svg-icon/index.vue";
|
||||
import { resolveIconForArtSvgIcon } from "@/utils/menuIconRemix";
|
||||
import { resolveIconForArtSvgIcon } from "@/utils/menuIcon/remix";
|
||||
import { nextTick, onBeforeUnmount, onMounted, watch, ref, computed } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import ArtSvgIcon from "@/components/Core/base/art-svg-icon/index.vue";
|
||||
import { resolveIconForArtSvgIcon } from "@/utils/menuIconRemix";
|
||||
import { resolveIconForArtSvgIcon } from "@/utils/menuIcon/remix";
|
||||
|
||||
const { isFullscreen, toggle } = useFullscreen();
|
||||
</script>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import ArtSvgIcon from "@/components/Core/base/art-svg-icon/index.vue";
|
||||
import { resolveIconForArtSvgIcon } from "@/utils/menuIconRemix";
|
||||
import { resolveIconForArtSvgIcon } from "@/utils/menuIcon/remix";
|
||||
|
||||
defineProps({
|
||||
isActive: { type: Boolean, required: true },
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
|
||||
import ArtSvgIcon from "@/components/Core/base/art-svg-icon/index.vue";
|
||||
import { isIconifyStoredIcon, resolveElementPlusIconComponent } from "@/utils/menuIcon";
|
||||
import { resolveIconForArtSvgIcon } from "@/utils/menuIconRemix";
|
||||
import { resolveIconForArtSvgIcon } from "@/utils/menuIcon/remix";
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
import ArtSvgIcon from "@/components/Core/base/art-svg-icon/index.vue";
|
||||
import { useAppStore } from "@/store/modules/app.store";
|
||||
import { LanguageEnum } from "@/enums/settings/locale.enum";
|
||||
import { resolveIconForArtSvgIcon } from "@/utils/menuIconRemix";
|
||||
import { resolveIconForArtSvgIcon } from "@/utils/menuIcon/remix";
|
||||
|
||||
defineProps({
|
||||
size: {
|
||||
|
||||
@@ -36,7 +36,7 @@ import {
|
||||
isElementPlusStoredIcon,
|
||||
resolveElementPlusIconComponent,
|
||||
} from "@/utils/menuIcon";
|
||||
import { resolveIconForArtSvgIcon } from "@/utils/menuIconRemix";
|
||||
import { resolveIconForArtSvgIcon } from "@/utils/menuIcon/remix";
|
||||
|
||||
defineOptions({ name: "MenuRouteIcon", inheritAttrs: false });
|
||||
|
||||
@@ -44,7 +44,7 @@ const props = defineProps<{
|
||||
icon?: string;
|
||||
color?: string;
|
||||
/** 与 Vue `class` 一致(含条件 `false`) */
|
||||
class?: import("vue").ClassValue;
|
||||
class?: string | string[] | Record<string, boolean> | null | undefined | false;
|
||||
style?: Record<string, unknown> | string;
|
||||
}>();
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@
|
||||
import ArtSvgIcon from "@/components/Core/base/art-svg-icon/index.vue";
|
||||
import MenuRouteIcon from "@/components/MenuRouteIcon/index.vue";
|
||||
import { router } from "@/router";
|
||||
import { resolveIconForArtSvgIcon } from "@/utils/menuIconRemix";
|
||||
import { resolveIconForArtSvgIcon } from "@/utils/menuIcon/remix";
|
||||
import { usePermissionStore } from "@/store";
|
||||
import { isExternal } from "@/utils";
|
||||
import { RouteRecordRaw, LocationQueryRaw } from "vue-router";
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
<script setup lang="ts">
|
||||
import ArtSvgIcon from "@/components/Core/base/art-svg-icon/index.vue";
|
||||
import NoticeAPI, { NoticeTable } from "@/api/module_system/notice";
|
||||
import { resolveIconForArtSvgIcon } from "@/utils/menuIconRemix";
|
||||
import { resolveIconForArtSvgIcon } from "@/utils/menuIcon/remix";
|
||||
import { ArrowRight, Timer, User } from "@element-plus/icons-vue";
|
||||
import { router } from "@/router";
|
||||
import { useNoticeStore } from "@/store";
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
import ArtIconButton from "@/components/Core/widget/art-icon-button/index.vue";
|
||||
import { ComponentSize } from "@/enums/settings/layout.enum";
|
||||
import { useAppStore } from "@/store/modules/app.store";
|
||||
import { resolveIconForArtSvgIcon } from "@/utils/menuIconRemix";
|
||||
import { resolveIconForArtSvgIcon } from "@/utils/menuIcon/remix";
|
||||
import { computed } from "vue";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -118,7 +118,7 @@ import {
|
||||
ElTreeSelect,
|
||||
type FormInstance,
|
||||
} from "element-plus";
|
||||
import { calculateResponsiveSpan, type ResponsiveBreakpoint } from "@/utils/form/responsive";
|
||||
import { calculateResponsiveSpan, type ResponsiveBreakpoint } from "@/utils/form";
|
||||
|
||||
defineOptions({ name: "ArtForm" });
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ import {
|
||||
ElTreeSelect,
|
||||
type FormInstance,
|
||||
} from "element-plus";
|
||||
import { calculateResponsiveSpan, type ResponsiveBreakpoint } from "@/utils/form/responsive";
|
||||
import { calculateResponsiveSpan, type ResponsiveBreakpoint } from "@/utils/form";
|
||||
|
||||
defineOptions({ name: "ArtSearchBar" });
|
||||
|
||||
|
||||
@@ -22,9 +22,10 @@ import "@wangeditor/editor/dist/css/style.css";
|
||||
import { onBeforeUnmount, onMounted, shallowRef, computed } from "vue";
|
||||
import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
|
||||
import { useUserStore } from "@/store/modules/user.store";
|
||||
import EmojiText from "@/utils/ui/emojo";
|
||||
import { EmojiText } from "@/utils/ui";
|
||||
import { IDomEditor, IToolbarConfig, IEditorConfig } from "@wangeditor/editor";
|
||||
import request from "@/utils/http";
|
||||
import type { AxiosResponse } from "axios";
|
||||
|
||||
defineOptions({ name: "ArtWangEditor" });
|
||||
|
||||
@@ -136,22 +137,26 @@ const editorConfig: Partial<IEditorConfig> = {
|
||||
};
|
||||
|
||||
// 自定义上传
|
||||
if (props.uploadConfig?.isCustomUpload && props.uploadConfig?.server && editorConfig.MENU_CONF) {
|
||||
const uploadConfig = props.uploadConfig;
|
||||
if (uploadConfig?.isCustomUpload && uploadConfig.server && editorConfig.MENU_CONF) {
|
||||
const uploadServerUrl = uploadConfig.server;
|
||||
editorConfig.MENU_CONF.uploadImage.customUpload = async (file: File, insertFn: InsertFnType) => {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append(mergedUploadConfig.value.fieldName, file);
|
||||
|
||||
const response = await request.post<{ url: string; alt: string; href: string }>({
|
||||
url: props.uploadConfig?.server,
|
||||
data: formData,
|
||||
type UploadImagePayload = { url: string; alt?: string; href?: string };
|
||||
const response = await request.post<
|
||||
ApiResponse<UploadImagePayload>,
|
||||
AxiosResponse<ApiResponse<UploadImagePayload>>
|
||||
>(uploadServerUrl, formData, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
Authorization: userStore.accessToken,
|
||||
},
|
||||
});
|
||||
|
||||
const { url, alt, href } = response;
|
||||
const { url, alt = "", href = "" } = response.data.data ?? ({} as any);
|
||||
|
||||
if (!url) {
|
||||
throw new Error("上传失败,请检查服务端配置");
|
||||
|
||||
@@ -6,15 +6,20 @@
|
||||
</div>
|
||||
|
||||
<div class="flex-c md:justify-end max-md:mt-3 max-sm:!hidden">
|
||||
<!-- 搜索显示 -->
|
||||
<div
|
||||
<!-- 搜索区域显示/隐藏:默认展示搜索(未高亮);点按收起后高亮表示当前为隐藏状态 -->
|
||||
<ElTooltip
|
||||
v-if="showSearchBar != null"
|
||||
class="button"
|
||||
@click="search"
|
||||
:class="showSearchBar ? 'active !bg-theme hover:!bg-theme/80' : ''"
|
||||
placement="bottom"
|
||||
:content="showSearchBar ? t('table.toolbar.hideSearch') : t('table.toolbar.showSearch')"
|
||||
>
|
||||
<ArtSvgIcon icon="ri:search-line" :class="showSearchBar ? 'text-white' : 'text-g-700'" />
|
||||
</div>
|
||||
<div
|
||||
class="button"
|
||||
@click="search"
|
||||
:class="!showSearchBar ? 'active !bg-theme hover:!bg-theme/80' : ''"
|
||||
>
|
||||
<ArtSvgIcon icon="ri:search-line" :class="!showSearchBar ? 'text-white' : 'text-g-700'" />
|
||||
</div>
|
||||
</ElTooltip>
|
||||
|
||||
<!-- 刷新 -->
|
||||
<div
|
||||
@@ -109,14 +114,14 @@
|
||||
<ArtSvgIcon icon="ri:settings-line" />
|
||||
</div>
|
||||
</template>
|
||||
<div>
|
||||
<ElCheckbox v-if="showZebra" v-model="isZebra" :value="true">
|
||||
<div class="flex min-w-[200px] flex-col gap-2">
|
||||
<ElCheckbox v-model="isZebra" :value="true">
|
||||
{{ t("table.zebra") }}
|
||||
</ElCheckbox>
|
||||
<ElCheckbox v-if="showBorder" v-model="isBorder" :value="true">
|
||||
<ElCheckbox v-model="isBorder" :value="true">
|
||||
{{ t("table.border") }}
|
||||
</ElCheckbox>
|
||||
<ElCheckbox v-if="showHeaderBackground" v-model="isHeaderBackground" :value="true">
|
||||
<ElCheckbox v-model="isHeaderBackground" :value="true">
|
||||
{{ t("table.headerBackground") }}
|
||||
</ElCheckbox>
|
||||
</div>
|
||||
@@ -141,12 +146,6 @@ defineOptions({ name: "ArtTableHeader" });
|
||||
const { t } = useI18n();
|
||||
|
||||
interface Props {
|
||||
/** 斑马纹 */
|
||||
showZebra?: boolean;
|
||||
/** 边框 */
|
||||
showBorder?: boolean;
|
||||
/** 表头背景 */
|
||||
showHeaderBackground?: boolean;
|
||||
/** 全屏 class */
|
||||
fullClass?: string;
|
||||
/** 组件布局,子组件名用逗号分隔 */
|
||||
@@ -158,9 +157,6 @@ interface Props {
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
showZebra: true,
|
||||
showBorder: true,
|
||||
showHeaderBackground: true,
|
||||
fullClass: "art-page-view",
|
||||
layout: "search,refresh,size,fullscreen,columns,settings",
|
||||
showSearchBar: undefined,
|
||||
|
||||
@@ -32,7 +32,22 @@
|
||||
</template>
|
||||
<!-- 整段单行:插槽内兄弟节点之间的空白文本会让 EP 误判单元格内容 -->
|
||||
<!-- eslint-disable-next-line vue/max-attributes-per-line, prettier/prettier -->
|
||||
<template #default="slotScope"><slot v-if="col.useSlot && col.prop && shouldRenderSlotScope(slotScope)" :name="col.slotName || col.prop" v-bind="{ ...slotScope, prop: col.prop, value: col.prop ? slotScope.row[col.prop] : undefined }" /><TableFormatterOutlet v-else-if="col.formatter && !col.useSlot && shouldRenderSlotScope(slotScope)" :column="col" :record="slotScope.row" /></template>
|
||||
<template #default="slotScope">
|
||||
<slot
|
||||
v-if="col.useSlot && col.prop && shouldRenderSlotScope(slotScope)"
|
||||
:name="col.slotName || col.prop"
|
||||
v-bind="{
|
||||
...slotScope,
|
||||
prop: col.prop,
|
||||
value: col.prop ? slotScope.row[col.prop] : undefined,
|
||||
}"
|
||||
/>
|
||||
<TableFormatterOutlet
|
||||
v-else-if="col.formatter && !col.useSlot && shouldRenderSlotScope(slotScope)"
|
||||
:column="col"
|
||||
:record="slotScope.row"
|
||||
/>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</template>
|
||||
|
||||
@@ -159,6 +174,13 @@ const props = withDefaults(defineProps<ArtTableProps>(), {
|
||||
const instance = getCurrentInstance();
|
||||
const attrs = useAttrs();
|
||||
|
||||
/** 仅当调用方显式传入对应 prop 时视为「固定」,否则交由表格 store */
|
||||
const hasExplicitTableProp = (propName: string): boolean => {
|
||||
const rawProps = (instance?.vnode.props || {}) as Record<string, unknown>;
|
||||
const kebabName = propName.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
||||
return propName in rawProps || kebabName in rawProps;
|
||||
};
|
||||
|
||||
const LAYOUT = {
|
||||
MOBILE: "prev, pager, next, sizes, jumper, total",
|
||||
IPAD: "prev, pager, next, jumper, total",
|
||||
@@ -268,13 +290,6 @@ const headerCellStyle = computed(() => ({
|
||||
...(props.headerCellStyle || {}), // 合并用户传入的样式
|
||||
}));
|
||||
|
||||
// 只有显式传入时才覆盖 ElTable 的原生默认值,避免继承的 Boolean props 把官方默认值冲掉。
|
||||
const hasExplicitTableProp = (propName: string): boolean => {
|
||||
const rawProps = (instance?.vnode.props || {}) as Record<string, unknown>;
|
||||
const kebabName = propName.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
||||
return propName in rawProps || kebabName in rawProps;
|
||||
};
|
||||
|
||||
const mergedTableProps = computed(() => ({
|
||||
...attrs,
|
||||
...props,
|
||||
|
||||
@@ -120,7 +120,7 @@ import { useI18n } from "vue-i18n";
|
||||
import { useSettingsStore } from "@/store/modules/setting.store";
|
||||
import { useUserStore } from "@/store/modules/user.store";
|
||||
import { useHeaderBar } from "@/hooks/core/useHeaderBar";
|
||||
import { themeAnimation } from "@/utils/ui/animation";
|
||||
import { themeAnimation } from "@/utils/ui";
|
||||
import { languageOptions } from "@/locales";
|
||||
import { LanguageEnum } from "@/enums/appEnum";
|
||||
import AppConfig from "@/config";
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
import AppConfig from "@/config";
|
||||
import loginIcon from "@imgs/svg/login_icon.svg";
|
||||
import { useConfigStore } from "@/store/modules/config.store";
|
||||
import { themeAnimation } from "@/utils/ui/animation";
|
||||
import { themeAnimation } from "@/utils/ui";
|
||||
|
||||
defineProps<{
|
||||
hideContent?: boolean;
|
||||
|
||||
Reference in New Issue
Block a user