Quellcode durchsuchen

plt模型渲染

lichunyang vor 4 Monaten
Ursprung
Commit
65acbfe993

+ 7 - 0
src/components.d.ts

@@ -7,11 +7,18 @@ export {}
 
 declare module 'vue' {
   export interface GlobalComponents {
+    CloudChart: typeof import('./components/cloudChart/index.vue')['default']
+    CloudMapDialog: typeof import('./components/cloudChart/dialog/CloudMapDialog.vue')['default']
+    ColorCardDialog: typeof import('./components/cloudChart/dialog/ColorCardDialog.vue')['default']
+    ContourDialog: typeof import('./components/cloudChart/dialog/ContourDialog.vue')['default']
+    DomainDialog: typeof import('./components/cloudChart/dialog/DomainDialog.vue')['default']
+    FileSelectDialog: typeof import('./components/cloudChart/dialog/FileSelectDialog.vue')['default']
     Header: typeof import('./components/header.vue')['default']
     NavigateBar: typeof import('./components/layout/NavigateBar.vue')['default']
     PdfReportView: typeof import('./components/pdfReportView/index.vue')['default']
     PythonEditor: typeof import('./components/PythonEditor/index.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
+    SubDialog: typeof import('./components/cloudChart/dialog/SubDialog.vue')['default']
   }
 }

+ 202 - 0
src/components/cloudChart/dialog/CloudMapDialog.vue

@@ -0,0 +1,202 @@
+<template>
+  <SubDialog
+    :modelValue="modelValue"
+    @update:modelValue="$emit('update:modelValue', $event)"
+    title="云图"
+    width="500"
+    height="600px"
+    contentHeight="450px"
+    @confirm="handleConfirm"
+    @cancel="handleCancel"
+  >
+    <div>
+      <el-collapse v-model="activeNames">
+        <el-collapse-item name="1">
+          <template #title>
+            <span class="collapse-title">标量</span>
+          </template>
+          <el-form label-position="left">
+            <el-form-item label="名称:" :label-width="formLabelWidth1">
+              <el-input v-model="ytvalue.name"></el-input>
+            </el-form-item>
+            <el-form-item label="类型:" :label-width="formLabelWidth1">
+              <el-input v-model="ytvalue.type"></el-input>
+            </el-form-item>
+            <el-form-item label="标量名:" :label-width="formLabelWidth1">
+              <el-select v-model="ytvalue.scalarname">
+                <el-option 
+                  v-for="item in scalarnameoptions" 
+                  :key="item.value" 
+                  :label="item.label" 
+                  :value="item.value"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="" :label-width="formLabelWidth1">
+              <el-row>
+                <el-col :span="12">
+                  <el-checkbox label="极值" v-model="ytvalue.jzcheck"></el-checkbox>
+                </el-col>
+                <el-col :span="12">
+                  <el-checkbox label="单元值离散到点" v-model="ytvalue.dycheck"></el-checkbox>
+                </el-col>
+              </el-row>
+            </el-form-item>
+          </el-form>
+        </el-collapse-item>
+
+        <el-collapse-item name="2">
+          <template #title>
+            <span class="collapse-title">云图间隔</span>
+          </template>
+          <el-form label-position="left">
+            <el-form-item label="名称:" :label-width="formLabelWidth1">
+              <el-input v-model="ytvalue.name2"></el-input>
+            </el-form-item>
+            <el-form-item label=" " :label-width="formLabelWidth1">
+              <el-row>
+                <el-col :span="24">
+                  <el-checkbox label="平滑云图" v-model="ytvalue.check3"></el-checkbox>
+                </el-col>
+              </el-row>
+            </el-form-item>
+          </el-form>
+        </el-collapse-item>
+
+        <el-collapse-item name="3">
+          <template #title>
+            <span class="collapse-title">数据范围</span>
+          </template>
+          <el-form label-position="left">
+            <el-form-item label="数据范围类型:" :label-width="formLabelWidth1">
+              <el-select v-model="ytvalue.dataAreaType">
+                <el-option 
+                  v-for="item in dataAreaTypeoptions" 
+                  :key="item.value" 
+                  :label="item.label" 
+                  :value="item.value"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="最大值:" :label-width="formLabelWidth1">
+              <el-input v-model="ytvalue.max"></el-input>
+            </el-form-item>
+            <el-form-item label="最小值:" :label-width="formLabelWidth1">
+              <el-input v-model="ytvalue.min"></el-input>
+            </el-form-item>
+          </el-form>
+        </el-collapse-item>
+
+        <el-collapse-item name="4">
+          <template #title>
+            <span class="collapse-title">色卡颜色范围</span>
+          </template>
+          <el-form label-position="left">
+            <el-form-item label="最大值:" :label-width="formLabelWidth1">
+              <el-row style="width: 100%;">
+                <el-col :span="22"><el-input v-model="ytvalue.maxcv"></el-input></el-col>
+                <el-col :span="2"><el-color-picker v-model="color1" @change="updateMaxValue"/></el-col>
+              </el-row>
+            </el-form-item>
+            <el-form-item label="最小值:" :label-width="formLabelWidth1">
+              <el-row style="width: 100%;">
+                <el-col :span="22"><el-input v-model="ytvalue.mincv"></el-input></el-col>
+                <el-col :span="2"><el-color-picker v-model="color2" @change="updateMinValue"/></el-col>
+              </el-row>
+            </el-form-item>
+          </el-form>
+        </el-collapse-item>
+      </el-collapse>
+    </div>
+  </SubDialog>
+</template>
+
+<script setup>
+import { ref, defineProps, defineEmits } from 'vue'
+import SubDialog from './SubDialog.vue'
+
+const props = defineProps({
+  modelValue: Boolean,
+  initialData: {
+    type: Object,
+    default: () => ({})
+  }
+})
+
+const emit = defineEmits(['update:modelValue', 'confirm', 'cancel'])
+
+// 表单标签宽度
+const formLabelWidth1 = '120px'
+
+// 折叠面板当前激活项
+const activeNames = ref(['1', '2', '3', '4'])
+
+// 表单数据
+let ytvalue = ref({
+  name:'00',
+  type:'00',
+  scalarname:'00',
+  jzcheck:'1',
+  dycheck:'1',
+  name2:'00',
+  check3:'1',
+  dataAreaType:'当前时间步',
+  max:'0.00',
+  min:'0.00',
+  maxcv:'34, 103, 177, 1',
+  mincv:'232, 0, 0, 1'
+})
+
+// 颜色选择器
+let color1 = ref('#2267B1')
+let color2 = ref('#E80000')
+
+// 下拉选项
+let scalarnameoptions = ref([
+  {label:'00',value:'00'}
+])
+
+let dataAreaTypeoptions = ref([
+  { label:'当前时间步', value: '当前时间步'},
+  { label:'所有时间步', value: '所有时间步'},
+  { label:'固定值', value: '固定值'}
+])
+
+// 更新最大值颜色
+const updateMaxValue = () => {
+  ytvalue.value.maxcv = hexToRgba(color1.value)
+}
+const updateMinValue = () => {
+  ytvalue.value.mincv = hexToRgba(color2.value)
+}
+
+// 确认操作
+const handleConfirm = () => {
+  emit('confirm', ytvalue.value)
+  emit('update:modelValue', false)
+}
+
+// 取消操作
+const handleCancel = () => {
+  emit('cancel')
+  emit('update:modelValue', false)
+}
+</script>
+
+<style scoped>
+.collapse-title {
+  font-weight: bold;
+  font-size: 14px;
+}
+
+/* 调整折叠面板内容间距 */
+.el-collapse-item__content {
+  padding-bottom: 10px;
+}
+
+/* 调整表单项间距 */
+.el-form-item {
+  margin-bottom: 16px;
+}
+
+</style>

+ 314 - 0
src/components/cloudChart/dialog/ColorCardDialog.vue

@@ -0,0 +1,314 @@
+<template>
+  <SubDialog
+    :modelValue="modelValue"
+    @update:modelValue="$emit('update:modelValue', $event)"
+    title="色卡"
+    width="500px"
+    height="600px"
+    contentHeight="450px"
+    @confirm="handleConfirm"
+    @cancel="handleCancel"
+  >
+    <div class="numberinput">
+      <el-form label-position="left" class="color-card-form">
+        <el-form-item label="" :label-width="formLabelWidth1">
+          <el-checkbox label="显示色卡" v-model="skvalue.check1"></el-checkbox>
+        </el-form-item>
+
+        <el-form-item label="朝向:" :label-width="formLabelWidth1">
+          <el-select v-model="skvalue.orientation">
+            <el-option
+              v-for="item in orientationoptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </el-form-item>
+
+        <el-row :gutter="10">
+          <el-col :span="10">
+            <el-form-item label="X(0-1):" :label-width="formLabelWidth2">
+              <el-input v-model="skvalue.X"></el-input>
+            </el-form-item>
+          </el-col>
+          <el-col :span="10">
+            <el-form-item label="Y(0-1):" :label-width="formLabelWidth2">
+              <el-input v-model="skvalue.Y"></el-input>
+            </el-form-item>
+          </el-col>
+          <el-col :span="4">
+            <el-button style="width: 100%">更新</el-button>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="10">
+          <el-col :span="10">
+            <el-form-item label="宽度(0-1):" :label-width="formLabelWidth2">
+              <el-input v-model="skvalue.width"></el-input>
+            </el-form-item>
+          </el-col>
+          <el-col :span="10">
+            <el-form-item label="高度(0-1):" :label-width="formLabelWidth2">
+              <el-input v-model="skvalue.height"></el-input>
+            </el-form-item>
+          </el-col>
+          <el-col :span="4">
+            <el-button style="width: 100%">更新</el-button>
+          </el-col>
+        </el-row>
+
+        <el-form-item label="跳过层级:" :label-width="formLabelWidth1">
+          <el-input-number
+            v-model="skvalue.skipc"
+            controls-position="right"
+          ></el-input-number>
+        </el-form-item>
+
+        <el-form-item label="字体:" :label-width="formLabelWidth1">
+          <el-row style="width: 100%" gutter="10">
+            <el-col :span="20">
+              <el-select v-model="skvalue.font">
+                <el-option
+                  v-for="item in fontoptions"
+                  :key="item.value"
+                  :label="item.label"
+                  :value="item.value"
+                />
+              </el-select>
+            </el-col>
+            <el-col :span="2">
+              <el-button
+                style="
+                  width: 32px;
+                  height: 32px;
+                  padding: 0;
+                  font-size: 18px;
+                  line-height: 32px;
+                  text-align: center;
+                "
+              >
+                <i style="font-style: italic; font-size: 24px">I</i>
+              </el-button>
+            </el-col>
+            <el-col :span="2">
+              <el-button
+                style="
+                  width: 32px;
+                  height: 32px;
+                  padding: 0;
+                  font-size: 18px;
+                  line-height: 32px;
+                  text-align: center;
+                "
+              >
+                <b style="font-weight: bold; font-size: 24px">B</b>
+              </el-button>
+            </el-col>
+          </el-row>
+        </el-form-item>
+
+        <el-form-item label="字体大小:" :label-width="formLabelWidth1">
+          <el-select v-model="skvalue.fontsize">
+            <el-option
+              v-for="item in fontsizeoptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="数据格式:" :label-width="formLabelWidth1">
+          <el-select v-model="skvalue.dataformat">
+            <el-option
+              v-for="item in dataformatoptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="精度:" :label-width="formLabelWidth1">
+          <el-input-number
+            v-model="skvalue.jingdu"
+            controls-position="right"
+          ></el-input-number>
+        </el-form-item>
+
+        <el-form-item label="" :label-width="formLabelWidth1">
+          <el-checkbox label="显示标题" v-model="skvalue.check2"></el-checkbox>
+        </el-form-item>
+
+        <el-form-item label="标题文本:" :label-width="formLabelWidth1">
+          <el-row>
+            <el-col :span="12">
+              <el-select v-model="skvalue.texttitle">
+                <el-option
+                  v-for="item in texttitleoptions"
+                  :key="item.value"
+                  :label="item.label"
+                  :value="item.value"
+                />
+              </el-select>
+            </el-col>
+            <el-col :span="12">
+              <el-input v-model="skvalue.customTitle"></el-input>
+            </el-col>
+          </el-row>
+        </el-form-item>
+
+        <el-form-item label="标题字体:" :label-width="formLabelWidth1">
+          <el-row style="width: 100%" gutter="10">
+            <el-col :span="20">
+              <el-select v-model="skvalue.titlefont">
+                <el-option
+                  v-for="item in titlefontoptions"
+                  :key="item.value"
+                  :label="item.label"
+                  :value="item.value"
+                />
+              </el-select>
+            </el-col>
+            <el-col :span="2">
+              <el-button
+                style="
+                  width: 32px;
+                  height: 32px;
+                  padding: 0;
+                  font-size: 18px;
+                  line-height: 32px;
+                  text-align: center;
+                "
+              >
+                <i style="font-style: italic; font-size: 24px">I</i>
+              </el-button>
+            </el-col>
+            <el-col :span="2">
+              <el-button
+                style="
+                  width: 32px;
+                  height: 32px;
+                  padding: 0;
+                  font-size: 18px;
+                  line-height: 32px;
+                  text-align: center;
+                "
+              >
+                <b style="font-weight: bold; font-size: 24px">B</b>
+              </el-button>
+            </el-col>
+          </el-row>
+        </el-form-item>
+
+        <el-form-item label="字体大小:" :label-width="formLabelWidth1">
+          <el-select v-model="skvalue.fontsize2">
+            <el-option
+              v-for="item in fontsizeoptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </el-form-item>
+      </el-form>
+    </div>
+  </SubDialog>
+</template>
+
+<script setup>
+import { ref, defineProps, defineEmits } from "vue"
+import SubDialog from "./SubDialog.vue"
+
+const props = defineProps({
+  modelValue: Boolean,
+  initialData: {
+    type: Object,
+    default: () => ({})
+  }
+})
+
+const emit = defineEmits(["update:modelValue", "confirm", "cancel"])
+
+// 表单标签宽度
+let formLabelWidth1 = ref(140)
+let formLabelWidth2 = ref(100)
+
+// 表单数据
+let skvalue = ref({
+  check1:'1',
+  orientation:'竖直',
+  X:'0.8',
+  Y:'0.05',
+  width:'0.8',
+  height:'0.05',
+  skipc:'2',
+  font:'微软雅黑',
+  fontsize:'15',
+  dataformat:'科学计数法',
+  jingdu:'2',
+  check2:'1',
+  texttitle:'使用变量名',
+  titlefont:'Arial',
+  fontsize2:'20',
+})
+
+// 下拉选项数据
+let orientationoptions = ref([
+  { label:'竖直', value: '竖直'},
+  { label:'水平', value: '水平'},
+])
+
+let fontoptions = ref([
+  { label:'微软雅黑', value: '微软雅黑'},
+])
+
+let fontsizeoptions = ref([
+  { label:'15', value: '15'},
+])
+
+const dataformatoptions = ref([
+  { value: "float", label: "浮点数" },
+  { value: "scientific", label: "科学计数法" }
+])
+
+const texttitleoptions = ref([
+  { value: "default", label: "默认标题" },
+  { value: "custom", label: "自定义" }
+])
+
+let titlefontoptions = ref([
+  { label:'Arial', value: 'Arial'},
+])
+
+// 确认操作
+const handleConfirm = () => {
+  emit("confirm", skvalue.value)
+  emit("update:modelValue", false)
+}
+
+// 取消操作
+const handleCancel = () => {
+  emit("cancel")
+  emit("update:modelValue", false)
+}
+</script>
+
+<style scoped>
+.color-card-form {
+  padding: 0 10px;
+  overflow-y: auto;
+  max-height: 100%;
+}
+
+/* 调整表单项间距 */
+.el-form-item {
+  margin-bottom: 8px;
+}
+
+/* 调整行内元素间距 */
+.el-row {
+  margin-bottom: 10px;
+}
+</style>

+ 141 - 0
src/components/cloudChart/dialog/ContourDialog.vue

@@ -0,0 +1,141 @@
+<template>
+  <SubDialog
+    :modelValue="modelValue"
+    @update:modelValue="$emit('update:modelValue', $event)"
+    title="等值线"
+    width="500px"
+    height="550px"
+    contentHeight="400px"
+    @confirm="handleConfirm"
+    @cancel="handleCancel"
+  >
+    <div class="contour-dialog-content">
+      <el-collapse v-model="activeNames2">
+        <el-collapse-item name="1">
+          <template #title>
+            <span class="collapse-title">标量</span>
+          </template>
+          <el-form label-position="left">
+            <el-form-item label="名称:" :label-width="formLabelWidth1">
+              <el-input v-model="dzxvalue.name"></el-input>
+            </el-form-item>
+            <el-form-item label="类型:" :label-width="formLabelWidth1">
+              <el-input v-model="dzxvalue.type"></el-input>
+            </el-form-item>
+            <el-form-item label="标量名:" :label-width="formLabelWidth1">
+              <el-select v-model="dzxvalue.scalarname2" style="width: 100%">
+                <el-option
+                  v-for="item in scalarname2options" 
+                  :key="item.value" 
+                  :label="item.label" 
+                  :value="item.value"
+                />
+              </el-select>
+            </el-form-item>
+          </el-form>
+        </el-collapse-item>
+
+        <el-collapse-item name="2">
+          <template #title>
+            <span class="collapse-title">云图间隔</span>
+          </template>
+          <el-form label-position="left">
+            <el-form-item label="层级:" :label-width="formLabelWidth1">
+              <el-input v-model="dzxvalue.cengji"></el-input>
+            </el-form-item>
+          </el-form>
+        </el-collapse-item>
+
+        <el-collapse-item name="3">
+          <template #title>
+            <span class="collapse-title">数据范围</span>
+          </template>
+          <el-form label-position="left">
+            <el-form-item label="最大值:" :label-width="formLabelWidth1">
+              <el-input v-model="dzxvalue.max"></el-input>
+            </el-form-item>
+            <el-form-item label="最小值:" :label-width="formLabelWidth1">
+              <el-input v-model="dzxvalue.min"></el-input>
+            </el-form-item>
+          </el-form>
+        </el-collapse-item>
+      </el-collapse>
+    </div>
+  </SubDialog>
+</template>
+
+<script setup>
+import { ref, defineProps, defineEmits } from 'vue'
+import SubDialog from './SubDialog.vue'
+
+const props = defineProps({
+  modelValue: Boolean,
+  initialData: {
+    type: Object,
+    default: () => ({})
+  }
+})
+
+const emit = defineEmits(['update:modelValue', 'confirm', 'cancel'])
+
+// 表单标签宽度
+let formLabelWidth1 = ref(140)
+
+// 折叠面板当前激活项
+const activeNames2 = ref(['1', '2', '3'])
+
+// 表单数据
+let dzxvalue = ref({
+  name:'00',
+  type:'00',
+  scalarname2:'00',
+  cengji:'00',
+  max:'0.00',
+  min:'0.00'
+})
+
+// 下拉选项
+let scalarname2options = ref([
+  {label:'00',value:'00'}
+])
+
+// 确认操作
+const handleConfirm = () => {
+  emit('confirm', dzxvalue.value)
+  emit('update:modelValue', false)
+}
+
+// 取消操作
+const handleCancel = () => {
+  emit('cancel')
+  emit('update:modelValue', false)
+}
+</script>
+
+<style scoped>
+.contour-dialog-content {
+  padding: 0 10px;
+  overflow-y: auto;
+  height: 100%;
+}
+
+/* 折叠面板标题样式 */
+.collapse-title {
+  font-weight: bold;
+  font-size: 14px;
+}
+
+/* 调整折叠面板内容间距 */
+:deep(.el-collapse-item__content) {
+  padding-bottom: 10px;
+}
+
+:deep(.el-dialog__body) {
+  padding: 10px !important;
+}
+
+/* 调整表单项间距 */
+.el-form-item {
+  margin-bottom: 8px;
+}
+</style>

+ 119 - 0
src/components/cloudChart/dialog/DomainDialog.vue

@@ -0,0 +1,119 @@
+<template>
+  <SubDialog
+    :modelValue="modelValue"
+    @update:modelValue="$emit('update:modelValue', $event)"
+    title="域"
+    width="600"
+    height="500px"
+    @confirm="handleConfirm"
+    @cancel="handleCancel"
+  >
+    <div>
+      <el-row style="margin-bottom: 10px;" :gutter="20">
+        <el-col v-for="(item,index) in domainbtnbox1" :key="index" :span="8">
+          <el-button style="width: 100%;">{{ item }}</el-button>
+        </el-col>
+      </el-row>
+      <el-row style="margin-bottom: 10px;" :gutter="20">
+        <el-col v-for="(item,index) in domainbtnbox2" :key="index" :span="8">
+          <el-button style="width: 100%;">{{ item }}</el-button>
+        </el-col>
+      </el-row>
+    </div>
+    <div class="classtable tabledomain">
+      <el-table 
+        :data="tableDatadomain" 
+        style="width: 100%; height: 230px" 
+        border="true" 
+        :header-cell-class-name="headerCellClassName"
+      >
+        <el-table-column prop="rowname" label="域名称" />
+        <el-table-column
+          v-for="(item, index) in tabledomainColumns"
+          :key="index"
+          :prop="item.prop"
+          :label="item.label"
+        >
+          <template #default="{ row }">
+            <el-input v-model="row[item.prop]" />
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+  </SubDialog>
+</template>
+
+<script setup>
+import { ref, defineProps, defineEmits } from 'vue'
+import SubDialog from './SubDialog.vue'
+
+const props = defineProps({
+  modelValue: Boolean,
+  initialData: {
+    type: Array,
+    default: () => []
+  }
+})
+
+const emit = defineEmits(['update:modelValue', 'confirm', 'cancel'])
+
+// 按钮数据
+let domainbtnbox1 = ref(['显示全部','隐藏全部','倒转互换'])
+let domainbtnbox2 = ref(['显示','隐藏','表面绘制'])
+
+// 表格数据
+let tableDatadomain = ref([
+  {rowname:"Z1", state:'1', type:'2', area:'3'},
+  {rowname:"Z2", state:'1', type:'2', area:'3'},
+  {rowname:"Z3", state:'1', type:'2', area:'3'},
+  {rowname:"Z4", state:'1', type:'2', area:'3'},
+  {rowname:"Z1", state:'1', type:'2', area:'3'},
+  {rowname:"Z2", state:'1', type:'2', area:'3'},
+  {rowname:"Z3", state:'1', type:'2', area:'3'},
+  {rowname:"Z4", state:'1', type:'2', area:'3'},
+])
+
+// 表格列配置
+let tabledomainColumns = ref([
+  {label:"状态", prop:'state'},
+  {label:"绘制类型", prop:'type'},
+  {label:"平面范围", prop:'area'},
+])
+
+// 表头样式
+const headerCellClassName = ({ column }) => {
+  // console.log('列:',column.property)
+  if (column.property === 'state') {
+    console.log('yanse',column.property)
+    return 'header-blue';
+  } else if (column.property === 'type') {
+    return 'header-green';
+  } else if (column.property === 'area') {
+    return 'header-yellow';
+  }
+  return '';
+};
+
+// 确认操作
+const handleConfirm = () => {
+  emit('confirm', tableDatadomain.value)
+  emit('update:modelValue', false)
+}
+
+// 取消操作
+const handleCancel = () => {
+  emit('cancel')
+  emit('update:modelValue', false)
+}
+</script>
+
+<style scoped>
+.classtable.tabledomain {
+  margin-top: 20px;
+}
+/* 表头样式 */
+:deep(.header-cell) {
+  background-color: #f5f7fa;
+  font-weight: bold;
+}
+</style>

+ 224 - 0
src/components/cloudChart/dialog/FileSelectDialog.vue

@@ -0,0 +1,224 @@
+<template>
+  <SubDialog
+    :modelValue="modelValue"
+    @update:modelValue="$emit('update:modelValue', $event)"
+    title="文件选择"
+    width="460"
+    height="430px"
+    @confirm="handleConfirm"
+    @cancel="handleCancel"
+  >
+    <el-form :model="form" label-position="left">
+      <el-form-item label="添加文件:" :label-width="formLabelWidth3" style="width: 100%">
+        <el-row style="width: 100%">
+          <el-col :span="24">
+            <el-input
+              v-model="cloudFileName"
+              readonly
+              :step="100"
+              :min="0"
+              :max="1000"
+              controls-position="right"
+            />
+          </el-col>
+          <!-- 文件上传按钮部分 -->
+          <el-col :span="1" style="display: flex; align-items: center; margin-left: -35px">
+            <fileUploads
+              :projectId="projectId" 
+              :solverType="solverType" 
+              accept=".plt"
+              upId="cloud" 
+              name="点击选择文件"
+              :imgSrc="meshFileImgSrc"
+              @upload-success="handleFileUploadSuccess"
+              @update-fileName="updateFileName"
+              @update-percentage="updatePercentage"
+              @upload-status="getUploadStatus"
+            />
+          </el-col>
+        </el-row>
+        <!-- 进度条 -->
+        <el-row v-if="showProgress" style="width: 100%; margin-top: 10px;">
+          <el-col :span="20">
+            <el-progress :percentage="percentage"></el-progress>
+          </el-col>
+          <el-col :span="4">
+            <div style="line-height: 15px">{{uploadStatus}}</div>
+          </el-col>
+        </el-row>
+      </el-form-item>
+    
+      <div style="display: flex; flex-direction: row; width: 100%; margin-top: 20px;">
+        <el-card shadow="hover" style="width: 70%; height: 200px; overflow-y: auto;" >
+          <el-checkbox-group v-model="selectedFiles">
+            <el-checkbox 
+              v-for="item in fileList" 
+              :key="item.value" 
+              :label="item.value" 
+              style="display: block"
+            >
+            <span style="font-size: 14px">{{ item.label }}</span>
+              
+            </el-checkbox>
+          </el-checkbox-group>
+        </el-card>
+        
+        <div style="width: 30%; padding-left: 10px;">
+          <el-button 
+            style="width: 100%; margin-bottom: 10px;" 
+            @click="deleteSelectedFiles"
+          >
+            删除选中文件
+          </el-button>
+          <el-button 
+            style="width: 100%; background-color: transparent; margin-left: 0;" 
+            @click="deleteAllFiles"
+          >
+            删除全部文件
+          </el-button>
+        </div>
+      </div>
+    </el-form>
+  </SubDialog>
+</template>
+
+<script setup>
+import { ref, defineProps, defineEmits, computed, watch } from 'vue'
+import SubDialog from './SubDialog.vue'
+import fileUploads from '@/views/components/fileuploads.vue'
+
+const props = defineProps({
+  modelValue: Boolean,
+  projectId: {
+    type: String,
+    required: true
+  },
+  solverType: {
+    type: String,
+    required: true
+  },
+  initialFiles: {
+    type: Array,
+    default: () => []
+  }
+})
+
+const emit = defineEmits(['update:modelValue', 'confirm', 'cancel'])
+
+// 表单数据
+const formLabelWidth = '100px'
+const cloudFileName = ref('')
+const selectedFiles = ref([])
+// const fileList = ref([...props.initialFiles])
+const fileList = ref([
+  {
+    value: 'file1',
+    label: '测试文件1.cgns',
+    raw: {
+      name: '测试文件1.cgns',
+      size: 1024 * 1024 * 5 // 5MB
+    }
+  },
+  {
+    value: 'file2',
+    label: '测试文件2.xyz',
+    raw: {
+      name: '测试文件2.xyz',
+      size: 1024 * 500 // 500KB
+    }
+  },
+  {
+    value: 'file3',
+    label: '测试文件3.bdf',
+    raw: {
+      name: '测试文件3.bdf',
+      size: 1024 * 1024 * 10 // 10MB
+    }
+  }
+])
+const upId = ref(`file-upload-${Date.now()}`)
+const meshFileImgSrc = new URL("@/assets/img/open.png", import.meta.url).href;
+let formLabelWidth3 = ref(80)
+let uploadStatus = ref('');
+let percentage = ref(0);
+let fid = ref('');
+
+watch(() => props.modelValue, (newVal) => {
+  if (!newVal) {
+    // 当modelValue变为false时,确保完全关闭
+    emit('cancel')
+  }
+})
+
+// 文件上传成功处理
+const handleFileUploadSuccess = (file) => {
+  //隐藏进度条
+  setTimeout(() => {
+    percentage.value = 0;
+  }, 1000);
+  const newFile = {
+    value: file.id || file.name,
+    label: file.name,
+    raw: file
+  }
+  fileList.value.push(newFile)
+  cloudFileName.value = file.name
+}
+
+// 更新文件名
+const updateFileName = (newValue) => {
+  meshFileName.value = newValue
+}
+
+// 更新进度条
+const updatePercentage = (newValue) => {
+  percentage.value = newValue
+}
+
+// 更新上传状态
+const getUploadStatus = (newValue) => {
+  uploadStatus.value = newValue
+}
+
+// 控制进度条显隐
+const showProgress = computed(() => percentage.value > 0 && percentage.value <= 100);
+
+// 删除选中文件
+const deleteSelectedFiles = () => {
+  fileList.value = fileList.value.filter(
+    file => !selectedFiles.value.includes(file.value)
+  )
+  selectedFiles.value = []
+}
+
+// 删除全部文件
+const deleteAllFiles = () => {
+  fileList.value = []
+  selectedFiles.value = []
+  cloudFileName.value = ''
+}
+
+// 确认选择
+const handleConfirm = () => {
+  emit('confirm', {
+    // selectedFiles: fileList.value.filter(
+    //   file => selectedFiles.value.includes(file.value)
+    // )
+  fid: 'f96450bc33fd407b89c27fb481c07b83'
+  })
+  emit('update:modelValue', false)
+}
+
+// 取消
+const handleCancel = () => {
+  emit('cancel')
+  emit('update:modelValue', false)
+}
+</script>
+
+<style scoped>
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+}
+</style>

