Commit ff410b224fafa2431a28d4f9c8e1912de160a373
1 parent
a4b13f8d
refactor(vuex): add element module
Showing
7 changed files
with
65 additions
and
350 deletions
front-end/h5/src/components/core/editor.js deleted
100644 → 0
| 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
| 1 | +import { mapState, mapActions } from 'vuex' | |
| 1 | 2 | import Shape from '../../support/shape' |
| 3 | + | |
| 2 | 4 | export default { |
| 3 | - props: ['elements', 'editingElement', 'handleClickElementProp', 'handleClickCanvasProp'], | |
| 5 | + props: ['elements', 'handleClickElementProp', 'handleClickCanvasProp'], | |
| 4 | 6 | data: () => ({ |
| 5 | 7 | vLines: [], |
| 6 | 8 | hLines: [], |
| 7 | 9 | contextmenuPos: [] |
| 8 | 10 | }), |
| 11 | + computed: { | |
| 12 | + ...mapState('element', { | |
| 13 | + editingElement: state => state.editingElement | |
| 14 | + }) | |
| 15 | + }, | |
| 9 | 16 | methods: { |
| 17 | + ...mapActions('element', [ | |
| 18 | + 'setEditingElement' // -> this.foo() | |
| 19 | + ]), | |
| 10 | 20 | // TODO #!zh: 优化代码 |
| 11 | 21 | // generate vertical line |
| 12 | 22 | drawVLine (newLeft) { |
| ... | ... | @@ -86,6 +96,11 @@ export default { |
| 86 | 96 | hideContextMenu () { |
| 87 | 97 | this.contextmenuPos = [] |
| 88 | 98 | }, |
| 99 | + handleClickCanvas (e) { | |
| 100 | + if (!e.target.classList.contains('element-on-edit-canvas')) { | |
| 101 | + this.setEditingElement() | |
| 102 | + } | |
| 103 | + }, | |
| 89 | 104 | /** |
| 90 | 105 | * #!zh: renderCanvas 渲染中间画布 |
| 91 | 106 | * elements |
| ... | ... | @@ -100,7 +115,7 @@ export default { |
| 100 | 115 | class="canvas-editor-wrapper" |
| 101 | 116 | onClick={(e) => { |
| 102 | 117 | this.hideContextMenu() |
| 103 | - this.handleClickCanvasProp(e) | |
| 118 | + this.handleClickCanvas(e) | |
| 104 | 119 | }} |
| 105 | 120 | onContextmenu={e => { |
| 106 | 121 | this.bindContextMenu(e) |
| ... | ... | @@ -117,7 +132,7 @@ export default { |
| 117 | 132 | props: element.pluginProps, // #6 #3 |
| 118 | 133 | on: { |
| 119 | 134 | // 高亮当前点击的元素 |
| 120 | - // click: () => this.handleClickElementProp(element) | |
| 135 | + // click: () => this.setEditingElement(element) | |
| 121 | 136 | input: ({ value, pluginName }) => { |
| 122 | 137 | if (pluginName === 'lbp-text') { |
| 123 | 138 | element.pluginProps.text = value |
| ... | ... | @@ -128,13 +143,12 @@ export default { |
| 128 | 143 | return ( |
| 129 | 144 | <Shape |
| 130 | 145 | element={element} |
| 131 | - editingElement={this.editingElement} | |
| 132 | 146 | active={this.editingElement === element} |
| 133 | 147 | handleMousedownProp={() => { |
| 134 | 148 | // 在 shape 上面添加 mousedown,而非 plugin 本身添加 onClick 的原因: |
| 135 | 149 | // 在 mousedown 的时候,即可激活 editingElement(当前选中元素) |
| 136 | 150 | // 这样,就不用等到鼠标抬起的时候,也就是 plugin 的 onClick 生效的时候,才给选中的元素添加边框等选中效果 |
| 137 | - this.handleClickElementProp(element) | |
| 151 | + this.setEditingElement(element) | |
| 138 | 152 | }} |
| 139 | 153 | handleElementMoveProp={this.handleElementMove} |
| 140 | 154 | > | ... | ... |
front-end/h5/src/components/core/editor/edit-panel/props.js
| 1 | +import { mapState, mapActions } from 'vuex' | |
| 2 | + | |
| 1 | 3 | export default { |
| 2 | - props: ['editingElement'], | |
| 4 | + computed: { | |
| 5 | + ...mapState('element', { | |
| 6 | + editingElement: state => state.editingElement | |
| 7 | + }) | |
| 8 | + }, | |
| 3 | 9 | methods: { |
| 10 | + ...mapActions('element', [ | |
| 11 | + 'setEditingElement' // -> this.foo() | |
| 12 | + ]), | |
| 4 | 13 | /** |
| 5 | 14 | * 将插件属性的 自定义增强编辑器注入 属性编辑面板中 |
| 6 | 15 | */ | ... | ... |
front-end/h5/src/components/core/editor/index.js
| ... | ... | @@ -31,7 +31,6 @@ export default { |
| 31 | 31 | activeMenuKey: 'pluginList', |
| 32 | 32 | pages: [], |
| 33 | 33 | elements: [], |
| 34 | - editingElement: null, | |
| 35 | 34 | isPreviewMode: false |
| 36 | 35 | }), |
| 37 | 36 | methods: { |
| ... | ... | @@ -51,14 +50,6 @@ export default { |
| 51 | 50 | // const defaultPropsValue = this.getPropsDefaultValue(name) |
| 52 | 51 | const editorConfig = this.getEditorConfig(name) |
| 53 | 52 | this.elements.push(new Element({ name, zindex, editorConfig })) |
| 54 | - }, | |
| 55 | - setCurrentEditingElement (element) { | |
| 56 | - this.editingElement = element | |
| 57 | - }, | |
| 58 | - handleClickCanvas (e) { | |
| 59 | - if (!e.target.classList.contains('element-on-edit-canvas')) { | |
| 60 | - this.editingElement = null | |
| 61 | - } | |
| 62 | 53 | } |
| 63 | 54 | }, |
| 64 | 55 | render (h) { |
| ... | ... | @@ -113,9 +104,6 @@ export default { |
| 113 | 104 | ? <RenderPreviewCanvas elements={this.elements}/> |
| 114 | 105 | : <RenderEditCanvas |
| 115 | 106 | elements={this.elements} |
| 116 | - handleClickElementProp={this.setCurrentEditingElement} | |
| 117 | - handleClickCanvasProp={this.handleClickCanvas} | |
| 118 | - editingElement={this.editingElement} | |
| 119 | 107 | /> |
| 120 | 108 | } |
| 121 | 109 | </div> |
| ... | ... | @@ -135,7 +123,7 @@ export default { |
| 135 | 123 | 属性 |
| 136 | 124 | </span> |
| 137 | 125 | {/* { this.renderPropsEditorPanel(h) } */} |
| 138 | - <RenderPropsEditor editingElement={this.editingElement} /> | |
| 126 | + <RenderPropsEditor/> | |
| 139 | 127 | </a-tab-pane> |
| 140 | 128 | <a-tab-pane label="动画" key='动画' tab='动画'>动画</a-tab-pane> |
| 141 | 129 | <a-tab-pane label="动作" key='动作' tab='动作'>动作</a-tab-pane> | ... | ... |
front-end/h5/src/components/core/support/shape.js
| ... | ... | @@ -13,7 +13,7 @@ const directionKey = { |
| 13 | 13 | const points = ['lt', 'rt', 'lb', 'rb', 'l', 'r', 't', 'b'] |
| 14 | 14 | |
| 15 | 15 | export default { |
| 16 | - props: ['element', 'active', 'editingElement', 'handleMousedownProp', 'handleElementMoveProp'], | |
| 16 | + props: ['element', 'active', 'handleMousedownProp', 'handleElementMoveProp'], | |
| 17 | 17 | methods: { |
| 18 | 18 | /** |
| 19 | 19 | * 通过方位计算样式,主要是 top、left、鼠标样式 | ... | ... |
front-end/h5/src/store/index.js
| ... | ... | @@ -4,6 +4,7 @@ import editor from './modules/editor' |
| 4 | 4 | import user from './modules/user' |
| 5 | 5 | import visible from './modules/visible' |
| 6 | 6 | import loading from './modules/loading' |
| 7 | +import element from './modules/element' | |
| 7 | 8 | |
| 8 | 9 | Vue.use(Vuex) |
| 9 | 10 | |
| ... | ... | @@ -21,6 +22,7 @@ export default new Vuex.Store({ |
| 21 | 22 | editor, |
| 22 | 23 | user, |
| 23 | 24 | visible, |
| 24 | - loading | |
| 25 | + loading, | |
| 26 | + element | |
| 25 | 27 | } |
| 26 | 28 | }) | ... | ... |
front-end/h5/src/store/modules/element.js
0 → 100644
| 1 | +// initial state | |
| 2 | +const state = { | |
| 3 | + editingElement: null | |
| 4 | +} | |
| 5 | + | |
| 6 | +// getters | |
| 7 | +const getters = { | |
| 8 | + | |
| 9 | +} | |
| 10 | + | |
| 11 | +// actions | |
| 12 | +const actions = { | |
| 13 | + setEditingElement ({ commit }, payload) { | |
| 14 | + commit('setEditingElement', payload) | |
| 15 | + } | |
| 16 | +} | |
| 17 | + | |
| 18 | +// mutations | |
| 19 | +const mutations = { | |
| 20 | + setEditingElement (state, payload) { | |
| 21 | + state.editingElement = payload | |
| 22 | + } | |
| 23 | +} | |
| 24 | + | |
| 25 | +export default { | |
| 26 | + namespaced: true, | |
| 27 | + state, | |
| 28 | + getters, | |
| 29 | + actions, | |
| 30 | + mutations | |
| 31 | +} | ... | ... |