hcong
2024-11-08 d7d7da5c09340eafcd2e2c672e6b2c001a4cc0be
基础数据产品-场景清单,监管清单
已修改7个文件
已添加21个文件
3475 ■■■■■ 文件已修改
package-lock.json 258 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
public/test.docx 补丁 | 查看 | 原始文档 | blame | 历史
src/api/fysp/taskApi.js 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components.d.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/constants/menu.js 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.js 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/ProdMonitorTaskInfo.vue 600 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/ProdScenseInfo.vue 637 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/ProdTreatmentDeviceInfo.vue 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/components/ComSelectMonitorTaskProdStage.vue 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/components/CompChangeHeaderTree.vue 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/components/CompGooderProdStage,.vue 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/components/CompPreviewProd.vue 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/components/CompProdGenMonitorInfoDescription.vue 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/components/CompProdGenScenseDescription.vue 286 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/components/CompProdSteps.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/components/CompProdTreatmentDeviceDescription.vue 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/components/CompSelectProdStage.vue 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/components/CompShowEChart.vue 211 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/components/DynamicTable.vue 246 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/components/MutiableOptionScene.vue 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/components/OptionChartProd.vue 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/components/TableColumn.vue 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/js/genChart.js 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/js/htmlTransfer.js 120 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/js/tableCol.js 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package-lock.json
@@ -13,13 +13,21 @@
        "@element-plus/icons-vue": "^2.0.10",
        "@vue-office/excel": "^1.7.11",
        "@vueuse/core": "^9.7.0",
        "angular-expressions": "^1.4.0",
        "axios": "^1.2.1",
        "dayjs": "^1.11.13",
        "docxtemplater": "^3.9.0",
        "docxtemplater-image-module-free": "^1.1.1",
        "echarts": "^4.8.0",
        "element-plus": "^2.8.3",
        "exceljs": "^4.4.0",
        "file-saver": "^2.0.5",
        "html2canvas": "^1.4.1",
        "js-base64": "^3.7.5",
        "jszip-utils": "^0.1.0",
        "md5": "^2.3.0",
        "pinia": "^2.0.26",
        "pizzip": "^3.1.7",
        "vue": "^3.2.45",
        "vue-demi": "^0.14.6",
        "vue-i18n": "^9.8.0",
@@ -31,6 +39,7 @@
        "@babel/core": "^7.20.5",
        "@babel/eslint-parser": "^7.19.1",
        "@babel/preset-env": "^7.20.2",
        "@types/html-docx-js": "^0.3.4",
        "@typescript-eslint/eslint-plugin": "^6.10.0",
        "@typescript-eslint/parser": "^6.10.0",
        "@vitejs/plugin-vue": "^3.2.0",
@@ -2221,6 +2230,16 @@
      "license": "MIT",
      "peer": true
    },
    "node_modules/@types/html-docx-js": {
      "version": "0.3.4",
      "resolved": "https://registry.npmmirror.com/@types/html-docx-js/-/html-docx-js-0.3.4.tgz",
      "integrity": "sha512-+t/4SxNnEq+0xbSUG7LrOOfG/y12aTXBjYKtOYtigVRIIOCEmK7jh0pSUeW5ATvI934274hkzdBg57t5RDrZow==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@types/node": "*"
      }
    },
    "node_modules/@types/json-schema": {
      "version": "7.0.15",
      "dev": true,
@@ -2853,6 +2872,12 @@
        "ajv": "^6.9.1"
      }
    },
    "node_modules/angular-expressions": {
      "version": "1.4.0",
      "resolved": "https://registry.npmmirror.com/angular-expressions/-/angular-expressions-1.4.0.tgz",
      "integrity": "sha512-LmbYpeZMuMhifpk+LmF2li9FzH2kYPwF/ykwHFKSnLrk8zJ8sZUjipcmGjJoHQTPcfj/YhkUoQYVbyZ08jDdYg==",
      "license": "Unlicense"
    },
    "node_modules/ansi-colors": {
      "version": "4.1.3",
      "dev": true,
@@ -3191,6 +3216,15 @@
    "node_modules/balanced-match": {
      "version": "1.0.2",
      "license": "MIT"
    },
    "node_modules/base64-arraybuffer": {
      "version": "1.0.2",
      "resolved": "https://registry.npmmirror.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
      "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.6.0"
      }
    },
    "node_modules/base64-js": {
      "version": "1.5.1",
@@ -3745,6 +3779,15 @@
        "node": "*"
      }
    },
    "node_modules/css-line-break": {
      "version": "2.1.0",
      "resolved": "https://registry.npmmirror.com/css-line-break/-/css-line-break-2.1.0.tgz",
      "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
      "license": "MIT",
      "dependencies": {
        "utrie": "^1.0.2"
      }
    },
    "node_modules/cssesc": {
      "version": "3.0.0",
      "dev": true,
@@ -3933,6 +3976,27 @@
        "node": ">=6.0.0"
      }
    },
    "node_modules/docxtemplater": {
      "version": "3.9.0",
      "resolved": "https://registry.npmmirror.com/docxtemplater/-/docxtemplater-3.9.0.tgz",
      "integrity": "sha512-l74k7jtT/Ui3Y+KZOxaUr85HxfsHZJ3xaYzYfGmgpHavfsxvtu2pF7rU/AqERGpY59mip0gj0PxqfI5Wfcxw7Q==",
      "license": "MIT",
      "dependencies": {
        "xmldom": "^0.1.27"
      },
      "engines": {
        "node": ">=0.10"
      }
    },
    "node_modules/docxtemplater-image-module-free": {
      "version": "1.1.1",
      "resolved": "https://registry.npmmirror.com/docxtemplater-image-module-free/-/docxtemplater-image-module-free-1.1.1.tgz",
      "integrity": "sha512-aWOzVQN7ggDYjfoy3pTTNrcrZ7/CJrQcI9cT+hmyHE6nRLR67nt5yPFPe9hm9VWbfYIED2fi+3itOnF0TE/RWQ==",
      "license": "MIT",
      "dependencies": {
        "xmldom": "^0.1.27"
      }
    },
    "node_modules/domexception": {
      "version": "4.0.0",
      "dev": true,
@@ -3991,6 +4055,15 @@
      "dependencies": {
        "jsbn": "~0.1.0",
        "safer-buffer": "^2.1.0"
      }
    },
    "node_modules/echarts": {
      "version": "4.8.0",
      "resolved": "https://registry.npmmirror.com/echarts/-/echarts-4.8.0.tgz",
      "integrity": "sha512-YwShpug8fWngj/RlgxDaYrLBoD+LsZUArrusjNPHpAF+is+gGe38xx4W848AwWMGoi745t3OXM52JedNrv+F6g==",
      "license": "Apache-2.0",
      "dependencies": {
        "zrender": "4.3.1"
      }
    },
    "node_modules/electron-to-chromium": {
@@ -4692,6 +4765,12 @@
        "node": "^10.12.0 || >=12.0.0"
      }
    },
    "node_modules/file-saver": {
      "version": "2.0.5",
      "resolved": "https://registry.npmmirror.com/file-saver/-/file-saver-2.0.5.tgz",
      "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==",
      "license": "MIT"
    },
    "node_modules/fill-range": {
      "version": "7.0.1",
      "dev": true,
@@ -5037,6 +5116,19 @@
      },
      "engines": {
        "node": ">=12"
      }
    },
    "node_modules/html2canvas": {
      "version": "1.4.1",
      "resolved": "https://registry.npmmirror.com/html2canvas/-/html2canvas-1.4.1.tgz",
      "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
      "license": "MIT",
      "dependencies": {
        "css-line-break": "^2.1.0",
        "text-segmentation": "^1.0.3"
      },
      "engines": {
        "node": ">=8.0.0"
      }
    },
    "node_modules/http-proxy-agent": {
@@ -5566,6 +5658,12 @@
        "readable-stream": "~2.3.6",
        "setimmediate": "^1.0.5"
      }
    },
    "node_modules/jszip-utils": {
      "version": "0.1.0",
      "resolved": "https://registry.npmmirror.com/jszip-utils/-/jszip-utils-0.1.0.tgz",
      "integrity": "sha512-tBNe0o3HAf8vo0BrOYnLPnXNo5A3KsRMnkBFYjh20Y3GPYGfgyoclEMgvVchx0nnL+mherPi74yLPIusHUQpZg==",
      "license": "(MIT OR GPL-3.0)"
    },
    "node_modules/jszip/node_modules/readable-stream": {
      "version": "2.3.8",
@@ -6431,6 +6529,21 @@
          "optional": true
        }
      }
    },
    "node_modules/pizzip": {
      "version": "3.1.7",
      "resolved": "https://registry.npmmirror.com/pizzip/-/pizzip-3.1.7.tgz",
      "integrity": "sha512-VemVeAQtdIA74AN1Fsd5OmbMbEeS4YOwwlcudgzvmUrOIOPrk1idYC5Tw5FUFq/I0c26ziNOw9z//iPmGfp1jA==",
      "license": "(MIT OR GPL-3.0)",
      "dependencies": {
        "pako": "^2.1.0"
      }
    },
    "node_modules/pizzip/node_modules/pako": {
      "version": "2.1.0",
      "resolved": "https://registry.npmmirror.com/pako/-/pako-2.1.0.tgz",
      "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
      "license": "(MIT AND Zlib)"
    },
    "node_modules/postcss": {
      "version": "8.4.20",
@@ -7317,6 +7430,15 @@
      "license": "MIT",
      "peer": true
    },
    "node_modules/text-segmentation": {
      "version": "1.0.3",
      "resolved": "https://registry.npmmirror.com/text-segmentation/-/text-segmentation-1.0.3.tgz",
      "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
      "license": "MIT",
      "dependencies": {
        "utrie": "^1.0.2"
      }
    },
    "node_modules/text-table": {
      "version": "0.2.0",
      "dev": true,
@@ -7798,6 +7920,15 @@
    "node_modules/util-deprecate": {
      "version": "1.0.2",
      "license": "MIT"
    },
    "node_modules/utrie": {
      "version": "1.0.2",
      "resolved": "https://registry.npmmirror.com/utrie/-/utrie-1.0.2.tgz",
      "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
      "license": "MIT",
      "dependencies": {
        "base64-arraybuffer": "^1.0.2"
      }
    },
    "node_modules/uuid": {
      "version": "8.3.2",
@@ -8290,6 +8421,16 @@
      "version": "2.2.0",
      "license": "MIT"
    },
    "node_modules/xmldom": {
      "version": "0.1.31",
      "resolved": "https://registry.npmmirror.com/xmldom/-/xmldom-0.1.31.tgz",
      "integrity": "sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==",
      "deprecated": "Deprecated due to CVE-2021-21366 resolved in 0.5.0",
      "license": "(LGPL-2.0 or MIT)",
      "engines": {
        "node": ">=0.1"
      }
    },
    "node_modules/yallist": {
      "version": "4.0.0",
      "dev": true,
@@ -8347,6 +8488,12 @@
      "engines": {
        "node": ">= 10"
      }
    },
    "node_modules/zrender": {
      "version": "4.3.1",
      "resolved": "https://registry.npmmirror.com/zrender/-/zrender-4.3.1.tgz",
      "integrity": "sha512-CeH2TpJeCdG0TAGYoPSAcFX2ogdug1K7LIn9UO/q9HWqQ54gWhrMAlDP9AwWYMUDhrPe4VeazQ4DW3msD96nUQ==",
      "license": "BSD-3-Clause"
    }
  },
  "dependencies": {
@@ -9727,6 +9874,15 @@
      "dev": true,
      "peer": true
    },
    "@types/html-docx-js": {
      "version": "0.3.4",
      "resolved": "https://registry.npmmirror.com/@types/html-docx-js/-/html-docx-js-0.3.4.tgz",
      "integrity": "sha512-+t/4SxNnEq+0xbSUG7LrOOfG/y12aTXBjYKtOYtigVRIIOCEmK7jh0pSUeW5ATvI934274hkzdBg57t5RDrZow==",
      "dev": true,
      "requires": {
        "@types/node": "*"
      }
    },
    "@types/json-schema": {
      "version": "7.0.15",
      "dev": true
@@ -10194,6 +10350,11 @@
      "peer": true,
      "requires": {}
    },
    "angular-expressions": {
      "version": "1.4.0",
      "resolved": "https://registry.npmmirror.com/angular-expressions/-/angular-expressions-1.4.0.tgz",
      "integrity": "sha512-LmbYpeZMuMhifpk+LmF2li9FzH2kYPwF/ykwHFKSnLrk8zJ8sZUjipcmGjJoHQTPcfj/YhkUoQYVbyZ08jDdYg=="
    },
    "ansi-colors": {
      "version": "4.1.3",
      "dev": true
@@ -10413,6 +10574,11 @@
    },
    "balanced-match": {
      "version": "1.0.2"
    },
    "base64-arraybuffer": {
      "version": "1.0.2",
      "resolved": "https://registry.npmmirror.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
      "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ=="
    },
    "base64-js": {
      "version": "1.5.1"
@@ -10745,6 +10911,14 @@
      "resolved": "https://registry.npmmirror.com/crypt/-/crypt-0.0.2.tgz",
      "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow=="
    },
    "css-line-break": {
      "version": "2.1.0",
      "resolved": "https://registry.npmmirror.com/css-line-break/-/css-line-break-2.1.0.tgz",
      "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
      "requires": {
        "utrie": "^1.0.2"
      }
    },
    "cssesc": {
      "version": "3.0.0",
      "dev": true
@@ -10879,6 +11053,22 @@
        "esutils": "^2.0.2"
      }
    },
    "docxtemplater": {
      "version": "3.9.0",
      "resolved": "https://registry.npmmirror.com/docxtemplater/-/docxtemplater-3.9.0.tgz",
      "integrity": "sha512-l74k7jtT/Ui3Y+KZOxaUr85HxfsHZJ3xaYzYfGmgpHavfsxvtu2pF7rU/AqERGpY59mip0gj0PxqfI5Wfcxw7Q==",
      "requires": {
        "xmldom": "^0.1.27"
      }
    },
    "docxtemplater-image-module-free": {
      "version": "1.1.1",
      "resolved": "https://registry.npmmirror.com/docxtemplater-image-module-free/-/docxtemplater-image-module-free-1.1.1.tgz",
      "integrity": "sha512-aWOzVQN7ggDYjfoy3pTTNrcrZ7/CJrQcI9cT+hmyHE6nRLR67nt5yPFPe9hm9VWbfYIED2fi+3itOnF0TE/RWQ==",
      "requires": {
        "xmldom": "^0.1.27"
      }
    },
    "domexception": {
      "version": "4.0.0",
      "dev": true,
@@ -10933,6 +11123,14 @@
      "requires": {
        "jsbn": "~0.1.0",
        "safer-buffer": "^2.1.0"
      }
    },
    "echarts": {
      "version": "4.8.0",
      "resolved": "https://registry.npmmirror.com/echarts/-/echarts-4.8.0.tgz",
      "integrity": "sha512-YwShpug8fWngj/RlgxDaYrLBoD+LsZUArrusjNPHpAF+is+gGe38xx4W848AwWMGoi745t3OXM52JedNrv+F6g==",
      "requires": {
        "zrender": "4.3.1"
      }
    },
    "electron-to-chromium": {
@@ -11406,6 +11604,11 @@
        "flat-cache": "^3.0.4"
      }
    },
    "file-saver": {
      "version": "2.0.5",
      "resolved": "https://registry.npmmirror.com/file-saver/-/file-saver-2.0.5.tgz",
      "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
    },
    "fill-range": {
      "version": "7.0.1",
      "dev": true,
@@ -11626,6 +11829,15 @@
      "dev": true,
      "requires": {
        "whatwg-encoding": "^2.0.0"
      }
    },
    "html2canvas": {
      "version": "1.4.1",
      "resolved": "https://registry.npmmirror.com/html2canvas/-/html2canvas-1.4.1.tgz",
      "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
      "requires": {
        "css-line-break": "^2.1.0",
        "text-segmentation": "^1.0.3"
      }
    },
    "http-proxy-agent": {
@@ -11994,6 +12206,11 @@
          }
        }
      }
    },
    "jszip-utils": {
      "version": "0.1.0",
      "resolved": "https://registry.npmmirror.com/jszip-utils/-/jszip-utils-0.1.0.tgz",
      "integrity": "sha512-tBNe0o3HAf8vo0BrOYnLPnXNo5A3KsRMnkBFYjh20Y3GPYGfgyoclEMgvVchx0nnL+mherPi74yLPIusHUQpZg=="
    },
    "klona": {
      "version": "2.0.5",
@@ -12544,6 +12761,21 @@
        "vue-demi": "*"
      }
    },
    "pizzip": {
      "version": "3.1.7",
      "resolved": "https://registry.npmmirror.com/pizzip/-/pizzip-3.1.7.tgz",
      "integrity": "sha512-VemVeAQtdIA74AN1Fsd5OmbMbEeS4YOwwlcudgzvmUrOIOPrk1idYC5Tw5FUFq/I0c26ziNOw9z//iPmGfp1jA==",
      "requires": {
        "pako": "^2.1.0"
      },
      "dependencies": {
        "pako": {
          "version": "2.1.0",
          "resolved": "https://registry.npmmirror.com/pako/-/pako-2.1.0.tgz",
          "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="
        }
      }
    },
    "postcss": {
      "version": "8.4.20",
      "requires": {
@@ -13085,6 +13317,14 @@
        "terser": "^5.14.1"
      }
    },
    "text-segmentation": {
      "version": "1.0.3",
      "resolved": "https://registry.npmmirror.com/text-segmentation/-/text-segmentation-1.0.3.tgz",
      "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
      "requires": {
        "utrie": "^1.0.2"
      }
    },
    "text-table": {
      "version": "0.2.0",
      "dev": true
@@ -13393,6 +13633,14 @@
    "util-deprecate": {
      "version": "1.0.2"
    },
    "utrie": {
      "version": "1.0.2",
      "resolved": "https://registry.npmmirror.com/utrie/-/utrie-1.0.2.tgz",
      "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
      "requires": {
        "base64-arraybuffer": "^1.0.2"
      }
    },
    "uuid": {
      "version": "8.3.2"
    },
@@ -13654,6 +13902,11 @@
    "xmlchars": {
      "version": "2.2.0"
    },
    "xmldom": {
      "version": "0.1.31",
      "resolved": "https://registry.npmmirror.com/xmldom/-/xmldom-0.1.31.tgz",
      "integrity": "sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ=="
    },
    "yallist": {
      "version": "4.0.0",
      "dev": true
@@ -13698,6 +13951,11 @@
          }
        }
      }
    },
    "zrender": {
      "version": "4.3.1",
      "resolved": "https://registry.npmmirror.com/zrender/-/zrender-4.3.1.tgz",
      "integrity": "sha512-CeH2TpJeCdG0TAGYoPSAcFX2ogdug1K7LIn9UO/q9HWqQ54gWhrMAlDP9AwWYMUDhrPe4VeazQ4DW3msD96nUQ=="
    }
  }
}
package.json
@@ -18,13 +18,21 @@
    "@element-plus/icons-vue": "^2.0.10",
    "@vue-office/excel": "^1.7.11",
    "@vueuse/core": "^9.7.0",
    "angular-expressions": "^1.4.0",
    "axios": "^1.2.1",
    "dayjs": "^1.11.13",
    "docxtemplater": "^3.9.0",
    "docxtemplater-image-module-free": "^1.1.1",
    "echarts": "^4.8.0",
    "element-plus": "^2.8.3",
    "exceljs": "^4.4.0",
    "file-saver": "^2.0.5",
    "html2canvas": "^1.4.1",
    "js-base64": "^3.7.5",
    "jszip-utils": "^0.1.0",
    "md5": "^2.3.0",
    "pinia": "^2.0.26",
    "pizzip": "^3.1.7",
    "vue": "^3.2.45",
    "vue-demi": "^0.14.6",
    "vue-i18n": "^9.8.0",
