Bladeren bron

报表接口调用

cynthia-qin 2 weken geleden
bovenliggende
commit
97b868ce9f

+ 2 - 2
package.json

@@ -28,9 +28,9 @@
     "@riophae/vue-treeselect": "0.4.0",
     "axios": "0.28.1",
     "clipboard": "2.0.8",
-    "core-js": "3.37.1",
+    "core-js": "^3.43.0",
     "d3": "^7.9.0",
-    "echarts": "^5.6.0",
+    "echarts": "^4.1.0",
     "element-ui": "2.15.14",
     "file-saver": "2.0.5",
     "fuse.js": "6.4.3",

+ 11 - 2
src/api/sample/sampleExperiment.js

@@ -46,7 +46,16 @@ export function delSampleExperiment(id) {
 // 拉取为解读报告
 export function pullSampleExperiment(id) {
   return request({
-    url: '/sample/sampleExperiment/readData/' + id,
-    method: 'put'
+    url: '/sample/sampleExperiment/loadReadData/' + id,
+    method: 'post'
   })
 }
+
+// 生成解读报告
+export function generateSampleExperiment(id) {
+  return request({
+    url: '/sample/sampleExperiment/generator/' + id,
+    method: 'post'
+  })
+}
+

+ 57 - 0
src/api/statistics/report.js

@@ -0,0 +1,57 @@
+import request from '@/utils/request'
+
+// 获取检出物占比统计信息
+export async function getjcwzbInfo(id) {
+    return request({
+    url: '/data/report/jcwzb/' + id,
+    method: 'get',
+  })
+}
+
+// 获取获取样本类型统计信息
+export async function getyblxtjInfo(id) {
+    return request({
+    url: '/data/report/yblxtj/' + id,
+    method: 'get',
+  })
+}
+
+// 获取样本统计信息
+export async function getybtjInfo(id) {
+    return request({
+    url: '/data/report/ybtj/' + id,
+    method: 'get',
+  })
+}
+// 获取病原体占比统计信息
+export async function getbytzbInfo(data) {
+    return request({
+    url: '/data/report/bytzb' ,
+    method: 'post',
+    data: data
+  })
+}
+// 获取病原体占比柱状图信息
+export async function getbytzzfbnfo(data) {
+    return request({
+    url: '/data/report/bytzzfb',
+    method: 'post',
+    data: data
+  })
+}
+// 获取世界病原体占比统计信息
+export async function getsjbytztqkInfo(data) {
+    return request({
+    url: '/data/report/sjbytztqk',
+    method: 'post',
+    data: data
+  })
+}
+// 获取中国病原体占比统计信息
+export async function getzgbytztqkInfo(data) {
+    return request({
+    url: '/data/report/zgbytztqk',
+    method: 'post',
+    data: data
+  })
+}

File diff suppressed because it is too large
+ 791 - 61
src/views/sample/sampleExperiment/filterReport.vue


+ 10 - 3
src/views/sample/sampleExperiment/index.vue

@@ -109,13 +109,13 @@
           v-if="scope.row.status !== 1"
             size="mini"
             type="text"
-            @click="handleDelete(scope.row)"
+            @click="handleRead(scope.row)"
           >查看报告</el-button>
           <el-button
            v-if="scope.row.status !== 1"
             size="mini"
             type="text"
-            @click="handleDelete(scope.row)"
+            @click="handlePull(scope.row)"
           >重新生成</el-button>
           <el-button
            v-if="scope.row.status === 1"
@@ -194,7 +194,7 @@
 </template>
 
 <script>
-import { listSampleExperiment, getSampleExperiment, delSampleExperiment, addSampleExperiment, updateSampleExperiment,pullSampleExperiment } from "@/api/sample/sampleExperiment"
+import { listSampleExperiment, getSampleExperiment, delSampleExperiment, addSampleExperiment, updateSampleExperiment,pullSampleExperiment,generateSampleExperiment } from "@/api/sample/sampleExperiment"
 
 export default {
   name: "SampleExperiment",
@@ -324,6 +324,13 @@ export default {
         }
       })
     },
