feat: 主站 /ai-web 嵌入 AI 创作子应用并修正路径与通信
Made-with: Cursor
This commit is contained in:
parent
b9ed5e17c6
commit
3fa1ef95ac
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -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/
|
||||
|
||||
@ -1,2 +1,4 @@
|
||||
# 生产环境
|
||||
VITE_CREATE_POST_MESSAGE_URL=http://localhost:3000
|
||||
# 若静态资源挂在主站 /ai-web 下,保持与开发一致;独立域名部署可改为 /
|
||||
VITE_APP_BASE=/ai-web/
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user