<template>
  <el-affix :offset="0">
    <div style="height: 44px; width: 100vw; background: #00002a"></div>
  </el-affix>
  <el-affix :offset="44">
    <div class="application-overview">
      <div class="wgn-body">
        <aside class="wgn-side-nav" aria-label="微功能锚点导航">
          <button
            v-for="block in groupedWgnSections"
            :key="block.cat.value"
            type="button"
            class="wgn-anchor"
            :class="{ active: activeAnchorId === block.cat.value }"
            @click="scrollToAnchor(block.cat.value)"
          >
            {{ block.cat.label }}
          </button>
        </aside>
        <!-- 右侧主内容区 -->
        <div class="main-content">
          <div style="display: flex; justify-content: space-between; padding-right: 30px">
            <!-- 搜索栏 -->
            <div class="search-bar">
              <el-input
                placeholder="输入搜索关键词"
                v-model="searchStr"
                class="search-input"
                clearable
                @clear="searchServerList"
                @keyup.enter="searchServerList"
              >
              </el-input>
              <el-button type="primary" class="search-btn" @click="searchServerList"
                >搜索</el-button
              >
              <!-- <span>搜索到{{total}}条微功能服务</span> -->
            </div>

            <div>
              <el-button
                v-if="$getUserType() != 1"
                type="primary"
                class="search-btn"
                @click="createTask"
                >大批量数据处理任务</el-button
              >
            </div>
          </div>

          <!-- 应用卡片：按一级场景分组，锚点滚动 -->
          <div class="wgn-content" ref="wgnScrollRoot">
            <template v-if="groupedWgnSections.length">
              <section
                v-for="block in groupedWgnSections"
                :key="block.cat.value"
                :id="sectionDomId(block.cat.value)"
                class="wgn-anchor-section"
              >
                <h2 class="wgn-anchor-section-title">{{ block.cat.label }}</h2>
                <div class="applications-grid">
                  <div
                    class="application-card"
                    v-for="(item, index) in block.items"
                    :key="(item.c_scene_name || '') + '-' + index"
                  >
                    <div class="card-image">
                      <img :src="curUrl + item.c_picture" :alt="item.title" />
                    </div>
                    <div class="card-content">
                      <div class="wgn-header">
                        <h3 class="wgn-name">{{ item.title }}</h3>
                      </div>
                      <el-tooltip placement="top" effect="dark">
                        <template #content>
                          <div style="max-width: 500px">{{ item.content }}</div>
                        </template>
                        <p class="wgn-description">{{ item.content }}</p>
                      </el-tooltip>
                      <div class="wgn-footer">
                        <el-button
                          type="primary"
                          size="small"
                          class="visit-button"
                          @click="handleOnlineDemo(item)"
                          >在线体验</el-button
                        >
                        <el-button
                          type="primary"
                          size="small"
                          class="visit-button"
                          @click="handleApply(item)"
                          >申请使用</el-button
                        >
                      </div>
                    </div>
                  </div>
                </div>
              </section>
            </template>
            <el-empty v-else description="暂无数据" />
          </div>

          <!-- 申请使用微功能服务的表单弹窗 -->
          <el-dialog
            title="申请使用微功能服务"
            v-model="showFrom"
            width="60%"
            :close-on-click-modal="false"
            :close-on-press-escape="false"
            :show-close="true"
          >
            <el-form :model="column.from" :rules="rules" ref="formRef" label-width="120px">
              <el-form-item label="应用名称" prop="c_application_name">
                <el-input v-model="column.from.c_application_name" placeholder="请输入应用名称" />
              </el-form-item>
              <el-form-item label="接口路径" prop="c_interface_path">
                <el-input
                  v-model="column.from.c_interface_path"
                  disabled
                  placeholder="请输入接口路径"
                />
              </el-form-item>
              <el-form-item label="联系电话" prop="c_phone">
                <el-input v-model="column.from.c_phone" placeholder="请输入联系电话" />
              </el-form-item>
              <el-form-item label="单位名称" prop="c_unit_name">
                <el-input v-model="column.from.c_unit_name" placeholder="请输入单位名称" />
              </el-form-item>
              <el-form-item label="部门名称" prop="c_department">
                <el-input v-model="column.from.c_department" placeholder="请输入部门名称" />
              </el-form-item>
              <!-- 项目负责人 -->
              <el-form-item label="项目负责人" prop="c_business_leader">
                <el-input v-model="column.from.c_business_leader" placeholder="请输入项目负责人" />
              </el-form-item>
              <!-- 申请使用微功能服务的详细信息 -->
              <el-form-item label="申请使用微功能服务的详细信息" prop="content">
                <el-input
                  type="textarea"
                  v-model="column.from.content"
                  placeholder="请输入申请使用微功能服务的详细信息"
                />
              </el-form-item>
            </el-form>
            <template #footer>
              <div class="dialog-footer">
                <el-button @click="showFrom = false">取消</el-button>
                <el-button type="primary" @click="handleApplySubmit">确定</el-button>
              </div>
            </template>
          </el-dialog>
          <!-- 创建任务的表单弹窗 -->
          <el-dialog
            title="创建任务"
            v-model="showTaskFrom"
            width="60%"
            :close-on-click-modal="false"
            :close-on-press-escape="false"
            :show-close="true"
          >
            <el-form
              :model="taskColumn.from"
              :rules="taskColumn.rules"
              ref="taskFormRef"
              label-width="120px"
            >
              <el-form-item label="任务名称" prop="c_name">
                <el-input v-model="taskColumn.from.c_name" placeholder="请输入任务名称" clearable>
                  <template #prepend>{{
                    $moment(new Date()).format("YYMMDD_HHmm") +
                    "_" +
                    (taskColumn.from.c_type
                      ? $getDmsTypes("yzt_task_type", taskColumn.from.c_type) + "_"
                      : "")
                  }}</template>
                </el-input>
              </el-form-item>
              <el-form-item label="任务备注" prop="c_comment">
                <el-input
                  type="textarea"
                  clearable
                  v-model="taskColumn.from.c_comment"
                  placeholder="请输入任务备注"
                />
              </el-form-item>
              <!-- 下拉框选择任务类型 -->
              <el-form-item label="任务类型" prop="c_type">
                <el-select
                  v-model="taskColumn.from.c_type"
                  @change="changeTaskType"
                  placeholder="请选择任务类型"
                >
                  <el-option
                    v-for="(name, index) in $store.state.DmsTypesMap['yzt_task_type']"
                    :key="index"
                    :label="name"
                    :value="index"
                  />
                </el-select>
              </el-form-item>
              <!-- 渲染任务描述:仅查看 -->
              <el-form-item
                label="任务描述"
                prop="apiDescription"
                v-if="taskColumn.apiFrom.apiDescription"
              >
                <el-input type="textarea" disabled v-model="taskColumn.apiFrom.apiDescription" />
              </el-form-item>
              <!-- 任务文件是必须的 -->
              <el-form-item label="任务文件" prop="c_source_file">
                <el-input disabled v-model="taskColumn.from.c_source_file" v-show="false" />
                <el-upload class="avatar-uploader" :http-request="handleTCUpload" :limit="1">
                  <el-button size="small" type="primary">点击上传</el-button>
                </el-upload>
              </el-form-item>
              <el-divider />
              <!-- 其他参数数据 -->
              <el-form-item label="其他参数数据" prop="c_source_data" v-show="false">
                <el-input
                  type="textarea"
                  clearable
                  v-model="taskColumn.from.c_source_data"
                  placeholder="请输入其他参数数据"
                />
              </el-form-item>
              <div
                v-if="
                  taskColumn.apiFrom.apiParameterRules &&
                  includesKey([
                    'lonKey',
                    'latKey',
                    'outFileType',
                    'inPrj',
                    'outPrj',
                    'compressionRatio',
                    'keyType',
                    'geocodeColumnKeyword',
                  ])
                "
              >
                <template
                  v-for="(rulesItem, index) in taskColumn.apiFrom.apiParameterRules"
                  :key="index"
                >
                  <el-form-item
                    v-if="
                      [
                        'lonKey',
                        'latKey',
                        'outFileType',
                        'inPrj',
                        'outPrj',
                        'compressionRatio',
                        'keyType',
                        'geocodeColumnKeyword',
                      ].includes(rulesItem)
                    "
                    :label="
                      rulesItem === 'keyType'
                        ? '类型字段'
                        : rulesItem === 'geocodeColumnKeyword'
                          ? '数据列名'
                          : rulesItem
                    "
                  >
                    <el-input
                      v-if="['lonKey', 'latKey'].includes(rulesItem)"
                      v-model="taskColumn.from.c_source_data[rulesItem]"
                      placeholder="当文件为xlsx时必填"
                    />
                    <el-select
                      v-if="
                        ['outFileType', 'inPrj', 'outPrj', 'compressionRatio'].includes(rulesItem)
                      "
                      v-model="taskColumn.from.c_source_data[rulesItem]"
                      :placeholder="'请选择' + rulesItem"
                    >
                      <el-option
                        v-for="item in taskColumn.selectOptions[rulesItem]"
                        :key="'wgn-task-selectOptions-form-' + item.label"
                        :label="item.label"
                        :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>
              <!-- 任务状态默认为初始化 -->
              <el-form-item label="任务状态" prop="c_state" v-show="false">
                <el-select disabled v-model="taskColumn.from.c_state" placeholder="请选择任务状态">
                  <el-option
                    v-for="(name, index) in $store.state.DmsTypesMap['task_status']"
                    :key="'wgn-task-form-' + index"
                    :label="name"
                    :value="index"
                  />
                </el-select>
              </el-form-item>
              <!-- 任务负责人默认为当前登录用户 -->
              <el-form-item label="用户id" prop="c_user_id" v-show="false">
                <el-input disabled v-model="taskColumn.from.c_user_id" />
              </el-form-item>
              <!-- 任务负责人默认为当前登录用户 -->
              <el-form-item label="用户名" prop="c_user_name" v-show="false">
                <el-input disabled v-model="taskColumn.from.c_user_name" />
              </el-form-item>
            </el-form>
            <template #footer>
              <div class="dialog-footer">
                <el-button @click="showTaskFrom = false">取消</el-button>
                <el-button type="primary" @click="handleTaskSubmit">确定</el-button>
              </div>
            </template>
          </el-dialog>
        </div>
      </div>
    </div>
  </el-affix>
