zhoucg 1 سال پیش
والد
کامیت
ff7d38b09a

+ 8 - 0
components.d.ts

@@ -82,8 +82,16 @@ declare module 'vue' {
     YvanBarcodeProps: typeof import('./src/components/elements/YvanBarcodeProps.vue')['default']
     YvanCircle: typeof import('./src/components/elements/YvanCircle.vue')['default']
     YvanCircleProps: typeof import('./src/components/elements/YvanCircleProps.vue')['default']
+    YvanComplexCell: typeof import('./src/components/elements/yvan-table/YvanComplexCell.vue')['default']
+    YvanComplexCol: typeof import('./src/components/elements/yvan-table/YvanComplexCol.vue')['default']
+    YvanComplexColumn: typeof import('./src/components/elements/yvan-table/YvanComplexColumn.vue')['default']
+    YvanComplexRow: typeof import('./src/components/elements/yvan-table/YvanComplexRow.vue')['default']
     YvanComplexTable: typeof import('./src/components/elements/yvan-table/YvanComplexTable.vue')['default']
+    YvanComplexTable_bak: typeof import('./src/components/elements/yvan-table/YvanComplexTable_bak.vue')['default']
     YvanComplexTableProps: typeof import('./src/components/elements/yvan-table/YvanComplexTableProps.vue')['default']
+    YvanComplexTableProps_bak: typeof import('./src/components/elements/yvan-table/YvanComplexTableProps_bak.vue')['default']
+    YvanComplexTablePropsAttr: typeof import('./src/components/elements/yvan-table/YvanComplexTablePropsAttr.vue')['default']
+    YvanComplexTablePropsStyle: typeof import('./src/components/elements/yvan-table/YvanComplexTablePropsStyle.vue')['default']
     YvanDesigner: typeof import('./src/components/YvanDesigner.vue')['default']
     YvanFieldText: typeof import('./src/components/elements/YvanFieldText.vue')['default']
     YvanFieldTextProps: typeof import('./src/components/elements/YvanFieldTextProps.vue')['default']

+ 4 - 0
src/components/YvanPrintDesignerLeftLayout.vue

@@ -142,6 +142,10 @@ export default {
 </script>
 
 <style lang="less" scoped>
+.fa {
+  font-size: 16px;
+}
+
 .yvan-print-designer-left-structure__main {
   height: 100%;
 

+ 88 - 31
src/components/config/globalConfig.ts

@@ -1,3 +1,5 @@
+import {deepCopy} from '@/utils/html-util';
+
 /** 纸张方向 */
 export const pageDirectionList = [
     // vertical or horizontal
@@ -46,13 +48,28 @@ export const elementBands = [
     {name: 'Title', code: 'title', height: 80, isDefaultShow: true},
     {name: 'Page Header', code: 'pageHeader', height: 80, isDefaultShow: true},
     {name: 'Column Header', code: 'columnHeader', height: 30, isDefaultShow: true},
-    {name: 'Detail', code: 'detail', height: 80, isDefaultShow: true},
+    {name: 'Detail', code: 'detail', height: 125, isDefaultShow: true},
     {name: 'Column Footer', code: 'columnFooter', height: 30, isDefaultShow: true},
     {name: 'Page Footer', code: 'pageFooter', height: 80, isDefaultShow: true},
     {name: 'Last Page Footer', code: 'lastPageFooter', height: 30, isDefaultShow: false},
     {name: 'Summary', code: 'summary', height: 30, isDefaultShow: false},
 ]
 
+const tableConfigTemplate = {
+    rowTemplate: {
+        rowNo: 1,
+        cols: {
+            1: {
+                colspan: 1,
+                rowspan: 1,
+                element: {}
+            }
+        }
+    }
+}
+
+const tableInnerElementCodes = ['simpleText', 'textField', 'image', 'barcode', 'qrcode']
+
 // 元素组件
 export const elementList = [
     {
@@ -73,7 +90,7 @@ export const elementList = [
             borderColor: '#212121',
             borderType: 'none',
             borderPosition: [],
-            width: 150,
+            width: 100,
             height: 30,
             fontSize: 10,
             background: '#FFFFFF',
@@ -86,7 +103,7 @@ export const elementList = [
     },
     {
         icon: 'fa fa-file-text',
-        code: 'text',
+        code: 'textField',
         name: '字段文本',
         component: 'YvanTextField',
         expression: '字段文本',
@@ -102,7 +119,7 @@ export const elementList = [
             borderColor: '#212121',
             borderType: 'none',
             borderPosition: [],
-            width: 150,
+            width: 100,
             height: 30,
             fontSize: 10,
             background: '#FFFFFF',
@@ -133,33 +150,68 @@ export const elementList = [
     //     },
     //     groupStyle: {}
     // },
-    {
-        icon: 'fa fa-th',
-        code: 'table',
-        name: '简单表格',
-        component: 'YvanSimpleTable',
-        propValue: {},
-        style: {
-            width: 'auto',
-            height: 'auto',
-            fontSize: 12,
-            background: '#FFFFFF',
-            borderWidth: 2,
-            borderColor: '#212121',
-            rotate: 0
-        },
-        groupStyle: {}
-    },
+    // {
+    //     icon: 'fa fa-th',
+    //     code: 'table',
+    //     name: '简单表格',
+    //     component: 'YvanSimpleTable',
+    //     propValue: {},
+    //     style: {
+    //         width: 'auto',
+    //         height: 'auto',
+    //         fontSize: 12,
+    //         background: '#FFFFFF',
+    //         borderWidth: 2,
+    //         borderColor: '#212121',
+    //         rotate: 0
+    //     },
+    //     groupStyle: {}
+    // },
     {
         icon: 'fa fa-table',
         code: 'complex-table',
-        name: '复杂表格',
+        name: '表格',
         component: 'YvanComplexTable',
         propValue: {},
-        showPrefix: true,
-        showHead: true,
-        showFoot: true,
-        showSuffix: true,
+        config: {
+            columnNum: 1,
+            datasetName: '',
+            tableHeader: {
+                rows: [
+                    {
+                        ...deepCopy(tableConfigTemplate.rowTemplate)
+                    }
+                ]
+            },
+            columnHeader: {
+                rows: [
+                    {
+                        ...deepCopy(tableConfigTemplate.rowTemplate)
+                    }
+                ]
+            },
+            detail: {
+                rows: [
+                    {
+                        ...deepCopy(tableConfigTemplate.rowTemplate)
+                    }
+                ]
+            },
+            columnFooter: {
+                rows: [
+                    {
+                        ...deepCopy(tableConfigTemplate.rowTemplate)
+                    }
+                ]
+            },
+            tableFooter: {
+                rows: [
+                    {
+                        ...deepCopy(tableConfigTemplate.rowTemplate)
+                    }
+                ]
+            },
+        },
         style: {
             width: 'auto',
             height: 'auto',
@@ -197,7 +249,7 @@ export const elementList = [
             borderColor: '#212121',
             borderType: 'solid',
             width: 100,
-            height: 100,
+            height: 50,
             background: '#FFFFFF',
             rotate: 0
         },
@@ -233,8 +285,8 @@ export const elementList = [
             borderWidth: 0,
             borderColor: '#212121',
             borderType: 'none',
-            width: 100,
-            height: 68,
+            width: 50,
+            height: 50,
             rotate: 0
         },
         groupStyle: {}
@@ -256,8 +308,8 @@ export const elementList = [
             borderWidth: 0,
             borderColor: '#212121',
             borderRadius: 0,
-            width: 200,
-            height: 114,
+            width: 100,
+            height: 30,
             background: '#fff',
             rotate: 0
         },
@@ -381,3 +433,8 @@ export const componentList = [
     {code: 'element', title: '元素组件', type: 'element', elementList: elementList},
     {code: 'function', title: '系统函数组件', type: 'function', elementList: functionList},
 ]
+
+export {
+    tableConfigTemplate,
+    tableInnerElementCodes
+}

+ 5 - 1
src/components/elements/style.js

@@ -393,7 +393,7 @@ export const StyledSimpleTable = styled('div', simpleTableProps)`
     background-color: ${(props) => `${props.background || '#FFF'}`};
   }
 
-  .yvan-simple-table-cell--selected {
+  .yvan-table-cell__selected {
     background: #d4e7f5;
   }
 
@@ -500,4 +500,8 @@ export const StyledComplexTable = styled('div', complexTableProps)`
     background-position: 50%;
     background-repeat: repeat;
   }
+  
+  .yvan-table-cell__selected {
+    background: #d4e7f5;
+  }
 `

+ 0 - 318
src/components/elements/yvan-table/TableDataSetting.vue

@@ -1,318 +0,0 @@
-<template>
-  <RoyModal
-      v-if="visible"
-      :show="visible"
-      class="TableDataSetting"
-      height="80%"
-      title="数据表格设置"
-      width="60%"
-      @close="handleModalClose"
-      @mousedown.stop.prevent="handleMouseDown"
-  >
-    <vxe-grid
-        ref="xGrid"
-        :data="tableDataIn"
-        height="100%"
-        v-bind="gridOptions"
-        @toolbar-button-click="handleToolbarButtonClick"
-    >
-      <template v-slot:form>
-        <div class="roy-table-data-form">
-          <div>
-            <span>绑定数据源:</span>
-            <el-select v-model="tableDataSource" placeholder="请选择数据源">
-              <el-option
-                  v-for="opt in dataSourceOption"
-                  :key="opt.field"
-                  :label="opt.title"
-                  :value="opt.field"
-              ></el-option>
-            </el-select>
-          </div>
-          <div>
-            <span>表格行高:</span>
-            <el-input
-                v-model="tableRowHeight"
-                max="100"
-                min="20"
-                placeholder="20-100"
-                type="number"
-            ></el-input>
-          </div>
-        </div>
-      </template>
-    </vxe-grid>
-  </RoyModal>
-</template>
-
-<script>
-import {mapState} from 'pinia'
-import {globalStore} from "@/store"
-import commonMixin from '@/mixin/commonMixin'
-// import RoyModal from '@/components/RoyModal/RoyModal'
-import toast from '@/utils/toast'
-
-/**
- * 表格数据设置
- */
-export default {
-  name: 'TableDataSetting',
-  mixins: [commonMixin],
-  components: {
-    // RoyModal
-  },
-  props: {
-    visible: {
-      type: Boolean,
-      default: false
-    },
-    tableConfig: {
-      type: Object,
-      default: () => {
-        return {}
-      }
-    }
-  },
-  computed: {
-    ...mapState(globalStore, {
-      dataSource: (state) => state.dataSource
-    }),
-    dataSourceOption() {
-      return this.dataSource.filter((item) => {
-        return item.typeName === 'Array'
-      })
-    }
-  },
-  data() {
-    return {
-      tableDataIn: [],
-      tableDataSource: '',
-      tableRowHeight: 40,
-      gridOptions: {
-        border: true,
-        showHeaderOverflow: true,
-        showOverflow: true,
-        keepSource: true,
-        id: 'full_edit_1',
-        rowId: 'id',
-        rowConfig: {
-          isHover: true
-        },
-        columnConfig: {
-          resizable: true
-        },
-        toolbarConfig: {
-          buttons: [
-            {
-              code: 'save',
-              name: '保存',
-              icon: 'ri-save-line',
-              status: 'primary'
-            },
-            {
-              code: 'add',
-              name: '新增行',
-              icon: 'ri-add-line'
-            },
-            {
-              code: 'remove',
-              name: '删除行',
-              icon: 'ri-subtract-line'
-            }
-          ],
-          refresh: false,
-          import: false,
-          export: false,
-          print: false,
-          zoom: false,
-          custom: false
-        },
-        columns: [
-          {type: 'checkbox', width: 50},
-          {
-            field: 'title',
-            title: '列标题',
-            sortable: true,
-            titlePrefix: {message: '用于展示于表头名称'},
-            editRender: {
-              name: '$input',
-              props: {placeholder: '请输入列标题'}
-            }
-          },
-          {
-            field: 'field',
-            title: '英文字段',
-            sortable: true,
-            titlePrefix: {message: '用于匹配对应列数据'},
-            editRender: {
-              name: '$input',
-              props: {placeholder: '请输入英文字段'}
-            }
-          },
-          {
-            field: 'align',
-            title: '对齐方式',
-            sortable: true,
-            titlePrefix: {message: '该列的对齐方式'},
-            editRender: {
-              name: '$select',
-              options: [
-                {
-                  value: 'left',
-                  label: '居左'
-                },
-                {
-                  value: 'center',
-                  label: '居中'
-                },
-                {
-                  value: 'right',
-                  label: '居右'
-                }
-              ],
-              props: {
-                placeholder: '请选择对齐方式'
-              }
-            }
-          },
-          {
-            field: 'width',
-            title: '列宽度',
-            sortable: true,
-            titlePrefix: {message: '该列宽度'},
-            editRender: {
-              name: '$input',
-              props: {
-                placeholder: '请输入列宽度',
-                type: 'number',
-                min: 10,
-                max: 1000
-              }
-            }
-          },
-          {
-            field: 'formatter',
-            title: '列类型',
-            sortable: true,
-            titlePrefix: {message: '该列的数据的展现方式'},
-            editRender: {
-              name: '$select',
-              options: [
-                {
-                  value: 'String',
-                  label: '文本'
-                },
-                {
-                  value: 'Money',
-                  label: '金额'
-                },
-                {
-                  value: 'BigNumber',
-                  label: '中文大写数字'
-                },
-                {
-                  value: 'BigMoney',
-                  label: '中文大写金额'
-                }
-              ],
-              props: {
-                placeholder: '请选择列类型'
-              }
-            }
-          }
-        ],
-        checkboxConfig: {
-          reserve: true,
-          highlight: true,
-          range: false
-        },
-        editRules: {
-          title: [{required: true, message: '请输入列标题'}],
-          field: [{required: true, message: '请输入英文字段名'}],
-          align: [{required: true, message: '请选择对齐方式'}],
-          width: [{required: true, message: '请输入列宽度'}],
-          formatter: [{required: true, message: '请选择列类型'}]
-        },
-        editConfig: {
-          trigger: 'click',
-          mode: 'row',
-          showStatus: true
-        }
-      }
-    }
-  },
-  methods: {
-    initMounted() {
-      const {tableCols, tableRowHeight, tableDataSource} = this.tableConfig
-      this.tableDataIn = this.deepCopy(tableCols)
-      this.tableRowHeight = tableRowHeight
-      this.tableDataSource = tableDataSource
-    },
-    handleMouseDown() {
-    },
-    handleModalClose() {
-      this.$store.commit('printTemplateModule/setCurTableSettingId', null)
-    },
-    handleToolbarButtonClick({code}) {
-      switch (code) {
-        case 'save':
-          this.doSaveTableSetting()
-          break
-        case 'add':
-          this.doAddRow()
-          break
-      }
-    },
-    doAddRow() {
-      const defaultData = {
-        width: 100,
-        align: 'left',
-        formatter: 'String'
-      }
-      this.$refs.xGrid.insertAt(defaultData, -1)
-    },
-    doSaveTableSetting() {
-      const tableData = this.$refs.xGrid.getTableData().fullData
-      this.$refs.xGrid.fullValidate(tableData, () => {
-        if (this.isBlank(this.tableDataSource)) {
-          toast('请选择数据源')
-          return
-        }
-        if (this.isBlank(this.tableRowHeight)) {
-          toast('请填写表格行高')
-          return
-        }
-        this.$emit('onSave', {
-          tableCols: tableData,
-          tableRowHeight: this.tableRowHeight,
-          tableDataSource: this.tableDataSource
-        })
-        this.handleModalClose()
-      })
-    }
-  },
-  created() {
-  },
-  mounted() {
-    this.initMounted()
-  },
-  watch: {}
-}
-</script>
-
-<style lang="less">
-.TableDataSetting {
-  height: 100%;
-  padding: 6px;
-
-  .roy-table-data-form {
-    display: flex;
-    align-items: center;
-    justify-content: flex-end;
-
-    & > div {
-      margin-left: 10px;
-    }
-  }
-}
-</style>

+ 111 - 0
src/components/elements/yvan-table/YvanComplexCell.vue

@@ -0,0 +1,111 @@
+<template>
+  <div ref="slot"
+       style="width: 120px; height:30px"
+       class="yvan-table-cell-container"
+       @dragover.prevent="handleDragOver"
+       @drop.stop.prevent="handleDrop">
+    <component
+        v-if="!!innerElement"
+        :is="innerElement.component"
+        :id="'yvan-print-component-' + innerElement.id"
+        :element="innerElement"
+        :expression="innerElement.expression"
+    />
+  </div>
+</template>
+
+<script>
+import {globalStore} from "@/store";
+import System from "@/utils/system"
+import commonMixin from "@/mixin/commonMixin";
+import {tableInnerElementCodes} from "@/components/config/globalConfig";
+
+export default {
+  name: "YvanComplexCell",
+  mixins: [commonMixin],
+  components: {},
+  props: {
+    bandRowName: {
+      type: String,
+      default: ''
+    },
+    rowNo: {
+      type: Number,
+      default: 1
+    },
+    colNo: {
+      type: Number,
+      default: 1
+    },
+    bandCode: {
+      type: String,
+      default: ''
+    },
+    elementId: {
+      type: String,
+      default: ''
+    },
+    innerElement: {
+      type: Object,
+      default: () => {
+
+      }
+    }
+  },
+  data() {
+    return {
+      selectedCells: [],
+      // mousedown的时候设置为其他值 否则都是-1
+      selectionHold: -1,
+      element: {}
+    }
+  },
+  computed: {},
+  mounted() {
+  },
+  methods: {
+    getIsActiveCell(row, col) {
+      return this.selectedCells.includes((row - 1) * this.tableConfig.cols + col - 1)
+    },
+    handleDragOver(event) {
+      event.dataTransfer.dropEffect = 'copy'
+    },
+    handleDrop(event) {
+      const elementCode = event.dataTransfer.getData('componentCode')
+      if (!elementCode) {
+        System.alert('无法获取该元素信息, 此次拖拽无效')
+        return
+      }
+      console.log('tableInnerElementCodes.includes(elementCode) >>', tableInnerElementCodes, elementCode, tableInnerElementCodes.includes(elementCode))
+      if (!tableInnerElementCodes.includes(elementCode)) {
+        System.alert(`${elementCode}类型元素不可拖入到表格单元格, 此次拖拽无效`)
+        return
+      }
+      const elementTemplate = this.findElementByCode(elementCode)
+      if (!elementTemplate) {
+        System.alert('无法获取该元素模板信息, 此次拖拽无效')
+        return
+      }
+      const element = this.deepCopy(elementTemplate)
+      element.id = this.getUuid()
+      element.label = `${element.name}-${element.id}`
+      element.bandCode = this.bandCode
+      globalStore().addTableCellElement(
+          {
+            bandCode: this.bandCode,
+            elementId: this.elementId,
+            bandRowName: this.bandRowName,
+            rowNo: this.rowNo,
+            colNo: this.colNo,
+            element: element
+          }
+      )
+      globalStore().recordSnapshot()
+    },
+  },
+}
+</script>
+
+<style lang="less" scoped>
+
+</style>

+ 49 - 0
src/components/elements/yvan-table/YvanComplexCol.vue

@@ -0,0 +1,49 @@
+<template>
+  <td v-for="col in columnTotal">
+    <YvanComplexCell
+        :bandCode="element.bandCode"
+        :elementId="element.id"
+        :bandRowName="bandRowName"
+        :rowNo="row.rowNo"
+        :colNo="col"
+        :inner-element="row.cols[col]?.element"/>
+  </td>
+</template>
+
+<script lang="js">
+import YvanComplexCell from "@/components/elements/yvan-table/YvanComplexCell.vue";
+
+export default {
+  name: "YvanComplexCol",
+  components: {
+    YvanComplexCell
+  },
+  props: {
+    bandRowName: {
+      type: String,
+      default: ''
+    },
+    row: {
+      type: Object,
+      default: () => {
+
+      }
+    },
+    element: {
+      type: Object,
+      default: () => {
+
+      }
+    }
+  },
+  computed: {
+    columnTotal() {
+      return this.element.config.columnNum
+    },
+  }
+}
+</script>
+
+<style scoped>
+
+</style>

+ 44 - 0
src/components/elements/yvan-table/YvanComplexRow.vue

@@ -0,0 +1,44 @@
+<template>
+  <tr v-if="showBand()" v-for="row in column.rows">
+    <yvan-complex-col :band-row-name="bandRowName" :row="row" :element="element"/>
+  </tr>
+</template>
+
+<script lang="js">
+import YvanComplexCol from "@/components/elements/yvan-table/YvanComplexCol.vue";
+
+export default {
+  name: "YvanComplexRow",
+  components: {
+    YvanComplexCol
+  },
+  props: {
+    bandRowName: {
+      type: String,
+      default: ''
+    },
+    element: {
+      type: Object,
+      default: () => {
+      }
+    },
+  },
+  data() {
+    return {}
+  },
+  computed: {
+    column() {
+      return this.element?.config[this.bandRowName]
+    }
+  },
+  methods: {
+    showBand() {
+      return !!this.element?.config[this.bandRowName];
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+
+</style>

+ 28 - 113
src/components/elements/yvan-table/YvanComplexTable.vue

@@ -1,115 +1,26 @@
 <template>
   <div v-if="initCompleted" class="yvan-complex-table">
     <StyledComplexTable v-bind="style">
-      <table class="yvan-complex-table__container">
-        <tr class="1234">
-          <td>
-            <div class="yvan-complex-table__prefix" style="border: #0a0a0a 1px solid">
-              <YvanSimpleTextInTable
-                key="prefix"
-                :element="prefixTextElement"
-                :prop-value="prefixTextElement.propValue"
-                style="min-height: 40px; min-width: 200px"
-                @componentUpdated="componentUpdated"
-              />
-            </div>
-          </td>
-        </tr>
-        <tr v-if="element.showHead">
-          <td>
-            <div class="yvan-complex-table__head">
-              <YvanSimpleTable
-                key="head"
-                :element="headSimpleTableElement"
-                :prop-value="headSimpleTableElement.propValue"
-                :scale="scale"
-                @componentUpdated="componentUpdated"
-              />
-            </div>
-          </td>
-        </tr>
-        <tr>
-          <td>
-            <div
-              :style="{
-                marginTop: element.showHead ? `-${style.borderWidth - 0.5}px` : '',
-                marginBottom: element.showFoot ? `-${style.borderWidth - 0.5}px` : ''
-              }"
-              class="yvan-complex-table__body"
-            >
-              <table :style="`width: ${bodyTableWidth}px`">
-                <thead>
-                  <tr>
-                    <th
-                      v-for="(item, index) in tableCols"
-                      :key="index"
-                      :style="{
-                        width: `${item.width}px`,
-                        height: `${tableRowHeight}px`
-                      }"
-                    >
-                      <div style="display: inline; width: 100%">
-                        {{ item.title }}
-                      </div>
-                    </th>
-                  </tr>
-                </thead>
-                <tbody>
-                  <tr :style="`height: ${tableRowHeight}px`">
-                    <td :colspan="tableCols.length" :style="`height: ${tableRowHeight}px`">
-                      <div class="yvan-complex-table__auto_fill">自动填充</div>
-                    </td>
-                  </tr>
-                </tbody>
-              </table>
-            </div>
-          </td>
-        </tr>
-        <tr v-if="element.showFoot">
-          <td>
-            <div class="yvan-complex-table__foot">
-<!--              <YvanSimpleTable-->
-<!--                key="foot"-->
-<!--                :element="footSimpleTableElement"-->
-<!--                :prop-value="footSimpleTableElement.propValue"-->
-<!--                :scale="scale"-->
-<!--                @componentUpdated="componentUpdated"-->
-<!--              />-->
-            </div>
-          </td>
-        </tr>
-        <tr v-if="element.showSuffix">
-          <td>
-            <div class="yvan-complex-table__suffix" style="border: #0a0a0a 1px solid">
-              <YvanSimpleTextInTable
-                key="suffix"
-                :element="suffixTextElement"
-                :prop-value="suffixTextElement.propValue"
-                style="min-height: 40px; min-width: 200px"
-                @componentUpdated="componentUpdated"
-              />
-            </div>
-          </td>
-        </tr>
+      <table class="yvan-complex-table__container" border="1">
+        <yvan-complex-row band-row-name="tableHeader" :element="element"/>
+        <yvan-complex-row band-row-name="columnHeader" :element="element"/>
+        <yvan-complex-row band-row-name="detail" :element="element"/>
+        <yvan-complex-row band-row-name="columnFooter" :element="element"/>
+        <yvan-complex-row band-row-name="tableFooter" :element="element"/>
       </table>
     </StyledComplexTable>
-    <TableDataSetting
-      v-if="showTableDataSetting"
-      :table-config="bodyDataTableElement"
-      :visible="showTableDataSetting"
-      @onSave="handleTableSettingSave"
-    />
   </div>
 </template>
 
 <script>
-import { mapState } from 'pinia'
+import {mapState} from 'pinia'
 import {globalStore} from "@/store"
 import commonMixin from '@/mixin/commonMixin'
-import { StyledComplexTable } from '@/components/elements/style'
+import {StyledComplexTable} from '@/components/elements/style'
 import ResizeObserver from '@/components/elements/yvan-table/ResizeObserver'
-import TableDataSetting from '@/components/elements/yvan-table/TableDataSetting.vue'
 import YvanSimpleTable from '@/components/elements/yvan-table/YvanSimpleTable.vue'
+import YvanComplexCell from '@/components/elements/yvan-table/YvanComplexCell.vue'
+import YvanComplexRow from '@/components/elements/yvan-table/YvanComplexRow.vue'
 import YvanRichTextInTable from '@/components/elements/yvan-table/YvanRichTextInTable.vue'
 import YvanSimpleTextInTable from '@/components/elements/yvan-table/YvanSimpleTextInTable.vue'
 import YvanComplexTableProps from '@/components/elements/yvan-table/YvanComplexTableProps.vue'
@@ -125,7 +36,7 @@ const defaultTextProp = {
     height: '100%',
     fontSize: 12,
     background: null,
-    textStyle:[],
+    textStyle: [],
     rotate: 0,
   },
   groupStyle: {}
@@ -178,15 +89,13 @@ const defaultDataTableProp = {
   ]
 }
 
-/**
- * 复杂表格
- */
 export default {
   name: 'YvanComplexTable',
   mixins: [commonMixin],
   components: {
     YvanSimpleTable,
-    TableDataSetting,
+    YvanComplexCell,
+    YvanComplexRow,
     StyledComplexTable,
     YvanRichTextInTable,
     YvanSimpleTextInTable
@@ -194,7 +103,8 @@ export default {
   props: {
     element: {
       type: Object,
-      default: () => {}
+      default: () => {
+      }
     },
     propValue: {
       type: Object,
@@ -226,12 +136,12 @@ export default {
     },
     bodyTableWidth() {
       return this.tableCols
-        .map((item) => {
-          return Number(item.width)
-        })
-        .reduce((a, b) => {
-          return a + b
-        })
+          .map((item) => {
+            return Number(item.width)
+          })
+          .reduce((a, b) => {
+            return a + b
+          })
     }
   },
   data() {
@@ -299,15 +209,20 @@ export default {
         }
         resizeObserver.onElResize(element, callback)
       })
+    },
+    showBand(bandConfig) {
+      return !!bandConfig;
     }
   },
-  created() {},
-  install(Vue){
+  created() {
+  },
+  install(Vue) {
     Vue.component(this.name, this)
     Vue.component(YvanComplexTableProps.name, YvanComplexTableProps)
   },
   mounted() {
     this.initMounted()
+    console.log('table element =>>', this.element)
   },
   watch: {
     style: {

+ 14 - 41
src/components/elements/yvan-table/YvanComplexTableProps.vue

@@ -1,46 +1,13 @@
 <template>
   <section class="yvan-complex-table-props">
-    <el-form ref="form" label-position="top">
-      <el-divider content-position="left">属性设置</el-divider>
-      <el-form-item label="展示头部留白">
-        <el-switch inline-prompt active-text="是" inactive-text="否"/>
-      </el-form-item>
-      <el-form-item label="展示头部单元格">
-        <el-switch inline-prompt active-text="是" inactive-text="否"/>
-      </el-form-item>
-      <el-form-item label="展示尾部单元格">
-        <el-switch inline-prompt active-text="是" inactive-text="否"/>
-      </el-form-item>
-      <el-form-item label="展示尾部留白">
-        <el-switch inline-prompt active-text="是" inactive-text="否"/>
-      </el-form-item>
-      <el-divider content-position="left">样式设置</el-divider>
-      <el-form-item label="背景颜色">
-        <el-color-picker v-model="activeComponent.style.background"/>
-      </el-form-item>
-      <el-form-item label="边框颜色">
-        <el-color-picker v-model="activeComponent.style.borderColor"/>
-      </el-form-item>
-      <el-form-item label="边框类型">
-        <el-select v-model="activeComponent.style.borderType" placeholder="请选择边框类型" filterable>
-          <el-option v-for="borderType in borderTypeList" :label="borderType.label" :value="borderType.code"/>
-        </el-select>
-      </el-form-item>
-      <el-form-item label="边框宽度">
-        <el-input-number v-model="activeComponent.style.borderWidth" :min="0" controls-position="right"/>
-      </el-form-item>
-      <el-form-item label="字体">
-        <el-select v-model="activeComponent.style.fontFamily" placeholder="请选择字体" filterable>
-          <el-option v-for="font in fontList" :label="font.name" :value="font.code"/>
-        </el-select>
-      </el-form-item>
-      <el-form-item label="字体颜色">
-        <el-color-picker v-model="activeComponent.style.fontColor"/>
-      </el-form-item>
-      <el-form-item label="字体大小(pt)">
-        <el-input-number v-model="activeComponent.style.fontSize" :min="0" controls-position="right"/>
-      </el-form-item>
-    </el-form>
+    <el-tabs type="border-card">
+      <el-tab-pane label="属性设置">
+        <YvanComplexTablePropsAttr/>
+      </el-tab-pane>
+      <el-tab-pane label="样式设置">
+        <YvanComplexTablePropsStyle/>
+      </el-tab-pane>
+    </el-tabs>
   </section>
 </template>
 
@@ -48,9 +15,15 @@
 import {mapState} from "pinia";
 import {globalStore} from "@/store";
 import {fontList, borderTypeList} from "@/components/config/globalConfig";
+import YvanComplexTablePropsAttr from "@/components/elements/yvan-table/YvanComplexTablePropsAttr.vue";
+import YvanComplexTablePropsStyle from "@/components/elements/yvan-table/YvanComplexTablePropsStyle.vue";
 
 export default {
   name: 'YvanComplexTableProps',
+  components: {
+    YvanComplexTablePropsAttr,
+    YvanComplexTablePropsStyle
+  },
   data() {
     return {
       fontList,

+ 140 - 0
src/components/elements/yvan-table/YvanComplexTablePropsAttr.vue

@@ -0,0 +1,140 @@
+<template>
+  <el-form ref="form" label-position="top">
+    <el-form-item label="列数">
+      <el-input-number v-model="activeComponent.config.columnNum" :min="1" controls-position="right"/>
+    </el-form-item>
+    <el-form-item label="设置数据源">
+      <el-select v-model="activeComponent.config.datasetName" placeholder="请选择数据源" filterable>
+        <el-option v-for="dataSource in dataSources" :label="dataSource.paramName" :value="dataSource.paramKey"/>
+      </el-select>
+    </el-form-item>
+    <el-form-item label="是否显示Table Header">
+      <el-switch v-model="columnOptions.tableHeader" inline-prompt active-text="展示" inactive-text="隐藏"
+                 @click="showOrHideBandRow('tableHeader')"/>
+    </el-form-item>
+    <el-form-item label="是否显示Column Header">
+      <el-space spacer="|">
+        <el-switch v-model="columnOptions.columnHeader" inline-prompt active-text="展示" inactive-text="隐藏"
+                   @click="showOrHideBandRow('columnHeader')"/>
+        <i class="fa fa-plus-square"
+           title="添加一行Column Header"
+           @click="addRow('columnHeader')"
+           v-if="showOrHideAddRowBtn('columnHeader')"/>
+        <i class="fa fa-minus-square"
+           title="减少一行Column Header"
+           @click="removeRow('columnHeader')"
+           v-if="showOrHideRemoveRowBtn('columnHeader')"/>
+      </el-space>
+    </el-form-item>
+    <el-form-item label="是否显示Detail">
+      <el-switch v-model="columnOptions.detail" inline-prompt active-text="展示" inactive-text="隐藏"
+                 @click="showOrHideBandRow('detail')"/>
+    </el-form-item>
+    <el-form-item label="是否显示Column Footer">
+      <el-space spacer="|">
+        <el-switch v-model="columnOptions.columnFooter" inline-prompt active-text="展示" inactive-text="隐藏"
+                   @click="showOrHideBandRow('columnFooter')"/>
+        <i class="fa fa-plus-square"
+           title="添加一行Column Footer"
+           @click="addRow('columnFooter')"
+           v-if="showOrHideAddRowBtn('columnFooter')"/>
+        <i class="fa fa-minus-square"
+           title="减少一行Column Footer"
+           @click="removeRow('columnFooter')"
+           v-if="showOrHideRemoveRowBtn('columnFooter')"/>
+      </el-space>
+    </el-form-item>
+    <el-form-item label="是否显示Table Footer">
+      <el-space spacer="|">
+        <el-switch v-model="columnOptions.tableFooter" inline-prompt active-text="展示" inactive-text="隐藏"
+                   @click="showOrHideBandRow('tableFooter')"/>
+        <i class="fa fa-plus-square"
+           title="添加一行Column Footer"
+           @click="addRow('tableFooter')"
+           v-if="showOrHideAddRowBtn('tableFooter')"/>
+        <i class="fa fa-minus-square"
+           title="减少一行Column Footer"
+           @click="removeRow('tableFooter')"
+           v-if="showOrHideRemoveRowBtn('tableFooter')"/>
+      </el-space>
+    </el-form-item>
+  </el-form>
+</template>
+
+<script>
+import {mapState} from "pinia";
+import {globalStore} from "@/store";
+import commonMixin from "@/mixin/commonMixin";
+import {fontList, borderTypeList, tableConfigTemplate} from "@/components/config/globalConfig";
+
+export default {
+  name: "YvanComplexTablePropsAttr",
+  mixins: [commonMixin],
+  components: {},
+  data() {
+    return {
+      fontList,
+      borderTypeList,
+      columnOptions: {
+        tableHeader: true,
+        columnHeader: true,
+        detail: true,
+        columnFooter: true,
+        tableFooter: true
+      }
+    }
+  },
+  computed: {
+    ...mapState(globalStore, {
+      activeComponent: (state) => {
+        console.log("activeComponent => ", state.curComponent)
+        return state.curComponent
+      },
+      dataSources: (state) => state.dataSources
+    }),
+  },
+  methods: {
+    showOrHideAddRowBtn(bandRowName) {
+      return this.columnOptions[bandRowName];
+    },
+    addRow(bandRowName) {
+      const rowTemplate = this.deepCopy(tableConfigTemplate.rowTemplate)
+      const rows = this.activeComponent.config[bandRowName].rows;
+      rowTemplate.rowNo = rows.length + 1;
+      this.activeComponent.config[bandRowName].rows.push(rowTemplate);
+    },
+    showOrHideRemoveRowBtn(bandRowName) {
+      return this.activeComponent.config[bandRowName]?.rows?.length > 1;
+    },
+    removeRow(bandRowName) {
+      // 删除行时, 删除最后一行
+      this.activeComponent.config[bandRowName].rows.pop();
+    },
+    showOrHideBandRow(bandRowName) {
+      if (!this.columnOptions[bandRowName]) {
+        const bandConfig = this.activeComponent.config;
+        delete bandConfig[bandRowName];
+        this.activeComponent.config = bandConfig;
+        return;
+      }
+      this.activeComponent.config[bandRowName] = {
+        rows: [
+          {
+            ...tableConfigTemplate.rowTemplate
+          }
+        ]
+      }
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+.fa {
+  font-size: 16px;
+}
+
+.fa:hover {
+  cursor: pointer;
+}
+</style>

+ 71 - 0
src/components/elements/yvan-table/YvanComplexTablePropsStyle.vue

@@ -0,0 +1,71 @@
+<template>
+  <el-form ref="form" label-position="top">
+    <el-divider content-position="left">属性设置</el-divider>
+    <el-form-item label="展示头部留白">
+      <el-switch v-model="activeComponent.showPrefix" inline-prompt active-text="是" inactive-text="否"/>
+    </el-form-item>
+    <el-form-item label="展示头部单元格">
+      <el-switch v-model="activeComponent.showHead" inline-prompt active-text="是" inactive-text="否"/>
+    </el-form-item>
+    <el-form-item label="展示尾部单元格">
+      <el-switch v-model="activeComponent.showFoot" inline-prompt active-text="是" inactive-text="否"/>
+    </el-form-item>
+    <el-form-item label="展示尾部留白">
+      <el-switch v-model="activeComponent.showSuffix" inline-prompt active-text="是" inactive-text="否"/>
+    </el-form-item>
+    <el-divider content-position="left">样式设置</el-divider>
+    <el-form-item label="背景颜色">
+      <el-color-picker v-model="activeComponent.style.background"/>
+    </el-form-item>
+    <el-form-item label="边框颜色">
+      <el-color-picker v-model="activeComponent.style.borderColor"/>
+    </el-form-item>
+    <el-form-item label="边框类型">
+      <el-select v-model="activeComponent.style.borderType" placeholder="请选择边框类型" filterable>
+        <el-option v-for="borderType in borderTypeList" :label="borderType.label" :value="borderType.code"/>
+      </el-select>
+    </el-form-item>
+    <el-form-item label="边框宽度">
+      <el-input-number v-model="activeComponent.style.borderWidth" :min="0" controls-position="right"/>
+    </el-form-item>
+    <el-form-item label="字体">
+      <el-select v-model="activeComponent.style.fontFamily" placeholder="请选择字体" filterable>
+        <el-option v-for="font in fontList" :label="font.name" :value="font.code"/>
+      </el-select>
+    </el-form-item>
+    <el-form-item label="字体颜色">
+      <el-color-picker v-model="activeComponent.style.fontColor"/>
+    </el-form-item>
+    <el-form-item label="字体大小(pt)">
+      <el-input-number v-model="activeComponent.style.fontSize" :min="0" controls-position="right"/>
+    </el-form-item>
+  </el-form>
+</template>
+
+<script>
+import {mapState} from "pinia";
+import {globalStore} from "@/store";
+import {fontList, borderTypeList} from "@/components/config/globalConfig";
+
+export default {
+  name: "YvanComplexTablePropsStyle",
+  data() {
+    return {
+      fontList,
+      borderTypeList
+    }
+  },
+  computed: {
+    ...mapState(globalStore, {
+      activeComponent: (state) => {
+        console.log("activeComponent => ", state.curComponent)
+        return state.curComponent
+      }
+    }),
+  },
+}
+</script>
+
+<style scoped>
+
+</style>

+ 82 - 0
src/components/elements/yvan-table/YvanComplexTableProps_bak.vue

@@ -0,0 +1,82 @@
+<template>
+  <section class="yvan-complex-table-props">
+    <el-form ref="form" label-position="top">
+      <el-divider content-position="left">属性设置</el-divider>
+      <el-form-item label="展示头部留白">
+        <el-switch v-model="activeComponent.showPrefix" inline-prompt active-text="是" inactive-text="否"/>
+      </el-form-item>
+      <el-form-item label="展示头部单元格">
+        <el-switch v-model="activeComponent.showHead" inline-prompt active-text="是" inactive-text="否"/>
+      </el-form-item>
+      <el-form-item label="展示尾部单元格">
+        <el-switch v-model="activeComponent.showFoot" inline-prompt active-text="是" inactive-text="否"/>
+      </el-form-item>
+      <el-form-item label="展示尾部留白">
+        <el-switch v-model="activeComponent.showSuffix" inline-prompt active-text="是" inactive-text="否"/>
+      </el-form-item>
+      <el-divider content-position="left">样式设置</el-divider>
+      <el-form-item label="背景颜色">
+        <el-color-picker v-model="activeComponent.style.background"/>
+      </el-form-item>
+      <el-form-item label="边框颜色">
+        <el-color-picker v-model="activeComponent.style.borderColor"/>
+      </el-form-item>
+      <el-form-item label="边框类型">
+        <el-select v-model="activeComponent.style.borderType" placeholder="请选择边框类型" filterable>
+          <el-option v-for="borderType in borderTypeList" :label="borderType.label" :value="borderType.code"/>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="边框宽度">
+        <el-input-number v-model="activeComponent.style.borderWidth" :min="0" controls-position="right"/>
+      </el-form-item>
+      <el-form-item label="字体">
+        <el-select v-model="activeComponent.style.fontFamily" placeholder="请选择字体" filterable>
+          <el-option v-for="font in fontList" :label="font.name" :value="font.code"/>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="字体颜色">
+        <el-color-picker v-model="activeComponent.style.fontColor"/>
+      </el-form-item>
+      <el-form-item label="字体大小(pt)">
+        <el-input-number v-model="activeComponent.style.fontSize" :min="0" controls-position="right"/>
+      </el-form-item>
+    </el-form>
+  </section>
+</template>
+
+<script lang="ts">
+import {mapState} from "pinia";
+import {globalStore} from "@/store";
+import {fontList, borderTypeList} from "@/components/config/globalConfig";
+
+export default {
+  name: 'YvanComplexTableProps',
+  data() {
+    return {
+      fontList,
+      borderTypeList
+    }
+  },
+  computed: {
+    ...mapState(globalStore, {
+      activeComponent: (state) => {
+        console.log("activeComponent => ", state.curComponent)
+        return state.curComponent
+      }
+    }),
+  },
+}
+</script>
+
+<style lang="less">
+.yvan-complex-table-props {
+
+  .el-input-number {
+    width: 100%;
+  }
+
+  .el-select {
+    width: 100%;
+  }
+}
+</style>

+ 322 - 0
src/components/elements/yvan-table/YvanComplexTable_bak.vue

@@ -0,0 +1,322 @@
+<template>
+  <div v-if="initCompleted" class="yvan-complex-table">
+    <StyledComplexTable v-bind="style">
+      <table class="yvan-complex-table__container">
+        <tr v-if="element.showPrefix">
+          <td>
+            <div class="yvan-complex-table__prefix" style="border: #0a0a0a 1px solid">
+              <YvanSimpleTextInTable
+                  key="prefix"
+                  :element="prefixTextElement"
+                  :prop-value="prefixTextElement.propValue"
+                  style="min-height: 40px; min-width: 200px"
+                  @componentUpdated="componentUpdated"
+              />
+            </div>
+          </td>
+        </tr>
+        <tr v-if="element.showHead">
+          <td>
+            <div class="yvan-complex-table__head">
+              <YvanSimpleTable
+                  key="head"
+                  :element="headSimpleTableElement"
+                  :prop-value="headSimpleTableElement.propValue"
+                  :scale="scale"
+                  @componentUpdated="componentUpdated"
+              />
+            </div>
+          </td>
+        </tr>
+        <tr>
+          <td>
+            <div
+                :style="{
+                marginTop: element.showHead ? `-${style.borderWidth - 0.5}px` : '',
+                marginBottom: element.showFoot ? `-${style.borderWidth - 0.5}px` : ''
+              }"
+                class="yvan-complex-table__body"
+            >
+              <table :style="`width: ${bodyTableWidth}px`">
+                <thead>
+                <tr>
+                  <th
+                      v-for="(item, index) in tableCols"
+                      :key="index"
+                      :style="{
+                        width: `${item.width}px`,
+                        height: `${tableRowHeight}px`
+                      }"
+                  >
+                    <div style="display: inline; width: 100%">
+                      {{ item.title }}
+                    </div>
+                  </th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr :style="`height: ${tableRowHeight}px`">
+                  <td :colspan="tableCols.length" :style="`height: ${tableRowHeight}px`">
+                    <div class="yvan-complex-table__auto_fill">自动填充</div>
+                  </td>
+                </tr>
+                </tbody>
+              </table>
+            </div>
+          </td>
+        </tr>
+        <tr v-if="element.showFoot">
+          <td>
+            <div class="yvan-complex-table__foot">
+              <YvanSimpleTable
+                  key="foot"
+                  :element="footSimpleTableElement"
+                  :prop-value="footSimpleTableElement.propValue"
+                  :scale="scale"
+                  @componentUpdated="componentUpdated"
+              />
+            </div>
+          </td>
+        </tr>
+        <tr v-if="element.showSuffix">
+          <td>
+            <div class="yvan-complex-table__suffix" style="border: #0a0a0a 1px solid">
+              <YvanSimpleTextInTable
+                  key="suffix"
+                  :element="suffixTextElement"
+                  :prop-value="suffixTextElement.propValue"
+                  style="min-height: 40px; min-width: 200px"
+                  @componentUpdated="componentUpdated"
+              />
+            </div>
+          </td>
+        </tr>
+      </table>
+    </StyledComplexTable>
+  </div>
+</template>
+
+<script>
+import {mapState} from 'pinia'
+import {globalStore} from "@/store"
+import commonMixin from '@/mixin/commonMixin'
+import {StyledComplexTable} from '@/components/elements/style'
+import ResizeObserver from '@/components/elements/yvan-table/ResizeObserver'
+import YvanSimpleTable from '@/components/elements/yvan-table/YvanSimpleTable.vue'
+import YvanRichTextInTable from '@/components/elements/yvan-table/YvanRichTextInTable.vue'
+import YvanSimpleTextInTable from '@/components/elements/yvan-table/YvanSimpleTextInTable.vue'
+import YvanComplexTableProps from '@/components/elements/yvan-table/YvanComplexTableProps.vue'
+
+const defaultTextProp = {
+  icon: 'ri-text',
+  code: 'text',
+  name: '文本',
+  component: 'YvanSimpleTextInTable',
+  propValue: '',
+  style: {
+    width: '100%',
+    height: '100%',
+    fontSize: 12,
+    background: null,
+    textStyle: [],
+    rotate: 0,
+  },
+  groupStyle: {}
+}
+
+const defaultSimpleTableProp = {
+  icon: 'ri-table-2',
+  code: 'table',
+  name: '单元格',
+  component: 'YvanSimpleTable',
+  propValue: {},
+  style: {
+    width: '100%',
+    height: 'auto',
+    fontSize: 12,
+    background: '#FFFFFF',
+    borderWidth: 2,
+    borderColor: '#212121',
+    rotate: 0,
+    isRelative: true
+  },
+  groupStyle: {}
+}
+
+const defaultDataTableProp = {
+  tableRowHeight: 30,
+  tableDataSource: '',
+  tableCols: [
+    {
+      field: 'field1',
+      title: '表头R1',
+      width: 100,
+      align: 'left',
+      formatter: 'String'
+    },
+    {
+      field: 'field2',
+      title: '表头R2',
+      width: 100,
+      align: 'center',
+      formatter: 'String'
+    },
+    {
+      field: 'field3',
+      title: '表头R3',
+      width: 100,
+      align: 'right',
+      formatter: 'String'
+    }
+  ]
+}
+
+/**
+ * 复杂表格
+ */
+export default {
+  name: 'YvanComplexTable',
+  mixins: [commonMixin],
+  components: {
+    YvanSimpleTable,
+    StyledComplexTable,
+    YvanRichTextInTable,
+    YvanSimpleTextInTable
+  },
+  props: {
+    element: {
+      type: Object,
+      default: () => {
+      }
+    },
+    propValue: {
+      type: Object,
+      default: () => {
+        return {}
+      }
+    },
+    scale: {
+      required: true,
+      type: [Number, String],
+      default: 1
+    }
+  },
+  computed: {
+    ...mapState(globalStore, {
+      curTableSettingId: (state) => state.curTableSettingId
+    }),
+    showTableDataSetting() {
+      return this.curTableSettingId !== null && this.curTableSettingId === this.element.id
+    },
+    style() {
+      return this.element.style || {}
+    },
+    tableCols() {
+      return this.bodyDataTableElement.tableCols || []
+    },
+    tableRowHeight() {
+      return this.bodyDataTableElement.tableRowHeight || 40
+    },
+    bodyTableWidth() {
+      return this.tableCols
+          .map((item) => {
+            return Number(item.width)
+          })
+          .reduce((a, b) => {
+            return a + b
+          })
+    }
+  },
+  data() {
+    return {
+      initCompleted: false,
+      prefixTextElement: {},
+      suffixTextElement: {},
+      bodyDataTableElement: {},
+      headSimpleTableElement: {},
+      footSimpleTableElement: {}
+    }
+  },
+  methods: {
+    initMounted() {
+      const {
+        prefixTextElement,
+        suffixTextElement,
+        headSimpleTableElement,
+        footSimpleTableElement,
+        bodyDataTableElement
+      } = this.propValue
+      this.prefixTextElement = prefixTextElement || this.deepCopy(defaultTextProp)
+      this.suffixTextElement = suffixTextElement || this.deepCopy(defaultTextProp)
+      this.headSimpleTableElement = headSimpleTableElement || this.deepCopy(defaultSimpleTableProp)
+      this.footSimpleTableElement = footSimpleTableElement || this.deepCopy(defaultSimpleTableProp)
+      this.bodyDataTableElement = bodyDataTableElement || this.deepCopy(defaultDataTableProp)
+      setTimeout(() => {
+        this.initCompleted = true
+        // this.observeElementWidth()
+      })
+    },
+    componentUpdated() {
+      this.bodyDataTableElement.bodyTableWidth = this.bodyTableWidth
+      const propValue = Object.assign({}, this.propValue, {
+        prefixTextElement: this.prefixTextElement,
+        suffixTextElement: this.suffixTextElement,
+        headSimpleTableElement: this.headSimpleTableElement,
+        footSimpleTableElement: this.footSimpleTableElement,
+        bodyDataTableElement: this.bodyDataTableElement
+      })
+      globalStore().setPropValue({
+        id: this.element.id,
+        propValue
+      })
+      this.$emit('componentUpdated')
+    },
+    handleTableSettingSave(data) {
+      this.bodyDataTableElement = data
+      this.componentUpdated()
+    },
+    observeElementWidth() {
+      this.$nextTick(() => {
+        const element = this.$el.querySelector('.yvan-complex-table__container')
+        if (!element) {
+          return
+        }
+        const resizeObserver = new ResizeObserver()
+        const callback = () => {
+          this.$nextTick(() => {
+            globalStore().setShapeStyle({
+              width: element.clientWidth,
+              height: element.clientHeight
+            })
+          })
+        }
+        resizeObserver.onElResize(element, callback)
+      })
+    }
+  },
+  created() {
+  },
+  install(Vue) {
+    Vue.component(this.name, this)
+    Vue.component(YvanComplexTableProps.name, YvanComplexTableProps)
+  },
+  mounted() {
+    this.initMounted()
+  },
+  watch: {
+    style: {
+      handler() {
+        Object.assign(this.headSimpleTableElement.style, this.style)
+        Object.assign(this.footSimpleTableElement.style, this.style)
+      },
+      deep: true
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+.yvan-complex-table {
+  padding: 0;
+}
+</style>

+ 0 - 3
src/components/elements/yvan-table/YvanSimpleTextInTable.vue

@@ -36,9 +36,6 @@ import system from '@/utils/system'
 import commonMixin from '@/mixin/commonMixin'
 import {StyledSimpleText} from '@/components/elements/style'
 
-/**
- *
- */
 export default {
   name: 'YvanSimpleTextInTable',
   mixins: [commonMixin],

+ 1 - 1
src/components/yvan-editor/Editor.vue

@@ -35,7 +35,7 @@
               :index="idx"
               :band-name="getElementBand(elementBandCode)?.name"
               :band-code="elementBandCode"
-              :band-eight="getElementBandFormStore(elementBandCode)?.band?.height"
+              :band-height="getElementBandFormStore(elementBandCode)?.band?.height"
               :used-height="getUsedHeight(elementBandCode)"
               @changeElementBandTop="changeElementBandTop"/>
 

+ 46 - 18
src/store/global.ts

@@ -2,6 +2,7 @@ import {defineStore} from 'pinia'
 import copy from '@/store/copy'
 import lock from '@/store/lock'
 import layer from '@/store/layer'
+import System from "@/utils/system"
 import compose from '@/store/compose'
 import snapshot from '@/store/snapshot'
 import Constant from "@/utils/constant";
@@ -194,7 +195,7 @@ export const globalStore = defineStore('global', {
         },
 
         addDataSource(dataSource) {
-          this.dataSources.push(dataSource);
+            this.dataSources.push(dataSource);
         },
 
         setDataSet(dataset) {
@@ -298,26 +299,53 @@ export const globalStore = defineStore('global', {
                 elementBand.elements.splice(elementIdx, 1)
             }
         },
-        addComponent({component, index}) {
-            if (index !== undefined) {
-                this.componentData.splice(index, 0, component)
-            } else {
-                this.componentData.push(component)
+        // addComponent({component, index}) {
+        //     if (index !== undefined) {
+        //         this.componentData.splice(index, 0, component)
+        //     } else {
+        //         this.componentData.push(component)
+        //     }
+        // },
+        // deleteComponent(index) {
+        //     if (index === undefined) {
+        //         index = this.curComponentIndex
+        //     }
+        //
+        //     if (index === this.curComponentIndex) {
+        //         this.curComponentIndex = null
+        //         this.curComponent = null
+        //     }
+        //
+        //     if (/\d/.test(index)) {
+        //         this.componentData.splice(index, 1)
+        //     }
+        // },
+        addTableCellElement({bandCode, elementId, bandRowName, rowNo, colNo, element}) {
+            const elementBand = this.elementBands.get(bandCode);
+            // 数组角标从0开始
+            rowNo = --rowNo;
+            if (elementId !== undefined) {
+                const tableElement = elementBand.elements.find(ele => ele.id === elementId);
+                if (!tableElement) {
+                    System.alert(`未获取到id=${elementId}的元素`)
+                    return;
+                }
+                const col = tableElement.config[bandRowName].rows[rowNo].cols[colNo];
+                if (col) {
+                    console.log('col 1 =>>', col)
+                    col.element = element
+                } else {
+                    console.log('col 2 =>>', col)
+                    tableElement.config[bandRowName].rows[rowNo].cols[colNo] = {
+                        colspan: 1,
+                        rowspan: 1,
+                        element: element
+                    }
+                }
             }
         },
-        deleteComponent(index) {
-            if (index === undefined) {
-                index = this.curComponentIndex
-            }
-
-            if (index === this.curComponentIndex) {
-                this.curComponentIndex = null
-                this.curComponent = null
-            }
+        deleteTableCellElement({bandRowName, rowNo, colNo}) {
 
-            if (/\d/.test(index)) {
-                this.componentData.splice(index, 1)
-            }
         },
         showEditorMenu({top, left}) {
             const editorMenuPos = EditorUtils.getEditorMenuPos(top, left);

+ 13 - 0
src/style.css

@@ -23,11 +23,24 @@ a:hover {
   color: #535bf2;
 }
 
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+}
+
 html, body {
   height: 100%;
   margin: 0;
 }
 
+table, th, td {
+  border-spacing: 0;
+  /*border: none;*/
+  color: inherit;
+  background-color: transparent;
+}
+
 #app {
   width: 100%;
   height: 100%;

+ 41 - 0
src/utils/UnitConverter.ts

@@ -0,0 +1,41 @@
+export default class UnitConverter {
+
+    public static getDPI() {
+        const arrDPI = new Array;
+        if (window.screen.deviceXDPI) {
+            arrDPI[0] = window.screen.deviceXDPI;
+            arrDPI[1] = window.screen.deviceYDPI;
+            return arrDPI;
+        } else {
+            const tmpNode = document.createElement("DIV");
+            tmpNode.style.cssText = "width:1in;height:1in;position:absolute;left:0px;top:0px;z-index:99;visibility:hidden";
+            document.body.appendChild(tmpNode);
+            arrDPI[0] = parseInt(String(tmpNode.offsetWidth));
+            arrDPI[1] = parseInt(String(tmpNode.offsetHeight));
+            tmpNode.parentNode.removeChild(tmpNode);
+        }
+        return arrDPI;
+    }
+
+    /**
+     * px转换为mm
+     * @param value
+     * @param isX
+     * @returns {number}
+     */
+    public static px2Mm(value, isX): number {
+        const inch = value / UnitConverter.getDPI()[isX ? 0 : 1];
+        return inch * 25.4;
+    };
+
+    /**
+     * mm转换为px
+     * @param value
+     * @param isX
+     * @returns {number}
+     */
+    public static mm2Px(value, isX): number {
+        const inch = value / 25.4;
+        return inch * UnitConverter.getDPI()[isX ? 0 : 1];
+    }
+}

+ 1 - 0
src/utils/system.ts

@@ -56,6 +56,7 @@ class System {
     }
 
     public alert(message: string = '', type: "" | "success" | "warning" | "error" | "info" = 'info', duration: number = 3000): void {
+        console.info(message)
         ElMessage({
             grouping: true,
             message: message,