+ 100 - 0
src/components/cloudChart/dialog/SubDialog.vue

@@ -0,0 +1,100 @@
+<template>
+  <el-dialog
+    :model-value="modelValue"
+    @update:model-value="$emit('update:modelValue', $event)"
+    align-center
+    :modal="false"
+    :close-on-click-modal="false"
+    :append-to-body="true"
+    draggable
+    :fullscreen="false"
+    :modal-append-to-body="false"
+    :modal-class="modalClass"
+    :width="width"
+    :class="dialogClass"
+    :style="{ height: height }"
+    :before-close="handleBeforeClose"
+  >
+    <template #header="{ titleId, titleClass }">
+      <div class="my-header">
+        <h4 :id="titleId" :class="titleClass">{{ title }}</h4>
+      </div>
+    </template>
+    
+    <!-- 内容插槽 -->
+    <div class="dialog-content" :style="{ height: contentHeight }">
+      <slot></slot>
+    </div>
+    
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button @click="handleCancel">取消</el-button>
+        <el-button type="primary" @click="handleConfirm">
+          确认
+        </el-button>
+      </span>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+import { defineProps, defineEmits } from 'vue'
+
+const props = defineProps({
+  modelValue: {
+    type: Boolean,
+    default: false
+  },
+  title: {
+    type: String,
+    default: '弹窗标题'
+  },
+  width: {
+    type: String,
+    default: '450'
+  },
+  height: {
+    type: String,
+    default: 'auto'
+  },
+  contentHeight: {
+    type: String,
+    default: 'calc(100% - 110px)'
+  },
+  modalClass: {
+    type: String,
+    default: 'summary-dlg'
+  },
+  dialogClass: {
+    type: String,
+    default: 'dialog_class bgcolor tianjia'
+  }
+})
+
+const emit = defineEmits(['update:modelValue', 'confirm', 'cancel'])
+
+const handleBeforeClose = (done) => {
+  emit('cancel')
+  done()
+}
+
+const handleCancel = () => {
+  emit('update:modelValue', false)
+  emit('cancel')
+}
+
+const handleConfirm = () => {
+  emit('confirm')
+  emit('update:modelValue', false)
+}
+</script>
+
+<style scoped>
+.dialog-content {
+  overflow-y: auto;  /* 只允许垂直滚动 */
+  overflow-x: hidden; /* 禁止水平滚动 */
+  padding: 10px 0;
+  width: 100%;
+  box-sizing: border-box; /* 确保padding不增加总宽度 */
+}
+</style>