@@ -36,6 +44,7 @@
    "@babel/core": "^7.20.5",
    "@babel/eslint-parser": "^7.19.1",
    "@babel/preset-env": "^7.20.2",
    "@types/html-docx-js": "^0.3.4",
    "@typescript-eslint/eslint-plugin": "^6.10.0",
    "@typescript-eslint/parser": "^6.10.0",
    "@vitejs/plugin-vue": "^3.2.0",
public/test.docx
Binary files differ
src/api/fysp/taskApi.js
@@ -22,6 +22,13 @@
    return $fysp.get('task/alltask/0').then((res) => res.data);
  },
  /**
   * èŽ·å–é¡¶å±‚ä»»åŠ¡å’Œæ—¥ä»»åŠ¡
   */
  getTopTaskWithDayTask() {
    return $fysp.get('task/alltask/1').then((res) => res.data);
  },
  getLastTopTask(task){
    return $fysp.post(`task/lastTask`, task).then((res) => res.data);
  },
@@ -119,9 +126,11 @@
  /** 
   * é€šè¿‡æ€»ä»»åŠ¡id和时间区间获取子任务列表
   */
  async getByTopTaskAndDate({startTime, endTime, sceneTypeId, topTaskId}) {
    const params = `?startTime=${startTime}&endTime=${endTime}&sceneTypeId=${sceneTypeId}&topTaskId=${topTaskId}`;
    return await $fysp.get(`subtask/getSubTask${params}`).then((res) => res.data);
  async getByTopTaskAndDate({startTime = null, endTime = null, sceneTypeId = null, topTaskId}) {
    // const params = `?startTime=${startTime}&endTime=${endTime}&sceneTypeId=${sceneTypeId}&topTaskId=${topTaskId}`;
    return await $fysp.get(`subtask/getSubTask`, {
      params: {startTime: startTime, endTime: endTime, sceneTypeId: sceneTypeId, topTaskId: topTaskId}
    }).then((res) => res.data);
  },
  /** 
   * èŽ·å–æŸä¸ªåœºæ™¯çš„å·¡æŸ¥ä»»åŠ¡
src/components.d.ts
@@ -12,23 +12,18 @@
    BasePanelLayout: typeof import('./components/core/BasePanelLayout.vue')['default']
    CompQuickSet: typeof import('./components/search-option/CompQuickSet.vue')['default']
    Content: typeof import('./components/core/Content.vue')['default']
    ElAffix: typeof import('element-plus/es')['ElAffix']
    ElAside: typeof import('element-plus/es')['ElAside']
    ElAvatar: typeof import('element-plus/es')['ElAvatar']
    ElBadge: typeof import('element-plus/es')['ElBadge']
    ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
    ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
    ElButton: typeof import('element-plus/es')['ElButton']
    ElCalendar: typeof import('element-plus/es')['ElCalendar']
    ElCard: typeof import('element-plus/es')['ElCard']
    ElCascader: typeof import('element-plus/es')['ElCascader']
    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
    ElCol: typeof import('element-plus/es')['ElCol']
    ElCollapse: typeof import('element-plus/es')['ElCollapse']
    ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
    ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
    ElContainer: typeof import('element-plus/es')['ElContainer']
    ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
    ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
    ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
    ElDialog: typeof import('element-plus/es')['ElDialog']
@@ -41,7 +36,6 @@
    ElIcon: typeof import('element-plus/es')['ElIcon']
    ElImage: typeof import('element-plus/es')['ElImage']
    ElInput: typeof import('element-plus/es')['ElInput']
    ElLink: typeof import('element-plus/es')['ElLink']
    ElMain: typeof import('element-plus/es')['ElMain']
    ElMenu: typeof import('element-plus/es')['ElMenu']
    ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
@@ -50,7 +44,6 @@
    ElPopover: typeof import('element-plus/es')['ElPopover']
    ElRow: typeof import('element-plus/es')['ElRow']
    ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
    ElSegmented: typeof import('element-plus/es')['ElSegmented']
    ElSelect: typeof import('element-plus/es')['ElSelect']
    ElSpace: typeof import('element-plus/es')['ElSpace']
    ElStep: typeof import('element-plus/es')['ElStep']
@@ -62,7 +55,6 @@
    ElTabs: typeof import('element-plus/es')['ElTabs']
    ElTag: typeof import('element-plus/es')['ElTag']
    ElText: typeof import('element-plus/es')['ElText']
    ElTooltip: typeof import('element-plus/es')['ElTooltip']
    ElTree: typeof import('element-plus/es')['ElTree']
    ElUpload: typeof import('element-plus/es')['ElUpload']
    Footer: typeof import('./components/core/Footer.vue')['default']
src/constants/menu.js
@@ -64,6 +64,21 @@
        icon: 'Document',
        name: '场景报告',
      },
      {
        path: '/fysp/data-product/ProdScenseInfo',
        icon: 'Document',
        name: '场景清单',
      },
      {
        path: '/fysp/data-product/ProdMonitorTaskInfo',
        icon: 'Document',
        name: '监管清单',
      },
      {
        path: '/fysp/data-product/ProdTreatmentDeviceInfo',
        icon: 'Document',
        name: '防治设备清单',
      },
    ],
  },
  // {
src/main.js
@@ -14,6 +14,10 @@
import 'element-plus/theme-chalk/src/message-box.scss';
import 'element-plus/theme-chalk/src/notification.scss';
// å¼•å…¥echarts
// import Echarts from 'vue-echarts'
import * as echarts from 'echarts'
// dayjs plugin
import dayjs from 'dayjs';
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';
@@ -29,7 +33,10 @@
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component);
}
// ä½¿ç”¨ç»„ä»¶
// app.component('e-charts',Echarts)
// å…¨å±€æŒ‚è½½ echarts
app.config.globalProperties.$echarts = echarts
app
  .use(pinia)
  .use(router)
src/router/index.js
@@ -41,6 +41,24 @@
    path: '/fysp/data-product/scenereport',
    component: () => import('@/views/fysp/data-product/ProdSceneReport.vue')
  },
  {
    // åŸºç¡€äº§å“-场景清单
    name: 'ProdScenseInfo',
    path: '/fysp/data-product/ProdScenseInfo',
    component: () => import('@/views/fysp/data-product/ProdScenseInfo.vue')
  },
  {
    // åŸºç¡€äº§å“-监管清单
    name: 'ProdMonitorTaskInfo',
    path: '/fysp/data-product/ProdMonitorTaskInfo',
    component: () => import('@/views/fysp/data-product/ProdMonitorTaskInfo.vue')
  },
  {
    // åŸºç¡€äº§å“-防治设备清单
    name: 'ProdTreatmentDeviceInfo',
    path: '/fysp/data-product/ProdTreatmentDeviceInfo',
    component: () => import('@/views/fysp/data-product/ProdTreatmentDeviceInfo.vue')
  },
  // {
  //   //场景报告-工地
  //   name: 'construction',
src/views/fysp/data-product/ProdMonitorTaskInfo.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,600 @@
<template>
  <el-col>
    <el-row justify="end" class="fixed-div">
      <!-- æŒ‰é’®ç»„ -->
      <div class="btn-group">
        <el-button v-if="this.activeStep.id > 0" @click="beforeStep"
          >上一步</el-button
        >
        <el-button
          v-if="this.activeStep.id == 0"
          @click="genDialog = true"
          type="primary"
          >产品生成配置</el-button
        >
        <el-button
          v-if="this.activeStep.id == 1"
          @click="gooderDialog = true"
          type="primary"
          >产品优化配置</el-button
        >
        <!-- <el-button type="primary" @click="chartDialog = true"
          >图表展示</el-button
        > -->
        <el-button
          v-if="this.activeStep.id == 2"
          type="primary"
          @click="previewProduct"
          >生成</el-button
        >
      </div>
    </el-row>
  </el-col>
  <!-- æ­¥éª¤ -->
  <el-steps
    :active="activeStep.id"
    finish-status="success"
    style=""
    align-center
  >
    <el-step v-for="(s, i) in steps" :key="s.id" :title="s.name" />
  </el-steps>
  <div class="center-div">
    <span class="title-input"> {{ title }} </span><br />
  </div>
  <div class="center-div">
    <div
      v-show="this.activeStep.id != 0"
      class="main"
      v-loading="searchLoading"
    >
      <span class="second-title"> {{ `一、监管概况` }} </span>
      <CompProdGenMonitorInfoDescription
        id="descriptionComp"
        class="product-item-margin"
        :table-data="tableData"
        :location="formSearch._locations"
        ref="prodGenDescriptionRef"
        @generated-description="onGeneratedDescription"
      >
      </CompProdGenMonitorInfoDescription>
      <template v-if="showChart">
        <span class="second-title"> {{ `二、统计透视` }} </span>
        <div class="charts product-item-margin">
          <div class="chart-item" v-for="chart in charts" :key="chart.id">
            <div class="chart" ref="myChart" :id="chart.id"></div>
            <!-- <el-button
              type="danger"
              @click="charts = charts.filter((item) => item.id != chart.id)"
              >删除</el-button
            > -->
          </div>
        </div>
      </template>
      <span class="second-title"> {{ `三、详细清单` }} </span>
      <DynamicTable
        ref="dynamicTableRef"
        v-loading="loading"
        :table-header="tableConfig"
        @search="onSearch"
        :pagination="false"
      ></DynamicTable>
    </div>
    <el-empty
      v-show="this.activeStep.id == 0"
      style="font-size: large"
      description="未配置产品"
    />
  </div>
  <el-dialog title="产品生成配置" v-model="genDialog">
    <ComSelectMonitorTaskProdStage
      :table-header="tableConfig"
      @next="selectProdStageOver"
    >
    </ComSelectMonitorTaskProdStage>
  </el-dialog>
  <el-dialog title="产品优化" v-model="gooderDialog" destroy-on-close>
    <CompGooderProdStage
      :table-header="tableConfig"
      @selected-gooder="onselectedGooder"
    >
    </CompGooderProdStage>
  </el-dialog>
  <el-dialog title="图表展示" v-model="chartDialog" destroy-on-close>
    <CompShowEChart
      @painted-chart="onPaintedChart"
      :table-data="tableData"
      :table-header="tableConfig"
      ref="chartRef"
    >
    </CompShowEChart>
  </el-dialog>
</template>
<script>
import sceneApi from '@/api/fysp/sceneApi';
import taskApi from '@/api/fysp/taskApi.js';
import DynamicTable from './components/DynamicTable.vue';
import { load } from '@amap/amap-jsapi-loader';
import ComSelectMonitorTaskProdStage from './components/ComSelectMonitorTaskProdStage.vue';
import CompProdGenMonitorInfoDescription from './components/CompProdGenMonitorInfoDescription.vue';
import CompShowEChart from './components/CompShowEChart.vue';
import htmlTransfer from './js/htmlTransfer';
import dayjs from 'dayjs';
import CompGooderProdStage from './components/CompGooderProdStage,.vue';
import genChart from './js/genChart';
export default {
  components: {
    DynamicTable,
    ComSelectMonitorTaskProdStage,
    CompProdGenMonitorInfoDescription,
    CompShowEChart,
    CompGooderProdStage
  },
  computed: {
    title() {
      return (
        (this.formSearch._locations.tName ||
          this.formSearch._locations.dName ||
          this.formSearch._locations.cName ||
          this.formSearch._locations.pName ||
          '') + '监管清单'
      );
    }
  },
  watch: {
    oneValue: {
      handler(nv, ov) {
        console.log('master', nv);
      },
      immediate: true,
      deep: true
    }
  },
  data() {
    return {
      gooderDialog: false,
      activeStep: {
        id: 0,
        name: '产品配置生成'
      },
      steps: [
        {
          id: 0,
          name: '产品配置生成'
        },
        {
          id: 1,
          name: '产品配置优化'
        },
        {
          id: 2,
          name: '生成'
        }
      ],
      mainVisible: false,
      showChart: false,
      chartDialog: false,
      genDialog: false,
      oneValue: {
        _locations: {},
        _scenetype: []
      },
      charts: [],
      chart: {},
      loading: false,
      tableConfig: [],
      tableData: [],
      formSearch: {
        topTask: '',
        _locations: {},
        searchText: '',
        _scenetype: {},
        online: {},
        topTasks: []
      },
      topTasks: []
    };
  },
  mounted() {
    this.getOptions();
    this.genHeaders();
  },
  methods: {
    genWord() {
      let imgUrls = this.charts.map((item) => {
        return htmlTransfer.chartToImageUrl(
          this.$echarts.getInstanceByDom(document.getElementById(item.id))
        );
      });
      const data = {
        title: this.title,
        description: this.description,
        tableData: this.tableData,
        imgUrls: imgUrls
      };
      const imgSize = {
        imgUrl: [200, 200]
      };
      htmlTransfer.ExportBriefDataDocx(
        '.\\test.docx', // æ¨¡æ¿è·¯å¾„
        data, // æ•°æ®
        this.title, // æ–‡ä»¶å
        imgSize
      );
    },
    previewProduct() {
      this.genWord();
    },
    onGeneratedDescription(description) {
      this.description = description;
    },
    onPaintedChart(chartOption) {
      this.showChart = true;
      let id = `chart${this.charts.length}`;
      this.charts.push({
        id: id,
        option: chartOption
      });
      console.log('chartOption', chartOption);
      setTimeout(() => {
        const dom = document.getElementById(id);
        const myChart = this.$echarts.init(dom); // åˆå§‹åŒ–echarts实例
        this.chart = myChart;
        myChart.setOption(chartOption);
        this.chartDialog = false;
      }, 100);
    },
    // ç¬¬ä¸€é˜¶æ®µç¡®å®šä¹‹åŽçš„回调
    selectProdStageOver(result) {
      this.genDialog = false;
      this.nextStep();
      if (result != null) {
        this.formSearch._locations = result._locations;
        this.formSearch.topTasks = result.topTasks;
        this.$refs.dynamicTableRef.onSearch();
      }
      console.log('第一阶段返回值', result);
    },
    onTopTaskChange(value) {
      this.$refs.dynamicTableRef.onSearch();
    },
    //获取查询条件
    getOptions() {
      taskApi.getTopTask().then((res) => {
        const list = res.map((r) => {
          return {
            value: r.tguid,
            label: r.name,
            data: r
          };
        });
        this.topTasks = list;
        this.formSearch.topTask = list[0];
        // this.$refs.dynamicTableRef.onSearch();
      });
    },
    addSceneRegion(obj) {
      obj.scene._region =
        obj.scene.provincename +
        obj.scene.cityname +
        obj.scene.districtname +
        obj.scene.townname +
        obj.scene.location;
    },
    standardSupervisedNum(obj) {
      obj.extension1 = obj.extension1 ? obj.extension1 : 0;
    },
    addSupervisedTime(monitorObj) {
      console.log('monitorObj', monitorObj);
      if (!('_subTasks' in monitorObj) || monitorObj._subTasks.length == 0) {
        monitorObj._executionstarttime = '/';
        return;
      }
      monitorObj._executionstarttime = monitorObj._subTasks
        .map((item) => dayjs(item.executionstarttime).format('YYYY-MM-DD'))
        .join(',');
    },
    onSearch(page, func) {
      this.loading = true;
      // èŽ·å–å½“å‰ç›‘ç®¡ä»»åŠ¡ä¸‹æ‰€æœ‰å­ä»»åŠ¡
      const getAllNeedSubTaskPromises = this.formSearch.topTasks.map((item) => {
        return taskApi.getByTopTaskAndDate({ topTaskId: item.tguid });
      });
      const subTaskPromises = this.formSearch.topTasks.map((item) => {
        return taskApi.fetchMonitorObjectVersion(item.tguid);
      });
      Promise.all(getAllNeedSubTaskPromises)
        .then((results) => {
          // å°†äºŒç»´æ•°ç»„展平成一维数组
          const subTasks = results.reduce((acc, val) => acc.concat(val), []);
          console.log('subTasks', subTasks);
          return Promise.all(subTasks);
        })
        .then((allSubTasks) => {
          // ä½¿ç”¨ Promise.all ç­‰å¾…所有的子任务请求完成
          Promise.all(subTaskPromises)
            .then((results) => {
              console.log('results', results);
              // å°†äºŒç»´æ•°ç»„展平成一维数组
              const flatResults = results.reduce(
                (acc, val) => acc.concat(val),
                []
              );
              // å†æ¬¡ä½¿ç”¨ Promise.all å¤„理一维数组中的每个元素
              return Promise.all(flatResults);
            })
            .then((moniterRes) => {
              // ä¿å­˜æ—¥ä»»åŠ¡åˆ—è¡¨
              taskApi.getTopTaskWithDayTask().then((allTopAndDay) => {
                let topWithDyTask = allTopAndDay.filter((item) => {
                  return (
                    this.formSearch.topTasks
                      .map((top) => top.tguid)
                      .indexOf(item.tguid) != -1
                  );
                });
                const subTasks = [];
                for (const r of moniterRes) {
                  // èµ‹å€¼æ€»ä»»åŠ¡ _topTask.daytaskList ä¸ºæ—¥ä»»åŠ¡åˆ—è¡¨
                  r._topTask = topWithDyTask.filter(
                    (item) => item.tguid == r.tid
                  )[0];
                  r._topTask.daytaskList.forEach((dayTask) => {
                    let dayTaskId = dayTask.tguid;
                    let topTaskId = dayTask.tsguid;
                    let sceneId = r.sguid;
                    let subTasks = this.selectSubTaskBySceneIdAndTaskId(
                      allSubTasks,
                      sceneId,
                      topTaskId,
                      dayTaskId
                    );
                    if (subTasks) {
                      r._subTasks = subTasks;
                    }
                  });
                  // ç”Ÿæˆåœºæ™¯åŒºåŸŸåˆ—
                  this.addSceneRegion(r);
                  // æ ‡å‡†åŒ–已监管次数
                  this.standardSupervisedNum(r);
                  // ç”Ÿæˆç›‘管时间列
                  this.addSupervisedTime(r);
                  // r._topTask = this.formSearch.topTask.data;
                  subTasks.push(r);
                }
                this.tableData = subTasks;
                this.genSomeCharts();
                func({
                  data: subTasks
                });
                this.loading = false;
              });
            })
            .catch((error) => {
              this.loading = false;
              console.error('Error fetching subtasks:', error);
            });
        });
    },
    genSomeCharts() {
      console.log("this.ta", this.tableData);
      // æ·»åŠ å›¾è¡¨
      this.charts = [];
      this.showChart = true;
      // ç”ŸæˆåŒºåŸŸä¸­çš„下一级的图表
      const l = this.formSearch._locations;
      console.log(this.tableData);
      let positionProp = '';
      if (l.pName != null) {
        positionProp = 'cityname';
        if (l.cName != null) {
          positionProp = 'districtname';
          if (l.dName != null) {
            positionProp = 'townname';
            if (l.tName != null) {
              positionProp = 'townname';
            }
          }
        }
      }
      positionProp = 'scene.' + positionProp;
      [
        genChart.getChartByDataAndProp(this.tableData, 'sceneType', '类型', 'bar'),
        genChart.getPieChartByDataAndProp(
          this.tableData,
          positionProp,
          '所属区域'
        )
      ].forEach((item) => {
        let id = `chart${this.charts.length}`;
        this.charts.push({
          id: id,
          option: item
        });
        setTimeout(() => {
          this.paintChart(id, item);
        }, 100);
      });
    },
    paintChart(id, chartOption) {
      const dom = document.getElementById(id);
      var startX, startY, chartWidth, chartHeight;
      const myChart = this.$echarts.init(dom); // åˆå§‹åŒ–echarts实例
      // å¼€å§‹æ‹–拽
      myChart.getZr().on('mousedown', function (event) {
        if (event.target) {
          startX = event.offsetX;
          startY = event.offsetY;
          chartWidth = myChart.getWidth();
          chartHeight = myChart.getHeight();
        }
      });
      // æ‹–动调整宽度
      myChart.getZr().on('mouseup', function (event) {
        if (startX != null || startY != null) {
          var deltaX = event.offsetX - startX;
          var deltaY = event.offsetY - startY;
          myChart.resize({
            width: chartWidth + deltaX,
            height: chartHeight + deltaY
          });
        }
        startX = null;
        startY = null;
      });
      this.chart = myChart;
      myChart.setOption(chartOption);
      this.chartDialog = false;
    },
    selectSubTaskBySceneIdAndTaskId(subTasks, sceneId, topTaskId, dayTaskId) {
      return subTasks.filter(
        (item) =>
          item.scenseid == sceneId &&
          item.tguid == topTaskId &&
          item.tsguid == dayTaskId
      );
    },
    beforeStep() {
      let currId = this.activeStep.id;
      this.activeStep = this.steps.filter((item) => item.id == currId - 1)[0];
    },
    nextStep() {
      let currId = this.activeStep.id;
      this.activeStep = this.steps.filter(
        (item) => item.id == (currId + 1) % this.steps.length
      )[0];
    },
    onselectedGooder(result) {
      let copy = this.tableConfig.map((item) => item);
      result.forEach((item) => {
        copy = copy.filter(
          (copyItem) => !(copyItem.id == item.id1 || copyItem.id == item.id2)
        );
        copy.push(item);
        this.tableData.forEach(
          (tableItem) =>
            (tableItem[item.prop] = `${tableItem[item.prop1]},${
              tableItem[item.prop2]
            }`)
        );
        copy.sort((a, b) => a.id - b.id);
      });
      this.tableConfig = copy;
      this.gooderDialog = false;
      this.nextStep();
    },
    genHeaders() {
      this.tableConfig = [
        {
          id: 1,
          label: '场景名称',
          prop: 'scene.name',
          width: '80'
        },
        {
          id: 2,
          label: '场景所属区域',
          prop: 'scene._region',
          width: '80'
        },
        {
          id: 3,
          label: '场景类型',
          prop: 'scene.type',
          width: '20'
        },
        {
          id: 4,
          label: '计划次数',
          prop: 'monitornum',
          width: '20'
        },
        {
          id: 5,
          label: '实际已监管次数',
          prop: 'extension1',
          width: '20'
        },
        {
          id: 6,
          label: '实际执行日期',
          prop: '_executionstarttime',
          width: '20'
        }
      ];
    }
  }
};
</script>
<style scoped>
.options {
  display: flex;
  padding: 5px;
}
.title-input {
  margin-top: -20px;
  border-radius: 6px;
  color: #000;
  width: 80%;
  font-size: 1.5rem;
  line-height: 5rem;
  text-align: center;
  border: none;
}
.center-div {
  display: flex;
  justify-content: center;
}
.btn-group {
  /* background-color: rgb(32, 127, 211); */
  white-space: nowrap;
}
.chart {
  margin-top: 5px;
  margin-bottom: 2px;
  width: 100%;
  height: 500px;
}
/* å›ºå®šå®šä½çš„ div */
.fixed-div {
  position: fixed;
  top: 7%;
  right: 20px; /* è·ç¦»å³ä¾§ 20px */
  width: 'auto'; /* div çš„宽度 */
  background-color: #f2f2f200; /* èƒŒæ™¯é¢œè‰² */
  padding: 5px;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.11); /* é˜´å½±æ•ˆæžœ */
  z-index: 1000; /* ç¡®ä¿ div åœ¨é¡µé¢å…¶ä»–内容之上 */
}
.charts {
  display: flex;
  flex-wrap: wrap;
}
.chart-item {
  text-align: center;
  width: 30%;
}
.product-item-margin {
  margin-bottom: 5px;
}
.second-title {
  color: var(--el-text-color-primary);
  font-size: 16px;
  font-weight: bold;
  margin-bottom: 16px;
}
.main {
  /* text-align: center; */
  width: 95%;
}
</style>
src/views/fysp/data-product/ProdScenseInfo.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,637 @@
<template>
  <el-col>
    <el-row justify="end" class="fixed-div">
      <!-- æŒ‰é’®ç»„ -->
      <div class="btn-group">
        <el-button v-if="this.activeStep.id > 0" @click="beforeStep"
          >上一步</el-button
        >
        <el-button
          v-if="this.activeStep.id == 0"
          @click="genDialog = true"
          type="primary"
          >产品生成配置</el-button
        >
        <el-button
          v-if="this.activeStep.id == 1"
          @click="gooderDialog = true"
          type="primary"
          >产品优化配置</el-button
        >
        <!-- <el-button type="primary" @click="chartDialog = true"
          >图表展示</el-button
        > -->
        <el-button
          v-if="this.activeStep.id == 2"
          type="primary"
          @click="previewProduct"
          >生成</el-button
        >
      </div>
    </el-row>
  </el-col>
  <!-- æ­¥éª¤ -->
  <el-steps
    :active="activeStep.id"
    finish-status="success"
    style=""
    align-center
  >
    <el-step v-for="(s, i) in steps" :key="s.id" :title="s.name" />
  </el-steps>
  <div class="center-div">
    <span class="title-input"> {{ title }} </span><br />
  </div>
  <div class="center-div">
    <div
      v-show="this.activeStep.id != 0"
      class="main"
      v-loading="searchLoading"
    >
      <span class="second-title"> {{ `一、场景概况` }} </span>
      <CompProdGenScenseDescription
        id="descriptionComp"
        class="product-item-margin"
        :table-data="tableData"
        :location="formSearch._locations"
        ref="prodGenDescriptionRef"
        @generated-description="onGeneratedDescription"
      >
      </CompProdGenScenseDescription>
      <template v-if="showChart">
        <span class="second-title"> {{ `二、统计透视` }} </span>
        <div class="charts product-item-margin">
          <div class="chart-item" v-for="chart in charts" :key="chart.id">
            <div class="chart" ref="myChart" :id="chart.id"></div>
            <!-- <el-button
              type="danger"
              @click="charts = charts.filter((item) => item.id != chart.id)"
              >删除</el-button
            > -->
          </div>
        </div>
      </template>
      <span class="second-title"> {{ `三、详细清单` }} </span>
      <DynamicTable
        class="product-item-margin"
        ref="dynamicTableRef"
        :table-header="tableConfig"
        @search="onSearch"
        :pagination="false"
      ></DynamicTable>
    </div>
    <el-empty
      v-show="this.activeStep.id == 0"
      style="font-size: large"
      description="未配置产品"
    />
  </div>
  <el-dialog title="产品生成配置" v-model="genDialog">
    <CompSelectProdStage
      :table-header="tableConfig"
      @next="selectProdStageOver"
    >
    </CompSelectProdStage>
  </el-dialog>
  <el-dialog title="产品预览" v-model="previewDialog" destroy-on-close>
    <CompSelectProdStage @next="selectProdStageOver"> </CompSelectProdStage>
  </el-dialog>
  <el-dialog title="产品优化" v-model="gooderDialog" destroy-on-close>
    <CompGooderProdStage
      :table-header="tableConfig"
      @selected-gooder="onselectedGooder"
    >
    </CompGooderProdStage>
  </el-dialog>
  <el-dialog title="图表展示" v-model="chartDialog" destroy-on-close>
    <CompShowEChart
      @painted-chart="onPaintedChart"
      :table-data="tableData"
      :table-header="tableConfig"
      ref="chartRef"
    >
    </CompShowEChart>
  </el-dialog>
