From 3fa1ef95acdf5e2961afce98d702da9c9eecc385 Mon Sep 17 00:00:00 2001 From: zhonghua Date: Wed, 8 Apr 2026 15:32:02 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B8=BB=E7=AB=99=20/ai-web=20?= =?UTF-8?q?=E5=B5=8C=E5=85=A5=20AI=20=E5=88=9B=E4=BD=9C=E5=AD=90=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E5=B9=B6=E4=BF=AE=E6=AD=A3=E8=B7=AF=E5=BE=84=E4=B8=8E?= =?UTF-8?q?=E9=80=9A=E4=BF=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made-with: Cursor --- frontend/.env.development | 3 + frontend/src/views/public/create/Index.vue | 17 ++++-- frontend/vite.config.ts | 15 ++++- lesingle-aicreate-client/.env.development | 4 +- lesingle-aicreate-client/.env.production | 2 + lesingle-aicreate-client/src/api/index.js | 2 +- lesingle-aicreate-client/src/router/index.js | 9 ++- lesingle-aicreate-client/src/utils/bridge.js | 7 ++- lesingle-aicreate-client/vite.config.js | 63 +++++++++++--------- 9 files changed, 77 insertions(+), 45 deletions(-) diff --git a/frontend/.env.development b/frontend/.env.development index 3fd5c3b..96a5e9f 100644 --- a/frontend/.env.development +++ b/frontend/.env.development @@ -1,3 +1,6 @@ # 开发环境 VITE_API_BASE_URL=/api +# AI 绘本子项目本地 dev server(vite proxy /ai-web → 该地址) +VITE_AI_CLIENT_DEV_URL=http://localhost:3001 +# 兼容旧名:与 VITE_AI_CLIENT_DEV_URL 二选一即可,优先前者 VITE_AI_POST_MESSAGE_URL=http://localhost:3001 diff --git a/frontend/src/views/public/create/Index.vue b/frontend/src/views/public/create/Index.vue index 502eb6a..e364d02 100644 --- a/frontend/src/views/public/create/Index.vue +++ b/frontend/src/views/public/create/Index.vue @@ -20,7 +20,12 @@ import { ref, onMounted, onUnmounted } from 'vue' import { useRouter } from 'vue-router' import { message } from 'ant-design-vue' import { leaiApi } from '@/api/public' -const VITE_AI_POST_MESSAGE_URL = import.meta.env.VITE_AI_POST_MESSAGE_URL; + +/** 子应用路径前缀,须与 lesingle-aicreate-client 的 Vite base、主站 /ai-web 代理一致;末尾必须有 / */ +const LEAI_EMBED_BASE = `${location.origin}/ai-web/` +/** postMessage 的 origin 仅为 scheme+host+port,不含路径 */ +const LEAI_PARENT_ORIGIN = location.origin + const router = useRouter() const leaiFrame = ref(null) const iframeSrc = ref('') @@ -31,10 +36,10 @@ const loadError = ref('') const initLeai = async () => { loading.value = true loadError.value = '' - console.log('VITE_AI_POST_MESSAGE_URL', VITE_AI_POST_MESSAGE_URL); + console.log('[创作工坊] 嵌入地址', LEAI_EMBED_BASE) try { const data = await leaiApi.getToken() - iframeSrc.value = `${VITE_AI_POST_MESSAGE_URL}?token=${encodeURIComponent(data.token)}&orgId=${encodeURIComponent(data.orgId)}&phone=${encodeURIComponent(data.phone)}&embed=1` + iframeSrc.value = `${LEAI_EMBED_BASE}?token=${encodeURIComponent(data.token)}&orgId=${encodeURIComponent(data.orgId)}&phone=${encodeURIComponent(data.phone)}&embed=1` } catch (err: any) { const errMsg = err?.response?.data?.message || err?.message || '加载失败' loadError.value = errMsg @@ -48,8 +53,8 @@ const initLeai = async () => { /** 监听乐读派 H5 postMessage 事件 */ const handleMessage = (event: MessageEvent) => { - if (event.origin !== VITE_AI_POST_MESSAGE_URL) { - console.log('event.origin', event.origin); + if (event.origin !== LEAI_PARENT_ORIGIN) { + console.log('event.origin', event.origin) return } const msg = event.data @@ -99,7 +104,7 @@ const handleTokenExpired = async (payload: any) => { orgId: data.orgId, phone: data.phone, }, - }, VITE_AI_POST_MESSAGE_URL) + }, LEAI_PARENT_ORIGIN) console.log('[创作工坊] Token已刷新') } catch (err) { console.error('[创作工坊] Token刷新失败:', err) diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 5c0af76..cb131c6 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,4 +1,4 @@ -import { defineConfig } from "vite"; +import { defineConfig, loadEnv } from "vite"; import vue from "@vitejs/plugin-vue"; import { resolve } from "path"; @@ -16,6 +16,14 @@ const getBase = (mode: string) => { // https://vitejs.dev/config/ export default defineConfig(({ mode }) => { + // vite.config 顶层无法从 .env 读取 VITE_*,需在回调内 loadEnv + const env = loadEnv(mode, process.cwd(), ""); + // AI 绘本子项目 Vite 开发服务地址(主站 /ai-web 代理到此地址,需与 lesingle-aicreate-client 端口一致) + const aiProxyTarget = + env.VITE_AI_CLIENT_DEV_URL || + env.VITE_AI_POST_MESSAGE_URL || + "http://localhost:3001"; + return { base: getBase(mode), plugins: [vue()], @@ -33,6 +41,11 @@ export default defineConfig(({ mode }) => { changeOrigin: true, // rewrite: (path) => path.replace(/^\/api/, ''), }, + "/ai-web": { + target: aiProxyTarget, + changeOrigin: true, + // 子项目 base 为 /ai-web/,不重写路径,直接转发到子 dev server + }, }, }, }; diff --git a/lesingle-aicreate-client/.env.development b/lesingle-aicreate-client/.env.development index 68e48b9..8d6df9f 100644 --- a/lesingle-aicreate-client/.env.development +++ b/lesingle-aicreate-client/.env.development @@ -1,3 +1,5 @@ # 开发环境 +# 与主项目同源的父页面 origin(iframe postMessage 校验目标),局域网调试可改为 http://192.168.1.119:3000 VITE_CREATE_POST_MESSAGE_URL=http://localhost:3000 - +# 子应用部署路径前缀,须与主项目代理路径 /ai-web 一致 +VITE_APP_BASE=/ai-web/ diff --git a/lesingle-aicreate-client/.env.production b/lesingle-aicreate-client/.env.production index e5a0901..580bd45 100644 --- a/lesingle-aicreate-client/.env.production +++ b/lesingle-aicreate-client/.env.production @@ -1,2 +1,4 @@ # 生产环境 VITE_CREATE_POST_MESSAGE_URL=http://localhost:3000 +# 若静态资源挂在主站 /ai-web 下,保持与开发一致;独立域名部署可改为 / +VITE_APP_BASE=/ai-web/ diff --git a/lesingle-aicreate-client/src/api/index.js b/lesingle-aicreate-client/src/api/index.js index d28c589..f95a6b5 100644 --- a/lesingle-aicreate-client/src/api/index.js +++ b/lesingle-aicreate-client/src/api/index.js @@ -44,7 +44,7 @@ function handleTokenExpired_standalone() { window.location.href = redirectUrl + (redirectUrl.includes('?') ? '&' : '?') + 'returnPath=' + returnPath + '&orgId=' + encodeURIComponent(store.orgId) } else { - window.location.href = '/' + window.location.href = import.meta.env.BASE_URL || '/' // 与 Vite base(/ai-web/)一致 } setTimeout(() => { isRefreshing = false }, 3000) } diff --git a/lesingle-aicreate-client/src/router/index.js b/lesingle-aicreate-client/src/router/index.js index 4276a50..8cbeddf 100644 --- a/lesingle-aicreate-client/src/router/index.js +++ b/lesingle-aicreate-client/src/router/index.js @@ -1,10 +1,9 @@ -import { createRouter, createWebHistory } from 'vue-router' -import config from '@/utils/config' +import { createRouter, createWebHistory } from 'vue-router' import { store } from '@/utils/store' -import bridge from '@/utils/bridge' +// base 由 Vite 的 import.meta.env.BASE_URL(如 /ai-web/)注入,路由表内不要再写 /ai-web 前缀 const router = createRouter({ - history: createWebHistory(), + history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: '/', @@ -79,7 +78,7 @@ router.beforeEach((to, from, next) => { // ─── 全局 token 初始化:从 URL query 读取 ─── // 支持 iframe src 直接带 token 加载任意页面 - // 如: /edit-info/190xxx?token=xxx&orgId=xxx&phone=xxx&embed=1 + // 例: /edit-info/190xxx?token=xxx&orgId=xxx&phone=xxx&embed=1 const searchParams = new URLSearchParams(window.location.search) const urlToken = searchParams.get('token') const urlOrgId = searchParams.get('orgId') diff --git a/lesingle-aicreate-client/src/utils/bridge.js b/lesingle-aicreate-client/src/utils/bridge.js index 7eb2bdf..1c76607 100644 --- a/lesingle-aicreate-client/src/utils/bridge.js +++ b/lesingle-aicreate-client/src/utils/bridge.js @@ -3,7 +3,8 @@ * 封装 H5 与企业父页面的双向通信 */ import config from './config' -const VITE_CREATE_POST_MESSAGE_URL = import.meta.env.VITE_CREATE_POST_MESSAGE_URL; +// const VITE_CREATE_POST_MESSAGE_URL = import.meta.env.VITE_CREATE_POST_MESSAGE_URL; +const VITE_AI_POST_MESSAGE_URL = location.origin; const SOURCE = 'leai-creation' const VERSION = 1 const TIMEOUT = 30000 @@ -17,7 +18,7 @@ const targetOrigin = config.parentOrigins.length > 0 ? config.parentOrigins[0] : export function send(type, payload = {}) { if (!isEmbedded) return - window.parent.postMessage({ source: SOURCE, version: VERSION, type, payload }, VITE_CREATE_POST_MESSAGE_URL) + window.parent.postMessage({ source: SOURCE, version: VERSION, type, payload }, VITE_AI_POST_MESSAGE_URL) } export function request(type, payload = {}) { if (!isEmbedded) return Promise.reject(new Error('Not in iframe')) @@ -41,7 +42,7 @@ function onMessage(event) { console.log('event.origin', event.origin); return } - console.log('onMessage',event); + console.log('onMessage', event); if (config.parentOrigins.length > 0 && !config.parentOrigins.includes(event.origin)) { return } diff --git a/lesingle-aicreate-client/vite.config.js b/lesingle-aicreate-client/vite.config.js index b4fcf14..38cac90 100644 --- a/lesingle-aicreate-client/vite.config.js +++ b/lesingle-aicreate-client/vite.config.js @@ -1,36 +1,43 @@ -import { defineConfig } from 'vite' +import { defineConfig, loadEnv } from 'vite' import vue from '@vitejs/plugin-vue' import { fileURLToPath, URL } from 'node:url' -// HTTPS: 启动时加 --https 参数,或设环境变量 VITE_HTTPS=true -// 默认 HTTP(局域网测试友好,无证书问题) -const useHttps = process.argv.includes('--https') || process.env.VITE_HTTPS === 'true' -let sslPlugin = [] -if (useHttps) { - try { - const basicSsl = (await import('@vitejs/plugin-basic-ssl')).default - sslPlugin = [basicSsl()] - } catch { /* basicSsl not installed, skip */ } -} +// 与主项目 frontend 的 /ai-web 代理一致;可通过 VITE_APP_BASE 覆盖 +export default defineConfig(async ({ mode }) => { + const env = loadEnv(mode, process.cwd(), '') + const base = env.VITE_APP_BASE || '/ai-web/' -export default defineConfig({ - plugins: [vue(), ...sslPlugin], - resolve: { - alias: { - '@': fileURLToPath(new URL('./src', import.meta.url)) + const useHttps = process.argv.includes('--https') || env.VITE_HTTPS === 'true' + let sslPlugin = [] + if (useHttps) { + try { + const basicSsl = (await import('@vitejs/plugin-basic-ssl')).default + sslPlugin = [basicSsl()] + } catch { + /* basicSsl not installed, skip */ } - }, - server: { - port: 3001, - host: '0.0.0.0', - proxy: { - '/api': { - target: 'http://localhost:8080', - changeOrigin: true - }, - '/ws': { - target: 'http://localhost:8080', - ws: true + } + + return { + base, + plugins: [vue(), ...sslPlugin], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + } + }, + server: { + port: 3001, + host: '0.0.0.0', + proxy: { + '/api': { + target: 'http://localhost:8080', + changeOrigin: true + }, + '/ws': { + target: 'http://localhost:8080', + ws: true + } } } }