+ 281 - 0
src/components/cloudChart/index.vue

@@ -0,0 +1,281 @@
+<template>
+  <el-dialog
+    :model-value="modelValue"
+    @update:model-value="emit('update:modelValue', $event)"
+    align-center
+    :modal="false"
+    :close-on-click-modal="false"
+    :append-to-body="true"
+    draggable
+    :fullscreen="false"
+    :modal-append-to-body="false"
+    modal-class="summary-dlg"
+    :before-close="handleClose"
+    width="700"
+    class="dialog_style bgcolor tianjia sel cloudChart"
+    style="height: 500px; overflow: auto"
+  >
+    <template #header>
+      <div class="my-header">
+        <h4 :id="titleId" :class="titleClass">云图/等值线显示</h4>
+      </div>
+    </template>
+    <div>
+      <div class="cloudbox">
+        <el-row gutter="20">
+          <el-col :span="2"></el-col>
+          <el-col v-for="(item, index) in cloudbtnbox" :key="index" :span="4">
+            <el-button style="width: 100%" @click="openSubDialog(item.btnname)">
+              <el-image
+                :src="getImgPath(item.url)"
+                alt="img"
+                fit="cover"
+                style="width: 20px; margin-right: 4px"
+              />
+              {{ item.btnname }}
+            </el-button>
+          </el-col>
+        </el-row>
+      </div>
+      <!-- 动态加载子弹窗 -->
+      <component
+        v-for="(dialog, index) in activeSubDialogs"
+        :key="index"
+        :is="dialog.component"
+        v-model="dialog.visible"
+        v-bind="dialog.props"
+        @confirm="handleSubDialogConfirm(dialog.type, $event)"
+        @cancel="closeSubDialog(dialog.type)"
+      />
+      <div
+        style="overflow: auto"
+        v-loading="isLoading"
+        element-loading-text="拼命加载中..."
+      >
+        <cloudChart height="400px" :data="pltData"/>
+      </div>
+    </div>
+  </el-dialog>
+</template>
+
+<script setup>
+import { defineProps, defineEmits } from "vue"
+import FileSelectDialog from "./dialog/FileSelectDialog.vue"
+import DomainDialog from "./dialog/DomainDialog.vue"
+import CloudMapDialog from "./dialog/CloudMapDialog.vue"
+import ColorCardDialog from "./dialog/ColorCardDialog.vue"
+import ContourDialog from "./dialog/ContourDialog.vue"
+import cloudChart from "@/views/threejsView/index.vue" // 云图
+import h5wasm from 'h5wasm'
+const props = defineProps({
+  modelValue: {
+    type: Boolean,
+    default: false
+  },
+  cloudbtnbox: {
+    type: Array,
+    default: () => []
+  },
+  titleId: String,
+  titleClass: String
+})
+
+const emit = defineEmits(["update:modelValue", "close"])
+
+let cloudbtnbox = ref([
+  { url: "meshFile.png", btnname: "文件选择" },
+  { url: "yu.png", btnname: "域" },
+  { url: "kk9.png", btnname: "云图" },
+  { url: "seka.png", btnname: "色卡" },
+  { url: "dengzx.png", btnname: "等值线" }
+])
+const subDialogComponents = {
+  文件选择: FileSelectDialog,
+  域: DomainDialog,
+  云图: CloudMapDialog,
+  色卡: ColorCardDialog,
+  等值线: ContourDialog
+}
+let isLoading = ref(false)
+// 当前活动的子弹窗
+const activeSubDialogs = ref([])
+let pltData = ref()
+// 打开对应的子弹窗
+const openSubDialog = (btnName) => {
+  // 先关闭同类型的对话框(如果存在)
+  closeSubDialog(btnName)
+
+  // 打开新对话框
+  activeSubDialogs.value.push({
+    type: btnName,
+    component: subDialogComponents[btnName],
+    visible: true,
+    props: {}
+  })
+}
+
+// 关闭子弹窗
+const closeSubDialog = (dialogType) => {
+  activeSubDialogs.value = activeSubDialogs.value.filter(
+    (d) => d.type !== dialogType
+  )
+}
+
+// 处理子弹窗确认
+const handleSubDialogConfirm = (dialogType, data) => {
+  switch (dialogType) {
+    case "文件选择":
+      // 获取结果文件
+      getResultFile(data.fid)
+      break
+    case "域":
+      console.log("dddddddddd域", data)
+      break
+    case "云图":
+      console.log("dddddddddd云图", data)
+      break
+    case "色卡":
+      console.log("dddddddddd色卡", data)
+      break
+    case "等值线":
+      console.log("dddddddddd等值线", data)
+      break
+    default:
+      break
+  }
+  closeSubDialog(dialogType)
+}
+
+const getImgPath = (url) => {
+  return new URL(`../../assets/img/${url}`, import.meta.url).href
+}
+
+const handleClose = (done) => {
+  done()
+}
+
+// 获取结果文件
+const getResultFile = (fid) => {
+  console.log("获取结果文件", fid)
+  isLoading.value = true
+  getPltData(fid)
+}
+
+const getPltData = async (fpid) => {
+  let url = import.meta.env.VITE_BASE_URL + getUrl()
+  try {
+    // 1. 先初始化h5wasm
+    const { ready, File, FS } = await h5wasm.ready;
+     // 必须等待WASM加载完成
+    const response = await fetch(url, {
+      method: "POST",
+      headers: {
+        "Content-Type": "application/json"
+      },
+      body: JSON.stringify({
+        channelNo: "service",
+        clientToken: "e47b87eec69545559d1e81e56626da68",
+        transCode: "MDO0076",
+        userId: "5f06c8bc77234f969d13e160b54c27e3",
+        fid: fpid
+      })
+    })
+    // 获取二进制数据
+    const arrayBuffer = await response.arrayBuffer()
+    // 将文件写入虚拟文件系统
+    const filename = `data_${Date.now()}.h5`
+    FS.writeFile(filename, new Uint8Array(arrayBuffer))
+    // 打开HDF5文件
+    const file = new h5wasm.File(filename, 'r')  // 'r' 表示只读模式
+    // 3. 调试:打印完整结构
+  console.log('HDF5结构:', {
+    keys: Array.from(file.keys()),
+    attrs: Object.entries(file.attrs).map(([k, v]) => ({ [k]: v.value }))
+  })
+    // 解析为Three.js可用格式
+    const parsedData = await parseHDF5ToThreeJS(file);
+    pltData.value = parsedData
+    isLoading.value = false
+  } catch (error) {
+    isLoading.value = false
+    console.error("请求失败:", error.response || error)
+  }
+}
+
+// HDF5转Three.js格式的解析器
+const parseHDF5ToThreeJS = (h5file) => {
+  const result = {
+    "data" : {
+      "datasetType": "plt",
+    },
+    metadata: {
+      title: h5file.attrs.title?.value || '',
+      variables: JSON.parse(h5file.attrs.variables?.value || "[]"),
+      version: h5file.attrs.version?.value || ''
+    },
+    zones: []
+  }
+
+  // 新版h5wasm数据提取方法
+  const extractDataset = (group, name) => {
+    try {
+      // 方法1:直接访问
+      if (group[name] && group[name].value) {
+        return group[name].value
+      }
+      
+      // 方法2:遍历entries
+      for (const [key, obj] of Object.entries(group)) {
+        if (key === name && obj?.value) {
+          return obj.value
+        }
+      }
+      
+      // 方法3:get方法
+      const dataset = group.get?.(name)
+      return dataset?.value
+    } catch (e) {
+      console.warn(`提取 ${name} 失败:`, e)
+      return null
+    }
+  }
+
+  // 处理每个区域
+  for (const zoneKey of h5file.keys()) {
+    if (!zoneKey.startsWith('zone_')) continue
+
+    const zone = h5file[zoneKey] || h5file.get(zoneKey)
+    console.log(`处理 ${zoneKey}:`, zone)
+
+    const zoneData = {
+      name: zoneKey,
+      vertices: extractDataset(zone, 'vertices'),
+      indices: extractDataset(zone, 'indices'),
+      variables: {}
+    }
+
+    // 提取变量数据
+    result.metadata.variables.forEach(varName => {
+      zoneData.variables[varName] = extractDataset(zone, `var_${varName}`)
+    })
+
+    result.zones.push(zoneData)
+  }
+
+  console.log('最终解析结果:', result)
+  return result
+}
+
+function getUrl(channelNo = "service") {
+  let url = ""
+  if (channelNo == "service") {
+    url = "/TransServlet"
+  } else if (channelNo == "manager") {
+    url = "/managersvr/TransServlet"
+  }
+  return url
+}
+</script>
+
+<style scoped>
+</style>

