流程图

点击模版列表添加到画布的行为流程图如下。

  • TemplatesData 为本地写死的模板数据,内部包含 l-text 组件的各个属性 。
  • 通过 Editor.vue 把 TemplatesData 传递给 ComponentsList.vue 组件。
  • ComponentsList.vue 组件接受并渲染父组件传递过来的 TemplatesData。
  • 在 l-text 外层包裹 Wrapper 用来传递事件。
  • 当点击 Wrapper 的时候,发送事件 onItemClick。
  • 父组件执行 store.commit 向 store 中添加一条数据。
  • 由于 store 是响应式的,所以当 store 多了一条数据的时候,画布就会自动渲染出来。

2编辑器开发之添加模版到画布 - 图2
vscode-code-to-type ,复制 js 对象,生成 ts 类型

添加/删除模版到画布

  1. export const defaultTextTemplates = [
  2. {
  3. text: '大标题',
  4. fontSize: '30px',
  5. fontWeight: 'bold',
  6. tag: 'h2',
  7. },
  8. {
  9. text: '正文内容',
  10. tag: 'p',
  11. },
  12. {
  13. text: '链接内容',
  14. color: '#1890ff',
  15. textDecoration: 'underline',
  16. tag: 'p',
  17. },
  18. {
  19. text: '按钮内容',
  20. color: '#ffffff',
  21. backgroundColor: '#1890ff',
  22. borderWidth: '1px',
  23. borderColor: '#1890ff',
  24. borderStyle: 'solid',
  25. borderRadius: '2px',
  26. paddingLeft: '10px',
  27. paddingRight: '10px',
  28. paddingTop: '5px',
  29. paddingBottom: '5px',
  30. width: '100px',
  31. tag: 'button',
  32. textAlign: 'center',
  33. },
  34. ]
  1. import { Module } from 'vuex'
  2. import { v4 as uuidv4 } from 'uuid'
  3. import { ImageComponentProps, TextComponentProps } from '@/ts/defaultProps'
  4. import { GloabalDataProps } from '.'
  5. import { ADDCOMPONENT, DELCOMPONENT } from './mutation-types'
  6. export interface ComponentData {
  7. // 这个元素的 属性,属性请详见下面
  8. props: Partial<TextComponentProps & ImageComponentProps>
  9. // id,uuid v4 生成
  10. id: string
  11. // 业务组件库名称 l-text,l-image 等等
  12. name: 'l-text' | 'l-image'
  13. }
  14. export const testComponents: ComponentData[] = [
  15. {
  16. id: uuidv4(),
  17. name: 'l-text',
  18. props: {
  19. text: 'hello',
  20. fontSize: '20px',
  21. color: '#000000',
  22. lineHeight: '1',
  23. textAlign: 'left',
  24. fontFamily: '',
  25. },
  26. },
  27. {
  28. id: uuidv4(),
  29. name: 'l-text',
  30. props: {
  31. text: 'hello2',
  32. fontSize: '10px',
  33. fontWeight: 'bold',
  34. lineHeight: '2',
  35. textAlign: 'left',
  36. fontFamily: '',
  37. },
  38. },
  39. {
  40. id: uuidv4(),
  41. name: 'l-text',
  42. props: {
  43. text: 'hello3',
  44. fontSize: '15px',
  45. actionType: 'url',
  46. url: 'https://www.baidu.com',
  47. lineHeight: '3',
  48. textAlign: 'left',
  49. fontFamily: '',
  50. },
  51. },
  52. ]
  53. export interface EditorProps {
  54. // 供中间编辑器渲染的数组
  55. components: ComponentData[]
  56. // 当前编辑的是哪个元素,uuid
  57. currentElement: string
  58. }
  59. //两个参数 第一个本地的interface,第二个是全局的interface
  60. const editor: Module<EditorProps, GloabalDataProps> = {
  61. state: {
  62. components: testComponents,
  63. currentElement: '',
  64. },
  65. mutations: {
  66. [ADDCOMPONENT](state, componentData: ComponentData) {
  67. state.components.push(componentData)
  68. },
  69. [DELCOMPONENT](state, componentData: ComponentData) {
  70. const res = state.components.filter(
  71. (item) => item.id !== componentData.id,
  72. )
  73. state.components = res
  74. },
  75. },
  76. getters: {},
  77. }
  78. export default editor
  1. <template>
  2. <div class="create-component-list">
  3. <div
  4. v-for="(item, index) in list"
  5. :key="index"
  6. class="component-item"
  7. @click="onItemClick(index)"
  8. >
  9. <l-text v-bind="item"></l-text>
  10. </div>
  11. </div>
  12. </template>
  13. <script lang="ts">
  14. import { defineComponent } from 'vue'
  15. import { v4 as uuidv4 } from 'uuid'
  16. import { message } from 'ant-design-vue'
  17. import LText from '@/components/LText.vue'
  18. import props from 'ant-design-vue/lib/dropdown/props'
  19. import { ComponentData } from '@/store/editor'
  20. export default defineComponent({
  21. props: {
  22. list: {
  23. type: Array,
  24. default: [],
  25. },
  26. },
  27. components: { LText },
  28. emits: ['on-item-click'],
  29. setup(props, context) {
  30. //子向父传值
  31. const onItemClick = (index: number) => {
  32. const componentData = {
  33. name: 'l-text',
  34. id: uuidv4(),
  35. props: props.list[index],
  36. }
  37. context.emit('on-item-click', componentData)
  38. }
  39. return { onItemClick }
  40. },
  41. })
  42. </script>
  43. <style>
  44. .component-item {
  45. width: 100px;
  46. margin: 0 auto;
  47. margin-bottom: 15px;
  48. }
  49. </style>
  1. <template>
  2. <div class="editor-container">
  3. <a-layout>
  4. <a-layout-sider width="300" style="background: #fff">
  5. <div class="sidebar-container">组件列表</div>
  6. <components-list
  7. :list="defaultTextTemplates"
  8. @on-item-click="addComponent"
  9. ></components-list>
  10. </a-layout-sider>
  11. <a-layout style="padding: 0 24px 24px">
  12. <a-layout-content class="preview-container">
  13. <p>画布区域</p>
  14. <!--引用的是component-->
  15. <div v-for="item in components" :key="item.id" class="l-text-wrapper">
  16. <close-outlined class="icon-close" @click="delComponent(item)" />
  17. <component
  18. class="preview-list"
  19. id="canvas-area"
  20. :is="item.name"
  21. v-bind="item.props"
  22. >
  23. </component>
  24. </div>
  25. </a-layout-content>
  26. </a-layout>
  27. <a-layout-sider
  28. width="300"
  29. style="background: #fff"
  30. class="settings-panel"
  31. >
  32. 组件属性
  33. </a-layout-sider>
  34. </a-layout>
  35. </div>
  36. </template>
  37. <script lang="ts">
  38. import { GloabalDataProps } from '@/store'
  39. import { defineComponent, computed } from 'vue'
  40. import { useStore } from 'vuex'
  41. import LText from '@/components/LText.vue'
  42. import { CloseOutlined } from '@ant-design/icons-vue'
  43. import ComponentsList from '@/components/ComponentsList.vue'
  44. import { defaultTextTemplates } from '@/ts/defaultTemplates'
  45. import { ComponentData } from '@/store/editor'
  46. import { ADDCOMPONENT, DELCOMPONENT } from '@/store/mutation-types'
  47. export default defineComponent({
  48. components: { LText, ComponentsList, CloseOutlined },
  49. setup() {
  50. const store = useStore<GloabalDataProps>()
  51. const components = computed(() => store.state.editor.components)
  52. const addComponent = (data: ComponentData) => {
  53. store.commit(ADDCOMPONENT, data)
  54. }
  55. const delComponent = (data: ComponentData) => {
  56. console.log(data)
  57. store.commit(DELCOMPONENT, data)
  58. }
  59. return {
  60. components,
  61. defaultTextTemplates,
  62. addComponent,
  63. delComponent,
  64. }
  65. },
  66. })
  67. </script>
  68. <style lang="less" scoped>
  69. .icon-close {
  70. position: absolute;
  71. z-index: 999;
  72. right: 0;
  73. }
  74. .l-text-wrapper {
  75. position: relative;
  76. border: 1px solid #999;
  77. margin: 2px 0;
  78. }
  79. .editor-container .preview-container {
  80. margin: 0;
  81. display: flex;
  82. flex-direction: column;
  83. // align-items: center;
  84. position: relative;
  85. }
  86. .editor-container .preview-list {
  87. padding: 0;
  88. margin: 0;
  89. border: 1px solid #efefef;
  90. background: #fff;
  91. overflow-x: hidden;
  92. overflow-y: auto;
  93. position: fixed;
  94. }
  95. </style>

image.png