|
@@ -8,45 +8,47 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
-
|
|
|
- <!-- 加载状态 & 错误提示 -->
|
|
|
- <div v-if="isLoading" class="fixed inset-0 bg-white bg-opacity-80 flex items-center justify-center z-50">
|
|
|
- <div class="text-center">
|
|
|
- <div class="spinner mx-auto"></div>
|
|
|
- <p class="mt-4 text-gray-700">数据加载中,请稍候...</p>
|
|
|
- <p class="text-sm text-gray-500 mt-2">正在初始化图表容器</p>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+
|
|
|
<div v-if="error" class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-6">
|
|
|
<p>数据加载失败: {{ error.message }}</p>
|
|
|
<button class="mt-2 px-3 py-1 bg-red-500 text-white rounded" @click="initCharts">重试</button>
|
|
|
</div>
|
|
|
|
|
|
<!-- 1. 初始Cd 单独箱线图 -->
|
|
|
- <section class="mb-6 chart-container" v-if="!isLoading && !error">
|
|
|
- <h3 class="section-title text-base font-semibold">初始Cd(Initial_Cd)分布箱线图</h3>
|
|
|
- <div ref="initialCdChart" style="width: 100%; height: 400px;"></div>
|
|
|
- <div v-if="!chartInstanceInitial" class="bg-yellow-50 border border-yellow-200 p-4 rounded mt-4">
|
|
|
- <p class="text-yellow-700">图表初始化中...</p>
|
|
|
- <button class="mt-2 px-3 py-1 bg-yellow-500 text-white rounded" @click="initInitialCdChart">
|
|
|
- 重新尝试初始化
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- </section>
|
|
|
-
|
|
|
- <!-- 2. 其他指标 合并箱线图 -->
|
|
|
- <section class="mb-6 chart-container" v-if="!isLoading && !error">
|
|
|
- <div class="flex flex-wrap justify-between items-center mb-4">
|
|
|
- <h3 class="section-title text-base font-semibold">其他通量Cd指标分布箱线图</h3>
|
|
|
- </div>
|
|
|
- <div ref="otherIndicatorsChart" style="width: 100%; height: 400px;"></div>
|
|
|
- <div v-if="!chartInstanceOther" class="bg-yellow-50 border border-yellow-200 p-4 rounded mt-4">
|
|
|
- <p class="text-yellow-700">图表初始化中...</p>
|
|
|
- <button class="mt-2 px-3 py-1 bg-yellow-500 text-white rounded" @click="initOtherIndicatorsChart">
|
|
|
- 重新尝试初始化
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- </section>
|
|
|
+<section class="mb-6 chart-container">
|
|
|
+ <h3 class="section-title text-base font-semibold">初始Cd(Initial_Cd)分布箱线图</h3>
|
|
|
+ <div ref="initialCdChart" style="width: 100%; height: 400px;"></div>
|
|
|
+ <!-- 容器内的加载遮罩 -->
|
|
|
+ <div v-if="isLoading" class="absolute inset-0 bg-white bg-opacity-80 flex items-center justify-center">
|
|
|
+ <div class="spinner"></div>
|
|
|
+ </div>
|
|
|
+ <!-- 错误提示(保留重试按钮) -->
|
|
|
+ <div v-if="error && !chartInstanceInitial" class="bg-yellow-50 border border-yellow-200 p-4 rounded mt-4">
|
|
|
+ <p class="text-yellow-700">图表初始化失败: {{ error.message }}</p>
|
|
|
+ <button class="mt-2 px-3 py-1 bg-yellow-500 text-white rounded" @click="initInitialCdChart">
|
|
|
+ 重新尝试初始化
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+</section>
|
|
|
+
|
|
|
+<!-- 2. 其他指标 合并箱线图 -->
|
|
|
+<section class="mb-6 chart-container">
|
|
|
+ <div class="flex flex-wrap justify-between items-center mb-4">
|
|
|
+ <h3 class="section-title text-base font-semibold">其他通量Cd指标分布箱线图</h3>
|
|
|
+ </div>
|
|
|
+ <div ref="otherIndicatorsChart" style="width: 100%; height: 400px;"></div>
|
|
|
+ <!-- 容器内的加载遮罩 -->
|
|
|
+ <div v-if="isLoading" class="absolute inset-0 bg-white bg-opacity-80 flex items-center justify-center">
|
|
|
+ <div class="spinner"></div>
|
|
|
+ </div>
|
|
|
+ <!-- 错误提示(保留重试按钮) -->
|
|
|
+ <div v-if="error && !chartInstanceOther" class="bg-yellow-50 border border-yellow-200 p-4 rounded mt-4">
|
|
|
+ <p class="text-yellow-700">图表初始化失败: {{ error.message }}</p>
|
|
|
+ <button class="mt-2 px-3 py-1 bg-yellow-500 text-white rounded" @click="initOtherIndicatorsChart">
|
|
|
+ 重新尝试初始化
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+</section>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
@@ -129,18 +131,28 @@ const calculateFieldStats = (data, fieldKey, fieldName) => {
|
|
|
const rawValues = data.map(item => item[fieldKey]);
|
|
|
const values = rawValues
|
|
|
.map((val, idx) => {
|
|
|
+ // 更严格的数值校验
|
|
|
+ if (val === null || val === undefined || val === '' || isNaN(Number(val))) {
|
|
|
+ log(`无效数据: ${fieldName} 第${idx+1}条 → ${val}`, fieldName);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
const num = Number(val);
|
|
|
- if (isNaN(num)) log(`无效数据: ${fieldName} 第${idx+1}条 →`, val);
|
|
|
- return isNaN(num) ? null : num;
|
|
|
+ // 过滤极端值(根据业务需求调整阈值)
|
|
|
+ if (num < -1000000 || num > 1000000) {
|
|
|
+ log(`极端值过滤: ${fieldName} 第${idx+1}条 → ${num}`, fieldName);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return num;
|
|
|
})
|
|
|
.filter(v => v !== null);
|
|
|
- //console.log(`${fieldName}有效数据量:`,values.length);
|
|
|
|
|
|
+ // 处理无有效数据的情况
|
|
|
if (values.length === 0) {
|
|
|
- log(`无有效数据: ${fieldName}`);
|
|
|
+ log(`无有效数据: ${fieldName}`, fieldName);
|
|
|
return { key: fieldKey, name: fieldName, min: null, q1: null, median: null, q3: null, max: null };
|
|
|
}
|
|
|
|
|
|
+ // 排序并计算分位数(保持原逻辑)
|
|
|
const sorted = [...values].sort((a, b) => a - b);
|
|
|
const min = sorted[0];
|
|
|
const max = sorted[sorted.length - 1];
|
|
@@ -186,119 +198,131 @@ const buildBoxplotData = (statsArray) => {
|
|
|
};
|
|
|
|
|
|
// 初始化【初始Cd】图表(独立箱线图)
|
|
|
-const initInitialCdChart = (retryCount = 0) => {
|
|
|
- // 容器检查与重试机制
|
|
|
+const initInitialCdChart = () => {
|
|
|
+ // 容器存在性检查
|
|
|
if (!initialCdChart.value) {
|
|
|
- if (retryCount < 10) {
|
|
|
- console.warn(`initialCdChart容器未找到,第${retryCount+1}次重试...`);
|
|
|
- setTimeout(() => initInitialCdChart(retryCount + 1), 100);
|
|
|
- return;
|
|
|
- } else {
|
|
|
- console.error('initialCdChart容器未找到,重试超时');
|
|
|
- return;
|
|
|
- }
|
|
|
+ console.error('initialCdChart容器未找到');
|
|
|
+ error.value = new Error('初始Cd图表容器未找到,请刷新页面重试');
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
// 容器尺寸检查
|
|
|
- if (initialCdChart.value.offsetWidth === 0 || initialCdChart.value.offsetHeight === 0) {
|
|
|
- if (retryCount < 5) {
|
|
|
- setTimeout(() => initInitialCdChart(retryCount + 1), 200);
|
|
|
- return;
|
|
|
- }
|
|
|
+ const { offsetWidth, offsetHeight } = initialCdChart.value;
|
|
|
+ if (offsetWidth === 0 || offsetHeight === 0) {
|
|
|
+ console.error('initialCdChart容器尺寸异常', { offsetWidth, offsetHeight });
|
|
|
+ error.value = new Error('初始Cd图表容器尺寸异常,请检查页面样式');
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
- if (chartInstanceInitial.value) chartInstanceInitial.value.dispose();
|
|
|
- chartInstanceInitial.value = echarts.init(initialCdChart.value);
|
|
|
- const xAxisData = fieldConfig.initialCd.map(ind => ind.name);
|
|
|
- const boxData = buildBoxplotData(initialCdStats.value);
|
|
|
+ // 销毁旧实例
|
|
|
+ if (chartInstanceInitial.value) {
|
|
|
+ chartInstanceInitial.value.dispose();
|
|
|
+ }
|
|
|
|
|
|
- chartInstanceInitial.value.setOption({
|
|
|
- title: { text: '初始Cd分布箱线图', left: 'center', textStyle: { fontSize: 14 } },
|
|
|
- tooltip: {
|
|
|
- trigger: "item",
|
|
|
- formatter: (params) => formatTooltip(initialCdStats.value[params.dataIndex])
|
|
|
- },
|
|
|
- grid: { top: 60, right: 30, bottom: 25, left: 60 },
|
|
|
- xAxis: {
|
|
|
- type: "category",
|
|
|
- data: xAxisData,
|
|
|
- axisLabel: { fontSize: 12 }
|
|
|
- },
|
|
|
- yAxis: {
|
|
|
- type: "value",
|
|
|
- name: 'g/ha',
|
|
|
- nameTextStyle: { fontSize: 12 },
|
|
|
- axisLabel: { fontSize: 11 },
|
|
|
- scale: true
|
|
|
- },
|
|
|
- series: [{
|
|
|
- name: '初始Cd',
|
|
|
- type: "boxplot",
|
|
|
- itemStyle: {
|
|
|
- color: (p) => fieldConfig.initialCd[p.dataIndex].color,
|
|
|
- borderWidth: 2
|
|
|
+ // 初始化图表
|
|
|
+ try {
|
|
|
+ chartInstanceInitial.value = echarts.init(initialCdChart.value);
|
|
|
+ const xAxisData = fieldConfig.initialCd.map(ind => ind.name);
|
|
|
+ const boxData = buildBoxplotData(initialCdStats.value);
|
|
|
+
|
|
|
+ chartInstanceInitial.value.setOption({
|
|
|
+ // 保持原配置不变...
|
|
|
+ title: { text: '初始Cd分布箱线图', left: 'center', textStyle: { fontSize: 14 } },
|
|
|
+ tooltip: {
|
|
|
+ trigger: "item",
|
|
|
+ formatter: (params) => formatTooltip(initialCdStats.value[params.dataIndex])
|
|
|
},
|
|
|
- data: boxData
|
|
|
- }]
|
|
|
- });
|
|
|
+ grid: { top: 60, right: 30, bottom: 25, left: 60 },
|
|
|
+ xAxis: {
|
|
|
+ type: "category",
|
|
|
+ data: xAxisData,
|
|
|
+ axisLabel: { fontSize: 12 }
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: "value",
|
|
|
+ name: 'g/ha',
|
|
|
+ nameTextStyle: { fontSize: 12 },
|
|
|
+ axisLabel: { fontSize: 11 },
|
|
|
+ scale: true
|
|
|
+ },
|
|
|
+ series: [{
|
|
|
+ name: '初始Cd',
|
|
|
+ type: "boxplot",
|
|
|
+ itemStyle: {
|
|
|
+ color: (p) => fieldConfig.initialCd[p.dataIndex].color,
|
|
|
+ borderWidth: 2
|
|
|
+ },
|
|
|
+ data: boxData
|
|
|
+ }]
|
|
|
+ });
|
|
|
+ } catch (err) {
|
|
|
+ console.error('初始Cd图表初始化失败', err);
|
|
|
+ error.value = new Error(`初始Cd图表初始化失败: ${err.message}`);
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
+
|
|
|
// 初始化【其他指标】合并图表
|
|
|
-const initOtherIndicatorsChart = (retryCount = 0) => {
|
|
|
+const initOtherIndicatorsChart = () => {
|
|
|
+ // 容器存在性检查
|
|
|
if (!otherIndicatorsChart.value) {
|
|
|
- if (retryCount < 10) {
|
|
|
- console.warn(`otherIndicatorsChart容器未找到,第${retryCount+1}次重试...`);
|
|
|
- setTimeout(() => initOtherIndicatorsChart(retryCount + 1), 100);
|
|
|
- return;
|
|
|
- } else {
|
|
|
- console.error('otherIndicatorsChart容器未找到,重试超时');
|
|
|
- return;
|
|
|
- }
|
|
|
+ console.error('otherIndicatorsChart容器未找到');
|
|
|
+ error.value = new Error('其他指标图表容器未找到,请刷新页面重试');
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
// 容器尺寸检查
|
|
|
- if (otherIndicatorsChart.value.offsetWidth === 0 || otherIndicatorsChart.value.offsetHeight === 0) {
|
|
|
- if (retryCount < 5) {
|
|
|
- setTimeout(() => initOtherIndicatorsChart(retryCount + 1), 200);
|
|
|
- return;
|
|
|
- }
|
|
|
+ const { offsetWidth, offsetHeight } = otherIndicatorsChart.value;
|
|
|
+ if (offsetWidth === 0 || offsetHeight === 0) {
|
|
|
+ console.error('otherIndicatorsChart容器尺寸异常', { offsetWidth, offsetHeight });
|
|
|
+ error.value = new Error('其他指标图表容器尺寸异常,请检查页面样式');
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
- if (chartInstanceOther.value) chartInstanceOther.value.dispose();
|
|
|
- chartInstanceOther.value = echarts.init(otherIndicatorsChart.value);
|
|
|
- const xAxisData = fieldConfig.otherIndicators.map(ind => ind.name);
|
|
|
- const boxData = buildBoxplotData(otherIndicatorsStats.value);
|
|
|
+ // 销毁旧实例
|
|
|
+ if (chartInstanceOther.value) {
|
|
|
+ chartInstanceOther.value.dispose();
|
|
|
+ }
|
|
|
|
|
|
- chartInstanceOther.value.setOption({
|
|
|
- title: { text: '其他通量Cd指标分布对比', left: 'center', textStyle: { fontSize: 14 } },
|
|
|
- tooltip: {
|
|
|
- trigger: "item",
|
|
|
- formatter: (params) => formatTooltip(otherIndicatorsStats.value[params.dataIndex])
|
|
|
- },
|
|
|
- grid: { top: 60, right: 30, bottom: 70, left: 60 },
|
|
|
- xAxis: {
|
|
|
- type: "category",
|
|
|
- data: xAxisData,
|
|
|
- axisLabel: { fontSize: 11, rotate: 45 }
|
|
|
- },
|
|
|
- yAxis: {
|
|
|
- type: "value",
|
|
|
- name: 'g/ha/a',
|
|
|
- nameTextStyle: { fontSize: 12 },
|
|
|
- axisLabel: { fontSize: 11 }
|
|
|
- },
|
|
|
- series: [{
|
|
|
- name: '其他指标',
|
|
|
- type: "boxplot",
|
|
|
- itemStyle: {
|
|
|
- color: (p) => fieldConfig.otherIndicators[p.dataIndex].color,
|
|
|
- borderWidth: 2
|
|
|
+ // 初始化图表
|
|
|
+ try {
|
|
|
+ chartInstanceOther.value = echarts.init(otherIndicatorsChart.value);
|
|
|
+ const xAxisData = fieldConfig.otherIndicators.map(ind => ind.name);
|
|
|
+ const boxData = buildBoxplotData(otherIndicatorsStats.value);
|
|
|
+
|
|
|
+ chartInstanceOther.value.setOption({
|
|
|
+ // 保持原配置不变...
|
|
|
+ title: { text: '其他通量Cd指标分布对比', left: 'center', textStyle: { fontSize: 14 } },
|
|
|
+ tooltip: {
|
|
|
+ trigger: "item",
|
|
|
+ formatter: (params) => formatTooltip(otherIndicatorsStats.value[params.dataIndex])
|
|
|
},
|
|
|
- data: boxData
|
|
|
- }]
|
|
|
- });
|
|
|
-
|
|
|
-
|
|
|
+ grid: { top: 60, right: 30, bottom: 70, left: 60 },
|
|
|
+ xAxis: {
|
|
|
+ type: "category",
|
|
|
+ data: xAxisData,
|
|
|
+ axisLabel: { fontSize: 11, rotate: 45 }
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: "value",
|
|
|
+ name: 'g/ha/a',
|
|
|
+ nameTextStyle: { fontSize: 12 },
|
|
|
+ axisLabel: { fontSize: 11 }
|
|
|
+ },
|
|
|
+ series: [{
|
|
|
+ name: '其他指标',
|
|
|
+ type: "boxplot",
|
|
|
+ itemStyle: {
|
|
|
+ color: (p) => fieldConfig.otherIndicators[p.dataIndex].color,
|
|
|
+ borderWidth: 2
|
|
|
+ },
|
|
|
+ data: boxData
|
|
|
+ }]
|
|
|
+ });
|
|
|
+ } catch (err) {
|
|
|
+ console.error('其他指标图表初始化失败', err);
|
|
|
+ error.value = new Error(`其他指标图表初始化失败: ${err.message}`);
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
|
|
@@ -323,15 +347,45 @@ const initCharts = async () => {
|
|
|
try {
|
|
|
isLoading.value = true;
|
|
|
error.value = null;
|
|
|
+ chartInstanceInitial.value = null;
|
|
|
+ chartInstanceOther.value = null;
|
|
|
|
|
|
+ // 1. 获取数据
|
|
|
const data = await fetchData();
|
|
|
+ if (!data || data.length === 0) {
|
|
|
+ throw new Error('未获取到有效数据');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 计算统计数据
|
|
|
calculateAllStats(data);
|
|
|
|
|
|
- await nextTick(); // 等待DOM渲染完成
|
|
|
+ // 3. 等待DOM更新
|
|
|
+ await nextTick();
|
|
|
+
|
|
|
+ // 4. 轮询检查容器尺寸(最多等待3秒)
|
|
|
+ const checkContainers = () => {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ let checkCount = 0;
|
|
|
+ const interval = setInterval(() => {
|
|
|
+ // 检查两个容器的宽度是否有效
|
|
|
+ const initialWidth = initialCdChart.value?.offsetWidth || 0;
|
|
|
+ const otherWidth = otherIndicatorsChart.value?.offsetWidth || 0;
|
|
|
+
|
|
|
+ if (initialWidth > 0 && otherWidth > 0) {
|
|
|
+ clearInterval(interval);
|
|
|
+ resolve();
|
|
|
+ } else if (checkCount >= 30) { // 30 * 100ms = 3秒
|
|
|
+ clearInterval(interval);
|
|
|
+ reject(new Error('图表容器尺寸异常,准备超时,请检查样式'));
|
|
|
+ }
|
|
|
+ checkCount++;
|
|
|
+ }, 100);
|
|
|
+ });
|
|
|
+ };
|
|
|
|
|
|
- // 添加额外延迟确保容器渲染
|
|
|
- await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
+ await checkContainers();
|
|
|
|
|
|
+ // 5. 初始化图表
|
|
|
initInitialCdChart();
|
|
|
initOtherIndicatorsChart();
|
|
|
|