+ 7 - 0
src/types/h5wasm.ts

@@ -0,0 +1,7 @@
+export interface ZoneData {
+  name: string;
+  type: number;
+  vertices?: number[][];
+  indices?: number[];
+  variables: Record<string, any>;
+}

+ 6 - 2
src/views/home.vue

@@ -871,10 +871,13 @@
             </template>
           </el-dialog>
           <!-- 云图/等值线图 -->
-          <el-dialog v-model="dialog.clouddialog" align-center :modal="false" :close-on-click-modal="false"
+          <cloundCharts 
+            v-model="dialog.clouddialog"
+            />
+          <el-dialog v-model="dialog.paretodialog" align-center :modal="false" :close-on-click-modal="false"
             :append-to-body="true" draggable :fullscreen="false" :modal-append-to-body="false" modal-class="summary-dlg"
             :before-close="handleClose" width="700" class="dialog_style bgcolor tianjia sel cloudChart" style="height: 500px;overflow: auto;">
-            <template #header="{ titleId, titleClass }">
+            <template #header>
               <div class="my-header ">
                 <h4 :id="titleId" :class="titleClass">云图/等值线显示</h4>
               </div>
@@ -1699,6 +1702,7 @@ import PythonEdit from '@/components/PythonEditor/index.vue'; // python编辑器
 import pdfReportView from '@/components/pdfReportView/index.vue'; //报告查看
 import cloudChart from './threejsView/index.vue'; // 云图
 import emitter from "@/utils/emitter";
