diff --git a/.gitignore b/.gitignore index f825d6f0..1a7328ba 100755 --- a/.gitignore +++ b/.gitignore @@ -79,4 +79,6 @@ frontend/app/* .plan CLAUDE.md AGENTS.md -.codebuddy \ No newline at end of file +.codebuddy +.agent +.workbuddy \ No newline at end of file diff --git a/frontend/web/README.md b/frontend/web/README.md index 5fa61551..898fe02d 100755 --- a/frontend/web/README.md +++ b/frontend/web/README.md @@ -160,3 +160,102 @@ src/ - **格式化与校验**:`pnpm lint` - **提交**:husky、lint-staged、commitlint;可使用 **`pnpm commit`**(Commitizen / cz-git) + +element-plus 组件库 + 基础组件: + el-button 按钮 + el-container 容器 + el-header 头部 + el-aside 侧边栏 + el-main 主体 + el-footer 底部 + el-icon 图标 + el-row 行布局 + el-col 列布局单元 + el-link 链接 + el-text 文本内容 + el-scrollbar 滚动条 + el-space 空格 + el-splitter、el-splitter-panel 分割面板 + Typography 排版 + 配置组件: + el-config-provider 全局配置 + Form表单组件: + el-autocomplete 自动补全 + el-cascader 级联选择器 + el-checkbox 多选框 + el-color-picker-panel 颜色选择器面板 + el-color-picker 颜色选择器 + el-date-picker-panel 日期选择器面板 + el-radio-group、el-radio-button 日期选择器 + el-date-picker 日期时间选择器 + el-time-picker 时间选择器 + el-time-select 时间选择 + el-form、el-form-item 表单 + el-input 输入框 + el-input-number 数字输入框 + el-input-tag 标签输入框 + el-input-otp 一次性密码输入框 + el-mention 提及 + el-radio-group、el-radio 单选框 + el-rate 评分 + el-select、el-option 选择器 + el-select-v2 虚拟化选择器 + el-slider 滑块 + el-switch 开关按钮 + el-transfer 穿梭框 + el-tree-select 树形选择 + el-upload 上传器 + Data数据展示组件: + el-avatar 头像 + el-badge 徽章 + el-calendar 日历 + el-card 卡片 + el-carousel 走马灯 + el-collapse、el-collapse-item 折叠面板 + el-descriptions、el-descriptions-item 描述列表. + el-empty 空状态 + el-image 图片 + Infinite Scroll 无限滚动:在要实现滚动加载的列表上添加v-infinite-scroll,并赋值相应的加载方法,可实现滚动到底部时自动执行加载方法。 + el-pagination 分页 + el-progress 进度条 + el-result 结果 + el-skeleton 骨架屏 + el-table、el-table-col 表格 + el-table-v2 虚拟化表格 + el-tag 标签 + el-timeline、el-timeline-item 时间线 + el-tour、el-tour-step 漫游式引导 + el-tree 树形控件 + el-tree-v2 虚拟化树形控件 + el-statistic 统计组件 + el-segmented 分段控制器 + 导航类组件: + el-affix 固钉 + el-anchor、el-anchor-link 锚点 + el-backtop 回到顶部按钮 + el-breadcrumb、el-breadcrumb-item 面包屑 + el-dropdown、el-dropdown-menu、el-dropdown-item 下拉菜单 + el-menu、el-sub-menu、el-menu-item-group、el-menu-item 菜单 + el-page-header 页头 + el-steps、el-step 步骤条 + el-tabs、el-tab-pane 标签页 + Feedback 反馈组件: + el-alert 提示 + el-dialog 对话框 + el-drawer 抽屉 + loading:Element Plus 提供了两种调用 Loading 的方法:指令和服务。 + 对于自定义指令 v-loading,只需要绑定 boolean 值即可。 + 默认状况下,Loading 遮罩会插入到绑定元素的子节点。 + 通过添加 body 修饰符,可以使遮罩插入至 Dom 中的 body 上。 + ElMessage 消息:与 Notification 的区别是后者更多用于系统级通知的被动提醒,默认情况下在顶部显示并在 3 秒后消失。 + ElMessageBox 消息弹框: + ElMessageBox.confirm 方法以打开 confirm 框。它模拟了系统的 confirm 方法。 + ElMessageBox.prompt 方法以打开 prompt 框。它模拟了系统的 prompt 方法。 + ElNotification 通知 + el-popconfirm 气泡确认框 + el-popover 弹出框 + el-tooltip 文字提示 + Others 其他组件: + el-divider 分割线 + el-watermark 水印 diff --git a/frontend/web/src/App.vue b/frontend/web/src/App.vue index 0b90adf3..2929975d 100755 --- a/frontend/web/src/App.vue +++ b/frontend/web/src/App.vue @@ -7,17 +7,7 @@ shadow: 'never', }" > - - - - - - + @@ -25,15 +15,12 @@ import { computed, onBeforeMount, onErrorCaptured, onMounted, onUnmounted } from "vue"; import { ElMessage } from "element-plus"; import { useWindowSize } from "@vueuse/core"; -import { useAppStore, useUserStore } from "./store"; -import { useSettingsStore } from "./store/modules/setting.store"; -import { defaultSettings } from "./config/setting"; +import { useAppStore } from "./store"; import { ComponentSize } from "./enums/settings/layout.enum"; import { MOBILE_BREAKPOINT } from "./utils/constants/definitions"; -import { hexToRgba, toggleTransition } from "./utils/ui"; +import { toggleTransition } from "./utils/ui"; import { initializeTheme } from "./hooks/core/useTheme"; import { useAppBootstrap } from "@/hooks/core/useAppBootstrap"; -import { ThemeMode } from "./enums"; import en from "element-plus/es/locale/lang/en"; import zhCn from "element-plus/es/locale/lang/zh-cn"; import { router } from "@/router"; @@ -41,8 +28,6 @@ import { ElNotification } from "element-plus"; import { initIconifyAsync } from "./plugins/iconify"; const appStore = useAppStore(); -const settingsStore = useSettingsStore(); -const userStore = useUserStore(); const { width } = useWindowSize(); // H5 用小尺寸,桌面用用户设置的大小 @@ -50,32 +35,12 @@ const size = computed(() => { if (width.value < MOBILE_BREAKPOINT) return "small" as ComponentSize; return appStore.size as ComponentSize; }); -const showWatermark = computed(() => settingsStore.showWatermark); -const watermarkContent = defaultSettings.watermarkContent; // 根据语言设置返回对应的语言包 const locale = computed(() => { return appStore.language === "en" ? en : zhCn; }); -// 只有在启用 AI 助手且用户已登录时才显示 -const enableAiAssistant = computed(() => { - const isEnabled = settingsStore.userEnableAi; - const isLoggedIn = userStore.basicInfo && Object.keys(userStore.basicInfo).length > 0; - return isEnabled && isLoggedIn; -}); - -// 水印文字默认使用当前主题色(半透明),随主题色设置变化 -const fontColor = computed(() => { - const hex = settingsStore.themeColor || defaultSettings.themeColor; - const alpha = settingsStore.theme === ThemeMode.DARK ? 0.22 : 0.16; - try { - return hexToRgba(hex, alpha).rgba; - } catch { - return hexToRgba(defaultSettings.themeColor, alpha).rgba; - } -}); - /** * 应用根组件生命周期: * diff --git a/frontend/web/src/components/others/fa-copy-button/index.vue b/frontend/web/src/components/actions/fa-copy-button/index.vue similarity index 100% rename from frontend/web/src/components/others/fa-copy-button/index.vue rename to frontend/web/src/components/actions/fa-copy-button/index.vue diff --git a/frontend/web/src/components/others/fa-icon-button/index.vue b/frontend/web/src/components/actions/fa-icon-button/index.vue similarity index 100% rename from frontend/web/src/components/others/fa-icon-button/index.vue rename to frontend/web/src/components/actions/fa-icon-button/index.vue diff --git a/frontend/web/src/components/others/fa-size-select/index.vue b/frontend/web/src/components/actions/fa-size-select/index.vue similarity index 100% rename from frontend/web/src/components/others/fa-size-select/index.vue rename to frontend/web/src/components/actions/fa-size-select/index.vue diff --git a/frontend/web/src/components/others/fa-upload/index.vue b/frontend/web/src/components/actions/fa-upload/index.vue similarity index 96% rename from frontend/web/src/components/others/fa-upload/index.vue rename to frontend/web/src/components/actions/fa-upload/index.vue index 71cb77ec..9129ee31 100644 --- a/frontend/web/src/components/others/fa-upload/index.vue +++ b/frontend/web/src/components/actions/fa-upload/index.vue @@ -20,7 +20,6 @@ :tool="true" :show-preview="true" :original-graph="false" - :file-type="cropFileType" :title="cropInnerTitle" :preview-title="cropPreviewTitle" @update:img-url="onCropConfirm" @@ -129,7 +128,6 @@ interface Props { cropCutWidth?: number; cropCutHeight?: number; cropQuality?: number; - cropFileType?: "png" | "jpeg" | "webp"; cropDialogTitle?: string; cropInnerTitle?: string; cropPreviewTitle?: string; @@ -151,7 +149,6 @@ const props = withDefaults(defineProps(), { cropCutWidth: 360, cropCutHeight: 200, cropQuality: 1, - cropFileType: "jpeg", cropDialogTitle: "裁剪图片", cropInnerTitle: "调整图片", cropPreviewTitle: "预览", @@ -167,6 +164,7 @@ const internalFileList = ref([]); const cropVisible = ref(false); const cropSourceUrl = ref(""); +const cropFileName = ref(""); function revokeCropUrl() { if (cropSourceUrl.value.startsWith("blob:")) { @@ -185,9 +183,7 @@ function onCropError() { async function onCropConfirm(dataURL: string) { try { - const ext = - props.cropFileType === "png" ? "png" : props.cropFileType === "webp" ? "webp" : "jpg"; - const file = dataURLToFile(dataURL, `upload.${ext}`); + const file = dataURLToFile(dataURL, cropFileName.value); await uploadFileInternal(file); cropVisible.value = false; revokeCropUrl(); @@ -293,6 +289,11 @@ function handleBeforeUpload(file: UploadRawFile) { } if (props.enableCrop) { + // 非 canvas 兼容格式(如 svg/gif/bmp 等),跳过裁剪直接上传 + if (file.type !== "image/png" && file.type !== "image/jpeg" && file.type !== "image/webp") { + return true; + } + cropFileName.value = file.name; revokeCropUrl(); cropSourceUrl.value = URL.createObjectURL(file); cropVisible.value = true; diff --git a/frontend/web/src/components/cards/fa-card-grid/index.vue b/frontend/web/src/components/cards/fa-card-grid/index.vue index 838ae579..95c23ad5 100644 --- a/frontend/web/src/components/cards/fa-card-grid/index.vue +++ b/frontend/web/src/components/cards/fa-card-grid/index.vue @@ -95,7 +95,7 @@ import { ElSkeletonItem, ElScrollbar, } from "element-plus"; -import FaPagination from "@/components/others/fa-pagination/index.vue"; +import FaPagination from "@/components/tables/fa-pagination/index.vue"; defineOptions({ name: "FaCardGrid" }); diff --git a/frontend/web/src/components/others/fa-calendar/index.vue b/frontend/web/src/components/display/fa-calendar/index.vue similarity index 100% rename from frontend/web/src/components/others/fa-calendar/index.vue rename to frontend/web/src/components/display/fa-calendar/index.vue diff --git a/frontend/web/src/components/display/fa-carousel/index.vue b/frontend/web/src/components/display/fa-carousel/index.vue new file mode 100644 index 00000000..e17f40cf --- /dev/null +++ b/frontend/web/src/components/display/fa-carousel/index.vue @@ -0,0 +1,65 @@ + + + diff --git a/frontend/web/src/components/others/fa-descriptions/index.vue b/frontend/web/src/components/display/fa-descriptions/index.vue similarity index 100% rename from frontend/web/src/components/others/fa-descriptions/index.vue rename to frontend/web/src/components/display/fa-descriptions/index.vue diff --git a/frontend/web/src/components/others/fa-json-pretty/index.vue b/frontend/web/src/components/display/fa-json-pretty/index.vue similarity index 94% rename from frontend/web/src/components/others/fa-json-pretty/index.vue rename to frontend/web/src/components/display/fa-json-pretty/index.vue index 4cf2b4e0..39b4ef1a 100644 --- a/frontend/web/src/components/others/fa-json-pretty/index.vue +++ b/frontend/web/src/components/display/fa-json-pretty/index.vue @@ -1,5 +1,5 @@ diff --git a/frontend/web/src/components/result/fa-result-page/index.vue b/frontend/web/src/components/display/fa-result-page/index.vue similarity index 59% rename from frontend/web/src/components/result/fa-result-page/index.vue rename to frontend/web/src/components/display/fa-result-page/index.vue index 482ed8c3..9b419385 100644 --- a/frontend/web/src/components/result/fa-result-page/index.vue +++ b/frontend/web/src/components/display/fa-result-page/index.vue @@ -1,29 +1,29 @@ diff --git a/frontend/web/src/components/display/fa-statistic/index.vue b/frontend/web/src/components/display/fa-statistic/index.vue new file mode 100644 index 00000000..c146a315 --- /dev/null +++ b/frontend/web/src/components/display/fa-statistic/index.vue @@ -0,0 +1,88 @@ + + + diff --git a/frontend/web/src/components/others/fa-status-tag/index.vue b/frontend/web/src/components/display/fa-status-tag/index.vue similarity index 100% rename from frontend/web/src/components/others/fa-status-tag/index.vue rename to frontend/web/src/components/display/fa-status-tag/index.vue diff --git a/frontend/web/src/components/forms/fa-autocomplete/index.vue b/frontend/web/src/components/forms/fa-autocomplete/index.vue new file mode 100644 index 00000000..bbda584e --- /dev/null +++ b/frontend/web/src/components/forms/fa-autocomplete/index.vue @@ -0,0 +1,70 @@ + + + + diff --git a/frontend/web/src/components/forms/fa-cascader/index.vue b/frontend/web/src/components/forms/fa-cascader/index.vue new file mode 100644 index 00000000..47129589 --- /dev/null +++ b/frontend/web/src/components/forms/fa-cascader/index.vue @@ -0,0 +1,64 @@ + + + diff --git a/frontend/web/src/components/forms/fa-input-tag/index.vue b/frontend/web/src/components/forms/fa-input-tag/index.vue new file mode 100644 index 00000000..7019bed1 --- /dev/null +++ b/frontend/web/src/components/forms/fa-input-tag/index.vue @@ -0,0 +1,51 @@ + + + diff --git a/frontend/web/src/components/forms/fa-mention/index.vue b/frontend/web/src/components/forms/fa-mention/index.vue new file mode 100644 index 00000000..1f397c9d --- /dev/null +++ b/frontend/web/src/components/forms/fa-mention/index.vue @@ -0,0 +1,79 @@ + + + diff --git a/frontend/web/src/components/forms/fa-rate/index.vue b/frontend/web/src/components/forms/fa-rate/index.vue new file mode 100644 index 00000000..5d2550c7 --- /dev/null +++ b/frontend/web/src/components/forms/fa-rate/index.vue @@ -0,0 +1,64 @@ + + + diff --git a/frontend/web/src/components/forms/fa-search-bar/FaUserTableSelect.vue b/frontend/web/src/components/forms/fa-search-bar/FaUserTableSelect.vue index a81048e9..d4654379 100644 --- a/frontend/web/src/components/forms/fa-search-bar/FaUserTableSelect.vue +++ b/frontend/web/src/components/forms/fa-search-bar/FaUserTableSelect.vue @@ -21,8 +21,8 @@ diff --git a/frontend/web/src/components/forms/fa-slider/index.vue b/frontend/web/src/components/forms/fa-slider/index.vue new file mode 100644 index 00000000..39a4b664 --- /dev/null +++ b/frontend/web/src/components/forms/fa-slider/index.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/frontend/web/src/components/forms/fa-time-select/index.vue b/frontend/web/src/components/forms/fa-time-select/index.vue new file mode 100644 index 00000000..5f955d6e --- /dev/null +++ b/frontend/web/src/components/forms/fa-time-select/index.vue @@ -0,0 +1,59 @@ + + + diff --git a/frontend/web/src/components/forms/fa-transfer/index.vue b/frontend/web/src/components/forms/fa-transfer/index.vue new file mode 100644 index 00000000..fda26b4a --- /dev/null +++ b/frontend/web/src/components/forms/fa-transfer/index.vue @@ -0,0 +1,66 @@ + + + diff --git a/frontend/web/src/components/modal/fa-dialog/index.vue b/frontend/web/src/components/modal/fa-dialog/index.vue index 5b3ac54e..e20d319d 100644 --- a/frontend/web/src/components/modal/fa-dialog/index.vue +++ b/frontend/web/src/components/modal/fa-dialog/index.vue @@ -68,7 +68,7 @@ import type { DialogProps } from "element-plus"; import { ElDialog } from "element-plus"; import { computed, ref, useAttrs, watch, onMounted, onUnmounted } from "vue"; -import FaIconButton from "@/components/others/fa-icon-button/index.vue"; +import FaIconButton from "@/components/actions/fa-icon-button/index.vue"; defineOptions({ name: "FaDialog", inheritAttrs: false }); diff --git a/frontend/web/src/components/modal/fa-drawer/index.vue b/frontend/web/src/components/modal/fa-drawer/index.vue index 6ab14d01..f4f5c2fc 100644 --- a/frontend/web/src/components/modal/fa-drawer/index.vue +++ b/frontend/web/src/components/modal/fa-drawer/index.vue @@ -59,7 +59,7 @@ diff --git a/frontend/web/src/components/others/fa-guide/index.vue b/frontend/web/src/components/navigation/fa-guide/index.vue similarity index 100% rename from frontend/web/src/components/others/fa-guide/index.vue rename to frontend/web/src/components/navigation/fa-guide/index.vue diff --git a/frontend/web/src/components/others/fa-interval-tab/index.vue b/frontend/web/src/components/navigation/fa-interval-tab/index.vue similarity index 100% rename from frontend/web/src/components/others/fa-interval-tab/index.vue rename to frontend/web/src/components/navigation/fa-interval-tab/index.vue diff --git a/frontend/web/src/components/navigation/fa-menu-item-group/index.vue b/frontend/web/src/components/navigation/fa-menu-item-group/index.vue new file mode 100644 index 00000000..d8c179f6 --- /dev/null +++ b/frontend/web/src/components/navigation/fa-menu-item-group/index.vue @@ -0,0 +1,16 @@ + + + diff --git a/frontend/web/src/components/others/fa-menu-right/index.vue b/frontend/web/src/components/navigation/fa-menu-right/index.vue similarity index 100% rename from frontend/web/src/components/others/fa-menu-right/index.vue rename to frontend/web/src/components/navigation/fa-menu-right/index.vue diff --git a/frontend/web/src/components/others/fa-menu-route-icon/index.vue b/frontend/web/src/components/navigation/fa-menu-route-icon/index.vue similarity index 100% rename from frontend/web/src/components/others/fa-menu-route-icon/index.vue rename to frontend/web/src/components/navigation/fa-menu-route-icon/index.vue diff --git a/frontend/web/src/components/others/fa-menu-tree-table/index.vue b/frontend/web/src/components/navigation/fa-menu-tree-table/index.vue similarity index 98% rename from frontend/web/src/components/others/fa-menu-tree-table/index.vue rename to frontend/web/src/components/navigation/fa-menu-tree-table/index.vue index d7fd1ef1..5df4f629 100644 --- a/frontend/web/src/components/others/fa-menu-tree-table/index.vue +++ b/frontend/web/src/components/navigation/fa-menu-tree-table/index.vue @@ -47,7 +47,7 @@ diff --git a/frontend/web/src/components/others/fa-infinite-scroll/index.vue b/frontend/web/src/components/others/fa-infinite-scroll/index.vue new file mode 100644 index 00000000..a9bf2dbc --- /dev/null +++ b/frontend/web/src/components/others/fa-infinite-scroll/index.vue @@ -0,0 +1,66 @@ + + + diff --git a/frontend/web/src/components/others/fa-transfer/index.vue b/frontend/web/src/components/others/fa-transfer/index.vue new file mode 100644 index 00000000..e95e848d --- /dev/null +++ b/frontend/web/src/components/others/fa-transfer/index.vue @@ -0,0 +1,68 @@ + + + diff --git a/frontend/web/src/components/others/fa-tree-v2/index.vue b/frontend/web/src/components/others/fa-tree-v2/index.vue new file mode 100644 index 00000000..546d6e86 --- /dev/null +++ b/frontend/web/src/components/others/fa-tree-v2/index.vue @@ -0,0 +1,53 @@ + + + diff --git a/frontend/web/src/components/others/fa-watermark/index.vue b/frontend/web/src/components/others/fa-watermark/index.vue index 242fa248..6b81d7e0 100644 --- a/frontend/web/src/components/others/fa-watermark/index.vue +++ b/frontend/web/src/components/others/fa-watermark/index.vue @@ -20,7 +20,6 @@ + + diff --git a/frontend/web/src/layouts/fa-work-tab/index.vue b/frontend/web/src/layouts/fa-work-tab/index.vue index 8ead1f9d..cab0b105 100644 --- a/frontend/web/src/layouts/fa-work-tab/index.vue +++ b/frontend/web/src/layouts/fa-work-tab/index.vue @@ -165,7 +165,7 @@ import { useI18n } from "vue-i18n"; import { storeToRefs } from "pinia"; import { ElMessage } from "element-plus"; import { useWorktabStore, useUserStore, useSettingsStore } from "@stores"; -import { MenuItemType } from "@/components/others/fa-menu-right/index.vue"; +import { MenuItemType } from "@/components/navigation/fa-menu-right/index.vue"; import { useCommon } from "@/hooks/core/useCommon"; import { formatMenuTitle, quickStartManager } from "@utils"; import type { WorkTab } from "@/types/store"; diff --git a/frontend/web/src/layouts/index.vue b/frontend/web/src/layouts/index.vue index 291b3867..a48b306f 100755 --- a/frontend/web/src/layouts/index.vue +++ b/frontend/web/src/layouts/index.vue @@ -8,6 +8,9 @@ --> @@ -44,12 +50,21 @@ * → settingStore.showGuide=false → 后续不再显示 */ import { computed } from "vue"; -import { useAppStore, useSettingsStore } from "@stores"; +import { useAppStore, useSettingsStore, useUserStore } from "@stores"; +import AppConfig from "@/config"; defineOptions({ name: "AppLayout" }); const appStore = useAppStore(); const settingStore = useSettingsStore(); +const userStore = useUserStore(); + +// ── AI 助手 ── +const enableAiAssistant = computed(() => { + const isEnabled = settingStore.userEnableAi; + const isLoggedIn = userStore.basicInfo && Object.keys(userStore.basicInfo).length > 0; + return isEnabled && isLoggedIn; +}); /** 新手指引显隐 —— session 级状态,首次登录/注册后自动弹出 */ const guideVisible = computed({ diff --git a/frontend/web/src/utils/table/statusFormatter.ts b/frontend/web/src/utils/table/statusFormatter.ts index 6e520909..99dad38c 100644 --- a/frontend/web/src/utils/table/statusFormatter.ts +++ b/frontend/web/src/utils/table/statusFormatter.ts @@ -28,7 +28,7 @@ * ]) */ import { h, type VNode } from "vue"; -import StatusTag from "@/components/others/fa-status-tag/index.vue"; +import StatusTag from "@/components/display/fa-status-tag/index.vue"; import type { ColumnOption, StatusColumnConfig } from "@/types/component"; /** ElTag 支持的 type */ export type StatusType = "primary" | "success" | "warning" | "danger" | "info"; diff --git a/frontend/web/src/views/dashboard/home/modules/todo-list.vue b/frontend/web/src/views/dashboard/home/modules/todo-list.vue index a083900b..a69124ca 100755 --- a/frontend/web/src/views/dashboard/home/modules/todo-list.vue +++ b/frontend/web/src/views/dashboard/home/modules/todo-list.vue @@ -10,7 +10,7 @@ - +
- + {{ currentTime }}
diff --git a/frontend/web/src/views/fastlink/current/profile.vue b/frontend/web/src/views/fastlink/current/profile.vue index 77d0f970..9fce09d9 100644 --- a/frontend/web/src/views/fastlink/current/profile.vue +++ b/frontend/web/src/views/fastlink/current/profile.vue @@ -463,6 +463,7 @@ const uploadRef = ref>(); const avatarCropVisible = ref(false); const avatarCropSrc = ref(""); +const avatarCropName = ref(""); function revokeAvatarCropSrc() { if (avatarCropSrc.value.startsWith("blob:")) { @@ -481,7 +482,7 @@ function onAvatarCropImgError() { async function onAvatarCropConfirm(dataURL: string) { try { - const file = dataURLToFile(dataURL, "avatar.jpg"); + const file = dataURLToFile(dataURL, avatarCropName.value); const formData = new FormData(); formData.append("file", file); const response = await UserAPI.uploadCurrentUserAvatar(formData); @@ -664,8 +665,18 @@ const handleAvatarFileChange = (file: UploadFile) => { fileList.value = []; return; } + + const raw = file.raw; + avatarCropName.value = raw.name; + + // 非 canvas 兼容格式(如 svg),跳过裁剪直接上传原文件 + if (raw.type !== "image/png" && raw.type !== "image/jpeg" && raw.type !== "image/webp") { + uploadRef.value?.submit(); + return; + } + revokeAvatarCropSrc(); - avatarCropSrc.value = URL.createObjectURL(file.raw); + avatarCropSrc.value = URL.createObjectURL(raw); avatarCropVisible.value = true; uploadRef.value?.clearFiles(); fileList.value = []; diff --git a/frontend/web/src/views/fastlink/fachat/index.vue b/frontend/web/src/views/fastlink/fachat/index.vue index cb5b8e8d..d73fe260 100644 --- a/frontend/web/src/views/fastlink/fachat/index.vue +++ b/frontend/web/src/views/fastlink/fachat/index.vue @@ -47,14 +47,12 @@ :class="{ 'bg-active-color': selectedPerson?.id === item.id }" @click="selectPerson(item)" > -
- - {{ item.name.charAt(0) }} - -
+
+ + + {{ item.name.charAt(0) }} + +
diff --git a/frontend/web/src/views/module_ai/chat/components/FaAiModelConfigPanel.vue b/frontend/web/src/views/module_ai/chat/components/FaAiModelConfigPanel.vue index 52e81311..db1301c9 100644 --- a/frontend/web/src/views/module_ai/chat/components/FaAiModelConfigPanel.vue +++ b/frontend/web/src/views/module_ai/chat/components/FaAiModelConfigPanel.vue @@ -39,20 +39,24 @@
-
-
- - -
-
添加你的第一个 AI 模型
-
- 支持 OpenAI、DeepSeek、Ollama 等任何 OpenAI 兼容服务
- 配置后即可在 AI 助手页一键切换 -
+ + + 立即添加 -
+
@@ -587,17 +591,6 @@ onMounted(loadList); color: var(--el-text-color-primary); } -/* 空状态 */ -.empty-state { - display: flex; - flex-direction: column; - gap: 12px; - align-items: center; - justify-content: center; - padding: 48px 20px; - text-align: center; -} - .empty-illust { position: relative; display: flex; @@ -605,13 +598,6 @@ onMounted(loadList); justify-content: center; width: 80px; height: 80px; - margin-bottom: 8px; -} - -.empty-icon { - position: relative; - z-index: 1; - color: var(--el-color-primary); } .empty-icon-bg { diff --git a/frontend/web/src/views/module_ai/chat/components/FaChatInput.vue b/frontend/web/src/views/module_ai/chat/components/FaChatInput.vue index 6bff4dce..5484cd7f 100644 --- a/frontend/web/src/views/module_ai/chat/components/FaChatInput.vue +++ b/frontend/web/src/views/module_ai/chat/components/FaChatInput.vue @@ -32,9 +32,11 @@ >
- - {{ activeModelName }} - + + + {{ activeModelName }} + + diff --git a/frontend/web/src/views/module_ai/memory/index.vue b/frontend/web/src/views/module_ai/memory/index.vue index 1671c159..de305911 100644 --- a/frontend/web/src/views/module_ai/memory/index.vue +++ b/frontend/web/src/views/module_ai/memory/index.vue @@ -58,10 +58,12 @@ @blur="handleSaveTitle(row)" @keyup.enter="handleSaveTitle(row)" /> - - {{ row.title || "未命名会话" }} - - + + + {{ row.title || "未命名会话" }} + + + @@ -153,7 +155,7 @@ import type { FormItem } from "@/components/forms/fa-form/index.vue"; import type FaForm from "@/components/forms/fa-form/index.vue"; import { formatToDateTime, renderTableOperationCell, type TableOperationAction } from "@utils"; import type { ColumnOption } from "@/types/component"; -import FaDescriptions from "@/components/others/fa-descriptions/index.vue"; +import FaDescriptions from "@/components/display/fa-descriptions/index.vue"; import FaTable from "@/components/tables/fa-table/index.vue"; import FaTableHeader from "@/components/tables/fa-table-header/index.vue"; @@ -355,7 +357,7 @@ const { dialogVisible, closeDialog } = useCrudDialog(); const detailFormData = ref>({}); -const memoryDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] = +const memoryDetailItems: import("@/components/display/fa-descriptions/index.vue").DescriptionsItem[] = [ { label: "会话ID", prop: "session_id" }, { label: "标题", prop: "title" }, diff --git a/frontend/web/src/views/module_example/demo/index.vue b/frontend/web/src/views/module_example/demo/index.vue index 9e7720b6..8e74a380 100644 --- a/frontend/web/src/views/module_example/demo/index.vue +++ b/frontend/web/src/views/module_example/demo/index.vue @@ -166,7 +166,7 @@ import DemoAPI, { type DemoTable, } from "@/api/module_example/demo"; import type { ColumnOption } from "@/types/component"; -import FaDescriptions from "@/components/others/fa-descriptions/index.vue"; +import FaDescriptions from "@/components/display/fa-descriptions/index.vue"; import FaForm from "@/components/forms/fa-form/index.vue"; import FaTableHeader from "@/components/tables/fa-table-header/index.vue"; @@ -368,7 +368,7 @@ const { dialogVisible } = useCrudDialog(); const detailFormData = ref({}); -const demoDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] = +const demoDetailItems: import("@/components/display/fa-descriptions/index.vue").DescriptionsItem[] = [ { label: "名称", prop: "name" }, { label: "UUID", prop: "uuid" }, diff --git a/frontend/web/src/views/module_monitor/cache/index.vue b/frontend/web/src/views/module_monitor/cache/index.vue index 0b8aabbf..e6c4860b 100644 --- a/frontend/web/src/views/module_monitor/cache/index.vue +++ b/frontend/web/src/views/module_monitor/cache/index.vue @@ -112,7 +112,7 @@ />
-
+ -
+ @@ -175,7 +175,7 @@ />
-
+ -
+ @@ -243,7 +243,7 @@
-
+ @@ -261,7 +261,7 @@ /> -
+
diff --git a/frontend/web/src/views/module_monitor/online/index.vue b/frontend/web/src/views/module_monitor/online/index.vue index cc655bbc..19d45ed3 100644 --- a/frontend/web/src/views/module_monitor/online/index.vue +++ b/frontend/web/src/views/module_monitor/online/index.vue @@ -63,7 +63,7 @@ import type { TableOperationAction } from "@/utils/table"; import { renderTableOperationCell } from "@utils"; import type FaSearchBar from "@/components/forms/fa-search-bar/index.vue"; import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue"; -import FaCopyButton from "@/components/others/fa-copy-button/index.vue"; +import FaCopyButton from "@/components/actions/fa-copy-button/index.vue"; import type { ColumnOption } from "@/types/component"; import FaTableHeader from "@/components/tables/fa-table-header/index.vue"; diff --git a/frontend/web/src/views/module_system/auth/login/components/backdrops/FaLoginLeftView.vue b/frontend/web/src/views/module_system/auth/login/components/backdrops/FaLoginLeftView.vue index 3a1445f8..a44f753a 100644 --- a/frontend/web/src/views/module_system/auth/login/components/backdrops/FaLoginLeftView.vue +++ b/frontend/web/src/views/module_system/auth/login/components/backdrops/FaLoginLeftView.vue @@ -6,9 +6,11 @@

{{ siteTitle }}

-
- {{ displayVersion }} -
+ +
+ {{ displayVersion }} +
+
diff --git a/frontend/web/src/views/module_system/auth/login/components/widgets/FaAuthTopBar.vue b/frontend/web/src/views/module_system/auth/login/components/widgets/FaAuthTopBar.vue index d255501a..0e70878d 100644 --- a/frontend/web/src/views/module_system/auth/login/components/widgets/FaAuthTopBar.vue +++ b/frontend/web/src/views/module_system/auth/login/components/widgets/FaAuthTopBar.vue @@ -8,9 +8,11 @@

{{ siteTitle }}

-
- {{ displayVersion }} -
+ +
+ {{ displayVersion }} +
+
diff --git a/frontend/web/src/views/module_system/dept/index.vue b/frontend/web/src/views/module_system/dept/index.vue index 5e1dd221..2660aed2 100644 --- a/frontend/web/src/views/module_system/dept/index.vue +++ b/frontend/web/src/views/module_system/dept/index.vue @@ -287,7 +287,7 @@ const { dialogVisible } = useCrudDialog(); const detailFormData = ref({ code: "" }); -const deptDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] = +const deptDetailItems: import("@/components/display/fa-descriptions/index.vue").DescriptionsItem[] = [ { label: "部门名称", prop: "name" }, { label: "部门编码", prop: "code" }, diff --git a/frontend/web/src/views/module_system/dict/components/DictDataPanel.vue b/frontend/web/src/views/module_system/dict/components/DictDataPanel.vue index 99043063..adcac03c 100644 --- a/frontend/web/src/views/module_system/dict/components/DictDataPanel.vue +++ b/frontend/web/src/views/module_system/dict/components/DictDataPanel.vue @@ -407,7 +407,7 @@ const { dialogVisible } = useCrudDialog(); const detailFormData = ref({}); -const dictDataDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] = +const dictDataDetailItems: import("@/components/display/fa-descriptions/index.vue").DescriptionsItem[] = [ { label: "数据标签", prop: "dict_label" }, { label: "数据类型", prop: "dict_type" }, diff --git a/frontend/web/src/views/module_system/dict/index.vue b/frontend/web/src/views/module_system/dict/index.vue index 16b440c6..ad44c81d 100644 --- a/frontend/web/src/views/module_system/dict/index.vue +++ b/frontend/web/src/views/module_system/dict/index.vue @@ -120,9 +120,9 @@ import type FaSearchBar from "@/components/forms/fa-search-bar/index.vue"; import type { FormItem } from "@/components/forms/fa-form/index.vue"; import FaForm from "@/components/forms/fa-form/index.vue"; import DictDataPanel from "./components/DictDataPanel.vue"; -import FaStatusTag from "@/components/others/fa-status-tag/index.vue"; +import FaStatusTag from "@/components/display/fa-status-tag/index.vue"; import FaTableHeader from "@/components/tables/fa-table-header/index.vue"; -import FaDescriptions from "@/components/others/fa-descriptions/index.vue"; +import FaDescriptions from "@/components/display/fa-descriptions/index.vue"; defineOptions({ name: "Dict", @@ -174,7 +174,7 @@ const { dialogVisible } = useCrudDialog(); const detailFormData = ref({}); -const dictDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] = +const dictDetailItems: import("@/components/display/fa-descriptions/index.vue").DescriptionsItem[] = [ { label: "字典名称", prop: "dict_name" }, { label: "字典类型", prop: "dict_type", slot: "dict_type" }, diff --git a/frontend/web/src/views/module_system/log/index.vue b/frontend/web/src/views/module_system/log/index.vue index 6d84cced..02e18417 100644 --- a/frontend/web/src/views/module_system/log/index.vue +++ b/frontend/web/src/views/module_system/log/index.vue @@ -199,11 +199,11 @@ import { import type { IObject } from "@/components/modal/types"; import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue"; import type FaSearchBar from "@/components/forms/fa-search-bar/index.vue"; -import FaStatusTag from "@/components/others/fa-status-tag/index.vue"; -import FaCopyButton from "@/components/others/fa-copy-button/index.vue"; +import FaStatusTag from "@/components/display/fa-status-tag/index.vue"; +import FaCopyButton from "@/components/actions/fa-copy-button/index.vue"; import type { ColumnOption } from "@/types/component"; import FaTableHeader from "@/components/tables/fa-table-header/index.vue"; -import FaDescriptions from "@/components/others/fa-descriptions/index.vue"; +import FaDescriptions from "@/components/display/fa-descriptions/index.vue"; defineOptions({ name: "Log", @@ -369,7 +369,7 @@ const opExportContentConfig = computed(() => ({ const opFormData = ref({} as OperationLogTable); -const opDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] = [ +const opDetailItems: import("@/components/display/fa-descriptions/index.vue").DescriptionsItem[] = [ { label: "描述", prop: "description", span: 8 }, { label: "请求路径", prop: "request_path" }, { label: "请求方法", prop: "request_method", slot: "request_method" }, diff --git a/frontend/web/src/views/module_system/menu/index.vue b/frontend/web/src/views/module_system/menu/index.vue index cb211964..ea4e79bc 100644 --- a/frontend/web/src/views/module_system/menu/index.vue +++ b/frontend/web/src/views/module_system/menu/index.vue @@ -19,10 +19,7 @@ @reset="onResetSearch" /> - - - - + ("pc"); +const menuSegmentedOptions = [ + { label: "Web 菜单", value: "pc" }, + { label: "APP 菜单", value: "app" }, +]; const searchForm = ref({ name: undefined, status: undefined, @@ -497,7 +498,7 @@ const createParentLocked = ref(false); const detailFormData = ref({}); -const menuDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] = +const menuDetailItems: import("@/components/display/fa-descriptions/index.vue").DescriptionsItem[] = [ { label: "编号", prop: "id" }, { label: "菜单名称", prop: "name" }, diff --git a/frontend/web/src/views/module_system/notice/index.vue b/frontend/web/src/views/module_system/notice/index.vue index 7dc3b849..32f3fc78 100644 --- a/frontend/web/src/views/module_system/notice/index.vue +++ b/frontend/web/src/views/module_system/notice/index.vue @@ -144,7 +144,7 @@ import type { FormItem } from "@/components/forms/fa-form/index.vue"; import FaForm from "@/components/forms/fa-form/index.vue"; import { ElMessage } from "element-plus"; import FaTableHeader from "@/components/tables/fa-table-header/index.vue"; -import FaDescriptions from "@/components/others/fa-descriptions/index.vue"; +import FaDescriptions from "@/components/display/fa-descriptions/index.vue"; defineOptions({ name: "Notice", @@ -243,7 +243,7 @@ const { dialogVisible } = useCrudDialog(); const detailFormData = ref({}); -const noticeDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] = +const noticeDetailItems: import("@/components/display/fa-descriptions/index.vue").DescriptionsItem[] = [ { label: "标题", prop: "notice_title" }, { label: "类型", prop: "notice_type", slot: "notice_type" }, diff --git a/frontend/web/src/views/module_system/position/index.vue b/frontend/web/src/views/module_system/position/index.vue index a13d5e8a..3c680ac8 100644 --- a/frontend/web/src/views/module_system/position/index.vue +++ b/frontend/web/src/views/module_system/position/index.vue @@ -377,7 +377,7 @@ const positionExportContentConfig = computed(() => ({ const detailFormData = ref({}); -const positionDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] = +const positionDetailItems: import("@/components/display/fa-descriptions/index.vue").DescriptionsItem[] = [ { label: "岗位名称", prop: "name" }, { label: "排序", prop: "order" }, diff --git a/frontend/web/src/views/module_system/role/components/FaPermissonDrawer.vue b/frontend/web/src/views/module_system/role/components/FaPermissonDrawer.vue index 75566531..fa496899 100644 --- a/frontend/web/src/views/module_system/role/components/FaPermissonDrawer.vue +++ b/frontend/web/src/views/module_system/role/components/FaPermissonDrawer.vue @@ -89,7 +89,7 @@ import MenuAPI, { MenuTable } from "@/api/module_system/menu"; import { DeviceEnum } from "@/enums/settings/device.enum"; import { useUserStore, useAppStore } from "@stores"; import { ElMessage } from "element-plus"; -import type FaMenuTreeTable from "@/components/others/fa-menu-tree-table/index.vue"; +import type FaMenuTreeTable from "@/components/navigation/fa-menu-tree-table/index.vue"; import FaDrawer from "@/components/modal/fa-drawer/index.vue"; const props = defineProps<{ diff --git a/frontend/web/src/views/module_system/role/index.vue b/frontend/web/src/views/module_system/role/index.vue index 1efb85e3..afbdb165 100644 --- a/frontend/web/src/views/module_system/role/index.vue +++ b/frontend/web/src/views/module_system/role/index.vue @@ -172,11 +172,11 @@ import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue" import type FaSearchBar from "@/components/forms/fa-search-bar/index.vue"; import type { FormItem } from "@/components/forms/fa-form/index.vue"; import FaForm from "@/components/forms/fa-form/index.vue"; -import StatusTag from "@/components/others/fa-status-tag/index.vue"; +import StatusTag from "@/components/display/fa-status-tag/index.vue"; import { ElMessage } from "element-plus"; import FaPermissonDrawer from "./components/FaPermissonDrawer.vue"; import FaTableHeader from "@/components/tables/fa-table-header/index.vue"; -import FaDescriptions from "@/components/others/fa-descriptions/index.vue"; +import FaDescriptions from "@/components/display/fa-descriptions/index.vue"; defineOptions({ name: "Role", @@ -382,7 +382,7 @@ const { dialogVisible } = useCrudDialog(); const detailFormData = ref({} as RoleTable); -const roleDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] = +const roleDetailItems: import("@/components/display/fa-descriptions/index.vue").DescriptionsItem[] = [ { label: "角色名称", prop: "name" }, { label: "排序", prop: "order" }, diff --git a/frontend/web/src/views/module_system/ticket/index.vue b/frontend/web/src/views/module_system/ticket/index.vue index f6c1a331..664acd2b 100644 --- a/frontend/web/src/views/module_system/ticket/index.vue +++ b/frontend/web/src/views/module_system/ticket/index.vue @@ -151,7 +151,7 @@ @confirm="handleDialogConfirm" > @@ -397,7 +397,7 @@ import { Plus, Delete, MoreFilled, CircleClose } from "@element-plus/icons-vue"; import DOMPurify from "dompurify"; import FaForm from "@/components/forms/fa-form/index.vue"; import FaTableHeader from "@/components/tables/fa-table-header/index.vue"; -import FaDescriptions from "@/components/others/fa-descriptions/index.vue"; +import FaDescriptions from "@/components/display/fa-descriptions/index.vue"; import FaCardGrid from "@/components/cards/fa-card-grid/index.vue"; defineOptions({ @@ -623,7 +623,7 @@ const detailFormData = ref( {} as TicketTable & { reply_content?: string } ); -const ticketDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] = +const ticketDetailItems: import("@/components/display/fa-descriptions/index.vue").DescriptionsItem[] = [ { label: "工单标题", prop: "title", span: 4 }, { label: "工单类型", prop: "ticket_type", slot: "ticket_type" }, diff --git a/frontend/web/src/views/module_system/user/index.vue b/frontend/web/src/views/module_system/user/index.vue index 4306b58f..79206582 100644 --- a/frontend/web/src/views/module_system/user/index.vue +++ b/frontend/web/src/views/module_system/user/index.vue @@ -221,7 +221,7 @@ import PositionAPI from "@/api/module_system/position"; import DeptAPI from "@/api/module_system/dept"; import RoleAPI from "@/api/module_system/role"; import { useUserStore } from "@stores"; -import type { DescriptionsItem } from "@/components/others/fa-descriptions/index.vue"; +import type { DescriptionsItem } from "@/components/display/fa-descriptions/index.vue"; import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue"; import FaSearchBar from "@/components/forms/fa-search-bar/index.vue"; import type { FormItem } from "@/components/forms/fa-form/index.vue"; @@ -229,7 +229,7 @@ import FaForm from "@/components/forms/fa-form/index.vue"; import FaTableHeader from "@/components/tables/fa-table-header/index.vue"; import FaTable from "@/components/tables/fa-table/index.vue"; import FaDrawer from "@/components/modal/fa-drawer/index.vue"; -import FaDescriptions from "@/components/others/fa-descriptions/index.vue"; +import FaDescriptions from "@/components/display/fa-descriptions/index.vue"; import type { IContentConfig, IObject } from "@/components/modal/types"; import FaDeptTree from "./components/FaDeptTree.vue"; import { ElMessage, ElMessageBox } from "element-plus"; diff --git a/frontend/web/src/views/module_system/version/index.vue b/frontend/web/src/views/module_system/version/index.vue index 63c15cb2..8c1ca828 100644 --- a/frontend/web/src/views/module_system/version/index.vue +++ b/frontend/web/src/views/module_system/version/index.vue @@ -151,7 +151,7 @@ import type { ColumnOption } from "@/types/component"; import FaTableHeader from "@/components/tables/fa-table-header/index.vue"; import FaTable from "@/components/tables/fa-table/index.vue"; import FaDialog from "@/components/modal/fa-dialog/index.vue"; -import FaDescriptions from "@/components/others/fa-descriptions/index.vue"; +import FaDescriptions from "@/components/display/fa-descriptions/index.vue"; import FaForm from "@/components/forms/fa-form/index.vue"; defineOptions({ @@ -324,7 +324,7 @@ const { dialogVisible } = useCrudDialog(); const detailFormData = ref({}); -const versionDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] = +const versionDetailItems: import("@/components/display/fa-descriptions/index.vue").DescriptionsItem[] = [ { label: "标题", prop: "title" }, { label: "版本号", prop: "version" }, diff --git a/frontend/web/src/views/module_task/cronjob/job/index.vue b/frontend/web/src/views/module_task/cronjob/job/index.vue index ad3efd33..3c6de330 100644 --- a/frontend/web/src/views/module_task/cronjob/job/index.vue +++ b/frontend/web/src/views/module_task/cronjob/job/index.vue @@ -167,7 +167,9 @@ class="job-card-dot" :class="`job-card-dot--${getJobStatusClass(job.status)}`" /> - {{ job.name }} + + {{ job.name }} + {{ getJobStatusLabel(job.status) }} @@ -863,7 +865,7 @@ const logDetailDialog = reactive({ }); const logDetailFormData = ref({} as JobLogTable); -const logDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] = [ +const logDetailItems: import("@/components/display/fa-descriptions/index.vue").DescriptionsItem[] = [ { label: "任务ID", prop: "job_id" }, { label: "任务名称", prop: "job_name" }, { label: "触发方式", prop: "trigger_type" },