Commit 7fd014b8540d6b828071675ec386dd0528536eac
1 parent
6afa3da0
feat: text plugin
Showing
5 changed files
with
660 additions
and
2 deletions
front-end/h5/src/components/core/editor.js
0 → 100644
| 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
| @@ -91,9 +91,15 @@ export default { | @@ -91,9 +91,15 @@ export default { | ||
| 91 | style, | 91 | style, |
| 92 | class: 'element-on-edit-canvas', // TODO 添加为何添加 class 的原因:与 handleClickCanvas 配合 | 92 | class: 'element-on-edit-canvas', // TODO 添加为何添加 class 的原因:与 handleClickCanvas 配合 |
| 93 | props: element.pluginProps, // #6 #3 | 93 | props: element.pluginProps, // #6 #3 |
| 94 | - nativeOn: { | 94 | + on: { |
| 95 | // 高亮当前点击的元素 | 95 | // 高亮当前点击的元素 |
| 96 | // click: () => this.handleClickElementProp(element) | 96 | // click: () => this.handleClickElementProp(element) |
| 97 | + input: ({ value, pluginName }) => { | ||
| 98 | + if (pluginName === 'lbp-text') { | ||
| 99 | + debugger | ||
| 100 | + element.pluginProps.text = value | ||
| 101 | + } | ||
| 102 | + } | ||
| 97 | } | 103 | } |
| 98 | } | 104 | } |
| 99 | return ( | 105 | return ( |
front-end/h5/src/components/core/models/element.js
| @@ -50,7 +50,7 @@ class Element { | @@ -50,7 +50,7 @@ class Element { | ||
| 50 | height: `${pluginProps.height || commonStyle.height}px`, | 50 | height: `${pluginProps.height || commonStyle.height}px`, |
| 51 | fontSize: `${pluginProps.fontSize || commonStyle.fontSize}px`, | 51 | fontSize: `${pluginProps.fontSize || commonStyle.fontSize}px`, |
| 52 | color: pluginProps.color || commonStyle.color, | 52 | color: pluginProps.color || commonStyle.color, |
| 53 | - backgroundColor: pluginProps.backgroundColor || commonStyle.backgroundColor, | 53 | + // backgroundColor: pluginProps.backgroundColor || commonStyle.backgroundColor, |
| 54 | textAlign: pluginProps.textAlign || commonStyle.textAlign | 54 | textAlign: pluginProps.textAlign || commonStyle.textAlign |
| 55 | } | 55 | } |
| 56 | return style | 56 | return style |
front-end/h5/src/components/plugins/lbp-text.js
0 → 100644
| 1 | +export default { | ||
| 2 | + render (h) { | ||
| 3 | + const self = this | ||
| 4 | + const { | ||
| 5 | + color, | ||
| 6 | + textAlign, | ||
| 7 | + fontSize, | ||
| 8 | + lineHeight, | ||
| 9 | + borderColor | ||
| 10 | + } = this | ||
| 11 | + | ||
| 12 | + const style = { | ||
| 13 | + color, | ||
| 14 | + textAlign, | ||
| 15 | + backgroundColor: 'transparent', | ||
| 16 | + fontSize: fontSize, | ||
| 17 | + lineHeight: lineHeight + 'em', | ||
| 18 | + borderColor, | ||
| 19 | + textDecoration: 'none' | ||
| 20 | + } | ||
| 21 | + return h('div', { | ||
| 22 | + style, | ||
| 23 | + on: { | ||
| 24 | + dblclick () { | ||
| 25 | + self.canEdit = true | ||
| 26 | + } | ||
| 27 | + } | ||
| 28 | + }, [ | ||
| 29 | + h('div', { | ||
| 30 | + ref: 'editableText', | ||
| 31 | + style: { | ||
| 32 | + height: '100%' | ||
| 33 | + }, | ||
| 34 | + domProps: { | ||
| 35 | + innerHTML: self.innerText, | ||
| 36 | + contentEditable: self.canEdit | ||
| 37 | + }, | ||
| 38 | + on: { | ||
| 39 | + blur () { | ||
| 40 | + self.canEdit = false | ||
| 41 | + }, | ||
| 42 | + input () { | ||
| 43 | + self.$emit('input', { | ||
| 44 | + value: self.$refs.editableText.innerHTML, | ||
| 45 | + pluginName: 'lbp-text' | ||
| 46 | + }) | ||
| 47 | + } | ||
| 48 | + } | ||
| 49 | + }) | ||
| 50 | + | ||
| 51 | + ]) | ||
| 52 | + }, | ||
| 53 | + name: 'lbp-text', | ||
| 54 | + data () { | ||
| 55 | + return { | ||
| 56 | + canEdit: false, | ||
| 57 | + innerText: this.text || '双击修改文字' | ||
| 58 | + } | ||
| 59 | + }, | ||
| 60 | + props: { | ||
| 61 | + text: { | ||
| 62 | + type: String, | ||
| 63 | + default: '双击修改文字' | ||
| 64 | + }, | ||
| 65 | + type: { | ||
| 66 | + type: String, | ||
| 67 | + default: 'text' | ||
| 68 | + }, | ||
| 69 | + placeholder: { | ||
| 70 | + type: String, | ||
| 71 | + default: '请填写提示文字' | ||
| 72 | + }, | ||
| 73 | + required: { | ||
| 74 | + type: Boolean, | ||
| 75 | + default: false | ||
| 76 | + }, | ||
| 77 | + disabled: { | ||
| 78 | + type: Boolean, | ||
| 79 | + default: false | ||
| 80 | + }, | ||
| 81 | + backgroundColor: { | ||
| 82 | + type: String, | ||
| 83 | + default: 'transparent' | ||
| 84 | + }, | ||
| 85 | + color: { | ||
| 86 | + type: String, | ||
| 87 | + default: 'black' | ||
| 88 | + }, | ||
| 89 | + fontSize: { | ||
| 90 | + type: Number, | ||
| 91 | + default: 14 | ||
| 92 | + }, | ||
| 93 | + lineHeight: { | ||
| 94 | + type: Number, | ||
| 95 | + default: 1 | ||
| 96 | + }, | ||
| 97 | + borderWidth: { | ||
| 98 | + type: Number, | ||
| 99 | + default: 1 | ||
| 100 | + }, | ||
| 101 | + borderRadius: { | ||
| 102 | + type: Number, | ||
| 103 | + default: 0 | ||
| 104 | + }, | ||
| 105 | + borderColor: { | ||
| 106 | + type: String, | ||
| 107 | + default: '#ced4da' | ||
| 108 | + }, | ||
| 109 | + borderStyle: { | ||
| 110 | + type: String, | ||
| 111 | + default: 'solid' | ||
| 112 | + }, | ||
| 113 | + textAlign: { | ||
| 114 | + type: String, | ||
| 115 | + default: 'center' | ||
| 116 | + } | ||
| 117 | + }, | ||
| 118 | + editorConfig: { | ||
| 119 | + propsConfig: { | ||
| 120 | + // text: { | ||
| 121 | + // type: 'a-input', | ||
| 122 | + // label: '按钮文字', | ||
| 123 | + // require: true, | ||
| 124 | + // defaultPropValue: '双击修改文字' | ||
| 125 | + // }, | ||
| 126 | + fontSize: { | ||
| 127 | + type: 'a-input-number', | ||
| 128 | + label: '字号(px)', | ||
| 129 | + require: true, | ||
| 130 | + prop: { | ||
| 131 | + step: 1, | ||
| 132 | + min: 12, | ||
| 133 | + max: 144 | ||
| 134 | + }, | ||
| 135 | + defaultPropValue: 14 | ||
| 136 | + }, | ||
| 137 | + color: { | ||
| 138 | + type: 'a-input', | ||
| 139 | + label: '文字颜色', | ||
| 140 | + // !#zh 为编辑组件指定 prop | ||
| 141 | + prop: { | ||
| 142 | + type: 'color' | ||
| 143 | + }, | ||
| 144 | + require: true, | ||
| 145 | + defaultPropValue: 'black' | ||
| 146 | + }, | ||
| 147 | + backgroundColor: { | ||
| 148 | + type: 'a-input', // lbs-color-picker | ||
| 149 | + label: '背景颜色', | ||
| 150 | + prop: { | ||
| 151 | + type: 'color' | ||
| 152 | + }, | ||
| 153 | + require: true, | ||
| 154 | + defaultPropValue: '#ffffff' // TODO why logogram for color does't work? | ||
| 155 | + }, | ||
| 156 | + borderColor: { | ||
| 157 | + type: 'a-input', // lbs-color-picker | ||
| 158 | + label: '边框颜色', | ||
| 159 | + prop: { | ||
| 160 | + type: 'color' | ||
| 161 | + }, | ||
| 162 | + require: true, | ||
| 163 | + defaultPropValue: '#333333' | ||
| 164 | + }, | ||
| 165 | + borderWidth: { | ||
| 166 | + type: 'a-input-number', | ||
| 167 | + label: '边框宽度(px)', | ||
| 168 | + require: true, | ||
| 169 | + prop: { | ||
| 170 | + step: 1, | ||
| 171 | + min: 1, | ||
| 172 | + max: 10 | ||
| 173 | + }, | ||
| 174 | + defaultPropValue: 1 | ||
| 175 | + }, | ||
| 176 | + borderRadius: { | ||
| 177 | + type: 'a-input-number', | ||
| 178 | + label: '圆角(px)', | ||
| 179 | + require: true, | ||
| 180 | + prop: { | ||
| 181 | + step: 0.1, | ||
| 182 | + min: 0, | ||
| 183 | + max: 10 | ||
| 184 | + }, | ||
| 185 | + defaultPropValue: 0 | ||
| 186 | + }, | ||
| 187 | + borderStyle: { | ||
| 188 | + type: 'a-input', | ||
| 189 | + label: '边框形式', | ||
| 190 | + require: true, | ||
| 191 | + defaultPropValue: 'solid' | ||
| 192 | + }, | ||
| 193 | + lineHeight: { | ||
| 194 | + type: 'a-input-number', | ||
| 195 | + label: '行高', | ||
| 196 | + require: true, | ||
| 197 | + prop: { | ||
| 198 | + step: 0.1, | ||
| 199 | + min: 0.1, | ||
| 200 | + max: 10 | ||
| 201 | + }, | ||
| 202 | + defaultPropValue: 1 | ||
| 203 | + }, | ||
| 204 | + textAlign: { | ||
| 205 | + type: 'lbs-text-align', | ||
| 206 | + label: '文字对齐', | ||
| 207 | + require: true, | ||
| 208 | + defaultPropValue: 'center' | ||
| 209 | + } | ||
| 210 | + }, | ||
| 211 | + components: { | ||
| 212 | + 'lbs-text-align': { | ||
| 213 | + template: ` | ||
| 214 | + <div class="wrap"> | ||
| 215 | + <a-radio-group v-model="value_" size="small"> | ||
| 216 | + <a-tooltip effect="dark" :content="item.label" placement="top" :key="index" v-for="(item, index) in textAlignTabs"> | ||
| 217 | + <a-radio-button :label="item.value"> | ||
| 218 | + <!-- issue #8 --> | ||
| 219 | + <i :class="['fa', 'fa-align-'+item.value]" aria-hidden="true"></i> | ||
| 220 | + </a-radio-button> | ||
| 221 | + </a-tooltip> | ||
| 222 | + </a-radio-group> | ||
| 223 | + </div>`, | ||
| 224 | + props: { | ||
| 225 | + value: { | ||
| 226 | + type: [String, Number] | ||
| 227 | + } | ||
| 228 | + }, | ||
| 229 | + data: () => ({ | ||
| 230 | + textAlignTabs: [{ | ||
| 231 | + label: '左对齐', | ||
| 232 | + value: 'left' | ||
| 233 | + }, | ||
| 234 | + { | ||
| 235 | + label: '居中对齐', | ||
| 236 | + value: 'center' | ||
| 237 | + }, | ||
| 238 | + { | ||
| 239 | + label: '右对齐', | ||
| 240 | + value: 'right' | ||
| 241 | + }] | ||
| 242 | + }), | ||
| 243 | + computed: { | ||
| 244 | + value_: { | ||
| 245 | + // TODO 关于箭头函数中的this:这里不能写成箭头函数,否则 this 为 undefined,为何? | ||
| 246 | + // http://davidshariff.com/blog/what-is-the-execution-context-in-javascript/ | ||
| 247 | + // https://tangxiaolang101.github.io/2016/08/01/%E6%B7%B1%E5%85%A5%E6%8E%A2%E8%AE%A8JavaScript%E7%9A%84%E6%89%A7%E8%A1%8C%E7%8E%AF%E5%A2%83%E5%92%8C%E6%A0%88%EF%BC%88What%20is%20the%20Execution%20Context%20&%20Stack%20in%20JavaScript%EF%BC%89/ | ||
| 248 | + get () { | ||
| 249 | + return this.value | ||
| 250 | + }, | ||
| 251 | + set (val) { | ||
| 252 | + this.$emit('input', val) | ||
| 253 | + } | ||
| 254 | + } | ||
| 255 | + } | ||
| 256 | + }, | ||
| 257 | + 'lbs-select-input-type': { | ||
| 258 | + props: ['value'], | ||
| 259 | + computed: { | ||
| 260 | + value_: { | ||
| 261 | + get () { | ||
| 262 | + return this.value | ||
| 263 | + }, | ||
| 264 | + set (val) { | ||
| 265 | + this.$emit('input', val) | ||
| 266 | + } | ||
| 267 | + } | ||
| 268 | + }, | ||
| 269 | + template: ` | ||
| 270 | + <a-select v-model="value_" placeholder="类型"> | ||
| 271 | + <a-option | ||
| 272 | + v-for="item in options" | ||
| 273 | + :key="item.value" | ||
| 274 | + :label="item.label" | ||
| 275 | + :value="item.value"> | ||
| 276 | + </a-option> | ||
| 277 | + </a-select> | ||
| 278 | + `, | ||
| 279 | + data: () => ({ | ||
| 280 | + options: [ | ||
| 281 | + { | ||
| 282 | + label: '文字', | ||
| 283 | + value: 'text' | ||
| 284 | + }, | ||
| 285 | + { | ||
| 286 | + label: '密码', | ||
| 287 | + value: 'password' | ||
| 288 | + }, | ||
| 289 | + { | ||
| 290 | + label: '日期', | ||
| 291 | + value: 'date' | ||
| 292 | + }, | ||
| 293 | + { | ||
| 294 | + label: '邮箱', | ||
| 295 | + value: 'email' | ||
| 296 | + }, | ||
| 297 | + { | ||
| 298 | + label: '手机号', | ||
| 299 | + value: 'tel' | ||
| 300 | + } | ||
| 301 | + ] | ||
| 302 | + }) | ||
| 303 | + } | ||
| 304 | + } | ||
| 305 | + } | ||
| 306 | +} |
front-end/h5/src/views/Editor.vue
| @@ -4,9 +4,26 @@ import CoreEditor from '../components/core/editor/index.js' | @@ -4,9 +4,26 @@ import CoreEditor from '../components/core/editor/index.js' | ||
| 4 | 4 | ||
| 5 | import LbpButton from '../components/plugins/lbp-button' | 5 | import LbpButton from '../components/plugins/lbp-button' |
| 6 | import LbpPicture from '../components/plugins/lbp-picture' | 6 | import LbpPicture from '../components/plugins/lbp-picture' |
| 7 | +import LbpText from '../components/plugins/lbp-text' | ||
| 7 | 8 | ||
| 8 | const PluginList = [ | 9 | const PluginList = [ |
| 9 | { | 10 | { |
| 11 | + title: '文字', | ||
| 12 | + icon: 'hand-pointer-o', | ||
| 13 | + component: LbpText, | ||
| 14 | + visible: true, | ||
| 15 | + name: 'lbp-text', | ||
| 16 | + children: [ | ||
| 17 | + { | ||
| 18 | + title: '文字', | ||
| 19 | + icon: 'hand-pointer-o', | ||
| 20 | + component: LbpText, | ||
| 21 | + visible: true, | ||
| 22 | + name: 'lbp-text' | ||
| 23 | + } | ||
| 24 | + ] | ||
| 25 | + }, | ||
| 26 | + { | ||
| 10 | title: '按钮', | 27 | title: '按钮', |
| 11 | icon: 'hand-pointer-o', | 28 | icon: 'hand-pointer-o', |
| 12 | component: LbpButton, | 29 | component: LbpButton, |