online-analysis-button 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +380 -0
- package/dist/online-analysis-button.cjs.js +2 -0
- package/dist/online-analysis-button.cjs.js.map +1 -0
- package/dist/online-analysis-button.css +1 -0
- package/dist/online-analysis-button.es.js +2 -0
- package/dist/online-analysis-button.es.js.map +1 -0
- package/dist/online-analysis-button.umd.js +2 -0
- package/dist/online-analysis-button.umd.js.map +1 -0
- package/package.json +65 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"online-analysis-button.umd.js","sources":["../src/components/PivotTable.vue","../src/components/OnlineAnalysisButton.vue","../src/index.ts"],"sourcesContent":["<script lang=\"ts\">\r\nimport Vue from 'vue'\r\nimport * as XLSX from 'xlsx'\r\n \r\n// jQuery, jQuery UI 和 PivotTable.js 已通过 CDN 在 index.html 中全局引入\r\n// 无需重复导入\r\ndeclare const $: JQueryStatic\r\n\r\ninterface PivotData {\r\n [key: string]: any\r\n}\r\n\r\nexport default Vue.extend({\r\n name: 'PivotTable',\r\n props: {\r\n data: {\r\n type: Array as () => PivotData[],\r\n default: () => []\r\n }\r\n },\r\n data() {\r\n return {\r\n showPivotTable: false,\r\n filters: [] as Array<{ field: string; operator: string; value: any; values?: any[]; customValue?: string; collapsed?: boolean }>,\r\n showFilters: false,\r\n pivotInitialized: false,\r\n fileInput: null as HTMLInputElement | null,\r\n isLoading: false,\r\n errorMessage: '',\r\n isFullscreen: false,\r\n showExportMenu: false,\r\n sidePanelCollapsed: false,\r\n filterSearchQuery: '',\r\n expandedFilters: new Set<number>(),\r\n displayedRows: 50\r\n }\r\n },\r\n computed: {\r\n hasData(): boolean {\r\n return this.data.length > 0\r\n },\r\n columns(): string[] {\r\n return this.hasData ? Object.keys(this.data[0]) : []\r\n },\r\n filteredData(): PivotData[] {\r\n if (this.filters.length === 0) {\r\n return this.data\r\n }\r\n \r\n return this.data.filter(item => {\r\n return this.filters.every(filter => {\r\n const itemValue = item[filter.field]\r\n const filterValue = filter.value\r\n const filterValues = filter.values\r\n \r\n switch (filter.operator) {\r\n case 'equals':\r\n return itemValue == filterValue\r\n case 'not_equals':\r\n return itemValue != filterValue\r\n case 'contains':\r\n return String(itemValue).toLowerCase().includes(String(filterValue).toLowerCase())\r\n case 'not_contains':\r\n return !String(itemValue).toLowerCase().includes(String(filterValue).toLowerCase())\r\n case 'greater_than':\r\n return Number(itemValue) > Number(filterValue)\r\n case 'less_than':\r\n return Number(itemValue) < Number(filterValue)\r\n case 'greater_equal':\r\n return Number(itemValue) >= Number(filterValue)\r\n case 'less_equal':\r\n return Number(itemValue) <= Number(filterValue)\r\n case 'in':\r\n return filterValues && filterValues.length > 0 ? filterValues.includes(itemValue) : true\r\n case 'not_in':\r\n return filterValues && filterValues.length > 0 ? !filterValues.includes(itemValue) : true\r\n default:\r\n return true\r\n }\r\n })\r\n })\r\n },\r\n uniqueFieldValues(): { [key: string]: any[] } {\r\n const result: { [key: string]: any[] } = {}\r\n this.columns.forEach(col => {\r\n const values = new Set(this.data.map(item => item[col]))\r\n result[col] = Array.from(values).sort()\r\n })\r\n return result\r\n }\r\n },\r\n watch: {\r\n showPivotTable(newVal) {\r\n if (newVal && !this.pivotInitialized) {\r\n this.$nextTick(() => {\r\n this.initializePivotTable()\r\n })\r\n }\r\n },\r\n filteredData: {\r\n deep: true,\r\n handler(newData, oldData) {\r\n // 只在数据真正变化时才更新透视表\r\n if (this.showPivotTable && this.pivotInitialized && this.hasDataChanged(newData, oldData)) {\r\n this.$nextTick(() => {\r\n this.updatePivotTable()\r\n })\r\n }\r\n }\r\n },\r\n data() {\r\n // 当数据变化时重置显示行数\r\n this.resetDisplayedRows()\r\n }\r\n },\r\n methods: {\r\n initializePivotTable() {\r\n const pivotElement = this.$refs.pivotOutput as HTMLElement\r\n if (!pivotElement) return\r\n\r\n // Clear existing content\r\n $(pivotElement).empty()\r\n\r\n // Check if data is valid\r\n if (!this.filteredData || this.filteredData.length === 0) {\r\n console.warn('No data available for pivot table')\r\n return\r\n }\r\n\r\n // Initialize PivotTable.js with full UI\r\n try {\r\n $(pivotElement).pivotUI(this.filteredData, {\r\n rows: [],\r\n cols: [],\r\n vals: [],\r\n aggregatorName: 'Count',\r\n rendererName: 'Table',\r\n renderers: $.extend(\r\n $.pivotUtilities.renderers,\r\n $.pivotUtilities.c3_renderers,\r\n $.pivotUtilities.d3_renderers,\r\n $.pivotUtilities.export_renderers\r\n ),\r\n hiddenAttributes: [],\r\n menuLimit: 500,\r\n colsLimit: 10,\r\n rowsLimit: 10,\r\n unusedAttrsVertical: true,\r\n autoSortUnusedAttrs: false,\r\n onRefresh: (config: any) => {\r\n console.log('Pivot table refreshed:', config)\r\n }\r\n })\r\n\r\n this.pivotInitialized = true\r\n } catch (error) {\r\n console.error('Error initializing pivot table:', error)\r\n }\r\n },\r\n\r\n updatePivotTable() {\r\n const pivotElement = this.$refs.pivotOutput as HTMLElement\r\n if (!pivotElement || !this.pivotInitialized) return\r\n\r\n // Update the pivot table with new data\r\n $(pivotElement).pivotUI(this.filteredData, {\r\n rows: [],\r\n cols: [],\r\n vals: [],\r\n aggregatorName: 'Count',\r\n rendererName: 'Table',\r\n renderers: $.extend(\r\n $.pivotUtilities.renderers,\r\n $.pivotUtilities.c3_renderers,\r\n $.pivotUtilities.d3_renderers,\r\n $.pivotUtilities.export_renderers\r\n ),\r\n hiddenAttributes: [],\r\n menuLimit: 500,\r\n colsLimit: 10,\r\n rowsLimit: 10,\r\n unusedAttrsVertical: true,\r\n autoSortUnusedAttrs: false\r\n }, true)\r\n },\r\n\r\n addFilter() {\r\n const newIndex = this.filters.length\r\n this.filters.push({\r\n field: this.columns[0],\r\n operator: 'equals',\r\n value: ''\r\n })\r\n // 新添加的过滤器默认展开\r\n this.expandedFilters.add(newIndex)\r\n },\r\n removeFilter(index: number) {\r\n this.filters.splice(index, 1)\r\n },\r\n clearFilters() {\r\n this.filters = []\r\n },\r\n getOperatorLabel(operator: string): string {\r\n const labels: { [key: string]: string } = {\r\n 'equals': '等于',\r\n 'not_equals': '不等于',\r\n 'contains': '包含',\r\n 'not_contains': '不包含',\r\n 'greater_than': '大于',\r\n 'less_than': '小于',\r\n 'greater_equal': '大于等于',\r\n 'less_equal': '小于等于',\r\n 'in': '包含于',\r\n 'not_in': '不包含于'\r\n }\r\n return labels[operator] || operator\r\n },\r\n \r\n // 切换多选值\r\n toggleMultiSelectValue(filter: any, value: any) {\r\n if (!filter.values) {\r\n this.$set(filter, 'values', [])\r\n }\r\n const index = filter.values.indexOf(value)\r\n if (index > -1) {\r\n filter.values.splice(index, 1)\r\n } else {\r\n filter.values.push(value)\r\n }\r\n // 清空单选值\r\n filter.value = ''\r\n \r\n // 强制更新过滤后的数据\r\n this.$forceUpdate()\r\n },\r\n \r\n // 检查值是否被选中\r\n isMultiSelectValueSelected(filter: any, value: any): boolean {\r\n return filter.values && filter.values.includes(value)\r\n },\r\n \r\n // 添加自定义值\r\n addCustomValue(filter: any) {\r\n if (!filter.customValue || filter.customValue.trim() === '') {\r\n return\r\n }\r\n \r\n if (!filter.values) {\r\n this.$set(filter, 'values', [])\r\n }\r\n \r\n const value = filter.customValue.trim()\r\n if (!filter.values.includes(value)) {\r\n filter.values.push(value)\r\n }\r\n \r\n this.$set(filter, 'customValue', '')\r\n \r\n // 强制更新过滤后的数据\r\n this.$forceUpdate()\r\n },\r\n \r\n // 文件上传相关方法\r\n handleFileUpload(event: Event) {\r\n const target = event.target as HTMLInputElement\r\n const file = target.files?.[0]\r\n \r\n if (!file) return\r\n\r\n this.isLoading = true\r\n this.errorMessage = ''\r\n\r\n try {\r\n const fileExtension = file.name.split('.').pop()?.toLowerCase()\r\n \r\n if (fileExtension === 'csv') {\r\n this.processCSV(file)\r\n } else if (fileExtension === 'xlsx' || fileExtension === 'xls') {\r\n this.processExcel(file)\r\n } else {\r\n throw new Error('不支持的文件格式。请上传 CSV 或 Excel 文件。')\r\n }\r\n } catch (error) {\r\n this.errorMessage = error instanceof Error ? error.message : '文件处理失败'\r\n console.error('文件处理错误:', error)\r\n } finally {\r\n this.isLoading = false\r\n }\r\n },\r\n\r\n processCSV(file: File) {\r\n const reader = new FileReader()\r\n \r\n reader.onload = (e) => {\r\n try {\r\n const text = e.target?.result as string\r\n const data = this.parseCSV(text)\r\n this.$emit('data-loaded', data)\r\n } catch (error) {\r\n this.errorMessage = error instanceof Error ? error.message : 'CSV 文件处理失败'\r\n }\r\n }\r\n \r\n reader.onerror = () => {\r\n this.errorMessage = 'CSV 文件读取失败'\r\n }\r\n reader.readAsText(file)\r\n },\r\n\r\n parseCSV(text: string): any[] {\r\n const lines = text.split('\\n').filter(line => line.trim())\r\n if (lines.length === 0) return []\r\n\r\n const headers = this.parseCSVLine(lines[0])\r\n const data: any[] = []\r\n\r\n for (let i = 1; i < lines.length; i++) {\r\n const values = this.parseCSVLine(lines[i])\r\n if (values.length === headers.length) {\r\n const row: any = {}\r\n headers.forEach((header, index) => {\r\n row[header] = values[index]\r\n })\r\n data.push(row)\r\n }\r\n }\r\n\r\n return data\r\n },\r\n\r\n parseCSVLine(line: string): string[] {\r\n const result: string[] = []\r\n let current = ''\r\n let inQuotes = false\r\n\r\n for (let i = 0; i < line.length; i++) {\r\n const char = line[i]\r\n \r\n if (char === '\"') {\r\n inQuotes = !inQuotes\r\n } else if (char === ',' && !inQuotes) {\r\n result.push(current.trim())\r\n current = ''\r\n } else {\r\n current += char\r\n }\r\n }\r\n \r\n result.push(current.trim())\r\n return result\r\n },\r\n\r\n processExcel(file: File) {\r\n const reader = new FileReader()\r\n \r\n reader.onload = (e) => {\r\n try {\r\n const data = e.target?.result\r\n const workbook = XLSX.read(data, { type: 'binary' })\r\n \r\n // 获取第一个工作表\r\n const firstSheetName = workbook.SheetNames[0]\r\n const worksheet = workbook.Sheets[firstSheetName]\r\n \r\n // 转换为 JSON\r\n const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 })\r\n \r\n console.log('Excel原始数据:', jsonData)\r\n \r\n if (jsonData.length === 0) {\r\n throw new Error('Excel 文件为空')\r\n }\r\n\r\n // 转换为对象数组\r\n const headers = jsonData[0] as string[]\r\n console.log('表头:', headers)\r\n \r\n const dataRows = jsonData.slice(1) as any[][]\r\n console.log('数据行数:', dataRows.length)\r\n \r\n const result: any[] = dataRows\r\n .filter(row => row.length > 0 && row.some(cell => cell !== undefined && cell !== ''))\r\n .map(row => {\r\n const obj: any = {}\r\n headers.forEach((header, index) => {\r\n let value = row[index]\r\n // 尝试转换为数字\r\n if (typeof value === 'string' && !isNaN(Number(value)) && value.trim() !== '') {\r\n value = Number(value)\r\n }\r\n obj[header] = value !== undefined ? value : ''\r\n })\r\n return obj\r\n })\r\n\r\n console.log('解析后的数据:', result)\r\n this.$emit('data-loaded', result)\r\n } catch (error) {\r\n console.error('Excel处理错误:', error)\r\n this.errorMessage = error instanceof Error ? error.message : 'Excel 文件处理失败'\r\n }\r\n }\r\n \r\n reader.onerror = () => {\r\n this.errorMessage = 'Excel 文件读取失败'\r\n }\r\n reader.readAsBinaryString(file)\r\n },\r\n\r\n triggerFileInput() {\r\n const input = this.$refs.fileInput as HTMLInputElement\r\n if (input) {\r\n input.click()\r\n }\r\n },\r\n \r\n // 检查数据是否真正发生了变化\r\n hasDataChanged(newData: PivotData[] | undefined, oldData: PivotData[] | undefined): boolean {\r\n if (!newData || !oldData) return true\r\n if (newData.length !== oldData.length) return true\r\n \r\n // 检查数据内容是否变化\r\n for (let i = 0; i < newData.length; i++) {\r\n const newKeys = Object.keys(newData[i])\r\n const oldKeys = Object.keys(oldData[i])\r\n \r\n if (newKeys.length !== oldKeys.length) return true\r\n \r\n for (const key of newKeys) {\r\n if (newData[i][key] !== oldData[i][key]) return true\r\n }\r\n }\r\n \r\n return false\r\n },\r\n \r\n // 全屏功能\r\n toggleFullscreen() {\r\n this.isFullscreen = !this.isFullscreen\r\n \r\n if (this.isFullscreen) {\r\n // 进入全屏模式\r\n document.body.style.overflow = 'hidden'\r\n document.body.classList.add('fullscreen-mode')\r\n } else {\r\n // 退出全屏模式\r\n document.body.style.overflow = ''\r\n document.body.classList.remove('fullscreen-mode')\r\n }\r\n },\r\n \r\n // 导出透视表数据到Excel\r\n exportPivotTableToExcel() {\r\n try {\r\n const pivotElement = this.$refs.pivotOutput as HTMLElement\r\n if (!pivotElement) {\r\n alert('透视表未初始化,无法导出')\r\n return\r\n }\r\n \r\n // 获取透视表数据\r\n const table = pivotElement.querySelector('table.pvtTable')\r\n if (!table) {\r\n alert('未找到透视表数据')\r\n return\r\n }\r\n \r\n // 解析表格数据\r\n const data: any[][] = []\r\n const rows = table.querySelectorAll('tr')\r\n \r\n rows.forEach(row => {\r\n const rowData: any[] = []\r\n const cells = row.querySelectorAll('th, td')\r\n \r\n cells.forEach(cell => {\r\n // 处理合并单元格\r\n const colSpan = parseInt(cell.getAttribute('colspan') || '1')\r\n const text = cell.textContent?.trim() || ''\r\n \r\n // 如果是合并单元格,填充空值\r\n for (let i = 0; i < colSpan; i++) {\r\n rowData.push(text)\r\n }\r\n })\r\n \r\n data.push(rowData)\r\n })\r\n \r\n // 创建工作簿\r\n const workbook = XLSX.utils.book_new()\r\n const worksheet = XLSX.utils.aoa_to_sheet(data)\r\n \r\n // 设置列宽\r\n const colWidths = data[0].map(() => ({ wch: 15 }))\r\n worksheet['!cols'] = colWidths\r\n \r\n // 添加工作表到工作簿\r\n XLSX.utils.book_append_sheet(workbook, worksheet, '透视表')\r\n \r\n // 生成文件名\r\n const now = new Date()\r\n const timestamp = now.toISOString().slice(0, 10).replace(/-/g, '')\r\n const timeStr = now.toTimeString().slice(0, 8).replace(/:/g, '')\r\n const filename = `透视表_${timestamp}_${timeStr}.xlsx`\r\n \r\n // 下载文件\r\n XLSX.writeFile(workbook, filename)\r\n \r\n console.log('透视表导出成功:', filename)\r\n } catch (error) {\r\n console.error('导出透视表失败:', error)\r\n alert('导出透视表失败,请重试')\r\n }\r\n },\r\n \r\n // 导出原始数据到Excel\r\n exportRawDataToExcel() {\r\n try {\r\n if (!this.filteredData || this.filteredData.length === 0) {\r\n alert('没有数据可导出')\r\n return\r\n }\r\n \r\n // 创建工作簿\r\n const workbook = XLSX.utils.book_new()\r\n \r\n // 将数据转换为工作表\r\n const worksheet = XLSX.utils.json_to_sheet(this.filteredData)\r\n \r\n // 设置列宽\r\n const colWidths = this.columns.map(() => ({ wch: 15 }))\r\n worksheet['!cols'] = colWidths\r\n \r\n // 添加工作表到工作簿\r\n XLSX.utils.book_append_sheet(workbook, worksheet, '数据')\r\n \r\n // 生成文件名\r\n const now = new Date()\r\n const timestamp = now.toISOString().slice(0, 10).replace(/-/g, '')\r\n const timeStr = now.toTimeString().slice(0, 8).replace(/:/g, '')\r\n const filename = `数据_${timestamp}_${timeStr}.xlsx`\r\n \r\n // 下载文件\r\n XLSX.writeFile(workbook, filename)\r\n \r\n console.log('数据导出成功:', filename)\r\n } catch (error) {\r\n console.error('导出数据失败:', error)\r\n alert('导出数据失败,请重试')\r\n }\r\n },\r\n \r\n // 导出透视表到HTML\r\n exportPivotTableToHTML() {\r\n try {\r\n const pivotElement = this.$refs.pivotOutput as HTMLElement\r\n if (!pivotElement) {\r\n alert('透视表未初始化,无法导出')\r\n return\r\n }\r\n \r\n // 获取透视表HTML\r\n const table = pivotElement.querySelector('table.pvtTable')\r\n if (!table) {\r\n alert('未找到透视表数据')\r\n return\r\n }\r\n \r\n // 创建完整的HTML文档\r\n const htmlContent = `\r\n<!DOCTYPE html>\r\n<html lang=\"zh-CN\">\r\n<head>\r\n <meta charset=\"UTF-8\">\r\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n <title>透视表导出</title>\r\n <style>\r\n * {\r\n margin: 0;\r\n padding: 0;\r\n box-sizing: border-box;\r\n }\r\n \r\n body {\r\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\r\n padding: 20px;\r\n background: #f8fafc;\r\n }\r\n \r\n .container {\r\n max-width: 100%;\r\n margin: 0 auto;\r\n background: white;\r\n padding: 20px;\r\n border-radius: 8px;\r\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\r\n }\r\n \r\n h1 {\r\n color: #334155;\r\n margin-bottom: 20px;\r\n font-size: 24px;\r\n }\r\n \r\n .export-info {\r\n color: #64748b;\r\n margin-bottom: 20px;\r\n font-size: 14px;\r\n }\r\n \r\n table {\r\n width: 100%;\r\n border-collapse: collapse;\r\n margin: 20px 0;\r\n font-size: 14px;\r\n }\r\n \r\n th, td {\r\n border: 1px solid #e2e8f0;\r\n padding: 12px;\r\n text-align: center;\r\n }\r\n \r\n th {\r\n background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);\r\n color: #334155;\r\n font-weight: 600;\r\n }\r\n \r\n tr:nth-child(even) {\r\n background: #f8fafc;\r\n }\r\n \r\n tr:hover {\r\n background: #e2e8f0;\r\n }\r\n \r\n .pvtTotal, .pvtGrandTotal {\r\n background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%) !important;\r\n font-weight: 700;\r\n color: #1e40af;\r\n }\r\n \r\n .pvtGrandTotal {\r\n background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%) !important;\r\n color: #92400e;\r\n }\r\n </style>\r\n</head>\r\n<body>\r\n <div class=\"container\">\r\n <h1>数据透视表</h1>\r\n <div class=\"export-info\">\r\n 导出时间: ${new Date().toLocaleString('zh-CN')}<br>\r\n 数据行数: ${this.filteredData.length}<br>\r\n 字段数: ${this.columns.length}\r\n </div>\r\n ${table.outerHTML}\r\n </div>\r\n</body>\r\n</html>`\r\n \r\n // 生成文件名\r\n const now = new Date()\r\n const timestamp = now.toISOString().slice(0, 10).replace(/-/g, '')\r\n const timeStr = now.toTimeString().slice(0, 8).replace(/:/g, '')\r\n const filename = `透视表_${timestamp}_${timeStr}.html`\r\n \r\n // 创建下载链接\r\n const blob = new Blob([htmlContent], { type: 'text/html;charset=utf-8' })\r\n const url = URL.createObjectURL(blob)\r\n const link = document.createElement('a')\r\n link.href = url\r\n link.download = filename\r\n document.body.appendChild(link)\r\n link.click()\r\n document.body.removeChild(link)\r\n URL.revokeObjectURL(url)\r\n \r\n console.log('透视表HTML导出成功:', filename)\r\n } catch (error) {\r\n console.error('导出透视表HTML失败:', error)\r\n alert('导出透视表HTML失败,请重试')\r\n }\r\n },\r\n \r\n // 导出原始数据到HTML\r\n exportRawDataToHTML() {\r\n try {\r\n if (!this.filteredData || this.filteredData.length === 0) {\r\n alert('没有数据可导出')\r\n return\r\n }\r\n \r\n // 创建表格HTML\r\n let tableHTML = '<table>\\n'\r\n \r\n // 表头\r\n tableHTML += '<thead>\\n<tr>\\n'\r\n this.columns.forEach(col => {\r\n tableHTML += `<th>${col}</th>\\n`\r\n })\r\n tableHTML += '</tr>\\n</thead>\\n'\r\n \r\n // 表体\r\n tableHTML += '<tbody>\\n'\r\n this.filteredData.forEach(row => {\r\n tableHTML += '<tr>\\n'\r\n this.columns.forEach(col => {\r\n tableHTML += `<td>${row[col] !== undefined ? row[col] : ''}</td>\\n`\r\n })\r\n tableHTML += '</tr>\\n'\r\n })\r\n tableHTML += '</tbody>\\n</table>'\r\n \r\n // 创建完整的HTML文档\r\n const htmlContent = `\r\n<!DOCTYPE html>\r\n<html lang=\"zh-CN\">\r\n<head>\r\n <meta charset=\"UTF-8\">\r\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n <title>数据导出</title>\r\n <style>\r\n * {\r\n margin: 0;\r\n padding: 0;\r\n box-sizing: border-box;\r\n }\r\n \r\n body {\r\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\r\n padding: 20px;\r\n background: #f8fafc;\r\n }\r\n \r\n .container {\r\n max-width: 100%;\r\n margin: 0 auto;\r\n background: white;\r\n padding: 20px;\r\n border-radius: 8px;\r\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\r\n }\r\n \r\n h1 {\r\n color: #334155;\r\n margin-bottom: 20px;\r\n font-size: 24px;\r\n }\r\n \r\n .export-info {\r\n color: #64748b;\r\n margin-bottom: 20px;\r\n font-size: 14px;\r\n }\r\n \r\n table {\r\n width: 100%;\r\n border-collapse: collapse;\r\n margin: 20px 0;\r\n font-size: 14px;\r\n }\r\n \r\n th, td {\r\n border: 1px solid #e2e8f0;\r\n padding: 12px;\r\n text-align: left;\r\n }\r\n \r\n th {\r\n background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);\r\n color: #334155;\r\n font-weight: 600;\r\n position: sticky;\r\n top: 0;\r\n }\r\n \r\n tr:nth-child(even) {\r\n background: #f8fafc;\r\n }\r\n \r\n tr:hover {\r\n background: #e2e8f0;\r\n }\r\n </style>\r\n</head>\r\n<body>\r\n <div class=\"container\">\r\n <h1>原始数据</h1>\r\n <div class=\"export-info\">\r\n 导出时间: ${new Date().toLocaleString('zh-CN')}<br>\r\n 数据行数: ${this.filteredData.length}<br>\r\n 字段数: ${this.columns.length}\r\n </div>\r\n ${tableHTML}\r\n </div>\r\n</body>\r\n</html>`\r\n \r\n // 生成文件名\r\n const now = new Date()\r\n const timestamp = now.toISOString().slice(0, 10).replace(/-/g, '')\r\n const timeStr = now.toTimeString().slice(0, 8).replace(/:/g, '')\r\n const filename = `数据_${timestamp}_${timeStr}.html`\r\n \r\n // 创建下载链接\r\n const blob = new Blob([htmlContent], { type: 'text/html;charset=utf-8' })\r\n const url = URL.createObjectURL(blob)\r\n const link = document.createElement('a')\r\n link.href = url\r\n link.download = filename\r\n document.body.appendChild(link)\r\n link.click()\r\n document.body.removeChild(link)\r\n URL.revokeObjectURL(url)\r\n \r\n console.log('数据HTML导出成功:', filename)\r\n } catch (error) {\r\n console.error('导出数据HTML失败:', error)\r\n alert('导出数据HTML失败,请重试')\r\n }\r\n },\r\n \r\n // 切换侧边面板折叠状态\r\n toggleSidePanel() {\r\n this.sidePanelCollapsed = !this.sidePanelCollapsed\r\n },\r\n \r\n // 切换过滤器折叠状态\r\n toggleFilterCollapse(index: number) {\r\n if (this.expandedFilters.has(index)) {\r\n this.expandedFilters.delete(index)\r\n } else {\r\n this.expandedFilters.add(index)\r\n }\r\n },\r\n \r\n // 检查过滤器是否展开\r\n isFilterExpanded(index: number): boolean {\r\n return this.expandedFilters.has(index)\r\n },\r\n \r\n // 获取过滤后的唯一值(支持搜索)\r\n getFilteredUniqueValues(field: string): any[] {\r\n const values = this.uniqueFieldValues[field] || []\r\n if (!this.filterSearchQuery) {\r\n return values\r\n }\r\n return values.filter(val => \r\n String(val).toLowerCase().includes(this.filterSearchQuery.toLowerCase())\r\n )\r\n },\r\n \r\n // 清空搜索\r\n clearFilterSearch() {\r\n this.filterSearchQuery = ''\r\n },\r\n \r\n // 加载更多数据\r\n loadMoreData() {\r\n this.displayedRows += 50\r\n },\r\n \r\n // 重置显示行数\r\n resetDisplayedRows() {\r\n this.displayedRows = 50\r\n }\r\n }\r\n})\r\n</script>\r\n\r\n <template>\r\n <div class=\"pivot-table\">\r\n <!-- 无数据状态 -->\r\n <div v-if=\"!hasData\" class=\"empty-state\">\r\n <div class=\"empty-state-content\">\r\n <div class=\"empty-icon\">\r\n <svg width=\"120\" height=\"120\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <path d=\"M3 3V21H21\" stroke=\"#E2E8F0\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\r\n <path d=\"M19 9L14 14L10 10L7 13\" stroke=\"#CBD5E1\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\r\n <path d=\"M19 9V13H15\" stroke=\"#CBD5E1\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\r\n </svg>\r\n </div>\r\n <h2 class=\"empty-title\">暂无数据</h2>\r\n <p class=\"empty-description\">请先导入数据文件或加载示例数据开始分析</p>\r\n </div>\r\n </div>\r\n\r\n <!-- 有数据状态 -->\r\n <div v-else class=\"data-container\">\r\n <!-- 隐藏的文件输入 -->\r\n <input\r\n ref=\"fileInput\"\r\n type=\"file\"\r\n accept=\".csv,.xlsx,.xls\"\r\n @change=\"handleFileUpload\"\r\n style=\"display: none\"\r\n />\r\n \r\n <!-- 顶部工具栏 -->\r\n <div class=\"toolbar\">\r\n <div class=\"toolbar-left\">\r\n <div class=\"view-toggle\">\r\n <button \r\n :class=\"['toggle-btn', { active: !showPivotTable }]\" \r\n @click=\"showPivotTable = false\"\r\n >\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <path d=\"M3 3h18v18H3zM3 9h18M3 15h18M9 3v18M15 3v18\"/>\r\n </svg>\r\n 原始数据\r\n </button>\r\n <button \r\n :class=\"['toggle-btn', { active: showPivotTable }]\" \r\n @click=\"showPivotTable = true\"\r\n >\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/>\r\n <path d=\"M3 9h18M3 15h18M9 3v18\"/>\r\n </svg>\r\n 透视分析\r\n </button>\r\n </div>\r\n </div>\r\n \r\n <div class=\"toolbar-right\">\r\n <div class=\"data-stats\">\r\n <div class=\"stat-item\">\r\n <span class=\"stat-label\">总行数</span>\r\n <span class=\"stat-value\">{{ data.length.toLocaleString() }}</span>\r\n </div>\r\n <div class=\"stat-item\">\r\n <span class=\"stat-label\">字段数</span>\r\n <span class=\"stat-value\">{{ columns.length }}</span>\r\n </div>\r\n <div v-if=\"filters.length > 0\" class=\"stat-item highlighted\">\r\n <span class=\"stat-label\">过滤后</span>\r\n <span class=\"stat-value\">{{ filteredData.length.toLocaleString() }}</span>\r\n </div>\r\n </div>\r\n \r\n <div class=\"toolbar-actions\">\r\n <div class=\"export-dropdown\">\r\n <button \r\n class=\"export-btn\" \r\n @click=\"showExportMenu = !showExportMenu\"\r\n :title=\"showPivotTable ? '导出透视表' : '导出原始数据'\"\r\n >\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/>\r\n <polyline points=\"7 10 12 15 17 10\"/>\r\n <line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"/>\r\n </svg>\r\n {{ showPivotTable ? '导出透视表' : '导出数据' }}\r\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <polyline points=\"6 9 12 15 18 9\"/>\r\n </svg>\r\n </button>\r\n \r\n <div v-if=\"showExportMenu\" class=\"export-menu\">\r\n <button \r\n class=\"export-menu-item\" \r\n @click=\"showPivotTable ? exportPivotTableToExcel() : exportRawDataToExcel(); showExportMenu = false\"\r\n >\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"/>\r\n <polyline points=\"14 2 14 8 20 8\"/>\r\n <line x1=\"16\" y1=\"13\" x2=\"8\" y2=\"13\"/>\r\n <line x1=\"16\" y1=\"17\" x2=\"8\" y2=\"17\"/>\r\n <polyline points=\"10 9 9 9 8 9\"/>\r\n </svg>\r\n 导出为 Excel (.xlsx)\r\n </button>\r\n <button \r\n class=\"export-menu-item\" \r\n @click=\"showPivotTable ? exportPivotTableToHTML() : exportRawDataToHTML(); showExportMenu = false\"\r\n >\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <path d=\"M12 2L2 7l10 5 10-5-10-5z\"/>\r\n <path d=\"M2 17l10 5 10-5\"/>\r\n <path d=\"M2 12l10 5 10-5\"/>\r\n </svg>\r\n 导出为 HTML (.html)\r\n </button>\r\n </div>\r\n </div>\r\n \r\n <button \r\n class=\"fullscreen-btn\" \r\n @click=\"toggleFullscreen\"\r\n :title=\"isFullscreen ? '退出全屏' : '全屏显示'\"\r\n >\r\n <svg v-if=\"!isFullscreen\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <path d=\"M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3\"/>\r\n </svg>\r\n <svg v-else width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <path d=\"M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3\"/>\r\n </svg>\r\n {{ isFullscreen ? '退出全屏' : '全屏' }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 主内容区域 -->\r\n <div class=\"main-content\">\r\n <!-- 左侧面板 - 过滤器 -->\r\n <div class=\"side-panel\" :class=\"{ collapsed: sidePanelCollapsed }\" v-if=\"showPivotTable\">\r\n <!-- 面板折叠按钮 -->\r\n <button class=\"panel-collapse-btn\" @click=\"toggleSidePanel\" :title=\"sidePanelCollapsed ? '展开过滤面板' : '折叠过滤面板'\">\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" :class=\"{ rotated: sidePanelCollapsed }\">\r\n <polyline points=\"9 18 15 12 9 6\"/>\r\n </svg>\r\n </button>\r\n \r\n <!-- 过滤器 -->\r\n <div class=\"panel-section\" v-show=\"!sidePanelCollapsed\">\r\n <div class=\"panel-header\">\r\n <h3 class=\"panel-title\">数据过滤</h3>\r\n <button class=\"icon-btn\" @click=\"addFilter\" title=\"添加过滤\">\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <line x1=\"12\" y1=\"5\" x2=\"12\" y2=\"19\"/>\r\n <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"/>\r\n </svg>\r\n </button>\r\n </div>\r\n \r\n <div v-if=\"filters.length === 0\" class=\"empty-filters\">\r\n <p>暂无过滤条件</p>\r\n <button class=\"text-btn\" @click=\"addFilter\">添加过滤</button>\r\n </div>\r\n \r\n <div v-else class=\"filters-list\">\r\n <div v-for=\"(filter, index) in filters\" :key=\"index\" class=\"filter-card\">\r\n <div class=\"filter-header\">\r\n <button \r\n class=\"collapse-btn\" \r\n @click=\"toggleFilterCollapse(index)\"\r\n :title=\"isFilterExpanded(index) ? '收起' : '展开'\"\r\n >\r\n <svg \r\n width=\"14\" \r\n height=\"14\" \r\n viewBox=\"0 0 24 24\" \r\n fill=\"none\" \r\n stroke=\"currentColor\" \r\n stroke-width=\"2\"\r\n :class=\"{ rotated: isFilterExpanded(index) }\"\r\n >\r\n <polyline points=\"6 9 12 15 18 9\"/>\r\n </svg>\r\n </button>\r\n <select v-model=\"filter.field\" class=\"filter-field-select\">\r\n <option v-for=\"col in columns\" :key=\"col\" :value=\"col\">{{ col }}</option>\r\n </select>\r\n <button class=\"icon-btn small\" @click=\"removeFilter(index)\" title=\"删除\">\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>\r\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\r\n </svg>\r\n </button>\r\n </div>\r\n <div class=\"filter-body\" v-show=\"isFilterExpanded(index)\">\r\n <select v-model=\"filter.operator\" class=\"filter-operator-select\">\r\n <option value=\"equals\">等于</option>\r\n <option value=\"not_equals\">不等于</option>\r\n <option value=\"contains\">包含</option>\r\n <option value=\"not_contains\">不包含</option>\r\n <option value=\"greater_than\">大于</option>\r\n <option value=\"less_than\">小于</option>\r\n <option value=\"greater_equal\">大于等于</option>\r\n <option value=\"less_equal\">小于等于</option>\r\n <option value=\"in\">包含于(多选)</option>\r\n <option value=\"not_in\">不包含于(多选)</option>\r\n </select>\r\n \r\n <!-- 单选模式:下拉选择 + 手动输入 -->\r\n <div v-if=\"filter.operator !== 'in' && filter.operator !== 'not_in'\" class=\"filter-value-wrapper\">\r\n <select \r\n v-model=\"filter.value\" \r\n class=\"filter-value-select\"\r\n >\r\n <option value=\"\">选择或输入值</option>\r\n <option v-for=\"val in uniqueFieldValues[filter.field]\" :key=\"val\" :value=\"val\">{{ val }}</option>\r\n </select>\r\n <input \r\n v-model=\"filter.value\" \r\n type=\"text\" \r\n class=\"filter-value-input\" \r\n placeholder=\"或手动输入\"\r\n >\r\n </div>\r\n \r\n <!-- 多选模式:复选框列表 -->\r\n <div v-else class=\"filter-multi-select\">\r\n <div class=\"multi-select-header\">\r\n <span class=\"selected-count\">已选 {{ filter.values ? filter.values.length : 0 }} 项</span>\r\n <button class=\"clear-selection-btn\" @click=\"filter.values = []\" v-if=\"filter.values && filter.values.length > 0\">\r\n 清空\r\n </button>\r\n </div>\r\n \r\n <!-- 搜索框 -->\r\n <div class=\"multi-select-search\">\r\n <input \r\n v-model=\"filterSearchQuery\" \r\n type=\"text\" \r\n class=\"search-input\"\r\n placeholder=\"搜索值...\"\r\n >\r\n <button \r\n v-if=\"filterSearchQuery\" \r\n class=\"clear-search-btn\" \r\n @click=\"clearFilterSearch\"\r\n title=\"清除搜索\"\r\n >\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>\r\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\r\n </svg>\r\n </button>\r\n </div>\r\n \r\n <div class=\"multi-select-list\">\r\n <label \r\n v-for=\"val in getFilteredUniqueValues(filter.field)\" \r\n :key=\"val\"\r\n :class=\"['multi-select-item', { selected: isMultiSelectValueSelected(filter, val) }]\"\r\n >\r\n <input \r\n type=\"checkbox\" \r\n :checked=\"isMultiSelectValueSelected(filter, val)\"\r\n @change=\"toggleMultiSelectValue(filter, val)\"\r\n >\r\n <span>{{ val }}</span>\r\n </label>\r\n <div v-if=\"getFilteredUniqueValues(filter.field).length === 0\" class=\"no-results\">\r\n 没有找到匹配的值\r\n </div>\r\n </div>\r\n <div class=\"multi-select-add\">\r\n <input \r\n v-model=\"filter.customValue\" \r\n type=\"text\" \r\n class=\"filter-custom-input\"\r\n placeholder=\"输入自定义值\"\r\n @keyup.enter=\"addCustomValue(filter)\"\r\n >\r\n <button class=\"add-value-btn\" @click=\"addCustomValue(filter)\">\r\n 添加\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n \r\n <button v-if=\"filters.length > 0\" class=\"clear-filters-btn\" @click=\"clearFilters\">\r\n 清除所有过滤\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 右侧主区域 -->\r\n <div class=\"content-area\">\r\n <!-- 原始数据表格 -->\r\n <div v-show=\"!showPivotTable\" class=\"data-view\">\r\n <div class=\"data-table-wrapper\">\r\n <table class=\"modern-table\">\r\n <thead>\r\n <tr>\r\n <th v-for=\"col in columns\" :key=\"col\" class=\"table-header\">\r\n <div class=\"header-content\">\r\n <span>{{ col }}</span>\r\n </div>\r\n </th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n <tr v-for=\"(row, index) in data.slice(0, displayedRows)\" :key=\"index\" class=\"table-row\">\r\n <td v-for=\"col in columns\" :key=\"col\" class=\"table-cell\">\r\n {{ row[col] }}\r\n </td>\r\n </tr>\r\n </tbody>\r\n </table>\r\n </div>\r\n <div v-if=\"data.length > displayedRows\" class=\"table-footer\">\r\n <div class=\"table-footer-content\">\r\n <span class=\"table-info\">显示 {{ displayedRows }} 行,共 {{ data.length.toLocaleString() }} 行数据</span>\r\n <button class=\"load-more-btn\" @click=\"loadMoreData\">\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <polyline points=\"6 9 12 15 18 9\"/>\r\n </svg>\r\n 加载更多\r\n </button>\r\n </div>\r\n </div>\r\n <div v-else-if=\"data.length > 0\" class=\"table-footer\">\r\n <span class=\"table-info\">已显示全部 {{ data.length.toLocaleString() }} 行数据</span>\r\n </div>\r\n </div>\r\n\r\n <!-- 透视表 -->\r\n <div v-show=\"showPivotTable\" class=\"pivot-view\">\r\n <div class=\"pivot-wrapper\">\r\n <div ref=\"pivotOutput\" class=\"pivot-output\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </template>\r\n\r\n<style scoped>\r\n/* 基础样式 */\r\n.pivot-table {\r\n width: 100%;\r\n height: 100%;\r\n background: #ffffff;\r\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\r\n color: #1e293b;\r\n}\r\n\r\n/* 空状态 */\r\n.empty-state {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n min-height: 600px;\r\n background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);\r\n}\r\n\r\n.empty-state-content {\r\n text-align: center;\r\n padding: 3rem;\r\n}\r\n\r\n.empty-icon {\r\n margin-bottom: 1.5rem;\r\n opacity: 0.6;\r\n}\r\n\r\n.empty-title {\r\n font-size: 1.5rem;\r\n font-weight: 600;\r\n color: #334155;\r\n margin: 0 0 0.75rem 0;\r\n}\r\n\r\n.empty-description {\r\n font-size: 1rem;\r\n color: #64748b;\r\n margin: 0;\r\n}\r\n\r\n/* 数据容器 */\r\n.data-container {\r\n display: flex;\r\n flex-direction: column;\r\n height: 100%;\r\n min-height: 600px;\r\n}\r\n\r\n/* 工具栏 */\r\n.toolbar {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n padding: 1rem 1.5rem;\r\n background: #ffffff;\r\n border-bottom: 1px solid #e2e8f0;\r\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);\r\n}\r\n\r\n.toolbar-left {\r\n display: flex;\r\n align-items: center;\r\n gap: 1rem;\r\n}\r\n\r\n/* 文件上传按钮 */\r\n.file-upload-btn {\r\n display: flex;\r\n align-items: center;\r\n gap: 0.5rem;\r\n padding: 0.5rem 1rem;\r\n background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);\r\n color: white;\r\n border: none;\r\n border-radius: 6px;\r\n font-size: 0.875rem;\r\n font-weight: 500;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n box-shadow: 0 2px 4px rgba(59, 130, 246, 0.2);\r\n}\r\n\r\n.file-upload-btn:hover:not(:disabled) {\r\n background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);\r\n box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);\r\n transform: translateY(-1px);\r\n}\r\n\r\n.file-upload-btn:disabled {\r\n opacity: 0.6;\r\n cursor: not-allowed;\r\n transform: none;\r\n}\r\n\r\n.view-toggle {\r\n display: flex;\r\n background: #f1f5f9;\r\n border-radius: 8px;\r\n padding: 4px;\r\n}\r\n\r\n.toggle-btn {\r\n display: flex;\r\n align-items: center;\r\n gap: 0.5rem;\r\n padding: 0.5rem 1rem;\r\n border: none;\r\n background: transparent;\r\n color: #64748b;\r\n font-size: 0.875rem;\r\n font-weight: 500;\r\n border-radius: 6px;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n}\r\n\r\n.toggle-btn:hover {\r\n color: #334155;\r\n background: rgba(255, 255, 255, 0.5);\r\n}\r\n\r\n.toggle-btn.active {\r\n background: #ffffff;\r\n color: #0f172a;\r\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n.toolbar-right {\r\n display: flex;\r\n align-items: center;\r\n gap: 1.5rem;\r\n}\r\n\r\n.data-stats {\r\n display: flex;\r\n gap: 1.5rem;\r\n}\r\n\r\n.stat-item {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: flex-end;\r\n}\r\n\r\n.stat-item.highlighted {\r\n background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);\r\n padding: 0.5rem 1rem;\r\n border-radius: 8px;\r\n border: 1px solid #3b82f6;\r\n}\r\n\r\n.stat-label {\r\n font-size: 0.75rem;\r\n color: #64748b;\r\n font-weight: 500;\r\n text-transform: uppercase;\r\n letter-spacing: 0.05em;\r\n}\r\n\r\n.stat-value {\r\n font-size: 1.125rem;\r\n font-weight: 600;\r\n color: #0f172a;\r\n}\r\n\r\n.stat-item.highlighted .stat-value {\r\n color: #1e40af;\r\n}\r\n\r\n/* 工具栏操作区域 */\r\n.toolbar-actions {\r\n display: flex;\r\n align-items: center;\r\n gap: 0.75rem;\r\n}\r\n\r\n/* 导出下拉菜单 */\r\n.export-dropdown {\r\n position: relative;\r\n}\r\n\r\n/* 导出按钮 */\r\n.export-btn {\r\n display: flex;\r\n align-items: center;\r\n gap: 0.5rem;\r\n padding: 0.5rem 1rem;\r\n background: linear-gradient(135deg, #10b981 0%, #059669 100%);\r\n color: white;\r\n border: none;\r\n border-radius: 6px;\r\n font-size: 0.875rem;\r\n font-weight: 500;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n box-shadow: 0 2px 4px rgba(16, 185, 129, 0.2);\r\n}\r\n\r\n.export-btn:hover {\r\n background: linear-gradient(135deg, #059669 0%, #047857 100%);\r\n box-shadow: 0 4px 8px rgba(16, 185, 129, 0.3);\r\n transform: translateY(-1px);\r\n}\r\n\r\n.export-btn:active {\r\n transform: translateY(0);\r\n}\r\n\r\n/* 导出菜单 */\r\n.export-menu {\r\n position: absolute;\r\n top: calc(100% + 8px);\r\n right: 0;\r\n min-width: 200px;\r\n background: #ffffff;\r\n border: 1px solid #e2e8f0;\r\n border-radius: 8px;\r\n box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);\r\n z-index: 1000;\r\n overflow: hidden;\r\n animation: slideDown 0.2s ease;\r\n}\r\n\r\n@keyframes slideDown {\r\n from {\r\n opacity: 0;\r\n transform: translateY(-10px);\r\n }\r\n to {\r\n opacity: 1;\r\n transform: translateY(0);\r\n }\r\n}\r\n\r\n.export-menu-item {\r\n display: flex;\r\n align-items: center;\r\n gap: 0.75rem;\r\n width: 100%;\r\n padding: 0.75rem 1rem;\r\n background: transparent;\r\n border: none;\r\n color: #334155;\r\n font-size: 0.875rem;\r\n font-weight: 500;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n text-align: left;\r\n}\r\n\r\n.export-menu-item:hover {\r\n background: #f8fafc;\r\n color: #059669;\r\n}\r\n\r\n.export-menu-item:active {\r\n background: #f1f5f9;\r\n}\r\n\r\n.export-menu-item svg {\r\n flex-shrink: 0;\r\n color: #64748b;\r\n transition: color 0.2s ease;\r\n}\r\n\r\n.export-menu-item:hover svg {\r\n color: #059669;\r\n}\r\n\r\n/* 全屏按钮 */\r\n.fullscreen-btn {\r\n display: flex;\r\n align-items: center;\r\n gap: 0.5rem;\r\n padding: 0.5rem 1rem;\r\n background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);\r\n color: white;\r\n border: none;\r\n border-radius: 6px;\r\n font-size: 0.875rem;\r\n font-weight: 500;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n box-shadow: 0 2px 4px rgba(99, 102, 241, 0.2);\r\n}\r\n\r\n.fullscreen-btn:hover {\r\n background: linear-gradient(135deg, #4f46e5 0%, #4338ca 100%);\r\n box-shadow: 0 4px 8px rgba(99, 102, 241, 0.3);\r\n transform: translateY(-1px);\r\n}\r\n\r\n.fullscreen-btn:active {\r\n transform: translateY(0);\r\n}\r\n\r\n/* 主内容区域 */\r\n.main-content {\r\n display: flex;\r\n flex: 1;\r\n overflow: hidden;\r\n}\r\n\r\n/* 侧边面板 */\r\n.side-panel {\r\n width: 320px;\r\n background: #f8fafc;\r\n border-right: 1px solid #e2e8f0;\r\n display: flex;\r\n flex-direction: column;\r\n overflow-y: auto;\r\n position: relative;\r\n transition: width 0.3s ease;\r\n}\r\n\r\n.side-panel.collapsed {\r\n width: 48px;\r\n}\r\n\r\n/* 面板折叠按钮 */\r\n.panel-collapse-btn {\r\n position: absolute;\r\n right: -12px;\r\n top: 50%;\r\n transform: translateY(-50%);\r\n width: 24px;\r\n height: 24px;\r\n border: 1px solid #e2e8f0;\r\n background: #ffffff;\r\n color: #64748b;\r\n border-radius: 50%;\r\n cursor: pointer;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n z-index: 10;\r\n transition: all 0.2s ease;\r\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n.panel-collapse-btn:hover {\r\n background: #f1f5f9;\r\n color: #334155;\r\n border-color: #cbd5e1;\r\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);\r\n}\r\n\r\n.panel-collapse-btn svg {\r\n transition: transform 0.2s ease;\r\n}\r\n\r\n.panel-collapse-btn svg.rotated {\r\n transform: rotate(180deg);\r\n}\r\n\r\n.panel-section {\r\n padding: 1.25rem;\r\n border-bottom: 1px solid #e2e8f0;\r\n}\r\n\r\n.panel-section:last-child {\r\n border-bottom: none;\r\n}\r\n\r\n.panel-header {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n margin-bottom: 1rem;\r\n}\r\n\r\n.panel-title {\r\n font-size: 0.875rem;\r\n font-weight: 600;\r\n color: #334155;\r\n margin: 0;\r\n text-transform: uppercase;\r\n letter-spacing: 0.05em;\r\n}\r\n\r\n.panel-badge {\r\n background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);\r\n color: white;\r\n font-size: 0.75rem;\r\n font-weight: 600;\r\n padding: 0.25rem 0.625rem;\r\n border-radius: 9999px;\r\n}\r\n\r\n/* 字段网格 */\r\n.fields-grid {\r\n display: grid;\r\n grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));\r\n gap: 0.75rem;\r\n}\r\n\r\n.field-card {\r\n display: flex;\r\n align-items: center;\r\n gap: 0.5rem;\r\n padding: 0.75rem;\r\n background: #ffffff;\r\n border: 1px solid #e2e8f0;\r\n border-radius: 8px;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n}\r\n\r\n.field-card:hover {\r\n border-color: #3b82f6;\r\n box-shadow: 0 2px 8px rgba(59, 130, 246, 0.1);\r\n transform: translateY(-1px);\r\n}\r\n\r\n.field-icon {\r\n color: #64748b;\r\n flex-shrink: 0;\r\n}\r\n\r\n.field-name {\r\n font-size: 0.875rem;\r\n font-weight: 500;\r\n color: #334155;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n/* 图标按钮 */\r\n.icon-btn {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 32px;\r\n height: 32px;\r\n border: none;\r\n background: #ffffff;\r\n color: #64748b;\r\n border-radius: 6px;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n border: 1px solid #e2e8f0;\r\n}\r\n\r\n.icon-btn:hover {\r\n background: #f1f5f9;\r\n color: #334155;\r\n border-color: #cbd5e1;\r\n}\r\n\r\n.icon-btn.small {\r\n width: 24px;\r\n height: 24px;\r\n}\r\n\r\n/* 空过滤器 */\r\n.empty-filters {\r\n text-align: center;\r\n padding: 2rem 1rem;\r\n}\r\n\r\n.empty-filters p {\r\n font-size: 0.875rem;\r\n color: #64748b;\r\n margin: 0 0 1rem 0;\r\n}\r\n\r\n.text-btn {\r\n background: transparent;\r\n border: none;\r\n color: #3b82f6;\r\n font-size: 0.875rem;\r\n font-weight: 500;\r\n cursor: pointer;\r\n padding: 0.5rem 1rem;\r\n border-radius: 6px;\r\n transition: all 0.2s ease;\r\n}\r\n\r\n.text-btn:hover {\r\n background: #dbeafe;\r\n}\r\n\r\n/* 过滤器列表 */\r\n.filters-list {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 0.75rem;\r\n}\r\n\r\n.filter-card {\r\n background: #ffffff;\r\n border: 1px solid #e2e8f0;\r\n border-radius: 8px;\r\n overflow: hidden;\r\n transition: all 0.2s ease;\r\n}\r\n\r\n.filter-card:hover {\r\n border-color: #cbd5e1;\r\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);\r\n}\r\n\r\n.filter-header {\r\n display: flex;\r\n align-items: center;\r\n gap: 0.5rem;\r\n padding: 0.75rem;\r\n background: #f8fafc;\r\n border-bottom: 1px solid #e2e8f0;\r\n}\r\n\r\n.filter-field-select {\r\n flex: 1;\r\n padding: 0.5rem;\r\n border: 1px solid #e2e8f0;\r\n border-radius: 6px;\r\n background: #ffffff;\r\n color: #334155;\r\n font-size: 0.875rem;\r\n font-weight: 500;\r\n cursor: pointer;\r\n}\r\n\r\n.filter-field-select:focus {\r\n outline: none;\r\n border-color: #3b82f6;\r\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);\r\n}\r\n\r\n.filter-body {\r\n display: flex;\r\n gap: 0.5rem;\r\n padding: 0.75rem;\r\n}\r\n\r\n.filter-operator-select,\r\n.filter-value-select,\r\n.filter-value-input {\r\n flex: 1;\r\n padding: 0.5rem;\r\n border: 1px solid #e2e8f0;\r\n border-radius: 6px;\r\n background: #ffffff;\r\n color: #334155;\r\n font-size: 0.875rem;\r\n}\r\n\r\n.filter-operator-select:focus,\r\n.filter-value-select:focus,\r\n.filter-value-input:focus {\r\n outline: none;\r\n border-color: #3b82f6;\r\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);\r\n}\r\n\r\n.clear-filters-btn {\r\n width: 100%;\r\n padding: 0.75rem;\r\n background: #fee2e2;\r\n color: #dc2626;\r\n border: 1px solid #fecaca;\r\n border-radius: 6px;\r\n font-size: 0.875rem;\r\n font-weight: 500;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n margin-top: 0.5rem;\r\n}\r\n\r\n.clear-filters-btn:hover {\r\n background: #fecaca;\r\n border-color: #fca5a5;\r\n}\r\n\r\n/* 过滤器值包装器 */\r\n.filter-value-wrapper {\r\n flex: 1;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 0.5rem;\r\n}\r\n\r\n/* 多选过滤器 */\r\n.filter-multi-select {\r\n flex: 1;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 0.5rem;\r\n max-height: 300px;\r\n}\r\n\r\n.multi-select-header {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n padding: 0.5rem;\r\n background: #f8fafc;\r\n border-radius: 6px;\r\n border: 1px solid #e2e8f0;\r\n}\r\n\r\n.selected-count {\r\n font-size: 0.75rem;\r\n color: #64748b;\r\n font-weight: 500;\r\n}\r\n\r\n.clear-selection-btn {\r\n background: transparent;\r\n border: none;\r\n color: #dc2626;\r\n font-size: 0.75rem;\r\n font-weight: 500;\r\n cursor: pointer;\r\n padding: 0.25rem 0.5rem;\r\n border-radius: 4px;\r\n transition: all 0.2s ease;\r\n}\r\n\r\n.clear-selection-btn:hover {\r\n background: #fee2e2;\r\n}\r\n\r\n.multi-select-list {\r\n flex: 1;\r\n overflow-y: auto;\r\n border: 1px solid #e2e8f0;\r\n border-radius: 6px;\r\n padding: 0.5rem;\r\n max-height: 150px;\r\n}\r\n\r\n.multi-select-item {\r\n display: flex;\r\n align-items: center;\r\n gap: 0.5rem;\r\n padding: 0.5rem;\r\n cursor: pointer;\r\n border-radius: 4px;\r\n transition: all 0.2s ease;\r\n font-size: 0.875rem;\r\n color: #475569;\r\n}\r\n\r\n.multi-select-item:hover {\r\n background: #f8fafc;\r\n}\r\n\r\n.multi-select-item.selected {\r\n background: #dbeafe;\r\n color: #1e40af;\r\n font-weight: 500;\r\n}\r\n\r\n.multi-select-item input[type=\"checkbox\"] {\r\n cursor: pointer;\r\n accent-color: #3b82f6;\r\n}\r\n\r\n.multi-select-add {\r\n display: flex;\r\n gap: 0.5rem;\r\n}\r\n\r\n.filter-custom-input {\r\n flex: 1;\r\n padding: 0.5rem;\r\n border: 1px solid #e2e8f0;\r\n border-radius: 6px;\r\n background: #ffffff;\r\n color: #334155;\r\n font-size: 0.875rem;\r\n}\r\n\r\n.filter-custom-input:focus {\r\n outline: none;\r\n border-color: #3b82f6;\r\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);\r\n}\r\n\r\n.add-value-btn {\r\n padding: 0.5rem 1rem;\r\n background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);\r\n color: white;\r\n border: none;\r\n border-radius: 6px;\r\n font-size: 0.875rem;\r\n font-weight: 500;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n box-shadow: 0 2px 4px rgba(59, 130, 246, 0.2);\r\n}\r\n\r\n.add-value-btn:hover {\r\n background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);\r\n box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);\r\n transform: translateY(-1px);\r\n}\r\n\r\n.add-value-btn:active {\r\n transform: translateY(0);\r\n}\r\n\r\n/* 内容区域 */\r\n.content-area {\r\n flex: 1;\r\n overflow: hidden;\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n/* 数据视图 */\r\n.data-view {\r\n flex: 1;\r\n display: flex;\r\n flex-direction: column;\r\n overflow: hidden;\r\n}\r\n\r\n.data-table-wrapper {\r\n flex: 1;\r\n overflow: auto;\r\n background: #ffffff;\r\n}\r\n\r\n.modern-table {\r\n width: 100%;\r\n border-collapse: collapse;\r\n font-size: 0.875rem;\r\n}\r\n\r\n.table-header {\r\n background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);\r\n color: #334155;\r\n font-weight: 600;\r\n text-align: left;\r\n padding: 0.875rem 1rem;\r\n border-bottom: 2px solid #e2e8f0;\r\n position: sticky;\r\n top: 0;\r\n z-index: 10;\r\n}\r\n\r\n.header-content {\r\n display: flex;\r\n align-items: center;\r\n gap: 0.5rem;\r\n}\r\n\r\n.table-row {\r\n border-bottom: 1px solid #f1f5f9;\r\n transition: background-color 0.15s ease;\r\n}\r\n\r\n.table-row:nth-child(even) {\r\n background: #f8fafc;\r\n}\r\n\r\n.table-row:nth-child(odd) {\r\n background: #ffffff;\r\n}\r\n\r\n.table-row:hover {\r\n background: #e2e8f0 !important;\r\n}\r\n\r\n.table-cell {\r\n padding: 0.75rem 1rem;\r\n color: #475569;\r\n border-right: 1px solid #f1f5f9;\r\n white-space: nowrap;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n min-width: 100px;\r\n}\r\n\r\n.table-cell:last-child {\r\n border-right: none;\r\n}\r\n\r\n.table-footer {\r\n padding: 1rem;\r\n background: #f8fafc;\r\n border-top: 1px solid #e2e8f0;\r\n text-align: center;\r\n color: #64748b;\r\n font-size: 0.875rem;\r\n}\r\n\r\n.table-footer-content {\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n gap: 1rem;\r\n}\r\n\r\n.table-info {\r\n color: #64748b;\r\n font-size: 0.875rem;\r\n}\r\n\r\n.load-more-btn {\r\n display: flex;\r\n align-items: center;\r\n gap: 0.5rem;\r\n padding: 0.5rem 1rem;\r\n background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);\r\n color: white;\r\n border: none;\r\n border-radius: 6px;\r\n font-size: 0.875rem;\r\n font-weight: 500;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n box-shadow: 0 2px 4px rgba(59, 130, 246, 0.2);\r\n}\r\n\r\n.load-more-btn:hover {\r\n background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);\r\n box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);\r\n transform: translateY(-1px);\r\n}\r\n\r\n.load-more-btn:active {\r\n transform: translateY(0);\r\n}\r\n\r\n.load-more-btn svg {\r\n transition: transform 0.2s ease;\r\n}\r\n\r\n.load-more-btn:hover svg {\r\n transform: translateY(2px);\r\n}\r\n\r\n/* 透视表视图 */\r\n.pivot-view {\r\n flex: 1;\r\n display: flex;\r\n flex-direction: column;\r\n overflow: hidden;\r\n}\r\n\r\n.pivot-wrapper {\r\n flex: 1;\r\n overflow: auto;\r\n background: #ffffff;\r\n padding: 1.5rem;\r\n}\r\n\r\n.pivot-output {\r\n width: 100%;\r\n min-height: 400px;\r\n}\r\n\r\n/* PivotTable.js 样式优化 */\r\n.pivot-output ::v-deep .pvtUi {\r\n color: #334155;\r\n font-family: inherit;\r\n font-size: 0.875rem;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtAxisContainer,\r\n.pivot-output ::v-deep .pvtVals {\r\n border: 1px solid #e2e8f0;\r\n background: #f8fafc;\r\n padding: 0.75rem;\r\n border-radius: 8px;\r\n min-width: 120px;\r\n min-height: 60px;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtAxisContainer li {\r\n padding: 0.5rem 0.75rem;\r\n list-style-type: none;\r\n cursor: move;\r\n background: #ffffff;\r\n border: 1px solid #e2e8f0;\r\n border-radius: 6px;\r\n margin-bottom: 0.5rem;\r\n font-weight: 500;\r\n color: #334155;\r\n transition: all 0.2s ease;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtAxisContainer li:hover {\r\n border-color: #3b82f6;\r\n box-shadow: 0 2px 4px rgba(59, 130, 246, 0.1);\r\n}\r\n\r\n.pivot-output ::v-deep .pvtAxisContainer li.pvtPlaceholder {\r\n background: #f1f5f9;\r\n border: 2px dashed #cbd5e1;\r\n color: #94a3b8;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtAxisContainer li.pvtPlaceholder::before {\r\n content: \"拖放字段到这里\";\r\n font-size: 0.75rem;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtTriangle {\r\n cursor: pointer;\r\n color: #64748b;\r\n transition: color 0.2s ease;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtTriangle:hover {\r\n color: #334155;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtHorizList li {\r\n display: inline-block;\r\n margin-right: 0.5rem;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtVertList {\r\n vertical-align: top;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtFilteredAttribute {\r\n font-style: italic;\r\n color: #64748b;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtFilterBox {\r\n z-index: 1000;\r\n width: 320px;\r\n border: 1px solid #e2e8f0;\r\n background-color: #fff;\r\n position: absolute;\r\n text-align: left;\r\n padding: 1rem;\r\n border-radius: 8px;\r\n box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);\r\n}\r\n\r\n.pivot-output ::v-deep .pvtFilterBox h4 {\r\n margin: 0 0 0.75rem 0;\r\n font-size: 0.875rem;\r\n font-weight: 600;\r\n color: #334155;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtFilterBox p {\r\n margin: 0.5rem 0;\r\n font-size: 0.875rem;\r\n color: #64748b;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtFilterBox button {\r\n margin: 0.25rem;\r\n padding: 0.5rem 1rem;\r\n border: 1px solid #e2e8f0;\r\n background: #ffffff;\r\n border-radius: 6px;\r\n cursor: pointer;\r\n font-size: 0.875rem;\r\n font-weight: 500;\r\n color: #334155;\r\n transition: all 0.2s ease;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtFilterBox button:hover {\r\n background: #f8fafc;\r\n border-color: #cbd5e1;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtFilterBox label {\r\n font-size: 0.875rem;\r\n color: #475569;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtButton {\r\n color: #334155;\r\n border-radius: 6px;\r\n padding: 0.5rem 1rem;\r\n background: #ffffff;\r\n border: 1px solid #e2e8f0;\r\n cursor: pointer;\r\n font-size: 0.875rem;\r\n font-weight: 500;\r\n transition: all 0.2s ease;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtButton:hover {\r\n background: #f8fafc;\r\n border-color: #cbd5e1;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtButton.selected {\r\n background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);\r\n color: white;\r\n border-color: #3b82f6;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtTable {\r\n width: 100%;\r\n border-spacing: 0;\r\n margin: 1rem 0;\r\n border-radius: 8px;\r\n overflow: hidden;\r\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n.pivot-output ::v-deep .pvtTable thead tr th,\r\n.pivot-output ::v-deep .pvtTable tbody tr th {\r\n background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);\r\n color: #334155;\r\n font-weight: 600;\r\n border: 1px solid #e2e8f0;\r\n padding: 0.875rem 1rem;\r\n text-align: center;\r\n font-size: 0.875rem;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtTable .pvtColLabel {\r\n text-align: center;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtTable .pvtTotalLabel {\r\n text-align: right;\r\n}\r\n\r\n/* 透视表基础单元格样式 */\r\n.pivot-output ::v-deep .pvtTable tbody tr td {\r\n color: #475569;\r\n padding: 0.75rem 1rem;\r\n border: 1px solid #f1f5f9;\r\n text-align: right;\r\n vertical-align: top;\r\n font-size: 0.875rem;\r\n}\r\n\r\n/* 透视表斑马纹样式 - 必须在基础样式之后 */\r\n.pivot-output ::v-deep .pvtTable tbody tr:nth-child(even) td:not(.pvtRowLabel):not(.pvtTotal):not(.pvtGrandTotal) {\r\n background: #f8fafc;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtTable tbody tr:nth-child(odd) td:not(.pvtRowLabel):not(.pvtTotal):not(.pvtGrandTotal) {\r\n background: #ffffff;\r\n}\r\n\r\n/* 特殊单元格样式 - 覆盖斑马纹 */\r\n.pivot-output ::v-deep .pvtTable tbody tr td.pvtRowLabel {\r\n text-align: left;\r\n background: #f8fafc;\r\n font-weight: 600;\r\n color: #334155;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtTable tbody tr td.pvtTotal {\r\n background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);\r\n font-weight: 700;\r\n color: #1e40af;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtTable tbody tr td.pvtGrandTotal {\r\n background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);\r\n font-weight: 700;\r\n color: #92400e;\r\n}\r\n\r\n/* 鼠标悬停效果 - 覆盖所有其他样式 */\r\n.pivot-output ::v-deep .pvtTable tbody tr:hover td:not(.pvtRowLabel):not(.pvtTotal):not(.pvtGrandTotal) {\r\n background: #e2e8f0 !important;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtTable tbody tr:hover td.pvtRowLabel {\r\n background: #f1f5f9 !important;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtTable tbody tr:hover td.pvtTotal {\r\n background: linear-gradient(135deg, #bfdbfe 0%, #93c5fd 100%) !important;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtTable tbody tr:hover td.pvtGrandTotal {\r\n background: linear-gradient(135deg, #fde68a 0%, #fcd34d 100%) !important;\r\n}\r\n\r\n/* 确保表头不受影响 */\r\n.pivot-output ::v-deep .pvtTable thead tr th {\r\n background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%) !important;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtTotal,\r\n.pivot-output ::v-deep .pvtGrandTotal {\r\n font-weight: 700;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtVals {\r\n text-align: center;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtAggregator {\r\n margin-bottom: 0.75rem;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtAggregator select {\r\n color: #334155;\r\n border: 1px solid #e2e8f0;\r\n border-radius: 6px;\r\n padding: 0.5rem 0.75rem;\r\n font-size: 0.875rem;\r\n background: #ffffff;\r\n cursor: pointer;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtAggregator select:focus {\r\n outline: none;\r\n border-color: #3b82f6;\r\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);\r\n}\r\n\r\n.pivot-output ::v-deep .pvtRenderer {\r\n margin-bottom: 0.75rem;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtRenderer select {\r\n color: #334155;\r\n border: 1px solid #e2e8f0;\r\n border-radius: 6px;\r\n padding: 0.5rem 0.75rem;\r\n font-size: 0.875rem;\r\n background: #ffffff;\r\n cursor: pointer;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtRenderer select:focus {\r\n outline: none;\r\n border-color: #3b82f6;\r\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);\r\n}\r\n\r\n.pivot-output ::v-deep .pvtAttrDropdown {\r\n display: inline-block;\r\n padding: 0.5rem 0.75rem;\r\n border: 1px solid #e2e8f0;\r\n border-radius: 6px;\r\n background: #fff;\r\n margin: 0.25rem;\r\n font-size: 0.875rem;\r\n color: #334155;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtAttrDropdown:hover {\r\n border-color: #cbd5e1;\r\n background: #f8fafc;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtAttrDropdown::after {\r\n content: \"▾\";\r\n margin-left: 0.5rem;\r\n color: #64748b;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtCheckContainer {\r\n text-align: left;\r\n font-size: 0.875rem;\r\n max-height: 250px;\r\n overflow: auto;\r\n padding: 0.5rem;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtCheckContainer p {\r\n margin: 0.5rem 0;\r\n color: #64748b;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtCheckContainer label {\r\n display: block;\r\n padding: 0.5rem;\r\n cursor: pointer;\r\n border-radius: 4px;\r\n transition: background-color 0.15s ease;\r\n color: #475569;\r\n}\r\n\r\n.pivot-output ::v-deep .pvtCheckContainer label:hover {\r\n background: #f8fafc;\r\n}\r\n\r\n/* 折叠按钮 */\r\n.collapse-btn {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 28px;\r\n height: 28px;\r\n min-width: 28px;\r\n min-height: 28px;\r\n border: 1px solid #e2e8f0;\r\n background: #ffffff;\r\n color: #64748b;\r\n border-radius: 4px;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n flex-shrink: 0;\r\n padding: 0;\r\n}\r\n\r\n.collapse-btn:hover {\r\n background: #f1f5f9;\r\n color: #334155;\r\n border-color: #cbd5e1;\r\n}\r\n\r\n.collapse-btn svg {\r\n transition: transform 0.2s ease;\r\n}\r\n\r\n.collapse-btn svg.rotated {\r\n transform: rotate(180deg);\r\n}\r\n\r\n/* 搜索框 */\r\n.multi-select-search {\r\n display: flex;\r\n align-items: center;\r\n gap: 0.5rem;\r\n padding: 0.5rem;\r\n background: #ffffff;\r\n border: 1px solid #e2e8f0;\r\n border-radius: 6px;\r\n}\r\n\r\n.search-input {\r\n flex: 1;\r\n padding: 0.5rem;\r\n border: none;\r\n background: transparent;\r\n color: #334155;\r\n font-size: 0.875rem;\r\n outline: none;\r\n}\r\n\r\n.search-input::placeholder {\r\n color: #94a3b8;\r\n}\r\n\r\n.clear-search-btn {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 20px;\r\n height: 20px;\r\n border: none;\r\n background: transparent;\r\n color: #94a3b8;\r\n border-radius: 4px;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n}\r\n\r\n.clear-search-btn:hover {\r\n background: #f1f5f9;\r\n color: #64748b;\r\n}\r\n\r\n/* 无结果提示 */\r\n.no-results {\r\n padding: 1rem;\r\n text-align: center;\r\n color: #94a3b8;\r\n font-size: 0.875rem;\r\n font-style: italic;\r\n}\r\n\r\n/* 响应式设计 */\r\n@media (max-width: 1024px) {\r\n .side-panel {\r\n width: 280px;\r\n }\r\n \r\n .fields-grid {\r\n grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));\r\n }\r\n}\r\n\r\n@media (max-width: 768px) {\r\n .toolbar {\r\n flex-direction: column;\r\n gap: 1rem;\r\n align-items: stretch;\r\n }\r\n \r\n .toolbar-left,\r\n .toolbar-right {\r\n justify-content: center;\r\n }\r\n \r\n .side-panel {\r\n width: 100%;\r\n border-right: none;\r\n border-bottom: 1px solid #e2e8f0;\r\n }\r\n \r\n .main-content {\r\n flex-direction: column;\r\n }\r\n}\r\n</style>\r\n","<script lang=\"ts\">\r\nimport Vue from 'vue'\r\nimport PivotTable from './PivotTable.vue'\r\n\r\nexport default Vue.extend({\r\n name: 'OnlineAnalysisButton',\r\n components: {\r\n PivotTable\r\n },\r\n data() {\r\n return {\r\n showModal: false,\r\n tableData: [] as any[],\r\n buttonPosition: 'bottom-right' as 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'\r\n }\r\n },\r\n props: {\r\n // 按钮位置\r\n position: {\r\n type: String as () => 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left',\r\n default: 'bottom-right'\r\n },\r\n // 按钮文本\r\n buttonText: {\r\n type: String,\r\n default: '📊 透视分析'\r\n },\r\n // 按钮样式类名\r\n buttonClass: {\r\n type: String,\r\n default: ''\r\n },\r\n // 自定义 el-table 选择器\r\n tableSelector: {\r\n type: String,\r\n default: '.el-table'\r\n },\r\n // 是否自动提取数据\r\n autoExtract: {\r\n type: Boolean,\r\n default: true\r\n }\r\n },\r\n mounted() {\r\n this.buttonPosition = this.position\r\n },\r\n methods: {\r\n openAnalysis() {\r\n if (this.autoExtract) {\r\n this.extractTableData()\r\n }\r\n this.showModal = true\r\n },\r\n \r\n closeAnalysis() {\r\n this.showModal = false\r\n },\r\n \r\n extractTableData() {\r\n try {\r\n // 查找页面中的 Element UI el-table 组件\r\n const elTables = document.querySelectorAll(this.tableSelector)\r\n \r\n if (elTables.length === 0) {\r\n console.warn('未找到 Element UI el-table 组件')\r\n this.tableData = []\r\n return\r\n }\r\n \r\n // 提取第一个 el-table 的数据\r\n const elTable = elTables[0]\r\n const data = this.parseElTable(elTable)\r\n \r\n console.log('提取到的 el-table 数据:', data)\r\n this.tableData = data\r\n } catch (error) {\r\n console.error('提取 el-table 数据失败:', error)\r\n this.tableData = []\r\n }\r\n },\r\n \r\n parseElTable(elTable: Element): any[] {\r\n const result: any[] = []\r\n \r\n try {\r\n // 获取表头\r\n const headers: string[] = []\r\n const headerCells = elTable.querySelectorAll('.el-table__header-wrapper thead tr th .cell')\r\n \r\n if (headerCells.length === 0) {\r\n console.warn('未找到表头')\r\n return []\r\n }\r\n \r\n headerCells.forEach(cell => {\r\n const text = cell.textContent?.trim() || ''\r\n if (text) {\r\n headers.push(text)\r\n }\r\n })\r\n \r\n console.log('提取到的表头:', headers)\r\n \r\n // 获取数据行\r\n const bodyRows = elTable.querySelectorAll('.el-table__body-wrapper tbody tr')\r\n \r\n bodyRows.forEach(row => {\r\n const cells = row.querySelectorAll('td .cell')\r\n \r\n if (cells.length === 0) return\r\n \r\n const rowData: any = {}\r\n cells.forEach((cell, index) => {\r\n if (index < headers.length) {\r\n const header = headers[index]\r\n let value = cell.textContent?.trim() || ''\r\n \r\n // 尝试转换为数字\r\n if (value !== '' && !isNaN(Number(value))) {\r\n value = Number(value)\r\n }\r\n \r\n rowData[header] = value\r\n }\r\n })\r\n \r\n // 只添加有数据的行\r\n if (Object.keys(rowData).length > 0) {\r\n result.push(rowData)\r\n }\r\n })\r\n \r\n console.log(`提取到 ${result.length} 行数据`)\r\n \r\n } catch (error) {\r\n console.error('解析 el-table 失败:', error)\r\n }\r\n \r\n return result\r\n },\r\n \r\n // 外部调用的方法:手动设置数据\r\n setData(data: any[]) {\r\n this.tableData = data\r\n },\r\n \r\n // 外部调用的方法:获取当前数据\r\n getData(): any[] {\r\n return this.tableData\r\n }\r\n }\r\n})\r\n</script>\r\n\r\n<template>\r\n <div class=\"online-analysis-button\">\r\n <!-- 分析按钮 -->\r\n <button \r\n :class=\"['analysis-button', buttonPosition, buttonClass]\"\r\n @click=\"openAnalysis\"\r\n :title=\"'点击进行透视分析'\"\r\n >\r\n {{ buttonText }}\r\n </button>\r\n \r\n <!-- 模态框 -->\r\n <transition name=\"modal\">\r\n <div v-if=\"showModal\" class=\"modal-overlay\" @click.self=\"closeAnalysis\">\r\n <div class=\"modal-content\">\r\n <!-- 模态框头部 -->\r\n <div class=\"modal-header\">\r\n <h2 class=\"modal-title\">📊 数据透视分析</h2>\r\n <button class=\"close-button\" @click=\"closeAnalysis\" title=\"关闭\">\r\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>\r\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\r\n </svg>\r\n </button>\r\n </div>\r\n \r\n <!-- 模态框内容 -->\r\n <div class=\"modal-body\">\r\n <PivotTable :data=\"tableData\" />\r\n </div>\r\n </div>\r\n </div>\r\n </transition>\r\n </div>\r\n</template>\r\n\r\n<style scoped>\r\n/* 按钮容器 */\r\n.online-analysis-button {\r\n position: relative;\r\n display: inline-block;\r\n}\r\n\r\n/* 分析按钮 */\r\n.analysis-button {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 0.5rem;\r\n padding: 0.75rem 1.5rem;\r\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\r\n color: white;\r\n border: none;\r\n border-radius: 8px;\r\n font-size: 1rem;\r\n font-weight: 600;\r\n cursor: pointer;\r\n transition: all 0.3s ease;\r\n box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);\r\n white-space: nowrap;\r\n z-index: 1000;\r\n}\r\n\r\n.analysis-button:hover {\r\n transform: translateY(-2px);\r\n box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);\r\n}\r\n\r\n.analysis-button:active {\r\n transform: translateY(0);\r\n}\r\n\r\n/* 按钮位置 */\r\n.analysis-button.bottom-right {\r\n position: fixed;\r\n bottom: 2rem;\r\n right: 2rem;\r\n}\r\n\r\n.analysis-button.bottom-left {\r\n position: fixed;\r\n bottom: 2rem;\r\n left: 2rem;\r\n}\r\n\r\n.analysis-button.top-right {\r\n position: fixed;\r\n top: 2rem;\r\n right: 2rem;\r\n}\r\n\r\n.analysis-button.top-left {\r\n position: fixed;\r\n top: 2rem;\r\n left: 2rem;\r\n}\r\n\r\n/* 模态框遮罩 */\r\n.modal-overlay {\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n right: 0;\r\n bottom: 0;\r\n background: rgba(0, 0, 0, 0.6);\r\n backdrop-filter: blur(4px);\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n z-index: 9999;\r\n padding: 1rem;\r\n}\r\n\r\n/* 模态框内容 */\r\n.modal-content {\r\n background: white;\r\n border-radius: 12px;\r\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);\r\n width: 100%;\r\n max-width: 95vw;\r\n height: 90vh;\r\n display: flex;\r\n flex-direction: column;\r\n overflow: hidden;\r\n animation: modalSlideIn 0.3s ease;\r\n}\r\n\r\n@keyframes modalSlideIn {\r\n from {\r\n opacity: 0;\r\n transform: scale(0.95) translateY(-20px);\r\n }\r\n to {\r\n opacity: 1;\r\n transform: scale(1) translateY(0);\r\n }\r\n}\r\n\r\n/* 模态框头部 */\r\n.modal-header {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n padding: 1.25rem 1.5rem;\r\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\r\n color: white;\r\n border-bottom: 1px solid #e2e8f0;\r\n flex-shrink: 0;\r\n}\r\n\r\n.modal-title {\r\n margin: 0;\r\n font-size: 1.25rem;\r\n font-weight: 600;\r\n display: flex;\r\n align-items: center;\r\n gap: 0.5rem;\r\n}\r\n\r\n.close-button {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 36px;\r\n height: 36px;\r\n border: none;\r\n background: rgba(255, 255, 255, 0.2);\r\n color: white;\r\n border-radius: 8px;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n flex-shrink: 0;\r\n}\r\n\r\n.close-button:hover {\r\n background: rgba(255, 255, 255, 0.3);\r\n transform: rotate(90deg);\r\n}\r\n\r\n.close-button:active {\r\n transform: rotate(90deg) scale(0.95);\r\n}\r\n\r\n/* 模态框主体 */\r\n.modal-body {\r\n flex: 1;\r\n overflow: hidden;\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n/* 模态框过渡动画 */\r\n.modal-enter-active,\r\n.modal-leave-active {\r\n transition: opacity 0.3s ease;\r\n}\r\n\r\n.modal-enter,\r\n.modal-leave-to {\r\n opacity: 0;\r\n}\r\n\r\n.modal-enter-active .modal-content,\r\n.modal-leave-active .modal-content {\r\n transition: transform 0.3s ease, opacity 0.3s ease;\r\n}\r\n\r\n.modal-enter .modal-content,\r\n.modal-leave-to .modal-content {\r\n transform: scale(0.95) translateY(-20px);\r\n opacity: 0;\r\n}\r\n\r\n/* 响应式设计 */\r\n@media (max-width: 768px) {\r\n .analysis-button {\r\n padding: 0.625rem 1.25rem;\r\n font-size: 0.875rem;\r\n }\r\n \r\n .analysis-button.bottom-right,\r\n .analysis-button.bottom-left {\r\n bottom: 1rem;\r\n right: 1rem;\r\n }\r\n \r\n .analysis-button.bottom-left {\r\n right: auto;\r\n left: 1rem;\r\n }\r\n \r\n .modal-content {\r\n height: 95vh;\r\n max-width: 100vw;\r\n border-radius: 0;\r\n }\r\n \r\n .modal-header {\r\n padding: 1rem;\r\n }\r\n \r\n .modal-title {\r\n font-size: 1rem;\r\n }\r\n}\r\n</style>","import Vue from 'vue'\r\nimport OnlineAnalysisButton from './components/OnlineAnalysisButton.vue'\r\nimport PivotTable from './components/PivotTable.vue'\r\n\r\n// 导出组件\r\nexport { OnlineAnalysisButton, PivotTable }\r\n\r\n// 导出类型\r\nexport interface PivotData {\r\n [key: string]: any\r\n}\r\n\r\n// Vue 插件安装方法\r\nconst install = function(vue: typeof Vue) {\r\n if (install.installed) return\r\n install.installed = true\r\n \r\n vue.component('OnlineAnalysisButton', OnlineAnalysisButton)\r\n vue.component('PivotTable', PivotTable)\r\n}\r\n\r\n// 自动安装\r\nif (typeof window !== 'undefined' && window.Vue) {\r\n install(window.Vue)\r\n}\r\n\r\n// 默认导出\r\nexport default {\r\n install,\r\n OnlineAnalysisButton,\r\n PivotTable\r\n}\r\n\r\n// 声明安装状态\r\ndeclare const install: {\r\n installed: boolean\r\n (vue: typeof Vue): void\r\n}"],"names":["Vue","extend","name","props","data","type","Array","default","showPivotTable","filters","showFilters","pivotInitialized","fileInput","isLoading","errorMessage","isFullscreen","showExportMenu","sidePanelCollapsed","filterSearchQuery","expandedFilters","Set","displayedRows","computed","hasData","this","length","columns","Object","keys","filteredData","filter","item","every","itemValue","field","filterValue","value","filterValues","values","operator","String","toLowerCase","includes","Number","uniqueFieldValues","result","forEach","col","map","from","sort","watch","newVal","$nextTick","initializePivotTable","deep","handler","newData","oldData","hasDataChanged","updatePivotTable","resetDisplayedRows","methods","pivotElement","$refs","pivotOutput","$","empty","pivotUI","rows","cols","vals","aggregatorName","rendererName","renderers","pivotUtilities","c3_renderers","d3_renderers","export_renderers","hiddenAttributes","menuLimit","colsLimit","rowsLimit","unusedAttrsVertical","autoSortUnusedAttrs","onRefresh","config","error","addFilter","newIndex","push","add","removeFilter","index","splice","clearFilters","getOperatorLabel","equals","not_equals","contains","not_contains","greater_than","less_than","greater_equal","less_equal","in","not_in","toggleMultiSelectValue","$set","indexOf","$forceUpdate","isMultiSelectValueSelected","addCustomValue","customValue","trim","handleFileUpload","event","target","file","files","fileExtension","split","pop","processCSV","Error","processExcel","message","reader","FileReader","onload","e","text","parseCSV","$emit","onerror","readAsText","lines","line","headers","parseCSVLine","i","row","header","current","inQuotes","char","workbook","XLSX","read","firstSheetName","SheetNames","worksheet","Sheets","jsonData","utils","sheet_to_json","slice","some","cell","obj","isNaN","readAsBinaryString","triggerFileInput","input","click","newKeys","oldKeys","key","toggleFullscreen","document","body","style","overflow","classList","remove","exportPivotTableToExcel","alert","table","querySelector","querySelectorAll","rowData","colSpan","parseInt","getAttribute","textContent","book_new","aoa_to_sheet","colWidths","wch","book_append_sheet","now","Date","timestamp","toISOString","replace","filename","toTimeString","writeFile","exportRawDataToExcel","json_to_sheet","exportPivotTableToHTML","htmlContent","toLocaleString","outerHTML","blob","Blob","url","URL","createObjectURL","link","createElement","href","download","appendChild","removeChild","revokeObjectURL","exportRawDataToHTML","tableHTML","toggleSidePanel","toggleFilterCollapse","has","delete","isFilterExpanded","getFilteredUniqueValues","val","clearFilterSearch","loadMoreData","components","PivotTable","showModal","tableData","buttonPosition","position","buttonText","buttonClass","tableSelector","autoExtract","Boolean","mounted","openAnalysis","extractTableData","closeAnalysis","elTables","elTable","parseElTable","headerCells","cells","setData","getData","install","vue","installed","component","OnlineAnalysisButton","window"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "online-analysis-button",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "一个适配所有表格的 Vue 2 组件,可以自动提取表格数据并进行透视分析,支持 Element UI el-table 等多种表格组件",
|
|
5
|
+
"main": "dist/online-analysis-button.cjs.js",
|
|
6
|
+
"module": "dist/online-analysis-button.es.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "vue-tsc --noEmit && vite build",
|
|
13
|
+
"build:lib": "vite build",
|
|
14
|
+
"preview": "vite preview",
|
|
15
|
+
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"vue",
|
|
19
|
+
"vue2",
|
|
20
|
+
"pivot-table",
|
|
21
|
+
"data-analysis",
|
|
22
|
+
"table",
|
|
23
|
+
"pivot",
|
|
24
|
+
"excel",
|
|
25
|
+
"csv",
|
|
26
|
+
"data-visualization",
|
|
27
|
+
"element-ui",
|
|
28
|
+
"el-table",
|
|
29
|
+
"data-pivot",
|
|
30
|
+
"business-intelligence",
|
|
31
|
+
"analytics",
|
|
32
|
+
"reporting"
|
|
33
|
+
],
|
|
34
|
+
"author": "wuzhongyou <453474785@qq.com>",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"bugs": {
|
|
37
|
+
"email": "453474785@qq.com"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"vue": "^2.6.0 || ^2.7.0"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"bootstrap": "^4.6.2",
|
|
44
|
+
"jquery": "^3.7.1",
|
|
45
|
+
"jquery-ui": "^1.14.2",
|
|
46
|
+
"pivottable": "^2.23.0",
|
|
47
|
+
"vuedraggable": "^2.24.3",
|
|
48
|
+
"xlsx": "^0.18.5"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/jquery": "^4.0.0",
|
|
52
|
+
"@typescript-eslint/eslint-plugin": "^8.59.4",
|
|
53
|
+
"@typescript-eslint/parser": "^8.59.4",
|
|
54
|
+
"@vitejs/plugin-vue2": "^2.3.1",
|
|
55
|
+
"@vue/tsconfig": "^0.5.0",
|
|
56
|
+
"esbuild": "^0.28.0",
|
|
57
|
+
"eslint": "^8.56.0",
|
|
58
|
+
"eslint-plugin-vue": "^9.20.1",
|
|
59
|
+
"terser": "^5.48.0",
|
|
60
|
+
"typescript": "~5.3.0",
|
|
61
|
+
"vite": "^7.3.3",
|
|
62
|
+
"vue": "^2.7.16",
|
|
63
|
+
"vue-tsc": "^3.3.1"
|
|
64
|
+
}
|
|
65
|
+
}
|