+    // 查看报告按钮操作
+    handleRead(row) {
+      const id = row.id
+      generateSampleExperiment(id).then(response => {
+       console.log(response)
+      }).catch(() => {})
+    },
     // 生成报告按钮操作
     handlePull(row) {
       const ids = row.id

+ 304 - 173
src/views/statistics/Spatiotemporal-distribution/index.vue

@@ -2,39 +2,34 @@
   <div class="spatiotemporal-distribution">
     <!-- 筛选条件 -->
     <el-card class="filter-card">
-      <el-form :inline="true" :model="filters" size="small">
-        <el-form-item label="疾病名称">
-          <el-select
-            v-model="filters.disease"
-            filterable
-            remote
-            reserve-keyword
-            placeholder="请输入疾病名称"
-            :remote-method="searchDisease"
-            :loading="diseaseLoading"
+      <el-form :inline="true" :model="form" size="small">
+        <el-form-item label="病原体名称">
+          <el-input
+            v-model="form.bytName"
+            placeholder="请输入病原体名称"
             style="width: 180px"
-          >
-            <el-option
-              v-for="item in diseaseOptions"
-              :key="item"
-              :label="item"
-              :value="item"
-            />
-          </el-select>
+            @keyup.enter="fetchAllData"
+          />
         </el-form-item>
         <el-form-item label="亚型">
-          <el-select v-model="filters.subtype" placeholder="请选择亚型" style="width: 120px">
-            <el-option v-for="item in subtypeOptions" :key="item" :label="item" :value="item" />
-          </el-select>
+          <el-input
+            v-model="form.batch"
+            placeholder="请输入亚型"
+            style="width: 180px"
+            @keyup.enter="fetchAllData"
+          />
         </el-form-item>
         <el-form-item label="分支">
-          <el-select v-model="filters.branch" placeholder="请选择分支" style="width: 120px">
-            <el-option v-for="item in branchOptions" :key="item" :label="item" :value="item" />
-          </el-select>
+          <el-input
+            v-model="form.model"
+            placeholder="请输入分支"
+            style="width: 180px"
+            @keyup.enter="fetchAllData"
+          />
         </el-form-item>
         <el-form-item label="时间">
           <el-date-picker
-            v-model="filters.dateRange"
+            v-model="form.dateRange"
             type="daterange"
             range-separator="至"
             start-placeholder="开始日期"
@@ -44,17 +39,39 @@
           />
         </el-form-item>
         <el-form-item label="地区">
-          <el-cascader
+          <el-input
+            v-model="form.country"
+            placeholder="请输入地区"
+            style="width: 180px"
+            @keyup.enter="fetchAllData"
+          />
+          <!-- <el-cascader
             v-model="filters.region"
             :options="regionOptions"
             :props="{ checkStrictly: true, emitPath: false }"
             placeholder="请选择地区"
             style="width: 200px"
             @change="onRegionChange"
+          /> -->
+        </el-form-item>
+                <el-form-item label="省份">
+          <el-input
+            v-model="form.province"
+            placeholder="请输入省份"
+            style="width: 180px"
+            @keyup.enter="fetchAllData"
           />
+          <!-- <el-cascader
+            v-model="filters.region"
+            :options="regionOptions"
+            :props="{ checkStrictly: true, emitPath: false }"
+            placeholder="请选择地区"
+            style="width: 200px"
+            @change="onRegionChange"
+          /> -->
         </el-form-item>
         <el-form-item>
-          <el-button type="primary" @click="fetchAllData">筛选</el-button>
+          <el-button type="primary" @click="fetchAllData">查询</el-button>
         </el-form-item>
       </el-form>
     </el-card>
@@ -71,10 +88,10 @@
         <div ref="barChart" style="height: 320px;"></div>
       </el-card>
       <!-- 子图三:分支饼图 -->
-      <el-card class="chart-card">
+      <!-- <el-card class="chart-card">
         <div slot="header">病原体分支分布饼图</div>
         <div ref="pieChart" style="height: 320px;"></div>
-      </el-card>
+      </el-card> -->
     </div>
 
     <!-- 地图 -->
@@ -89,16 +106,52 @@
 
 <script>
 import * as echarts from 'echarts';
-import AMapLoader from '@amap/amap-jsapi-loader';
+import chinaJson from 'echarts/map/json/china.json';
+import worldJson from 'echarts/map/json/world.json';
 
-// 引入中国地图
-// // 按需引入中国地图和世界地图
-// import chinaJson from 'echarts/map/json/china.json';
-// import worldJson from 'echarts/map/json/world.json';
+import {getbytzbInfo, getbytzzfbnfo, getsjbytztqkInfo,getzgbytztqkInfo} from '@/api/statistics/report';
+
+echarts.registerMap('china', chinaJson);
+echarts.registerMap('world', worldJson);
+
+// 假数据
+const worldPieData = {
+  '中国': [
+    { value: 335, name: '分支 A' },
+    { value: 310, name: '分支 B' },
+    { value: 234, name: '分支 C' }
+  ],
+  '美国': [
+    { value: 200, name: '分支 X' },
+    { value: 150, name: '分支 Y' },
+    { value: 100, name: '分支 Z' }
+  ]
+};
+
+const chinaPieData = {
+  '北京市': [
+    { value: 150, name: '分支 A1' },
+    { value: 120, name: '分支 B1' },
+    { value: 80, name: '分支 C1' }
+  ],
+  '上海市': [
+    { value: 180, name: '分支 A2' },
+    { value: 130, name: '分支 B2' },
+    { value: 90, name: '分支 C2' }
+  ]
+};
+
+// 定义中国省份和世界国家的经纬度
+const chinaGeoCoordMap = {
+  '北京市': [116.46, 39.92],
+  '上海市': [121.48, 31.22]
+};
+
+const worldGeoCoordMap = {
+  '中国': [104.1954, 35.8617],
+  '美国': [-95.7129, 37.0902]
+};
 
-// // 注册地图
-// echarts.registerMap('china', chinaJson);
-// echarts.registerMap('world', worldJson);
 export default {
   name: 'SpatiotemporalDistribution',
   data() {
@@ -120,21 +173,35 @@ export default {
       pieData: [],
       mapData: [],
       description: '',
-      isChinaMap: false
-    }
+      isChinaMap: false,
+      growthChart: null,
+      barChart: null,
+      pieChart: null,
+      mapChart: null,
+      form:{
+  batch: "",
+  bytName: "",
+  country: "",
+  endDate: "",
+  model: "",
+  province: "",
+  startDate: "",
+  dateRange: []
+      }
+    };
   },
   mounted() {
-    this.initRegionOptions()
-    this.initCharts()
-    this.initMap()
+    // this.initRegionOptions();
+    this.fetchAllData()
+    this.initCharts();
   },
   methods: {
     async searchDisease(query) {
-      if (!query) return
-      this.diseaseLoading = true
+      if (!query) return;
+      this.diseaseLoading = true;
       // 假数据
-      this.diseaseOptions = ['疾病 A', '疾病 B', '疾病 C'].filter(item => item.includes(query))
-      this.diseaseLoading = false
+      this.diseaseOptions = ['疾病 A', '疾病 B', '疾病 C'].filter(item => item.includes(query));
+      this.diseaseLoading = false;
     },
     async initRegionOptions() {
       // 假数据
@@ -154,59 +221,86 @@ export default {
             { value: '美国', label: '美国' }
           ]
         }
-      ]
+      ];
     },
     async onRegionChange(val) {
       // 切换地图模式
       if (val && val.length && val[val.length - 1] === '中国') {
-        this.isChinaMap = true
+        this.isChinaMap = true;
       } else {
-        this.isChinaMap = false
+        this.isChinaMap = false;
       }
-      // 可根据地区动态加载下一级选项
+      this.fetchAllData();
     },
     async fetchAllData() {
-      // 联动下拉框
-      if (this.filters.disease) {
-        // 假数据
-        this.subtypeOptions = ['亚型 1', '亚型 2', '亚型 3']
+      if (this.form.dateRange.length  > 0) {
+      const [startDate, endDate] = this.form.dateRange;
+      this.form.startDate = startDate;
+      this.form.endDate = endDate;
       }
-      if (this.filters.subtype) {
-        // 假数据
-        this.branchOptions = ['分支 A', '分支 B', '分支 C']
+
+      // 获取数据
+      try {
+        const response = await getbytzbInfo(this.form);
+        const response2 = await getbytzzfbnfo(this.form);
+        const response3 = await getsjbytztqkInfo(this.form);
+        const response4 = await getzgbytztqkInfo(this.form);
+        this.growthData = response.data;
+        this.barData = response2.data;
+      } catch (error) {
+        console.error('获取数据失败:', error);
+        this.$message.error('获取数据失败,请稍后重试');
       }
-      // 获取假数据
-      this.growthData = [
-        { date: '2025-06-01', value: 10, ratio: 10, count: 5 },
-        { date: '2025-06-02', value: 20, ratio: 20, count: 8 }
-      ]
-      this.barData = [
-        { region: '中国', count: 100, percent: 60 },
-        { region: '美国', count: 80, percent: 40 }
-      ]
-      this.pieData = [
-        { value: 335, name: '分支 A' },
-        { value: 310, name: '分支 B' },
-        { value: 234, name: '分支 C' }
-      ]
-      this.mapData = [
-        { name: '中国', value: 100 },
-        { name: '美国', value: 80 }
-      ]
-      this.description = '这是一段假的数据分析文本。'
-      this.renderCharts()
+
+      // // 联动下拉框
+      // if (this.filters.disease) {
+      //   // 假数据
+      //   this.subtypeOptions = ['亚型 1', '亚型 2', '亚型 3'];
+      // }
+      // if (this.filters.subtype) {
+      //   // 假数据
+      //   this.branchOptions = ['分支 A', '分支 B', '分支 C'];
+      // }
+      // // 获取假数据
+      // this.growthData = [
+      //   { date: '2025-06-01', value: 10, ratio: 10, count: 5 },
+      //   { date: '2025-06-02', value: 20, ratio: 20, count: 8 }
+      // ];
+      // this.barData = [
+      //   { region: '中国', count: 100, percent: 60 },
+      //   { region: '美国', count: 80, percent: 40 }
+      // ];
+      // this.mapData = this.isChinaMap
+      //   ? Object.keys(chinaPieData).map(name => {
+      //       const geoCoord = chinaGeoCoordMap[name] || [0, 0]; // 若经纬度不存在,使用默认值 [0, 0]
+      //       return {
+      //         name,
+      //         value: [...geoCoord, chinaPieData[name].reduce((sum, item) => sum + item.value, 0)],
+      //         data: chinaPieData[name]
+      //       };
+      //     })
+      //   : Object.keys(worldPieData).map(name => {
+      //       const geoCoord = worldGeoCoordMap[name] || [0, 0]; // 若经纬度不存在,使用默认值 [0, 0]
+      //       return {
+      //         name,
+      //         value: [...geoCoord, worldPieData[name].reduce((sum, item) => sum + item.value, 0)],
+      //         data: worldPieData[name]
+      //       };
+      //     });
+      // this.description = '这是一段假的数据分析文本。';
+      this.renderCharts();
     },
     initCharts() {
-      this.growthChart = echarts.init(this.$refs.growthChart)
-      this.barChart = echarts.init(this.$refs.barChart)
-      this.pieChart = echarts.init(this.$refs.pieChart)
-      // this.mapChart = echarts.init(this.$refs.mapChart)
+      this.growthChart = echarts.init(this.$refs.growthChart);
+      this.barChart = echarts.init(this.$refs.barChart);
+      // this.pieChart = echarts.init(this.$refs.pieChart);
+      this.mapChart = echarts.init(this.$refs.mapContainer);
       window.addEventListener('resize', () => {
-        this.growthChart.resize()
-        this.barChart.resize()
-        this.pieChart.resize()
-        // this.mapChart.resize()
-      })
+        this.growthChart.resize();
+        this.barChart.resize();
+        // this.pieChart.resize();
+        this.mapChart.resize();
+      });
     },
     renderCharts() {
       // 子图一:增长率曲线
@@ -214,114 +308,151 @@ export default {
         tooltip: {
           trigger: 'axis',
           formatter: params => {
-            const p = params[0]
-            return `时间: ${p.name}<br/>累计占比: ${p.data.ratio}%<br/>分支数: ${p.data.count}`
+            const p = params[0];
+            return `时间: ${p.name}<br/>数量: ${p.value}`;
           }
         },
-        xAxis: { type: 'category', data: this.growthData.map(i => i.date) },
-        yAxis: { type: 'value' },
+        xAxis: {
+          type: 'category',
+          data: this.growthData.map(item => item.day)
+        },
+        yAxis: {
+          type: 'value',
+          name: '增长率'
+        },
         series: [{
-          data: this.growthData.map(i => ({ value: i.value, ratio: i.ratio, count: i.count })),
+          data: this.growthData.map(item => ({ value: item.per })),
           type: 'line',
           smooth: true
         }]
-      })
+      });
       // 子图二:分区条形图
