Vue3一种仿有赞微页面的实现方案
前言
最近答应教弟弟用Vue3+ts写业务代码,拿商城练手。 牵涉到类似有赞微页面的功能,随手把实现方案分享一下。 demo
需求
设计功能组件,按需布局,可控参数,多端复用。下图
分析
图中视图区域分Widgets
、Page
、Action
。
分别为组件列表、渲染区域、组件配置。
用户根据需要拖拽左侧Widget至Page区域渲染,右侧显示组件配置项。
Action
区域参数变动,Page
区域渲染
实现方案
根据图片视图布局分析,Layout
需要暴露Api挂载Widget
供用户选择。
考虑到维护和扩展,Layout
只负责Widget
挂载、视图渲染,不做其它扩展功能。
单Widget
包含Page
区域和Action
区域渲染,并联动。
type WidgetNode = ReturnType<typeof defineComponent> | VNode | Component;
interface Widget {
title: string;
key: string;
component: WidgetNode,
data?: Object;
}
interface PageWidget extends Widget {
id: number;
}
export default defineComponent({
name: 'LayoutCore'
setup () {
// 左侧列表
const asideWidgets = ref<Widget[]>([]);
// Page
const pageWidgetsRef = ref<PageWidget[]>([]);
// Action
const actionRender = ref<WidgetNode>();
}
render () {
return (
<div class='layout'>
<div>
// 渲染asideWidgets
</div>
<div>
// 渲染pageWidgetsRef
</div>
<div>
// 渲染actionRender
</div>
</div>
)
}
})
考虑到Widget
、Action
部分跟Page
区域牵涉到数据联动,首先想到单Widget
渲染区域跟Action
区域共用一个VNode
实例。省去维护数据流操作。
// Widget结构
interface TitleTextProps {
title: String;
}
const titleTextProps = {
data: Object as PropType<TitleTextProps>
}
export default defineComponent({
name: 'TitleText',
props: titleTextProps,
setup () {
const model = ref({
title
});
const modelUnrf= unref(model)
// 焦点id
const currentPageWidgetId = ref<number>();
function renderAction () {
return (
// 渲染Action
...
<input v-model:value={modelUnrf.title} />
)
}
// 这里返回model, renderAction
return {
model,
renderAction
}
},
render () {
const {
model
} = this
return (
// 渲染模板
...
<div>
{model.title}
</div>
)
}
})
这里Widget
暴露model
、renderAction
,供Layout
渲染Page区域和Action区域。
用户选中拖拽左侧Widgets区域组件至Page区域,Layout获取当前Widget push到pageWidgetsRef
// 添加Page widget
function handleAddPageWidget (widget: AsideWidget<any>) {
// 生成唯一id
const id = generatorPageWidgetId(pageWidgetsRef.value);
// 插入渲染区域数据
pageWidgetsRef.value.push({
...widget,
id
});
// 设置焦点id
handleSetCurrentPageId(id);
}
Page
区域渲染Widget
interface WidgetsRef {
[key: number]: Ref<WidgetNode>;
}
...
setup () {
...
const widgetsRef = ref<WidgetsRef>({})
// 设置page widgets ref用于获取实体
// 对应关系 id => widget
function handleSetRefs (node: any, id: number) {
widgetsRefs.value[id] = node;
}
return {
pageWidgets: pageWidgetsRef
}
},
render () {
const {
pageWidgets
} = this
return (
...
{pageWidgets.map((widget) => {
return (
<widget.component {...{
id: widget.id,
widgetKey: widget.key,
// 默认数据(记得深拷贝)
data: _.cloneDeep(widget.data) || {},
}} ref={(e) => handleSetRefs(e, widget.id)} />
)
})}
...
)
}
渲染Page
区域时我们拿到当前Widget
的句柄ref
, 通过handleSetRefs(e, widget.id)
保存当前组件渲染实例。
我们拿到焦点id
, 通过currentPageWidgetId
获取当前Page区域焦点Widgetref
实例句柄渲染renderAction
:
const renderAction = computed<WidgetNode>(() => {
return pageWidgetsRef.value[currentPageWidgetId].renderAction
})
...
<renderAction />
...
上边只是简单原理实现,主要功能在ref={(e) => handleSetRefs(e, widget.id)}
获取渲染Widget的实例句柄,获取暴露的renderAction
渲染。
上述的Widget
都包含渲染视图和renderAction
, 有种情况,Page
区域不需要渲染,只渲染renderAction
。这时候拿不到Widget
实例,解决方案也很简单。单独维护一份列表隐式渲染获取Widget
实例句柄。
附上github free-core
转载自:https://juejin.cn/post/7087195245636485134