Skip to content

Commit 7b0e48c

Browse files
committed
feat: preview support auto update
1 parent 9de1275 commit 7b0e48c

14 files changed

Lines changed: 815 additions & 290 deletions

File tree

packages/common/js/preview.js

Lines changed: 234 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,53 +10,257 @@
1010
*
1111
*/
1212

13-
import { constants } from '@opentiny/tiny-engine-utils'
13+
import { useThrottleFn } from '@vueuse/core'
14+
import {
15+
useMaterial,
16+
useResource,
17+
useMessage,
18+
useCanvas,
19+
usePage,
20+
useBlock,
21+
getMetaApi,
22+
META_SERVICE,
23+
getMergeMeta
24+
} from '@opentiny/tiny-engine-meta-register'
25+
import { utils } from '@opentiny/tiny-engine-utils'
1426
import { isDevelopEnv } from './environments'
15-
import { useMaterial, useResource } from '@opentiny/tiny-engine-meta-register'
16-
// prefer old unicode hacks for backward compatibility
1727

18-
const { COMPONENT_NAME } = constants
28+
const { deepClone } = utils
1929

20-
export const utoa = (string) => btoa(unescape(encodeURIComponent(string)))
21-
22-
export const atou = (base64) => decodeURIComponent(escape(atob(base64)))
23-
24-
const open = (params = {}) => {
25-
const paramsMap = new URLSearchParams(location.search)
26-
params.app = paramsMap.get('id')
27-
params.tenant = paramsMap.get('tenant')
30+
// 保存预览窗口引用
31+
let previewWindow = null
2832

33+
const getScriptAndStyleDeps = () => {
2934
const { scripts, styles } = useMaterial().getCanvasDeps()
3035
const utilsDeps = useResource().getUtilsDeps()
3136

32-
params.scripts = [...scripts, ...utilsDeps].reduce((res, item) => {
37+
const scriptsDeps = [...scripts, ...utilsDeps].reduce((res, item) => {
3338
res[item.package] = item.script
3439

3540
return res
3641
}, {})
37-
params.styles = [...styles]
42+
const stylesDeps = [...styles]
43+
44+
return {
45+
scripts: scriptsDeps,
46+
styles: stylesDeps
47+
}
48+
}
49+
50+
const getSchemaParams = async () => {
51+
const { isBlock, getPageSchema, getCurrentPage, getSchema } = useCanvas()
52+
const isBlockPreview = isBlock()
53+
const { scripts, styles } = getScriptAndStyleDeps()
54+
55+
if (isBlockPreview) {
56+
const { getCurrentBlock } = useBlock()
57+
const block = getCurrentBlock()
58+
59+
const latestPage = {
60+
...block,
61+
page_content: getSchema()
62+
}
63+
64+
return deepClone({
65+
currentPage: latestPage,
66+
ancestors: [],
67+
scripts,
68+
styles
69+
})
70+
}
71+
72+
const pageSchema = getPageSchema()
73+
const currentPage = getCurrentPage()
74+
const { getFamily } = usePage()
75+
const latestPage = {
76+
...currentPage,
77+
page_content: pageSchema
78+
}
79+
80+
const ancestors = await getFamily(latestPage)
81+
82+
return deepClone({
83+
currentPage: latestPage,
84+
ancestors,
85+
scripts,
86+
styles
87+
})
88+
}
89+
90+
// 当 schema 变化时发送更新
91+
const sendSchemaUpdate = (data) => {
92+
previewWindow.postMessage(
93+
{
94+
source: 'designer',
95+
type: 'schema',
96+
data
97+
},
98+
'*'
99+
)
100+
}
101+
102+
// 监听来自预览页面的消息
103+
const setupMessageListener = () => {
104+
window.addEventListener('message', async (event) => {
105+
// 确保消息来源安全
106+
if (event.origin === window.location.origin || event.origin.includes(window.location.hostname)) {
107+
const { event: eventType, source } = event.data || {}
108+
// 通过 heartbeat 消息来重新建立连接,避免刷新页面后 previewWindow 为 null
109+
if (source === 'preview' && eventType === 'heartbeat' && !previewWindow) {
110+
previewWindow = event.source
111+
}
112+
113+
if (source === 'preview' && eventType === 'onMounted' && previewWindow) {
114+
const params = await getSchemaParams()
115+
sendSchemaUpdate(params)
116+
}
117+
}
118+
})
119+
}
120+
121+
// 初始化消息监听
122+
setupMessageListener()
123+
124+
let schemaChangeListener = null
125+
const handleSchemaChange = async () => {
126+
const { unsubscribe } = useMessage()
127+
// 如果预览窗口不存在或已关闭,则取消订阅
128+
if (!previewWindow || previewWindow.closed) {
129+
unsubscribe({
130+
topic: 'schemaChange',
131+
subscriber: 'preview-communication'
132+
})
133+
unsubscribe({
134+
topic: 'schemaImport',
135+
subscriber: 'preview-communication'
136+
})
137+
unsubscribe({
138+
topic: 'pageOrBlockInit',
139+
subscriber: 'preview-communication'
140+
})
141+
schemaChangeListener = null
142+
return
143+
}
144+
145+
const params = await getSchemaParams()
146+
sendSchemaUpdate(params)
147+
}
148+
149+
// 设置监听 schemaChange 事件,自动发送更新到预览页面
150+
export const setupSchemaChangeListener = () => {
151+
// 如果已经存在监听,则取消之前的监听
152+
if (schemaChangeListener) {
153+
return
154+
}
38155

156+
const { subscribe } = useMessage()
157+
158+
schemaChangeListener = subscribe({
159+
topic: 'schemaChange',
160+
subscriber: 'preview-communication',
161+
// 防抖更新,防止因为属性变化频繁触发
162+
callback: useThrottleFn(handleSchemaChange, 1000, true)
163+
})
164+
165+
subscribe({
166+
topic: 'schemaImport',
167+
subscriber: 'preview-communication',
168+
callback: useThrottleFn(handleSchemaChange, 1000, true)
169+
})
170+
171+
subscribe({
172+
topic: 'pageOrBlockInit',
173+
subscriber: 'preview-communication',
174+
callback: handleSchemaChange
175+
})
176+
}
177+
178+
const handleHistoryPreview = (params, url) => {
179+
let historyPreviewWindow = null
180+
const handlePreviewReady = (event) => {
181+
if (event.origin === window.location.origin || event.origin.includes(window.location.hostname)) {
182+
const { event: eventType, source } = event.data || {}
183+
if (source === 'preview' && eventType === 'onMounted' && historyPreviewWindow) {
184+
const { scripts, styles, ancestors = [], ...rest } = params
185+
186+
historyPreviewWindow.postMessage(
187+
{
188+
source: 'designer',
189+
type: 'schema',
190+
data: deepClone({
191+
currentPage: rest,
192+
ancestors,
193+
scripts,
194+
styles
195+
})
196+
},
197+
'*'
198+
)
199+
}
200+
}
201+
}
202+
203+
window.addEventListener('message', handlePreviewReady)
204+
205+
historyPreviewWindow = window.open(url, '_blank')
206+
}
207+
208+
const getQueryParams = (params = {}, isHistory = false) => {
209+
const paramsMap = new URLSearchParams(location.search)
210+
const tenant = paramsMap.get('tenant') || ''
211+
const pageId = paramsMap.get('pageid')
212+
const blockId = paramsMap.get('blockid')
213+
const theme = getMetaApi(META_SERVICE.ThemeSwitch)?.getThemeState()?.theme
214+
const framework = getMergeMeta('engine.config')?.dslMode
215+
const platform = getMergeMeta('engine.config')?.platformId
216+
const { scripts, styles } = getScriptAndStyleDeps()
217+
218+
let query = `tenant=${tenant}&id=${paramsMap.get('id')}&theme=${theme}&framework=${framework}`
219+
220+
query += `&platform=${platform}&scripts=${JSON.stringify(scripts)}&styles=${JSON.stringify(styles)}`
221+
222+
if (pageId) {
223+
query += `&pageid=${pageId}`
224+
}
225+
226+
if (blockId) {
227+
query += `&blockid=${blockId}`
228+
}
229+
230+
if (isHistory) {
231+
query += `&history=${params.history}`
232+
}
233+
234+
return query
235+
}
236+
237+
const open = (params = {}, isHistory = false) => {
39238
const href = window.location.href.split('?')[0] || './'
40-
const tenant = new URLSearchParams(location.search).get('tenant') || ''
239+
const { scripts, styles } = getScriptAndStyleDeps()
240+
const query = getQueryParams(params, isHistory)
241+
41242
let openUrl = ''
42-
const hashString = utoa(JSON.stringify(params))
43243

44-
openUrl = isDevelopEnv
45-
? `./preview.html?tenant=${tenant}#${hashString}`
46-
: `${href.endsWith('/') ? href : `${href}/`}preview?tenant=${tenant}#${hashString}`
244+
openUrl = isDevelopEnv ? `./preview.html?${query}` : `${href.endsWith('/') ? href : `${href}/`}preview?${query}`
47245

48-
const aTag = document.createElement('a')
49-
aTag.href = openUrl
50-
aTag.target = '_blank'
51-
aTag.click()
52-
}
246+
if (isHistory) {
247+
handleHistoryPreview({ ...params, scripts, styles }, openUrl)
248+
return
249+
}
250+
251+
if (previewWindow && !previewWindow.closed) {
252+
// 如果预览窗口存在,则聚焦预览窗口
253+
previewWindow.focus()
254+
return
255+
}
256+
257+
// 打开新窗口并保存引用
258+
previewWindow = window.open(openUrl, '_blank')
53259

54-
export const previewPage = (params = {}) => {
55-
params.type = COMPONENT_NAME.Page
56-
open(params)
260+
// 设置 schemaChange 事件监听
261+
setupSchemaChangeListener()
57262
}
58263

59-
export const previewBlock = (params = {}) => {
60-
params.type = COMPONENT_NAME.Block
61-
open(params)
264+
export const previewPage = (params = {}, isHistory = false) => {
265+
open(params, isHistory)
62266
}

packages/common/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"@opentiny/tiny-engine-meta-register": "workspace:*",
4040
"@opentiny/tiny-engine-utils": "workspace:*",
4141
"@vue/shared": "^3.3.4",
42+
"@vueuse/core": "^9.6.0",
4243
"axios": "~0.28.0",
4344
"css-tree": "^2.3.1",
4445
"eslint-linter-browserify": "8.57.0",
@@ -50,7 +51,6 @@
5051
"@opentiny/tiny-engine-vite-plugin-meta-comments": "workspace:*",
5152
"@vitejs/plugin-vue": "^5.1.2",
5253
"@vitejs/plugin-vue-jsx": "^4.0.1",
53-
"@vueuse/core": "^9.6.0",
5454
"glob": "^10.3.4",
5555
"vite": "^5.4.2"
5656
},

packages/design-core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
"devDependencies": {
104104
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
105105
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
106+
"@types/babel__core": "^7.20.5",
106107
"@types/node": "^18.0.0",
107108
"@vitejs/plugin-vue": "^5.1.2",
108109
"@vitejs/plugin-vue-jsx": "^4.0.1",

packages/design-core/src/preview/src/Toolbar.vue

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@
1414
</template>
1515

1616
<script lang="jsx">
17+
import { watch } from 'vue'
1718
import { useBreadcrumb, getMergeRegistry, getMergeMeta } from '@opentiny/tiny-engine-meta-register'
1819
import { Switch as TinySwitch } from '@opentiny/vue'
19-
import { getSearchParams } from './preview/http'
20+
import { constants } from '@opentiny/tiny-engine-utils'
2021
import { BROADCAST_CHANNEL } from '../src/preview/srcFiles/constant'
2122
import { injectDebugSwitch } from './preview/debugSwitch'
23+
import { previewState } from './preview/usePreviewData'
2224
2325
export default {
2426
components: {
@@ -30,10 +32,18 @@ export default {
3032
const ChangeLang = getMergeRegistry('toolbars', 'engine.toolbars.lang')?.entry
3133
const langOptions = getMergeMeta('engine.toolbars.lang').options
3234
const ToolbarMedia = null // TODO: Media plugin rely on layout/canvas. Further processing is required.
35+
const { setBreadcrumbPage, setBreadcrumbBlock } = useBreadcrumb()
3336
34-
const { setBreadcrumbPage } = useBreadcrumb()
35-
const { pageInfo } = getSearchParams()
36-
setBreadcrumbPage([pageInfo?.name])
37+
watch(
38+
() => previewState.currentPage,
39+
(newVal) => {
40+
if (newVal?.page_content?.componentName === constants.COMPONENT_NAME.Block) {
41+
setBreadcrumbBlock([newVal?.name_cn || newVal?.page_content?.fileName])
42+
} else {
43+
setBreadcrumbPage([newVal?.name])
44+
}
45+
}
46+
)
3747
3848
const setViewPort = (item) => {
3949
const iframe = document.getElementsByClassName('iframe-container')[0]

0 commit comments

Comments
 (0)