feat: 主站 /ai-web 嵌入 AI 创作子应用并修正路径与通信

Made-with: Cursor
This commit is contained in:
zhonghua 2026-04-08 15:32:02 +08:00
parent b9ed5e17c6
commit 3fa1ef95ac
9 changed files with 77 additions and 45 deletions

View File

@ -1,3 +1,6 @@
# 开发环境
VITE_API_BASE_URL=/api
# AI 绘本子项目本地 dev servervite proxy /ai-web → 该地址)
VITE_AI_CLIENT_DEV_URL=http://localhost:3001
# 兼容旧名:与 VITE_AI_CLIENT_DEV_URL 二选一即可,优先前者
VITE_AI_POST_MESSAGE_URL=http://localhost:3001

View File

@ -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<HTMLIFrameElement | null>(null)
const iframeSrc = ref<string>('')
@ -31,10 +36,10 @@ const loadError = ref<string>('')
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)

View File

@ -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
},
},
},
};

View File

@ -1,3 +1,5 @@
# 开发环境
# 与主项目同源的父页面 originiframe postMessage 校验目标),局域网调试可改为 http://192.168.1.119:3000
VITE_CREATE_POST_MESSAGE_URL=http://localhost:3000
# 子应用部署路径前缀,须与主项目代理路径 /ai-web 一致
VITE_APP_BASE=/ai-web/

View File

@ -1,2 +1,4 @@
# 生产环境
VITE_CREATE_POST_MESSAGE_URL=http://localhost:3000
# 若静态资源挂在主站 /ai-web 下,保持与开发一致;独立域名部署可改为 /
VITE_APP_BASE=/ai-web/

View File

@ -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)
}

View File

@ -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')

View File

@ -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
}

View File

@ -1,19 +1,25 @@
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) {
// 与主项目 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/'
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 */ }
}
} catch {
/* basicSsl not installed, skip */
}
}
export default defineConfig({
return {
base,
plugins: [vue(), ...sslPlugin],
resolve: {
alias: {
@ -34,4 +40,5 @@ export default defineConfig({
}
}
}
}
})