Skip to content

Commit

Permalink
refactor: 变量绑定功能支持全局注册变量来源
Browse files Browse the repository at this point in the history
  • Loading branch information
gene9831 committed Jun 5, 2024
1 parent efd411c commit 9f8da92
Show file tree
Hide file tree
Showing 13 changed files with 368 additions and 183 deletions.
242 changes: 60 additions & 182 deletions packages/common/component/MetaBindVariable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -123,15 +123,14 @@
import { reactive, ref, computed, nextTick, watch } from 'vue'
import { camelize, capitalize } from '@vue/shared'
import { Button, DialogBox, Search, Switch, Input, Tooltip, Alert } from '@opentiny/vue'
import { useHttp } from '@opentiny/tiny-engine-http'
import { useCanvas, useResource, useLayout, useApp, useProperties, useData } from '@opentiny/tiny-engine-controller'
import { useCanvas, useProperties } from '@opentiny/tiny-engine-controller'
import { theme } from '@opentiny/tiny-engine-controller/adapter'
import { constants } from '@opentiny/tiny-engine-utils'
import SvgButton from './SvgButton.vue'
import { parse, traverse, generate } from '@opentiny/tiny-engine-controller/js/ast'
import { DEFAULT_LOOP_NAME } from '@opentiny/tiny-engine-controller/js/constants'
import MonacoEditor from './VueMonaco.vue'
import { formatString } from '@opentiny/tiny-engine-controller/js/ast'
import { register, getRegistrationArray } from '@opentiny/tiny-engine-entry'
const { EXPRESSION_TYPE } = constants
Expand Down Expand Up @@ -165,6 +164,43 @@ const getJsSlotParams = () => {
return isJsSlot ? jsSlot?.params || [] : []
}
register(
'VARIABLE_CONFIGURATOR_LIST',
{
loop: {
content: '循环变量',
condition: () => useProperties().getSchema()?.loop,
getVariables: () => {
const [loopItem = DEFAULT_LOOP_NAME.ITEM, loopIndex = DEFAULT_LOOP_NAME.INDEX] =
useProperties().getSchema()?.loopArgs || []
return {
bindPrefix: '',
variables: [loopItem, loopIndex].reduce((variables, param) => ({ ...variables, [param]: param }), {})
}
},
_order: 800
},
slotScope: {
id: 'slotScope',
content: '暴露给插槽使用的变量',
condition: () => {
const [isInJsSlot] = getJsSlot()
return isInJsSlot
},
getVariables: () => {
const params = getJsSlotParams()
return {
bindPrefix: '',
variables: params.reduce((variables, param) => ({ ...variables, [param]: param }), {})
}
},
_order: 900
}
},
{ mergeObject: true }
)
export default {
name: 'MetaBindVariable',
components: {
Expand Down Expand Up @@ -195,45 +231,20 @@ export default {
},
setup(props, { emit }) {
const editor = ref(null)
const http = useHttp()
let oldValue = ''
let postConfirm = null
const list = [
{ id: 'state', content: 'State 属性' },
{ id: 'store', content: '应用状态' },
{ id: 'function', content: '自定义处理函数' },
{ id: 'utils', content: '工具类' },
{ id: 'bridge', content: '桥接源' },
{ id: 'datasource', content: '数据源' }
]
const list = getRegistrationArray('VARIABLE_CONFIGURATOR_LIST')
const state = reactive({
isBlock: computed(() => useCanvas().isBlock()),
variables: {},
// 控制变量列表显示/隐藏
isVisible: false,
// 搜索框value
value: '',
active: 'state',
// 某一类型下的变量列表
variableList: computed(() => {
const extendedVars = []
const [isInJsSlot] = getJsSlot()
if (state.isBlock) {
extendedVars.push({ id: 'props', content: 'props' })
}
if (state.loopData) {
extendedVars.push({ id: 'loop', content: '循环变量' })
}
if (isInJsSlot) {
extendedVars.push({ id: 'slotScope', content: '暴露给插槽使用的变量' })
}
return [...list, ...extendedVars]
}),
variableList: [],
// 绑定的变量名/变量表达式
variable: '',
// 绑定的变量指向的值内容
Expand All @@ -246,7 +257,6 @@ export default {
// 静态值
mock: props.modelValue?.value || props.modelValue,
bindPrefix: '',
loopData: null,
loopArgs: '',
isPoll: false,
pollInterval: 5000
Expand All @@ -260,7 +270,9 @@ export default {
(value) => {
if (value) {
oldValue = state.variable
state.loopData = useProperties().getSchema()?.loop
state.variableList = list.filter((item) =>
typeof item.condition === 'function' ? item.condition(state) : true
)
}
}
)
Expand Down Expand Up @@ -297,79 +309,6 @@ export default {
})
}
const removeInterval = (start, end, intervalId, pageSchema) => {
const unmountedFn = pageSchema.lifeCycles?.onUnmounted?.value
const fetchBody = `
/** ${start} */
clearInterval(state.${intervalId});
/** ${end} */`
if (!unmountedFn) {
pageSchema.lifeCycles = pageSchema.lifeCycles || {}
pageSchema.lifeCycles.onUnmounted = {
type: 'JSFunction',
value: `function onUnmounted() {${fetchBody}}`
}
} else {
if (!unmountedFn.includes(`${intervalId}`)) {
pageSchema.lifeCycles.onUnmounted.value = unmountedFn.trim().replace(/\}$/, fetchBody + '}')
}
}
}
const genRemoteMethodToLifeSetup = (variableName, sourceRef, pageSchema) => {
if (sourceRef?.data?.data) {
const setupFn = pageSchema.lifeCycles?.setup?.value
const { getCommentByKey } = useData()
const { start, end } = getCommentByKey(variableName)
const intervalId = `${CONSTANTS.INTERVALID}${capitalize(camelize(sourceRef.name))}`
const isPoll = state.isPoll && state.pollInterval !== undefined
let fetchBodyFn = `${CONSTANTS.DATASOURCEMAP}${sourceRef.name}.load().then(res => {
state.${variableName} = res?.data?.items || res?.data || res
})`
if (isPoll) {
fetchBodyFn = `state.${intervalId} = setInterval(() => {${CONSTANTS.DATASOURCEMAP}${sourceRef.name}.load().then(res => {
state.${variableName} = res?.data?.items || res?.data || res
})}, ${state.pollInterval})`
}
const fetchBody = `
/** ${start} */
${fetchBodyFn};
/** ${end} */`
if (!setupFn) {
pageSchema.lifeCycles = pageSchema.lifeCycles || {}
pageSchema.lifeCycles.setup = {
type: 'JSFunction',
value: `function setup({ props, state, watch, onMounted }) {${fetchBody}}`
}
} else {
if (!setupFn.includes(`${CONSTANTS.DATASOURCEMAP}${sourceRef.name}`)) {
pageSchema.lifeCycles.setup.value = setupFn.trim().replace(/\}$/, fetchBody + '}')
} else {
const ast = parse(setupFn)
traverse(ast, {
ExpressionStatement(path) {
if (path.toString().includes(sourceRef.name)) {
path.replaceWithSourceString(fetchBodyFn)
path.stop()
}
}
})
pageSchema.lifeCycles.setup.value = generate(ast).code
}
}
if (isPoll) {
removeInterval(start, end, intervalId, pageSchema)
}
}
}
const variableClick = (key, item) => {
if (state.bindPrefix === CONSTANTS.DATASOUCEPREFIX) {
// 当选中数据源时,直接生成对应state变量并绑定数据源的静态数据
Expand Down Expand Up @@ -403,7 +342,7 @@ export default {
const confirm = () => {
let variableContent = state.isEditorEditMode ? editor.value?.getEditor().getValue() : state.variable
const { setSaved, canvasApi } = useCanvas()
const { setSaved } = useCanvas()
// 如果新旧值不一样就显示未保存状态
if (oldValue !== variableContent) {
setSaved(false)
Expand All @@ -414,18 +353,8 @@ export default {
const needFetchDataFormat = props.name === 'fetchData' && !pattern.test(variableContent)
if (variableContent) {
if (state.bindPrefix === CONSTANTS.DATASOUCEPREFIX) {
const pageSchema = canvasApi.value.getSchema()
const stateName = state.variable.replace(`${CONSTANTS.STATE}`, '')
const staticData = state.variableContent.map(({ _id, ...other }) => other)
pageSchema.state[stateName] = staticData
// 设置画布上下文环境,让画布触发更新渲染
canvasApi.value.setState({ [stateName]: staticData })
// 这里在setup生命周期函数内部处理用户真实环境中的数据源请求
genRemoteMethodToLifeSetup(stateName, state.dataSouce, pageSchema)
if (typeof postConfirm === 'function') {
postConfirm(state)
}
emit('update:modelValue', {
Expand Down Expand Up @@ -465,71 +394,20 @@ export default {
const selectItem = (item) => {
state.active = item.id
const { canvasApi } = useCanvas()
if (item.id === 'function') {
state.bindPrefix = CONSTANTS.THIS
const { PLUGIN_NAME, getPluginApi } = useLayout()
const { getMethods } = getPluginApi(PLUGIN_NAME.PageController)
state.variables = { ...getMethods?.() }
} else if (item.id === 'bridge' || item.id === 'utils') {
state.bindPrefix = `${CONSTANTS.THIS}${item.id}.`
const bridge = {}
useResource().resState[item.id]?.forEach((res) => {
bridge[res.name] = `${item.id}.${res.content.exportName}`
})
state.variables = bridge
} else if (item.id === 'props') {
state.bindPrefix = CONSTANTS.PROPS
const properties = canvasApi.value.getSchema()?.schema?.properties
const bindProperties = {}
properties?.forEach(({ content }) => {
content.forEach(({ property }) => {
bindProperties[property] = property
})
})
state.variables = bindProperties
} else if (item.id === 'datasource') {
state.bindPrefix = CONSTANTS.DATASOUCEPREFIX
const { appInfoState } = useApp()
const url = new URLSearchParams(location.search)
const selectedId = appInfoState.selectedId || url.get('id')
// 实时请求数据源列表数据,保证数据源获取最新的数据源数据
http.get(`/app-center/api/sources/list/${selectedId}`).then((data) => {
const sourceData = {}
data.forEach((res) => {
sourceData[res.name] = res
})
state.variables = sourceData
})
} else if (item.id === 'store') {
state.bindPrefix = CONSTANTS.STORE
state.variables = {}
const stores = canvasApi.value.getGlobalState()
stores.forEach(({ id, state: storeState = {}, getters = {} }) => {
const loadProp = (prop) => {
const propBinding = `${id}.${prop}`
state.variables[propBinding] = propBinding
}
Object.keys(storeState).forEach(loadProp)
Object.keys(getters).forEach(loadProp)
postConfirm = item.postConfirm
if (typeof item.getVariables === 'function') {
const { bindPrefix, variables } = item.getVariables()
state.bindPrefix = bindPrefix
state.variables = variables
} else if (typeof item.getVariablesAsync === 'function') {
item.getVariablesAsync().then(({ bindPrefix, variables }) => {
state.bindPrefix = bindPrefix
state.variables = variables
})
} else if (item.id === 'loop') {
state.bindPrefix = ''
const [loopItem = DEFAULT_LOOP_NAME.ITEM, loopIndex = DEFAULT_LOOP_NAME.INDEX] =
useProperties().getSchema()?.loopArgs || []
state.variables = [loopItem, loopIndex].reduce((variables, param) => ({ ...variables, [param]: param }), {})
} else if (item.id === 'slotScope') {
state.bindPrefix = ''
const params = getJsSlotParams()
state.variables = params.reduce((variables, param) => ({ ...variables, [param]: param }), {})
} else {
state.bindPrefix = CONSTANTS.STATE
state.variables = canvasApi.value.getSchema()?.[item.id]
state.bindPrefix = ''
state.variables = {}
}
}
Expand Down
44 changes: 44 additions & 0 deletions packages/entry/src/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,47 @@ export const generateRegistry = (registry) => {
export const getMergeMeta = (id) => {
return metasHashMap[id]
}

// 全局注册数据
const registration = {}

/**
* 获取token对应的注册的数据
* @param {string} token
* @returns
*/
export const getRegistration = (token) => registration[token]

/**
* 获取token对应的注册的数组数据。数组实际上是以对象保存的,根据_order字段排序
* @param {string} token
* @returns
*/
export const getRegistrationArray = (token) => {
const obj = getRegistration(token)

if (typeof obj !== 'object' || obj === null) {
return []
}

const result = Object.entries(obj).map(([key, value]) => ({ ...value, id: key }))

return result
.map(({ _order, ...rest }) => ({ ...rest, _order: _order ?? Number.MAX_SAFE_INTEGER }))
.sort((a, b) => a._order - b._order)
}

/**
* 注册全局数据
* @param {string} token
* @param {any} value
* @param {{ mergeObject: boolean }}
*/
export const register = (token, value, { mergeObject } = {}) => {
if (mergeObject) {
const registered = getRegistration(token) || {}
registration[token] = { ...registered, ...value }
} else {
registration[token] = value
}
}
2 changes: 1 addition & 1 deletion packages/entry/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*
*/

export { getMergeMeta, getPluginApi, getOptions } from './common'
export { getMergeMeta, getPluginApi, getOptions, register, getRegistration, getRegistrationArray } from './common'
export { useCompile } from './templateHash'
export { defineEntry, callEntry, beforeCallEntry, afterCallEntry, mergeRegistry, getMergeRegistry } from './entryHash'
export { getLayoutComponent } from './layoutHash'
Expand Down
3 changes: 3 additions & 0 deletions packages/plugins/block/src/Main.vue
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ import {
publishBlock
} from './js/blockSetting'
import { fetchBlockList, requestBlocks, requestInitBlocks, fetchBlockContent } from './js/http'
import { registerVariableConfiguratorList } from './register'
registerVariableConfiguratorList()
const docsUrl = useHelp().getDocsUrl('block')
const { SORT_TYPE } = constants
Expand Down
Loading

0 comments on commit 9f8da92

Please sign in to comment.