基于element-plus一个vue搞定自定义报表

报表功能:根据数据源配置报表

image

 

 代码入学:

<template>
  <ele-page>
    <!-- 顶部工具栏 -->
    <div class="toolbar">
      <div class="toolbar-title">报表设计器</div>
      <div class="toolbar-buttons">
        <button class="toolbar-btn" @click="showDtaSource">
          <i>🗄️</i> 数据源
        </button>
        <button class="toolbar-btn" @click="showNewReportDialog">
          <i>📄</i> 新建
        </button>
        <button class="toolbar-btn" @click="saveReport">
          <i>💾</i> 保存
        </button>
        <button class="toolbar-btn" @click="showPreviewDialog">
          <i>🔍</i> 预览
        </button>
        <button class="toolbar-btn" @click="printReport">
          <i>🖨️</i> 打印
        </button>
        <button class="toolbar-btn" @click="toggleZoomMode" :class="{ active: isZoomMode }">
          <i>🔍</i> {{ isZoomMode ? '退出缩放' : '缩放模式' }}
        </button>
        <button class="toolbar-btn btn-danger" @click="deleteSelectedElement" :disabled="!selectedElement">
          <i>🗑️</i> 删除
        </button>
        <button class="toolbar-btn" @click="showSettingsDialog">
          <i>⚙️</i> 设置
        </button>
      </div>
      <div class="zoom-controls">
        <button class="zoom-btn" @click="zoomOut" title="缩小">−</button>
        <span class="zoom-level">{{ Math.round(zoom * 100) }}%</span>
        <button class="zoom-btn" @click="zoomIn" title="放大">+</button>
        <button class="zoom-btn" @click="resetZoom" title="重置缩放">↺</button>
      </div>
    </div>

    <!-- 主内容区域 -->
    <div class="main-content">
      <!-- 左侧面板 -->
      <div class="left-panel">
        <div class="panel-header">报表组件</div>
        <div class="panel-content">
          <div class="component-list">
            <div
              v-for="component in reportComponents"
              :key="component.id"
              class="component-item"
              draggable="true"
              @dragstart="onDragStart($event, component)"
            >
              <i>{{ component.icon }}</i> {{ component.name }}
            </div>
          </div>
        </div>
      </div>

      <!-- 中间设计区域 -->
      <div
        class="design-area"
        @click="clearSelection"
        @dragover.prevent
        @drop="onDrop"
        @wheel="onWheel"
        :class="{ 'zoom-cursor': isZoomMode }"
      >
        <div
          class="report-paper"
          ref="paperRef"
          :style="{
            transform: `scale(${zoom})`,
            transformOrigin: 'top left',
            width: paperSize.width + 'px',
            height: paperSize.height + 'px'
          }"
        >
          <!-- 渲染所有组件 -->
          <div
            v-for="element in sortedReportElements"
            :key="element.id"
            class="report-element"
            :class="{
              selected: selectedElement?.id === element.id,
              resizing: resizingElement?.id === element.id,
              'data-table': element.type === 'data-table',
              'group-marker': element.type === 'group-marker',
              'line-element': element.type === 'line',
              'rectangle-element': element.type === 'rectangle',
              'field-image-element': element.type === 'field-image',
              'statistics-element': element.type === 'statistics'
            }"
            :style="getElementStyle(element)"
            @click.stop="selectElement(element)"
            @mousedown="startDrag($event, element)"
            @contextmenu.prevent="showContextMenu($event, element)"
          >
            <!-- 分组标记组件 -->
            <div v-if="element.type === 'group-marker'" class="group-marker-container">
              <div class="group-marker-header">
                <div class="group-marker-title">分组标记: {{ element.name }}</div>
                <div class="group-marker-info">分组字段: {{ element.groupField || '未设置' }}</div>
                <div class="group-marker-info">每页组数: {{ element.groupsPerPage || 1 }}</div>
              </div>
            </div>

            <!-- 数据表格 -->
            <div v-else-if="element.type === 'data-table'" class="data-table-container">
              <table class="data-table" v-if="element.tableConfig" :style="{'--header-font-family': element.headerFontFamily,'--header-color': element.headerColor}">
                <thead>
                <tr>
                  <th v-for="(column, index) in element.tableConfig.columns" :key="index"
                      :style="getTableHeaderStyle(element, column)">
                    {{ column.title }}
                  </th>
                </tr>
                </thead>
                <tbody>
                <tr v-for="(row, rowIndex) in getTableData(element)" :key="rowIndex">
                  <td v-for="(column, colIndex) in element.tableConfig.columns" :key="colIndex"
                      :style="getTableCellStyle(column)">
                    {{ formatTableCell(row[column.field], column) }}
                  </td>
                </tr>
                </tbody>
              </table>
              <div v-else class="table-placeholder">
                请配置表格字段
              </div>
            </div>

            <!-- 字段文本框 -->
            <div v-else-if="element.type === 'field-text'" :style="{ color: element.color || '#000000' }">
              {{ getFieldTextContent(element) }}
            </div>

            <!-- 静态文本框 -->
            <div v-else-if = " element.type === 'static-text'" :style="{ color: element.color || '#000000' }">
              {{ getFormattedTextContent(element) }}
            </div>

            <!-- 字段二维码 -->
            <div v-else-if="element.type === 'field-qrcode'" class="field-qrcode-container">
              <div class="field-qrcode-placeholder" v-if="!getFieldQRCodeContent(element)">
                请配置数据字段
              </div>
              <ele-qr-code v-else :value="getFieldQRCodeContent(element)" :size="element.width" tag="svg"/>
            </div>

            <!--静态二维码-->
            <div v-else-if = "element.type === 'qr-code'" class="qe-code-container">
              <ele-qr-code :value="element.content" :size="element.width" tag="svg"/>
            </div>

            <!-- 线条组件 -->
            <div v-else-if="element.type === 'line'" class="line-container">
              <div class="line-preview" :style="getLineStyle(element)"></div>
            </div>

            <!-- 矩形组件 -->
            <div v-else-if="element.type === 'rectangle'" class="rectangle-container">
              <div class="rectangle-preview" :style="getRectangleStyle(element)"></div>
            </div>

            <!-- 图片组件 -->
            <div v-else-if="element.type === 'image'" class="image-container">
              <div style="width: 100%;height: 100%;position: absolute"><!--占位框,方便拖动--></div>
              <img
                v-if="element.imageUrl || element.imageBase64"
                :src="element.imageBase64 || element.imageUrl"
                :alt="element.name"
                style="width: 100%; height: 100%; object-fit: contain;"
              />
              <div v-else class="image-placeholder">
                <i>🖼️</i>
                <div>请设置图片</div>
              </div>
            </div>

            <!-- 字段图片组件 -->
            <div v-else-if="element.type === 'field-image'" class="field-image-container">
              <div style="width: 100%;height: 100%;position: absolute"><!--占位框,方便拖动--></div>
              <img
                v-if="getFieldImageContent(element)"
                :src="getFieldImageContent(element)"
                :alt="element.name"
                style="width: 100%; height: 100%; object-fit: contain;"
              />
              <div v-else class="image-placeholder">
                <i>📷</i>
                <div>请设置数据字段</div>
              </div>
            </div>

            <!-- 统计组件 -->
            <div v-else-if="element.type === 'statistics'" class="statistics-container">
              <div :style="{ color: element.color || '#000000' }">
                {{ getStatisticsContent(element) }}
              </div>
            </div>

            <div
              class="element-handle nw"
              @mousedown.stop="startResize($event, element, 'nw')"
            ></div>
            <div
              class="element-handle ne"
              @mousedown.stop="startResize($event, element, 'ne')"
            ></div>
            <div
              class="element-handle sw"
              @mousedown.stop="startResize($event, element, 'sw')"
            ></div>
            <div
              class="element-handle se"
              @mousedown.stop="startResize($event, element, 'se')"
            ></div>
          </div>
        </div>
      </div>

      <!-- 右键菜单 -->
      <div
        v-if="contextMenu.visible"
        class="context-menu"
        :style="{
          left: contextMenu.x + 'px',
          top: contextMenu.y + 'px'
        }"
      >
        <div class="context-item" @click="deleteElement(contextMenu.element)">
          <i>🗑️</i> 删除元素
        </div>
        <div class="context-item" @click="copyElement(contextMenu.element)">
          <i>📋</i> 复制元素
        </div>
      </div>

      <!-- 右侧面板 -->
      <div class="right-panel">
        <div class="tabs">
          <div
            class="tab"
            :class="{ active: activeTab === 'properties' }"
            @click="activeTab = 'properties'"
          >属性</div>
          <div
            class="tab"
            :class="{ active: activeTab === 'data' }"
            @click="activeTab = 'data'"
          >数据</div>
        </div>

        <div class="tab-content">
          <!-- 属性面板 -->
          <div v-if="activeTab === 'properties' && selectedElement" class="property-group">
            <div class="property-label">基础设置</div>
            <div style="display: flex; gap: 5px; margin-bottom: 10px;">
              <el-input
                type="text"
                v-model="selectedElement.name"
                @input="updateElement"
                size="small"
              >
                <template #prepend>名称</template>
              </el-input>
            </div>

            <div style="display: flex; gap: 5px; margin-bottom: 5px;">
              <el-input
                type="number"
                v-model.number="selectedElement.x"
                @input="updateElement"
                size="small"
              >
                <template #prepend>横轴</template>
              </el-input>
              <el-input
                type="number"
                v-model.number="selectedElement.y"
                @input="updateElement"
                size="small"
              >
                <template #prepend>竖轴</template>
              </el-input>
            </div>
            <div style="display: flex; gap: 5px;">
              <el-input
                type="number"
                v-model.number="selectedElement.width"
                @input="updateElement"
                size="small"
              >
                <template #prepend>宽度</template>
              </el-input>
              <el-input
                type="number"
                v-model.number="selectedElement.height"
                @input="updateElement"
                size="small"
              >
                <template #prepend>高度</template>
              </el-input>
            </div>

            <!-- 分组关联 -->
            <div v-if="selectedElement.type !== 'group-marker'" class="property-group">
              <div class="property-label">分组关联</div>
              <el-select v-model="selectedElement.groupId" @change="updateElement" size="small" style="width: 100%">
                <el-option value="" label="无分组(独立显示)" />
                <el-option
                  v-for="group in availableGroups"
                  :key="group.id"
                  :value="group.id"
                  :label="group.name"
                />
                <template #prepend>分组关联</template>
              </el-select>
              <div class="property-hint" v-if="selectedElement.groupId">
                此组件将根据分组字段显示数据
              </div>
            </div>

            <!-- 统计组件特殊属性 -->
            <div v-if="selectedElement.type === 'statistics'" class="property-group">
              <div class="property-label">统计字段</div>
              <el-select
                v-model="selectedElement.dataField"
                @change="validateStatisticsField"
                size="small"
                style="width: 100%"
              >
                <el-option value="" label="选择统计字段" />
                <el-option
                  v-for="field in availableFields"
                  :key="field.value"
                  :value="field.value"
                  :label="field.label"
                />
              </el-select>

              <div class="property-label" v-if="selectedElement.dataField">统计结果</div>
              <div class="property-preview" v-if="selectedElement.dataField">
                {{ getStatisticsContent(selectedElement) }}
              </div>

              <div class="property-label">统计类型</div>
              <el-select
                v-model="selectedElement.statisticsType"
                @change="updateElement"
                size="small"
                style="width: 100%"
              >
                <el-option value="sum" label="合计" />
                <el-option value="average" label="平均" />
                <el-option value="max" label="最大" />
                <el-option value="min" label="最小" />
                <el-option value="count" label="个数" />
              </el-select>

              <div class="property-label">字体颜色</div>
              <el-color-picker v-model="selectedElement.color" @change="updateElement" />

            </div>

            <!-- 图片组件特殊属性 -->
            <div v-if="selectedElement.type === 'image'" class="property-group">
              <div class="property-label">图片显示方式</div>
              <el-select v-model="selectedElement.objectFit" @change="updateElement" size="small" style="width: 100%">
                <el-option value="contain" label="包含(保持比例)" />
                <el-option value="cover" label="覆盖(填充)" />
                <el-option value="fill" label="填充(拉伸)" />
              </el-select>
            </div>

            <!-- 字段图片组件特殊属性 -->
            <div v-if="selectedElement.type === 'field-image'" class="property-group">
              <div class="property-label">图片显示方式</div>
              <el-select v-model="selectedElement.objectFit" @change="updateElement" size="small" style="width: 100%">
                <el-option value="contain" label="包含(保持比例)" />
                <el-option value="cover" label="覆盖(填充)" />
                <el-option value="fill" label="填充(拉伸)" />
              </el-select>
            </div>

            <!-- 分组标记组件特殊属性 -->
            <div v-if="selectedElement.type === 'group-marker'" class="property-group">
              <div class="property-label">分组字段</div>
              <el-select v-model="selectedElement.groupField" @change="updateElement" size="small" style="width: 100%">
                <el-option value="" label="选择分组字段" />
                <el-option
                  v-for="field in availableFields"
                  :key="field.value"
                  :value="field.value"
                  :label="field.label"
                />
              </el-select>

              <div class="property-label">每页组数</div>
              <el-input
                type="number"
                v-model.number="selectedElement.groupsPerPage"
                @input="updateElement"
                size="small"
                min="1"
                max="10"
              />
            </div>

            <!-- 线条组件特殊属性 -->
            <div v-if="selectedElement.type === 'line'" class="property-group">
              <div class="property-label">线条颜色</div>
              <el-color-picker v-model="selectedElement.lineColor" show-alpha @change="updateElement" />

              <div class="property-label">线条类型</div>
              <el-select v-model="selectedElement.lineStyle" @change="updateElement" size="small" style="width: 100%">
                <el-option value="solid" label="实线" />
                <el-option value="dashed" label="虚线" />
                <el-option value="dotted" label="点状" />
              </el-select>

              <div class="property-label">线条粗细</div>
              <el-select v-model="selectedElement.lineWidth" @change="updateElement" size="small" style="width: 100%">
                <el-option
                  v-for="width in [1,2,3,4,5]"
                  :key="width"
                  :value="width"
                  :label="`${width}px`"
                />
              </el-select>

              <div class="property-label">线条方向</div>
              <el-select v-model="selectedElement.lineDirection" @change="updateElement" size="small" style="width: 100%">
                <el-option value="horizontal" label="水平" />
                <el-option value="vertical" label="垂直" />
                <el-option value="diagonal" label="对角线" />
              </el-select>
            </div>

            <!-- 矩形组件特殊属性 -->
            <div v-if="selectedElement.type === 'rectangle'" class="property-group">
              <div class="property-label">背景颜色</div>
              <el-color-picker v-model="selectedElement.backgroundColor" show-alpha @change="updateElement" />

              <div class="property-label">边框颜色</div>
              <el-color-picker v-model="selectedElement.borderColor" show-alpha @change="updateElement" />

              <div class="property-label">边框类型</div>
              <el-select v-model="selectedElement.borderStyle" @change="updateElement" size="small" style="width: 100%">
                <el-option value="solid" label="实线" />
                <el-option value="dashed" label="虚线" />
                <el-option value="dotted" label="点状" />
              </el-select>

              <div class="property-label">边框粗细</div>
              <el-select v-model="selectedElement.borderWidth" @change="updateElement" size="small" style="width: 100%">
                <el-option
                  v-for="width in [1,2,3,4,5]"
                  :key="width"
                  :value="width"
                  :label="`${width}px`"
                />
              </el-select>
            </div>

            <!-- 表格组件样式设置 -->
            <div v-if="selectedElement.type === 'data-table'" class="property-group">
              <!-- 表头样式 -->
              <div class="property-label">表头样式设置</div>
              <div class="table-top-column-style-config">
                <!-- 表头字体 -->
                <div class="property-label">表头字体</div>
                <el-select v-model="selectedElement.headerFontFamily" @change="updateElement" size="small" style="width: 100%">
                  <el-option value="微软雅黑" label="微软雅黑" />
                  <el-option value="宋体" label="宋体" />
                  <el-option value="黑体" label="黑体" />
                  <el-option value="楷体" label="楷体" />
                </el-select>

                <!-- 表头字号 -->
                <div class="property-label">表头字号</div>
                <el-select v-model="selectedElement.headerFontSize" @change="updateElement" size="small" style="width: 100%">
                  <el-option
                    v-for="size in [12,14,16,18,20,24,26,28,30]"
                    :key="size"
                    :value="size"
                    :label="size"
                  />
                </el-select>

                <!-- 表头字体样式 -->
                <div class="property-label">表头字体样式</div>
                <div>
                  <el-checkbox v-model="selectedElement.headerBold" @change="updateElement">粗体</el-checkbox>
                  <el-checkbox v-model="selectedElement.headerItalic" @change="updateElement">斜体</el-checkbox>
                  <el-checkbox v-model="selectedElement.headerUnderline" @change="updateElement">下划线</el-checkbox>
                </div>

                <!-- 表头对齐方式 -->
                <div class="property-label">表头对齐方式</div>
                <el-select v-model="selectedElement.headerTextAlign" @change="updateElement" size="small" style="width: 100%">
                  <el-option value="left" label="左对齐" />
                  <el-option value="center" label="居中对齐" />
                  <el-option value="right" label="右对齐" />
                </el-select>

                <!-- 表头颜色 -->
                <div class="property-label">表头颜色</div>
                <el-color-picker v-model="selectedElement.headerColor" @change="updateElement" />
              </div>

              <!-- 列样式设置 -->
              <div class="property-label">列样式设置</div>
              <div
                v-for="(column, index) in selectedElement.tableConfig.columns"
                :key="index"
                class="column-style-config"
              >
                <!-- 列标题折叠面板 -->
                <div
                  class="column-style-header"
                  @click="toggleColumnStyle(index)"
                  :class="{ expanded: expandedColumns[index] }"
                >
                  <div class="column-header-content">
                    <span class="column-title">{{ column.title || '未命名列' }}</span>
                    <span class="column-field" v-if="column.field">({{ column.field }})</span>
                    <i class="expand-icon" :class="expandedColumns[index] ? 'el-icon-arrow-down' : 'el-icon-arrow-right'"></i>
                  </div>
                </div>

                <!-- 列样式设置内容 -->
                <div class="column-style-content" v-show="expandedColumns[index]">
                  <!-- 列字体 -->
                  <div class="property-label">字体</div>
                  <el-select v-model="column.fontFamily" @change="updateElement" size="small" style="width: 100%; margin-bottom: 5px;">
                    <el-option value="微软雅黑" label="微软雅黑" />
                    <el-option value="宋体" label="宋体" />
                    <el-option value="黑体" label="黑体" />
                    <el-option value="楷体" label="楷体" />
                  </el-select>

                  <!-- 列字号 -->
                  <div class="property-label">字号</div>
                  <el-select v-model="column.fontSize" @change="updateElement" size="small" style="width: 100%; margin-bottom: 5px;">
                    <el-option
                      v-for="size in [12,14,16,18,20,24,26,28,30]"
                      :key="size"
                      :value="size"
                      :label="size"
                    />
                  </el-select>

                  <!-- 列字体样式 -->
                  <div class="property-label">字体样式</div>
                  <div style="margin-bottom: 5px;">
                    <el-checkbox v-model="column.bold" @change="updateElement">粗体</el-checkbox>
                    <el-checkbox v-model="column.italic" @change="updateElement">斜体</el-checkbox>
                    <el-checkbox v-model="column.underline" @change="updateElement">下划线</el-checkbox>
                  </div>

                  <!-- 列对齐方式 -->
                  <div class="property-label">对齐方式</div>
                  <el-select v-model="column.textAlign" @change="updateElement" size="small" style="width: 100%; margin-bottom: 5px;">
                    <el-option value="left" label="左对齐" />
                    <el-option value="center" label="居中对齐" />
                    <el-option value="right" label="右对齐" />
                  </el-select>

                  <!-- 列颜色 -->
                  <div class="property-label">字体颜色</div>
                  <el-color-picker v-model="column.color" @change="updateElement" style="margin-bottom: 5px;" />

                  <!-- 列格式设置 -->
                  <div class="property-label">列格式</div>
                  <el-select v-model="column.formatType" @change="updateElement" size="small" style="width: 100%; margin-bottom: 5px;">
                    <el-option value="text" label="文本" />
                    <el-option value="number" label="数字" />
                    <el-option value="date" label="日期" />
                    <el-option value="currency" label="货币" />
                  </el-select>

                  <!-- 数字格式设置 -->
                  <div v-if="column.formatType === 'number'" class="property-group">
                    <div class="property-label">小数位数</div>
                    <el-input-number
                      v-model="column.decimalPlaces"
                      @change="updateElement"
                      :min="0"
                      :max="6"
                      size="small"
                      style="width: 100%"
                    />
                  </div>

                  <!-- 货币格式设置 -->
                  <div v-if="column.formatType === 'currency'" class="property-group">
                    <div class="property-label">货币格式</div>
                    <el-checkbox v-model="column.useChineseAmount" @change="updateElement">大写汉字金额</el-checkbox>
                    <el-checkbox v-model="column.useThousandSeparator" @change="updateElement">千分位分隔</el-checkbox>
                  </div>

                  <!-- 日期格式设置 -->
                  <div v-if="column.formatType === 'date'" class="property-group">
                    <div class="property-label">日期格式</div>
                    <el-select v-model="column.dateFormat" @change="updateElement" size="small" style="width: 100%">
                      <el-option value="yyyy-MM-dd" label="年-月-日 (2023-12-31)" />
                      <el-option value="yyyy/MM/dd" label="年/月/日 (2023/12/31)" />
                      <el-option value="yyyy年MM月dd日" label="年月日 (2023年12月31日)" />
                      <el-option value="yyyy-MM-dd HH:mm:ss" label="年-月-日 时:分:秒 (2023-12-31 23:59:59)" />
                      <el-option value="yyyy/MM/dd HH:mm:ss" label="年/月/日 时:分:秒 (2023/12/31 23:59:59)" />
                      <el-option value="yyyy年MM月dd日 HH时mm分ss秒" label="年月日 时分秒 (2023年12月31日 23时59分59秒)" />
                      <el-option value="MM-dd" label="月-日 (12-31)" />
                      <el-option value="HH:mm:ss" label="时:分:秒 (23:59:59)" />
                    </el-select>
                  </div>
                </div>
              </div>
            </div>

            <!-- 静态文本框内容 -->
            <div v-if="['qr-code','static-text'].includes(selectedElement.type)" class="property-group">
              <div class="property-label">文本内容</div>
              <el-input
                type="text"
                v-model="selectedElement.content"
                @input="updateElement"
                size="small"
              />
            </div>

            <!-- 文本框格式设置 -->
            <div v-if="['field-text','static-text'].includes(selectedElement.type)" class="property-group">
              <div class="property-label">文本格式</div>
              <el-select v-model="selectedElement.formatType" @change="updateElement" size="small" style="width: 100%">
                <el-option value="text" label="文本" />
                <el-option value="number" label="数字" />
                <el-option value="date" label="日期" />
                <el-option value="currency" label="货币" />
              </el-select>

              <!-- 数字格式设置 -->
              <div v-if="selectedElement.formatType === 'number'" class="property-group">
                <div class="property-label">小数位数</div>
                <el-input-number
                  v-model="selectedElement.decimalPlaces"
                  @change="updateElement"
                  :min="0"
                  :max="6"
                  size="small"
                  style="width: 100%"
                />
              </div>

              <!-- 货币格式设置 -->
              <div v-if="selectedElement.formatType === 'currency'" class="property-group">
                <div class="property-label">货币格式</div>
                <el-checkbox v-model="selectedElement.useChineseAmount" @change="updateElement">大写汉字金额</el-checkbox>
                <el-checkbox v-model="selectedElement.useThousandSeparator" @change="updateElement">千分位分隔</el-checkbox>
              </div>

              <!-- 日期格式设置 -->
              <div v-if="selectedElement.formatType === 'date'" class="property-group">
                <div class="property-label">日期格式</div>
                <el-select v-model="selectedElement.dateFormat" @change="updateElement" size="small" style="width: 100%">
                  <el-option value="yyyy-MM-dd" label="年-月-日 (2023-12-31)" />
                  <el-option value="yyyy/MM/dd" label="年/月/日 (2023/12/31)" />
                  <el-option value="yyyy年MM月dd日" label="年月日 (2023年12月31日)" />
                  <el-option value="yyyy-MM-dd HH:mm:ss" label="年-月-日 时:分:秒 (2023-12-31 23:59:59)" />
                  <el-option value="yyyy/MM/dd HH:mm:ss" label="年/月/日 时:分:秒 (2023/12/31 23:59:59)" />
                  <el-option value="yyyy年MM月dd日 HH时mm分ss秒" label="年月日 时分秒 (2023年12月31日 23时59分59秒)" />
                  <el-option value="MM-dd" label="月-日 (12-31)" />
                  <el-option value="HH:mm:ss" label="时:分:秒 (23:59:59)" />
                </el-select>
                <div class="property-hint">
                  支持格式: yyyy-年, MM-月, dd-日, HH-时, mm-分, ss-秒
                </div>
              </div>

              <!-- 字体颜色 -->
              <div class="property-label">字体颜色</div>
              <el-color-picker v-model="selectedElement.color" @change="updateElement" />
            </div>

            <!--字体-->
            <div v-if="['field-text','static-text','statistics'].includes(selectedElement.type)">
              <div class="property-label">字体</div>
              <el-select v-model="selectedElement.fontFamily" @change="updateElement" size="small" style="width: 100%">
                <el-option value="微软雅黑" label="微软雅黑" />
                <el-option value="宋体" label="宋体" />
                <el-option value="黑体" label="黑体" />
                <el-option value="楷体" label="楷体" />
              </el-select>
            </div>

            <!--字号-->
            <div v-if="['field-text','static-text','statistics'].includes(selectedElement.type)">
              <div class="property-label">字号</div>
              <el-select v-model="selectedElement.fontSize" @change="updateElement" size="small" style="width: 100%">
                <el-option
                  v-for="size in [12,14,16,18,20,24,26,28,30]"
                  :key="size"
                  :value="size"
                  :label="size"
                />
              </el-select>
            </div>

            <!--字体样式-->
            <div v-if="['field-text','static-text','statistics'].includes(selectedElement.type)">
              <div class="property-label">字体样式</div>
              <div>
                <el-checkbox v-model="selectedElement.bold" @change="updateElement">粗体</el-checkbox>
                <el-checkbox v-model="selectedElement.italic" @change="updateElement">斜体</el-checkbox>
                <el-checkbox v-model="selectedElement.underline" @change="updateElement">下划线</el-checkbox>
              </div>
            </div>

            <!--对齐方式-->
            <div v-if="['field-text','static-text','statistics'].includes(selectedElement.type)">
              <div class="property-label">对齐方式</div>
              <el-select v-model="selectedElement.textAlign" @change="updateElement" size="small" style="width: 100%">
                <el-option value="left" label="左对齐" />
                <el-option value="center" label="居中对齐" />
                <el-option value="right" label="右对齐" />
              </el-select>
            </div>
          </div>

          <!-- 数据面板 -->
          <div v-if="activeTab === 'data' && selectedElement" class="property-group">
            <!-- 图片组件数据配置 -->
            <div v-if="selectedElement.type === 'image'" class="property-group">
              <div class="property-label">图片地址</div>
              <el-input
                type="text"
                v-model="selectedElement.imageUrl"
                @input="updateElement"
                placeholder="输入图片URL地址"
                size="small"
                style="width: 100%; margin-bottom: 10px;"
              />

              <div class="property-label">上传图片</div>
              <el-upload
                class="image-upload"
                action="#"
                :show-file-list="false"
                :before-upload="beforeImageUpload"
                :http-request="handleImageUpload"
              >
                <el-button size="small" type="primary">
                  <i>📁</i> 选择图片
                </el-button>
                <template #tip>
                  <div class="el-upload__tip" style="font-size: 12px; color: #666; margin-top: 5px;">
                    支持 jpg、png、gif 格式,大小不超过 2MB
                  </div>
                </template>
              </el-upload>

              <div class="property-label" v-if="selectedElement.imageUrl || selectedElement.imageBase64">图片预览</div>
              <div class="image-preview" v-if="selectedElement.imageUrl || selectedElement.imageBase64">
                <img
                  :src="selectedElement.imageBase64 || selectedElement.imageUrl"
                  style="max-width: 100%; max-height: 150px; display: block; margin: 0 auto;"
                />
              </div>
            </div>

            <!-- 字段图片组件数据配置 -->
            <div v-else-if="selectedElement.type === 'field-image'" class="property-group">
              <div class="property-label">数据字段</div>
              <el-select v-model="selectedElement.dataField" @change="updateElement" size="small" style="width: 100%">
                <el-option value="" label="选择字段" />
                <el-option
                  v-for="field in availableFields"
                  :key="field.value"
                  :value="field.value"
                  :label="field.label"
                />
              </el-select>

              <div class="property-label" v-if="selectedElement.dataField">预览值</div>
              <div class="property-preview" v-if="selectedElement.dataField">
                {{ getFieldPreview(selectedElement.dataField) }}
              </div>

              <div class="property-label">图片显示方式</div>
              <el-select v-model="selectedElement.objectFit" @change="updateElement" size="small" style="width: 100%">
                <el-option value="contain" label="包含(保持比例)" />
                <el-option value="cover" label="覆盖(填充)" />
                <el-option value="fill" label="填充(拉伸)" />
              </el-select>
            </div>

            <!-- 数据表格配置 -->
            <div v-if="selectedElement.type === 'data-table'" class="property-group">
              <div class="property-label">表格列配置</div>
              <div
                v-for="(column, index) in selectedElement.tableConfig.columns"
                :key="index"
                class="column-config"
                draggable="true"
                @dragstart="onColumnDragStart($event, index)"
                @dragover.prevent="onColumnDragOver($event, index)"
                @drop="onColumnDrop($event, index)"
                @dragenter.prevent
                @dragleave.prevent
              >
                <div style="display: flex; gap: 5px; margin-bottom: 5px;">
                  <el-input
                    placeholder="列标题"
                    v-model="column.title"
                    @input="updateElement"
                    size="small"
                  />
                  <el-select
                    v-model="column.field"
                    @change="updateElement"
                    size="small"

                  >
                    <el-option value="" label="选择字段" />
                    <el-option
                      v-for="field in availableFields"
                      :key="field.value"
                      :value="field.value"
                      :label="field.label"
                    />
                  </el-select>
                  <el-button
                    type="danger"
                    @click="removeColumn(index)"
                    size="small"
                    style="padding: 4px 8px;"
                  >
                    ×
                  </el-button>
                </div>
              </div>

              <el-button @click="addColumn" style="width: 100%; margin-top: 10px;" size="small">
                <i>➕</i> 添加列
              </el-button>
            </div>

            <!-- 字段文本框配置 -->
            <div v-else-if="['field-text','field-qrcode'].includes(selectedElement.type)" class="property-group">
              <div class="property-label">数据字段</div>
              <el-select v-model="selectedElement.dataField" @change="updateElement" size="small" style="width: 100%">
                <el-option value="" label="选择字段" />
                <el-option
                  v-for="field in availableFields"
                  :key="field.value"
                  :value="field.value"
                  :label="field.label"
                />
              </el-select>

              <div class="property-label" v-if="selectedElement.dataField">预览值</div>
              <div class="property-preview" v-if="selectedElement.dataField">
                {{ getFieldPreview(selectedElement.dataField) }}
              </div>
            </div>

            <!-- 统计组件数据配置 -->
            <div v-else-if="selectedElement.type === 'statistics'" class="property-group">
              <div class="property-label">统计组件</div>
              <div class="property-hint">统计配置在属性面板中设置</div>
            </div>

            <!-- 静态文本框无数据配置 -->
            <div v-else-if="selectedElement.type === 'static-text'" class="property-group">
              <div class="property-label">静态文本</div>
              <div class="property-hint">此组件为静态文本,无需数据绑定</div>
            </div>

            <!-- 静态二维码无数据配置 -->
            <div v-else-if="selectedElement.type === 'qr-code'" class="property-group">
              <div class="property-label">静态二维码</div>
              <div class="property-hint">此组件为静态二维码,无需数据绑定</div>
            </div>

            <!-- 线条组件无数据配置 -->
            <div v-else-if="selectedElement.type === 'line'" class="property-group">
              <div class="property-label">线条组件</div>
              <div class="property-hint">此组件为装饰线条,无需数据绑定</div>
            </div>

            <!-- 矩形组件无数据配置 -->
            <div v-else-if="selectedElement.type === 'rectangle'" class="property-group">
              <div class="property-label">矩形组件</div>
              <div class="property-hint">此组件为装饰矩形,无需数据绑定</div>
            </div>

            <!-- 分组标记组件无数据配置 -->
            <div v-else-if="selectedElement.type === 'group-marker'" class="property-group">
              <div class="property-label">分组配置</div>
              <div class="property-hint">分组配置在属性面板中设置</div>
            </div>
          </div>

          <div v-if="!selectedElement" class="no-selection">
            请选择一个报表元素进行编辑
          </div>
        </div>
      </div>
    </div>

    <!-- 底部状态栏 -->
    <div class="status-bar">
      <div>{{ statusMessage }}</div>
      <div v-if="selectedElement">
        X: {{ selectedElement.x }}, Y: {{ selectedElement.y }} | 宽度: {{ selectedElement.width }}, 高度: {{ selectedElement.height }}
      </div>
      <div v-else>就绪</div>
      <div class="zoom-hint" v-if="isZoomMode">
        🔍 缩放模式:鼠标滚轮缩放,按住Ctrl加速
      </div>
    </div>

    <!-- 打印预览 -->
    <ele-printer v-model="printing" target="_iframe">
      <div v-html="printHtml"></div>
    </ele-printer>

    <!-- 新建报表对话框 -->
    <el-dialog
      v-model="newReportDialogVisible"
      title="新建报表"
      width="500px"
      :before-close="handleNewReportDialogClose"
    >
      <div class="dialog-content">
        <div class="form-group">
          <div class="form-label">选择纸张大小</div>
          <el-select v-model="selectedPaperSize" placeholder="请选择纸张大小" style="width: 100%">
            <el-option
              v-for="size in paperSizes"
              :key="size.name"
              :label="size.name"
              :value="size.name"
            />
          </el-select>
        </div>

        <div class="form-group" v-if="selectedPaperSize === '自定义'">
          <div class="form-row">
            <div class="form-item">
              <div class="form-label">宽度 (px)</div>
              <el-input-number
                v-model="customPaperSize.width"
                :min="100"
                :max="2000"
                :step="10"
                controls-position="right"
                style="width: 100%"
              />
            </div>
            <div class="form-item">
              <div class="form-label">高度 (px)</div>
              <el-input-number
                v-model="customPaperSize.height"
                :min="100"
                :max="3000"
                :step="10"
                controls-position="right"
                style="width: 100%"
              />
            </div>
          </div>
        </div>

        <div class="paper-preview">
          <div class="preview-label">预览</div>
          <div class="preview-container">
            <div
              class="preview-paper"
              :style="{
                width: getPreviewPaperSize().width + 'px',
                height: getPreviewPaperSize().height + 'px'
              }"
            >
              <div class="paper-info">
                {{ getPreviewPaperSize().width }} × {{ getPreviewPaperSize().height }} px
              </div>
            </div>
          </div>
        </div>
      </div>

      <template #footer>
        <span class="dialog-footer">
          <el-button @click="newReportDialogVisible = false">取消</el-button>
          <el-button type="primary" @click="confirmNewReport">确定</el-button>
        </span>
      </template>
    </el-dialog>

    <!-- 预览对话框 -->
    <el-dialog
      v-model="previewDialogVisible"
      title="报表预览"
      :width="getPreviewDialogWidth()"
      :fullscreen="isPreviewFullscreen"
      :before-close="handlePreviewDialogClose"
    >
      <div class="preview-header">
        <el-button
          size="small"
          @click="togglePreviewFullscreen"
          style="margin-bottom: 10px;"
        >
          {{ isPreviewFullscreen ? '退出全屏' : '全屏' }}
        </el-button>
      </div>
      <div
        class="preview-content"
        :style="{ width: paperSize.width + 'px' }"
        v-html="previewHtml"
      ></div>

      <template #footer>
    <span class="dialog-footer">
      <el-button @click="previewDialogVisible = false">关闭</el-button>
      <el-button type="primary" @click="printReportFromPreview">打印</el-button>
    </span>
      </template>
    </el-dialog>

    <!-- 设置对话框 -->
    <el-dialog
      v-model="settingsDialogVisible"
      title="页面设置"
      width="500px"
      :before-close="handleSettingsDialogClose"
    >
      <div class="dialog-content">
        <div class="form-group">
          <div class="form-label">页面宽度 (px)</div>
          <el-input-number
            v-model="paperSize.width"
            :min="100"
            :max="2000"
            :step="10"
            controls-position="right"
            style="width: 100%"
          />
        </div>

        <div class="form-group">
          <div class="form-label">页面高度 (px)</div>
          <el-input-number
            v-model="paperSize.height"
            :min="100"
            :max="3000"
            :step="10"
            controls-position="right"
            style="width: 100%"
          />
        </div>

        <div class="paper-preview">
          <div class="preview-label">当前页面大小</div>
          <div class="preview-container">
            <div
              class="preview-paper"
              :style="{
                width: (paperSize.width / 4) + 'px',
                height: (paperSize.height / 4) + 'px'
              }"
            >
              <div class="paper-info">
                {{ paperSize.width }} × {{ paperSize.height }} px
              </div>
            </div>
          </div>
        </div>
      </div>

      <template #footer>
        <span class="dialog-footer">
          <el-button @click="settingsDialogVisible = false">取消</el-button>
          <el-button type="primary" @click="confirmSettings">确定</el-button>
        </span>
      </template>
    </el-dialog>

    <!-- 数据源对话框 -->
    <el-dialog
      v-model="dataSourceDialogVisible"
      title="数据源配置"
      width="600px"
      :before-close="handleDataSourceDialogClose"
    >
      <div class="dialog-content">
        <div class="form-group">
          <div class="form-label">数据源类型</div>
          <el-select
            v-model="selectedDataSourceType"
            placeholder="请选择数据源类型"
            style="width: 100%"
            @change="handleDataSourceTypeChange"
          >
            <el-option label="JSON数据" value="json" />
            <el-option label="SQL查询数据" value="sql" />
            <el-option label="接口查询数据" value="api" />
          </el-select>
        </div>

        <!-- JSON数据配置 -->
        <div v-if="selectedDataSourceType === 'json'" class="form-group">
          <div class="form-label">JSON数据</div>
          <el-input
            v-model="jsonData"
            type="textarea"
            :rows="12"
            placeholder='请输入JSON数据,例如: [{"name": "张三", "age": 25}, {"name": "李四", "age": 30}]'
            style="width: 100%"
          />
          <div class="form-hint">
            提示:请输入有效的JSON数组格式数据
          </div>
        </div>

        <!-- SQL数据配置 -->
        <div v-if="selectedDataSourceType === 'sql'" class="form-group">
          <div class="form-row">
            <div class="form-item">
              <div class="form-label">数据库地址</div>
              <el-input
                v-model="sqlConfig.host"
                placeholder="例如: localhost:3306"
                style="width: 100%"
              />
            </div>
            <div class="form-item">
              <div class="form-label">数据库名称</div>
              <el-input
                v-model="sqlConfig.database"
                placeholder="例如: test_db"
                style="width: 100%"
              />
            </div>
          </div>

          <div class="form-row">
            <div class="form-item">
              <div class="form-label">数据库账号</div>
              <el-input
                v-model="sqlConfig.username"
                placeholder="请输入数据库账号"
                style="width: 100%"
              />
            </div>
            <div class="form-item">
              <div class="form-label">数据库密码</div>
              <el-input
                v-model="sqlConfig.password"
                type="password"
                placeholder="请输入数据库密码"
                style="width: 100%"
              />
            </div>
          </div>

          <div class="form-group">
            <div class="form-label">SQL查询语句</div>
            <el-input
              v-model="sqlConfig.sql"
              type="textarea"
              :rows="4"
              placeholder="请输入SQL查询语句,例如: SELECT * FROM users WHERE status = 1"
              style="width: 100%"
            />
          </div>
        </div>

        <!-- 接口数据配置 -->
        <div v-if="selectedDataSourceType === 'api'" class="form-group">
          <div class="form-group">
            <div class="form-label">接口地址</div>
            <el-input
              v-model="apiConfig.url"
              placeholder="请输入接口URL地址"
              style="width: 100%"
            >
              <template #prepend>HTTP</template>
            </el-input>
          </div>

          <div class="form-row">
            <div class="form-item">
              <div class="form-label">请求方法</div>
              <el-select v-model="apiConfig.method" style="width: 100%">
                <el-option label="GET" value="GET" />
                <el-option label="POST" value="POST" />
                <el-option label="PUT" value="PUT" />
                <el-option label="DELETE" value="DELETE" />
              </el-select>
            </div>
            <div class="form-item">
              <div class="form-label">请求超时(秒)</div>
              <el-input-number
                v-model="apiConfig.timeout"
                :min="5"
                :max="60"
                style="width: 100%"
              />
            </div>
          </div>

          <div class="form-group">
            <div class="form-label">请求头</div>
            <el-input
              v-model="apiConfig.headers"
              type="textarea"
              :rows="3"
              placeholder='请输入请求头JSON,例如: {"Content-Type": "application/json", "Authorization": "Bearer token"}'
              style="width: 100%"
            />
          </div>

          <div class="form-group" v-if="apiConfig.method === 'POST' || apiConfig.method === 'PUT'">
            <div class="form-label">请求参数</div>
            <el-input
              v-model="apiConfig.body"
              type="textarea"
              :rows="3"
              placeholder='请输入请求参数JSON,例如: {"page": 1, "size": 20}'
              style="width: 100%"
            />
          </div>
        </div>

        <!-- 数据预览 -->
        <div class="form-group" v-if="previewData.length > 0">
          <div class="form-label">数据预览 (前5行)</div>
          <div class="data-preview">
            <el-table
              :data="previewData"
              border
              size="small"
              style="width: 100%"
              max-height="200"
            >
              <el-table-column
                v-for="key in Object.keys(previewData[0] || {})"
                :key="key"
                :prop="key"
                :label="key"
                min-width="100"
              />
            </el-table>
          </div>
        </div>
      </div>

      <template #footer>
    <span class="dialog-footer">
      <!-- JSON数据源显示验证按钮 -->
      <el-button
        v-if="selectedDataSourceType === 'json'"
        @click="validateJsonData"
        :loading="validatingJson"
        :disabled="!jsonData.trim()"
      >
        {{ validatingJson ? '验证中...' : '验证数据' }}
      </el-button>
      <el-button
        v-if="selectedDataSourceType === 'sql'"
        @click="testSqlConnection"
        :loading="testingConnection"
        :disabled="!sqlConfig.host || !sqlConfig.database "
      >
        {{ testingConnection ? '测试中...' : '连接测试' }}
      </el-button>
      <el-button
        v-if="selectedDataSourceType === 'api'"
        @click="testApiConnection"
        :loading="testingApi"
        :disabled="!apiConfig.url"
      >
        {{ testingApi ? '测试中...' : '接口测试' }}
      </el-button>
      <el-button @click="dataSourceDialogVisible = false">取消</el-button>
      <el-button
        type="primary"
        @click="saveDataSource"
        :loading="savingDataSource"
        :disabled="!isDataSourceValid || (selectedDataSourceType === 'json' && previewData.length === 0)"
      >
        {{ savingDataSource ? '保存中...' : '保存' }}
      </el-button>
    </span>
      </template>
    </el-dialog>
  </ele-page>
