Quellcode durchsuchen

//代码优化故障预测功能完善

wyj0522 vor 1 Woche
Ursprung
Commit
1eb06ba9ec

+ 8 - 0
gm-base-ser/src/main/java/com/goomood/phm/controller/KGraphEntityController.java

@@ -89,4 +89,12 @@ public class KGraphEntityController extends BaseController {
     public AjaxResult remove(@PathVariable Long[] ids) {
         return success(kGraphEntityService.deleteByIds(ids));
     }
+
+
+    @Log(title = "删除知识图谱实体关系", businessType = BusinessType.DELETE)
+    @DeleteMapping("/deletebyId/{id}")
+    public AjaxResult deletebyId(@PathVariable Long id) {
+        kGraphEntityService.deletebyId(id);
+        return success();
+    }
 }

+ 3 - 1
gm-base-ser/src/main/java/com/goomood/phm/service/IKGraphEntityService.java

@@ -45,7 +45,7 @@ public interface IKGraphEntityService {
      * @param po 知识图谱实体关系Bo
      * @return 结果:true 操作成功,false 操作失败
      */
-    void insert(KGraphEntity po);
+    boolean insert(KGraphEntity po);
 
     /**
      * 新增知识图谱实体关系
@@ -78,4 +78,6 @@ public interface IKGraphEntityService {
      * @return 结果:true 删除成功,false 删除失败
      */
     boolean deleteByIds(Long[] ids);
+
+    void deletebyId(Long id);
 }

+ 24 - 17
gm-base-ser/src/main/java/com/goomood/phm/service/impl/FaultMonitorServiceAImpl.java

@@ -8,6 +8,7 @@ import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
 import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.util.*;
 import java.util.stream.Collectors;