</template>
<script>
import DynamicTable from './components/DynamicTable.vue';
import CompSelectProdStage from './components/CompSelectProdStage.vue';
import sceneApi from '@/api/fysp/sceneApi';
import taskApi from '@/api/fysp/taskApi.js';
import CompProdGenScenseDescription from './components/CompProdGenScenseDescription.vue';
import htmlTransfer from './js/htmlTransfer';
import CompShowEChart from './components/CompShowEChart.vue';
import html2canvas from 'html2canvas';
import CompGooderProdStage from './components/CompGooderProdStage,.vue';
import dayjs from 'dayjs';
import genChart from './js/genChart';
export default {
  computed: {
    title() {
      return (
        (this.formSearch._locations.tName ||
          this.formSearch._locations.dName ||
          this.formSearch._locations.cName ||
          this.formSearch._locations.pName ||
          '') + '地区场景清单'
      );
    }
  },
  components: {
    DynamicTable,
    CompSelectProdStage,
    CompProdGenScenseDescription,
    CompShowEChart,
    CompGooderProdStage
  },
  data() {
    return {
      activeStep: {
        id: 0,
        name: '产品配置生成'
      },
      steps: [
        {
          id: 0,
          name: '产品配置生成'
        },
        {
          id: 1,
          name: '产品配置优化'
        },
        {
          id: 2,
          name: '生成'
        }
      ],
      gooderDialog: false,
      searchLoading: false,
      description: '',
      showChart: false,
      chart: {},
      charts: [],
      chartDialog: false,
      genDialog: false,
      tableConfig: [],
      tableData: [],
      formSearch: {
        _locations: {},
        searchText: '',
        _scenetypes: [],
        _scenetype: {},
        online: {}
      }
    };
  },
  mounted() {
    this.genHeaders();
  },
  methods: {
    beforeStep() {
      let currId = this.activeStep.id;
      this.activeStep = this.steps.filter((item) => item.id == currId - 1)[0];
    },
    nextStep() {
      let currId = this.activeStep.id;
      this.activeStep = this.steps.filter(
        (item) => item.id == (currId + 1) % this.steps.length
      )[0];
    },
    onselectedGooder(result) {
      let copy = this.tableConfig.map((item) => item);
      result.forEach((item) => {
        copy = copy.filter(
          (copyItem) => !(copyItem.id == item.id1 || copyItem.id == item.id2)
        );
        copy.push(item);
        this.tableData.forEach(
          (tableItem) =>
            (tableItem[item.prop] = `${tableItem[item.prop1]},${
              tableItem[item.prop2]
            }`)
        );
        copy.sort((a, b) => a.id - b.id);
      });
      this.tableConfig = copy;
      this.gooderDialog = false;
      this.nextStep();
    },
    captureScreenshot() {
      const element = document.getElementById('descriptionComp');
      return html2canvas(element).then((canvas) =>
        canvas.toDataURL('image/png')
      );
    },
    onGeneratedDescription(description) {
      this.description = description;
    },
    paintChart(id, chartOption) {
      const dom = document.getElementById(id);
      var startX, startY, chartWidth, chartHeight;
      const myChart = this.$echarts.init(dom); // åˆå§‹åŒ–echarts实例
      // å¼€å§‹æ‹–拽
      myChart.getZr().on('mousedown', function (event) {
        if (event.target) {
          startX = event.offsetX;
          startY = event.offsetY;
          chartWidth = myChart.getWidth();
          chartHeight = myChart.getHeight();
        }
      });
      // æ‹–动调整宽度
      myChart.getZr().on('mouseup', function (event) {
        if (startX != null || startY != null) {
          var deltaX = event.offsetX - startX;
          var deltaY = event.offsetY - startY;
          myChart.resize({
            width: chartWidth + deltaX,
            height: chartHeight + deltaY
          });
        }
        startX = null;
        startY = null;
      });
      this.chart = myChart;
      myChart.setOption(chartOption);
      this.chartDialog = false;
    },
    onPaintedChart(chartOption) {
      this.showChart = true;
      let id = `chart${this.charts.length}`;
      this.charts.push({
        id: id,
        option: chartOption
      });
      console.log('chartOption', chartOption);
      setTimeout(() => {
        this.paintChart(id, chartOption);
      }, 100);
    },
    genWord() {
      let imgUrls = this.charts.map((item) => {
        return htmlTransfer.chartToImageUrl(
          this.$echarts.getInstanceByDom(document.getElementById(item.id))
        );
      });
      this.captureScreenshot().then((item) => {
        const month = dayjs(new Date()).month() + 1;
        const day = dayjs(new Date()).date();
        const data = {
          title: this.title,
          month: month,
          day: day,
          sceneTypeImg: imgUrls[0],
          positionImg: imgUrls[1],
          description: this.description,
          tableData: this.tableData,
          imgUrls: imgUrls
        };
        const imgSize = {
          imgUrl: [300, 350]
        };
        htmlTransfer.ExportBriefDataDocx(
          '.\\test.docx', // æ¨¡æ¿è·¯å¾„
          data, // æ•°æ®
          this.title, // æ–‡ä»¶å
          imgSize
        );
      });
    },
    previewProduct() {
      this.genWord();
    },
    // ç¬¬ä¸€é˜¶æ®µç¡®å®šä¹‹åŽçš„回调
    selectProdStageOver(result) {
      this.genDialog = false;
      this.nextStep();
      if (result != null) {
        this.formSearch._locations = result._locations;
        this.formSearch._scenetypes = result._scenetype;
        this.$refs.dynamicTableRef.onSearch();
      }
    },
    genSomeCharts() {
      // æ·»åŠ å›¾è¡¨
      this.charts = [];
      this.showChart = true;
      // ç”ŸæˆåŒºåŸŸä¸­çš„下一级的图表
      const l = this.formSearch._locations;
      console.log(this.tableData);
      let positionProp = '';
      if (l.pName != null) {
        positionProp = 'cityname';
        if (l.cName != null) {
          positionProp = 'districtname';
          if (l.dName != null) {
            positionProp = 'townname';
            if (l.tName != null) {
              positionProp = 'townname';
            }
          }
        }
      }
      [
        genChart.getChartByDataAndProp(this.tableData, 'type', '类型', 'bar'),
        genChart.getPieChartByDataAndProp(
          this.tableData,
          positionProp,
          '所属区域'
        )
      ].forEach((item) => {
        let id = `chart${this.charts.length}`;
        this.charts.push({
          id: id,
          option: item
        });
        setTimeout(() => {
          this.paintChart(id, item);
        }, 100);
      });
    },
    getList(page) {
      let promises = [];
      if (this.formSearch._scenetypes == undefined) {
        let obj = {};
        this.fillPosition(obj);
        promises.push(
          // sceneApi.searchScene(obj, page.currentPage, page.pageSize)
          sceneApi.searchScene(obj, 1, 9999)
        );
        this.addOnlineProp();
        this.addExistTopTask();
        return promises;
      } else {
        for (
          let index = 0;
          index < this.formSearch._scenetypes.length;
          index++
        ) {
          const item = this.formSearch._scenetypes[index];
          let obj = {
            // online: true
          };
          this.fillPosition(obj);
          obj.scensetypeid = item.value;
          if (obj.scensetypeid == '0') obj.scensetypeid = null;
          promises.push(sceneApi.searchScene(obj, 1, 9999));
        }
        this.addOnlineProp();
        this.addExistTopTask();
        return promises;
      }
    },
    onSearch(page, func) {
      this.searchLoading = true;
      return Promise.all(this.getList(page)).then((results) => {
        this.tableData = [];
        results.forEach((res) => {
          if (res.success) {
            res.data.forEach((item) => {
              this.tableData.push(item);
            });
          }
        });
        this.addOnlineProp();
        this.addExistTopTask();
        this.genSomeCharts();
        this.searchLoading = false;
        func({
          data: this.tableData
        });
      });
    },
    fillPosition(area) {
      const f = this.formSearch;
      // è¡Œæ”¿åŒºåˆ’
      area.provincecode = f._locations.pCode;
      area.citycode = f._locations.cCode;
      area.districtcode = f._locations.dCode;
      area.towncode = f._locations.tCode;
    },
    getArea(area) {
      const f = this.formSearch;
      // åœºæ™¯ç±»åž‹
      area.scensetypeid = f._scenetype.value;
      if (area.scensetypeid == '0') area.scensetypeid = null;
      // ä¸Šä¸‹çº¿çŠ¶æ€
      area.online = f.online.value;
      // æŸ¥è¯¢å…³é”®å­—(场景名称)
      area.sceneName = f.searchText;
    },
    addOnlineProp() {
      this.tableData.forEach(
        (item) => (item._isOnline = item.extension1 == 1 ? '是' : '否')
      );
    },
    addExistTopTask() {
      taskApi.getTopTask().then((res) => {
        const topTaskId = res[0].tguid;
        taskApi.fetchMonitorObjectVersion(topTaskId).then((subTaskRes) => {
          let alreadyExistTopTask = subTaskRes.map((item) => item.sguid);
          this.noExistTopTaskNum = 0;
          this.existTopTaskNum = 0;
          this.tableData.forEach((e) => {
            if (alreadyExistTopTask.indexOf(e.guid) == -1) {
              e._isExistInTopTask = '不在监管计划内';
            } else {
              e._isExistInTopTask = '在监管计划内';
            }
          });
        });
      });
    },
    genHeaders() {
      this.tableConfig = [
        {
          id: 1,
          label: '编号',
          prop: 'index',
          width: '80',
          disabled: true
        },
        {
          id: 2,
          label: '名称',
          prop: 'name',
          width: '400',
          disabled: true
        },
        {
          id: 3,
          label: '类型',
          prop: 'type',
          width: '130',
          sortable: true,
          disabled: true
        },
        {
          id: 4,
          label: '省',
          prop: 'provincename',
          width: '90',
          disabled: true
        },
        {
          id: 5,
          label: '市',
          prop: 'cityname',
          width: '90',
          disabled: true
        },
        {
          id: 6,
          label: '区县',
          prop: 'districtname',
          width: '90',
          disabled: true
        },
        {
          id: 7,
          label: '街道',
          prop: 'townname',
          width: '110',
          disabled: true
        },
        {
          id: 8,
          label: '地址',
          prop: 'location',
          width: '400',
          disabled: true
        },
        {
          id: 9,
          label: '经度',
          prop: 'longitude',
          width: '110',
          merge: 10,
          title: '经纬度',
          disabled: true
        },
        {
          id: 10,
          label: '纬度',
          prop: 'latitude',
          width: '110',
          merge: 9,
          disabled: true
        },
        {
          id: 11,
          label: '联系人',
          prop: 'contacts',
          width: '110',
          disabled: true
        },
        {
          id: 12,
          label: '联系电话',
          prop: 'contactst',
          width: '110',
          disabled: true
        },
        {
          id: 13,
          label: '备注',
          prop: 'remark',
          width: '110'
        },
        {
          id: 14,
          label: '运营中',
          prop: '_isOnline',
          width: '110',
          sortable: true,
          disabled: true
        },
        {
          id: 15,
          label: '是否存在于总任务中',
          prop: '_isExistInTopTask',
          width: '110',
          sortable: true,
          disabled: true
        }
      ];
    }
  }
};
</script>
<style scoped>
.options {
  display: flex;
  padding: 5px;
}
.title-input {
  margin-top: -20px;
  border-radius: 6px;
  color: #000;
  width: 80%;
  font-size: 1.5rem;
  line-height: 5rem;
  text-align: center;
  border: none;
}
.center-div {
  display: flex;
  justify-content: center;
}
.btn-group {
  /* background-color: rgb(32, 127, 211); */
  white-space: nowrap;
}
.chart {
  margin-top: 5px;
  margin-bottom: 2px;
  width: 100%;
  height: 500px;
}
.fixed-div-steps {
  position: fixed;
  top: 7%;
  width: 100%; /* div çš„宽度 */
  background-color: #f2f2f200; /* èƒŒæ™¯é¢œè‰² */
  padding: 5px;
  z-index: 1000; /* ç¡®ä¿ div åœ¨é¡µé¢å…¶ä»–内容之上 */
}
/* å›ºå®šå®šä½çš„ div */
.fixed-div {
  position: fixed;
  top: 7%;
  right: 20px; /* è·ç¦»å³ä¾§ 20px */
  width: 'auto'; /* div çš„宽度 */
  background-color: #f2f2f200; /* èƒŒæ™¯é¢œè‰² */
  padding: 5px;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.11); /* é˜´å½±æ•ˆæžœ */
  z-index: 1000; /* ç¡®ä¿ div åœ¨é¡µé¢å…¶ä»–内容之上 */
}
.charts {
  display: flex;
  flex-wrap: wrap;
}
.chart-item {
  text-align: center;
  width: 30%;
}
.product-item-margin {
  margin-bottom: 5px;
}
.second-title {
  color: var(--el-text-color-primary);
  font-size: 16px;
  font-weight: bold;
  margin-bottom: 16px;
}
.main {
  text-align: center;
  width: 95%;
}
</style>
src/views/fysp/data-product/ProdTreatmentDeviceInfo.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,80 @@
<template>
    <el-col>
    <el-row justify="end" class="fixed-div">
      <div class="btn-group">
        <el-button @click="genDialog = true" type="primary"
          >产品生成配置</el-button
        >
        <el-button type="primary" @click="chartDialog = true"
          >图表展示</el-button
        >
        <el-button type="primary" @click="previewProduct">导出</el-button>
      </div>
    </el-row>
  </el-col>