+      // 提取所有地区名称
+      const regions = this.barData.length > 0
+        ? Object.keys(this.barData[0]).filter(key => key!== 'day')
+        : [];
+      // 提取所有日期
+      const dates = this.barData.map(item => item.day);
+
+      // 生成每个地区对应的系列数据
+      const seriesData = regions.map(region => ({
+        name: region,
+        type: 'bar',
+        data: this.barData.map(item => item[region])
+      }));
+
       this.barChart.setOption({
-        tooltip: { trigger: 'axis' },
-        legend: { data: ['数量', '百分比'] },
-        xAxis: { type: 'category', data: this.barData.map(i => i.region) },
-        yAxis: [{ type: 'value', name: '数量' }, { type: 'value', name: '百分比', max: 100 }],
-        series: [
-          {
-            name: '数量',
-            type: 'bar',
-            data: this.barData.map(i => i.count)
-          },
-          {
-            name: '百分比',
-            type: 'bar',
-            yAxisIndex: 1,
-            data: this.barData.map(i => i.percent)
-          }
-        ]
-      })
-      // 子图三:分支饼图
-      this.pieChart.setOption({
         tooltip: {
-          trigger: 'item',
-          formatter: '{b}: {c} ({d}%)'
+          trigger: 'axis',
+          axisPointer: {
+            type: 'cross',
+            crossStyle: {
+              color: '#999'
+            }
+          }
         },
-        series: [{
-          type: 'pie',
-          radius: '60%',
-          data: this.pieData
-        }]
-      })
+        legend: {
+          data: regions
+        },
+        xAxis: {
+          type: 'category',
+          // 使用动态日期数据
+          data: dates
+        },
+        yAxis: {
+          type: 'value',
+          name: '数量'
+        },
+        // 使用动态生成的系列数据
+        series: seriesData
+      });
+      // 子图三:分支饼图
+      // this.pieChart.setOption({
+      //   tooltip: {
+      //     trigger: 'item',
+      //     formatter: '{b}: {c} ({d}%)'
+      //   },
+      //   series: [{
+      //     type: 'pie',
+      //     radius: '60%',
+      //     data: this.pieData
+      //   }]
+      // })
       // // 地图
       // this.mapChart.setOption({
       //   tooltip: {
       //     trigger: 'item',