</template>

<script lang="ts" setup>
  import { ref, reactive, onMounted, nextTick, computed ,watch} from 'vue'
  import QRCode from 'qrcode';
  import { ElMessage } from 'element-plus';

  // 数据
  const data = ref([
    {
      "tenantId": 2000017,
      "createBy": "lq3",
      "createTime": "2025-10-30 14:05:26",
      "updateBy": "lq3",
      "updateTime": "2025-10-30 14:05:44",
      "isDeleted": null,
      "inOrderDetailId": 66,
      "inOrderId": 54,
      "materialId": 11,
      "materialName": "5",
      "materialSpecification": "",
      "supplierId": 2,
      "supplierName": "供应商A",
      "batchNumber": "P2025103000017",
      "productionDate": "2025-10-30",
      "expiryDate": "2025-11-09",
      "inputQuantity": 5.0000,
      "inputUnit": "支",
      "quantity": 5.0000,
      "unitPrice": 1.0000,
      "amount": 5.0000,
      "inOrderNumber": "RKD-20251030-00018",
      "applicantName": "lq3",
      "reviewerName": "lq3",
      "applyTime": "2025-10-30 14:05:26",
      "warehouseId": 7,
      "prefix": null,
      "lockKey": null,
      "img":"https://cdn.eleadmin.com/20200610/avatar.jpg"
    },
    {
      "tenantId": 2000017,
      "createBy": "lq3",
      "createTime": "2025-11-13 14:03:43",
      "updateBy": "lq3",
      "updateTime": "2025-11-13 14:54:15",
      "isDeleted": null,
      "inOrderDetailId": 73,
      "inOrderId": 59,
      "materialId": 14,
      "materialName": "笔记本",
      "materialSpecification": "",
      "supplierId": 2,
      "supplierName": "供应商B",
      "batchNumber": null,
      "productionDate": "2025-11-13",
      "expiryDate": "2026-11-08",
      "inputQuantity": 1000.0000,
      "inputUnit": "个",
      "quantity": 1000.0000,
      "unitPrice": 29.0000,
      "amount": 29000.0000,
      "inOrderNumber": "RKD-20251113-00001",
      "applicantName": "lq3",
      "reviewerName": "",
      "applyTime": "2025-11-13 14:03:43",
      "warehouseId": 7,
      "prefix": null,
      "lockKey": null,
      "img":"http://192.168.0.243:8022/group1/M00/00/00/wKgA82kcAsOAd4MdAABQoHY25rw105.png"
    },
    {
      "tenantId": 2000017,
      "createBy": "lq3",
      "createTime": "2025-11-13 14:03:43",
      "updateBy": "lq3",
      "updateTime": "2025-11-13 14:54:15",
      "isDeleted": null,
      "inOrderDetailId": 74,
      "inOrderId": 59,
      "materialId": 15,
      "materialName": "水泥",
      "materialSpecification": "",
      "supplierId": 2,
      "supplierName": "供应商C",
      "batchNumber": null,
      "productionDate": "2025-11-13",
      "expiryDate": "2026-11-08",
      "inputQuantity": 110.0000,
      "inputUnit": "个",
      "quantity": 110.0000,
      "unitPrice": 80.0000,
      "amount": 8800.0000,
      "inOrderNumber": "RKD-20251113-00001",
      "applicantName": "lq3",
      "reviewerName": "",
      "applyTime": "2025-11-13 14:03:43",
      "warehouseId": 7,
      "prefix": null,
      "lockKey": null
    }
  ])

  // 报表元素 - 使用分组标记结构
  const reportElements = reactive([
    {
      "id": 0, // 使用 0 作为纸张配置的ID
      "name": "纸张配置",
      "type": "paper-config",
      "paperSize": {
        "width": 794,
        "height": 1123,
        "name": "A4"
      }
    },
    {
      "id": 1,
      "name": "订单分组",
      "type": "group-marker",
      "x": 0,
      "y": 0,
      "width": 790,
      "height": 400,
      "fontFamily": "微软雅黑",
      "fontSize": 12,
      "bold": false,
      "italic": false,
      "underline": false,
      "textAlign": "left",
      "borderStyle": "none",
      "groupField": "inOrderId",
      "groupsPerPage": 1
    },
    {
      "id": 2,
      "name": "报表标题",
      "content": "入库明细报表",
      "type": "static-text",
      "x": 336,
      "y": 39,
      "width": 200,
      "height": 30,
      "fontFamily": "微软雅黑",
      "fontSize": 16,
      "bold": true,
      "italic": false,
      "underline": false,
      "textAlign": "center",
      "borderStyle": "none",
      "groupId": 1,
      "color": "#000000",
      "formatType": "text"
    },
    {
      "id": 3,
      "name": "订单编号",
      "content": "",
      "type": "field-text",
      "dataField": "inOrderNumber",
      "x": 362,
      "y": 88,
      "width": 144,
      "height": 20,
      "fontFamily": "微软雅黑",
      "fontSize": 12,
      "bold": true,
      "italic": false,
      "underline": false,
      "textAlign": "left",
      "borderStyle": "none",
      "groupId": 1,
      "color": "#000000",
      "formatType": "text"
    },
    {
      "id": 4,
      "name": "数据表格",
      "content": "入库明细表",
      "type": "data-table",
      "x": 0,
      "y": 124,
      "width": 790,
      "height": 200,
      "fontFamily": "宋体",
      "fontSize": 12,
      "bold": false,
      "italic": false,
      "underline": false,
      "textAlign": "left",
      "borderStyle": "none",
      "groupId": 1,
      "headerFontFamily": "微软雅黑",
      "headerColor": "#000000",
      "tableConfig": {
        "columns": [
          {
            "title": "序号",
            "field": "inOrderDetailId",
            "fontFamily": "宋体",
            "color": "#000000"
          },
          {
            "title": "物资名称",
            "field": "materialName",
            "fontFamily": "宋体",
            "color": "#000000"
          },
          {
            "title": "规格型号",
            "field": "materialSpecification",
            "fontFamily": "宋体",
            "color": "#000000"
          },
          {
            "title": "有效期",
            "field": "expiryDate",
            "fontFamily": "宋体",
            "color": "#000000"
          },
          {
            "title": "数量",
            "field": "inputQuantity",
            "fontFamily": "宋体",
            "color": "#000000"
          },
          {
            "title": "单位",
            "field": "inputUnit",
            "fontFamily": "宋体",
            "color": "#000000"
          }
        ]
      }
    },
    {
      "id": 5,
      "name": "分割线",
      "type": "line",
      "x": 0,
      "y": 350,
      "width": 790,
      "height": 2,
      "lineColor": "#000000",
      "lineStyle": "solid",
      "lineWidth": 1,
      "lineDirection": "horizontal"
    },
    {
      "id": 6,
      "name": "边框矩形",
      "type": "rectangle",
      "x": 0,
      "y": 284,
      "width": 790,
      "height": 20,
      "backgroundColor": "rgba(15, 150, 78, 0.98)",
      "borderColor": "#000000",
      "borderStyle": "solid",
      "borderWidth": 1
    },
    {
      "id": 7,
      "name": "金额统计",
      "type": "statistics",
      "x": 639,
      "y": 360,
      "width": 150,
      "height": 40,
      "dataField": "amount",
      "statisticsType": "sum",
      "color": "#000000"
    }
  ]);

  // 纸张大小配置
  const paperSizes = [
    { name: 'A4', width: 794, height: 1123 },
    { name: 'A3', width: 1123, height: 1587 },
    { name: 'A5', width: 559, height: 794 },
    { name: 'B5', width: 693, height: 984 },
    { name: '信纸', width: 756, height: 1056 },
    { name: '自定义', width: 794, height: 1123 }
  ];

  // 响应式数据
  const activeTab = ref('properties')
  const statusMessage = ref('就绪')
  const paperRef = ref()
  const selectedElement = ref<any>(null)
  const resizingElement = ref<any>(null)
  const isZoomMode = ref(false)
  const zoom = ref(1)
  const printHtml = ref();
  const printing = ref(false);
  // 预览相关响应式数据
  const isPreviewFullscreen = ref(false);
  // 对话框状态
  const newReportDialogVisible = ref(false);
  const previewDialogVisible = ref(false);
  const settingsDialogVisible = ref(false);
  const previewHtml = ref('');

  // 纸张大小
  const paperSize = reactive({
    width: 794,
    height: 1123
  });

  const selectedPaperSize = ref('A4');
  const customPaperSize = reactive({
    width: 794,
    height: 1123
  });

  // 表格列拖拽相关
  const draggingColumnIndex = ref(-1);

  // 计算属性
  const availableFields = computed(() => {
    if (data.value.length === 0) return [];
    const sample = data.value[0];
    return Object.keys(sample).map(key => ({
      value: key,
      label: key
    }));
  });

  const availableGroups = computed(() => {
    return reportElements.filter(element => element.type === 'group-marker');
  });

  // 按id排序的报表元素
  const sortedReportElements = computed(() => {
    return [...reportElements].filter(element => element.type !== 'paper-config').sort((a, b) => a.id - b.id);
  });

  // 右键菜单
  const contextMenu = reactive({
    visible: false,
    x: 0,
    y: 0,
    element: null as any
  })

  // 缩放相关
  const zoomStep = 0.1
  const minZoom = 0.3
  const maxZoom = 3

  // 报表组件列表
  const reportComponents = reactive([
    { id: 1, name: '分组标记', icon: '📦', type: 'group-marker' },
    { id: 2, name: '数据表格', icon: '📊', type: 'data-table' },
    { id: 3, name: '字段文本框', icon: '🔢', type: 'field-text' },
    { id: 4, name: '静态文本框', icon: '📝', type: 'static-text' },
    /*{ id: 5, name: '日期字段', icon: '📅', type: 'date' },*/
    { id: 6, name: '静态图片', icon: '🖼️', type: 'image' },
    { id: 7, name: '字段图片', icon: '📷', type: 'field-image' }, // 新增字段图片组件
    { id: 8, name: '矩形框', icon: '🔲', type: 'rectangle' },
    { id: 9, name: '线条', icon: '➖', type: 'line' },
    { id: 10, name: '静态二维码', icon: '📱', type: 'qr-code'},
    { id: 11, name: '字段二维码', icon: '🔗', type: 'field-qrcode' }, // 新增字段二维码组件
    { id: 12, name: '统计组件', icon: '📈', type: 'statistics' } // 新增统计组件
  ])

  const dataSourceDialogVisible = ref(false);
  const selectedDataSourceType = ref('json');
  const savingDataSource = ref(false);
  const testingConnection = ref(false);
  const testingApi = ref(false);
  const validatingJson = ref(false);
  // JSON数据配置
  const jsonData = ref('');

  // SQL配置
  const sqlConfig = reactive({
    host: '',
    database: '',
    username: '',
    password: '',
    sql: ''
  });

  // 接口配置
  const apiConfig = reactive({
    url: '',
    method: 'GET',
    timeout: 30,
    headers: '',
    body: ''
  });

  // 数据预览
  const previewData = ref([]);

  // 拖拽相关状态
  let isDragging = false
  let dragOffsetX = 0
  let dragOffsetY = 0

  // 缩放相关状态
  let isResizing = false
  let resizeDirection = ''
  let startWidth = 0
  let startHeight = 0
  let startX = 0
  let startY = 0
  let startMouseX = 0
  let startMouseY = 0

  // 新建报表功能
  const showNewReportDialog = () => {
    selectedPaperSize.value = 'A4';
    customPaperSize.width = 794;
    customPaperSize.height = 1123;
    newReportDialogVisible.value = true;
  }

  // 纸张配置相关
  const paperConfig = computed(() => {
    return reportElements.find(el => el.type === 'paper-config') || {
      paperSize: { width: 794, height: 1123, name: 'A4' }
    };
  });

  // 更新纸张大小的方法
  const updatePaperSize = (width, height, name) => {
    let paperConfigElement = reportElements.find(el => el.type === 'paper-config');

    if (!paperConfigElement) {
      // 如果没有纸张配置元素,创建一个
      paperConfigElement = {
        id: 0,
        name: "纸张配置",
        type: "paper-config",
        paperSize: { width, height, name }
      };
      reportElements.unshift(paperConfigElement);
    } else {
      paperConfigElement.paperSize = { width, height, name };
    }

    // 同时更新 paperSize 响应式对象
    paperSize.width = width;
    paperSize.height = height;

    statusMessage.value = `纸张大小已更新: ${name} (${width}×${height}px)`;
  };

  const handleNewReportDialogClose = () => {
    newReportDialogVisible.value = false;
  }

  const getPreviewPaperSize = () => {
    if (selectedPaperSize.value === '自定义') {
      return customPaperSize;
    } else {
      const size = paperSizes.find(s => s.name === selectedPaperSize.value);
      return size || { width: 794, height: 1123 };
    }
  }

  const confirmNewReport = () => {
    const newSize = getPreviewPaperSize();

    // 使用新的更新方法
    updatePaperSize(newSize.width, newSize.height, selectedPaperSize.value);

    // 清空现有元素(保留纸张配置)
    const paperConfig = reportElements.find(el => el.type === 'paper-config');
    reportElements.length = 0;
    if (paperConfig) {
      reportElements.push(paperConfig);
    }

    selectedElement.value = null;
    newReportDialogVisible.value = false;
    statusMessage.value = `已创建新报表 (${selectedPaperSize.value}: ${newSize.width}×${newSize.height}px)`;
    ElMessage.success('已创建新报表');
  }

  const printReportFromPreview = () => {
    printHtml.value = previewHtml.value;
    printing.value = true;
    previewDialogVisible.value = false;
  }

  // 计算属性:验证数据源是否有效
  const isDataSourceValid = computed(() => {
    switch (selectedDataSourceType.value) {
      case 'json':
        return jsonData.value.trim() !== '';
      case 'sql':
        return sqlConfig.host && sqlConfig.database && sqlConfig.username && sqlConfig.sql;
      case 'api':
        return apiConfig.url;
      default:
        return false;
    }
  });

  // 方法:显示数据源对话框
  const showDtaSource = () => {
    dataSourceDialogVisible.value = true;
    // 重置表单
    selectedDataSourceType.value = 'json';
  };

  // 方法:处理数据源类型变化
  const handleDataSourceTypeChange = () => {
    previewData.value = [];
  };

  // 方法:处理数据源对话框关闭
  const handleDataSourceDialogClose = () => {
    dataSourceDialogVisible.value = false;
  };

  // 方法:测试SQL连接
  const testSqlConnection = async () => {
    testingConnection.value = true;
    try {
      // 这里应该是实际的SQL连接测试逻辑
      // 模拟测试
      await new Promise(resolve => setTimeout(resolve, 1000));
      const mockData = data.value.slice(0, 3).map(item => ({ ...item }));
      previewData.value = mockData;

    } catch (error) {
      ElMessage.error('数据库连接失败:' + error.message);
    } finally {
      testingConnection.value = false;
    }
  };

  // 方法:测试接口连接
  const testApiConnection = async () => {
    testingApi.value = true;
    try {
      // 解析请求头
      let headers = {};
      if (apiConfig.headers) {
        try {
          headers = JSON.parse(apiConfig.headers);
        } catch (e) {
          throw new Error('请求头格式错误,必须是有效的JSON');
        }
      }

      // 解析请求体
      let body = null;
      if (apiConfig.body && (apiConfig.method === 'POST' || apiConfig.method === 'PUT')) {
        try {
          body = JSON.parse(apiConfig.body);
        } catch (e) {
          throw new Error('请求参数格式错误,必须是有效的JSON');
        }
      }

      // 模拟API请求
      await new Promise(resolve => setTimeout(resolve, 1000));

      // 使用模拟数据作为预览
      const mockData = data.value.slice(0, 3).map(item => ({ ...item }));
      previewData.value = mockData;

      ElMessage.success('接口请求成功!');
    } catch (error) {
      ElMessage.error('接口请求失败:' + error.message);
      previewData.value = [];
    } finally {
      testingApi.value = false;
    }
  };

  // 方法:验证JSON数据
  const validateJsonData = () => {
    validatingJson.value = true;
    try {
      // 验证并解析JSON数据
      const parsedData = JSON.parse(jsonData.value);
      if (!Array.isArray(parsedData)) {
        throw new Error('JSON数据必须是数组格式');
      }

      // 显示数据预览
      previewData.value = parsedData.slice(0, 5);
      ElMessage.success(`JSON数据验证成功!共${parsedData.length}条数据`);
    } catch (error) {
      ElMessage.error('JSON格式错误:' + error.message);
      previewData.value = [];
    } finally {
      validatingJson.value = false;
    }
  };

  // 方法:保存数据源
  const saveDataSource = async () => {
    // 如果是JSON数据源且没有预览数据,先验证数据
    if (selectedDataSourceType.value === 'json' && previewData.value.length === 0) {
      ElMessage.warning('请先验证JSON数据');
      return;
    }

    savingDataSource.value = true;
    try {
      let newData = [];

      switch (selectedDataSourceType.value) {
        case 'json':
          // 使用已验证的数据
          try {
            const parsedData = JSON.parse(jsonData.value);
            if (!Array.isArray(parsedData)) {
              throw new Error('JSON数据必须是数组格式');
            }
            newData = parsedData;
          } catch (error) {
            throw new Error('JSON格式错误:' + error.message);
          }
          break;

        case 'sql':
          // ... SQL数据源处理逻辑保持不变 ...
          newData = [...data.value];
          ElMessage.success('SQL数据源配置已保存');
          break;

        case 'api':
          // ... API数据源处理逻辑保持不变 ...
          newData = [...data.value];
          ElMessage.success('接口数据源配置已保存');
          break;
      }

      // 更新全局数据
      if (newData.length > 0) {
        // 在实际应用中更新数据
        data.value = newData;
        ElMessage.success(`数据源已更新,共${newData.length}条数据`);
      }

      dataSourceDialogVisible.value = false;
    } catch (error) {
      ElMessage.error('保存数据源失败:' + error.message);
    } finally {
      savingDataSource.value = false;
    }
  };

  // 设置功能
  const showSettingsDialog = () => {
    settingsDialogVisible.value = true;
  }

  const handleSettingsDialogClose = () => {
    settingsDialogVisible.value = false;
  }

  const confirmSettings = () => {
    settingsDialogVisible.value = false;
    statusMessage.value = `页面大小已更新: ${paperSize.width}×${paperSize.height}px`;
    ElMessage.success('页面设置已保存');
  }

  // 原有的 newReport 方法改为直接清空
  const newReport = () => {
    reportElements.length = 0
    selectedElement.value = null
    statusMessage.value = '已创建新报表'
  }

  // 原有的 previewReport 方法改为调用预览对话框
  const previewReport = () => {
    showPreviewDialog();
  }

  // ... 其余所有原有方法保持不变(包括 saveReport, printReport, 表格列拖拽方法,统计组件方法等)...

  // 表格列拖拽方法
  const onColumnDragStart = (event: DragEvent, index: number) => {
    draggingColumnIndex.value = index;
    if (event.dataTransfer) {
      event.dataTransfer.effectAllowed = 'move';
    }
  }

  const onColumnDragOver = (event: DragEvent, index: number) => {
    event.preventDefault();
    if (event.dataTransfer) {
      event.dataTransfer.dropEffect = 'move';
    }
  }

  const onColumnDrop = (event: DragEvent, targetIndex: number) => {
    event.preventDefault();

    if (draggingColumnIndex.value === -1 || draggingColumnIndex.value === targetIndex) {
      return;
    }

    if (selectedElement.value && selectedElement.value.type === 'data-table') {
      const columns = selectedElement.value.tableConfig.columns;

      // 移动列
      const draggedColumn = columns[draggingColumnIndex.value];
      columns.splice(draggingColumnIndex.value, 1);
      columns.splice(targetIndex, 0, draggedColumn);

      updateElement();
      statusMessage.value = '列顺序已更新';
    }

    draggingColumnIndex.value = -1;
  }

  // 统计组件相关方法
  // 验证统计字段是否为数字
  const validateStatisticsField = () => {
    if (!selectedElement.value.dataField) return;

    const field = selectedElement.value.dataField;
    const sampleValue = data.value[0]?.[field];

    if (sampleValue !== undefined && isNaN(Number(sampleValue))) {
      ElMessage.error(`字段 "${field}" 的值不是数字,无法进行统计`);
      selectedElement.value.dataField = '';
      return;
    }

    updateElement();
  }

  // 获取统计内容
  const getStatisticsContent = (element: any) => {
    if (!element.dataField) return '请配置统计字段';

    let targetData = data.value;

    // 如果有关联分组,只统计当前分组的数据
    if (element.groupId) {
      const groupMarker = reportElements.find(el => el.id === element.groupId);
      if (groupMarker && groupMarker.groupField) {
        // 获取分组数据
        const groupedData = getGroupedData(groupMarker);
        if (groupedData.length > 0) {
          // 在预览时,使用第一组数据来显示效果
          targetData = groupedData[0].data;
        } else {
          return '无分组数据';
        }
      }
    }

    const values = targetData.map(item => Number(item[element.dataField])).filter(val => !isNaN(val));

    if (values.length === 0) return '无有效数据';

    let result;
    switch (element.statisticsType) {
      case 'sum':
        result = values.reduce((a, b) => a + b, 0);
        break;
      case 'average':
        result = (values.reduce((a, b) => a + b, 0) / values.length).toFixed(2);
        break;
      case 'max':
        result = Math.max(...values);
        break;
      case 'min':
        result = Math.min(...values);
        break;
      case 'count':
        result = values.length;
        break;
      default:
        return '';
    }

    return `${result}`;
  }

  // 数字转大写汉字金额
  const numberToChineseAmount = (num: number): string => {
    const digits = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];
    const units = ['', '拾', '佰', '仟'];
    const bigUnits = ['', '万', '亿'];

    // 处理小数部分
    let integerPart = Math.floor(num);
    let decimalPart = Math.round((num - integerPart) * 100);

    // 处理整数部分
    let integerStr = '';
    if (integerPart === 0) {
      integerStr = '零';
    } else {
      let tempStr = '';
      let unitIndex = 0;
      let bigUnitIndex = 0;

      while (integerPart > 0) {
        const digit = integerPart % 10;
        if (digit === 0) {
          if (tempStr && !tempStr.startsWith('零')) {
            tempStr = digits[0] + tempStr;
          }
        } else {
          tempStr = digits[digit] + units[unitIndex] + tempStr;
        }

        integerPart = Math.floor(integerPart / 10);
        unitIndex++;

        if (unitIndex === 4) {
          if (tempStr.startsWith('零')) {
            tempStr = tempStr.substring(1);
          }
          integerStr = tempStr + bigUnits[bigUnitIndex] + integerStr;
          tempStr = '';
          unitIndex = 0;
          bigUnitIndex++;
        }
      }

      if (tempStr) {
        integerStr = tempStr + bigUnits[bigUnitIndex] + integerStr;
      }
    }

    // 处理小数部分(角和分)
    let decimalStr = '';
    if (decimalPart > 0) {
      const jiao = Math.floor(decimalPart / 10);
      const fen = decimalPart % 10;

      if (jiao > 0) {
        decimalStr += digits[jiao] + '角';
      }
      if (fen > 0) {
        decimalStr += digits[fen] + '分';
      }
    } else {
      decimalStr = '整';
    }

    return integerStr + '元' + decimalStr;
  }

  // 根据类型格式化值
  const formatValueByType = (value: any, formatType: string, options: any = {}) => {
    if (value === undefined || value === null) return '';

    switch (formatType) {
      case 'number':
        const decimalPlaces = options.decimalPlaces !== undefined ? options.decimalPlaces : 2;
        const numValue = Number(value);
        return isNaN(numValue) ? value : numValue.toFixed(decimalPlaces);

      case 'currency':
        let currencyValue = Number(value);
        if (isNaN(currencyValue)) return value;

        // 大写汉字金额
        if (options.useChineseAmount) {
          return numberToChineseAmount(currencyValue);
        }

        // 千分位分隔
        if (options.useThousandSeparator) {
          return `¥${currencyValue.toLocaleString()}`;
        }

        return `¥${currencyValue}`;

      case 'date':
        // 日期格式化 - 使用自定义格式
        const date = new Date(value);
        if (isNaN(date.getTime())) return value;

        let format = options.dateFormat || 'yyyy-MM-dd';

        // 替换格式字符串
        return format
          .replace('yyyy', date.getFullYear().toString())
          .replace('MM', (date.getMonth() + 1).toString().padStart(2, '0'))
          .replace('dd', date.getDate().toString().padStart(2, '0'))
          .replace('HH', date.getHours().toString().padStart(2, '0'))
          .replace('mm', date.getMinutes().toString().padStart(2, '0'))
          .replace('ss', date.getSeconds().toString().padStart(2, '0'));

      default:
        return value;
    }
  }

  // 格式化表格单元格内容
  const formatTableCell = (value, column) => {
    if (value === undefined || value === null) return '';

    // 如果列有格式设置,按照列格式格式化
    if (column.formatType) {
      return formatValueByType(value, column.formatType, column);
    }

    return value;
  }

  const saveReport = () => {
    console.log(reportElements)
    console.log(data.value)
    statusMessage.value = '报表已保存'
  }

  const printReport = async () => {
    try {
      statusMessage.value = '正在生成打印内容...';
      const paperContent = await generatePrintContent();
      printHtml.value = paperContent;
      console.log(reportElements);
      console.log(paperContent);
      statusMessage.value = '正在打印报表...';
      printing.value = true;
    } catch (error) {
      console.error('生成打印内容失败:', error);
      statusMessage.value = '生成打印内容失败';
    }
  }

  // 新增:带参数的打印函数
  const printReportByParam = async (customData, customElements, options = {}) => {
    try {
      // 保存当前状态
      const originalData = [...data.value];
      const originalElements = [...reportElements];

      // 使用传入的参数
      data.value = customData || data.value;

      if (customElements && Array.isArray(customElements)) {
        // 清空现有元素
        reportElements.length = 0;

        // 如果有纸张配置元素,优先添加
        const paperConfigElement = customElements.find(el => el.type === 'paper-config');
        if (paperConfigElement) {
          reportElements.push(paperConfigElement);

          // 更新纸张大小
          if (paperConfigElement.paperSize) {
            paperSize.width = paperConfigElement.paperSize.width;
            paperSize.height = paperConfigElement.paperSize.height;
          }
        } else {
          // 如果没有纸张配置,添加一个默认的
          reportElements.unshift({
            id: 0,
            name: "纸张配置",
            type: "paper-config",
            paperSize: {
              width: paperSize.width,
              height: paperSize.height,
              name: 'A4'
            }
          });
        }

        // 添加其他元素
        const otherElements = customElements.filter(el => el.type !== 'paper-config');
        reportElements.push(...otherElements);
      }

      // 设置状态消息
      statusMessage.value = '正在生成打印内容...';

      // 生成打印内容
      const paperContent = await generatePrintContent();
      printHtml.value = paperContent;

      // 可选:打印前预览
      if (options.showPreview) {
        previewHtml.value = paperContent;
        previewDialogVisible.value = true;
        statusMessage.value = '打印预览已打开';
      } else {
        // 直接打印
        statusMessage.value = '正在打印报表...';
        printing.value = true;
      }

      // 打印完成后恢复原始数据
      const restoreData = () => {
        data.value = originalData;
        reportElements.length = 0;
        reportElements.push(...originalElements);
      };

      // 监听打印完成事件
      if (options.autoRestore !== false) {
        // 通过监听 printing 值变化来恢复数据
        watchOnce(printing, (newVal) => {
          if (!newVal) {
            restoreData();
            statusMessage.value = '打印完成,数据已恢复';
          }
        });
      }

      return paperContent;

    } catch (error) {
      console.error('生成打印内容失败:', error);
      statusMessage.value = '生成打印内容失败';
      ElMessage.error('打印失败: ' + error.message);

      // 恢复原始数据
      data.value = originalData;
      reportElements.length = 0;
      reportElements.push(...originalElements);

      throw error;
    }
  }

  // 生成分页打印内容
  const generatePrintContent = async () => {
    let content = '';

    // 首先处理独立元素(没有分组的元素)
    const independentElements = reportElements.filter(el => !el.groupId && el.type !== 'group-marker');

    // 处理所有二维码、字段二维码、图片和字段图片元素
    const specialElements = independentElements.filter(el =>
      el.type === 'qr-code' ||
      el.type === 'field-qrcode'
    );

    const specialElementPromises = specialElements.map(async (element) => {
      const elementDiv = document.createElement('div');
      elementDiv.className = 'print-element';
      elementDiv.style.cssText = `
      position: absolute;
      left: ${element.x}px;
      top: ${element.y}px;
      width: ${element.width}px;
      height: ${element.height}px;
      z-index: ${element.id};
    `;

      if (element.type === 'qr-code') {
        elementDiv.innerHTML = await generateQRCodeHTML(element);
      } else if (element.type === 'field-qrcode') {
        const qrContent = getFieldQRCodeContent(element);
        if (qrContent) {
          elementDiv.innerHTML = await generateQRCodeHTML(element, qrContent);
        } else {
          elementDiv.innerHTML = '<div style="color: #999; text-align: center; padding: 10px;">无数据</div>';
        }
      }

      return elementDiv.outerHTML;
    });

    // 等待所有特殊元素生成完成
    const specialElementContents = await Promise.all(specialElementPromises);
    content += specialElementContents.join('');

    // 处理其他非特殊独立元素
    const otherElements = independentElements.filter(el =>
      el.type !== 'qr-code' &&
      el.type !== 'field-qrcode'
    );
    otherElements.forEach(element => {
      const elementDiv = document.createElement('div');
      elementDiv.className = 'print-element';
      elementDiv.style.cssText = `
      position: absolute;
      left: ${element.x}px;
      top: ${element.y}px;
      width: ${element.width}px;
      height: ${element.height}px;
      font-family: ${element.fontFamily};
      font-size: ${element.fontSize}px;
      font-weight: ${element.bold ? 'bold' : 'normal'};
      font-style: ${element.italic ? 'italic' : 'normal'};
      text-decoration: ${element.underline ? 'underline' : 'none'};
      text-align: ${element.textAlign};
      color: ${element.color || '#000000'};
      z-index: ${element.id};
    `;

      if (element.type === 'data-table') {
        const tableData = getTableData(element);
        elementDiv.innerHTML = generateTableHTML(element, tableData);
      } else if (element.type === 'field-text') {
        // 应用格式设置
        elementDiv.textContent = getFormattedTextForPrint(element);
      } else if (element.type === 'static-text') {
        // 应用格式设置
        elementDiv.textContent = getFormattedTextForPrint(element);
      } else if (element.type === 'line') {
        elementDiv.innerHTML = generateLineHTML(element);
      } else if (element.type === 'rectangle') {
        elementDiv.innerHTML = generateRectangleHTML(element);
      } else if (element.type === 'image') {
        if (element.imageUrl || element.imageBase64) {
          elementDiv.innerHTML = `<img src="${element.imageBase64 || element.imageUrl}" style="width: 100%; height: 100%; object-fit: ${element.objectFit || 'contain'};" />`;
        } else {
          elementDiv.innerHTML = '<div style="color: #999; text-align: center; padding: 10px;">无图片</div>';
        }
      } else if (element.type === 'field-image') {
        const imageContent = getFieldImageContent(element);
        if (imageContent) {
          elementDiv.innerHTML = `<img src="${imageContent}" style="width: 100%; height: 100%; object-fit: ${element.objectFit || 'contain'};" />`;
        } else {
          elementDiv.innerHTML = '<div style="color: #999; text-align: center; padding: 10px;">无数据</div>';
        }
      } else if (element.type === 'statistics') {
        // 独立统计组件 - 统计所有数据
        const statisticsData = getIndependentStatisticsData(element);
        elementDiv.innerHTML = generateStatisticsHTML(element,statisticsData);
      }else {
        elementDiv.textContent = element.content || '';
      }

      content += elementDiv.outerHTML;
    });

    // 处理分组标记
    const groupMarkers = reportElements.filter(el => el.type === 'group-marker');

    for (const groupMarker of groupMarkers) {
      const groupedData = getGroupedData(groupMarker);
      const groupsPerPage = groupMarker.groupsPerPage || 1;
      const totalPages = Math.ceil(groupedData.length / groupsPerPage);

      for (let page = 0; page < totalPages; page++) {
        const startIndex = page * groupsPerPage;
        const endIndex = Math.min(startIndex + groupsPerPage, groupedData.length);
        const pageGroups = groupedData.slice(startIndex, endIndex);

        for (const group of pageGroups) {
          const groupElements = reportElements.filter(el => el.groupId === groupMarker.id);
          const groupDiv = document.createElement('div');
          groupDiv.className = 'print-group';
          groupDiv.style.cssText = `
          position: relative;
          width: ${groupMarker.width}px;
          height: ${groupMarker.height}px;
          margin-bottom: 20px;
          page-break-inside: avoid;
        `;

          // 处理分组中的特殊元素(二维码、图片等)
          const groupSpecialElements = groupElements.filter(el =>
            el.type === 'qr-code' ||
            el.type === 'field-qrcode'
          );

          const groupSpecialPromises = groupSpecialElements.map(async (child) => {
            const childDiv = document.createElement('div');
            childDiv.className = 'print-child';
            childDiv.style.cssText = `
            position: absolute;
            left: ${child.x}px;
            top: ${child.y}px;
            width: ${child.width}px;
            height: ${child.height}px;
            z-index: ${child.id};
          `;

            if (child.type === 'qr-code') {
              childDiv.innerHTML = await generateQRCodeHTML(child);
            } else if (child.type === 'field-qrcode') {
              const fieldData = getGroupFieldData(child, group);
              const qrContent = fieldData[child.dataField] || '';
              if (qrContent) {
                childDiv.innerHTML = await generateQRCodeHTML(child, qrContent);
              } else {
                childDiv.innerHTML = '<div style="color: #999; text-align: center; padding: 10px;">无数据</div>';
              }
            }

            return childDiv.outerHTML;
          });

          const groupSpecialContents = await Promise.all(groupSpecialPromises);
          groupDiv.innerHTML = groupSpecialContents.join('');

          // 处理分组中的其他元素
          const otherGroupElements = groupElements.filter(el =>
            el.type !== 'qr-code' &&
            el.type !== 'field-qrcode'
          );

          otherGroupElements.forEach(child => {
            const childDiv = document.createElement('div');
            childDiv.className = 'print-child';
            childDiv.style.cssText = `
            position: absolute;
            left: ${child.x}px;
            top: ${child.y}px;
            width: ${child.width}px;
            height: ${child.height}px;
            font-family: ${child.fontFamily};
            font-size: ${child.fontSize}px;
            font-weight: ${child.bold ? 'bold' : 'normal'};
            font-style: ${child.italic ? 'italic' : 'normal'};
            text-decoration: ${child.underline ? 'underline' : 'none'};
            text-align: ${child.textAlign};
            color: ${child.color || '#000000'};
            z-index: ${child.id};
          `;

            if (child.type === 'data-table') {
              const tableData = getGroupTableData(child, group);
              childDiv.innerHTML = generateTableHTML(child, tableData);
            } else if (child.type === 'field-text') {
              const fieldData = getGroupFieldData(child, group);
              const rawValue = fieldData[child.dataField] || '';
              // 应用格式设置
              childDiv.textContent = formatValueByType(rawValue, child.formatType, child);
            } else if (child.type === 'static-text') {
              // 应用格式设置
              childDiv.textContent = formatValueByType(child.content || '', child.formatType, child);
            } else if (child.type === 'line') {
              childDiv.innerHTML = generateLineHTML(child);
            } else if (child.type === 'rectangle') {
              childDiv.innerHTML = generateRectangleHTML(child);
            } else if (child.type === 'image') {
              if (child.imageUrl || child.imageBase64) {
                childDiv.innerHTML = `<img src="${child.imageBase64 || child.imageUrl}" style="width: 100%; height: 100%; object-fit: ${child.objectFit || 'contain'};" />`;
              } else {
                childDiv.innerHTML = '<div style="color: #999; text-align: center; padding: 10px;">无图片</div>';
              }
            } else if (child.type === 'field-image') {
              const fieldData = getGroupFieldData(child, group);
              const imageContent = fieldData[child.dataField] || '';
              if (imageContent) {
                childDiv.innerHTML = `<img src="${imageContent}" style="width: 100%; height: 100%; object-fit: ${child.objectFit || 'contain'};" />`;
              } else {
                childDiv.innerHTML = '<div style="color: #999; text-align: center; padding: 10px;">无数据</div>';
              }
            }  else if (child.type === 'statistics') {
              // 获取分组内的统计数据
              const statisticsData = getGroupStatisticsData(child, group);
              childDiv.innerHTML = generateStatisticsHTML(child, statisticsData);;
            } else {
              childDiv.textContent = child.content || '';
            }

            groupDiv.innerHTML += childDiv.outerHTML;
          });

          content += groupDiv.outerHTML;
        }

        if (page < totalPages - 1) {
          content += '<div style="page-break-after: always;"></div>';
        }
      }
    }

    return content;
  }

  // 生成统计组件的 HTML 内容(优化版)
  const generateStatisticsHTML = (element: any, statisticsData: string) => {
    const {
      textAlign = 'center',
      fontSize = 14,
      fontFamily = 'inherit',
      bold = false,
      italic = false,
      underline = false,
      color = '#000000'
    } = element;

    const styles = {
      padding: '8px',
      textAlign,
      fontSize: `${fontSize}px`,
      fontFamily,
      fontWeight: bold ? 'bold' : 'normal',
      fontStyle: italic ? 'italic' : 'normal',
      textDecoration: underline ? 'underline' : 'none',
      color,
      height: 'calc(100% - 32px)',
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'center'
    };

    const styleString = Object.entries(styles)
      .map(([key, value]) => `${key}: ${value};`)
      .join(' ');

    return `<div style="width: 100%; height: 100%; overflow: hidden;">
    <div style="${styleString}">
      ${statisticsData}
    </div>
  </div>`;
  }
  // 获取独立统计数据(无分组)
  const getIndependentStatisticsData = (element: any) => {
    if (!element.dataField) return '请配置统计字段';
    const values = data.value.map(item => Number(item[element.dataField])).filter(val => !isNaN(val));

    if (values.length === 0) return '无有效数据';

    let result;
    switch (element.statisticsType) {
      case 'sum':
        result = values.reduce((a, b) => a + b, 0);
        break;
      case 'average':
        result = (values.reduce((a, b) => a + b, 0) / values.length).toFixed(2);
        break;
      case 'max':
        result = Math.max(...values);
        break;
      case 'min':
        result = Math.min(...values);
        break;
      case 'count':
        result = values.length;
        break;
      default:
        return '';
    }
    return `${result}`;
  }

  // 获取分组统计数据
  const getGroupStatisticsData = (element: any, groupData: any) => {
    if (!element.dataField) return '请配置统计字段';

    const targetData = groupData?.data || [];

    const values = targetData.map(item => Number(item[element.dataField])).filter(val => !isNaN(val));

    if (values.length === 0) return '无有效数据';

    let result;
    switch (element.statisticsType) {
      case 'sum':
        result = values.reduce((a, b) => a + b, 0);
        break;
      case 'average':
        result = (values.reduce((a, b) => a + b, 0) / values.length).toFixed(2);
        break;
      case 'max':
        result = Math.max(...values);
        break;
      case 'min':
        result = Math.min(...values);
        break;
      case 'count':
        result = values.length;
        break;
      default:
        return '';
    }
    return `${result}`;
  }

  // 获取格式化文本内容用于打印
  const getFormattedTextForPrint = (element: any) => {
    let value = '';
    if (element.type === 'static-text') {
      value = element.content || '';
    } else if (element.type === 'field-text') {
      if (!element.dataField || data.value.length === 0) {
        value = element.content || '';
      } else {
        value = data.value[0][element.dataField] || '';
      }
    }
    if (element.formatType) {
      return formatValueByType(value, element.formatType, element);
    }
    return value;
  }

  // 生成线条HTML
  const generateLineHTML = (element: any) => {
    let borderStyle = '';
    let transform = '';
    let actualWidth = element.width;
    let actualHeight = element.height;

    switch (element.lineDirection) {
      case 'horizontal':
        borderStyle = `border-top: ${element.lineWidth || 1}px ${element.lineStyle || 'solid'} ${element.lineColor || '#000000'}`;
        actualHeight = element.lineWidth || 1;
        break;
      case 'vertical':
        borderStyle = `border-left: ${element.lineWidth || 1}px ${element.lineStyle || 'solid'} ${element.lineColor || '#000000'}`;
        actualWidth = element.lineWidth || 1;
        break;
      case 'diagonal':
        // 计算对角线长度 - 勾股定理
        const diagonalLength = Math.sqrt(element.width * element.width + element.height * element.height);
        borderStyle = `border-top: ${element.lineWidth || 1}px ${element.lineStyle || 'solid'} ${element.lineColor || '#000000'}`;
        transform = `transform: rotate(${Math.atan2(element.height, element.width) * 180 / Math.PI}deg);`;
        actualWidth = diagonalLength;
        actualHeight = element.lineWidth || 1;
        break;
      default:
        borderStyle = `border-top: ${element.lineWidth || 1}px ${element.lineStyle || 'solid'} ${element.lineColor || '#000000'}`;
        actualHeight = element.lineWidth || 1;
    }

    return `<div style="width: ${actualWidth}px; height: ${actualHeight}px; ${borderStyle}; ${transform}"></div>`;
  }

  // 生成矩形HTML
  const generateRectangleHTML = (element: any) => {
    const rectangleStyle = `
      width: 100%;
      height: 100%;
      background-color: ${element.backgroundColor || 'rgba(255, 255, 255, 0)'};
      border: ${element.borderWidth || 1}px ${element.borderStyle || 'solid'} ${element.borderColor || '#000000'};
    `;

    return `<div style="${rectangleStyle}"></div>`;
  }

  // 生成二维码HTML
  const generateQRCodeHTML = async (element: any, content?: string) => {
    const qrContent = content || element.content
    if (!qrContent) {
      return '<div style="color: #999; text-align: center; padding: 10px;">请输入二维码内容</div>'
    }

    try {
      const svgString = await QRCode.toString(qrContent, {
        type: 'svg',
        width: element.width,
        margin: 0,
        color: {
          dark: '#000000',
          light: '#FFFFFF'
        }
      })
      return svgString
    } catch (error) {
      console.error('生成二维码失败:', error)
      return `<div style="color: #ff0000; text-align: center; padding: 10px;">生成二维码失败</div>`
    }
  }

  // 生成表格HTML
  const generateTableHTML = (element: any, tableData: any[]) => {
    if (!element.tableConfig) return '';

    let html = '<table style="width: 100%; border-collapse: collapse;">';

    // 表头
    html += '<thead><tr>';
    element.tableConfig.columns.forEach((column: any) => {
      const headerStyle = `
      border: 1px solid #ddd;
      padding: 4px 8px;
      background-color: #f5f5f5;
      font-family: ${element.headerFontFamily || 'inherit'};
      font-size: ${element.headerFontSize || 12}px;
      font-weight: ${element.headerBold ? 'bold' : 'normal'};
      font-style: ${element.headerItalic ? 'italic' : 'normal'};
      text-decoration: ${element.headerUnderline ? 'underline' : 'none'};
      text-align: ${element.headerTextAlign || 'center'};
      color: ${element.headerColor || '#000000'};
    `;
      html += `<th style="${headerStyle}">${column.title}</th>`;
    });
    html += '</tr></thead>';

    // 表格内容
    html += '<tbody>';
    tableData.forEach((row, index) => {
      html += '<tr>';
      element.tableConfig.columns.forEach((column: any) => {
        const rawValue = row[column.field] || '';
        // 应用列格式设置
        const formattedValue = column.formatType ?
          formatValueByType(rawValue, column.formatType, column) :
          rawValue;

        const cellStyle = `
        border: 1px solid #ddd;
        padding: 4px 8px;
        font-family: ${column.fontFamily || 'inherit'};
        font-size: ${column.fontSize || 12}px;
        font-weight: ${column.bold ? 'bold' : 'normal'};
        font-style: ${column.italic ? 'italic' : 'normal'};
        text-decoration: ${column.underline ? 'underline' : 'none'};
        text-align: ${column.textAlign || 'left'};
        color: ${column.color || '#000000'};
      `;
        html += `<td style="${cellStyle}">${formattedValue}</td>`;
      });
      html += '</tr>';
    });
    html += '</tbody></table>';

    return html;
  }

  // 获取分组数据
  const getGroupedData = (groupMarker: any) => {
    if (!groupMarker.groupField) return [];

    const groups: any = {};
    data.value.forEach(item => {
      const groupValue = item[groupMarker.groupField];
      if (!groups[groupValue]) {
        groups[groupValue] = {
          groupValue: groupValue,
          data: []
        };
      }
      groups[groupValue].data.push(item);
    });

    return Object.values(groups);
  }

  // 获取分组表格数据
  const getGroupTableData = (tableElement: any, groupData: any) => {
    if (!tableElement.tableConfig || !tableElement.tableConfig.columns) return [];

    return groupData.data.map((item: any) => {
      const row: any = {};
      tableElement.tableConfig.columns.forEach((column: any) => {
        if (column.field && item.hasOwnProperty(column.field)) {
          row[column.field] = item[column.field];
        } else {
          row[column.field] = '';
        }
      });
      return row;
    });
  }

  // 获取分组字段数据
  const getGroupFieldData = (fieldElement: any, groupData: any) => {
    return groupData?.data?.[0] || {};
  }

  // 获取表格数据(独立表格)
  const getTableData = (element: any) => {
    if (!element.tableConfig || !element.tableConfig.columns) return [];

    // 如果表格关联了分组,只显示对应分组的第一组数据
    if (element.groupId) {
      const groupMarker = reportElements.find(el => el.id === element.groupId);
      if (groupMarker && groupMarker.groupField) {
        // 获取分组数据
        const groupedData = getGroupedData(groupMarker);
        if (groupedData.length > 0) {
          // 返回第一组的数据
          return groupedData[0].data.map((item: any) => {
            const row: any = {};
            element.tableConfig.columns.forEach((column: any) => {
              if (column.field && item.hasOwnProperty(column.field)) {
                row[column.field] = item[column.field];
              } else {
                row[column.field] = '';
              }
            });
            return row;
          });
        }
      }
    }

    // 独立表格显示所有数据
    return data.value.map(item => {
      const row: any = {};
      element.tableConfig.columns.forEach((column: any) => {
        if (column.field && item.hasOwnProperty(column.field)) {
          row[column.field] = item[column.field];
        } else {
          row[column.field] = '';
        }
      });
      return row;
    });
  }

  // 获取字段文本框内容
  const getFieldTextContent = (element: any) => {
    if (element.type === 'static-text') {
      const value = element.content || '';
      if (element.formatType) {
        return formatValueByType(value, element.formatType, element);
      }
      return value;
    } else {
      if (!element.dataField || data.value.length === 0) {
        return element.content || '';
      }
      const value = data.value[0][element.dataField] || '';
      if (element.formatType) {
        return formatValueByType(value, element.formatType, element);
      }
      return value;
    }
  }

  // 获取格式化文本内容(响应式)
  const getFormattedTextContent = (element: any) => {
    let value = '';
    if (element.type === 'static-text') {
      value = element.content || '';
    } else if (element.type === 'field-text') {
      if (!element.dataField || data.value.length === 0) {
        value = element.content || '';
      } else {
        value = data.value[0][element.dataField] || '';
      }
    }
    if (element.formatType) {
      return formatValueByType(value, element.formatType, element);
    }
    return value;
  };

  // 获取字段预览值
  const getFieldPreview = (field: string) => {
    if (data.value.length === 0) return '';
    return data.value[0][field] || '';
  }

  const toggleZoomMode = () => {
    isZoomMode.value = !isZoomMode.value
    statusMessage.value = isZoomMode.value ? '缩放模式已启用' : '缩放模式已关闭'
  }

  const deleteSelectedElement = () => {
    if (selectedElement.value) {
      deleteElement(selectedElement.value)
    }
  }

  const selectElement = (element: any) => {
    selectedElement.value = element
    statusMessage.value = `已选择: ${element.name}`
    hideContextMenu()
  }

  const clearSelection = () => {
    selectedElement.value = null
    statusMessage.value = '就绪'
    hideContextMenu()
  }

  const updateElement = () => {
    statusMessage.value = `已更新: ${selectedElement.value?.name}`
  }

  // 获取元素样式
  const getElementStyle = (element: any) => {
    const baseStyle = {
      top: element.y + 'px',
      left: element.x + 'px',
      width: element.width + 'px',
      height: element.height + 'px',
      fontSize: (element.fontSize || 12) + 'px',
      fontFamily: element.fontFamily || '微软雅黑',
      fontWeight: element.bold ? 'bold' : 'normal',
      fontStyle: element.italic ? 'italic' : 'normal',
      textDecoration: element.underline ? 'underline' : 'none',
      textAlign: element.textAlign || 'left',
      borderStyle: element.borderStyle,
      zIndex: element.id
    };

    // 分组标记特殊样式
    if (element.type === 'group-marker') {
      baseStyle.backgroundColor = 'rgba(52, 152, 219, 0.05)';
    }

    // 线条和矩形不需要背景色
    if (element.type === 'line' || element.type === 'rectangle') {
      baseStyle.backgroundColor = 'transparent';
    }

    return baseStyle;
  }

  // 获取线条样式
  const getLineStyle = (element: any) => {
    let borderStyle = '';
    let transform = '';
    let actualWidth = element.width;
    let actualHeight = element.height;

    switch (element.lineDirection) {
      case 'horizontal':
        borderStyle = `border-top: ${element.lineWidth || 1}px ${element.lineStyle || 'solid'} ${element.lineColor || '#000000'}`;
        actualHeight = element.lineWidth || 1;
        break;
      case 'vertical':
        borderStyle = `border-left: ${element.lineWidth || 1}px ${element.lineStyle || 'solid'} ${element.lineColor || '#000000'}`;
        actualWidth = element.lineWidth || 1;
        break;
      case 'diagonal':
        // 计算对角线长度 - 勾股定理
        const diagonalLength = Math.sqrt(element.width * element.width + element.height * element.height);
        borderStyle = `border-top: ${element.lineWidth || 1}px ${element.lineStyle || 'solid'} ${element.lineColor || '#000000'}`;
        transform = `transform: rotate(${Math.atan2(element.height, element.width) * 180 / Math.PI}deg);`;
        actualWidth = diagonalLength;
        actualHeight = element.lineWidth || 1;
        break;
      default:
        borderStyle = `border-top: ${element.lineWidth || 1}px ${element.lineStyle || 'solid'} ${element.lineColor || '#000000'}`;
        actualHeight = element.lineWidth || 1;
    }

    return `
      width: ${actualWidth}px;
      height: ${actualHeight}px;
      ${borderStyle};
      ${transform}
    `;
  }

  // 获取表头样式
  const getTableHeaderStyle = (element: any, column: any) => {
    return {
      'font-family': element.headerFontFamily || 'inherit',
      'font-size': (element.headerFontSize || 12) + 'px',
      'font-weight': element.headerBold ? 'bold' : 'normal',
      'font-style': element.headerItalic ? 'italic' : 'normal',
      'text-decoration': element.headerUnderline ? 'underline' : 'none',
      'text-align': element.headerTextAlign || 'center',
      'color': element.headerColor || '#000000'
    };
  }

  // 获取表格单元格样式
  const getTableCellStyle = (column: any) => {
    return {
      'font-family': column.fontFamily || 'inherit',
      'font-size': (column.fontSize || 12) + 'px',
      'font-weight': column.bold ? 'bold' : 'normal',
      'font-style': column.italic ? 'italic' : 'normal',
      'text-decoration': column.underline ? 'underline' : 'none',
      'text-align': column.textAlign || 'left',
      'color': column.color || '#000000'
    };
  }

  // 获取矩形样式
  const getRectangleStyle = (element: any) => {
    return `
      width: 100%;
      height: 100%;
      background-color: ${element.backgroundColor || 'rgba(255, 255, 255, 0)'};
      border: ${element.borderWidth || 1}px ${element.borderStyle || 'solid'} ${element.borderColor || '#000000'};
    `;
  }

  // 获取字段图片内容
  const getFieldImageContent = (element: any) => {
    if (!element.dataField) return '';
    if (element.groupId) {
      const groupMarker = reportElements.find(el => el.id === element.groupId);
      if (groupMarker && groupMarker.groupField) {
        return data.value[0]?.[element.dataField] || '';
      }
    }
    return data.value[0]?.[element.dataField] || '';
  };

  // 图片上传处理
  const beforeImageUpload = (file: File) => {
    const isImage = file.type.startsWith('image/')
    const isLt2M = file.size / 1024 / 1024 < 2

    if (!isImage) {
      ElMessage.error('只能上传图片文件!')
      return false
    }
    if (!isLt2M) {
      ElMessage.error('图片大小不能超过 2MB!')
      return false
    }
    return true
  }

  const handleImageUpload = (options: any) => {
    const file = options.file
    const reader = new FileReader()

    reader.onload = (e) => {
      if (e.target && e.target.result && selectedElement.value) {
        // 保存为 base64
        selectedElement.value.imageBase64 = e.target.result as string
        // 清空 URL,优先使用 base64
        selectedElement.value.imageUrl = ''
        updateElement()
        ElMessage.success('图片上传成功!')
      }
    }

    reader.onerror = () => {
      ElMessage.error('图片读取失败!')
    }

    reader.readAsDataURL(file)
  }

  // 表格列操作
  const addColumn = () => {
    if (selectedElement.value && selectedElement.value.type === 'data-table') {
      if (!selectedElement.value.tableConfig) {
        selectedElement.value.tableConfig = { columns: [] };
      }
      selectedElement.value.tableConfig.columns.push({
        title: '新列',
        field: '',
        fontFamily: '',
        color: ''
      });
      updateElement();
    }
  }

  const removeColumn = (index: number) => {
    if (selectedElement.value && selectedElement.value.type === 'data-table') {
      selectedElement.value.tableConfig.columns.splice(index, 1);
      updateElement();
    }
  }

  // 拖拽相关方法
  const onDragStart = (event: DragEvent, component: any) => {
    if (event.dataTransfer) {
      event.dataTransfer.setData('text/plain', JSON.stringify(component))
    }
  }

  const onDrop = (event: DragEvent) => {
    if (!event.dataTransfer) return

    try {
      const componentData = JSON.parse(event.dataTransfer.getData('text/plain'))
      const paperRect = paperRef.value?.getBoundingClientRect()

      if (paperRect) {
        const x = (event.clientX - paperRect.left - 20) / zoom.value
        const y = (event.clientY - paperRect.top - 10) / zoom.value

        const baseElement = {
          id: Date.now(),
          name: componentData.name,
          content: componentData.name,
          type: componentData.type,
          x: Math.max(0, x),
          y: Math.max(0, y),
          width: 120,
          height: 40,
          fontFamily: '微软雅黑',
          fontSize: 12,
          bold: false,
          italic: false,
          underline: false,
          textAlign: 'left',
          borderStyle: 'none',
          groupId: null
        }
        if (componentData.type === 'image') {
          baseElement.width = 150
          baseElement.height = 100
          baseElement.imageUrl = ''
          baseElement.imageBase64 = ''
          baseElement.objectFit = 'contain'
        } else if (componentData.type === 'field-image') {
          baseElement.width = 150
          baseElement.height = 100
          baseElement.dataField = ''
          baseElement.objectFit = 'contain'
        } else if (componentData.type === 'group-marker') {
          baseElement.width = 200
          baseElement.height = 60
          baseElement.borderStyle = 'dashed'
          baseElement.groupField = ''
          baseElement.groupsPerPage = 1
        } else if (componentData.type === 'data-table') {
          baseElement.tableConfig = {
            columns: [
              {
                title: '列1',
                field: '',
                fontFamily: '宋体',
                fontSize: 12,
                bold: false,
                italic: false,
                underline: false,
                textAlign: 'left',
                color: '#000000',
                formatType: 'text',
                decimalPlaces: 2,
                useChineseAmount: false,
                useThousandSeparator: false,
                dateFormat: 'yyyy-MM-dd'
              },
              {
                title: '列2',
                field: '',
                fontFamily: '宋体',
                fontSize: 12,
                bold: false,
                italic: false,
                underline: false,
                textAlign: 'left',
                color: '#000000',
                formatType: 'text',
                decimalPlaces: 2,
                useChineseAmount: false,
                useThousandSeparator: false,
                dateFormat: 'yyyy-MM-dd'
              }
            ]
          };
          baseElement.width = 400
          baseElement.height = 150
          baseElement.headerFontFamily = '微软雅黑'
          baseElement.headerFontSize = 12
          baseElement.headerBold = true
          baseElement.headerItalic = false
          baseElement.headerUnderline = false
          baseElement.headerTextAlign = 'center'
          baseElement.headerColor = '#000000'
        } else if (componentData.type === 'field-text') {
          baseElement.dataField = ''
          baseElement.content = ''
          baseElement.formatType = 'text'
          baseElement.color = '#000000'
          baseElement.decimalPlaces = 2
          baseElement.useChineseAmount = false
          baseElement.useThousandSeparator = false
        } else if (componentData.type === 'static-text') {
          baseElement.content = '静态文本'
          baseElement.formatType = 'text'
          baseElement.color = '#000000'
          baseElement.decimalPlaces = 2
          baseElement.useChineseAmount = false
          baseElement.useThousandSeparator = false
          baseElement.dateFormat = 'yyyy-MM-dd' // 添加默认日期格式
        } else if (componentData.type === 'qr-code') {
          baseElement.width = 120
          baseElement.height = 120
          baseElement.content = '二维码'
        } else if (componentData.type === 'field-qrcode') {
          baseElement.dataField = ''
          baseElement.width = 120
          baseElement.height = 120
          baseElement.content = ''
        } else if (componentData.type === 'line') {
          baseElement.width = 200
          baseElement.height = 2
          baseElement.lineColor = '#000000'
          baseElement.lineStyle = 'solid'
          baseElement.lineWidth = 1
          baseElement.lineDirection = 'horizontal'
        } else if (componentData.type === 'rectangle') {
          baseElement.width = 150
          baseElement.height = 100
          baseElement.backgroundColor = 'rgba(255, 255, 255, 0)' // 默认透明背景
          baseElement.borderColor = '#000000'
          baseElement.borderStyle = 'solid'
          baseElement.borderWidth = 1
        } else if (componentData.type === 'statistics') {
          baseElement.width = 90
          baseElement.height = 20
          baseElement.dataField = ''
          baseElement.statisticsType = 'sum'
          baseElement.color = '#000000'
        }

        reportElements.push(baseElement)
        selectedElement.value = baseElement
        statusMessage.value = `已添加: ${componentData.name}`
      }
    } catch (error) {
      console.error('拖拽数据解析错误:', error)
      statusMessage.value = '拖拽失败: 数据格式错误'
    }
  }

  // 获取字段二维码内容
  const getFieldQRCodeContent = (element: any) => {
    if (!element.dataField) return '';
    if (element.groupId) {
      const groupMarker = reportElements.find(el => el.id === element.groupId);
      if (groupMarker && groupMarker.groupField) {
        return data.value[0]?.[element.dataField] || '';
      }
    }
    return data.value[0]?.[element.dataField] || '';
  };

  // 其他所有方法保持不变...
  const startDrag = (event: MouseEvent, element: any) => {
    if ((event.target as HTMLElement).classList.contains('element-handle')) {
      return
    }

    isDragging = true
    selectedElement.value = element
    dragOffsetX = event.offsetX
    dragOffsetY = event.offsetY

    document.addEventListener('mousemove', onMouseMove)
    document.addEventListener('mouseup', onMouseUp)
  }

  const onMouseMove = (event: MouseEvent) => {
    if (isDragging && selectedElement.value) {
      const paperRect = paperRef.value?.getBoundingClientRect()
      if (paperRect) {
        const x = (event.clientX - paperRect.left - dragOffsetX) / zoom.value
        const y = (event.clientY - paperRect.top - dragOffsetY) / zoom.value

        if (x >= 0 && y >= 0 &&
          x + selectedElement.value.width <= paperRect.width / zoom.value &&
          y + selectedElement.value.height <= paperRect.height / zoom.value) {
          selectedElement.value.x = Math.round(x)
          selectedElement.value.y = Math.round(y)
        }
      }
    }

    if (isResizing && resizingElement.value) {
      const paperRect = paperRef.value?.getBoundingClientRect()
      if (paperRect) {
        const mouseX = (event.clientX - paperRect.left) / zoom.value
        const mouseY = (event.clientY - paperRect.top) / zoom.value

        let newWidth = startWidth
        let newHeight = startHeight
        let newX = startX
        let newY = startY

        const deltaX = mouseX - startMouseX
        const deltaY = mouseY - startMouseY

        switch (resizeDirection) {
          case 'se':
            newWidth = Math.max(20, startWidth + deltaX)
            newHeight = Math.max(20, startHeight + deltaY)
            break
          case 'sw':
            newWidth = Math.max(20, startWidth - deltaX)
            newHeight = Math.max(20, startHeight + deltaY)
            newX = startX + deltaX
            break
          case 'ne':
            newWidth = Math.max(20, startWidth + deltaX)
            newHeight = Math.max(20, startHeight - deltaY)
            newY = startY + deltaY
            break
          case 'nw':
            newWidth = Math.max(20, startWidth - deltaX)
            newHeight = Math.max(20, startHeight - deltaY)
            newX = startX + deltaX
            newY = startY + deltaY
            break
        }

        if (newX >= 0 && newY >= 0 &&
          newX + newWidth <= paperRect.width / zoom.value &&
          newY + newHeight <= paperRect.height / zoom.value) {
          resizingElement.value.width = Math.round(newWidth)
          resizingElement.value.height = Math.round(newHeight)
          resizingElement.value.x = Math.round(newX)
          resizingElement.value.y = Math.round(newY)
        }
      }
    }
  }

  const onMouseUp = () => {
    isDragging = false
    isResizing = false
    resizingElement.value = null
    document.removeEventListener('mousemove', onMouseMove)
    document.removeEventListener('mouseup', onMouseUp)
  }

  const startResize = (event: MouseEvent, element: any, direction: string) => {
    isResizing = true
    resizeDirection = direction
    resizingElement.value = element
    selectedElement.value = element

    startWidth = element.width
    startHeight = element.height
    startX = element.x
    startY = element.y

    const paperRect = paperRef.value?.getBoundingClientRect()
    if (paperRect) {
      startMouseX = (event.clientX - paperRect.left) / zoom.value
      startMouseY = (event.clientY - paperRect.top) / zoom.value
    }

    document.addEventListener('mousemove', onMouseMove)
    document.addEventListener('mouseup', onMouseUp)
  }

  // 缩放功能
  const zoomIn = () => {
    zoom.value = Math.min(maxZoom, zoom.value + zoomStep)
  }

  const zoomOut = () => {
    zoom.value = Math.max(minZoom, zoom.value - zoomStep)
  }

  const resetZoom = () => {
    zoom.value = 1
  }

  const onWheel = (event: WheelEvent) => {
    if (isZoomMode.value || event.ctrlKey) {
      event.preventDefault()
      const delta = -Math.sign(event.deltaY) * zoomStep * (event.ctrlKey ? 2 : 1)
      zoom.value = Math.min(maxZoom, Math.max(minZoom, zoom.value + delta))
    }
  }

  // 右键菜单功能
  const showContextMenu = (event: MouseEvent, element: any) => {
    contextMenu.visible = true
    contextMenu.x = event.clientX
    contextMenu.y = event.clientY
    contextMenu.element = element
    selectedElement.value = element
  }

  const hideContextMenu = () => {
    contextMenu.visible = false
  }

  const deleteElement = (element: any) => {
    const index = reportElements.findIndex(el => el.id === element.id)
    if (index !== -1) {
      reportElements.splice(index, 1)
      statusMessage.value = `已删除: ${element.name}`
      if (selectedElement.value?.id === element.id) {
        selectedElement.value = null
      }
    }
    hideContextMenu()
  }

  const copyElement = (element: any) => {
    const newElement = {
      ...element,
      id: Date.now(),
      x: element.x + 20,
      y: element.y + 20
    }
    reportElements.push(newElement)
    selectedElement.value = newElement
    statusMessage.value = `已复制: ${element.name}`
    hideContextMenu()
  }

  // 响应式数据 - 在 existing refs 后面添加
  const expandedColumns = ref({});

  // 切换列样式展开状态
  const toggleColumnStyle = (index: number) => {
    expandedColumns.value[index] = !expandedColumns.value[index];
  }

  // 获取预览对话框宽度
  const getPreviewDialogWidth = () => {
    if (isPreviewFullscreen.value) {
      return '100%';
    }
    // 对话框宽度 = 内容宽度 + 一些边距(约100px)
    const contentWidth = paperSize.width + 100;
    // 限制最小和最大宽度
    return Math.min(Math.max(contentWidth, 600), window.innerWidth - 40) + 'px';
  }

  // 切换预览全屏状态
  const togglePreviewFullscreen = () => {
    isPreviewFullscreen.value = !isPreviewFullscreen.value;
  }

  // 预览功能
  const showPreviewDialog = async () => {
    try {
      statusMessage.value = '正在生成预览内容...';
      previewHtml.value = await generatePrintContent();
      isPreviewFullscreen.value = false; // 重置全屏状态
      previewDialogVisible.value = true;
      statusMessage.value = '预览已打开';
    } catch (error) {
      console.error('生成预览内容失败:', error);
      statusMessage.value = '生成预览内容失败';
      ElMessage.error('生成预览内容失败');
    }
  }

  const handlePreviewDialogClose = () => {
    previewDialogVisible.value = false;
    isPreviewFullscreen.value = false; // 关闭时重置全屏状态
  }

  // 加载报表的方法示例
  const loadReport = (savedElements) => {
    reportElements.length = 0;
    reportElements.push(...savedElements);

    // 恢复纸张大小
    const paperConfig = reportElements.find(el => el.type === 'paper-config');
    if (paperConfig && paperConfig.paperSize) {
      paperSize.width = paperConfig.paperSize.width;
      paperSize.height = paperConfig.paperSize.height;
    }

    selectedElement.value = null;
    statusMessage.value = '报表已加载';
  };

  // 添加计算属性来获取当前纸张大小
  const currentPaperSize = computed(() => {
    const config = paperConfig.value;
    return config.paperSize || { width: 794, height: 1123, name: 'A4' };
  });

  // 当选择元素改变时,重置展开状态
  watch(selectedElement, (newElement) => {
    if (newElement && newElement.type === 'data-table') {
      // 重置所有列的展开状态为 false
      expandedColumns.value = {};
    }
  });
  watch(currentPaperSize, (newSize) => {
    paperSize.width = newSize.width;
    paperSize.height = newSize.height;
  }, { immediate: true });




  onMounted(() => {
    document.addEventListener('click', hideContextMenu);
    statusMessage.value = '报表设计器已就绪';

    // 初始化纸张配置
    if (!reportElements.find(el => el.type === 'paper-config')) {
      reportElements.unshift({
        id: 0,
        name: "纸张配置",
        type: "paper-config",
        paperSize: {
          width: paperSize.width,
          height: paperSize.height,
          name: 'A4'
        }
      });
    }
  })
