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
|
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
|
VITE_AI_POST_MESSAGE_URL=http://localhost:3001
|
||||||
|
|||||||
@ -20,7 +20,12 @@ import { ref, onMounted, onUnmounted } from 'vue'
|
|||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import { leaiApi } from '@/api/public'
|
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 router = useRouter()
|
||||||
const leaiFrame = ref<HTMLIFrameElement | null>(null)
|
const leaiFrame = ref<HTMLIFrameElement | null>(null)
|
||||||
const iframeSrc = ref<string>('')
|
const iframeSrc = ref<string>('')
|
||||||
@ -31,10 +36,10 @@ const loadError = ref<string>('')
|
|||||||
const initLeai = async () => {
|
const initLeai = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
loadError.value = ''
|
loadError.value = ''
|
||||||
console.log('VITE_AI_POST_MESSAGE_URL', VITE_AI_POST_MESSAGE_URL);
|
console.log('[创作工坊] 嵌入地址', LEAI_EMBED_BASE)
|
||||||
try {
|
try {
|
||||||
const data = await leaiApi.getToken()
|
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) {
|
} catch (err: any) {
|
||||||
const errMsg = err?.response?.data?.message || err?.message || '加载失败'
|
const errMsg = err?.response?.data?.message || err?.message || '加载失败'
|
||||||
loadError.value = errMsg
|
loadError.value = errMsg
|
||||||
@ -48,8 +53,8 @@ const initLeai = async () => {
|
|||||||
|
|
||||||
/** 监听乐读派 H5 postMessage 事件 */
|
/** 监听乐读派 H5 postMessage 事件 */
|
||||||
const handleMessage = (event: MessageEvent) => {
|
const handleMessage = (event: MessageEvent) => {
|
||||||
if (event.origin !== VITE_AI_POST_MESSAGE_URL) {
|
if (event.origin !== LEAI_PARENT_ORIGIN) {
|
||||||
console.log('event.origin', event.origin);
|
console.log('event.origin', event.origin)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const msg = event.data
|
const msg = event.data
|
||||||
@ -99,7 +104,7 @@ const handleTokenExpired = async (payload: any) => {
|
|||||||
orgId: data.orgId,
|
orgId: data.orgId,
|
||||||
phone: data.phone,
|
phone: data.phone,
|
||||||
},
|
},
|
||||||
}, VITE_AI_POST_MESSAGE_URL)
|
}, LEAI_PARENT_ORIGIN)
|
||||||
console.log('[创作工坊] Token已刷新')
|
console.log('[创作工坊] Token已刷新')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[创作工坊] Token刷新失败:', 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 vue from "@vitejs/plugin-vue";
|
||||||
import { resolve } from "path";
|
import { resolve } from "path";
|
||||||
|
|
||||||
@ -16,6 +16,14 @@ const getBase = (mode: string) => {
|
|||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig(({ mode }) => {
|
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 {
|
return {
|
||||||
base: getBase(mode),
|
base: getBase(mode),
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
@ -33,6 +41,11 @@ export default defineConfig(({ mode }) => {
|
|||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
// rewrite: (path) => path.replace(/^\/api/, ''),
|
// 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
|
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
|
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('?') ? '&' : '?')
|
window.location.href = redirectUrl + (redirectUrl.includes('?') ? '&' : '?')
|
||||||
+ 'returnPath=' + returnPath + '&orgId=' + encodeURIComponent(store.orgId)
|
+ 'returnPath=' + returnPath + '&orgId=' + encodeURIComponent(store.orgId)
|
||||||
} else {
|
} else {
|
||||||
window.location.href = '/'
|
window.location.href = import.meta.env.BASE_URL || '/' // 与 Vite base(/ai-web/)一致
|
||||||
}
|
}
|
||||||
setTimeout(() => { isRefreshing = false }, 3000)
|
setTimeout(() => { isRefreshing = false }, 3000)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import config from '@/utils/config'
|
|
||||||
import { store } from '@/utils/store'
|
import { store } from '@/utils/store'
|
||||||
import bridge from '@/utils/bridge'
|
|
||||||
|
|
||||||
|
// base 由 Vite 的 import.meta.env.BASE_URL(如 /ai-web/)注入,路由表内不要再写 /ai-web 前缀
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
@ -79,7 +78,7 @@ router.beforeEach((to, from, next) => {
|
|||||||
|
|
||||||
// ─── 全局 token 初始化:从 URL query 读取 ───
|
// ─── 全局 token 初始化:从 URL query 读取 ───
|
||||||
// 支持 iframe src 直接带 token 加载任意页面
|
// 支持 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 searchParams = new URLSearchParams(window.location.search)
|
||||||
const urlToken = searchParams.get('token')
|
const urlToken = searchParams.get('token')
|
||||||
const urlOrgId = searchParams.get('orgId')
|
const urlOrgId = searchParams.get('orgId')
|
||||||
|
|||||||
@ -3,7 +3,8 @@
|
|||||||
* 封装 H5 与企业父页面的双向通信
|
* 封装 H5 与企业父页面的双向通信
|
||||||
*/
|
*/
|
||||||
import config from './config'
|
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 SOURCE = 'leai-creation'
|
||||||
const VERSION = 1
|
const VERSION = 1
|
||||||
const TIMEOUT = 30000
|
const TIMEOUT = 30000
|
||||||
@ -17,7 +18,7 @@ const targetOrigin = config.parentOrigins.length > 0 ? config.parentOrigins[0] :
|
|||||||
|
|
||||||
export function send(type, payload = {}) {
|
export function send(type, payload = {}) {
|
||||||
if (!isEmbedded) return
|
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 = {}) {
|
export function request(type, payload = {}) {
|
||||||
if (!isEmbedded) return Promise.reject(new Error('Not in iframe'))
|
if (!isEmbedded) return Promise.reject(new Error('Not in iframe'))
|
||||||
@ -41,7 +42,7 @@ function onMessage(event) {
|
|||||||
console.log('event.origin', event.origin);
|
console.log('event.origin', event.origin);
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.log('onMessage',event);
|
console.log('onMessage', event);
|
||||||
if (config.parentOrigins.length > 0 && !config.parentOrigins.includes(event.origin)) {
|
if (config.parentOrigins.length > 0 && !config.parentOrigins.includes(event.origin)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,36 +1,43 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig, loadEnv } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
import { fileURLToPath, URL } from 'node:url'
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
// HTTPS: 启动时加 --https 参数,或设环境变量 VITE_HTTPS=true
|
// 与主项目 frontend 的 /ai-web 代理一致;可通过 VITE_APP_BASE 覆盖
|
||||||
// 默认 HTTP(局域网测试友好,无证书问题)
|
export default defineConfig(async ({ mode }) => {
|
||||||
const useHttps = process.argv.includes('--https') || process.env.VITE_HTTPS === 'true'
|
const env = loadEnv(mode, process.cwd(), '')
|
||||||
let sslPlugin = []
|
const base = env.VITE_APP_BASE || '/ai-web/'
|
||||||
if (useHttps) {
|
|
||||||
try {
|
|
||||||
const basicSsl = (await import('@vitejs/plugin-basic-ssl')).default
|
|
||||||
sslPlugin = [basicSsl()]
|
|
||||||
} catch { /* basicSsl not installed, skip */ }
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineConfig({
|
const useHttps = process.argv.includes('--https') || env.VITE_HTTPS === 'true'
|
||||||
plugins: [vue(), ...sslPlugin],
|
let sslPlugin = []
|
||||||
resolve: {
|
if (useHttps) {
|
||||||
alias: {
|
try {
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
const basicSsl = (await import('@vitejs/plugin-basic-ssl')).default
|
||||||
|
sslPlugin = [basicSsl()]
|
||||||
|
} catch {
|
||||||
|
/* basicSsl not installed, skip */
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
server: {
|
|
||||||
port: 3001,
|
return {
|
||||||
host: '0.0.0.0',
|
base,
|
||||||
proxy: {
|
plugins: [vue(), ...sslPlugin],
|
||||||
'/api': {
|
resolve: {
|
||||||
target: 'http://localhost:8080',
|
alias: {
|
||||||
changeOrigin: true
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
},
|
}
|
||||||
'/ws': {
|
},
|
||||||
target: 'http://localhost:8080',
|
server: {
|
||||||
ws: true
|
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