</template>

<script>
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",
  data() {
    return {
      total: 0,
      curUrl: systemConfig.dmsDataProxy,
      // 搜索关键词
      searchStr: "",
      // 微功能服务列表
      dmsServerList: [],
      sceneMenuTree: WGN_SCENE_LIST,
      activeAnchorId: null,
      anchorScrollLock: false,
      // 是否显示申请使用微功能服务的表单弹窗
      showFrom: false,
      // 是否显示创建任务的表单弹窗
      showTaskFrom: false,
      // 申请使用微功能服务的表单验证规则
      rules: {
        c_application_name: [{ required: true, message: "请输入应用名称", trigger: "blur" }],
        c_interface_path: [{ required: true, message: "请输入接口路径", trigger: "blur" }],
        c_business_leader: [{ required: true, message: "请输入项目负责人", trigger: "blur" }],
        // 添加手机号正则验证
        c_phone: [
          { required: true, message: "请输入联系电话", trigger: "blur" },
          {
            validator: (rule, value, callback) => {
              // 手机号正则验证
              let phoneRegex = /^1[3456789]\d{9}$/;
              if (!phoneRegex.test(value)) {
                callback(new Error("请输入正确的手机号"));
              } else {
                callback();
              }
            },
            trigger: "blur",
          },
        ],
        c_unit_name: [{ required: true, message: "请输入单位名称", trigger: "blur" }],
        c_department: [{ required: true, message: "请输入部门名称", trigger: "blur" }],
      },
      // 申请使用微功能服务的表单数据
      column: {
        columnId: systemConfig.columnIds[4],
        modelId: systemConfig.modelIds[2],
        from: {
          // 备注：申请使用微功能服务的详细信息，非必填
          content: "",
          // 项目负责人，必填
          c_business_leader: "",
          // 应用名称,必填
          c_application_name: "",
          // 接口路径，必填
          c_interface_path: "",
          // 联系电话，必填
          c_phone: "",
          // 单位名称，必填
          c_unit_name: "",
          // 部门名称，必填
          c_department: "",
        },
      },
      // 创建任务的表单数据
      taskColumn: {
        columnId: systemConfig.columnIds[6],
        modelId: systemConfig.modelIds[3],
        rules: {
          c_name: [{ required: true, message: "请输入任务名称", trigger: "blur" }],
          c_comment: [{ required: true, message: "请输入任务备注", trigger: "blur" }],
          c_type: [{ required: true, message: "请选择任务类型", trigger: "change" }],
          c_source_file: [{ required: true, message: "请上传任务文件", trigger: "change" }],
        },
        from: {
          title: "",
          content: "",
          // 任务名称，必填（默认应该是：日期-任务类型-用户自定义名称）,这里保存的就是用户自定义名称，提交的时候会自动补充日期-任务类型-
          c_name: "",
          // 备注：任务的详细信息，必填
          c_comment: "",
          // 任务类型，必填
          c_type: "",
          // 任务创建人用户名，必填
          c_user_name: "",
          // 任务创建人，必填
          c_user_id: "",
          // 任务文件，必填
          c_source_file: "",
          // 任务文件名,上传文件时自动带入
          c_source_file_name: "",
          // 其他参数数据
          c_source_data: {},
          // 申请状态，默认初始化【0:初始化；1:运行中；2:成功完成；3:异常中断】
          c_state: "0",
        },
        // 切换任务类型时，渲染的接口详情
        apiFrom: {
          // 任务描述，仅查看
          apiDescription: "",
          // 接口参数，仅查看
          apiParameterRules: [],
          // 接口路径
          apiUrl: "",
        },
        // 其他参数
        selectOptions: {
          keyType: [
            { value: "地址", label: "地址" },
            { value: "村居", label: "村居" },
            { value: "街镇", label: "街镇" },
            { value: "区县", label: "区县" },
            // { value: "要素ID", label: "要素ID" },
          ],
          geocodeColumnKeyword: [],
          // 单位
          unit: [
            {
              value: "METER",
              label: "米",
            },
            {
              value: "KILOMETER",
              label: "千米",
            },
          ],
          inPrj: [
            {
              value: "WGS84",
              label: "WGS84",
            },
            {
              value: "SH2000",
              label: "SH2000",
            },
            {
              value: "UTM",
              label: "UTM",
            },
            {
              value: "WEB_Mercator",
              label: "WEB_Mercator",
            },
          ],
          compressionRatio: [
            {
              value: "100%",
              label: "100%",
            },
            {
              value: "80%",
              label: "80%",
            },
            {
              value: "50%",
              label: "50%",
            },
            {
              value: "25%",
              label: "25%",
            },
          ],
          outPrj: [
            {
              value: "WGS84",
              label: "WGS84",
            },
            {
              value: "SH2000",
              label: "SH2000",
            },
            {
              value: "UTM",
              label: "UTM",
            },
            {
              value: "WEB_Mercator",
              label: "WEB_Mercator",
            },
          ],
          outFileType: [
            {
              value: "geoJson",
              label: "geoJson",
            },
            {
              value: "shp",
              label: "shp",
            },
            {
              value: "csv",
              label: "csv",
            },
            {
              value: "xlsx",
              label: "xlsx",
            },
          ],
        },
      },
      // 搜索微功能服务的防抖定时器
      searchTimeout: null,
      taskUploadRawFile: null,
    };
  },
  computed: {
    /** 按一级场景分组，仅保留当前搜索结果下仍有卡片的分类 */
    groupedWgnSections() {
      const list = this.dmsServerList || [];
      return this.sceneMenuTree
        .map((cat) => {
          const codes = new Set((cat.children || []).map((ch) => ch.value));
          const items = list.filter((i) => codes.has(i.c_scene_name));
          return { cat, items };
        })
        .filter((g) => g.items.length > 0);
    },
    isTaskGeocodeZipForColumnInput() {
      const n =
        (this.taskUploadRawFile && this.taskUploadRawFile.name) ||
        this.taskColumn.from.c_source_file_name ||
        "";
      return String(n).toLowerCase().endsWith(".zip");
    },
  },
  watch: {
    groupedWgnSections: {
      handler(blocks) {
        this.$nextTick(() => {
          if (blocks.length) {
            if (!blocks.some((b) => b.cat.value === this.activeAnchorId)) {
              this.activeAnchorId = blocks[0].cat.value;
            }
          } else {
            this.activeAnchorId = null;
          }
          this.bindAnchorSpy();
        });
      },
    },
  },
  mounted() {
    // 任务负责人默认为当前登录用户
    this.taskColumn.from.c_user_id = this.$store.state.userInfo.id;
    // 任务创建人用户名默认为当前登录用户用户名
    this.taskColumn.from.c_user_name = this.$store.state.userInfo.username;
    this.searchServerList();
  },
  beforeUnmount() {
    this.unbindAnchorSpy();
  },
  methods: {
    sectionDomId(catValue) {
      return "wgn-section-" + String(catValue).replace(/\./g, "-");
    },
    scrollToAnchor(catValue) {
      const root = this.$refs.wgnScrollRoot;
      const el = document.getElementById(this.sectionDomId(catValue));
      if (!root || !el) return;
      this.anchorScrollLock = true;
      this.activeAnchorId = catValue;
      const rootRect = root.getBoundingClientRect();
      const elRect = el.getBoundingClientRect();
      const nextTop = elRect.top - rootRect.top + root.scrollTop - 8;
      root.scrollTo({ top: Math.max(0, nextTop), behavior: "smooth" });
      window.setTimeout(() => {
        this.anchorScrollLock = false;
        this.updateActiveAnchorFromScroll();
      }, 480);
    },
    unbindAnchorSpy() {
      const root = this.$refs.wgnScrollRoot;
      if (root && this._wgnOnScroll) {
        root.removeEventListener("scroll", this._wgnOnScroll);
      }
      this._wgnOnScroll = null;
    },
    bindAnchorSpy() {
      this.unbindAnchorSpy();
      const root = this.$refs.wgnScrollRoot;
      if (!root) return;
      this._wgnOnScroll = () => this.updateActiveAnchorFromScroll();
      root.addEventListener("scroll", this._wgnOnScroll, { passive: true });
      this.updateActiveAnchorFromScroll();
    },
    updateActiveAnchorFromScroll() {
      if (this.anchorScrollLock) return;
      const root = this.$refs.wgnScrollRoot;
      const blocks = this.groupedWgnSections;
      if (!root || !blocks.length) return;
      const rootRect = root.getBoundingClientRect();
      const threshold = 28;
      let current = blocks[0].cat.value;
      for (const { cat } of blocks) {
        const el = document.getElementById(this.sectionDomId(cat.value));
        if (!el) continue;
        const top = el.getBoundingClientRect().top - rootRect.top;
        if (top <= threshold) {
          current = cat.value;
        }
      }
      this.activeAnchorId = current;
    },
    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) => {
        if (this.taskColumn.apiFrom.apiParameterRules.includes(key)) {
          hasKey = true;
        }
      });
      return hasKey;
    },
    handleTCUpload(emit) {
      this.taskUploadRawFile = emit.file;
      // 上传任务文件
      let formData = new FormData();
      formData.append("file", emit.file);
      formData.append("columnId", this.taskColumn.columnId);
      formData.append("type", 0);
      formData.append("paraName", emit.file.name);
      api
        .uploadFile(formData)
        .then((res) => {
          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,
              offset: 80,
            });
          }
        })
        .catch((e) => {
          this.taskUploadRawFile = null;
          ElNotification.error({
            title: "失败",
            message: "上传任务文件失败" + e,
            offset: 80,
          });
        });
    },
    changeTaskType() {
      if (this.taskColumn.from.c_type) {
        // 先把任务类型每个字符中间都添加一个句号
        let serviceId = this.taskColumn.from.c_type.split("").join(".");
        // 根据任务类型获取任务参数
        api
          .getSimpleContentList({
            columnId: systemConfig.columnIds[5],
            states: 0,
            pageSize: 1,
            page: 0,
            search: JSON.stringify([
              {
                field: "c_scene_name",
                searchType: 1,
                content: { value: serviceId },
              },
            ]),
          })
          .then((res) => {
            if (res.code === 200) {
              let apiInfo = res.content.data[0];
              // 接口描述
              this.taskColumn.apiFrom.apiDescription = apiInfo.content;
              // 接口参数规则
              this.taskColumn.apiFrom.apiParameterRules = JSON.parse(
                apiInfo.c_input_parameter_rules
              );
              // 接口路径
              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 = [];
              this.taskColumn.apiFrom.apiUrl = "";
              ElNotification.error({
                title: "失败",
                message: "获取任务参数失败" + res.msg,
                offset: 80,
              });
            }
          })
          .catch((e) => {
            this.taskColumn.apiFrom.apiDescription = "";
            this.taskColumn.apiFrom.apiParameterRules = [];
            ElNotification.error({
              title: "失败",
              message: "获取任务参数失败" + e,
              offset: 80,
            });
          });
        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,
        });
      }
    },
    // 申请使用微功能服务
    handleApply(item) {
      // 申请使用微功能服务时，先从localStorage中获取数据，填充到表单中
      let applyInfo = localStorage.getItem("wgnApplyInfo");
      if (applyInfo) {
        this.column.from = JSON.parse(applyInfo);
      }
      // 先打开弹窗，显示from表单，用户输入表单数据并提交
      this.column.from.c_interface_path = item.c_url;
      this.showFrom = true;
    },
    // 在线演示微功能服务
    handleOnlineDemo(item) {
      let routerPath = {};
      // console.log("000000000"+item);

      // 1. 解析目标路由（支持传参、命名路由等）
      if (item.c_scene_name == "view") {
        routerPath = {
          // path: item.c_url,
          path: "sksj",
        };
        // this.$router.push(routerPath);
        window.open(routerPath.path, "_blank");
      } else {
        routerPath = {
          path: "/wgnSingle", // 微功能
          query: { sceneId: item.c_scene_name },
        };
        const routeData = this.$router.resolve(routerPath);
        // 2. 打开新窗口（_blank 表示新窗口）
        window.open(routeData.href, "_blank");
      }
    },
    // 搜索微功能服务
    searchServerList() {
      // 添加防抖功能，避免频繁搜索导致性能问题
      if (this.searchTimeout != null) {
        clearTimeout(this.searchTimeout);
      }
      this.searchTimeout = setTimeout(() => {
        let requestParams = {
          columnId: systemConfig.columnIds[5],
          states: 0,
          pageSize: 999,
          page: 0,
        };
        if (this.searchStr) {
          requestParams.search = JSON.stringify([
            {
              field: "title",
              searchType: 2,
              content: { value: "%" + this.searchStr + "%" },
            },
          ]);
        }
        // 获取微功能服务列表
        api
          .getSimpleContentList(requestParams)
          .then((res) => {
            if (res.code === 200) {
              // 过滤掉自定义工具微功能服务1.5.8.1
              // this.dmsServerList = res.content.data.filter(
              //   (item) => item.c_scene_name != "1.5.8.1"
              // );
              // 转换时间格式
              this.dmsServerList = res.content.data.map((item) => ({
                ...item,
                createTime: moment(item.create_time).format("YYYY-MM-DD HH:mm:ss"),
              }));
              this.total = this.dmsServerList.length;
              if (this.total === 0) {
                this.$message({
                  message: "暂无数据",
                  type: "info",
                });
              }
              // ElNotification.success({
              //   title: "成功",
              //   message: "搜索到" + this.dmsServerList.length + "条微功能服务",
              //   offset: 80,
              // });
            } else {
              this.dmsServerList = [];
              this.total = 0;
              this.$message({
                message: "暂无数据",
                type: "info",
              });
              // ElNotification.error({
              //   title: "失败",
              //   message: "搜索到0条微功能服务",
              //   offset: 80,
              // });
            }
          })
          .catch((e) => {
            this.total = 0;
            // ElNotification.error({
            //   title: "失败",
            //   message: "搜索微功能服务失败" + e,
            //   offset: 80,
            // });
          });
      }, 0);
    },
    // 提交申请使用微功能服务表单
    handleApplySubmit() {
      let that = this;
      this.$refs.formRef.validate((valid) => {
        if (valid) {
          // 提交时把信息添加到localStorage中,下次打开弹窗时，从localStorage中获取数据，填充到表单中
          localStorage.setItem("wgnApplyInfo", JSON.stringify(that.column.from));
          // 标题：电话+时间
          that.column.from.title =
            that.column.from.c_unit_name +
            "_" +
            that.column.from.c_business_leader +
            "_" +
            that.column.from.c_phone;
          // 备注：申请使用微功能服务的详细信息
          let params = {
            content: JSON.stringify(that.column.from),
            columnId: that.column.columnId,
            modelId: that.column.modelId,
          };
          api.addContent(params).then((res) => {
            if (res.code === 200) {
              // 确认申请使用
              that.$message({
                message: "申请成功，请等待审核通过后工作人员专人联系，请保持电话通畅！",
                type: "success",
              });
              that.showFrom = false;
            } else {
              that.$message({
                message: "申请使用" + item.title + "失败" + res.msg,
                type: "error",
              });
            }
          });
        } else {
          // 表单验证失败，提示用户填写完整信息
          that.$message({
            message: "请填写完整信息",
            type: "warning",
          });
        }
      });
    },
    // 创建任务
    createTask() {
      // 打开弹窗，显示任务创建表单
      this.showTaskFrom = true;
    },
    // 提交任务创建表单
    handleTaskSubmit() {
      let that = this;
      // 表单验证
      this.$refs.taskFormRef.validate((valid) => {
        if (valid) {
          this.syncKeyTypeForGeocodingTask();
          let content = this.taskColumn.from;
          content.title =
            this.$moment(new Date()).format("YYMMDD_HHmm") +
            "_" +
            (this.taskColumn.from.c_type
              ? this.$getDmsTypes("yzt_task_type", this.taskColumn.from.c_type) + "_"
              : "") +
            content.c_name;
          content.content = this.taskColumn.apiFrom.apiDescription
            ? this.taskColumn.apiFrom.apiDescription
            : "";
          content.c_name = content.title;
          // 提交任务创建表单
          let params = {
            content: JSON.stringify(content),
            columnId: this.taskColumn.columnId,
            modelId: this.taskColumn.modelId,
          };
          api.addContent(params).then(async (res) => {
            if (res.code === 200) {
              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,
                type: "error",
              });
            }
          });
        } else {
          // 表单验证失败，提示用户填写完整信息
          that.$message({
            message: "请填写完整信息",
            type: "warning",
          });
        }
      });
    },
  },
};
</script>