-      //     formatter: p => `${p.name}<br/>分支数: ${p.value || 0}`
+      //     formatter: p => {
+      //       if (p.seriesType === 'scatter') {
+      //         const total = p.data.value[2];
+      //         const pieData = p.data.data;
+      //         let tooltipStr = `${p.name}<br/>总分支数: ${total}<br/>`;
+      //         if (pieData) {
+      //           pieData.forEach(item => {
+      //             const percent = ((item.value / total) * 100).toFixed(2);
+      //             tooltipStr += `${item.name}: ${item.value} (${percent}%)<br/>`;
+      //           });
+      //         } else {
+      //           tooltipStr += '暂无分支数据<br/>';
+      //         }
+      //         return tooltipStr;
+      //       }
+      //       return p.name;
+      //     }
       //   },
-      //   series: [{
-      //     type: 'map',
+      //   // 添加 geo 组件,用于显示地图背景
+      //   geo: {
       //     map: this.isChinaMap ? 'china' : 'world',
       //     roam: true,
-      //     data: this.mapData
-      //   }]
-      // })
+      //     // 设置中国地图和世界地图的中心位置和缩放级别
+      //     center: this.isChinaMap ? [104.1954, 35.8617] : [10, 10],
+      //     zoom: this.isChinaMap ? 2 : 1.2,
+      //     label: {
+      //       emphasis: {
+      //         show: false
+      //       }
+      //     },
+      //     itemStyle: {
+      //       normal: {
+      //         // 修改为浅色背景,这里使用浅灰色作为示例
+      //         areaColor: '#f0f0f0',
+      //         borderColor: '#999'
+      //       },
+      //       emphasis: {
+      //         // 修改为稍深一点的浅色,用于鼠标悬停状态
+      //         areaColor: '#e0e0e0'
+      //       }
+      //     }
+      //   },
+      //   series: [
+      //     {
+      //       type: 'scatter',
+      //       coordinateSystem: 'geo',
+      //       data: this.mapData,
+      //       symbolSize: val => Math.sqrt(val[2]) * 2,
+      //       label: {
+      //         show: true,
+      //         formatter: '{b}'
+      //       },
+      //       itemStyle: {
+      //         color: 'rgba(255, 0, 0, 0.8)'
+      //       },
+      //       emphasis: {
+      //         itemStyle: {
+      //           color: 'red'
+      //         }
+      //       }
+      //     }
+      //   ]
+      // });
       // // 地图点击切换