+import cloundCharts from '@/components/cloudChart/index.vue';
 
 let Sidebarref = ref();
 let resource=ref(0);

+ 71 - 0
src/views/threejsView/components/ThreeScene.vue

@@ -19,6 +19,7 @@ import { PolyDataRenderer } from "../utils/renderers/PolyDataRenderer"
 import { xyzDataRenderer } from "../utils/renderers/xyzDataRenderer"
 import { bdfDataRenderer } from "../utils/renderers/bdfDataRenderer"
 import { CgnsJSONRenderer } from "../utils/renderers/CgnsJSONRenderer";
+import { PltDataRenderer } from "../utils/renderers/pltDataRenderer";
 import {
   initScene,
   initCamera,
@@ -104,6 +105,11 @@ const renderVTK = (data) => {
       adjustCameraForCgns(scene, camera);
       });
       break;
+    case "plt":
+      dataRenderer = new PltDataRenderer(updateProgress, () => {
+      adjustCameraForPlt(scene, camera);
+      });
+      break;
     default:
       console.log("11111")
       
@@ -132,6 +138,12 @@ const renderVTK = (data) => {
     adjustCameraForCgns(scene, camera);
     return;
   }
+
+  if (data.data.datasetType === "plt"){
+    adjustCameraForPlt(scene, camera);
+    return;
+  }
+
 }
 
 // 监听 data 变化
