Kaynağa Gözat

Merge branch 'MicroFunction' of http://47.103.92.60:3003/skyversation/qp_onemap_ui into onemap_zmg

DESKTOP-6LTVLN7\Liumouren 3 hafta önce
ebeveyn
işleme
56a1effb05

+ 96 - 1
package-lock.json

@@ -19,7 +19,8 @@
         "vue-json-editor": "^1.4.3",
         "vue-json-viewer": "^3.0.4",
         "vue-router": "^4.0.3",
-        "vuex": "^4.0.0"
+        "vuex": "^4.0.0",
+        "xlsx": "^0.18.5"
       },
       "devDependencies": {
         "@vue/cli-plugin-router": "~5.0.0",
@@ -1200,6 +1201,14 @@
         "node": ">= 10.0.0"
       }
     },
+    "node_modules/adler-32": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmmirror.com/adler-32/-/adler-32-1.3.1.tgz",
+      "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/ajv": {
       "version": "8.17.1",
       "dev": true,
@@ -1700,6 +1709,18 @@
         "node": ">=4"
       }
     },
+    "node_modules/cfb": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmmirror.com/cfb/-/cfb-1.2.2.tgz",
+      "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
+      "dependencies": {
+        "adler-32": "~1.3.0",
+        "crc-32": "~1.2.0"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/chalk": {
       "version": "3.0.0",
       "dev": true,
@@ -1877,6 +1898,14 @@
         "node": ">=6"
       }
     },
+    "node_modules/codepage": {
+      "version": "1.15.0",
+      "resolved": "https://registry.npmmirror.com/codepage/-/codepage-1.15.0.tgz",
+      "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/color-convert": {
       "version": "2.0.1",
       "dev": true,
@@ -2136,6 +2165,17 @@
         "node": ">=10"
       }
     },
+    "node_modules/crc-32": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz",
+      "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
+      "bin": {
+        "crc32": "bin/crc32.njs"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/cross-spawn": {
       "version": "6.0.6",
       "dev": true,
@@ -3234,6 +3274,14 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/frac": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/frac/-/frac-1.1.2.tgz",
+      "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/fraction.js": {
       "version": "5.3.4",
       "dev": true,
@@ -6526,6 +6574,17 @@
         "wbuf": "^1.7.3"
       }
     },
+    "node_modules/ssf": {
+      "version": "0.11.2",
+      "resolved": "https://registry.npmmirror.com/ssf/-/ssf-0.11.2.tgz",
+      "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
+      "dependencies": {
+        "frac": "~1.1.2"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/ssri": {
       "version": "8.0.1",
       "dev": true,
@@ -7516,6 +7575,22 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/wmf": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/wmf/-/wmf-1.0.2.tgz",
+      "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
+    "node_modules/word": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmmirror.com/word/-/word-0.3.0.tgz",
+      "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/wrap-ansi": {
       "version": "7.0.0",
       "dev": true,
@@ -7557,6 +7632,26 @@
         }
       }
     },