-      // this.mapChart.off('click')
+      // this.mapChart.off('click');
       // this.mapChart.on('click', params => {
       //   if (!this.isChinaMap && params.name === '中国') {
-      //     this.isChinaMap = true
-      //     this.fetchAllData()
+      //     this.isChinaMap = true;
+      //     this.fetchAllData();
       //   }
-      // })
-    },
-    async initMap() {
-      try {
-        // 检查 ref 是否正确获取到 DOM 元素
-        if (!this.$refs.mapContainer) {
-          console.error('未找到地图容器 DOM 元素');
-          return;
-        }
-        const AMap = await AMapLoader.load({
-          key: 'ddcbab53e51ba9adfdcf281a32d513f0', // 替换为你的高德 Key
-          version: '2.0',
-          plugins: []
-        });
-        this.map = new AMap.Map(this.$refs.mapContainer, {
-          zoom: 4, // 初始缩放级别
-          center: [105.403119, 38.028658] // 初始地图中心点
-        });
-      } catch (error) {
-        console.error('地图初始化失败:', error);
-      }
-    },
-    // async fetchAllData() {
-    //   // ... 已有代码 ...
-    //   this.updateMap();
-    // },
-    updateMap() {
-      // 清空现有覆盖物
-      this.map.clearMap();
-      this.mapData.forEach(item => {
-        const marker = new AMap.Marker({
-          position: this.getCoordinates(item.name), // 根据地区名称获取经纬度
-          title: `${item.name}: ${item.value} 分支数`
-        });
-        marker.setMap(this.map);
-      });
-    },
-    getCoordinates(regionName) {
-      // 简单示例,实际需要根据地区名称获取准确经纬度
-      const coordinatesMap = {
-        '中国': [105.403119, 38.028658],
-        '美国': [-95.712891, 37.09024]
-      };
-      return coordinatesMap[regionName] || [0, 0];
+      // });
     }
   }
 };

+ 229 - 58
src/views/statistics/popularity-prediction/index.vue

@@ -2,76 +2,28 @@
   <div class="popularity-prediction">
     <div class="tree-map-container">
       <div class="phylo-tree">
-        <h3>病毒进化树</h3>
-        <div id="phyloTree" ref="treeContainer" style="width: 600px; height: 500px;"></div>
+        <!-- <h3>病毒进化树</h3> -->
+        <div id="phyloTree" ref="treeContainer" style="width: 50vw; height: 500px;"></div>
       </div>
       <div class="world-map">
-        <h3>世界地图病毒预测图</h3>
-        <div id="virusMap" style="width: 600px; height: 500px;"></div>
+        <!-- <h3>世界地图病毒预测图</h3> -->
+        <div id="virusMap" style="width: 50vw; height: 500px;"></div>
       </div>
     </div>
   </div>
 </template>
 
 <script>
-import * as d3 from "d3";
-import { phylotree } from "phylotree";
-import "phylotree/dist/phylotree.css";
 import * as echarts from 'echarts';