@@ -27,16 +28,27 @@ public class FaultMonitorServiceAImpl implements FaultMonitorService {
         LocalDate nextMonth = currentDate.plusMonths(1);
         int targetMonth = nextMonth.getMonthValue();
 
-        // 查询近三年数据
+        // 构建前三年同月的开始和结束日期
+        LocalDate startDate1 = LocalDate.of(currentDate.getYear() - 3, targetMonth, 1);
+        LocalDate endDate1 = startDate1.withDayOfMonth(startDate1.lengthOfMonth());
+
+        LocalDate startDate2 = LocalDate.of(currentDate.getYear() - 2, targetMonth, 1);
+        LocalDate endDate2 = startDate2.withDayOfMonth(startDate2.lengthOfMonth());
+
+        LocalDate startDate3 = LocalDate.of(currentDate.getYear() - 1, targetMonth, 1);
+        LocalDate endDate3 = startDate3.withDayOfMonth(startDate3.lengthOfMonth());
+
+        // 查询前三年同月的数据
         QueryWrapper<FaultMonitorA> queryWrapper = new QueryWrapper<>();
-        queryWrapper.between("incident_date",
-                toDate(currentDate.minusYears(3)),
-                toDate(currentDate));
+        queryWrapper.and(wrapper -> wrapper
+                .between("incident_date", toDate(startDate1), toDate(endDate1))
+                .or().between("incident_date", toDate(startDate2), toDate(endDate2))
+                .or().between("incident_date", toDate(startDate3), toDate(endDate3))
+        );
 
         List<FaultMonitorA> faultMonitors = faultMonitorAMapper.selectList(queryWrapper);
 
         return faultMonitors.stream()
-                .filter(fault -> isTargetMonth(fault.getIncidentDate(), targetMonth))
                 .filter(this::isValidFaultPart)
                 .collect(Collectors.groupingBy(
                         FaultMonitorA::getFaultyPartName,
@@ -56,12 +68,13 @@ public class FaultMonitorServiceAImpl implements FaultMonitorService {
     @Override
     public Map<String, Long> getTop5FaultyPartsInSixMonths() {
         LocalDate currentDate = LocalDate.now();
+        LocalDate sixMonthsAgo = currentDate.minusMonths(6);
 
-        // 使用MyBatis-Plus查询近半年数据
+        // 查询当前月及前六个月的数据(共7个月)
         QueryWrapper<FaultMonitorA> queryWrapper = new QueryWrapper<>();
         queryWrapper.between("incident_date",
-                toDate(currentDate.minusMonths(6)),
-                toDate(currentDate));
+                toDate(sixMonthsAgo),
+                toDate(currentDate.withDayOfMonth(currentDate.lengthOfMonth())));
 
         List<FaultMonitorA> faultMonitors = faultMonitorAMapper.selectList(queryWrapper);
 
@@ -87,15 +100,9 @@ public class FaultMonitorServiceAImpl implements FaultMonitorService {
         return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
     }
 
-    // 检查是否为目标月份的故障
-    private boolean isTargetMonth(Date date, int targetMonth) {
-        if (date == null) return false;
-        return toLocalDate(date).getMonthValue() == targetMonth;
-    }
-
-    // 辅助方法:Date转LocalDate
-    private LocalDate toLocalDate(Date date) {
-        return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+    // 辅助方法:LocalDateTime转Date
+    private Date toDate(LocalDateTime localDateTime) {
+        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
     }
 
     // 验证故障部件有效性

+ 88 - 19
gm-base-ser/src/main/java/com/goomood/phm/service/impl/KGraphEntityServiceImpl.java

@@ -8,18 +8,20 @@ import com.goomood.phm.mapper.KGraphEntityMapper;
 import com.goomood.phm.service.IKGraphEntityService;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.ObjectUtils;
+import org.apache.ibatis.executor.BatchResult;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
+import java.util.Arrays;
 import java.util.List;
 
 @Service
 @Slf4j
-public class KGraphEntityServiceImpl  implements IKGraphEntityService {
+public class KGraphEntityServiceImpl implements IKGraphEntityService {
     @Resource
     private KGraphEntityMapper kGraphEntityMapper;
 
-
     /**
      * 构建知识图谱实体关系查询条件
      */
@@ -37,14 +39,13 @@ public class KGraphEntityServiceImpl  implements IKGraphEntityService {
                 .eq(ObjectUtils.isNotEmpty(query.getStatus()), "status", query.getStatus())
                 .eq(query.getDelFlag() != null, "del_flag", query.getDelFlag());
 
-        // 时间范围查询(示例:创建时间)
-        if (query.getCreateTime() != null) {
-            // 假设前端传入的是开始时间
+        // 时间范围查询
+        if (query.getCreateTime() != null && query.getUpdateTime() != null) {
+            wrapper.between("create_time", query.getCreateTime(), query.getUpdateTime());
+        } else if (query.getCreateTime() != null) {
             wrapper.ge("create_time", query.getCreateTime());
-        }
-        if (query.getUpdateTime() != null) {
-            // 假设前端传入的是结束时间
-            wrapper.le("update_time", query.getUpdateTime());
+        } else if (query.getUpdateTime() != null) {
+            wrapper.le("create_time", query.getUpdateTime());
         }
 
         // 排序条件(默认按创建时间降序)
@@ -52,6 +53,7 @@ public class KGraphEntityServiceImpl  implements IKGraphEntityService {
 
         return wrapper;
     }
+
     /**
      * 查询知识图谱实体关系
      *
@@ -70,8 +72,9 @@ public class KGraphEntityServiceImpl  implements IKGraphEntityService {
 
     @Override
     public List<KGraphEntity> selectListByTaskId(Long taskId) {
-        return  kGraphEntityMapper.selectListByTaskId(taskId);
+        return kGraphEntityMapper.selectListByTaskId(taskId);
     }
+
     /**
      * 分页查询知识图谱实体关系列表
      *
@@ -80,9 +83,9 @@ public class KGraphEntityServiceImpl  implements IKGraphEntityService {
      */
     @Override
     public Page<KGraphEntity> selectPage(KGraphEntity po) {
-        Page<KGraphEntity> kGraphEntityPage = kGraphEntityMapper.selectPage(new Page<>(), buildQueryWrapper(po));
-        return kGraphEntityPage;
+        return kGraphEntityMapper.selectPage(new Page<>(), buildQueryWrapper(po));
     }
+
     /**
      * 新增知识图谱实体关系
      *
@@ -90,27 +93,93 @@ public class KGraphEntityServiceImpl  implements IKGraphEntityService {
      * @return 结果:true 操作成功,false 操作失败
      */
     @Override
-    public void insert(KGraphEntity po) {
-         kGraphEntityMapper.insert(po);
+    public boolean insert(KGraphEntity po) {
+        int count = kGraphEntityMapper.insert(po);
+        return count > 0;
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public boolean saveBatch(List<KGraphEntity> kGraphEntityList) {
-        return false;
+        if (kGraphEntityList == null || kGraphEntityList.isEmpty()) {
+            log.warn("批量插入的实体列表为空");
+            return false;
+        }
+
+        List<BatchResult> results = kGraphEntityMapper.insert(kGraphEntityList, 100);
+
+        if (results == null || results.isEmpty()) {
+            log.error("批量插入返回空结果");
+            return false;
+        }
+
+        boolean isSuccess = true;
+        for (BatchResult result : results) {
+            try {
+                int[] updateCounts = result.getUpdateCounts();
+                for (int count : updateCounts) {
+                    if (count <= 0) {
+                        isSuccess = false;
+                        log.warn("发现未成功的插入操作,受影响行数: {}", count);
+                    }
+                }
+            } catch (Exception e) {
+                isSuccess = false;
+                log.error("批量操作异常", e);
+                throw new RuntimeException("批量插入失败", e); // 触发事务回滚
+            }
+        }
+
+        return isSuccess;
     }
 
     @Override
     public boolean insertWithPk(KGraphEntity po) {
-        return false;
+        if (po == null || po.getId() == null) {
+            log.warn("带主键插入时实体或ID为空");
+            return false;
+        }
+
+        try {
+            int count = kGraphEntityMapper.insert(po);
+            return count > 0;
+        } catch (Exception e) {
+            log.error("带主键插入失败,ID: {}", po.getId(), e);
+            return false;
+        }
     }
 
     @Override
     public boolean update(KGraphEntity po) {
-        return false;
+        if (ObjectUtils.isEmpty(po) || ObjectUtils.isEmpty(po.getId())) {
+            log.warn("更新时实体或ID为空");
+            return false;
+        }
+
+        int count = kGraphEntityMapper.updateById(po);
+        return count > 0;
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public boolean deleteByIds(Long[] ids) {
-        return false;
+        if (ids == null || ids.length == 0) {
+            log.warn("删除操作的ID数组为空");
+            return false;
+        }
+
+        try {
+            int count = kGraphEntityMapper.deleteByIds(Arrays.asList(ids));
+            return count > 0;
+        } catch (Exception e) {
+            log.error("批量删除失败,IDs: {}", Arrays.toString(ids), e);
+            throw new RuntimeException("批量删除失败", e); // 触发事务回滚
+        }
+    }
+
+    @Override
+    public void deletebyId(Long id) {
+
+        kGraphEntityMapper.deleteById(id);
     }
-}
+}

+ 9 - 7
gm-base-ser/src/main/java/com/goomood/phm/service/impl/KGraphTaskServiceImpl.java

@@ -77,6 +77,7 @@ public class KGraphTaskServiceImpl implements IKGraphTaskService {
     @Override
     public List<KGraphTask> selectPage(KGraphTask po) {
         QueryWrapper<KGraphTask> wrapper = new QueryWrapper<>();
+        wrapper.lambda().like(StringUtils.isNotEmpty(po.getTaskName()),KGraphTask::getTaskName,po.getTaskName());
         List<KGraphTask> kGraphTasks = kGraphTaskMapper.selectList(wrapper);
         return kGraphTasks;
     }
@@ -165,6 +166,7 @@ public class KGraphTaskServiceImpl implements IKGraphTaskService {
                 List<KGraphClause> graphClauseVos = kGraphClauseService.selectList(kGraphClauseBo);
                 extraGraphTaskVo.setTaskId(po.getId());
                 extraGraphTaskVo.setGraphClauseList(graphClauseVos);
+                extraGraphTaskVo.setTaskType(po.getTaskType());
                 result = HttpUtils.postJson(extractUrl, extraGraphTaskVo);
                 jsonObject = JSON.parseObject(result);
                 if (!StringUtils.equals("200", jsonObject.getString("code"))) {
@@ -224,12 +226,7 @@ public class KGraphTaskServiceImpl implements IKGraphTaskService {
     @Override
     public boolean resultCallback(ExtraKGraphTaskDTO dto) {
         if(ObjectUtils.isNotEmpty(dto.getGraphEntityList())){
-            KGraphTask kGraphTaskVo = this.selectById(dto.getTaskId());
-            KGraphTask kGraphTaskBo = new KGraphTask();
-            kGraphTaskBo.setId(dto.getTaskId());
-            kGraphTaskBo.setVersion(kGraphTaskVo.getVersion());
-            kGraphTaskBo.setStatus(StringUtils.equals(dto.getTaskType(), "1") ? GraphStatus.STATUS_2.getCode() : GraphStatus.STATUS_4.getCode());
-            return this.update(kGraphTaskBo);
+           log.info("当前吗文件未抽取到内容{}",dto.getGraphEntityList());
         }
         if (StringUtils.equals(dto.getTaskType(), "1")) {
             boolean isSave = kGraphClauseService.saveBatch(dto.getGraphClauseList());
@@ -243,7 +240,12 @@ public class KGraphTaskServiceImpl implements IKGraphTaskService {
                 return false;
             }
         }
-        return false;
+        KGraphTask kGraphTaskVo = this.selectById(dto.getTaskId());
+        KGraphTask kGraphTaskBo = new KGraphTask();
+        kGraphTaskBo.setId(dto.getTaskId());
+        kGraphTaskBo.setVersion(kGraphTaskVo.getVersion());
+        kGraphTaskBo.setStatus(StringUtils.equals(dto.getTaskType(), "1") ? GraphStatus.STATUS_2.getCode() : GraphStatus.STATUS_4.getCode());
+        return this.update(kGraphTaskBo);
     }
 
     @Override

+ 2 - 2
gm-base-ser/src/main/resources/application.yml

@@ -52,9 +52,9 @@ elasticsearch:
 user:
   password:
     # 密码最大错误次数
-    maxRetryCount: 5
+    maxRetryCount: 1000
     # 密码锁定时间(默认10分钟)
-    lockTime: 10
+    lockTime: 1
 
 # Spring配置
 spring:

+ 1 - 1
gm-web/src/api/als/knowledgeExtraction.js

@@ -159,7 +159,7 @@ export function updateExtractInfo(data) {
  */
 export function removeExtractInfo(id) {
   return request({
-    url: `/als/kGraphEntity/selectOne/${id}`,
+    url: `/als/kGraphEntity/deletebyId/${id}`,
     method: 'delete'
   })
 }

+ 1 - 0
gm-web/src/main.js

@@ -38,6 +38,7 @@ import VueMeta from 'vue-meta'
 // 字典数据组件
 import DictData from '@/components/DictData'
 
+
 // 全局方法挂载
 Vue.prototype.getDicts = getDicts
 Vue.prototype.getConfigKey = getConfigKey

+ 9 - 0
gm-web/src/router/index.js

@@ -87,6 +87,15 @@ export const constantRoutes = [
         meta: { title: '个人中心', icon: 'user' }
       }
     ]
+  },
+  {
+    path: '/',
+    component: Layout,
+    children: [
+      { path: 'chatAI', component: () => import('@/views/chat/catAi.vue') },
+      { path: 'settings', component: () => import('@/views/knowledgeExtraction/index.vue') },
+      { path: 'faultMonitor', component: () => import('@/views/FaultStatisticsBoard.vue') },
+    ]
   }
 ]
 

+ 35 - 0
gm-web/src/utils/tools.js

@@ -0,0 +1,35 @@
+/**
+ * 防抖函数 - 限制函数的执行频率,只有在延迟时间后才执行最后一次调用
+ * @param {Function} func - 要防抖的函数
+ * @param {number} wait - 延迟时间(毫秒)
+ * @param {boolean} immediate - 是否立即执行(true: 首次调用立即执行,false: 延迟后执行)
+ * @returns {Function} - 返回一个经过防抖处理的函数
+ */
+export function debounce(func, wait = 300, immediate = false) {
+  let timeout
+
+  return function executedFunction(...args) {
+    const context = this
+
+    // 如果需要立即执行,并且还没有定时器
+    const callNow = immediate && !timeout
+
+    // 清除之前的定时器
+    clearTimeout(timeout)
+
+    // 设置新的定时器,在延迟时间后执行函数
+    timeout = setTimeout(() => {
+      timeout = null
+
+      // 如果不需要立即执行,则执行函数
+      if (!immediate) {
+        func.apply(context, args)
+      }
+    }, wait)
+
+    // 如果需要立即执行,则立即调用函数
+    if (callNow) {
+      func.apply(context, args)
+    }
+  }
+}

+ 445 - 219
gm-web/src/views/FaultStatisticsBoard.vue

@@ -1,333 +1,559 @@
 <template>
   <div class="fault-statistics-board">
-    <!-- 看板标题 -->
+    <!-- 看板标题区域 -->
     <div class="board-header">
-      <h2>故障部件统计综合看板</h2>
-      <el-tag type="info" size="small">{{ updateTime }}</el-tag>
+      <div class="header-content">
+        <h2>故障部件统计综合看板</h2>
+        <p class="time-desc">
+          数据更新时间:<span class="update-time">{{ updateTime || '--' }}</span>
+        </p>
+      </div>
+      <el-tag type="info" size="small" v-if="timeRangesVisible">
+        <template v-if="activeTimeRange === 'nextMonth'">
+          近三年下月周期:{{ threeYearsNextMonthRange }}
+        </template>
+        <template v-else-if="activeTimeRange === 'sixMonths'">
+          近半年周期:{{ sixMonthsRange }}
+        </template>
+      </el-tag>
     </div>
-
-    <!-- 数据卡片区域 -->
-    <div class="statistic-cards">
-      <el-card class="stat-card">
-        <div slot="header">近三年下月故障总次数</div>
-        <div class="stat-value">{{ totalNextMonthFaults }}</div>
-        <div class="stat-desc">统计周期:{{ threeYearsRange }}</div>
-      </el-card>
-
-      <el-card class="stat-card">
-        <div slot="header">近半年故障总次数</div>
-        <div class="stat-value">{{ totalSixMonthsFaults }}</div>
-        <div class="stat-desc">统计周期:{{ sixMonthsRange }}</div>
-      </el-card>
-    </div>
-
-    <!-- 图表区域(响应式布局) -->
+    <!-- 图表区域 -->
     <div class="charts-container">
-      <!-- 近三年下月故障部件TOP5 -->
-      <el-card class="chart-card">
-        <div slot="header">近三年下月故障最多的5类部件</div>
+      <!-- 加载状态 -->
+      <el-loading
+        v-if="loading"
+        :fullscreen="false"
+        target=".charts-container"
+        text="正在加载数据..."
+        spinner="el-icon-loading"
+      ></el-loading>
+
+      <div class="charts-row">
+        <el-card class="chart-card first-two-charts" shadow="hover" @mouseenter="activeTimeRange = 'nextMonth'">
+          <div slot="header">基于节气的部件故障预测</div>
+          <div class="chart-wrapper">
+            <div ref="nextMonthChart" class="chart-dom"></div>
+            <div class="empty-state" v-if="!loading && nextMonthData.length === 0">
+              <el-icon class="empty-icon">
+                <warning/>
+              </el-icon>
+              <p>暂无相关数据</p>
+            </div>
+          </div>
+        </el-card>
+        <el-card class="chart-card first-two-charts" shadow="hover" @mouseenter="activeTimeRange = 'sixMonths'">
+          <div slot="header">基于历史数据的部件故障预测</div>
+          <div class="chart-wrapper">
+            <div ref="sixMonthsChart" class="chart-dom"></div>
+            <div class="empty-state" v-if="!loading && sixMonthsData.length === 0">
+              <el-icon class="empty-icon">
+                <warning />
+              </el-icon>
+              <p>暂无相关数据</p>
+            </div>
+          </div>
+        </el-card>
+      </div>
+
+      <!-- 3. 合并数据统计 -->
+      <el-card class="chart-card merged-chart" shadow="hover">
+        <div slot="header">部件综合故障预测</div>
         <div class="chart-wrapper">
-          <div ref="nextMonthChart" class="chart-dom"></div>
-        </div>
-      </el-card>
-
-      <!-- 近半年故障部件TOP5 -->
-      <el-card class="chart-card">
-        <div slot="header">近半年故障最多的5类部件</div>
-        <div class="chart-wrapper">
-          <div ref="sixMonthsChart" class="chart-dom"></div>
+          <div ref="mergedChart" class="chart-dom"></div>
+          <div class="empty-state" v-if="!loading && mergedData.length === 0">
+            <el-icon class="empty-icon">
+              <warning/>
+            </el-icon>
+            <p>暂无相关数据</p>
+          </div>
         </div>
       </el-card>
     </div>
-
-<!--    &lt;!&ndash; 加载状态 &ndash;&gt;-->
-<!--    <el-loading-->
-<!--      v-loading="loading"-->
-<!--      text="正在加载统计数据..."-->
-<!--      background="rgba(255, 255, 255, 0.8)"-->
-<!--    ></el-loading>-->
   </div>
 </template>
 
 <script>
 import * as echarts from 'echarts'
-import { getTop5NextMonth, getTop5SixMonths } from '@/api/faultStatistics' // 引入API
-import { formatDate } from '@/utils/dataUtils' // 日期工具函数
+import { formatDate } from '@/utils/dataUtils'
+import { debounce } from '@/utils/tools' // 引入防抖工具函数
 
 export default {
   name: 'FaultStatisticsBoard',
   data() {
     return {
-      // 数据
-      nextMonthData: [], // 近三年下月TOP5数据
-      sixMonthsData: [], // 近半年TOP5数据
-      totalNextMonthFaults: 0, // 近三年下月总故障数
-      totalSixMonthsFaults: 0, // 近半年总故障数
-
-      // 时间信息
-      updateTime: '', // 数据更新时间
-      threeYearsRange: '', // 近三年时间范围
-      sixMonthsRange: '', // 近半年时间范围
-
-      // 状态
-      loading: true,
+      // 原始数据
+      nextMonthData: [], // 近三年下月数据
+      sixMonthsData: [], // 近半年数据
+      mergedData: [], // 合并后数据
+
+      // 总计数据
+      totalNextMonthFaults: 0,
+      totalSixMonthsFaults: 0,
+      totalMergedFaults: 0,
+
+      // 时间范围文本
+      updateTime: '',
+      threeYearsNextMonthRange: '', // 近三年下月周期
+      sixMonthsRange: '', // 近半年周期
+      activeTimeRange: '', // 当前激活的时间范围(用于显示标签)
+      timeRangesVisible: false, // 时间范围标签是否显示
+
       // 图表实例
       nextMonthChartIns: null,
-      sixMonthsChartIns: null
+      sixMonthsChartIns: null,
+      mergedChartIns: null,
+
+      // 状态控制
+      loading: false
     }
   },
   mounted() {
-    // 初始化图表
     this.initCharts()
-    // 加载数据
     this.loadStatisticsData()
-    // 监听窗口大小变化,重绘图表
-    window.addEventListener('resize', this.handleResize)
+    // 防抖处理窗口resize事件(300ms延迟)
+    window.addEventListener('resize', debounce(this.handleResize, 300))
   },
   beforeDestroy() {
-    // 销毁图表实例,释放资源
-    if (this.nextMonthChartIns) this.nextMonthChartIns.dispose()
-    if (this.sixMonthsChartIns) this.sixMonthsChartIns.dispose()
+    // 销毁所有图表实例
+    this.destroyCharts()
     window.removeEventListener('resize', this.handleResize)
   },
   methods: {
-    // 初始化图表实例
+    // 初始化三个图表实例
     initCharts() {
-      this.nextMonthChartIns = echarts.init(this.$refs.nextMonthChart)
-      this.sixMonthsChartIns = echarts.init(this.$refs.sixMonthsChart)
+      // 确保DOM元素已渲染
+      this.$nextTick(() => {
+        console.log('图表容器是否存在:', this.$refs.nextMonthChart); // 检查是否为null
+        this.nextMonthChartIns = echarts.init(this.$refs.nextMonthChart)
+        this.sixMonthsChartIns = echarts.init(this.$refs.sixMonthsChart)
+        this.mergedChartIns = echarts.init(this.$refs.mergedChart)
+      })
+    },
+
+    // 销毁图表实例
+    destroyCharts() {
+      this.nextMonthChartIns?.dispose()
+      this.sixMonthsChartIns?.dispose()
+      this.mergedChartIns?.dispose()
     },
 
-    // 加载统计数据
+    // 加载并处理数据
     async loadStatisticsData() {
+
+      await this.$nextTick();
       this.loading = true
       try {
-        // 并行请求两个接口
-        const [nextMonthRes, sixMonthsRes] = await Promise.all([
-          getTop5NextMonth(),
-          getTop5SixMonths()
-        ])
-
-        // 处理近三年下月数据
-        this.nextMonthData = nextMonthRes.data || []
-        this.totalNextMonthFaults = this.nextMonthData.reduce(
-          (sum, item) => sum + item.count, 0
-        )
-
-        // 处理近半年数据
-        this.sixMonthsData = sixMonthsRes.data || []
-        this.totalSixMonthsFaults = this.sixMonthsData.reduce(
-          (sum, item) => sum + item.count, 0
-        )
-
-        // 设置时间信息
+        // 实际项目中启用API请求
+        // const [nextMonthRes, sixMonthsRes] = await Promise.all([
+        //   getTop5NextMonth(),
+        //   getTop5SixMonths()
+        // ])
+        // this.nextMonthData = nextMonthRes.data || []
+        // this.sixMonthsData = sixMonthsRes.data || []
+
+        // 模拟数据(已修正排序问题)
+        this.sixMonthsData = [
+          { partName: '滚动轴承', count: 88 },
+          { partName: '液压泵', count: 72 },
+          { partName: '密封件', count: 59 },
+          { partName: '驱动电机', count: 48 },
+          { partName: '电磁阀', count: 36 }
+        ]
+        this.nextMonthData = [
+          { partName: '驱动电机', count: 128 },
+          { partName: '滚动轴承', count: 95 },
+          { partName: '温度传感器', count: 76 },
+          { partName: '液压泵', count: 62 },
+          { partName: '控制模块', count: 45 }
+        ]
+
+        // 处理数据
+        this.calculateTotals() // 计算总计
+        this.mergedData = this.mergeAndSortData() // 合并排序数据
+        this.setTimeRangeText() // 设置时间范围
         this.updateTime = formatDate(new Date(), 'yyyy-MM-dd HH:mm:ss')
-        this.setTimeRangeText()
-
+        this.timeRangesVisible = true
         // 渲染图表
-        this.renderNextMonthChart()
-        this.renderSixMonthsChart()
-
+        this.renderAllCharts()
       } catch (error) {
-        this.$message.error('数据加载失败:' + (error.message || '未知错误'))
+        this.$message.error(`数据加载失败:${error.message || '未知错误'}`)
+        console.error('数据加载错误:', error)
       } finally {
         this.loading = false
       }
     },
 
+    // 计算各统计项总计
+    calculateTotals() {
+      this.totalNextMonthFaults = this.nextMonthData.reduce((sum, item) => sum + item.count, 0)
+      this.totalSixMonthsFaults = this.sixMonthsData.reduce((sum, item) => sum + item.count, 0)
+      this.totalMergedFaults = this.mergedData.reduce((sum, item) => sum + item.count, 0)
+    },
+
+    // 合并并排序数据(相同部件累加,按count升序)
+    mergeAndSortData() {
+      const mergedMap = new Map()
+
+      // 累加近三年下月数据
+      this.nextMonthData.forEach(item => {
+        mergedMap.set(item.partName, (mergedMap.get(item.partName) || 0) + item.count)
+      })
+
+      // 累加近半年数据
+      this.sixMonthsData.forEach(item => {
+        mergedMap.set(item.partName, (mergedMap.get(item.partName) || 0) + item.count)
+      })
+
+      // 转换为数组并按count升序排序
+      return Array.from(mergedMap.entries())
+        .map(([partName, count]) => ({ partName, count }))
+        .sort((a, b) => a.count - b.count)
+    },
+
     // 设置时间范围文本
     setTimeRangeText() {
       const now = new Date()
-      // 近三年范围:例如 2022-08 至 2025-08
-      const threeYearsAgo = new Date(now.getFullYear() - 3, now.getMonth(), 1)
-      this.threeYearsRange = `${formatDate(threeYearsAgo, 'yyyy-MM')} 至 ${formatDate(now, 'yyyy-MM')}`
+      const nextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1)
+      const month = String(nextMonth.getMonth() + 1).padStart(2, '0') // 补零处理
+
+      // 近三年下月周期(如:2022-08、2023-08、2024-08)
+      const years = [now.getFullYear() - 3, now.getFullYear() - 2, now.getFullYear() - 1]
+      this.threeYearsNextMonthRange = years.map(year => `${year}-${month}`).join('、')
 
-      // 近半年范围:例如 2025-02 至 2025-08
+      // 近半年周期(如:2025-02 至 2025-07)
       const sixMonthsAgo = new Date(now.getFullYear(), now.getMonth() - 5, 1)
       this.sixMonthsRange = `${formatDate(sixMonthsAgo, 'yyyy-MM')} 至 ${formatDate(now, 'yyyy-MM')}`
     },
 
-    // 渲染近三年下月故障部件图表
+    // 渲染所有图表
+    renderAllCharts() {
+      this.renderNextMonthChart()
+      this.renderSixMonthsChart()
+      this.renderMergedChart()
+    },
+
+    // 渲染近三年下月图表
     renderNextMonthChart() {
-      const parts = this.nextMonthData.map(item => item.partName)
-      const counts = this.nextMonthData.map(item => item.count)
+      const { parts, counts } = this.extractChartData(this.nextMonthData)
+      console.log('近三年下月图表数据:', parts, counts); // 检查是否有值
+      this.nextMonthChartIns.setOption(this.getBarChartOption(parts, counts, '故障次数'))
+    },
 
-      this.nextMonthChartIns.setOption({
+    // 渲染近半年图表
+    renderSixMonthsChart() {
+      const { parts, counts } = this.extractChartData(this.sixMonthsData)
+      console.log('渲染近半年图表:', parts, counts); // 检查是否有值
+      this.sixMonthsChartIns.setOption(this.getBarChartOption(parts, counts, '故障次数'))
+    },
+
+    // 渲染合并数据图表
+    renderMergedChart() {
+      const { parts, counts } = this.extractChartData(this.mergedData)
+      console.log('渲染合并数据图表:', parts, counts); // 检查是否有值
+      this.mergedChartIns.setOption(this.getBarChartOption(parts, counts, '总故障次数', 'horizontal'))
+    },
+
+    // 提取图表数据(部件名和数量)
+    extractChartData(data) {
+      return {
+        parts: data.map(item => item.partName),
+        counts: data.map(item => item.count)
+      }
+    },
+
+    // 柱状图通用配置
+    getBarChartOption(xData, yData, seriesName, layout = 'vertical') {
+      const isHorizontal = layout === 'horizontal'
+      const themeColor = {
+        start: '#409EFF',
+        end: '#67C23A'
+      }
+
+      return {
         tooltip: {
           trigger: 'axis',
           axisPointer: { type: 'shadow' },
-          formatter: '{b}: {c} 次' // 提示框格式:部件名: 次数
-        },
-        grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
-        xAxis: {
-          type: 'value',
-          name: '故障次数',
-          axisLabel: { formatter: '{value}' }
-        },
-        yAxis: {
-          type: 'category',
-          data: parts,
-          axisLabel: {
-            // 部件名过长时自动换行
-            interval: 0,
-            formatter: function(value) {
-              return value.length > 6 ? value.substring(0, 6) + '...' : value
-            }
-          }
+          backgroundColor: 'rgba(255, 255, 255, 0.9)',
+          borderColor: '#eee',
+          borderWidth: 1,
+          textStyle: { color: '#333' },
+          formatter: '{b}: {c} 次'
         },
-        series: [{
-          name: '故障次数',
-          type: 'bar',
-          data: counts,
-          itemStyle: {
-            // 柱状图颜色渐变
-            color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
-              { offset: 0, color: '#409EFF' },
-              { offset: 1, color: '#67C23A' }
-            ])
+        grid: { left: '3%', right: '4%', bottom: '8%', top: '5%', containLabel: true },
+        xAxis: isHorizontal
+          ? {
+            type: 'value',
+            name: seriesName,
+            axisLabel: { formatter: '{value}' },
+            splitLine: { lineStyle: { type: 'dashed', color: '#f0f0f0' } }
+          } // <-- 这里补充了缺少的右括号
+          : {
+            type: 'category',
+            data: xData,
+            axisLabel: { interval: 0, rotate: 30 },
+            axisLine: { lineStyle: { color: '#ddd' } }
           },
-          label: {
-            show: true,
-            position: 'right',
-            formatter: '{c} 次'
+        yAxis: isHorizontal
+          ? {
+            type: 'category',
+            data: xData,
+            axisLabel: { interval: 0 },
+            axisLine: { lineStyle: { color: '#ddd' } }
           }
-        }]
-      })
-    },
-
-    // 渲染近半年故障部件图表
-    renderSixMonthsChart() {
-      const parts = this.sixMonthsData.map(item => item.partName)
-      const counts = this.sixMonthsData.map(item => item.count)
-
-      this.sixMonthsChartIns.setOption({
-        tooltip: {
-          trigger: 'item',
-          formatter: '{a} <br/>{b}: {c} 次 ({d}%)'
-        },
-        legend: {
-          orient: 'vertical',
-          left: 10,
-          data: parts
-        },
+          : {
+            type: 'value',
+            name: seriesName,
+            axisLabel: { formatter: '{value}' },
+            splitLine: { lineStyle: { type: 'dashed', color: '#f0f0f0' } }
+          },
         series: [{
-          name: '故障部件',
-          type: 'pie',
-          radius: ['40%', '70%'], // 环形图
-          avoidLabelOverlap: false,
+          name: seriesName,
+          type: 'bar',
+          data: yData,
+          barWidth: isHorizontal ? '60%' : '40%',
           itemStyle: {
-            borderRadius: 4,
-            borderColor: '#fff',
-            borderWidth: 2
+            color: new echarts.graphic.LinearGradient(
+              isHorizontal ? 0 : 0,
+              0,
+              isHorizontal ? 1 : 0,
+              1,
+              [
+                { offset: 0, color: themeColor.start },
+                { offset: 1, color: themeColor.end }
+              ]
+            ),
+            borderRadius: 4
           },
           label: {
             show: false,
-            position: 'center'
-          },
-          emphasis: {
-            label: {
-              show: true,
-              fontSize: '16',
-              fontWeight: 'bold'
-            }
-          },
-          labelLine: {
-            show: false
-          },
-          data: this.sixMonthsData.map((item, index) => ({
-            name: parts[index],
-            value: item.count
-          }))
+            position: isHorizontal ? 'right' : 'top',
+            textStyle: { fontWeight: 'bold' }
+          }
         }]
-      })
+      }
     },
 
-    // 窗口大小变化时重绘图表
-    handleResize() {
-      this.nextMonthChartIns.resize()
-      this.sixMonthsChartIns.resize()
+      // 窗口resize时重绘图表
+      handleResize()
+      {
+        this.nextMonthChartIns?.resize()
+        this.sixMonthsChartIns?.resize()
+        this.mergedChartIns?.resize()
+      }
     }
   }
-}
 </script>
 
-<style scoped>
+<style scoped lang="scss">
+// 变量定义
+$primary-color: #409EFF;
+$text-primary: #333;
+$text-secondary: #666;
+$text-light: #999;
+$bg-card: #fff;
+$border-radius: 6px;
+$spacing-sm: 8px;
+$spacing-md: 16px;
+$spacing-lg: 20px;
+
 .fault-statistics-board {
-  padding: 20px;
+  padding: $spacing-lg;
   background-color: #f5f7fa;
   min-height: 100%;
+  box-sizing: border-box;
 }
 
+// 标题区域
 .board-header {
   display: flex;
   justify-content: space-between;
-  align-items: center;
-  margin-bottom: 20px;
+  align-items: flex-start;
+  margin-bottom: $spacing-lg;
+  padding-bottom: $spacing-md;
+  border-bottom: 1px solid #eee;
+
+  .header-content {
+    h2 {
+      margin: 0 0 $spacing-sm 0;
+      color: $text-primary;
+      font-size: 1.5rem;
+    }
+
+    .time-desc {
+      margin: 0;
+      color: $text-secondary;
+      font-size: 0.9rem;
+
+      .update-time {
+        color: $primary-color;
+        font-weight: 500;
+      }
+    }
+  }
 }
 
+// 数据卡片区域
 .statistic-cards {
   display: flex;
-  gap: 20px;
-  margin-bottom: 20px;
+  gap: $spacing-lg;
+  margin-bottom: $spacing-lg;
   flex-wrap: wrap;
-}
 
-.stat-card {
-  flex: 1;
-  min-width: 280px;
-}
+  .stat-card {
+    flex: 1;
+    min-width: 280px;
+    background-color: $bg-card;
+    border-radius: $border-radius;
+    transition: all 0.3s ease;
+
+    &:hover {
+      transform: translateY(-3px);
+      box-shadow: 0 6px 12px rgba(0, 0, 0, 0.05);
+    }
 
-.stat-value {
-  font-size: 28px;
-  font-weight: bold;
-  color: #1890ff;
-  margin: 15px 0;
-}
+    .card-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      font-weight: 500;
+
+      .info-icon {
+        color: $text-light;
+        cursor: help;
+        font-size: 14px;
+      }
+    }
+
+    .stat-value {
+      font-size: 2rem;
+      font-weight: bold;
+      color: $primary-color;
+      margin: $spacing-lg 0;
+      text-align: center;
+    }
 
-.stat-desc {
-  color: #666;
-  font-size: 14px;
+    .stat-footer {
+      text-align: right;
+      padding: 0 $spacing-md $spacing-md;
+
+      .range-text {
+        color: $text-light;
+        font-size: 0.85rem;
+      }
+    }
+  }
 }
 
+// 图表区域
 .charts-container {
   display: flex;
-  gap: 20px;
-  flex-wrap: wrap;
+  flex-direction: column;
+  gap: $spacing-lg;
+  width: 100%;
+  box-sizing: border-box;
 }
 
-.chart-card {
-  flex: 1;
-  min-width: 450px;
-  height: 450px;
+.charts-row {
+  display: flex;
+  gap: $spacing-lg;
+  width: 100%;
+  height: 400px; // 固定第一行高度
 }
 
-.chart-wrapper {
-  width: 100%;
-  height: calc(100% - 44px); /* 减去卡片头部高度 */
+.chart-card {
+  background-color: $bg-card;
+  border-radius: $border-radius;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+
+  &.first-two-charts {
+    width: 50%;
+  }
+
+  &.merged-chart {
+    width: 100%;
+    height: 500px; // 合并图表更高一些
+  }
+
+  .chart-wrapper {
+    width: 100%;
+    flex: 1;
+    position: relative; // 用于空状态绝对定位
+
+    .chart-dom {
+      width: 100%;
+      height: 100%; /* 保留百分比的同时,强制最小高度 */
+      min-height: 300px; /* 新增:确保至少有高度 */
+    }
+
+    .empty-state {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      align-items: center;
+      color: $text-light;
+
+      .empty-icon {
+        font-size: 2rem;
+        margin-bottom: $spacing-sm;
+      }
+    }
+  }
 }
 
-.chart-dom {
-  width: 100%;
-  height: 100%;
+// 响应式调整
+@media (max-width: 1200px) {
+  .chart-card {
+    &.merged-chart {
+      height: 450px;
+    }
+  }
 }
 
-/* 响应式调整 */
 @media (max-width: 992px) {
-  .charts-container {
-    flex-direction: column;
+  .charts-row {
+    height: 350px;
   }
+
   .chart-card {
-    height: 400px;
+    &.merged-chart {
+      height: 400px;
+    }
   }
 }
 
-@media (max-width: 576px) {
+@media (max-width: 768px) {
+  .fault-statistics-board {
+    padding: $spacing-md;
+  }
+
+  .charts-row {
+    flex-direction: column;
+    height: auto;
+    gap: $spacing-md;
+  }
+
   .chart-card {
-    min-width: 100%;
-    height: 350px;
+    &.first-two-charts {
+      width: 100%;
+      height: 350px;
+    }
+
+    &.merged-chart {
+      height: 380px;
+    }
   }
+
   .statistic-cards {
-    flex-direction: column;
+    gap: $spacing-md;
   }
+
   .stat-card {
     min-width: 100%;
   }

+ 28 - 9
gm-web/src/views/index.vue

@@ -3,42 +3,61 @@
     <el-tabs type="border-card" v-model="activeName" @tab-click="handleTabClick">
       <el-tab-pane label="智能问答" name="chat">
         <div class="tab-content_app">
-          <ChatComponent :autoLoadWarning="true" />
+          <ChatComponent :autoLoadWarning="true"  v-if="activeName === 'chat'"/>
         </div>
       </el-tab-pane>
       <el-tab-pane label="智能AI" name="chatAI">
         <div class="tab-content_app">
-         <cat-ai/>
+          <keep-alive>
+            <cat-ai v-if="activeName === 'chatAI'"/>
+          </keep-alive>
         </div>
       </el-tab-pane>
-      <el-tab-pane label="文件管理" name="statistics">
+
+      <el-tab-pane label="知识抽取" name="settings">
         <div class="tab-content_app">
-        <file-view></file-view>
+          <keep-alive>
+            <KnowledgeExtraction v-if="activeName === 'settings'"/>
+          </keep-alive>
         </div>
       </el-tab-pane>
-      <el-tab-pane label="知识抽取" name="settings">
+      <el-tab-pane label="故障统计" name="faultMonitor">
         <div class="tab-content_app">
-          <KnowledgeExtraction/>
+          <keep-alive>
+            <fault-statistics-board v-if="activeName === 'faultMonitor'"/>
+          </keep-alive>
         </div>
       </el-tab-pane>
-      <el-tab-pane label="故障统计" name="faultMonitor">
+      <el-tab-pane label="实体管理" name="ERManage">
         <div class="tab-content_app">
-         <fault-statistics-board/>
+          <keep-alive>
+            <ERManage v-if="activeName === 'ERManage'"/>
+          </keep-alive>
+        </div>
+      </el-tab-pane>
+      <el-tab-pane label="实体关系管理" name="entity-manage">
+        <div class="tab-content_app">
+          <keep-alive>
+            <entity-manage v-if="activeName === 'entity-manage'"/>
+          </keep-alive>
         </div>
       </el-tab-pane>
     </el-tabs>
   </div>
 </template>
-
 <script>
 import ChatComponent from './ChatComponent.vue';
 import FileView from '@/views/file'
 import KnowledgeExtraction from '@/views/knowledgeExtraction/index.vue'
 import CatAi from '@/views/chat/catAi.vue'
 import FaultStatisticsBoard from '@/views/FaultStatisticsBoard.vue'
+import ERManage from '@/views/knowledgeManage/ERManage/index.vue'
+import entityManage from '@/views/knowledgeManage/entityManage/index.vue'
 export default {
   name: 'MainPage',
   components: {
+    ERManage,
+    entityManage,
     ChatComponent,
     FileView,
     KnowledgeExtraction,

+ 7 - 6
gm-web/src/views/knowledgeExtraction/index.vue

@@ -91,7 +91,7 @@
         <el-dialog width="80%" title="抽取详情" :visible.sync="extractDialogVisible" :before-close="extractHandleClose" append-to-body>
           <div class="content">
             <div class="left">
-              <p>{{ extractData.content }}</p>
+              <p>{{ this.extractData.content }}</p>
             </div>
             <div class="right">
               <LTable
@@ -350,7 +350,7 @@ export default {
               round: false,
               plain: false,
               onClick: (row, index, scope) => {
-                // this.getExtractListAPI({ clauseId: row.id })
+                this.getExtractListAPI({ clauseId: row.id })
                 this.checkExtractResult(row)
               }
             }
@@ -522,8 +522,8 @@ export default {
     init() {
       this.getTaskListAPI()
       this.getAtlasFileAPI()
-      this.getEntityClassAPI()
-      this.getAllRelationClassAPI()
+      // this.getEntityClassAPI()
+      // this.getAllRelationClassAPI()
 
     },
     infoOptions(){
@@ -618,7 +618,7 @@ export default {
       const { keyWord } = this
       const { pageSize, pageIndex } = this.extractDetailRequest
       await getExtractList({ pageSize, pageNum: pageIndex, ...params }).then(res=>{
-        this.extractData.extractDetailTable = res.rows;
+        this.extractData.extractDetailTable = res.data.records;
         this.extractDetailRequest.total = res.total
       })
     },
@@ -713,9 +713,10 @@ export default {
 
     // 查看抽取结果
     checkExtractResult(row) {
-      this.extractData.content = row.clause
       this.clauseForm = deepClone(row)
       this.getExtractListAPI({ clauseId: row.id })
+      this.extractData.content = row.clause
+      console.log('this.extractData.content',this.extractData.content)
       this.extractDialogVisible = true
     },
 

+ 387 - 0
gm-web/src/views/knowledgeManage/ERManage/index.vue

@@ -0,0 +1,387 @@
+<template>
+  <div class="view-table-content">
+    <div style="width: 100%">
+      <div class="view-dataType-title">
+        <div class="view-dataType-title-btn">
+          <el-button type="success" @click="openDialog()">新增</el-button>
+          <el-button type="warning" @click="reset()">重置</el-button>
+        </div>
+        <div class="view-dataType-title-search">
+          <el-input placeholder="请输入主体/客体名称" v-model="keyWordData" class="input1">
+            <el-button slot="append" icon="el-icon-search" @click="searchClick"></el-button>
+          </el-input>
+        </div>
+      </div>
+      <div class="view-dataType-table">
+        <LTable ref="table" @selection-change="selection" :defaultFetch="false" :fetch="fetch" :columns="columns" :dataSource="tableData" :options="options" :pagination="tableRequset"></LTable>
+      </div>
+      <!-- 添加或修改模型信息对话框 -->
+      <el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="600px" :before-close="handleClose">
+        <el-form ref="form" :model="form" label-width="80px" :rules="rules">
+          <el-form-item label="主体名称" prop="subjectName">
+            <!-- <el-input v-model="form.subjectName" placeholder="请输入主体名称" :disabled="dialogTitle === '编辑'" /> -->
+            <el-select v-model="form.subjectId" placeholder="请选择主体" @change="handleSelectSubject">
+              <el-option v-for="item in entityList" :key="item.id" :label="item.name" :value="item.id" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="主体类型" prop="subjectCategory">
+            <el-input disabled v-model="form.subjectCategory" placeholder="主体类型" />
+          </el-form-item>
+
+          <el-form-item label="客体名称" prop="objectId">
+            <el-select v-model="form.objectId" placeholder="请选择客体" @change="handleSelectObject">
+              <el-option v-for="item in entityList" :key="item.id" :label="item.name" :value="item.id" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="客体类型" prop="objectCategory">
+            <el-input disabled v-model="form.objectCategory" placeholder="客体类型" />
+          </el-form-item>
+          <el-form-item label="关系" prop="relationName">
+            <el-select v-model="form.relationName" placeholder="请选择关系">
+              <el-option v-for="(item, index) in relationList" :key="index" :label="item.name" :value="item.name" />
+            </el-select>
+            <!-- <el-input v-model="form.relationName" placeholder="请输入实体名称" /> -->
+          </el-form-item>
+        </el-form>
+
+        <span slot="footer" class="dialog-footer">
+          <el-button @click="handleClose">取 消</el-button>
+          <el-button type="primary" @click="submit">确 定</el-button>
+        </span>
+      </el-dialog>
+    </div>
+  </div>
+</template>
+
+<script>
+import { getERList, getAllRelationClass, addRelation, updateRelation, deleteRelation } from '@/api/als/ERManage'
+import { getEntityList } from '@/api/als/entityManage'
+import { deepClone, debounce } from '@/utils'
+import LTable from '@/views/components/LTable/index.vue'
+
+export default {
+  name: 'ERManage',
+  components: { LTable },
+  data() {
+    // 这里存放数据
+    return {
+      dialogTitle: '新增',
+      dialogVisible: false,
+      keyWordData: '',
+      searchValue: '',
+      rules: {
+        subjectName: [{ required: true, message: '主体名称不能为空', trigger: 'blur' }],
+        subjectCategory: [{ required: true, message: '主体类型不能为空', trigger: 'blur' }],
+        objectName: [{ required: true, message: '客体名称不能为空', trigger: 'blur' }],
+        objectCategory: [{ required: true, message: '客体类型不能为空', trigger: 'blur' }],
+        relationName: [{ required: true, message: '关系不能为空', trigger: 'change' }]
+      },
+      columns: [
+        {
+          prop: 'subjectName',
+          label: '主体名称'
+        },
+        {
+          prop: 'subjectCategory',
+          label: '主体类型'
+        },
+        {
+          prop: 'objectName',
+          label: '客体名称'
+        },
+        {
+          prop: 'objectCategory',
+          label: '客体类型'
+        },
+        {
+          prop: 'relationName',
+          label: '关系'
+        },
+        {
+          button: true,
+          label: '操作',
+          width: '240px',
+          group: [
+            {
+              name: '编辑',
+              type: 'text',
+              round: false,
+              plain: false,
+              onClick: (row, index, scope) => {
+                this.handUpdate(row)
+              }
+            },
+            {
+              name: '删除',
+              type: 'text',
+              round: false,
+              plain: false,
+              onClick: (row, index, scope) => {
+                this.remove([row])
+              }
+            }
+          ]
+        }
+      ],
+      options: {
+        stripe: false, // 斑马纹
+        mutiSelect: true, // 多选框
+        index: false, // 显示序号, 多选则 mutiSelect
+        loading: false, // 表格动画
+        initTable: false, // 是否一挂载就加载数据
+        border: true,
+        height: 'calc(100vh - 300px)'
+      },
+      tableCheckItems: [],
+      tableData: [],
+      tableRequset: {
+        total: 0,
+        pageIndex: 1,
+        pageSize: 20,
+        searchValue: ''
+      },
+      form: {
+        subjectId: '',
+        subjectName: '',
+        subjectCategory: '',
+        objectId: '',
+        objectName: '',
+        objectCategory: '',
+        relationId: '',
+        relationName: ''
+      },
+      debounceFn: debounce(this.fetch, 500),
+      relationList: [],
+      entityList: [
+        { id: 6, name: 'A通道右鸭翼指令输出指令故障', category: '故障描述' },
+        { id: 6363, name: '更换对应报故ISACF', category: '维修策略' }
+      ],
+      loading: null
+    }
+  },
+  watch: {
+    keyWord() {
+      this.tableRequset.pageIndex = 1
+      this.debounceFn()
+    }
+  },
+  mounted() {
+    this.getInit()
+  },
+  methods: {
+    async getInit() {
+      this.getRelationAPI()
+      this.getAllRelationClassAPI()
+    },
+
+    async deleteRelationAPI(row) {
+      try {
+        const deleteData = {
+          node_id_heard: row.subjectId,
+          node_id_last: row.objectId,
+          relation_name: row.relationName
+        }
+        const { code } = await deleteRelation(deleteData)
+        if (code === 200) {
+          this.$message({
+            type: 'success',
+            message: '操作成功!'
+          })
+          this.getRelationAPI()
+          this.handleClose()
+        }
+      } catch (error) {}
+    },
+    reset() {
+      this.getRelationAPI()
+      this.keyWordData = ''
+    },
+    async getRelationAPI(params) {
+      try {
+        if (this.$refs.table) this.$refs.table.clearSelection()
+        const { pageSize, pageIndex } = this.tableRequset
+        this.loading = this.$loading({
+          lock: true,
+          text: '正在获取知识图谱实体关系数据,请稍候...',
+          spinner: 'el-icon-loading'
+        })
+        const {
+          data: { dataList, total }
+        } = await getERList({ pageSize, pageNum: pageIndex, ...params })
+        this.tableData = dataList
+        this.tableRequset.total = total
+        this.loading.close()
+      } catch (error) {
+        this.loading.close()
+      }
+    },
+
+    handleERData(data) {
+      const tableData = []
+      data.forEach((item) => {
+        let subData = {},
+          obData = {},
+          reData = {}
+        for (const key in item) {
+          if (key === 'subject') {
+            subData.subjectId = item.subject.id
+            subData.subjectName = item.subject.name
+            subData.subjectCategory = item.subject.category
+          } else if (key === 'object') {
+            obData.objectId = item.object.id
+            obData.objectName = item.object.name
+            obData.objectCategory = item.object.category
+          } else if (key === 'relation') {
+            reData.relationId = item.relation.id
+            reData.relationName = item.relation.name
+          }
+        }
+        tableData.push({ ...subData, ...obData, ...reData })
+      })
+      return tableData
+    },
+
+    handleSelectSubject(val) {
+      const data = this.entityList.find((item) => {
+        return item.id == val
+      })
+      this.form.subjectCategory = data.category
+    },
+
+    handleSelectObject(val) {
+      const data = this.entityList.find((item) => {
+        return item.id == val
+      })
+      this.form.objectCategory = data.category
+    },
+
+    async getAllRelationClassAPI(params) {
+      try {
+        const {
+          data: { dataList }
+        } = await getAllRelationClass()
+        this.relationList = dataList
+        this.loading.close()
+      } catch (error) {}
+    },
+
+    async getEntityListAPI() {
+      try {
+        const {
+          data: { dataList }
+        } = await getEntityList()
+        this.entityList = dataList
+      } catch (error) {}
+    },
+
+    fetch() {
+      this.getRelationAPI()
+    },
+
+    async searchClick() {
+      try {
+        if (this.$refs.table) this.$refs.table.clearSelection()
+        const {
+          data: { dataList, total }
+        } = await checkRelation({ node_name: this.keyWordData })
+        this.tableData = dataList
+        this.tableRequset.total = total
+      } catch (error) {}
+    },
+
+    async addRelationAPI() {
+      // try {
+      const addData = {
+        head_node_id: this.form.subjectId,
+        tail_node_id: this.form.objectId,
+        new_relation_name: this.form.relationName
+      }
+      // const { code } = await addRelation(addData)
+      // if (code === 200) {
+      this.$message({
+        type: 'success',
+        message: '操作成功!'
+      })
+      this.handleClose()
+      this.getRelationAPI()
+      // }
+      // } catch (error) {}
+    },
+
+    async updateRelationAPI() {
+      try {
+        const updateData = {
+          head_node_id: this.form.subjectId,
+          tail_node_id: this.form.objectId,
+          new_relation_name: this.form.relationName
+        }
+        const { code } = await updateRelation(updateData)
+        if (code === 200) {
+          this.$message({
+            type: 'success',
+            message: '操作成功!'
+          })
+          this.handleClose()
+          this.getRelationAPI()
+        }
+      } catch (error) {}
+    },
+
+    openDialog() {
+      this.dialogTitle = '新增'
+      this.dialogVisible = true
+    },
+
+    handleClose() {
+      this.dialogVisible = false
+      this.form = {
+        subjectId: '',
+        subjectName: '',
+        subjectCategory: '',
+        objectId: '',
+        objectName: '',
+        objectCategory: '',
+        relationId: '',
+        relationName: ''
+      }
+    },
+
+    handUpdate(row) {
+      this.dialogTitle = '编辑'
+      this.form = deepClone(row)
+      this.dialogVisible = true
+    },
+
+    submit() {
+      switch (this.dialogTitle) {
+        case '编辑':
+          this.updateRelationAPI()
+          break
+        case '新增':
+          this.addRelationAPI()
+          break
+      }
+    },
+
+    selection(val) {
+      this.tableCheckItems = val
+    },
+
+    remove(row) {
+      this.$confirm('是否删除该数据', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      })
+        .then(() => {
+          // this.deleteRelationAPI(row.map((e) => e.id))
+          this.deleteRelationAPI(row[0])
+        })
+        .catch(() => {})
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+</style>

+ 288 - 0
gm-web/src/views/knowledgeManage/entityManage/index.vue

@@ -0,0 +1,288 @@
+<template>
+  <div class="view-table-content">
+    <div style="width: 100%">
+      <div class="view-dataType-title">
+        <div class="view-dataType-title-btn">
+          <el-button type="success" @click="openDialog()">新增</el-button>
+          <el-button type="warning" @click="reset()">重置</el-button>
+        </div>
+        <div class="view-dataType-title-search">
+          <el-input placeholder="请输入实体名称" v-model="keyWordData" class="input1">
+            <el-button slot="append" icon="el-icon-search" @click="searchClick"></el-button>
+          </el-input>
+        </div>
+      </div>
+      <div class="view-dataType-table">
+        <LTable ref="table" @selection-change="selection" :defaultFetch="false" :fetch="fetch" :columns="columns" :dataSource="tableData" :options="options" :pagination="tableRequset"></LTable>
+      </div>
+      <!-- 添加或修改模型信息对话框 -->
+      <el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="600px" :before-close="handleClose" :rules="rules">
+        <el-form ref="form" :model="form" label-width="80px">
+          <el-form-item label="实体名称" prop="name">
+            <el-input v-model="form.name" placeholder="请输入实体名称" />
+          </el-form-item>
+          <el-form-item label="实体类型" prop="category">
+            <el-select v-model="form.category" placeholder="请选择实体类型">
+              <el-option v-for="(item, index) in categoryList" :key="index" :label="item.name" :value="item.name" />
+            </el-select>
+          </el-form-item>
+        </el-form>
+
+        <span slot="footer" class="dialog-footer">
+          <el-button @click="handleClose">取 消</el-button>
+          <el-button type="primary" @click="submit">确 定</el-button>
+        </span>
+      </el-dialog>
+    </div>
+  </div>
+</template>
+
+<script>
+import { getEntityList, getAllEntityClass, addEntity, updateEntity, deleteEntity, checkEntity } from '@/api/als/entityManage'
+import { deepClone, debounce } from '@/utils'
+import LTable from '@/views/components/LTable/index.vue'
+
+export default {
+  name: 'EntityManage',
+  components: { LTable },
+  data() {
+    // 这里存放数据
+    return {
+      dialogTitle: '新增',
+      dialogVisible: false,
+      keyWordData: '',
+      searchValue: '',
+      rules: {
+        name: [{ required: true, message: '实体名称不能为空', trigger: 'blur' }],
+        category: [{ required: true, message: '实体类型不能为空', trigger: 'change' }]
+      },
+      columns: [
+        {
+          prop: 'name',
+          label: '实体名称'
+        },
+        {
+          prop: 'category',
+          label: '实体类型'
+        },
+        {
+          button: true,
+          label: '操作',
+          width: '240px',
+          group: [
+            {
+              name: '编辑',
+              type: 'text',
+              round: false,
+              plain: false,
+              onClick: (row, index, scope) => {
+                this.handUpdate(row)
+              }
+            },
+            {
+              name: '删除',
+              type: 'text',
+              round: false,
+              plain: false,
+              onClick: (row, index, scope) => {
+                this.remove([row])
+              }
+            }
+          ]
+        }
+      ],
+      options: {
+        stripe: false, // 斑马纹
+        mutiSelect: true, // 多选框
+        index: false, // 显示序号, 多选则 mutiSelect
+        loading: false, // 表格动画
+        initTable: false, // 是否一挂载就加载数据
+        border: true,
+        height: 'calc(100vh - 300px)'
+      },
+      tableCheckItems: [],
+      tableData: [],
+      tableRequset: {
+        total: 0,
+        pageIndex: 1,
+        pageSize: 20,
+        searchValue: ''
+      },
+      form: {
+        id: '',
+        name: '',
+        category: ''
+      },
+      debounceFn: debounce(this.fetch, 500),
+      categoryList: []
+    }
+  },
+  watch: {
+    keyWord() {
+      this.tableRequset.pageIndex = 1
+      this.debounceFn()
+    }
+  },
+  mounted() {
+    this.getEntityAPI()
+    this.getEntityClassAPI()
+  },
+  methods: {
+    async deleteEntityAPI(params) {
+      try {
+        const deleteData = {
+          node_id: params[0]
+        }
+        const { code } = await deleteEntity(deleteData)
+        if (code === 200) {
+          this.$message({
+            type: 'success',
+            message: '操作成功!'
+          })
+          this.getEntityAPI()
+          this.handleClose()
+        }
+      } catch (error) {}
+    },
+    reset() {
+      this.getEntityAPI()
+      this.keyWordData = ''
+    },
+    async getEntityAPI(params) {
+      try {
+        if (this.$refs.table) this.$refs.table.clearSelection()
+        const { pageSize, pageIndex } = this.tableRequset
+        this.loading = this.$loading({
+          lock: true,
+          text: '正在获取知识图谱实体数据,请稍候...',
+          spinner: 'el-icon-loading'
+        })
+        const {
+          data: { dataList, total }
+        } = await getEntityList({ pageSize, pageNum: pageIndex, ...params })
+        this.tableData = dataList
+        this.tableRequset.total = total
+        this.loading.close()
+      } catch (error) {
+        this.loading.close()
+      }
+    },
+
+    async getEntityClassAPI(params) {
+      try {
+        const {
+          data: { dataList }
+        } = await getAllEntityClass()
+        this.categoryList = dataList
+        this.loading.close()
+      } catch (error) {}
+    },
+
+    fetch() {
+      this.getEntityAPI()
+    },
+
+    async searchClick() {
+      try {
+        if (this.$refs.table) this.$refs.table.clearSelection()
+        const {
+          data: { dataList, total }
+        } = await checkEntity({ node_name: this.keyWordData })
+        this.tableData = dataList
+        this.tableRequset.total = total
+      } catch (error) {}
+    },
+
+    async addEntityAPI() {
+      try {
+        const addData = {
+          node_name: this.form.name,
+          node_label: this.form.category
+        }
+        const { code } = await addEntity(addData)
+        if (code === 200) {
+          this.$message({
+            type: 'success',
+            message: '操作成功!'
+          })
+          this.handleClose()
+          this.getEntityAPI()
+        }
+      } catch (error) {}
+    },
+
+    async updateEntityAPI() {
+      try {
+        const updateData = {
+          node_id: this.form.id,
+          new_node_name: this.form.name,
+          new_node_label: this.form.category
+        }
+        const { code } = await updateEntity(updateData)
+        if (code === 200) {
+          this.$message({
+            type: 'success',
+            message: '操作成功!'
+          })
+          this.handleClose()
+          this.getEntityAPI()
+        }
+      } catch (error) {}
+    },
+
+    openDialog() {
+      this.dialogTitle = '新增'
+      this.dialogVisible = true
+    },
+
+    handleClose() {
+      this.dialogVisible = false
+      this.form = {
+        id: '',
+        name: '',
+        category: ''
+      }
+    },
+
+    handUpdate(row) {
+      this.dialogTitle = '编辑'
+      this.form = deepClone(row)
+      this.dialogVisible = true
+    },
+
+    submit() {
+      this.$refs.form.validate((valid) => {
+        if (valid) {
+          switch (this.dialogTitle) {
+            case '编辑':
+              this.updateEntityAPI()
+              break
+            case '新增':
+              this.addEntityAPI()
+              break
+          }
+        }
+      })
+    },
+
+    selection(val) {
+      this.tableCheckItems = val
+    },
+
+    remove(row) {
+      this.$confirm('是否删除该数据', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      })
+        .then(() => {
+          this.deleteEntityAPI(row.map((e) => e.id))
+        })
+        .catch(() => {})
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+</style>

+ 17 - 0
gm-web/src/views/knowledgeManage/index.vue

@@ -0,0 +1,17 @@
+<template>
+  <div class="view-knowledgeManage"></div>
+</template>
+
+<script>
+export default {
+  name: 'KnowledgeManage',
+  data() {
+    return {}
+  },
+  watch: {},
+  created() {},
+  methods: {}
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 25 - 0
gm-web/vue.config.js

@@ -41,6 +41,31 @@ module.exports = {
         pathRewrite: {
           ['^' + process.env.VUE_APP_BASE_API]: ''
         }
+      },
+      ['/api/kgqa']: {
+        // target: 'http://192.168.0.107:7073',
+        target: 'http://192.168.2.120:7073',
+        ws: false,
+        changeOrigin: true,
+        pathRewrite: {
+          ['^' + '/api/kgqa']: '/kgqa'
+        }
+      },
+      ['/api/knowledge_base']: {
+        target: 'http://192.168.2.120:7861',
+        ws: false,
+        changeOrigin: true,
+        pathRewrite: {
+          ['^' + '/api/knowledge_base']: '/knowledge_base'
+        }
+      },
+      [process.env.VUE_APP_BASE_API]: {
+        target: process.env.VUE_APP_BASE_API_target,
+        ws: false, //也可以忽略不写,不写不会影响跨域
+        changeOrigin: true, //是否开启跨域,值为 true 就是开启, false 不开启
+        pathRewrite: {
+          ['^' + process.env.VUE_APP_BASE_API]: ''
+        }
       }
     },
     disableHostCheck: true