</template>
<script>
</script>
<style scoped>
.options {
  display: flex;
  padding: 5px;
}
.title-input {
  margin-top: -20px;
  border-radius: 6px;
  color: #000;
  width: 80%;
  font-size: 1.5rem;
  line-height: 5rem;
  text-align: center;
  border: none;
}
.center-div {
  display: flex;
  justify-content: center;
}
.btn-group {
  /* background-color: rgb(32, 127, 211); */
  white-space: nowrap;
}
.chart {
  margin-top: 5px;
  margin-bottom: 2px;
  width: 100%;
  height: 500px;
}
/* å›ºå®šå®šä½çš„ div */
.fixed-div {
  position: fixed;
  top: 7%;
  right: 20px; /* è·ç¦»å³ä¾§ 20px */
  width: 'auto'; /* div çš„宽度 */
  background-color: #f2f2f200; /* èƒŒæ™¯é¢œè‰² */
  padding: 5px;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.11); /* é˜´å½±æ•ˆæžœ */
  z-index: 1000; /* ç¡®ä¿ div åœ¨é¡µé¢å…¶ä»–内容之上 */
}
.charts {
  display: flex;
  flex-wrap: wrap;
}
.chart-item {
  text-align: center;
  width: 30%;
}
.product-item-margin {
  margin-bottom: 5px;
}
.second-title {
  color: var(--el-text-color-primary);
  font-size: 16px;
  font-weight: bold;
  margin-bottom: 16px;
}
.main {
  text-align: center;
  width: 95%;
}
</style>
src/views/fysp/data-product/components/ComSelectMonitorTaskProdStage.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,143 @@
<template>
  <span class="second-title"> {{ `基本信息` }} </span>
  <el-form :inline="true">
    <FYOptionLocation
      :allOption="false"
      :level="4"
      v-model:value="result._locations"
    ></FYOptionLocation>
    <MutiableOptionScene
      :allOption="false"
      :type="2"
      :multiple="true"
      :initValue="false"
      v-model:value="result._scenetype"
    ></MutiableOptionScene>
    <!-- <el-form-item label="月份对比">
        <el-date-picker
        v-model="result.months"
        type="monthrange"
        range-separator="-"
        start-placeholder="Start month"
        @change="onSelectedMonthsChange"
        end-placeholder="End month"
      />
    </el-form-item> -->
    <el-form-item label="总任务">
      <!-- <el-input v-model="formSearch.topTaskId" placeholder="总任务" /> -->
      <el-select
        multiple
        value-key="value"
        v-model="result.topTasks"
        placeholder="总任务"
        style="width: 320px; margin-left: 30px"
      >
        <el-option
          v-for="s in topTasks"
          :key="s.value"
          :label="s.label"
          :value="s"
        />
      </el-select>
    </el-form-item>
  </el-form>
  <el-divider />
  <span class="second-title"> {{ `选择表格中需要展示的列信息` }} </span>
  <CompChangeHeaderTree :table-header="tableHeader" ref="changeHeaderRef">
  </CompChangeHeaderTree>
  <div class="center">
    <div>
      <el-button type="primary" @click="ok">下一步</el-button>
      <el-button type="primary" @click="cancel">取消</el-button>
    </div>
  </div>