+// 引入世界地图数据
+import worldJson from 'echarts/map/json/world.json';
+
+// 注册世界地图
+echarts.registerMap('world', worldJson);
 
 export default {
   name: 'PopularityPrediction',
-  mounted() {
-    // 延迟 500 毫秒渲染,确保容器尺寸正确
-    setTimeout(() => {
-      this.renderTree();
-      // this.renderVirusMap();
-    }, 500);
-  },
   methods: {
-    renderTree() {
-      //接受的接受渲染phylotree的文件
-let test_string = "((Human:0.1,Chimp:0.2):0.3,Gorilla:0.4)"
-let tree = new phylotree(test_string);
-console.log(tree),'111';
-tree.render({
-    container: "#phyloTree",
-    "draw-size-bubbles": true,//在叶子节点展示圆圈
-    'height':'800',
-    "width":'800',
-    "left-right-spacing": "fit-to-size",    //从左到右确定布局大小。默认为"fixed-step".
-    "top-bottom-spacing":"fit-to-size",      //从上到下确定布局大小。默认为"fixed-step".
-    "align-tips":true,  //确定提示名称是否对齐。默认为false。
-    "zoom": false,    //确定是否启用缩放。默认为false.
-    "brush":false,    //是否应激活画笔。默认为true.
-    "show-scale":true,    //是否显示分支长度的比例尺
-});
-console.log(tree,'222');
-
-      // const newickString = "((A:0.1,B:0.2):0.3,C:0.4);";
-      // const tree = new phylotree(newickString, {
-      //   branch_scale: 100,
-      //   collapsed: false
-      // });
-
-      // const container = this.$refs.treeContainer;
-      // if (!container) {
-      //   console.error('未找到进化树容器元素');
-      //   return;
-      // }
-
-      // const svg = d3.select(container)
-      //   .append("svg")
-      //   .attr("width", "100%")
-      //   .attr("height", "100%");
-
-      // try {
-      //   tree.render(svg);
-      //   console.log('进化树渲染成功');
-      // } catch (error) {
-      //   console.error('渲染进化树出错:', error);
-      // }
-    },
     renderVirusMap() {
       // 示例病毒预测数据
       const mapData = [
@@ -96,13 +48,232 @@ console.log(tree,'222');
           {
             name: '病毒预测',
             type: 'map',
+            // 确保地图名称正确
             map: 'world',
             roam: true,
-            data: mapData
+            // 处理空数据情况
+            silent: false,
+            // 确保数据正确绑定
+            data: mapData.map(item => ({
+              name: this.convertCountryName(item.name),
+              value: item.value
+            })),
+            // 显示地图标签
+            label: {
+              show: false,
+            }
+          }
+        ]
+      });
+    },
+    // 转换国家名称为 ECharts 世界地图支持的名称
+    convertCountryName(name) {
+      const nameMap = {
+        'China': 'China',
+        'United States': 'United States of America',
+        'Brazil': 'Brazil',
+        'India': 'India'
+      };
+      return nameMap[name] || name;
+    },
+    renderPhyloTree() {
+      const strainColors = {
+        A: '#5470C6',
+        B: '#91CC75',
+        C: '#FAC858',
+        D: '#EE6666',
+        E: '#73C0DE',
+        F: '#3BA272',
+        G: '#FC8452',
+        H: '#9A60B4',
+        I: '#EA7CCC',
+        J: '#FFA500'
+      };
+      const strains = Object.keys(strainColors);
+
+      // 生成树结构
+      function createExampleTree(rootStrain, rootYear, rootEndYear, depth = 0, maxDepth = 5, usedSet = new Set()) {
+        if (depth > maxDepth) return null;
+        const key = `${rootStrain}_${rootYear}`;
+        if (usedSet.has(key)) return null; // 跳过重复
+        usedSet.add(key);
+
+        const node = {
+          name: `病毒${rootStrain}`,
+          strain: rootStrain,
+          year: rootYear,
+          endYear: rootEndYear,
+          children: []
+        };
+        if (depth < maxDepth) {
+          const childCount = Math.floor(Math.random() * 2) + 1;
+          for (let i = 0; i < childCount; i++) {
+            const nextStrain = strains[(strains.indexOf(rootStrain) + i + 1) % strains.length];
+            const childYear = Math.min(rootEndYear - 1, rootYear + 1 + Math.floor(Math.random() * 3));
+            const childEndYear = Math.min(rootEndYear, childYear + 3 + Math.floor(Math.random() * 6));
+            const child = createExampleTree(nextStrain, childYear, childEndYear, depth + 1, maxDepth, usedSet);
+            if (child) node.children.push(child);
+          }
+        }
+        return node;
+      }
+
+      // 生成5条主干
+      const roots = [];
+      for (let i = 0; i < 5; i++) {
+        const strain = strains[i % strains.length];
+        const startYear = 2010 + Math.floor(Math.random() * 3);
+        const endYear = startYear + 8 + Math.floor(Math.random() * 5);
+        roots.push(createExampleTree(strain, startYear, endYear, 0, 5, new Set()));
+      }
+
+      // 展平树结构,分配y坐标
+      let yIndex = 0;
+      const lines = [];
+      function flatten(node, parentY = null, parentName = null) {
+        const myY = yIndex++;
+        lines.push({
+          name: `${node.name}(${node.year}~${node.endYear})`,
+          strain: node.strain,
+          year: node.year,
+          endYear: node.endYear,
+          y: myY,
+          parentName // 记录父节点名称
+        });
+        if (node.children && node.children.length) {
+          node.children.forEach(child => flatten(child, myY, `${node.name}(${node.year}~${node.endYear})`));
+        }
+      }
+
+      // 主干之间插入空行,增加间距
+      roots.forEach((root, idx) => {
+        flatten(root, null, null);
+        if (idx !== roots.length - 1) {
+          // 每两个主干之间插入2个空行
+          yIndex += 3;
+        }
+      });
+
+      // 图例
+      const legendData = strains.map(k => `病毒${k}`);
+
+      const chart = echarts.init(this.$refs.treeContainer);
+      chart.setOption({
+        title: { text: '病毒进化树(横坐标为年份)', left: 'center' },
+        tooltip: {
+          trigger: 'item',
+          formatter: params => params.data ? params.data.name : params.name
+        },
+        legend: {
+          data: legendData,
+          orient: 'vertical',
+          right: 10,
+          top: 60,
+          formatter: name => `{a|●} {b|${name}}`,
+          textStyle: {
+            rich: {
+              a: { color: (params) => params, fontSize: 18 },
+              b: { color: '#333', fontSize: 14 }
+            }
+          }
+        },
+        grid: { left: 80, right: 120, top: 60, bottom: 40 },
+        xAxis: {
+          type: 'value',
+          min: 2010,
+          max: 2025,
+          interval: 1,
+          name: '年份',
+          axisLabel: { formatter: '{value}' }
+        },
+        yAxis: {
+          type: 'category',
+          data: lines.map(l => l.name),
+          show: false,
+          axisLabel: { show: false },
+          splitLine: { show: false }
+        },
+        series: [
+          // 横线
+          {
+            type: 'custom',
+            renderItem: function(params, api) {
+              const y = api.coord([0, params.dataIndex])[1];
+              const x1 = api.coord([lines[params.dataIndex].year, params.dataIndex])[0];
+              const x2 = api.coord([lines[params.dataIndex].endYear, params.dataIndex])[0];
+              const parentY = (() => {
+                // 查找父节点的y坐标
+                const parentName = lines[params.dataIndex].parentName;
+                if (!parentName) return null;
+                const parentIdx = lines.findIndex(l => l.name === parentName);
+                if (parentIdx === -1) return null;
+                return api.coord([lines[params.dataIndex].year, parentIdx])[1];
+              })();
+
+              const children = [];
+
+              // 折线:所有节点都加竖线
+              let startX;
+              let endX = x1;
+              let startY = y;
+              if (parentY !== null && parentY !== y) {
+                // 分支节点:父节点到当前节点y
+                startX = x1;
+                startY = parentY;
+              } else {
+                // 主干节点:从x轴(y轴0刻度线)到当前节点
+                startX = x1;
+                // 获取x轴像素y坐标
+                const xAxisY = api.coord([lines[params.dataIndex].year, -0.8])[1];
+                startY = xAxisY;
+              }
+              children.push(
+                {
+                  type: 'line',
+                  shape: { x1: startX, y1: startY, x2: endX, y2: y },
+                  style: {
+                    stroke: strainColors[lines[params.dataIndex].strain],
+                    lineWidth: 4
+                  }
+                }
+              );
+
+              // 横线:当前节点生命周期
+              children.push({
+                type: 'line',
+                shape: { x1, y1: y, x2, y2: y },
+                style: {
+                  stroke: strainColors[lines[params.dataIndex].strain],
+                  lineWidth: 4
+                }
+              });
+
+              // 终点圆点
+              children.push({
+                type: 'circle',
+                shape: { cx: x2, cy: y, r: 7 },
+                style: {
+                  fill: strainColors[lines[params.dataIndex].strain],
+                  stroke: '#fff',
+                  lineWidth: 2
+                }
+              });
+
+              return { type: 'group', children };
+            },
+            data: lines,
+            encode: { x: [ 'year', 'endYear' ], y: 'y' }
           }
         ]
       });
     }
