Skip to content

Commit 7fd014b

Browse files
committed
feat: text plugin
1 parent 6afa3da commit 7fd014b

File tree

5 files changed

+660
-2
lines changed

5 files changed

+660
-2
lines changed
+329
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
import Vue from 'vue'
2+
import Element from './models/element'
3+
import './styles/index.scss'
4+
5+
export default {
6+
name: 'Editor',
7+
components: {
8+
ShortcutButton: {
9+
functional: true,
10+
props: {
11+
faIcon: {
12+
required: true,
13+
type: String
14+
},
15+
title: {
16+
required: true,
17+
type: String
18+
},
19+
clickFn: {
20+
required: false,
21+
type: Function
22+
}
23+
},
24+
render: (h, { props, listeners, slots }) => {
25+
const onClick = props.clickFn || function () {}
26+
return (
27+
<a-button
28+
class="shortcut-button"
29+
onClick={onClick}
30+
>
31+
<i
32+
class={['shortcut-icon', 'fa', `fa-${props.faIcon}`]}
33+
aria-hidden='true'
34+
/>
35+
<span>{ props.title }</span>
36+
</a-button>
37+
)
38+
}
39+
}
40+
},
41+
data: () => ({
42+
activeMenuKey: 'pluginList',
43+
pages: [],
44+
elements: [],
45+
editingElement: null,
46+
isPreviewMode: false
47+
}),
48+
methods: {
49+
getEditorConfig (pluginName) {
50+
// const pluginCtor = Vue.options[pluginName]
51+
// const pluginCtor = this.$options.components[pluginName]
52+
const PluginCtor = Vue.component(pluginName)
53+
return new PluginCtor().$options.editorConfig
54+
},
55+
/**
56+
* !#zh 点击插件,copy 其基础数据到组件树(中间画布)
57+
* #!en click the plugin shortcut, create new Element with the plugin's meta data
58+
* pluginInfo {Object}: 插件列表中的基础数据, {name}=pluginInfo
59+
*/
60+
clone ({ name }) {
61+
const zindex = this.elements.length + 1
62+
// const defaultPropsValue = this.getPropsDefaultValue(name)
63+
const editorConfig = this.getEditorConfig(name)
64+
this.elements.push(new Element({ name, zindex, editorConfig }))
65+
},
66+
mixinPluginCustomComponents2Editor () {
67+
const { components } = this.editingElement.editorConfig
68+
for (const key in components) {
69+
if (this.$options.components[key]) return
70+
this.$options.components[key] = components[key]
71+
}
72+
},
73+
setCurrentEditingElement (element) {
74+
this.editingElement = element
75+
this.mixinPluginCustomComponents2Editor()
76+
},
77+
/**
78+
* #!zh: 在左侧或顶部导航上显示可用的组件快捷方式,用户点击之后,即可将其添加到中间画布上
79+
* #!en: render shortcust at the sidebar or the header. if user click the shortcut, the related plugin will be added to the canvas
80+
* @param {Object} group: {children, title, icon}
81+
*/
82+
renderPluginShortcut (group) {
83+
return group.children.length === 1
84+
? this.renderSinglePluginShortcut(group)
85+
: this.renderMultiPluginShortcuts(group)
86+
},
87+
/**
88+
* #!zh 渲染多个插件的快捷方式
89+
* #!en render shortcuts for multi plugins
90+
* @param {Object} group: {children, title, icon}
91+
*/
92+
renderMultiPluginShortcuts (group) {
93+
const plugins = group.children
94+
return <a-popover
95+
placement="bottom"
96+
class="shortcust-button"
97+
trigger="hover">
98+
<a-row slot="content" gutter={20} style={{ width: '400px' }}>
99+
{
100+
plugins.sort().map(item => (
101+
<a-col span={6}>
102+
<ShortcutButton
103+
clickFn={this.clone.bind(this, item)}
104+
title={item.title}
105+
faIcon={item.icon}
106+
/>
107+
</a-col>
108+
))
109+
}
110+
</a-row>
111+
<ShortcutButton
112+
title={group.title}
113+
faIcon={group.icon}
114+
/>
115+
</a-popover>
116+
},
117+
/**
118+
* #!zh: 渲染单个插件的快捷方式
119+
* #!en: render shortcut for single plugin
120+
* @param {Object} group: {children, title, icon}
121+
*/
122+
renderSinglePluginShortcut ({ children }) {
123+
const [plugin] = children
124+
return <ShortcutButton
125+
clickFn={this.clone.bind(this, plugin)}
126+
title={plugin.title}
127+
faIcon={plugin.icon}
128+
/>
129+
},
130+
/**
131+
* #!zh: renderCanvas 渲染中间画布
132+
* elements
133+
* @param {*} h
134+
* @param {*} elements
135+
* @returns
136+
*/
137+
renderCanvas (h, elements) {
138+
return (
139+
<div style={{ height: '100%' }}>
140+
{elements.map((element, index) => {
141+
return (() => {
142+
const data = {
143+
style: element.getStyle(),
144+
props: element.pluginProps, // #6 #3
145+
nativeOn: {
146+
click: this.setCurrentEditingElement.bind(this, element)
147+
},
148+
on: {
149+
input ({ pluginName, value }) {
150+
if (pluginName === 'lbp-text') {
151+
element.pluginProps.text = value
152+
}
153+
}
154+
}
155+
}
156+
return h(element.name, data)
157+
})()
158+
})}
159+
</div>
160+
)
161+
},
162+
renderPreview (h, elements) {
163+
return (
164+
<div style={{ height: '100%' }}>
165+
{elements.map((element, index) => {
166+
return (() => {
167+
const data = {
168+
style: element.getStyle(),
169+
props: element.pluginProps, // #6 #3
170+
nativeOn: {}
171+
}
172+
return h(element.name, data)
173+
})()
174+
})}
175+
</div>
176+
)
177+
},
178+
renderPluginListPanel () {
179+
return (
180+
<a-row gutter={20}>
181+
{
182+
this.groups.sort().map(group => (
183+
<a-col span={12} style={{ marginTop: '10px' }}>
184+
{this.renderPluginShortcut(group)}
185+
</a-col>
186+
))
187+
}
188+
</a-row>
189+
)
190+
},
191+
renderPropsEditorPanel (h) {
192+
const formLayout = {
193+
labelCol: { span: 5 },
194+
wrapperCol: { span: 8 }
195+
}
196+
if (!this.editingElement) return (<span>请先选择一个元素</span>)
197+
const editingElement = this.editingElement
198+
const propsConfig = editingElement.editorConfig.propsConfig
199+
return (
200+
<a-form ref="form" layout="horizontal">
201+
{
202+
Object.keys(propsConfig).map(propKey => {
203+
const item = propsConfig[propKey]
204+
// https://vuejs.org/v2/guide/render-function.html
205+
const data = {
206+
props: {
207+
...item.prop,
208+
// https://vuejs.org/v2/guide/render-function.html#v-model
209+
value: editingElement.pluginProps[propKey] || item.defaultPropValue
210+
},
211+
on: {
212+
// https://vuejs.org/v2/guide/render-function.html#v-model
213+
// input (e) {
214+
// editingElement.pluginProps[propKey] = e.target ? e.target.value : e
215+
// }
216+
change (e) {
217+
editingElement.pluginProps[propKey] = e.target ? e.target.value : e
218+
}
219+
}
220+
}
221+
return (
222+
<a-form-item
223+
label={item.label}
224+
{...formLayout}
225+
>
226+
{ h(item.type, data) }
227+
</a-form-item>
228+
)
229+
})
230+
}
231+
</a-form>
232+
)
233+
}
234+
},
235+
render (h) {
236+
return (
237+
<a-layout id="luban-layout" style={{ height: '100vh' }}>
238+
<a-layout-header class="header">
239+
<div class="logo">鲁班 H5</div>
240+
{/* TODO we can show the plugins shortcuts here
241+
<a-menu
242+
theme="dark"
243+
mode="horizontal"
244+
defaultSelectedKeys={['2']}
245+
style={{ lineHeight: '64px', float: 'left', marginLeft: '30%', background: 'transparent' }}
246+
>
247+
{
248+
this.groups.sort().map((group, id) => (
249+
<a-menu-item key={id} class="transparent-bg">
250+
{this.renderPluginShortcut(group)}
251+
</a-menu-item>
252+
))
253+
}
254+
</a-menu> */}
255+
<a-menu
256+
theme="dark"
257+
mode="horizontal"
258+
defaultSelectedKeys={['2']}
259+
style={{ lineHeight: '64px', float: 'right', background: 'transparent' }}
260+
>
261+
<a-menu-item key="1" class="transparent-bg"><a-button type="primary" size="small">预览</a-button></a-menu-item>
262+
<a-menu-item key="2" class="transparent-bg"><a-button size="small">保存</a-button></a-menu-item>
263+
<a-menu-item key="3" class="transparent-bg"><a-button size="small">发布</a-button></a-menu-item>
264+
</a-menu>
265+
</a-layout-header>
266+
<a-layout>
267+
<a-layout-sider width="160" style="background: #fff">
268+
<a-menu onSelect={val => { this.activeMenuKey = val }} mode="inline" defaultSelectedKeys={['pluginList']} style={{ height: '100%', borderRight: 1 }}>
269+
<a-menu-item key="pluginList">
270+
<a-icon type="user" />
271+
<span>组件列表</span>
272+
</a-menu-item>
273+
<a-menu-item key="2">
274+
<a-icon type="video-camera" />
275+
<span>页面管理</span>
276+
</a-menu-item>
277+
<a-menu-item key="3">
278+
<a-icon type="upload" />
279+
<span>更多模板</span>
280+
</a-menu-item>
281+
</a-menu>
282+
</a-layout-sider>
283+
<a-layout-sider width="240" theme='light' style={{ background: '#fff', padding: '0 12px' }}>
284+
{ this.renderPluginListPanel() }
285+
</a-layout-sider>
286+
<a-layout style="padding: 0 24px 24px">
287+
<a-layout-content style={{ padding: '24px', margin: 0, minHeight: '280px' }}>
288+
<div style="text-align: center;">
289+
<a-radio-group
290+
value={this.isPreviewMode}
291+
onInput={value => {
292+
this.isPreviewMode = value
293+
}}
294+
>
295+
<a-radio-button label={false} value={false}>Edit</a-radio-button>
296+
<a-radio-button label={true} value={true}>Preview</a-radio-button>
297+
</a-radio-group>
298+
</div>
299+
<div class='canvas-wrapper'>
300+
{ this.isPreviewMode ? this.renderPreview(h, this.elements) : this.renderCanvas(h, this.elements) }
301+
</div>
302+
</a-layout-content>
303+
</a-layout>
304+
<a-layout-sider width="240" theme='light' style={{ background: '#fff', padding: '0 12px' }}>
305+
<a-tabs type="card" style="height: 100%;">
306+
{/*
307+
#!zh tab 标题:
308+
#!en tab title
309+
ElementUI:label
310+
Ant Design Vue:tab
311+
*/}
312+
<a-tab-pane key="属性">
313+
<span slot="tab">
314+
<a-icon type="apple" />
315+
属性
316+
</span>
317+
<div style={{ overflow: 'scroll', height: '100vh' }}>
318+
{ this.renderPropsEditorPanel(h) }
319+
</div>
320+
</a-tab-pane>
321+
<a-tab-pane label="动画" key='动画' tab='动画'>动画</a-tab-pane>
322+
<a-tab-pane label="动作" key='动作' tab='动作'>动作</a-tab-pane>
323+
</a-tabs>
324+
</a-layout-sider>
325+
</a-layout>
326+
</a-layout>
327+
)
328+
}
329+
}

front-end/h5/src/components/core/editor/canvas/edit.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,15 @@ export default {
9191
style,
9292
class: 'element-on-edit-canvas', // TODO 添加为何添加 class 的原因:与 handleClickCanvas 配合
9393
props: element.pluginProps, // #6 #3
94-
nativeOn: {
94+
on: {
9595
// 高亮当前点击的元素
9696
// click: () => this.handleClickElementProp(element)
97+
input: ({ value, pluginName }) => {
98+
if (pluginName === 'lbp-text') {
99+
debugger
100+
element.pluginProps.text = value
101+
}
102+
}
97103
}
98104
}
99105
return (

front-end/h5/src/components/core/models/element.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class Element {
5050
height: `${pluginProps.height || commonStyle.height}px`,
5151
fontSize: `${pluginProps.fontSize || commonStyle.fontSize}px`,
5252
color: pluginProps.color || commonStyle.color,
53-
backgroundColor: pluginProps.backgroundColor || commonStyle.backgroundColor,
53+
// backgroundColor: pluginProps.backgroundColor || commonStyle.backgroundColor,
5454
textAlign: pluginProps.textAlign || commonStyle.textAlign
5555
}
5656
return style

0 commit comments

Comments
 (0)