2 Angajamente 6df754c69c ... eff663be8c

Autor SHA1 Permisiunea de a trimite mesaje. Dacă este dezactivată, utilizatorul nu va putea trimite nici un fel de mesaj Data
  jianggs eff663be8c Merge branch 'master' of http://git.yangzhiqiang.tech/jiayq/ai-tagging 2 săptămâni în urmă
  jianggs 60e6eff2fc bug修复,页面交互优化 2 săptămâni în urmă
26 a modificat fișierele cu 2899 adăugiri și 1039 ștergeri
  1. 472 0
      web/docs/system-implementation.md
  2. 253 0
      web/docs/智能打标确认页面前端对接说明.md
  3. 8 0
      web/src/router/modules/aiTagging-routes.js
  4. 2 2
      web/src/router/router-filter.js
  5. 8 2
      web/src/views/aiTagging/externalPage/components/IntelligentRecommend.vue
  6. 15 8
      web/src/views/aiTagging/externalPage/components/ManualTagging.vue
  7. 67 41
      web/src/views/aiTagging/externalPage/externalEdit.vue
  8. 8 5
      web/src/views/aiTagging/externalPage/externalResult.vue
  9. 264 23
      web/src/views/aiTagging/externalPage/index.vue
  10. 2 2
      web/src/views/aiTagging/taggingDetail/index.vue
  11. 3 2
      web/src/views/aiTagging/taggingLogs/components/LogDetailDrawer.vue
  12. 8 5
      web/src/views/aiTagging/taggingLogs/index.vue
  13. 82 103
      web/src/views/aiTagging/taggingResults/components/Overview.vue
  14. 16 21
      web/src/views/aiTagging/taggingResults/index.vue
  15. 106 41
      web/src/views/aiTagging/taggingSystemManage/TagSystemEdit.vue
  16. 110 83
      web/src/views/aiTagging/taggingSystemManage/components/TagConfig.vue
  17. 24 5
      web/src/views/aiTagging/taggingSystemManage/components/TagDetailEdit.vue
  18. 24 1
      web/src/views/aiTagging/taggingSystemManage/components/TagDetailEditRegexDrawer.vue
  19. 8 3
      web/src/views/aiTagging/taggingSystemManage/components/TagDetailView.vue
  20. 5 4
      web/src/views/aiTagging/taggingSystemManage/components/TagTest.vue
  21. 112 57
      web/src/views/aiTagging/taggingSystemManage/components/TagTree.vue
  22. 9 2
      web/src/views/aiTagging/taggingSystemManage/index.vue
  23. 70 27
      web/src/views/aiTagging/taggingTest/TagTestComponent.vue
  24. 40 602
      web/src/views/aiTagging/taggingTest/index.vue
  25. 628 0
      web/src/views/aiTagging/taggingTest/taggingApply.vue
  26. 555 0
      web/src/views/aiTagging/taggingTest/taggingConfirm.vue

+ 472 - 0
web/docs/system-implementation.md

@@ -0,0 +1,472 @@
+# 智能打标系统第三方对接实现指南
+
+## 概述
+
+本文档详细说明 智能打标 系统如何实现与第三方系统的对接功能,包括路由配置、消息处理、数据验证等系统内部实现细节。
+
+## 路由配置
+
+### 1. 路由白名单配置
+
+**文件路径**:`/src/router/router-filter.js`
+
+**配置内容**:
+```javascript
+const whiteList = ['/login', '/forgetPwd', '/about', '/externalPage'];
+```
+
+**说明**:将 `/externalPage` 添加到白名单,确保第三方系统可以直接访问该页面而不需要登录。
+
+### 2. 路由定义
+
+**文件路径**:`/src/router/index.js`
+
+**配置内容**:
+```javascript
+{
+  path: '/externalPage',
+  component: () => import('@/views/aiTagging/externalPage/index.vue'),
+  meta: {
+    title: '外部打标页面',
+    noCache: true
+  }
+}
+```
+
+## 页面实现
+
+### 1. 主页面结构
+
+**文件路径**:`/src/views/aiTagging/externalPage/index.vue`
+
+**核心功能**:
+- 页面加载完成后发送 `externalPageLoaded` 消息
+- 监听并处理第三方系统发送的 `externalParams` 消息
+- 验证数据完整性并发送 `externalParamsReceived` 确认消息
+- 显示数据接收状态和加载状态
+
+### 2. 消息处理逻辑
+
+**初始化消息监听器**:
+```javascript
+mounted() {
+  // 监听来自第三方系统的消息
+  window.addEventListener('message', this.handleMessage);
+  
+  // 页面加载完成后发送加载完成消息
+  this.sendPageLoadedMessage();
+},
+
+beforeDestroy() {
+  // 清理事件监听器
+  window.removeEventListener('message', this.handleMessage);
+}
+```
+
+**页面加载完成消息**:
+```javascript
+sendPageLoadedMessage() {
+  const message = {
+    type: 'externalPageLoaded',
+    loadedAt: Date.now(),
+    status: 'ready'
+  };
+  
+  // 发送消息给父窗口(iframe 方式)
+  if (window.parent !== window) {
+    window.parent.postMessage(message, '*');
+  }
+}
+```
+
+**处理第三方系统消息**:
+```javascript
+handleMessage(event) {
+  const data = event.data;
+  
+  if (data && data.type === 'externalParams') {
+    this.processExternalParams(data);
+  }
+}
+
+processExternalParams(params) {
+  // 验证必填字段
+  const missingFields = this.validateRequiredFields(params);
+  
+  if (missingFields.length > 0) {
+    // 数据不完整,显示错误信息
+    this.loadingStatus = 'error';
+    this.errorMessage = '数据接收不完整,请检查缺失字段';
+    this.missingFields = missingFields;
+    this.receivedData = params;
+  } else {
+    // 数据完整,开始处理
+    this.loadingStatus = 'processing';
+    this.receivedData = params;
+    
+    // 存储参数
+    this.businessAttr = params.business_attr;
+    this.userData = {
+      user_id: params.user_id,
+      user_nm: params.user_nm,
+      user_org: params.user_org || '',
+      user_endpoint: params.user_endpoint || '',
+      contract_no: params.contract_no || ''
+    };
+    
+    // 发送确认消息
+    this.sendConfirmationMessage(params);
+    
+    // 调用业务属性查询
+    this.queryBusinessAttr();
+  }
+}
+```
+
+**发送确认消息**:
+```javascript
+sendConfirmationMessage(originalData) {
+  const message = {
+    type: 'externalParamsReceived',
+    originalData: originalData,
+    receivedAt: Date.now(),
+    status: 'success'
+  };
+  
+  // 发送消息给父窗口(iframe 方式)
+  if (window.parent !== window) {
+    window.parent.postMessage(message, '*');
+  }
+}
+```
+
+## 数据验证
+
+### 1. 必填字段验证
+
+**验证函数**:
+```javascript
+validateRequiredFields(params) {
+  const missingFields = [];
+  
+  if (!params.business_attr) {
+    missingFields.push('贷款申请编号');
+  }
+  
+  if (!params.user_id) {
+    missingFields.push('用户ID');
+  }
+  
+  if (!params.user_nm) {
+    missingFields.push('用户姓名');
+  }
+  
+  if (!params.user_org) {
+    missingFields.push('所属机构');
+  }
+  
+  if (!params.user_endpoint) {
+    missingFields.push('所属行社');
+  }
+  
+  if (!params.contract_no) {
+    missingFields.push('合同编号');
+  }
+  
+  return missingFields;
+}
+```
+
+## API 调用
+
+### 1. 业务属性查询
+
+**配置说明**:API 请求不需要携带 token,需要设置 `needToken: false`
+
+**实现代码**:
+```javascript
+queryBusinessAttr() {
+  yufp.service.request({
+    url: backend.tagServer + "/api/fastapi/query",
+    method: 'get',
+    data: {
+      businessAttr: this.businessAttr
+    },
+    needToken: false,  // 确保设置为 false
+    callback: (code, error, response) => {
+      if (response.code == '0') {
+        // 处理成功响应
+        this.loadingStatus = 'success';
+        this.taggingData = response.data;
+      } else {
+        // 处理错误
+        this.loadingStatus = 'error';
+        this.errorMessage = response.message || '查询业务属性失败';
+      }
+    }
+  });
+}
+```
+
+## 加载状态管理
+
+### 1. 状态定义
+
+```javascript
+data() {
+  return {
+    // 加载状态:loading, success, error, idle
+    loadingStatus: 'idle',
+    // 错误信息
+    errorMessage: '',
+    // 缺失字段
+    missingFields: [],
+    // 接收到的数据
+    receivedData: {},
+    // 业务属性
+    businessAttr: '',
+    // 用户数据
+    userData: {},
+    // 打标数据
+    taggingData: {}
+  };
+}
+```
+
+### 2. 状态显示
+
+**加载中**:
+```html
+<div v-if="loadingStatus === 'loading'" class="loading-container">
+  <el-icon class="is-loading"><loading /></el-icon>
+  <span>数据加载中...</span>
+</div>
+```
+
+**数据不完整**:
+```html
+<div v-else-if="loadingStatus === 'error'" class="error-container">
+  <el-alert
+    title="数据接收不完整"
+    type="error"
+    :closable="false"
+    show-icon
+  >
+    <div class="error-content">
+      <p>{{ errorMessage }}</p>
+      <div v-if="missingFields.length > 0" class="missing-fields">
+        <span class="missing-label">缺失字段:</span>
+        <el-tag v-for="field in missingFields" :key="field" type="danger" size="small">
+          {{ field }}
+        </el-tag>
+      </div>
+      <div v-if="Object.keys(receivedData).length > 0" class="received-data">
+        <span class="received-label">已接收数据:</span>
+        <pre>{{ JSON.stringify(receivedData, null, 2) }}</pre>
+      </div>
+    </div>
+  </el-alert>
+</div>
+```
+
+**加载成功**:
+```html
+<div v-else-if="loadingStatus === 'success'" class="success-container">
+  <!-- 打标结果展示 -->
+  <TaggingResult :tagging-data="taggingData" />
+</div>
+```
+
+## 样式优化
+
+### 1. 响应式布局
+
+```css
+.external-page-container {
+  min-height: 100vh;
+  background-color: #f5f7fa;
+  display: flex;
+  flex-direction: column;
+}
+
+.content-wrapper {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  padding: 20px;
+}
+
+.loading-container,
+.error-container,
+.success-container {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  padding: 40px;
+  background-color: #fff;
+  border-radius: 4px;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+}
+```
+
+### 2. 滚动支持
+
+**确保 iframe 内容可以滚动**:
+```html
+<iframe 
+  src="http://your-domain.com/#/externalPage" 
+  frameborder="0" 
+  width="100%" 
+  height="100%" 
+  scrolling="auto"
+></iframe>
+```
+
+## 错误处理
+
+### 1. 网络错误处理
+
+```javascript
+// 渐进式超时机制
+const timeouts = [5000, 10000, 15000];
+let currentTimeoutIndex = 0;
+
+function checkPageLoaded() {
+  if (pageLoaded) {
+    return;
+  }
+  
+  if (currentTimeoutIndex < timeouts.length) {
+    setTimeout(() => {
+      if (!pageLoaded) {
+        showLoadingMessage(`页面加载中,已等待 ${timeouts[currentTimeoutIndex] / 1000} 秒...`);
+        currentTimeoutIndex++;
+        checkPageLoaded();
+      }
+    }, timeouts[currentTimeoutIndex]);
+  } else {
+    showErrorMessage('页面加载超时,请刷新重试');
+  }
+}
+```
+
+### 2. 消息处理错误
+
+```javascript
+handleMessage(event) {
+  try {
+    const data = event.data;
+    
+    if (data && data.type === 'externalParams') {
+      this.processExternalParams(data);
+    }
+  } catch (error) {
+    console.error('处理消息时发生错误:', error);
+    this.loadingStatus = 'error';
+    this.errorMessage = '处理数据时发生错误';
+  }
+}
+```
+
+## 性能优化
+
+### 1. 页面加载优化
+
+- **懒加载**:使用 Vue 的异步组件
+- **资源压缩**:压缩静态资源文件
+- **缓存策略**:合理设置缓存策略
+
+### 2. 通信优化
+
+- **消息批处理**:合并多次消息为一次发送
+- **消息过滤**:只处理必要的消息类型
+- **错误重试**:实现自动重试机制
+
+## 安全措施
+
+### 1. 消息来源验证
+
+```javascript
+// 验证消息来源
+const trustedOrigins = ['https://trusted-third-party.com'];
+
+window.addEventListener('message', (event) => {
+  // 验证消息来源
+  if (!trustedOrigins.includes(event.origin)) {
+    console.warn('来自未知来源的消息:', event.origin);
+    return;
+  }
+  
+  // 处理消息
+});
+```
+
+### 2. 数据验证
+
+- **输入验证**:验证所有输入数据的合法性
+- **防 XSS**:对用户输入进行转义处理
+- **数据脱敏**:敏感数据脱敏处理
+
+## 调试工具
+
+### 1. 日志记录
+
+```javascript
+// 详细的日志记录
+console.log('页面加载完成,发送消息:', message);
+console.log('接收到第三方消息:', event.data);
+console.log('消息来源:', event.origin);
+console.log('数据验证结果:', missingFields);
+```
+
+### 2. 开发模式提示
+
+```javascript
+if (process.env.NODE_ENV === 'development') {
+  console.log('开发模式:第三方对接功能');
+  // 模拟数据
+  setTimeout(() => {
+    this.processExternalParams({
+      type: 'externalParams',
+      business_attr: 'TEST2024001',
+      user_id: 'testuser001',
+      user_nm: '测试用户',
+      user_org: '测试机构',
+      user_endpoint: '测试分行',
+      contract_no: 'TESTCONTRACT001',
+      timestamp: Date.now(),
+      source: 'testSystem'
+    });
+  }, 1000);
+}
+```
+
+## 版本兼容性
+
+### 当前版本特性
+- ✅ 支持 iframe 和新窗口两种方式
+- ✅ 完整的消息通信协议
+- ✅ 必填字段验证
+- ✅ 渐进式超时机制
+- ✅ 错误处理和状态反馈
+- ✅ 支持 iframe 滚动
+
+### 向后兼容
+系统设计考虑了向后兼容性,新增消息类型不会影响现有功能。
+
+## 更新日志
+
+### v1.0 (当前版本)
+- 初始版本发布
+- 实现完整的第三方对接功能
+- 支持 iframe 和新窗口两种方式
+- 完善的错误处理机制
+- 新增数据接收状态反馈功能
+- 实现 iframe 滚动支持
+- 补充详细的配置示例和最佳实践
+
+---
+
+*本文档最后更新:2026年3月17日*

+ 253 - 0
web/docs/智能打标确认页面前端对接说明.md