<style lang="less" scoped>
.application-overview {
  display: fixed !important;
  width: 100%;
  height: 100vh;
  // background-color: #08224a;
  background-color: #00002a;
  color: #ffffff;
}

.wgn-body {
  display: flex;
  align-items: flex-start;
  width: 100%;
  box-sizing: border-box;
  min-height: calc(100vh - 44px);
}

.wgn-side-nav {
  flex: 0 0 200px;
  padding: 12px 12px 24px 24px;
  background: transparent;
  border: none;
  position: sticky;
  top: 52px;
  align-self: flex-start;
  max-height: calc(100vh - 56px);
  overflow-y: auto;
}

.wgn-anchor {
  display: block;
  width: 100%;
  padding: 10px 4px 10px 0;
  margin: 0 0 2px;
  border: none;
  background: transparent;
  color: rgba(255, 255, 255, 0.85);
  font-size: 16px;
  text-align: left;
  cursor: pointer;
  border-radius: 4px;
}

.wgn-anchor:hover {
  color: #ffffff;
}

.wgn-anchor.active {
  color: #69b1ff;
  font-weight: 600;
}

.wgn-anchor-section {
  scroll-margin-top: 12px;
}

.wgn-anchor-section + .wgn-anchor-section {
  margin-top: 36px;
}

