Browse Source

//功能验证

wyj0522 3 weeks ago
parent
commit
5447955bf2
28 changed files with 689 additions and 272 deletions
  1. 2 2
      fdapfe-admin/src/main/java/com/cn/fdapfe/biz/controller/FaultPhysicalModelController.java
  2. 1 1
      fdapfe-admin/src/main/java/com/cn/fdapfe/biz/controller/FdAlgorithmController.java
  3. 3 3
      fdapfe-admin/src/main/java/com/cn/fdapfe/biz/controller/ModelDataGenController.java
  4. 3 0
      fdapfe-admin/src/main/java/com/cn/fdapfe/biz/domain/FaultPhysicalModel.java
  5. 15 7
      fdapfe-admin/src/main/java/com/cn/fdapfe/biz/domain/FdAlgorithm.java
  6. 0 46
      fdapfe-admin/src/main/java/com/cn/fdapfe/biz/mapper/FdAlgorithmMapper.java
  7. 1 1
      fdapfe-admin/src/main/java/com/cn/fdapfe/biz/service/IFaultPhysicalModelService.java
  8. 1 1
      fdapfe-admin/src/main/java/com/cn/fdapfe/biz/service/IFdAlgorithmService.java
  9. 6 2
      fdapfe-admin/src/main/java/com/cn/fdapfe/biz/service/impl/FaultPhysicalModelServiceImpl.java
  10. 54 58
      fdapfe-admin/src/main/java/com/cn/fdapfe/biz/service/impl/FdAlgorithmServiceImpl.java
  11. 3 11
      fdapfe-admin/src/main/java/com/cn/fdapfe/biz/service/impl/ModelDataGenServiceImpl.java
  12. 1 0
      fdapfe-admin/src/main/java/com/cn/fdapfe/web/controller/common/CommonController.java
  13. 2 1
      fdapfe-admin/src/main/resources/mapper/model/FaultPhysicalModelMapper.xml
  14. 15 3
      fdapfe-admin/src/main/resources/mapper/test/FdAlgorithmMapper.xml
  15. 58 5
      fdapfe-common/src/main/java/com/cn/fdapfe/common/utils/file/FileCopyUtils.java
  16. 4 0
      fdapfe-common/src/main/java/com/cn/fdapfe/common/utils/file/FileUploadUtils.java
  17. 1 1
      fdapfe-common/src/main/java/com/cn/fdapfe/common/utils/file/MimeTypeUtils.java
  18. 2 1
      fdapfe-ui/package.json
  19. 4 3
      fdapfe-ui/src/api/dataGen/phyModel.js
  20. 2 2
      fdapfe-ui/src/api/model/faultPhysical.js
  21. 1 1
      fdapfe-ui/src/components/FileUpload/index.vue
  22. 24 21
      fdapfe-ui/src/views/dataGen/index.vue
  23. 2 2
      fdapfe-ui/src/views/index.vue
  24. 30 5
      fdapfe-ui/src/views/model/faultPhysical/form.vue
  25. 12 1
      fdapfe-ui/src/views/model/faultPhysical/index.vue
  26. 42 8
      fdapfe-ui/src/views/test/ddAlgorithm/form.vue
  27. 72 86
      fdapfe-ui/src/views/test/ddAlgorithm/index.vue
  28. 328 0
      fdapfe-ui/src/views/test/ddAlgorithm/info.vue

+ 2 - 2
fdapfe-admin/src/main/java/com/cn/fdapfe/biz/controller/FaultPhysicalModelController.java

@@ -110,8 +110,8 @@ public class FaultPhysicalModelController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('model:faultPhysical:list')")
     @GetMapping("/getOptions")
-    public AjaxResult getOptions(String typs)
+    public AjaxResult getOptions(FaultPhysicalModel model)
     {
-        return success(faultPhysicalModelService.getOptions(typs));
+        return success(faultPhysicalModelService.getOptions(model));
     }
 }

+ 1 - 1
fdapfe-admin/src/main/java/com/cn/fdapfe/biz/controller/FdAlgorithmController.java

@@ -81,7 +81,7 @@ public class FdAlgorithmController extends BaseController
     @PreAuthorize("@ss.hasPermi('test:ddAlgorithm:add')")
     @Log(title = "故障诊断算法功能验证", businessType = BusinessType.INSERT)
     @PostMapping
-    public AjaxResult add(@RequestBody FdAlgorithm fdAlgorithm) throws IOException, InterruptedException {
+    public AjaxResult add(@RequestBody FdAlgorithm fdAlgorithm) throws Exception {
         return toAjax(fdAlgorithmService.insertFdAlgorithm(fdAlgorithm));
     }
 

+ 3 - 3
fdapfe-admin/src/main/java/com/cn/fdapfe/biz/controller/ModelDataGenController.java

@@ -66,10 +66,10 @@ public class ModelDataGenController extends BaseController
      * 获取物理模型数据生成管理详细信息
      */
     @PreAuthorize("@ss.hasPermi('dataGen:query')")
-    @GetMapping(value = "/{id}")
-    public AjaxResult getInfo(@PathVariable("id") String id)
+    @PostMapping(value = "/phyModel")
+    public AjaxResult getInfo(@RequestBody ModelDataGen modelDataGen)
     {
-        return success(service.selectOne(id));
+        return success(service.selectOne(modelDataGen.getDataGenId()));
     }
 
     /**

+ 3 - 0
fdapfe-admin/src/main/java/com/cn/fdapfe/biz/domain/FaultPhysicalModel.java

@@ -47,6 +47,9 @@ public class FaultPhysicalModel extends BasePO
     @Excel(name = "故障归属")
     @TableField(value ="model_attribution")
     private String modelAttribution;
+    @Excel(name = "故障归属")
+    @TableField(value ="model_type_t")
+    private String modelTypet;
 
     @TableField(exist = false)
     private List<ParamsConfig> configData;

+ 15 - 7
fdapfe-admin/src/main/java/com/cn/fdapfe/biz/domain/FdAlgorithm.java

@@ -54,8 +54,8 @@ public class FdAlgorithm extends BasePO
 
     /** 测试标签 */
     @Excel(name = "测试标签")
-    @TableField(value = "test_labels")
-    private String testLabels;
+    @TableField(value = "return_data_image")
+    private String returnDataImage;
 
         /** 开始时间 */
         @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@@ -70,12 +70,20 @@ public class FdAlgorithm extends BasePO
         private Date endTime;
 
 
-    @TableField(exist = false)
-    private String apiPath;
-
-    @TableField(exist = false)
-    private String outputPath;
+//    @TableField(exist = false)
+//    private String apiPath;
+//
+//    @TableField(exist = false)
+//    private String outputPath;
 
     @TableField(exist = false)
     private List<String> verifyData;
+    @TableField(exist = false)
+    private String postApiData;
+    @TableField(exist = false)
+    private String modelType;
+    @TableField(exist = false)
+    private String modelTypet;
+
+
 }

+ 0 - 46
fdapfe-admin/src/main/java/com/cn/fdapfe/biz/mapper/FdAlgorithmMapper.java