@@ -0,0 +1,253 @@
+# 智能打标确认页面前端对接说明
+
+## 概述
+
+本文档详细说明第三方系统如何通过 `/externalPage` 接口与 智能打标 系统进行集成。对接采用安全的跨窗口通信机制,确保数据传输的安全性和可靠性。
+
+## 对接方式
+
+### 1. 嵌入方式(推荐)
+使用 iframe 嵌入打标确认页面,通过 postMessage 进行跨窗口通信。
+
+## 通信协议
+
+### 消息类型定义
+
+#### 1. 页面加载完成消息(由打标系统发送)
+```javascript
+{
+  type: 'externalPageLoaded',
+  loadedAt: 1672531200000, // 时间戳
+  status: 'ready'
+}
+```
+
+#### 2. 用户数据消息(由第三方系统发送)
+```javascript
+{
+  type: 'externalParams',
+  business_attr: 'LOAN2024001',     // 贷款申请编号(必填)
+  user_id: 'user001',              // 用户ID(必填)
+  user_nm: '张三',                 // 用户姓名(必填)
+  user_org: '福建农信',            // 所属机构(必填)
+  user_endpoint: '福州分行',       // 所属行社(必填)
+  contract_no: 'CONTRACT001',      // 合同编号(必填)
+  timestamp: 1672531200000,        // 时间戳
+  source: 'thirdPartySystem'       // 来源标识
+}
+```
+
+#### 3. 数据接收确认消息(由打标系统发送)
+```javascript
+{
+  type: 'externalParamsReceived',
+  originalData: {
+    // 原始用户数据
+  },
+  receivedAt: 1672531200000,      // 接收时间戳
+  status: 'success'                // 接收状态
+}
+```
+
+## 对接流程
+
+### 步骤1:加载打标页面
+```javascript
+// iframe嵌入方式
+const iframeUrl = 'http://your-domain.com/#/externalPage';
+const iframe = document.createElement('iframe');
+iframe.src = iframeUrl;
+iframe.style.width = '100%';
+iframe.style.height = '600px';
+document.body.appendChild(iframe);
+```
+
+### 步骤2:监听页面加载完成消息
+```javascript
+// 监听智能打标页面发送的加载完成消息
+window.addEventListener('message', (event) => {
+  if (event.data && event.data.type === 'externalPageLoaded') {
+    console.log('智能打标页面已加载完成,可以发送用户数据');
+    
+    // 发送用户数据
+    sendUserData();
+  }
+});
+```
+
+### 步骤3:发送用户数据
+```javascript
+function sendUserData() {
+  const userData = {
+    type: 'externalParams',
+    business_attr: 'LOAN2024001',
+    user_id: 'user001',
+    user_nm: '张三',
+    user_org: '福建农信',
+    user_endpoint: '福州分行',
+    contract_no: 'CONTRACT001',
+    timestamp: Date.now(),
+    source: 'yourSystemName'
+  };
+
+  // 发送给iframe
+  if (iframe && iframe.contentWindow) {
+    iframe.contentWindow.postMessage(userData, '*');
+  }
+}
+```
+
+### 步骤4:监听数据接收确认
+```javascript
+// 监听智能打标系统的确认消息
+window.addEventListener('message', (event) => {
+  if (event.data && event.data.type === 'externalParamsReceived') {
+    console.log('智能打标系统已成功接收数据');
+    
+    // 更新UI状态
+    updateIntegrationStatus('数据接收成功');
+  }
+});
+```
+
+## 必填字段验证
+
+智能打标系统会对以下字段进行必填验证:
+- `business_attr` - 贷款申请编号
+- `user_id` - 用户ID  
+- `user_nm` - 用户姓名
+- `user_org` - 所属机构
+- `user_endpoint` - 所属行社
+- `contract_no` - 合同编号
+
+如果缺少任一必填字段,系统将拒绝处理并显示警告信息。
+
+## 安全注意事项
+
+### 1. 消息来源验证
+在生产环境中,建议验证消息来源:
+```javascript
+window.addEventListener('message', (event) => {
+  // 验证消息来源域名
+  if (event.origin !== 'https://your-tagging-system.com') {
+    return;
+  }
+  
+  // 处理消息
+});
+```
+
+### 2. 数据验证
+- 验证必填字段完整性
+- 对用户输入进行合法性检查
+- 防止 XSS 攻击
+
+### 3. 错误处理
+- 实现完善的错误捕获机制
+- 提供用户友好的错误提示
+- 记录通信日志便于排查
+
+## 调试指南
+
+### 1. 浏览器开发者工具
+1. 打开控制台查看错误信息
+2. 使用 Network 面板检查请求状态
+3. 查看 Console 面板的通信日志
+
+### 2. 消息跟踪
+```javascript
+// 添加详细的日志记录
+console.log('发送消息:', message);
+console.log('接收消息:', event.data);
+console.log('消息来源:', event.origin);
+```
+
+### 3. 网络监控
+- 监控页面加载时间
+- 检查跨域请求是否被阻止
+- 验证 SSL 证书有效性
+
+## 性能优化建议
+
+### 1. 页面加载优化
+- 压缩静态资源
+- 使用 CDN 加速
+- 实现懒加载机制
+
+### 2. 通信优化
+- 减少不必要的消息传输
+- 使用消息批处理
+- 实现消息缓存机制
+
+### 3. 用户体验优化
+- 提供清晰的加载状态
+- 实现自动重试机制
+- 添加操作超时提示
+
+## 常见问题及解决方案
+
+### 问题1:postMessage 通信失败
+**症状**:数据发送后无响应
+**原因**:
+1. 消息格式不正确
+2. 目标窗口未正确引用
+3. 跨域限制
+**解决方案**:
+1. 检查消息格式是否符合规范
+2. 确保使用正确的窗口引用(contentWindow 或 opener)
+3. 在生产环境配置正确的域名白名单
+
+### 问题2:页面加载缓慢导致通信超时
+**症状**:长时间等待无响应
+**原因**:网络延迟或页面资源加载慢
+**解决方案**:
+1. 系统已实现渐进式超时机制(5s/10s/15s)
+2. 添加加载状态提示
+3. 优化第三方系统网络环境
+
+### 问题3:iframe 内容无法滚动
+**症状**:内容超出显示区域无法查看
+**原因**:iframe 样式限制
+**解决方案**:
+1. 添加 scrolling="auto" 属性:
+   ```html
+   <iframe src="http://your-domain.com/#/externalPage" 
+           frameborder="0" 
+           width="100%" 
+           height="100%" 
+           scrolling="auto"></iframe>
+   ```
+2. 设置合适的 height 和 width
+3. 使用响应式 CSS 样式:
+   ```css
+   .iframe-container {
+     flex: 1;
+     min-height: 0;
+     overflow: hidden;
+     border: 1px solid #e4e7ed;
+     border-radius: 4px;
+     background-color: #fff;
+     display: flex;
+     flex-direction: column;
+   }
+   
+   .iframe-container iframe {
+     flex: 1;
+     width: 100%;
+     height: 100%;
+     border: none;
+     background-color: #fff;
+   }
+   ```
+
+## 技术支持
+
+如遇技术问题,请提供以下信息:
+1. 浏览器版本和操作系统
+2. 错误截图或日志
+3. 复现步骤
+4. 网络环境信息
+
+---
+
+*本文档最后更新:2026年3月17日*

+ 8 - 0
web/src/router/modules/aiTagging-routes.js

@@ -21,6 +21,14 @@ const aiTaggingRoutes = [
     name: 'taggingDetail',
     meta: { title: '打标详情', noCache: true },
     hidden: true
+  },
+  {
+    path: '/externalPage',
+    component: () => import('@/views/aiTagging/externalPage/index'),
+    name: 'externalPage',
+    meta: { title: '外部打标页面', noCache: true },
+    hidden: true,
+    fullscreen: true
   }
   // {
   //   path: 'instanceInfo',

+ 2 - 2
web/src/router/router-filter.js

