diff --git a/docs/活动页/建页执行流程.md b/docs/活动页/建页执行流程.md index 4a9f51d..0c51408 100644 --- a/docs/活动页/建页执行流程.md +++ b/docs/活动页/建页执行流程.md @@ -27,6 +27,8 @@ 7. 安全白名单片段和权限页面片段也使用 route path 自动生成,不留空。 8. OSS 相对路径和本地开发目录根据页面类型和页面目录自动生成;用户后续可以自行修改。 9. 如果用户要求,或 md 中“是否需要 Codex 自动创建本地开发目录”为“是”,创建 public/oss/h5/Azizi/... 本地图片目录。 + - 本地开发目录只作为开发期 OSS 图片镜像使用,方便页面优先读取本地图片;发布时图片由用户另行上传到真实 OSS,不随项目提交 Git。 + - 创建本地开发目录时只创建文件夹,不生成 `.gitkeep`;空目录不被 Git 追踪是正常现象。 10. 如果本地开发目录已存在且里面有图片,扫描图片文件并预填 3.1 多语言图片清单。 11. 根据页面类型、3.1 图片清单和模块预设,自动预填 3.2、3.3、4.x 模块配置。 12. 可按 route path 生成路由、白名单、权限片段;是否立即写入 `src/router/index.js`、`src/config/security.js`、`src/utils/permissionManager.js` 取决于用户要求和当前是否需要保持构建通过。 @@ -75,7 +77,7 @@ src/views/Ranking/OverallSeason2 ```text scanDir = 专属 md 中的本地开发目录 只扫描常见图片扩展名:png、jpg、jpeg、webp、gif、svg。 -忽略 .gitkeep 等非图片文件。 +忽略 .gitkeep 等非图片文件;历史遗留的 `.gitkeep` 按非图片文件处理,新建本地开发目录时不要生成 `.gitkeep`。 文件名不带语言后缀时,记为 en 原图,例如 bg0.png -> bg0: en。 文件名带 _ar、_bn、_tr 等语言后缀时,按基础名合并,例如 rankBt.png + rankBt_ar.png -> rankBt: en, ar。 如果只有 rankBt_ar.png 且没有 rankBt.png,也生成 rankBt: ar,并在备注里标记缺少原图。 @@ -104,6 +106,7 @@ rankingTopBg 统一解释为“排行榜前几名展示区背景”;历史 top 6. 只处理专属 md 中已填写的信息,未填写项跳过。 7. 以专属页面信息 md 为唯一生成源,同步更新 page.config.js,把已填写的活动字段、预加载、tabs、sections、children、popups、features 写入配置。 8. 如果专属 md 中“是否需要 Codex 自动创建本地开发目录”为“是”,创建 public/oss/h5/Azizi/... 本地图片目录。 + - 这里仍然保留自动创建文件夹的功能,用于开发期存放图片;不要为该目录生成 `.gitkeep`。 9. 添加或确认路由。 10. 补充或确认 src/config/security.js。 11. 补充或确认 src/utils/permissionManager.js。 diff --git a/docs/活动页/建页操作模板.md b/docs/活动页/建页操作模板.md index 65f6437..f2cdc77 100644 --- a/docs/活动页/建页操作模板.md +++ b/docs/活动页/建页操作模板.md @@ -30,6 +30,8 @@ 2. 在页面目录中创建 页面创建信息.md。 3. 根据 页面创建信息.md 创建 page.config.js 初始配置。 4. 按需要创建本地 OSS 图片目录。 + - 本地 OSS 图片目录仅用于开发期存放图片,方便页面读取本地资源;发布时图片由你上传到真实 OSS,不随项目提交 Git。 + - Codex 仍会按需要创建这个文件夹,但不会生成 `.gitkeep`;空文件夹不被 Git 追踪是正常现象。 5. 预填 route、OSS、图片清单、预加载和模块配置。 6. 不生成 index.vue,除非你明确要求继续创建页面。 ``` @@ -128,6 +130,8 @@ src/views/Activities/GulbenFestival/页面创建信息.md - 是否需要 Codex 自动创建本地开发目录:是 / 否 - 图片是否已放入本地目录:是 / 否 +说明:本地开发目录只作为开发期 OSS 图片镜像使用,Codex 需要保留创建文件夹的能力,便于你放入开发用图片;目录内图片和空目录占位文件不作为 Git 提交内容,新建时不要生成 `.gitkeep`。 + ### 3.1 多语言图片清单 | 图片名 | 支持语言 | 备注 | diff --git a/vite.config.js b/vite.config.js index e00af73..ecbe168 100644 --- a/vite.config.js +++ b/vite.config.js @@ -2,15 +2,21 @@ import vue from '@vitejs/plugin-vue' import Components from 'unplugin-vue-components/vite' import { defineConfig } from 'vite' +import fs from 'node:fs' +import path from 'node:path' import { fileURLToPath, URL } from 'node:url' -import { existsSync } from 'node:fs' -import { isAbsolute, relative, resolve } from 'node:path' import { VantResolver } from 'unplugin-vue-components/resolvers' const PRODUCTION_LIKE_MODES = new Set(['production', 'production-atu']) const STRIPPABLE_CONSOLE_METHODS = ['log', 'debug', 'info'] -const PUBLIC_DIR = fileURLToPath(new URL('./public', import.meta.url)) -const PUBLIC_ROOT = resolve(PUBLIC_DIR) +const LOCAL_OSS_MIME_TYPES = { + '.gif': 'image/gif', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.png': 'image/png', + '.svg': 'image/svg+xml', + '.webp': 'image/webp', +} function isEscaped(code, index) { let backslashCount = 0 @@ -259,12 +265,56 @@ function createBuildLogControlPlugin(stripDebugLogs) { } } +function createLocalOssAssetPlugin() { + const ossRoot = fileURLToPath(new URL('./public/oss', import.meta.url)) + + return { + name: 'likei-local-oss-assets', + apply: 'serve', + configureServer(server) { + server.middlewares.use('/oss', (req, res, next) => { + const rawPath = req.url?.split('?')[0] || '/' + let decodedPath = '' + + try { + decodedPath = decodeURIComponent(rawPath) + } catch { + next() + return + } + + const normalizedPath = path.normalize(decodedPath).replace(/^(\.\.[/\\])+/, '') + const filePath = path.join(ossRoot, normalizedPath) + const safeRoot = `${ossRoot}${path.sep}` + + if (!filePath.startsWith(safeRoot)) { + next() + return + } + + if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) { + next() + return + } + + const contentType = LOCAL_OSS_MIME_TYPES[path.extname(filePath).toLowerCase()] + if (contentType) { + res.setHeader('Content-Type', contentType) + } + + fs.createReadStream(filePath).pipe(res) + }) + }, + } +} + export default defineConfig(({ mode }) => { const keepBuildLogs = process.env.LIKEI_KEEP_BUILD_LOGS === 'true' const stripDebugLogs = PRODUCTION_LIKE_MODES.has(mode) && !keepBuildLogs return { plugins: [ + createLocalOssAssetPlugin(), vue(), Components({ resolvers: [VantResolver()], @@ -318,23 +368,6 @@ export default defineConfig(({ mode }) => { target: 'https://cdn.azizichat.com', changeOrigin: true, secure: true, - bypass: (req) => { - const requestUrl = req.url || '' - - try { - const pathname = decodeURIComponent(new URL(requestUrl, 'http://localhost').pathname) - const localPublicFile = resolve(PUBLIC_ROOT, `.${pathname}`) - const localPathInPublic = relative(PUBLIC_ROOT, localPublicFile) - - if (!localPathInPublic.startsWith('..') && !isAbsolute(localPathInPublic) && existsSync(localPublicFile)) { - return pathname - } - } catch (error) { - console.warn('Failed to check local OSS fallback:', error) - } - - return null - }, rewrite: (path) => path.replace(/^\/oss/, ''), configure: (proxy) => { proxy.on('proxyRes', (proxyRes) => {