</script>

<style scoped>
  .tab-content :deep(.el-input-group__prepend){
    font-size: 12px;
    font-weight: bold;
    color: #0d68a6;
  }
  .property-group :deep(.el-color-picker){
    width: 100%;
  }
  :deep(.el-color-picker){
    width: 25px;
  }
  :deep(.el-color-picker__trigger){
    height: 24px;
  }
  .data-table-container :deep(.data-table th){
    text-align: inherit;
    font-family: inherit;
    font-size: inherit;
    font-weight: inherit;
    font-style: inherit;
    text-decoration: inherit;
  }
  .data-table-container :deep(.data-table td){
    text-align: inherit;
    font-family: inherit;
    font-size: inherit;
    font-weight: inherit;
    font-style: inherit;
    text-decoration: inherit;
  }

  .data-table-container :deep(.data-table){
    font-size: inherit;
  }
  /* 图片容器样式 */
  .image-container, .field-image-container {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: rgba(255, 255, 255, 0.8);
  }

  .image-placeholder {
    color: #999;
    font-style: italic;
    text-align: center;
    padding: 10px;
  }

  .image-placeholder i {
    font-size: 24px;
    display: block;
    margin-bottom: 5px;
  }

  /* 统计组件样式 */
  .statistics-container {
    width: 100%;
    height: 100%;
    overflow: hidden;
  }


  /* 列样式配置 */
  .column-style-config {
    margin-bottom: 10px;
    border: 1px solid #ddd;
    border-radius: 4px;
    overflow: hidden;
    background: rgba(255,255,255,0.5);
  }

  .column-style-header {
    padding: 12px 8px;
    background: #f8f9fa;
    border-bottom: 1px solid #e9ecef;
    cursor: pointer;
    transition: all 0.3s ease;
    user-select: none;
  }

  .column-header-content {
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-weight: bold;
  }

  .column-title {
    flex: 1;
    font-size: 14px;
  }

  .column-field {
    font-size: 12px;
    opacity: 0.7;
    margin-left: 8px;
    font-weight: normal;
  }

  .expand-icon {
    transition: transform 0.3s ease;
    font-size: 12px;
  }

  .column-style-header.expanded .expand-icon {
    transform: rotate(0deg);
  }

  .column-style-header:not(.expanded) .expand-icon {
    transform: rotate(-90deg);
  }

  .column-style-content {
    padding: 12px;
    background: white;
    border-top: 1px solid #e9ecef;
  }

  .table-top-column-style-config {
    margin-bottom: 10px;
    padding: 12px;
    background: rgba(255,255,255,0.5);
    border-radius: 4px;
    border: 1px solid #ddd;
  }

  .column-style-header:hover {
    background: #e9ecef;
  }

  .column-style-header.expanded {
    background: #3498db;
    color: white;
  }

  .column-style-header.expanded:hover {
    background: #2980b9;
  }

  /* 图片上传样式 */
  .image-upload {
    margin-bottom: 10px;
  }

  .image-preview {
    margin-top: 10px;
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 4px;
    background: #f8f9fa;
  }

  .field-qrcode-container {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .field-qrcode-placeholder {
    color: #999;
    font-style: italic;
    text-align: center;
    padding: 10px;
  }

  /* 线条容器 */
  .line-container {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .line-preview {
    width: 100%;
    height: 100%;
  }

  /* 矩形容器 */
  .rectangle-container {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .rectangle-preview {
    width: 100%;
    height: 100%;
  }

  /* 分组标记样式 */
  .group-marker-container {
    width: 100%;
    height: 100%;
  }

  .group-marker-header {
    background-color: rgba(52, 152, 219, 0.1);
    padding: 5px 8px;
    border-bottom: 1px dashed #3498db;
    font-size: 11px;
    overflow: hidden;
    color: #2c3e50;
  }

  .group-marker-title {
    font-weight: bold;
    float: left;
    margin-bottom: 2px;
    margin-right: 10px;
  }

  .group-marker-info {
    float: left;
    font-size: 10px;
    color: #7f8c8d;
    margin-right: 10px;
  }

  /* 表格列配置拖拽样式 */
  .column-config {
    margin-bottom: 10px;
    padding: 8px;
    background: rgba(255,255,255,0.5);
    border-radius: 4px;
    border: 1px solid transparent;
    transition: all 0.2s;
    cursor: move;
  }

  .column-config:hover {
    background: rgba(52, 152, 219, 0.05);
    border-color: #3498db;
  }

  .column-config[draggable="true"]:active {
    background: rgba(52, 152, 219, 0.1);
  }

  /* 对话框样式 */
  .dialog-content {
    padding: 10px 0;
  }

  .form-group {
    margin-bottom: 20px;
  }

  .form-label {
    margin-bottom: 8px;
    font-weight: bold;
    color: #2c3e50;
  }

  .form-row {
    display: flex;
    gap: 15px;
  }

  .form-item {
    flex: 1;
  }

  .paper-preview {
    margin-top: 20px;
  }

  .preview-label {
    margin-bottom: 10px;
    font-weight: bold;
    color: #2c3e50;
  }

  .preview-container {
    display: flex;
    justify-content: center;
    padding: 20px;
    background: #f8f9fa;
    border-radius: 8px;
  }

  .preview-paper {
    background: white;
    border: 1px solid #ddd;
    box-shadow: 0 2px 8px rgba(0,0,0,0.1);
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
  }

  .paper-info {
    position: absolute;
    bottom: 10px;
    right: 10px;
    background: rgba(0,0,0,0.7);
    color: white;
    padding: 4px 8px;
    border-radius: 4px;
    font-size: 12px;
  }

  /* 预览内容样式 */
  .preview-content {
    margin: 0 auto;
    padding: 0px;
    background: white;
    box-shadow: 0 0 10px rgba(0,0,0,0.1);
    overflow: auto;
    max-width: 100%;
    position: relative;
  }

  .preview-header {
    display: flex;
    justify-content: flex-end;
    padding: 0 10px;
  }

  /* 其他样式保持不变 */
  .ele-page {
    display: flex;
    flex-direction: column;
    height: 100vh;
    overflow: hidden;
  }

  .toolbar {
    background-color: #2c3e50;
    color: white;
    padding: 8px 15px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    box-shadow: 0 2px 5px rgba(0,0,0,0.1);
  }

  .toolbar-title {
    font-size: 18px;
    font-weight: bold;
    margin-right: 20px;
  }

  .toolbar-buttons {
    display: flex;
    gap: 10px;
    flex: 1;
  }

  .toolbar-btn {
    background-color: #3498db;
    border: none;
    color: white;
    padding: 6px 12px;
    border-radius: 4px;
    cursor: pointer;
    font-size: 13px;
    display: flex;
    align-items: center;
    gap: 5px;
    transition: all 0.2s;
  }

  .toolbar-btn:hover {
    background-color: #2980b9;
  }

  .toolbar-btn.active {
    background-color: #e74c3c;
  }

  .toolbar-btn:disabled {
    background-color: #7f8c8d;
    cursor: not-allowed;
  }

  .btn-danger {
    background-color: #e74c3c;
  }

  .btn-danger:hover {
    background-color: #c0392b;
  }

  .toolbar-btn i {
    font-size: 14px;
  }

  .zoom-controls {
    display: flex;
    align-items: center;
    gap: 8px;
    background: rgba(255,255,255,0.1);
    padding: 4px 8px;
    border-radius: 4px;
  }

  .zoom-btn {
    background: rgba(255,255,255,0.2);
    border: none;
    color: white;
    width: 24px;
    height: 24px;
    border-radius: 3px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 16px;
  }

  .zoom-btn:hover {
    background: rgba(255,255,255,0.3);
  }

  .zoom-level {
    font-size: 12px;
    min-width: 50px;
    text-align: center;
  }

  .main-content {
    display: flex;
    flex: 1;
    overflow: hidden;
  }

  .left-panel {
    width: 250px;
    background-color: #ecf0f1;
    border-right: 1px solid #bdc3c7;
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }

  .panel-header {
    background-color: #34495e;
    color: white;
    padding: 8px 12px;
    font-weight: bold;
    font-size: 14px;
  }

  .panel-content {
    flex: 1;
    padding: 10px;
    overflow-y: auto;
  }

  .panel-content2 {
    padding: 10px;
    overflow-y: auto;
  }

  .component-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
  }

  .component-item {
    background-color: white;
    border: 1px solid #ddd;
    border-radius: 4px;
    padding: 10px;
    cursor: move;
    font-size: 13px;
    display: flex;
    align-items: center;
    gap: 8px;
    transition: all 0.2s;
  }

  .component-item:hover {
    background-color: #f8f9fa;
    border-color: #3498db;
  }

  .component-item i {
    color: #3498db;
    font-size: 16px;
  }

  .design-area {
    flex: 1;
    background-color: white;
    overflow: auto;
    position: relative;
    background-image:
      linear-gradient(#e0e0e0 1px, transparent 1px),
      linear-gradient(90deg, #e0e0e0 1px, transparent 1px);
    background-size: 20px 20px;
  }

  .design-area.zoom-cursor {
    cursor: zoom-in;
  }

  .report-paper {
    width: 794px; /* A4宽度 */
    height: 1123px; /* A4高度 */
    margin: 20px auto;
    background-color: white;
    box-shadow: 0 0 10px rgba(0,0,0,0.1);
    position: relative;
    padding: 0px;
    transition: transform 0.1s ease;
  }

  .report-element {
    position: absolute;
    border: 1px dashed rgba(52, 152, 219, 0.1);
    background-color: rgba(52, 152, 219, 0.1);
    padding: 0px;
    font-size: 12px;
    cursor: move;
    user-select: none;
    min-width: 20px;
    min-height: 20px;
    overflow: hidden;
  }

  .report-element.selected {
    border: 1px solid rgba(231, 76, 60, 0.1) !important;
    background-color: rgba(231, 76, 60, 0.1) !important;
  }

  .report-element.resizing {
    border: 2px solid #f39c12;
    background-color: rgba(243, 156, 18, 0.1);
  }

  .data-table-container {
    width: 100%;
    height: 100%;
    overflow: auto;
  }

  .qe-code-container{
    width: 120px;
    height: 120px;
  }

  .data-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 12px;
  }

  .data-table th {
    border: 1px solid #ddd;
    padding: 4px 8px;
    text-align: left;
    background-color: #f5f5f5;
    font-weight: bold;
    font-family: var(--header-font-family, inherit);
    color: var(--header-color, inherit);
  }

  .data-table td {
    border: 1px solid #ddd;
    padding: 4px 8px;
    text-align: left;
  }

  .data-table tr:nth-child(even) {
    background-color: #f9f9f9;
  }

  .table-placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    color: #999;
    font-style: italic;
  }

  .element-handle {
    position: absolute;
    width: 8px;
    height: 8px;
    background-color: #3498db;
    border: 1px solid white;
    border-radius: 1px;
  }

  .element-handle.nw { top: -4px; left: -4px; cursor: nw-resize; }
  .element-handle.ne { top: -4px; right: -4px; cursor: ne-resize; }
  .element-handle.sw { bottom: -4px; left: -4px; cursor: sw-resize; }
  .element-handle.se { bottom: -4px; right: -4px; cursor: se-resize; }

  .context-menu {
    position: fixed;
    background: white;
    border: 1px solid #bdc3c7;
    border-radius: 4px;
    box-shadow: 0 2px 10px rgba(0,0,0,0.1);
    z-index: 1000;
    min-width: 150px;
  }

  .context-item {
    padding: 8px 12px;
    cursor: pointer;
    font-size: 13px;
    display: flex;
    align-items: center;
    gap: 8px;
    transition: background-color 0.2s;
  }

  .context-item:hover {
    background-color: #ecf0f1;
  }

  .context-item i {
    font-size: 14px;
    width: 16px;
  }

  .right-panel {
    width: 300px;
    background-color: #ecf0f1;
    border-left: 1px solid #bdc3c7;
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }

  .property-group {
    margin-bottom: 15px;
  }

  .property-label {
    margin-bottom: 5px;
    font-size: 13px;
    margin-top: 10px;
    font-weight: bold;
    color: #2c3e50;
  }

  .property-preview {
    padding: 6px 8px;
    background: #f8f9fa;
    border: 1px solid #dee2e6;
    border-radius: 4px;
    font-size: 13px;
    color: #6c757d;
  }

  .property-hint {
    padding: 8px;
    background: #e9ecef;
    border-radius: 4px;
    font-size: 12px;
    color: #6c757d;
    text-align: center;
  }

  .status-bar {
    background-color: #34495e;
    color: white;
    padding: 5px 15px;
    font-size: 12px;
    display: flex;
    justify-content: space-between;
    align-items: center;
  }

  .zoom-hint {
    background: rgba(52, 152, 219, 0.3);
    padding: 2px 8px;
    border-radius: 3px;
    font-size: 11px;
  }

  .tabs {
    display: flex;
    border-bottom: 1px solid #bdc3c7;
  }

  .tab {
    padding: 8px 15px;
    cursor: pointer;
    font-size: 13px;
    border-bottom: 2px solid transparent;
  }

  .tab.active {
    border-bottom: 2px solid #3498db;
    color: #3498db;
    font-weight: bold;
  }

  .tab-content {
    flex: 1;
    padding: 10px;
    overflow-y: auto;
  }

  .no-selection {
    text-align: center;
    color: #7f8c8d;
    padding: 20px;
    font-style: italic;
  }

  /* 数据源对话框样式 */
  .data-preview {
    border: 1px solid #e0e0e0;
    border-radius: 4px;
    padding: 10px;
    background: #fafafa;
    max-height: 250px;
    overflow: auto;
  }

  .form-hint {
    font-size: 12px;
    color: #666;
    margin-top: 5px;
    font-style: italic;
  }

  .form-row {
    display: flex;
    gap: 15px;
    margin-bottom: 15px;
  }

  .form-item {
    flex: 1;
  }

  .dialog-content {
    max-height: 70vh;
    overflow-y: auto;
  }
</style>

  

posted @ 2025-12-04 17:29  肖镜泽  阅读(37)  评论(0)    收藏  举报