@@ -42,7 +42,7 @@ const unLoginFn = (to, from, next) => {
   /**
    * 路由白名单(不作权限过滤),不跳转路由
    */
-  const whiteList = ['/login', '/forgetPwd', '/about'];
+  const whiteList = ['/login', '/forgetPwd', '/about', '/externalPage'];
   if (whiteList.indexOf(to.path) !== -1) {
     // 2-1.当前访问路由,在免登录白名单中,直接访问当前
     next();
@@ -84,7 +84,7 @@ router.beforeEach(async (to, from, next) => {
       to = { path: to.redirectedFrom };
       next({ ...to, replace: true });
     } else {
-      if (to.matched[1].meta.title === undefined && to.matched[1].meta.params === undefined) {
+      if (to.matched[1] && to.matched[1].meta && to.matched[1].meta.title === undefined && to.matched[1].meta.params === undefined) {
         to.matched[1].meta = sessionStore.get(ROUTE_MATCHED);
       }
       next();

+ 8 - 2
web/src/views/aiTagging/externalPage/components/IntelligentRecommend.vue

@@ -139,13 +139,17 @@ export default {
 <style scoped>
 .intelligent-recommend {
   padding: 20px;
+  height: calc(100% - 40px);
+  display: flex;
+  flex-direction: column;
 }
 
 .section-header {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  margin-bottom: 24px;
+  margin-bottom: 16px;
+  flex-shrink: 0;
 }
 
 .section-title {
@@ -200,7 +204,9 @@ export default {
 }
 
 .recommendation-list {
-  margin-top: 20px;
+  flex: 1;
+  overflow-y: auto;
+  min-height: 0;
 }
 
 .recommendation-item {

+ 15 - 8
web/src/views/aiTagging/externalPage/components/ManualTagging.vue

@@ -1,6 +1,5 @@
 <template>
   <div class="manual-tagging">
-    <!-- 人工打标标题 -->
     <div class="section-header">
       <h3 class="section-title">人工打标</h3>
       <a href="javascript:void(0)" class="switch-to-intelligent" @click="$emit('switchToIntelligent')">
@@ -25,11 +24,11 @@
         ref="tagTree"
         :data="tagTreeData"
         :props="defaultProps"
+        :filter-node-method="filterNode"
+        class="tag-tree-list"
         node-key="id"
         :highlight-current="true"
-        :filter-node-method="filterNode"
         @node-click="handleTagClick"
-        class="tag-tree-list"
       ></el-tree>
     </div>
   </div>
@@ -134,6 +133,7 @@ export default {
           tagNm: "",
           categoryId: selectedSystem.id
         },
+        needToken: false,
         callback: (code, error, response) => {
           if (response.code == '0') {
             this.tagTreeData = response.data || [];
@@ -185,13 +185,17 @@ export default {
 <style scoped>
 .manual-tagging {
   padding: 20px;
+  height: calc(100% - 40px);
+  display: flex;
+  flex-direction: column;
 }
 
 .section-header {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  margin-bottom: 24px;
+  margin-bottom: 20px;
+  flex-shrink: 0;
 }
 
 .section-title {
@@ -212,20 +216,23 @@ export default {
 }
 
 .search-box {
-  margin-bottom: 20px;
+  display: flex;
+  justify-content: flex-end;
+  margin-bottom: 5px;
+  flex-shrink: 0;
 }
 
 .search-input {
-  width: 100%;
-  max-width: 400px;
+  width: 240px;
 }
 
 .tag-tree {
-  max-height: 500px;
+  flex: 1;
   overflow-y: auto;
   border: 1px solid #EBEEF5;
   border-radius: 4px;
   padding: 10px;
+  min-height: 0;
 }
 
 .tag-tree-list {

+ 67 - 41
web/src/views/aiTagging/externalPage/externalEdit.vue

@@ -26,8 +26,9 @@
           class="selected-tag"
           closable
           @close="handleTagRemove(index)"
+          :title="tag.tag_name"
         >
-          {{ tag.tag_name }}
+          {{ tag.tag_name.length > 5 ? tag.tag_name.substring(0, 5) + '...' : tag.tag_name }}
         </el-tag>
       </div>
       
@@ -45,8 +46,9 @@
             class="nav-item"
             :class="{ 'active': currentSystem === system }"
             @click="handleSystemSelect(system)"
+            :title="system"
           >
-            {{ system }}
+            <span class="nav-item-text">{{ system }}</span>
           </div>
         </div>
       </div>
@@ -96,17 +98,20 @@ export default {
     queryResult: {
       type: Object,
       default: null
+    },
+    userData: {
+      type: Object,
+      default: () => ({
+        "user_id": "", 
+        "user_nm": "",
+        "contract_no": "", 
+        "user_org": "", 
+        "user_endpoint": "", 
+      })
     }
   },
   data() {
     return {
-      testData:{
-        "user_id": "12345", 
-        "user_nm": "张三",
-        "contract_no": "TET-2026-03-081613", 
-        "user_org": "XX公司", 
-        "user_endpoint": "XX网点", 
-      },
       // 当前模式:intelligent(智能推荐)或 manual(人工打标)
       currentMode: 'intelligent',
       
@@ -179,19 +184,17 @@ export default {
       const taggingData = this.assembleTaggingData();
       console.log('确认打标数据:', taggingData);
       
-      // 触发确认事件,传递打标数据给父组件
-      this.$emit('confirm', taggingData);
-      
       // 调用反馈接口
       yufp.service.request({
         url: backend.tagServer + "/api/fastapi/feedback",
         method: 'post',
         data: taggingData,
+        needToken: false,
         callback: (code, error, response) => {
           if (response.code == '0') {
             this.$message.success('打标成功');
-            // 调用父组件的refresh方法重置页面状态
-            this.$emit('refresh');
+            // 打标成功后,触发确认事件并传递打标数据给父组件
+            this.$emit('confirm', taggingData);
           } else {
             this.$message.error(response.message || '打标失败');
           }
@@ -201,8 +204,8 @@ export default {
     
     // 组装打标数据
     assembleTaggingData() {
-      // 从testData中获取基础信息
-      const { user_id, user_nm, contract_no, user_org, user_endpoint } = this.testData;
+      // 从userData中获取基础信息
+      const { user_id, user_nm, contract_no, user_org, user_endpoint } = this.userData;
       
       // 获取business_attr
       const business_attr = this.businessAttr;
@@ -407,6 +410,7 @@ export default {
         url: backend.tagServer + "/api/aitag-tagcategory/enablelistnoauth",
         method: 'get',
         data: {},
+        needToken: false,
         callback: (code, error, response) => {
           if (response.code == '0') {
             // 处理返回的标签体系数据
@@ -575,11 +579,6 @@ export default {
       }
     },
     
-    // 刷新打标结果
-    refreshTaggingResult() {
-      this.$emit('refresh');
-    },
-    
     // 设置推荐数据
     setRecommendations(data) {
       this.originalResponseData = data;
@@ -619,8 +618,11 @@ export default {
 .intelligent-tagging {
   padding: 0;
   background-color: transparent !important;
-  height: 100% !important;
+  height: 100vh !important;
   box-shadow: none !important;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
 }
 
 /* 顶部通知栏 */
@@ -684,10 +686,22 @@ export default {
   margin-right: 8px;
 }
 
+/* 顶部通知栏 */
+.top-notification {
+  flex-shrink: 0;
+}
+
+/* 已选标签 */
+.selected-tags {
+  flex-shrink: 0;
+}
+
 /* 主体内容 */
 .main-content {
   display: flex;
-  min-height: 600px;
+  flex: 1;
+  overflow: auto;
+  min-height: 0;
 }
 
 /* 左侧标签体系导航 */
@@ -696,6 +710,8 @@ export default {
   background-color: #FFFFFF;
   border-right: 1px solid #EBEEF5;
   padding: 20px;
+  height: calc(100% - 40px);
+  overflow: hidden;
 }
 
 .nav-title {
@@ -709,6 +725,8 @@ export default {
   list-style: none;
   padding: 0;
   margin: 0;
+  overflow-y: auto;
+  height: calc(100% - 40px);
 }
 
 .nav-item {
@@ -719,6 +737,16 @@ export default {
   font-size: 14px;
   color: #606266;
   transition: all 0.3s ease;
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+}
+
+.nav-item-text {
+  display: block;
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
 }
 
 .nav-item:hover {
@@ -737,38 +765,37 @@ export default {
   flex: 1;
   background-color: #FFFFFF;
   padding: 0;
+  overflow: auto;
 }
 
 /* 响应式布局 */
 @media screen and (max-width: 768px) {
-  .main-content {
-    flex-direction: column;
-  }
-  
   .tag-system-nav {
-    width: 100%;
-    border-right: none;
-    border-bottom: 1px solid #EBEEF5;
+    width: 120px;
+    padding: 12px;
+    height: calc(100% - 24px);
   }
   
-  .nav-list {
-    display: flex;
-    flex-wrap: wrap;
-    gap: 8px;
+  .nav-title {
+    font-size: 13px;
+    margin-bottom: 12px;
   }
   
   .nav-item {
-    margin-bottom: 0;
+    font-size: 13px;
+    padding: 6px 8px;
+    margin-bottom: 6px;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
   }
   
   .top-notification {
-    flex-direction: column;
-    align-items: flex-start;
-    gap: 12px;
+    align-items: center;
   }
   
   .button-group {
-    align-self: flex-end;
+    margin-left: 10px;
   }
   
   .cancel-btn {
@@ -780,8 +807,7 @@ export default {
   }
   
   .selected-tags {
-    flex-direction: column;
-    align-items: flex-start;
+    align-items: center;
     gap: 8px;
   }
 }

+ 8 - 5
web/src/views/aiTagging/externalPage/externalResult.vue

@@ -174,6 +174,8 @@ export default {
   background-color: transparent !important;
   height: 100% !important;
   box-shadow: none !important;
+  display: flex;
+  flex-direction: column;
 }
 
 /* 顶部通知栏 */
@@ -183,6 +185,7 @@ export default {
   padding: 12px 20px;
   background-color: #F0F9EB;
   border-bottom: 1px solid #C2E7B0;
+  flex-shrink: 0;
 }
 
 .top-notification i {
@@ -205,6 +208,9 @@ export default {
 /* 标签详情列表 */
 .tag-detail-list {
   margin-top: 20px;
+  flex: 1;
+  overflow-y: auto;
+  padding-right: 8px;
 }
 
 /* 列表头部 */
@@ -284,14 +290,11 @@ export default {
 /* 响应式布局 */
 @media screen and (max-width: 768px) {
   .top-notification {
-    flex-direction: column;
-    align-items: flex-start;
-    gap: 12px;
+    align-items: center;
   }
   
   .edit-btn {
-    margin-left: 0;
-    align-self: flex-end;
+    margin-left: 10px;
   }
   
   .tag-info {

+ 264 - 23
web/src/views/aiTagging/externalPage/index.vue

@@ -1,27 +1,53 @@
 <template>
   <div class="intelligent-tagging">
-    <div class="business-attr-input">
-        <el-input
-          v-model="businessAttr"
-          placeholder="请输入"
-          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>
-      
       <!-- Loading状态 -->
       <div v-if="isLoading" class="loading-container">
         <i class="el-icon-loading loading-icon"></i>
         <div class="loading-text">{{ loadingText }}</div>
+        
+        <!-- 数据接收状态显示 -->
+        <div v-if="dataReceiveStatus === 'incomplete'" class="data-status-panel">
+          <div class="status-title">数据接收状态:<el-tag type="warning" size="small">数据不完整</el-tag></div>
+          <div class="received-data" v-if="receivedData">
+            <div class="data-item" v-if="receivedData.businessAttr">
+              <span class="data-label">贷款申请编号:</span>
+              <span class="data-value">{{ receivedData.businessAttr }}</span>
+            </div>
+            <div class="data-item" v-if="receivedData.user_id">
+              <span class="data-label">用户ID:</span>
+              <span class="data-value">{{ receivedData.user_id }}</span>
+            </div>
+            <div class="data-item" v-if="receivedData.user_nm">
+              <span class="data-label">用户姓名:</span>
+              <span class="data-value">{{ receivedData.user_nm }}</span>
+            </div>
+            <div class="data-item" v-if="receivedData.contract_no">
+              <span class="data-label">合同编号:</span>
+              <span class="data-value">{{ receivedData.contract_no }}</span>
+            </div>
+            <div class="data-item" v-if="receivedData.user_org">
+              <span class="data-label">所属机构:</span>
+              <span class="data-value">{{ receivedData.user_org }}</span>
+            </div>
+            <div class="data-item" v-if="receivedData.user_endpoint">
+              <span class="data-label">所属行社:</span>
+              <span class="data-value">{{ receivedData.user_endpoint }}</span>
+            </div>
+          </div>
+          <div class="missing-fields" v-if="missingFields.length > 0">
+            <div class="missing-title">缺失字段:</div>
+            <el-tag 
+              v-for="field in missingFields" 
+              :key="field" 
+              type="danger" 
+              size="small" 
+              class="missing-tag"
+            >
+              {{ field }}
+            </el-tag>
+          </div>
+        </div>
+        
         <el-button 
           v-if="taggingState === 0" 
           type="primary" 
@@ -39,7 +65,7 @@
         v-else-if="currentState === 'edit'"
         :business-attr="businessAttr"
         :query-result="queryData"
-        @refresh="queryBusinessAttr"
+        :user-data="userData"
         @confirm="handleTaggingConfirm"
         @close="handleCancelEdit"
         ref="externalEditRef"
@@ -71,16 +97,173 @@ export default {
       // 是否显示loading状态
       isLoading: true,
       // loading提示文本
-      loadingText: '打标执行中...',
+      loadingText: '数据加载中...',
       // 打标状态:0-打标执行中;1-打标完成;2-客户经理已经确认;3-结果已推送
       taggingState: null,
       // 当前状态:edit(编辑)或 result(结果)
       currentState: 'edit',
       // 查询结果数据(统一变量)
-      queryData: null
+      queryData: null,
+      // 用户数据,传递给ExternalEdit组件
+      userData:{
+        "user_id": "", 
+        "user_nm": "",
+        "contract_no": "", 
+        "user_org": "", 
+        "user_endpoint": "", 
+      },
+      // 接收到的第三方数据
+      receivedData: null,
+      // 缺失字段列表
+      missingFields: [],
+      // 数据接收状态
+      dataReceiveStatus: 'waiting' // waiting, incomplete, complete
     }
   },
+  mounted() {
+    // 监听 postMessage 事件
+    this.setupPostMessageListener();
+    
+    // 页面加载完成后发送"页面已加载"消息
+    this.sendPageLoadedMessage();
+  },
+  beforeDestroy() {
+    // 移除事件监听
+    this.removePostMessageListener();
+  },
   methods: {
+    // 设置 postMessage 监听器
+    setupPostMessageListener() {
+      this.handlePostMessage = (event) => {
+        // 验证消息来源(可选,增强安全性)
+        // if (event.origin !== 'https://第三方域名.com') return;
+        
+        const data = event.data;
+        // 检查是否为参数数据
+        if (data && data.type === 'externalParams') {
+          console.log('收到 postMessage 参数:', data);
+          
+          // 检查数据完整性
+          this.checkDataCompleteness(data);
+          
+          // 发送确认消息
+          this.sendConfirmationMessage(event.source, data);
+          
+          // 处理 businessAttr - 总是处理,即使为空也更新
+          this.businessAttr = data.business_attr || '';
+          
+          // 处理用户信息 - 总是处理,即使为空也更新
+          this.userData = { 
+            user_id: data.user_id || '',
+            user_nm: data.user_nm || '',
+            user_org: data.user_org || '',
+            user_endpoint: data.user_endpoint || '',
+            contract_no: data.contract_no || ''
+          };
+          
+          // 保存接收到的数据用于显示
+          this.receivedData = { ...data };
+          
+          // 根据数据完整性状态处理后续逻辑
+          if (this.dataReceiveStatus === 'complete') {
+            // 数据完整,执行查询
+            this.loadingText = '数据接收完整,开始执行打标...';
+            this.queryBusinessAttr();
+          } else {
+            // 数据不完整,显示缺失信息
+            this.loadingText = '数据接收不完整,请检查缺失字段';
+          }
+        }
+      };
+      
+      window.addEventListener('message', this.handlePostMessage);
+    },
+    
+    // 发送确认消息
+    sendConfirmationMessage(source, originalData) {
+      try {
+        const confirmationData = {
+          type: 'externalParamsReceived',
+          originalData: {
+            business_attr: originalData.business_attr,
+            user_id: originalData.user_id,
+            user_nm: originalData.user_nm,
+            user_org: originalData.user_org,
+            user_endpoint: originalData.user_endpoint,
+            contract_no: originalData.contract_no,
+            timestamp: originalData.timestamp,
+            source: originalData.source
+          },
+          receivedAt: Date.now(),
+          status: 'success'
+        };
+        
+        source.postMessage(confirmationData, '*');
+        console.log('已发送确认消息:', confirmationData);
+      } catch (error) {
+        console.error('发送确认消息失败:', error);
+      }
+    },
+    
+    // 检查数据完整性
+    checkDataCompleteness(data) {
+      // 重置缺失字段列表
+      this.missingFields = [];
+      
+      // 检查必填字段
+      const requiredFields = ['business_attr', 'user_id', 'user_nm', 'user_org', 'user_endpoint', 'contract_no'];
+      const fieldNames = {
+        business_attr: '贷款申请编号',
+        user_id: '用户ID',
+        user_nm: '用户姓名',
+        user_org: '所属机构',
+        user_endpoint: '所属行社',
+        contract_no: '合同编号'
+      };
+      
+      requiredFields.forEach(field => {
+        if (!data[field] || data[field].toString().trim() === '') {
+          this.missingFields.push(fieldNames[field]);
+        }
+      });
+      
+      // 更新数据接收状态
+      if (this.missingFields.length > 0) {
+        this.dataReceiveStatus = 'incomplete';
+      } else {
+        this.dataReceiveStatus = 'complete';
+      }
+    },
+    
+    // 发送页面已加载消息
+    sendPageLoadedMessage() {
+      try {
+        // 延迟发送,确保页面完全加载
+        setTimeout(() => {
+          const loadedMessage = {
+            type: 'externalPageLoaded',
+            loadedAt: Date.now(),
+            status: 'ready'
+          };
+          
+          // 发送给父窗口(第三方页面 - iframe方式)
+          if (window.parent && window.parent !== window) {
+            window.parent.postMessage(loadedMessage, '*');
+            console.log('已发送页面加载完成消息:', loadedMessage);
+          }
+        }, 1000);
+      } catch (error) {
+        console.error('发送页面加载消息失败:', error);
+      }
+    },
+    
+    // 移除 postMessage 监听器
+    removePostMessageListener() {
+      if (this.handlePostMessage) {
+        window.removeEventListener('message', this.handlePostMessage);
+      }
+    },
+    
     // 查询businessAttr
     queryBusinessAttr() {
       if (!this.businessAttr) {
@@ -98,6 +281,7 @@ export default {
         data: {
           businessAttr: this.businessAttr
         },
+        needToken: false,
         callback: (code, error, response) => {
           // 关闭loading状态
           this.isLoading = false;
@@ -178,6 +362,7 @@ export default {
   padding: 0;
   background-color: transparent !important;
   height: 100% !important;
+  overflow-y: auto;
   box-shadow: none !important;
 }
 
@@ -209,8 +394,64 @@ export default {
 
 .loading-text {
   font-size: 16px;
-  color: #606266;
-  margin-bottom: 20px;
+}
+
+/* 数据状态显示面板 */
+.data-status-panel {
+  margin-top: 20px;
+  padding: 16px;
+  background-color: #f8f9fa;
+  border-radius: 8px;
+  border: 1px solid #e9ecef;
+  max-width: 400px;
+  text-align: left;
+}
+
+.status-title {
+  font-weight: 600;
+  margin-bottom: 12px;
+  font-size: 14px;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.received-data {
+  margin-bottom: 12px;
+}
+
+.data-item {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 6px;
+  font-size: 13px;
+}
+
+.data-label {
+  color: #6c757d;
+  font-weight: 500;
+}
+
+.data-value {
+  color: #495057;
+  font-weight: 600;
+}
+
+.missing-fields {
+  border-top: 1px solid #dee2e6;
+  padding-top: 12px;
+}
+
+.missing-title {
+  font-weight: 600;
+  margin-bottom: 8px;
+  font-size: 13px;
+  color: #dc3545;
+}
+
+.missing-tag {
+  margin-right: 6px;
+  margin-bottom: 4px;
 }
 
 .refresh-btn {

+ 2 - 2
web/src/views/aiTagging/taggingDetail/index.vue

@@ -62,8 +62,8 @@
       <!-- 反馈信息 -->
       <div class="feedback-info" style="justify-content: flex-end;">
         <span class="meta-item">客户经理:{{ taggingInfo.feedbackUserNm || '-' }}</span>
-        <span class="meta-item">所属行社:{{ taggingInfo.feedbackUserEndpoint || '-' }}</span>
-        <span class="meta-item">所属机构:{{ taggingInfo.feedbackUserOrg || '-' }}</span>
+        <span class="meta-item">所属行社:{{ taggingInfo.feedbackUserOrg || '-' }}</span>
+        <span class="meta-item">所属机构:{{ taggingInfo.feedbackUserEndpoint || '-' }}</span>
         <span class="meta-item">反馈时间:{{ taggingInfo.feedbackTime || '-' }}</span>
       </div>
       

+ 3 - 2
web/src/views/aiTagging/taggingLogs/components/LogDetailDrawer.vue

@@ -1,7 +1,7 @@
 <template>
   <el-drawer
     title="日志详情"
-    :visible.sync="visible"
+    :visible="visible"
     direction="rtl"
     size="50%"
     :close-on-click-modal="false"
@@ -119,7 +119,8 @@ export default {
 .label {
   font-weight: bold;
   color: #606266;
-  min-width: 60px;
+  width: 80px; /* 设置固定宽度 */
+  text-align: right; /* 文字居右显示 */
   flex-shrink: 0;
   margin-right: 12px;
 }

+ 8 - 5
web/src/views/aiTagging/taggingLogs/index.vue

@@ -30,11 +30,11 @@
           <el-button type="primary" size="small" icon="el-icon-refresh" @click="handleRefresh">刷新</el-button>
         </div>
         <el-table :data="logs" style="width: 100%">
-          <el-table-column prop="userName" label="用户名称" min-width="120"></el-table-column>
-          <el-table-column prop="userId" label="用户id" min-width="180"></el-table-column>
-          <el-table-column prop="operationType" label="操作类型" min-width="120"></el-table-column>
-          <el-table-column prop="createDate" label="操作时间" min-width="180"></el-table-column>
-          <el-table-column prop="action" label="操作" min-width="100">
+          <el-table-column prop="userName" label="用户名称" ></el-table-column>
+          <el-table-column prop="userId" label="用户id" ></el-table-column>
+          <el-table-column prop="operationType" label="操作类型" ></el-table-column>
+          <el-table-column prop="createDate" label="操作时间" ></el-table-column>
+          <el-table-column prop="action" label="操作" width="100">
             <template slot-scope="scope">
               <el-button type="text" @click="handleViewDetail(scope.row)">查看详情</el-button>
             </template>
@@ -224,6 +224,8 @@ export default {
   background-color: transparent !important;
   height: 100% !important;
   box-shadow: none !important;
+  display: flex;
+  flex-direction: column;
 }
 
 /* 搜索栏样式 */
@@ -262,6 +264,7 @@ export default {
 /* 表格和分页容器 */
 .table-and-pagination-container {
   background-color: #fff;
+  flex: 1;
   border-radius: 8px;
   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
   margin-bottom: 20px;

+ 82 - 103
web/src/views/aiTagging/taggingResults/components/Overview.vue

@@ -218,16 +218,12 @@ export default {
       });
     },
     
-    // 更新打标趋势图表
-    updateTrendChart(data) {
-      if (!this.trendChart || !data || data.length === 0) {
-        return
-      }
-      
+    // 生成打标趋势图表配置
+    getTrendChartOption(data = []) {
       const labels = data.map(item => item.statShow || '')
       const values = data.map(item => parseInt(item.val) || 0)
       
-      const option = {
+      return {
         tooltip: {
           trigger: 'axis'
         },
@@ -235,15 +231,22 @@ export default {
           left: '3%',
           right: '4%',
           bottom: '3%',
+          top: '3%', // 统一设置top值为3%
           containLabel: true
         },
         xAxis: {
           type: 'category',
           boundaryGap: false,
-          data: labels
+          data: labels,
+          axisLabel: {
+            rotate: data.length >= 4 ? 45 : 0, // 当数据项数量大于等于4时旋转45度,否则不旋转
+            interval: 0,
+            fontSize: 10
+          }
         },
         yAxis: {
-          type: 'value'
+          type: 'value',
+          name: '数量'
         },
         series: [
           {
@@ -259,24 +262,17 @@ export default {
           }
         ]
       }
-      
-      this.trendChart.setOption(option)
     },
     
-    // 更新标签分布图表
-    updateDistributionChart(data) {
-      if (!this.distributionChart) {
-        return
-      }
-      
+    // 生成标签分布图表配置
+    getDistributionChartOption(data = []) {
       if (!data || data.length === 0) {
-        // 无数据时的处理
-        this.distributionChart.setOption({
+        return {
           grid: {
             left: '3%',
             right: '4%',
-            bottom: '10%',
-            top: '10%',
+            bottom: '3%',
+            top: '3%', // 统一设置top值为3%
             containLabel: true
           },
           xAxis: {
@@ -284,7 +280,8 @@ export default {
             data: []
           },
           yAxis: {
-            type: 'value'
+            type: 'value',
+            name: '数量'
           },
           series: [],
           graphic: {
@@ -297,8 +294,7 @@ export default {
               fill: '#999'
             }
           }
-        })
-        return
+        }
       }
       
       // 对数据进行排序
@@ -312,7 +308,7 @@ export default {
       const values = data.map(item => parseInt(item.val) || 0)
       const fullNames = data.map(item => item.stat || '')
       
-      const option = {
+      return {
         tooltip: {
           trigger: 'axis',
           axisPointer: {
@@ -326,15 +322,15 @@ export default {
         grid: {
           left: '3%',
           right: '4%',
-          bottom: '10%', // 减少底部空间
-          top: '10%', // 调整顶部空间
+          bottom: '3%',
+          top: '3%', // 统一设置top值为3%
           containLabel: true
         },
         xAxis: {
           type: 'category',
           data: names,
           axisLabel: {
-            rotate: 45,
+            rotate: data.length >= 4 ? 45 : 0, // 当数据项数量大于等于4时旋转45度,否则不旋转
             interval: 0,
             fontSize: 10
           }
@@ -348,7 +344,8 @@ export default {
             name: '标签数量',
             type: 'bar',
             data: values,
-            barWidth: '60%', // 调整柱形宽度
+            barWidth: 'auto', // 自动宽度
+            barMaxWidth: 30, // 最大宽度限制为30px
             itemStyle: {
               color: function(params) {
                 // 使用渐变色
@@ -368,10 +365,29 @@ export default {
               }
             }
           }
-        ]
+        ],
+        graphic: null // 显式设置为null,移除原来的暂无数据提示
+      }
+    },
+    
+    // 更新打标趋势图表
+    updateTrendChart(data) {
+      if (!this.trendChart) {
+        return
       }
       
-      this.distributionChart.setOption(option)
+      const option = this.getTrendChartOption(data)
+      this.trendChart.setOption(option)
+    },
+    
+    // 更新标签分布图表
+    updateDistributionChart(data) {
+      if (!this.distributionChart) {
+        return
+      }
+      
+      const option = this.getDistributionChartOption(data)
+      this.distributionChart.setOption(option, true) // 添加true参数,强制完全替换配置
     },
     
     // 初始化图表
@@ -398,38 +414,8 @@ export default {
       try {
         this.trendChart = echarts.init(this.$refs.trendChart)
         
-        const option = {
-          tooltip: {
-            trigger: 'axis'
-          },
-          grid: {
-            left: '3%',
-            right: '4%',
-            bottom: '3%',
-            containLabel: true
-          },
-          xAxis: {
-            type: 'category',
-            boundaryGap: false,
-            data: []
-          },
-          yAxis: {
-            type: 'value'
-          },
-          series: [
-            {
-              name: '智能打标数',
-              type: 'line',
-              data: [],
-              lineStyle: {
-                color: '#1890ff'
-              },
-              itemStyle: {
-                color: '#1890ff'
-              }
-            }
-          ]
-        }
+        // 使用统一的配置生成方法
+        const option = this.getTrendChartOption()
         
         this.trendChart.setOption(option)
         
@@ -450,43 +436,8 @@ export default {
       try {
         this.distributionChart = echarts.init(this.$refs.distributionChart)
         
-        const option = {
-          tooltip: {
-            trigger: 'axis',
-            axisPointer: {
-              type: 'shadow'
-            }
-          },
-          grid: {
-            left: '3%',
-            right: '4%',
-            bottom: '3%',
-            containLabel: true
-          },
-          xAxis: {
-            type: 'category',
-            data: [],
-            axisLabel: {
-              rotate: 45
-            }
-          },
-          yAxis: {
-            type: 'value'
-          },
-          series: [
-            {
-              name: '标签数量',
-              type: 'bar',
-              data: [],
-              itemStyle: {
-                color: function(params) {
-                  const colors = ['#1890ff', '#36cfc9', '#52c41a', '#faad14', '#722ed1', '#eb2f96']
-                  return colors[params.dataIndex % colors.length]
-                }
-              }
-            }
-          ]
-        }
+        // 使用统一的配置生成方法
+        const option = this.getDistributionChartOption()
         
         this.distributionChart.setOption(option)
         
@@ -518,9 +469,9 @@ export default {
 /* 统计指标容器 */
 .stats-container {
   display: grid;
-  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+  grid-template-columns: repeat(4, 1fr);
   gap: 20px;
-  padding: 20px;
+  padding: 0 20px 20px;
   /* margin-bottom: 20px; */
 }
 
@@ -623,7 +574,35 @@ export default {
 }
 
 /* 响应式设计 */
-@media (max-width: 1200px) {
+@media (max-width: 768px) {
+  .stats-container {
+    grid-template-columns: repeat(4, 1fr);
+    gap: 10px;
+    padding: 0 10px 10px;
+  }
+  
+  .stat-item {
+    padding: 12px;
+  }
+  
+  .stat-value {
+    font-size: 18px;
+  }
+  
+  .stat-label {
+    font-size: 12px;
+  }
+  
+  .stat-rate {
+    font-size: 10px;
+  }
+  
+  .stat-icon {
+    width: 30px;
+    height: 30px;
+    font-size: 16px;
+  }
+  
   .charts-container {
     grid-template-columns: 1fr;
   }

+ 16 - 21
web/src/views/aiTagging/taggingResults/index.vue

@@ -94,7 +94,7 @@ export default {
       detailData: [],
       
       // 分页数据
-      totalDetailCount: 120,
+      totalDetailCount: 0,
       currentDetailPage: 1,
       detailPageSize: 10,
       
@@ -132,7 +132,7 @@ export default {
 
     // 重置搜索
     handleReset() {
-      this.searchForm.dateRange = []
+      this.searchForm.dateRange = this.getDefaultDateRange();
       // 重置为默认选中第一条数据
       if (this.tagSystems.length > 0) {
         this.searchForm.tagSystem = this.tagSystems[0].categoryId;
@@ -175,26 +175,9 @@ export default {
     // 初始化图表
     initCharts() {
       // 模拟数据
-      const trendData = [
-        { month: '1月', value: 60 },
-        { month: '2月', value: 80 },
-        { month: '3月', value: 110 },
-        { month: '4月', value: 65 },
-        { month: '5月', value: 90 },
-        { month: '6月', value: 130 },
-        { month: '7月', value: 110 },
-        { month: '8月', value: 140 },
-        { month: '9月', value: 160 }
-      ]
+      const trendData = []
       
-      const distributionData = [
-        { name: '远洋捕捞', value: 150 },
-        { name: '船舶制造', value: 130 },
-        { name: '海水养殖', value: 100 },
-        { name: '海洋运输', value: 70 },
-        { name: '海洋旅游', value: 50 },
-        { name: '生物医药', value: 40 }
-      ]
+      const distributionData = []
       
       // 这里可以使用G2Plot或其他图表库初始化图表
       console.log('初始化图表', trendData, distributionData)
@@ -286,6 +269,8 @@ export default {
   background-color: transparent !important;
   height: 100% !important;
   box-shadow: none !important;
+  display: flex;
+  flex-direction: column;
 }
 
 /* 搜索栏样式 */
@@ -295,6 +280,7 @@ export default {
   border-radius: 8px;
   margin-bottom: 20px;
   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+  flex-shrink: 0;
 }
 
 .search-form {
@@ -309,6 +295,15 @@ export default {
   border-radius: 8px;
   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
   overflow: hidden;
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+}
+
+/* 标签页内容样式 */
+.result-tabs >>> .el-tabs__content {
+  flex: 1;
+  overflow-y: auto;
 }
 
 /* 响应式设计 */

+ 106 - 41
web/src/views/aiTagging/taggingSystemManage/TagSystemEdit.vue

@@ -26,40 +26,54 @@
 
     <!-- 标签体系编辑区 -->
     <div class="tag-edit-area">
-      <!-- 选项卡导航 -->
-      <el-tabs v-model="activeTab">
-        <el-tab-pane label="标签配置" name="config">
-          <!-- 标签树区域 -->
-          <div class="tag-tree-section">
-            <TagConfig 
-              ref="tagConfig"
-              :tag-system="currentSystem"
-              :tree-props="treeProps"
-              :system-status="currentSystem.state === 0"
-              :can-edit-tag="canEditTag"
-              @node-click="handleNodeClick"
-              @dropdown-command="handleDropdownCommand"
-              @edit-tag="handleEditTag"
-              @save-tag="handleSaveTag"
-              @cancel-edit="handleCancelEdit"
-              @add-node="handleAddNode"
-              @import-nodes="handleImportNodes"
-              @refresh-nodes="handleRefreshNodes"
-              @search="handleSearch"
-            />
-          </div>
-        </el-tab-pane>
-        <el-tab-pane label="标签测试" name="test">
-          <div class="tag-test-section">
-            <TagTest 
-              :tag-system="currentSystem"
-              @test-run="handleTestRun"
-              @smart-tagging="handleSmartTagging"
-              @test-clear="handleTestClear"
-            />
-          </div>
-        </el-tab-pane>
-      </el-tabs>
+      <!-- 自定义标签头 -->
+      <div class="custom-tabs">
+        <div 
+          class="custom-tab" 
+          :class="{ active: activeTab === 'config' }"
+          @click="activeTab = 'config'"
+        >
+          标签配置
+        </div>
+        <div 
+          class="custom-tab" 
+          :class="{ active: activeTab === 'test', disabled: currentSystem.state !== 0 }"
+          @click="handleTestTabClick"
+        >
+          标签测试
+        </div>
+      </div>
+
+      <!-- 标签配置内容 -->
+      <div v-if="activeTab === 'config'" class="tab-content">
+        <!-- 标签树区域 -->
+        <TagConfig 
+          ref="tagConfig"
+          :tag-system="currentSystem"
+          :tree-props="treeProps"
+          :system-status="currentSystem.state === 0"
+          :can-edit-tag="canEditTag"
+          @node-click="handleNodeClick"
+          @dropdown-command="handleDropdownCommand"
+          @edit-tag="handleEditTag"
+          @save-tag="handleSaveTag"
+          @cancel-edit="handleCancelEdit"
+          @add-node="handleAddNode"
+          @import-nodes="handleImportNodes"
+          @refresh-nodes="handleRefreshNodes"
+          @search="handleSearch"
+        />
+      </div>
+
+      <!-- 标签测试内容 -->
+      <div v-if="activeTab === 'test'" class="tab-content">
+        <TagTest 
+          :tag-system="currentSystem"
+          @test-run="handleTestRun"
+          @smart-tagging="handleSmartTagging"
+          @test-clear="handleTestClear"
+        />
+      </div>
     </div>
   </div>
 </template>
@@ -269,6 +283,18 @@ export default {
         query: { refresh: Date.now().toString() }
       });
     },
+    // 处理标签测试tab点击
+    handleTestTabClick() {
+      // 如果体系状态不是已启用(state !== 0)
+      if (this.currentSystem.state !== 0) {
+        // 提示用户需要启用标签体系
+        this.$message.warning('标签体系启用状态下才能进行标签测试');
+      } else {
+        // 切换到标签测试tab
+        this.activeTab = 'test';
+      }
+    },
+    
     // 处理状态变化
     handleStatusChange(newStatus) {
       console.log('状态变化:', newStatus);
@@ -355,17 +381,20 @@ export default {
   background-color: transparent !important;
   height: 100% !important;
   box-shadow: none !important;
+  display: flex;
+  flex-direction: column;
 }
 
 .edit-header {
   display: flex;
   justify-content: space-between;
-  align-items: flex-start;
+  align-items: center;
   background-color: #fff;
   padding: 24px;
   border-radius: 8px;
   margin-bottom: 20px;
   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+  flex-shrink: 0;
 }
 
 .header-left {
@@ -417,17 +446,53 @@ export default {
 .tag-edit-area {
   background-color: #fff;
   border-radius: 8px;
-  padding: 24px;
+  padding: 10px 20px 20px;
   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+  flex: 1;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
 }
 
-.tag-tree-section {
-  /* margin-top: 20px; */
+/* 自定义标签样式 */
+.custom-tabs {
+  display: flex;
+  border-bottom: 1px solid #e4e7ed;
+  margin-bottom: 20px;
+  flex-shrink: 0;
+}
+
+.custom-tab {
+  padding: 10px 20px;
+  cursor: pointer;
+  border-bottom: 2px solid transparent;
+  transition: all 0.3s;
+  font-size: 14px;
+  color: #606266;
+}
+
+.custom-tab:hover {
+  color: #409eff;
 }
 
-.tag-test-section {
-  text-align: center;
-  color: #909399;
+.custom-tab.active {
+  color: #409eff;
+  border-bottom-color: #409eff;
+  font-weight: 500;
+}
+
+.custom-tab.disabled {
+  color: #c0c4cc;
+  cursor: not-allowed;
+}
+
+.custom-tab.disabled:hover {
+  color: #c0c4cc;
+}
+
+.tab-content {
+  flex: 1;
+  overflow: hidden;
 }
 
 /* 确保TagTree组件的宽度 */

+ 110 - 83
web/src/views/aiTagging/taggingSystemManage/components/TagConfig.vue

@@ -1,67 +1,66 @@
 <template>
-  <div class="tag-config">
-    <!-- 标签树和操作区 -->
-    <div class="tag-content">
-      <!-- 标签树 -->
-      <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"
-          :can-edit-tag="canEditTag"
-          @node-click="handleNodeClick"
-          @dropdown-command="handleDropdownCommand"
-          @refresh="loadTagTreeData"
-          @save-success="loadTagTreeData"
-        />
-      </div>
+  <!-- 标签树和操作区 -->
+  <div class="tag-config tag-content">
+    <!-- 标签树 -->
+    <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"
+        :can-edit-tag="canEditTag"
+        @node-click="handleNodeClick"
+        @dropdown-command="handleDropdownCommand"
+        @refresh="loadTagTreeData"
+        @save-success="loadTagTreeData"
+        style="width: 100%; height: 100%;"
+      />
+    </div>
 
-      <!-- 右侧操作区 -->
-      <div class="tag-actions">
-        <TagDetail 
-          v-if="selectedNode"
-          :tag-data="selectedNode"
-          :is-edit-mode="isEditMode"
-          :parent-options="tagTreeData"
-          :tree-props="treeProps"
-          :category-id="tagSystem.id"
-          :category-name="tagSystem.categoryNm"
-          :system-status="systemStatus"
-          :can-edit-tag="canEditTag"
-          @edit="handleEditTag"
-          @save-success="loadTagTreeData"
-          @version-rollback="handleVersionRollback"
-          @delete="handleDeleteTag"
-          @cancel="handleCancelEdit"
-        />
-        <div v-else class="empty-data">
-          <div v-if="isTagTreeEmpty && !systemStatus && canEditTag" class="empty-data-import">
-            <!-- 提示条 -->
-            <div class="import-tip">
-              <i class="el-icon-warning-outline tip-icon"></i>
-              <span>小提示:您可以通过批量导入方式,快速完成标签配置,无需手动逐个添加~</span>
-              <a href="javascript:void(0)" class="import-link" @click="handleImportClick">点击此处</a>
-              <span>开始导入</span>
-            </div>
-            <img src="@/assets/images/noData.png" alt="暂无数据">
-          </div>
-          <div v-else-if="isTagTreeEmpty && systemStatus" class="empty-data-import">
-            <img src="@/assets/images/noData.png" alt="暂无数据">
-            <h4>暂无标签数据</h4>
-            <p>标签体系已启用,无法导入标签</p>
-          </div>
-          <div v-else-if="isTagTreeEmpty && !canEditTag" class="empty-data-import">
-            <img src="@/assets/images/noData.png" alt="暂无数据">
-            <h4>暂无标签数据</h4>
-            <p>当前暂无标签数据,且您没有编辑权限</p>
-          </div>
-          <div v-else class="empty-data-select">
-            <img src="@/assets/images/noData.png" alt="暂无数据">
-            <p>请选择一个标签节点查看详情</p>
+    <!-- 右侧操作区 -->
+    <div class="tag-actions">
+      <TagDetail 
+        v-if="selectedNode"
+        :tag-data="selectedNode"
+        :is-edit-mode="isEditMode"
+        :parent-options="tagTreeData"
+        :tree-props="treeProps"
+        :category-id="tagSystem.id"
+        :category-name="tagSystem.categoryNm"
+        :system-status="systemStatus"
+        :can-edit-tag="canEditTag"
+        @edit="handleEditTag"
+        @save-success="loadTagTreeData"
+        @version-rollback="handleVersionRollback"
+        @delete="handleDeleteTag"
+        @cancel="handleCancelEdit"
+      />
+      <div v-else>
+        <div v-if="isTagTreeEmpty && !systemStatus && canEditTag" class="empty-data empty-data-import">
+          <!-- 提示条 -->
+          <div class="import-tip">
+            <i class="el-icon-warning-outline tip-icon"></i>
+            <span>小提示:您可以通过批量导入方式,快速完成标签配置,无需手动逐个添加~</span>
+            <a href="javascript:void(0)" class="import-link" @click="handleImportClick">点击此处</a>
+            <span>开始导入</span>
           </div>
+          <img src="@/assets/images/noData.png" alt="暂无数据">
+        </div>
+        <div v-else-if="isTagTreeEmpty && systemStatus" class="empty-data empty-data-import">
+          <img src="@/assets/images/noData.png" alt="暂无数据">
+          <h4>暂无标签数据</h4>
+          <p>标签体系已启用,无法导入标签</p>
+        </div>
+        <div v-else-if="isTagTreeEmpty && !canEditTag" class="empty-data empty-data-import">
+          <img src="@/assets/images/noData.png" alt="暂无数据">
+          <h4>暂无标签数据</h4>
+          <p>当前暂无标签数据,且您没有编辑权限</p>
+        </div>
+        <div v-else class="empty-data empty-data-select">
+          <img src="@/assets/images/noData.png" alt="暂无数据">
+          <p>请选择一个标签节点查看详情</p>
         </div>
       </div>
     </div>
@@ -146,17 +145,31 @@ export default {
             this.tagTreeData = response.data || [];
             console.log('标签树数据:', this.tagTreeData);
             
-            // 刷新后重新选中之前的节点
+            // 刷新后验证之前的选中节点是否存在
             if (this.currentNodeKey) {
               setTimeout(() => {
-                // 模拟点击操作,执行完整的点击逻辑
-                // 1. 加载标签详情
-                this.loadTagDetail(this.currentNodeKey);
-                // 2. 退出编辑模式(与handleNodeClick保持一致)
-                this.isEditMode = false;
-                // 3. 触发TagTree组件的节点选中效果
-                if (this.$refs.tagTree && this.$refs.tagTree.$refs.tree) {
-                  this.$refs.tagTree.$refs.tree.setCurrentKey(this.currentNodeKey);
+                // 检查节点是否存在
+                const nodeExists = this.findNodeById(this.tagTreeData, this.currentNodeKey);
+                if (nodeExists) {
+                  // 节点存在,执行完整的点击逻辑
+                  // 1. 加载标签详情
+                  this.loadTagDetail(this.currentNodeKey);
+                  // 2. 退出编辑模式(与handleNodeClick保持一致)
+                  this.isEditMode = false;
+                  // 3. 触发TagTree组件的节点选中效果
+                  if (this.$refs.tagTree && this.$refs.tagTree.$refs.tree) {
+                    this.$refs.tagTree.$refs.tree.setCurrentKey(this.currentNodeKey);
+                  }
+                } else {
+                  // 节点不存在,重置状态
+                  console.log('选中的节点已不存在,重置状态');
+                  this.currentNodeKey = '';
+                  this.selectedNode = null;
+                  this.isEditMode = false;
+                  // 清空Tree组件的选中状态
+                  if (this.$refs.tagTree && this.$refs.tagTree.$refs.tree) {
+                    this.$refs.tagTree.$refs.tree.setCurrentKey(null);
+                  }
                 }
               }, 100);
             }
@@ -241,6 +254,25 @@ export default {
       // 否则使用标签名称
       return tagDetail.tagNm || '';
     },
+    
+    // 递归查找树节点
+    findNodeById(tree, id) {
+      if (!Array.isArray(tree)) {
+        return null;
+      }
+      for (const node of tree) {
+        if (node.id === id || node.tagId === id || node.tagCode === id) {
+          return node;
+        }
+        if (node.children && node.children.length > 0) {
+          const found = this.findNodeById(node.children, id);
+          if (found) {
+            return found;
+          }
+        }
+      }
+      return null;
+    },
 
     // 下拉菜单命令处理
     handleDropdownCommand(command, data, node) {
@@ -359,20 +391,15 @@ export default {
 </script>
 
 <style scoped>
-.tag-config {
+.tag-config.tag-content {
   height: 100%;
-}
-
-
-
-.tag-content {
   display: flex;
   gap: 20px;
-  min-height: 600px;
+  width: 100%;
 }
 
 .tag-actions {
-  flex: 1;
+  flex: 1; /* 自适应剩余宽度 */
   background-color: #fafafa;
   border-radius: 8px;
   overflow-y: auto;
@@ -396,14 +423,15 @@ export default {
 
 /* 标签树容器 */
 .tag-tree-container {
-  flex: 0 0 220px;
-  max-height: 600px;
-  overflow-y: auto;
+  width: 220px; /* 固定宽度220px */
+  height: 100%; /* 确保容器高度为100% */
+  overflow: hidden; /* 隐藏容器滚动条,只显示el-tree的滚动条 */
+  flex-shrink: 0; /* 防止被压缩 */
 }
 
 /* 响应式设计 */
 @media (max-width: 1200px) {
-  .tag-content {
+  .tag-config.tag-content {
     flex-direction: column;
   }
   
@@ -445,7 +473,6 @@ export default {
   flex-direction: column;
   align-items: center;
   gap: 16px;
-  width: 100%;
 }
 
 .empty-icon {

+ 24 - 5
web/src/views/aiTagging/taggingSystemManage/components/TagDetailEdit.vue

@@ -112,7 +112,7 @@
     </div>
     
     <!-- 正则表达式规则说明抽屉 -->
-    <TagDetailEditRegexDrawer :visible.sync="showRegexDrawer" />
+    <TagDetailEditRegexDrawer v-model="showRegexDrawer" />
   </div>
 </template>
 
@@ -243,6 +243,12 @@ export default {
         console.log('TagDetailEdit updateEditForm - 查找结果:', parent);
       }
       
+      // 如果parentId为null,设置为标签体系根节点
+      if (parent === null && this.processedParentOptions && this.processedParentOptions.length > 0) {
+        console.log('TagDetailEdit updateEditForm - parentId为null,设置为标签体系根节点');
+        parent = this.processedParentOptions[0];
+      }
+      
       // 确保 parent 是对象
       if (parent && typeof parent === 'string') {
         console.log('TagDetailEdit updateEditForm - parent仍然是字符串,无法反显:', parent);
@@ -457,6 +463,10 @@ export default {
       // 如果用户选择了具体的层级,以选择的为准
       if (this.editForm.parent) {
         if (typeof this.editForm.parent === 'object') {
+          // 如果选择的是标签体系(根节点),返回null
+          if (this.editForm.parent.id === this.categoryId || this.editForm.parent.id === 'root') {
+            return null;
+          }
           return this.editForm.parent.id || '';
         }
         return this.editForm.parent;
@@ -467,14 +477,18 @@ export default {
         // 从 formData 中获取 parentId
         if (this.formData && (this.formData.parentId || this.formData.parent)) {
           if (typeof this.formData.parent === 'object') {
+            // 如果选择的是标签体系(根节点),返回null
+            if (this.formData.parent.id === this.categoryId || this.formData.parent.id === 'root') {
+              return null;
+            }
             return this.formData.parent.id || '';
           }
           return this.formData.parentId || this.formData.parent || '';
         }
       }
       
-      // 如果没有选择层级且不是编辑状态,默认生成一级标签,parentId 是标签体系的 id(categoryId)
-      return this.categoryId || '';
+      // 如果没有选择层级且不是编辑状态,默认生成一级标签,parentId 返回null
+      return null;
     },
     handleCancel() {
       this.$emit('cancel');
@@ -483,8 +497,13 @@ export default {
     handleOpenRegexDrawer() {
       console.log('打开正则表达式规则抽屉');
       console.log('当前showRegexDrawer值:', this.showRegexDrawer);
-      this.showRegexDrawer = true;
-      console.log('设置后showRegexDrawer值:', this.showRegexDrawer);
+      // 先设置为false,确保状态重置
+      this.showRegexDrawer = false;
+      // 使用$nextTick确保DOM更新后再设置为true
+      this.$nextTick(() => {
+        this.showRegexDrawer = true;
+        console.log('设置后showRegexDrawer值:', this.showRegexDrawer);
+      });
     },
     handleSmartGenerate() {
       // 检查标签名称和标签说明是否为空

+ 24 - 1
web/src/views/aiTagging/taggingSystemManage/components/TagDetailEditRegexDrawer.vue

@@ -1,7 +1,8 @@
 <template>
   <el-drawer
     title="正则表达式规则说明"
-    :visible.sync="visible"
+    :visible="drawerVisible"
+    @close="handleClose"
     direction="rtl"
     size="50%"
   >
@@ -204,12 +205,34 @@
 <script>
 export default {
   name: 'TagDetailEditRegexDrawer',
+  model: {
+    prop: 'visible',
+    event: 'update:visible'
+  },
   props: {
     visible: {
       type: Boolean,
       default: false
     }
   },
+  data() {
+    return {
+      drawerVisible: this.visible
+    }
+  },
+  watch: {
+    visible: {
+      handler(newVal) {
+        this.drawerVisible = newVal
+      },
+      immediate: true
+    }
+  },
+  methods: {
+    handleClose() {
+      this.$emit('update:visible', false)
+    }
+  }
 }
 </script>
 

+ 8 - 3
web/src/views/aiTagging/taggingSystemManage/components/TagDetailView.vue

@@ -2,7 +2,7 @@
   <div class="detail-view">
     <!-- 标签路径 -->
     <div class="tag-path">
-      {{ tagData.path || '无路径信息' }}
+      {{ tagData.tagPath || '无路径信息' }}
     </div>
     <!-- 版本信息 -->
     <div class="version-info">
@@ -101,7 +101,7 @@
     </el-dialog>
     
     <!-- 正则表达式规则说明抽屉 -->
-    <TagDetailEditRegexDrawer :visible.sync="showRegexDrawer" />
+    <TagDetailEditRegexDrawer v-model="showRegexDrawer" />
   </div>
 </template>
 
@@ -140,7 +140,12 @@ export default {
       this.dialogVisible = true;
     },
     handleOpenRegexDrawer() {
-      this.showRegexDrawer = true;
+      // 先设置为false,确保状态重置
+      this.showRegexDrawer = false;
+      // 使用$nextTick确保DOM更新后再设置为true
+      this.$nextTick(() => {
+        this.showRegexDrawer = true;
+      });
     },
     handleVersionRollback() {
       // 显示确认提示

+ 5 - 4
web/src/views/aiTagging/taggingSystemManage/components/TagTest.vue

@@ -10,8 +10,8 @@
           <el-input
             type="textarea"
             v-model="testText"
-            placeholder="请输入职业、投向及贷款用途"
-            :rows="8"
+            placeholder="请输入要测试的文本内容,例如:职业:水产养殖人员 投向:海水养殖 用途:个人经营"
+            :rows="4"
             style="width: 100%; margin-bottom: 16px;"
           ></el-input>
         </el-tab-pane>
@@ -134,7 +134,7 @@ export default {
       this.$message({
         message: '正在进行智能打标,请稍候...',
         type: 'info',
-        duration: 3000
+        duration: 1500
       });
       
       // 生成16位随机字符串(数字和小写英文)
@@ -175,7 +175,7 @@ export default {
               text: '智能打标中,请稍候...',
               spinner: 'el-icon-loading',
               background: 'rgba(0, 0, 0, 0.7)',
-              target: this.$el
+              target: this.$parent.$el // 使用父级组件的DOM元素作为target
             });
             
             // 开始轮询查询打标结果
@@ -344,6 +344,7 @@ export default {
 <style scoped>
 .tag-test {
   height: 100%;
+  overflow-y: auto;
 }
 
 /* 测试输入区 */

+ 112 - 57
web/src/views/aiTagging/taggingSystemManage/components/TagTree.vue

@@ -31,11 +31,27 @@
       :props="treeProps"
       node-key="id"
       default-expand-all
-      :render-content="renderContent"
       :current-node-key="currentNodeKey"
       :highlight-current="true"
       @node-click="handleNodeClick"
-    ></el-tree>
+    >
+      <template slot-scope="{ node, data }">
+        <span class="custom-tree-node">
+          <span class="node-label" :title="data.tagNm">{{ data.tagNm }}</span>
+          <span class="node-dropdown" v-if="!systemStatus && canEditTag">
+            <el-dropdown trigger="click" @command="(command) => handleDropdownCommand(command, data, node)">
+              <span class="el-dropdown-link vertical-dots node-action">
+                <i class="el-icon-more"></i>
+              </span>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="add" v-if="node.level < 4">新增</el-dropdown-item>
+                <el-dropdown-item command="delete">删除</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
+          </span>
+        </span>
+      </template>
+    </el-tree>
     
     <!-- 新增标签弹框 -->
     <el-dialog
@@ -83,7 +99,10 @@
             <p class="upload-format">仅支持单个Excel(.xlsx)或CSV格式文件,文件内数据量不超过1000条</p>
           </div>
         </div>
-        <p v-if="file" class="file-name">{{ file.name }}</p>
+        <div v-if="file" class="file-info">
+          <p class="file-name">{{ file.name }}</p>
+          <el-button type="text" size="small" icon="el-icon-delete" @click="handleDeleteFile" class="delete-file-btn">删除</el-button>
+        </div>
       </div>
       <span slot="footer" class="dialog-footer">
         <el-button @click="showImportDialog = false">取消</el-button>
@@ -149,6 +168,14 @@ export default {
       }
     }
   },
+  watch: {
+    // 监听弹框关闭,清除已选文件
+    showImportDialog(newVal) {
+      if (!newVal) {
+        this.clearImportData();
+      }
+    }
+  },
   computed: {
     // 过滤后的树数据
     filteredTreeData() {
@@ -224,46 +251,7 @@ export default {
       console.log('搜索关键词:', this.searchKeyword);
     },
     
-    // 渲染树节点内容
-    renderContent(h, { node, data, store }) {
-      // 判断是否允许编辑:已停用状态 且 有编辑权限
-      const allowEdit = !this.systemStatus && this.canEditTag;
-      // 判断是否允许新增:最多4个层级,第4层级不显示新增操作
-      const allowAdd = allowEdit && node.level < 4;
-      return h('span', { class: 'custom-tree-node' }, [
-        h('span', {
-          class: 'node-label',
-          attrs: { title: data.tagNm }
-        }, data.tagNm),
-        h('span', {
-          class: 'node-dropdown',
-          style: { display: allowEdit ? 'inline-block' : 'none' }
-        }, [
-          h('el-dropdown', {
-            props: { trigger: 'click' },
-            on: {
-              command: (command) => this.handleDropdownCommand(command, data, node)
-            }
-          }, [
-            h('span', { class: 'el-dropdown-link vertical-dots node-action' }, [
-              '⋮'
-            ]),
-            h('el-dropdown-menu', {
-              slot: 'dropdown'
-            }, [
-              h('el-dropdown-item', {
-                props: { command: 'add' },
-                style: { display: allowAdd ? 'block' : 'none' }
-              }, '新增'),
-              h('el-dropdown-item', {
-                props: { command: 'delete' },
-                style: { display: allowEdit ? 'block' : 'none' }
-              }, '删除')
-            ])
-          ])
-        ])
-      ]);
-    },
+
 
     handleNodeClick(data, node) {
       console.log('点击节点:', data.tagNm);
@@ -384,6 +372,30 @@ export default {
       }, 5000);
       
       this.$message.success('模板下载中,请查看下载文件');
+    },
+    
+    // 删除已选择的文件
+    handleDeleteFile() {
+      console.log('删除已选择的文件');
+      // 清空文件数据
+      this.file = null;
+      // 重置文件输入框,以便用户可以重新选择同一文件
+      if (this.$refs.fileInput) {
+        this.$refs.fileInput.value = '';
+      }
+      // 提示用户文件已删除
+      this.$message.info('已删除选中的文件');
+    },
+    
+    // 清除导入相关数据
+    clearImportData() {
+      console.log('清除导入相关数据');
+      // 清空文件数据
+      this.file = null;
+      // 重置文件输入框
+      if (this.$refs.fileInput) {
+        this.$refs.fileInput.value = '';
+      }
     }
   }
 }
@@ -395,6 +407,7 @@ export default {
   flex-direction: column;
   gap: 16px;
   height: 100%;
+  width: 100%;
 }
 
 /* 标签树头部 */
@@ -402,8 +415,7 @@ export default {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  padding-bottom: 12px;
-  border-bottom: 1px solid #e8e8e8;
+  flex-shrink: 0; /* 防止被压缩 */
 }
 
 .tree-title {
@@ -421,51 +433,69 @@ export default {
 /* 搜索框 */
 .tree-header-search {
   padding: 0 4px;
+  flex-shrink: 0; /* 防止被压缩 */
 }
 
 /* 标签树 */
 .el-tree {
-  flex: 1;
-  overflow-y: auto;
+  flex: 1; /* 占据剩余高度 */
+  overflow-y: auto; /* 内容溢出时显示滚动条 */
   margin: 0;
+  padding-bottom: 16px; /* 增加底部内边距,防止内容被遮挡 */
 }
 
 /* 自定义树节点 */
 .custom-tree-node {
   display: flex;
-  justify-content: space-between;
   align-items: center;
   width: 100%;
+  min-width: 0;
+  padding: 4px 0;
+  box-sizing: border-box;
 }
 
 .node-label {
   flex: 1;
+  min-width: 0;
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
-  margin-right: 8px;
+  font-size: 14px;
+  color: #303133;
 }
 
 .node-dropdown {
   display: flex;
   align-items: center;
+  gap: 8px;
+  opacity: 0;
+  transition: opacity 0.3s;
+  flex-shrink: 0;
+  padding-left: 8px;
 }
 
-.node-action {
-  opacity: 0 !important;
-  transition: opacity 0.3s;
+.custom-tree-node:hover .node-dropdown {
+  opacity: 1;
 }
 
-/* 鼠标滑过树节点时显示操作按钮 */
-.el-tree >>> .el-tree-node__content:hover .node-action {
+.node-action {
   opacity: 1 !important;
+  transition: opacity 0.3s;
 }
 
 .vertical-dots {
-  font-size: 16px;
+  font-size: 14px;
+  line-height: 1;
   cursor: pointer;
-  color: #909399;
+  color: #666;
   transition: color 0.3s;
+  padding: 4px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 24px;
+  height: 24px;
+  border-radius: 4px;
 }
 
 .vertical-dots:hover {
@@ -578,11 +608,36 @@ export default {
   color: #909399;
 }
 
+.file-info {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-top: 12px;
+  padding: 8px 12px;
+  background-color: #f0f9eb;
+  border-radius: 4px;
+  border: 1px solid #e8f5e8;
+}
+
 .file-name {
   font-size: 14px;
   color: #67c23a;
   font-weight: 500;
-  margin-top: 12px;
+  margin: 0;
+  flex: 1;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.delete-file-btn {
+  color: #f56c6c;
+  margin: 0;
+  padding: 0 8px;
+}
+
+.delete-file-btn:hover {
+  color: #f78989;
 }
 
 .dialog-footer {

+ 9 - 2
web/src/views/aiTagging/taggingSystemManage/index.vue

@@ -61,7 +61,7 @@
     </div>
 
     <!-- 分页控件 -->
-    <div class="pagination" v-if="totalCount > pageSize">
+    <div class="pagination">
       <el-pagination
         :current-page="currentPage"
         :page-size="pageSize"
@@ -385,6 +385,8 @@ export default {
   background-color: transparent !important;
   height: 100% !important;
   box-shadow: none !important;
+  display: flex;
+  flex-direction: column;
 }
 
 /* 搜索栏样式 */
@@ -394,6 +396,7 @@ export default {
   border-radius: 8px;
   margin-bottom: 20px;
   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+  flex-shrink: 0;
 }
 
 .search-form {
@@ -406,7 +409,8 @@ export default {
   display: grid;
   grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
   gap: 20px;
-  margin-bottom: 30px;
+  flex: 1;
+  overflow-y: auto;
 }
 
 /* 标签体系卡片 */
@@ -414,6 +418,7 @@ export default {
   background-color: #fff;
   border-radius: 8px;
   padding: 20px;
+  max-height: 230px;
   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
   transition: all 0.3s ease;
   cursor: pointer;
@@ -431,6 +436,7 @@ export default {
   align-items: center;
   justify-content: center;
   min-height: 200px;
+  max-height: 230px;
   border: 2px dashed #e8e8e8;
   background-color: #fafafa;
 }
@@ -548,6 +554,7 @@ export default {
   display: flex;
   justify-content: flex-end;
   align-items: center;
+  flex-shrink: 0;
 }
 .pagination .el-pagination{
   padding: 10px 20px;

+ 70 - 27
web/src/views/aiTagging/taggingTest/TagTestComponent.vue

@@ -9,21 +9,16 @@
         :data="treeData"
         node-key="id"
         default-expand-all
-        show-checkbox
         @node-click="handleNodeClick"
         class="tag-tree"
       >
         <template slot-scope="{ node, data }">
-          <span class="custom-tree-node">
-            <span class="node-label">{{ node.label }}</span>
-            <span class="node-actions" v-if="data.children && data.children.length > 0">
-              <span class="node-count">{{ data.children.length }}</span>
+          <span class="custom-tree-node" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave">
+            <span class="node-label" :title="node.label">{{ node.label }}</span>
+            <span class="node-actions" v-show="isHovered">
               <el-dropdown trigger="click" @command="(command) => handleDropdownCommand(command, data)">
-                <span class="el-dropdown-link">
-                  <el-button size="mini" type="primary" plain>
-                    操作
-                    <i class="el-icon-arrow-down el-icon--right"></i>
-                  </el-button>
+                <span class="el-dropdown-link node-action-btn">
+                  <i class="el-icon-more"></i>
                 </span>
                 <el-dropdown-menu slot="dropdown">
                   <el-dropdown-item command="add">新增</el-dropdown-item>
@@ -43,6 +38,7 @@ export default {
   name: 'TagTestComponent',
   data() {
     return {
+      isHovered: false,
       treeData: [
         {
           id: 1,
@@ -132,12 +128,10 @@ export default {
     }
   },
   methods: {
-    // 处理节点点击
     handleNodeClick(data, node) {
       console.log('点击节点:', data, node);
     },
     
-    // 处理下拉菜单命令
     handleDropdownCommand(command, data) {
       console.log('命令:', command, '数据:', data);
       if (command === 'add') {
@@ -153,6 +147,14 @@ export default {
           console.log('取消删除');
         });
       }
+    },
+    
+    handleMouseEnter() {
+      this.isHovered = true;
+    },
+    
+    handleMouseLeave() {
+      this.isHovered = false;
     }
   }
 }
@@ -182,20 +184,27 @@ export default {
 
 .component-content {
   height: calc(100% - 44px);
-  overflow: auto;
+  overflow-x: hidden;
+  overflow-y: auto;
+  width: 100%;
+  box-sizing: border-box;
 }
 
 .custom-tree-node {
   display: flex;
   align-items: center;
-  justify-content: space-between;
   width: 100%;
+  min-width: 0;
   padding: 4px 0;
+  box-sizing: border-box;
 }
 
 .node-label {
   flex: 1;
-  word-break: break-all;
+  min-width: 0;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
   font-size: 14px;
   color: #303133;
 }
@@ -204,6 +213,35 @@ export default {
   display: flex;
   align-items: center;
   gap: 8px;
+  opacity: 0;
+  transition: opacity 0.3s;
+  flex-shrink: 0;
+  padding-left: 8px;
+}
+
+.custom-tree-node:hover .node-actions {
+  opacity: 1;
+}
+
+.node-action-btn {
+  font-size: 16px;
+  cursor: pointer;
+  color: #303133;
+  transition: all 0.3s;
+  padding: 4px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 24px;
+  height: 24px;
+  border-radius: 4px;
+  background-color: rgba(255, 255, 255, 0.9);
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.node-action-btn:hover {
+  color: #409EFF;
+  background-color: #f5f7fa;
 }
 
 .node-count {
@@ -219,10 +257,19 @@ export default {
 /* 调整树形结构样式 */
 .tag-tree {
   height: 100%;
+  overflow-x: hidden;
+  overflow-y: auto;
+}
+
+.el-tree {
+  overflow-x: hidden;
+  overflow-y: auto;
 }
 
 .el-tree-node {
-  white-space: normal;
+  min-width: 100%;
+  width: auto;
+  display: inline-block;
 }
 
 .el-tree-node__content {
@@ -230,19 +277,15 @@ export default {
   min-height: 36px;
   align-items: center;
   padding: 0 8px;
+  width: calc(100% - 24px);
+  box-sizing: border-box;
+  display: inline-flex;
 }
 
 .el-tree-node__content:hover {
   background-color: #f5f7fa;
 }
 
-/* 调整下拉菜单样式 */
-.el-dropdown-link .el-button {
-  padding: 0 8px;
-  font-size: 12px;
-  border-radius: 4px;
-}
-
 /* 响应式设计 */
 @media (max-width: 768px) {
   .tag-test-component {
@@ -250,13 +293,13 @@ export default {
   }
   
   .custom-tree-node {
-    flex-direction: column;
-    align-items: flex-start;
+    flex-direction: row;
+    align-items: center;
     gap: 8px;
   }
   
   .node-actions {
-    align-self: flex-start;
+    opacity: 1;
   }
 }
-</style>
+</style>

+ 40 - 602
web/src/views/aiTagging/taggingTest/index.vue

@@ -1,640 +1,78 @@
 <template>
-  <div class="tag-test">
-    <div class="treeTest">
-      <TagTestComponent />
-    </div>
-    <!-- 测试输入区 -->
-    <div class="test-input-section">
-      <h4 class="section-title">测试输入</h4>
+  <div class="tagging-test-container">
+    <!-- 标签页切换 -->
+    <el-tabs v-model="activeTab" type="border-card">
+      <!-- 打标发起标签页 -->
+      <el-tab-pane label="打标发起" name="apply">
+        <TaggingApply @smart-tagging="handleSmartTagging" @test-clear="handleTestClear" />
+      </el-tab-pane>
       
-      <!-- 输入信息区域 -->
-      <div class="input-section">
-        <!-- 第一行:标签体系 + 贷款申请编号 -->
-        <div class="form-row">
-          <div class="form-item">
-            <label class="form-label">标签体系:</label>
-            <el-select v-model="tagCategoryId" placeholder="请选择标签体系" style="width: 250px;">
-              <el-option v-for="(system, index) in tagSystems" :key="index" :label="system.categoryNm" :value="system.categoryId"></el-option>
-            </el-select>
-          </div>
-          <div class="form-item">
-            <label class="form-label">贷款申请编号:</label>
-            <div class="business-attr-wrapper">
-              <el-input v-model="businessAttr" readonly style="width: 200px;"></el-input>
-              <el-button type="text" @click="generateBusinessAttr">更新</el-button>
-            </div>
-          </div>
-        </div>
-        
-        <!-- 第二行:用户ID + 用户姓名 -->
-        <div class="form-row">
-          <div class="form-item">
-            <label class="form-label">用户ID:</label>
-            <el-input v-model="userId" placeholder="请输入用户ID" style="width: 250px;"></el-input>
-          </div>
-          <div class="form-item">
-            <label class="form-label">用户姓名:</label>
-            <el-input v-model="userNm" placeholder="请输入用户姓名" style="width: 250px;"></el-input>
-          </div>
-        </div>
-        
-        <!-- 第三行:所属机构 + 所属行社 -->
-        <div class="form-row">
-          <div class="form-item">
-            <label class="form-label">所属机构:</label>
-            <el-input v-model="userOrg" placeholder="请输入所属机构" style="width: 250px;"></el-input>
-          </div>
-          <div class="form-item">
-            <label class="form-label">所属行社:</label>
-            <el-input v-model="userEndpoint" placeholder="请输入所属行社" style="width: 250px;"></el-input>
-          </div>
-        </div>
-      </div>
-      
-      <!-- 打标内容 -->
-      <div class="form-item">
-        <label class="form-label">打标内容:</label>
-        <el-input
-          type="textarea"
-          v-model="phrase"
-          placeholder="请输入要测试的文本内容,例如:职业:水产养殖人员 投向:海水养殖 用途:个人经营"
-          :rows="6"
-          style="width: 100%;"
-        ></el-input>
-      </div>
-      
-      <!-- 操作按钮 -->
-      <div class="input-actions">
-        <el-button @click="handleClear">清空</el-button>
-        <el-button type="primary" @click="handleSmartTagging">智能打标发起</el-button>
-      </div>
-    </div>
-    
-    <!-- 智能打标结果区 -->
-    <div class="smart-tagging-result">
-      <div class="result-header">
-        <h4 class="section-title">智能打标结果</h4>
-        <div class="result-info" v-if="taggingResults.length > 0">
-          <span>打标结果数:{{ taggingResults.length }}</span>
-          <span>打标时间:{{ taggingTime }}</span>
-        </div>
-      </div>
-      
-      <!-- 有结果时显示结果卡片 -->
-      <div class="result-content" v-if="taggingResults.length > 0">
-        <el-card 
-          v-for="(result, index) in taggingResults" 
-          :key="index"
-          class="tag-result-card"
-        >
-          <div class="tag-result-header">
-            <span class="tag-path">{{ result.path }}</span>
-          </div>
-          <div class="tag-result-body">
-            <div class="result-item">
-              <span class="result-label">打标依据:</span>
-              <div class="result-text">{{ result.reason || '暂无依据' }}</div>
-            </div>
-          </div>
-        </el-card>
-      </div>
-      
-      <!-- 空结果提示 -->
-        <div class="empty-result" v-else>
-          <img src="@/assets/images/noData.png" alt="暂无数据">
-          <p>{{ noMatchFound ? '未找到匹配的标签' : '暂无打标结果,请输入内容并点击智能打标按钮' }}</p>
-        </div>
-    </div>
+      <!-- 打标确认标签页 -->
+      <el-tab-pane label="打标确认" name="confirm">
+        <TaggingConfirm />
+      </el-tab-pane>
+    </el-tabs>
   </div>
 </template>
 
 <script>
-import TagTestComponent from './TagTestComponent.vue'
+import TaggingApply from './taggingApply.vue';
+import TaggingConfirm from './taggingConfirm.vue';
 
 export default {
-  name: 'TagTest',
+  name: 'TaggingTest',
   components: {
-    TagTestComponent
+    TaggingApply,
+    TaggingConfirm
   },
   data() {
     return {
-      // 标签体系选择
-      tagCategoryId: '',
-      // 标签体系列表
-      tagSystems: [],
-      // 贷款申请编号(20位随机字符串)
-      businessAttr: '',
-      // 打标内容
-      phrase: '',
-      // 用户信息
-      userId: yufp.session.userId || '',
-      userNm: yufp.session.userName || '',
-      userOrg: yufp.session.org ? yufp.session.org.name : '',
-      userEndpoint: '',
-      // 智能打标结果
-      taggingResults: [],
-      // 是否未找到匹配的标签
-      noMatchFound: false,
-      // 打标时间
-      taggingTime: '',
-      // 轮询相关
-      pollingTimer: null
+      // 当前激活的标签页
+      activeTab: 'apply'
     }
   },
-  mounted() {
-    // 生成初始的businessAttr
-    this.generateBusinessAttr();
-    // 加载标签体系列表
-    this.loadTagSystems();
-  },
-  beforeDestroy() {
-    // 组件销毁前清除轮询定时器
-    this.clearPolling();
-  },
   methods: {
-    // 生成20位随机字符串(数字和小写英文)
-    generateBusinessAttr() {
-      const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
-      let result = '';
-      for (let i = 0; i < 20; i++) {
-        result += chars.charAt(Math.floor(Math.random() * chars.length));
-      }
-      this.businessAttr = result;
-      console.log('生成businessAttr:', this.businessAttr);
-    },
-    
-    // 加载标签体系列表
-    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);
-            // 默认选中第一条数据
-            if (this.tagSystems.length > 0) {
-              this.tagCategoryId = this.tagSystems[0].categoryId;
-            }
-          } else {
-            this.$message.error(response.message || '获取标签体系列表失败');
-          }
-        }
-      });
-    },
-    
-    // 智能打标
-    handleSmartTagging() {
-      // 检查必填项
-      if (!this.tagCategoryId) {
-        this.$message.warning('请选择标签体系');
-        return;
-      }
-      if (!this.businessAttr) {
-        this.$message.warning('贷款申请编号不能为空');
-        return;
-      }
-      if (!this.phrase.trim()) {
-        this.$message.warning('请输入打标内容');
-        return;
-      }
-      
-      console.log('智能打标:', {
-        businessAttr: this.businessAttr,
-        phrase: this.phrase,
-        tagCategoryId: this.tagCategoryId,
-        userId: this.userId,
-        userNm: this.userNm,
-        userOrg: this.userOrg,
-        userEndpoint: this.userEndpoint
-      });
-      
-      // 显示加载提示
-      this.$message({
-        message: '正在进行智能打标,请稍候...',
-        type: 'loading',
-        duration: 3000
-      });
-      
-      // 构建请求参数
-      const requestData = {
-        businessAttr: this.businessAttr,
-        phrase: this.phrase.trim(),
-        tagCategoryId: this.tagCategoryId,
-        userId: this.userId,
-        userNm: this.userNm,
-        userOrg: this.userOrg,
-        userEndpoint: this.userEndpoint
-      };
-      
-      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(this.businessAttr, loadingInstance);
-          } else {
-            this.$message.error(response.message || '智能打标失败');
-          }
-        }
-      });
-    },
-    
-    // 轮询查询打标结果
-    pollTaggingResult(businessAttr, loadingInstance) {
-      // 构建查询参数
-      const queryParams = {
-        businessAttr: businessAttr
-      };
-
-      // 调用查询接口
-      yufp.service.request({
-        url: backend.tagServer + "/api/fastapi/query",
-        method: 'get',
-        data: queryParams,
-        callback: (code, error, response) => {
-          if (response.code == '0') {
-            // 处理查询结果
-            // 实际数据在 response.data.data 中
-            const responseData = response.data && response.data.data ? response.data.data : null;
-
-            if (!responseData) {
-              // 无数据,继续轮询
-              this.pollingTimer = setTimeout(() => {
-                this.pollTaggingResult(businessAttr, loadingInstance);
-              }, 3000);
-              return;
-            }
-
-            // 获取state状态:0-打标执行中;1-打标完成;2-已确认;3-已推送
-            const state = responseData.state;
-
-            if (state === 0) {
-              // 打标执行中,继续轮询
-              this.pollingTimer = setTimeout(() => {
-                this.pollTaggingResult(businessAttr, loadingInstance);
-              }, 3000);
-            } else if (state === 1 || state === 2 || state === 3) {
-              // 打标完成、已确认或已推送,停止轮询
-              loadingInstance.close();
-              this.clearPolling();
-
-              // 处理result字段
-              let resultList = [];
-              if (responseData.result) {
-                if (typeof responseData.result === 'string') {
-                  try {
-                    const parsedResult = JSON.parse(responseData.result);
-                    if (Array.isArray(parsedResult)) {
-                      resultList = parsedResult;
-                    }
-                  } catch (error) {
-                    console.error('解析result失败:', error);
-                  }
-                } else if (Array.isArray(responseData.result)) {
-                  resultList = responseData.result;
-                }
-              }
-
-              // 处理打标结果(测试环境不过滤passr状态)
-              this.taggingResults = resultList.map(item => ({
-                code: item.tag_code || '',
-                name: item.tag_name || '',
-                path: item.tag_path || '',
-                method: '智能打标',
-                reason: item.desc || ''
-              }));
-
-              // 设置打标时间(使用接口返回的insert_time)
-              this.taggingTime = responseData.insert_time || new Date().toLocaleString();
-
-              if (this.taggingResults.length > 0) {
-                this.$message.success('智能打标完成');
-                this.noMatchFound = false;
-              } else {
-                this.noMatchFound = true;
-              }
-
-              // 触发父组件事件
-              this.$emit('smart-tagging', {
-                businessAttr: this.businessAttr,
-                phrase: this.phrase,
-                tagCategoryId: this.tagCategoryId,
-                userId: this.userId,
-                userNm: this.userNm,
-                userOrg: this.userOrg,
-                userEndpoint: this.userEndpoint,
-                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 || '查询打标结果失败');
-          }
-        }
-      });
+    // 处理智能打标事件
+    handleSmartTagging(data) {
+      console.log('智能打标事件:', data);
+      // 可以在这里处理打标结果,例如保存到本地存储或传递给其他组件
     },
     
-    // 清除轮询
-    clearPolling() {
-      if (this.pollingTimer) {
-        clearTimeout(this.pollingTimer);
-        this.pollingTimer = null;
-      }
-    },
-    
-    // 清空
-    handleClear() {
-      // 重新生成businessAttr
-      this.generateBusinessAttr();
-      // 清空打标内容
-      this.phrase = '';
-      // 清空用户信息
-      this.userId = '';
-      this.userNm = '';
-      this.userOrg = '';
-      this.userEndpoint = '';
-      // 清空打标结果
-      this.taggingResults = [];
-      this.taggingTime = '';
-      
-      this.$message.info('已清空测试内容');
-      
-      // 触发父组件事件
-      this.$emit('test-clear');
+    // 处理测试清空事件
+    handleTestClear() {
+      console.log('测试清空事件');
+      // 可以在这里处理清空操作的后续逻辑
     }
   }
 }
 </script>
 
 <style scoped>
-  .treeTest{
-    width: 240px;
-    height: 400px;
-  }
-.tag-test {
+.tagging-test-container {
   height: 100%;
-}
-
-/* 测试输入区 */
-.test-input-section {
-  background-color: #fafafa;
-  border-radius: 8px;
   padding: 20px;
-  margin-bottom: 24px;
-}
-
-.section-title {
-  font-size: 14px;
-  font-weight: 600;
-  color: #303133;
-  margin: 0 0 16px 0;
-  text-align: left;
-}
-
-/* 输入信息区域 */
-.input-section {
-  margin-bottom: 16px;
-}
-
-/* 表单行 */
-.form-row {
-  display: flex;
-  gap: 24px;
-  margin-bottom: 16px;
-  flex-wrap: wrap;
-}
-
-/* 表单项 */
-.form-item {
-  display: flex;
-  align-items: center;
-  flex: 1;
-  min-width: 350px;
-}
-
-/* 标签统一宽度 */
-.form-label {
-  width: 100px;
-  font-size: 14px;
-  color: #606266;
-  text-align: right;
-  margin-right: 12px;
-  flex-shrink: 0;
-}
-
-/* businessAttr包装器 */
-.business-attr-wrapper {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-  flex: 1;
-}
-
-.business-attr-wrapper .el-input {
-  flex: 1;
 }
 
-/* 输入操作按钮 */
-.input-actions {
-  display: flex;
-  justify-content: flex-end;
-  gap: 12px;
-}
-
-/* 智能打标结果区 */
-.smart-tagging-result {
-  background-color: #fafafa;
-  border-radius: 8px;
-  padding: 20px;
-}
-
-/* 结果头部 */
-.result-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 16px;
-  padding-bottom: 12px;
-  border-bottom: 1px solid #e8e8e8;
-}
-
-.result-info {
-  display: flex;
-  gap: 20px;
-  font-size: 12px;
-  color: #909399;
-}
-
-/* 结果内容 */
-.result-content {
-  display: flex;
-  flex-direction: column;
-  gap: 16px;
-}
-
-/* 标签结果卡片 */
-.tag-result-card {
-  border-radius: 8px;
-  overflow: hidden;
-}
-
-/* 标签结果头部 */
-.tag-result-header {
-  background-color: #e6f7ff;
-  padding: 12px 16px;
-  margin: -16px -16px 16px;
-  display: flex;
-  align-items: center;
-  gap: 12px;
-}
-
-.tag-path {
-  font-weight: 600;
-  color: #303133;
-}
-
-/* 标签结果主体 */
-.tag-result-body {
-  display: flex;
-  flex-direction: column;
-  gap: 12px;
-}
-
-.result-item {
-  display: flex;
-  flex-direction: column;
-  gap: 4px;
-}
-
-.result-label {
-  font-size: 12px;
-  color: #606266;
-  margin-bottom: 4px;
-  text-align: left;
-}
-
-.result-input {
-  font-size: 14px;
-}
-
-.result-text {
-  font-size: 14px;
-  color: #303133;
-  line-height: 1.6;
-  padding: 8px 12px;
-  background-color: #f5f7fa;
-  border-radius: 4px;
-  min-height: 60px;
-  text-align: left;
-}
-
-/* 空结果提示 */
-.empty-result {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  padding: 60px 20px;
-  color: #909399;
-  background-color: #fafafa;
-  margin-top: 16px;
-  min-height: 200px;
-}
-
-.empty-result img {
-  width: 120px;
-  height: 120px;
-  margin-bottom: 16px;
-  opacity: 0.6;
+/* 标签页样式 */
+.el-tabs {
+  height: 100%;
 }
 
-.empty-result p {
-  font-size: 14px;
-  color: #909399;
-  margin: 0;
-  line-height: 1.5;
+.el-tabs__content {
+  height: calc(100% - 60px);
+  overflow: auto;
+  padding-top: 20px;
 }
 
-
 /* 响应式设计 */
 @media (max-width: 768px) {
-  .result-header {
-    flex-direction: column;
-    align-items: flex-start;
-    gap: 8px;
-  }
-  
-  .result-info {
-    align-self: stretch;
-    justify-content: space-between;
-  }
-  
-  .input-actions {
-    flex-direction: column;
-  }
-  
-  .input-actions .el-button {
-    width: 100%;
-  }
-  
-  .form-row {
-    flex-direction: column;
-    gap: 16px;
-  }
-  
-  .form-item {
-    flex-direction: column;
-    align-items: flex-start;
-    min-width: auto;
-  }
-  
-  .form-label {
-    text-align: left;
-    margin-bottom: 8px;
-    width: 100%;
-  }
-  
-  .business-attr-wrapper {
-    width: 100%;
-  }
-  
-  .business-attr-wrapper .el-input {
-    width: 100% !important;
+  .tagging-test-container {
+    padding: 10px;
   }
   
-  .form-item .el-select,
-  .form-item .el-input {
-    width: 100% !important;
+  .el-tabs__content {
+    height: calc(100% - 50px);
+    padding-top: 10px;
   }
 }
 </style>

+ 628 - 0
web/src/views/aiTagging/taggingTest/taggingApply.vue

@@ -0,0 +1,628 @@
+<template>
+  <div class="tag-test">
+    <!-- 测试输入区 -->
+    <div class="test-input-section">
+      <h4 class="section-title">测试输入</h4>
+      
+      <!-- 输入信息区域 -->
+      <div class="input-section">
+        <!-- 第一行:标签体系 + 贷款申请编号 -->
+        <div class="form-row">
+          <div class="form-item">
+            <label class="form-label">标签体系:</label>
+            <el-select v-model="tagCategoryId" placeholder="请选择标签体系" style="width: 250px;">
+              <el-option v-for="(system, index) in tagSystems" :key="index" :label="system.categoryNm" :value="system.categoryId"></el-option>
+            </el-select>
+          </div>
+          <div class="form-item">
+            <label class="form-label">贷款申请编号:</label>
+            <div class="business-attr-wrapper">
+              <el-input v-model="businessAttr" readonly style="width: 200px;"></el-input>
+              <el-button type="text" @click="generateBusinessAttr">更新</el-button>
+            </div>
+          </div>
+        </div>
+        
+        <!-- 第二行:用户ID + 用户姓名 -->
+        <div class="form-row">
+          <div class="form-item">
+            <label class="form-label">用户ID:</label>
+            <el-input v-model="userId" placeholder="请输入用户ID" style="width: 250px;"></el-input>
+          </div>
+          <div class="form-item">
+            <label class="form-label">用户姓名:</label>
+            <el-input v-model="userNm" placeholder="请输入用户姓名" style="width: 250px;"></el-input>
+          </div>
+        </div>
+        
+        <!-- 第三行:所属机构 + 所属行社 -->
+        <div class="form-row">
+          <div class="form-item">
+            <label class="form-label">所属机构:</label>
+            <el-input v-model="userOrg" placeholder="请输入所属机构" style="width: 250px;"></el-input>
+          </div>
+          <div class="form-item">
+            <label class="form-label">所属行社:</label>
+            <el-input v-model="userEndpoint" placeholder="请输入所属行社" style="width: 250px;"></el-input>
+          </div>
+        </div>
+      </div>
+      
+      <!-- 打标内容 -->
+      <div class="form-item">
+        <label class="form-label">打标内容:</label>
+        <el-input
+          type="textarea"
+          v-model="phrase"
+          placeholder="请输入要测试的文本内容,例如:职业:水产养殖人员 投向:海水养殖 用途:个人经营"
+          :rows="6"
+          style="width: 100%;"
+        ></el-input>
+      </div>
+      
+      <!-- 操作按钮 -->
+      <div class="input-actions">
+        <el-button @click="handleClear">清空</el-button>
+        <el-button type="primary" @click="handleSmartTagging">智能打标发起</el-button>
+      </div>
+    </div>
+    
+    <!-- 智能打标结果区 -->
+    <div class="smart-tagging-result">
+      <div class="result-header">
+        <h4 class="section-title">智能打标结果</h4>
+        <div class="result-info" v-if="taggingResults.length > 0">
+          <span>打标结果数:{{ taggingResults.length }}</span>
+          <span>打标时间:{{ taggingTime }}</span>
+        </div>
+      </div>
+      
+      <!-- 有结果时显示结果卡片 -->
+      <div class="result-content" v-if="taggingResults.length > 0">
+        <el-card 
+          v-for="(result, index) in taggingResults" 
+          :key="index"
+          class="tag-result-card"
+        >
+          <div class="tag-result-header">
+            <span class="tag-path">{{ result.path }}</span>
+          </div>
+          <div class="tag-result-body">
+            <div class="result-item">
+              <span class="result-label">打标依据:</span>
+              <div class="result-text">{{ result.reason || '暂无依据' }}</div>
+            </div>
+          </div>
+        </el-card>
+      </div>
+      
+      <!-- 空结果提示 -->
+        <div class="empty-result" v-else>
+          <img src="@/assets/images/noData.png" alt="暂无数据">
+          <p>{{ noMatchFound ? '未找到匹配的标签' : '暂无打标结果,请输入内容并点击智能打标按钮' }}</p>
+        </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'TaggingApply',
+  data() {
+    return {
+      // 标签体系选择
+      tagCategoryId: '',
+      // 标签体系列表
+      tagSystems: [],
+      // 贷款申请编号(20位随机字符串)
+      businessAttr: '',
+      // 打标内容
+      phrase: '',
+      // 用户信息
+      userId: yufp.session.userId || '',
+      userNm: yufp.session.userName || '',
+      userOrg: yufp.session.org ? yufp.session.org.name : '',
+      userEndpoint: '',
+      // 智能打标结果
+      taggingResults: [],
+      // 是否未找到匹配的标签
+      noMatchFound: false,
+      // 打标时间
+      taggingTime: '',
+      // 轮询相关
+      pollingTimer: null
+    }
+  },
+  mounted() {
+    // 生成初始的businessAttr
+    this.generateBusinessAttr();
+    // 加载标签体系列表
+    this.loadTagSystems();
+  },
+  beforeDestroy() {
+    // 组件销毁前清除轮询定时器
+    this.clearPolling();
+  },
+  methods: {
+    // 生成20位随机字符串(数字和小写英文)
+    generateBusinessAttr() {
+      const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
+      let result = '';
+      for (let i = 0; i < 20; i++) {
+        result += chars.charAt(Math.floor(Math.random() * chars.length));
+      }
+      this.businessAttr = result;
+      console.log('生成businessAttr:', this.businessAttr);
+    },
+    
+    // 加载标签体系列表
+    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);
+            // 默认选中第一条数据
+            if (this.tagSystems.length > 0) {
+              this.tagCategoryId = this.tagSystems[0].categoryId;
+            }
+          } else {
+            this.$message.error(response.message || '获取标签体系列表失败');
+          }
+        }
+      });
+    },
+    
+    // 智能打标
+    handleSmartTagging() {
+      // 检查必填项
+      if (!this.tagCategoryId) {
+        this.$message.warning('请选择标签体系');
+        return;
+      }
+      if (!this.businessAttr) {
+        this.$message.warning('贷款申请编号不能为空');
+        return;
+      }
+      if (!this.phrase.trim()) {
+        this.$message.warning('请输入打标内容');
+        return;
+      }
+      
+      console.log('智能打标:', {
+        businessAttr: this.businessAttr,
+        phrase: this.phrase,
+        tagCategoryId: this.tagCategoryId,
+        userId: this.userId,
+        userNm: this.userNm,
+        userOrg: this.userOrg,
+        userEndpoint: this.userEndpoint
+      });
+      
+      // 显示加载提示
+      this.$message({
+        message: '正在进行智能打标,请稍候...',
+        type: 'loading',
+        duration: 3000
+      });
+      
+      // 构建请求参数
+      const requestData = {
+        businessAttr: this.businessAttr,
+        phrase: this.phrase.trim(),
+        tagCategoryId: this.tagCategoryId,
+        userId: this.userId,
+        userNm: this.userNm,
+        userOrg: this.userOrg,
+        userEndpoint: this.userEndpoint
+      };
+      
+      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(this.businessAttr, loadingInstance);
+          } else {
+            this.$message.error(response.message || '智能打标失败');
+          }
+        }
+      });
+    },
+    
+    // 轮询查询打标结果
+    pollTaggingResult(businessAttr, loadingInstance) {
+      // 构建查询参数
+      const queryParams = {
+        businessAttr: businessAttr
+      };
+
+      // 调用查询接口
+      yufp.service.request({
+        url: backend.tagServer + "/api/fastapi/query",
+        method: 'get',
+        data: queryParams,
+        callback: (code, error, response) => {
+          if (response.code == '0') {
+            // 处理查询结果
+            // 实际数据在 response.data.data 中
+            const responseData = response.data && response.data.data ? response.data.data : null;
+
+            if (!responseData) {
+              // 无数据,继续轮询
+              this.pollingTimer = setTimeout(() => {
+                this.pollTaggingResult(businessAttr, loadingInstance);
+              }, 3000);
+              return;
+            }
+
+            // 获取state状态:0-打标执行中;1-打标完成;2-已确认;3-已推送
+            const state = responseData.state;
+
+            if (state === 0) {
+              // 打标执行中,继续轮询
+              this.pollingTimer = setTimeout(() => {
+                this.pollTaggingResult(businessAttr, loadingInstance);
+              }, 3000);
+            } else if (state === 1 || state === 2 || state === 3) {
+              // 打标完成、已确认或已推送,停止轮询
+              loadingInstance.close();
+              this.clearPolling();
+
+              // 处理result字段
+              let resultList = [];
+              if (responseData.result) {
+                if (typeof responseData.result === 'string') {
+                  try {
+                    const parsedResult = JSON.parse(responseData.result);
+                    if (Array.isArray(parsedResult)) {
+                      resultList = parsedResult;
+                    }
+                  } catch (error) {
+                    console.error('解析result失败:', error);
+                  }
+                } else if (Array.isArray(responseData.result)) {
+                  resultList = responseData.result;
+                }
+              }
+
+              // 处理打标结果(测试环境不过滤passr状态)
+              this.taggingResults = resultList.map(item => ({
+                code: item.tag_code || '',
+                name: item.tag_name || '',
+                path: item.tag_path || '',
+                method: '智能打标',
+                reason: item.desc || ''
+              }));
+
+              // 设置打标时间(使用接口返回的insert_time)
+              this.taggingTime = responseData.insert_time || new Date().toLocaleString();
+
+              if (this.taggingResults.length > 0) {
+                this.$message.success('智能打标完成');
+                this.noMatchFound = false;
+              } else {
+                this.noMatchFound = true;
+              }
+
+              // 触发父组件事件
+              this.$emit('smart-tagging', {
+                businessAttr: this.businessAttr,
+                phrase: this.phrase,
+                tagCategoryId: this.tagCategoryId,
+                userId: this.userId,
+                userNm: this.userNm,
+                userOrg: this.userOrg,
+                userEndpoint: this.userEndpoint,
+                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 || '查询打标结果失败');
+          }
+        }
+      });
+    },
+    
+    // 清除轮询
+    clearPolling() {
+      if (this.pollingTimer) {
+        clearTimeout(this.pollingTimer);
+        this.pollingTimer = null;
+      }
+    },
+    
+    // 清空
+    handleClear() {
+      // 重新生成businessAttr
+      this.generateBusinessAttr();
+      // 清空打标内容
+      this.phrase = '';
+      // 清空用户信息
+      this.userId = '';
+      this.userNm = '';
+      this.userOrg = '';
+      this.userEndpoint = '';
+      // 清空打标结果
+      this.taggingResults = [];
+      this.taggingTime = '';
+      
+      this.$message.info('已清空测试内容');
+      
+      // 触发父组件事件
+      this.$emit('test-clear');
+    }
+  }
+}
+</script>
+
+<style scoped>
+.tag-test {
+  height: 100%;
+}
+
+/* 测试输入区 */
+.test-input-section {
+  background-color: #fafafa;
+  border-radius: 8px;
+  padding: 20px;
+  margin-bottom: 24px;
+}
+
+.section-title {
+  font-size: 14px;
+  font-weight: 600;
+  color: #303133;
+  margin: 0 0 16px 0;
+  text-align: left;
+}
+
+/* 输入信息区域 */
+.input-section {
+  margin-bottom: 16px;
+}
+
+/* 表单行 */
+.form-row {
+  display: flex;
+  gap: 24px;
+  margin-bottom: 16px;
+  flex-wrap: wrap;
+}
+
+/* 表单项 */
+.form-item {
+  display: flex;
+  align-items: center;
+  flex: 1;
+  min-width: 350px;
+}
+
+/* 标签统一宽度 */
+.form-label {
+  width: 100px;
+  font-size: 14px;
+  color: #606266;
+  text-align: right;
+  margin-right: 12px;
+  flex-shrink: 0;
+}
+
+/* businessAttr包装器 */
+.business-attr-wrapper {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  flex: 1;
+}
+
+.business-attr-wrapper .el-input {
+  flex: 1;
+}
+
+/* 输入操作按钮 */
+.input-actions {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+}
+
+/* 智能打标结果区 */
+.smart-tagging-result {
+  background-color: #fafafa;
+  border-radius: 8px;
+  padding: 20px;
+}
+
+/* 结果头部 */
+.result-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16px;
+  padding-bottom: 12px;
+  border-bottom: 1px solid #e8e8e8;
+}
+
+.result-info {
+  display: flex;
+  gap: 20px;
+  font-size: 12px;
+  color: #909399;
+}
+
+/* 结果内容 */
+.result-content {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+
+/* 标签结果卡片 */
+.tag-result-card {
+  border-radius: 8px;
+  overflow: hidden;
+}
+
+/* 标签结果头部 */
+.tag-result-header {
+  background-color: #e6f7ff;
+  padding: 12px 16px;
+  margin: -16px -16px 16px;
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.tag-path {
+  font-weight: 600;
+  color: #303133;
+}
+
+/* 标签结果主体 */
+.tag-result-body {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.result-item {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+
+.result-label {
+  font-size: 12px;
+  color: #606266;
+  margin-bottom: 4px;
+  text-align: left;
+}
+
+.result-input {
+  font-size: 14px;
+}
+
+.result-text {
+  font-size: 14px;
+  color: #303133;
+  line-height: 1.6;
+  padding: 8px 12px;
+  background-color: #f5f7fa;
+  border-radius: 4px;
+  min-height: 60px;
+  text-align: left;
+}
+
+/* 空结果提示 */
+.empty-result {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 60px 20px;
+  color: #909399;
+  background-color: #fafafa;
+  margin-top: 16px;
+  min-height: 200px;
+}
+
+.empty-result img {
+  width: 120px;
+  height: 120px;
+  margin-bottom: 16px;
+  opacity: 0.6;
+}
+
+.empty-result p {
+  font-size: 14px;
+  color: #909399;
+  margin: 0;
+  line-height: 1.5;
+}
+
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .result-header {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 8px;
+  }
+  
+  .result-info {
+    align-self: stretch;
+    justify-content: space-between;
+  }
+  
+  .input-actions {
+    flex-direction: column;
+  }
+  
+  .input-actions .el-button {
+    width: 100%;
+  }
+  
+  .form-row {
+    flex-direction: column;
+    gap: 16px;
+  }
+  
+  .form-item {
+    flex-direction: column;
+    align-items: flex-start;
+    min-width: auto;
+  }
+  
+  .form-label {
+    text-align: left;
+    margin-bottom: 8px;
+    width: 100%;
+  }
+  
+  .business-attr-wrapper {
+    width: 100%;
+  }
+  
+  .business-attr-wrapper .el-input {
+    width: 100% !important;
+  }
+  
+  .form-item .el-select,
+  .form-item .el-input {
+    width: 100% !important;
+  }
+}
+</style>

+ 555 - 0
web/src/views/aiTagging/taggingTest/taggingConfirm.vue

@@ -0,0 +1,555 @@
+<template>
+  <div class="tagging-confirm">
+    <div class="confirm-container">
+      <!-- 左侧用户信息输入 -->
+      <div class="left-section">
+        <h4 class="section-title">用户信息输入</h4>
+        
+        <!-- 输入信息区域 -->
+        <el-form :model="formData" label-position="top" size="small">
+          <!-- 贷款申请编号 -->
+          <el-form-item label="贷款申请编号" prop="businessAttr">
+          <el-input v-model="formData.businessAttr" placeholder="请输入贷款申请编号" style="width: 100%;"></el-input>
+        </el-form-item>
+        
+        <!-- 合同编号 -->
+        <el-form-item label="合同编号" prop="contractNo">
+          <el-input v-model="formData.contractNo" placeholder="请输入合同编号" style="width: 100%;"></el-input>
+        </el-form-item>
+        
+        <!-- 用户ID -->
+        <el-form-item label="用户ID" prop="userId">
+          <el-input v-model="formData.userId" placeholder="请输入用户ID" />
+        </el-form-item>
+          
+          <!-- 用户姓名 -->
+          <el-form-item label="用户姓名" prop="userNm">
+            <el-input v-model="formData.userNm" placeholder="请输入用户姓名" style="width: 100%;"></el-input>
+          </el-form-item>
+          
+          <!-- 所属机构 -->
+          <el-form-item label="所属机构" prop="userOrg">
+            <el-input v-model="formData.userOrg" placeholder="请输入所属机构" style="width: 100%;"></el-input>
+          </el-form-item>
+          
+          <!-- 所属行社 -->
+          <el-form-item label="所属行社" prop="userEndpoint">
+            <el-input v-model="formData.userEndpoint" placeholder="请输入所属行社" style="width: 100%;"></el-input>
+          </el-form-item>
+        </el-form>
+        
+        <!-- 操作按钮 -->
+        <div class="input-actions">
+          <el-button @click="handleClear">清空</el-button>
+          <el-button type="primary" @click="handleConfirm">确认</el-button>
+        </div>
+      </div>
+      
+      <!-- 右侧iframe展示和状态监控 -->
+      <div class="right-section">
+        <h4 class="section-title">打标确认</h4>
+        <div class="iframe-container" v-if="showIframe">
+          <iframe :src="iframeUrl" frameborder="0" width="100%" height="100%" scrolling="auto" style="overflow: auto;"></iframe>
+        </div>
+        <div class="empty-iframe" v-else>
+          <img src="@/assets/images/noData.png" alt="暂无数据">
+          <p>请输入用户信息并点击确认按钮加载内容</p>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'TaggingConfirm',
+  data() {
+    return {
+      // 表单数据
+      formData: {
+        // 贷款申请编号
+        businessAttr: '',
+        // 合同编号
+        contractNo: '',
+        // 用户信息
+        userId: yufp.session.userId || '',
+        userNm: yufp.session.userName || '',
+        userOrg: yufp.session.org ? yufp.session.org.name : '',
+        userEndpoint: ''
+      },
+      // 是否显示iframe
+      showIframe: false,
+      // iframe URL
+      iframeUrl: ''
+    }
+  },
+  mounted() {
+    // 监听来自externalPage的消息(包括页面加载完成和确认消息)
+    this.setupExternalPageListener();
+  },
+  beforeDestroy() {
+    // 清理事件监听器
+    this.removeExternalPageListener();
+  },
+  methods: {
+    // 设置externalPage监听器
+    setupExternalPageListener() {
+      this.handleExternalPageMessage = (event) => {
+        const data = event.data;
+        
+        // 检查是否为externalPage发送的页面加载完成消息
+        if (data && data.type === 'externalPageLoaded') {
+          // 如果已经输入了用户信息,自动发送数据
+          if (this.hasUserData()) {
+            this.sendDataToExternalPage();
+          }
+        }
+        
+        // 检查是否为externalPage发送的确认消息
+        else if (data && data.type === 'externalParamsReceived') {
+          // 移除临时确认监听器,避免重复处理
+          if (window.confirmationListener) {
+            window.removeEventListener('message', window.confirmationListener);
+            window.confirmationListener = null;
+          }
+        }
+      };
+      
+      window.addEventListener('message', this.handleExternalPageMessage);
+    },
+    
+    // 移除externalPage监听器
+    removeExternalPageListener() {
+      if (this.handleExternalPageMessage) {
+        window.removeEventListener('message', this.handleExternalPageMessage);
+      }
+    },
+    
+    // 检查是否已输入用户信息
+    hasUserData() {
+      return this.formData.businessAttr && 
+             this.formData.contractNo && 
+             this.formData.userId && 
+             this.formData.userNm;
+    },
+    
+    // 发送数据到externalPage
+    sendDataToExternalPage() {
+      if (!this.showIframe) {
+        return;
+      }
+      
+      const iframe = document.querySelector('iframe');
+      if (!iframe) {
+        return;
+      }
+      
+      // 监听iframe的load事件,确保页面完全加载完成
+      if (iframe.contentDocument && iframe.contentDocument.readyState === 'complete') {
+        // iframe已经加载完成,直接发送消息
+        this.sendMessageToIframe(iframe);
+      } else {
+        // iframe还在加载中,等待加载完成
+        
+        // 设置加载状态监听
+        let isMessageSent = false;
+        
+        iframe.onload = () => {
+          if (!isMessageSent) {
+            this.sendMessageToIframe(iframe);
+            isMessageSent = true;
+          }
+        };
+        
+        // 设置渐进式超时机制,适应不同网络环境
+        const timeouts = [5000, 10000, 15000]; // 5秒、10秒、15秒
+        
+        timeouts.forEach((timeout, index) => {
+          setTimeout(() => {
+            if (!isMessageSent) {
+              if (iframe.contentDocument && iframe.contentDocument.readyState === 'complete') {
+                this.sendMessageToIframe(iframe);
+                isMessageSent = true;
+              } else {
+                if (index === timeouts.length - 1) {
+                  // 最后一次超时,强制尝试发送消息
+                  this.sendMessageToIframe(iframe);
+                  isMessageSent = true;
+                }
+              }
+            }
+          }, timeout);
+        });
+      }
+    },
+    
+    // 确认按钮点击事件
+    handleConfirm() {
+      // 检查必填项
+      if (!this.formData.businessAttr) {
+        this.$message.warning('请输入贷款申请编号');
+        return;
+      }
+      if (!this.formData.contractNo) {
+        this.$message.warning('请输入合同编号');
+        return;
+      }
+      if (!this.formData.userId) {
+        this.$message.warning('请输入用户ID');
+        return;
+      }
+      if (!this.formData.userNm) {
+        this.$message.warning('请输入用户姓名');
+        return;
+      }
+      
+      // 模拟iframe访问发起逻辑
+      this.simulateIframeAccess();
+      
+      // 构建iframe URL - 不使用地址栏传参,只使用基础路径
+      // this.iframeUrl = `http://10.192.72.13:31080/#/externalPage`;
+      // this.iframeUrl = `http://localhost:8000/#/externalPage`;
+      this.iframeUrl = `/#/externalPage`;
+      
+      // 如果iframe已经显示,先隐藏再显示以重新加载
+      if (this.showIframe) {
+        this.showIframe = false;
+        this.$nextTick(() => {
+          this.showIframe = true;
+        });
+      } else {
+        // 显示iframe
+        this.showIframe = true;
+      }
+    },
+    
+    // 模拟iframe访问发起逻辑
+    simulateIframeAccess() {
+    },
+    
+
+    
+    // 实际发送消息到iframe
+    sendMessageToIframe(iframe) {
+      if (!iframe.contentWindow) {
+        return;
+      }
+      
+      const messageData = {
+        type: 'externalParams',
+        business_attr: this.formData.businessAttr,
+        user_id: this.formData.userId,
+        user_nm: this.formData.userNm,
+        user_org: this.formData.userOrg,
+        user_endpoint: this.formData.userEndpoint,
+        contract_no: this.formData.contractNo,
+        timestamp: Date.now(),
+        source: 'taggingConfirm'
+      };
+      
+      try {
+        // 发送postMessage到iframe
+        iframe.contentWindow.postMessage(messageData, '*');
+        
+        // 监听来自iframe的确认消息
+        this.setupMessageConfirmation();
+      } catch (error) {
+        console.error('发送postMessage失败:', error);
+      }
+    },
+    
+    // 设置消息确认机制
+    setupMessageConfirmation() {
+      // 监听来自iframe的确认消息
+      const handleConfirmation = (event) => {
+        if (event.data && event.data.type === 'externalParamsReceived') {
+          // 移除事件监听器
+          window.removeEventListener('message', handleConfirmation);
+        }
+      };
+      
+      window.addEventListener('message', handleConfirmation);
+      
+      // 设置渐进式超时机制,适应网络延迟
+      const confirmationTimeouts = [10000, 20000, 30000]; // 10秒、20秒、30秒
+      
+      confirmationTimeouts.forEach((timeout, index) => {
+        setTimeout(() => {
+          if (window.confirmationListener === handleConfirmation) {
+            if (index === confirmationTimeouts.length - 1) {
+              // 最终超时
+              window.removeEventListener('message', handleConfirmation);
+            }
+          }
+        }, timeout);
+      });
+      
+      // 保存监听器引用,便于后续清理
+      window.confirmationListener = handleConfirmation;
+    },
+    
+    // 清空按钮点击事件
+    handleClear() {
+      // 清空所有输入
+      this.formData.businessAttr = '';
+      this.formData.userId = '';
+      this.formData.userNm = '';
+      this.formData.userOrg = '';
+      this.formData.userEndpoint = '';
+      // 隐藏iframe
+      this.showIframe = false;
+      this.iframeUrl = '';
+      
+      this.$message.info('已清空输入内容');
+    }
+  }
+}
+</script>
+
+<style scoped>
+.tagging-confirm {
+  height: 100%;
+}
+
+.confirm-container {
+  display: flex;
+  gap: 20px;
+  height: 100%;
+}
+
+/* 左侧用户信息输入区域 */
+.left-section {
+  flex: 0 0 200px;
+  background-color: #fafafa;
+  border-radius: 8px;
+  padding: 12px;
+  min-width: 190px;
+  max-width: 200px;
+}
+
+/* 右侧iframe展示区域 */
+.right-section {
+  flex: 2;
+  background-color: #fafafa;
+  border-radius: 8px;
+  padding: 20px;
+  height: 500px;
+  display: flex;
+  flex-direction: column;
+  min-height: 0; /* 重要:允许内容区域收缩 */
+}
+
+.section-title {
+  font-size: 14px;
+  font-weight: 600;
+  color: #303133;
+  margin: 0 0 16px 0;
+  text-align: left;
+  flex-shrink: 0; /* 标题不收缩 */
+}
+
+/* iframe容器样式 */
+.iframe-container {
+  flex: 1;
+  min-height: 0;
+  overflow: auto;
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+  background-color: #fff;
+  display: flex;
+  flex-direction: column;
+  max-height: 100%;
+}
+
+.iframe-container iframe {
+  flex: 1;
+  width: 100%;
+  height: 100%;
+  border: none;
+  background-color: #fff;
+  overflow: auto;
+}
+
+/* 状态面板样式 */
+.status-panel {
+  background-color: #f8fafc;
+  border: 1px solid #e2e8f0;
+  border-radius: 6px;
+  padding: 12px 16px;
+  margin-bottom: 16px;
+  font-size: 13px;
+}
+
+.status-item {
+  display: flex;
+  align-items: center;
+  margin-bottom: 8px;
+}
+
+.status-item:last-child {
+  margin-bottom: 0;
+}
+
+.status-label {
+  font-weight: 600;
+  color: #4a5568;
+  min-width: 100px;
+  margin-right: 8px;
+}
+
+.status-value {
+  color: #2d3748;
+  font-family: 'Courier New', monospace;
+  background-color: #edf2f7;
+  padding: 2px 6px;
+  border-radius: 3px;
+  font-size: 12px;
+}
+
+.missing-tag {
+  margin-right: 4px;
+  margin-bottom: 4px;
+}
+
+/* 空iframe状态样式 */
+.empty-iframe {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  background-color: #f8f9fa;
+  border: 1px dashed #dcdfe6;
+  border-radius: 4px;
+  color: #909399;
+}
+
+.empty-iframe img {
+  width: 80px;
+  height: 80px;
+  margin-bottom: 16px;
+  opacity: 0.6;
+}
+
+.empty-iframe p {
+  font-size: 14px;
+  margin: 0;
+}
+
+/* 输入信息区域 */
+/* 调整el-form样式 */
+.el-form {
+  margin-bottom: 16px;
+}
+
+.el-form-item {
+  margin-bottom: 12px;
+}
+
+.el-form-item__label {
+  font-size: 13px;
+  color: #606266;
+  padding-bottom: 4px;
+}
+
+/* 输入操作按钮 */
+.input-actions {
+  display: flex;
+  justify-content: space-between;
+  gap: 8px;
+  margin-top: 16px;
+  width: 100%;
+}
+
+/* 调整按钮大小 */
+.input-actions .el-button {
+  padding: 6px 12px;
+  font-size: 12px;
+  flex: 1;
+}
+
+/* 确认按钮样式 */
+.input-actions .el-button--primary {
+  flex: 1.5;
+}
+
+/* iframe容器 */
+.iframe-container {
+  flex: 1;
+  border: 1px solid #e8e8e8;
+  border-radius: 4px;
+  overflow: hidden;
+  background-color: #ffffff;
+}
+
+/* 空iframe提示 */
+.empty-iframe {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 60px 20px;
+  color: #909399;
+  background-color: #fafafa;
+  min-height: 400px;
+}
+
+.empty-iframe img {
+  width: 120px;
+  height: 120px;
+  margin-bottom: 16px;
+  opacity: 0.6;
+}
+
+.empty-iframe p {
+  font-size: 14px;
+  color: #909399;
+  margin: 0;
+  line-height: 1.5;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .confirm-container {
+    flex-direction: column;
+  }
+  
+  .left-section {
+    max-width: 100%;
+  }
+  
+  .right-section {
+    min-height: 400px;
+  }
+  
+  .form-row {
+    flex-direction: column;
+    gap: 16px;
+  }
+  
+  .form-item {
+    flex-direction: column;
+    align-items: flex-start;
+    min-width: auto;
+  }
+  
+  .form-label {
+    text-align: left;
+    margin-bottom: 8px;
+    width: 100%;
+  }
+  
+  .form-item .el-input {
+    width: 100% !important;
+  }
+  
+  .input-actions {
+    flex-direction: column;
+  }
+  
+  .input-actions .el-button {
+    width: 100%;
+  }
+}
+</style>