From 53b7d11ad98b2e07387417d3ca0e573e698d04d9 Mon Sep 17 00:00:00 2001 From: chenxu <499889298@qq.com> Date: Wed, 29 Apr 2026 09:54:27 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E5=89=8D=E7=AB=AF=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- GZIP_COMPRESSION_GUIDE.md | 244 ++++ config/dev.env.js | 4 +- package.json | 12 +- src/api/bigscreen.js | 34 +- src/api/dict-data.js | 2 +- src/api/login/index.js | 2 +- src/api/upload.js | 4 +- src/assets/images/one.png | Bin 0 -> 831 bytes src/assets/images/three.png | Bin 0 -> 649 bytes src/assets/images/two.png | Bin 0 -> 836 bytes src/components/AnjiPlus/anji-autocomplete.vue | 2 +- src/components/AnjiPlus/anji-countryCity.vue | 4 +- .../AnjiPlus/anji-crud/anji-crud.vue | 2 +- .../AnjiPlus/anji-crud/edit-form.vue | 6 +- src/components/AnjiPlus/anji-crud/edit.vue | 40 +- src/components/AnjiPlus/anji-upload.vue | 10 +- src/components/Dictionary/index.vue | 2 +- src/components/FlvVideo.vue | 2 +- src/components/configForm.vue | 6 +- src/components/eachForm.vue | 4 +- src/main.js | 4 +- src/mixins/queryform.js | 43 +- src/store/modules/designer.js | 8 +- src/utils/compress.js | 112 ++ src/utils/revoke.js | 11 + src/utils/screen.js | 2 +- src/utils/screenMixins.js | 94 +- .../accessRole/components/RoleAuthority.vue | 2 +- .../designer/components/componentLinkage.vue | 90 +- .../designer/components/customUpload.vue | 2 +- .../designer/components/drillDrownSetting.vue | 42 +- .../designer/components/dynamicComponents.vue | 15 +- .../designer/components/dynamicForm.vue | 59 +- .../designer/components/imageSelect.vue | 4 +- .../bigscreenDesigner/designer/index.vue | 285 +++- .../designer/linkageLogic.js | 153 ++- .../tools/configure/form/widget-form-time.js | 2 + .../scatterCharts/widget-quadrant-scatter.js | 1198 ++++++++++++++++ .../configure/texts/widget-pagination.js | 260 ++++ .../tools/configure/texts/widget-table.js | 133 +- .../bigscreenDesigner/designer/tools/main.js | 4 + .../widget/bar/widgetBarCompareChart.vue | 7 + .../widget/bar/widgetBarDoubleYaxisChart.vue | 7 + .../widget/bar/widgetBarStackChart.vue | 7 + .../bar/widgetBarStackMoreShowChart.vue | 7 + .../designer/widget/bar/widgetBarchart.vue | 7 + .../bar/widgetGradientColorBarchart.vue | 7 + .../barline/widgetBarLineSingleChart.vue | 7 + .../barline/widgetBarLineStackChart.vue | 7 + .../widget/barline/widgetBarlinechart.vue | 22 +- .../designer/widget/form/widgetButton.vue | 75 +- .../widget/form/widgetFormTime copy.vue | 128 ++ .../designer/widget/form/widgetFormTime.vue | 47 +- .../designer/widget/form/widgetSelect.vue | 7 + .../designer/widget/form/widgetTabs copy.vue | 876 ++++++++++++ .../designer/widget/form/widgetTabs.vue | 543 +++++++- .../designer/widget/funnel/widgetFunnel.vue | 7 + .../designer/widget/heatmap/widgetHeatmap.vue | 7 + .../widget/line/widgetLineCompareChart.vue | 7 + .../widget/line/widgetLineStackChart.vue | 7 + .../designer/widget/line/widgetLinechart.vue | 21 +- .../widget/map/widgetAirBubbleMap.vue | 21 + .../designer/widget/map/widgetBarMap.vue | 21 + .../designer/widget/map/widgetChinaMap.vue | 9 +- .../designer/widget/map/widgetGlobalMap.vue | 11 +- .../designer/widget/map/widgetLineMap.vue | 21 + .../designer/widget/percent/widgetGauge.vue | 7 + .../percent/widgetPiePercentageChart.vue | 7 + .../widget/pie/widgetPieNightingaleRose.vue | 7 + .../designer/widget/pie/widgetPiechart.vue | 7 + .../widget/scale/widgetScaleHorizontal.vue | 7 + .../widget/scale/widgetScaleVertical.vue | 7 + .../widget/scatter/widgetQuadrantScatter.vue | 1181 ++++++++++++++++ .../designer/widget/temp.vue | 10 +- .../designer/widget/texts/widgetCalendar.vue | 26 +- .../designer/widget/texts/widgetImage.vue | 2 +- .../designer/widget/texts/widgetMarquee.vue | 12 +- .../widget/texts/widgetPagination.vue | 412 ++++++ .../widget/texts/widgetTable copy 2.vue | 1224 +++++++++++++++++ .../widget/texts/widgetTable copy.vue | 713 ++++++++++ .../designer/widget/texts/widgetTable.vue | 1119 +++++++++++++-- .../designer/widget/texts/widgetText.vue | 7 +- .../designer/widget/texts/widgetTime.vue | 25 +- .../designer/widget/widget.vue | 8 +- .../widget/wordcloud/widgetWordCloud.vue | 7 + .../viewer/drillDrownView.vue | 11 + src/views/bigscreenDesigner/viewer/index.vue | 213 ++- src/views/excelreport/designer/drag.js | 2 +- src/views/excelreport/designer/index.vue | 8 +- src/views/layout/components/Navbar.vue | 4 +- src/views/login.vue | 28 +- .../reportManage/components/copyDialog.vue | 2 +- src/views/reportManage/index.vue | 2 +- .../resultset/components/EditDataSet.vue | 2 +- .../screenDesigner/components/contentMenu.vue | 2 +- .../components/customUpload.vue | 2 +- .../components/dynamicComponents.vue | 15 +- .../screenDesigner/components/dynamicForm.vue | 60 +- src/views/screenDesigner/index备份.vue | 4 +- .../screenDesigner/layout/middleScreen.vue | 2 +- src/views/screenDesigner/util/screen.js | 2 +- .../widget/texts/widgetTable.vue | 30 +- .../widget/texts/widgetTime.vue | 2 +- 103 files changed, 9437 insertions(+), 531 deletions(-) create mode 100644 GZIP_COMPRESSION_GUIDE.md create mode 100644 src/assets/images/one.png create mode 100644 src/assets/images/three.png create mode 100644 src/assets/images/two.png create mode 100644 src/utils/compress.js create mode 100644 src/views/bigscreenDesigner/designer/tools/configure/scatterCharts/widget-quadrant-scatter.js create mode 100644 src/views/bigscreenDesigner/designer/tools/configure/texts/widget-pagination.js create mode 100644 src/views/bigscreenDesigner/designer/widget/form/widgetFormTime copy.vue create mode 100644 src/views/bigscreenDesigner/designer/widget/form/widgetTabs copy.vue create mode 100644 src/views/bigscreenDesigner/designer/widget/scatter/widgetQuadrantScatter.vue create mode 100644 src/views/bigscreenDesigner/designer/widget/texts/widgetPagination.vue create mode 100644 src/views/bigscreenDesigner/designer/widget/texts/widgetTable copy 2.vue create mode 100644 src/views/bigscreenDesigner/designer/widget/texts/widgetTable copy.vue diff --git a/GZIP_COMPRESSION_GUIDE.md b/GZIP_COMPRESSION_GUIDE.md new file mode 100644 index 0000000..876db35 --- /dev/null +++ b/GZIP_COMPRESSION_GUIDE.md @@ -0,0 +1,244 @@ +# 大屏保存 Gzip 压缩 - 后端处理示例 + +## 前端实现说明 + +当前端保存数据超过 100KB 时,会自动启用 Gzip 压缩,请求格式如下: + +### 压缩数据请求格式 +```json +{ + "compressed": true, + "content": "" +} +``` + +### 请求头 +``` +Content-Encoding: gzip +X-Data-Original-Size: 123456 +X-Data-Compressed-Size: 34567 +Authorization: +``` + +## Java Spring Boot 后端处理示例 + +### Controller 层 +```java +@PostMapping("/reportDashboard") +public Result saveDashboard(@RequestBody Map params, + @RequestHeader(value = "Content-Encoding", required = false) String encoding, + @RequestHeader(value = "X-Data-Original-Size", required = false) Integer originalSize, + @RequestHeader(value = "X-Data-Compressed-Size", required = false) Integer compressedSize) { + + try { + DashboardData dashboardData; + + // 判断是否为压缩数据 + if ("gzip".equalsIgnoreCase(encoding) && params.containsKey("compressed")) { + Boolean isCompressed = (Boolean) params.get("compressed"); + if (isCompressed != null && isCompressed) { + String compressedContent = (String) params.get("content"); + // 解压数据 + dashboardData = gzipDecompress(compressedContent); + + log.info("接收压缩数据:原始 {} KB -> 压缩后 {} KB", + originalSize != null ? originalSize / 1024.0 : 0, + compressedSize != null ? compressedSize / 1024.0 : 0); + } else { + // 未压缩数据直接转换 + dashboardData = convertToDashboard(params); + } + } else { + // 普通数据直接处理 + dashboardData = convertToDashboard(params); + } + + // 保存业务逻辑 + return dashboardService.save(dashboardData); + + } catch (Exception e) { + log.error("保存大屏失败", e); + return Result.error("保存失败:" + e.getMessage()); + } +} + +/** + * Gzip 解压工具方法 + */ +private DashboardData gzipDecompress(String base64Data) throws IOException { + // Base64 解码 + byte[] compressed = Base64.getDecoder().decode(base64Data); + + // Gzip 解压 + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (GZIPInputStream in = new GZIPInputStream(new ByteArrayInputStream(compressed))) { + byte[] buffer = new byte[1024]; + int len; + while ((len = in.read(buffer)) > 0) { + out.write(buffer, 0, len); + } + } + + // 转回字符串 + String json = out.toString("UTF-8"); + + // 解析为对象 + return JSON.parseObject(json, DashboardData.class); +} + +/** + * 转换为 Dashboard 对象 + */ +private DashboardData convertToDashboard(Map params) { + // 根据实际业务对象转换 + DashboardData data = new DashboardData(); + data.setReportCode((String) params.get("reportCode")); + // ... 其他字段转换 + return data; +} +``` + +### 实体类示例 +```java +@Data +public class DashboardData { + private String reportCode; + private DashboardInfo dashboard; + private List widgets; + + @Data + public static class DashboardInfo { + private String title; + private String width; + private String height; + private String backgroundColor; + private String backgroundImage; + private Integer refreshSeconds; + } + + @Data + public static class WidgetInfo { + private String type; + private WidgetValue value; + + @Data + public static class WidgetValue { + private SetupInfo setup; + private PositionInfo position; + private DataInfo data; + } + } +} +``` + +## Node.js 后端处理示例 + +```javascript +const zlib = require('zlib'); +const util = require('util'); +const gunzip = util.promisify(zlib.gunzip); + +app.post('/reportDashboard', async (req, res) => { + try { + let dashboardData; + const encoding = req.headers['content-encoding']; + + if (encoding === 'gzip' && req.body.compressed) { + // 解压数据 + const compressedBuffer = Buffer.from(req.body.content, 'base64'); + const uncompressedBuffer = await gunzip(compressedBuffer); + const jsonString = uncompressedBuffer.toString('utf8'); + dashboardData = JSON.parse(jsonString); + + console.log(`接收压缩数据:原始 ${req.headers['x-data-original-size'] / 1024} KB -> 压缩后 ${req.headers['x-data-compressed-size'] / 1024} KB`); + } else { + // 普通数据 + dashboardData = req.body; + } + + // 保存业务逻辑 + await saveDashboard(dashboardData); + + res.json({ code: '200', message: '保存成功' }); + } catch (error) { + console.error('保存失败:', error); + res.status(500).json({ code: '500', message: '保存失败' }); + } +}); +``` + +## Python Flask 处理示例 + +```python +import gzip +import base64 +import json +from flask import request, jsonify + +@app.route('/reportDashboard', methods=['POST']) +def save_dashboard(): + try: + data = request.get_json() + encoding = request.headers.get('Content-Encoding') + + if encoding == 'gzip' and data.get('compressed'): + # 解压数据 + compressed_data = base64.b64decode(data['content']) + decompressed_data = gzip.decompress(compressed_data) + dashboard_data = json.loads(decompressed_data) + + original_size = request.headers.get('X-Data-Original-Size') + compressed_size = request.headers.get('X-Data-Compressed-Size') + print(f"接收压缩数据:原始 {int(original_size)/1024:.2f} KB -> 压缩后 {int(compressed_size)/1024:.2f} KB") + else: + dashboard_data = data + + # 保存业务逻辑 + result = save_to_database(dashboard_data) + + return jsonify({'code': '200', 'message': '保存成功'}) + except Exception as e: + print(f"保存失败:{e}") + return jsonify({'code': '500', 'message': '保存失败'}), 500 +``` + +## Nginx 配置建议 + +如果使用了 Nginx 反向代理,需要调整请求体大小限制: + +```nginx +server { + # ... 其他配置 + + # 允许最大请求体大小(根据实际需求调整) + client_max_body_size 50M; + + # 超时时间设置 + client_body_timeout 120s; + proxy_read_timeout 120s; +} +``` + +## 测试验证 + +### 使用 curl 测试压缩数据 +```bash +# 构造测试数据 +DATA='{"compressed":true,"content":"H4sIAAAAAAAAA..."}' + +# 发送请求 +curl -X POST http://localhost:8080/reportDashboard \ + -H "Content-Type: application/json" \ + -H "Content-Encoding: gzip" \ + -H "X-Data-Original-Size: 123456" \ + -H "X-Data-Compressed-Size: 34567" \ + -d "$DATA" +``` + +## 注意事项 + +1. **字符编码**:确保前后端都使用 UTF-8 编码 +2. **Base64 安全**:考虑使用 URL安全的 Base64 编码变体 +3. **错误处理**:解压失败时返回明确的错误信息 +4. **日志记录**:记录压缩/解压的大小信息便于排查问题 +5. **性能监控**:监控解压耗时,避免性能瓶颈 diff --git a/config/dev.env.js b/config/dev.env.js index c1d675c..28237ff 100644 --- a/config/dev.env.js +++ b/config/dev.env.js @@ -4,8 +4,8 @@ const prodEnv = require('./prod.env') module.exports = merge(prodEnv, { NODE_ENV: '"development"', - BASE_API: '"http://127.0.0.1:48090"' + // BASE_API: '"http://127.0.0.1:48090"' - // BASE_API: '"http://192.168.1.241:8080/prod-api"' + BASE_API: '"http://192.168.1.241:8080/prod-api"' }) diff --git a/package.json b/package.json index d18c1c7..474b802 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,12 @@ "description": "report-ui", "author": "beliefteam", "scripts": { - "dev": "SET NODE_OPTIONS=--openssl-legacy-provider && webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", + "dev": "cross-env NODE_ENV=development webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", "start": "npm run dev", - "build": "SET NODE_OPTIONS=--openssl-legacy-provider && cross-env NODE_ENV=production node build/build.js", - "build:dev": "SET NODE_OPTIONS=--openssl-legacy-provider && cross-env NODE_ENV=development node build/build.js", - "build:test": "SET NODE_OPTIONS=--openssl-legacy-provider && cross-env NODE_ENV=testing node build/build.js", - "build:prod": "SET NODE_OPTIONS=--openssl-legacy-provider && cross-env NODE_ENV=production node build/build.js" + "build": "cross-env NODE_ENV=production node build/build.js", + "build:dev": "cross-env NODE_ENV=development node build/build.js", + "build:test": "cross-env NODE_ENV=testing node build/build.js", + "build:prod": "cross-env NODE_ENV=production node build/build.js" }, "dependencies": { "@ckeditor/ckeditor5-build-decoupled-document": "^23.1.0", @@ -19,6 +19,7 @@ "chokidar": "^3.5.2", "codemirror": "^5.58.1", "crypto-js": "^3.1.9-1", + "decimal.js": "^10.6.0", "echarts": "^5.5.1", "echarts-gl": "^2.0.9", "echarts-liquidfill": "^3.1.0", @@ -32,6 +33,7 @@ "monaco-editor": "^0.28.0", "normalize.css": "7.0.0", "nprogress": "0.2.0", + "pako": "^2.1.0", "qrcodejs2": "0.0.2", "sortablejs": "^1.10.2", "uninstall": "0.0.0", diff --git a/src/api/bigscreen.js b/src/api/bigscreen.js index 69555fc..b0a7881 100644 --- a/src/api/bigscreen.js +++ b/src/api/bigscreen.js @@ -1,13 +1,45 @@ import request from '@/utils/request' import { getShareToken, getToken } from "@/utils/auth"; import axios from 'axios'; +import { gzipCompress } from '@/utils/compress'; // 保存大屏设计 export function insertDashboard(data) { + // 计算原始数据大小 + const originalSize = JSON.stringify(data).length + console.log('[API] insertDashboard - 原始数据大小:', originalSize); + + // 始终启用压缩 + console.log('[API] 启用 Gzip 压缩...'); + const compressedData = gzipCompress(data) + + if (!compressedData) { + console.error('[API] 压缩失败,compressedData 为 null'); + // 压缩失败时,尝试不压缩发送 + return request({ + url: 'reportDashboard', + method: 'post', + data, + timeout: 120000 + }) + } + + console.log('[API] 压缩后数据长度:', compressedData.length); + return request({ url: 'reportDashboard', method: 'post', - data, + data: { + compressed: true, + content: compressedData + }, + headers: { + 'Content-Encoding': 'gzip', + 'X-Data-Original-Size': originalSize, + 'X-Data-Compressed-Size': compressedData.length, + 'Authorization': getToken() + }, + timeout: 120000 }) } diff --git a/src/api/dict-data.js b/src/api/dict-data.js index 3446780..a2005ff 100644 --- a/src/api/dict-data.js +++ b/src/api/dict-data.js @@ -66,7 +66,7 @@ export function getAllDict() { export function initDictToLocalstorage(callback) { getAllDict().then((res) => { if (res.code != 200) { - console.error('初始化数据字典到local storage失败: ' + res.message) + // console.error('初始化数据字典到local storage失败: ' + res.message) return } diff --git a/src/api/login/index.js b/src/api/login/index.js index da1bdd4..fc96851 100644 --- a/src/api/login/index.js +++ b/src/api/login/index.js @@ -25,7 +25,7 @@ export function logout () { export function outlogcas (data) { - console.log(data) + // console.log(data) return request({ url: 'accessUser/outlogcas', method: 'post', diff --git a/src/api/upload.js b/src/api/upload.js index 0bb4d08..30df4ca 100644 --- a/src/api/upload.js +++ b/src/api/upload.js @@ -30,13 +30,13 @@ service.interceptors.response.use( type: 'error', duration: 3 * 1000, }).then(() => { - console.log(1) + // console.log(1) sessionStorage.clear(); localStorage.clear(); // location.reload(); window.location.href = "/"; }).catch(err => { - console.log(2) + // console.log(2) }) }else if(res.repCode == "3100" || res.repCode == "3101"){ return res; diff --git a/src/assets/images/one.png b/src/assets/images/one.png new file mode 100644 index 0000000000000000000000000000000000000000..b143039293e468690fad4a93c83bda7fa26184d1 GIT binary patch literal 831 zcmV-F1Hk-=P)Px#1am@3R0s$N2z&@+hyVZr=}AOERCt`_n?X+7Koo|*7Y}UfBjgBh1K78UvO@?& zxIsAq;RKWu1VmM_vk=|W8!}3Q6qPGxm)6mxMg$`xwT~n;AKNP{l!R zzlpf0YdfvFKS13vb$@_7IZSk?Wf%zTlq@yW30G#qkL#`s2|>U?qV34=2*ei3#nR$@ zEPMgZSSG-?|J`jvNA1ABN9fH2eAYSOO{n(*{-xZztna;myRJOzgbP>oy`KaDes-h} z^w<4PFVT2xc-geJ_ah*0##>w#d0!(09RJn|`Y<0k+`@Nq`G@LJ{B!o)84MgvWOQmf+Wl@m7GX zzr4bSTjBiAjBgzvED!rM<#fyNcdiLCoOOc$8Vx_oL89$#Lx*XD<*qBwv)8;5;CjMa z7HN>Vn6IyTH-C_UnSj2LU3Sta{BgJU$3eS^l6>1w1|oVDho=UllyQOoB)E z+85yjA=U|3(>8b|sZ2mG(Rdtjj?hCHs&Z2$=&DpApqFTz8Xgyyw{^lr-4dM70juDr zSMRvKepZ|cSPKt`T3+@p5N86i-~m**adrK?aWV(wfk(K${VJy}uD?mF3kV4w+X6y^ zXHmeq#z9s>glAsB(Sz;@B=fDKQOa%}38BKXutVf>VJli}3yC@2XbkK{5y?TKZ3p$O#$8)nXm3UAIje6e^8&0dL`n-M$8v4veGX8r5x!fjJ#3Ba zo%nKf#`$doL}MPx#1am@3R0s$N2z&@+hyVZrGf6~2RA_c7bN)@o zB76W?!sqJ~`Q&PCakbF-cO3})cs`abczrXJM;Ge{E*1d@gjdC}qlh=o)_1kgq2r-l zi-&eC9@@2dXxHMQU5kfyEgssncxcxWuDkF))#D?0oDJod+bz{gl4|@L`s?;wx}#^Q z_D6Gl=!N{uLmozRO`PqvW=3;O@{oUfK1;P%gl@RXKfpCPxV1Qq2=<;<30HzAd>p#=SUA~Rhwsr>hrU^gO{Os?L_ZE?Kv z@p5%4 z7WFQ%CXv)Ry|}=dMpE~fW)o`zNt5%a4XiCBO%L8Cu{M#Ic*9@SBc`g9zWa9}BHZ^fCSe|uq70nYf-a*B@`yM9jt{*T>rs+G;eRNs?}EjG j;p%N%C!=tmEx7mtm5yU<*Vnz*00000NkvXXu0mjfBStHk literal 0 HcmV?d00001 diff --git a/src/assets/images/two.png b/src/assets/images/two.png new file mode 100644 index 0000000000000000000000000000000000000000..80dddd88e5fb038ff18968dd37a5dfbd40a1ed7f GIT binary patch literal 836 zcmV-K1H1f*P)=00001b5ch_0Itp) z=>Px#1am@3R0s$N2z&@+hyVZr?ny*JRA_v2$!`^ueC8=waqPG* z{R&G09J(%jhmc0mndFTs^%{Z~c~lsw^1zCxooAK;$K^-+FT(u>db=PaV zGFD7Q&!4+vFIqKTjcB6DsQairpdY)2-4%)-!tm})ZcLjPU(wD?8wg(tmjndmU)RZI<*qXgw2X%^n=FEIWkZQ<4YkM2dQ#$L2)>_w}_UbJfLMXN^QS~pMH zQ}tYzi>>xl#N2bXMzk~2hQJ)n=ls@mT@gO!d46j|bA8OA_|}N#NeDx0#%L4@OA(eE zg1o3*D1Q%!4DOn8W3?zD596j>#T{->)pHM+$33~#kilf#zO-HUg=FN|ekn;=MUzTW zM$t5qlub0PBxMraMv}6KZYxO{M6-~@rlLLyIpD~Z?Ip2Y6s2cU444qzb!&F-u}>;V z?8aoc$SkXjNh8UBx^AO8)42$v9d1(I*o)Fi0@M_iF}pS!sx-usQM`>LfU|KKWC7bs z0ysgy0PKC`BMV6Y=e{LA%B796SxNHz5*{yZHLB7O@Ctp6E5(MXP5cH0z#4;Ik9orY O0000 item.id === value[0]) - console.log(country) + // console.log(country) if (!country || !country.children) { this.getData().then((list) => { this.getData({ country: value[0], level: 1 }).then((list) => { @@ -80,7 +80,7 @@ export default { return this.$store.dispatch('dict/add_countryCity', params) }, lazyLoad(node, resolve) { - console.log(node) + // console.log(node) const { level, path, data } = node if (data && data.children) { resolve(data.children) diff --git a/src/components/AnjiPlus/anji-crud/anji-crud.vue b/src/components/AnjiPlus/anji-crud/anji-crud.vue index a775667..acc8ec1 100644 --- a/src/components/AnjiPlus/anji-crud/anji-crud.vue +++ b/src/components/AnjiPlus/anji-crud/anji-crud.vue @@ -527,7 +527,7 @@ export default { } else { this.hasCustomButtonInRowMore = false; } - console.log(`是否有自定义行按钮: ${this.hasCustomButtonInRowMore}`); + // console.log(`是否有自定义行按钮: ${this.hasCustomButtonInRowMore}`); }, methods: { queryFormFieldSpan(item) { diff --git a/src/components/AnjiPlus/anji-crud/edit-form.vue b/src/components/AnjiPlus/anji-crud/edit-form.vue index e4ef73b..7533a7e 100644 --- a/src/components/AnjiPlus/anji-crud/edit-form.vue +++ b/src/components/AnjiPlus/anji-crud/edit-form.vue @@ -467,7 +467,7 @@ export default { callback(); } } else { - console.log(`提交表单调用新增接口失败:${message}`); + // console.log(`提交表单调用新增接口失败:${message}`); } } else { // 当edit-from是作为关联子表的界面,补全关联属性 @@ -482,11 +482,11 @@ export default { callback(); } } else { - console.log(`提交表单调用更新接口失败:${message}`); + // console.log(`提交表单调用更新接口失败:${message}`); } } } else { - console.log("表单校验失败"); + // console.log("表单校验失败"); } }); }, diff --git a/src/components/AnjiPlus/anji-crud/edit.vue b/src/components/AnjiPlus/anji-crud/edit.vue index 191fa02..d4e7b59 100644 --- a/src/components/AnjiPlus/anji-crud/edit.vue +++ b/src/components/AnjiPlus/anji-crud/edit.vue @@ -224,7 +224,7 @@ export default { }); // 为主表的编辑表单,渲染上默认值 this.saveForm = this.deepClone(defaultSaveForm); - console.log("编辑框默认值:" + JSON.stringify(this.saveForm)); + // console.log("编辑框默认值:" + JSON.stringify(this.saveForm)); }, handleCloseDialog(val) { // 为主表的编辑表单,渲染上默认值 @@ -274,16 +274,16 @@ export default { console.warn("主表单校验失败"); return; } - console.log("主表单校验完成"); + // console.log("主表单校验完成"); if (this.joinEntitys == null || this.joinEntitys.length == 0) { // 如果子表没有信息,直接提交 this.handleSave(); return; } for (let i = 0; i < this.joinEntitys.length; i++) { - console.log(`开始校验子表单-${i} 校验`); + // console.log(`开始校验子表单-${i} 校验`); let item = this.joinEntitys[i]; - console.log(item); + // console.log(item); if ( this.$refs["joinForm" + i] == null || item.hide == true || @@ -302,15 +302,15 @@ export default { // console.log('子表单没有数据,直接跳过') this.countForValidJoinForm++; - console.log( - "已经校验的子表单:" + - this.countForValidJoinForm + - " 共:" + - this.joinEntitys.length - ); + // console.log( + // "已经校验的子表单:" + + // this.countForValidJoinForm + + // " 共:" + + // this.joinEntitys.length + // ); // 所有关联表单校验通过 if (this.countForValidJoinForm == this.joinEntitys.length) { - console.log("子表单校验完成,提交主表单"); + // console.log("子表单校验完成,提交主表单"); this.handleSave(); } continue; @@ -322,15 +322,15 @@ export default { joinForm.validate(joinValid => { if (joinValid) { this.countForValidJoinForm++; - console.log( - "已经校验的子表单:" + - this.countForValidJoinForm + - " 共:" + - this.joinEntitys.length - ); + // console.log( + // "已经校验的子表单:" + + // this.countForValidJoinForm + + // " 共:" + + // this.joinEntitys.length + // ); // 所有关联表单校验通过 if (this.countForValidJoinForm == this.joinEntitys.length) { - console.log("子表单校验完成,提交主表单"); + // console.log("子表单校验完成,提交主表单"); this.handleSave(); } } else { @@ -353,7 +353,7 @@ export default { return; } else { // ;(this.countForValidJoinForm = 0), // 已成功校验的关联表单个数 - console.log(`提交表单调用新增接口失败:${message}`); + // console.log(`提交表单调用新增接口失败:${message}`); } } // 修改 @@ -367,7 +367,7 @@ export default { return; } else { // ;(this.countForValidJoinForm = 0), // 已成功校验的关联表单个数 - console.log(`提交表单调用更新接口失败:${message}`); + // console.log(`提交表单调用更新接口失败:${message}`); } } }, diff --git a/src/components/AnjiPlus/anji-upload.vue b/src/components/AnjiPlus/anji-upload.vue index dda7809..3b81627 100644 --- a/src/components/AnjiPlus/anji-upload.vue +++ b/src/components/AnjiPlus/anji-upload.vue @@ -95,8 +95,8 @@ export default { methods: { handleRemove(file) { this.fileList = []; - console.log(this.fileList); - console.log(this.limit); + // console.log(this.fileList); + // console.log(this.limit); this.change(); }, handleExceed() { @@ -117,13 +117,13 @@ export default { fileId: file.response.data.fileId, fileType: file.response.data.fileType }); - console.log(this.fileList); + // console.log(this.fileList); this.change(); }, // 回传出去 change() { const fileList = this.fileList; - console.log(fileList); + // console.log(fileList); this.$emit("input", fileList); this.$emit("change", fileList); }, @@ -153,7 +153,7 @@ export default { }, // 回显 echoUpload(val) { - console.log(val); + // console.log(val); if (val && val.length > 0) { this.fileList = [ { diff --git a/src/components/Dictionary/index.vue b/src/components/Dictionary/index.vue index a51323f..8c6a9a1 100644 --- a/src/components/Dictionary/index.vue +++ b/src/components/Dictionary/index.vue @@ -17,7 +17,7 @@ v-for="item in dictionaryOptions" :key="item.id" :label="item.text" - :value="item.id" + :value="item.code || item.id" /> diff --git a/src/components/FlvVideo.vue b/src/components/FlvVideo.vue index 3218707..a2b49a1 100644 --- a/src/components/FlvVideo.vue +++ b/src/components/FlvVideo.vue @@ -96,7 +96,7 @@ export default { }) } - console.log('this.players:', result) + // console.log('this.players:', result) return result } diff --git a/src/components/configForm.vue b/src/components/configForm.vue index 6bb5460..3c0d293 100644 --- a/src/components/configForm.vue +++ b/src/components/configForm.vue @@ -56,15 +56,15 @@ export default { } }, formItems(val){ - console.log(val, 'formItems1'); + // console.log(val, 'formItems1'); this.formItemsArr = val - console.log(this.formItemsArr) + // console.log(this.formItemsArr) } }, computed: {}, methods: { eachChange(val){ - console.log(val, '回传的值'); + // console.log(val, '回传的值'); this.$emit('myChanged', val) }, // 无论哪个输入框改变 都需要触发事件 将值回传 diff --git a/src/components/eachForm.vue b/src/components/eachForm.vue index 066cf08..8311915 100644 --- a/src/components/eachForm.vue +++ b/src/components/eachForm.vue @@ -126,7 +126,7 @@ export default { this.ConfigData = newValue }, item(val){ - console.log(val, 'item233'); + // console.log(val, 'item233'); } }, computed: {}, @@ -150,7 +150,7 @@ export default { }, // 移除图片的回调 handleRemove(file, fileList, imgName) { - console.log(imgName) + // console.log(imgName) this.ConfigData[imgName] = "" }, // 预览图片 diff --git a/src/main.js b/src/main.js index 9336170..4c1ff5d 100644 --- a/src/main.js +++ b/src/main.js @@ -14,7 +14,7 @@ import store from './store' import * as filter from './filter' import mixins from '@/mixins' import * as echarts from 'echarts'; -// 全局定义echarts +// 全局定义 echarts import ECharts from 'vue-echarts' import 'echarts/lib/chart/bar' import 'echarts/lib/component/tooltip' @@ -22,7 +22,7 @@ import 'echarts/lib/component/tooltip' //import 'echarts-liquidfill' // import 'echarts-gl' Vue.component('v-chart', ECharts) -// 全局引入datav +// 全局引入 datav import dataV from '@jiaminghi/data-view' Vue.use(dataV) // anji component diff --git a/src/mixins/queryform.js b/src/mixins/queryform.js index f3f93aa..23ebf3c 100644 --- a/src/mixins/queryform.js +++ b/src/mixins/queryform.js @@ -191,6 +191,8 @@ export default { return this.stackMoreShowFn(params.chartProperties, data); } else if (chartType == "widget-videoMonitor") { return this.widgetVideoMonitor(params.chartProperties, data); + } else if (chartType == "widget-quadrant-scatter") { + return this.quadrantScatterChartFn(params.chartProperties, data); } else { return data } @@ -342,6 +344,45 @@ export default { analysisData["series"] = series; return analysisData; }, + // 四象限散点图数据解析 + quadrantScatterChartFn(chartProperties, data) { + const analysisData = {}; + const series = []; + + // 安全检查 + if (!chartProperties || !data || !Array.isArray(data) || data.length === 0) { + console.warn('[四象限图] 数据无效,返回空结构'); + analysisData["series"] = []; + return analysisData; + } + + // chartProperties 格式: { 'name': 'name字段', 'xAxis': 'xData字段', 'yAxis': 'yData字段' } + // 获取各个字段名,注意 chartProperties 中的 value 是数据库字段名,而非字面 'xAxis'/'yAxis' + // 例如 chartProperties = { 'name': 'productName', 'xAxis': 'growthRate', 'yAxis': 'profitRate' } + const nameField = chartProperties['name'] || 'name'; + // chartProperties['xAxis'] 的 value 才是 x 轴数据对应的数据库字段名 + const xAxisField = chartProperties['xAxis'] || 'xData'; + const yAxisField = chartProperties['yAxis'] || 'yData'; + + // 构建 series + series.push({ + type: 'scatter', + name: 'scatter', + data: data.map((item) => { + return { + name: item[nameField] || item.name || item.axis || '', + xData: item[xAxisField] || item.xData || item[0], + yData: item[yAxisField] || item.yData || item[1], + amount: item.amount || item.bar, + growth: item.growth, + profit: item.profit + }; + }) + }); + + analysisData["series"] = series; + return analysisData; + }, // 中国地图。路线图数据解析,适合source、target、value linemapChartFn(chartProperties, data) { const analysisData = []; @@ -431,7 +472,7 @@ export default { }, // 堆叠图多显示 stackMoreShowFn(chartProperties, data) { - console.log(data) + // console.log(data) const analysisData = {}; //全部字段字典值 const types = Object.values(chartProperties) diff --git a/src/store/modules/designer.js b/src/store/modules/designer.js index ae275ca..9295ac2 100644 --- a/src/store/modules/designer.js +++ b/src/store/modules/designer.js @@ -15,13 +15,13 @@ const designer = { SET_ALL_COMPONENT_LINKAGE: (state, params) => { var { index = -1, widgetId = '', linkageArr } = params try { - console.log('params---', params) + // 解析 linkageArr,widgetValue 格式: targetId-$-targetName-$-targetIndex linkageArr = linkageArr.map(item => { const arr = item.widgetValue.split('-$-') return { - originId: widgetId, - targetId: arr[0], - targetName: arr[1], + originId: widgetId, // 当前组件(源组件)的 ID + targetId: arr[0], // 目标组件的 ID + targetName: arr[1], // 目标组件的名称 paramsConfig: item.paramsConfig } }) diff --git a/src/utils/compress.js b/src/utils/compress.js new file mode 100644 index 0000000..f216f44 --- /dev/null +++ b/src/utils/compress.js @@ -0,0 +1,112 @@ +import pako from 'pako' + +/** + * 将对象压缩为 Gzip 格式 + * @param {Object} obj - 需要压缩的对象 + * @returns {string} - 压缩后的 base64 字符串 + */ +export function gzipCompress(obj) { + try { + // 转换为 JSON 字符串 + const str = JSON.stringify(obj) + if (!str) { + console.error('gzipCompress: 输入对象为空') + return null + } + console.log('[压缩] JSON字符串长度:', str.length); + + // 转换为 Uint8Array (兼容旧浏览器) + let uint8Array + if (typeof TextEncoder !== 'undefined') { + uint8Array = new TextEncoder().encode(str) + } else { + // 降级处理:手动转换为 UTF-8 字节数组 + uint8Array = new Uint8Array(str.length) + for (let i = 0; i < str.length; i++) { + uint8Array[i] = str.charCodeAt(i) + } + } + console.log('[压缩] Uint8Array 长度:', uint8Array.length); + + // Gzip 压缩 + const compressed = pako.gzip(uint8Array) + console.log('[压缩] 压缩后字节长度:', compressed.length); + + // 转为 base64 便于传输 + const base64 = arrayToBase64(compressed) + console.log('[压缩] Base64 长度:', base64 ? base64.length : 'null'); + return base64 + } catch (error) { + console.error('压缩失败:', error) + return null + } +} + +/** + * 解压 Gzip 数据 + * @param {string} base64Str - base64 编码的压缩数据 + * @returns {Object} - 解压后的对象 + */ +export function gzipDecompress(base64Str) { + try { + if (!base64Str) { + console.error('gzipDecompress: 输入数据为空') + return null + } + // base64 转 Uint8Array + const uint8Array = base64ToArray(base64Str) + // Gzip 解压 + const decompressed = pako.ungzip(uint8Array) + // 转回字符串 (兼容旧浏览器) + let str + if (typeof TextDecoder !== 'undefined') { + str = new TextDecoder().decode(decompressed) + } else { + // 降级处理:手动转换为字符串 + str = '' + for (let i = 0; i < decompressed.length; i++) { + str += String.fromCharCode(decompressed[i]) + } + } + // 解析 JSON + return JSON.parse(str) + } catch (error) { + console.error('解压失败:', error) + return null + } +} + +/** + * Uint8Array 转 Base64 + */ +function arrayToBase64(arr) { + try { + let binary = '' + const len = arr.length + for (let i = 0; i < len; i++) { + binary += String.fromCharCode(arr[i]) + } + return window.btoa(binary) + } catch (error) { + console.error('arrayToBase64 失败:', error) + return null + } +} + +/** + * Base64 转 Uint8Array + */ +function base64ToArray(base64) { + try { + const binary = window.atob(base64) + const len = binary.length + const arr = new Uint8Array(len) + for (let i = 0; i < len; i++) { + arr[i] = binary.charCodeAt(i) + } + return arr + } catch (error) { + console.error('base64ToArray 失败:', error) + return null + } +} diff --git a/src/utils/revoke.js b/src/utils/revoke.js index 119b769..f149afd 100644 --- a/src/utils/revoke.js +++ b/src/utils/revoke.js @@ -99,4 +99,15 @@ export class Revoke { return JSON.parse(record); } + + /** + * @description: 清空历史记录 + * @return {void} + */ + clear() { + this.recordList = []; + this.redoList = []; + this.currentRecord = null; + this.time = 0; + } } diff --git a/src/utils/screen.js b/src/utils/screen.js index 62f8bc0..c98fc27 100644 --- a/src/utils/screen.js +++ b/src/utils/screen.js @@ -9,7 +9,7 @@ export function setAssChartData(widgets, options) { }) widgets.forEach(item => { const setup = item['options']['setup'] - console.log(setup) + // console.log(setup) setup.forEach(sItem => { if (sItem.name == 'assChart') { sItem['selectOptions'] = selectOptions diff --git a/src/utils/screenMixins.js b/src/utils/screenMixins.js index e99b5f9..33d2256 100644 --- a/src/utils/screenMixins.js +++ b/src/utils/screenMixins.js @@ -10,6 +10,7 @@ const mixin = { revoke: null, //处理历史记录 rightClickIndex: -1, rightClickWidget: null, + isSaving: false, // 保存中状态标志,用于幂等控制 } }, computed: { @@ -108,6 +109,12 @@ const mixin = { this.dashboard = this.initScreenData(data.dashboard); this.bigscreenWidth = this.dashboard.width; this.bigscreenHeight = this.dashboard.height; + + // 【关键修复】数据加载完成后,初始化所有组件的联动配置 + // 这确保 targetWidgetLinkageLogic 和 originWidgetLinkageLogic 能正确工作 + this.$nextTick(() => { + this.initAllComponentLinkage && this.initAllComponentLinkage(); + }); }, // 组件数据 initWidgetsData(data) { @@ -126,10 +133,21 @@ const mixin = { if (process.env.NODE_ENV === "development") { this.$message.error(message); } + console.error('[加载] 未找到组件工具:', widget.type); continue; // 找不到就跳过,避免整个报表都加载不出来 } + + // 【调试】打印组件类型和 value 结构 + console.log('[加载] 组件类型:', widget.type, 'value.setup keys:', Object.keys(setup || {})); + obj.options = this.setDefaultWidgetConfigValue(widget.value, tool.options); obj.value.widgetId = obj.value.setup.widgetId; + + // 【调试】打印处理后的 options.setup 关键字段 + if (widget.type === 'widget-quadrant-scatter') { + console.log('[加载] 四象限图 setup keys:', Object.keys(obj.options.setup || {}).slice(0, 20)); + } + widgetsData.push(obj); } return widgetsData; @@ -175,21 +193,69 @@ const mixin = { }, // 保存数据 async saveData() { + // 幂等控制:防止重复提交 + if (this.isSaving) { + this.$message.warning('正在保存中,请稍候...'); + return; + } + if (!this.widgets || this.widgets.length == 0) { return this.$message.error("请添加组件"); } - const { title, width, height, backgroundColor, backgroundImage, refreshSeconds } = { ...this.dashboard } - const screenData = { - reportCode: this.reportCode, - dashboard: { title, width, height, backgroundColor, backgroundImage, refreshSeconds }, - widgets: this.widgets, - }; - screenData.widgets.forEach((widget) => { - widget.value.setup.widgetId = widget.value.widgetId; - widget.value.setup.widgetCode = widget.type + + this.isSaving = true; + + const saveLoading = this.$loading({ + lock: true, + text: '正在压缩并保存...', + spinner: 'el-icon-loading' }); - const { code, data } = await insertDashboard(screenData); - if (code == "200") return this.$message.success("保存成功!"); + + try { + const { title, width, height, backgroundColor, backgroundImage, refreshSeconds } = { ...this.dashboard } + + // 【调试】打印 this.widgets 的结构 + console.log('[保存] this.widgets.length:', this.widgets.length); + this.widgets.forEach((w, idx) => { + console.log(`[保存] widgets[${idx}] type:`, w.type, 'value.setup keys:', Object.keys(w.value.setup || {}).slice(0, 10)); + }); + + const screenData = { + reportCode: this.reportCode, + dashboard: { title, width, height, backgroundColor, backgroundImage, refreshSeconds }, + widgets: this.widgets, + }; + + screenData.widgets.forEach((widget) => { + widget.value.setup.widgetId = widget.value.widgetId; + widget.value.setup.widgetCode = widget.type + // 【调试】打印保存的组件类型和 setup keys + console.log('[保存] 组件类型:', widget.type, 'setup keys:', Object.keys(widget.value.setup || {}).slice(0, 20)); + }); + + // 【调试】打印四象限图的保存数据 + const quadrantWidget = screenData.widgets.find(w => w.type === 'widget-quadrant-scatter'); + if (quadrantWidget) { + console.log('[保存] 四象限图 widget.value.setup:', Object.keys(quadrantWidget.value.setup || {})); + console.log('[保存] 四象限图 widget.value.data:', Object.keys(quadrantWidget.value.data || {})); + console.log('[保存] 四象限图完整 value:', JSON.stringify(quadrantWidget.value).substring(0, 500)); + } + + const { code, data } = await insertDashboard(screenData) + if (code == "200") { + this.$message.success("保存成功!") + // 保存成功后清空历史记录,防止重复调用,增加安全检查 + if (this.revoke && typeof this.revoke.clear === 'function') { + this.revoke.clear() + } + } + } catch (error) { + console.error('保存失败:', error) + this.$message.error("保存失败,请检查网络或联系管理员") + } finally { + saveLoading.close() + this.isSaving = false; // 重置保存状态 + } }, // 预览 viewScreen() { @@ -282,6 +348,10 @@ const mixin = { this.selectedWidgets.forEach(sw=>{ _this.widgets = _this.widgets.filter(w=>w.value.widgetId !== sw.value.widgetId); }) + // 【关键修复】删除组件后,重新初始化所有组件的联动配置 + this.$nextTick(() => { + this.initAllComponentLinkage && this.initAllComponentLinkage(); + }); }, // 锁定 lockLayer() { @@ -396,7 +466,7 @@ const mixin = { this.$message.error("请至少选择两个组件对齐"); return; } - console.log("对齐方式:" + align); + // console.log("对齐方式:" + align); let topWidget = this.selectedWidgets[0]; //最上组件(左右对齐使用) let leftWidget = this.selectedWidgets[0]; //最左组件(上下对齐使用) let minTop = this.selectedWidgets[0].value.position.top; diff --git a/src/views/accessRole/components/RoleAuthority.vue b/src/views/accessRole/components/RoleAuthority.vue index 54704ff..20995e5 100644 --- a/src/views/accessRole/components/RoleAuthority.vue +++ b/src/views/accessRole/components/RoleAuthority.vue @@ -58,7 +58,7 @@ export default { visib(val) { if (val) { // 弹窗弹出时需要执行的逻辑 - console.log(1); + // console.log(1); this.getTreeData(); } } diff --git a/src/views/bigscreenDesigner/designer/components/componentLinkage.vue b/src/views/bigscreenDesigner/designer/components/componentLinkage.vue index 3891426..a6f3b18 100644 --- a/src/views/bigscreenDesigner/designer/components/componentLinkage.vue +++ b/src/views/bigscreenDesigner/designer/components/componentLinkage.vue @@ -61,13 +61,15 @@
{{ item.originKey }}
+ + + +
+
+
+ +
+ 目标组件未配置参数,可手动添加参数映射 +
+ + 添加参数映射 +
@@ -168,20 +193,22 @@ export default { initFormDynamicData() { let paramsKey = [] const dynamicParamsWidget = ['widgetButtonGroup', 'widget-table'] - if (dynamicParamsWidget.includes(this.layerWidget[this.widgetIndex].code)) { // 参数不确定的 通过消息接收 - paramsKey = this.layerWidget[this.widgetIndex].paramsKeys || [] + const currentWidget = this.layerWidget[this.widgetIndex] + const widgetCode = currentWidget ? currentWidget.code : '' + if (dynamicParamsWidget.includes(widgetCode)) { // 参数不确定的 通过消息接收 + paramsKey = currentWidget.paramsKeys || [] } else { - const widgetConfigTemp = getOneConfigByCode(this.layerWidget[this.widgetIndex].code) + const widgetConfigTemp = getOneConfigByCode(widgetCode) if (!widgetConfigTemp) return // console.log('this.layerWidget[this.widgetIndex---', this.layerWidget, ' --- ', this.widgetIndex) - paramsKey = widgetConfigTemp.paramsKey + paramsKey = widgetConfigTemp.paramsKey || [] } this.linkageForm = { widgetValue: '', // 选中的组件的名字 paramsConfig: paramsKey.map(item => { return { originKey: item, - targetKey: '' + targetKey: item // 默认 targetKey 等于 originKey } }) } @@ -191,15 +218,17 @@ export default { if('widget-button'=== this.layerWidget[this.widgetIndex].code){ //根据目标组件的参数个数来确定按钮组件的参数个数 this.linkageForm.paramsConfig = []; //每次切换都置空 - let paramKeys = Object.keys(this.widgetParamsConfig[this.targetIndex].dynamicData.contextData); - paramKeys.forEach(param=>{ - this.linkageForm.paramsConfig.push({ - originKey: param, - targetKey: '' + try { + let paramKeys = Object.keys(this.widgetParamsConfig[this.targetIndex].dynamicData.contextData); + paramKeys.forEach(param=>{ + this.linkageForm.paramsConfig.push({ + originKey: param, + targetKey: param + }); }); - }); - //把参数传给button组件的表单 - //this.layerWidget[this.widgetIndex].setFormData(paramKeys); + } catch (error) { + // 如果目标组件没有 contextData,保持 paramsConfig 为空,用户可以手动添加 + } } }, // 弹出框关闭 @@ -228,6 +257,17 @@ export default { this.$emit('input', this.formData) this.$emit('change', this.formData) }, + // 添加参数映射 + handleAddParam() { + this.linkageForm.paramsConfig.push({ + originKey: '', + targetKey: '' + }) + }, + // 删除单个参数映射 + handleDeleteParam(index) { + this.linkageForm.paramsConfig.splice(index, 1) + }, // 确定 handleSaveClick() { const obj = JSON.parse(JSON.stringify(this.linkageForm)) @@ -298,10 +338,26 @@ export default { display: flex; flex-wrap: nowrap; align-items: center; - margin-bottom: 20px; + margin-bottom: 12px; .label { - margin-right: 20px; + min-width: 80px; + margin-right: 10px; + text-align: right; } + .value { + flex: 1; + } + } + .empty-tip { + color: #E6A23C; + font-size: 12px; + margin-bottom: 10px; + padding: 8px; + background: rgba(230, 162, 60, 0.1); + border-radius: 4px; + } + .add-param-btn { + margin-top: 8px; } } diff --git a/src/views/bigscreenDesigner/designer/components/customUpload.vue b/src/views/bigscreenDesigner/designer/components/customUpload.vue index faac75f..63ca6d8 100644 --- a/src/views/bigscreenDesigner/designer/components/customUpload.vue +++ b/src/views/bigscreenDesigner/designer/components/customUpload.vue @@ -52,7 +52,7 @@ export default { }, upload(imgUrl) { let that = this; - console.log(that.headers); + // console.log(that.headers); let formdata = new FormData(); formdata.append("file", imgUrl); axios diff --git a/src/views/bigscreenDesigner/designer/components/drillDrownSetting.vue b/src/views/bigscreenDesigner/designer/components/drillDrownSetting.vue index aa2a816..a169575 100644 --- a/src/views/bigscreenDesigner/designer/components/drillDrownSetting.vue +++ b/src/views/bigscreenDesigner/designer/components/drillDrownSetting.vue @@ -611,7 +611,7 @@ export default { async getData() { const data = {dashboard:{...this.editData.dashboard,...this.editData},widgets:null}; - console.log(data,1231231) + // console.log(data,1231231) this.widgets = this.initWidgetsData(data); this.dashboard = this.initScreenData(data.dashboard); this.bigscreenWidth = this.dashboard.width; @@ -902,7 +902,7 @@ export default { this.$message.error("请至少选择两个组件对齐"); return; } - console.log("对齐方式:" + align); + // console.log("对齐方式:" + align); let topWidget = this.selectedWidgets[0]; //最上组件(左右对齐使用) let leftWidget = this.selectedWidgets[0]; //最左组件(上下对齐使用) let minTop = this.selectedWidgets[0].value.position.top; @@ -1009,11 +1009,11 @@ export default { this.$nextTick(() => { if (this.widgets.length === this.currentWidgetTotal + 1) { // 确实新增了一个组件到操作面板上 - console.log( - `新添加 '${ - this.widgets[this.currentWidgetTotal].value.setup.layerName - }' 组件到操作面板` - ); + // console.log( + // `新添加 '${ + // this.widgets[this.currentWidgetTotal].value.setup.layerName + // }' 组件到操作面板` + // ); const uuid = Number(Math.random().toString().substr(2)).toString(36); this.widgets[this.currentWidgetTotal].value.widgetId = uuid; this.layerWidget[this.currentWidgetTotal].widgetId = uuid; @@ -1103,7 +1103,7 @@ export default { options: tool.options, }; // 处理默认值 - console.log(widgetType) + // console.log(widgetType) const widgetJsonValue = this.getWidgetConfigValue(widgetJson); widgetJsonValue.value.position.left = @@ -1150,7 +1150,7 @@ export default { }, // 如果是点击大屏设计器中的底层,加载大屏底层属性 setOptionsOnClickScreen() { - console.log("setOptionsOnClickScreen"); + // console.log("setOptionsOnClickScreen"); if(this.selectedWidgets.length > 0 && this.kuangSelectFlag){ //如果Ctrl多选过程中,点击了大屏底层,就清空 this.selectedWidgets return; @@ -1189,7 +1189,7 @@ export default { */ setOptionsOnClickInnerWidget(payload) { if (!payload) return; - console.log('setOptionsOnClickInnerWidget payload:', payload); + // console.log('setOptionsOnClickInnerWidget payload:', payload); const { rootWidgetIndex, tabIndex, childIndex } = payload; const rootIndex = typeof rootWidgetIndex === "number" ? rootWidgetIndex : 0; @@ -1297,7 +1297,7 @@ export default { document.addEventListener('mouseup', onUp); }, widgetsClick(event,index) { - console.log("widgetsClick"); + // console.log("widgetsClick"); //判断是否按住了Ctrl按钮,表示Ctrl多选 let _this = this; let eventWidget = null; @@ -1312,7 +1312,7 @@ export default { if(eventWidget.value.widgetId === w.value.widgetId){ setTimeout(function (){ _this.$refs.widgets[index].$refs.draggable.setActive(false); - console.log("触发取消选中, eventWidget.value.widgetId = " + eventWidget.value.widgetId +", w.value.widgetId= "+ w.value.widgetId); + // console.log("触发取消选中, eventWidget.value.widgetId = " + eventWidget.value.widgetId +", w.value.widgetId= "+ w.value.widgetId); },200); //设置超时,防止效果被覆盖 } }) @@ -1368,14 +1368,14 @@ export default { }, // 将当前选中的组件,右侧属性值更新 widgetValueChanged(key, val) { - console.log("key", key); - console.log("val", val); - console.log(this.widgetOptions); + // console.log("key", key); + // console.log("val", val); + // console.log(this.widgetOptions); if (this.screenCode == "screen") { let newSetup = []; this.dashboard = this.deepClone(val); - console.log("asd", this.dashboard); - console.log(this.widgetOptions); + // console.log("asd", this.dashboard); + // console.log(this.widgetOptions); if (this.bigscreenWidth != this.dashboard.width) { this.bigscreenWidth = this.dashboard.width; } @@ -1392,7 +1392,7 @@ export default { } newSetup.push(el); }); - console.log(newSetup); + // console.log(newSetup); this.widgetOptions.setup = newSetup; } else { // 如果当前选中的是 Tabs 内部子组件,优先更新内部子组件的配置 @@ -1480,7 +1480,7 @@ export default { }, //鼠标按下事件 downEvent(event){ - console.log("downEvent") + // console.log("downEvent") this.moveTimes = 0; this.selectedWidgets = []; this.openMulDrag = false; @@ -1496,7 +1496,7 @@ export default { }, //鼠标移动事件 moveEvent(event){ - console.log("moveEvent"); + // console.log("moveEvent"); //测试的时候发现,每次点击组件,再次点击大屏的时候,偶尔会触发一次moveEvent,导致会生成rect,所以加了移动次数moveTimes 变量控制一下,只有移动多次的情况下,才能说明是框选多选 if(this.selectFlag && this.selectedWidgets.length <= 1 && this.moveTimes >= 1){ if(this.rect === null){ @@ -1559,7 +1559,7 @@ export default { } }, upEvent(event){ - console.log("upEvent") + // console.log("upEvent") if(this.selectFlag && this.selectedWidgets.length === 0 && this.rect !== null){ this.calculateMousePosition(event, false); diff --git a/src/views/bigscreenDesigner/designer/components/dynamicComponents.vue b/src/views/bigscreenDesigner/designer/components/dynamicComponents.vue index 6e874ad..7d979e7 100644 --- a/src/views/bigscreenDesigner/designer/components/dynamicComponents.vue +++ b/src/views/bigscreenDesigner/designer/components/dynamicComponents.vue @@ -96,13 +96,13 @@ export default { methods: { async loadDataSet() { const { code, data } = await queryAllDataSet(); - this.dataSet = data; + this.dataSet = data || []; if (code != "200") return; }, async selectDataSet() { const { code, data } = await detailBysetId(this.dataSetValue); - this.userNameList = data.dataSetParamDtoList; - this.setParamList = data.setParamList; + this.userNameList = data ? data.dataSetParamDtoList : []; + this.setParamList = data ? data.setParamList : []; this.chartProperties = {}; if (code != "200") return; }, @@ -134,14 +134,17 @@ export default { }, // 数据集回显 async echoDataSet(val) { - if (!val) return; + if (!val || typeof val !== 'object') return; const setCode = val.setCode; await this.loadDataSet(); - this.dataSetValue = this.dataSet.filter( + const dd = this.dataSet.filter( el => setCode == el.setCode - )[0].id; + ); + if (dd && dd.length > 0) { + this.dataSetValue = dd[0].id; + } await this.selectDataSet(); this.echoDynamicData(val); diff --git a/src/views/bigscreenDesigner/designer/components/dynamicForm.vue b/src/views/bigscreenDesigner/designer/components/dynamicForm.vue index 52f8ee0..4e6192c 100644 --- a/src/views/bigscreenDesigner/designer/components/dynamicForm.vue +++ b/src/views/bigscreenDesigner/designer/components/dynamicForm.vue @@ -415,6 +415,7 @@ export default { dialogVisibleStaticData: false, methodsVisible: false, validationRules: "", + changeTimer: null, // 【关键优化】防抖定时器 optionsJavascript: { @@ -432,7 +433,10 @@ export default { }, watch: { value(newValue, oldValue) { - this.formData = newValue || {}; + // 【关键优化】只在初始化或 value 引用真正变化时才更新 formData + if (!oldValue || newValue !== oldValue) { + this.formData = newValue || {}; + } }, options(val) { @@ -445,37 +449,58 @@ export default { this.setDefaultValue(); }, mounted() {}, + beforeDestroy() { + // 【关键优化】清理防抖定时器,避免内存泄漏 + if (this.changeTimer) { + clearTimeout(this.changeTimer); + this.changeTimer = null; + } + }, methods: { handleCollapse(){ this.$emit('handleCollapse',this.formData) }, + changeDrillData(val){ this.changed(val, 'drill_drown_setting') }, onJsonChange(val) { - console.log(val); + // console.log(val); }, onJsonSave(val) { - console.log(val); + // console.log(val); }, // 无论哪个输入框改变 都需要触发事件 将值回传 changed(val, key) { - console.log(val); - console.log(key); - if (val.extend) { - this.$set(this.formData, key, val.value); - } else { - this.$set(this.formData, key, val); + // 【关键优化】使用防抖,避免配置修改时频繁触发上层组件更新 + if (this.changeTimer) { + clearTimeout(this.changeTimer); } - this.$emit("onChanged", this.formData); - // key为当前用户操作的表单组件 - for (let i = 0; i < this.options.length; i++) { - let item = this.options[i]; - if (item.relactiveDom == key) { - this.inputShow[item.name] = val == item.relactiveDomValue; - this.inputShow = Object.assign({}, this.inputShow); + + this.changeTimer = setTimeout(() => { + // 处理 dynamicComponents 组件返回的 params 对象 + // dynamicComponents 返回的是完整的 params 对象,包含 chartType/setCode/chartProperties/contextData + if (val && val.chartType && val.setCode) { + // dynamicComponents 返回的是完整的 params 对象 + // 需要同时设置 dynamicData(供 mixin 的 queryEchartsData 使用)和 chartProperties + this.$set(this.formData, 'dynamicData', val); + this.$set(this.formData, 'chartProperties', val.chartProperties || {}); + } else if (val.extend) { + this.$set(this.formData, key, val.value); + } else { + this.$set(this.formData, key, val); } - } + this.$emit("onChanged", this.formData); + + // 处理关联显示逻辑 + for (let i = 0; i < this.options.length; i++) { + let item = this.options[i]; + if (item.relactiveDom == key) { + this.inputShow[item.name] = val == item.relactiveDomValue; + this.inputShow = Object.assign({}, this.inputShow); + } + } + }, 100); // 100ms 防抖 }, saveData() { this.$emit("onChanged", this.formData); diff --git a/src/views/bigscreenDesigner/designer/components/imageSelect.vue b/src/views/bigscreenDesigner/designer/components/imageSelect.vue index c047112..0736552 100644 --- a/src/views/bigscreenDesigner/designer/components/imageSelect.vue +++ b/src/views/bigscreenDesigner/designer/components/imageSelect.vue @@ -298,7 +298,7 @@ export default { this.submitSelect(); }, submitSelect() { - console.log(this.headers); + // console.log(this.headers); this.ImgUrl = this.currentRow.urlPath; this.$emit("input", this.currentRow.urlPath); this.$emit("change", this.currentRow.urlPath); @@ -309,7 +309,7 @@ export default { }, changeInput(e) { if (e) { - console.log(11111111) + // console.log(11111111) this.ImgUrl = e; } else { this.ImgUrl = ""; // 直接设置为空字符串 diff --git a/src/views/bigscreenDesigner/designer/index.vue b/src/views/bigscreenDesigner/designer/index.vue index 0f71964..6362eff 100644 --- a/src/views/bigscreenDesigner/designer/index.vue +++ b/src/views/bigscreenDesigner/designer/index.vue @@ -280,22 +280,25 @@ @mousemove="moveEvent($event)" >
- + + @@ -458,18 +461,29 @@ export default { widgetParamsConfig: [], // 各组件动态数据集的参数配置情况 selectedWidgets: [], //多选组件集合 - moveTimes: 0, //鼠标移动次数 + moveTimes: 0, //鼠标移动次数 selectFlag: false, //选择标识 kuangSelectFlag: false, //框选标识 - downX: 0, //移动开始X坐标 - downY: 0, //移动开始Y坐标 - downX2: 0, //移动结束X坐标 - downY2: 0, //移动结束Y坐标 + downX: 0, //移动开始 X 坐标 + downY: 0, //移动开始 Y 坐标 + downX2: 0, //移动结束 X 坐标 + downY2: 0, //移动结束 Y 坐标 rect : null, //框选矩形对象 openMulDrag: false, //批量拖拽开关 - moveWidgets:{}, //记录移动的组件的起始left和top属性 + moveWidgets:{}, //记录移动的组件的起始 left 和 top 属性 // 记录当前是否选中了 Tabs 内部子组件,如果为 null 则表示选中的是顶层组件或大屏 innerWidgetSelected: null, + + // 分批渲染优化 + renderBatches: [], // 分批渲染的批次 + batchSize: 15, // 设计器每批渲染的组件数量 (比预览多一些,因为需要交互) + renderTimer: null, // 分批渲染定时器 + renderedCount: 0, // 已渲染的组件数量 + initialRendered: false, // 是否已初始化渲染 + previousWidgetCount: 0, // 上次组件数量,用于检测组件数量变化 + + // 【关键优化】配置变更防抖定时器 + configChangeTimer: null, }; }, computed: { @@ -514,12 +528,23 @@ export default { handler(val) { this.getLayerData(val); this.handlerdynamicDataParamsConfig(val); + //以下部分是记录历史 this.$nextTick(() => { this.revoke.push(this.widgets); }); + + // 关键优化:只在初始化或组件数量变化时重新渲染,避免频繁操作导致卡顿 + if (!this.initialRendered || val.length !== this.previousWidgetCount) { + this.initialRendered = true; + this.previousWidgetCount = val.length; + setTimeout(() => { + this.renderAllWidgets(); + }, 50); + } + // 其他情况 (如位置调整) 不触发重新渲染,由组件自己更新 }, - deep: true, + // deep: true, // 移除深度监听,避免每次属性变化都触发 }, }, mounted() { @@ -531,7 +556,87 @@ export default { this.initVueRulerTool(); // 初始化 修正插件样式 }); }, + beforeDestroy() { + console.log('[Designer] beforeDestroy 开始清理资源'); + + // 1. 清除所有定时器 + if (this.flagInter) { + clearInterval(this.flagInter); + this.flagInter = null; + } + + // 清除分批渲染定时器 + if (this.renderTimer) { + clearTimeout(this.renderTimer); + this.renderTimer = null; + console.log('[Designer] 已清除分批渲染定时器'); + } + + // 2. 移除 window 事件监听 + window.removeEventListener("mouseup", () => { + this.grade = false; + }); + + // 3. 清空大型数据引用,释放内存 + this.widgets = []; + this.renderWidgets = []; + this.layerWidget = []; + this.renderBatches = []; + this.dashboard = {}; + this.widgetOptions = { setup: [], data: [], position: [] }; + this.selectedWidgets = []; + this.moveWidgets = {}; + this.widgetParamsConfig = []; + this.screenData = { dashboard: {}, widgets: [] }; + this.innerWidgetSelected = null; + + // 【关键优化】清理所有定时器 + if (this.renderTimer) { + clearTimeout(this.renderTimer); + this.renderTimer = null; + } + if (this.configChangeTimer) { + clearTimeout(this.configChangeTimer); + this.configChangeTimer = null; + } + + // 4. 清空撤销重做记录 + if (this.revoke && typeof this.revoke.clear === 'function') { + this.revoke.clear(); + } + + console.log('[Designer] 资源清理完成'); + }, methods: { + /** + * 初始化所有组件的联动配置到 Vuex Store + * 在设计器模式下,每个组件都需要将其 componentLinkage 配置提交到 store + * 这样 targetWidgetLinkageLogic 和 originWidgetLinkageLogic 才能正确查找联动关系 + */ + initAllComponentLinkage() { + // 清空之前的联动配置 + this.$store.state.designer.allComponentLinkage = []; + + // 遍历所有组件,将 componentLinkage 配置提交到 store + this.widgets.forEach((widgetItem, index) => { + const widgetValue = widgetItem.value || {}; + const setup = widgetValue.setup || {}; + const widgetId = widgetValue.widgetId; + + if (widgetId && setup.componentLinkage && setup.componentLinkage.length) { + this.$store.commit('SET_ALL_COMPONENT_LINKAGE', { + index, + widgetId: widgetId, + linkageArr: setup.componentLinkage + }); + } + }); + + console.log('[Designer] 组件联动配置已初始化:', { + 组件总数: this.widgets.length, + 联动配置数: this.$store.state.designer.allComponentLinkage.length + }); + }, submitDrillDrownData(data){ this.dialogVisibleDrillDrown=false this.$refs.formData1.changeDrillData(data) @@ -593,11 +698,11 @@ export default { this.$nextTick(() => { if (this.widgets.length === this.currentWidgetTotal + 1) { // 确实新增了一个组件到操作面板上 - console.log( - `新添加 '${ - this.widgets[this.currentWidgetTotal].value.setup.layerName - }' 组件到操作面板` - ); + // console.log( + // `新添加 '${ + // this.widgets[this.currentWidgetTotal].value.setup.layerName + // }' 组件到操作面板` + // ); const uuid = Number(Math.random().toString().substr(2)).toString(36); this.widgets[this.currentWidgetTotal].value.widgetId = uuid; this.layerWidget[this.currentWidgetTotal].widgetId = uuid; @@ -606,6 +711,9 @@ export default { this.widgetIndex = index; this.widgetsClickAndCtrl(index); // 选中当前新增的组件 this.grade = false; // 去除网格线 + + // 【关键修复】新增组件后,重新初始化所有组件的联动配置 + this.initAllComponentLinkage(); } }); }, @@ -687,7 +795,7 @@ export default { options: tool.options, }; // 处理默认值 - console.log(widgetType) + // console.log(widgetType) const widgetJsonValue = this.getWidgetConfigValue(widgetJson); widgetJsonValue.value.position.left = @@ -779,7 +887,7 @@ export default { }, // 如果是点击大屏设计器中的底层,加载大屏底层属性 setOptionsOnClickScreen() { - console.log("setOptionsOnClickScreen"); + // console.log("setOptionsOnClickScreen"); if(this.selectedWidgets.length > 0 && this.kuangSelectFlag){ //如果Ctrl多选过程中,点击了大屏底层,就清空 this.selectedWidgets return; @@ -798,7 +906,14 @@ export default { // 选中顶层组件时清空内部子组件的选中状态 this.innerWidgetSelected = null; if (typeof obj == "number") { - this.widgetOptions = this.deepClone(this.widgets[obj]["options"]); + // 【关键优化】不再从 widgets 读取配置,而是直接从 renderWidgets 读取冻结数据 + // 避免频繁修改 widgetOptions 触发响应式更新 + const renderWidget = this.renderWidgets[obj]; + if (renderWidget) { + this.widgetOptions = this.deepClone(renderWidget.options); + } else { + this.widgetOptions = this.deepClone(this.widgets[obj]["options"]); + } return; } if (obj.index < 0 || obj.index >= this.widgets.length) { @@ -820,7 +935,7 @@ export default { */ setOptionsOnClickInnerWidget(payload) { if (!payload) return; - console.log('setOptionsOnClickInnerWidget payload:', payload); + // console.log('setOptionsOnClickInnerWidget payload:', payload); const { rootWidgetIndex, tabIndex, childIndex } = payload; const rootIndex = typeof rootWidgetIndex === "number" ? rootWidgetIndex : 0; @@ -922,7 +1037,7 @@ export default { this.activeName = "first"; }, widgetsClick(event,index) { - console.log("widgetsClick"); + // console.log("widgetsClick"); // 如果是 Tabs 组件,内部点击由 widgetTabs 自己处理,这里只负责选中 Tabs 整体 const rootWidget = this.widgets[index]; if (rootWidget && rootWidget.type === "widget-tabs") { @@ -943,7 +1058,7 @@ export default { if(eventWidget.value.widgetId === w.value.widgetId){ setTimeout(function (){ _this.$refs.widgets[index].$refs.draggable.setActive(false); - console.log("触发取消选中, eventWidget.value.widgetId = " + eventWidget.value.widgetId +", w.value.widgetId= "+ w.value.widgetId); + // console.log("触发取消选中, eventWidget.value.widgetId = " + eventWidget.value.widgetId +", w.value.widgetId= "+ w.value.widgetId); },200); //设置超时,防止效果被覆盖 } }) @@ -999,14 +1114,14 @@ export default { }, // 将当前选中的组件,右侧属性值更新 widgetValueChanged(key, val) { - console.log("key", key); - console.log("val", val); - console.log(this.widgetOptions); + // console.log("key", key); + // console.log("val", val); + // console.log(this.widgetOptions); if (this.screenCode == "screen") { let newSetup = []; this.dashboard = this.deepClone(val); - console.log("asd", this.dashboard); - console.log(this.widgetOptions); + // console.log("asd", this.dashboard); + // console.log(this.widgetOptions); if (this.bigscreenWidth != this.dashboard.width) { this.bigscreenWidth = this.dashboard.width; } @@ -1023,7 +1138,7 @@ export default { } newSetup.push(el); }); - console.log(newSetup); + // console.log(newSetup); this.widgetOptions.setup = newSetup; } else { // 如果当前选中的是 Tabs 内部子组件,优先更新内部子组件的配置 @@ -1059,9 +1174,26 @@ export default { if (this.widgetIndex == i) { this.widgets[i].value[key] = this.deepClone(val); this.setDefaultValue(this.widgets[i].options[key], val); + // 同步 tabsList 回 widgetOptions,防止 setup 配置变更时丢失 tabs 的子组件数据 + const currentWidget = this.widgets[i]; + if (currentWidget && currentWidget.value && currentWidget.value.setup && currentWidget.value.setup.tabsList) { + const tabsListItem = this.widgetOptions.setup.find(item => item.name === 'tabsList'); + if (tabsListItem) { + tabsListItem.value = currentWidget.value.setup.tabsList; + } + } } } } + + // 【关键优化】配置变更后,不立即触发整体重渲染! + // 只在用户停止操作后,才异步更新 renderWidgets + if (this.configChangeTimer) { + clearTimeout(this.configChangeTimer); + } + this.configChangeTimer = setTimeout(() => { + this.updateRenderWidgets(); + }, 300); // 300ms 防抖,等待用户完成连续操作 } }, setDefaultValue(options, val) { @@ -1111,7 +1243,7 @@ export default { }, //鼠标按下事件 downEvent(event){ - console.log("downEvent") + // console.log("downEvent") this.moveTimes = 0; this.selectedWidgets = []; this.openMulDrag = false; @@ -1127,7 +1259,7 @@ export default { }, //鼠标移动事件 moveEvent(event){ - console.log("moveEvent"); + // console.log("moveEvent"); //测试的时候发现,每次点击组件,再次点击大屏的时候,偶尔会触发一次moveEvent,导致会生成rect,所以加了移动次数moveTimes 变量控制一下,只有移动多次的情况下,才能说明是框选多选 if(this.selectFlag && this.selectedWidgets.length <= 1 && this.moveTimes >= 1){ if(this.rect === null){ @@ -1227,7 +1359,74 @@ export default { (widget.value.position.top - this.downY) * (widget.value.position.top - this.downY2) < 0 || (widget.value.position.top + widget.value.position.height - this.downY) * (widget.value.position.top + widget.value.position.height- this.downY2) < 0 ); - } + }, + /** + * 分批渲染组件,避免一次性创建过多 DOM 节点导致页面卡顿 + */ + startBatchRender() { + this.renderedCount = 0; + this.renderBatches = []; + + const renderNextBatch = () => { + if (this.renderedCount >= this.widgets.length) { + console.log('[Designer] 所有组件渲染完成,总数:', this.widgets.length); + return; + } + + // 计算本批次要渲染的组件 + const start = this.renderedCount; + const end = Math.min(start + this.batchSize, this.widgets.length); + + // 使用 slice 创建新批次并冻结,避免响应式系统占用内存 + const batch = []; + for (let i = start; i < end; i++) { + batch.push(Object.freeze({ + ...this.widgets[i], + originalIndex: i + })); + } + + // 添加到渲染批次 + this.renderBatches.push(Object.freeze(batch)); + this.renderedCount = end; + + console.log(`[Designer] 渲染第 ${this.renderBatches.length} 批,组件 ${start}-${end-1}`); + + // 继续渲染下一批 + if (this.renderedCount < this.widgets.length) { + this.renderTimer = setTimeout(renderNextBatch, 50); // 每批间隔 50ms + } + }; + + // 开始渲染第一批 + renderNextBatch(); + }, + /** + * 立即渲染所有组件 (用于组件数量较少时) + */ + renderAllWidgets() { + if (this.widgets.length <= this.batchSize) { + // 组件数量少,直接渲染 + const batch = this.widgets.map((w, i) => Object.freeze({ + ...w, + originalIndex: i + })); + this.renderBatches = [Object.freeze(batch)]; + } else { + // 组件数量多,分批渲染 + this.startBatchRender(); + } + }, + /** + * 更新渲染批次(用于配置变更后刷新组件列表) + */ + updateRenderWidgets() { + if (!this.widgets || this.widgets.length === 0) { + this.renderBatches = []; + return; + } + this.renderAllWidgets(); + }, }, }; diff --git a/src/views/bigscreenDesigner/designer/linkageLogic.js b/src/views/bigscreenDesigner/designer/linkageLogic.js index 3f81cdb..265670f 100644 --- a/src/views/bigscreenDesigner/designer/linkageLogic.js +++ b/src/views/bigscreenDesigner/designer/linkageLogic.js @@ -6,6 +6,41 @@ * @Description: 各联动组件的参数配置 参数paramsKey的值具体封装时再改 */ import { eventBus as bus } from "@/utils/eventBus"; + +// 组件实例映射表:通过 widgetId 获取组件实例 +export const widgetInstanceMap = {}; + +/** + * 注册组件实例 + * @param {Object} instance - Vue组件实例 + */ +export const registerWidgetInstance = function(instance) { + if (instance && instance.value && instance.value.setup && instance.value.setup.widgetId) { + const widgetId = instance.value.setup.widgetId; + widgetInstanceMap[widgetId] = instance; + } +}; + +/** + * 注销组件实例 + * @param {Object} instance - Vue组件实例 + */ +export const unregisterWidgetInstance = function(instance) { + if (instance && instance.value && instance.value.setup && instance.value.setup.widgetId) { + const widgetId = instance.value.setup.widgetId; + delete widgetInstanceMap[widgetId]; + } +}; + +/** + * 根据 widgetId 获取组件实例 + * @param {String} widgetId + * @returns {Object} 组件实例 + */ +export const getWidgetInstance = function(widgetId) { + return widgetInstanceMap[widgetId]; +}; + export const lickageParamsConfig = [ // { // name: '按钮组', @@ -63,6 +98,16 @@ export const lickageParamsConfig = [ code: 'widget-button', paramsKey: [] //按钮需要的key来源于要联动的组件所需要的参数 }, + { + name: '分页组件', + code: 'widget-pagination', + paramsKey: ['currentPage', 'pageSize', 'total', 'pageCount'] + }, + { + name: '表格', + code: 'widget-table', + paramsKey: ['list', 'total', 'currentPage', 'pageSize', 'pageCount'] + }, ] export const getOneConfigByCode = function (code) { @@ -78,47 +123,51 @@ export const getOneConfigByName = function (name) { * @param self 组件实例对象 this * @param isActiveClick 主动触发(非echart类click事件触发) * @param buttonConfig 按钮组组件的配置 - * 40@remarks - * 1、v-chart 需添加 ref="myVChart" 以获取实例 - * 2、 发消息发过去的对象 待封装配置动态兼容 */ export const originWidgetLinkageLogic = function (self, isActiveClick = false, buttonConfig = {}) { - // if (self.allComponentLinkage && self.allComponentLinkage.length && self.allComponentLinkage[self.widgetIndex].index !== -1 && self.allComponentLinkage[self.widgetIndex].linkageArr.length) { - if (self.optionsSetup.componentLinkage && self.optionsSetup.componentLinkage.length) { - if (isActiveClick) { // 主动触发 - self.allComponentLinkage[self.widgetIndex].linkageArr.forEach(item => { - console.log(item) - console.log(`bus_${item.originId}_${item.targetId}`, ' -联动逻辑点击-发送消息', buttonConfig) - bus.$emit(`bus_${item.originId}_${item.targetId}`, buttonConfig.currentData) + if (!self.optionsSetup.componentLinkage || !self.optionsSetup.componentLinkage.length) { + return; + } + + const currentWidgetId = self.value.setup.widgetId; + const currentLinkage = self.allComponentLinkage.find(item => { + return item && item.widgetId === currentWidgetId; + }); + + if (currentLinkage && currentLinkage.linkageArr && currentLinkage.linkageArr.length) { + if (isActiveClick) { // 主动触发(按钮点击) + // 按钮组件:需要从源组件获取实时数据 + if (self.value.setup.widgetCode === 'widget-button') { + const firstLinkage = currentLinkage.linkageArr[0]; + + if (firstLinkage && firstLinkage.originId) { + const sourceWidgetInstance = getWidgetInstance(firstLinkage.originId); + + if (sourceWidgetInstance && typeof sourceWidgetInstance.getLinkageData === 'function') { + buttonConfig.currentData = sourceWidgetInstance.getLinkageData(); + } else { + // 源组件未找到或没有 getLinkageData 方法,使用传入的数据 + console.warn('[Linkage] 源组件未找到或无 getLinkageData:', firstLinkage.originId); + } + } + } + console.log('buttonConfig.currentData', buttonConfig.currentData) + currentLinkage.linkageArr.forEach(item => { + bus.$emit(`bus_${item.originId}_${item.targetId}`, { + ...buttonConfig.currentData, + _paramsConfig: item.paramsConfig + }) }) - } else { // chart 组件 + } + else { self.$refs.myVChart.chart.on('click', function (params) { - self.allComponentLinkage[self.widgetIndex].linkageArr.forEach(item => { - console.log(`bus_${item.originId}_${item.targetId}`, ' -联动逻辑点击-发送消息', params) - console.log(self.value) + currentLinkage.linkageArr.forEach(item => { let message = {} const widgetConfigTemp = getOneConfigByCode(self.value.widgetCode) - console.log('widgetConfigTemp', widgetConfigTemp) - if (widgetConfigTemp && widgetConfigTemp.paramsKey.length) { // 动态加载各组件的参数来封装 + if (widgetConfigTemp && widgetConfigTemp.paramsKey.length) { widgetConfigTemp.paramsKey.forEach(key => { message[key] = params[key] }) - // 40@remarks 部分组件 传参需要特殊处理下 - // …… - // 40@remarks 专用于测试联动发消息 手动改造消息内容 - // if (self.value.widgetCode === 'widgetMap2d') { - // const nameTemp = ['苹果', '三星', '小米', '华为', 'OPPO', 'VIVO'] - // // message = { - // // name: nameTemp[(params.dataIndex % 6)], - // // value: params.value, - // // dataIndex: params.dataIndex - // // } - // // message.name = nameTemp[(+params.value % 6)] - // message.name = nameTemp[(parseInt(Math.random() * 6) % 6)] - // } - // if (self.value.widgetCode === 'widget-piechart') { - // message.name = (parseInt(Math.random() * 2) % 2) === 0 ? '深圳市' : '盐田区' - // } } else { message = { name: params.name, @@ -138,33 +187,39 @@ export const originWidgetLinkageLogic = function (self, isActiveClick = false, b * @returns */ export const targetWidgetLinkageLogic = function (self) { - const busEvents = [] - // 有无有关联的组件 - if (!self.allComponentLinkage || !self.allComponentLinkage.length) return + if (!self.allComponentLinkage || !self.allComponentLinkage.length) { + return; + } + + const currentWidgetId = self.value.setup.widgetId; + const componentSelf = self; + const busEvents = []; + self.allComponentLinkage.some(item => { - if (item.index !== -1 && item.linkageArr.length) { + if (item && item.linkageArr && item.linkageArr.length) { item.linkageArr.some(obj => { - if (obj.targetId === self.value.setup.widgetId) { - //如果当前对象是button按钮,需要把传递参数设置给他,方便点击的时候校验表单输入情况 + if (obj.targetId === currentWidgetId) { if (self.value.setup.widgetCode === 'widget-button') { - self.setFormData(obj.paramsConfig); //把要校验的参数传给button表单 + self.setFormData(obj.paramsConfig); } - self.hasLinkage = true + self.hasLinkage = true; busEvents.push({ eventName: `bus_${obj.originId}_${obj.targetId}`, paramsConfig: obj.paramsConfig - }) - return true + }); + return true; } - }) + }); } - }) + }); + if (self.hasLinkage) { busEvents.forEach(item => { - bus.$on(item.eventName, e => { - console.log(item.eventName, ' 接收消息e', e) - self.setOptionsData(e, item.paramsConfig) - }) - }) + bus.$on(item.eventName, function(e) { + if (componentSelf && typeof componentSelf.setOptionsData === 'function') { + componentSelf.setOptionsData(e, item.paramsConfig); + } + }); + }); } } diff --git a/src/views/bigscreenDesigner/designer/tools/configure/form/widget-form-time.js b/src/views/bigscreenDesigner/designer/tools/configure/form/widget-form-time.js index 14657ff..a4653fb 100644 --- a/src/views/bigscreenDesigner/designer/tools/configure/form/widget-form-time.js +++ b/src/views/bigscreenDesigner/designer/tools/configure/form/widget-form-time.js @@ -27,6 +27,8 @@ export const widgetFormTime = { selectOptions: [ { code: 'datetimerange', name: 'yyyy-MM-dd HH:mm:ss' }, { code: 'daterange', name: 'yyyy-MM-dd' }, + { code: 'monthrange', name: 'yyyy-MM' }, + { code: 'yearrange', name: 'yyyy' }, ], value: 'datetimerange', }, diff --git a/src/views/bigscreenDesigner/designer/tools/configure/scatterCharts/widget-quadrant-scatter.js b/src/views/bigscreenDesigner/designer/tools/configure/scatterCharts/widget-quadrant-scatter.js new file mode 100644 index 0000000..946c5a1 --- /dev/null +++ b/src/views/bigscreenDesigner/designer/tools/configure/scatterCharts/widget-quadrant-scatter.js @@ -0,0 +1,1198 @@ +/* + * @Descripttion: 四象限散点图json + * @version: + * @Author: qianlishi + * @Date: 2026-04-11 + */ +export const widgetQuadrantScatter = { + code: 'widget-quadrant-scatter', + type: 'quadrantScatter', + tabName: '四象限图', + label: '四象限散点图', + icon: 'icon-24gf-chartScatter', + options: { + setup: [ + { + type: 'el-input-text', + label: '图层名称', + name: 'layerName', + required: false, + placeholder: '', + value: '四象限散点图', + }, + { + type: 'vue-color', + label: '背景颜色', + name: 'background', + required: false, + placeholder: '', + value: '' + }, + [ + { + name: '标题设置', + list: [ + { + type: 'el-switch', + label: '标题显示', + name: 'isShowTitle', + required: false, + placeholder: '', + value: true, + }, + { + type: 'el-input-text', + label: '标题名', + name: 'text', + required: false, + placeholder: '', + value: '产品分析', + }, + { + type: 'vue-color', + label: '字体颜色', + name: 'textColor', + required: false, + placeholder: '', + value: '#FFD700' + }, + { + type: 'el-input-number', + label: '字体字号', + name: 'textFontSize', + required: false, + placeholder: '', + value: 20 + }, + { + type: 'el-select', + label: '字体粗细', + name: 'textFontWeight', + required: false, + placeholder: '', + selectOptions: [ + {code: 'normal', name: '正常'}, + {code: 'bold', name: '粗体'}, + {code: 'bolder', name: '特粗体'}, + {code: 'lighter', name: '细体'} + ], + value: 'normal' + }, + { + type: 'el-select', + label: '字体风格', + name: 'textFontStyle', + required: false, + placeholder: '', + selectOptions: [ + {code: 'normal', name: '正常'}, + {code: 'italic', name: 'italic斜体'}, + {code: 'oblique', name: 'oblique斜体'}, + ], + value: 'normal' + }, + { + type: 'el-select', + label: '字体系列', + name: 'textFontFamily', + required: false, + placeholder: '', + selectOptions: [ + {code: 'sans-serif', name: 'sans-serif'}, + {code: 'serif', name: 'serif'}, + {code: 'Arial', name: 'Arial'}, + {code: 'Courier New', name: 'Courier New'}, + ], + value: 'sans-serif' + }, + { + type: 'el-input-text', + label: '副标题名', + name: 'subtext', + required: false, + placeholder: '', + value: '' + }, + { + type: 'vue-color', + label: '副标题颜色', + name: 'subtextColor', + required: false, + placeholder: '', + value: 'rgba(30, 144, 255, 1)' + }, + { + type: 'el-input-number', + label: '副标题字号', + name: 'subtextFontSize', + required: false, + placeholder: '', + value: 16 + }, + { + type: 'el-select', + label: '副标题粗细', + name: 'subtextFontWeight', + required: false, + placeholder: '', + selectOptions: [ + {code: 'normal', name: '正常'}, + {code: 'bold', name: '粗体'}, + ], + value: 'normal' + }, + { + type: 'el-select', + label: '左右位置', + name: 'titleLeft', + required: false, + placeholder: '', + selectOptions: [ + {code: 'center', name: '居中'}, + {code: 'left', name: '左对齐'}, + {code: 'right', name: '右对齐'}, + ], + value: 'center' + }, + { + type: 'el-slider', + label: '上下间距', + name: 'titleTop', + required: false, + placeholder: '', + value: 5, + }, + { + type: 'el-input-number', + label: '主副标题间距', + name: 'titleItemGap', + required: false, + placeholder: '', + value: 0 + }, + ], + }, + { + name: '四象限设置', + list: [ + { + type: 'el-input-number', + label: 'X轴分割值', + name: 'splitX', + required: false, + placeholder: '', + value: 30, + }, + { + type: 'el-input-number', + label: 'Y轴分割值', + name: 'splitY', + required: false, + placeholder: '', + value: 30, + }, + { + type: 'el-switch', + label: '显示分割线标注', + name: 'isShowSplitLabel', + required: false, + placeholder: '', + value: true, + }, + { + type: 'el-input-text', + label: 'X轴分割标注', + name: 'splitXLabel', + required: false, + placeholder: '如: 销售额增长率>30%', + value: '销售额增长率>30%', + }, + { + type: 'el-input-text', + label: 'Y轴分割标注', + name: 'splitYLabel', + required: false, + placeholder: '如: 毛利率>30%', + value: '毛利率>30%', + }, + { + type: 'vue-color', + label: '分割标注背景', + name: 'splitLabelBgColor', + required: false, + placeholder: '', + value: 'rgba(0, 123, 255, 0.8)', + }, + { + type: 'vue-color', + label: '分割标注颜色', + name: 'splitLabelColor', + required: false, + placeholder: '', + value: '#fff', + }, + { + type: 'el-input-number', + label: '分割标注字号', + name: 'splitLabelFontSize', + required: false, + placeholder: '', + value: 12, + }, + { + type: 'vue-color', + label: '分割线颜色', + name: 'splitLineColor', + required: false, + placeholder: '', + value: '#666666', + }, + { + type: 'el-input-number', + label: '分割线宽度', + name: 'splitLineWidth', + required: false, + placeholder: '', + value: 1, + }, + { + type: 'el-select', + label: '分割线类型', + name: 'splitLineType', + required: false, + placeholder: '', + selectOptions: [ + {code: 'solid', name: '实线'}, + {code: 'dashed', name: '虚线'}, + {code: 'dotted', name: '点线'}, + ], + value: 'dashed' + }, + { + type: 'el-switch', + label: '显示象限标签', + name: 'isShowQuadrantLabel', + required: false, + placeholder: '', + value: true, + }, + { + type: 'el-input-number', + label: '象限字号', + name: 'quadrantLabelFontSize', + required: false, + placeholder: '', + value: 14, + }, + { + type: 'el-select', + label: '象限字重', + name: 'quadrantLabelFontWeight', + required: false, + placeholder: '', + selectOptions: [ + {code: 'normal', name: '正常'}, + {code: 'bold', name: '粗体'}, + ], + value: 'bold' + }, + ], + }, + { + name: '第二象限设置(右上)', + list: [ + { + type: 'el-input-text', + label: '象限名称', + name: 'quadrant2Label', + required: false, + placeholder: '', + value: '问题产品', + }, + { + type: 'vue-color', + label: '文字颜色', + name: 'quadrant2LabelColor', + required: false, + placeholder: '', + value: '#FF9800', + }, + { + type: 'vue-color', + label: '背景颜色', + name: 'quadrant2BgColor', + required: false, + placeholder: '', + value: 'rgba(255, 152, 0, 0.2)', + }, + ], + }, + { + name: '第一象限设置(左上)', + list: [ + { + type: 'el-input-text', + label: '象限名称', + name: 'quadrant1Label', + required: false, + placeholder: '', + value: '明星产品', + }, + { + type: 'vue-color', + label: '文字颜色', + name: 'quadrant1LabelColor', + required: false, + placeholder: '', + value: '#4CAF50', + }, + { + type: 'vue-color', + label: '背景颜色', + name: 'quadrant1BgColor', + required: false, + placeholder: '', + value: 'rgba(76, 175, 80, 0.2)', + }, + ], + }, + { + name: '第三象限设置(左下)', + list: [ + { + type: 'el-input-text', + label: '象限名称', + name: 'quadrant3Label', + required: false, + placeholder: '', + value: '瘦狗产品', + }, + { + type: 'vue-color', + label: '文字颜色', + name: 'quadrant3LabelColor', + required: false, + placeholder: '', + value: '#9E9E9E', + }, + { + type: 'vue-color', + label: '背景颜色', + name: 'quadrant3BgColor', + required: false, + placeholder: '', + value: 'rgba(158, 158, 158, 0.2)', + }, + ], + }, + { + name: '第四象限设置(右下)', + list: [ + { + type: 'el-input-text', + label: '象限名称', + name: 'quadrant4Label', + required: false, + placeholder: '', + value: '金牛产品', + }, + { + type: 'vue-color', + label: '文字颜色', + name: 'quadrant4LabelColor', + required: false, + placeholder: '', + value: '#2196F3', + }, + { + type: 'vue-color', + label: '背景颜色', + name: 'quadrant4BgColor', + required: false, + placeholder: '', + value: 'rgba(33, 150, 243, 0.2)', + }, + ], + }, + { + name: '散点设置', + list: [ + { + type: 'el-select', + label: '点样式', + name: 'symbol', + required: false, + placeholder: '', + selectOptions: [ + { code: 'circle', name: '实心点' }, + { code: 'emptyCircle', name: '空心点' }, + ], + value: 'circle' + }, + { + type: 'el-slider', + label: '点大小', + name: 'pointSize', + required: false, + placeholder: '', + value: 15, + }, + { + type: 'vue-color', + label: '点边框颜色', + name: 'pointBorderColor', + required: false, + placeholder: '', + value: '#007BFF', + }, + { + type: 'el-input-number', + label: '点边框宽度', + name: 'pointBorderWidth', + required: false, + placeholder: '', + value: 2, + }, + { + type: 'vue-color', + label: '点填充颜色', + name: 'pointFillColor', + required: false, + placeholder: '', + value: '#FF7F50', + }, + { + type: 'el-select', + label: '配色样式', + name: 'colorStyle', + required: false, + placeholder: '', + selectOptions: [ + { code: 'same', name: '同色' }, + { code: 'unsame', name: '异色' }, + ], + value: 'unsame' + }, + { + type: 'customColor', + label: '', + name: 'customColor', + required: false, + value: [{ color: '#FF7F50' }, { color: '#87cefa' }, { color: '#da70d6' }, { color: '#32cd32' }, { color: '#6495ed' }], + }, + ], + }, + { + name: '数据标注设置', + list: [ + { + type: 'el-switch', + label: '显示标注', + name: 'isShowLabel', + required: false, + placeholder: '', + value: true, + }, + { + type: 'el-input-text', + label: '标注格式', + name: 'labelFormat', + required: false, + placeholder: '可用占位符: {name},{value},{x},{y}', + value: '{name}', + }, + { + type: 'el-input-text', + label: '金额字段名', + name: 'amountField', + required: false, + placeholder: '如: amount', + value: 'amount', + }, + { + type: 'el-input-text', + label: '金额单位', + name: 'amountUnit', + required: false, + placeholder: '如: 万', + value: '万', + }, + { + type: 'el-select', + label: '标注位置', + name: 'labelPosition', + required: false, + placeholder: '', + selectOptions: [ + { code: 'top', name: '上' }, + { code: 'bottom', name: '下' }, + { code: 'right', name: '右' }, + { code: 'left', name: '左' }, + ], + value: 'right' + }, + { + type: 'el-input-number', + label: '标注偏移X', + name: 'labelOffsetX', + required: false, + placeholder: '', + value: 10, + }, + { + type: 'el-input-number', + label: '标注偏移Y', + name: 'labelOffsetY', + required: false, + placeholder: '', + value: 0, + }, + { + type: 'vue-color', + label: '标注颜色', + name: 'labelColor', + required: false, + placeholder: '', + value: '#fff', + }, + { + type: 'el-input-number', + label: '标注字号', + name: 'labelFontSize', + required: false, + placeholder: '', + value: 12, + }, + { + type: 'el-select', + label: '标注字重', + name: 'labelFontWeight', + required: false, + placeholder: '', + selectOptions: [ + { code: 'normal', name: '正常' }, + { code: 'bold', name: '粗体' }, + ], + value: 'normal' + }, + ], + }, + { + name: '右侧数据列表', + list: [ + { + type: 'el-switch', + label: '显示列表', + name: 'isShowRightList', + required: false, + placeholder: '', + value: true, + }, + { + type: 'el-input-text', + label: '列表标题', + name: 'rightListTitle', + required: false, + placeholder: '', + value: '数据分析', + }, + { + type: 'vue-color', + label: '标题颜色', + name: 'rightListTitleColor', + required: false, + placeholder: '', + value: '#FFD700', + }, + { + type: 'el-input-number', + label: '标题字号', + name: 'rightListTitleFontSize', + required: false, + placeholder: '', + value: 16, + }, + { + type: 'el-input-number', + label: '列表宽度', + name: 'rightListWidth', + required: false, + placeholder: '', + value: 200, + }, + { + type: 'el-slider', + label: '宽度占比(%)', + name: 'rightListWidthRatio', + required: false, + placeholder: '', + value: 20, + }, + { + type: 'el-slider', + label: '高度占比(%)', + name: 'rightListHeightRatio', + required: false, + placeholder: '', + value: 100, + }, + { + type: 'vue-color', + label: '列表背景色', + name: 'rightListBgColor', + required: false, + placeholder: '', + value: 'rgba(0, 0, 0, 0.3)', + }, + { + type: 'el-input-number', + label: '内边距', + name: 'rightListPadding', + required: false, + placeholder: '', + value: 15, + }, + { + type: 'vue-color', + label: '象限标题颜色', + name: 'quadrantTitleColor', + required: false, + placeholder: '', + value: '#fff', + }, + { + type: 'el-input-number', + label: '象限标题字号', + name: 'quadrantTitleFontSize', + required: false, + placeholder: '', + value: 14, + }, + { + type: 'vue-color', + label: '数据文字颜色', + name: 'listItemColor', + required: false, + placeholder: '', + value: 'rgba(255, 255, 255, 0.8)', + }, + { + type: 'el-input-number', + label: '数据字号', + name: 'listItemFontSize', + required: false, + placeholder: '', + value: 12, + }, + { + type: 'el-input-text', + label: '列表数据格式', + name: 'listItemFormat', + required: false, + placeholder: '{name}:{amount}{unit}', + value: '{name}:{amount}{unit}', + }, + ], + }, + { + name: 'X轴设置', + list: [ + { + type: 'el-switch', + label: '显示', + name: 'hideX', + required: false, + placeholder: '', + value: true, + }, + { + type: 'el-input-text', + label: '坐标名', + name: 'nameX', + required: false, + placeholder: '', + value: '销售额增长率' + }, + { + type: 'vue-color', + label: '坐标名颜色', + name: 'nameColorX', + required: false, + placeholder: '', + value: '#fff' + }, + { + type: 'el-input-number', + label: '坐标名字号', + name: 'nameFontSizeX', + required: false, + placeholder: '', + value: 14 + }, + { + type: 'el-input-text', + label: '最小值', + name: 'minX', + required: false, + placeholder: '', + value: '', + }, + { + type: 'el-input-text', + label: '最大值', + name: 'maxX', + required: false, + placeholder: '', + value: '', + }, + { + type: 'vue-color', + label: '数值颜色', + name: 'colorX', + required: false, + placeholder: '', + value: '#fff', + }, + { + type: 'el-input-number', + label: '数值字号', + name: 'fontSizeX', + required: false, + placeholder: '', + value: 14, + }, + { + type: 'vue-color', + label: '坐标轴颜色', + name: 'lineColorX', + required: false, + placeholder: '', + value: '#fff', + }, + { + type: 'el-input-number', + label: '坐标轴宽度', + name: 'lineWidthX', + required: false, + placeholder: '', + value: 1, + }, + { + type: 'el-switch', + label: '分割线显示', + name: 'isShowSplitLineX', + require: false, + placeholder: '', + value: false, + }, + { + type: 'vue-color', + label: '分割线颜色', + name: 'splitLineColorX', + required: false, + placeholder: '', + value: '#fff', + }, + ], + }, + { + name: 'Y轴设置', + list: [ + { + type: 'el-switch', + label: '显示', + name: 'isShowY', + require: false, + placeholder: '', + value: true, + }, + { + type: 'el-input-text', + label: '最大值', + name: 'maxY', + required: false, + placeholder: '', + value: '', + }, + { + type: 'el-input-text', + label: '最小值', + name: 'minY', + required: false, + placeholder: '', + value: '', + }, + { + type: 'el-input-text', + label: '坐标名', + name: 'textNameY', + require: false, + placeholder: '', + value: '毛利率' + }, + { + type: 'vue-color', + label: '坐标名颜色', + name: 'nameColorY', + required: false, + placeholder: '', + value: '#fff', + }, + { + type: 'el-input-number', + label: '坐标名字号', + name: 'nameFontSizeY', + required: false, + placeholder: '', + value: 14, + }, + { + type: 'vue-color', + label: '数值颜色', + name: 'colorY', + required: false, + placeholder: '', + value: '#fff', + }, + { + type: 'el-input-number', + label: '数值字号', + name: 'fontSizeY', + required: false, + placeholder: '', + value: 14, + }, + { + type: 'vue-color', + label: '坐标轴颜色', + name: 'lineColorY', + required: false, + placeholder: '', + value: '#fff', + }, + { + type: 'el-input-number', + label: '坐标轴宽度', + name: 'lineWidthY', + required: false, + placeholder: '', + value: 1, + }, + { + type: 'el-switch', + label: '分割线显示', + name: 'isShowSplitLineY', + require: false, + placeholder: '', + value: false, + }, + { + type: 'vue-color', + label: '分割线颜色', + name: 'splitLineColorY', + required: false, + placeholder: '', + value: '#fff', + }, + ], + }, + { + name: '数值设定', + list: [ + { + type: 'el-switch', + label: '显示', + name: 'isShow', + required: false, + placeholder: '', + value: false + }, + { + type: 'el-select', + label: '位置', + name: 'fontPosition', + required: false, + placeholder: '', + selectOptions: [ + {code: 'top', name: '上'}, + {code: 'left', name: '左'}, + {code: 'right', name: '右'}, + {code: 'inside', name: '里'}, + {code: 'insideTop', name: '里顶'}, + {code: 'insideLeft', name: '里左'}, + {code: 'insideRight', name: '里右'}, + {code: 'insideBottom', name: '里底'}, + ], + value: 'inside' + }, + { + type: 'el-input-number', + label: '距离', + name: 'fontDistance', + required: false, + placeholder: '', + value: 10 + }, + { + type: 'vue-color', + label: '字体颜色', + name: 'dataColor', + required: false, + placeholder: '', + value: '' + }, + { + type: 'el-input-number', + label: '字体字号', + name: 'fontSize', + required: false, + placeholder: '', + value: 12 + }, + { + type: 'el-select', + label: '字体粗细', + name: 'fontWeight', + required: false, + placeholder: '', + selectOptions: [ + { code: 'normal', name: '正常' }, + { code: 'bold', name: '粗体' }, + ], + value: 'normal' + }, + ], + }, + { + name: '提示语设置', + list: [ + { + type: 'el-switch', + label: '显示', + name: 'isShowTooltip', + required: false, + placeholder: '', + value: true + }, + { + type: 'el-select', + label: '触发类型', + name: 'tooltipTrigger', + required: false, + placeholder: '', + selectOptions: [ + {code: 'item', name: '数据项'}, + {code: 'axis', name: '坐标轴'}, + ], + value: 'item' + }, + { + type: 'vue-color', + label: '背景颜色', + name: 'tooltipBackgroundColor', + required: false, + placeholder: '', + value: '#333' + }, + { + type: 'vue-color', + label: '字体颜色', + name: 'tooltipColor', + required: false, + placeholder: '', + value: '#00FEFF' + }, + { + type: 'el-input-number', + label: '字体字号', + name: 'tooltipFontSize', + required: false, + placeholder: '', + value: 14 + }, + ], + }, + { + name: '图例操作', + list: [ + { + type: 'el-switch', + label: '图例显示', + name: 'isShowLegend', + required: false, + placeholder: '', + value: false, + }, + { + type: 'vue-color', + label: '字体颜色', + name: 'legendColor', + required: false, + placeholder: '', + value: '#fff', + }, + { + type: 'el-input-number', + label: '字体字号', + name: 'legendFontSize', + required: false, + placeholder: '', + value: 12, + }, + { + type: 'el-select', + label: '横向位置', + name: 'lateralPosition', + required: false, + placeholder: '', + selectOptions: [ + {code: 'center', name: '居中'}, + {code: 'left', name: '左对齐'}, + {code: 'right', name: '右对齐'}, + ], + value: 'center' + }, + { + type: 'el-select', + label: '纵向位置', + name: 'longitudinalPosition', + required: false, + placeholder: '', + selectOptions: [ + {code: 'top', name: '顶部'}, + {code: 'bottom', name: '底部'}, + ], + value: 'top' + }, + ], + }, + { + name: '坐标轴边距设置', + list: [ + { + type: 'el-slider', + label: '左边距(像素)', + name: 'marginLeft', + required: false, + placeholder: '', + value: 10, + }, { + type: 'el-slider', + label: '顶边距(像素)', + name: 'marginTop', + required: false, + placeholder: '', + value: 50, + }, { + type: 'el-slider', + label: '右边距(像素)', + name: 'marginRight', + required: false, + placeholder: '', + value: 40, + }, { + type: 'el-slider', + label: '底边距(像素)', + name: 'marginBottom', + required: false, + placeholder: '', + value: 40, + }, + ], + }, + ], + ], + data: [ + { + type: 'el-radio-group', + label: '数据类型', + name: 'dataType', + require: false, + placeholder: '', + selectValue: true, + selectOptions: [ + { + code: 'staticData', + name: '静态数据', + }, + { + code: 'dynamicData', + name: '动态数据', + }, + ], + value: 'staticData', + }, + { + type: 'el-input-number', + label: '刷新时间(毫秒)', + name: 'refreshTime', + relactiveDom: 'dataType', + relactiveDomValue: 'dynamicData', + value: 600000 + }, + { + type: 'el-button', + label: '静态数据', + name: 'staticData', + required: false, + placeholder: '', + relactiveDom: 'dataType', + relactiveDomValue: 'staticData', + value: [ + + ], + }, + { + type: 'dycustComponents', + label: '字段映射', + name: 'dynamicData', + required: false, + placeholder: '', + relactiveDom: 'dataType', + relactiveDomValue: 'dynamicData', + chartType: 'widget-quadrant-scatter', + dictKey: 'SCATTER_PROPERTIES_FOUR', + value: { + 'name': 'name', + 'xAxis': 'xData', + 'yAxis': 'yData' + }, + }, + ], + position: [ + { + type: 'el-input-number', + label: '左边距', + name: 'left', + required: false, + placeholder: '', + value: 0, + }, + { + type: 'el-input-number', + label: '上边距', + name: 'top', + required: false, + placeholder: '', + value: 0, + }, + { + type: 'el-input-number', + label: '宽度', + name: 'width', + required: false, + placeholder: '该容器在1920px大屏中的宽度', + value: 500, + }, + { + type: 'el-input-number', + label: '高度', + name: 'height', + required: false, + placeholder: '该容器在1080px大屏中的高度', + value: 400, + }, + ], + methods: [] + } +} diff --git a/src/views/bigscreenDesigner/designer/tools/configure/texts/widget-pagination.js b/src/views/bigscreenDesigner/designer/tools/configure/texts/widget-pagination.js new file mode 100644 index 0000000..8d71593 --- /dev/null +++ b/src/views/bigscreenDesigner/designer/tools/configure/texts/widget-pagination.js @@ -0,0 +1,260 @@ +/* + * @Descripttion: 分页组件 json + * @version: + * @Author: lishuaiwu + * @Date: 2025-06-05 13:44:32 + */ +export const widgetPagination = { + code: 'widget-pagination', + type: 'text', + tabName: '文本栏', + label: '分页', + icon: 'iconbiaoge', + options: { + // 配置 + setup: [ + { + type: 'el-input-text', + label: '图层名称', + name: 'layerName', + required: false, + placeholder: '', + value: '分页', + }, + [ + { + name: '分页样式', + list: [ + { + type: 'el-switch', + label: '开启背景色', + name: 'enableBackground', + required: false, + placeholder: '', + value: true, + }, + { + type: 'vue-color', + label: '页码背景色', + name: 'itemBackground', + required: false, + placeholder: '', + value: 'rgb(255, 255, 255)', + relactiveDom: 'enableBackground', + relactiveDomValue: true + }, + { + type: 'vue-color', + label: '页码文字颜色', + name: 'itemTextColor', + required: false, + placeholder: '', + value: 'rgb(51, 51, 51)' + }, + { + type: 'vue-color', + label: '选中背景色', + name: 'selectedBackground', + required: false, + placeholder: '', + value: 'rgb(10, 115, 255)', + relactiveDom: 'enableBackground', + relactiveDomValue: true + }, + { + type: 'vue-color', + label: '选中文字颜色', + name: 'selectedTextColor', + required: false, + placeholder: '', + value: 'rgb(255, 255, 255)' + }, + { + type: 'vue-color', + label: '边框颜色', + name: 'borderColor', + required: false, + placeholder: '', + value: 'rgb(221, 221, 221)', + relactiveDom: 'enableBackground', + relactiveDomValue: true + }, + { + type: 'vue-color', + label: '禁用文字颜色', + name: 'disabledTextColor', + required: false, + placeholder: '', + value: 'rgb(204, 204, 204)' + }, + { + type: 'vue-color', + label: '总数字体颜色', + name: 'totalTextColor', + required: false, + placeholder: '', + value: 'rgb(51, 51, 51)' + }, + { + type: 'el-input-number', + label: '字体大小', + name: 'fontSize', + required: false, + placeholder: '', + value: 14, + min: 12, + max: 32 + }, + { + type: 'el-switch', + label: '自定义每页条数', + name: 'customPageSize', + required: false, + placeholder: '', + value: false, + }, + { + type: 'el-input-number', + label: '设置条数', + name: 'pageSize', + required: false, + placeholder: '', + value: 10, + min: 1, + max: 1000, + step: 1, + relactiveDom: 'customPageSize', + relactiveDomValue: true + }, + { + type: 'el-switch', + label: '显示上一页/下一页', + name: 'showPrevNext', + required: false, + placeholder: '', + value: true, + }, + { + type: 'el-switch', + label: '显示跳页器', + name: 'showJumper', + required: false, + placeholder: '', + value: false, + }, + { + type: 'el-switch', + label: '显示总条数', + name: 'showTotal', + required: false, + placeholder: '', + value: true, + } + ] + } + ], + [{ + name: '组件联动', + list: [ + { + type: 'componentLinkage', + label: '', + name: 'componentLinkage', + required: false, + value: [] + } + ] + }] + ], + data: [ + { + type: 'el-radio-group', + label: '数据类型', + name: 'dataType', + require: false, + placeholder: '', + selectValue: true, + selectOptions: [ + { + code: 'staticData', + name: '静态数据', + }, + { + code: 'dynamicData', + name: '动态数据', + }, + ], + value: 'staticData', + }, + { + type: 'el-input-number', + label: '刷新时间 (毫秒)', + name: 'refreshTime', + relactiveDom: 'dataType', + relactiveDomValue: 'dynamicData', + value: 600000 + }, + { + type: 'el-button', + label: '静态数据', + name: 'staticData', + required: false, + placeholder: '', + relactiveDom: 'dataType', + relactiveDomValue: 'staticData', + value: { + total: 0, + currentPage: 1, + pageSize: 10, + pageCount: 0 + }, + }, + { + type: 'dycustComponents', + label: '', + name: 'dynamicData', + required: false, + placeholder: '', + relactiveDom: 'dataType', + relactiveDomValue: 'dynamicData', + chartType: 'widget-pagination', + dictKey: 'PAGINATION_PROPERTIES', + value: '', + } + ], + position: [ + { + type: 'el-input-number', + label: '左边距', + name: 'left', + required: false, + placeholder: '', + value: 0, + }, + { + type: 'el-input-number', + label: '上边距', + name: 'top', + required: false, + placeholder: '', + value: 0, + }, + { + type: 'el-input-number', + label: '宽度', + name: 'width', + required: false, + placeholder: '该容器在 1920px 大屏中的宽度', + value: 600, + }, + { + type: 'el-input-number', + label: '高度', + name: 'height', + required: false, + placeholder: '该容器在 1080px 大屏中的高度', + value: 400, + }, + ] + } +} diff --git a/src/views/bigscreenDesigner/designer/tools/configure/texts/widget-table.js b/src/views/bigscreenDesigner/designer/tools/configure/texts/widget-table.js index bf4e7d1..d6bba10 100644 --- a/src/views/bigscreenDesigner/designer/tools/configure/texts/widget-table.js +++ b/src/views/bigscreenDesigner/designer/tools/configure/texts/widget-table.js @@ -299,7 +299,7 @@ export const widgetTable = { }, { type: 'el-input-number', - label: '滚动间隔(毫秒)', + label: '滚动间隔 (毫秒)', name: 'interTime', required: false, placeholder: '', @@ -307,7 +307,7 @@ export const widgetTable = { }, { type: 'el-input-number', - label: '动效时间(毫秒)', + label: '动效时间 (毫秒)', name: 'delayTime', required: false, placeholder: '', @@ -323,6 +323,135 @@ export const widgetTable = { }, ] }, + { + name: '分页设置', + list: [ + { + type: 'el-switch', + label: '显示分页', + name: 'showPagination', + required: false, + placeholder: '', + value: false + }, + { + type: 'el-input-number', + label: '每页条数', + name: 'pageSize', + required: false, + placeholder: '', + value: 10, + min: 1, + max: 100, + step: 1 + }, + { + type: 'el-select', + label: '分页布局', + name: 'paginationLayout', + required: false, + placeholder: '', + selectOptions: [ + { code: 'total, sizes, prev, pager, next, jumper', name: '完整' }, + { code: 'total, prev, pager, next', name: '简洁' }, + { code: 'prev, pager, next', name: '仅翻页' }, + { code: 'total', name: '仅总数' }, + ], + value: 'total, sizes, prev, pager, next, jumper' + }, + { + type: 'el-switch', + label: '显示总条数', + name: 'showTotal', + required: false, + placeholder: '', + value: true + }, + { + type: 'el-switch', + label: '带背景色', + name: 'paginationBackground', + required: false, + placeholder: '', + value: true + }, + { + type: 'vue-color', + label: '分页主题色', + name: 'paginationActiveColor', + require: false, + placeholder: '', + value: '#409EFF', + }, + { + type: 'vue-color', + label: '总条数颜色', + name: 'paginationTotalColor', + require: false, + placeholder: '', + value: '#606266', + }, + { + type: 'vue-color', + label: '页码文字颜色', + name: 'paginationPageNumberColor', + require: false, + placeholder: '', + value: '#606266', + }, + { + type: 'vue-color', + label: '选中背景色', + name: 'paginationSelectedBgColor', + require: false, + placeholder: '', + value: '#409EFF', + }, + { + type: 'el-switch', + label: '显示跳页器', + name: 'showPaginationJumper', + required: false, + placeholder: '', + value: true + }, + { + type: 'el-select', + label: '分页位置', + name: 'paginationPosition', + required: false, + placeholder: '', + selectOptions: [ + { code: 'center', name: '居中' }, + { code: 'left', name: '左对齐' }, + { code: 'right', name: '右对齐' }, + ], + value: 'center' + }, + { + type: 'el-input-number', + label: '分页上边距', + name: 'paginationMarginTop', + required: false, + placeholder: '', + value: 10, + min: 0, + max: 100, + step: 1 + }, + { + type: 'el-input-number', + label: '分页下边距', + name: 'paginationMarginBottom', + required: false, + placeholder: '', + value: 10, + min: 0, + max: 100, + step: 1 + } + ] + }, ], { type: 'el-switch', diff --git a/src/views/bigscreenDesigner/designer/tools/main.js b/src/views/bigscreenDesigner/designer/tools/main.js index a322fb2..1e85194 100644 --- a/src/views/bigscreenDesigner/designer/tools/main.js +++ b/src/views/bigscreenDesigner/designer/tools/main.js @@ -20,6 +20,7 @@ import {widgetVideoMonitor} from "./configure/texts/widget-videoMonitor" import {widgetTable} from "./configure/texts/widget-table" import {widgetIframe} from "./configure/texts/widget-iframe" import {widgetCalendar} from "./configure/texts/widget-calendar" +import {widgetPagination} from "./configure/texts/widget-pagination" import {widgetUniversal} from "./configure/widget-universal" import {widgetBarchart} from "./configure/barCharts/widget-barchart" import {widgetGradientBarchart} from "./configure/barCharts/widget-gradient-barchart" @@ -48,6 +49,7 @@ import {widgetFormTime} from "./configure/form/widget-form-time"; import {widgetScaleVertical} from "./configure/scaleCharts/widget-scale-vertical"; import {widgetScaleHorizontal} from "./configure/scaleCharts/widget-scale-horizontal" import {widgetScatter} from "./configure/scatterCharts/widget-scatter"; +import {widgetQuadrantScatter} from "./configure/scatterCharts/widget-quadrant-scatter"; import {widgetBarDoubleYaxis} from "./configure/barCharts/widget-bar-double-yaxis-chart"; import {widgetBorder} from "./configure/styleWidget/widget-border"; import {widgetDecorateFlowLine} from "./configure/styleWidget/widget-decorate-flow-line"; @@ -77,6 +79,7 @@ export const widgetTool = [ widgetIframe, widgetCalendar, // widgetUniversal, + widgetPagination, widgetBarchart, widgetGradientBarchart, widgetLinechart, @@ -101,6 +104,7 @@ export const widgetTool = [ widgetScaleVertical, widgetScaleHorizontal, widgetScatter, + widgetQuadrantScatter, widgetSelect, // widgetInput, widgetFormTime, diff --git a/src/views/bigscreenDesigner/designer/widget/bar/widgetBarCompareChart.vue b/src/views/bigscreenDesigner/designer/widget/bar/widgetBarCompareChart.vue index 49bcefe..843227d 100644 --- a/src/views/bigscreenDesigner/designer/widget/bar/widgetBarCompareChart.vue +++ b/src/views/bigscreenDesigner/designer/widget/bar/widgetBarCompareChart.vue @@ -743,6 +743,13 @@ export default { this.setOptionsLegendName(legendName); }, }, + beforeDestroy() { + // 清除定时器,避免内存泄漏 + if (this.flagInter) { + clearInterval(this.flagInter); + this.flagInter = null; + } + }, }; diff --git a/src/views/bigscreenDesigner/designer/widget/bar/widgetBarDoubleYaxisChart.vue b/src/views/bigscreenDesigner/designer/widget/bar/widgetBarDoubleYaxisChart.vue index 3a35711..8f0e5f3 100644 --- a/src/views/bigscreenDesigner/designer/widget/bar/widgetBarDoubleYaxisChart.vue +++ b/src/views/bigscreenDesigner/designer/widget/bar/widgetBarDoubleYaxisChart.vue @@ -622,6 +622,13 @@ export default { this.setOptionsLegendName(legendName); }, }, + beforeDestroy() { + // 清除定时器,避免内存泄漏 + if (this.flagInter) { + clearInterval(this.flagInter); + this.flagInter = null; + } + }, }; diff --git a/src/views/bigscreenDesigner/designer/widget/bar/widgetBarStackChart.vue b/src/views/bigscreenDesigner/designer/widget/bar/widgetBarStackChart.vue index a0e98c5..2cc8072 100644 --- a/src/views/bigscreenDesigner/designer/widget/bar/widgetBarStackChart.vue +++ b/src/views/bigscreenDesigner/designer/widget/bar/widgetBarStackChart.vue @@ -619,6 +619,13 @@ export default { }) }, }, + beforeDestroy() { + // 清除定时器,避免内存泄漏 + if (this.flagInter) { + clearInterval(this.flagInter); + this.flagInter = null; + } + }, }; diff --git a/src/views/bigscreenDesigner/designer/widget/bar/widgetBarStackMoreShowChart.vue b/src/views/bigscreenDesigner/designer/widget/bar/widgetBarStackMoreShowChart.vue index 2793e77..0251221 100644 --- a/src/views/bigscreenDesigner/designer/widget/bar/widgetBarStackMoreShowChart.vue +++ b/src/views/bigscreenDesigner/designer/widget/bar/widgetBarStackMoreShowChart.vue @@ -598,6 +598,13 @@ export default { this.setOptionsLegendName(legendName); }, }, + beforeDestroy() { + // 清除定时器,避免内存泄漏 + if (this.flagInter) { + clearInterval(this.flagInter); + this.flagInter = null; + } + }, }; diff --git a/src/views/bigscreenDesigner/designer/widget/bar/widgetBarchart.vue b/src/views/bigscreenDesigner/designer/widget/bar/widgetBarchart.vue index f555e57..6a1fc21 100644 --- a/src/views/bigscreenDesigner/designer/widget/bar/widgetBarchart.vue +++ b/src/views/bigscreenDesigner/designer/widget/bar/widgetBarchart.vue @@ -625,6 +625,13 @@ export default { this.setOptionsLegendName(legendName); }, }, + beforeDestroy() { + // 清除定时器,避免内存泄漏 + if (this.flagInter) { + clearInterval(this.flagInter); + this.flagInter = null; + } + }, }; diff --git a/src/views/bigscreenDesigner/designer/widget/bar/widgetGradientColorBarchart.vue b/src/views/bigscreenDesigner/designer/widget/bar/widgetGradientColorBarchart.vue index 593b5cc..69185f1 100644 --- a/src/views/bigscreenDesigner/designer/widget/bar/widgetGradientColorBarchart.vue +++ b/src/views/bigscreenDesigner/designer/widget/bar/widgetGradientColorBarchart.vue @@ -597,6 +597,13 @@ export default { } }, }, + beforeDestroy() { + // 清除定时器,避免内存泄漏 + if (this.flagInter) { + clearInterval(this.flagInter); + this.flagInter = null; + } + }, }; diff --git a/src/views/bigscreenDesigner/designer/widget/barline/widgetBarLineSingleChart.vue b/src/views/bigscreenDesigner/designer/widget/barline/widgetBarLineSingleChart.vue index e4ed57b..efb4b45 100644 --- a/src/views/bigscreenDesigner/designer/widget/barline/widgetBarLineSingleChart.vue +++ b/src/views/bigscreenDesigner/designer/widget/barline/widgetBarLineSingleChart.vue @@ -646,6 +646,13 @@ export default { this.setOptionsLegendName(legendName); }, }, + beforeDestroy() { + // 清除定时器,避免内存泄漏 + if (this.flagInter) { + clearInterval(this.flagInter); + this.flagInter = null; + } + }, }; diff --git a/src/views/bigscreenDesigner/designer/widget/barline/widgetBarLineStackChart.vue b/src/views/bigscreenDesigner/designer/widget/barline/widgetBarLineStackChart.vue index 029f339..1b0651c 100644 --- a/src/views/bigscreenDesigner/designer/widget/barline/widgetBarLineStackChart.vue +++ b/src/views/bigscreenDesigner/designer/widget/barline/widgetBarLineStackChart.vue @@ -795,6 +795,13 @@ export default { return data; }, }, + beforeDestroy() { + // 清除定时器,避免内存泄漏 + if (this.flagInter) { + clearInterval(this.flagInter); + this.flagInter = null; + } + }, }; diff --git a/src/views/bigscreenDesigner/designer/widget/barline/widgetBarlinechart.vue b/src/views/bigscreenDesigner/designer/widget/barline/widgetBarlinechart.vue index b7211ff..b8d508f 100644 --- a/src/views/bigscreenDesigner/designer/widget/barline/widgetBarlinechart.vue +++ b/src/views/bigscreenDesigner/designer/widget/barline/widgetBarlinechart.vue @@ -204,6 +204,9 @@ export default { interval: optionsSetup.textIntervalX, // 文字角度 rotate: optionsSetup.textAngleX, + // 动态宽度基础保底,实际宽度会在图表数据填充时重新计算 + width: 60, + overflow: "truncate", textStyle: { // 坐标文字颜色 color: optionsSetup.textColorX, @@ -570,12 +573,19 @@ export default { legendName.push("line"); const optionsSetup = this.optionsSetup; // 根据图表的宽度 x轴的字体大小、长度来估算X轴的label能展示多少个字 - const rowsNum = optionsSetup.textRowsNum !== "" ? optionsSetup.textRowsNum : parseInt((this.optionsStyle.width / axis.length) / optionsSetup.textFontSizeX); + const xAxisDataLength = axis && axis.length > 0 ? axis.length : 1; + const labelWidth = Math.max( + parseInt((this.optionsStyle.width - 60) / xAxisDataLength), + 30 + ); + const rowsNum = optionsSetup.textRowsNum !== "" ? optionsSetup.textRowsNum : parseInt(labelWidth / optionsSetup.textFontSizeX); const axisLabel = { show: optionsSetup.isShowAxisLabelX, interval: optionsSetup.textIntervalX, // 文字角度 rotate: optionsSetup.textAngleX, + width: labelWidth, + overflow: optionsSetup.textRowsBreakAuto ? "break" : "truncate", textStyle: { // 坐标文字颜色 color: optionsSetup.textColorX, @@ -633,13 +643,19 @@ export default { } const optionsSetup = this.optionsSetup; // 根据图表的宽度 x轴的字体大小、长度来估算X轴的label能展示多少个字 - const xAxisDataLength = val.length !== 0 ? val.xAxis.length : 1; - const rowsNum = optionsSetup.textRowsNum !== "" ? optionsSetup.textRowsNum : parseInt((this.optionsStyle.width / xAxisDataLength) / optionsSetup.textFontSizeX); + const xAxisDataLength = val && val.xAxis && val.xAxis.length > 0 ? val.xAxis.length : 1; + const labelWidth = Math.max( + parseInt((this.optionsStyle.width - 60) / xAxisDataLength), + 30 + ); + const rowsNum = optionsSetup.textRowsNum !== "" ? optionsSetup.textRowsNum : parseInt(labelWidth / optionsSetup.textFontSizeX); const axisLabel = { show: optionsSetup.isShowAxisLabelX, interval: optionsSetup.textIntervalX, // 文字角度 rotate: optionsSetup.textAngleX, + width: labelWidth, + overflow: optionsSetup.textRowsBreakAuto ? "break" : "truncate", textStyle: { // 坐标文字颜色 color: optionsSetup.textColorX, diff --git a/src/views/bigscreenDesigner/designer/widget/form/widgetButton.vue b/src/views/bigscreenDesigner/designer/widget/form/widgetButton.vue index e6a6444..19afc6d 100644 --- a/src/views/bigscreenDesigner/designer/widget/form/widgetButton.vue +++ b/src/views/bigscreenDesigner/designer/widget/form/widgetButton.vue @@ -51,7 +51,8 @@ export default { data() { return { options: {}, - formData: {} , //要提交的参数表单 + formData: {}, //要提交的参数表单 + linkageData: null, // 存储从源组件接收到的原始联动数据 }; }, computed: { @@ -102,57 +103,73 @@ export default { methods: { //设置表单Key setFormData(paramConfig){ - console.log("paramConfig:" + paramConfig); + // console.log("paramConfig:" + paramConfig); paramConfig.forEach(item =>{ this.formData[item.targetKey] = ""; }); }, click() { - console.log(this.formData); + const currentData = this.linkageData || this.formData; + console.log("currentData", currentData); + // 检查是否有必填字段需要校验 let formDataKey = Object.keys(this.formData); let needInputAll = false; - for(let index in formDataKey){ - if(this.formData[formDataKey[index]] === null || this.formData[formDataKey[index]] === "" ){ - needInputAll = true; - break; + if (formDataKey.length > 0) { + // 只有在有配置必填字段时才校验 + for(let index in formDataKey){ + // 使用 linkageData 中的值进行校验,而不是 formData + const value = currentData[formDataKey[index]]; + if(value === null || value === undefined || value === ""){ + needInputAll = true; + break; + } } } if(needInputAll){ this.$message.error("请填写所有必填字段"); return; } + // 发送联动消息,携带当前数据 originWidgetLinkageLogic(this, true, { - currentData: this.formData, - }); // 联动-源组件逻辑 + currentData: currentData, + }); }, setOptionsData(e, paramsConfig) { let _this = this; - console.log("ces", e); - console.log("ces", paramsConfig); + // console.log("ces", e); + // console.log("ces", paramsConfig); const optionsData = this.optionsData; // 数据类型 静态 or 动态 - // 联动接收者逻辑开始 - optionsData.dynamicData = optionsData.dynamicData || {}; // 兼容 dynamicData undefined - const myDynamicData = optionsData.dynamicData; - clearInterval(this.flagInter); // 不管咋,先干掉上一次的定时任务,避免多跑 - if ( - e && - optionsData.dataType !== "staticData" && - Object.keys(myDynamicData.contextData).length - ) { - const keyArr = Object.keys(myDynamicData.contextData); + + // 处理来自其他组件的联动数据 + if (e && paramsConfig && paramsConfig.length) { + // 存储原始联动数据,供 click() 使用 + this.linkageData = e; + // 从 paramsConfig 中获取参数映射 paramsConfig.forEach((conf) => { - if (keyArr.includes(conf.targetKey)) { - myDynamicData.contextData[conf.targetKey] = e[conf.originKey]; - _this.formData[conf.targetKey] = e[conf.originKey]; //把参数设置到FormData + if (conf.targetKey && conf.originKey) { + // 将源数据映射到 formData + _this.formData[conf.targetKey] = e[conf.originKey]; } }); } - //这里,button按钮是要通过【按钮】联动触发其他组件,本身没有要展示的动态数据,所以无需执行 dynamicDataFn - // // 联动接收者逻辑结束 - // optionsData.dataType == "staticData" - // ? this.staticDataFn(optionsData.staticData) - // : this.dynamicDataFn(optionsData.dynamicData, optionsData.refreshTime); + + // 如果是动态数据,同时更新 contextData 以便后续刷新 + if (e && optionsData.dataType !== "staticData") { + optionsData.dynamicData = optionsData.dynamicData || {}; + const myDynamicData = optionsData.dynamicData; + clearInterval(this.flagInter); + + if (myDynamicData.contextData && Object.keys(myDynamicData.contextData).length) { + const keyArr = Object.keys(myDynamicData.contextData); + paramsConfig.forEach((conf) => { + if (keyArr.includes(conf.targetKey)) { + myDynamicData.contextData[conf.targetKey] = e[conf.originKey]; + _this.formData[conf.targetKey] = e[conf.originKey]; + } + }); + } + } } }, }; diff --git a/src/views/bigscreenDesigner/designer/widget/form/widgetFormTime copy.vue b/src/views/bigscreenDesigner/designer/widget/form/widgetFormTime copy.vue new file mode 100644 index 0000000..3ec82ed --- /dev/null +++ b/src/views/bigscreenDesigner/designer/widget/form/widgetFormTime copy.vue @@ -0,0 +1,128 @@ + + + + diff --git a/src/views/bigscreenDesigner/designer/widget/form/widgetFormTime.vue b/src/views/bigscreenDesigner/designer/widget/form/widgetFormTime.vue index 3ec82ed..d1e05dd 100644 --- a/src/views/bigscreenDesigner/designer/widget/form/widgetFormTime.vue +++ b/src/views/bigscreenDesigner/designer/widget/form/widgetFormTime.vue @@ -12,7 +12,7 @@ :style="styleObj" v-model="startTime" :value-format="valueFormat" - :type="dateType=='datetimerange'?'datetime':'date'" + :type="getPickerType" @[eventChange]="change" /> @@ -21,7 +21,7 @@ :style="{...styleObj,left:styleObj.leftEnd,width:styleObj.widthEnd}" v-model="endTime" :value-format="valueFormat" - :type="dateType=='datetimerange'?'datetime':'date'" + :type="getPickerType" @[eventChange]="change" /> @@ -30,6 +30,8 @@ import { originWidgetLinkageLogic, targetWidgetLinkageLogic, + registerWidgetInstance, + unregisterWidgetInstance, } from "@/views/bigscreenDesigner/designer/linkageLogic"; import miment from 'miment' @@ -70,10 +72,35 @@ export default { return "change"; }, dateType() { + // 支持: 'datetimerange', 'daterange', 'monthrange', 'yearrange' return this.optionsSetup.dateType || 'datetimerange'; }, + getPickerType() { + // 将范围类型映射为单个选择器类型(因为是两个独立的 picker) + const type = this.dateType; + if (type === 'yearrange') { + return 'year'; + } else if (type === 'monthrange') { + return 'month'; + } else if (type === 'daterange') { + return 'date'; + } else { + // datetimerange 或其他默认情况 + return 'datetime'; + } + }, valueFormat() { - return this.dateType === 'daterange' ? 'yyyy-MM-dd' : 'yyyy-MM-dd HH:mm:ss'; + const type = this.dateType; + if (type === 'yearrange') { + return 'yyyy'; + } else if (type === 'monthrange') { + return 'yyyy-MM'; + } else if (type === 'daterange') { + return 'yyyy-MM-dd'; + } else { + // datetimerange 或其他默认情况 + return 'yyyy-MM-dd HH:mm:ss'; + } }, allComponentLinkage() { return this.$store.state.designer.allComponentLinkage; @@ -95,8 +122,22 @@ export default { this.optionsStyle = this.value.position; targetWidgetLinkageLogic(this); // 联动-目标组件逻辑 + registerWidgetInstance(this); // 注册组件实例,供按钮等组件获取数据 + }, + beforeDestroy() { + unregisterWidgetInstance(this); // 注销组件实例 }, methods: { + /** + * 获取联动数据 - 供按钮等组件调用 + * 返回当前时间选择器的值 + */ + getLinkageData() { + return { + startTime: this.startTime, + endTime: this.endTime + }; + }, change(event) { const formTimeData = {} formTimeData['startTime'] = this.startTime //event[0] //startTime diff --git a/src/views/bigscreenDesigner/designer/widget/form/widgetSelect.vue b/src/views/bigscreenDesigner/designer/widget/form/widgetSelect.vue index 9a4d62e..314498d 100644 --- a/src/views/bigscreenDesigner/designer/widget/form/widgetSelect.vue +++ b/src/views/bigscreenDesigner/designer/widget/form/widgetSelect.vue @@ -129,6 +129,13 @@ export default { this.options = val; }, }, + beforeDestroy() { + // 清除定时器,避免内存泄漏 + if (this.flagInter) { + clearInterval(this.flagInter); + this.flagInter = null; + } + }, }; + diff --git a/src/views/bigscreenDesigner/designer/widget/form/widgetTabs.vue b/src/views/bigscreenDesigner/designer/widget/form/widgetTabs.vue index 0cb7935..ba1eae5 100644 --- a/src/views/bigscreenDesigner/designer/widget/form/widgetTabs.vue +++ b/src/views/bigscreenDesigner/designer/widget/form/widgetTabs.vue @@ -5,7 +5,7 @@ * @Last Modified time: 2024-01-01 00:00:00 !-->