@@ -331,6 +343,65 @@ const adjustCameraForCgns = (scene, camera) => {
   console.log("Adjusted camera position:", camera.position);
   console.log("Adjusted camera fov:", camera.fov);
 };
+
+function adjustCameraForPlt(scene, camera, paddingFactor = 1.2) {
+  // 1. 计算所有PLT对象的联合包围盒
+  const bbox = new THREE.Box3();
+  let hasPLTObjects = false;
+
+  scene.traverse(obj => {
+    if (obj.isMesh && obj.userData?.isPLT) {
+      if (!obj.geometry.boundingBox) {
+        obj.geometry.computeBoundingBox();
+      }
+      bbox.union(obj.geometry.boundingBox);
+      hasPLTObjects = true;
+    }
+  });
+
+  if (!hasPLTObjects) {
+    console.warn('未找到PLT网格对象');
+    return;
+  }
+
+  // 2. 计算场景中心点和尺寸
+  const center = new THREE.Vector3();
+  bbox.getCenter(center);
+  const size = bbox.getSize(new THREE.Vector3());
+  const maxDim = Math.max(size.x, size.y, size.z);
+  const minDim = Math.min(size.x, size.y, size.z);
+  
+  // 3. 智能相机位置计算(考虑不同维度比例)
+  const cameraPosition = center.clone();
+  const aspect = camera.aspect;
+  
+  // 根据场景形状调整相机距离
+  let distance;
+  if (maxDim / minDim > 5) { // 长条形场景
+    distance = maxDim * paddingFactor * 1.5;
+  } else { // 常规场景
+    distance = maxDim * paddingFactor * (aspect > 1 ? 1.5 : 2.0);
+  }
+
+  // 4. 设置相机位置和参数
+  cameraPosition.z += distance;
+  camera.position.copy(cameraPosition);
+  camera.lookAt(center);
+  
+  // 5. 自适应相机远平面
+  camera.far = distance * 10;
+  camera.near = Math.max(0.1, distance / 100);
+  camera.updateProjectionMatrix();
+
+  // 6. 返回相机参数用于后续控制
+  return {
+    center,
+    distance,
+    boundingBox: bbox,
+    size,
+    aspectRatio: aspect
+  };
+}
 </script>
 
 <style>

