jianggs 4 недель назад
Родитель
Сommit
46b7199423
22 измененных файлов с 3797 добавлено и 2007 удалено
  1. 4 4
      web/.env.development
  2. 20 1075
      web/src/views/@xdjf/intelligent-due-diligence/views/home/index.vue
  3. 1089 0
      web/src/views/@xdjf/intelligent-due-diligence/views/home/indexDefault.vue
  4. 38 11
      web/src/views/aiTagging/externalPage/components/IntelligentRecommend.vue
  5. 106 9
      web/src/views/aiTagging/externalPage/components/ManualTagging.vue
  6. 187 123
      web/src/views/aiTagging/externalPage/index.vue
  7. 131 75
      web/src/views/aiTagging/taggingDetail/index.vue
  8. 31 1
      web/src/views/aiTagging/taggingLogs/index.vue
  9. 99 33
      web/src/views/aiTagging/taggingResults/components/Detail.vue
  10. 101 42
      web/src/views/aiTagging/taggingResults/components/Overview.vue
  11. 37 15
      web/src/views/aiTagging/taggingResults/index.vue
  12. 58 24
      web/src/views/aiTagging/taggingSystemManage/TagSystemEdit.vue
  13. 272 68
      web/src/views/aiTagging/taggingSystemManage/components/TagConfig.vue
  14. 54 13
      web/src/views/aiTagging/taggingSystemManage/components/TagDetail.vue
  15. 381 84
      web/src/views/aiTagging/taggingSystemManage/components/TagDetailEdit.vue
  16. 440 0
      web/src/views/aiTagging/taggingSystemManage/components/TagDetailEditRegexDrawer.vue
  17. 84 33
      web/src/views/aiTagging/taggingSystemManage/components/TagDetailView.vue
  18. 142 33
      web/src/views/aiTagging/taggingSystemManage/components/TagTest.vue
  19. 336 301
      web/src/views/aiTagging/taggingSystemManage/components/TagTree.vue
  20. 120 17
      web/src/views/aiTagging/taggingSystemManage/components/TreeSelect.vue
  21. 33 30
      web/src/views/aiTagging/taggingSystemManage/index.vue
  22. 34 16
      web/vite.config.js

+ 4 - 4
web/.env.development

@@ -1,15 +1,15 @@
 # 开发环境服务地址/网关地址
 # VITE_APP_BASE_API = http://110.16.193.170:20011
-VITE_APP_BASE_API = http://10.192.72.13:9195
+VITE_APP_BASE_API=http://10.192.72.13:9195
 # VITE_APP_BASE_API = http://10.192.72.13:22227
 
 # yusp-uaa yusp-oca服务相关接口走本地mock服务 支持配置多个地址,分隔
 # VITE_APP_BASE_AUTH = http://110.16.193.170:20011
-VITE_APP_BASE_AUTH = http://10.192.72.13:9195
+VITE_APP_BASE_AUTH=http://10.192.72.13:9195
 # VITE_APP_BASE_AUTH = http://10.192.72.13:22227
 
 # 开发环境登录相关代理前缀
-VITE_APP_PROXY_API = /yusp-flow,/cdp-oca,/bj-npam-starter,/account,/session,/oauth,/swagger-resources,/v2/api-docs,/zzh-api,/yusp-ai
+VITE_APP_PROXY_API=/yusp-flow,/cdp-oca,/bj-npam-starter,/account,/session,/oauth,/swagger-resources,/v2/api-docs,/zzh-api,/yusp-ai
 
 # 开发环境代理shuffle业务功能接口代理前缀
-VITE_APP_PROXY_SHUFFLE = /api
+VITE_APP_PROXY_SHUFFLE=/api

+ 20 - 1075
web/src/views/@xdjf/intelligent-due-diligence/views/home/index.vue

@@ -1,1089 +1,34 @@
+<!--
+ * @Author: qfkong kongqf@yusys.com.cn
+ * @Date: 2024-04-08 14:14:36
+ * @LastEditors: zhanglin3
+ * @LastEditTime: 2024-08-08 14:55:55
+ * @Description: 首页入口容器
+-->
 <template>
-  <div class="Home">
-    <div class="top-wrapper">
-      <img class="picture" src="@xdjf/idd/assets/images/首页/业务办理图.png" />
-      <div class="greeting float-item">
-        <div class="wrap">
-          <p class="title">{{ greetingTitle }}</p>
-          <span class="content">欢迎使用智能尽调平台</span>
-        </div>
-      </div>
-      <div class="float-item" :class="{ active: activeName === taskSts.todo }" @click="itemClick(taskSts.todo)">
-        <span class="image">
-          <img v-if="activeName === taskSts.todo" src="@xdjf/idd/assets/images/首页/待办.png" />
-          <img v-else src="@xdjf/idd/assets/images/首页/待办默认.png" />
-        </span>
-        <div class="wrap">
-          <p class="title">待办尽调</p>
-          <span class="content">{{ todoCount }}</span>
-        </div>
-      </div>
-      <div class="float-item" :class="{ active: activeName === taskSts.done }" @click="itemClick(taskSts.done)">
-        <span class="image">
-          <img v-if="activeName === taskSts.done" src="@xdjf/idd/assets/images/首页/已办.png" />
-          <img v-else src="@xdjf/idd/assets/images/首页/已办默认.png" />
-        </span>
-        <div class="wrap">
-          <p class="title">已办尽调</p>
-          <span class="content">{{ doneCount }}</span>
-        </div>
-      </div>
-      <div
-        class="float-item"
-        :class="{ active: activeName === taskSts.completed }"
-        @click="itemClick(taskSts.completed)"
-      >
-        <span class="image">
-          <img v-if="activeName === taskSts.completed" src="@xdjf/idd/assets/images/首页/完结.png" />
-          <img v-else src="@xdjf/idd/assets/images/首页/完结默认.png" />
-        </span>
-        <div class="wrap">
-          <p class="title">完结尽调</p>
-          <span class="content">{{ complateCount }}</span>
-        </div>
-      </div>
-    </div>
-    <div class="middle-wrapper">
-      <div class="info-wrapper">
-        <div class="warp">
-          <img class="avatar" src="@xdjf/idd/assets/images/首页/数据展示/头像.png" />
-          <span class="role">客户经理</span>
-          <span class="username"><OverflowTooltipTitle :title="String(userName || '')" /></span>
-          <span class="organization"><OverflowTooltipTitle :title="String(org.name || '')" /></span>
-        </div>
-      </div>
-      <div class="data-wrapper">
-        <div class="wrap">
-          <div class="data-item my-rank">
-            <span class="label">我的排名</span>
-            <span class="desc">排名数</span>
-            <span class="bg-img"><span class="rank">3</span></span>
-          </div>
-          <div class="data-item">
-            <div class="top">
-              <span class="label">当月放款金额(元)</span>
-              <span class="value">239,837</span>
-            </div>
-            <div class="bottom">
-              <div class="area-chart"><img src="@xdjf/idd/assets/images/首页/数据展示/走势图.png" /></div>
-              <div class="doughnut-chart">
-                <span class="chat-img">
-                  <span class="rate">30%</span>
-                </span>
-                <!-- <DoughnutChart name="30" /> -->
-              </div>
-            </div>
-          </div>
-          <div class="data-item">
-            <div class="top">
-              <span class="label">当月放款笔数(笔)</span>
-              <span class="value">5342</span>
-            </div>
-            <div class="bottom">
-              <div class="area-chart"><img src="@xdjf/idd/assets/images/首页/数据展示/走势图1.png" /></div>
-              <div class="doughnut-chart">
-                <span class="chat-img">
-                  <span class="rate">20%</span>
-                </span>
-                <!-- <DoughnutChart name="20" /> -->
-              </div>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-    <div class="bottom-wrapper">
-      <div class="left-wrapper">
-        <div class="title">
-          <i class="line"></i>
-          <span class="text">信贷助手</span>
-          <span class="operate">
-            更多
-            <i class="iconfont el-icon-arrow-right"></i>
-          </span>
-        </div>
-        <div class="content">
-          <div class="item">
-            <span class="image">
-              <img src="@xdjf/idd/assets/images/首页/客诉讼处理专员.png" />
-            </span>
-            <div class="wrap">
-              <p class="title">客诉讼处理专员</p>
-              <span class="content">及时、有效解决问题</span>
-            </div>
-          </div>
-          <div class="item">
-            <span class="image">
-              <img src="@xdjf/idd/assets/images/首页/产品推广达人.png" />
-            </span>
-            <div class="wrap">
-              <p class="title">产品推广达人</p>
-              <span class="content">精准推荐产品</span>
-            </div>
-          </div>
-          <div class="item">
-            <span class="image">
-              <img src="@xdjf/idd/assets/images/首页/业务办理能手.png" />
-            </span>
-            <div class="wrap">
-              <p class="title">业务办理能手</p>
-              <span class="content">智能处理、高效办理</span>
-            </div>
-          </div>
-          <div class="item">
-            <span class="image">
-              <img src="@xdjf/idd/assets/images/首页/风险识别专家.png" />
-            </span>
-            <div class="wrap">
-              <p class="title">风险识别专家</p>
-              <span class="content">智能定位风险</span>
-            </div>
-          </div>
-          <div class="item">
-            <span class="image">
-              <img src="@xdjf/idd/assets/images/首页/行业研究专家.png" />
-            </span>
-            <div class="wrap">
-              <p class="title">行业研究专家</p>
-              <span class="content">行业信息快速推荐</span>
-            </div>
-          </div>
-          <div class="item">
-            <span class="image">
-              <img src="@xdjf/idd/assets/images/首页/拓客高手.png" />
-            </span>
-            <div class="wrap">
-              <p class="title">拓客高手</p>
-              <span class="content">精准拓客、智能助手</span>
-            </div>
-          </div>
-          <div class="item">
-            <span class="image">
-              <img src="@xdjf/idd/assets/images/首页/证书达人.png" />
-            </span>
-            <div class="wrap">
-              <p class="title">证书达人</p>
-              <span class="content">学习帮手</span>
-            </div>
-          </div>
-          <div class="item">
-            <span class="image">
-              <img src="@xdjf/idd/assets/images/首页/大师妙笔.png" />
-            </span>
-            <div class="wrap">
-              <p class="title">大师妙笔</p>
-              <span class="content">AI智能优化文案</span>
-            </div>
-          </div>
-          <div class="item">
-            <span class="image">
-              <img src="@xdjf/idd/assets/images/首页/资深投资顾问.png" />
-            </span>
-            <div class="wrap">
-              <p class="title">资深投资顾问</p>
-              <span class="content">精准服务 助力财富增长</span>
-            </div>
-          </div>
-        </div>
-      </div>
-      <div class="right-wrapper">
-        <div class="title">
-          <i class="line"></i>
-          <span class="text">快捷入口</span>
-          <span class="operate"> 自定义 </span>
-        </div>
-        <div class="content">
-          <div class="item">
-            <span class="image">
-              <img src="@xdjf/idd/assets/images/首页/对公作业系统.png" />
-            </span>
-            <p class="title">对公作业系统</p>
-          </div>
-          <div class="item">
-            <span class="image">
-              <img src="@xdjf/idd/assets/images/首页/评级系统.png" />
-            </span>
-            <p class="title">评级系统</p>
-          </div>
-          <div class="item">
-            <span class="image">
-              <img src="@xdjf/idd/assets/images/首页/押品系统.png" />
-            </span>
-            <p class="title">押品系统</p>
-          </div>
-          <div class="item">
-            <span class="image">
-              <img src="@xdjf/idd/assets/images/首页/智能贷后系统.png" />
-            </span>
-            <p class="title">智能贷后系统</p>
-          </div>
-          <div class="item">
-            <span class="image">
-              <img src="@xdjf/idd/assets/images/首页/反欺诈系统.png" />
-            </span>
-            <p class="title">反欺诈系统</p>
-          </div>
-          <div class="item">
-            <span class="image">
-              <img src="@xdjf/idd/assets/images/首页/贷款计算器.png" />
-            </span>
-            <p class="title">贷款计算器</p>
-          </div>
-        </div>
-      </div>
-    </div>
+  <div class="home-container">
+    <component :is="currentRole" />
   </div>
 </template>
 
 <script>
-import { getTaskCountByStatus } from '@xdjf/idd/api/idd/index.js';
-import { mapGetters } from 'vuex';
-import { lookup } from '@yufp/xy-utils';
-import { CHAT_URL } from '@xdjf/idd/config/constants.js';
-import OverflowTooltipTitle from '@xdjf/idd/components/OverflowTooltipTitle/index.vue';
-import DoughnutChart from './charts/DoughnutChart.vue';
-
-const taskSts = {
-  todo: '待办',
-  done: '已办',
-  completed: '已完结',
-};
-
+import DefaultHome from '@/views/aiTagging/taggingResults/index.vue';
 export default {
   name: 'Home',
-  components: { OverflowTooltipTitle, DoughnutChart },
+  components: {
+    DefaultHome
+  },
   data() {
     return {
-      // 任务状态map
-      taskSts: taskSts,
-      activeName: taskSts.todo,
-      // 待办个数
-      todoCount: 0,
-      // 已办个数
-      doneCount: 0,
-      // 已完结个数
-      complateCount: 0,
-      // 小宇问答地址
-      chatUrl: CHAT_URL,
-      dragstartClientX: 0,
-      dragstartClientY: 0,
-      // 小宇图片
-      xiaoyu: {
-        width: 87,
-        height: 134,
-        top: 0,
-        left: 0,
-      },
-      // 问答框
-      chat: {
-        show: false,
-        height: 0,
-        rate: 1.5 / 2,
-        minHeight: 400,
-        maxHeight: 600,
-      },
-      // 欢迎语
-      box: {
-        width: 258,
-        height: 104,
-      },
-      // 关闭按钮高度
-      closeIconHeight: 28,
-      // 间隙
-      gap: 20,
-      // 拖拽对象
-      draggie: null,
+      // 当前视图组件
+      currentRole: 'DefaultHome'
     };
-  },
-  computed: {
-    ...mapGetters(['userName', 'org', 'selectedRoles', 'visitedViews']),
-    // 问候语
-    greetingTitle() {
-      const now = new Date();
-      const hour = now.getHours();
-      let msg = '';
-      if (hour >= 0 && hour <= 10) {
-        msg = '早上好! ';
-      } else if (hour > 10 && hour <= 14) {
-        msg = '中午好! ';
-      } else if (hour > 14 && hour <= 18) {
-        msg = '下午好! ';
-      } else if (hour > 18 && hour <= 24) {
-        msg = '晚上好! ';
-      }
-      return msg;
-    },
-    // 问答框宽度
-    chatWidth() {
-      let width = this.chat.height * this.chat.rate;
-      if (String(width).includes('.')) {
-        width = width.toFixed(0);
-      }
-      return Number(width);
-    },
-    // 问答容器样式
-    chatIframeContainerStryle() {
-      let top = Math.abs(this.xiaoyu.height - this.chat.height - this.gap);
-      const chatTotalHeight = this.chat.height + this.gap + this.closeIconHeight / 2;
-      const height = this.draggie.element.offsetTop + this.xiaoyu.height;
-      if (height < chatTotalHeight) {
-        top = top - (chatTotalHeight - height);
-      }
-
-      let left = this.chatWidth;
-      if (this.chatWidth > this.draggie.element.offsetLeft) {
-        left = -80;
-      }
-
-      const style = {
-        top: `calc(${-top}px)`,
-        left: `calc(0px - ${left}px)`,
-        height: `${this.chat.height}px`,
-        width: `${this.chatWidth}px`,
-      };
-      return style;
-    },
-  },
-  created() {
-    this.taskStatus = lookup.find('TASK_STATUS', false);
-    const { activeTaskStatus } = this.$store.state.iddTask;
-    if (activeTaskStatus === null || activeTaskStatus === undefined) {
-      this.$store.dispatch('iddTask/setActiveTaskStatus', 0);
-    }
-  },
-  mounted() {
-    if (this.$store.state.app.sidebar.opened) {
-      this.$store.commit('app/TOGGLE_SIDEBAR');
-      this.$store.commit('app/SET_MENU_SHOW_STAT', 2);
-    }
-    // this.getTaskCountByStatus();
-  },
-  methods: {
-    async getTaskCountByStatus() {
-      try {
-        const { code, data } = await getTaskCountByStatus();
-        if (code === '0' && data) {
-          if (Array.isArray(data)) {
-            data.forEach((item) => {
-              if (item.taskSts === '0') {
-                this.todoCount = item.count;
-              } else if (item.taskSts === '1') {
-                this.doneCount = item.count;
-              } else if (item.taskSts === '2') {
-                this.complateCount = item.count;
-              }
-            });
-          }
-        }
-      } catch (err) {}
-    },
-    itemClick(status) {
-      // return false;
-      const toTagView = this.visitedViews.find((item) => item.title === '尽调任务');
-      if (toTagView) {
-        this.$router.push(toTagView.fullPath);
-      } else {
-        this.$router.push({
-          path: '/@xdjf/intelligent-due-diligence/views/dueDiligenceTask/index',
-        });
-      }
-      const sts = Object.entries(this.taskStatus).find((item) => item[1] === status);
-      const value = sts[0] ? Number(sts[0]) : null;
-      if (typeof value === 'number') {
-        this.$store.dispatch('iddTask/setActiveTaskStatus', value);
-      }
-    },
-  },
+  }
 };
 </script>
