diff --git a/.gitignore b/.gitignore index 057e908..80d0967 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules/ dist/ +**/public/ apps/*/dist/ .pnpm-store .env diff --git a/WonderQ-Admin-UI-Vue/README.md b/WonderQ-Admin-UI-Vue/README.md index 1c2082c..be84eaf 100644 --- a/WonderQ-Admin-UI-Vue/README.md +++ b/WonderQ-Admin-UI-Vue/README.md @@ -13,6 +13,8 @@ yarn dev 默认通过 Vite `/api` 代理访问 `http://localhost:4000`,可使用 `VITE_API_PROXY_TARGET` 覆盖。生产构建使用 `yarn build`,静态产物由反向代理挂载到 `/admin`。 +管理端会将后端返回的本地兜底图片 `/assets/...` 映射到管理端静态资源 `/admin/assets/...`;首页配置所需的本地兜底图片已随管理端构建提供。OSS 图片仍使用后端返回的临时可访问 URL。 + ## 认证约定 - Access Token 只保存在运行时内存,不写入 localStorage。 @@ -20,6 +22,7 @@ yarn dev - 401 会自动调用 `/api/admin/auth/refresh` 并重试一次;刷新失败后返回登录页。 - 菜单和按钮权限来自 `/api/admin/system/profile`,前端只加载预注册组件。 - 管理端界面图标统一使用 `@element-plus/icons-vue`;菜单图标通过 `src/components/layout/LayoutIcon.vue` 的白名单适配器映射,未知图标回退为默认图标。 +- 菜单管理按 RuoYi 风格提供目录、页面、按钮三类编辑字段;父级菜单使用服务端树,排除按钮和当前编辑分支,图标选择器支持搜索、清空和 Element Plus 图标网格。 - `ConciergeDetail.icon` 等接口字段是业务内容数据,不属于 UI 图标,不在图标迁移范围内。 ## 验证 diff --git a/WonderQ-Admin-UI-Vue/src/components/layout/LayoutIcon.vue b/WonderQ-Admin-UI-Vue/src/components/layout/LayoutIcon.vue index a84f50d..4e91331 100644 --- a/WonderQ-Admin-UI-Vue/src/components/layout/LayoutIcon.vue +++ b/WonderQ-Admin-UI-Vue/src/components/layout/LayoutIcon.vue @@ -3,20 +3,63 @@ import { computed, type Component } from "vue"; import { ArrowDown, Back as BackIcon, + Bell, + Calendar, + ChatLineRound, + CircleCheck, + CircleClose, + Clock, Close as CloseIcon, Collection as CollectionIcon, + CollectionTag, + Connection, + DataAnalysis, + DataBoard, + Delete, + Document, + Download, + Edit, FullScreen as FullScreenIcon, + Folder, + FolderOpened, Grid as GridIcon, Guide, + Help, + House, + Key, + List, + Location, + MapLocation, Menu as MenuIcon, + Message, + Monitor, Moon as MoonIcon, More as MoreIcon, + Notification, + Odometer, + Operation, + Paperclip, + Picture, + PieChart, + Plus, + Rank, + Reading, Refresh as RefreshIcon, Right as RightIcon, Search as SearchIcon, Setting as SettingIcon, + Share, + ShoppingBag, Sunny as SunnyIcon, + Tickets, + Tools, + TrendCharts, + Upload, User as UserIcon, + UserFilled, + View, + Warning, + ZoomIn, } from "@element-plus/icons-vue"; import type { LayoutIconName } from "@/lib/menu-display"; @@ -46,6 +89,49 @@ const iconRegistry: Record = { Back: BackIcon, Right: RightIcon, Chevron: ArrowDown, + Bell, + Calendar, + ChatLineRound, + CircleCheck, + CircleClose, + Clock, + CollectionTag, + Connection, + DataAnalysis, + DataBoard, + Delete, + Document, + Download, + Edit, + Folder, + FolderOpened, + Help, + House, + Key, + List, + Location, + MapLocation, + Message, + Monitor, + Notification, + Odometer, + Operation, + Paperclip, + Picture, + PieChart, + Plus, + Rank, + Reading, + Share, + ShoppingBag, + Tickets, + Tools, + TrendCharts, + Upload, + UserFilled, + View, + Warning, + ZoomIn, }; const iconComponent = computed(() => iconRegistry[props.name] ?? MenuIcon); diff --git a/WonderQ-Admin-UI-Vue/src/components/media/SingleMediaEditor.vue b/WonderQ-Admin-UI-Vue/src/components/media/SingleMediaEditor.vue index e0f33fc..fec288e 100644 --- a/WonderQ-Admin-UI-Vue/src/components/media/SingleMediaEditor.vue +++ b/WonderQ-Admin-UI-Vue/src/components/media/SingleMediaEditor.vue @@ -2,6 +2,7 @@ import { ref } from "vue"; import { uploadMediaAsset } from "@/api/client"; import { validateMediaFile } from "@/lib/media"; +import { resolveAdminMediaUrl } from "@/lib/media-url"; const props = defineProps<{ modelValue: string; @@ -13,6 +14,11 @@ const emit = defineEmits<{ (event: "update:modelValue", value: string): void }>( const input = ref(null); const uploading = ref(false); const error = ref(""); +const previewError = ref(false); + +function previewUrl(value: string) { + return resolveAdminMediaUrl(value); +} function openPicker() { input.value?.click(); } @@ -30,6 +36,7 @@ async function upload(event: Event) { error.value = ""; try { const asset = await uploadMediaAsset(file, props.group); + previewError.value = false; emit("update:modelValue", asset.url); } catch (uploadError) { error.value = uploadError instanceof Error ? uploadError.message : "图片上传失败,请重试"; @@ -38,7 +45,10 @@ async function upload(event: Event) { } } -function clear() { emit("update:modelValue", ""); } +function clear() { + previewError.value = false; + emit("update:modelValue", ""); +}