+ 6 - 0
src/views/threejsView/utils/parsers/PltDataParser.js

@@ -0,0 +1,6 @@
+export class PltDataParser {
+    parse(jsonData) {
+        console.log("Parsing PLT data...", jsonData);
+        return jsonData;
+    }
+}

+ 3 - 0
src/views/threejsView/utils/parsers/VTKParserFactory.js

@@ -3,6 +3,7 @@ import { PolyDataParser } from './PolyDataParser';
 import { XyzDataParser } from './XyzDataParser';
 import { BdfDataParser } from './BdfDataParser';
 import { CgnsJSONParser } from './CgnsJSONParser';
+import { PltDataParser } from './PltDataParser';
 
 
 export class VTKParserFactory {
@@ -18,6 +19,8 @@ export class VTKParserFactory {
         return new BdfDataParser();
       case "cgns":
       return new CgnsJSONParser();
+      case "plt":
+      return new PltDataParser();
       default:
         console.log('2222222');
     }

+ 245 - 0
src/views/threejsView/utils/renderers/PltDataRenderer.js

@@ -0,0 +1,245 @@
+import * as THREE from 'three';
+
+export class PltDataRenderer {
+  constructor(updateProgress, onComplete) {
+    this.updateProgress = updateProgress || (() => {});
+    this.onComplete = onComplete || (() => {});
+    this.chunkSize = 5000; // 每个区域的最大三角面片数(用于分块加载)
+    this.defaultMaterial = new THREE.MeshPhongMaterial({
+      color: 0x4477ff,
+      specular: 0x111111,
+      shininess: 30,
+      side: THREE.DoubleSide, // 关键修改:双面渲染
+      flatShading: false,     // 平滑着色
+      transparent: true,
+      opacity: 0.95
+    });
+    this.wireframeMaterial = new THREE.MeshBasicMaterial({
+      color: 0x00ff00,
+      wireframe: true
+    });
+  }
+  
+
+  async render(pltData, scene, specificZoneIndex = null) {
+    console.log("原始PLT数据结构诊断:", {
+      zones: pltData.zones.map(zone => ({
+        name: zone.name,
+        verticesType: zone.vertices?.constructor?.name,
+        verticesLength: zone.vertices?.length,
+        verticesSample: zone.vertices?.slice(0, 3),
+        indicesType: zone.indices?.constructor?.name,
+        indicesLength: zone.indices?.length,
+        indicesSample: zone.indices?.slice(0, 3)
+      }))
+    });
+    if (!pltData?.zones?.length) {
+      console.error('Invalid PLT data');
+      return;
+    }
+
+    // 清空现有PLT相关对象
+    this._cleanupPreviousRender(scene);
+
+    // 如果指定了具体区域索引,则只渲染该区域
+    if (specificZoneIndex !== null) {
+      if (specificZoneIndex < 0 || specificZoneIndex >= pltData.zones.length) {
+        console.error(`Invalid zone index: ${specificZoneIndex} (total zones: ${pltData.zones.length})`);
+        return;
+      }
+      
+      const zone = pltData.zones[specificZoneIndex];
+      await this._renderZone(zone, scene, specificZoneIndex, pltData.zones.length);
+      console.log(`[DEBUG] 仅渲染了区域 ${specificZoneIndex}: ${zone.name}`);
+      return;
+    }
+
+    // 否则渲染所有区域(原有逻辑)
+    const totalZones = pltData.zones.length;
+    for (const [index, zone] of pltData.zones.entries()) {
+      try {
+        await this._renderZone(zone, scene, index, totalZones);
+        this.updateProgress((index + 1) / totalZones);
+      } catch (error) {
+        console.error(`Failed to render zone ${zone.name}:`, error);
+      }
+      await new Promise(resolve => setTimeout(resolve, 0));
+    }
+
+    this.onComplete();
+  }
+
+  async _renderZone(zone, scene, zoneIndex, totalZones) {
+    return new Promise((resolve) => {
+      // 使用requestAnimationFrame避免阻塞UI
+      requestAnimationFrame(() => {
+        if (!zone.vertices || !zone.indices) {
+          console.warn(`Zone ${zone.name} has no geometry data`);
+          return resolve();
+        }
+
+        // 创建几何体
+        const geometry = this._createZoneGeometry(zone);
+        
+        // 分配材质(交替使用实体和线框材质)
+        const material = zoneIndex % 2 === 0 
+          ? this.defaultMaterial.clone()
+          : this.wireframeMaterial.clone();
+        
+        // 设置区域特定颜色
+        if (zoneIndex % 2 === 0) {
+          material.color.setHSL(zoneIndex / totalZones, 0.7, 0.5);
+        } else {
+          material.color.setHSL(zoneIndex / totalZones, 0.7, 0.7);
+        }
+
+        // 创建网格
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.name = `plt_zone_${zoneIndex}`;
+        mesh.userData = {
+          zoneName: zone.name,
+          zoneType: zone.type,
+          isPLT: true
+        };
+
+        // 添加到场景
+        scene.add(mesh);
+        
+        // 添加区域标签(可选)
+        this._addZoneLabel(zone, mesh, zoneIndex);
+
+        console.log(`Rendered zone ${zone.name} with ${geometry.index.count / 3} triangles`);
+        resolve();
+      });
+    });
+  }
+
+  _createZoneGeometry(zone) {
+    const geometry = new THREE.BufferGeometry();
+    
+    // 顶点数据
+    geometry.setAttribute(
+      'position',
+      new THREE.BufferAttribute(zone.vertices, 3)
+    );
+  
+    // 安全极值计算函数
+    const getArrayExtremes = (arr) => {
+      let min = Infinity, max = -Infinity;
+      for (let i = 0; i < arr.length; i++) {
+        if (arr[i] < min) min = arr[i];
+        if (arr[i] > max) max = arr[i];
+      }
+      return { min, max };
+    };
+  
+    // 索引处理
+    let indicesArray = zone.indices || new Uint32Array(0);
+    if (indicesArray.length > 0) {
+      // 计算极值
+      const { min: minIndex, max: maxIndex } = getArrayExtremes(indicesArray);
+      const vertexCount = zone.vertices.length / 3;
+  
+      // 索引验证
+      if (maxIndex >= vertexCount) {
+        console.warn(`修正${maxIndex - vertexCount + 1}个超出范围的索引`);
+        const validIndices = new Uint32Array(indicesArray.length);
+        for (let i = 0; i < indicesArray.length; i++) {
+          validIndices[i] = indicesArray[i] >= vertexCount ? 0 : indicesArray[i];
+        }
+        indicesArray = validIndices;
+      }
+  
+      // 四边形转换
+      if (indicesArray.length % 4 === 0) {
+        console.log('四边形->三角形转换');
+        const triCount = indicesArray.length / 4 * 6;
+        const triangles = new Uint32Array(triCount);
+        
+        for (let i = 0; i < indicesArray.length / 4; i++) {
+          const base = i * 4;
+          triangles[i*6]   = indicesArray[base];
+          triangles[i*6+1] = indicesArray[base+1];
+          triangles[i*6+2] = indicesArray[base+2];
+          triangles[i*6+3] = indicesArray[base];
+          triangles[i*6+4] = indicesArray[base+2];
+          triangles[i*6+5] = indicesArray[base+3];
+        }
+        indicesArray = triangles;
+      }
+  
+      geometry.setIndex(new THREE.BufferAttribute(indicesArray, 1));
+    }
+  
+    // 计算边界
+    geometry.computeBoundingSphere();
+    geometry.computeBoundingBox();
+    
+    console.log('几何体验证:', {
+      vertices: zone.vertices.length / 3,
+      triangles: indicesArray.length / 3,
+      radius: geometry.boundingSphere.radius.toFixed(2)
+    });
+
+    geometry.computeVertexNormals(); 
+    console.log('法线检查:', {
+      firstNormal: geometry.attributes.normal?.array.slice(0, 3)
+    });
+  
+    return geometry;
+  }
+
+  _addZoneLabel(zone, mesh, zoneIndex) {
+    // 使用CSS2DRenderer需要先初始化
+    if (!this.labelRenderer) return;
+    
+    const labelDiv = document.createElement('div');
+    labelDiv.className = 'plt-zone-label';
+    labelDiv.textContent = `${zone.name}`;
+    labelDiv.style.backgroundColor = `hsla(${zoneIndex * 360 / 8}, 70%, 50%, 0.7)`;
+    
+    const label = new THREE.CSS2DObject(labelDiv);
+    label.position.set(0, 0, 0); // 位置会在动画中更新
+    
+    mesh.userData.label = label;
+    mesh.add(label);
+  }
+
+  _cleanupPreviousRender(scene) {
+    scene.children.forEach(obj => {
+      if (obj.userData?.isPLT) {
+        // 清理几何体和材质
+        if (obj.geometry) obj.geometry.dispose();
+        if (obj.material) {
+          if (Array.isArray(obj.material)) {
+            obj.material.forEach(m => m.dispose());
+          } else {
+            obj.material.dispose();
+          }
+        }
+        // 清理标签
+        if (obj.userData.label) {
+          obj.remove(obj.userData.label);
+        }
+        scene.remove(obj);
+      }
+    });
+  }
+
+  // 更新标签位置(需要在动画循环中调用)
+  updateLabels(camera) {
+    if (!this.labelRenderer) return;
+    
+    this.labelRenderer.render(scene, camera);
+    
+    scene.children.forEach(obj => {
+      if (obj.userData?.label) {
+        // 将标签定位到几何中心
+        obj.geometry.computeBoundingSphere();
+        const center = obj.geometry.boundingSphere.center.clone();
+        obj.localToWorld(center);
+        obj.userData.label.position.copy(center);
+      }
+    });
+  }
+}