</template>
<script>
import CompChangeHeaderTree from './CompChangeHeaderTree.vue';
import MutiableOptionScene from './MutiableOptionScene.vue';
import tableCol from '../js/tableCol';
import taskApi from '@/api/fysp/taskApi.js';
import dayjs from 'dayjs';
export default {
  mounted() {
    this.getTopTasks();
  },
  props: {
    tableHeader: {
      type: Array,
      default: () => []
    }
  },
  components: {
    MutiableOptionScene,
    CompChangeHeaderTree
  },
  data() {
    return {
      result: {
        _locations: {},
        _scenetype: [],
        topTasks: [],
        months: []
      },
      topTasks: []
    };
  },
  methods: {
    onSelectedMonthsChange(val) {
        // let m1 = dayjs(val[0]).format("YYYY-MM")
        // let m2 = dayjs(val[0]).format("YYYY-MM")
        // function
        // this.topTasks = this.topTasks.filter(item=>{
        //     console.log("dayjs(new Date(item.starttime))", dayjs(item.starttime).format("YYYY-MM"));
        //     return dayjs(new Date(item.starttime)).format("YYYY-MM") == m1 || dayjs(item.starttime).format("YYYY-MM") == m2
        // })
    },
    getTopTasks() {
      taskApi.getTopTask().then((res) => {
        const list = res.map((r) => {
          return {
            value: r.tguid,
            label: r.name,
            data: r
          };
        });
        this.topTasks = list;
        // this.$refs.dynamicTableRef.onSearch();
      });
    },
    ok() {
      let changedHeaders = this.$refs.changeHeaderRef.getChangedHeaders();
      let copy = this.tableHeader.map((item) => item);
      let changedHeadeArray = tableCol.treeToArray(changedHeaders);
      tableCol.treeToArray(copy).forEach((item) => {
        for (let index = 0; index < changedHeadeArray.length; index++) {
          const element = changedHeadeArray[index];
          if (element.id == item.id) {
            item.hidden = element.hidden;
            changedHeadeArray.splice(index, 1);
          }
        }
      });
      this.result.topTasks = this.result.topTasks.map((item) => item.data);
      this.$emit('next', this.result);
    },
    cancel() {
      this.$emit('next', null);
    }
  }
};
</script>
<style scoped>
.center {
  display: flex;
  justify-content: center;
}
.second-title {
  color: var(--el-text-color-primary);
  font-size: 16px;
  font-weight: bold;
  margin-bottom: 16px;
}
</style>
src/views/fysp/data-product/components/CompChangeHeaderTree.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,90 @@
<template>
  <!-- åˆ—出表头的树状结构 -->
  <el-tree
  class="tree"
    ref="tree"
    :props="headTreeProps"
    :data="copyTableHeader"
    node-key="id"
    show-checkbox
    :default-checked-keys="defaultCheckedKeys"
    @check="check"
  />