.wgn-anchor-section-title {
  margin: 0 0 18px;
  font-size: 20px;
  font-weight: 600;
  color: #ffffff;
  letter-spacing: 0.02em;
}

/* 右侧主内容区 */
.main-content {
  flex: 1;
  min-width: 0;
  padding: 20px 40px 20px 8px;
  overflow-y: auto;
}

/* 搜索栏样式 */
.search-bar {
  margin-bottom: 20px;

  .search-input {
    width: 400px;

    // ::v-deep .el-input__inner {
    //   background-color: rgba(255, 255, 255, 0.1);
    //   border: 1px solid rgba(255, 255, 255, 0.2);
    //   color: #ffffff;

    //   &::placeholder {
    //     color: rgba(255, 255, 255, 0.6);
    //   }
    // }

    // ::v-deep .el-input__prepend {
    //   background-color: rgba(255, 255, 255, 0.1);
    //   border-color: rgba(255, 255, 255, 0.2);
    //   color: rgba(255, 255, 255, 0.8);
    // }
  }
  .search-btn {
    margin-left: 10px;
  }
}

/* 过滤标签样式 */
.filter-tabs {
  margin-bottom: 30px;

  :deep(.el-radio-group) {
    .el-radio-button {
      background-color: rgba(255, 255, 255, 0.1);
      border-color: rgba(255, 255, 255, 0.2);
      color: rgba(255, 255, 255, 0.8);

      &:first-child .el-radio-button__inner {
        border-left-color: rgba(255, 255, 255, 0.2);
      }

      .el-radio-button__inner {
        background-color: transparent;
        border-color: rgba(255, 255, 255, 0.2);
        color: rgba(255, 255, 255, 0.8);
      }

      &.is-active {
        background-color: #1890ff;

        .el-radio-button__inner {
          background-color: #1890ff;
          border-color: #1890ff;
          color: #ffffff;
        }
      }
    }
  }
}
.wgn-content {
  // height: 640px;
  height: calc(100vh - 160px);
  overflow: auto;
  padding-right: 20px;
}