@@ -14,53 +14,7 @@ import com.cn.fdapfe.biz.domain.FdAlgorithm;
  */
 public interface FdAlgorithmMapper  extends BaseMapper<FdAlgorithm>
 {
-    /**
-     * 查询故障诊断算法功能验证
-     * 
-     * @param id 故障诊断算法功能验证主键
-     * @return 故障诊断算法功能验证
-     */
-     FdAlgorithm selectFdAlgorithmById(Long id);
 
-    /**
-     * 查询故障诊断算法功能验证列表
-     * 
-     * @param fdAlgorithm 故障诊断算法功能验证
-     * @return 故障诊断算法功能验证集合
-     */
-     List<FdAlgorithm> selectFdAlgorithmList(FdAlgorithm fdAlgorithm);
-
-    /**
-     * 新增故障诊断算法功能验证
-     * 
-     * @param fdAlgorithm 故障诊断算法功能验证
-     * @return 结果
-     */
-     int insertFdAlgorithm(FdAlgorithm fdAlgorithm);
-
-    /**
-     * 修改故障诊断算法功能验证
-     * 
-     * @param fdAlgorithm 故障诊断算法功能验证
-     * @return 结果
-     */
-    int updateFdAlgorithm(FdAlgorithm fdAlgorithm);
-
-    /**
-     * 删除故障诊断算法功能验证
-     * 
-     * @param id 故障诊断算法功能验证主键
-     * @return 结果
-     */
-     int deleteFdAlgorithmById(Long id);
-
-    /**
-     * 批量删除故障诊断算法功能验证
-     * 
-     * @param ids 需要删除的数据主键集合
-     * @return 结果
-     */
-    int deleteFdAlgorithmByIds(Long[] ids);
 
      List<Map<String,Object>> getOptions();
 }

+ 1 - 1
fdapfe-admin/src/main/java/com/cn/fdapfe/biz/service/IFaultPhysicalModelService.java

@@ -54,5 +54,5 @@ public interface IFaultPhysicalModelService
     void deleteFaultPhysicalModelByIds(String[] ids);
 
 
-    List<Map<String,Object>> getOptions(String typs);
+    List<FaultPhysicalModel> getOptions(FaultPhysicalModel model);
 }

+ 1 - 1
fdapfe-admin/src/main/java/com/cn/fdapfe/biz/service/IFdAlgorithmService.java

@@ -36,7 +36,7 @@ public interface IFdAlgorithmService
      * @param fdAlgorithm 故障诊断算法功能验证
      * @return 结果
      */
-     int insertFdAlgorithm(FdAlgorithm fdAlgorithm) throws IOException, InterruptedException;
+     int insertFdAlgorithm(FdAlgorithm fdAlgorithm) throws Exception;
 
     /**
      * 修改故障诊断算法功能验证

+ 6 - 2
fdapfe-admin/src/main/java/com/cn/fdapfe/biz/service/impl/FaultPhysicalModelServiceImpl.java

@@ -146,7 +146,11 @@ public class FaultPhysicalModelServiceImpl implements IFaultPhysicalModelService
     }
 
     @Override
-    public List<Map<String, Object>> getOptions(String typs) {
-        return faultPhysicalModelMapper.getOptions(typs);
+    public List<FaultPhysicalModel> getOptions(FaultPhysicalModel model) {
+        QueryWrapper<FaultPhysicalModel> wrapper = new QueryWrapper<>();
+        wrapper.lambda().eq(StringUtils.isNotEmpty(model.getModelTypet()),FaultPhysicalModel::getModelTypet,model.getModelTypet())
+                .eq(StringUtils.isNotEmpty(model.getModelType()),FaultPhysicalModel::getModelType,model.getModelType())
+                .eq(StringUtils.isNotEmpty(model.getModelAttribution()),FaultPhysicalModel::getModelAttribution,model.getModelAttribution());
+        return faultPhysicalModelMapper.selectList(wrapper);
     }
 }

+ 54 - 58
fdapfe-admin/src/main/java/com/cn/fdapfe/biz/service/impl/FdAlgorithmServiceImpl.java

@@ -2,6 +2,8 @@ package com.cn.fdapfe.biz.service.impl;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.text.DecimalFormat;
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
@@ -15,10 +17,14 @@ import com.alibaba.fastjson2.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.cn.fdapfe.biz.domain.Formulas;
 import com.cn.fdapfe.biz.mapper.FormulasMapper;
+import com.cn.fdapfe.common.core.domain.AjaxResult;
 import com.cn.fdapfe.common.utils.DateUtils;
 import com.cn.fdapfe.common.utils.SecurityUtils;
 import com.cn.fdapfe.common.utils.StringUtils;
+import com.cn.fdapfe.common.utils.file.FileCopyUtils;
+import com.cn.fdapfe.common.utils.file.FileToMultipartFile;
 import com.cn.fdapfe.common.utils.http.HttpUtils;
+import com.cn.fdapfe.web.controller.common.CommonController;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -27,6 +33,7 @@ import org.springframework.stereotype.Service;
 import com.cn.fdapfe.biz.mapper.FdAlgorithmMapper;
 import com.cn.fdapfe.biz.domain.FdAlgorithm;
 import com.cn.fdapfe.biz.service.IFdAlgorithmService;
+import org.springframework.web.multipart.MultipartFile;
 
 import javax.annotation.Resource;
 import javax.script.ScriptEngine;
@@ -49,6 +56,8 @@ public class FdAlgorithmServiceImpl implements IFdAlgorithmService
     private static final long CHECK_INTERVAL = 1000; // 检查间隔1秒(毫秒)
     @Resource
     private FormulasMapper formulasMapper;
+    @Resource
+    private CommonController commonController;
 
     /**
      * 查询故障诊断算法功能验证
@@ -93,70 +102,57 @@ public class FdAlgorithmServiceImpl implements IFdAlgorithmService
      * @return 结果
      */
     @Override