+    "node_modules/xlsx": {
+      "version": "0.18.5",
+      "resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz",
+      "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
+      "dependencies": {
+        "adler-32": "~1.3.0",
+        "cfb": "~1.2.1",
+        "codepage": "~1.15.0",
+        "crc-32": "~1.2.1",
+        "ssf": "~0.11.2",
+        "wmf": "~1.0.1",
+        "word": "~0.3.0"
+      },
+      "bin": {
+        "xlsx": "bin/xlsx.njs"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/y18n": {
       "version": "5.0.8",
       "dev": true,

+ 2 - 1
package.json

@@ -20,7 +20,8 @@
     "vue-json-editor": "^1.4.3",
     "vue-json-viewer": "^3.0.4",
     "vue-router": "^4.0.3",
-    "vuex": "^4.0.0"
+    "vuex": "^4.0.0",
+    "xlsx": "^0.18.5"
   },
   "devDependencies": {
     "@vue/cli-plugin-router": "~5.0.0",

Dosya farkı çok büyük olduğundan ihmal edildi
+ 953 - 71
src/components/wgn/controlPanel.vue


+ 6 - 3
src/utils/request.js

@@ -131,13 +131,16 @@ function postform(url, data) {
 }
 function postBody(url, data) {
   return new Promise((resolve, reject) => {
+    const isFormData = typeof FormData !== "undefined" && data instanceof FormData;
     let params = {
       method: "POST",
       url,
       data: data,
-      headers: {
-        "Content-Type": "application/json;",
-      },
+      headers: isFormData
+        ? {}
+        : {
+            "Content-Type": "application/json;",
+          },
     };
     if (url.indexOf("downloadFile") != -1 || url.indexOf("downFileAllServiceDatas") != -1) {
       params.responseType = "blob";

+ 189 - 2
src/views/Wgn.vue

@@ -203,6 +203,8 @@
                   'inPrj',
                   'outPrj',
                   'compressionRatio',
+                  'keyType',
+                  'geocodeColumnKeyword',
                 ])
               "
             >
@@ -219,9 +221,17 @@
                       'inPrj',
                       'outPrj',
                       'compressionRatio',
+                      'keyType',
+                      'geocodeColumnKeyword',
                     ].includes(rulesItem)
                   "
-                  :label="rulesItem"
+                  :label="
+                    rulesItem === 'keyType'
+                      ? '类型字段'
+                      : rulesItem === 'geocodeColumnKeyword'
+                        ? '数据列名'
+                        : rulesItem
+                  "
                 >
                   <el-input
                     v-if="['lonKey', 'latKey'].includes(rulesItem)"
@@ -242,6 +252,45 @@
                       :value="item.value"
                     />
                   </el-select>
+                  <el-select
+                    v-if="rulesItem === 'keyType'"
+                    v-model="taskColumn.from.c_source_data.keyType"
+                    placeholder="非空转空时选择地物类型"
+                    :clearable="!isGeocodingTaskKeyTypeReadonly()"
+                    filterable
+                    :disabled="isGeocodingTaskKeyTypeReadonly()"
+                  >
+                    <el-option
+                      v-for="item in taskColumn.selectOptions.keyType"
+                      :key="'wgn-task-keyType-' + item.value"
+                      :label="item.label"
+                      :value="item.value"
+                    />
+                  </el-select>
+                  <el-input
+                    v-if="rulesItem === 'geocodeColumnKeyword' && isTaskGeocodeZipForColumnInput"
+                    v-model="taskColumn.from.c_source_data.geocodeColumnKeyword"
+                    placeholder="zip 内含多个表格时请手动输入列名(与各表表头一致)"
+                    clearable
+                  />
+                  <el-select
+                    v-if="rulesItem === 'geocodeColumnKeyword' && !isTaskGeocodeZipForColumnInput"
+                    v-model="taskColumn.from.c_source_data.geocodeColumnKeyword"
+                    placeholder="请先上传 CSV / XLSX,将自动读取表头"
+                    clearable
+                    filterable
+                    :disabled="
+                      !taskColumn.selectOptions.geocodeColumnKeyword ||
+                      !taskColumn.selectOptions.geocodeColumnKeyword.length
+                    "
+                  >
+                    <el-option
+                      v-for="item in taskColumn.selectOptions.geocodeColumnKeyword"
+                      :key="'wgn-task-geocol-' + item.value"
+                      :label="item.label"
+                      :value="item.value"
+                    />
+                  </el-select>
                 </el-form-item>
               </template>
             </div>
@@ -280,6 +329,7 @@
 <script>
 import { ElNotification } from "element-plus";
 import moment from "moment";
+import * as XLSX from "xlsx";
 import api from "@/api/content";
 export default {
   name: "ApplicationOverview",
@@ -383,6 +433,14 @@ export default {
         },
         // 其他参数
         selectOptions: {
+          keyType: [
+            { value: "地址", label: "地址" },
+            { value: "村居", label: "村居" },
+            { value: "街镇", label: "街镇" },
+            { value: "区县", label: "区县" },
+            { value: "要素ID", label: "要素ID" },
+          ],
+          geocodeColumnKeyword: [],
           // 单位
           unit: [
             {
@@ -470,8 +528,18 @@ export default {
       },
       // 搜索微功能服务的防抖定时器
       searchTimeout: null,
+      taskUploadRawFile: null,
     };
   },
+  computed: {
+    isTaskGeocodeZipForColumnInput() {
+      const n =
+        (this.taskUploadRawFile && this.taskUploadRawFile.name) ||
+        this.taskColumn.from.c_source_file_name ||
+        "";
+      return String(n).toLowerCase().endsWith(".zip");
+    },
+  },
   mounted() {
     // 任务负责人默认为当前登录用户
     this.taskColumn.from.c_user_id = this.$store.state.userInfo.id;
@@ -480,6 +548,23 @@ export default {
     this.searchServerList();
   },
   methods: {
+    isGeocodingTaskKeyTypeReadonly() {
+      const label = this.$getDmsTypes("yzt_task_type", this.taskColumn.from.c_type);
+      return label === "文件地名地址查询";
+    },
+    syncKeyTypeForGeocodingTask() {
+      if (!this.isGeocodingTaskKeyTypeReadonly()) {
+        return;
+      }
+      const pr = this.taskColumn.apiFrom.apiParameterRules;
+      if (!Array.isArray(pr) || !pr.includes("keyType")) {
+        return;
+      }
+      if (!this.taskColumn.from.c_source_data) {
+        this.taskColumn.from.c_source_data = {};
+      }
+      this.taskColumn.from.c_source_data.keyType = "地址";
+    },
     includesKey(keys) {
       let hasKey = false;
       keys.forEach((key) => {
@@ -490,6 +575,7 @@ export default {
       return hasKey;
     },
     handleTCUpload(emit) {
+      this.taskUploadRawFile = emit.file;
       // 上传任务文件
       let formData = new FormData();
       formData.append("file", emit.file);
@@ -502,12 +588,15 @@ export default {
           if (res.code === 200) {
             this.taskColumn.from.c_source_file_name = emit.file.name;
             this.taskColumn.from.c_source_file = res.content;
+            this.refreshTaskGeocodeColumns(emit.file);
+            this.syncKeyTypeForGeocodingTask();
             ElNotification.success({
               title: "成功",
               message: "上传任务文件成功",
               offset: 80,
             });
           } else {
+            this.taskUploadRawFile = null;
             ElNotification.error({
               title: "失败",
               message: "上传任务文件失败" + res.msg,
@@ -516,6 +605,7 @@ export default {
           }
         })
         .catch((e) => {
+          this.taskUploadRawFile = null;
           ElNotification.error({
             title: "失败",
             message: "上传任务文件失败" + e,
@@ -553,6 +643,15 @@ export default {
               );
               // 接口路径
               this.taskColumn.apiFrom.apiUrl = apiInfo.c_url;
+              const pr = this.taskColumn.apiFrom.apiParameterRules;
+              if (
+                pr &&
+                pr.includes("geocodeColumnKeyword") &&
+                this.taskUploadRawFile
+              ) {
+                this.refreshTaskGeocodeColumns(this.taskUploadRawFile);
+              }
+              this.syncKeyTypeForGeocodingTask();
             } else {
               this.taskColumn.apiFrom.apiDescription = "";
               this.taskColumn.apiFrom.apiParameterRules = [];
@@ -566,7 +665,7 @@ export default {
           })
           .catch((e) => {
             this.taskColumn.apiFrom.apiDescription = "";
-            this.taskColumn.apiFrom.apiParameterRules = "";
+            this.taskColumn.apiFrom.apiParameterRules = [];
             ElNotification.error({
               title: "失败",
               message: "获取任务参数失败" + e,
@@ -574,6 +673,93 @@ export default {
             });
           });
         this.taskColumn.from.c_source_data = {};
+        this.taskColumn.selectOptions.geocodeColumnKeyword = [];
+      }
+    },
+    parseCsvHeaderLine(line) {
+      const out = [];
+      let cur = "";
+      let inq = false;
+      for (let i = 0; i < line.length; i++) {
+        const c = line[i];
+        if (c === '"') {
+          inq = !inq;
+          continue;
+        }
+        if (!inq && c === ",") {
+          out.push(cur.trim());
+          cur = "";
+          continue;
+        }
+        cur += c;
+      }
+      out.push(cur.trim());
+      return out.filter((h) => h.length > 0);
+    },
+    async refreshTaskGeocodeColumns(file) {
+      const rules = this.taskColumn.apiFrom.apiParameterRules;
+      if (!rules || !rules.includes("geocodeColumnKeyword")) {
+        return;
+      }
+      if (!this.taskColumn.from.c_source_data) {
+        this.taskColumn.from.c_source_data = {};
+      }
+      this.taskColumn.from.c_source_data.geocodeColumnKeyword = "";
+      this.taskColumn.selectOptions.geocodeColumnKeyword = [];
+      const raw = file && (file.raw || file);
+      if (!raw || !raw.name) {
+        return;
+      }
+      const name = String(raw.name).toLowerCase();
+      if (name.endsWith(".zip")) {
+        ElNotification.info({
+          title: "数据列名",
+          message: "zip 包请在「数据列名」中手动填写与各 xlsx 表头一致的列名",
+          offset: 80,
+        });
+        return;
+      }
+      try {
+        if (name.endsWith(".csv")) {
+          const text = await raw.text();
+          const firstLine = (text.split(/\r?\n/).find((l) => l.trim().length > 0) || "").trim();
+          if (!firstLine) {
+            return;
+          }
+          const headers = this.parseCsvHeaderLine(firstLine);
+          this.taskColumn.selectOptions.geocodeColumnKeyword = headers.map((h) => ({
+            value: h,
+            label: h,
+          }));
+          ElNotification.success({
+            title: "表头",
+            message: "已解析 CSV 表头,共 " + headers.length + " 列",
+            offset: 80,
+          });
+        } else if (name.endsWith(".xlsx") || name.endsWith(".xls")) {
+          const buf = await raw.arrayBuffer();
+          const wb = XLSX.read(buf, { type: "array" });
+          const sheetName = wb.SheetNames[0];
+          const sheet = wb.Sheets[sheetName];
+          const rows = XLSX.utils.sheet_to_json(sheet, { header: 1, defval: "" });
+          const headers = (rows[0] || []).map((c) => String(c).trim()).filter((c) => c.length > 0);
+          this.taskColumn.selectOptions.geocodeColumnKeyword = headers.map((h) => ({
+            value: h,
+            label: h,
+          }));
+          ElNotification.success({
+            title: "表头",
+            message: "已解析 Excel 首行表头,共 " + headers.length + " 列",
+            offset: 80,
+          });
+        }
+      } catch (e) {
+        console.error(e);
+        ElNotification.warning({
+          title: "表头解析",
+          message: "解析表头失败,请确认上传为 CSV 或 XLSX",
+          offset: 80,
+        });
       }
     },
     // 申请使用微功能服务
@@ -726,6 +912,7 @@ export default {
       // 表单验证
       this.$refs.taskFormRef.validate((valid) => {
         if (valid) {
+          this.syncKeyTypeForGeocodingTask();
           let content = this.taskColumn.from;
           content.title =
             this.$moment(new Date()).format("YYMMDD_HHmm") +

+ 208 - 4
src/views/WgnLast.vue

@@ -220,7 +220,15 @@
           <div
             v-if="
               taskColumn.apiFrom.apiParameterRules &&
-              includesKey(['lonKey', 'latKey', 'outFileType', 'inPrj', 'outPrj'])
+              includesKey([
+                'lonKey',
+                'latKey',
+                'outFileType',
+                'inPrj',
+                'outPrj',
+                'keyType',
+                'geocodeColumnKeyword',
+              ])
             "
           >
             <template
@@ -228,8 +236,24 @@
               :key="index"
             >
               <el-form-item
-                v-if="['lonKey', 'latKey', 'outFileType', 'inPrj', 'outPrj'].includes(rulesItem)"
-                :label="rulesItem"
+                v-if="
+                  [
+                    'lonKey',
+                    'latKey',
+                    'outFileType',
+                    'inPrj',
+                    'outPrj',
+                    'keyType',
+                    'geocodeColumnKeyword',
+                  ].includes(rulesItem)
+                "
+                :label="
+                  rulesItem === 'keyType'
+                    ? '类型字段'
+                    : rulesItem === 'geocodeColumnKeyword'
+                      ? '数据列名'
+                      : rulesItem
+                "
               >
                 <el-input
                   v-if="['lonKey', 'latKey'].includes(rulesItem)"
@@ -248,6 +272,45 @@
                     :value="item.value"
                   />
                 </el-select>
+                <el-select
+                  v-if="rulesItem === 'keyType'"
+                  v-model="taskColumn.from.c_source_data.keyType"
+                  placeholder="非空转空时选择地物类型"
+                  :clearable="!isGeocodingTaskKeyTypeReadonly()"
+                  filterable
+                  :disabled="isGeocodingTaskKeyTypeReadonly()"
+                >
+                  <el-option
+                    v-for="item in taskColumn.selectOptions.keyType"
+                    :key="'wgn-task-keyType-' + item.value"
+                    :label="item.label"
+                    :value="item.value"
+                  />
+                </el-select>
+                <el-input
+                  v-if="rulesItem === 'geocodeColumnKeyword' && isTaskGeocodeZipForColumnInput"
+                  v-model="taskColumn.from.c_source_data.geocodeColumnKeyword"
+                  placeholder="zip 内含多个表格时请手动输入列名(与各表表头一致)"
+                  clearable
+                />
+                <el-select
+                  v-if="rulesItem === 'geocodeColumnKeyword' && !isTaskGeocodeZipForColumnInput"
+                  v-model="taskColumn.from.c_source_data.geocodeColumnKeyword"
+                  placeholder="请先上传 CSV / XLSX,将自动读取表头"
+                  clearable
+                  filterable
+                  :disabled="
+                    !taskColumn.selectOptions.geocodeColumnKeyword ||
+                    !taskColumn.selectOptions.geocodeColumnKeyword.length
+                  "
+                >
+                  <el-option
+                    v-for="item in taskColumn.selectOptions.geocodeColumnKeyword"
+                    :key="'wgn-task-geocol-' + item.value"
+                    :label="item.label"
+                    :value="item.value"
+                  />
+                </el-select>
               </el-form-item>
             </template>
           </div>
@@ -284,6 +347,7 @@
 
 <script>
 import { ElNotification } from "element-plus";
+import * as XLSX from "xlsx";
 import api from "@/api/content";
 export default {
   name: "微功能服务",
@@ -387,6 +451,14 @@ export default {
         },
         // 其他参数
         selectOptions: {
+          keyType: [
+            { value: "地址", label: "地址" },
+            { value: "村居", label: "村居" },
+            { value: "街镇", label: "街镇" },
+            { value: "区县", label: "区县" },
+            { value: "要素ID", label: "要素ID" },
+          ],
+          geocodeColumnKeyword: [],
           // 单位
           unit: [
             {
@@ -456,8 +528,20 @@ export default {
       },
       // 搜索微功能服务的防抖定时器
       searchTimeout: null,
+      /** 最近一次任务上传的原始文件,用于先上传后切换任务类型时补解析表头 */
+      taskUploadRawFile: null,
     };
   },
+  computed: {
+    /** 任务源文件为 zip(多表打包)时,数据列名改用手动输入 */
+    isTaskGeocodeZipForColumnInput() {
+      const n =
+        (this.taskUploadRawFile && this.taskUploadRawFile.name) ||
+        this.taskColumn.from.c_source_file_name ||
+        "";
+      return String(n).toLowerCase().endsWith(".zip");
+    },
+  },
   mounted() {
     this.dmsDataProxy = systemConfig.dmsDataProxy;
     // 任务负责人默认为当前登录用户
@@ -467,6 +551,24 @@ export default {
     this.searchServerList();
   },
   methods: {
+    /** 任务类型为「文件地名地址查询」时,类型字段固定为地址且不可改 */
+    isGeocodingTaskKeyTypeReadonly() {
+      const label = this.$getDmsTypes("yzt_task_type", this.taskColumn.from.c_type);
+      return label === "文件地名地址查询";
+    },
+    syncKeyTypeForGeocodingTask() {
+      if (!this.isGeocodingTaskKeyTypeReadonly()) {
+        return;
+      }
+      const pr = this.taskColumn.apiFrom.apiParameterRules;
+      if (!Array.isArray(pr) || !pr.includes("keyType")) {
+        return;
+      }
+      if (!this.taskColumn.from.c_source_data) {
+        this.taskColumn.from.c_source_data = {};
+      }
+      this.taskColumn.from.c_source_data.keyType = "地址";
+    },
     includesKey(keys) {
       let hasKey = false;
       keys.forEach((key) => {
@@ -477,6 +579,7 @@ export default {
       return hasKey;
     },
     handleTCUpload(emit) {
+      this.taskUploadRawFile = emit.file;
       // 上传任务文件
       let formData = new FormData();
       formData.append("file", emit.file);
@@ -489,12 +592,15 @@ export default {
           if (res.code === 200) {
             this.taskColumn.from.c_source_file_name = emit.file.name;
             this.taskColumn.from.c_source_file = res.content;
+            this.refreshTaskGeocodeColumns(emit.file);
+            this.syncKeyTypeForGeocodingTask();
             ElNotification.success({
               title: "成功",
               message: "上传任务文件成功",
               offset: 80,
             });
           } else {
+            this.taskUploadRawFile = null;
             ElNotification.error({
               title: "失败",
               message: "上传任务文件失败" + res.msg,
@@ -503,6 +609,7 @@ export default {
           }
         })
         .catch((e) => {
+          this.taskUploadRawFile = null;
           ElNotification.error({
             title: "失败",
             message: "上传任务文件失败" + e,
@@ -540,6 +647,15 @@ export default {
               );
               // 接口路径
               this.taskColumn.apiFrom.apiUrl = apiInfo.c_url;
+              const pr = this.taskColumn.apiFrom.apiParameterRules;
+              if (
+                pr &&
+                pr.includes("geocodeColumnKeyword") &&
+                this.taskUploadRawFile
+              ) {
+                this.refreshTaskGeocodeColumns(this.taskUploadRawFile);
+              }
+              this.syncKeyTypeForGeocodingTask();
             } else {
               this.taskColumn.apiFrom.apiDescription = "";
               this.taskColumn.apiFrom.apiParameterRules = [];
@@ -553,7 +669,7 @@ export default {
           })
           .catch((e) => {
             this.taskColumn.apiFrom.apiDescription = "";
-            this.taskColumn.apiFrom.apiParameterRules = "";
+            this.taskColumn.apiFrom.apiParameterRules = [];
             ElNotification.error({
               title: "失败",
               message: "获取任务参数失败" + e,
@@ -561,6 +677,93 @@ export default {
             });
           });
         this.taskColumn.from.c_source_data = {};
+        this.taskColumn.selectOptions.geocodeColumnKeyword = [];
+      }
+    },
+    parseCsvHeaderLine(line) {
+      const out = [];
+      let cur = "";
+      let inq = false;
+      for (let i = 0; i < line.length; i++) {
+        const c = line[i];
+        if (c === '"') {
+          inq = !inq;
+          continue;
+        }
+        if (!inq && c === ",") {
+          out.push(cur.trim());
+          cur = "";
+          continue;
+        }
+        cur += c;
+      }
+      out.push(cur.trim());
+      return out.filter((h) => h.length > 0);
+    },
+    async refreshTaskGeocodeColumns(file) {
+      const rules = this.taskColumn.apiFrom.apiParameterRules;
+      if (!rules || !rules.includes("geocodeColumnKeyword")) {
+        return;
+      }
+      if (!this.taskColumn.from.c_source_data) {
+        this.taskColumn.from.c_source_data = {};
+      }
+      this.taskColumn.from.c_source_data.geocodeColumnKeyword = "";
+      this.taskColumn.selectOptions.geocodeColumnKeyword = [];
+      const raw = file && (file.raw || file);
+      if (!raw || !raw.name) {
+        return;
+      }
+      const name = String(raw.name).toLowerCase();
+      if (name.endsWith(".zip")) {
+        ElNotification.info({
+          title: "数据列名",
+          message: "zip 包请在「数据列名」中手动填写与各 xlsx 表头一致的列名",
+          offset: 80,
+        });
+        return;
+      }
+      try {
+        if (name.endsWith(".csv")) {
+          const text = await raw.text();
+          const firstLine = (text.split(/\r?\n/).find((l) => l.trim().length > 0) || "").trim();
+          if (!firstLine) {
+            return;
+          }
+          const headers = this.parseCsvHeaderLine(firstLine);
+          this.taskColumn.selectOptions.geocodeColumnKeyword = headers.map((h) => ({
+            value: h,
+            label: h,
+          }));
+          ElNotification.success({
+            title: "表头",
+            message: "已解析 CSV 表头,共 " + headers.length + " 列",
+            offset: 80,
+          });
+        } else if (name.endsWith(".xlsx") || name.endsWith(".xls")) {
+          const buf = await raw.arrayBuffer();
+          const wb = XLSX.read(buf, { type: "array" });
+          const sheetName = wb.SheetNames[0];
+          const sheet = wb.Sheets[sheetName];
+          const rows = XLSX.utils.sheet_to_json(sheet, { header: 1, defval: "" });
+          const headers = (rows[0] || []).map((c) => String(c).trim()).filter((c) => c.length > 0);
+          this.taskColumn.selectOptions.geocodeColumnKeyword = headers.map((h) => ({
+            value: h,
+            label: h,
+          }));
+          ElNotification.success({
+            title: "表头",
+            message: "已解析 Excel 首行表头,共 " + headers.length + " 列",
+            offset: 80,
+          });
+        }
+      } catch (e) {
+        console.error(e);
+        ElNotification.warning({
+          title: "表头解析",
+          message: "解析表头失败,请确认上传为 CSV 或 XLSX",
+          offset: 80,
+        });
       }
     },
     // 搜索微功能服务
@@ -635,6 +838,7 @@ export default {
       // 表单验证
       this.$refs.taskFormRef.validate((valid) => {
         if (valid) {
+          this.syncKeyTypeForGeocodingTask();
           let content = this.taskColumn.from;
           content.title =
             this.$moment(new Date()).format("YYMMDD_HHmm") +

+ 1 - 1
src/views/rwgl/Index.vue

@@ -1,6 +1,6 @@
 <template>
   <el-affix :offset="0">
-    <div style="height: 44px; width: 100vw; background: #00002a"></div>
+    <div style="block-size: 44px; inline-size: 100vw; background: #00002a"></div>
   </el-affix>
   <el-affix :offset="44">
     <div class="blue-background">

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor