浏览代码

部分功能

cynthia-qin 2 周之前
父节点
当前提交
b049d1422c

+ 7 - 1
package.json

@@ -24,11 +24,13 @@
     "url": "https://gitee.com/y_project/RuoYi-Vue.git"
   },
   "dependencies": {
+    "@amap/amap-jsapi-loader": "^1.0.1",
     "@riophae/vue-treeselect": "0.4.0",
     "axios": "0.28.1",
     "clipboard": "2.0.8",
     "core-js": "3.37.1",
-    "echarts": "^5.4.0",
+    "d3": "^7.9.0",
+    "echarts": "^5.6.0",
     "element-ui": "2.15.14",
     "file-saver": "2.0.5",
     "fuse.js": "6.4.3",
@@ -36,12 +38,15 @@
     "js-beautify": "1.13.0",
     "js-cookie": "3.0.1",
     "jsencrypt": "3.0.0-rc.1",
+    "leaflet": "^1.9.4",
     "nprogress": "0.2.0",
+    "phylotree": "^2.1.0",
     "quill": "2.0.2",
     "screenfull": "5.0.2",
     "sortablejs": "1.10.2",
     "splitpanes": "2.4.1",
     "vue": "2.6.12",
+    "vue-amap": "^0.5.10",
     "vue-count-to": "1.0.13",
     "vue-cropper": "0.5.5",
     "vue-router": "3.4.9",
@@ -50,6 +55,7 @@
     "xlsx": "^0.18.5"
   },
   "devDependencies": {
+    "@types/leaflet": "^1.9.18",
     "@vue/cli-plugin-babel": "4.4.6",
     "@vue/cli-service": "4.4.6",
     "babel-plugin-dynamic-import-node": "2.3.3",

+ 87 - 0
src/api/readData/readData.js

@@ -0,0 +1,87 @@
+import request from '@/utils/request'
+
+// 根据实验编号获取病原体信息
+export async function getBYTInfo(id) {
+    return request({
+    url: '/read/readData/getBYT/' + id,
+    method: 'get',
+  })
+}
+// 根据实验编号获取过滤病原体信息
+export async function getBYTFilterInfo(id) {
+    return request({
+    url: '/read/readData/getBYTFilter/' + id,
+    method: 'get',
+  })
+}
+// 根据实验编号获取毒力基因信息
+export async function getDLJYInfo(id) {
+    return request({
+    url: '/read/readData/getDLJY/' + id,
+    method: 'get',
+  })
+}
+// 根据实验编号获取过滤毒力基因信息
+export async function getDLJYFilterInfo(id) {
+    return request({
+    url: '/read/readData/getDLJYFilter/' + id,
+    method: 'get',
+  })
+}
+// 根据实验编号获取点突变耐药基因信息
+export async function getDTBNYJYfo(id) {
+    return request({
+    url: '/read/readData/getDTBNYJY/' + id,
+    method: 'get',
+  })
+}
+// 根据实验编号获取过滤点突变耐药基因信息
+export async function getDTBNYJYFilterInfo(id) {
+    return request({
+    url: '/read/readData/getDTBNYJYFilter/' + id,
+    method: 'get',
+  })
+}
+// 根据实验编号获取耐药基因信息
+export async function getNYJYInfo(id) {
+    return request({
+    url: '/read/readData/getNYJY/' + id,
+    method: 'get',
+  })
+}
+// 根据实验编号获取过滤耐药基因信息
+export async function getNYJYFilterInfo(id) {
+    return request({
+    url: '/read/readData/getNYJYFilter/' + id,
+    method: 'get',
+  })
+}
+// 根据实验编号获取患者信息
+export async function getPatientInfo(id) {
+    return request({
+    url: '/read/readData/getPatient/' + id,
+    method: 'get',
+  })
+}
+// 根据实验编号获取解读备注信息
+export async function getRemarkInfo(id) {
+    return request({
+    url: '/read/readData/getRemark/' + id,
+    method: 'get',
+  })
+}
+// 根据实验编号获取样品信息
+export async function getSampleInfo(id) {
+    return request({
+    url: '/read/readData/getSample/' + id,
+    method: 'get',
+  })
+}
+// 保存过滤信息
+export async function postsaveData(data) {
+    return request({
+    url: '/read/readData/saveData',
+    method: 'post',
+    data: data
+  })
+}

+ 9 - 9
src/views/data/dataBase/byt.vue

@@ -321,29 +321,29 @@
       <el-table-column label="分类类型" align="center" prop="isolate" />
       <el-table-column label="拼装级别" align="center" prop="assemblyLevel" />
       <el-table-column label="基因组级别" align="center" prop="genomeRep" />
-      <el-table-column label="seq_rel_date" align="center" prop="seqRelDate" width="180">
+      <!-- <el-table-column label="seq_rel_date" align="center" prop="seqRelDate" width="180">
         <template slot-scope="scope">
           <span>{{ parseTime(scope.row.seqRelDate, '{y}-{m}-{d}') }}</span>
         </template>
-      </el-table-column>
-      <el-table-column label="ASM编号" align="center" prop="asmName" />
+      </el-table-column> -->
+      <!-- <el-table-column label="ASM编号" align="center" prop="asmName" />
       <el-table-column label="gbrs_paired_asm" align="center" prop="gbrsPairedAsm" />
-      <el-table-column label="paired_asm_comp" align="center" prop="pairedAsmComp" />
-      <el-table-column label="来源地址" align="center" prop="ftpPath" />
-      <el-table-column label="excluded_from_refseq" align="center" prop="excludedFromRefseq" />
+      <el-table-column label="paired_asm_comp" align="center" prop="pairedAsmComp" /> -->
+      <el-table-column label="来源地址" align="center" prop="ftpPath" width="200"/>
+      <!-- <el-table-column label="excluded_from_refseq" align="center" prop="excludedFromRefseq" /> -->
       <el-table-column label="装配类型" align="center" prop="assemblyType" />
       <el-table-column label="病源类型" align="center" prop="group" />
       <el-table-column label="基因组大小" align="center" prop="genomeSize" />
       <el-table-column label="基因组大小" align="center" prop="genomeSizeUngapped" />
-      <el-table-column label="GC比例" align="center" prop="gcPercent" />
+      <!-- <el-table-column label="GC比例" align="center" prop="gcPercent" />
       <el-table-column label="replicon_count" align="center" prop="repliconCount" />
       <el-table-column label="scaffold_count" align="center" prop="scaffoldCount" />
-      <el-table-column label="contig_count" align="center" prop="contigCount" />
+      <el-table-column label="contig_count" align="center" prop="contigCount" /> -->
       <el-table-column label="总基因数" align="center" prop="totalGeneCount" />
       <el-table-column label="翻译蛋白基因数" align="center" prop="proteinCodingGeneCount" />
       <el-table-column label="非编码基因数" align="center" prop="nonCodingGeneCount" />
       <el-table-column label="中文名" align="center" prop="nameCn" />
-      <el-table-column label="定义" align="center" prop="defined" />
+      <!-- <el-table-column label="定义" align="center" prop="defined" /> -->
       <el-table-column label="序列来源" align="center" prop="seqSource" />
       <el-table-column label="解释来源" align="center" prop="express" />
       <el-table-column label="文件路径" align="center" prop="filePath" />

+ 7 - 7
src/views/data/dataBase/dljy.vue

@@ -321,29 +321,29 @@
       <el-table-column label="分类类型" align="center" prop="isolate" />
       <el-table-column label="拼装级别" align="center" prop="assemblyLevel" />
       <el-table-column label="基因组级别" align="center" prop="genomeRep" />
-      <el-table-column label="seq_rel_date" align="center" prop="seqRelDate" width="180">
+      <!-- <el-table-column label="seq_rel_date" align="center" prop="seqRelDate" width="180">
         <template slot-scope="scope">
           <span>{{ parseTime(scope.row.seqRelDate, '{y}-{m}-{d}') }}</span>
         </template>
       </el-table-column>
       <el-table-column label="ASM编号" align="center" prop="asmName" />
       <el-table-column label="gbrs_paired_asm" align="center" prop="gbrsPairedAsm" />
-      <el-table-column label="paired_asm_comp" align="center" prop="pairedAsmComp" />
-      <el-table-column label="来源地址" align="center" prop="ftpPath" />
-      <el-table-column label="excluded_from_refseq" align="center" prop="excludedFromRefseq" />
+      <el-table-column label="paired_asm_comp" align="center" prop="pairedAsmComp" /> -->
+      <el-table-column label="来源地址" align="center" prop="ftpPath" width="200"/>
+      <!-- <el-table-column label="excluded_from_refseq" align="center" prop="excludedFromRefseq" /> -->
       <el-table-column label="装配类型" align="center" prop="assemblyType" />
       <el-table-column label="病源类型" align="center" prop="group" />
       <el-table-column label="基因组大小" align="center" prop="genomeSize" />
       <el-table-column label="基因组大小" align="center" prop="genomeSizeUngapped" />
-      <el-table-column label="GC比例" align="center" prop="gcPercent" />
+      <!-- <el-table-column label="GC比例" align="center" prop="gcPercent" />
       <el-table-column label="replicon_count" align="center" prop="repliconCount" />
       <el-table-column label="scaffold_count" align="center" prop="scaffoldCount" />
-      <el-table-column label="contig_count" align="center" prop="contigCount" />
+      <el-table-column label="contig_count" align="center" prop="contigCount" /> -->
       <el-table-column label="总基因数" align="center" prop="totalGeneCount" />
       <el-table-column label="翻译蛋白基因数" align="center" prop="proteinCodingGeneCount" />
       <el-table-column label="非编码基因数" align="center" prop="nonCodingGeneCount" />
       <el-table-column label="中文名" align="center" prop="nameCn" />
-      <el-table-column label="定义" align="center" prop="defined" />
+      <!-- <el-table-column label="定义" align="center" prop="defined" /> -->
       <el-table-column label="序列来源" align="center" prop="seqSource" />
       <el-table-column label="解释来源" align="center" prop="express" />
       <el-table-column label="文件路径" align="center" prop="filePath" />

+ 7 - 7
src/views/data/dataBase/dtbnyjy.vue

@@ -321,29 +321,29 @@
       <el-table-column label="分类类型" align="center" prop="isolate" />
       <el-table-column label="拼装级别" align="center" prop="assemblyLevel" />
       <el-table-column label="基因组级别" align="center" prop="genomeRep" />
-      <el-table-column label="seq_rel_date" align="center" prop="seqRelDate" width="180">
+      <!-- <el-table-column label="seq_rel_date" align="center" prop="seqRelDate" width="180">
         <template slot-scope="scope">
           <span>{{ parseTime(scope.row.seqRelDate, '{y}-{m}-{d}') }}</span>
         </template>
       </el-table-column>
       <el-table-column label="ASM编号" align="center" prop="asmName" />
       <el-table-column label="gbrs_paired_asm" align="center" prop="gbrsPairedAsm" />
-      <el-table-column label="paired_asm_comp" align="center" prop="pairedAsmComp" />
-      <el-table-column label="来源地址" align="center" prop="ftpPath" />
-      <el-table-column label="excluded_from_refseq" align="center" prop="excludedFromRefseq" />
+      <el-table-column label="paired_asm_comp" align="center" prop="pairedAsmComp" /> -->
+      <el-table-column label="来源地址" align="center" prop="ftpPath" width="200"/>
+      <!-- <el-table-column label="excluded_from_refseq" align="center" prop="excludedFromRefseq" /> -->
       <el-table-column label="装配类型" align="center" prop="assemblyType" />
       <el-table-column label="病源类型" align="center" prop="group" />
       <el-table-column label="基因组大小" align="center" prop="genomeSize" />
       <el-table-column label="基因组大小" align="center" prop="genomeSizeUngapped" />
-      <el-table-column label="GC比例" align="center" prop="gcPercent" />
+      <!-- <el-table-column label="GC比例" align="center" prop="gcPercent" />
       <el-table-column label="replicon_count" align="center" prop="repliconCount" />
       <el-table-column label="scaffold_count" align="center" prop="scaffoldCount" />
-      <el-table-column label="contig_count" align="center" prop="contigCount" />
+      <el-table-column label="contig_count" align="center" prop="contigCount" /> -->
       <el-table-column label="总基因数" align="center" prop="totalGeneCount" />
       <el-table-column label="翻译蛋白基因数" align="center" prop="proteinCodingGeneCount" />
       <el-table-column label="非编码基因数" align="center" prop="nonCodingGeneCount" />
       <el-table-column label="中文名" align="center" prop="nameCn" />
-      <el-table-column label="定义" align="center" prop="defined" />
+      <!-- <el-table-column label="定义" align="center" prop="defined" /> -->
       <el-table-column label="序列来源" align="center" prop="seqSource" />
       <el-table-column label="解释来源" align="center" prop="express" />
       <el-table-column label="文件路径" align="center" prop="filePath" />

+ 7 - 7
src/views/data/dataBase/nyjy.vue

@@ -321,29 +321,29 @@
       <el-table-column label="分类类型" align="center" prop="isolate" />
       <el-table-column label="拼装级别" align="center" prop="assemblyLevel" />
       <el-table-column label="基因组级别" align="center" prop="genomeRep" />
-      <el-table-column label="seq_rel_date" align="center" prop="seqRelDate" width="180">
+      <!-- <el-table-column label="seq_rel_date" align="center" prop="seqRelDate" width="180">
         <template slot-scope="scope">
           <span>{{ parseTime(scope.row.seqRelDate, '{y}-{m}-{d}') }}</span>
         </template>
       </el-table-column>
       <el-table-column label="ASM编号" align="center" prop="asmName" />
       <el-table-column label="gbrs_paired_asm" align="center" prop="gbrsPairedAsm" />
-      <el-table-column label="paired_asm_comp" align="center" prop="pairedAsmComp" />
-      <el-table-column label="来源地址" align="center" prop="ftpPath" />
-      <el-table-column label="excluded_from_refseq" align="center" prop="excludedFromRefseq" />
+      <el-table-column label="paired_asm_comp" align="center" prop="pairedAsmComp" /> -->
+      <el-table-column label="来源地址" align="center" prop="ftpPath" width="200"/>
+      <!-- <el-table-column label="excluded_from_refseq" align="center" prop="excludedFromRefseq" /> -->
       <el-table-column label="装配类型" align="center" prop="assemblyType" />
       <el-table-column label="病源类型" align="center" prop="group" />
       <el-table-column label="基因组大小" align="center" prop="genomeSize" />
       <el-table-column label="基因组大小" align="center" prop="genomeSizeUngapped" />
-      <el-table-column label="GC比例" align="center" prop="gcPercent" />
+      <!-- <el-table-column label="GC比例" align="center" prop="gcPercent" />
       <el-table-column label="replicon_count" align="center" prop="repliconCount" />
       <el-table-column label="scaffold_count" align="center" prop="scaffoldCount" />
-      <el-table-column label="contig_count" align="center" prop="contigCount" />
+      <el-table-column label="contig_count" align="center" prop="contigCount" /> -->
       <el-table-column label="总基因数" align="center" prop="totalGeneCount" />
       <el-table-column label="翻译蛋白基因数" align="center" prop="proteinCodingGeneCount" />
       <el-table-column label="非编码基因数" align="center" prop="nonCodingGeneCount" />
       <el-table-column label="中文名" align="center" prop="nameCn" />
-      <el-table-column label="定义" align="center" prop="defined" />
+      <!-- <el-table-column label="定义" align="center" prop="defined" /> -->
       <el-table-column label="序列来源" align="center" prop="seqSource" />
       <el-table-column label="解释来源" align="center" prop="express" />
       <el-table-column label="文件路径" align="center" prop="filePath" />

+ 58 - 1
src/views/sample/sampleExperiment/filterReport.vue

@@ -116,6 +116,7 @@
 </template>
 
 <script>
+import { getBYTInfo,getBYTFilterInfo,getDLJYInfo,getDLJYFilterInfo,getDTBNYJYfo,getDTBNYJYFilterInfo,getNYJYInfo,getNYJYFilterInfo,getPatientInfo,getRemarkInfo,getSampleInfo,postsaveData} from '@/api/readData/readData';
 export default {
   name: 'FilterReport',
   data() {
@@ -134,7 +135,24 @@ export default {
       ],
       tempSelectedRows: [],
       selectedRows: [],
-      selectAll: false
+      selectAll: false,
+      experimentId:this.$route.params.reportId || '',
+      BYTData: [],
+      BYTFilterData: [],
+      DLJYData: [],
+      DLJYFilterData: [],
+      DTBNYJYData: [],
+      DTBNYJYFilterData: [],
+      NYJYData: [],
+      NYJYFilterData: [],
+      patientData: {},
+      remarkData: '',
+    }
+  },
+  watch: {
+    '$route.params.reportId'(newVal) {
+      this.experimentId = newVal;
+      this.init();
     }
   },
   computed: {
@@ -145,7 +163,46 @@ export default {
       return this.allData.filter(item => !this.selectedRows.some(selected => selected.id === item.id));
     }
   },
+  mounted() {
+    this.init();
+  },
   methods: {
+    init(){
+      console.log('Experiment ID:', this.$route);
+      getBYTInfo(this.experimentId).then(res=>{
+        console.log(res);
+      })
+      getBYTFilterInfo(this.experimentId).then(res=>{
+        console.log(res);
+      })
+      getDLJYInfo(this.experimentId).then(res=>{
+        console.log(res);
+      })
+      getDLJYFilterInfo(this.experimentId).then(res=>{
+        console.log(res);
+      })
+      getDTBNYJYfo(this.experimentId).then(res=>{
+        console.log(res);
+      })
+      getDTBNYJYFilterInfo(this.experimentId).then(res=>{
+        console.log(res);
+      })
+      getNYJYInfo(this.experimentId).then(res=>{
+        console.log(res);
+      })
+      getNYJYFilterInfo(this.experimentId).then(res=>{
+        console.log(res);
+      })
+      getPatientInfo(this.experimentId).then(res=>{
+        this.patient = res.data;
+      })
+      getRemarkInfo(this.experimentId).then(res=>{
+        console.log(res);
+      })
+      getSampleInfo(this.experimentId).then(res=>{
+        console.log(res);
+      })
+    },
     handleSelectionChange(val) {
       this.tempSelectedRows = val;
       this.selectAll = val.length === this.availableData.length;

+ 357 - 0
src/views/statistics/Spatiotemporal-distribution/index.vue

@@ -0,0 +1,357 @@
+<template>
+  <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"
+            style="width: 180px"
+          >
+            <el-option
+              v-for="item in diseaseOptions"
+              :key="item"
+              :label="item"
+              :value="item"
+            />
+          </el-select>
+        </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-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-form-item>
+        <el-form-item label="时间">
+          <el-date-picker
+            v-model="filters.dateRange"
+            type="daterange"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            style="width: 240px"
+            value-format="yyyy-MM-dd"
+          />
+        </el-form-item>
+        <el-form-item label="地区">
+          <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-form-item>
+      </el-form>
+    </el-card>
+
+    <div class="charts-container">
+      <!-- 子图一:增长率曲线 -->
+      <el-card class="chart-card">
+        <div slot="header">增长率曲线</div>
+        <div ref="growthChart" style="height: 320px;"></div>
+      </el-card>
+      <!-- 子图二:分区条形图 -->
+      <el-card class="chart-card">
+        <div slot="header">地区分布条形图</div>
+        <div ref="barChart" style="height: 320px;"></div>
+      </el-card>
+      <!-- 子图三:分支饼图 -->
+      <el-card class="chart-card">
+        <div slot="header">病原体分支分布饼图</div>
+        <div ref="pieChart" style="height: 320px;"></div>
+      </el-card>
+    </div>
+
+    <!-- 地图 -->
+    <el-card class="map-card">
+      <div slot="header">全球/中国地图</div>
+      <!-- 确保 ref 名称正确 -->
+      <div ref="mapContainer" style="width: 100%; height: 600px;"></div>
+    </el-card>
+
+  </div>
+</template>
+
+<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';
+
+// // 注册地图
+// echarts.registerMap('china', chinaJson);
+// echarts.registerMap('world', worldJson);
+export default {
+  name: 'SpatiotemporalDistribution',
+  data() {
+    return {
+      filters: {
+        disease: '',
+        subtype: '',
+        branch: '',
+        dateRange: [],
+        region: []
+      },
+      diseaseOptions: [],
+      diseaseLoading: false,
+      subtypeOptions: [],
+      branchOptions: [],
+      regionOptions: [],
+      growthData: [],
+      barData: [],
+      pieData: [],
+      mapData: [],
+      description: '',
+      isChinaMap: false
+    }
+  },
+  mounted() {
+    this.initRegionOptions()
+    this.initCharts()
+    this.initMap()
+  },
+  methods: {
+    async searchDisease(query) {
+      if (!query) return
+      this.diseaseLoading = true
+      // 假数据
+      this.diseaseOptions = ['疾病 A', '疾病 B', '疾病 C'].filter(item => item.includes(query))
+      this.diseaseLoading = false
+    },
+    async initRegionOptions() {
+      // 假数据
+      this.regionOptions = [
+        {
+          value: '全球',
+          label: '全球',
+          children: [
+            {
+              value: '中国',
+              label: '中国',
+              children: [
+                { value: '北京市', label: '北京市' },
+                { value: '上海市', label: '上海市' }
+              ]
+            },
+            { value: '美国', label: '美国' }
+          ]
+        }
+      ]
+    },
+    async onRegionChange(val) {
+      // 切换地图模式
+      if (val && val.length && val[val.length - 1] === '中国') {
+        this.isChinaMap = true
+      } else {
+        this.isChinaMap = false
+      }
+      // 可根据地区动态加载下一级选项
+    },
+    async fetchAllData() {
+      // 联动下拉框
+      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.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()
+    },
+    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)
+      window.addEventListener('resize', () => {
+        this.growthChart.resize()
+        this.barChart.resize()
+        this.pieChart.resize()
+        // this.mapChart.resize()
+      })
+    },
+    renderCharts() {
+      // 子图一:增长率曲线
+      this.growthChart.setOption({
+        tooltip: {
+          trigger: 'axis',
+          formatter: params => {
+            const p = params[0]
+            return `时间: ${p.name}<br/>累计占比: ${p.data.ratio}%<br/>分支数: ${p.data.count}`
+          }
+        },
+        xAxis: { type: 'category', data: this.growthData.map(i => i.date) },
+        yAxis: { type: 'value' },
+        series: [{
+          data: this.growthData.map(i => ({ value: i.value, ratio: i.ratio, count: i.count })),
+          type: 'line',
+          smooth: true
+        }]
+      })
+      // 子图二:分区条形图
+      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}%)'
+        },
+        series: [{
+          type: 'pie',
+          radius: '60%',
+          data: this.pieData
+        }]
+      })
+      // // 地图
+      // this.mapChart.setOption({
+      //   tooltip: {
+      //     trigger: 'item',
+      //     formatter: p => `${p.name}<br/>分支数: ${p.value || 0}`
+      //   },
+      //   series: [{
+      //     type: 'map',
+      //     map: this.isChinaMap ? 'china' : 'world',
+      //     roam: true,
+      //     data: this.mapData
+      //   }]
+      // })
+      // // 地图点击切换
+      // this.mapChart.off('click')
+      // this.mapChart.on('click', params => {
+      //   if (!this.isChinaMap && params.name === '中国') {
+      //     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];
+    }
+  }
+};
+</script>
+
+<style scoped>
+.spatiotemporal-distribution {
+  padding: 16px;
+}
+.filter-card {
+  margin-bottom: 16px;
+}
+.charts-container {
+  display: flex;
+  gap: 16px;
+  margin-bottom: 16px;
+}
+.chart-card {
+  flex: 1;
+  min-width: 0;
+}
+.map-card {
+  margin-bottom: 16px;
+}
+.desc-card {
+  margin-bottom: 16px;
+}
+.desc-text {
+  font-size: 15px;
+  line-height: 1.8;
+  color: #333;
+}
+</style>

+ 121 - 0
src/views/statistics/popularity-prediction/index.vue

@@ -0,0 +1,121 @@
+<template>
+  <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>
+      </div>
+      <div class="world-map">
+        <h3>世界地图病毒预测图</h3>
+        <div id="virusMap" style="width: 600px; 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';
+
+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 = [
+        { name: 'China', value: 120 },
+        { name: 'United States', value: 80 },
+        { name: 'Brazil', value: 60 },
+        { name: 'India', value: 90 }
+      ];
+      const chart = echarts.init(document.getElementById('virusMap'));
+      chart.setOption({
+        title: { text: '病毒预测分布', left: 'center' },
+        tooltip: { trigger: 'item' },
+        visualMap: {
+          min: 0,
+          max: 150,
+          left: 'left',
+          top: 'bottom',
+          text: ['高', '低'],
+          calculable: true
+        },
+        series: [
+          {
+            name: '病毒预测',
+            type: 'map',
+            map: 'world',
+            roam: true,
+            data: mapData
+          }
+        ]
+      });
+    }
+  }
+};
+</script>
+
+<style scoped>
+.tree-map-container {
+  display: flex;
+  justify-content: space-between;
+}
+.phylo-tree, .world-map {
+  background: #fff;
+  padding: 16px;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+</style>