-    public int insertFdAlgorithm(FdAlgorithm po) throws IOException, InterruptedException {
+    public int insertFdAlgorithm(FdAlgorithm po) throws Exception {
         po.setCreateTime(DateUtils.getNowDate());
         po.setCreateBy(SecurityUtils.getUsername());
-
+        ArrayList<String> todoFileUrl = new ArrayList<>();
+        ArrayList<String>jsonDataArray = new ArrayList<>();
         // 记录开始执行外部程序的时间
         Date startTime = new Date();
         po.setStartTime(startTime);
-
-        // 启动外部程序
-        ExeProcessManager.ExeResult topOneResult = ExeProcessManager.startExe(
-                po.getOutputPath() + File.separator + "topOne.exe",
-                false
-        );
-        ExeProcessManager.ExeResult topTwoResult = ExeProcessManager.startExe(
-                po.getOutputPath() + File.separator + "topTwo.exe",
-                false
-        );
-        // 记录执行结束时间(两个进程都已执行完毕)
-        Date endTime = new Date();
-        po.setEndTime(endTime);
-
-        // 循环获取并解析输出文件(保留原有逻辑)
-        Object json = checkAndParseJson(po.getOutputPath() + File.separator + "output", "return_indicators.json");
-        System.err.println(json);
-        po.setReturnData(json.toString());
-
-        return mapper.insert(po);
-    }
-
-    /**
-     * 因为文件生成有一定延迟,循环获取生成的文件
-     * @param directoryPath
-     * @param fileName
-     * @return
-     */
-    public static Object checkAndParseJson(String directoryPath, String fileName) {
-        File directory = new File(directoryPath);
-        if (!directory.exists() || !directory.isDirectory()) {
-            System.err.println("错误:指定目录不存在或不是有效目录 - " + directoryPath);
-            return null;
-        }
-
-        File jsonFile = new File(directory, fileName);
-        while (!jsonFile.exists()) {
-            System.out.println("等待文件出现:" + fileName);
-            try {
-                Thread.sleep(CHECK_INTERVAL);
-            } catch (InterruptedException e) {
-                Thread.currentThread().interrupt();
-                System.err.println("检查过程被中断");
-                return null;
+        // 将传递的参数解析为数组进行单独取值操作
+        ObjectMapper dataMapper = new ObjectMapper();
+        LinkedHashMap[] array = dataMapper.readValue(po.getPostApiData(), LinkedHashMap[].class);
+        // 循环数组,取出每个参数组
+        for (LinkedHashMap item : array) {
+            // 将上传的文件重命名并复制到当前模型输入目录中
+            FileCopyUtils.copyFile2source(item.get("inputFile").toString(), item.get("inputFilePath").toString());
+            // 将算法配置信息重新写入到算法的input.txt中
+            FileCopyUtils.writeToTxt(item.get("inputPath").toString(), item.get("modelData").toString());
+            // 启动诊断算法对进行诊断
+            ExeProcessManager.startExe(item.get("exePath").toString(), true);
+            // 获取输出目录
+            String outputPath = item.get("outputPath").toString();
+            File outputDir = new File(outputPath);
+            // 检查输出目录是否存在
+            if (outputDir.exists() && outputDir.isDirectory()) {
+                // 获取目录中的所有文件
+                File[] files = outputDir.listFiles();
+                if (files != null) {
+                    for (File file : files) {
+                        if (file.isFile()) {
+                            FileToMultipartFile multipartFile = new FileToMultipartFile(file.getAbsolutePath(), "file");
+                            AjaxResult ajaxResult = commonController.customUploadFile(multipartFile, "D:/modelData");
+                            // 如果是JSON文件,解析其内容作为返回数据
+                            if (ajaxResult.get("fileSuffix").equals("json") || ajaxResult.get("fileSuffix").equals("txt")) {
+                                String jsonPath = file.getAbsolutePath();
+                                String jsonData = new String(Files.readAllBytes(Paths.get(jsonPath)));
+                                jsonDataArray.add(jsonData);
+                            }else if(ajaxResult.get("fileSuffix").equals("png") || ajaxResult.get("fileSuffix").equals("jpg")){
+                                todoFileUrl.add(ajaxResult.get("todofilePath").toString());
+                            }
+                        }
+                    }
+                }
             }
-            jsonFile = new File(directory, fileName); // 重新获取文件对象(防止目录变化)
-        }
-
-        System.out.println("找到文件:" + jsonFile.getAbsolutePath());
-        try {
-            return new ObjectMapper().readValue(jsonFile, Object.class);
-        } catch (IOException e) {
-            System.err.println("解析错误:" + e.getMessage());
-            e.printStackTrace();
-            return null;
         }
+        // 将算法生成的图片结果集转换成JSONString进行入库前准备,图片结果集
+        po.setReturnDataImage(JSON.toJSONString(todoFileUrl));
+        //json 文件结果集
+        po.setReturnData(JSON.toJSONString(jsonDataArray));
+        return mapper.insert(po);
     }
+
     /**
      * 修改故障诊断算法功能验证
      * 
@@ -168,7 +164,7 @@ public class FdAlgorithmServiceImpl implements IFdAlgorithmService
         FdAlgorithm id = mapper.selectById(po.getId());
         po.setUpdateTime(DateUtils.getNowDate());
         po.setUpdateBy(SecurityUtils.getUsername());
-        return id == null? insertFdAlgorithm(po): mapper.updateById(po);
+        return id == null? mapper.insert(po): mapper.updateById(po);
     }
 
     /**
@@ -200,7 +196,7 @@ public class FdAlgorithmServiceImpl implements IFdAlgorithmService
         String returnData = po.getReturnData();
         JSONArray dataArray = convertToStandardJson(returnData);
 
-        // 创建 JavaScript 脚本引擎
+        // 创建 JavaScript 脚本
         ScriptEngineManager manager = new ScriptEngineManager();
         ScriptEngine engine = manager.getEngineByName("JavaScript");
         Map<String, List<Object>> resultMap = new HashMap<>();

+ 3 - 11
fdapfe-admin/src/main/java/com/cn/fdapfe/biz/service/impl/ModelDataGenServiceImpl.java

@@ -11,6 +11,7 @@ import com.cn.fdapfe.biz.service.IModelDataGenService;
 import com.cn.fdapfe.common.core.domain.AjaxResult;
 import com.cn.fdapfe.common.utils.DateUtils;
 import com.cn.fdapfe.common.utils.SecurityUtils;
+import com.cn.fdapfe.common.utils.file.FileCopyUtils;
 import com.cn.fdapfe.common.utils.file.FileToMultipartFile;
 import com.cn.fdapfe.web.controller.common.CommonController;
 import com.fasterxml.jackson.databind.ObjectMapper;
@@ -126,7 +127,7 @@ public class ModelDataGenServiceImpl implements IModelDataGenService {
             if(model.getModelAttribution().equals("0")){
                 try {
                     //写入配置文件信息
-                    writeToTxt(item.get("inputPath").toString(),item.get("modelData").toString());
+                    FileCopyUtils.writeToTxt(item.get("inputPath").toString(),item.get("modelData").toString());
                     //启动模型开始生成
                     ExeProcessManager.startExe(item.get("exePath").toString(),true);
                     //判断是否进行加噪
@@ -252,16 +253,7 @@ public class ModelDataGenServiceImpl implements IModelDataGenService {
         return "";
     }
 
-    /**
-     * 将内容写入文本文件
-     */
-    public static void writeToTxt(String filePath, String content) throws IOException {
-        try (FileWriter writer = new FileWriter(filePath, false)) {
-            writer.write("");
-            writer.write(content);
-            writer.flush();
-        }
-    }
+
 
     @Override
     public int update(ModelDataGen po) {

+ 1 - 0
fdapfe-admin/src/main/java/com/cn/fdapfe/web/controller/common/CommonController.java

@@ -93,6 +93,7 @@ public class CommonController
             ServerConfig serverConfig = new ServerConfig();
             String url = serverConfig.getUrl() +"/"+ infoPath;
             AjaxResult ajax = AjaxResult.success();
+            ajax.put("todofilePath", serverConfig.getUrl() + upload.get("todoPath"));
             ajax.put("url", infoPath);
             ajax.put("filelocalPath", filelocalPath);
             ajax.put("fileSize", fileSize);

+ 2 - 1
fdapfe-admin/src/main/resources/mapper/model/FaultPhysicalModelMapper.xml

@@ -10,6 +10,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="modelType"    column="model_type"    />
         <result property="modelPath"    column="model_path"    />
         <result property="modelAttribution"    column="model_attribution"    />
+        <result property="modelTypet"    column="model_type_t" />
         <result property="remark"    column="remark"    />
         <result property="createBy"    column="create_by"    />
         <result property="createTime"    column="create_time"    />
@@ -18,7 +19,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </resultMap>
 
     <sql id="selectFaultPhysicalModelVo">
-        select model_id, model_name, model_type, model_path, remark, create_by, create_time, update_by, update_time,model_attribution from biz_fault_physical_model
+        select model_id, model_name, model_type, model_path, remark, create_by, create_time, update_by, update_time,model_attribution,model_type_t from biz_fault_physical_model
     </sql>
 
 

+ 15 - 3
fdapfe-admin/src/main/resources/mapper/test/FdAlgorithmMapper.xml

@@ -9,8 +9,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="name"    column="name"    />
         <result property="modelId"    column="model_id"    />
         <result property="dataId"    column="data_id"    />
-        <result property="returnData"    column="truth_labels"    />
-        <result property="testLabels"    column="test_labels"    />
+        <result property="returnData"    column="return_data"    />
+        <result property="modelType"    column="model_type"    />
+        <result property="modelTypet"    column="model_type_t"    />
+        <result property="returnDataImage"    column="return_data_image"    />
         <result property="remark"    column="remark"    />
         <result property="startTime"    column="start_time"    />
         <result property="endTime"    column="end_time"    />
@@ -21,9 +23,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </resultMap>
 
     <sql id="selectFdAlgorithmVo">
-        select id, name, model_id, data_id, truth_labels, test_labels, remark, start_time, end_time, create_by, create_time, update_by, update_time from biz_fd_algorithm
+        select id, name, model_id, data_id, truth_labels, return_data_image, remark, start_time, end_time, create_by, create_time, update_by, update_time from biz_fd_algorithm
     </sql>
+    <select id="selectList" resultMap="FdAlgorithmResult">
+        SELECT
+            t.*,
+            a.model_type,
+            a.model_type_t
+        FROM
+            biz_fd_algorithm t
+        LEFT JOIN biz_fault_model_t a ON t.model_id = a.model_id
+            ${ew.customSqlSegment}
 
+    </select>
     <select id="getOptions" resultType="map">
         select * from biz_fd_algorithm order by id desc
     </select>

+ 58 - 5
fdapfe-common/src/main/java/com/cn/fdapfe/common/utils/file/FileCopyUtils.java

@@ -1,7 +1,10 @@
 package com.cn.fdapfe.common.utils.file;
 
+import java.io.FileWriter;
 import java.io.IOException;
 import java.nio.file.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 public class FileCopyUtils {
 
@@ -54,14 +57,64 @@ public class FileCopyUtils {
         }
         return "ok";
     }
+    /**
+     * 将源文件复制到目标目录,并根据特定规则重命名
+     *
+     * 该方法会将指定的源文件复制到目标目录,并自动移除文件名中的时间戳后缀
+     * 例如:将 "fea_all_20250528165156A007.mat" 重命名为 "fea_all.mat"
+     *
+     * @param sourceFilePath 源文件的完整路径(例如:"F:/fdapfe/uploadPath/fea_all_20250528165156A007.mat")
+     * @param targetDir      目标目录路径(例如:"E:/model_test/input/")
+     * @return 操作结果描述
+     * @throws IOException              当文件操作失败时抛出
+     * @throws IllegalArgumentException 当参数为空或不合法时抛出
+     */
+    public static Object copyFile2source(String sourceFilePath, String targetDir) throws IOException {
+        // 校验参数合法性
+        if (sourceFilePath == null || targetDir == null) {
+            throw new IllegalArgumentException("源文件路径和目标目录均不可为空");
+        }
 
-    // 示例使用
-    public static void main(String[] args) {
+        // 解析源文件路径和文件名
+        Path sourcePath = Paths.get(sourceFilePath);
+        if (!Files.exists(sourcePath) || Files.isDirectory(sourcePath)) {
+            throw new IOException("源文件不存在或为目录:" + sourcePath);
+        }
+
+        String fileName = sourcePath.getFileName().toString();
+        // 截取文件名,去掉 "_20250421144724A001" 部分
+        int underscoreIndex = fileName.lastIndexOf('_'); // 找到最后一个下划线的位置
+        int dotIndex = fileName.lastIndexOf('.');        // 找到最后一个点的位置
+        String newFileName= "";
+        // 如果文件名符合预期格式(包含下划线和扩展名),则进行截取
+        if (underscoreIndex > 0 && dotIndex > underscoreIndex) {
+            newFileName = fileName.substring(0, underscoreIndex) + fileName.substring(dotIndex);
+        }
+        // 构建目标文件路径
+        Path targetPath = Paths.get(targetDir, newFileName);
+        // 创建目标目录(如果不存在)
+        try {
+            Files.createDirectories(targetPath.getParent());
+        } catch (IOException e) {
+            throw new IOException("目标目录创建失败:" + e.getMessage());
+        }
+
+        // 执行文件复制(覆盖已存在文件)
         try {
-            copyFile("E:/source", "E:/target", "example.txt");
-            System.out.println("文件复制成功");
+            Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING);
+            return "文件复制成功,新文件路径:" + targetPath.toString();
         } catch (IOException e) {
-            e.printStackTrace();
+            throw new IOException("文件复制失败:" + e.getMessage());
+        }
+    }
+    /**
+     * 将内容写入文本文件
+     */
+    public static void writeToTxt(String filePath, String content) throws IOException {
+        try (FileWriter writer = new FileWriter(filePath, false)) {
+            writer.write("");
+            writer.write(content);
+            writer.flush();
         }
     }
 }

+ 4 - 0
fdapfe-common/src/main/java/com/cn/fdapfe/common/utils/file/FileUploadUtils.java

@@ -172,6 +172,9 @@ public class FileUploadUtils
 
     public static final Map getPathFileName(String uploadDir, String fileName) throws IOException {
         // 查找最后一个斜杠的位置(处理可能的多级目录)
+        int dirLastIndex = RuoYiConfig.getProfile().length() + 1;
+        String currentDir = StringUtils.substring(uploadDir, dirLastIndex);
+        String s = Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName;
         String FileLicationPath = uploadDir + "/" + fileName;
         int lastSlashIndex = FileLicationPath.lastIndexOf('/');
         String datePath = FileLicationPath.substring(0, lastSlashIndex);
@@ -182,6 +185,7 @@ public class FileUploadUtils
         fileInfoMap.put("fileName", fileName);          // 文件名称(不带路径)
         fileInfoMap.put("filelocalPath", datePath);      // 本地存储路径(带目录)
         fileInfoMap.put("infoPath",FileLicationPath);
+        fileInfoMap.put("todoPath",s);
         return fileInfoMap;
     }
 

+ 1 - 1
fdapfe-common/src/main/java/com/cn/fdapfe/common/utils/file/MimeTypeUtils.java

@@ -36,7 +36,7 @@ public class MimeTypeUtils
             // 视频格式
             "mp4", "avi", "rmvb",
             // pdf
-            "pdf", "mat" };
+            "pdf", "mat","json" };
 
     public static String getExtension(String prefix)
     {

+ 2 - 1
fdapfe-ui/package.json

@@ -53,12 +53,13 @@
     "jsencrypt": "3.0.0-rc.1",
     "mathjax": "^3.2.2",
     "nprogress": "0.2.0",
+    "numeral": "^2.0.6",
     "quill": "2.0.2",
     "ruoyi": "file:",
     "screenfull": "5.0.2",
     "sortablejs": "1.10.2",
     "splitpanes": "2.4.1",
-    "vue": "2.6.12",
+    "vue": "^2.6.12",
     "vue-count-to": "1.0.13",
     "vue-cropper": "0.5.5",
     "vue-meta": "2.4.0",

+ 4 - 3
fdapfe-ui/src/api/dataGen/phyModel.js

@@ -10,10 +10,11 @@ export function listPhyModel(query) {
 }
 
 // 查询物理模型数据生成管理详细
-export function getPhyModel(id) {
+export function getPhyModel(data) {
   return request({
-    url: "/dataGen/phyModel/" + id,
-    method: "get",
+    url: "/dataGen/phyModel",
+    method: "post",
+    data: data,
   });
 }
 

+ 2 - 2
fdapfe-ui/src/api/model/faultPhysical.js

@@ -44,10 +44,10 @@ export function delFaultPhysical(id) {
 }
 
 // 故障故障物理模型getOptions
-export function getFaultPhysicalOptions(typs) {
+export function getFaultPhysicalOptions(data) {
   return request({
     url: "/model/faultPhysical/getOptions",
     method: "get",
-    params: { typs: typs },
+    params: data,
   });
 }

+ 1 - 1
fdapfe-ui/src/components/FileUpload/index.vue

@@ -60,7 +60,7 @@ export default {
     // 文件类型, 例如['png', 'jpg', 'jpeg']
     fileType: {
       type: Array,
-      default: () => ["csv","mat","doc", "xls", "ppt", "txt", "pdf"],
+      default: () => ["jpg","png","csv","mat","doc", "xls", "ppt", "txt", "pdf"],
     },
     // 是否显示提示
     isShowTip: {

+ 24 - 21
fdapfe-ui/src/views/dataGen/index.vue

@@ -60,17 +60,17 @@
           v-hasPermi="['dataGen:phyModel:add']"
         >新增</el-button>
       </el-col>
-      <el-col :span="1.5">
-        <el-button
-          type="success"
-          plain
-          icon="el-icon-edit"
-          size="mini"
-          :disabled="single"
-          @click="handleUpdate"
-          v-hasPermi="['dataGen:phyModel:edit']"
-        >修改</el-button>
-      </el-col>
+<!--      <el-col :span="1.5">-->
+<!--        <el-button-->
+<!--          type="success"-->
+<!--          plain-->
+<!--          icon="el-icon-edit"-->
+<!--          size="mini"-->
+<!--          :disabled="single"-->
+<!--          @click="handleUpdate"-->
+<!--          v-hasPermi="['dataGen:phyModel:edit']"-->
+<!--        >修改</el-button>-->
+<!--      </el-col>-->
       <el-col :span="1.5">
         <el-button
           type="danger"
@@ -107,7 +107,11 @@
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="数据生成名称" align="center" prop="dataGenName" />
       <el-table-column label="数据生成模型" align="center" prop="modelId" />
-      <el-table-column label="参数" align="center" prop="bizParams" />
+      <el-table-column label="参数" align="center" prop="bizParams" >
+        <template slot-scope="scope">
+          <el-input type="textarea" disabled v-model="scope.row.bizParams"/>
+        </template>
+      </el-table-column>
       <el-table-column label="状态" align="center" prop="status" />
       <el-table-column
         label="开始时间"
@@ -136,13 +140,13 @@
         class-name="small-padding fixed-width"
       >
         <template slot-scope="scope">
-          <el-button
-            size="mini"
-            type="text"
-            icon="el-icon-edit"
-            @click="handleUpdate(scope.row)"
-            v-hasPermi="['dataGen:phyModel:edit']"
-          >修改</el-button>
+<!--          <el-button-->
+<!--            size="mini"-->
+<!--            type="text"-->
+<!--            icon="el-icon-edit"-->
+<!--            @click="handleUpdate(scope.row)"-->
+<!--            v-hasPermi="['dataGen:phyModel:edit']"-->
+<!--          >修改</el-button>-->
           <el-button
             size="mini"
             type="text"
@@ -333,7 +337,7 @@ export default {
     handleUpdate(row) {
       this.reset();
       const id = row.dataGenId || this.ids;
-      getPhyModel(id)
+      getPhyModel(row)
         .then((response) => {
           this.form = response.data;
           this.open = true;
@@ -344,7 +348,6 @@ export default {
         });
     },
 
-    /** 删除按钮操作 */
     /** 删除按钮操作 */
     handleDelete(idsToDelete) {
       // 处理单个/多个删除时的名称展示

+ 2 - 2
fdapfe-ui/src/views/index.vue

@@ -68,11 +68,11 @@ export default {
       imagedata:{
         data:[{
           name: 'Precision',
-          value: 91
+          value: 0.91
         },
           {
             name: 'Recall',
-            value: 91
+            value: 0.91
           },
           {
             name: 'F1 ',

+ 30 - 5
fdapfe-ui/src/views/model/faultPhysical/form.vue

@@ -25,6 +25,20 @@
           />
         </el-select>
       </el-form-item>
+      <el-form-item label="模型类型" prop="type">
+        <el-select
+          v-model="formData.modelTypet"
+          placeholder="请选择模型种类"
+          clearable
+        >
+          <el-option
+            v-for="dict in dict.type.model_tepe_t"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
       <el-form-item v-if="!disable"  label="模型接口地址" prop="modelPath">
         <el-input v-model="formData.modelPath" placeholder="请输入模型接口地址"/>
       </el-form-item>
@@ -38,24 +52,34 @@
       <el-form-item label="参数配置" prop="remark">
         <div class="body-toolbar">
           <el-button circle icon="el-icon-plus"
-                     :disabled="disable"
+
                      @click="openDialog">
           </el-button>
         </div>
         <el-table :data="formData.configData">
           <el-table-column prop="paramName" label="字段名称" min-width="60" header-align="center" align="center">
             <template slot-scope="scope">
-              <el-input :disabled="disable" v-model="scope.row.paramName" placeholder="字段名称"></el-input>
+<!--              <el-input :disabled="disable" v-model="scope.row.paramName" placeholder="字段名称"></el-input>-->
+              <el-input  v-model="scope.row.paramName" placeholder="字段名称"></el-input>
             </template>
           </el-table-column>
           <el-table-column prop="paramChineseName" label="中文名称" min-width="60" align="center">
             <template slot-scope="scope">
-              <el-input :disabled="disable" v-model="scope.row.paramChineseName" placeholder="中文名称"></el-input>
+<!--              <el-input :disabled="disable" v-model="scope.row.paramChineseName" placeholder="中文名称"></el-input>-->
+              <el-input  v-model="scope.row.paramChineseName" placeholder="中文名称"></el-input>
             </template>
           </el-table-column>
           <el-table-column prop="paramType" label="参数类型" header-align="center" align="center">
             <template slot-scope="scope">
-              <el-select :disabled="disable" v-model="scope.row.paramType" placeholder="参数类型" >
+<!--              <el-select :disabled="disable" v-model="scope.row.paramType" placeholder="参数类型" >-->
+<!--                <el-option-->
+<!--                  v-for="dict in dict.type.param_type"-->
+<!--                  :key="dict.value"-->
+<!--                  :label="dict.label"-->
+<!--                  :value="dict.value"-->
+<!--                />-->
+<!--              </el-select>-->
+              <el-select  v-model="scope.row.paramType" placeholder="参数类型" >
                 <el-option
                   v-for="dict in dict.type.param_type"
                   :key="dict.value"
@@ -131,10 +155,11 @@ export default {
       default: 'add'
     },
   },
-  dicts: ["biz_model_type","param_type"],
+  dicts: ["biz_model_type","param_type","model_tepe_t"],
   data() {
     return {
       formData: {
+        modelTypet:'',
         exeLocalPath:'',
         modelId: '',
         modelName: '',

+ 12 - 1
fdapfe-ui/src/views/model/faultPhysical/index.vue

@@ -103,6 +103,17 @@
       <el-table-column type="selection" width="55" align="center"/>
       <el-table-column label="模型名称" align="center" prop="modelName"/>
       <el-table-column  label="模型接口地址" align="center" prop="modelPath"/>
+      <el-table-column label="模型类型" align="center" prop="modelType">
+        <template slot-scope="scope">
+          <dict-tag :options="dict.type.biz_model_type" :value="scope.row.modelType" />
+        </template>
+      </el-table-column>
+      <el-table-column label="模型种类" align="center" prop="modelTypet">
+        <template slot-scope="scope">
+          <dict-tag :options="dict.type.model_tepe_t" :value="scope.row.modelTypet" />
+        </template>
+      </el-table-column>
+
       <el-table-column label="备注" align="center" prop="remark"/>
       <el-table-column label="操作"  align="center" class-name="small-padding fixed-width" >
         <template slot-scope="scope">
@@ -165,7 +176,7 @@ export default {
   components: {
     FaultPhysicalForm
   },
-  dicts: ["biz_model_type"],
+  dicts: ["biz_model_type","model_tepe_t"],
   data() {
     return {
       options: 'add',

+ 42 - 8
fdapfe-ui/src/views/test/ddAlgorithm/form.vue

@@ -12,6 +12,34 @@
       <el-form-item label="任务名称" prop="name">
         <el-input v-model="formData.name" placeholder="请输入任务名称" />
       </el-form-item>
+      <el-form-item label="模型类型" prop="type" @change="queryModels">
+        <el-select
+          v-model="queryFrom.modelType"
+          placeholder="请选择模型类型"
+          clearable
+        >
+          <el-option
+            v-for="dict in dict.type.biz_model_type"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="模型类型" prop="type" @change="queryModels">
+        <el-select
+          v-model="queryFrom.modelTypet"
+          placeholder="请选择模型种类"
+          clearable
+        >
+          <el-option
+            v-for="dict in dict.type.model_tepe_t"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
       <el-form-item label="模型" prop="modelId">
         <el-select v-model="formData.modelId" placeholder="请选择模型" @change="onModelChange">
           <el-option
@@ -86,8 +114,14 @@ export default {
     },
 
   },
+  dicts: ["biz_model_type","param_type","model_tepe_t"],
   data() {
     return {
+      queryFrom:{
+        modelType:'',
+        modelTypet:'',
+        modelAttribution:1
+      },
       faultPhysicalOptions:[],
       //指定文件上传路径
       customPath:"",
@@ -126,6 +160,13 @@ export default {
   },
 
   methods: {
+    queryModels(){
+      console.log('111111',this.queryFrom)
+      getFaultPhysicalOptions(this.queryFrom).then(resp => {
+        this.faultPhysicalOptions = resp.data || [];
+        console.log(this.faultPhysicalOptions)
+      });
+    },
     onModelChange(modelId){
       //获取模型参数详情
       getFaultPhysical(modelId).then(rest => {
@@ -163,15 +204,8 @@ export default {
       fileCopys(fileData);
       this.formData.outputPath = this.selectedModel.modelPath
     },
-    loadOptions() {
-      // 加载模型选项
-      getFaultPhysicalOptions(1).then(resp => {
-        this.faultPhysicalOptions = resp.data || [];
-        console.log(this.faultPhysicalOptions)
-      });
-    },
     initData() {
-      this.loadOptions();
+      this.queryModels();
       if (this.options!== 'add') {
         this.formData = { ...this.dataInfo };
       }

+ 72 - 86
fdapfe-ui/src/views/test/ddAlgorithm/index.vue

@@ -83,19 +83,19 @@
     >
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="任务名称" align="center" prop="name" />
-      <el-table-column label="文本结果" align="center" prop="resultText" >
+      <el-table-column label="文本结果" align="center" prop="returnData" >
         <template slot-scope="scope">
-          {{content}}
+          <el-input type="textarea" disabled v-model="scope.row.returnData"/>
         </template>
       </el-table-column>
-      <el-table-column
-        label="开始时间"
-        align="center"
-        prop="startTime"
-        width="180"
-      >
+      <el-table-column label="模型类型" align="center" prop="modelType">
         <template slot-scope="scope">
-          <span>{{ parseTime(scope.row.startTime, "{y}-{m}-{d}") }}</span>
+          <dict-tag :options="dict.type.biz_model_type" :value="scope.row.modelType" />
+        </template>
+      </el-table-column>
+      <el-table-column label="模型种类" align="center" prop="modelTypet">
+        <template slot-scope="scope">
+          <dict-tag :options="dict.type.model_tepe_t" :value="scope.row.modelTypet" />
         </template>
       </el-table-column>
       <el-table-column label="说明" align="center" prop="remark" />
@@ -109,16 +109,16 @@
           <span>{{ parseTime(scope.row.startTime) }}</span>
         </template>
       </el-table-column>
-      <el-table-column
-        label="结束时间"
-        align="center"
-        prop="endTime"
-        width="180"
-      >
-        <template slot-scope="scope">
-          <span>{{ parseTime(scope.row.endTime) }}</span>
-        </template>
-      </el-table-column>
+<!--      <el-table-column-->
+<!--        label="结束时间"-->
+<!--        align="center"-->
+<!--        prop="endTime"-->
+<!--        width="180"-->
+<!--      >-->
+<!--        <template slot-scope="scope">-->
+<!--          <span>{{ parseTime(scope.row.endTime) }}</span>-->
+<!--        </template>-->
+<!--      </el-table-column>-->
       <el-table-column
         label="操作"
         align="center"
@@ -132,13 +132,13 @@
             @click="handleVerify(scope.row)"
             v-hasPermi="['test:ddAlgorithm:edit']"
           >开始评估</el-button>
-          <el-button
-            size="mini"
-            type="text"
-            icon="el-icon-edit"
-            @click="handleUpdate(scope.row)"
-            v-hasPermi="['test:ddAlgorithm:edit']"
-          >查看</el-button>
+<!--          <el-button-->
+<!--            size="mini"-->
+<!--            type="text"-->
+<!--            icon="el-icon-edit"-->
+<!--            @click="handleUpdate(scope.row)"-->
+<!--            v-hasPermi="['test:ddAlgorithm:edit']"-->
+<!--          >查看</el-button>-->
           <el-button
             size="mini"
             type="text"
@@ -161,45 +161,34 @@
     <el-dialog
       :title="!returnDataShow?'验证配置':'验证结果'"
       :visible.sync="verifyDialogVisible"
-      width="1000px"
+      width="1100px"
       append-to-body
     >
       <el-form :model="verifyForm" label-width="80px">
-        <el-form-item v-if="!returnDataShow" label="验证类型">
-          <el-select
-            v-model="verifyRow.verifyData"
-            multiple
-            filterable
-            placeholder="请选择需要验证的类型(支持多选)"
-            style="width: 100%"
-          >
-            <el-option
-              v-for="dict in dict.type.biz_perf_eval_type"
-              :key="dict.value"
-              :label="dict.label"
-              :value="dict.value"
-            />
-
-          </el-select>
-        </el-form-item>
-        <el-form-item label="验证结果" v-if="returnDataShow">
+<!--        <el-form-item v-if="!returnDataShow" label="验证类型">-->
+<!--          <el-select-->
+<!--            v-model="verifyRow.verifyData"-->
+<!--            multiple-->
+<!--            filterable-->
+<!--            placeholder="请选择需要验证的类型(支持多选)"-->
+<!--            style="width: 100%"-->
+<!--          >-->
+<!--            <el-option-->
+<!--              v-for="dict in dict.type.biz_perf_eval_type"-->
+<!--              :key="dict.value"-->
+<!--              :label="dict.label"-->
+<!--              :value="dict.value"-->
+<!--            />-->
+<!--          </el-select>-->
+<!--        </el-form-item>-->
+        <el-form-item label="验证结果" >
+<!--          <info :data="verifyRow.verifyData" />-->
           <template>
-            <div class="demo-image__placeholder">
-              <div class="image-container">
-                <div >
-                  <span class="demonstration">默认</span>
-                  <el-image :src="src1"></el-image>
-                </div>
-                <div >
-                  <span class="demonstration">自定义</span>
-                  <el-image :src="src2">
-                    <div slot="placeholder" class="image-slot">
-                      加载中<span class="dot">...</span>
-                    </div>
-                  </el-image>
-                </div>
-              </div>
-            </div>
+            <el-row :gutter="5">
+              <el-col v-for="(url, index) in verifyRow.imageData" :key="index" :span="11"> <!-- 每列占 12 份(24 份为满行) -->
+                <el-image :src="url" :preview-src-list="[url]" style="width: 95%; height: 150px;"></el-image>
+              </el-col>
+            </el-row>
           </template>
         </el-form-item>
       </el-form>
@@ -233,20 +222,14 @@ import { getDataOptions } from "@/api/data/data";
 import { getFaultDiagnosisOptions } from "@/api/model/faultDiagnosis";
 import DdAlgorithmForm from './form.vue';
 import txtContent from '@/assets/testImage/output3.txt';
+import info from './info.vue'
 export default {
   name: "DdAlgorithm",
   components: {
-    DdAlgorithmForm
-  },
-  dicts: ["biz_model_type","biz_perf_eval_type"],
-  computed: {
-    src1(){
-      return require('@/assets/testImage/output1.png');
-    },
-    src2(){
-      return  require('@/assets/testImage/output2.png')
-    }
+    DdAlgorithmForm,
+    info
   },
+  dicts: ["biz_model_type","biz_perf_eval_type","model_tepe_t"],
   data() {
     return {
       verifyForm:null,
@@ -288,8 +271,10 @@ export default {
       dataOptions: [],
       faultDiagnosisOptions: [],
       verifyRow:{
-        verifyData:[],
+        verifyData:{},
+        imageData:[],
       },
+
       returnDataMap:null,
       returnDataShow:false
     };
@@ -298,23 +283,24 @@ export default {
     this.getList();
     this.getOption();
   },
-  mounted() {
-    // 假设文件在 src/assets/txt 目录下
-    import('@/assets/testImage/output3.txt')
-      .then((module) => {
-        this.txtContent = module.default;
-      })
-      .catch((error) => {
-        console.error('引入 TXT 文件时出错:', error);
-        this.txtContent = '无法加载文件内容';
-      });
-  },
   methods: {
     handleVerify(row){
+     const json= JSON.parse(row.returnData)
+      this.verifyRow.imageData =[...JSON.parse(row.returnDataImage)]
+      console.log('imgae', this.verifyRow.imageData)
+      json.forEach((jsonStr, index) => {
+          // 解析 JSON 字符串为对象
+
+        this.verifyRow.verifyData= JSON.parse(jsonStr);
+
+          // 输出当前数据的序号
+          console.log(this.verifyRow.verifyData);
+      });
+      // console.log(json.metrics)
       this.verifyDialogVisible = true;
-      this.verifyRow={
-        ...row
-      }
+      // this.verifyRow={
+      //   ...row
+      // }
     },
     /** 验证*/
     handleConfirmVerify() {

+ 328 - 0
fdapfe-ui/src/views/test/ddAlgorithm/info.vue

@@ -0,0 +1,328 @@
+<template>
+  <div class="datav-metrics-container">
+    <!-- 顶部环形指标 -->
+    <dv-row :gutter="40" style="margin-bottom: 40px;">
+      <dv-col :span="6">
+        <dv-card class="tech-card">
+          <div class="card-title">准确率</div>
+          <dv-ring-chart
+            :value="metrics.accuracy"
+            :max="1"
+            :color="['#409EFF']"
+            :width="120"
+            :height="120"
+            :label="metrics.accuracy | numberFormat"
+          />
+          <div class="card-desc">Accuracy</div>
+        </dv-card>
+      </dv-col>
+      <dv-col :span="6">
+        <dv-card class="tech-card">
+          <div class="card-title">精确率</div>
+          <dv-ring-chart
+            :value="metrics.precision"
+            :max="1"
+            :color="['#67C23A']"
+            :width="120"
+            :height="120"
+            :label="metrics.precision | numberFormat"
+          />
+          <div class="card-desc">Precision</div>
+        </dv-card>
+      </dv-col>
+      <dv-col :span="6">
+        <dv-card class="tech-card">
+          <div class="card-title">召回率</div>
+          <dv-ring-chart
+            :value="metrics.recall"
+            :max="1"
+            :color="['#E6A23C']"
+            :width="120"
+            :height="120"
+            :label="metrics.recall | numberFormat"
+          />
+          <div class="card-desc">Recall</div>
+        </dv-card>
+      </dv-col>
+      <dv-col :span="6">
+        <dv-card class="tech-card">
+          <div class="card-title">F1值</div>
+          <dv-ring-chart
+            :value="metrics.F1_score"
+            :max="1"
+            :color="['#F56C6C']"
+            :width="120"
+            :height="120"
+            :label="metrics.F1_score | numberFormat"
+          />
+          <div class="card-desc">F1 Score</div>
+        </dv-card>
+      </dv-col>
+    </dv-row>
+
+    <!-- 分类统计环形图 -->
+    <dv-row :gutter="40" style="margin-bottom: 40px;">
+      <dv-col :span="12">
+        <dv-card class="tech-card">
+          <div class="card-title">分类TPR分布</div>
+          <dv-pie
+            :data="tprPieData"
+            :radius="150"
+            :color="['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399']"
+            :label-visible="false"
+            :legend-visible="false"
+            style="height: 400px;"
+          />
+        </dv-card>
+      </dv-col>
+      <dv-col :span="12">
+        <dv-card class="tech-card">
+          <div class="card-title">分类PPV分布</div>
+          <dv-pie
+            :data="ppvPieData"
+            :radius="150"
+            :color="['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399']"
+            :label-visible="false"
+            :legend-visible="false"
+            :rosetype="area"
+            style="height: 400px;"
+          />
+        </dv-card>
+      </dv-col>
+    </dv-row>
+
+    <!-- 动态数据表格 -->
+    <dv-card class="tech-card">
+      <div class="card-title">分类统计详情</div>
+      <dv-table
+        :data="classificationData"
+        border
+        stripe
+        :columns="tableColumns"
+        style="width: 100%;"
+        :row-class-name="tableRowClassName"
+      >
+        <template slot="class" slot-scope="scope">
+          <span class="tech-badge">{{ scope.row.class }}</span>
+        </template>
+        <template slot="action" slot-scope="scope">
+          <div class="metric-item">
+            <span>PPV: {{ scope.row.PPV | numberFormat }}</span>
+            <span>TPR: {{ scope.row.TPR | numberFormat }}</span>
+          </div>
+        </template>
+      </dv-table>
+    </dv-card>
+  </div>
+</template>
+
+<script>
+import {
+  DvRow, DvCol, DvCard, DvTable,
+  DvRingChart, DvPie
+} from '@jiaminghi/data-view';
+import numeral from 'numeral';
+
+export default {
+  name: 'TechDataVisualizer',
+  components: {
+    DvRow, DvCol, DvCard, DvTable,
+    DvRingChart, DvPie
+  },
+  props: {
+    data: {
+      type: Object,
+      required: true,
+      validator: val => val.classification_stats && val.metrics
+    }
+  },
+  computed: {
+    metrics() {
+      return this.data.metrics;
+    },
+    classificationData() {
+      return this.data.classification_stats.map(item => ({
+        ...item,
+        PPV: this.calculatePPV(item.TP, item.FP),
+        TPR: this.calculateTPR(item.TP, item.FN)
+      }));
+    },
+    tprPieData() {
+      return this.classificationData.map(item => ({
+        name: `Class ${item.class}`,
+        value: item.TPR
+      }));
+    },
+    ppvPieData() {
+      return this.classificationData.map(item => ({
+        name: `Class ${item.class}`,
+        value: item.PPV
+      }));
+    },
+    tableColumns() {
+      return [
+        {
+          title: '分类',
+          key: 'class',
+          width: 80,
+          slot: 'class'
+        },
+        {
+          title: 'TP',
+          key: 'TP',
+          width: 80
+        },
+        {
+          title: 'FP',
+          key: 'FP',
+          width: 80
+        },
+        {
+          title: 'TN',
+          key: 'TN',
+          width: 80
+        },
+        {
+          title: 'FN',
+          key: 'FN',
+          width: 80
+        },
+        {
+          title: '衍生指标',
+          key: 'action',
+          width: 180,
+          slot: 'action'
+        }
+      ];
+    }
+  },
+  filters: {
+    numberFormat(value) {
+      return numeral(value).format('0.00%');
+    }
+  },
+  methods: {
+    calculatePPV(TP, FP) {
+      return TP / (TP + FP || 1);
+    },
+    calculateTPR(TP, FN) {
+      return TP / (TP + FN || 1);
+    },
+    tableRowClassName({ row, index }) {
+      return index % 2 === 0 ? 'tech-row-even' : 'tech-row-odd';
+    }
+  }
+};
+</script>
+
+<style scoped>
+.datav-metrics-container {
+  background: #0a1220;
+  padding: 40px;
+  min-height: 1000px;
+  overflow-x: hidden;
+}
+
+.tech-card {
+  background: linear-gradient(135deg, rgba(10, 18, 32, 0.9), rgba(20, 32, 58, 0.9));
+  border: 1px solid rgba(64, 158, 255, 0.2);
+  border-radius: 20px;
+  box-shadow: 0 0 40px rgba(0, 0, 0, 0.3);
+  padding: 30px;
+  color: #fff;
+  position: relative;
+}
+
+.tech-card::before {
+  content: '';
+  position: absolute;
+  width: 60%;
+  height: 80%;
+  background: rgba(64, 158, 255, 0.1);
+  filter: blur(80px);
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  z-index: -1;
+}
+
+.card-title {
+  font-size: 18px;
+  color: #409EFF;
+  text-align: center;
+  margin-bottom: 25px;
+  position: relative;
+}
+
+.card-title::after {
+  content: '';
+  display: block;
+  width: 60px;
+  height: 3px;
+  background: #409EFF;
+  margin: 10px auto 0;
+}
+
+.tech-badge {
+  display: inline-block;
+  width: 30px;
+  height: 30px;
+  background: #409EFF;
+  color: #0a1220;
+  border-radius: 50%;
+  line-height: 30px;
+  text-align: center;
+  font-weight: bold;
+  box-shadow: 0 0 10px rgba(64, 158, 255, 0.5);
+}
+
+.dv-table {
+  color: #fff;
+  --dv-table-border-color: rgba(64, 158, 255, 0.1);
+  font-size: 14px;
+}
+
+.dv-table .metric-item {
+  display: flex;
+  justify-content: space-around;
+  width: 100%;
+  color: #ccc;
+}
+
+.tech-row-even {
+  background: rgba(64, 158, 255, 0.05);
+}
+
+.tech-row-odd {
+  background: rgba(64, 158, 255, 0.02);
+}
+
+/* 环形图样式 */
+.dv-ring-chart .dv-chart-content {
+  stroke-width: 12px;
+}
+
+.dv-ring-chart .dv-chart-title {
+  fill: #fff;
+  font-size: 14px;
+}
+
+.dv-ring-chart .dv-chart-value {
+  fill: #fff;
+  font-size: 20px;
+  font-weight: bold;
+}
+
+/* 饼图样式 */
+.dv-pie .dv-chart-series path {
+  stroke: #0a1220;
+  stroke-width: 2px;
+}
+
+.dv-pie .dv-chart-tooltip {
+  background: rgba(64, 158, 255, 0.9);
+  color: #fff;
+  border-radius: 8px;
+  padding: 8px;
+}
+</style>