-<style lang="scss" scoped>
-$min-height: calc(100vh - 108px);
-$top-min-height: 140px;
-$middle-min-height: 200px;
-$bottom-min-height: 350px;
-$base-gap: 16px;
-
-.Home {
-  position: absolute;
-  top: 0;
-  left: 0;
-  box-sizing: border-box;
-  display: flex;
-  flex-direction: column;
-  justify-content: space-between;
-  width: 100% !important;
-  height: 100% !important;
-  min-height: $min-height;
-  padding: 30px 20px;
-  overflow: auto;
-  background: #f5f5f5 !important;
-  background-color: #f5f5f5 !important;
-  box-shadow: none !important;
-
-  .top-wrapper,
-  .middle-wrapper,
-  .bottom-wrapper {
-    background-color: #fff;
-    border-radius: 8px;
-    box-shadow: 0 0 1px #eee;
-  }
-
-  .top-wrapper {
-    position: relative;
-    box-sizing: border-box;
-    //height: calc((100% - $base-gap * 2) * 0.2);
-    min-height: $top-min-height;
-    padding: 30px 20px;
-    background-image: linear-gradient(172deg, rgb(206 235 255 / 40%) 1%, rgb(255 255 255 / 64%) 69%);
-    border-radius: 8px;
-
-    .picture {
-      position: absolute;
-      top: -20px;
-      left: 28px;
-      width: 168px;
-      height: 150px;
-    }
-
-    .float-item {
-      $image-width: 48px;
-
-      position: relative;
-      box-sizing: border-box;
-      float: left;
-      width: calc((100% - ($base-gap * 3)) / 5);
-      height: 80px;
-      margin-left: $base-gap;
-      cursor: pointer;
-      background: #fff;
-      border: 1px solid rgb(235 237 240 / 100%);
-      border-radius: 8px;
-
-      .image {
-        position: absolute;
-        top: 50%;
-        left: 20px;
-        width: $image-width;
-        height: $image-width;
-        border-radius: 9.6px;
-        transform: translateY(-50%);
-
-        img {
-          width: 100%;
-        }
-      }
-
-      .wrap {
-        position: absolute;
-        top: 50%;
-        left: $base-gap * 2 + $image-width;
-        width: calc(100% - 80px);
-        height: 48px;
-        color: #333;
-        transform: translateY(-50%);
-
-        .title {
-          position: absolute;
-          top: 0;
-          left: 0;
-          font-size: 14px;
-          font-weight: 600;
-        }
-
-        .content {
-          position: absolute;
-          bottom: 0;
-          left: 0;
-          margin-top: 2px;
-          font-size: 24px;
-          font-weight: 600;
-        }
-      }
-    }
-
-    .float-item.greeting {
-      box-sizing: border-box;
-      width: calc(((100% - ($base-gap * 3)) / 5) * 2);
-      margin-left: 0;
-      cursor: auto;
-      background: none;
-      border: none;
-
-      .wrap {
-        left: 210px;
-        width: 160px;
-
-        .title {
-          font-size: 20px;
-          color: #296ee6;
-        }
-
-        .content {
-          margin-top: 10px;
-          font-size: 14px;
-          font-weight: 400;
-          color: #296ee6;
-        }
-      }
-    }
-
-    .float-item.active {
-      border: 1.5px solid rgb(41 110 230 / 100%);
-
-      .image {
-        background-image: linear-gradient(134deg, #ddeafc 0%, #fefff7 100%) !important;
-        border: 1.2px solid rgb(118 147 226 / 20%) !important;
-      }
-    }
-  }
-
-  .middle-wrapper {
-    position: relative;
-    box-sizing: border-box;
-    display: flex;
-    height: calc((100% - $base-gap * 2) * 0.3);
-    min-height: $middle-min-height;
-    padding: 20px;
-    margin-top: 16px;
-
-    .info-wrapper,
-    .data-wrapper {
-      min-height: 160px;
-    }
-
-    .info-wrapper {
-      $rank-height: 60px;
-
-      position: relative;
-      flex: 1;
-
-      .warp {
-        position: absolute;
-        top: 50%;
-        left: 50%;
-        height: 146px;
-        transform: translate(-50%, -50%);
-
-        .avatar {
-          position: absolute;
-          top: 12px;
-          left: 50%;
-          width: $rank-height;
-          height: $rank-height;
-          text-align: center;
-          border-radius: 50%;
-          transform: translateX(-50%);
-        }
-
-        .role {
-          position: absolute;
-          top: $rank-height;
-          left: 50%;
-          width: 60px;
-          height: 20px;
-          font-size: 12px;
-          line-height: 20px;
-          color: #9f5a11;
-          text-align: center;
-          background-image: linear-gradient(106deg, #fef2dd 0%, #f9c881 100%);
-          border-radius: 2px;
-          transform: translateX(-50%);
-        }
-
-        .username,
-        .organization {
-          .OverflowTooltipTitle {
-            height: inherit;
-            line-height: inherit;
-          }
-        }
-
-        .username {
-          position: absolute;
-          top: calc($rank-height + $base-gap + 10px);
-          left: 50%;
-          max-width: 120px;
-          height: 32px;
-          font-size: 24px;
-          font-weight: 500;
-          line-height: 32px;
-          color: #141d1d;
-          text-align: center;
-          transform: translateX(-50%);
-        }
-
-        .organization {
-          position: absolute;
-          bottom: 6px;
-          left: 50%;
-          max-width: 250px;
-          height: 16px;
-          padding: 0 20px;
-          font-size: 14px;
-          line-height: 16px;
-          color: #141d1d;
-          text-align: center;
-          transform: translateX(-50%);
-        }
-      }
-    }
-
-    .data-wrapper {
-      position: relative;
-      flex: 3;
-
-      .wrap {
-        position: absolute;
-        top: 50%;
-        left: 50%;
-        display: flex;
-        justify-content: space-between;
-        width: 100%;
-        height: 200px;
-        transform: translate(-50%, -50%);
-
-        .data-item.my-rank {
-          position: relative;
-
-          .label {
-            position: absolute;
-            top: 22px;
-            left: 24px;
-            font-size: 14px;
-            font-weight: 600;
-            color: #141d1d;
-          }
-
-          .desc {
-            position: absolute;
-            bottom: 34px;
-            left: 24px;
-            font-size: 14px;
-            font-weight: 400;
-            color: #4f4f4f;
-          }
-
-          span.bg-img {
-            position: absolute;
-            top: 40px;
-            right: 40px;
-            width: 66px;
-            height: 92px;
-            background-image: url('@xdjf/idd/assets/images/首页/数据展示/排名.png');
-            background-repeat: no-repeat;
-            background-position: center center;
-            background-size: 100% 100%;
-
-            .rank {
-              position: absolute;
-              top: 14px;
-              left: 50%;
-              font-size: 28px;
-              font-weight: 600;
-              color: #333;
-              transform: translateX(-50%);
-            }
-          }
-        }
-
-        .data-item {
-          box-sizing: border-box;
-          flex: 1;
-          width: calc((100% - $base-gap) / 3);
-          min-width: calc((100% - $base-gap) / 3);
-          max-width: calc((100% - $base-gap) / 3);
-          height: 156px;
-          max-height: 200px;
-          padding: 12px $base-gap;
-          margin-top: 22px;
-          background-color: #f7faff;
-          border-radius: 8px;
-
-          .top {
-            height: 40px;
-            line-height: 40px;
-
-            .label {
-              float: left;
-              font-size: 14px;
-              color: #141d1d;
-            }
-
-            .value {
-              float: right;
-              font-size: 24px;
-              font-weight: 600;
-              color: #141d1d;
-            }
-          }
-
-          .bottom {
-            height: calc(100% - 40px);
-            overflow: hidden;
-
-            .area-chart {
-              float: left;
-              width: 50%;
-              min-width: 155px;
-              height: 100% !important;
-              min-height: 78px;
-
-              img {
-                position: relative;
-                top: 12px;
-                width: 100%;
-                height: 100%;
-              }
-            }
-
-            .doughnut-chart {
-              position: relative;
-              float: left;
-              width: 50%;
-              max-width: calc(100% - 155px);
-              height: 100%;
-              min-height: 80px;
-
-              .chat-img {
-                position: absolute;
-                top: 50%;
-                left: 50%;
-                width: 80px;
-                height: 80px;
-                background-image: url('@xdjf/idd/assets/images/首页/数据展示/同比.png');
-                background-repeat: no-repeat;
-                background-position: center center;
-                background-size: 100% 100%;
-                transform: translate(-50%, -50%);
-
-                .rate {
-                  position: absolute;
-                  top: 50%;
-                  left: 50%;
-                  font-size: 24px;
-                  font-weight: 600;
-                  color: #333;
-                  transform: translate(-50%, -50%);
-                }
-              }
-            }
-          }
-        }
-      }
-    }
-  }
-
-  .bottom-wrapper {
-    position: relative;
-    display: flex;
-    justify-content: space-between;
-    height: calc((100% - $base-gap * 2) * 0.5);
-    min-height: $bottom-min-height;
-    padding-bottom: $base-gap;
-    margin-top: 16px;
-    background-color: transparent !important;
-
-    .left-wrapper {
-      $width: calc(100% * (2 / 3) - $base-gap);
-
-      box-sizing: border-box;
-      flex: 2;
-      width: $width;
-      min-width: $width;
-      max-width: $width;
-      height: 100%;
-      padding: 16px;
-      margin-right: $base-gap;
-      background-color: #fff;
-      border-radius: 8px;
-
-      .title {
-        height: 24px;
-        line-height: 24px;
-
-        .line {
-          position: relative;
-          top: 2px;
-          display: inline-block;
-          width: 4px;
-          height: 16px;
-          margin-right: 8px;
-          background-color: #1f66fe;
-        }
-
-        .text {
-          font-size: 16px;
-          font-weight: 600;
-          color: #333;
-        }
-
-        .operate {
-          float: right;
-          font-size: 14px;
-          color: #78797e;
-          cursor: pointer;
-        }
-      }
-
-      .content {
-        box-sizing: border-box;
-        display: flex;
-        flex-wrap: wrap;
-        align-items: flex-start;
-        justify-content: space-between;
-        height: calc(100% - 24px - $base-gap);
-        margin-top: $base-gap;
-        overflow: auto;
-
-        .item {
-          $width: calc((100% - 10px * 2) / 3);
-          $image-width: 50px;
-
-          position: relative;
-          box-sizing: border-box;
-          flex: 1;
-          width: $width;
-          min-width: $width;
-          max-width: $width;
-          height: 80px;
-          cursor: pointer;
-          background-color: #f5f8fe;
-          border-radius: 8px;
-
-          &:hover {
-            background-color: #ccc;
-          }
-
-          .image {
-            position: absolute;
-            top: 50%;
-            left: $base-gap;
-            width: $image-width;
-            height: $image-width;
-            background-color: #797979;
-            border-radius: 8px;
-            transform: translateY(-50%);
-
-            img {
-              width: 100%;
-            }
-          }
-
-          .wrap {
-            position: absolute;
-            top: 50%;
-            left: calc($base-gap + $image-width + 10px);
-            box-sizing: border-box;
-            width: calc(100% - $base-gap - $image-width - $base-gap);
-            height: $image-width;
-            transform: translateY(-50%);
-
-            .title {
-              position: absolute;
-              top: 0;
-              left: 0;
-              font-size: 16px;
-              font-weight: 600;
-              color: #333;
-            }
-
-            .content {
-              position: absolute;
-              bottom: 0;
-              left: 0;
-              height: 24px;
-              font-size: 14px;
-              color: #909090;
-            }
-          }
-        }
-      }
-    }
-
-    .right-wrapper {
-      $width: calc(100% * (1 / 3));
-
-      box-sizing: border-box;
-      flex: 1;
-      width: $width;
-      min-width: $width;
-      max-width: $width;
-      height: 100%;
-      padding: 16px;
-      background-color: #fff;
-      border-radius: 8px;
-
-      .title {
-        height: 24px;
-        line-height: 24px;
-
-        .line {
-          position: relative;
-          top: 2px;
-          display: inline-block;
-          width: 4px;
-          height: 16px;
-          margin-right: 8px;
-          background-color: #1f66fe;
-        }
-
-        .text {
-          font-size: 16px;
-          font-weight: 600;
-          color: #333;
-        }
-
-        .operate {
-          float: right;
-          font-size: 14px;
-          color: #78797e;
-          cursor: pointer;
-        }
-      }
-
-      .content {
-        box-sizing: border-box;
-        display: flex;
-        flex-wrap: wrap;
-        align-items: flex-start;
-        justify-content: space-between;
-        height: calc(100% - 24px - $base-gap);
-        margin-top: $base-gap;
-        overflow: auto;
-
-        .item {
-          $width: calc((100% - 10px) / 2);
-          $image-width: 50px;
-
-          position: relative;
-          box-sizing: border-box;
-          flex: 1;
-          width: $width;
-          min-width: $width;
-          max-width: $width;
-          height: 80px;
-          cursor: pointer;
-
-          &:hover {
-            font-weight: 600;
-          }
-
-          .image {
-            position: absolute;
-            top: 0;
-            left: 50%;
-            width: $image-width;
-            height: $image-width;
-            border-radius: 50%;
-            transform: translateX(-50%);
-
-            img {
-              width: 100%;
-            }
-          }
-
-          .title {
-            position: absolute;
-            bottom: 0;
-            left: 50%;
-            width: 100px;
-            height: 24px;
-            font-size: 14px;
-            color: #666;
-            text-align: center;
-            transform: translateX(-50%);
-          }
-        }
-      }
-    }
-  }
-
-  .xiaoyu {
-    // 小宇宽高
-    $xy-width: 87px;
-    $xy-height: 134px;
-    // 欢迎语宽高
-    $box-width: 258px;
-    $box-height: 104px;
-    // 问答框宽高
-    $iframe-width: 400px;
-    $iframe-height: 600px;
-    // 间隙
-    $gap: 20px;
-    // z轴值
-    $z-index: 10001;
-
-    position: fixed;
-    right: 0;
-    bottom: 0;
-    z-index: $z-index;
-    display: inline-block;
-    width: $xy-width;
-    height: $xy-height;
-    cursor: pointer;
-    background-image: url('@xdjf/idd/assets/images/首页/小宇.png');
-    background-repeat: no-repeat;
-    background-position: center center;
-    background-size: 100% 100%;
-
-    &:hover {
-      span.box {
-        display: block;
-      }
-    }
-
-    span.box {
-      position: absolute;
-      top: calc(0px - ($xy-height - $box-height) - $gap);
-      left: calc(0px - $box-width + 10px);
-      z-index: 200;
-      display: none;
-      width: $box-width;
-      height: $box-height;
-      background-image: url('@xdjf/idd/assets/images/首页/对话框.png');
-      background-repeat: no-repeat;
-      background-position: center center;
-      background-size: calc(100% + $gap * 2) calc(100% + $gap * 2);
-
-      .value1 {
-        position: absolute;
-        top: 30px;
-        left: 70px;
-        font-size: 14px;
-        color: #fff;
-      }
-
-      .value2 {
-        position: absolute;
-        top: 50px;
-        left: 70px;
-        font-size: 14px;
-        color: #fff;
-      }
-    }
-
-    .chat-iframe-container {
-      position: absolute;
-      border-radius: 8px;
-      box-shadow: 0 0 15px #ccc;
-
-      .chat-iframe {
-        width: 100%;
-        height: 100%;
-      }
-
-      .close-icon {
-        $icon-size: 28px;
-
-        position: absolute;
-        top: calc(0px - ($icon-size / 2));
-        right: calc(0px - ($icon-size / 2));
-        z-index: calc($z-index + 1);
-        font-size: $icon-size;
-        color: #333;
-        cursor: pointer;
-      }
-    }
-  }
+<style scoped>
+.home-container {
+  background: transparent!important;
+  box-shadow: none!important;
 }
 </style>

+ 1089 - 0
web/src/views/@xdjf/intelligent-due-diligence/views/home/indexDefault.vue

@@ -0,0 +1,1089 @@
+<template>
+  <div class="Home">
+    <div class="top-wrapper">
+      <img class="picture" src="@xdjf/idd/assets/images/首页/业务办理图.png" />
+      <div class="greeting float-item">
+        <div class="wrap">
+          <p class="title">{{ greetingTitle }}</p>
+          <span class="content">欢迎使用智能尽调平台</span>
+        </div>
+      </div>
+      <div class="float-item" :class="{ active: activeName === taskSts.todo }" @click="itemClick(taskSts.todo)">
+        <span class="image">
+          <img v-if="activeName === taskSts.todo" src="@xdjf/idd/assets/images/首页/待办.png" />
+          <img v-else src="@xdjf/idd/assets/images/首页/待办默认.png" />
+        </span>
+        <div class="wrap">
+          <p class="title">待办尽调</p>
+          <span class="content">{{ todoCount }}</span>
+        </div>
+      </div>
+      <div class="float-item" :class="{ active: activeName === taskSts.done }" @click="itemClick(taskSts.done)">
+        <span class="image">
+          <img v-if="activeName === taskSts.done" src="@xdjf/idd/assets/images/首页/已办.png" />
+          <img v-else src="@xdjf/idd/assets/images/首页/已办默认.png" />
+        </span>
+        <div class="wrap">
+          <p class="title">已办尽调</p>
+          <span class="content">{{ doneCount }}</span>
+        </div>
+      </div>
+      <div
+        class="float-item"
+        :class="{ active: activeName === taskSts.completed }"
+        @click="itemClick(taskSts.completed)"
+      >
+        <span class="image">
+          <img v-if="activeName === taskSts.completed" src="@xdjf/idd/assets/images/首页/完结.png" />
+          <img v-else src="@xdjf/idd/assets/images/首页/完结默认.png" />
+        </span>
+        <div class="wrap">
+          <p class="title">完结尽调</p>
+          <span class="content">{{ complateCount }}</span>
+        </div>
+      </div>
+    </div>
+    <div class="middle-wrapper">
+      <div class="info-wrapper">
+        <div class="warp">
+          <img class="avatar" src="@xdjf/idd/assets/images/首页/数据展示/头像.png" />
+          <span class="role">客户经理</span>
+          <span class="username"><OverflowTooltipTitle :title="String(userName || '')" /></span>
+          <span class="organization"><OverflowTooltipTitle :title="String(org.name || '')" /></span>
+        </div>
+      </div>
+      <div class="data-wrapper">
+        <div class="wrap">
+          <div class="data-item my-rank">
+            <span class="label">我的排名</span>
+            <span class="desc">排名数</span>
+            <span class="bg-img"><span class="rank">3</span></span>
+          </div>
+          <div class="data-item">
+            <div class="top">
+              <span class="label">当月放款金额(元)</span>
+              <span class="value">239,837</span>
+            </div>
+            <div class="bottom">
+              <div class="area-chart"><img src="@xdjf/idd/assets/images/首页/数据展示/走势图.png" /></div>
+              <div class="doughnut-chart">
+                <span class="chat-img">
+                  <span class="rate">30%</span>
+                </span>
+                <!-- <DoughnutChart name="30" /> -->
+              </div>
+            </div>
+          </div>
+          <div class="data-item">
+            <div class="top">
+              <span class="label">当月放款笔数(笔)</span>
+              <span class="value">5342</span>
+            </div>
+            <div class="bottom">
+              <div class="area-chart"><img src="@xdjf/idd/assets/images/首页/数据展示/走势图1.png" /></div>
+              <div class="doughnut-chart">
+                <span class="chat-img">
+                  <span class="rate">20%</span>
+                </span>
+                <!-- <DoughnutChart name="20" /> -->
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="bottom-wrapper">
+      <div class="left-wrapper">
+        <div class="title">
+          <i class="line"></i>
+          <span class="text">信贷助手</span>
+          <span class="operate">
+            更多
+            <i class="iconfont el-icon-arrow-right"></i>
+          </span>
+        </div>
+        <div class="content">
+          <div class="item">
+            <span class="image">
+              <img src="@xdjf/idd/assets/images/首页/客诉讼处理专员.png" />
+            </span>
+            <div class="wrap">
+              <p class="title">客诉讼处理专员</p>
+              <span class="content">及时、有效解决问题</span>
+            </div>
+          </div>
+          <div class="item">
+            <span class="image">
+              <img src="@xdjf/idd/assets/images/首页/产品推广达人.png" />
+            </span>
+            <div class="wrap">
+              <p class="title">产品推广达人</p>
+              <span class="content">精准推荐产品</span>
+            </div>
+          </div>
+          <div class="item">
+            <span class="image">
+              <img src="@xdjf/idd/assets/images/首页/业务办理能手.png" />
+            </span>
+            <div class="wrap">
+              <p class="title">业务办理能手</p>
+              <span class="content">智能处理、高效办理</span>
+            </div>
+          </div>
+          <div class="item">
+            <span class="image">
+              <img src="@xdjf/idd/assets/images/首页/风险识别专家.png" />
+            </span>
+            <div class="wrap">
+              <p class="title">风险识别专家</p>
+              <span class="content">智能定位风险</span>
+            </div>
+          </div>
+          <div class="item">
+            <span class="image">
+              <img src="@xdjf/idd/assets/images/首页/行业研究专家.png" />
+            </span>
+            <div class="wrap">
+              <p class="title">行业研究专家</p>
+              <span class="content">行业信息快速推荐</span>
+            </div>
+          </div>
+          <div class="item">
+            <span class="image">
+              <img src="@xdjf/idd/assets/images/首页/拓客高手.png" />
+            </span>
+            <div class="wrap">
+              <p class="title">拓客高手</p>
+              <span class="content">精准拓客、智能助手</span>
+            </div>
+          </div>
+          <div class="item">
+            <span class="image">
+              <img src="@xdjf/idd/assets/images/首页/证书达人.png" />
+            </span>
+            <div class="wrap">
+              <p class="title">证书达人</p>
+              <span class="content">学习帮手</span>
+            </div>
+          </div>
+          <div class="item">
+            <span class="image">
+              <img src="@xdjf/idd/assets/images/首页/大师妙笔.png" />
+            </span>
+            <div class="wrap">
+              <p class="title">大师妙笔</p>
+              <span class="content">AI智能优化文案</span>
+            </div>
+          </div>
+          <div class="item">
+            <span class="image">
+              <img src="@xdjf/idd/assets/images/首页/资深投资顾问.png" />
+            </span>
+            <div class="wrap">
+              <p class="title">资深投资顾问</p>
+              <span class="content">精准服务 助力财富增长</span>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="right-wrapper">
+        <div class="title">
+          <i class="line"></i>
+          <span class="text">快捷入口</span>
+          <span class="operate"> 自定义 </span>
+        </div>
+        <div class="content">
+          <div class="item">
+            <span class="image">
+              <img src="@xdjf/idd/assets/images/首页/对公作业系统.png" />
+            </span>
+            <p class="title">对公作业系统</p>
+          </div>
+          <div class="item">
+            <span class="image">
+              <img src="@xdjf/idd/assets/images/首页/评级系统.png" />
+            </span>
+            <p class="title">评级系统</p>
+          </div>
+          <div class="item">
+            <span class="image">
+              <img src="@xdjf/idd/assets/images/首页/押品系统.png" />
+            </span>
+            <p class="title">押品系统</p>
+          </div>
+          <div class="item">
+            <span class="image">
+              <img src="@xdjf/idd/assets/images/首页/智能贷后系统.png" />
+            </span>
+            <p class="title">智能贷后系统</p>
+          </div>
+          <div class="item">
+            <span class="image">
+              <img src="@xdjf/idd/assets/images/首页/反欺诈系统.png" />
+            </span>
+            <p class="title">反欺诈系统</p>
+          </div>
+          <div class="item">
+            <span class="image">
+              <img src="@xdjf/idd/assets/images/首页/贷款计算器.png" />
+            </span>
+            <p class="title">贷款计算器</p>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { getTaskCountByStatus } from '@xdjf/idd/api/idd/index.js';
+import { mapGetters } from 'vuex';
+import { lookup } from '@yufp/xy-utils';
+import { CHAT_URL } from '@xdjf/idd/config/constants.js';
+import OverflowTooltipTitle from '@xdjf/idd/components/OverflowTooltipTitle/index.vue';
+import DoughnutChart from './charts/DoughnutChart.vue';
+
+const taskSts = {
+  todo: '待办',
+  done: '已办',
+  completed: '已完结',
+};
+
+export default {
+  name: 'Home',
+  components: { OverflowTooltipTitle, DoughnutChart },
+  data() {
+    return {
+      // 任务状态map
+      taskSts: taskSts,
+      activeName: taskSts.todo,
+      // 待办个数
+      todoCount: 0,
+      // 已办个数
+      doneCount: 0,
+      // 已完结个数
+      complateCount: 0,
+      // 小宇问答地址
+      chatUrl: CHAT_URL,
+      dragstartClientX: 0,
+      dragstartClientY: 0,
+      // 小宇图片
+      xiaoyu: {
+        width: 87,
+        height: 134,
+        top: 0,
+        left: 0,
+      },
+      // 问答框
+      chat: {
+        show: false,
+        height: 0,
+        rate: 1.5 / 2,
+        minHeight: 400,
+        maxHeight: 600,
+      },
+      // 欢迎语
+      box: {
+        width: 258,
+        height: 104,
+      },
+      // 关闭按钮高度
+      closeIconHeight: 28,
+      // 间隙
+      gap: 20,
+      // 拖拽对象
+      draggie: null,
+    };
+  },
+  computed: {
+    ...mapGetters(['userName', 'org', 'selectedRoles', 'visitedViews']),
+    // 问候语
+    greetingTitle() {
+      const now = new Date();
+      const hour = now.getHours();
+      let msg = '';
+      if (hour >= 0 && hour <= 10) {
+        msg = '早上好! ';
+      } else if (hour > 10 && hour <= 14) {
+        msg = '中午好! ';
+      } else if (hour > 14 && hour <= 18) {
+        msg = '下午好! ';
+      } else if (hour > 18 && hour <= 24) {
+        msg = '晚上好! ';
+      }
+      return msg;
+    },
+    // 问答框宽度
+    chatWidth() {
+      let width = this.chat.height * this.chat.rate;
+      if (String(width).includes('.')) {
+        width = width.toFixed(0);
+      }
+      return Number(width);
+    },
+    // 问答容器样式
+    chatIframeContainerStryle() {
+      let top = Math.abs(this.xiaoyu.height - this.chat.height - this.gap);
+      const chatTotalHeight = this.chat.height + this.gap + this.closeIconHeight / 2;
+      const height = this.draggie.element.offsetTop + this.xiaoyu.height;
+      if (height < chatTotalHeight) {
+        top = top - (chatTotalHeight - height);
+      }
+
+      let left = this.chatWidth;
+      if (this.chatWidth > this.draggie.element.offsetLeft) {
+        left = -80;
+      }
+
+      const style = {
+        top: `calc(${-top}px)`,
+        left: `calc(0px - ${left}px)`,
+        height: `${this.chat.height}px`,
+        width: `${this.chatWidth}px`,
+      };
+      return style;
+    },
+  },
+  created() {
+    this.taskStatus = lookup.find('TASK_STATUS', false);
+    const { activeTaskStatus } = this.$store.state.iddTask;
+    if (activeTaskStatus === null || activeTaskStatus === undefined) {
+      this.$store.dispatch('iddTask/setActiveTaskStatus', 0);
+    }
+  },
+  mounted() {
+    if (this.$store.state.app.sidebar.opened) {
+      this.$store.commit('app/TOGGLE_SIDEBAR');
+      this.$store.commit('app/SET_MENU_SHOW_STAT', 2);
+    }
+    // this.getTaskCountByStatus();
+  },
+  methods: {
+    async getTaskCountByStatus() {
+      try {
+        const { code, data } = await getTaskCountByStatus();
+        if (code === '0' && data) {
+          if (Array.isArray(data)) {
+            data.forEach((item) => {
+              if (item.taskSts === '0') {
+                this.todoCount = item.count;
+              } else if (item.taskSts === '1') {
+                this.doneCount = item.count;
+              } else if (item.taskSts === '2') {
+                this.complateCount = item.count;
+              }
+            });
+          }
+        }
+      } catch (err) {}
+    },
+    itemClick(status) {
+      // return false;
+      const toTagView = this.visitedViews.find((item) => item.title === '尽调任务');
+      if (toTagView) {
+        this.$router.push(toTagView.fullPath);
+      } else {
+        this.$router.push({
+          path: '/@xdjf/intelligent-due-diligence/views/dueDiligenceTask/index',
+        });
+      }
+      const sts = Object.entries(this.taskStatus).find((item) => item[1] === status);
+      const value = sts[0] ? Number(sts[0]) : null;
+      if (typeof value === 'number') {
+        this.$store.dispatch('iddTask/setActiveTaskStatus', value);
+      }
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+$min-height: calc(100vh - 108px);
+$top-min-height: 140px;
+$middle-min-height: 200px;
+$bottom-min-height: 350px;
+$base-gap: 16px;
+
+.Home {
+  position: absolute;
+  top: 0;
+  left: 0;
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  width: 100% !important;
+  height: 100% !important;
+  min-height: $min-height;
+  padding: 30px 20px;
+  overflow: auto;
+  background: #f5f5f5 !important;
+  background-color: #f5f5f5 !important;
+  box-shadow: none !important;
+
+  .top-wrapper,
+  .middle-wrapper,
+  .bottom-wrapper {
+    background-color: #fff;
+    border-radius: 8px;
+    box-shadow: 0 0 1px #eee;
+  }
+
+  .top-wrapper {
+    position: relative;
+    box-sizing: border-box;
+    //height: calc((100% - $base-gap * 2) * 0.2);
+    min-height: $top-min-height;
+    padding: 30px 20px;
+    background-image: linear-gradient(172deg, rgb(206 235 255 / 40%) 1%, rgb(255 255 255 / 64%) 69%);
+    border-radius: 8px;
+
+    .picture {
+      position: absolute;
+      top: -20px;
+      left: 28px;
+      width: 168px;
+      height: 150px;
+    }
+
+    .float-item {
+      $image-width: 48px;
+
+      position: relative;
+      box-sizing: border-box;
+      float: left;
+      width: calc((100% - ($base-gap * 3)) / 5);
+      height: 80px;
+      margin-left: $base-gap;
+      cursor: pointer;
+      background: #fff;
+      border: 1px solid rgb(235 237 240 / 100%);
+      border-radius: 8px;
+
+      .image {
+        position: absolute;
+        top: 50%;
+        left: 20px;
+        width: $image-width;
+        height: $image-width;
+        border-radius: 9.6px;
+        transform: translateY(-50%);
+
+        img {
+          width: 100%;
+        }
+      }
+
+      .wrap {
+        position: absolute;
+        top: 50%;
+        left: $base-gap * 2 + $image-width;
+        width: calc(100% - 80px);
+        height: 48px;
+        color: #333;
+        transform: translateY(-50%);
+
+        .title {
+          position: absolute;
+          top: 0;
+          left: 0;
+          font-size: 14px;
+          font-weight: 600;
+        }
+
+        .content {
+          position: absolute;
+          bottom: 0;
+          left: 0;
+          margin-top: 2px;
+          font-size: 24px;
+          font-weight: 600;
+        }
+      }
+    }
+
+    .float-item.greeting {
+      box-sizing: border-box;
+      width: calc(((100% - ($base-gap * 3)) / 5) * 2);
+      margin-left: 0;
+      cursor: auto;
+      background: none;
+      border: none;
+
+      .wrap {
+        left: 210px;
+        width: 160px;
+
+        .title {
+          font-size: 20px;
+          color: #296ee6;
+        }
+
+        .content {
+          margin-top: 10px;
+          font-size: 14px;
+          font-weight: 400;
+          color: #296ee6;
+        }
+      }
+    }
+
+    .float-item.active {
+      border: 1.5px solid rgb(41 110 230 / 100%);
+
+      .image {
+        background-image: linear-gradient(134deg, #ddeafc 0%, #fefff7 100%) !important;
+        border: 1.2px solid rgb(118 147 226 / 20%) !important;
+      }
+    }
+  }
+
+  .middle-wrapper {
+    position: relative;
+    box-sizing: border-box;
+    display: flex;
+    height: calc((100% - $base-gap * 2) * 0.3);
+    min-height: $middle-min-height;
+    padding: 20px;
+    margin-top: 16px;
+
+    .info-wrapper,
+    .data-wrapper {
+      min-height: 160px;
+    }
+
+    .info-wrapper {
+      $rank-height: 60px;
+
+      position: relative;
+      flex: 1;
+
+      .warp {
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        height: 146px;
+        transform: translate(-50%, -50%);
+
+        .avatar {
+          position: absolute;
+          top: 12px;
+          left: 50%;
+          width: $rank-height;
+          height: $rank-height;
+          text-align: center;
+          border-radius: 50%;
+          transform: translateX(-50%);
+        }
+
+        .role {
+          position: absolute;
+          top: $rank-height;
+          left: 50%;
+          width: 60px;
+          height: 20px;
+          font-size: 12px;
+          line-height: 20px;
+          color: #9f5a11;
+          text-align: center;
+          background-image: linear-gradient(106deg, #fef2dd 0%, #f9c881 100%);
+          border-radius: 2px;
+          transform: translateX(-50%);
+        }
+
+        .username,
+        .organization {
+          .OverflowTooltipTitle {
+            height: inherit;
+            line-height: inherit;
+          }
+        }
+
+        .username {
+          position: absolute;
+          top: calc($rank-height + $base-gap + 10px);
+          left: 50%;
+          max-width: 120px;
+          height: 32px;
+          font-size: 24px;
+          font-weight: 500;
+          line-height: 32px;
+          color: #141d1d;
+          text-align: center;
+          transform: translateX(-50%);
+        }
+
+        .organization {
+          position: absolute;
+          bottom: 6px;
+          left: 50%;
+          max-width: 250px;
+          height: 16px;
+          padding: 0 20px;
+          font-size: 14px;
+          line-height: 16px;
+          color: #141d1d;
+          text-align: center;
+          transform: translateX(-50%);
+        }
+      }
+    }
+
+    .data-wrapper {
+      position: relative;
+      flex: 3;
+
+      .wrap {
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        display: flex;
+        justify-content: space-between;
+        width: 100%;
+        height: 200px;
+        transform: translate(-50%, -50%);
+
+        .data-item.my-rank {
+          position: relative;
+
+          .label {
+            position: absolute;
+            top: 22px;
+            left: 24px;
+            font-size: 14px;
+            font-weight: 600;
+            color: #141d1d;
+          }
+
+          .desc {
+            position: absolute;
+            bottom: 34px;
+            left: 24px;
+            font-size: 14px;
+            font-weight: 400;
+            color: #4f4f4f;
+          }
+
+          span.bg-img {
+            position: absolute;
+            top: 40px;
+            right: 40px;
+            width: 66px;
+            height: 92px;
+            background-image: url('@xdjf/idd/assets/images/首页/数据展示/排名.png');
+            background-repeat: no-repeat;
+            background-position: center center;
+            background-size: 100% 100%;
+
+            .rank {
+              position: absolute;
+              top: 14px;
+              left: 50%;
+              font-size: 28px;
+              font-weight: 600;
+              color: #333;
+              transform: translateX(-50%);
+            }
+          }
+        }
+
+        .data-item {
+          box-sizing: border-box;
+          flex: 1;
+          width: calc((100% - $base-gap) / 3);
+          min-width: calc((100% - $base-gap) / 3);
+          max-width: calc((100% - $base-gap) / 3);
+          height: 156px;
+          max-height: 200px;
+          padding: 12px $base-gap;
+          margin-top: 22px;
+          background-color: #f7faff;
+          border-radius: 8px;
+
+          .top {
+            height: 40px;
+            line-height: 40px;
+
+            .label {
+              float: left;
+              font-size: 14px;
+              color: #141d1d;
+            }
+
+            .value {
+              float: right;
+              font-size: 24px;
+              font-weight: 600;
+              color: #141d1d;
+            }
+          }
+
+          .bottom {
+            height: calc(100% - 40px);
+            overflow: hidden;
+
+            .area-chart {
+              float: left;
+              width: 50%;
+              min-width: 155px;
+              height: 100% !important;
+              min-height: 78px;
+
+              img {
+                position: relative;
+                top: 12px;
+                width: 100%;
+                height: 100%;
+              }
+            }
+
+            .doughnut-chart {
+              position: relative;
+              float: left;
+              width: 50%;
+              max-width: calc(100% - 155px);
+              height: 100%;
+              min-height: 80px;
+
+              .chat-img {
+                position: absolute;
+                top: 50%;
+                left: 50%;
+                width: 80px;
+                height: 80px;
+                background-image: url('@xdjf/idd/assets/images/首页/数据展示/同比.png');
+                background-repeat: no-repeat;
+                background-position: center center;
+                background-size: 100% 100%;
+                transform: translate(-50%, -50%);
+
+                .rate {
+                  position: absolute;
+                  top: 50%;
+                  left: 50%;
+                  font-size: 24px;
+                  font-weight: 600;
+                  color: #333;
+                  transform: translate(-50%, -50%);
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  .bottom-wrapper {
+    position: relative;
+    display: flex;
+    justify-content: space-between;
+    height: calc((100% - $base-gap * 2) * 0.5);
+    min-height: $bottom-min-height;
+    padding-bottom: $base-gap;
+    margin-top: 16px;
+    background-color: transparent !important;
+
+    .left-wrapper {
+      $width: calc(100% * (2 / 3) - $base-gap);
+
+      box-sizing: border-box;
+      flex: 2;
+      width: $width;
+      min-width: $width;
+      max-width: $width;
+      height: 100%;
+      padding: 16px;
+      margin-right: $base-gap;
+      background-color: #fff;
+      border-radius: 8px;
+
+      .title {
+        height: 24px;
+        line-height: 24px;
+
+        .line {
+          position: relative;
+          top: 2px;
+          display: inline-block;
+          width: 4px;
+          height: 16px;
+          margin-right: 8px;
+          background-color: #1f66fe;
+        }
+
+        .text {
+          font-size: 16px;
+          font-weight: 600;
+          color: #333;
+        }
+
+        .operate {
+          float: right;
+          font-size: 14px;
+          color: #78797e;
+          cursor: pointer;
+        }
+      }
+
+      .content {
+        box-sizing: border-box;
+        display: flex;
+        flex-wrap: wrap;
+        align-items: flex-start;
+        justify-content: space-between;
+        height: calc(100% - 24px - $base-gap);
+        margin-top: $base-gap;
+        overflow: auto;
+
+        .item {
+          $width: calc((100% - 10px * 2) / 3);
+          $image-width: 50px;
+
+          position: relative;
+          box-sizing: border-box;
+          flex: 1;
+          width: $width;
+          min-width: $width;
+          max-width: $width;
+          height: 80px;
+          cursor: pointer;
+          background-color: #f5f8fe;
+          border-radius: 8px;
+
+          &:hover {
+            background-color: #ccc;
+          }
+
+          .image {
+            position: absolute;
+            top: 50%;
+            left: $base-gap;
+            width: $image-width;
+            height: $image-width;
+            background-color: #797979;
+            border-radius: 8px;
+            transform: translateY(-50%);
+
+            img {
+              width: 100%;
+            }
+          }
+
+          .wrap {
+            position: absolute;
+            top: 50%;
+            left: calc($base-gap + $image-width + 10px);
+            box-sizing: border-box;
+            width: calc(100% - $base-gap - $image-width - $base-gap);
+            height: $image-width;
+            transform: translateY(-50%);
+
+            .title {
+              position: absolute;
+              top: 0;
+              left: 0;
+              font-size: 16px;
+              font-weight: 600;
+              color: #333;
+            }
+
+            .content {
+              position: absolute;
+              bottom: 0;
+              left: 0;
+              height: 24px;
+              font-size: 14px;
+              color: #909090;
+            }
+          }
+        }
+      }
+    }
+
+    .right-wrapper {
+      $width: calc(100% * (1 / 3));
+
+      box-sizing: border-box;
+      flex: 1;
+      width: $width;
+      min-width: $width;
+      max-width: $width;
+      height: 100%;
+      padding: 16px;
+      background-color: #fff;
+      border-radius: 8px;
+
+      .title {
+        height: 24px;
+        line-height: 24px;
+
+        .line {
+          position: relative;
+          top: 2px;
+          display: inline-block;
+          width: 4px;
+          height: 16px;
+          margin-right: 8px;
+          background-color: #1f66fe;
+        }
+
+        .text {
+          font-size: 16px;
+          font-weight: 600;
+          color: #333;
+        }
+
+        .operate {
+          float: right;
+          font-size: 14px;
+          color: #78797e;
+          cursor: pointer;
+        }
+      }
+
+      .content {
+        box-sizing: border-box;
+        display: flex;
+        flex-wrap: wrap;
+        align-items: flex-start;
+        justify-content: space-between;
+        height: calc(100% - 24px - $base-gap);
+        margin-top: $base-gap;
+        overflow: auto;
+
+        .item {
+          $width: calc((100% - 10px) / 2);
+          $image-width: 50px;
+
+          position: relative;
+          box-sizing: border-box;
+          flex: 1;
+          width: $width;
+          min-width: $width;
+          max-width: $width;
+          height: 80px;
+          cursor: pointer;
+
+          &:hover {
+            font-weight: 600;
+          }
+
+          .image {
+            position: absolute;
+            top: 0;
+            left: 50%;
+            width: $image-width;
+            height: $image-width;
+            border-radius: 50%;
+            transform: translateX(-50%);
+
+            img {
+              width: 100%;
+            }
+          }
+
+          .title {
+            position: absolute;
+            bottom: 0;
+            left: 50%;
+            width: 100px;
+            height: 24px;
+            font-size: 14px;
+            color: #666;
+            text-align: center;
+            transform: translateX(-50%);
+          }
+        }
+      }
+    }
+  }
+
+  .xiaoyu {
+    // 小宇宽高
+    $xy-width: 87px;
+    $xy-height: 134px;
+    // 欢迎语宽高
+    $box-width: 258px;
+    $box-height: 104px;
+    // 问答框宽高
+    $iframe-width: 400px;
+    $iframe-height: 600px;
+    // 间隙
+    $gap: 20px;
+    // z轴值
+    $z-index: 10001;
+
+    position: fixed;
+    right: 0;
+    bottom: 0;
+    z-index: $z-index;
+    display: inline-block;
+    width: $xy-width;
+    height: $xy-height;
+    cursor: pointer;
+    background-image: url('@xdjf/idd/assets/images/首页/小宇.png');
+    background-repeat: no-repeat;
+    background-position: center center;
+    background-size: 100% 100%;
+
+    &:hover {
+      span.box {
+        display: block;
+      }
+    }
+
+    span.box {
+      position: absolute;
+      top: calc(0px - ($xy-height - $box-height) - $gap);
+      left: calc(0px - $box-width + 10px);
+      z-index: 200;
+      display: none;
+      width: $box-width;
+      height: $box-height;
+      background-image: url('@xdjf/idd/assets/images/首页/对话框.png');
+      background-repeat: no-repeat;
+      background-position: center center;
+      background-size: calc(100% + $gap * 2) calc(100% + $gap * 2);
+
+      .value1 {
+        position: absolute;
+        top: 30px;
+        left: 70px;
+        font-size: 14px;
+        color: #fff;
+      }
+
+      .value2 {
+        position: absolute;
+        top: 50px;
+        left: 70px;
+        font-size: 14px;
+        color: #fff;
+      }
+    }
+
+    .chat-iframe-container {
+      position: absolute;
+      border-radius: 8px;
+      box-shadow: 0 0 15px #ccc;
+
+      .chat-iframe {
+        width: 100%;
+        height: 100%;
+      }
+
+      .close-icon {
+        $icon-size: 28px;
+
+        position: absolute;
+        top: calc(0px - ($icon-size / 2));
+        right: calc(0px - ($icon-size / 2));
+        z-index: calc($z-index + 1);
+        font-size: $icon-size;
+        color: #333;
+        cursor: pointer;
+      }
+    }
+  }
+}
+</style>

+ 38 - 11
web/src/views/aiTagging/externalPage/components/IntelligentRecommend.vue

@@ -3,20 +3,14 @@
     <!-- 智能推荐标题 -->
     <div class="section-header">
       <h3 class="section-title">智能推荐</h3>
-      <a href="javascript:void(0)" class="switch-to-manual" @click="$emit('switchToManual')">
+      <a href="javascript:void(0)" class="switch-to-manual" @click="handleSwitchToManual">
         以下都不合适?切换人工打标
       </a>
     </div>
 
     <!-- 无推荐结果状态 -->
     <div v-if="!hasRecommendations" class="no-recommendations">
-      <div class="no-data-icon">
-        <img src="https://trae-api-cn.mchost.guru/api/ide/v1/text_to_image?prompt=empty%20box%20with%20flying%20icons%2C%20blue%20color%2C%20simple%20illustration&image_size=square" alt="暂无数据" />
-      </div>
       <div class="no-data-text">暂无数据</div>
-      <a href="javascript:void(0)" class="retry-btn" @click="$emit('retryIntelligentTagging')">
-        点击此处,再次发起智能打标
-      </a>
     </div>
 
     <!-- 有推荐结果状态 -->
@@ -30,14 +24,14 @@
       >
         <!-- 标签路径 -->
         <div class="tag-path">
-          <span>{{ tag.path }}</span>
+          <span>{{ tag.tag_path }}</span>
           <i v-if="tag.selected" class="el-icon-check selected-icon"></i>
         </div>
         
         <!-- 打标依据 -->
         <div class="tag-basis">
           <div class="basis-label">打标依据:</div>
-          <div class="basis-content">{{ tag.basis }}</div>
+          <div class="basis-content">{{ tag.desc }}</div>
         </div>
         
         <!-- 不准确反馈 -->
@@ -63,6 +57,10 @@ export default {
     recommendations: {
       type: Array,
       default: () => []
+    },
+    currentSystem: {
+      type: String,
+      default: ''
     }
   },
   computed: {
@@ -72,9 +70,38 @@ export default {
     }
   },
   methods: {
-    // 选择标签
+    // 选择标签(单选,支持取消选中)
     handleTagSelect(index) {
-      this.$emit('tagSelect', index);
+      // 检查当前标签是否已选中
+      const isCurrentlySelected = this.recommendations[index].selected;
+      
+      // 如果已选中,则取消所有标签的选中状态
+      if (isCurrentlySelected) {
+        this.recommendations.forEach(tag => {
+          tag.selected = false;
+        });
+      } else {
+        // 如果未选中,则取消其他标签的选中状态,只选中当前标签
+        this.recommendations.forEach((tag, i) => {
+          tag.selected = i === index;
+        });
+      }
+      
+      // 计算选中的标签
+      const selectedTags = this.recommendations.filter(tag => tag.selected);
+      // 将选中的标签传递给父组件
+      this.$emit('tagSelect', selectedTags);
+    },
+    
+    // 处理切换到人工打标
+    handleSwitchToManual() {
+      // 判断是否选择了标签体系
+      if (!this.currentSystem) {
+        this.$message.warning('请先选择标签体系再切换人工打标');
+        return;
+      }
+      // 切换到人工打标模式
+      this.$emit('switchToManual');
     }
   }
 }

+ 106 - 9
web/src/views/aiTagging/externalPage/components/ManualTagging.vue

@@ -23,12 +23,12 @@
     <!-- 标签树 -->
     <div class="tag-tree">
       <el-tree
+        ref="tagTree"
         :data="tagTreeData"
         :props="defaultProps"
-        show-checkbox
         node-key="id"
-        :check-strictly="true"
-        @check="handleTagCheck"
+        :highlight-current="true"
+        @node-click="handleTagClick"
         class="tag-tree-list"
       ></el-tree>
     </div>
@@ -39,7 +39,15 @@
 export default {
   name: 'ManualTagging',
   props: {
-    tagTreeData: {
+    currentSystem: {
+      type: String,
+      default: ''
+    },
+    tagSystemsData: {
+      type: Array,
+      default: () => []
+    },
+    selectedTags: {
       type: Array,
       default: () => []
     }
@@ -49,8 +57,38 @@ export default {
       searchKeyword: '',
       defaultProps: {
         children: 'children',
-        label: 'label'
-      }
+        label: 'tagNm'
+      },
+      tagTreeData: []
+    };
+  },
+  watch: {
+    // 监听标签体系变化,重新加载标签树数据
+    currentSystem: {
+      handler() {
+        this.loadTagTreeData();
+      },
+      immediate: true
+    },
+    // 监听已选标签变化,重新反显选中的标签
+    selectedTags: {
+      handler() {
+        this.reverseSelectTags();
+      },
+      deep: true
+    }
+  },
+  mounted() {
+    // 组件挂载时加载标签树数据
+    this.loadTagTreeData();
+  },
+  watch: {
+    // 监听currentSystem变化,重新加载标签树数据
+    currentSystem: {
+      handler() {
+        this.loadTagTreeData();
+      },
+      immediate: true
     }
   },
   methods: {
@@ -59,9 +97,68 @@ export default {
       this.$emit('search', this.searchKeyword);
     },
     
-    // 处理标签勾选
-    handleTagCheck(data, checkedInfo) {
-      this.$emit('tagCheck', data, checkedInfo);
+    // 处理标签点击
+    handleTagClick(data, node, component) {
+      this.$emit('tagCheck', data, { checked: true });
+    },
+    
+    // 加载标签树数据
+    loadTagTreeData() {
+      // 查找当前选中的标签体系对应的categoryId
+      const selectedSystem = this.tagSystemsData.find(system => system.categoryNm === this.currentSystem);
+      if (!selectedSystem || !selectedSystem.id) {
+        return;
+      }
+      
+      yufp.service.request({
+        url: backend.tagServer + "/api/aitagtaginfo/queryTagTreeNoAuth",
+        method: 'post',
+        data: {
+          tagNm: "",
+          categoryId: selectedSystem.id
+        },
+        callback: (code, error, response) => {
+          if (response.code == '0') {
+            this.tagTreeData = response.data || [];
+            console.log('标签树数据加载成功:', this.tagTreeData);
+            // 反显选中的标签
+            this.reverseSelectTags();
+          } else {
+            this.$message.error(response.message || '获取标签树数据失败');
+          }
+        }
+      });
+    },
+    
+    // 反显选中的标签
+    reverseSelectTags() {
+      if (!this.$refs.tagTree || !this.tagTreeData.length) {
+        return;
+      }
+      
+      // 遍历标签树,查找与已选标签匹配的节点
+      const findAndSelectTag = (nodes) => {
+        for (const node of nodes) {
+          // 检查当前节点的标签编码是否在已选标签列表中
+          const isSelected = this.selectedTags.some(selectedTag => 
+            selectedTag.tagCode === node.tagCode
+          );
+          if (isSelected) {
+            // 设置当前节点为选中状态
+            this.$refs.tagTree.setCurrentKey(node.id);
+            return true;
+          }
+          // 递归检查子节点
+          if (node.children && node.children.length) {
+            if (findAndSelectTag(node.children)) {
+              return true;
+            }
+          }
+        }
+        return false;
+      };
+      
+      findAndSelectTag(this.tagTreeData);
     }
   }
 }

+ 187 - 123
web/src/views/aiTagging/externalPage/index.vue

@@ -6,7 +6,23 @@
       <span>系统已自动生成标签推荐结果,请您勾选确认准确标签,针对不符合标签可提交反馈内容以优化后续推荐服务;若无适用标签,可通过手动选标完成打标操作。</span>
       <el-button type="primary" class="confirm-btn" @click="handleConfirmTagging" :disabled="!hasSelectedTag">确认打标</el-button>
     </div>
-    
+    <div class="business-attr-input">
+        <el-input
+          v-model="businessAttr"
+          placeholder="请输入businessAttr"
+          style="width: 200px"
+          suffix-icon="el-icon-search"
+          @keyup.enter="queryBusinessAttr"
+        />
+        <el-button 
+          type="primary" 
+          size="small" 
+          style="margin-left: 8px" 
+          @click="queryBusinessAttr"
+        >
+          查询
+        </el-button>
+      </div>
     <!-- 已选标签 -->
     <div class="selected-tags">
       <div class="tags-label">已选标签:</div>
@@ -20,6 +36,7 @@
           {{ tag }}
         </el-tag>
       </div>
+      
     </div>
     
     <!-- 主体内容 -->
@@ -46,6 +63,7 @@
         <IntelligentRecommend
           v-if="currentMode === 'intelligent'"
           :recommendations="tagRecommendations"
+          :current-system="currentSystem"
           @tagSelect="handleTagSelect"
           @switchToManual="switchToManual"
           @retryIntelligentTagging="retryIntelligentTagging"
@@ -54,7 +72,9 @@
         <!-- 人工打标组件 -->
         <ManualTagging
           v-else
-          :tagTreeData="tagTreeData"
+          :current-system="currentSystem"
+          :tag-systems-data="tagSystemsData"
+          :selected-tags="selectedTags"
           @search="handleSearch"
           @tagCheck="handleTagCheck"
           @switchToIntelligent="switchToIntelligent"
@@ -94,119 +114,33 @@ export default {
       currentMode: 'intelligent',
       
       // 当前选中的标签体系
-      currentSystem: '海洋经济',
+      currentSystem: '',
       
       // 标签体系列表
-      tagSystems: ['海洋经济', '绿色金融', '养老产业', '科技创新', '乡村振兴'],
+      tagSystems: [],
+      
+      // 完整的标签体系数据
+      tagSystemsData: [],
       
-      // 已选标签
-      selectedTags: ['远洋捕捞', '绿色金融', '养老产业', '科技创新', '科技创新'],
+      // 已选标签(包含标签体系id、标签名称、标签编码)
+      selectedTags: [],
       
       // 标签推荐结果
-      tagRecommendations: [
-        {
-          path: '海洋产业 / 海洋渔业 / 海洋捕捞 / 远洋捕捞',
-          basis: '文本中明确提到"主要从事远洋捕捞业务"、"拥有5艘远洋捕捞船"、"购买新的远洋捕捞设备"等关键信息,直接指向"远洋捕捞"标签。企业名称"舟山远洋渔业有限公司"也包含"远洋渔业"关键词,进一步支持此标签。',
-          feedback: '',
-          selected: true
-        },
-        {
-          path: '海洋经济 / 海洋产业 / 海洋船舶工业 / 海洋船舶制造',
-          basis: '文本中明确提到"主要从事远洋捕捞业务"、"拥有5艘远洋捕捞船"、"购买新的远洋捕捞设备"等关键信息,直接指向"远洋捕捞"标签。企业名称"舟山远洋渔业有限公司"也包含"远洋渔业"关键词,进一步支持此标签。',
-          feedback: '',
-          selected: false
-        }
-      ],
+      tagRecommendations: [],
       
-      // 标签树数据
-      tagTreeData: [
-        {
-          id: 1,
-          label: '海洋经济',
-          children: [
-            {
-              id: 2,
-              label: '海洋产业(A)',
-              children: [
-                {
-                  id: 3,
-                  label: '海洋渔业(A1)',
-                  children: [
-                    {
-                      id: 4,
-                      label: '海洋捕捞(A11)',
-                      children: [
-                        {
-                          id: 5,
-                          label: '远洋捕捞(A111)'
-                        }
-                      ]
-                    }
-                  ]
-                }
-              ]
-            },
-            {
-              id: 6,
-              label: '海洋科研教育(B)',
-              children: [
-                {
-                  id: 7,
-                  label: '海洋科研(B1)'
-                }
-              ]
-            },
-            {
-              id: 8,
-              label: '海洋公共管理服务(C)',
-              children: [
-                {
-                  id: 9,
-                  label: '海洋管理(C18)'
-                },
-                {
-                  id: 10,
-                  label: '海洋技术服务(C20)'
-                },
-                {
-                  id: 11,
-                  label: '海洋信息服务(C21)'
-                },
-                {
-                  id: 12,
-                  label: '海洋生态环境保护修复(C22)'
-                },
-                {
-                  id: 13,
-                  label: '海洋地质勘查(C23)',
-                  children: [
-                    {
-                      id: 14,
-                      label: '海洋矿产地质勘查(C231)',
-                      children: [
-                        {
-                          id: 15,
-                          label: '海洋能源矿产地质勘查(C2311)'
-                        },
-                        {
-                          id: 16,
-                          label: '海洋固体矿产地质勘查(C2312)'
-                        },
-                        {
-                          id: 17,
-                          label: '其他海洋矿产地质勘查(C2319)'
-                        }
-                      ]
-                    }
-                  ]
-                }
-              ]
-            }
-          ]
-        }
-      ]
+      // 原始推荐数据(用于过滤)
+      originalRecommendations: [],
+      
+      // businessAttr
+      businessAttr: '',
+      
+
     }
   },
+  mounted() {
+    // 加载标签体系列表
+    this.loadTagSystems();
+  },
   computed: {
     // 检查是否有选中的标签
     hasSelectedTag() {
@@ -240,31 +174,46 @@ export default {
     },
     
     // 选择标签
-    handleTagSelect(index) {
-      this.tagRecommendations[index].selected = !this.tagRecommendations[index].selected;
-      
-      // 更新已选标签列表
-      this.updateSelectedTags();
-    },
-    
-    // 更新已选标签列表
-    updateSelectedTags() {
-      const selectedTagPaths = this.tagRecommendations
-        .filter(tag => tag.selected)
-        .map(tag => tag.path.split('/').pop().trim());
+    handleTagSelect(selectedTags) {
+      // 从选中的标签中提取完整信息(标签体系id、标签名称、标签编码)
+      const selectedTagInfos = selectedTags
+        .map(tag => ({
+          categoryId: tag.category_id,
+          tagName: tag.tag_name,
+          tagCode: tag.tag_code
+        }));
       
-      // 去重并更新已选标签
-      this.selectedTags = [...new Set(selectedTagPaths)];
+      // 更新已选标签
+      this.selectedTags = selectedTagInfos;
     },
     
     // 切换到人工打标
     switchToManual() {
       this.currentMode = 'manual';
+      // 切换后,手动触发一次标签树数据加载,确保数据正确
+      // 注意:实际加载逻辑在ManualTagging组件的watch中处理
     },
     
     // 切换到智能推荐
     switchToIntelligent() {
       this.currentMode = 'intelligent';
+      // 更新智能推荐的选中状态
+      this.updateIntelligentSelectedState();
+    },
+    
+    // 更新智能推荐的选中状态
+    updateIntelligentSelectedState() {
+      if (!this.tagRecommendations.length) {
+        return;
+      }
+      
+      // 遍历推荐标签,检查是否在已选标签列表中
+      this.tagRecommendations.forEach(tag => {
+        // 检查是否在已选标签列表中(通过标签编码匹配)
+        tag.selected = this.selectedTags.some(selectedTag => 
+          selectedTag.tagCode === tag.tag_code
+        );
+      });
     },
     
     // 重新发起智能打标
@@ -276,7 +225,56 @@ export default {
     // 选择标签体系
     handleSystemSelect(system) {
       this.currentSystem = system;
-      // 这里可以添加切换标签体系后重新加载数据的逻辑
+      // 过滤智能推荐数据,只展示当前体系下的推荐标签
+      this.filterRecommendationsBySystem();
+    },
+    
+    // 根据当前选中的标签体系过滤智能推荐数据
+    filterRecommendationsBySystem() {
+      // 保存原始推荐数据
+      if (!this.originalRecommendations) {
+        this.originalRecommendations = [...this.tagRecommendations];
+      }
+      
+      // 查找当前选中的标签体系对应的id
+      const selectedSystem = this.tagSystemsData.find(system => system.categoryNm === this.currentSystem);
+      
+      // 如果没有选中标签体系,展示全部数据
+      if (!selectedSystem || !selectedSystem.id) {
+        this.tagRecommendations = [...this.originalRecommendations];
+        return;
+      }
+      
+      // 过滤推荐数据,只保留与当前体系id匹配的推荐标签
+      this.tagRecommendations = this.originalRecommendations.filter(tag => {
+        return tag.category_id == selectedSystem.id;
+      });
+    },
+    
+    // 加载标签体系列表
+    loadTagSystems() {
+      yufp.service.request({
+        url: backend.tagServer + "/api/aitag-tagcategory/enablelistnoauth",
+        method: 'get',
+        data: {},
+        callback: (code, error, response) => {
+          if (response.code == '0') {
+            // 处理返回的标签体系数据
+            const systems = response.data || [];
+            // 保存完整的标签体系数据
+            this.tagSystemsData = systems;
+            // 更新标签体系列表,使用categoryNm字段
+            this.tagSystems = systems.map(system => system.categoryNm || '').filter(name => name);
+            
+            // 不默认选中任何标签体系
+            // if (this.tagSystems.length > 0) {
+            //   this.currentSystem = this.tagSystems[0];
+            // }
+          } else {
+            this.$message.error(response.message || '获取标签体系列表失败');
+          }
+        }
+      });
     },
     
     // 处理搜索
@@ -288,13 +286,72 @@ export default {
     // 处理标签勾选
     handleTagCheck(data, checkedInfo) {
       console.log('勾选标签:', data, checkedInfo);
-      // 这里可以添加处理标签勾选的逻辑
+      
+      // 查找当前选中的标签体系对应的id
+      const selectedSystem = this.tagSystemsData.find(system => system.categoryNm === this.currentSystem);
+      if (!selectedSystem || !selectedSystem.id) {
+        return;
+      }
+      
+      // 过滤掉当前体系的标签,保留其他体系的标签
+      const otherSystemTags = this.selectedTags.filter(tag => tag.categoryId !== selectedSystem.id);
+      
+      // 添加当前选择的标签(包含标签体系id、标签名称、标签编码)
+      const newSelectedTags = [...otherSystemTags, {
+        categoryId: selectedSystem.id,
+        tagName: data.tagNm,
+        tagCode: data.tagCode
+      }];
+      
+      // 更新已选标签
+      this.selectedTags = newSelectedTags;
+      
+      // 更新智能推荐的选中状态
+      this.updateIntelligentSelectedState();
     },
     
     // 获取标签类型
     getTagType(index) {
       const types = ['', 'success', 'warning', 'danger', 'info'];
       return types[index % types.length];
+    },
+    
+    // 查询businessAttr
+    queryBusinessAttr() {
+      if (!this.businessAttr) {
+        this.$message.warning('请输入businessAttr');
+        return;
+      }
+      
+      yufp.service.request({
+        url: backend.tagServer + "/api/fastapi/query",
+        method: 'get',
+        data: {
+          businessAttr: this.businessAttr
+        },
+        callback: (code, error, response) => {
+          if (response.code == '0') {
+            console.log('查询结果:', response.data);
+            // 处理返回的数据,直接使用接口返回的字段
+            if (response.data && response.data.data && response.data.data.result) {
+              const result = response.data.data.result;
+              // 更新原始推荐数据
+              this.originalRecommendations = result.map(item => ({
+                ...item,
+                feedback: '',
+                selected: false
+              }));
+              // 切换到智能推荐模式
+              this.currentMode = 'intelligent';
+              // 根据当前选中的标签体系过滤推荐数据
+              this.filterRecommendationsBySystem();
+            }
+            this.$message.success('查询成功');
+          } else {
+            this.$message.error(response.message || '查询失败');
+          }
+        }
+      });
     }
   }
 }
@@ -343,6 +400,13 @@ export default {
   border-bottom: 1px solid #EBEEF5;
 }
 
+.business-attr-input {
+  margin-left: auto;
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+}
+
 .tags-label {
   font-size: 14px;
   color: #606266;

+ 131 - 75
web/src/views/aiTagging/taggingDetail/index.vue

@@ -12,23 +12,23 @@
       <div class="info-grid">
         <div class="info-item">
           <span class="label">贷款申请编号:</span>
-          <span class="value">{{ businessInfo.loanApplyNo }}</span>
+          <span class="value">{{ businessInfo.businessAttr }}</span>
         </div>
         <div class="info-item">
           <span class="label">合同编号:</span>
           <span class="value">{{ businessInfo.contractNo }}</span>
         </div>
-        <div class="info-item">
-          <span class="label">投向:</span>
-          <span class="value">{{ businessInfo.investmentDirection }}</span>
+        <div class="info-item full-width">
+          <span class="label">职业:</span>
+          <span class="value">{{ this.extractInfoFromPhrase('职业') }}</span>
         </div>
-        <div class="info-item">
-          <span class="label">调查报告:</span>
-          <a href="#" class="value file-link">{{ businessInfo.reportName }}</a>
+        <div class="info-item full-width">
+          <span class="label">投向:</span>
+          <span class="value">{{ this.extractInfoFromPhrase('投向') }}</span>
         </div>
         <div class="info-item full-width">
           <span class="label">贷款用途:</span>
-          <span class="value">{{ businessInfo.loanPurpose }}</span>
+          <span class="value">{{ this.extractInfoFromPhrase('用途') }}</span>
         </div>
       </div>
     </div>
@@ -40,36 +40,30 @@
         <div class="tagging-time">打标时间:{{ taggingInfo.taggingTime }}</div>
       </div>
       
+      <!-- 反馈信息 -->
+      <div class="feedback-info">
+        <span class="meta-item">法人机构:-</span>
+        <span class="meta-item">网点:-</span>
+        <span class="meta-item">客户经理:-</span>
+        <span class="meta-item">反馈时间:-</span>
+        <el-tag size="small">未反馈</el-tag>
+      </div>
+      
       <!-- 打标结果列表 -->
       <div class="tagging-results">
         <div v-for="(result, index) in taggingInfo.results" :key="index" class="tagging-item">
           <div class="tagging-header">
             <div class="tag-path">{{ result.tagPath }}</div>
-            <div class="tag-meta">
-              <span class="labeler">标注人员:{{ result.labeler }}</span>
-              <span class="tag-time">标注时间:{{ result.tagTime }}</span>
-              <el-tag :type="result.isCorrect ? 'success' : 'danger'" size="small">
-                {{ result.isCorrect ? '正确' : '不准确' }}
-              </el-tag>
-            </div>
           </div>
           
           <div class="tagging-details">
             <div class="detail-item">
-              <span class="detail-label">打标手段:</span>
-              <input type="text" class="detail-input" :value="result.tagMethod" readonly>
-            </div>
-            <div class="detail-item">
-              <span class="detail-label">智能体:</span>
-              <input type="text" class="detail-input" :value="result.agent" readonly>
-            </div>
-            <div class="detail-item">
               <span class="detail-label">打标依据:</span>
-              <textarea class="detail-textarea" :value="result.tagBasis" readonly></textarea>
+              <div class="detail-content">{{ result.tagBasis }}</div>
             </div>
             <div class="detail-item">
-              <span class="detail-label">标签验证结果:</span>
-              <textarea class="detail-textarea" :value="result.validationResult" readonly></textarea>
+              <span class="detail-label">打标结果反馈:</span>
+              <div class="detail-content">暂未反馈</div>
             </div>
           </div>
         </div>
@@ -97,50 +91,90 @@ export default {
     }
   },
   created() {
-    // 从路由参数中获取贷款申请编号
-    const loanApplyNo = this.$route.query.loanApplyNo
-    if (loanApplyNo) {
-      this.loadDetailData(loanApplyNo)
+    // 从路由参数中获取id或loanApplyNo
+    const id = this.$route.query.id || this.$route.query.loanApplyNo
+    if (id) {
+      this.loadDetailData(id)
     }
   },
   methods: {
     // 加载详情数据
-    loadDetailData(loanApplyNo) {
-      // 模拟数据,实际项目中应该调用后端接口
-      this.businessInfo = {
-        loanApplyNo: 'AP20240302001',
-        contractNo: 'CT20250203002',
-        investmentDirection: 'A041 水产养殖',
-        reportName: 'XX渔业有限公司调查报告.pdf',
-        loanPurpose: '用于购买远洋捕捞设备及船舶升级改造,提升捕捞能力和作业安全系数。项目预计增加捕捞量30%,年新增产值约1200万元。'
+    loadDetailData(id) {
+      // 调用后端接口
+      yufp.service.request({
+        url: backend.tagServer + "/api/aitagtaglog/show/" + id,
+        method: 'get',
+        data: {},
+        callback: (code, error, response) => {
+          if (response.code == '0') {
+            const data = response.data || {};
+            // 直接使用接口返回的所有字段
+            this.businessInfo = data;
+            
+            // 更新打标信息
+            this.taggingInfo = {
+              taggingTime: data.insertTime || '',
+              results: this.parseTagResults(data.result || '', data)
+            };
+          } else {
+            this.$message.error(response.message || '获取打标详情失败');
+          }
+        }
+      });
+    },
+    
+    // 解析标签结果
+    parseTagResults(resultStr, data) {
+      if (!resultStr) return [];
+      try {
+        const resultArray = JSON.parse(resultStr);
+        return resultArray.map(item => ({
+          tagPath: item.tag_path || '',
+          labeler: data.feedbackUserNm || '',
+          tagTime: data.insertTime || '',
+          isCorrect: item.passr || false,
+          tagMethod: '', // 接口可能没有此字段
+          agent: '', // 接口可能没有此字段
+          tagBasis: item.desc || '',
+          validationResult: item.passr ? '打标正确' : '打标不准确'
+        }));
+      } catch (error) {
+        console.error('解析标签结果失败:', error);
+        return [];
       }
+    },
+    
+    // 从短语中提取信息
+    extractInfoFromPhrase(key) {
+      if (!this.businessInfo || !this.businessInfo.phrase) return '';
       
-      this.taggingInfo = {
-        taggingTime: '2026-02-06 17:00:30',
-        results: [
-          {
-            tagPath: '海洋经济 / 海洋产业 / 海洋渔业 / 海洋捕捞 / 远洋捕捞',
-            labeler: '王武',
-            tagTime: '2026-02-07 15:30:43',
-            isCorrect: true,
-            tagMethod: '',
-            agent: '',
-            tagBasis: '文本中明确提到"主要从事远洋捕捞业务"、"拥有5艘远洋捕捞船"、"购买新的远洋捕捞设备"等关键信息,直接指向"远洋捕捞"标签。企业名称"舟山远洋渔业有限公司"也包含"远洋渔业"关键词,进一步支持此标签。',
-            validationResult: '打标正确'
-          },
-          {
-            tagPath: '海洋经济 / 海洋产业 / 海洋船舶工业 / 海洋船舶制造',
-            labeler: '王武',
-            tagTime: '2026-02-07 15:31:12',
-            isCorrect: false,
-            tagMethod: '',
-            agent: '',
-            tagBasis: '文本中明确提到"主要从事远洋捕捞业务"、"拥有5艘远洋捕捞船"、"购买新的远洋捕捞设备"等关键信息,直接指向"远洋捕捞"标签。企业名称"舟山远洋渔业有限公司"也包含"远洋渔业"关键词,进一步支持此标签。',
-            validationResult: '文本中"远洋捕捞船""远洋捕捞设备"等关键词,描述的是企业的生产工具,而非其生产制造的产品,因此不应归类为"海洋船舶制造"。'
-          }
-        ]
+      const phrase = this.businessInfo.phrase;
+      console.log('提取信息:', key, 'from phrase:', phrase);
+      
+      // 最简单直接的方法:查找key:的位置,然后截取到下一个空格或结束
+      const startIndex = phrase.indexOf(`${key}:`);
+      if (startIndex === -1) {
+        console.log('未找到', key, '在短语中');
+        return '';
+      }
+      
+      const valueStart = startIndex + key.length + 1;
+      const nextSpaceIndex = phrase.indexOf(' ', valueStart);
+      
+      let value;
+      if (nextSpaceIndex === -1) {
+        // 到短语结束
+        value = phrase.substring(valueStart);
+      } else {
+        // 到下一个空格
+        value = phrase.substring(valueStart, nextSpaceIndex);
       }
+      
+      value = value.trim();
+      console.log('提取到的', key, ':', value);
+      return value;
     },
+    
     // 返回上一页
     handleBack() {
       this.$router.back()
@@ -205,10 +239,23 @@ export default {
   color: #606266;
 }
 
+/* 反馈信息 */
+.feedback-info {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+  padding: 12px 16px;
+  background-color: #f5f7fa;
+  border-radius: 4px;
+  margin-bottom: 20px;
+  font-size: 14px;
+  color: #606266;
+}
+
 /* 信息网格 */
 .info-grid {
   display: grid;
-  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+  grid-template-columns: repeat(2, 1fr);
   gap: 16px;
 }
 
@@ -226,6 +273,7 @@ export default {
   margin-right: 8px;
   min-width: 100px;
   color: #606266;
+  text-align: right;
 }
 
 .value {
@@ -279,6 +327,10 @@ export default {
   color: #606266;
 }
 
+.meta-item {
+  white-space: nowrap;
+}
+
 /* 打标详情 */
 .tagging-details {
   display: flex;
@@ -297,24 +349,17 @@ export default {
   min-width: 100px;
   color: #606266;
   flex-shrink: 0;
+  padding-top: 4px;
 }
 
-.detail-input {
-  flex: 1;
-  padding: 8px;
-  border: 1px solid #dcdfe6;
-  border-radius: 4px;
-  background-color: #fff;
-}
-
-.detail-textarea {
+.detail-content {
   flex: 1;
-  padding: 8px;
+  padding: 10px;
   border: 1px solid #dcdfe6;
   border-radius: 4px;
   background-color: #fff;
-  min-height: 100px;
-  resize: vertical;
+  line-height: 1.5;
+  min-height: 80px;
 }
 
 /* 响应式设计 */
@@ -348,6 +393,17 @@ export default {
     gap: 8px;
   }
 
+  .tagging-header {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 10px;
+  }
+
+  .tag-meta {
+    flex-wrap: wrap;
+    gap: 8px;
+  }
+
   .detail-item {
     flex-direction: column;
     gap: 8px;

+ 31 - 1
web/src/views/aiTagging/taggingLogs/index.vue

@@ -97,11 +97,26 @@ export default {
   methods: {
     // 加载日志列表
     loadLogs() {
+      // 处理时间参数
+      let startTime = '';
+      let endTime = '';
+      
+      if (this.searchForm.callTime && this.searchForm.callTime.length === 2) {
+        // 格式化开始时间为 2026-03-02 00:00:00
+        const start = new Date(this.searchForm.callTime[0]);
+        startTime = this.formatDateTime(start, 'start');
+        
+        // 格式化结束时间为 2026-03-02 23:59:59
+        const end = new Date(this.searchForm.callTime[1]);
+        endTime = this.formatDateTime(end, 'end');
+      }
+      
       yufp.service.request({
         url: backend.tagServer + "/api/aitag-apilog/list",
         method: 'get',
         data: {
-          callTime: this.searchForm.callTime,
+          startTime: startTime,
+          endTime: endTime,
           page: this.currentPage,
           pageSize: this.pageSize
         },
@@ -178,6 +193,21 @@ export default {
       console.log('当前页码:', current)
       this.currentPage = current
       this.loadLogs();
+    },
+    
+    // 格式化日期时间
+    formatDateTime(date, type) {
+      const year = date.getFullYear();
+      const month = String(date.getMonth() + 1).padStart(2, '0');
+      const day = String(date.getDate()).padStart(2, '0');
+      
+      if (type === 'start') {
+        return `${year}-${month}-${day} 00:00:00`;
+      } else if (type === 'end') {
+        return `${year}-${month}-${day} 23:59:59`;
+      }
+      
+      return '';
     }
   }
 }

+ 99 - 33
web/src/views/aiTagging/taggingResults/components/Detail.vue

@@ -24,16 +24,20 @@
     <!-- 数据表格 -->
     <div class="table-container">
       <el-table :data="detailData" style="width: 100%">
-        <el-table-column prop="loanApplyNo" label="贷款申请编号" width="180"></el-table-column>
+        <el-table-column prop="businessAttr" label="贷款申请编号" width="180"></el-table-column>
         <el-table-column prop="contractNo" label="合同编号" width="180"></el-table-column>
-        <el-table-column prop="taggingTime" label="打标时间" width="180"></el-table-column>
-        <el-table-column prop="tags" label="智能标签">
+        <el-table-column label="打标时间" width="180">
           <template slot-scope="scope">
-            <el-tag v-for="(tag, index) in scope.row.tags" :key="index" size="small" class="tag-item">{{ tag }}</el-tag>
+            {{ scope.row.insertTime || '-' }}
           </template>
         </el-table-column>
-        <el-table-column prop="operator" label="标注人员" width="120"></el-table-column>
-        <el-table-column prop="action" label="操作" width="100">
+        <el-table-column label="智能标签">
+          <template slot-scope="scope">
+            <el-tag v-for="(tag, index) in parseTags(scope.row.result)" :key="index" size="small" class="tag-item">{{ tag.label }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="feedbackUserNm" label="标注人员" width="120"></el-table-column>
+        <el-table-column label="操作" width="100">
           <template slot-scope="scope">
             <el-button type="text" @click="handleViewDetail(scope.row)">查看详情</el-button>
           </template>
@@ -60,22 +64,6 @@
 export default {
   name: 'Detail',
   props: {
-    detailData: {
-      type: Array,
-      default: () => []
-    },
-    totalDetailCount: {
-      type: Number,
-      default: 0
-    },
-    currentDetailPage: {
-      type: Number,
-      default: 1
-    },
-    detailPageSize: {
-      type: Number,
-      default: 10
-    },
     searchParams: {
       type: Object,
       default: () => ({
@@ -87,6 +75,12 @@ export default {
   },
   data() {
     return {
+      // 明细数据
+      detailData: [],
+      // 分页数据
+      totalDetailCount: 0,
+      currentDetailPage: 1,
+      detailPageSize: 10,
       // 明细搜索表单
       detailSearchForm: {
         loanApplyNo: '',
@@ -94,6 +88,11 @@ export default {
       }
     }
   },
+  mounted() {
+    // 首次加载数据
+    this.loadDetailData()
+  },
+  
   watch: {
     // 监听搜索参数变化,重新加载数据
     searchParams: {
@@ -107,18 +106,24 @@ export default {
   methods: {
     // 加载明细数据
     loadDetailData() {
+      // 确保分页参数有默认值
+      const currentPage = this.currentDetailPage || 1;
+      const pageSize = this.detailPageSize || 10;
+      
       // 构建请求参数
       const params = {
         startTaggingTime: this.searchParams.dateRange[0] || '',
         endTaggingTime: this.searchParams.dateRange[1] || '',
-        categoryCode: this.searchParams.tagSystem || '',
+        categoryId: this.searchParams.tagSystem || '',
         sort: this.searchParams.tagSort || 'desc',
-        page: this.searchParams.page || this.currentDetailPage,
-        size: this.searchParams.pageSize || this.detailPageSize,
+        page: currentPage, // 接口要求从1开始
+        size: pageSize,
         loanApplicationNo: this.detailSearchForm.loanApplyNo || '',
         contractNo: this.detailSearchForm.contractNo || ''
       };
       
+      console.log('请求明细数据参数:', params);
+      
       // 调用后端接口
       yufp.service.request({
         url: backend.tagServer + "/api/aitagtaglog/taggingTransaction",
@@ -126,8 +131,8 @@ export default {
         data: params,
         callback: (code, error, response) => {
           if (response.code == '0') {
-            // 更新明细数据
-            this.detailList = response.data || [];
+            // 更新内部数据
+            this.detailData = response.data || [];
             this.totalDetailCount = response.total || 0;
           } else {
             this.$message.error(response.message || '获取明细数据失败');
@@ -144,9 +149,10 @@ export default {
         ...this.detailSearchForm
       };
       console.log('明细搜索:', searchParams)
+      // 重置页码为第一页
+      this.$emit('update:currentDetailPage', 1);
       // 调用接口获取明细数据
       this.loadDetailData()
-      this.$message.success('搜索成功')
     },
 
     // 重置明细搜索
@@ -164,16 +170,56 @@ export default {
       // 跳转到详情页面
       this.$router.push({
         path: '/taggingDetail',
-        query: { loanApplyNo: row.loanApplyNo }
+        query: { id: row.id || row.businessAttr }
       })
     },
 
     // 导出数据
     handleExport() {
-      console.log('导出数据')
-      this.$emit('export-data')
-      this.$message.success('导出成功')
-      // 这里可以调用后端接口导出数据
+      // 构建请求参数
+      const exportParams = {
+        startTaggingTime: this.searchParams.dateRange ? this.searchParams.dateRange[0] : '',
+        endTaggingTime: this.searchParams.dateRange ? this.searchParams.dateRange[1] : '',
+        categoryId: this.searchParams.tagSystem || '',
+        loanApplicationNo: this.detailSearchForm.loanApplyNo || '',
+        contractNo: this.detailSearchForm.contractNo || ''
+      };
+      
+      console.log('导出数据请求参数:', exportParams);
+      
+      // 调用导出接口
+      yufp.service.request({
+        url: backend.tagServer + "/api/aitagtaglog/exportData",
+        method: 'get',
+        params: exportParams,
+        responseType: 'blob',
+        callback: (code, error, response) => {
+          if (response && response.data) {
+            // 处理导出成功的逻辑
+            // 创建下载链接
+            let blob;
+            if (response.data instanceof Blob) {
+              blob = response.data;
+            } else {
+              blob = new Blob([response.data]);
+            }
+            
+            const url = URL.createObjectURL(blob);
+            const a = document.createElement('a');
+            a.href = url;
+            a.download = `打标结果_${new Date().toISOString().slice(0, 10)}.xlsx`;
+            document.body.appendChild(a);
+            a.click();
+            document.body.removeChild(a);
+            URL.revokeObjectURL(url);
+            this.$message.success('数据导出成功');
+            // 触发父组件事件
+            this.$emit('export-data');
+          } else {
+            this.$message.error('数据导出失败');
+          }
+        }
+      });
     },
 
     // 明细分页大小变化
@@ -190,6 +236,26 @@ export default {
       this.currentDetailPage = current
       // 重新加载数据
       this.loadDetailData()
+    },
+    
+    // 解析标签数据
+    parseTags(result) {
+      if (!result) return [];
+      try {
+        const tags = JSON.parse(result);
+        if (Array.isArray(tags)) {
+          return tags.map(tag => ({
+            label: tag.tag_name || tag.label || '',
+            code: tag.tag_code || tag.label_code || '',
+            desc: tag.desc || '',
+            passr: tag.passr || false
+          }));
+        }
+        return [];
+      } catch (error) {
+        console.error('解析标签数据失败:', error);
+        return [];
+      }
     }
   }
 }

+ 101 - 42
web/src/views/aiTagging/taggingResults/components/Overview.vue

@@ -32,12 +32,6 @@
       <div class="chart-item">
         <div class="chart-header">
           <h3>打标趋势</h3>
-          <div class="chart-period">
-            <el-radio-group v-model="trendPeriod" size="small">
-              <el-radio-button label="day">日</el-radio-button>
-              <el-radio-button label="month">月</el-radio-button>
-            </el-radio-group>
-          </div>
         </div>
         <div ref="trendChart" class="chart-content"></div>
       </div>
@@ -46,12 +40,6 @@
       <div class="chart-item">
         <div class="chart-header">
           <h3>标签分布统计</h3>
-          <div class="chart-sort">
-            <el-select v-model="tagSort" placeholder="数量降序" size="small" style="width: 120px;">
-              <el-option label="数量降序" value="desc"></el-option>
-              <el-option label="数量升序" value="asc"></el-option>
-            </el-select>
-          </div>
         </div>
         <div ref="distributionChart" class="chart-content"></div>
       </div>
@@ -93,14 +81,6 @@ export default {
     }
   },
   props: {
-    trendPeriod: {
-      type: String,
-      default: 'month'
-    },
-    tagSort: {
-      type: String,
-      default: 'desc'
-    },
     searchParams: {
       type: Object,
       default: () => ({
@@ -114,8 +94,26 @@ export default {
     this.$nextTick(() => {
       this.loadOverviewData()
       this.initCharts()
+      // 添加窗口大小变化监听
+      this.resizeHandler = () => {
+        this.resizeCharts()
+      }
+      window.addEventListener('resize', this.resizeHandler)
     })
   },
+  beforeDestroy() {
+    // 移除窗口大小变化监听
+    if (this.resizeHandler) {
+      window.removeEventListener('resize', this.resizeHandler)
+    }
+    // 销毁图表实例
+    if (this.trendChart) {
+      this.trendChart.dispose()
+    }
+    if (this.distributionChart) {
+      this.distributionChart.dispose()
+    }
+  },
   watch: {
     // 监听搜索参数变化,重新加载数据
     searchParams: {
@@ -125,14 +123,6 @@ export default {
         this.loadDistributionData()
       },
       deep: true
-    },
-    // 监听趋势周期变化,重新加载趋势数据
-    trendPeriod() {
-      this.loadTrendData()
-    },
-    // 监听排序方式变化,重新加载分布数据
-    tagSort() {
-      this.loadDistributionData()
     }
   },
 
@@ -143,7 +133,7 @@ export default {
       const params = {
         startTaggingTime: this.searchParams.dateRange[0] || '',
         endTaggingTime: this.searchParams.dateRange[1] || '',
-        categoryCode: this.searchParams.tagSystem || ''
+        categoryId: this.searchParams.tagSystem || ''
       };
       console.log('加载概览数据参数:', params);
       
@@ -176,8 +166,7 @@ export default {
       const params = {
         startTaggingTime: this.searchParams.dateRange[0] || '',
         endTaggingTime: this.searchParams.dateRange[1] || '',
-        categoryCode: this.searchParams.tagSystem || '',
-        statisticalPeriod: this.trendPeriod
+        categoryId: this.searchParams.tagSystem || ''
       };
       console.log('加载打标趋势数据参数:', params);
       
@@ -203,8 +192,7 @@ export default {
       const params = {
         startTaggingTime: this.searchParams.dateRange[0] || '',
         endTaggingTime: this.searchParams.dateRange[1] || '',
-        categoryCode: this.searchParams.tagSystem || '',
-        sort: this.tagSort
+        categoryId: this.searchParams.tagSystem || ''
       };
       console.log('加载标签分布统计数据参数:', params);
       
@@ -230,8 +218,8 @@ export default {
         return
       }
       
-      const labels = data.map(item => item.label || '')
-      const values = data.map(item => item.value || 0)
+      const labels = data.map(item => item.stat || '')
+      const values = data.map(item => parseInt(item.val) || 0)
       
       const option = {
         tooltip: {
@@ -271,46 +259,107 @@ export default {
     
     // 更新标签分布图表
     updateDistributionChart(data) {
-      if (!this.distributionChart || !data || data.length === 0) {
+      if (!this.distributionChart) {
+        return
+      }
+      
+      if (!data || data.length === 0) {
+        // 无数据时的处理
+        this.distributionChart.setOption({
+          grid: {
+            left: '3%',
+            right: '4%',
+            bottom: '10%',
+            top: '10%',
+            containLabel: true
+          },
+          xAxis: {
+            type: 'category',
+            data: []
+          },
+          yAxis: {
+            type: 'value'
+          },
+          series: [],
+          graphic: {
+            type: 'text',
+            left: 'center',
+            top: 'center',
+            style: {
+              text: '暂无数据',
+              fontSize: 14,
+              fill: '#999'
+            }
+          }
+        })
         return
       }
       
-      const names = data.map(item => item.name || '')
-      const values = data.map(item => item.value || 0)
+      // 对数据进行排序
+      data.sort((a, b) => parseInt(b.val) - parseInt(a.val))
+      
+      const names = data.map(item => {
+        // 截断长标签,最多显示5个字
+        const label = item.stat || ''
+        return label.length > 5 ? label.substring(0, 5) + '...' : label
+      })
+      const values = data.map(item => parseInt(item.val) || 0)
+      const fullNames = data.map(item => item.stat || '')
       
       const option = {
         tooltip: {
           trigger: 'axis',
           axisPointer: {
             type: 'shadow'
+          },
+          formatter: function(params) {
+            const index = params[0].dataIndex
+            return `${fullNames[index]}: ${values[index]}`
           }
         },
         grid: {
           left: '3%',
           right: '4%',
-          bottom: '3%',
+          bottom: '10%', // 减少底部空间
+          top: '10%', // 调整顶部空间
           containLabel: true
         },
         xAxis: {
           type: 'category',
           data: names,
           axisLabel: {
-            rotate: 45
+            rotate: 45,
+            interval: 0,
+            fontSize: 10
           }
         },
         yAxis: {
-          type: 'value'
+          type: 'value',
+          name: '数量'
         },
         series: [
           {
             name: '标签数量',
             type: 'bar',
             data: values,
+            barWidth: '60%', // 调整柱形宽度
             itemStyle: {
               color: function(params) {
-                const colors = ['#1890ff', '#36cfc9', '#52c41a', '#faad14', '#722ed1', '#eb2f96']
+                // 使用渐变色
+                const colors = [
+                  '#1890ff', '#36cfc9', '#52c41a', 
+                  '#faad14', '#722ed1', '#eb2f96',
+                  '#fa541c', '#a0d911'
+                ]
                 return colors[params.dataIndex % colors.length]
               }
+            },
+            emphasis: {
+              itemStyle: {
+                shadowBlur: 10,
+                shadowOffsetX: 0,
+                shadowColor: 'rgba(0, 0, 0, 0.5)'
+              }
             }
           }
         ]
@@ -440,6 +489,16 @@ export default {
       } catch (error) {
         console.error('Error initializing distribution chart:', error);
       }
+    },
+    
+    // 调整图表大小
+    resizeCharts() {
+      if (this.trendChart) {
+        this.trendChart.resize()
+      }
+      if (this.distributionChart) {
+        this.distributionChart.resize()
+      }
     }
   }
 }

+ 37 - 15
web/src/views/aiTagging/taggingResults/index.vue

@@ -6,21 +6,19 @@
         <el-form-item label="打标时间:">
           <el-date-picker
             v-model="searchForm.dateRange"
-            type="daterange"
+            type="datetimerange"
             range-separator="~"
             start-placeholder="开始日期"
             end-placeholder="结束日期"
+            format="yyyy-MM-dd HH:mm:ss"
+            value-format="yyyy-MM-dd HH:mm:ss"
             style="width: 300px;"
           ></el-date-picker>
         </el-form-item>
         <el-form-item label="标签体系:">
           <el-select v-model="searchForm.tagSystem" placeholder="全部" style="width: 200px;">
             <el-option label="全部" value=""></el-option>
-            <el-option label="海洋经济" value="ocean"></el-option>
-            <el-option label="绿色金融" value="green"></el-option>
-            <el-option label="养老产业" value="elderly"></el-option>
-            <el-option label="科技创新" value="tech"></el-option>
-            <el-option label="乡村振兴" value="rural"></el-option>
+            <el-option v-for="(system, index) in tagSystems" :key="index" :label="system.categoryNm" :value="system.categoryId"></el-option>
           </el-select>
         </el-form-item>
         <el-form-item>
@@ -48,18 +46,9 @@
       <el-tab-pane label="打标明细" name="detail">
         <Detail 
           ref="detailRef"
-          :detail-data="detailData"
-          :total-detail-count="totalDetailCount"
-          :current-detail-page="currentDetailPage"
-          :detail-page-size="detailPageSize"
           :search-params="searchForm"
-          :tag-sort="tagSort"
-          @detail-search="handleDetailSearch"
-          @detail-reset="handleDetailReset"
           @view-detail="handleViewDetail"
           @export-data="handleExport"
-          @size-change="handleDetailSizeChange"
-          @current-change="handleDetailCurrentChange"
         />
       </el-tab-pane>
     </el-tabs>
@@ -87,6 +76,9 @@ export default {
         tagSystem: ''
       },
       
+      // 标签体系列表
+      tagSystems: [],
+      
       // 打标趋势周期
       trendPeriod: 'month',
       
@@ -186,6 +178,16 @@ export default {
   mounted() {
     // 初始化图表
     this.initCharts()
+    // 加载标签体系列表
+    this.loadTagSystems()
+  },
+  watch: {
+    'searchForm.tagSystem': {
+      handler(newVal, oldVal) {
+        console.log('tagSystem变化:', oldVal, '->', newVal);
+      },
+      immediate: true
+    }
   },
   methods: {
     // 搜索
@@ -214,6 +216,26 @@ export default {
         this.$refs.detailRef.handleDetailSearch();
       }
     },
+    
+    // 加载标签体系列表
+    loadTagSystems() {
+      yufp.service.request({
+        url: backend.tagServer + "/api/aitag-tagcategory/enablelist",
+        method: 'get',
+        data: {},
+        callback: (code, error, response) => {
+          if (response.code == '0') {
+            this.tagSystems = (response.data || []).map(item => ({
+              ...item,
+              categoryId: item.categoryId || item.id || ''
+            }));
+            console.log('标签体系数据:', this.tagSystems);
+          } else {
+            this.$message.error(response.message || '获取标签体系列表失败');
+          }
+        }
+      });
+    },
 
     // 初始化图表
     initCharts() {

+ 58 - 24
web/src/views/aiTagging/taggingSystemManage/TagSystemEdit.vue

@@ -7,16 +7,10 @@
         <p class="system-description">{{ currentSystem.description }}</p>
       </div>
       <div class="header-right">
-        <el-switch v-model="currentSystem.status" active-text="已启用" inactive-text="已停用"></el-switch>
+        <el-switch v-model="currentSystem.status" active-text="已启用" inactive-text="已停用" @change="handleStatusChange"></el-switch>
       </div>
     </div>
 
-    <!-- 提示信息 -->
-    <div class="warning-box">
-      <i class="el-icon-warning-outline"></i>
-      <span>调整标签层级仅影响后续发起的打标任务,不影响历史已完成的打标结果。修改前请确保已充分评估对业务的影响</span>
-    </div>
-
     <!-- 标签体系编辑区 -->
     <div class="tag-edit-area">
       <!-- 选项卡导航 -->
@@ -27,6 +21,7 @@
             <TagConfig 
               :tag-system="currentSystem"
               :tree-props="treeProps"
+              :system-status="currentSystem.status"
               @node-click="handleNodeClick"
               @dropdown-command="handleDropdownCommand"
               @edit-tag="handleEditTag"
@@ -68,10 +63,10 @@ export default {
     return {
       // 当前编辑的标签体系
       currentSystem: {
-        id: 1,
-        name: '海洋经济',
-        status: 'enabled',
-        description: '涵盖海洋渔业、海洋装备、海洋旅游等海洋经济相关产业的标签分类体系'
+        id: '',
+        name: '加载中...',
+        status: false,
+        description: ''
       },
       
       // 搜索关键词
@@ -80,7 +75,7 @@ export default {
       // 树节点配置
       treeProps: {
         children: 'children',
-        label: 'label'
+        label: 'tagNm'
       },
       
       // 当前激活的选项卡
@@ -94,16 +89,33 @@ export default {
     // 加载标签体系数据
     loadTagSystemData() {
       // 从路由参数中获取标签体系ID
-      const systemId = parseInt(this.$route.query.id) || 1;
+      const systemId = this.$route.query.id;
+      if (!systemId) {
+        this.$message.error('缺少标签体系ID');
+        return;
+      }
+      
       console.log('加载标签体系数据:', systemId);
-      // 这里可以根据systemId调用后端接口获取数据
-      // 模拟数据加载
-      this.currentSystem = {
-        id: systemId,
-        name: '海洋经济',
-        status: 'enabled',
-        description: '涵盖海洋渔业、海洋装备、海洋旅游等海洋经济相关产业的标签分类体系'
-      };
+      
+      // 调用后端接口获取标签体系详情
+      yufp.service.request({
+        url: backend.tagServer + "/api/aitag-tagcategory/detail/" + systemId,
+        method: 'get',
+        data: {},
+        callback: (code, error, response) => {
+          if (response.code == '0') {
+            const data = response.data || {};
+            this.currentSystem = {
+              id: data.id,
+              name: data.categoryNm || '未命名',
+              status: data.state === 0,
+              description: data.categoryDesc || ''
+            };
+          } else {
+            this.$message.error(response.message || '获取标签体系详情失败');
+          }
+        }
+      });
     },
 
     // 节点点击
@@ -152,8 +164,7 @@ export default {
     // 刷新节点
     handleRefreshNodes() {
       console.log('刷新节点');
-      // 这里可以添加刷新节点的逻辑
-      this.loadTagTreeData();
+      // 刷新标签树数据的逻辑已在子组件中处理
     },
 
     // 搜索
@@ -184,6 +195,29 @@ export default {
     handleBackToList() {
       console.log('返回标签体系列表页面');
       this.$router.push('/home/test');
+    },
+    // 处理状态变化
+    handleStatusChange() {
+      console.log('状态变化:', this.currentSystem.status);
+      
+      // 调用后端接口更新状态
+      yufp.service.request({
+        url: backend.tagServer + "/api/aitag-tagcategory/modify",
+        method: 'post',
+        data: {
+          id: this.currentSystem.id,
+          state: this.currentSystem.status ? 0 : 1
+        },
+        callback: (code, error, response) => {
+          if (response.code == '0') {
+            this.$message.success('标签体系状态更新成功');
+          } else {
+            // 恢复原状态
+            this.currentSystem.status = !this.currentSystem.status;
+            this.$message.error(response.message || '标签体系状态更新失败');
+          }
+        }
+      });
     }
   }
 }
@@ -289,4 +323,4 @@ export default {
     align-self: flex-start;
   }
 }
-</style>
+</style>

+ 272 - 68
web/src/views/aiTagging/taggingSystemManage/components/TagConfig.vue

@@ -5,9 +5,16 @@
       <!-- 标签树 -->
       <div class="tag-tree-container">
         <TagTree 
+          ref="tagTree"
+          :tag-system="tagSystem"
           :tree-props="treeProps"
+          :tag-tree-data="tagTreeData"
+          :current-node-key="currentNodeKey"
+          :system-status="systemStatus"
           @node-click="handleNodeClick"
           @dropdown-command="handleDropdownCommand"
+          @refresh="loadTagTreeData"
+          @save-success="loadTagTreeData"
         />
       </div>
 
@@ -17,13 +24,26 @@
           v-if="selectedNode"
           :tag-data="selectedNode"
           :is-edit-mode="isEditMode"
+          :parent-options="tagTreeData"
+          :tree-props="treeProps"
+          :category-id="tagSystem.id"
+          :system-status="systemStatus"
           @edit="handleEditTag"
-          @save="handleSaveTag"
+          @save-success="loadTagTreeData"
+          @version-rollback="handleVersionRollback"
+          @delete="handleDeleteTag"
           @cancel="handleCancelEdit"
         />
         <div v-else class="empty-data">
-          <!-- <img src="https://trae-api-cn.mchost.guru/api/ide/v1/text_to_image?prompt=empty%20state%2C%20box%20with%20confetti%2C%20no%20data%20illustration%2C%20simple%20style&image_size=square" alt="暂无数据"> -->
-          <p>请选择一个标签节点查看详情</p>
+          <div v-if="isTagTreeEmpty" class="empty-data-import">
+            <!-- <i class="el-icon-upload2 empty-icon"></i> -->
+            <h4>暂无标签数据</h4>
+            <p>您可以通过批量导入的方式快速创建标签</p>
+            <el-button type="primary" @click="handleImportClick">点击此处导入</el-button>
+          </div>
+          <div v-else class="empty-data-select">
+            <p>请选择一个标签节点查看详情</p>
+          </div>
         </div>
       </div>
     </div>
@@ -49,92 +69,193 @@ export default {
       type: Object,
       default: () => ({
         children: 'children',
-        label: 'label'
+        label: 'tagNm'
       })
+    },
+    systemStatus: {
+      type: Boolean,
+      default: true
     }
   },
   data() {
     return {
       selectedNode: null,
-      isEditMode: false
+      isEditMode: false,
+      tagTreeData: [],
+      currentNodeKey: ''
+    }
+  },
+  computed: {
+    // 标签树数据是否为空
+    isTagTreeEmpty() {
+      return !this.tagTreeData || this.tagTreeData.length === 0;
+    }
+  },
+  watch: {
+    // 监听标签体系变化,重新加载标签树数据
+    'tagSystem.id': {
+      handler(newId) {
+        if (newId) {
+          this.loadTagTreeData();
+        }
+      },
+      immediate: true
     }
   },
   methods: {
+    // 加载标签树数据
+    loadTagTreeData() {
+      if (!this.tagSystem.id) {
+        return;
+      }
+      
+      console.log('加载标签树数据:', this.tagSystem.id);
+      
+      // 调用后端接口获取标签树
+      yufp.service.request({
+        url: backend.tagServer + "/api/aitagtaginfo/queryTagTree",
+        method: 'post',
+        data: {
+          tagNm: '',
+          categoryId: this.tagSystem.id
+        },
+        callback: (code, error, response) => {
+          if (response.code == '0') {
+            this.tagTreeData = response.data || [];
+            console.log('标签树数据:', this.tagTreeData);
+          } else {
+            this.$message.error(response.message || '获取标签树数据失败');
+          }
+        }
+      });
+    },
+    
     // 节点点击
     handleNodeClick(data, node) {
-      console.log('点击节点:', data.label, '级别:', node.level);
-      // 设置选中的节点
-      this.selectedNode = this.enrichNodeData(data, node);
+      console.log('点击节点:', data.tagNm, '级别:', node.level, '节点数据:', data);
+      // 调用接口获取标签详情
+      const tagId = data.id || data.tagId || data.tagCode;
+      console.log('获取到的标签ID:', tagId);
+      // 更新当前选中节点的key
+      this.currentNodeKey = data.id || data.tagId || data.tagCode;
+      this.loadTagDetail(tagId);
       // 退出编辑模式
       this.isEditMode = false;
     },
 
-    // 丰富节点数据,添加标签详情信息
-    enrichNodeData(data, node) {
-      // 构建标签路径
-      const path = [];
-      let currentNode = node;
-      while (currentNode) {
-        path.unshift(currentNode.label);
-        currentNode = currentNode.parent;
+    // 加载标签详情数据
+    loadTagDetail(tagId) {
+      if (!tagId) {
+        this.$message.error('缺少标签ID');
+        return;
       }
       
-      // 模拟标签详情数据
-      return {
-        ...data,
-        path: path.join(' / '),
-        version: '1.0',
-        revisionTime: '2026-02-05 12:00:00',
-        revisionUser: '张三',
-        description: data.description || '包含海洋行政管理、涉海行业管理、海洋开发区管理和海洋社会保障服务等活动',
-        regex: data.regex || '(abc)|(adc)|(adc)|(adc)|(adc)|(adc)|(adc)|(adc)|(adc)|(adc)|(adc)|(adc)|(adc)',
-        hintWords: data.hintWords || '',
-        name: data.label || path.join(' / '),
-        scope: '文本内容涉及海洋行政管理、涉海行业管理、海洋开发区管理、海洋社会保障服务等相关活动时,标注上述标签。',
-        rules: '逐句分析文本,判断是否包含标签适用范围内的核心行为与管理场景;只要文本涵盖海洋行政管理、涉海行业监管、海洋园区/开发区运营管理、海洋领域社会保障服务任一内容,即匹配标签;严格按照给定格式输出标签,不得修改、拆分、合并标签文字,无匹配内容输出「无匹配标签」。',
-        revisions: [
-          {
-            version: '1.0',
-            revisionTime: '2026-02-05 12:00:00',
-            revisionUser: '张三',
-            description: '包含海洋行政管理、涉海行业管理、海洋开发区管理和海洋社会保障服务等活动',
-            regex: '(abc)|(adc)|(adc)|(adc)|(adc)|(adc)|(adc)|(adc)|(adc)|(adc)|(adc)|(adc)|(adc)',
-            name: data.label || path.join(' / '),
-            scope: '文本内容涉及海洋行政管理、涉海行业管理、海洋开发区管理、海洋社会保障服务等相关活动时,标注上述标签。',
-            rules: '逐句分析文本,判断是否包含标签适用范围内的核心行为与管理场景;只要文本涵盖海洋行政管理、涉海行业监管、海洋园区/开发区运营管理、海洋领域社会保障服务任一内容,即匹配标签;严格按照给定格式输出标签,不得修改、拆分、合并标签文字,无匹配内容输出「无匹配标签」。'
-          },
-          {
-            version: '0.9',
-            revisionTime: '2026-02-01 10:30:00',
-            revisionUser: '李四',
-            description: '包含海洋行政管理、涉海行业管理和海洋开发区管理等活动',
-            regex: '(abc)|(adc)|(adc)|(adc)|(adc)',
-            name: data.label || path.join(' / '),
-            scope: '文本内容涉及海洋行政管理、涉海行业管理、海洋开发区管理等相关活动时,标注上述标签。',
-            rules: '逐句分析文本,判断是否包含标签适用范围内的核心行为与管理场景;只要文本涵盖海洋行政管理、涉海行业监管、海洋园区/开发区运营管理任一内容,即匹配标签;严格按照给定格式输出标签,不得修改、拆分、合并标签文字,无匹配内容输出「无匹配标签」。'
+      console.log('加载标签详情:', tagId);
+      
+      // 调用后端接口获取标签详情
+      yufp.service.request({
+        url: backend.tagServer + "/api/aitagtaginfo/info/" + tagId,
+        method: 'get',
+        data: {},
+        callback: (code, error, response) => {
+          if (response.code == '0') {
+            const tagDetail = response.data || {};
+            console.log('标签详情数据:', tagDetail);
+            // 确保tagDetail包含id字段
+            if (!tagDetail.id && tagDetail.tagId) {
+              tagDetail.id = tagDetail.tagId;
+            }
+            // 设置选中的节点
+            this.selectedNode = this.formatTagDetail(tagDetail);
+            console.log('格式化后的选中节点:', this.selectedNode);
+          } else {
+            this.$message.error(response.message || '获取标签详情失败');
           }
-        ]
+        }
+      });
+    },
+
+    // 格式化标签详情数据
+    formatTagDetail(tagDetail) {
+      return {
+        ...tagDetail,
+        id: tagDetail.id || tagDetail.tagId || '',
+        name: tagDetail.tagNm || '',
+        path: this.buildTagPath(tagDetail),
+        version: tagDetail.version || '1.0',
+        revisionTime: tagDetail.revisionTime || '',
+        revisionUser: tagDetail.reviser || '',
+        description: tagDetail.tagRemark || '',
+        keywords: tagDetail.reg || '',
+        judgment: tagDetail.tagPrompt || '',
+        scope: tagDetail.scope || '',
+        rules: tagDetail.rules || '',
+        revisions: tagDetail.revisions || []
       };
     },
 
+    // 构建标签路径
+    buildTagPath(tagDetail) {
+      // 这里可以根据实际情况构建标签路径
+      // 如果接口返回了路径信息,直接使用
+      if (tagDetail.path) {
+        return tagDetail.path;
+      }
+      // 否则使用标签名称
+      return tagDetail.tagNm || '';
+    },
+
     // 下拉菜单命令处理
     handleDropdownCommand(command, data, node) {
-      console.log('执行命令:', command, '目标节点:', data.label, '节点信息:', node);
-      
-      // 设置选中的节点
-      this.selectedNode = this.enrichNodeData(data, node);
+      console.log('执行命令:', command, '目标节点:', data.tagNm, '节点信息:', node);
       
       switch(command) {
         case 'add':
-          console.log('新增子节点到:', data.label);
+          console.log('新增子节点到:', data.tagNm);
           break;
         case 'edit':
-          console.log('编辑节点:', data.label);
+          console.log('编辑节点:', data.tagNm);
+          // 调用接口获取标签详情
+          this.loadTagDetail(data.id);
           // 进入编辑模式
           this.isEditMode = true;
           break;
         case 'delete':
-          console.log('删除节点:', data.label);
+          console.log('删除节点:', data.tagNm);
+          // 显示删除确认提示
+          this.$confirm('您确定要删除该标签吗?删除后将无法恢复,请慎重操作', '确认删除', {
+            confirmButtonText: '删除',
+            cancelButtonText: '取消',
+            type: 'danger'
+          }).then(() => {
+            // 构建请求数据
+            const requestData = {
+              ids: [data.id || data.tagId || data.tagCode]
+            };
+            
+            console.log('删除标签请求数据:', requestData);
+            
+            // 调用接口
+            yufp.service.request({
+              url: backend.tagServer + "/api/aitagtaginfo/delete",
+              method: 'post',
+              data: requestData,
+              callback: (code, error, response) => {
+                if (response.code == '0') {
+                  this.$message.success('标签删除成功');
+                  // 重新加载标签树数据
+                  this.loadTagTreeData();
+                  // 清空选中节点
+                  this.selectedNode = null;
+                } else {
+                  this.$message.error(response.message || '标签删除失败');
+                }
+              }
+            });
+          }).catch(() => {
+            // 取消操作
+            this.$message.info('已取消删除操作');
+          });
           break;
       }
     },
@@ -144,20 +265,52 @@ export default {
       this.isEditMode = true;
     },
 
-    // 保存标签
-    handleSaveTag(formData) {
-      console.log('保存标签数据:', formData);
-      // 这里可以添加保存标签的逻辑
-      this.$message.success('标签保存成功');
-      // 退出编辑模式
-      this.isEditMode = false;
-      // 更新选中节点的数据
-      this.selectedNode = { ...this.selectedNode, ...formData };
-    },
-
     // 取消编辑
     handleCancelEdit() {
       this.isEditMode = false;
+    },
+    // 版本回溯成功后刷新数据
+    handleVersionRollback() {
+      // 重新加载标签树数据
+      this.loadTagTreeData();
+      // 重新加载标签详情数据
+      if (this.selectedNode && this.selectedNode.id) {
+        this.loadTagDetail(this.selectedNode.id);
+      }
+    },
+    // 删除标签
+    handleDeleteTag(tagData) {
+      // 构建请求数据
+      const requestData = {
+        ids: [tagData.id || tagData.tagId || tagData.tagCode]
+      };
+      
+      console.log('删除标签请求数据:', requestData);
+      
+      // 调用接口
+      yufp.service.request({
+        url: backend.tagServer + "/api/aitagtaginfo/delete",
+        method: 'post',
+        data: requestData,
+        callback: (code, error, response) => {
+          if (response.code == '0') {
+            this.$message.success('标签删除成功');
+            // 重新加载标签树数据
+            this.loadTagTreeData();
+            // 清空选中节点
+            this.selectedNode = null;
+          } else {
+            this.$message.error(response.message || '标签删除失败');
+          }
+        }
+      });
+    },
+    // 处理导入按钮点击事件
+    handleImportClick() {
+      // 调用TagTree组件的方法显示导入弹框
+      if (this.$refs.tagTree) {
+        this.$refs.tagTree.showImportDialog = true;
+      }
     }
   }
 }
@@ -234,4 +387,55 @@ export default {
     width: 100%;
   }
 }
+
+/* 空数据提示样式 */
+.empty-data {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 40px 20px;
+  text-align: center;
+}
+
+.empty-data-import {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 16px;
+  max-width: 400px;
+}
+
+.empty-icon {
+  font-size: 48px;
+  color: #c0c4cc;
+  margin-bottom: 16px;
+}
+
+.empty-data-import h4 {
+  font-size: 18px;
+  font-weight: 600;
+  color: #303133;
+  margin: 0;
+}
+
+.empty-data-import p {
+  font-size: 14px;
+  color: #606266;
+  margin: 0;
+  line-height: 1.5;
+}
+
+.empty-data-select {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 16px;
+}
+
+.empty-data-select p {
+  font-size: 14px;
+  color: #909399;
+  margin: 0;
+}
 </style>

+ 54 - 13
web/src/views/aiTagging/taggingSystemManage/components/TagDetail.vue

@@ -2,11 +2,12 @@
   <div class="tag-detail">
     <!-- 详情展示模式 -->
     <div v-if="!isEditMode">
-      <TagDetailView :tag-data="tagData" />
+      <TagDetailView :tag-data="tagData" @version-rollback="handleVersionRollback" />
       
-      <!-- 编辑按钮 -->
+      <!-- 操作按钮 -->
       <div class="action-buttons">
-        <el-button type="primary" @click="handleEdit">编辑</el-button>
+        <el-button v-if="!systemStatus" type="danger" @click="handleDelete">删除</el-button>
+        <el-button v-if="!systemStatus" type="primary" @click="handleEdit">编辑</el-button>
       </div>
     </div>
     
@@ -14,7 +15,12 @@
     <div v-else class="edit-view">
       <TagDetailEdit
         :form-data="editForm"
+        :is-add-mode="false"
+        :parent-options="parentOptions"
+        :tree-props="treeProps"
+        :category-id="categoryId"
         @save="handleSave"
+        @save-success="$emit('save-success')"
         @cancel="handleCancel"
       />
     </div>
@@ -39,6 +45,25 @@ export default {
     isEditMode: {
       type: Boolean,
       default: false
+    },
+    categoryId: {
+      type: String,
+      default: ''
+    },
+    parentOptions: {
+      type: Array,
+      default: () => []
+    },
+    treeProps: {
+      type: Object,
+      default: () => ({
+        children: 'children',
+        label: 'tagNm'
+      })
+    },
+    systemStatus: {
+      type: Boolean,
+      default: true
     }
   },
   data() {
@@ -46,8 +71,8 @@ export default {
       editForm: {
         name: '',
         description: '',
-        regex: '',
-        hintWords: ''
+        keywords: '',
+        judgment: ''
       }
     }
   },
@@ -68,10 +93,13 @@ export default {
   methods: {
     updateEditForm(data) {
       this.editForm = {
-        name: data.name || '',
-        description: data.description || '',
-        regex: data.regex || '',
-        hintWords: data.hintWords || ''
+        id: data.id || data.tagId || '',
+        name: data.name || data.tagNm || '',
+        description: data.description || data.tagRemark || '',
+        keywords: data.keywords || data.reg || '',
+        judgment: data.judgment || data.tagPrompt || '',
+        tagCode: data.tagCode || '',
+        parentId: data.parentId || data.parent || ''
       }
     },
     handleEdit() {
@@ -83,6 +111,23 @@ export default {
     handleCancel() {
       this.$emit('cancel')
       this.updateEditForm(this.tagData)
+    },
+    handleVersionRollback() {
+      this.$emit('version-rollback')
+    },
+    handleDelete() {
+      // 显示删除确认提示
+      this.$confirm('您确定要删除该标签吗?删除后将无法恢复,请慎重操作', '确认删除', {
+        confirmButtonText: '删除',
+        cancelButtonText: '取消',
+        type: 'danger'
+      }).then(() => {
+        // 触发父组件的删除方法
+        this.$emit('delete', this.tagData)
+      }).catch(() => {
+        // 取消操作
+        this.$message.info('已取消删除操作');
+      });
     }
   }
 }
@@ -93,8 +138,6 @@ export default {
   background-color: #fff;
   border-radius: 8px;
   padding: 20px;
-  height: 100%;
-  overflow-y: auto;
 }
 
 .tag-path {
@@ -124,8 +167,6 @@ export default {
   display: flex;
   justify-content: flex-end;
   gap: 10px;
-  padding-top: 20px;
-  border-top: 1px solid #e8e8e8;
 }
 
 .edit-view {

+ 381 - 84
web/src/views/aiTagging/taggingSystemManage/components/TagDetailEdit.vue

@@ -9,9 +9,21 @@
       ></el-input>
     </div>
     
-    <!-- 上级标签 (仅新增时显示) -->
-    <div v-if="isAddMode" class="edit-section">
-      <h4 class="section-title">上级标签<span class="required">*</span></h4>
+    <!-- 上级标签 -->
+    <div class="edit-section">
+      <div class="section-title-container">
+        <h4 class="section-title">
+          上级标签
+          <el-tooltip
+            content="调整标签层级仅影响后续发起的打标任务,不影响历史已完成的打标结果。修改前请确保已充分评估对业务的影响"
+            placement="top"
+            effect="dark"
+          >
+            <i class="el-icon-warning-outline warning-icon"></i>
+          </el-tooltip>
+          <span class="required">*</span>
+        </h4>
+      </div>
       <div class="tree-select-container">
         <TreeSelect
           v-model="editForm.parent"
@@ -22,78 +34,64 @@
       </div>
     </div>
     
-    <!-- 标签描述 -->
+    <!-- 标签定义 -->
     <div class="edit-section">
-      <h4 class="section-title">标签描述<span class="required">*</span></h4>
+      <h4 class="section-title">标签定义</h4>
       <el-input
         type="textarea"
         v-model="editForm.description"
-        placeholder="请输入标签描述"
-        :rows="3"
+        placeholder="请输入"
+        :rows="4"
       ></el-input>
-      <p class="section-hint">请简要描述标签的概念、内涵和适用范围,用于辅助智能标体对标签含义的准确理解</p>
+      <p class="section-hint">说明:请简要描述标签的概念、内涵和适用范围,用于辅助智能标体对标签含义的准确理解</p>
     </div>
     
-    <!-- 标签正则表达式 -->
+    <!-- 关键词 -->
     <div class="edit-section">
-      <h4 class="section-title">标签正则表达式<span class="required">*</span></h4>
-      <div class="regex-input-container">
-        <el-input
-          v-model="editForm.regex"
-          placeholder="请输入"
-        ></el-input>
-        <div class="regex-tools">
-          <el-button 
-            size="mini" 
-            title="括号" 
-            @click="insertRegexChar('(')"
-            class="regex-tool-btn"
-          >(</el-button>
-          <el-button 
-            size="mini" 
-            title="括号" 
-            @click="insertRegexChar(')')"
-            class="regex-tool-btn"
-          >)</el-button>
-          <el-button 
-            size="mini" 
-            title="竖线" 
-            @click="insertRegexChar('|')"
-            class="regex-tool-btn"
-          >|</el-button>
-          <el-button 
-            size="mini" 
-            title="脱字符" 
-            @click="insertRegexChar('^')"
-            class="regex-tool-btn"
-          >^</el-button>
+      <div class="section-title-container">
+        <h4 class="section-title">关键词</h4>
+        <el-button type="text" icon="el-icon-question" @click="handleOpenRegexDrawer"></el-button>
+      </div>
+      <div class="keyword-container">
+        <div class="keyword-tools">
           <el-button 
-            size="mini" 
-            title="美元符" 
-            @click="insertRegexChar('$')"
-            class="regex-tool-btn"
-          >$</el-button>
+            v-for="(tool, index) in keywordTools" 
+            :key="index"
+            size="small" 
+            :title="tool.title" 
+            @click="insertKeywordChar(tool.char)"
+            class="keyword-tool-btn"
+          >{{ tool.char }}</el-button>
           <el-button 
-            size="mini" 
-            title="星号" 
-            @click="insertRegexChar('*')"
-            class="regex-tool-btn"
-          >*</el-button>
+            type="primary" 
+            size="small" 
+            icon="el-icon-magic-stick" 
+            @click="handleSmartGenerate"
+            class="smart-generate-btn"
+          >智能生成</el-button>
         </div>
+        <el-input
+          v-model="editForm.keywords"
+          placeholder="请输入"
+          :rows="3"
+          type="textarea"
+        ></el-input>
       </div>
-      <p class="section-hint">优先根据正则表达式进行标注</p>
+      <p class="section-hint">说明:输入的关键词用于缩小打标范围,排除无关标签</p>
     </div>
     
-    <!-- 标签提示词 -->
+    <!-- 标签判断说明 -->
     <div class="edit-section">
-      <h4 class="section-title">标签提示词<span class="required">*</span></h4>
+      <h4 class="section-title">标签判断说明</h4>
       <el-input
         type="textarea"
-        v-model="editForm.hintWords"
-        placeholder="请输入"
-        :rows="3"
+        v-model="editForm.judgment"
+        placeholder="请输入标签判定要点及标签排除说明"
+        :rows="4"
       ></el-input>
-      <p class="section-hint">正则匹配失败时,智能体将依据提示词理解标签含义,完成智能打标。提示词需清晰描述标签的概念、内涵和适用场景,以确保打标标准准确性。</p>
+      <p class="section-hint">说明:包含标签判定要点、标签排除说明。用于智能体对第一轮标签初稿结果进行最终打标</p>
+      <p class="section-hint">标签判定要点:明确说明本标签的关键判定标准和方法,写明如何从文本中识别该类活动,包括关键词、语义特征、行业分类依据、推理方法等。</p>
+      <p class="section-hint">标签排除说明:明确界定哪些情况不属于本标签,它的作用是划清边界,防止将语义相近但本质不同的经济活动错误归入此类。</p>
     </div>
     
     <!-- 操作按钮 -->
@@ -101,16 +99,21 @@
       <el-button @click="handleCancel">取消</el-button>
       <el-button type="primary" @click="handleSave">保存</el-button>
     </div>
+    
+    <!-- 正则表达式规则说明抽屉 -->
+    <TagDetailEditRegexDrawer :visible.sync="showRegexDrawer" />
   </div>
 </template>
 
 <script>
 import TreeSelect from './TreeSelect.vue';
+import TagDetailEditRegexDrawer from './TagDetailEditRegexDrawer.vue';
 
 export default {
   name: 'TagDetailEdit',
   components: {
-    TreeSelect
+    TreeSelect,
+    TagDetailEditRegexDrawer
   },
   props: {
     formData: {
@@ -119,8 +122,8 @@ export default {
         name: '',
         parent: '',
         description: '',
-        regex: '',
-        hintWords: ''
+        keywords: '',
+        judgment: ''
       })
     },
     isAddMode: {
@@ -135,8 +138,12 @@ export default {
       type: Object,
       default: () => ({
         children: 'children',
-        label: 'label'
+        label: 'tagNm'
       })
+    },
+    categoryId: {
+      type: String,
+      default: ''
     }
   },
   data() {
@@ -145,9 +152,25 @@ export default {
         name: '',
         parent: '',
         description: '',
-        regex: '',
-        hintWords: ''
-      }
+        keywords: '',
+        judgment: ''
+      },
+      showRegexDrawer: false,
+      keywordTools: [
+        { char: '^', title: '脱字符' },
+        { char: '$', title: '美元符' },
+        { char: '.', title: '点' },
+        { char: '*', title: '星号' },
+        { char: '+', title: '加号' },
+        { char: '?', title: '问号' },
+        { char: '|', title: '竖线' },
+        { char: '(', title: '左括号' },
+        { char: ')', title: '右括号' },
+        { char: '[', title: '左方括号' },
+        { char: ']', title: '右方括号' },
+        { char: '{', title: '左大括号' },
+        { char: '}', title: '右大括号' }
+      ]
     }
   },
   watch: {
@@ -159,25 +182,271 @@ export default {
       immediate: true
     }
   },
+  created() {
+    console.log("this.formData", this.formData);
+    
+    // // 初始化编辑表单数据
+    // this.updateEditForm(this.formData);
+  },
   methods: {
     updateEditForm(data) {
+      console.log('TagDetailEdit updateEditForm - 传入数据:', data);
+      console.log('TagDetailEdit updateEditForm - parentOptions:', this.parentOptions);
+      
+      let parent = data.parent || data.parentId || '';
+      console.log('TagDetailEdit updateEditForm - 原始parent值:', parent);
+      
+      // 如果 parent 是ID,尝试在 parentOptions 中找到对应的对象
+      if (parent && typeof parent === 'string' && this.parentOptions && this.parentOptions.length > 0) {
+        console.log('TagDetailEdit updateEditForm - 开始查找parent对象,ID:', parent);
+        parent = this.findNodeById(this.parentOptions, parent);
+        console.log('TagDetailEdit updateEditForm - 查找结果:', parent);
+      }
+      
+      // 确保 parent 是对象
+      if (parent && typeof parent === 'string') {
+        console.log('TagDetailEdit updateEditForm - parent仍然是字符串,无法反显:', parent);
+      }
+      
       this.editForm = {
         name: data.name || '',
-        parent: data.parent || '',
+        parent: parent || '',
         description: data.description || '',
-        regex: data.regex || '',
-        hintWords: data.hintWords || ''
+        keywords: data.keywords || '',
+        judgment: data.judgment || ''
+      };
+      
+      console.log('TagDetailEdit updateEditForm - 最终editForm:', this.editForm);
+    },
+    
+    // 根据ID在树结构中查找节点
+    findNodeById(tree, id) {
+      for (const node of tree) {
+        if (node.id === id || node.tagId === id) {
+          return node;
+        }
+        if (node.children && node.children.length > 0) {
+          const found = this.findNodeById(node.children, id);
+          if (found) {
+            return found;
+          }
+        }
       }
+      return null;
     },
-    insertRegexChar(char) {
-      this.editForm.regex += char;
+    insertKeywordChar(char) {
+      this.editForm.keywords += char;
+    },
+    handleSmartGenerate() {
+      // 智能生成关键词的逻辑
+      console.log('智能生成关键词');
+      this.$message.info('智能生成功能开发中');
     },
     handleSave() {
-      this.$emit('save', this.editForm)
+      // 验证表单
+      if (!this.editForm.name) {
+        this.$message.error('请输入标签名称');
+        return;
+      }
+      
+      if (!this.categoryId) {
+        this.$message.error('缺少标签体系ID');
+        return;
+      }
+      
+      console.log('TagDetailEdit handleSave - isAddMode:', this.isAddMode);
+      console.log('TagDetailEdit handleSave - formData:', this.formData);
+      console.log('TagDetailEdit handleSave - editForm:', this.editForm);
+      
+      // 根据新增/编辑状态调用不同接口
+      if (this.isAddMode) {
+        console.log('TagDetailEdit handleSave - 调用 handleAddSave');
+        this.handleAddSave();
+      } else {
+        console.log('TagDetailEdit handleSave - 调用 handleUpdateSave');
+        this.handleUpdateSave();
+      }
+    },
+    
+    // 新增标签保存
+    handleAddSave() {
+      // 构建请求数据
+      const requestData = {
+        categoryId: this.categoryId,
+        tagNm: this.editForm.name,
+        tagCode: this.generateTagCode(),
+        tagRemark: this.editForm.description,
+        parentId: this.getParentId(),
+        reg: this.editForm.keywords,
+        tagPrompt: this.editForm.judgment,
+        reviser: yufp.session.userId || '' // 操作人
+      };
+      
+      console.log('新增标签数据:', requestData);
+      
+      // 调用新增接口
+      yufp.service.request({
+        url: backend.tagServer + "/api/aitagtaginfo/save",
+        method: 'post',
+        data: requestData,
+        callback: (code, error, response) => {
+          if (response.code == '0') {
+            this.$message.success('标签新增成功');
+            this.$emit('save', this.editForm);
+            this.$emit('save-success');
+          } else {
+            this.$message.error(response.message || '标签新增失败');
+          }
+        }
+      });
+    },
+    
+    // 编辑标签保存
+    handleUpdateSave() {
+      // 获取标签ID
+      const tagId = this.getTagId();
+      if (!tagId) {
+        this.$message.error('缺少标签ID');
+        return;
+      }
+      
+      // 构建请求数据
+      const requestData = {
+        id: tagId,
+        categoryId: this.categoryId,
+        tagNm: this.editForm.name,
+        tagCode: this.editForm.tagCode || this.formData.tagCode || this.generateTagCode(),
+        tagRemark: this.editForm.description,
+        parentId: this.getParentId(),
+        reg: this.editForm.keywords,
+        tagPrompt: this.editForm.judgment,
+        reviser: yufp.session.userId || '' // 操作人
+      };
+      
+      console.log('编辑标签数据:', requestData);
+      // return
+      // 调用编辑接口
+      yufp.service.request({
+        url: backend.tagServer + "/api/aitagtaginfo/update",
+        method: 'post',
+        data: requestData,
+        callback: (code, error, response) => {
+          if (response.code == '0') {
+            this.$message.success('标签编辑成功');
+            this.$emit('save', this.editForm);
+            this.$emit('save-success');
+          } else {
+            this.$message.error(response.message || '标签编辑失败');
+          }
+        }
+      });
+    },
+    
+    // 获取标签ID
+    getTagId() {
+      // 优先从formData中获取id
+      if (this.formData) {
+        console.log('formData:', this.formData);
+        if (this.formData.id) {
+          console.log('从formData.id获取ID:', this.formData.id);
+          return this.formData.id;
+        }
+        if (this.formData.tagId) {
+          console.log('从formData.tagId获取ID:', this.formData.tagId);
+          return this.formData.tagId;
+        }
+      }
+      // 从editForm中获取id
+      if (this.editForm) {
+        console.log('editForm:', this.editForm);
+        if (this.editForm.id) {
+          console.log('从editForm.id获取ID:', this.editForm.id);
+          return this.editForm.id;
+        }
+        if (this.editForm.tagId) {
+          console.log('从editForm.tagId获取ID:', this.editForm.tagId);
+          return this.editForm.tagId;
+        }
+      }
+      console.log('未找到标签ID');
+      return '';
+    },
+    generateTagCode() {
+      // 生成标签编码
+      const prefix = 'TAG_';
+      const suffix = Math.random().toString(36).substr(2, 6).toUpperCase();
+      return prefix + suffix;
+    },
+    // 获取父标签ID
+    getParentId() {
+      // 如果用户选择了具体的层级,以选择的为准
+      if (this.editForm.parent) {
+        if (typeof this.editForm.parent === 'object') {
+          return this.editForm.parent.id || '';
+        }
+        return this.editForm.parent;
+      }
+      
+      // 如果是编辑状态,优先使用当前标签的 parentId
+      if (!this.isAddMode) {
+        // 从 formData 中获取 parentId
+        if (this.formData && (this.formData.parentId || this.formData.parent)) {
+          if (typeof this.formData.parent === 'object') {
+            return this.formData.parent.id || '';
+          }
+          return this.formData.parentId || this.formData.parent || '';
+        }
+      }
+      
+      // 如果没有选择层级且不是编辑状态,默认生成一级标签,parentId 是标签体系的 id(categoryId)
+      return this.categoryId || '';
     },
     handleCancel() {
-      this.$emit('cancel')
-      this.updateEditForm(this.formData)
+      this.$emit('cancel');
+      this.updateEditForm(this.formData);
+    },
+    handleOpenRegexDrawer() {
+      console.log('打开正则表达式规则抽屉');
+      console.log('当前showRegexDrawer值:', this.showRegexDrawer);
+      this.showRegexDrawer = true;
+      console.log('设置后showRegexDrawer值:', this.showRegexDrawer);
+    },
+    handleSmartGenerate() {
+      // 检查标签名称和标签说明是否为空
+      if (!this.editForm.name) {
+        this.$message.error('请输入标签名称');
+        return;
+      }
+      if (!this.editForm.description) {
+        this.$message.error('请输入标签定义');
+        return;
+      }
+      
+      // 构建请求数据
+      const requestData = {
+        tag_name: this.editForm.name,
+        tag_remark: this.editForm.description
+      };
+      
+      console.log('智能生成请求数据:', requestData);
+      
+      // 调用接口
+      yufp.service.request({
+        url: backend.tagServer + "/api/aitagtaginfo/generateRegex",
+        method: 'post',
+        data: requestData,
+        callback: (code, error, response) => {
+          if (response.code == '0') {
+            // 将返回的数据填充到关键词输入框
+            if (response.data) {
+              this.editForm.keywords = response.data;
+            }
+            this.$message.success('关键词生成成功');
+          } else {
+            this.$message.error(response.message || '关键词生成失败');
+          }
+        }
+      });
     }
   }
 }
@@ -205,6 +474,34 @@ export default {
   border-left: 3px solid #409eff;
 }
 
+.section-title-container {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.warning-icon {
+  font-size: 14px;
+  color: #e6a23c;
+  cursor: help;
+  transition: color 0.3s;
+}
+
+.warning-icon:hover {
+  color: #f56c6c;
+}
+
+.section-title-container .el-icon-question {
+  font-size: 12px;
+  color: #909399;
+  cursor: pointer;
+  transition: color 0.3s;
+}
+
+.section-title-container .el-icon-question:hover {
+  color: #409eff;
+}
+
 .required {
   color: #f56c6c;
   margin-left: 4px;
@@ -215,31 +512,29 @@ export default {
   display: flex;
   justify-content: flex-end;
   gap: 10px;
-  padding-top: 20px;
-  border-top: 1px solid #e8e8e8;
 }
 
 .edit-actions {
   margin-top: 20px;
 }
 
-.regex-input-container {
+.keyword-container {
   display: flex;
   flex-direction: column;
   gap: 8px;
 }
 
-.regex-tools {
+.keyword-tools {
   display: flex;
   gap: 0;
   align-items: center;
+  flex-wrap: wrap;
 }
 
-/* 使用::v-deep确保样式应用到Element UI按钮 */
-.regex-tools ::v-deep .regex-tool-btn {
-  width: 28px;
-  height: 28px;
-  min-width: 28px;
+.keyword-tool-btn {
+  width: 32px;
+  height: 32px;
+  min-width: 32px;
   display: flex;
   align-items: center;
   justify-content: center;
@@ -251,17 +546,19 @@ export default {
   border: 1px solid transparent;
 }
 
-.regex-tools ::v-deep .regex-tool-btn:hover {
+.keyword-tool-btn:hover {
   background-color: #3a8ee6;
   color: #fff;
 }
 
+.smart-generate-btn {
+  margin-left: auto;
+}
+
 .section-hint {
   font-size: 12px;
   color: #909399;
   margin: 0;
-  margin-top: 4px;
-  padding: 4px 0;
 }
 
 .el-input {

+ 440 - 0
web/src/views/aiTagging/taggingSystemManage/components/TagDetailEditRegexDrawer.vue

@@ -0,0 +1,440 @@
+<template>
+  <el-drawer
+    title="正则表达式规则说明"
+    :visible.sync="visible"
+    direction="rtl"
+    size="50%"
+  >
+    <div class="regex-drawer-content">
+      <!-- 基础符号解释 -->
+      <div class="regex-section">
+        <div class="section-header">
+          <i class="el-icon-info"></i>
+          <h3 class="regex-section-title">一、基础符号解释</h3>
+        </div>
+        <div class="card">
+          <table class="regex-table">
+            <thead>
+              <tr>
+                <th style="width: 80px;">符号</th>
+                <th style="width: 200px;">含义</th>
+                <th>在您规则中的用法示例</th>
+              </tr>
+            </thead>
+            <tbody>
+              <tr>
+                <td class="symbol-cell">^</td>
+                <td>匹配字符串的开始位置</td>
+                <td class="example-cell">^(?!.*A) 表示从字符串开头就要满足后面的条件</td>
+              </tr>
+              <tr>
+                <td class="symbol-cell">$</td>
+                <td>匹配字符串的结束位置</td>
+                <td class="example-cell">部分规则末尾有 $,表示匹配项必须到结尾,确保整个字符串被检查</td>
+              </tr>
+              <tr>
+                <td class="symbol-cell">.</td>
+                <td>匹配除换行符外的任意单个字符</td>
+                <td class="example-cell">(0,6) 中的 . 表示任意字符(包括汉字、字母、数字、符号)</td>
+              </tr>
+              <tr>
+                <td class="symbol-cell">*</td>
+                <td>前面的元素重复 0 次或多次(尽可能多)</td>
+                <td class="example-cell">.* 表示任意数量的任意字符,常用于包含或排除检查</td>
+              </tr>
+              <tr>
+                <td class="symbol-cell">?</td>
+                <td>1. 量词:前面的元素重复 0 次或 1 次<br>2. 跟在量词后表示非贪婪(尽可能少)<br>3. 放在 (?!...) 中表示否定前瞻</td>
+                <td class="example-cell">(?!.*A) 中的 ! 与 ? 组合,表示后面不能出现 A</td>
+              </tr>
+              <tr>
+                <td class="symbol-cell">+</td>
+                <td>前面的元素重复 1 次或多次</td>
+                <td class="example-cell">本规则中未使用,但常见于其他规则</td>
+              </tr>
+              <tr>
+                <td class="symbol-cell">|</td>
+                <td>逻辑或,分隔多个可选项</td>
+                <td class="example-cell">(淡水|池塘|河|湖|河中) 表示匹配 "淡水" 或 "池塘" 或 "河" 中的任何一个词</td>
+              </tr>
+              <tr>
+                <td class="symbol-cell">()</td>
+                <td>1. 分组:将多个元素组合成一个整体<br>2. 捕获分组:会捕获匹配的内容(本规则中未使用捕获功能)</td>
+                <td class="example-cell">(养殖|捕捞) 作为一个整体,(养殖捕捞) 表示一组候选词</td>
+              </tr>
+              <tr>
+                <td class="symbol-cell">[ ]</td>
+                <td>字符集,匹配其中任意一个字符,例如 [abc] 匹配 a、b、c 中的任意一个</td>
+                <td class="example-cell">本规则中未使用,但常见于匹配特定字符范围</td>
+              </tr>
+              <tr>
+                <td class="symbol-cell">{n,m}</td>
+                <td>量词,前面的元素重复 n 到 m 次</td>
+                <td class="example-cell">(0,6) 表示任意字符出现 0 到 6 次,用于控制两个词之间的距离</td>
+              </tr>
+              <tr>
+                <td class="symbol-cell">?!</td>
+                <td>是 (?!...) 的一部分,表示当前位置后面不能出现指定的模式</td>
+                <td class="example-cell">^(?!.*A) 表示字符串中不能包含 A</td>
+              </tr>
+              <tr>
+                <td class="symbol-cell">[^...]</td>
+                <td>在方括号中使用脱字符(^),表示匹配其中任意一个字符的否定</td>
+                <td class="example-cell">在排除条件中 (?!.*[^A]) 表示字符串不能包含非 A 的字符</td>
+              </tr>
+            </tbody>
+          </table>
+        </div>
+      </div>
+      
+      <!-- 规则中的典型结构拆解 -->
+      <div class="regex-section">
+        <div class="section-header">
+          <i class="el-icon-s-grid"></i>
+          <h3 class="regex-section-title">二、规则中的典型结构拆解</h3>
+        </div>
+        <div class="card">
+          <p class="regex-paragraph">每条规则通常由两部分组成:排除条件 + 匹配模式(多个匹配分支用 | 连接)。</p>
+          <div class="example-box">
+            <code class="regex-code">^(?!.*(淡水|池塘|河|湖|江湖|海洋|鱼缸|鱼塘|龙鱼|花鸟|海).{0,6}(养殖|捕捞|渔网|渔船|贝|虾|蟹|鱼|海参|对虾|扇贝|贻贝|牡蛎|海蜇|海胆|鲍鱼|鱿鱼|墨鱼|章鱼|贝类|鱼粉|鱼油|鱼干|鱼罐头|水产加工))</code>
+          </div>
+          
+          <div class="structure-item">
+            <h4 class="regex-subtitle">1. 排除条件 (?!...) 解析:</h4>
+            <ul class="regex-list">
+              <li><span class="highlight">^(?!...):</span>确保从字符串开头开始检查。</li>
+              <li><span class="highlight">.*:</span>允许任意字符出现任意次,只要字符串中包含后面的任何一个词,就触发排除。</li>
+              <li><span class="highlight">(词1|词2|...):</span>列出所有要排除的关键词(用 | 分隔)。</li>
+              <li><span class="highlight">含义:</span>整个字符串不能包含这些词中的任何一个。如果包含,则该规则不匹配。</li>
+            </ul>
+          </div>
+          
+          <div class="structure-item">
+            <h4 class="regex-subtitle">2. 匹配模式 (A.{0,6}B|B.{0,6}A) 解析:</h4>
+            <ul class="regex-list">
+              <li>这是一种常见模式,用于匹配两个词(A 和 B)同时出现,且它们之间的字符数不超过 6 个(顺序可互换)。</li>
+              <li><span class="highlight">(A):</span>表示关键词 A(例如 "海")。</li>
+              <li><span class="highlight">.{0,6}:</span>表示 0 到 6 个任意字符(中间可以有任何内容)。</li>
+              <li><span class="highlight">(B):</span>表示关键词 B(例如 "养殖")。</li>
+              <li><span class="highlight">两个分支:</span>(A.{0,6}B) 和 (B.{0,6}A) 分别覆盖两种顺序。</li>
+              <li><span class="highlight">含义:</span>字符串中必须同时出现 A 和 B,且它们之间的距离不超过 6 个字符(无论顺序)。</li>
+            </ul>
+          </div>
+          
+          <div class="structure-item">
+            <h4 class="regex-subtitle">3. 直接匹配关键词/短语 (词1|词2|...) 解析:</h4>
+            <ul class="regex-list">
+              <li>有些规则末尾直接给出一个关键词组,例如 <code class="inline-code">(鱼丸|鱼糕|鱼饼|虾饼|虾米|咸鱼干|咸鱼|鱼干|水产加工)</code>。</li>
+              <li>此时只要字符串中包含这些词中的任何一个,就匹配成功。</li>
+            </ul>
+          </div>
+        </div>
+      </div>
+      
+      <!-- 特殊符号补充说明 -->
+      <div class="regex-section">
+        <div class="section-header">
+          <i class="el-icon-warning-outline"></i>
+          <h3 class="regex-section-title">三、特殊符号补充说明</h3>
+        </div>
+        <div class="card">
+          <div class="tip-item">
+            <div class="tip-icon">
+              <i class="el-icon-info"></i>
+            </div>
+            <div class="tip-content">
+              <strong>中文括号和竖线</strong>:在正则表达式中,括号和竖线必须使用英文半角符号(( ) |),中文括号(())和中文竖线(|)会被视为普通字符,不具备特殊含义。您的规则中使用的都是英文符号,是正确的。
+            </div>
+          </div>
+          <div class="tip-item">
+            <div class="tip-icon">
+              <i class="el-icon-info"></i>
+            </div>
+            <div class="tip-content">
+              <strong>空格处理</strong>:正则表达式中的空格会被当作普通字符匹配。例如 <code class="inline-code">(海 鲜)</code> 中的空格要求匹配位置也有空格。建议在配置时注意是否包含空格。
+            </div>
+          </div>
+          <div class="tip-item">
+            <div class="tip-icon">
+              <i class="el-icon-info"></i>
+            </div>
+            <div class="tip-content">
+              <strong>{0,6} 的精确含义</strong>:表示前面的元素(通常是一个点 .)重复 0 到 6 次。重复 0 次意味着两个词可以紧邻(中间无字符)。
+            </div>
+          </div>
+          <div class="tip-item">
+            <div class="tip-icon">
+              <i class="el-icon-info"></i>
+            </div>
+            <div class="tip-content">
+              <strong>排除条件的嵌套</strong>:排除条件中 .* 可能会在性能上有下降,但对于文本长度(如标题、关键词)影响不大。
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </el-drawer>
+</template>
+
+<script>
+export default {
+  name: 'TagDetailEditRegexDrawer',
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    }
+  },
+}
+</script>
+
+<style scoped>
+/* 正则表达式规则说明抽屉样式 */
+.regex-drawer-content {
+  padding: 20px;
+  max-height: calc(100vh - 120px);
+  overflow-y: auto;
+  background-color: #f5f7fa;
+}
+
+.regex-section {
+  margin-bottom: 24px;
+}
+
+.regex-section:last-child {
+  margin-bottom: 0;
+}
+
+/* 章节头部 */
+.section-header {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  margin-bottom: 16px;
+  padding-bottom: 10px;
+  border-bottom: 2px solid #409eff;
+}
+
+.section-header i {
+  font-size: 18px;
+  color: #409eff;
+}
+
+.regex-section-title {
+  font-size: 18px;
+  font-weight: 600;
+  color: #303133;
+  margin: 0;
+}
+
+/* 卡片样式 */
+.card {
+  background-color: #fff;
+  border-radius: 8px;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  padding: 20px;
+  transition: all 0.3s ease;
+}
+
+.card:hover {
+  box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.15);
+}
+
+.regex-subtitle {
+  font-size: 16px;
+  font-weight: 500;
+  color: #303133;
+  margin: 16px 0 12px 0;
+  padding-left: 10px;
+  border-left: 3px solid #409eff;
+}
+
+.regex-paragraph {
+  font-size: 14px;
+  line-height: 1.6;
+  color: #606266;
+  margin: 8px 0 16px 0;
+}
+
+/* 代码示例 */
+.example-box {
+  background-color: #f8f9fa;
+  border: 1px solid #e9ecef;
+  border-radius: 4px;
+  padding: 12px;
+  margin: 12px 0;
+  overflow-x: auto;
+}
+
+.regex-code {
+  font-family: 'Courier New', Courier, monospace;
+  font-size: 13px;
+  color: #c7254e;
+  background-color: #f9f2f4;
+  padding: 2px 4px;
+  border-radius: 3px;
+  white-space: pre-wrap;
+  word-break: break-all;
+}
+
+.inline-code {
+  font-family: 'Courier New', Courier, monospace;
+  font-size: 13px;
+  color: #c7254e;
+  background-color: #f9f2f4;
+  padding: 2px 4px;
+  border-radius: 3px;
+}
+
+/* 结构项 */
+.structure-item {
+  margin-bottom: 20px;
+  padding: 12px;
+  background-color: #f8f9fa;
+  border-radius: 6px;
+}
+
+/* 列表样式 */
+.regex-list {
+  font-size: 14px;
+  line-height: 1.6;
+  color: #606266;
+  margin: 8px 0 16px 20px;
+  padding-left: 0;
+}
+
+.regex-list li {
+  margin-bottom: 10px;
+  position: relative;
+  padding-left: 20px;
+}
+
+.regex-list li:before {
+  content: "•";
+  position: absolute;
+  left: 0;
+  color: #409eff;
+  font-weight: bold;
+}
+
+/* 高亮文本 */
+.highlight {
+  color: #409eff;
+  font-weight: 500;
+}
+
+/* 表格样式 */
+.regex-table {
+  width: 100%;
+  border-collapse: collapse;
+  margin: 12px 0;
+  border-radius: 4px;
+  overflow: hidden;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.regex-table th,
+.regex-table td {
+  padding: 12px;
+  border: 1px solid #e8e8e8;
+  text-align: left;
+  font-size: 14px;
+}
+
+.regex-table th {
+  background-color: #409eff;
+  font-weight: 500;
+  color: #fff;
+}
+
+.regex-table tr:nth-child(even) {
+  background-color: #f9f9f9;
+}
+
+.regex-table tr:hover {
+  background-color: #ecf5ff;
+}
+
+/* 符号单元格 */
+.symbol-cell {
+  font-family: 'Courier New', Courier, monospace;
+  font-weight: bold;
+  color: #c7254e;
+  text-align: center;
+  background-color: #f9f2f4;
+}
+
+/* 示例单元格 */
+.example-cell {
+  font-family: 'Courier New', Courier, monospace;
+  font-size: 13px;
+  color: #606266;
+  background-color: #f8f9fa;
+}
+
+/* 提示项 */
+.tip-item {
+  display: flex;
+  align-items: flex-start;
+  gap: 12px;
+  margin-bottom: 16px;
+  padding: 12px;
+  background-color: #ecf5ff;
+  border-radius: 6px;
+  border-left: 4px solid #409eff;
+}
+
+.tip-item:last-child {
+  margin-bottom: 0;
+}
+
+.tip-icon {
+  flex-shrink: 0;
+  width: 24px;
+  height: 24px;
+  border-radius: 50%;
+  background-color: #409eff;
+  color: #fff;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 14px;
+  margin-top: 2px;
+}
+
+.tip-content {
+  flex: 1;
+  font-size: 14px;
+  line-height: 1.5;
+  color: #606266;
+}
+
+.tip-content strong {
+  color: #303133;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .regex-drawer-content {
+    padding: 16px;
+  }
+  
+  .card {
+    padding: 16px;
+  }
+  
+  .regex-table th,
+  .regex-table td {
+    padding: 8px;
+    font-size: 13px;
+  }
+  
+  .example-box {
+    padding: 8px;
+  }
+  
+  .regex-code {
+    font-size: 12px;
+  }
+}
+</style>

+ 84 - 33
web/src/views/aiTagging/taggingSystemManage/components/TagDetailView.vue

@@ -10,26 +10,25 @@
       版本{{ tagData.version || '1.0' }}. 修订时间: {{ tagData.revisionTime || '未知' }} 修订人: {{ tagData.revisionUser || '未知' }}
     </div>
     
-    <!-- 标签描述 -->
+    <!-- 标签定义 -->
     <div class="detail-section">
-      <h4 class="section-title">标签描述*</h4>
-      <p class="section-content">{{ tagData.description || '无描述信息' }}</p>
+      <h4 class="section-title">标签定义</h4>
+      <p class="section-content">{{ tagData.description || '无定义信息' }}</p>
     </div>
     
-    <!-- 标签正则表达式 -->
+    <!-- 关键词 -->
     <div class="detail-section">
-      <h4 class="section-title">标签正则表达式*</h4>
-      <p class="section-content code">{{ tagData.regex || '无正则表达式' }}</p>
+      <div class="section-title-container">
+        <h4 class="section-title">关键词</h4>
+        <el-button type="text" icon="el-icon-question" @click="handleOpenRegexDrawer"></el-button>
+      </div>
+      <p class="section-content code">{{ tagData.keywords || '无关键词' }}</p>
     </div>
     
-    <!-- 标签提示词 -->
+    <!-- 标签判断说明 -->
     <div class="detail-section">
-      <h4 class="section-title">标签提示词*</h4>
-      <p class="section-content">
-        <span class="info-label">标签名称:</span>{{ tagData.name || '无名称' }}<br>
-        <span class="info-label">标签适用范围:</span>{{ tagData.scope || '无适用范围' }}<br>
-        <span class="info-label">标注规则:</span>{{ tagData.rules || '无标注规则' }}
-      </p>
+      <h4 class="section-title">标签判断说明</h4>
+      <p class="section-content">{{ tagData.judgment || '无判断说明' }}</p>
     </div>
     
     <!-- 修订记录 -->
@@ -37,12 +36,12 @@
       <h4 class="section-title">修订记录</h4>
       <div class="revision-list">
         <div 
-          v-for="(revision, index) in tagData.revisions || []" 
+          v-for="(revision, index) in tagData.tagInfoVersionDtos || []" 
           :key="index" 
           class="revision-item"
         >
           <div class="revision-info">
-            版本{{ revision.version }}. 修订时间: {{ revision.revisionTime }} 修订人: {{ revision.revisionUser }}
+            {{ revision.tagVersionShow }}. 修订时间: {{ revision.revisionTime }} 修订人: {{ revision.reviser }}
           </div>
           <el-button 
             type="text" 
@@ -51,7 +50,7 @@
             icon="el-icon-view"
           ></el-button>
         </div>
-        <div v-if="!tagData.revisions || tagData.revisions.length === 0" class="no-revisions">
+        <div v-if="!tagData.tagInfoVersionDtos || tagData.tagInfoVersionDtos.length === 0" class="no-revisions">
           无修订记录
         </div>
       </div>
@@ -65,46 +64,50 @@
     >
       <div class="version-detail">
         <div class="detail-header">
-          {{ selectedVersion.name || '无名称' }}
+          {{ selectedVersion.tagNm || '无名称' }}
         </div>
         <div class="detail-meta">
-          版本{{ selectedVersion.version }}. 修订时间: {{ selectedVersion.revisionTime }} 修订人: {{ selectedVersion.revisionUser }}
+          {{ selectedVersion.tagVersionShow }}. 修订时间: {{ selectedVersion.revisionTime }} 修订人: {{ selectedVersion.reviser }}
         </div>
         
         <div class="detail-content">
-          <!-- 标签描述 -->
+          <!-- 标签定义 -->
           <div class="detail-item">
-            <h5 class="item-title">标签描述*</h5>
-            <p class="item-content">{{ selectedVersion.description || '无描述信息' }}</p>
+            <h5 class="item-title">标签定义</h5>
+            <p class="item-content">{{ selectedVersion.tagRemark || '无定义信息' }}</p>
           </div>
           
-          <!-- 标签正则表达式 -->
+          <!-- 关键词 -->
           <div class="detail-item">
-            <h5 class="item-title">标签正则表达式*</h5>
-            <p class="item-content code">{{ selectedVersion.regex || '无正则表达式' }}</p>
+            <h5 class="item-title">关键词</h5>
+            <p class="item-content code">{{ selectedVersion.reg || '无关键词' }}</p>
           </div>
           
-          <!-- 标签提示词 -->
+          <!-- 标签判断说明 -->
           <div class="detail-item">
-            <h5 class="item-title">标签提示词*</h5>
-            <p class="item-content">
-              <span class="info-label">标签名称:</span>{{ selectedVersion.name || '无名称' }}<br>
-              <span class="info-label">标签适用范围:</span>{{ selectedVersion.scope || '无适用范围' }}<br>
-              <span class="info-label">标注规则:</span>{{ selectedVersion.rules || '无标注规则' }}
-            </p>
+            <h5 class="item-title">标签判断说明</h5>
+            <p class="item-content">{{ selectedVersion.tagPrompt || '无判断说明' }}</p>
           </div>
         </div>
       </div>
       <span slot="footer" class="dialog-footer">
-        <el-button @click="dialogVisible = false">版本回溯</el-button>
+        <el-button @click="handleVersionRollback">版本回溯</el-button>
       </span>
     </el-dialog>
+    
+    <!-- 正则表达式规则说明抽屉 -->
+    <TagDetailEditRegexDrawer :visible.sync="showRegexDrawer" />
   </div>
 </template>
 
 <script>
+import TagDetailEditRegexDrawer from './TagDetailEditRegexDrawer.vue';
+
 export default {
   name: 'TagDetailView',
+  components: {
+    TagDetailEditRegexDrawer
+  },
   props: {
     tagData: {
       type: Object,
@@ -114,13 +117,55 @@ export default {
   data() {
     return {
       dialogVisible: false,
-      selectedVersion: {}
+      selectedVersion: {},
+      showRegexDrawer: false
     }
   },
   methods: {
     showVersionDetail(revision) {
       this.selectedVersion = revision;
       this.dialogVisible = true;
+    },
+    handleOpenRegexDrawer() {
+      this.showRegexDrawer = true;
+    },
+    handleVersionRollback() {
+      // 显示确认提示
+      this.$confirm('您确定要回溯该版本吗?版本回溯后,该版本的后置版本将全部丢失,请慎重操作', '确认版本回溯', {
+        confirmButtonText: '确认',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        // 构建请求数据
+        const requestData = {
+          id: this.tagData.id || '',
+          tagCode: this.tagData.tagCode || '',
+          version: this.selectedVersion.tagVersion || '',
+          reviser: yufp.session.userId || '' // 操作人
+        };
+        
+        console.log('版本回溯请求数据:', requestData);
+        
+        // 调用接口
+        yufp.service.request({
+          url: backend.tagServer + "/api/aitagtaginfo/versionRollback",
+          method: 'post',
+          data: requestData,
+          callback: (code, error, response) => {
+            if (response.code == '0') {
+              this.$message.success('版本回溯成功');
+              this.dialogVisible = false;
+              // 触发父组件刷新数据
+              this.$emit('version-rollback');
+            } else {
+              this.$message.error(response.message || '版本回溯失败');
+            }
+          }
+        });
+      }).catch(() => {
+        // 取消操作
+        this.$message.info('已取消版本回溯');
+      });
     }
   }
 }
@@ -163,6 +208,12 @@ export default {
   border-left: 3px solid #409eff;
 }
 
+.section-title-container {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
 .section-content {
   font-size: 14px;
   color: #606266;

+ 142 - 33
web/src/views/aiTagging/taggingSystemManage/components/TagTest.vue

@@ -15,7 +15,7 @@
             style="width: 100%; margin-bottom: 16px;"
           ></el-input>
         </el-tab-pane>
-        <el-tab-pane label="文件上传" name="file">
+        <el-tab-pane label="文件上传" name="file" v-if="false">
           <div class="file-upload-section">
             <el-upload
               class="file-upload"
@@ -118,9 +118,15 @@ export default {
       // 智能打标结果
       taggingResults: [],
       // 打标时间
-      taggingTime: ''
+      taggingTime: '',
+      // 轮询相关
+      pollingTimer: null
     }
   },
+  beforeDestroy() {
+    // 组件销毁前清除轮询定时器
+    this.clearPolling();
+  },
   methods: {
     // 智能打标
     handleSmartTagging() {
@@ -137,43 +143,145 @@ export default {
       
       console.log('智能打标:', this.activeInputTab === 'text' ? this.testText : this.uploadedFile.name);
       
-      // 模拟智能打标过程
+      // 显示加载提示
       this.$message({
         message: '正在进行智能打标,请稍候...',
         type: 'loading',
-        duration: 1500
+        duration: 3000
+      });
+      
+      // 生成16位随机字符串(数字和小写英文)
+      const generateRandomString = (length) => {
+        const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
+        let result = '';
+        for (let i = 0; i < length; i++) {
+          result += chars.charAt(Math.floor(Math.random() * chars.length));
+        }
+        return result;
+      };
+      
+      // 生成businessAttr
+      const businessAttr = 'test-' + generateRandomString(16);
+      
+      // 构建请求参数
+      const requestData = {
+        businessAttr: businessAttr,
+        tag_category_id: this.tagSystem.id || this.tagSystem.categoryCode || '',
+        phrase: this.testText.trim()
+      };
+      
+      console.log('智能打标请求参数:', requestData);
+      
+      // 调用智能打标接口
+      yufp.service.request({
+        url: backend.tagServer + "/api/fastapi/tagging",
+        method: 'post',
+        data: requestData,
+        callback: (code, error, response) => {
+          if (response.code == '0') {
+            // 清除之前的轮询
+            this.clearPolling();
+            
+            // 显示处理中提示
+            const loadingInstance = this.$loading({
+              lock: true,
+              text: '智能打标中,请稍候...',
+              spinner: 'el-icon-loading',
+              background: 'rgba(0, 0, 0, 0.7)',
+              target: this.$el
+            });
+            
+            // 开始轮询查询打标结果
+            this.pollTaggingResult(businessAttr, loadingInstance);
+          } else {
+            this.$message.error(response.message || '智能打标失败');
+          }
+        }
       });
+    },
+    
+    // 轮询查询打标结果
+    pollTaggingResult(businessAttr, loadingInstance) {
+      // 构建查询参数
+      const queryParams = {
+        businessAttr: businessAttr
+      };
       
-      // 模拟打标结果
-      setTimeout(() => {
-        this.taggingResults = [
-          {
-            code: 'A051',
-            name: '远洋捕捞',
-            method: '正则表达式',
-            reason: '文本中明确提到"主要从事远洋捕捞业务"、"拥有5艘远洋捕捞船"、"购买新的远洋捕捞设备"等关键信息,直接指向"远洋捕捞"标签。企业名称"舟山远洋渔业有限公司"也包含"远洋渔业"关键词,进一步支持此标签。'
-          },
-          {
-            code: 'C344',
-            name: '船舶制造',
-            method: '智能体',
-            reason: '文本中明确提到"主要从事远洋捕捞业务"、"拥有5艘远洋捕捞船"、"购买新的远洋捕捞设备"等关键信息,直接指向"远洋捕捞"标签。企业名称"舟山远洋渔业有限公司"也包含"远洋渔业"关键词,进一步支持此标签。'
+      // 调用查询接口
+      yufp.service.request({
+        url: backend.tagServer + "/api/fastapi/query",
+        method: 'get',
+        data: queryParams,
+        callback: (code, error, response) => {
+          if (response.code == '0') {
+            // 处理查询结果
+            const resultData = response.data;
+            if (resultData && (Array.isArray(resultData) || resultData.result)) {
+              // 有结果,停止轮询
+              loadingInstance.close();
+              this.clearPolling();
+              
+              if (Array.isArray(resultData)) {
+                this.taggingResults = resultData.map(item => ({
+                  code: item.label_code || item.tag_code || '',
+                  name: item.label || item.tag_name || '',
+                  method: '',
+                  reason: item.desc || ''
+                }));
+              } else if (resultData.result) {
+                try {
+                  const parsedResult = JSON.parse(resultData.result);
+                  if (Array.isArray(parsedResult)) {
+                    this.taggingResults = parsedResult.map(item => ({
+                      code: item.label_code || item.tag_code || '',
+                      name: item.label || item.tag_name || '',
+                      method: '',
+                      reason: item.desc || ''
+                    }));
+                  } else {
+                    this.taggingResults = [];
+                    this.$message.warning('打标结果格式异常');
+                  }
+                } catch (error) {
+                  this.taggingResults = [];
+                  this.$message.warning('打标结果解析失败');
+                }
+              }
+              
+              // 设置打标时间
+              this.taggingTime = new Date().toLocaleString();
+              
+              this.$message.success('智能打标完成');
+              
+              // 触发父组件事件
+              this.$emit('smart-tagging', {
+                inputType: this.activeInputTab,
+                input: this.testText,
+                results: this.taggingResults,
+                time: this.taggingTime
+              });
+            } else {
+              // 无结果,继续轮询
+              this.pollingTimer = setTimeout(() => {
+                this.pollTaggingResult(businessAttr, loadingInstance);
+              }, 3000);
+            }
+          } else {
+            // 接口调用失败,停止轮询
+            loadingInstance.close();
+            this.clearPolling();
+            this.$message.error(response.message || '查询打标结果失败');
           }
-        ];
-        
-        // 设置打标时间
-        this.taggingTime = '2026-02-05 12:00:00';
-        
-        this.$message.success('智能打标完成');
-        
-        // 触发父组件事件
-        this.$emit('smart-tagging', {
-          inputType: this.activeInputTab,
-          input: this.activeInputTab === 'text' ? this.testText : this.uploadedFile.name,
-          results: this.taggingResults,
-          time: this.taggingTime
-        });
-      }, 1500);
+        }
+      });
+    },
+    
+    // 清除轮询
+    clearPolling() {
+      if (this.pollingTimer) {
+        clearTimeout(this.pollingTimer);
+        this.pollingTimer = null;
+      }
     },
     
     // 处理文件上传
@@ -242,6 +350,7 @@ export default {
   font-weight: 600;
   color: #303133;
   margin: 0 0 16px 0;
+  text-align: left;
 }
 
 /* 文件上传区 */

+ 336 - 301
web/src/views/aiTagging/taggingSystemManage/components/TagTree.vue

@@ -1,8 +1,25 @@
 <template>
   <div class="tag-tree">
+    <!-- 标签树标题和操作按钮 -->
+    <div class="tree-header">
+      <h3 class="tree-title">{{ tagSystem.name || '标签树' }}</h3>
+      <div class="tree-header-buttons">
+        <el-button v-if="!systemStatus" type="text" size="small" icon="el-icon-plus" @click="handleAddRootNode"></el-button>
+        <el-button 
+          v-if="!systemStatus && isTagTreeEmpty" 
+          type="text" 
+          size="small" 
+          icon="el-icon-upload2" 
+          @click="showImportDialog = true"
+          title="批量导入标签"
+        ></el-button>
+        <el-button type="text" size="small" icon="el-icon-refresh" @click="$emit('refresh')"></el-button>
+      </div>
+    </div>
+    
     <!-- 搜索框 -->
     <div class="tree-header-search">
-      <el-input v-model="searchKeyword" placeholder="请输入关键字" style="width: 100%;">
+      <el-input v-model="searchKeyword" placeholder="请输入" style="width: 100%;">
         <el-button slot="append" icon="el-icon-search" @click="handleSearch"></el-button>
       </el-input>
     </div>
@@ -14,6 +31,8 @@
       node-key="id"
       default-expand-all
       :render-content="renderContent"
+      :current-node-key="currentNodeKey"
+      :highlight-current="true"
       @node-click="handleNodeClick"
     ></el-tree>
     
@@ -28,10 +47,51 @@
         :is-add-mode="true"
         :parent-options="tagTreeData"
         :tree-props="treeProps"
+        :category-id="tagSystem.id"
         @save="handleAddSave"
         @cancel="handleAddCancel"
       />
     </el-dialog>
+    
+    <!-- 批量导入标签弹框 -->
+    <el-dialog
+      title="批量导入标签"
+      :visible.sync="showImportDialog"
+      width="500px"
+    >
+      <div class="import-dialog-content">
+        <!-- 提示信息 -->
+        <div class="import-hint">
+          <i class="el-icon-info"></i>
+          <span>请先下载模板,按照模板要求填写后上传</span>
+        </div>
+        
+        <!-- 文件上传区域 -->
+        <div class="upload-area" :class="{ 'has-file': file }">
+          <input 
+            type="file" 
+            ref="fileInput" 
+            class="file-input" 
+            accept=".xlsx,.csv" 
+            @change="handleFileChange"
+          >
+          <div class="upload-content">
+            <i class="el-icon-upload upload-icon"></i>
+            <p>点击或拖拽文件到此处上传</p>
+            <p class="upload-format">仅支持单个Excel(xlsx)或CSV格式文件,文件不超过5MB</p>
+            <p v-if="file" class="file-name">{{ file.name }}</p>
+          </div>
+        </div>
+      </div>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="showImportDialog = false">取消</el-button>
+        <el-button 
+          type="primary" 
+          @click="handleImport" 
+          :disabled="!file"
+        >开始导入</el-button>
+      </span>
+    </el-dialog>
   </div>
 </template>
 
@@ -44,130 +104,43 @@ export default {
     TagDetailEdit
   },
   props: {
+    tagSystem: {
+      type: Object,
+      default: () => ({})
+    },
     treeProps: {
       type: Object,
       default: () => ({
         children: 'children',
-        label: 'label'
+        label: 'tagNm'
       })
+    },
+    tagTreeData: {
+      type: Array,
+      default: () => []
+    },
+    currentNodeKey: {
+      type: [String, Number],
+      default: ''
+    },
+    systemStatus: {
+      type: Boolean,
+      default: true
     }
   },
   data() {
     return {
       searchKeyword: '',
       addDialogVisible: false,
+      showImportDialog: false,
+      file: null,
       addForm: {
         name: '',
         parent: '',
         description: '',
-        regex: '',
-        hintWords: ''
-      },
-      // 标签树模拟数据
-      tagTreeData: [
-        {
-          id: 1,
-          label: '海洋经济',
-          children: [
-            {
-              id: 2,
-              label: '海洋产业(A)',
-              children: [
-                {
-                  id: 3,
-                  label: '海洋渔业(A1)',
-                  children: [
-                    {
-                      id: 4,
-                      label: '海洋捕捞(A11)',
-                      children: [
-                        {
-                          id: 5,
-                          label: '远洋捕捞(A111)',
-                        },
-                        {
-                          id: 6,
-                          label: '近海捕捞(A112)',
-                        }
-                      ]
-                    },
-                    {
-                      id: 7,
-                      label: '海水养殖(A12)',
-                    }
-                  ]
-                },
-                {
-                  id: 8,
-                  label: '海洋盐业(A2)',
-                }
-              ]
-            },
-            {
-              id: 9,
-              label: '海洋科研教育(B)',
-            },
-            {
-              id: 10,
-              label: '海洋公共管理服务(C)',
-              children: [
-                {
-                  id: 11,
-                  label: '海洋管理(C18)',
-                },
-                {
-                  id: 12,
-                  label: '海洋社会团体基金会与国际组织(C19)',
-                },
-                {
-                  id: 13,
-                  label: '海洋技术服务(C20)',
-                },
-                {
-                  id: 14,
-                  label: '海洋信息服务(C21)',
-                },
-                {
-                  id: 15,
-                  label: '海洋生态环境保护修复(C22)',
-                },
-                {
-                  id: 16,
-                  label: '海洋地质勘查(C23)',
-                  children: [
-                    {
-                      id: 17,
-                      label: '海洋矿产地质勘查(C231)',
-                      children: [
-                        {
-                          id: 18,
-                          label: '海洋能源矿产地质勘查(C2311)',
-                        },
-                        {
-                          id: 19,
-                          label: '海洋固体矿产地质勘查(C2312)',
-                        },
-                        {
-                          id: 20,
-                          label: '其他海洋矿产地质勘查(C2319)',
-                        }
-                      ]
-                    }
-                  ]
-                }
-              ]
-            },
-            {
-              id: 21,
-              label: '海洋上游相关产业(D)',
-            },
-            {
-              id: 22,
-              label: '海洋下游相关产业(E)',
-            }
-          ]
-        }
-      ]
+        keywords: '',
+        judgment: ''
+      }
     }
   },
   computed: {
@@ -183,7 +156,7 @@ export default {
       const filterTree = (tree) => {
         return tree.filter(node => {
           // 检查当前节点是否匹配
-          const matches = node.label.toLowerCase().includes(keyword);
+          const matches = (node.tagNm || '').toLowerCase().includes(keyword);
           
           // 检查子节点是否匹配
           if (node.children && node.children.length > 0) {
@@ -197,18 +170,41 @@ export default {
       };
       
       return filterTree(JSON.parse(JSON.stringify(this.tagTreeData)));
+    },
+    // 判断标签树数据是否为空
+    isTagTreeEmpty() {
+      return !this.tagTreeData || this.tagTreeData.length === 0;
     }
   },
   methods: {
+    // 新增根节点(一级标签)
+    handleAddRootNode() {
+      console.log('新增根节点(一级标签)');
+      this.addForm = {
+        name: '',
+        parent: '',
+        description: '',
+        keywords: '',
+        judgment: ''
+      };
+      this.addDialogVisible = true;
+    },
+    
+    // 搜索
+    handleSearch() {
+      console.log('搜索关键词:', this.searchKeyword);
+    },
+    
     // 渲染树节点内容
     renderContent(h, { node, data, store }) {
       return h('span', { class: 'custom-tree-node' }, [
         h('span', {
           class: 'node-label',
-          attrs: { title: node.label }
-        }, node.label),
+          attrs: { title: data.tagNm }
+        }, data.tagNm),
         h('span', {
-          class: 'node-dropdown'
+          class: 'node-dropdown',
+          style: { display: !this.systemStatus ? 'inline-block' : 'none' }
         }, [
           h('el-dropdown', {
             props: { trigger: 'click' },
@@ -216,17 +212,19 @@ export default {
               command: (command) => this.handleDropdownCommand(command, data, node)
             }
           }, [
-            h('span', { class: 'el-dropdown-link vertical-dots' }, [
+            h('span', { class: 'el-dropdown-link vertical-dots node-action' }, [
               '⋮'
             ]),
             h('el-dropdown-menu', {
               slot: 'dropdown'
             }, [
               h('el-dropdown-item', {
-                props: { command: 'add' }
+                props: { command: 'add' },
+                style: { display: !this.systemStatus ? 'block' : 'none' }
               }, '新增'),
               h('el-dropdown-item', {
-                props: { command: 'delete' }
+                props: { command: 'delete' },
+                style: { display: !this.systemStatus ? 'block' : 'none' }
               }, '删除')
             ])
           ])
@@ -235,180 +233,101 @@ export default {
     },
 
     handleNodeClick(data, node) {
-      console.log('点击节点:', data.label);
+      console.log('点击节点:', data.tagNm);
       // 触发节点点击事件,传递给父组件
       this.$emit('node-click', data, node);
     },
 
     handleDropdownCommand(command, data, node) {
-      console.log('执行命令:', command, '目标节点:', data.label);
-      // 触发下拉菜单命令事件,传递给父组件
-      this.$emit('dropdown-command', command, data, node);
+      console.log('执行命令:', command, '目标节点:', data.tagNm, '节点信息:', node);
       
       switch(command) {
         case 'add':
-          this.handleAddNode(data, node);
-          break;
-        case 'up':
-          this.handleMoveUp(data, node);
-          break;
-        case 'down':
-          this.handleMoveDown(data, node);
+          console.log('新增子节点到:', data.tagNm);
+          // 新增子级标签,parentId 是点击的当前数据的 id
+          this.addForm = {
+            name: '',
+            parent: data,
+            description: '',
+            keywords: '',
+            judgment: ''
+          };
+          this.addDialogVisible = true;
           break;
         case 'delete':
-          this.handleDeleteNode(data, node);
+          console.log('删除节点:', data.tagNm);
           break;
       }
-    },
-
-    // 添加节点
-    handleAddNode(parentData, parentNode) {
-      console.log('新增节点到:', parentData ? parentData.label : '根节点');
-      
-      // 重置新增表单
-      this.addForm = {
-        name: '',
-        parent: parentData || '',
-        description: '',
-        regex: '',
-        hintWords: ''
-      };
       
-      // 打开新增标签弹框
-      this.addDialogVisible = true;
+      // 触发下拉菜单命令事件,传递给父组件
+      this.$emit('dropdown-command', command, data, node);
     },
-    
-    // 处理新增标签保存
+
     handleAddSave(formData) {
       console.log('保存新增标签:', formData);
-      
-      // 生成新节点ID
-      const newId = Math.max(...this.getAllNodeIds(this.tagTreeData)) + 1;
-      
-      // 创建新节点
-      const newNode = {
-        id: newId,
-        label: formData.name,
-        children: []
-      };
-      
-      // 获取父节点ID(兼容对象和数字两种情况)
-      const parentId = typeof formData.parent === 'object' && formData.parent ? formData.parent.id : formData.parent;
-      
-      // 查找父节点
-      const parentNode = this.findNodeById(this.tagTreeData, parentId);
-      
-      // 添加到父节点或根节点
-      if (parentNode) {
-        if (!parentNode.children) {
-          parentNode.children = [];
-        }
-        parentNode.children.push(newNode);
-      } else {
-        this.tagTreeData.push(newNode);
-      }
-      
-      // 关闭弹框
       this.addDialogVisible = false;
-      
-      // 提示保存成功
-      this.$message.success('标签新增成功');
+      // 触发保存成功事件,通知父组件刷新标签树
+      this.$emit('save-success');
     },
-    
-    // 处理新增标签取消
+
     handleAddCancel() {
       console.log('取消新增标签');
-      // 关闭弹框
       this.addDialogVisible = false;
     },
-    
-    // 根据ID查找节点
-    findNodeById(tree, id) {
-      for (const node of tree) {
-        if (node.id === id) {
-          return node;
-        }
-        if (node.children && node.children.length > 0) {
-          const found = this.findNodeById(node.children, id);
-          if (found) {
-            return found;
-          }
+    // 处理文件选择
+    handleFileChange(event) {
+      const file = event.target.files[0];
+      if (file) {
+        // 检查文件大小(5MB)
+        if (file.size > 5 * 1024 * 1024) {
+          this.$message.error('文件大小不能超过5MB');
+          return;
         }
-      }
-      return null;
-    },
-
-    // 获取所有节点ID
-    getAllNodeIds(tree, ids = []) {
-      for (const node of tree) {
-        ids.push(node.id);
-        if (node.children && node.children.length > 0) {
-          this.getAllNodeIds(node.children, ids);
+        // 检查文件类型
+        const validTypes = ['.xlsx', '.csv'];
+        const fileExtension = '.' + file.name.split('.').pop().toLowerCase();
+        if (!validTypes.includes(fileExtension)) {
+          this.$message.error('仅支持Excel(xlsx)或CSV格式文件');
+          return;
         }
+        this.file = file;
       }
-      return ids;
     },
-
-    // 上移节点
-    handleMoveUp(data, node) {
-      console.log('上移节点:', data.label);
-      
-      // 找到父节点
-      const parent = node.parent;
-      if (!parent || parent.level === 0) return;
-      
-      // 找到当前节点在父节点children中的索引
-      const siblings = parent.data.children || [];
-      const index = siblings.findIndex(item => item.id === data.id);
-      
-      // 如果不是第一个节点,则上移
-      if (index > 0) {
-        // 交换位置
-        [siblings[index], siblings[index - 1]] = [siblings[index - 1], siblings[index]];
+    // 处理文件导入
+    handleImport() {
+      if (!this.file) {
+        this.$message.error('请选择要导入的文件');
+        return;
       }
-    },
-
-    // 下移节点
-    handleMoveDown(data, node) {
-      console.log('下移节点:', data.label);
+      console.log('当前登录人ID:', yufp.session.userId || '');
+      // 构建FormData
+      const formData = new FormData();
+      formData.append('file', this.file);
+      formData.append('reviser', yufp.session.userId || ''); // 当前登录人id
+      formData.append('categoryId', this.tagSystem.id); // 当前体系id
       
-      // 找到父节点
-      const parent = node.parent;
-      if (!parent || parent.level === 0) return;
+      console.log('导入标签请求数据:', formData);
       
-      // 找到当前节点在父节点children中的索引
-      const siblings = parent.data.children || [];
-      const index = siblings.findIndex(item => item.id === data.id);
-      
-      // 如果不是最后一个节点,则下移
-      if (index < siblings.length - 1) {
-        // 交换位置
-        [siblings[index], siblings[index + 1]] = [siblings[index + 1], siblings[index]];
-      }
-    },
-
-    // 删除节点
-    handleDeleteNode(data, node) {
-      console.log('删除节点:', data.label);
-      
-      // 找到父节点
-      const parent = node.parent;
-      if (!parent) return;
-      
-      // 找到当前节点在父节点children中的索引
-      const siblings = parent.data.children || [];
-      const index = siblings.findIndex(item => item.id === data.id);
-      
-      // 删除节点
-      if (index > -1) {
-        siblings.splice(index, 1);
-      }
-    },
-
-    // 搜索
-    handleSearch() {
-      console.log('搜索:', this.searchKeyword);
-      // 搜索功能已经通过computed属性实现
+      // 调用接口
+      yufp.service.request({
+        url: backend.tagServer + "/api/aitagtaginfo/batchImport",
+        method: 'post',
+        data: formData,
+        headers: {
+          'Content-Type': 'multipart/form-data'
+        },
+        callback: (code, error, response) => {
+          if (response.code == '0') {
+            this.$message.success('标签导入成功');
+            this.showImportDialog = false;
+            this.file = null;
+            // 触发刷新事件,通知父组件刷新标签树
+            this.$emit('refresh');
+          } else {
+            this.$message.error(response.message || '标签导入失败');
+          }
+        }
+      });
     }
   }
 }
@@ -416,82 +335,198 @@ export default {
 
 <style scoped>
 .tag-tree {
-  background-color: #ffffff;
-  max-height: 600px;
-  overflow-y: auto;
-  box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+  height: 100%;
+}
+
+/* 标签树头部 */
+.tree-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding-bottom: 12px;
+  border-bottom: 1px solid #e8e8e8;
+}
+
+.tree-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+  margin: 0;
+}
+
+.tree-header-buttons {
+  display: flex;
+  gap: 8px;
 }
 
 /* 搜索框 */
 .tree-header-search {
-  margin-bottom: 12px;
-  width: 100%;
+  padding: 0 4px;
 }
 
-.tag-tree .el-tree {
-  background-color: transparent;
-  border: none;
+/* 标签树 */
+.el-tree {
+  flex: 1;
+  overflow-y: auto;
+  margin: 0;
 }
 
-.tag-tree ::v-deep .custom-tree-node {
-  flex: 1;
+/* 自定义树节点 */
+.custom-tree-node {
   display: flex;
-  align-items: center;
   justify-content: space-between;
-  font-size: 14px;
-  min-width: 0;
-  padding: 4px 0;
+  align-items: center;
+  width: 100%;
 }
 
-.tag-tree ::v-deep .node-label {
+.node-label {
   flex: 1;
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
   margin-right: 8px;
-  max-width: calc(100% - 20px);
 }
 
-/* 使用::v-deep()确保样式能够正确应用 */
-.tag-tree ::v-deep .node-dropdown {
-  opacity: 0;
+.node-dropdown {
+  display: flex;
+  align-items: center;
+}
+
+.node-action {
+  opacity: 0 !important;
   transition: opacity 0.3s;
 }
 
-.tag-tree ::v-deep .custom-tree-node:hover .node-dropdown {
-  opacity: 1;
+/* 鼠标滑过树节点时显示操作按钮 */
+.el-tree >>> .el-tree-node__content:hover .node-action {
+  opacity: 1 !important;
 }
 
-.tag-tree ::v-deep .el-dropdown-link {
+.vertical-dots {
+  font-size: 16px;
   cursor: pointer;
   color: #909399;
-  font-size: 12px;
-  margin-left: 8px;
+  transition: color 0.3s;
 }
 
-.tag-tree ::v-deep .el-dropdown-link.vertical-dots {
-  font-size: 16px;
-  line-height: 1;
-  margin-left: 3px;
-  margin-right: 3px;
+.vertical-dots:hover {
+  color: #409EFF;
 }
 
-.tag-tree ::v-deep .el-dropdown-link:hover {
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .tree-header {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 12px;
+  }
+  
+  .tree-header-search {
+    align-self: stretch;
+  }
+  
+  .tree-header-search .el-input {
+    width: 100%;
+  }
+}
+
+/* 批量导入标签弹框样式 */
+.import-dialog-content {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+}
+
+.import-hint {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 12px;
+  background-color: #ecf5ff;
+  border-radius: 4px;
   color: #409eff;
 }
 
-/* 确保下拉菜单显示 */
-.tag-tree ::v-deep .el-dropdown {
+.upload-area {
+  border: 2px dashed #d9d9d9;
+  border-radius: 4px;
+  padding: 40px 20px;
+  text-align: center;
+  cursor: pointer;
+  transition: all 0.3s;
   position: relative;
-  z-index: 1000;
+  overflow: hidden;
 }
 
-/* 树节点样式 */
-.tag-tree .el-tree-node.is-current > .el-tree-node__content {
+.upload-area:hover {
+  border-color: #409eff;
   background-color: #ecf5ff;
 }
 
-.tag-tree .el-tree-node__content {
-  padding-right: 8px;
+.upload-area.has-file {
+  border-color: #67c23a;
+  background-color: #f0f9eb;
+}
+
+.file-input {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  opacity: 0;
+  cursor: pointer;
+  z-index: 1;
+}
+
+.upload-content {
+  position: relative;
+  z-index: 0;
+}
+
+.upload-icon {
+  font-size: 48px;
+  color: #c0c4cc;
+  margin-bottom: 16px;
+  transition: color 0.3s;
+}
+
+.upload-area:hover .upload-icon {
+  color: #409eff;
+}
+
+.upload-area.has-file .upload-icon {
+  color: #67c23a;
+}
+
+.upload-content p {
+  margin: 8px 0;
+  color: #606266;
+}
+
+.upload-format {
+  font-size: 12px;
+  color: #909399;
+}
+
+.file-name {
+  font-size: 14px;
+  color: #67c23a;
+  font-weight: 500;
+  margin-top: 12px;
+}
+
+.dialog-footer {
+  text-align: right;
+}
+</style>
+
+<style>
+/* 全局样式:鼠标滑过树节点时显示操作按钮 */
+.el-tree-node__content:hover .node-action {
+  opacity: 1 !important;
 }
-</style>
+</style>

+ 120 - 17
web/src/views/aiTagging/taggingSystemManage/components/TreeSelect.vue

@@ -23,10 +23,11 @@
     <div v-if="showTree" class="tree-dropdown">
       <el-tree
         :data="filteredData"
-        :props="treeProps"
+        :props="resolvedTreeProps"
         node-key="id"
         default-expand-all
         :highlight-current="true"
+        :current-node-key="selectedNode?.id"
         @node-click="handleNodeClick"
       ></el-tree>
     </div>
@@ -64,44 +65,120 @@ export default {
       inputText: ''
     }
   },
+  mounted() {
+    // 添加全局点击事件监听器,点击外部区域关闭下拉框
+    document.addEventListener('click', this.handleClickOutside);
+  },
+  beforeDestroy() {
+    // 移除全局点击事件监听器
+    document.removeEventListener('click', this.handleClickOutside);
+  },
   computed: {
+    // 解析 treeProps,确保有默认值
+    resolvedTreeProps() {
+      const defaultProps = {
+        children: 'children',
+        label: 'label'
+      };
+      const resolved = { ...defaultProps, ...this.treeProps };
+      console.log('TreeSelect resolvedTreeProps:', resolved);
+      return resolved;
+    },
     displayText() {
-      return this.inputText || (this.selectedNode ? this.selectedNode[this.treeProps.label] : '');
+      console.log('TreeSelect displayText - selectedNode:', this.selectedNode);
+      console.log('TreeSelect displayText - resolvedTreeProps:', this.resolvedTreeProps);
+      if (this.selectedNode) {
+        const labelField = this.resolvedTreeProps.label;
+        const labelValue = this.selectedNode[labelField];
+        console.log('TreeSelect displayText - labelField:', labelField, 'labelValue:', labelValue);
+        return this.inputText || (labelValue || '');
+      }
+      return this.inputText || '';
     },
     filteredData() {
+      console.log('TreeSelect filteredData - data:', this.data);
+      console.log('TreeSelect filteredData - resolvedTreeProps:', this.resolvedTreeProps);
       if (!this.inputText) {
         return this.data;
       }
       
       const keyword = this.inputText.toLowerCase();
+      const labelField = this.resolvedTreeProps.label;
       
       const filterTree = (tree) => {
+        if (!Array.isArray(tree)) {
+          console.log('TreeSelect filterTree - tree is not array:', tree);
+          return [];
+        }
         return tree.filter(node => {
-          const matches = node[this.treeProps.label].toLowerCase().includes(keyword);
+          if (!node) {
+            return false;
+          }
+          const nodeLabel = node[labelField] || '';
+          console.log('TreeSelect filterTree - node:', node, 'nodeLabel:', nodeLabel);
+          const matches = nodeLabel.toLowerCase().includes(keyword);
           
-          if (node.children && node.children.length > 0) {
-            node.children = filterTree(node.children);
-            return matches || node.children.length > 0;
+          const childrenField = this.resolvedTreeProps.children;
+          if (node[childrenField] && Array.isArray(node[childrenField]) && node[childrenField].length > 0) {
+            node[childrenField] = filterTree(node[childrenField]);
+            return matches || node[childrenField].length > 0;
           }
           
           return matches;
         });
       };
       
-      return filterTree(JSON.parse(JSON.stringify(this.data)));
+      try {
+        return filterTree(JSON.parse(JSON.stringify(this.data)));
+      } catch (error) {
+        console.error('过滤树数据失败:', error);
+        return this.data;
+      }
     }
   },
   watch: {
     value: {
       handler(newVal) {
+        console.log('TreeSelect value change - newVal:', newVal);
         if (newVal) {
-          const nodeId = typeof newVal === 'object' ? newVal.id : newVal;
-          this.selectedNode = this.findNodeById(this.data, nodeId);
+          try {
+            // 如果传入的值是对象,直接使用该对象
+            if (typeof newVal === 'object' && newVal !== null) {
+              this.selectedNode = newVal;
+              console.log('TreeSelect value change - 直接使用对象:', this.selectedNode);
+            } else {
+              // 如果传入的值是ID,查找对应的节点
+              const nodeId = newVal;
+              if (nodeId) {
+                this.selectedNode = this.findNodeById(this.data, nodeId);
+                console.log('TreeSelect value change - 根据ID查找节点:', this.selectedNode);
+              } else {
+                this.selectedNode = null;
+              }
+            }
+          } catch (error) {
+            console.error('处理选中值失败:', error);
+            this.selectedNode = null;
+          }
         } else {
           this.selectedNode = null;
         }
       },
       immediate: true
+    },
+    data: {
+      handler(newData) {
+        console.log('TreeSelect data change - newData:', newData);
+      },
+      deep: true,
+      immediate: true
+    },
+    treeProps: {
+      handler(newProps) {
+        console.log('TreeSelect treeProps change - newProps:', newProps);
+      },
+      deep: true,
+      immediate: true
     }
   },
   methods: {
@@ -117,7 +194,10 @@ export default {
       console.log('点击后showTree:', this.showTree);
     },
     handleNodeClick(data, node) {
-      console.log('点击节点:', data.label);
+      console.log('点击节点:', data, 'node:', node);
+      const labelField = this.resolvedTreeProps.label;
+      const labelValue = data[labelField];
+      console.log('点击节点 - labelField:', labelField, 'labelValue:', labelValue);
       this.selectedNode = data;
       this.inputText = '';
       this.showTree = false;
@@ -132,18 +212,41 @@ export default {
       this.$emit('change', '');
     },
     findNodeById(tree, id) {
+      if (!Array.isArray(tree)) {
+        return null;
+      }
+      const childrenField = this.resolvedTreeProps.children;
       for (const node of tree) {
-        if (node.id === id) {
-          return node;
-        }
-        if (node.children && node.children.length > 0) {
-          const found = this.findNodeById(node.children, id);
-          if (found) {
-            return found;
+        if (node) {
+          // 如果传入的id是对象,直接比较对象引用
+          if (typeof id === 'object' && node === id) {
+            return node;
+          }
+          // 如果传入的id是字符串,比较id字段
+          if (typeof id === 'string' && node.id === id) {
+            return node;
+          }
+          // 如果传入的id是数字,比较id字段
+          if (typeof id === 'number' && node.id === id) {
+            return node;
+          }
+          // 递归查找子节点
+          if (node[childrenField] && Array.isArray(node[childrenField]) && node[childrenField].length > 0) {
+            const found = this.findNodeById(node[childrenField], id);
+            if (found) {
+              return found;
+            }
           }
         }
       }
       return null;
+    },
+    // 处理点击外部区域关闭下拉框
+    handleClickOutside(event) {
+      const treeSelectEl = this.$el;
+      if (treeSelectEl && !treeSelectEl.contains(event.target)) {
+        this.showTree = false;
+      }
     }
   }
 }

+ 33 - 30
web/src/views/aiTagging/taggingSystemManage/index.vue

@@ -4,7 +4,7 @@
     <div class="search-bar">
       <el-form :inline="true" :model="searchForm" class="search-form">
         <el-form-item label="标签体系名称:">
-          <el-input v-model="searchForm.name" placeholder="请输入" style="width: 300px;"></el-input>
+          <el-input v-model="searchForm.categoryNm" placeholder="请输入" style="width: 300px;"></el-input>
         </el-form-item>
         <el-form-item>
           <el-button type="primary" @click="handleSearch">查询</el-button>
@@ -31,20 +31,20 @@
         <div class="card-header">
           <div class="system-name">{{ system.categoryNm }}</div>
           <div class="card-actions">
-            <el-button type="text" icon="el-icon-edit" @click.stop="handleEditTagSystem(system)"></el-button>
+            <el-button v-if="system.state !== 0" type="text" icon="el-icon-edit" @click.stop="handleEditTagSystem(system)"></el-button>
             <el-dropdown trigger="click" @command="(command) => handleDropdownCommand(command, system)">
               <el-button type="text" icon="el-icon-more"></el-button>
               <el-dropdown-menu slot="dropdown">
-                <el-dropdown-item v-if="system.state === 1" command="disable">停用</el-dropdown-item>
+                <el-dropdown-item v-if="system.state === 0" command="disable">停用</el-dropdown-item>
                 <el-dropdown-item v-else command="enable">启用</el-dropdown-item>
-                <el-dropdown-item command="delete" divided>删除</el-dropdown-item>
+                <el-dropdown-item v-if="system.state !== 0" command="delete" divided>删除</el-dropdown-item>
               </el-dropdown-menu>
             </el-dropdown>
           </div>
         </div>
         <div class="system-status">
-          <el-tag :type="system.state === 1 ? 'success' : 'info'">
-            {{ system.state === 1 ? '已启用' : '未启用' }}
+          <el-tag :type="system.state === 0 ? 'success' : 'info'">
+            {{ system.state === 0 ? '已启用' : '已停用' }}
           </el-tag>
         </div>
         <div class="system-desc">{{ system.categoryDesc }}</div>
@@ -104,14 +104,14 @@ export default {
     return {
       // 搜索表单
       searchForm: {
-        name: ''
+        categoryNm: ''
       },
       
       // 标签体系列表
       tagSystems: [],
       
       // 分页数据
-      totalCount: 15,
+      totalCount: 0,
       currentPage: 1,
       pageSize: 10,
       
@@ -131,10 +131,11 @@ export default {
     // 加载标签体系列表
     loadTagSystems() {
       yufp.service.request({
+        // url: backend.tagServer + "/api/aitag-tagcategory/query",
         url: backend.tagServer + "/api/aitag-tagcategory/list",
         method: 'get',
         data: {
-          name: this.searchForm.name,
+          categoryNm: this.searchForm.name,
           page: this.currentPage,
           pageSize: this.pageSize
         },
@@ -150,14 +151,14 @@ export default {
     },
     // 搜索标签体系
     handleSearch() {
-      console.log('搜索标签体系:', this.searchForm.name)
+      console.log('搜索标签体系:', this.searchForm.categoryNm)
       this.currentPage = 1;
       this.loadTagSystems();
     },
 
     // 重置搜索
     handleReset() {
-      this.searchForm.name = ''
+      this.searchForm.categoryNm = ''
       this.currentPage = 1;
       this.loadTagSystems();
       console.log('重置搜索')
@@ -184,6 +185,10 @@ export default {
 
     // 编辑标签体系
     handleEditTagSystem(system) {
+      if (system.state === 0) {
+        this.$message.warning('已启用的标签体系不可编辑');
+        return;
+      }
       console.log('打开编辑标签体系对话框:', system.categoryNm)
       this.currentEditSystem = { 
         id: system.id,
@@ -225,18 +230,16 @@ export default {
 
     // 启用标签体系
     handleEnableTagSystem(system) {
-      console.log('启用标签体系:', system.name)
+      console.log('启用标签体系:', system.categoryNm)
       
       // 调用后端接口启用标签体系
       yufp.service.request({
-        url: backend.tagServer + "/api/aitag-tagcategory/enable",
+        url: backend.tagServer + "/api/aitag-tagcategory/enable?id=" + system.id,
         method: 'post',
-        data: {
-          id: system.id
-        },
+        data: {},
         callback: (code, error, response) => {
           if (response.code == '0') {
-            this.$message.success(`启用标签体系: ${system.name}`)
+            this.$message.success(`启用标签体系: ${system.categoryNm}`)
             
             // 重新加载标签体系列表
             this.loadTagSystems();
@@ -249,18 +252,16 @@ export default {
 
     // 停用标签体系
     handleDisableTagSystem(system) {
-      console.log('停用标签体系:', system.name)
+      console.log('停用标签体系:', system.categoryNm)
       
       // 调用后端接口停用标签体系
       yufp.service.request({
-        url: backend.tagServer + "/api/aitag-tagcategory/disable",
+        url: backend.tagServer + "/api/aitag-tagcategory/disable?id=" + system.id,
         method: 'post',
-        data: {
-          id: system.id
-        },
+        data: {},
         callback: (code, error, response) => {
           if (response.code == '0') {
-            this.$message.success(`停用标签体系: ${system.name}`)
+            this.$message.success(`停用标签体系: ${system.categoryNm}`)
             
             // 重新加载标签体系列表
             this.loadTagSystems();
@@ -273,23 +274,25 @@ export default {
 
     // 删除标签体系
     handleDeleteTagSystem(system) {
-      this.$confirm(`确定要删除标签体系「${system.name}」吗?`, '警告', {
+      if (system.state === 0) {
+        this.$message.warning('已启用的标签体系不可删除');
+        return;
+      }
+      this.$confirm(`确定要删除标签体系「${system.categoryNm}」吗?`, '警告', {
         confirmButtonText: '确定',
         cancelButtonText: '取消',
         type: 'warning'
       }).then(() => {
-        console.log('删除标签体系:', system.name)
+        console.log('删除标签体系:', system.categoryNm)
         
         // 调用后端接口删除标签体系
         yufp.service.request({
-          url: backend.tagServer + "/api/aitag-tagcategory/delete",
+          url: backend.tagServer + "/api/aitag-tagcategory/delete?id=" + system.id,
           method: 'post',
-          data: {
-            id: system.id
-          },
+          data: {},
           callback: (code, error, response) => {
             if (response.code == '0') {
-              this.$message.success(`删除标签体系: ${system.name}`)
+              this.$message.success(`删除标签体系: ${system.categoryNm}`)
               
               // 重新加载标签体系列表
               this.loadTagSystems();

+ 34 - 16
web/vite.config.js

@@ -11,15 +11,13 @@ import vue2 from '@vitejs/plugin-vue2';
 import vue2Jsx from '@vitejs/plugin-vue2-jsx';
 import { viteExternalsPlugin } from 'vite-plugin-externals';
 
-import mockDevServer from 'vite-plugin-mock-dev-server';
 // 引入svg需要用到的插件, 安装 yarn add vite-plugin-svg-icons
 import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
 
 // 导入低码代理配置和忽略编译配置
-import { watchOptionsIgnored } from '@yuxp/previewer/src/integration/server';
-import createYuxpServerPlugin from '@yuxp/integration/yuxpServer/vite-plugin-yuxp-server';
 import { getProxyConfig } from './build-proxy';
-import insertYuxpProxy, { VITE_APP_YUXP_WATCH_IGNORED } from './src/config/yuxp/proxyConfig.js';
+import insertYuxpProxy from './src/config/yuxp/proxyConfig.js';
+
 // 根据运行模式加载环境变量,并配置Vite
 export default ({ mode }) => {
   // 加载环境变量
@@ -27,13 +25,13 @@ export default ({ mode }) => {
   const {
     VITE_APP_DEV_PORT,
     VITE_APP_BASE_URL,
-    VITE_APP_PROXY_API,
-    VITE_APP_BASE_API,
-    VITE_APP_BASE_AUTH,
-    VITE_APP_PROXY_SHUFFLE,
   } = loadEnvVar;
   const proxy = getProxyConfig(loadEnvVar, insertYuxpProxy);
   const viteHost = '0.0.0.0';
+  
+  // 判断是否为开发模式
+  const isDev = mode === 'development';
+  
   // 返回Vite配置对象
   return defineConfig({
     define: {
@@ -43,7 +41,6 @@ export default ({ mode }) => {
     plugins: [
       vue2(),
       vue2Jsx(),
-      // mockDevServer(),
       createSvgIconsPlugin({
         // 配置svg图标
         iconDirs: [
@@ -55,12 +52,11 @@ export default ({ mode }) => {
         ],
         symbolId: 'icon-[name]',
       }),
-      // 低码yuxpServer服务插件
-      //createYuxpServerPlugin(),
-      viteExternalsPlugin({
+      // 生产模式下使用 externals
+      !isDev && viteExternalsPlugin({
         echarts: 'echarts',
       }),
-    ], // 使用的插件
+    ].filter(Boolean), // 过滤掉 false 值
     resolve: {
       alias: {
         package: resolve(__dirname, 'package.json'),
@@ -116,8 +112,29 @@ export default ({ mode }) => {
       host: viteHost, // 监听所有网络接口
       cors: true, // 启用跨域请求
       proxy: proxy, // 使用定义的代理规则
+      // 优化热更新配置
+      hmr: {
+        overlay: false, // 关闭错误遮罩,提高响应速度
+      },
+      // 优化文件监听配置
       watch: {
-        ignored: (filePath) => watchOptionsIgnored(filePath, VITE_APP_YUXP_WATCH_IGNORED),
+        // 使用数组形式的忽略规则,性能更好
+        ignored: [
+          '**/node_modules/**',
+          '**/.git/**',
+          '**/dist/**',
+          '**/coverage/**',
+          '**/*.log',
+          '**/.temp/**',
+          '**/.cache/**',
+          '**/public/**',
+          '**/.vscode/**',
+          '**/.idea/**',
+        ],
+        // 使用原生监听,提高性能
+        usePolling: false,
+        // 增加防抖延迟,减少频繁触发
+        interval: 500,
       },
     },
     build: {
@@ -126,8 +143,9 @@ export default ({ mode }) => {
       sourcemap: false, // 是否生成源码映射
       chunkSizeWarningLimit: 2000, // 设置块大小警告限制
       reportCompressedSize: false, // 是否报告压缩后的大小
-      minify: 'terser',
-      cssMinify: false,
+      // 开发模式下禁用压缩,提高构建速度
+      minify: isDev ? false : 'terser',
+      cssMinify: !isDev,
       terserOptions: {
         compress: {
           drop_debugger: true,