Sfoglia il codice sorgente

Merge branch 'MicroFunction' into onemap_zmg

mork 3 settimane fa
parent
commit
ec548c12cc

+ 6 - 1
src/api/rwgl.js

@@ -1,4 +1,4 @@
-import { postform, post } from "../utils/request";
+import { get, postform, post } from "../utils/request";
 import content from "./content";
 
 const dmsPath = systemConfig.dmsDataProxy;
@@ -90,3 +90,8 @@ async function resolveDmsMultiTableResult(result) {
 export function executeTask(taskId) {
   return post(taskExecute, { taskId });
 }
+
+/** 补全任务详情(含 c_error_message),与列表接口 /task/getTask 同前缀 */
+export function getTaskDetail(taskId) {
+  return get(systemConfig.baseServicerPath + "/task/detail", { taskId });
+}

+ 23 - 23
src/components/wgn/controlPanel.vue

@@ -607,8 +607,9 @@ export default {
       return null;
     },
     /**
-     * 发送前校验:元素个数(numberOf)与入参 FeatureCollection.features;
-     * 点线面参数按 elementTypes 声明顺序与要素几何类型一一对应(忽略 unit/file 等非几何项)。
+     * 发送前校验:以「元素个数」numberOf 为主,与入参 FeatureCollection.features 数量对齐;
+     * 「参数类型」elementTypes 中的 point/polyline/polygon 仅表示允许的几何类型(可多选),
+     * 不要求每种类型各来一个,也不要求与声明顺序一一对应(忽略 unit/file 等非几何项)。
      */
     validateSendGeometryParams() {
       if (!this.dmsServerItem || !Array.isArray(this.dmsServerItem.elementTypes)) {
@@ -626,17 +627,13 @@ export default {
         return { ok: true };
       }
 
+      const allowedCanonSet = new Set(geoSlots.map((s) => s.canon));
+
       const rawN = this.dmsServerItem.numberOf;
-      let expectedGeomCount = geoSlots.length;
+      let expectedGeomCount = null;
       if (rawN !== undefined && rawN !== null && rawN !== "") {
         const parsedN = typeof rawN === "number" ? rawN : Number(rawN);
         if (Number.isFinite(parsedN) && parsedN >= 0) {
-          if (parsedN !== geoSlots.length) {
-            return {
-              ok: false,
-              message: `场景元数据不一致:元素个数为 ${parsedN},点/线/面参数为 ${geoSlots.length} 个,请检查 DMS「元素个数」与「参数类型」配置`,
-            };
-          }
           expectedGeomCount = parsedN;
         }
       }
@@ -644,20 +641,22 @@ export default {
       const fc = this.jsonData;
       const features = fc && Array.isArray(fc.features) ? fc.features : [];
 
-      if (features.length > expectedGeomCount) {
-        return {
-          ok: false,
-          message: `入参几何要素过多:当前 ${features.length} 个,场景要求 ${expectedGeomCount} 个(元素个数)`,
-        };
-      }
-      if (features.length < expectedGeomCount) {
-        return {
-          ok: false,
-          message: `入参几何要素不足:当前 ${features.length} 个,场景要求 ${expectedGeomCount} 个(元素个数),请绘制或渲染足够要素`,
-        };
+      if (expectedGeomCount !== null) {
+        if (features.length > expectedGeomCount) {
+          return {
+            ok: false,
+            message: `入参几何要素过多:当前 ${features.length} 个,场景要求 ${expectedGeomCount} 个(元素个数)`,
+          };
+        }
+        if (features.length < expectedGeomCount) {
+          return {
+            ok: false,
+            message: `入参几何要素不足:当前 ${features.length} 个,场景要求 ${expectedGeomCount} 个(元素个数),请绘制或渲染足够要素`,
+          };
+        }
       }
 
-      for (let i = 0; i < geoSlots.length; i++) {
+      for (let i = 0; i < features.length; i++) {
         const f = features[i];
         const g = f && f.geometry;
         if (!g || !g.type) {
@@ -673,10 +672,11 @@ export default {
             message: `第 ${i + 1} 个几何要素类型「${g.type}」不符合场景要求,请使用点、线或面`,
           };
         }
-        if (fk !== geoSlots[i].canon) {
+        if (!allowedCanonSet.has(fk)) {
+          const allowedLabel = [...allowedCanonSet].join("、");
           return {
             ok: false,
-            message: `第 ${i + 1} 个几何要素类型错误:场景参数要求「${geoSlots[i].declared}」,当前为「${g.type}」`,
+            message: `第 ${i + 1} 个几何要素类型「${g.type}」不在场景允许的类型内,允许:${allowedLabel}`,
           };
         }
       }

+ 29 - 6
src/views/Wgn.vue

@@ -363,6 +363,7 @@ import { ElNotification } from "element-plus";
 import moment from "moment";
 import * as XLSX from "xlsx";
 import api from "@/api/content";
+import { executeTask } from "@/api/rwgl";
 import { WGN_SCENE_LIST } from "@/data/wgnSceneList";
 export default {
   name: "ApplicationOverview",
@@ -1055,14 +1056,36 @@ export default {
             columnId: this.taskColumn.columnId,
             modelId: this.taskColumn.modelId,
           };
-          api.addContent(params).then((res) => {
+          api.addContent(params).then(async (res) => {
             if (res.code === 200) {
-              that.$message({
-                message: "任务创建成功",
-                type: "success",
-              });
-              // 不直接开始任务,需要到任务管理页面启动
               that.showTaskFrom = false;
+              const taskId = res.content;
+              try {
+                const runRes = await executeTask(taskId);
+                if (runRes && runRes.code === 200) {
+                  that.$message({
+                    message: "任务已创建并已提交后台执行,可在任务管理中查看进度与结果",
+                    type: "success",
+                  });
+                } else {
+                  const errText =
+                    (runRes && (runRes.message || runRes.content || runRes.msg)) || "未知错误";
+                  that.$message({
+                    message:
+                      "任务已创建,但自动执行提交失败:" +
+                      errText +
+                      "。请到任务管理中找到该任务后手动点击「运行」",
+                    type: "warning",
+                  });
+                }
+              } catch (e) {
+                console.error(e);
+                that.$message({
+                  message:
+                    "任务已创建,但自动执行请求失败,请到任务管理中找到该任务后手动点击「运行」",
+                  type: "warning",
+                });
+              }
             } else {
               that.$message({
                 message: "任务创建失败" + res.msg,

+ 29 - 6
src/views/WgnLast.vue

@@ -352,6 +352,7 @@
 import { ElNotification } from "element-plus";
 import * as XLSX from "xlsx";
 import api from "@/api/content";
+import { executeTask } from "@/api/rwgl";
 export default {
   name: "微功能服务",
   data() {
@@ -863,14 +864,36 @@ export default {
             columnId: this.taskColumn.columnId,
             modelId: this.taskColumn.modelId,
           };
-          api.addContent(params).then((res) => {
+          api.addContent(params).then(async (res) => {
             if (res.code === 200) {
-              that.$message({
-                message: "任务创建成功",
-                type: "success",
-              });
-              // 不直接开始任务,需要到任务管理页面启动
               that.showTaskFrom = false;
+              const taskId = res.content;
+              try {
+                const runRes = await executeTask(taskId);
+                if (runRes && runRes.code === 200) {
+                  that.$message({
+                    message: "任务已创建并已提交后台执行,可在任务管理中查看进度与结果",
+                    type: "success",
+                  });
+                } else {
+                  const errText =
+                    (runRes && (runRes.message || runRes.content || runRes.msg)) || "未知错误";
+                  that.$message({
+                    message:
+                      "任务已创建,但自动执行提交失败:" +
+                      errText +
+                      "。请到任务管理中找到该任务后手动点击「运行」",
+                    type: "warning",
+                  });
+                }
+              } catch (e) {
+                console.error(e);
+                that.$message({
+                  message:
+                    "任务已创建,但自动执行请求失败,请到任务管理中找到该任务后手动点击「运行」",
+                  type: "warning",
+                });
+              }
             } else {
               that.$message({
                 message: "任务创建失败" + res.msg,

+ 109 - 48
src/views/rwgl/Index.vue

@@ -75,17 +75,19 @@
       </div>
       <div class="lighter-container">
         <!-- <div style="padding-bottom: 10px">检索出{{ taskNum }}条任务</div> -->
-        <el-table
-          table-layout="fixed"
-          row-key="main_id"
-          :data="taskData"
-          class="table"
-          :header-cell-style="headerCellStyle"
-          :row-style="rowStyle"
-          :cell-style="cellStyle"
-          stripe
-          border
-        >
+        <div class="task-table-wrap" ref="taskTableWrap">
+          <el-table
+            table-layout="fixed"
+            row-key="main_id"
+            :data="taskData"
+            class="table"
+            :height="tableHeight"
+            :header-cell-style="headerCellStyle"
+            :row-style="rowStyle"
+            :cell-style="cellStyle"
+            stripe
+            border
+          >
           <el-table-column prop="main_c_name" label="名称" />
           <el-table-column prop="main_c_user_name" label="用户" />
           <el-table-column prop="main_c_state" label="类型">
@@ -132,15 +134,7 @@
           </el-table-column>
           <el-table-column label="操作" width="420">
             <template #default="scope">
-              <el-button
-                type="primary"
-                @click="
-                  () => {
-                    dialog = true;
-                    focusTask = scope.row;
-                  }
-                "
-              >
+              <el-button type="primary" @click="openTaskDetail(scope.row)">
                 查看详情
               </el-button>
               <el-button
@@ -180,6 +174,7 @@
             </template>
           </el-table-column>
         </el-table>
+        </div>
         <!-- <div class="between-row pagination-container">
           <div></div>
           <el-pagination
@@ -281,6 +276,12 @@
               </div>
             </template>
           </el-descriptions-item>
+          <el-descriptions-item
+            v-if="focusTask.main_c_state == 3 && taskErrorText(focusTask)"
+            label="失败详情"
+          >
+            <pre class="task-error-detail">{{ taskErrorText(focusTask) }}</pre>
+          </el-descriptions-item>
         </el-descriptions>
       </el-dialog>
     </div>
@@ -288,7 +289,7 @@
 </template>
 
 <script>
-import { getTasks, getCName, executeTask } from "@/api/rwgl";
+import { getTasks, getCName, executeTask, getTaskDetail } from "@/api/rwgl";
 
 export default {
   data() {
@@ -318,7 +319,7 @@ export default {
       dialog: false,
       page: 1,
       pageSize: 10,
-      tableHeight: 0,
+      tableHeight: 420,
     };
   },
   computed: {
@@ -386,6 +387,7 @@ export default {
       // if(this.taskStatus.length==this.focusTaskStatus.length){
       //     this.focusTaskStatus = ["all"]
       // }
+      this.$nextTick(() => this.calculateTableHeight());
     },
     getCheckedStatus() {
       if (this.focusTaskStatus.includes("all")) {
@@ -436,6 +438,7 @@ export default {
         }
         this.focusTaskType = Array.from(set);
       }
+      this.$nextTick(() => this.calculateTableHeight());
     },
     getCheckedType() {
       if (this.focusTaskType.includes("all")) {
@@ -463,6 +466,7 @@ export default {
       );
       this.taskNum = res.count;
       this.taskData = res.data;
+      this.calculateTableHeight();
     },
     timeFormatter(time) {
       if (time == null) return "";
@@ -495,16 +499,13 @@ export default {
     async runTask(taskId) {
       try {
         const res = await executeTask(taskId);
-        if (res.code === 200) {
+        if (res && (res.code === 200 || res.code === "200")) {
           this.$message({
             type: "success",
             message: res.content || "任务已提交,正在后台执行",
           });
-          // 先禁用按钮,防止重复点击
-          this.$refs.runTaskBtn.disabled = true;
-          // 刷新任务列表
+          // 列表每行各有「运行/重试」按钮,不能使用单一 ref;刷新列表即可反映最新状态
           setTimeout(() => {
-            // 执行成功后,刷新任务列表
             this.pullTaskData(this.page);
           }, 300);
         } else {
@@ -527,26 +528,65 @@ export default {
       }
       return text.substring(0, maxLength) + "…";
     },
+    /**
+     * 失败说明:优先 main_c_error_message;否则从 main_c_source_data JSON 的 _qpyzt_task_error 读取。
+     */
+    taskErrorText(row) {
+      if (!row) return "";
+      const direct =
+        row.main_c_error_message ??
+        row.c_error_message ??
+        row.main_C_error_message ??
+        "";
+      if (direct != null && String(direct).trim()) return String(direct).trim();
+      const raw = row.main_c_source_data;
+      if (!raw || typeof raw !== "string") return "";
+      try {
+        const o = JSON.parse(raw);
+        const e = o && o._qpyzt_task_error;
+        if (e != null && String(e).trim()) return String(e).trim();
+      } catch (_) {
+        /* ignore */
+      }
+      return "";
+    },
+    openTaskDetail(row) {
+      this.dialog = true;
+      this.focusTask = { ...row };
+      this.refreshTaskDetailFromServer(row.main_id);
+    },
+    async refreshTaskDetailFromServer(taskId) {
+      if (!taskId) return;
+      try {
+        const res = await getTaskDetail(taskId);
+        if (res && res.code === 200 && res.content) {
+          const c = res.content;
+          this.focusTask = {
+            ...this.focusTask,
+            main_c_error_message: c.c_error_message ?? this.focusTask.main_c_error_message,
+            main_c_source_data: c.c_source_data ?? this.focusTask.main_c_source_data,
+            main_c_comment: c.c_comment ?? this.focusTask.main_c_comment,
+          };
+        }
+      } catch (e) {
+        console.warn("加载任务详情失败", e);
+      }
+    },
     calculateTableHeight() {
-      // 计算表格高度:窗口高度 - header高度 - footer高度 - 页面padding - 其他元素高度
-      const windowHeight = window.innerHeight;
-      const headerHeight = 70; // Header组件高度
-      const footerHeight = 50; // Footer组件高度
-      const pagePadding = 40 * 2; // 页面上下padding
-      const filterAreaHeight = 120; // 筛选条件区域高度
-      const taskCountHeight = 30; // 任务数量文字高度
-      const paginationHeight = 50; // 分页区域高度
-      const containerMargin = 15 * 2; // 容器上下margin
-
-      this.tableHeight =
-        windowHeight -
-        headerHeight -
-        footerHeight -
-        pagePadding -
-        filterAreaHeight -
-        taskCountHeight -
-        paginationHeight -
-        containerMargin;
+      this.$nextTick(() => {
+        const winH = window.innerHeight;
+        const wrap = this.$refs.taskTableWrap;
+        let top = 0;
+        if (wrap && typeof wrap.getBoundingClientRect === "function") {
+          top = wrap.getBoundingClientRect().top;
+        } else {
+          top = 220;
+        }
+        // 分页条 + lighter-container 下内边距 + 与视口底部的安全间距
+        const bottomReserve = 88;
+        let h = winH - top - bottomReserve;
+        this.tableHeight = Math.max(240, Math.floor(h));
+      });
     },
   },
 };
@@ -594,6 +634,21 @@ export default {
 :deep(.el-table__body) {
   background-color: rgba(0, 0, 0, 0.05);
 }
+
+.task-error-detail {
+  margin: 0;
+  padding: 10px 12px;
+  max-height: 360px;
+  overflow: auto;
+  white-space: pre-wrap;
+  word-break: break-word;
+  font-size: 12px;
+  line-height: 1.45;
+  color: #ffb4b4;
+  background: rgba(0, 0, 0, 0.35);
+  border-radius: 6px;
+  border: 1px solid rgba(255, 77, 79, 0.35);
+}
 :deep(.el-table--striped .el-table__body tr.el-table__row--striped td.el-table__cell) {
   background-color: rgba(255, 255, 255, 0.02);
 }
@@ -653,8 +708,9 @@ link {
   margin-left: 0;
   padding-left: 90px;
   padding-right: 90px;
-  // min-height: 80vh
-  height: 100vh;
+  /* 用 min-height 避免整页被固定为 100vh 时把分页等内容裁切到视口外 */
+  min-height: 100vh;
+  box-sizing: border-box;
 }
 
 .darkblue-background {
@@ -688,6 +744,11 @@ body {
   border-radius: 10px;
 }
 
+.task-table-wrap {
+  width: 100%;
+  min-height: 0;
+}
+
 .task-count {
   margin-bottom: 10px;
   color: #fff;

+ 5 - 1
src/views/wgn/Example.vue

@@ -71,7 +71,11 @@ export default {
 
       // 定位
       viewer.camera.setView({
-        destination: SkyScenery.Cartesian3.fromDegrees(121.1, 31, 30000.0), // 设置位置
+        destination: SkyScenery.Cartesian3.fromDegrees(
+          121.10708551540051,
+          31.14836073612525,
+          30000.0
+        ), // 默认场景中心
         orientation: {
           heading: SkyScenery.Math.toRadians(0.0), // 方向
           pitch: SkyScenery.Math.toRadians(-90.0), // 倾斜角度