</template>
<script>
import { useCloned } from '@vueuse/core';
import tableCol from '../js/tableCol';
export default {
  computed: {
    defaultCheckedKeys() {
      if (this.copyTableHeader.length == 0) {
        this.tableHeaderCopy()
      }
      let result = this.collectLabels(this.copyTableHeader);
      console.log("resultsss", result);
      return result
    }
  },
  mounted() {
    this.tableHeaderCopy()
  },
  data() {
    return {
      copyTableHeader: [],
      headTreeProps: {
        children: 'children',
        label: 'label',
        disabled: 'disabled'
      }
    };
  },
  props: {
    // å¤šçº§è¡¨å¤´çš„æ•°æ®
    tableHeader: {
      type: Array,
      required: true
    }
  },
  methods: {
    getChangedHeaders() {
      return this.copyTableHeader
    },
    tableHeaderCopy() {
      this.copyTableHeader = useCloned(this.tableHeader).cloned.value
    },
    collectLabels(nodes) {
      let labels = [];
      function traverseNodes(nodes) {
        nodes.forEach((node) => {
          if (!('hidden' in node) || !node.hidden) {
            labels.push(node.id);
          }
          // å¦‚果当前节点有子节点,递归调用遍历函数
          if (node.children && node.children.length > 0) {
            traverseNodes(node.children);
          }
        });
      }
      traverseNodes(nodes); // å¼€å§‹é€’归遍历
      return labels; // è¿”回收集到的 labels æ•°ç»„
    },
    check(checkedNode, checkedKeys, halfCheckedNodes, halfCheckedKeys) {
      checkedNode.hidden = !checkedNode.hidden;
      this.$refs.tree.updateKeyChildren(checkedNode.id, checkedNode);
    },
  }
};
</script>
<style scoped>
.tree {
  margin-left: -16.5px;
  display: flex !important;
  width: 100% !important;
  flex-flow: row wrap !important;
}
</style>
src/views/fysp/data-product/components/CompGooderProdStage,.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,106 @@
<template>
  <div class="gooderstage-center-div">
    <el-transfer
      :titles="['可合并列', '已选中']"
      v-model="selected"
      :props="{
        key: 'id',
        label: 'label'
      }"
      :data="data"
    />
    <el-button @click="ok" type="primary" class="ok-style">下一步</el-button>
  </div>
</template>
<script>
export default {
  props: {
    tableHeader: {
      type: Array,
      default: () => []
    }
  },
  mounted() {
    this.initData();
  },
  data() {
    return {
      data: [],
      selected: []
    };
  },
  methods: {
    ok() {
      this.$emit(
        'selected-gooder',
        this.data.filter((item) => this.selected.indexOf(item.id) != -1)
      );
    },
    initData() {
      let obj = this.processElements(this.tableHeader);
      this.selected = obj.mergedElements;
      this.data = obj.canMergePairs;
    },
    processElements(elements) {
      const canMergePairs = [];
      const mergedElements = [];
      const seenPairs = new Set(); // ç”¨äºŽè·Ÿè¸ªå·²å¤„理的 ID å¯¹
      // åˆ›å»ºä¸€ä¸ªç”¨äºŽæŸ¥æ‰¾å…ƒç´ çš„æ˜ å°„,以便根据 ID å¿«é€Ÿè®¿é—®
      const elementMap = new Map(elements.map((el) => [el.id, el]));
      for (const element of elements) {
        // æ£€æŸ¥æ˜¯å¦æœ‰ merge å±žæ€§
        if (element.merge) {
          const targetElement = elementMap.get(element.merge);
          if (targetElement) {
            // æ‰¾åˆ°ç›®æ ‡å…ƒç´ ï¼Œç”Ÿæˆæ–°çš„合并对
            const id1 = element.id;
            const id2 = targetElement.id;
            const prop1 = element.prop;
            const prop2 = targetElement.prop;
            const smallerId = Math.min(id1, id2);
            const largerId = Math.max(id1, id2);
            const title = element.prop + targetElement.prop;
            // åˆ›å»ºä¸€ä¸ªå”¯ä¸€çš„ ID å¯¹ï¼Œç¡®ä¿ä¸ä¼šé‡å¤
            const pairKey = `${smallerId}-${largerId}`;
            if (!seenPairs.has(pairKey)) {
              canMergePairs.push({
                id1,
                id2,
                prop1,
                prop2,
                id: smallerId,
                label: `${element.label}/${targetElement.label}`,
                prop: title,
                merged: false
                // children: [element, targetElement]
              });
              seenPairs.add(pairKey); // æ ‡è®°ä¸ºå·²å¤„理
            }
          }
          // å°†å½“前元素标记为已合并
          if (element.merged) {
            mergedElements.push(element);
          }
        }
      }
      return { canMergePairs, mergedElements };
    },
    getSelected() {
      return this.selected;
    }
  }
};
</script>
<style scoped>
.gooderstage-center-div {
  display: block;
  text-align: center;
}
.ok-style {
  margin-top: 2px;
}
</style>
src/views/fysp/data-product/components/CompPreviewProd.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
<template>
    <div></div>
</template>
<script>
import { saveAs } from 'file-saver';
export default {
  methods: {
    exportToWord() {
    }
  }
};
</script>
<style scoped></style>
src/views/fysp/data-product/components/CompProdGenMonitorInfoDescription.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,109 @@
<template>
  <span class="description"> {{ description }} </span>
</template>
<script>
import dayjs from 'dayjs';
export default {
  watch: {
    tableData: {
      handler(nv, ov) {
        this.buildDescription()
      },
      deep: true,
      immediate: true
    }
  },
  methods: {
    // æ®µé¦–
    startStr() {
      return `${'  '}`;
    },
    // æ®µå°¾
    endStr() {
      return '。';
    },
    buildMonitorNum() {
      let str = '';
      str += this.startStr();
      str += `当前清单包括${this.tableData.length}个监管任务`;
      str += this.endStr();
      return str;
    },
    buildTopTaskNum() {
      let topNames = Array.from(new Set(this.tableData.map((item) => item._topTask.name)))
      let str = '';
      str += this.startStr();
      str += `月度任务有${
        topNames.length
      }个,分别为${topNames.join(',')}`;
      str += this.endStr();
      return str;
    },
    buildScene() {
      let scenes = Array.from(new Set(this.tableData.map(item=>item.sceneType)))
      let str = '';
      str += this.startStr();
      str += `总共${scenes.length}个场景,`;
      str += `包括${scenes.join(',')}场景`;
      str += this.endStr();
      return str;
    },
    buildDescription() {
      let result = '';
      // ç›‘管任务数量
      result += this.buildMonitorNum();
      // æ¶‰åŠå¤šå°‘总任务
      result += this.buildTopTaskNum();
      // åœºæ™¯ä¸ªæ•°å’Œç±»åž‹
      result += this.buildScene();
      this.description = result
    },
  },
  computed: {
    locationStr() {
      let result = '';
      if (this.location.pName) {
        // result += this.location.pName;
        if (this.location.cName) {
          result += this.location.cName;
          if (this.location.dName) {
            result += this.location.dName;
            if (this.location.tName) {
              result += this.location.tName;
            }
          }
        }
      }
      if (result == '') {
        if (this.location.pName) {
          result += this.location.pName;
        }
      }
      return result;
    },
    time() {
      if (!this.tableData || this.tableData.length == 0) {
        return '';
      } else {
        return dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss');
      }
    }
  },
  props: {
    location: {},
    tableData: []
  },
  data() {
    return {
      description: ''
    };
  }
};
</script>
<style scoped>
.description {
  display: block;
  /* white-space: pre-line; */
  width: 100%;
}
</style>
src/views/fysp/data-product/components/CompProdGenScenseDescription.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,286 @@
<template>
  <!-- <el-descriptions :column="2" size="default" border>
    <el-descriptions-item label="区域">
      {{ locationStr }}
    </el-descriptions-item>
    <el-descriptions-item label="时间"> {{ time }} </el-descriptions-item>
    <el-descriptions-item label="描述">
      <span class="description"> {{ description }} </span>
    </el-descriptions-item>
  </el-descriptions> -->
  <span class="description"> {{ description }} </span>
</template>
<script>
import taskApi from '@/api/fysp/taskApi.js';
import dayjs from 'dayjs';
export default {
  watch: {
    tableData: {
      handler(nv, ov) {
        if (nv == null || nv.length == 0) {
          this.init()
          return;
        }
        this.countScenseNum();
        this.countTownScenses();
        this.countTown();
        this.countExistTopTask();
        this.countOnline();
      },
      deep: true,
      immediate: true
    }
  },
  props: {
    location: {},
    tableData: []
  },
  computed: {
    time() {
      if (!this.tableData || this.tableData.length == 0) {
        return '';
      } else {
        return dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss');
      }
    },
    locationStr() {
      let result = '';
      if (this.location.pName) {
        // result += this.location.pName;
        if (this.location.cName) {
          result += this.location.cName;
          if (this.location.dName) {
            result += this.location.dName;
            if (this.location.tName) {
              result += this.location.tName;
            }
          }
        }
      }
      if (result == '') {
        if (this.location.pName) {
          result += this.location.pName;
        }
      }
      return result;
    },
    description() {
      let result = '';
      if (!this.tableData || this.tableData.length == 0) {
        return result;
      }
      // result += `${this.startStr()}当前清单包括`;
      result += this.startStr()
      result += `当前场景清单包括`;
      result += this.scenses.map((item) => item.type).join(',') + '场景类型,';
      result += `总计${this.scenseNum}种场景`;
      result += this.endStr();
      result += `${
        Array.from(new Set(this.tableData.map((item) => item.townname))).length
      }个街道,`;
      // åœºæ™¯åˆ†å¸ƒ
      result = this.buildTownScenses(result);
      result = this.buildScenseDistribution(result);
      result = this.buildOnline(result);
      result = this.buildExistTopTask(result);
      this.$emit('generated-description', result);
      return result;
    }
  },
  data() {
    return {
      scenseNum: 0,
      scenses: [],
      towns: [
        // {
        //   name: '',
        //   scense: 0
        // }
      ],
      townScenses: [],
      existTopTaskNum: 0,
      noExistTopTaskNum: 0,
      online: 0,
      noOnline: 0
    };
  },
  mounted() {
    this.countScenseNum();
    this.countTown();
    this.countOnline();
    this.countExistTopTask();
  },
  methods: {
    // æ®µé¦–
    startStr() {
      return `${'  '}`;
    },
    // æ®µå°¾
    endStr() {
      return '。';
    },
    init() {
      this.scenseNum = 0,
      this.scenses = [],
      this.towns = [
        // {
        //   name: '',
        //   scense: 0
        // }
      ],
      this.townScenses = [],
      this.existTopTaskNum = 0,
      this.noExistTopTaskNum = 0,
      this.online = 0,
      this.noOnline = 0
    },
    countTownScenses() {
      this.townScenses = [];
      const groupedData = this.tableData.reduce((acc, current) => {
        if (!acc[current.townname]) {
          acc[current.townname] = {
            type: [current.type]
          };
        }
        if (acc[current.townname].type.indexOf(current.type) == -1) {
          acc[current.townname].type.push(current.type);
        }
        return acc;
      }, {});
      for (const prop in groupedData) {
        this.townScenses.push({
          name: prop,
          types: groupedData[prop].type
        });
      }
    },
    buildTownScenses(result) {
      // result += this.startStr()
      for (let index = 0; index < this.townScenses.length; index++) {
        const item = this.townScenses[index];
        if (index == 0) {
          result += `${item.name}${item.types.length}种场景(${item.types.join(
            ','
          )})`;
        } else {
          result += `,${item.name}${item.types.length}种场景(${item.types.join(
            ','
          )})`;
        }
      }
      result += this.endStr();
      return result;
    },
    getDescription() {
      let obj = {
        description: this.description,
        location: this.locationStr,
        time: this.time
      };
      return obj;
    },
    buildOnline(result) {
      // result += this.startStr()
      result +=
        `在线数量${this.online}个,下线数量${this.noOnline}个` + this.endStr();
      return result;
    },
    // æ˜¯å¦åœ¨ç›‘管任务中
    buildExistTopTask(result) {
      // result += this.startStr()
      result +=
        `在线的${this.online}个场景中${this.existTopTaskNum}个场景在监管任务中,${this.noExistTopTaskNum}个场景不在监管任务中` +
        this.endStr();
      return result;
    },
    // åœºæ™¯åˆ†å¸ƒ
    buildScenseDistribution(result) {
      // result += this.startStr()
      result += `${this.tableData.length}个场景,`;
      for (let index = 0; index < this.towns.length; index++) {
        const item = this.towns[index];
        if (index == 0) {
          result += `${item.name}${item.town}个`;
        } else {
          result += `,${item.name}${item.town}个`;
        }
      }
      result += this.endStr();
      return result;
    },
    // æ˜¯å¦åœ¨çº¿
    countOnline() {
      this.online = this.tableData.filter(
        (item) => item.extension1 == 1
      ).length;
      this.noOnline = this.tableData.filter(
        (item) => item.extension1 != 1
      ).length;
    },
    countExistTopTask() {
      taskApi.getTopTask().then((res) => {
        const topTaskId = res[0].tguid;
        taskApi.fetchMonitorObjectVersion(topTaskId).then((subTaskRes) => {
          let alreadyExistTopTask = subTaskRes.map((item) => item.sguid);
          this.noExistTopTaskNum = 0;
          this.existTopTaskNum = 0;
          this.tableData
            .filter((item) => item.extension1 == 1)
            .forEach((e) => {
              if (alreadyExistTopTask.indexOf(e.guid) == -1) {
                e._isExistInTopTask = '不在监管计划内';
                this.noExistTopTaskNum++;
              } else {
                e._isExistInTopTask = '在监管计划内';
                this.existTopTaskNum++;
              }
            });
        });
      });
    },
    countScenseNum() {
      this.scenses = [];
      let scenseIds = this.scenses.map((item) => item.typeid);
      this.tableData.forEach((item) => {
        if (scenseIds.indexOf(item.typeid) == -1) {
          this.scenses.push(item);
          scenseIds.push(item.typeid);
        }
      });
      this.scenseNum = scenseIds.length;
    },
    countTown() {
      this.towns = [];
      const groupedData = this.tableData.reduce((acc, current) => {
        if (!acc[current.type]) {
          acc[current.type] = {
            name: current.type,
            town: 1
          };
        }
        let town = {
          name: current.type,
          town: acc[current.type].town + 1
        };
        acc[current.type] = town;
        return acc;
      }, {});
      for (const prop in groupedData) {
        this.towns.push({
          name: prop,
          town: groupedData[prop].town
        });
      }
    }
  }
};
</script>
<style scoped>
.description {
  display: block;
  white-space: pre-wrap;
  width: 100%;
}
</style>
src/views/fysp/data-product/components/CompProdSteps.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,9 @@
<template>
    <div></div>
</template>
<script>
</script>
<style scoped>
</style>
src/views/fysp/data-product/components/CompProdTreatmentDeviceDescription.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
<template>
    <el-descriptions :column="2" size="default" border>
      <el-descriptions-item label="区域">
        {{ locationStr }}
      </el-descriptions-item>
      <el-descriptions-item label="时间"> {{ time }} </el-descriptions-item>
      <el-descriptions-item label="描述">
        <span class="description"> {{ description }} </span>
      </el-descriptions-item>
    </el-descriptions>
  </template>
  <script>
  import dayjs from 'dayjs';
  export default {
    computed: {
      description() {
        let result = ''
        return result
      },
      locationStr() {
        let result = '';
        if (this.location.pName) {
          // result += this.location.pName;
          if (this.location.cName) {
            result += this.location.cName;
            if (this.location.dName) {
              result += this.location.dName;
              if (this.location.tName) {
                result += this.location.tName;
              }
            }
          }
        }
        if (result == '') {
          if (this.location.pName) {
            result += this.location.pName;
          }
        }
        return result;
      },
      time() {
        if (!this.tableData || this.tableData.length == 0) {
          return '';
        } else {
          return dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss');
        }
      }
    },
    props: {
      location: {},
      tableData: []
    },
    data() {
      return {
        description: ''
      };
    }
  };
  </script>
  <style scoped></style>
src/views/fysp/data-product/components/CompSelectProdStage.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,85 @@
<template>
  <span class="second-title"> {{ `基本信息` }} </span>
  <el-form :inline="true">
    <FYOptionLocation
      :allOption="false"
      :level="4"
      v-model:value="result._locations"
    ></FYOptionLocation>
    <MutiableOptionScene
      :allOption="false"
      :type="2"
      :multiple="true"
      :initValue="false"
      v-model:value="result._scenetype"
    ></MutiableOptionScene>
  </el-form>
  <el-divider />
  <span class="second-title"> {{ `选择表格中需要展示的列信息` }} </span>
  <CompChangeHeaderTree :table-header="tableHeader" ref="changeHeaderRef"> </CompChangeHeaderTree>
  <div class="center">
    <div>
      <el-button type="primary" @click="ok">下一步</el-button>
      <el-button type="primary" @click="cancel">取消</el-button>
    </div>
  </div>
</template>
<script>
import CompChangeHeaderTree from './CompChangeHeaderTree.vue';
import MutiableOptionScene from './MutiableOptionScene.vue';
import tableCol from '../js/tableCol';
export default {
  mounted() {},
  props: {
    tableHeader: {
      type: Array,
      default: () => []
    }
  },
  components: {
    MutiableOptionScene,
    CompChangeHeaderTree
  },
  data() {
    return {
      result: {
        _locations: {},
        _scenetype: []
      }
    };
  },
  methods: {
    ok() {
      let changedHeaders = this.$refs.changeHeaderRef.getChangedHeaders()
      let copy = this.tableHeader.map(item=>item)
      let changedHeadeArray = tableCol.treeToArray(changedHeaders)
      tableCol.treeToArray(copy).forEach(item=>{
        for (let index = 0; index < changedHeadeArray.length; index++) {
          const element = changedHeadeArray[index];
          if (element.id == item.id) {
            item.hidden = element.hidden
            changedHeadeArray.splice(index, 1)
          }
        }
      })
      this.$emit('next', this.result);
    },
    cancel() {
      this.$emit('next', null);
    }
  }
};
</script>
<style scoped>
.center {
  display: flex;
  justify-content: center;
}
.second-title {
  color: var(--el-text-color-primary);
  font-size: 16px;
  font-weight: bold;
  margin-bottom: 16px;
}
</style>
src/views/fysp/data-product/components/CompShowEChart.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,211 @@
<template>
  <!-- é€‰é¡¹ -->
  <div class="options">
    <el-form :inline="true">
      <!-- <el-form-item>
        <el-option
          v-model="charType"
          v-for="type of charTypeOptions"
          :key="type"
          :value="type"
          :label="type"
        ></el-option>
      </el-form-item> -->
      <el-form-item>
        <el-select
          v-model="charType"
          style="width: 150px"
          placeholder="请选择图表类型"
        >
          <el-option
            v-for="item in charTypeOptions"
            :key="item.value"
            :value="item.value"
            :label="item.label"
          ></el-option>
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-select
          v-model="selectHeader"
          style="width: 200px"
          @change="onSelectHeaderChange"
          value-key="id"
          placeholder="请选择展示的列"
        >
          <el-option
            v-for="item in allHeader"
            :key="item.id"
            :value="item"
            :label="item.label"
          ></el-option>
        </el-select>
      </el-form-item>
    </el-form>
  </div>
  <div ref="myChart" id="myChart" class="chart"></div>
  <el-button type="primary" @click="ok()">确定</el-button>
</template>
<script>
export default {
  props: {
    tableHeader: {
      type: Array,
      required: true
    },
    tableData: {
      type: Array,
      required: true
    }
  },
  computed: {
    allHeader() {
      return this.treeToArray(this.tableHeader);
    }
  },
  data() {
    return {
      selectHeader: null,
      charType: null,
      charTypeOptions: [
        {
          label: '直方图',
          value: 'bar'
        },
        {
          label: '折线图',
          value: 'line'
        },
        {
          label: '饼图',
          value: 'pie'
        }
      ],
      charts: [],
      chart: {}
    };
  },
  mounted() {
    // this.setChartOptions('myChart', this.header.prop, this.header.label);
    // this.selectHeader = this.tableHeader[0]
  },
  methods: {
    getChartObj() {
      return this.chart;
    },
    ok() {
      let option = this.setChartOptions(
        'myChart',
        this.selectHeader.prop,
        this.selectHeader.label
      );
      this.$emit('painted-chart', option);
    },
    onSelectHeaderChange() {
      this.setChartOptions(
        'myChart',
        this.selectHeader.prop,
        this.selectHeader.label
      );
    },
    // é€’归的获取obj中的prop属性
    getPropValueLoop(obj, prop) {
      if (typeof prop !== 'string') {
        return obj;
      }
      const props = prop.split('.');
      let result = obj;
      props.forEach((item) => {
        result = result[item];
      });
      return result;
    },
    addChart(ref) {},
    setChartOptions(refName, prop, label) {
      let series = this.tableData.map((item) =>
        this.getPropValueLoop(item, prop)
      );
      const dom = this.$refs[refName];
      const myChart = this.$echarts.init(dom); // åˆå§‹åŒ–echarts实例
      this.chart = myChart;
      const option = {
        title: {
          text: label //设置我们的标题
        },
        tooltip: {
          trigger: 'item'
        },
        legend: {
          orient: 'vertical',
          left: 'left'
        },
        xAxis: {
          type: 'category',
          data: Array.from(new Set(series)),
          axisLabel: {
            rotate: 45, // æ—‹è½¬æ ‡ç­¾ï¼Œé¿å…é‡å 
            // æˆ–者
            interval: 0 // æ˜¾ç¤ºæ‰€æœ‰æ ‡ç­¾ï¼Œå¯èƒ½å¯¼è‡´é‡å ï¼Œæ ¹æ®éœ€æ±‚调整
          }
        },
        yAxis: {
          type: 'value'
        },
        series: [
          {
            data: Array.from(new Set(series)).map((item) =>
              this.getCount(series, item)
            ),
            type: this.charType,
            smooth: true
          }
        ]
      };
      // è®¾ç½®å®žä¾‹å‚æ•°
      myChart.setOption(option);
      return option;
    },
    getCount(array, element) {
      let count = 0;
      array.forEach((e) => {
        if (e == element) {
          count++;
        }
      });
      return count;
    },
    treeToArray(nodes) {
      let labels = [];
      function traverseNodes(nodes) {
        nodes.forEach((node) => {
          labels.push(node);
          // å¦‚果当前节点有子节点,递归调用遍历函数
          if (node.children && node.children.length > 0) {
            traverseNodes(node.children);
          }
        });
      }
      traverseNodes(nodes); // å¼€å§‹é€’归遍历
      return labels; // è¿”回收集到的 labels æ•°ç»„
    }
  }
};
</script>
<style scoped>
.options {
  display: flex;
  margin: 5px;
  margin-left: 0px;
}
.chart {
  width: 'auto';
  height: 500px;
}
</style>
src/views/fysp/data-product/components/DynamicTable.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,246 @@
<template>
  <!-- ä¿®æ”¹è¡¨å¤´ -->
  <el-col>
    <el-row justify="end" class="btn-group">
      <el-popover placement="bottom" :width="400" trigger="click">
        <template #reference>
          <!-- <el-button type="primary">表头修改</el-button> -->
        </template>
        <!-- åˆ—出表头的树状结构 -->
        <el-tree
          ref="tree"
          :props="headTreeProps"
          :data="tableHeader"
          node-key="id"
          show-checkbox
          :default-checked-keys="defaultCheckedKeys"
          @check="check"
        />
      </el-popover>
      <!-- <el-button type="primary" @click="openChart">图表展示</el-button> -->
    </el-row>
  </el-col>
  <el-table
    :data="tableData"
    border
    :header-cell-style="headerStyle"
    :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
    row-key="id"
    lazy
    max-height="60vh"
    v-loading="loading"
    ref="tableRef"
  >
    <template v-for="item in tableHeader.filter((item) => !item.hidden)">
      <table-column
        v-if="item.children && item.children.length"
        :key="item.id"
        :coloumn-header="item"
      ></table-column>
      <el-table-column
        v-else
        :key="item.id + 'else'"
        :label="item.label"
        :prop="item.prop"
        align="center"
        :min-width="item.width ? item.width : '200px'"
        :sortable="item.sortable == undefined ? false : item.sortable"
      >
        <!-- <template #header>
          {{ item.label }}
          <el-button type="primary" @click="hidden(item)">隐藏</el-button>
        </template> -->
      </el-table-column>
    </template>
  </el-table>
  <el-pagination
    v-if="pagination"
    ref="paginationRef"
    class="el-pagination"
    v-model:current-page="currentPage"
    v-model:page-size="pageSize"
    :page-sizes="[10, 20, 50, 100]"
    :background="true"
    layout="total, sizes, prev, pager, next, jumper"
    :total="total"
  />
  <el-dialog title="图表展示" v-model="chartDialog" destroy-on-close>
    <CompShowEChart :table-data="tableData" :table-header="tableHeader">
    </CompShowEChart>
  </el-dialog>
</template>
<script>
import CompShowEChart from './CompShowEChart.vue';
import TableColumn from './TableColumn.vue';
import tableCol from '../js/tableCol';
export default {
  components: {
    TableColumn,
    CompShowEChart
  },
  mounted() {
    // this.onSearch();
  },
  watch: {
    currentPage(nValue, oValue) {
      if (nValue != oValue) {
        this.onSearch();
      }
    },
    pageSize(nValue, oValue) {
      if (nValue != oValue) {
        this.onSearch();
      }
    }
  },
  computed: {
    defaultCheckedKeys() {
      return this.collectLabels(this.tableHeader);
    }
  },
  data() {
    return {
      total: 0,
      currentPage: 1,
      pageSize: 20,
      tableData: [],
      chartDialog: false,
      char1: {},
      headTreeProps: {
        children: 'children',
        label: 'label',
        disabled: 'noncloseable'
      }
    };
  },
  props: {
    // è¡¨æ ¼çš„æ•°æ®
    // tableData: {
    //   type: Array,
    //   required: true
    // },
    pagination: {
      type: Boolean,
      default: false
    },
    // å¤šçº§è¡¨å¤´çš„æ•°æ®
    tableHeader: {
      type: Array,
      required: true
    }
  },
  methods: {
    // @Description: è®¾ç½®è¡¨å¤´æ ·å¼
    headerStyle({ row, rowIndex, columnIndex }) {
      if (row[columnIndex].order != '' && row[columnIndex].order != null) {
        return { 'background-color': '#F56C6C', 'color': '#000000' };
      }
      return {}
    },
    // æ–¹æ³•不自动触发,由父组件主动调用获取列表数据
    onSearch() {
      this.loading = true;
      this.$emit(
        'search',
        {
          currentPage: this.currentPage,
          pageSize: this.pageSize
        },
        (res) => {
          this.tableData = res.data;
          this.total = res.total ? res.total : 0;
          this.loading = false;
          // this.doLayout();
        }
      );
    },
    doLayout() {
      this.$refs.tableRef.doLayout();
    },
    openChart() {
      this.chartDialog = true;
    },
    check(checkedNode, checkedKeys, halfCheckedNodes, halfCheckedKeys) {
      checkedNode.hidden = !checkedNode.hidden;
      this.treeToArray(this.tableHeader).map((item) => {
        if (item.hidden) {
          console.log('隐藏', item.id);
          this.$refs.tree.setCheckedKeys([item.id], false);
        } else {
          console.log('显示', item.id);
          this.$refs.tree.setCheckedKeys([item.id], true);
        }
      });
    },
    load(tree, treeNode, resolve) {
      this.$emit('load', tree, treeNode, resolve);
    },
    hidden(header) {
      header.hidden = true;
    },
    collectLabels(nodes) {
      let labels = [];
      function traverseNodes(nodes) {
        nodes.forEach((node) => {
          if (!('hidden' in node) || !node.hidden) {
            labels.push(node.id);
          }
          // å¦‚果当前节点有子节点,递归调用遍历函数
          if (node.children && node.children.length > 0) {
            traverseNodes(node.children);
          }
        });
      }
      traverseNodes(nodes); // å¼€å§‹é€’归遍历
      return labels; // è¿”回收集到的 labels æ•°ç»„
    },
    treeToArray(nodes) {
      let labels = [];
      function traverseNodes(nodes) {
        nodes.forEach((node) => {
          labels.push(node);
          // å¦‚果当前节点有子节点,递归调用遍历函数
          if (node.children && node.children.length > 0) {
            traverseNodes(node.children);
          }
        });
      }
      traverseNodes(nodes); // å¼€å§‹é€’归遍历
      return labels; // è¿”回收集到的 labels æ•°ç»„
    }
  }
};
</script>
<style scoped>
.btns {
  display: flex;
  margin: 5px;
  margin-left: 0px;
}
.el-pagination {
  background-color: var(--el-color-white);
  padding-top: 20px;
  border-top: 1px solid rgba(0, 0, 0, 0.096);
  /* margin-top: 2px; */
}
.btn-group {
  /* background-color: rgb(32, 127, 211); */
  white-space: nowrap;
}
.table-cell-red {
  background-color: rgb(196, 23, 23);
}
</style>
src/views/fysp/data-product/components/MutiableOptionScene.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,84 @@
<template>
  <el-form-item label="场景类型" :prop="prop">
    <el-select
      :model-value="value"
      @change="handleChange"
      placeholder="场景类型"
      style="width: 350px"
      :multiple="multiple"
    >
      <el-option
        v-for="s in sceneTypes"
        :key="s.value"
        :label="s.label"
        :value="s"
      />
    </el-select>
  </el-form-item>
</template>
<script>
import { enumScene } from '@/enum/scene';
export default {
  props: {
    // æ˜¯å¦å¯ä»¥å¤šé€‰
    multiple: {
      type: Boolean,
      default: false
    },
    // æ˜¯å¦åœ¨é¦–选项处添加“全部”选项
    allOption: {
      type: Boolean,
      default: true
    },
    // 1:飞羽环境系统;2:飞羽监管系统;
    type: {
      type: Number || String,
      default: 1
    },
    // è¿”回结果
    value: Object,
    // æ˜¯å¦é»˜è®¤è¿”回初始选项
    initValue: {
      type: Boolean,
      default: true
    },
    // form表单绑定属性名
    prop: {
      type: String,
      default: '_scenetype'
    },
    // åˆ‡æ¢ type åŽï¼Œå½“前选项是否清空
    sourceInit: {
      type: Boolean,
      default: true
    }
  },
  emits: ['update:value'],
  data() {
    return {
      // sceneTypes: enumScene(this.type, this.allOption),
    };
  },
  computed: {
    sceneTypes() {
      if (this.sourceInit) {
        // å½“因为type或者allOption参数变化引起选项变更时,清空当前选项
        this.handleChange();
      }
      return enumScene(this.type, this.allOption);
    }
  },
  methods: {
    handleChange(value) {
      this.$emit('update:value', value);
    }
  },
  mounted() {
    if (this.initValue) {
      this.handleChange(this.sceneTypes[0]);
    }
  }
};
</script>
src/views/fysp/data-product/components/OptionChartProd.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
<template>
  <el-form-item>
    <el-select
      :model-value="value"
      style="width: 150px"
      placeholder="请选择图表类型"
      :multiple="multiple"
    >
      <el-option
        v-for="item in charTypeOptions"
        :key="item.value"
        :value="item.value"
        :label="item.label"
      ></el-option>
    </el-select>
  </el-form-item>
</template>
<script>
export default {
  methods: {
  },
  props: {
    // ç»“果返回
    value: Object,
    // æ˜¯å¦å¤šé€‰
    multiple: {
        type: Boolean,
        default: false
    }
  },
  data() {
    return {
      charTypeOptions: [
        {
          label: '直方图',
          value: 'bar'
        },
        {
          label: '折线图',
          value: 'line'
        }
      ]
    };
  }
};
</script>
<style scoped></style>
src/views/fysp/data-product/components/TableColumn.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,71 @@
<template>
  <el-table-column
    :label="coloumnHeader.label"
    :prop="coloumnHeader.label"
    show-overflow-tooltip
    align="center"
    :min-width="coloumnHeader.width ? coloumnHeader.width : '200px'"
    :sortable="
      coloumnHeader.sortable == undefined ? false : coloumnHeader.sortable
    "
  >
    <!-- <template #header>
        {{ coloumnHeader.label }}
      <el-button type="primary" @click="hidden1">隐藏</el-button>
    </template> -->
    <template
      v-for="item in coloumnHeader.children.filter((item) => !item.hidden)"
    >
      <tableColumn
        v-if="item.children && item.children.length"
        :key="item.id"
        :coloumn-header="item"
      >
      </tableColumn>
      <el-table-column
        v-else
        :key="item.name"
        :label="item.label"
        :prop="item.prop"
        align="center"
        :min-width="item.width ? item.width : '200px'"
        :sortable="item.sortable == undefined ? false : item.sortable"
      >
        <!-- <template #header>
            {{ item.label }}
          <el-button type="primary" @click="hidden2(item)">隐藏</el-button>
        </template> -->
      </el-table-column>
    </template>
  </el-table-column>
</template>
<script>
import tableCol from '../js/tableCol';
export default {
  name: 'tableColumn',
  props: {
    // è¡¨å¤´æ•°æ®ä¿¡æ¯
    coloumnHeader: {
      type: Object,
      required: true
    }
  },
  methods: {
    hidden1() {
      let header = this.coloumnHeader;
      header.hidden = true;
    },
    hidden2(header) {
      header.hidden = true;
    },
    sortChangeFunc(column, prop, order) {
      console.log('tableCol', tableCol);
      tableCol.sortChange(column, prop, order);
    }
  }
};
</script>
<style scoped></style>
src/views/fysp/data-product/js/genChart.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,123 @@
// é€’归的获取obj中的prop属性 è§£å†³æœ‰æ—¶éœ€è¦å–val.obj.prop的情况
function getPropValueLoop(obj, prop) {
  if (typeof prop !== 'string') {
    return obj;
  }
  const props = prop.split('.');
  let result = obj;
  props.forEach((item) => {
    result = result[item];
  });
  return result;
}
function getCount(array, element) {
  let count = 0;
  array.forEach((e) => {
    if (e == element) {
      count++;
    }
  });
  return count;
}
export default {
  getPieChartByDataAndProp(data, prop, label) {
    let chartData = []
    function addNewName(name) {
      chartData.push({
        name: name,
        value: 1
      })
    }
    function addCount(name) {
      chartData.map(item=>{
        if (item.name === name) {
          item.value++
        }
      })
    }
    function hasThisName(name) {
      for (let index = 0; index < chartData.length; index++) {
        const element = chartData[index];
        if (element.name === name) {
          return true
        }
      }
      return false
    }
    data
      .map((item) => {
        const name = getPropValueLoop(item, prop)
        if (hasThisName(name)) {
          addCount(name)
        }else {
          addNewName(name)
        }
      })
    return {
      title: {
        text: label,
        left: 'center'
      },
      tooltip: {
        trigger: 'item'
      },
      legend: {
        orient: 'vertical',
        left: 'left'
      },
      series: [
        {
          type: 'pie',
          radius: '50%',
          data: chartData,
          emphasis: {
            itemStyle: {
              shadowBlur: 10,
              shadowOffsetX: 0,
              shadowColor: 'rgba(0, 0, 0, 0.5)'
            }
          }
        }
      ]
    };
  },
  getChartByDataAndProp(data, prop, label, chartType = 'bar') {
    let series = data.map((item) => getPropValueLoop(item, prop));
    const option = {
      title: {
        text: label //设置标题
      },
      tooltip: {
        trigger: 'item'
      },
      legend: {
        orient: 'vertical',
        left: 'left'
      },
      xAxis: {
        type: 'category',
        data: Array.from(new Set(series)),
        axisLabel: {
          rotate: 45, // æ—‹è½¬æ ‡ç­¾ï¼Œé¿å…é‡å 
          // æˆ–者
          interval: 0 // æ˜¾ç¤ºæ‰€æœ‰æ ‡ç­¾ï¼Œå¯èƒ½å¯¼è‡´é‡å ï¼Œæ ¹æ®éœ€æ±‚调整
        }
      },
      yAxis: {
        type: 'value'
      },
      series: [
        {
          data: Array.from(new Set(series)).map((item) =>
            getCount(series, item)
          ),
          type: chartType,
          smooth: true
        }
      ]
    };
    return option;
  }
};
src/views/fysp/data-product/js/htmlTransfer.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,120 @@
import Docxtemplater from 'docxtemplater'
import PizZip from 'pizzip'
import JSZipUtils from 'jszip-utils'
import { saveAs } from 'file-saver'
import ImageModule from 'docxtemplater-image-module-free'
import expressions from 'angular-expressions'
import PizZipUtils from 'pizzip/utils/index.js'
/**
 * å°†base64格式的数据转为ArrayBuffer
 * @param {Object} dataURL base64格式的数据
 */
function base64DataURLToArrayBuffer(dataURL) {
    const base64Regex = /^data:image\/(png|jpg|jpeg|svg|svg\+xml);base64,/;
    if (!base64Regex.test(dataURL)) {
        return false;
    }
    const stringBase64 = dataURL.replace(base64Regex, "");
    let binaryString;
    if (typeof window !== "undefined") {
        binaryString = window.atob(stringBase64);
    } else {
        binaryString = Buffer.from(stringBase64, "base64").toString("binary");
    }
    const len = binaryString.length;
    const bytes = new Uint8Array(len);
    for (let i = 0; i < len; i++) {
        const ascii = binaryString.charCodeAt(i);
        bytes[i] = ascii;
    }
    return bytes.buffer;
}
export default {
    chartToImageUrl(chart) {
        const dataURL = chart.getDataURL({
            pixelRatio: 5, // æé«˜å›¾ç‰‡è´¨é‡
            backgroundColor: '#FFFFFF', // è®¾ç½®èƒŒæ™¯é¢œè‰²
            excludeComponents: ['toolbox'], // æŽ’除工具箱组件
            type: 'png' // è¾“出图片类型为PNG
        });
        return dataURL;
    },
    /**
 * å¯¼å‡ºword文档(带图片)
 *
 */
ExportBriefDataDocx(tempDocxPath, data, fileName, imgSize) {
  expressions.filters.lower = function(input) {
    if (!input) return input
    return input.toLowerCase()
  }
  JSZipUtils.getBinaryContent(tempDocxPath, (error, content) => {
    if (error) {
      console.log(error)
    }
    expressions.filters.size = function(input, width, height) {
      return {
        data: input,
        size: [width, height]
      }
    }
    let opts = {}
    opts = {
      // å›¾åƒæ˜¯å¦å±…中
      centered: false
    }
    opts.getImage = (chartId) => {
        //将base64的数据转为ArrayBuffer
        return base64DataURLToArrayBuffer(chartId);
    }
    opts.getSize = function(img, tagValue, tagName) {
      console.log("img, tagValue, tagName", img, tagValue, tagName);
      if (imgSize[tagName]) {
        console.log("imgSize[tagName]", imgSize[tagName]);
        return imgSize[tagName]
      } else {
        return [300, 300]
      }
    }
    // åˆ›å»ºä¸€ä¸ªJSZip实例,内容为模板的内容
    const zip = new PizZip(content)
    // åˆ›å»ºå¹¶åŠ è½½ Docxtemplater å®žä¾‹å¯¹è±¡
    // è®¾ç½®æ¨¡æ¿å˜é‡çš„值
    const doc = new Docxtemplater()
    doc.attachModule(new ImageModule(opts))
    doc.loadZip(zip)
    doc.setOptions({
      nullGetter: function() { // è®¾ç½®ç©ºå€¼ undefined ä¸º''
        return ''
      }
    })
    doc.setData(data)
    try {
      // å‘ˆçŽ°æ–‡æ¡£ï¼Œä¼šå°†å†…éƒ¨æ‰€æœ‰å˜é‡æ›¿æ¢æˆå€¼ï¼Œ
      doc.render()
    } catch (error) {
      const e = {
        message: error.message,
        name: error.name,
        stack: error.stack,
        properties: error.properties
      }
      console.log('err', { error: e })
      // å½“使用json记录时,此处抛出错误信息
      throw error
    }
    // ç”Ÿæˆä¸€ä¸ªä»£è¡¨Docxtemplater对象的zip文件(不是一个真实的文件,而是在内存中的表示)
    const out = doc.getZip().generate({
      type: 'blob',
      mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
    })
    // å°†ç›®æ ‡æ–‡ä»¶å¯¹è±¡ä¿å­˜ä¸ºç›®æ ‡ç±»åž‹çš„æ–‡ä»¶ï¼Œå¹¶å‘½å
    saveAs(out, fileName)
  })
}
}
src/views/fysp/data-product/js/tableCol.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
export default {
    treeToArray(nodes) {
        let labels = [];
        function traverseNodes(nodes) {
          nodes.forEach((node) => {
            labels.push(node);
            // å¦‚果当前节点有子节点,递归调用遍历函数
            if (node.children && node.children.length > 0) {
              traverseNodes(node.children);
            }
          });
        }
        traverseNodes(nodes); // å¼€å§‹é€’归遍历
        return labels; // è¿”回收集到的 labels æ•°ç»„
      }
}