/* 应用卡片网格样式 */
.applications-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 25px;
}

/* 应用卡片样式 */
.application-card {
  background-color: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 8px;
  overflow: hidden;
  transition: all 0.3s ease;

  &:hover {
    transform: translateY(-5px);
    box-shadow: 0 10px 30px rgba(24, 144, 255, 0.2);
    border-color: rgba(24, 144, 255, 0.4);
  }

  .card-image {
    width: 100%;
    height: 160px;
    overflow: hidden;
    position: relative;

    img {
      width: 100%;
      height: 100%;
      object-fit: cover;
      transition: transform 0.3s ease;
    }

    // &:hover img {
    //   transform: scale(1.05);
    // }
  }

  .card-content {
    padding: 15px;
  }

  .wgn-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 10px;

    .wgn-name {
      font-size: 16px;
      font-weight: bold;
      margin: 0;
      color: #ffffff;
    }
    .wgn-level {
      font-size: 12px;
      color: rgba(255, 255, 255, 0.6);
      background-color: rgba(27, 117, 236, 0.8);
      padding: 2px 6px;
      border-radius: 4px;
    }

    .wgn-version {
      font-size: 12px;
      color: rgba(255, 255, 255, 0.6);
      background-color: rgba(255, 255, 255, 0.1);
      padding: 2px 6px;
      border-radius: 4px;
    }
  }

  .wgn-tags {
    margin-bottom: 10px;

    .el-tag {
      margin-right: 5px;
      margin-bottom: 5px;
    }
  }

  .wgn-description {
    font-size: 13px;
    line-height: 1.6;
    height: 40px;
    color: rgba(255, 255, 255, 0.7);
    margin-bottom: 15px;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
  }

  .wgn-footer {
    display: flex;
    justify-content: space-between;
    align-items: center;

    .wgn-date {
      font-size: 12px;
      color: rgba(255, 255, 255, 0.5);
    }

    .visit-button {
      padding: 4px 12px;
      font-size: 12px;
      border-radius: 4px;
    }
  }
}

/* 加载更多按钮 */
.load-more {
  text-align: center;
  margin-bottom: 30px;

  .el-button {
    padding: 10px 30px;
    background-color: rgba(24, 144, 255, 0.2);
    border-color: rgba(24, 144, 255, 0.4);
    color: #1890ff;

    &:hover {
      background-color: rgba(24, 144, 255, 0.3);
      border-color: rgba(24, 144, 255, 0.6);
    }
  }
}

/* 响应式设计 */
@media (max-width: 1200px) {
  .applications-grid {
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    gap: 20px;
  }
}

@media (max-width: 992px) {
  .sidebar {
    width: 180px;

    .sidebar-menu .menu-item {
      padding: 12px 15px;

      i {
        font-size: 16px;
      }
    }
  }

  .main-content {
    padding: 15px 20px;
  }

  .search-input {
    width: 300px !important;
  }

  .applications-grid {
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 15px;
  }
}
</style>