+  },
+  mounted() {
+    // 延迟 500 毫秒渲染,确保容器尺寸正确
+    setTimeout(() => {
+      this.renderVirusMap();
+      this.renderPhyloTree(); // 新增
+    }, 500);
   }
 };
 </script>

+ 70 - 37
src/views/statistics/sample-statistics/index.vue

@@ -10,10 +10,10 @@
       </el-col>
       <el-col :span="12" class="filter-col">
         <el-select v-model="selectedRange" placeholder="选择时间范围" @change="onRangeChange" size="small">
-          <el-option label="24小时" value="24h"></el-option>
-          <el-option label="三天" value="3d"></el-option>
-          <el-option label="五天" value="5d"></el-option>
-          <el-option label="七天" value="7d"></el-option>
+          <el-option label="近24小时" value="1"></el-option>
+          <el-option label="近3天(72小时)" value="2"></el-option>
+          <el-option label="近7天" value="3"></el-option>
+          <el-option label="近30天" value="4"></el-option>
         </el-select>
       </el-col>
     </el-row>
@@ -39,11 +39,12 @@
 
 <script>
 import * as echarts from 'echarts';
+import {getjcwzbInfo, getyblxtjInfo, getybtjInfo} from '@/api/statistics/report';
 export default {
   name: 'SampleStatistics',
   data() {
     return {
-      selectedRange: '24h',
+      selectedRange: '1',
       totalSamples: 1234,
       lineChart: null,
       typePieChart: null,
@@ -61,41 +62,73 @@ export default {
     onRangeChange() {
       this.fetchData()
     },
-    fetchData() {
-      // 模拟数据,根据selectedRange更新
-      const now = new Date()
-      let days = 1
-      if (this.selectedRange === '3d') days = 3
-      if (this.selectedRange === '5d') days = 5
-      if (this.selectedRange === '7d') days = 7
-
-      // 生成日期
-      const xData = []
+   async fetchData() {
+    const response =  await getjcwzbInfo(this.selectedRange)
+     if (response.data ) {
+          let arr = []
+          for(let k in response.data) {
+            arr.push({
+              name: k,
+              value: response.data[k]
+            })
+          }
+          this.detectedPieData = [...arr];
+        }
+   const res = await getyblxtjInfo(this.selectedRange)
+       if (res.data ) {
+          let arr = []
+          for(let k in res.data) {
+            arr.push({
+              name: k,
+              value: res.data[k]
+            })
+          }
+          this.typePieData = [...arr];
+        }
+     const result = await getybtjInfo(this.selectedRange)
       const analysisData = []
       const adoptionData = []
-      for (let i = days - 1; i >= 0; i--) {
-        const d = new Date(now)
-        d.setDate(now.getDate() - i)
-        xData.push(`${d.getMonth() + 1}-${d.getDate()}`)
-        analysisData.push(Math.floor(Math.random() * 100 + 100))
-        adoptionData.push(Math.floor(Math.random() * 80 + 50))
+      // 生成日期
+      const xData = []
+      if (result.data ) {
+        result.data.forEach(item => {
+          analysisData.push(item.fxl)
+          adoptionData.push(item.ybl)
+          xData.push(item.hour)
+        })
       }
+      // 模拟数据,根据selectedRange更新
+      // const now = new Date()
+      // let days = 1
+      // if (this.selectedRange === '2') days = 3
+      // if (this.selectedRange === '3') days = 7
+      // if (this.selectedRange === '4') days = 30
+
+
+
+      // for (let i = days - 1; i >= 0; i--) {
+      //   const d = new Date(now)
+      //   d.setDate(now.getDate() - i)
+      //   xData.push(`${d.getMonth() + 1}-${d.getDate()}`)
+      //   analysisData.push(Math.floor(Math.random() * 100 + 100))
+      //   adoptionData.push(Math.floor(Math.random() * 80 + 50))
+      // }
       this.lineData = { xData, analysisData, adoptionData }
 
-      // 饼图数据
-      this.typePieData = [
-        { value: 335, name: '血液' },
-        { value: 310, name: '尿液' },
-        { value: 234, name: '唾液' },
-        { value: 135, name: '其他' }
-      ]
-      this.detectedPieData = [
-        { value: 400, name: '物质A' },
-        { value: 335, name: '物质B' },
-        { value: 310, name: '物质C' },
-        { value: 234, name: '其他' }
-      ]
-      this.totalSamples = analysisData.reduce((a, b) => a + b, 0)
+      // // 饼图数据
+      // this.typePieData = [
+      //   { value: 335, name: '血液' },
+      //   { value: 310, name: '尿液' },
+      //   { value: 234, name: '唾液' },
+      //   { value: 135, name: '其他' }
+      // ]
+      // this.detectedPieData = [
+      //   { value: 400, name: '物质A' },
+      //   { value: 335, name: '物质B' },
+      //   { value: 310, name: '物质C' },
+      //   { value: 234, name: '其他' }
+      // ]
+      // this.totalSamples = analysisData.reduce((a, b) => a + b, 0)
       this.updateCharts()
     },
     initCharts() {
@@ -107,7 +140,7 @@ export default {
       // 折线图
       this.lineChart.setOption({
         tooltip: { trigger: 'axis' },
-        legend: { data: ['数据分析量', '收量'] },
+        legend: { data: ['数据分析量', '收量'] },
         xAxis: { type: 'category', data: this.lineData.xData },
         yAxis: { type: 'value', name: '数量' },
         series: [
@@ -118,7 +151,7 @@ export default {
             data: this.lineData.analysisData
           },
           {
-            name: '收量',
+            name: '收量',
             type: 'line',
             smooth: true,
             data: this.lineData.adoptionData