Commit 9142da4f by zhaopanyu

zpy

parent 08340864
......@@ -80,7 +80,7 @@ export const constantRoutes = [
path: "index",
component: () => import("@/views/index"),
name: "Index",
meta: { title: "钻头推荐", icon: "dashboard", affix: true },
meta: { title: "首页", icon: "dashboard", affix: true },
},
],
},
......
<template>
<div class="chart-container">
<div class="chart-wrapper">
<div id="mainzftdj" class="chart" ref="chartRef"></div>
<div v-if="legendItems.length" class="legend-panel">
<div class="legend-title">层位图例</div>
<div class="legend-scroll">
<div
v-for="(item, index) in legendItems"
:key="getLegendKey(item, index)"
class="legend-item"
>
<span class="legend-swatch">
<img
v-if="hasLegendSvg(item)"
:src="getLegendSvgSrc(item)"
alt=""
class="legend-swatch-img"
/>
<span
v-else
class="legend-swatch-color"
:style="{ backgroundColor: getLegendColor(index) }"
></span>
</span>
<span class="legend-label">{{ formatLegendName(item) }}</span>
</div>
</div>
</div>
</div>
<div v-if="loading" class="loading-overlay">
<div class="loading-spinner"></div>
<span>加载中...</span>
</div>
</div>
</template>
<script>
import * as echarts from "echarts";
import { getdjZft } from "@/api/optimization/initialization";
import { text } from "d3";
export default {
name: "HistogramGraph",
props: {
jh: {
type: String,
default: "",
},
jhs: {
type: String,
default: ''
},
// 美化配置选项
theme: {
type: String,
default: "modern", // modern, elegant, vibrant
validator: value => ["modern", "elegant", "vibrant"].includes(value)
}
},
data() {
return {
mockData: {},
myChart: null,
initRetryCount: 0,
maxRetryCount: 5,
resizeObserver: null,
loading: false,
debounceTimer: null,
lastStackedAreas: null,
lastChartConfig: null,
lastXAxisLabels: null,
lastDepthIntervals: null,
currentGraphicElements: [],
tlList: [],
};
},
computed: {
// 根据主题获取颜色配置
colorScheme() {
const schemes = {
modern: {
primary: "#3B82F6",
secondary: "#10B981",
accent: "#F59E0B",
background: "#F8FAFC",
text: "#1F2937",
border: "#E5E7EB",
gradient: {
start: "#3B82F6",
end: "#1D4ED8"
}
},
elegant: {
primary: "#6366F1",
secondary: "#8B5CF6",
accent: "#EC4899",
background: "#FAFAFA",
text: "#374151",
border: "#D1D5DB",
gradient: {
start: "#6366F1",
end: "#4F46E5"
}
},
vibrant: {
primary: "#EF4444",
secondary: "#06B6D4",
accent: "#F97316",
background: "#FEFEFE",
text: "#111827",
border: "#F3F4F6",
gradient: {
start: "#EF4444",
end: "#DC2626"
}
}
};
return schemes[this.theme];
},
legendItems() {
return Array.isArray(this.tlList) ? this.tlList : [];
}
},
watch: {
jh: {
handler(newVal) {
// 不自动刷新,等待父级触发 loadData
},
immediate: false
}
},
mounted() {
// 初始化空图表,不拉数据;等待父组件触发 loadData
this.initChart();
this.setupEventListeners();
},
beforeDestroy() {
this.cleanup();
},
methods: {
// 初始化图表
async initChart() {
try {
this.loading = true;
const chartDom = this.$refs.chartRef;
if (!chartDom) {
throw new Error("未找到图表容器 DOM");
}
this.setChartDimensions(chartDom);
// 重试机制优化
if (this.shouldRetry()) {
this.scheduleRetry();
return;
}
this.resetRetryCount();
this.disposeChart();
this.createChart(chartDom);
const mockData = await this.getList();
if (!this.hasValidData(mockData)) {
this.renderEmpty(chartDom);
return;
}
await this.renderRealData(mockData);
} catch (error) {
console.error("图表初始化失败:", error);
this.handleError(error);
} finally {
this.loading = false;
}
},
// 设置图表尺寸
setChartDimensions(chartDom) {
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const availableHeight = viewportHeight - 120;
const availableWidth = viewportWidth - 80;
chartDom.style.width = `${availableWidth}px`;
chartDom.style.height = `${availableHeight}px`;
chartDom.offsetHeight; // 强制重排
},
// 检查是否需要重试
shouldRetry() {
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const availableHeight = viewportHeight - 120;
const availableWidth = viewportWidth - 80;
return (availableWidth <= 0 || availableHeight <= 0) && this.initRetryCount < this.maxRetryCount;
},
// 安排重试
scheduleRetry() {
this.initRetryCount++;
setTimeout(() => this.initChart(), 500);
},
// 重置重试计数
resetRetryCount() {
this.initRetryCount = 0;
},
// 销毁图表
disposeChart() {
if (this.myChart) {
this.myChart.dispose();
}
},
// 创建图表实例
createChart(chartDom) {
this.myChart = echarts.init(chartDom, null, {
renderer: 'canvas',
useDirtyRect: true
});
},
// 检查数据有效性
hasValidData(mockData) {
return mockData &&
mockData.wellData &&
mockData.wellData.depthLine &&
mockData.wellData.depthLine.length > 0;
},
// 设置事件监听器
setupEventListeners() {
window.addEventListener("resize", this.handleResize);
this.observeParentResize();
},
// 清理资源
cleanup() {
window.removeEventListener("resize", this.handleResize);
if (this.myChart) {
this.myChart.dispose();
}
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
},
// 观察父容器大小变化
observeParentResize() {
const parentDom = this.$refs.chartRef?.parentElement;
if (parentDom && window.ResizeObserver) {
this.resizeObserver = new ResizeObserver(() => {
this.debouncedResize();
});
this.resizeObserver.observe(parentDom);
}
},
// 获取数据
async getList() {
try {
const res = await getdjZft({
jhs: `${this.jh},${this.jhs}`
});
this.mockData = res?.mockData || {};
this.tlList = res?.tlList || res?.mockData?.tlList || [];
return this.mockData;
} catch (error) {
console.error("获取数据失败:", error);
throw error;
}
},
// 供父组件在切换到本tab时调用
async loadData() {
await this.initChart();
},
// 处理窗口大小变化
handleResize() {
this.debouncedResize();
},
// 防抖处理resize
debouncedResize() {
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
this.debounceTimer = setTimeout(() => {
this.performResize();
}, 200);
},
// 执行resize操作
performResize() {
if (this.myChart) {
const chartDom = this.$refs.chartRef;
if (chartDom) {
this.setChartDimensions(chartDom);
}
this.myChart.resize();
if (this.lastStackedAreas && this.lastChartConfig && this.lastXAxisLabels) {
this.drawStratumLabels(this.lastStackedAreas, this.lastChartConfig, this.lastXAxisLabels);
}
if (this.lastDepthIntervals && this.lastXAxisLabels && this.lastChartConfig) {
this.drawJhSeparators(this.lastDepthIntervals, this.lastXAxisLabels, this.lastChartConfig);
}
if (this.lastXAxisLabels && this.lastChartConfig) {
this.drawCategoryEdgeLines(this.lastXAxisLabels, this.lastChartConfig);
this.drawTopBandSvg(this.lastXAxisLabels, this.lastChartConfig, this.lastStackedAreas);
}
}
},
// 刷新图表
async refreshChart() {
if (this.myChart) {
await this.initChart();
}
},
// 渲染真实数据
async renderRealData(mockData) {
try {
const { wellData } = mockData;
console.log(wellData, 'wellData');
const xAxisLabels = wellData.depthLine.map((point) => point.x);
try { window.__hist_xLabels = xAxisLabels; } catch (e) { }
const option = {
...this.getDefaultChartOption(),
xAxis: this.createXAxis(xAxisLabels),
yAxis: this.createYAxis(mockData.chartConfig),
series: this.createSeries(wellData)
};
this.myChart.setOption(option, true);
// 确保图表完全渲染后再resize
this.$nextTick(() => {
this.myChart.resize();
this.lastStackedAreas = wellData.stackedAreas;
this.lastChartConfig = mockData.chartConfig;
this.lastXAxisLabels = xAxisLabels;
this.drawStratumLabels(this.lastStackedAreas, this.lastChartConfig, this.lastXAxisLabels);
this.lastDepthIntervals = wellData.depthIntervals || [];
this.drawJhSeparators(this.lastDepthIntervals, this.lastXAxisLabels, this.lastChartConfig);
// 在顶部为每个类目绘制左右两条短竖线
this.drawCategoryEdgeLines(this.lastXAxisLabels, this.lastChartConfig);
// 顶部段内根据 x 和 y2 渲染 SVG 纹理
this.drawTopBandSvg(this.lastXAxisLabels, this.lastChartConfig, this.lastStackedAreas);
});
} catch (error) {
console.error("渲染数据失败:", error);
this.handleError(error);
}
},
// 获取默认图表配置
getDefaultChartOption() {
const colors = this.colorScheme;
return {
title: { text: "", subtext: "" },
aria: { enabled: false },
backgroundColor: colors.background,
tooltip: {
trigger: "axis",
axisPointer: {
type: "cross",
label: { backgroundColor: colors.primary },
crossStyle: { color: colors.border }
},
backgroundColor: 'rgba(255,255,255,0.95)',
borderColor: colors.border,
borderWidth: 1,
textStyle: { color: colors.text },
extraCssText: 'box-shadow: 0 4px 12px rgba(0,0,0,0.15); border-radius: 8px;',
formatter: (params) => {
if (!params || params.length === 0) return '';
const dataIndex = params[0].dataIndex;
const depthInterval = this.mockData?.wellData?.depthIntervals?.[dataIndex];
if (!depthInterval) {
// 如果没有depthInterval数据,使用默认格式
let result = `<div style="font-weight: 600; margin-bottom: 8px; color: ${colors.primary};">${params[0].axisValue}</div>`;
params.forEach(param => {
const color = param.color;
result += `<div style="margin: 4px 0;">
<span style="display: inline-block; width: 10px; height: 10px; background-color: ${color}; border-radius: 50%; margin-right: 8px;"></span>
<span style="font-weight: 500;">${param.seriesName}:</span>
<span style="margin-left: 8px; font-weight: 600;">${param.value}</span>
</div>`;
});
return result;
}
// 显示井号信息
let result = `<div style="font-weight: 600; margin-bottom: 8px; color: ${colors.primary};">${depthInterval.jh || depthInterval.x}</div>`;
// 显示详细信息
const modelLabel = this.formatXAxisLabel(depthInterval.x || '-');
result += `<div style="margin: 4px 0; padding: 4px 0; border-top: 1px solid ${colors.border};">
<div style="margin: 2px 0;"><span style="font-weight: 500;">型号:</span> <span style="margin-left: 8px; font-weight: 600;">${modelLabel}</span></div>
<div style="margin: 2px 0;"><span style="font-weight: 500;">尺寸:</span> <span style="margin-left: 8px; font-weight: 600;">${depthInterval.ztcc || '-'}</span></div>
<div style="margin: 2px 0;"><span style="font-weight: 500;">进尺mm:</span> <span style="margin-left: 8px; font-weight: 600;">${depthInterval.jc || '-'}</span></div>
<div style="margin: 2px 0;"><span style="font-weight: 500;">深度区间:</span> <span style="margin-left: 8px; font-weight: 600;">${depthInterval.interval || '-'}</span></div>
</div>`;
// 只显示非地层信息的系列数据(过滤掉地层相关的系列)
// params.forEach(param => {
// // 过滤掉地层相关的系列名称
// if (param.seriesName && !param.seriesName.includes('地层') && param.seriesName !== '占位') {
// const color = param.color;
// result += `<div style="margin: 4px 0;">
// <span style="display: inline-block; width: 10px; height: 10px; background-color: ${color}; border-radius: 50%; margin-right: 8px;"></span>
// <span style="font-weight: 500;">${param.seriesName}:</span>
// <span style="margin-left: 8px; font-weight: 600;">${param.value}</span>
// </div>`;
// }
// });
return result;
}
},
grid: {
top: 10,
left: "4%",
right: "4%",
bottom: "10%",
containLabel: true,
show: false,
},
animation: true,
animationDuration: 1500,
animationEasing: 'cubicOut',
animationDelay: function (idx) {
return idx * 100;
}
};
},
// 处理区域系列
async processAreaSeries(wellData, xAxisLabels) {
const stackedAreas = wellData?.stackedAreas || [];
// 兼容旧格式:数组(所有井已按全局顺序平铺)
if (Array.isArray(stackedAreas)) {
return await Promise.all(
stackedAreas
.filter(area => area.svg !== null)
.map(async (area) => {
let areaStyle = { opacity: 0.6 };
if (area.svg) {
try {
const svgImage = await this.createSvgImage(area.svg);
areaStyle.color = { image: svgImage, repeat: "repeat" };
} catch (error) {
console.error('SVG转换失败:', error);
}
}
return {
name: area.name,
type: "line",
xAxisIndex: 1,
z: -1,
stack: "总量",
symbol: "none",
showSymbol: false,
areaStyle: areaStyle,
lineStyle: { width: 0 },
data: area.points.map((point) => (point && point.y2 != null) ? point.y2 : point.y),
};
})
);
}
// 新格式:对象,key 为井号,每口井内是该井的地层数组
const totalLen = Array.isArray(xAxisLabels) ? xAxisLabels.length : 0;
console.log(totalLen, 'totalLen');
// 全局 x 标签 -> 索引数组 映射
const xToIndices = new Map();
console.log(xToIndices, 'xToIndices');
(xAxisLabels || []).forEach((x, idx) => {
const key = String(x);
if (!xToIndices.has(key)) xToIndices.set(key, []);
xToIndices.get(key).push(idx);
});
const buildNullArray = (len) => Array.from({ length: len }, () => null);
console.log(buildNullArray, 'buildNullArray');
const results = [];
for (const [jh, layers] of Object.entries(stackedAreas)) {
if (!Array.isArray(layers) || layers.length === 0) continue;
// 估算该井的横向覆盖范围:取该井所有层的所有点对应的全局索引的 min/max
const indicesForJh = [];
const missingInXAxis = [];
for (const area of layers) {
for (const p of (area?.points || [])) {
const arr = xToIndices.get(String(p?.x)) || [];
if (arr.length === 0) {
missingInXAxis.push({ x: p?.x, jh, areaName: area?.name });
} else {
indicesForJh.push(...arr);
}
}
}
const startIdx = Math.min(...indicesForJh);
const endIdx = Math.max(...indicesForJh);
for (const area of (layers || [])) {
if (area == null) continue;
let areaStyle = { opacity: 0.6 };
if (area.svg) {
try {
const svgImage = await this.createSvgImage(area.svg);
areaStyle.color = { image: svgImage, repeat: "repeat" };
} catch (error) {
console.error('SVG转换失败:', error);
}
}
const data = buildNullArray(totalLen);
// 把该层每个点的 y2/y 填到该点 x 对应的全局索引中,仅限落在该井的覆盖范围内
const outOfRangePoints = [];
for (const p of (area.points || [])) {
const yVal = (p && p.y2 != null) ? p.y2 : p?.y;
const cands = xToIndices.get(String(p?.x)) || [];
if (cands.length === 0) continue;
let placed = false;
cands.forEach(ix => {
if (ix >= startIdx && ix <= endIdx) {
data[ix] = yVal;
placed = true;
}
});
if (!placed) {
outOfRangePoints.push({ x: p?.x, cands, jh, areaName: area?.name, startIdx, endIdx });
}
}
results.push({
name: area.name,
type: "line",
xAxisIndex: 1,
z: -1,
stack: "总量",
symbol: "none",
showSymbol: false,
areaStyle: areaStyle,
lineStyle: { width: 0 },
data
});
// 仅在有异常时输出调试信息,方便定位 x 匹配问题
if (missingInXAxis.length > 0 || outOfRangePoints.length > 0) {
// 使用 console.group 让日志更清晰
console.groupCollapsed && console.groupCollapsed(`层定位告警: 井号=${jh}`);
if (missingInXAxis.length > 0) {
console.warn('x 不在 xAxisLabels 中:', missingInXAxis);
}
if (outOfRangePoints.length > 0) {
console.warn('x 命中但不在井段范围内:', outOfRangePoints);
console.info('井段范围:', { jh, startIdx, endIdx });
}
console.groupEnd && console.groupEnd();
}
}
}
return results;
},
// 创建X轴配置
createXAxis(xAxisLabels) {
const colors = this.colorScheme;
const formatLabel = (value) => this.formatXAxisLabel(value);
return [
{
type: "category",
boundaryGap: true,
position: "top",
data: xAxisLabels,
axisLabel: {
interval: 0,
rotate: 0,
margin: 12,
align: "center",
fontSize: 13,
color: colors.text,
fontWeight: 500,
formatter: formatLabel
},
axisTick: {
alignWithLabel: true,
length: 6,
lineStyle: { color: colors.border }
},
splitLine: { show: false },
axisLine: {
show: true,
lineStyle: {
color: colors.border,
width: 2
}
}
},
{
type: "category",
boundaryGap: false,
position: "top",
show: false,
data: xAxisLabels
},
];
},
// 创建Y轴配置
createYAxis(chartConfig) {
const colors = this.colorScheme;
return [
{
type: "value",
name: "井深(m)",
nameTextStyle: {
color: colors.text,
fontSize: 13,
fontWeight: 600,
padding: [0, 0, 0, 8]
},
min: chartConfig.yAxis.min,
max: chartConfig.yAxis.max,
interval: chartConfig.yAxis.interval,
inverse: true,
axisLabel: {
formatter: "{value}",
fontSize: 12,
color: colors.text,
fontWeight: 500
},
splitLine: {
show: true,
lineStyle: {
color: colors.border,
type: 'dashed',
opacity: 0.3
}
},
axisLine: {
show: true,
lineStyle: {
color: colors.border,
width: 2
}
},
axisTick: {
show: true,
lineStyle: { color: colors.border }
}
},
{
type: "value",
name: "深度(m)",
nameTextStyle: {
color: colors.text,
fontSize: 13,
fontWeight: 600,
padding: [0, 8, 0, 0]
},
min: chartConfig.yAxis2.min,
max: chartConfig.yAxis2.max,
interval: chartConfig.yAxis2.interval,
position: "right",
axisLabel: {
formatter: "{value}",
fontSize: 12,
color: colors.text,
fontWeight: 500
},
splitLine: { show: false },
axisLine: {
show: true,
lineStyle: {
color: colors.border,
width: 2
}
},
axisTick: {
show: true,
lineStyle: { color: colors.border }
}
},
];
},
// 创建系列配置
createSeries(wellData, areaSeries) {
const colors = this.colorScheme;
return [
{
name: "井深数据",
type: "line",
zlevel: 25,
yAxisIndex: 1,
symbol: "circle",
symbolSize: 10,
itemStyle: {
color: colors.accent,
borderColor: '#fff',
borderWidth: 3,
shadowColor: 'rgba(0,0,0,0.2)',
shadowBlur: 8
},
lineStyle: {
color: colors.accent,
width: 3,
type: 'solid',
shadowColor: 'rgba(0,0,0,0.1)',
shadowBlur: 4
},
label: {
show: true,
position: "top",
formatter: "{c}",
fontSize: 13,
fontWeight: 600,
color: colors.accent,
backgroundColor: "rgba(255,255,255,0.95)",
padding: [6, 8],
borderRadius: 6,
borderColor: colors.accent,
borderWidth: 2,
shadowColor: 'rgba(0,0,0,0.1)',
shadowBlur: 4
},
data: wellData.depthLine.map((point) => point.y),
},
{
name: "占位",
type: "bar",
zlevel: 20,
stack: "total",
silent: true,
barWidth: "8%",
itemStyle: {
borderColor: "transparent",
color: "transparent"
},
emphasis: {
itemStyle: {
borderColor: "transparent",
color: "transparent"
}
},
data: wellData.depthIntervals.map((item) => item.placeholder),
},
{
name: "深度区间",
type: "bar",
zlevel: 20,
stack: "total",
barWidth: "6%",
label: {
show: true,
position: 'insideTop',
color: '#fff',
fontSize: 11,
fontWeight: 600,
backgroundColor: 'rgba(0,0,0,0.1)',
padding: [2, 3],
borderRadius: 4,
borderColor: 'rgba(255,255,255,0.3)',
borderWidth: 1,
formatter: (params) => {
const di = wellData.depthIntervals[params.dataIndex];
const value = di?.jc ?? di?.interval;
if (value === undefined || value === null || value === '') return params.data;
return `${value}`;
}
},
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: colors.gradient.start },
{ offset: 1, color: colors.gradient.end }
]
},
borderRadius: [4, 4, 0, 0],
shadowColor: 'rgba(0,0,0,0.2)',
shadowBlur: 8,
shadowOffsetY: 2
},
emphasis: {
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: this.adjustColor(colors.gradient.start, 1.2) },
{ offset: 1, color: this.adjustColor(colors.gradient.end, 1.2) }
]
},
shadowBlur: 12,
shadowOffsetY: 4
}
},
data: wellData.depthIntervals.map((item) => {
const value = item?.jc ?? item?.interval;
return typeof value === 'number' ? value : Number(value) || 0;
}),
},
// 底部显示 interval 数值的透明条,用于放置底部标签
{
name: '深度区间-底部标签',
type: 'bar',
zlevel: 20,
// 与主柱重叠以便把标签放在柱底部区域内
barGap: '-100%',
barWidth: '6%',
itemStyle: {
color: 'transparent',
borderColor: 'transparent'
},
emphasis: { itemStyle: { color: 'transparent', borderColor: 'transparent' } },
label: {
show: false,
position: 'insideBottom',
color: '#fff',
fontSize: 11,
fontWeight: 600,
backgroundColor: 'rgba(0,0,0,0.1)',
padding: [2, 3],
borderRadius: 4,
borderColor: 'rgba(255,255,255,0.3)',
borderWidth: 1,
distance: 0,
formatter: (params) => {
const di = wellData.depthIntervals[params.dataIndex];
const value = di?.jc ?? di?.interval;
if (value === undefined || value === null || value === '') return params.data;
return `进尺 ${value}`;
}
},
data: wellData.depthIntervals.map((item) => {
const value = item?.jc ?? item?.interval;
return typeof value === 'number' ? value : Number(value) || 0;
})
}
];
},
// 颜色调整工具方法
adjustColor(color, factor) {
if (color.startsWith('#')) {
const r = parseInt(color.slice(1, 3), 16);
const g = parseInt(color.slice(3, 5), 16);
const b = parseInt(color.slice(5, 7), 16);
const newR = Math.min(255, Math.round(r * factor));
const newG = Math.min(255, Math.round(g * factor));
const newB = Math.min(255, Math.round(b * factor));
return `rgb(${newR}, ${newG}, ${newB})`;
}
return color;
},
// 渲染空状态
renderEmpty(chartDom) {
const colors = this.colorScheme;
chartDom.innerHTML = `
<div class="empty-state" style="
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: ${colors.text};
font-size: 16px;
background: ${colors.background};
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
">
<div style="
font-size: 48px;
margin-bottom: 16px;
opacity: 0.6;
color: ${colors.primary};
">📊</div>
<div style="
font-size: 18px;
color: ${colors.text};
font-weight: 500;
">暂无数据</div>
<div style="
font-size: 14px;
color: ${colors.text};
opacity: 0.6;
margin-top: 8px;
">请检查数据配置</div>
</div>
`;
},
// 处理错误
handleError(error) {
console.error("图表错误:", error);
},
formatXAxisLabel(value) {
if (value == null) return "";
const str = String(value);
const idx = str.indexOf("-");
return idx === -1 ? str : str.slice(0, idx);
},
// 创建SVG图片
createSvgImage(svgString) {
return new Promise((resolve, reject) => {
const svgBlob = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' });
const svgUrl = URL.createObjectURL(svgBlob);
const img = new Image();
img.onload = () => {
URL.revokeObjectURL(svgUrl);
resolve(img);
};
img.onerror = (e) => {
URL.revokeObjectURL(svgUrl);
reject(e);
};
img.src = svgUrl;
});
},
// 清空地层标签(暂不在图内展示层位名称)
drawStratumLabels(stackedAreas, chartConfig, xAxisLabels) {
if (!this.myChart) return;
this.currentGraphicElements = [];
this.myChart.setOption({ graphic: { elements: [] } });
},
// 根据 depthIntervals 的顺序按 jh 分段,绘制虚线与顶部井号
drawJhSeparators(depthIntervals, xAxisLabels, chartConfig) {
if (!this.myChart || !depthIntervals || depthIntervals.length === 0) return;
// 扫描 depthIntervals 顺序,形成段
const segments = [];
let currentJh = null;
let currentMin = Infinity;
let currentMax = -Infinity;
let started = false;
for (let i = 0; i < depthIntervals.length; i++) {
// 直接使用在 depthIntervals 中的顺序索引作为 x 轴位置,
// 以避免相同 x 标签造成的覆盖与错位
const { jh } = depthIntervals[i];
const idx = i;
if (!started) {
currentJh = jh;
currentMin = currentMax = idx;
started = true;
continue;
}
if (jh === currentJh) {
currentMin = Math.min(currentMin, idx);
currentMax = Math.max(currentMax, idx);
} else {
segments.push({ jh: currentJh, startIdx: Math.min(currentMin, currentMax), endIdx: Math.max(currentMin, currentMax) });
// 开始新段
currentJh = jh;
currentMin = currentMax = idx;
}
}
if (started) {
segments.push({ jh: currentJh, startIdx: Math.min(currentMin, currentMax), endIdx: Math.max(currentMin, currentMax) });
}
const graphics = [];
// 垂直范围(像素)——考虑 y 轴 inverse,取像素最小为顶部、最大为底部
const yMinPx = this.myChart.convertToPixel({ yAxisIndex: 0 }, chartConfig.yAxis.min);
const yMaxPx = this.myChart.convertToPixel({ yAxisIndex: 0 }, chartConfig.yAxis.max);
if (yMinPx === null || yMaxPx === null) {
console.warn('convertToPixel 返回 null 值,跳过绘制井号分隔符');
return;
}
const yTopPx = Math.min(yMinPx, yMaxPx);
const yBottomPx = Math.max(yMinPx, yMaxPx);
// 边界虚线(段与段之间),改为更浅的灰色虚线
for (let i = 0; i < segments.length - 1; i++) {
const leftEnd = segments[i].endIdx;
const rightStart = segments[i + 1].startIdx;
const pxLeft = this.myChart.convertToPixel({ xAxisIndex: 0 }, leftEnd);
const pxRight = this.myChart.convertToPixel({ xAxisIndex: 0 }, rightStart);
if (pxLeft === null || pxRight === null) {
console.warn('convertToPixel 返回 null 值,跳过绘制边界虚线');
continue;
}
const midX = (pxLeft + pxRight) / 2;
graphics.push({
type: 'line',
shape: { x1: midX, y1: yTopPx, x2: midX, y2: yBottomPx },
style: { stroke: '#bbb', lineDash: [8, 6], lineWidth: 1.5 },
silent: true,
z: 100,
zlevel: 10
});
}
// 每段顶部显示 jh(居中),带背景标签并做简单防重叠处理
let lastLabelRight = -Infinity;
let rowShift = 0; // 逐行上移避免覆盖
segments.forEach((seg, idx) => {
if (!seg.jh) return;
const pxStart = this.myChart.convertToPixel({ xAxisIndex: 0 }, seg.startIdx);
const pxEnd = this.myChart.convertToPixel({ xAxisIndex: 0 }, seg.endIdx);
if (pxStart === null || pxEnd === null) {
console.warn('convertToPixel 返回 null 值,跳过绘制井号标签');
return;
}
const midX = (pxStart + pxEnd) / 2;
let topY = yTopPx - 30; // 再上移,避免遮挡
// 估算标签宽度用于避免与上一个重叠
const fontSize = 12;
const paddingH = 6;
const estimatedWidth = seg.jh.length * fontSize * 0.6 + paddingH * 2 + 10;
const labelLeft = midX - estimatedWidth / 2;
const labelRight = midX + estimatedWidth / 2;
if (labelLeft < lastLabelRight) {
rowShift += 18; // 叠一行
} else {
rowShift = 0;
}
lastLabelRight = Math.max(lastLabelRight, labelRight);
topY -= rowShift;
const labelWidth = Math.max(estimatedWidth, 80);
const labelHeight = 22;
const pointerSize = 6;
graphics.push({
type: 'group',
x: midX - labelWidth / 2,
y: topY - labelHeight,
z: 101,
zlevel: 10,
silent: true,
children: [
{
type: 'rect',
shape: { x: 0, y: 0, width: labelWidth, height: labelHeight, r: 8 },
style: {
fill: '#2563eb',
stroke: '#1e40af',
lineWidth: 1.5,
shadowBlur: 6,
shadowColor: 'rgba(37,99,235,0.35)'
}
},
{
type: 'polygon',
shape: {
points: [
[labelWidth / 2 - pointerSize, labelHeight],
[labelWidth / 2 + pointerSize, labelHeight],
[labelWidth / 2, labelHeight + pointerSize]
]
},
style: { fill: '#2563eb', stroke: '#1e40af' }
},
{
type: 'text',
style: {
x: labelWidth / 2,
y: labelHeight / 2,
text: seg.jh,
fill: '#fff',
fontSize: fontSize + 1,
fontWeight: 700,
align: 'center',
verticalAlign: 'middle'
},
zlevel: 40
}
]
});
});
// 叠加到现有 graphic 上(保留原有 graphic)
const prevElements = Array.isArray(this.currentGraphicElements) ? this.currentGraphicElements : [];
const merged = prevElements.concat(graphics);
this.currentGraphicElements = merged;
this.myChart.setOption({ graphic: { elements: merged } });
},
// 为每个 x 类目在左右各画一条黑色竖线(仅顶部短竖线)
drawCategoryEdgeLines(xAxisLabels, chartConfig) {
if (!this.myChart || !Array.isArray(xAxisLabels) || xAxisLabels.length === 0) return;
const yMinPx = this.myChart.convertToPixel({ yAxisIndex: 0 }, chartConfig.yAxis.min);
const yMaxPx = this.myChart.convertToPixel({ yAxisIndex: 0 }, chartConfig.yAxis.max);
if (yMinPx === null || yMaxPx === null) return;
const yTopPx = Math.min(yMinPx, yMaxPx);
const yBottomPx = Math.max(yMinPx, yMaxPx);
// 只在上方画短竖线:长度占绘图区高度的 12%,并限制在 30-120 像素
const plotHeight = Math.abs(yBottomPx - yTopPx);
const stemLen = Math.max(30, Math.min(120, plotHeight * 0.12));
// 仅保留上方的线:整段都在 top 之上
const extraHead = Math.max(10, Math.min(60, plotHeight * 0.05));
const yStemStart = yTopPx - (extraHead + stemLen);
const yStemEnd = yTopPx - 1;
const graphics = [];
const n = xAxisLabels.length;
// 计算每个类目的像素中心
const centers = [];
for (let i = 0; i < n; i++) {
const cx = this.myChart.convertToPixel({ xAxisIndex: 0 }, i);
if (cx != null) centers.push(cx);
}
if (centers.length > 0) {
// 第一个点左侧边界:用前两个中心的半步估计
let firstEdge;
if (centers.length >= 2) {
firstEdge = centers[0] - (centers[1] - centers[0]) / 2;
} else {
// 只有一个点时,给一个合理的固定偏移
firstEdge = centers[0] - 40;
}
graphics.push({
type: 'line',
shape: { x1: firstEdge, y1: yStemStart, x2: firstEdge, y2: yStemEnd },
style: { stroke: '#bbb', lineWidth: 1.5 },
silent: true,
z: 120,
zlevel: 10
});
// 相邻两点之间的中点
for (let i = 0; i < centers.length - 1; i++) {
const mid = (centers[i] + centers[i + 1]) / 2;
graphics.push({
type: 'line',
shape: { x1: mid, y1: yStemStart, x2: mid, y2: yStemEnd },
style: { stroke: '#bbb', lineWidth: 1.5 },
silent: true,
z: 120,
zlevel: 10
});
}
// 右侧最后一个边界线:对称估算半步并绘制(补齐“最后那条线”)
let lastEdge;
if (centers.length >= 2) {
lastEdge = centers[centers.length - 1] + (centers[centers.length - 1] - centers[centers.length - 2]) / 2;
} else {
lastEdge = centers[0] + 40;
}
graphics.push({
type: 'line',
shape: { x1: lastEdge, y1: yStemStart, x2: lastEdge, y2: yStemEnd },
style: { stroke: '#bbb', lineWidth: 1.5 },
silent: true,
z: 120,
zlevel: 10
});
// 顶部段标签:第一个段使用第一个 x 标签,之后每个段用对应中心的标签
const boundaries = [firstEdge];
for (let i = 0; i < centers.length - 1; i++) {
boundaries.push((centers[i] + centers[i + 1]) / 2);
}
const labelY = yStemStart - 8;
for (let i = 0; i < boundaries.length - 1; i++) {
const midX = (boundaries[i] + boundaries[i + 1]) / 2;
const label = String(xAxisLabels[i] ?? '');
const displayLabel = this.formatXAxisLabel(label);
graphics.push({
type: 'text',
position: [midX, labelY],
style: {
text: displayLabel,
fontSize: 12,
fontWeight: 600,
fill: '#333',
backgroundColor: 'rgba(255,255,255,0.85)',
padding: [2, 4],
borderRadius: 4,
align: 'center',
verticalAlign: 'bottom'
},
z: 121,
zlevel: 10,
silent: true,
bounding: 'raw'
});
}
}
const prev = Array.isArray(this.currentGraphicElements) ? this.currentGraphicElements : [];
const merged = prev.concat(graphics);
this.currentGraphicElements = merged;
this.myChart.setOption({ graphic: { elements: merged } });
},
// 图内根据 x 和 y2 渲染 SVG 纹理(每段使用该 x 在 stackedAreas 中最浅层 y2 对应的 svg),从顶部填充到 y2 深度
async drawTopBandSvg(xAxisLabels, chartConfig, stackedAreas) {
if (!this.myChart || !Array.isArray(xAxisLabels) || !stackedAreas) return;
// 计算每个类目的像素中心
const centers = [];
for (let i = 0; i < xAxisLabels.length; i++) {
const cx = this.myChart.convertToPixel({ xAxisIndex: 0 }, i);
if (cx != null) centers.push(cx);
}
if (centers.length === 0) return;
// 计算段边界(第一个左边界 + 各中点)
let firstEdge;
if (centers.length >= 2) {
firstEdge = centers[0] - (centers[1] - centers[0]) / 2;
} else {
firstEdge = centers[0] - 40;
}
const boundaries = [firstEdge];
for (let i = 0; i < centers.length - 1; i++) {
boundaries.push((centers[i] + centers[i + 1]) / 2);
}
// 图内纵向像素范围:从图内顶部到 y2 所在像素
const yMinPx = this.myChart.convertToPixel({ yAxisIndex: 0 }, chartConfig.yAxis.min);
const yMaxPx = this.myChart.convertToPixel({ yAxisIndex: 0 }, chartConfig.yAxis.max);
if (yMinPx == null || yMaxPx == null) return;
const yTopPx = Math.min(yMinPx, yMaxPx);
const plotHeight = Math.abs(yMaxPx - yMinPx);
// 摊平 stackedAreas 以便按 x 查找图层(保留全部层,用于全部渲染)
const allLayers = [];
if (Array.isArray(stackedAreas)) {
for (const l of stackedAreas) if (l) allLayers.push(l);
} else {
for (const layers of Object.values(stackedAreas)) {
if (Array.isArray(layers)) allLayers.push(...layers.filter(Boolean));
}
}
const graphics = [];
// 仅在每口井的最后一列标注层名称
const depthIntervals = this.mockData?.wellData?.depthIntervals || [];
const lastIndexByJh = new Map();
for (let idx = 0; idx < depthIntervals.length; idx++) {
const jhVal = depthIntervals[idx]?.jh;
if (jhVal != null) lastIndexByJh.set(jhVal, idx);
}
for (let i = 0; i < xAxisLabels.length; i++) {
const xLabel = String(xAxisLabels[i]);
const left = boundaries[i];
const right = (i < boundaries.length - 1) ? boundaries[i + 1] : left + (centers[1] ? (centers[1] - centers[0]) : 80);
const width = Math.max(10, right - left);
const currentJh = depthIntervals[i]?.jh;
const isLastColumnOfJh = currentJh != null && lastIndexByJh.get(currentJh) === i;
// 找到包含该 x 的所有层,提取该 x 对应点的 y2;若有重叠(相同 y2)则只保留一次
const y2ToLayer = new Map();
for (const layer of allLayers) {
const pts = Array.isArray(layer?.points) ? layer.points : [];
const p = pts.find(p0 => String(p0?.x) === xLabel);
if (!p) continue;
const y2val = Number(p?.y2 ?? layer?.sjdjsd);
if (Number.isNaN(y2val)) continue;
if (!y2ToLayer.has(y2val)) y2ToLayer.set(y2val, layer);
}
const sortedY2 = Array.from(y2ToLayer.keys()).sort((a, b) => a - b);
let prevBottom = null; // 上一个片段的底(y2),为空则从图内顶部开始
for (const y2val of sortedY2) {
const layer = y2ToLayer.get(y2val);
const yTopDepth = prevBottom == null ? chartConfig?.yAxis?.min : prevBottom;
const yBottomDepth = y2val;
const yPixTop = this.myChart.convertToPixel({ yAxisIndex: 0 }, yTopDepth);
const yPixBottom = this.myChart.convertToPixel({ yAxisIndex: 0 }, yBottomDepth);
if (yPixTop == null || yPixBottom == null) { prevBottom = y2val; continue; }
const rectTop = Math.min(yPixTop, yPixBottom);
const rectBottom = Math.max(yPixTop, yPixBottom);
const y1pix = Math.max(yTopPx, rectTop);
const y2pix = Math.min(Math.max(yMinPx, yMaxPx), rectBottom);
const height = Math.max(0, y2pix - y1pix);
if (height > 0) {
let fill = '#d0d3d8';
if (layer?.svg) {
try {
const img = await this.createSvgImage(layer.svg);
fill = { image: img, repeat: 'repeat' };
} catch (e) { /* ignore */ }
}
graphics.push({
type: 'rect',
shape: { x: left, y: y1pix, width, height },
style: { fill, stroke: 'rgba(0,0,0,0.12)', lineWidth: 1 },
silent: true,
z: -10,
zlevel: 1,
__band: true
});
// 在该井的最后一列才标注地层名称
if (isLastColumnOfJh) {
const midX = left + width / 2;
const midY = y1pix + height / 2;
const labelText = String(layer?.name || '');
if (labelText) {
graphics.push({
type: 'text',
position: [midX + 30, midY],
style: {
text: labelText,
fontSize: 12,
fontWeight: 600,
fill: '#111827',
stroke: 'rgba(255,255,255,0.9)',
lineWidth: 3,
align: 'left',
verticalAlign: 'middle'
},
silent: true,
z: 15,
zlevel: 32,
__band: true,
bounding: 'raw'
});
}
}
}
prevBottom = y2val;
}
}
// 合并到当前 graphic 上:先移除旧 band,再追加新的,避免重复渲染
const prev = Array.isArray(this.currentGraphicElements) ? this.currentGraphicElements : [];
const kept = prev.filter(el => !el.__band);
const merged = kept.concat(graphics);
this.currentGraphicElements = merged;
this.myChart.setOption({ graphic: { elements: merged } });
},
svgToDataUrl(svgString) {
if (!svgString) return null;
try {
const encoded = encodeURIComponent(svgString)
.replace(/'/g, "%27")
.replace(/"/g, "%22");
return `data:image/svg+xml,${encoded}`;
} catch (error) {
console.warn("SVG 转 dataUrl 失败:", error);
return null;
}
},
getLegendColor(index) {
const palette = [
"#6B7280", "#9CA3AF", "#F59E0B", "#F97316", "#EF4444",
"#10B981", "#14B8A6", "#3B82F6", "#6366F1", "#8B5CF6",
"#EC4899", "#0EA5E9", "#84CC16", "#D97706", "#A16207"
];
return palette[index % palette.length];
},
hasLegendSvg(item) {
return Boolean(item && typeof item.svg === "string" && item.svg.trim().length > 0);
},
getLegendSvgSrc(item) {
if (!this.hasLegendSvg(item)) return "";
return this.svgToDataUrl(item.svg);
},
formatLegendName(item) {
return item && item.name ? item.name : '-';
},
getLegendKey(item, index) {
return `${this.formatLegendName(item)}-${index}`;
}
},
};
</script>
<style lang="scss" scoped>
/* 容器样式优化 */
.chart-container {
width: 100%;
height: 100vh;
padding: 0 20px;
margin: 0;
box-sizing: border-box;
display: flex;
flex-direction: column;
position: relative;
}
.chart-wrapper {
flex: 1;
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 16px;
position: relative;
}
.legend-panel {
width: 240px;
min-width: 220px;
align-self: flex-start;
padding: 16px;
border-radius: 16px;
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.08);
border: 1px solid rgba(226, 232, 240, 0.8);
display: flex;
flex-direction: column;
gap: 12px;
}
.legend-title {
font-size: 15px;
font-weight: 600;
color: #111827;
letter-spacing: 0.5px;
}
.legend-scroll {
flex: none;
max-height: none;
overflow: visible;
padding-right: 4px;
display: flex;
flex-direction: column;
gap: 8px;
}
.legend-item {
display: flex;
align-items: center;
gap: 10px;
padding: 6px 4px;
}
.legend-swatch {
width: 44px;
height: 26px;
border-radius: 6px;
border: 1px solid rgba(71, 85, 105, 0.35);
flex-shrink: 0;
overflow: hidden;
display: inline-flex;
align-items: center;
justify-content: center;
background: #fff;
box-shadow: inset 0 1px 3px rgba(15, 23, 42, 0.2);
}
.legend-swatch-img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 2px;
}
.legend-swatch-color {
width: 100%;
height: 100%;
border-radius: 2px;
}
.legend-label {
font-size: 14px;
font-weight: 600;
color: #111827;
}
/* 井号显示样式 */
.well-number-display {
position: absolute;
top: 16px;
left: 16px;
z-index: 5;
background: transparent;
padding: 8px 12px;
display: flex;
align-items: center;
gap: 6px;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
transition: all 0.3s ease;
opacity: 0.9;
}
.well-number-display:hover {
opacity: 1;
}
.well-label {
color: #6b7280;
font-size: 13px;
font-weight: 500;
white-space: nowrap;
}
.well-number {
color: #3b82f6;
font-size: 14px;
font-weight: 600;
}
/* 图表容器样式 */
.chart {
flex: 1;
width: 100%;
height: 100%;
min-height: 400px;
display: block;
border-radius: 16px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
border: 1px solid rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
transition: all 0.3s ease;
}
.chart:hover {
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
/* transform: translateY(-2px); */
}
/* 加载状态样式 */
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.95);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 10;
border-radius: 16px;
backdrop-filter: blur(10px);
}
.loading-spinner {
width: 48px;
height: 48px;
border: 4px solid #f3f4f6;
border-top: 4px solid #3b82f6;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 16px;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.2);
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* 响应式优化 */
@media (max-width: 768px) {
.chart-container {
padding: 0 10px;
}
.chart-wrapper {
flex-direction: column;
}
.chart {
min-height: 300px;
border-radius: 12px;
}
.legend-panel {
width: 100%;
min-width: auto;
flex-direction: column;
}
.legend-scroll {
flex-direction: column;
}
}
/* 深色模式支持 */
@media (prefers-color-scheme: dark) {
.chart {
/* background: linear-gradient(135deg, #1f2937 0%, #111827 100%); */
/* box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); */
border: 1px solid rgba(255, 255, 255, 0.1);
}
/*
.chart:hover {
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
} */
.loading-overlay {
background: rgba(17, 24, 39, 0.95);
}
}
/* 动画效果 */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.chart-container {
animation: fadeIn 0.6s ease-out;
}
</style>
\ No newline at end of file
<template>
<div class="chart-container">
<div class="chart-wrapper">
<div class="chart-layout">
<div id="mainzftdj" class="chart" ref="chartRef"></div>
<div v-if="legendItems.length" class="legend-panel">
<div class="legend-title">层位图例</div>
<div class="legend-scroll">
<div
v-for="(item, index) in legendItems"
:key="getLegendKey(item, index)"
class="legend-item"
>
<span class="legend-swatch">
<img
v-if="hasLegendSvg(item)"
:src="getLegendSvgSrc(item)"
alt=""
class="legend-swatch-img"
/>
<span
v-else
class="legend-swatch-color"
:style="{ backgroundColor: getLegendColor(index) }"
></span>
</span>
<span class="legend-label">{{ formatLegendName(item) }}</span>
<aside v-if="legendItems && legendItems.length" class="strata-legend">
<div class="legend-header">层位图例</div>
<div class="legend-list">
<div v-for="(item, index) in legendItems" :key="item.name || index" class="legend-item">
<div class="legend-icon" :style="getLegendSwatchStyle(item)"></div>
<span class="legend-label">{{ item.name || '-' }}</span>
</div>
</div>
</div>
</aside>
</div>
<div v-if="loading" class="loading-overlay">
<div class="loading-spinner"></div>
......@@ -61,6 +45,7 @@ export default {
data() {
return {
mockData: {},
legendItems: [],
myChart: null,
initRetryCount: 0,
maxRetryCount: 5,
......@@ -72,7 +57,6 @@ export default {
lastXAxisLabels: null,
lastDepthIntervals: null,
currentGraphicElements: [],
tlList: [],
};
},
computed: {
......@@ -118,8 +102,8 @@ export default {
};
return schemes[this.theme];
},
legendItems() {
return Array.isArray(this.tlList) ? this.tlList : [];
legendPanelWidth() {
return this.legendItems && this.legendItems.length ? 260 : 0;
}
},
watch: {
......@@ -128,6 +112,18 @@ export default {
// 不自动刷新,等待父级触发 loadData
},
immediate: false
},
legendItems: {
handler() {
this.$nextTick(() => {
const chartDom = this.$refs.chartRef;
if (chartDom) {
this.setChartDimensions(chartDom);
this.performResize();
}
});
},
deep: true
}
},
mounted() {
......@@ -181,8 +177,19 @@ export default {
setChartDimensions(chartDom) {
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const availableHeight = viewportHeight - 120;
const availableWidth = viewportWidth - 80;
const containerWidth = this.$el?.clientWidth || viewportWidth;
const rect = this.$el?.getBoundingClientRect();
const topOffset = rect ? rect.top : 0;
const legendWidth = this.legendPanelWidth || 0;
const containerPadding = 20; // chart-container 水平 padding 之和
const panelGap = legendWidth ? 5 : 0; // 与右侧图例的间距
const safetyMargin = 12; // 额外预留
const widthPadding = containerPadding + panelGap + legendWidth + safetyMargin;
const availableWidth = Math.max(360, containerWidth - widthPadding);
const verticalPadding = 40;
const heightByViewport = viewportHeight - topOffset - verticalPadding;
const fallbackHeight = this.$el?.clientHeight || viewportHeight;
const availableHeight = Math.max(360, heightByViewport, fallbackHeight - 20);
chartDom.style.width = `${availableWidth}px`;
chartDom.style.height = `${availableHeight}px`;
chartDom.offsetHeight; // 强制重排
......@@ -262,7 +269,12 @@ export default {
jhs: `${this.jh},${this.jhs}`
});
this.mockData = res?.mockData || {};
this.tlList = res?.tlList || res?.mockData?.tlList || [];
const legendList = Array.isArray(res?.tlList)
? res.tlList
: Array.isArray(res?.mockData?.tlList)
? res.mockData.tlList
: [];
this.legendItems = legendList;
return this.mockData;
} catch (error) {
console.error("获取数据失败:", error);
......@@ -330,6 +342,10 @@ export default {
yAxis: this.createYAxis(mockData.chartConfig),
series: this.createSeries(wellData)
};
const chartDom = this.$refs.chartRef;
if (chartDom) {
this.setChartDimensions(chartDom);
}
this.myChart.setOption(option, true);
// 确保图表完全渲染后再resize
this.$nextTick(() => {
......@@ -414,8 +430,8 @@ export default {
},
grid: {
top: 10,
left: "4%",
right: "4%",
left: "2%",
right: "3%",
bottom: "10%",
containLabel: true,
show: false,
......@@ -914,12 +930,42 @@ export default {
img.src = svgUrl;
});
},
// 为右侧图例生成纹理样式
getLegendSwatchStyle(item = {}) {
const baseStyle = {
backgroundColor: '#d1d5db',
backgroundRepeat: 'repeat',
backgroundSize: '36px 36px'
};
if (!item?.svg) return baseStyle;
const dataUrl = this.getSvgDataUrl(item.svg);
if (!dataUrl) return baseStyle;
return {
backgroundImage: `url("${dataUrl}")`,
backgroundRepeat: 'repeat',
backgroundSize: '36px 36px',
backgroundColor: 'transparent'
};
},
getSvgDataUrl(svgString) {
if (!svgString || typeof svgString !== 'string') return '';
try {
const compact = svgString.replace(/\s+/g, ' ').trim();
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(compact)}`;
} catch (e) {
console.warn('SVG 编码失败:', e);
return '';
}
},
// 清空地层标签(暂不在图内展示层位名称)
drawStratumLabels(stackedAreas, chartConfig, xAxisLabels) {
// 层位名称不再图内显示,仅清理旧的文字元素
drawStratumLabels() {
if (!this.myChart) return;
this.currentGraphicElements = [];
this.myChart.setOption({ graphic: { elements: [] } });
const elements = Array.isArray(this.currentGraphicElements)
? this.currentGraphicElements.filter(el => !el.__stratumLabel)
: [];
this.currentGraphicElements = elements;
this.myChart.setOption({ graphic: { elements } });
},
// 根据 depthIntervals 的顺序按 jh 分段,绘制虚线与顶部井号
......@@ -1240,9 +1286,6 @@ export default {
const left = boundaries[i];
const right = (i < boundaries.length - 1) ? boundaries[i + 1] : left + (centers[1] ? (centers[1] - centers[0]) : 80);
const width = Math.max(10, right - left);
const currentJh = depthIntervals[i]?.jh;
const isLastColumnOfJh = currentJh != null && lastIndexByJh.get(currentJh) === i;
// 找到包含该 x 的所有层,提取该 x 对应点的 y2;若有重叠(相同 y2)则只保留一次
const y2ToLayer = new Map();
for (const layer of allLayers) {
......@@ -1287,33 +1330,6 @@ export default {
zlevel: 1,
__band: true
});
// 在该井的最后一列才标注地层名称
if (isLastColumnOfJh) {
const midX = left + width / 2;
const midY = y1pix + height / 2;
const labelText = String(layer?.name || '');
if (labelText) {
graphics.push({
type: 'text',
position: [midX + 30, midY],
style: {
text: labelText,
fontSize: 12,
fontWeight: 600,
fill: '#111827',
stroke: 'rgba(255,255,255,0.9)',
lineWidth: 3,
align: 'left',
verticalAlign: 'middle'
},
silent: true,
z: 15,
zlevel: 32,
__band: true,
bounding: 'raw'
});
}
}
}
prevBottom = y2val;
}
......@@ -1325,40 +1341,6 @@ export default {
const merged = kept.concat(graphics);
this.currentGraphicElements = merged;
this.myChart.setOption({ graphic: { elements: merged } });
},
svgToDataUrl(svgString) {
if (!svgString) return null;
try {
const encoded = encodeURIComponent(svgString)
.replace(/'/g, "%27")
.replace(/"/g, "%22");
return `data:image/svg+xml,${encoded}`;
} catch (error) {
console.warn("SVG 转 dataUrl 失败:", error);
return null;
}
},
getLegendColor(index) {
const palette = [
"#6B7280", "#9CA3AF", "#F59E0B", "#F97316", "#EF4444",
"#10B981", "#14B8A6", "#3B82F6", "#6366F1", "#8B5CF6",
"#EC4899", "#0EA5E9", "#84CC16", "#D97706", "#A16207"
];
return palette[index % palette.length];
},
hasLegendSvg(item) {
return Boolean(item && typeof item.svg === "string" && item.svg.trim().length > 0);
},
getLegendSvgSrc(item) {
if (!this.hasLegendSvg(item)) return "";
return this.svgToDataUrl(item.svg);
},
formatLegendName(item) {
return item && item.name ? item.name : '-';
},
getLegendKey(item, index) {
return `${this.formatLegendName(item)}-${index}`;
}
},
};
......@@ -1368,88 +1350,83 @@ export default {
/* 容器样式优化 */
.chart-container {
width: 100%;
height: 100vh;
padding: 0 20px;
min-height: calc(100vh - 140px);
padding: 0 10px;
margin: 0;
box-sizing: border-box;
display: flex;
flex-direction: column;
position: relative;
overflow: auto;
}
.chart-wrapper {
flex: 1;
.chart-layout {
display: flex;
flex-direction: row;
flex: 1;
width: 100%;
gap: 5px;
align-items: stretch;
gap: 16px;
position: relative;
justify-content: flex-start;
overflow: hidden;
}
.legend-panel {
width: 220px;
min-width: 200px;
.strata-legend {
width: 240px;
min-width: 240px;
max-height: 100%;
padding: 16px;
border-radius: 16px;
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.08);
border: 1px solid rgba(226, 232, 240, 0.8);
background: #fff;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
border: 1px solid rgba(15, 23, 42, 0.06);
display: flex;
flex-direction: column;
gap: 12px;
}
.legend-title {
font-size: 15px;
.legend-header {
font-size: 16px;
font-weight: 600;
color: #111827;
letter-spacing: 0.5px;
color: #1f2937;
border-bottom: 1px solid rgba(15, 23, 42, 0.08);
padding-bottom: 8px;
}
.legend-scroll {
flex: 1;
.legend-list {
overflow-y: auto;
flex: 1;
display: flex;
flex-direction: column;
gap: 1px;
padding-right: 4px;
}
.legend-item {
display: flex;
align-items: center;
gap: 10px;
padding: 6px 4px;
}
.legend-swatch {
width: 32px;
height: 18px;
border-radius: 4px;
border: 1px solid rgba(148, 163, 184, 0.6);
flex-shrink: 0;
overflow: hidden;
display: inline-flex;
align-items: center;
justify-content: center;
background: #fff;
gap: 5px;
padding: 0px 4px;
border-radius: 8px;
transition: background 0.2s ease;
}
.legend-swatch-img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 2px;
.legend-item:hover {
background-color: rgba(59, 130, 246, 0.08);
}
.legend-swatch-color {
width: 100%;
height: 100%;
border-radius: 2px;
.legend-icon {
width: 40px;
height: 24px;
border-radius: 6px;
border: 1px solid rgba(15, 23, 42, 0.15);
background-color: #d1d5db;
}
.legend-label {
flex: 1;
font-size: 13px;
font-weight: 500;
color: #374151;
color: #1f2937;
}
/* 井号显示样式 */
......@@ -1489,6 +1466,7 @@ export default {
.chart {
flex: 1;
width: 100%;
max-width: 100%;
height: 100%;
min-height: 400px;
display: block;
......@@ -1549,22 +1527,10 @@ export default {
padding: 0 10px;
}
.chart-wrapper {
flex-direction: column;
}
.chart {
min-height: 300px;
border-radius: 12px;
}
.legend-panel {
width: 100%;
min-width: auto;
flex-direction: row;
flex-wrap: wrap;
max-height: 220px;
}
}
/* 深色模式支持 */
......
<template>
<div class="chart-container">
<!-- 井号显示11 -->
<div v-if="jh" class="well-number-display">
<span class="well-label">井号:</span>
<span class="well-number">{{ jh }}</span>
</div>
<div id="mainzft" class="chart" ref="chartRef"></div>
<div class="chart-layout">
<div id="mainzft" class="chart" ref="chartRef"></div>
<aside v-if="legendItems && legendItems.length" class="strata-legend">
<div class="legend-header">层位图例</div>
<div class="legend-list">
<div v-for="(item, index) in legendItems" :key="item.name || index" class="legend-item">
<div class="legend-icon" :style="getLegendSwatchStyle(item)"></div>
<span class="legend-label">{{ item.name || '-' }}</span>
</div>
</div>
</aside>
</div>
<div v-if="loading" class="loading-overlay">
<div class="loading-spinner"></div>
<span>加载中...</span>
......@@ -36,12 +46,16 @@ export default {
data() {
return {
mockData: {},
legendItems: [],
myChart: null,
initRetryCount: 0,
maxRetryCount: 5,
resizeObserver: null,
loading: false,
debounceTimer: null,
lastStackedAreas: null,
lastChartConfig: null,
lastXAxisLabels: null,
};
},
computed: {
......@@ -86,6 +100,9 @@ export default {
}
};
return schemes[this.theme];
},
legendPanelWidth() {
return this.legendItems && this.legendItems.length ? 260 : 0;
}
},
watch: {
......@@ -96,6 +113,18 @@ export default {
}
},
immediate: true
},
legendItems: {
handler() {
this.$nextTick(() => {
const chartDom = this.$refs.chartRef;
if (chartDom) {
this.setChartDimensions(chartDom);
this.performResize();
}
});
},
deep: true
}
},
mounted() {
......@@ -148,9 +177,19 @@ export default {
setChartDimensions(chartDom) {
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const availableHeight = viewportHeight - 120;
const availableWidth = viewportWidth - 80;
const containerWidth = this.$el?.clientWidth || viewportWidth;
const rect = this.$el?.getBoundingClientRect();
const topOffset = rect ? rect.top : 0;
const legendWidth = this.legendPanelWidth || 0;
const containerPadding = 20;
const panelGap = legendWidth ? 5 : 0;
const safetyMargin = 12;
const widthPadding = containerPadding + panelGap + legendWidth + safetyMargin;
const availableWidth = Math.max(360, containerWidth - widthPadding);
const verticalPadding = 40;
const heightByViewport = viewportHeight - topOffset - verticalPadding;
const fallbackHeight = this.$el?.clientHeight || viewportHeight;
const availableHeight = Math.max(360, heightByViewport, fallbackHeight - 20);
chartDom.style.width = `${availableWidth}px`;
chartDom.style.height = `${availableHeight}px`;
chartDom.offsetHeight; // 强制重排
......@@ -235,6 +274,12 @@ export default {
try {
const res = await getZft({ jhe: this.jh });
this.mockData = res?.mockData || {};
const legendList = Array.isArray(res?.tlList)
? res.tlList
: Array.isArray(res?.mockData?.tlList)
? res.mockData.tlList
: [];
this.legendItems = legendList;
return this.mockData;
} catch (error) {
console.error("获取数据失败:", error);
......@@ -787,63 +832,38 @@ export default {
img.src = svgUrl;
});
},
// 为右侧图例生成纹理样式
getLegendSwatchStyle(item = {}) {
const baseStyle = {
backgroundColor: '#d1d5db',
backgroundRepeat: 'repeat',
backgroundSize: '36px 36px'
};
if (!item?.svg) return baseStyle;
const dataUrl = this.getSvgDataUrl(item.svg);
if (!dataUrl) return baseStyle;
return {
backgroundImage: `url("${dataUrl}")`,
backgroundRepeat: 'repeat',
backgroundSize: '36px 36px',
backgroundColor: 'transparent'
};
},
getSvgDataUrl(svgString) {
if (!svgString || typeof svgString !== 'string') return '';
try {
const compact = svgString.replace(/\s+/g, ' ').trim();
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(compact)}`;
} catch (e) {
console.warn('SVG 编码失败:', e);
return '';
}
},
// 使用像素坐标绘制地层标签,精确对齐
drawStratumLabels(stackedAreas, chartConfig, xAxisLabels) {
if (!this.myChart || !stackedAreas || stackedAreas.length === 0) return;
const totalDepth = chartConfig.yAxis.max - chartConfig.yAxis.min;
let currentDepth = chartConfig.yAxis.min;
// 清理旧的 graphic
this.myChart.setOption({ graphic: [] });
const graphics = [];
const lastXIndex = Math.max(0, (xAxisLabels?.length || 1) - 1);
const xOffset = 16; // 与图形右边缘的间距
stackedAreas.forEach((area) => {
if (!area.points || area.points.length === 0) return;
const thickness = area.points[0].y;
const centerDepth = currentDepth + thickness / 2;
// 将值坐标转换为像素坐标(使用最右一个类目,确保靠近右侧)
const pixelRight = this.myChart.convertToPixel({ xAxisIndex: 0, yAxisIndex: 0 }, [lastXIndex, centerDepth]);
if (!pixelRight || !Array.isArray(pixelRight) || pixelRight.length < 2) {
console.warn('convertToPixel 返回无效值:', pixelRight);
return;
}
const [pxRight, py] = pixelRight;
graphics.push({
type: 'text',
position: [pxRight + xOffset, py],
style: {
text: area.name,
fontSize: 12,
fontWeight: 600,
fill: '#333',
backgroundColor: 'transparent',
padding: [0, 0],
borderRadius: 0,
lineWidth: 0,
stroke: 'transparent',
align: 'left',
verticalAlign: 'middle'
},
// anchor 让文本垂直居中
origin: [0, 0],
z: 10,
// 使用 position 计算,避免随缩放偏移
bounding: 'raw',
silent: true
});
currentDepth += thickness;
});
this.myChart.setOption({ graphic: graphics });
// 图内层位名称整体去除,仅清理旧元素
drawStratumLabels() {
if (!this.myChart) return;
this.myChart.setOption({ graphic: { elements: [] } });
}
},
};
......@@ -853,13 +873,84 @@ export default {
/* 容器样式优化 */
.chart-container {
width: 100%;
/* height: 100vh; */
/* padding: 0 20px; */
min-height: calc(100vh - 140px);
padding: 0 10px;
margin: 0;
box-sizing: border-box;
display: flex;
flex-direction: column;
position: relative;
overflow: auto;
animation: fadeIn 0.6s ease-out;
}
.chart-layout {
display: flex;
flex: 1;
width: 100%;
gap: 5px;
align-items: stretch;
justify-content: flex-start;
overflow: hidden;
}
.strata-legend {
width: 240px;
min-width: 240px;
max-height: 100%;
padding: 16px;
border-radius: 16px;
background: #fff;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
border: 1px solid rgba(15, 23, 42, 0.06);
display: flex;
flex-direction: column;
gap: 12px;
}
.legend-header {
font-size: 16px;
font-weight: 600;
color: #1f2937;
border-bottom: 1px solid rgba(15, 23, 42, 0.08);
padding-bottom: 8px;
}
.legend-list {
overflow-y: auto;
flex: 1;
display: flex;
flex-direction: column;
gap: 1px;
padding-right: 4px;
}
.legend-item {
display: flex;
align-items: center;
gap: 5px;
padding: 0 4px;
border-radius: 8px;
transition: background 0.2s ease;
}
.legend-item:hover {
background-color: rgba(59, 130, 246, 0.08);
}
.legend-icon {
width: 40px;
height: 24px;
border-radius: 6px;
border: 1px solid rgba(15, 23, 42, 0.15);
background-color: #d1d5db;
}
.legend-label {
flex: 1;
font-size: 13px;
font-weight: 500;
color: #1f2937;
}
/* 井号显示样式 */
......@@ -899,7 +990,7 @@ export default {
.chart {
flex: 1;
width: 100%;
max-width: 100%;
height: 100%;
min-height: 400px;
display: block;
......@@ -1026,8 +1117,4 @@ export default {
transform: translateY(0);
}
}
.chart-container {
animation: fadeIn 0.6s ease-out;
}
</style>
\ No newline at end of file
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch">
<el-form-item prop="jh">
<el-input v-model="queryParams.jh" placeholder="请输入井号" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd">新建井</el-button>
</el-form-item>
</el-form>
<div class="card-container">
<el-card class="box-card" v-for="item in ytLists" :key="item.id"
style="margin: 10px 10px 0 0; width: calc(20% - 10px);">
<div slot="header" class="clearfix header">
<div class="header-content">
<div class="left">
<img style="width: 80px;height: 80px" :src="wellImg" class="sidebar-logo" />
</div>
<div class="right">
<div> <!-- 使用v-for指令循环渲染每一条数据 -->
<div style="font-size: 24px;color: #1c84c6;font-weight: bold;cursor: pointer"
@click="toDesignScheme(item)">{{ item.jh }} <!-- 在循环中使用当前数据项的属性 -->
</div>
<div>
<i style="color: #909399" class="el-icon-user-solid"></i>
<span style="color: #909399">{{ item.jd }}</span> <!-- 在循环中使用当前数据项的属性 -->
</div>
</div>
</div>
</div>
<div class="text item" style="display: flex; justify-content: flex-end;margin: 10px;">
<el-button size="large" icon="el-icon-edit-outline" style="margin-right: 10px" type="text"
@click="handleEdit(item)"></el-button>
<el-button size="medium" icon="el-icon-delete" style="margin-right: 10px" type="text"
@click="handleDelete(item)"></el-button>
<el-button size="medium" icon="el-icon-lock" style="margin-right: 10px" type="text"
@click="handleLock(item)"></el-button>
<!-- <el-button size="medium" icon="el-icon-unlock" style=" margin-right: 10px" type="text" @click="handleFinalize"></el-button> -->
</div>
</div>
</el-card>
</div>
<!-- 添加或修改菜单对话框 -->
<el-dialog :title="title" :visible.sync="open" width="70%" append-to-body>
<div class="jsjStyle"
style="background-color: #f4f8fe;border-radius: 10px;margin: -30px -10px -30px;padding: 10px;">
<el-form ref="form" :model="form" :rules="rules" label-width="150px">
<div class="cardStyle" style="border: 1px solid #eee">
<div style="font-size: 14px;padding: 10px;border-bottom: 1px solid #eee;margin-top: -10px;">基本信息
</div>
<el-row style="margin-top: 10px">
<el-col :span="8">
<el-form-item label="钻井公司" prop="zjgs">
<el-input style="width: 200px" v-model="form.zjgs" clearable />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="区块名称" prop="qkmc">
<el-select v-model="form.qkmc" placeholder="请选择区块名称" clearable filterable
style="width: 200px;">
<el-option v-for="opt in filteredBlockOptions" :key="opt.value"
:label="opt.label" :value="opt.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="油田公司" prop="ytgs">
<el-input style="width: 200px" v-model="form.ytgs" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="井号" prop="jh">
<el-input style="width: 200px" v-model="form.jh" clearable />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="井型" prop="jx">
<el-input style="width: 200px" v-model="form.jx" clearable />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="井队" prop="jd">
<el-input style="width: 200px" v-model="form.jd" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="设计作业周期" prop="sjzyzq">
<el-input style="width: 200px" v-model="form.sjzyzq" clearable
oninput="value=value.replace(/^([0-9-]\d*\.?\d{0,2})?.*$/,'$1')"
@blur="form.sjzyzq = $event.target.value" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="井别" prop="jb">
<el-input style="width: 200px" v-model="form.jb" clearable />
</el-form-item>
</el-col>
</el-row>
</div>
<div class="cardStyle" style="border: 1px solid #eee;margin-top: 10px">
<div style="font-size: 14px;padding: 10px;border-bottom: 1px solid #eee;margin-top: -10px;">地质信息
</div>
<el-row style="margin-top: 10px">
<el-col :span="8">
<el-form-item label="井口坐标(X)" prop="jkzbx">
<el-input style="width: 200px" v-model="form.jkzbx" clearable
oninput="value=value.replace(/^([0-9-]\d*\.?\d{0,2})?.*$/,'$1')"
@blur="form.jkzbx = $event.target.value" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="井口坐标(Y)" prop="jkzby">
<el-input style="width: 200px" v-model="form.jkzby" clearable
oninput="value=value.replace(/^([0-9-]\d*\.?\d{0,2})?.*$/,'$1')"
@blur="form.jkzby = $event.target.value" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="入靶点A坐标(X)" prop="rbdazb">
<el-input style="width: 180px" v-model="form.rbdazb" clearable
oninput="value=value.replace(/^([0-9-]\d*\.?\d{0,2})?.*$/,'$1')"
@blur="form.rbdazb = $event.target.value" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="入靶点A坐标(Y)" prop="rbdbzb">
<el-input style="width: 200px" v-model="form.rbdbzb" clearable
oninput="value=value.replace(/^([0-9-]\d*\.?\d{0,2})?.*$/,'$1')"
@blur="form.rbdbzb = $event.target.value" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="地面海拔" prop="dmhb">
<el-input style="width: 200px" v-model="form.dmhb" clearable
oninput="value=value.replace(/^([0-9-]\d*\.?\d{0,2})?.*$/,'$1')"
@blur="form.dmhb = $event.target.value" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="完井方式" prop="wjfs">
<el-input style="width: 180px" v-model="form.wjfs" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="定稿设计井深m" prop="dgsjjs" style="width: 200px">
<el-input style="width: 200px" v-model="form.dgsjjs" clearable
oninput="value=value.replace(/^([0-9-]\d*\.?\d{0,2})?.*$/,'$1')"
@blur="form.dgsjjs = $event.target.value" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="深度零点" prop="sdld">
<el-input style="width: 200px" v-model="form.sdld" clearable />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="定稿设计水平段长度" prop="dgsjspdcd">
<el-input style="width: 200px" v-model="form.dgsjspdcd" clearable
oninput="value=value.replace(/^([0-9-]\d*\.?\d{0,2})?.*$/,'$1')"
@blur="form.dgsjspdcd = $event.target.value" />
</el-form-item>
</el-col>
<!-- <el-col :span="8">
<el-form-item label="目的层" prop="mdc">
<el-input style="width: 180px" v-if="!showCascader" v-model="form.mdc" readonly>
<template slot="append">
<el-button @click="openCascader">选择</el-button>
</template>
</el-input>
<el-cascader v-if="showCascader" ref="cascader" v-model="form.mdc" @change="change" :props="mdc"></el-cascader>
</el-form-item>
</el-col> -->
</el-row>
<!-- <el-row>
</el-row> -->
</div>
</el-form>
<div style="display: flex;justify-content: center;margin: 10px;" slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</div>
</el-dialog>
<!-- <el-dialog title="定稿" :visible.sync="openFinalize" width="300px" append-to-body>
<el-form ref="form" :model="finalizeForm" :rules="rules" label-width="90px">
<el-form-item label="井设计方案" prop="menuType">
<el-select v-model="finalizeForm.wellDesignScheme" placeholder="请选择">
<el-option v-for="item in wellDesignSchemeOptions" :key="item.value" :label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitFinalizeForm">确 定</el-button>
<el-button @click="cancelFinalizeForm">取 消</el-button>
</div>
</el-dialog> -->
</div>
</template>
<script>
import { getQkxl } from "@/api/system/jsaa";
import wellImg from '../assets/images/home/well.png'
import { listFayh, deptTreeSelect, getBasinSelect, getBlockByPdid, deleteJtDjjc, addJtDjjc, getJtDjjc, updateJtDjjc, getMdcByQkid, LockJtDjjc } from "@/api/scientificDrill/schemeOptimization";
import { blob } from 'd3';
import Treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
export default {
name: "tabsCard",
components: { Treeselect },
dicts: ['jt_ytgs', 'jt_jx', 'jt_jb', 'jt_wjfs', 'jt_sdld', 'jt_qklx'],
props: {
tabsId: {
type: String,
default: ''
},
},
watch: {
tabsId: {
handler(val) {
if (val) {
this.getList();
}
},
immediate: true,
},
},
data() {
return {
blockOptions: [],
showCascader: false,
mdcOptions: [],
zjgs: "",
jh: "",
jhdm: "",
wellImg: wellImg,
ytLists: [],
// 显示搜索条件
showSearch: true,
// 查询参数
queryParams: {
jh: undefined,
pageNum: 1,
pageSize: 10
},
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
openFinalize: false,
// 表单参数
form: {
remark: "",
jh: "",
pd: "",
ytgs: "",
zjgs: "",
jd: "",
qkdm: "",
qkmc: "",
qkid: "",
jx: "",
sjzyzq: "",
jb: "",
jkzbx: "",
jkzby: "",
rbdazb: "",
rbdbzb: "",
dmhb: "",
wjfs: "",
dgsjjs: "",
sdld: "",
dgsjspdcd: "",
zjgs: "",
mdcid: "",
mdc: "",
},
finalizeForm: {},
// 表单校验
rules: {
jh: [
{ required: true, message: "井号不能为空", trigger: "blur" }
],
ytgs: [
{ required: true, message: "油田公司不能为空", trigger: "blur" }
],
zjgs: [
{ required: true, message: "钻井公司不能为空", trigger: "blur" }
],
jd: [
{ required: true, message: "井队不能为空", trigger: "blur" }
],
},
basinValue: "",
selectedBasinId: "",
basinOptions: [],
deptOptions: undefined,
blockOptions: [],
oilOptions: [],
wellTypeOptions: [],
wellClassifyOptions: [],
completionMethodOptions: [],
zeroDepthOptions: [],
wellDesignSchemeOptions: [],
pd: "",
mdc: {
lazy: true,
lazyLoad: this.loadTreeNode // 直接将 loadTreeNode 方法作为 lazyLoad 属性传递
},
selectedBasinId: null, // 添加一个用于存储选中盆地的变量
oilOptions: [] // 用于存储 MDC 数据
}
},
computed: {
filteredBlockOptions() {
if (!Array.isArray(this.blockOptions)) return []
return this.blockOptions
.filter(item => item && (item.qk || item.qkmc))
.map(item => ({ label: item.qk || item.qkmc, value: item.qk || item.qkmc }))
}
},
mounted() {
console.log('this.tabsId', this.tabsId);
this.getList();
this.getDeptTree();
this.getBlockOptions();
this.getBasinSelect();
},
methods: {
onBlockChange(val) {
this.form.qkmc = val || '';
},
/** 获取区块下拉选项 */
getBlockOptions() {
getQkxl().then(response => {
// 过滤掉无效的选项
this.blockOptions = response.data.filter(item => item && item.qk);
console.log(this.blockOptions, ' this.blockOptions ');
});
},
/** 搜索按钮操作 */
handleQuery() {
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.queryParams.jh = ''
this.resetForm("queryForm");
this.queryParams.pageNum = 1;
this.getList()
},
openCascader() {
this.showCascader = true;
},
change(val) {
this.showCascader = false; // 选择后关闭级联选择器
let nodesObj = this.$refs['cascader'].getCheckedNodes();
console.log(nodesObj, 'nodesObj');
if (nodesObj && nodesObj.length > 0) {
const lastNode = nodesObj[nodesObj.length - 1];
this.form.mdc = lastNode.label; // 设置选中层的 mc 到表单中
console.log(this.form.mdc, 'this.form.mdc ');
this.form.mdcid = lastNode.value; // 设置选中层的 id 到表单中
console.log(this.form.mdcid, 'this.form.mdcid ');
}
},
// 查询
getList() {
// 创建一个本地变量,初始值为 props 中的 tabsId
let localTabsId = this.tabsId;
// 如果 localTabsId 为 "所有油田",则将其设置为空字符串
if (localTabsId === "所有油田") {
localTabsId = "";
}
const val = {
ytgs: localTabsId,
jh: this.queryParams.jh
}
listFayh(val).then(response => {
console.log('this.tabsId', localTabsId);
console.log('查询');
this.ytLists = response.rows;
console.log(this.ytLists, 'this.ytLists');
});
},
/** 查询部门树结构 */
getDeptTree() {
deptTreeSelect({ level: "3" }).then(response => {
this.deptOptions = response.data;
console.log(this.deptOptions, 'this.deptOptions');
});
},
// 查询盆地结构
getBasinSelect() {
getBasinSelect().then(response => {
this.basinOptions = response;
console.log(this.basinOptions, ' this.basinOptions');
// 不要在这里清空或覆盖 blockOptions,避免和区块下拉数据竞争
// 仅在用户选择盆地后,再通过 basinChange 主动更新区块选项
});
},
// 盆地选项改变时触发 区块
basinChange() {
const selectedBasinName = this.form.pd; // 获取选中的盆地名称
console.log(selectedBasinName, 'selectedBasinName');
const selectedBasin = this.basinOptions.find(item => item.pdmc === selectedBasinName); // 根据名称获取相应的盆地对象
console.log(selectedBasin, 'selectedBasin');
if (selectedBasin) {
getBlockByPdid(selectedBasin.id).then(response => { // 使用盆地对象的ID来获取区块
this.blockOptions = response.rows;
});
}
},
// 获取 MDC 数据
// getMdc() {
// const params = {
// dclevel: '1',
// pid: ''
// };
// getMdcByQkid(params).then(response => {
// const oilOptions = response.data; // 保存获取到的数据
// console.log(oilOptions, 'this.oilOptions');
// this.oilOptions = oilOptions; // 将数据存储到组件的属性中
// this.loadTreeNode((nodes) => {
// this.mdcOptions = nodes;
// }, oilOptions);
// }).catch(error => {
// console.error('Error fetching MDC data:', error);
// });
// },
loadTreeNode(node, resolve) {
console.log(node, 'node');
console.log(resolve, 'resolve');
const params = {
dclevel: node.level + 1,
pid: node.value,
};
console.log(params, 'params');
// 发起请求获取节点数据
getMdcByQkid(params).then(response => {
const oilOptions = response.data; // 获取到的数据
console.log(oilOptions, 'this.oilOptions');
if (Array.isArray(oilOptions)) {
// 将数据转换为符合 Cascader 要求的格式
const nodes = oilOptions.map(item => ({
value: item.id,
label: item.mc,
leaf: item.dclevel >= 5
}));
console.log(nodes, 'nodes');
resolve(nodes);
} else {
// 处理异常情况
}
}).catch(error => {
// 处理请求失败的情况
});
},
//点击卡片
toDesignScheme(item) {
this.$store.dispatch('setJh', item.jh);
this.$store.dispatch('setJhdm', item.jhdm);
console.log(item, 'item');
this.$router.push({
path: "/wellDesign",
query: {
jhdm: item.jhdm,
jh: item.jh,
qkmc: item.qkmc
}
})
},
//新建井
handleAdd() {
this.reset();
this.open = true;
this.title = "新增井设计"
},
//编辑
handleEdit(item) {
this.reset();
this.open = true;
console.log(item, 'item');
const jhdm = item.jhdm;
getJtDjjc(jhdm).then(response => {
this.form = response.data;
// this.form.mdc = response.data.mdcid;
console.log(response, 'response');
this.open = true;
this.title = "修改";
});
this.title = "修改井设计"
},
/** 提交按钮 */
submitForm() {
// const pd = this.form.pd
// this.form.pdid = pd == '' ? '' : this.basinOptions.find(item => item.pdmc == pd).pdid
// const qkmc = this.form.qkmc
// this.form.qkdm = qkmc == '' ? '' : this.blockOptions.find(item => item.qkmc == qkmc).qkdm
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.jhdm != null && this.form.jhdm !== '') {
// 如果 jhdm 存在且不为空,则执行更新操作
updateJtDjjc(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
// 否则执行新增操作
addJtDjjc(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
// 表单重置
reset() {
this.form = {
};
this.resetForm("form");
},
cancel() {
this.open = false;
},
/** 删除按钮操作 */
handleDelete(item) {
const jhdms = item.jhdm;
this.$modal.confirm('是否确认删除编号为"' + jhdms + '"的数据项?').then(function () {
return deleteJtDjjc(jhdms);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => { });
},
/** 锁定按钮操作 */
handleLock(item) {
const jhdm = item.jhdm;
this.$modal.confirm('是否确认锁定?').then(function () {
return LockJtDjjc(jhdm);
}).then(() => {
this.getList();
this.$modal.msgSuccess("已锁定");
}).catch(() => { });
},
submitFinalizeForm() {
this.openFinalize = false
},
cancelFinalizeForm() {
this.openFinalize = false
},
}
}
</script>
<style scoped lang="scss">
.cardStyle {
// border: 1px solid red;
border: 1px solid #dedede;
background-color: #fff;
border-radius: 5px;
box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.1);
padding: 12px;
}
.cardStyle:hover {
box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.2);
}
::v-deep .el-form-item {
margin-bottom: 10px;
}
.text {
font-size: 14px;
}
.clearfix:before,
.clearfix:after {
display: table;
content: "";
}
.clearfix:after {
clear: both
}
.card-container {
display: flex;
flex-wrap: wrap;
/* 当空间不足时换行 */
justify-content: flex-start;
/* 卡片从左向右排列 */
align-items: flex-start;
/* 卡片顶部对齐 */
margin-top: -10px;
}
.box-card {
flex: 0 0 auto;
width: 300px;
}
.header-content {
display: flex;
align-items: center;
/* 垂直居中对齐 */
}
.left {
margin-right: 10px;
/* 左右间距 */
}
.right {
flex-grow: 1;
/* 右侧内容占据剩余空间 */
}
::v-deep .el-card__body {
padding: 0 !important;
}
</style>
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch">
<el-form-item prop="jh">
<el-input v-model="queryParams.jh" placeholder="请输入井号" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd">新建井</el-button>
</el-form-item>
</el-form>
<div class="card-container">
<el-card class="box-card" v-for="item in ytLists" :key="item.id"
style="margin: 10px 10px 0 0; width: calc(20% - 10px);">
<div slot="header" class="clearfix header">
<div class="header-content">
<div class="left">
<img style="width: 80px;height: 80px" :src="wellImg" class="sidebar-logo" />
</div>
<div class="right">
<div> <!-- 使用v-for指令循环渲染每一条数据 -->
<div style="font-size: 24px;color: #1c84c6;font-weight: bold;cursor: pointer"
@click="toDesignScheme(item)">{{ item.jh }} <!-- 在循环中使用当前数据项的属性 -->
</div>
<div>
<i style="color: #909399" class="el-icon-user-solid"></i>
<span style="color: #909399">{{ item.jd }}</span> <!-- 在循环中使用当前数据项的属性 -->
</div>
</div>
</div>
</div>
<div class="text item" style="display: flex; justify-content: flex-end;margin: 10px;">
<el-button size="large" icon="el-icon-edit-outline" style="margin-right: 10px" type="text"
@click="handleEdit(item)"></el-button>
<el-button size="medium" icon="el-icon-delete" style="margin-right: 10px" type="text"
@click="handleDelete(item)"></el-button>
<el-button size="medium" icon="el-icon-lock" style="margin-right: 10px" type="text"
@click="handleLock(item)"></el-button>
<div>
<!-- <el-button size="medium" icon="el-icon-unlock" style=" margin-right: 10px" type="text" @click="handleFinalize"></el-button> -->
</div>
</div>
</el-card>
</div>
<!-- 添加或修改菜单对话框 -->
<el-dialog :title="title" :visible.sync="open" width="70%" append-to-body>
<div class="jsjStyle"
style="background-color: #f4f8fe;border-radius: 10px;margin: -30px -10px -30px;padding: 10px;">
<el-form ref="form" :model="form" :rules="rules" label-width="150px">
<div class="cardStyle" style="border: 1px solid #eee">
<div style="font-size: 14px;padding: 10px;border-bottom: 1px solid #eee;margin-top: -10px;">基本信息
</div>
<el-row style="margin-top: 10px">
<el-col :span="8">
<el-form-item label="钻井公司" prop="zjgs">
<el-input style="width: 200px" v-model="form.zjgs" clearable />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="区块名称" prop="qkmc">
<el-select v-model="form.qkmc" placeholder="请选择区块名称" clearable filterable
style="width: 200px;">
<el-option v-for="opt in filteredBlockOptions" :key="opt.value"
:label="opt.label" :value="opt.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="油田公司" prop="ytgs">
<el-input style="width: 200px" v-model="form.ytgs" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="井号" prop="jh">
<el-input style="width: 200px" v-model="form.jh" clearable />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="井型" prop="jx">
<el-input style="width: 200px" v-model="form.jx" clearable />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="井队" prop="jd">
<el-input style="width: 200px" v-model="form.jd" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="设计作业周期" prop="sjzyzq">
<el-input style="width: 200px" v-model="form.sjzyzq" clearable
oninput="value=value.replace(/^([0-9-]\d*\.?\d{0,2})?.*$/,'$1')"
@blur="form.sjzyzq = $event.target.value" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="井别" prop="jb">
<el-input style="width: 200px" v-model="form.jb" clearable />
</el-form-item>
</el-col>
</el-row>
</div>
<div class="cardStyle" style="border: 1px solid #eee;margin-top: 10px">
<div style="font-size: 14px;padding: 10px;border-bottom: 1px solid #eee;margin-top: -10px;">地质信息
</div>
<el-row style="margin-top: 10px">
<el-col :span="8">
<el-form-item label="井口坐标(X)" prop="jkzbx">
<el-input style="width: 200px" v-model="form.jkzbx" clearable
oninput="value=value.replace(/^([0-9-]\d*\.?\d{0,2})?.*$/,'$1')"
@blur="form.jkzbx = $event.target.value" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="井口坐标(Y)" prop="jkzby">
<el-input style="width: 200px" v-model="form.jkzby" clearable
oninput="value=value.replace(/^([0-9-]\d*\.?\d{0,2})?.*$/,'$1')"
@blur="form.jkzby = $event.target.value" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="入靶点A坐标(X)" prop="rbdazb">
<el-input style="width: 180px" v-model="form.rbdazb" clearable
oninput="value=value.replace(/^([0-9-]\d*\.?\d{0,2})?.*$/,'$1')"
@blur="form.rbdazb = $event.target.value" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="入靶点A坐标(Y)" prop="rbdbzb">
<el-input style="width: 200px" v-model="form.rbdbzb" clearable
oninput="value=value.replace(/^([0-9-]\d*\.?\d{0,2})?.*$/,'$1')"
@blur="form.rbdbzb = $event.target.value" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="地面海拔" prop="dmhb">
<el-input style="width: 200px" v-model="form.dmhb" clearable
oninput="value=value.replace(/^([0-9-]\d*\.?\d{0,2})?.*$/,'$1')"
@blur="form.dmhb = $event.target.value" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="完井方式" prop="wjfs">
<el-input style="width: 180px" v-model="form.wjfs" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="定稿设计井深m" prop="dgsjjs" style="width: 200px">
<el-input style="width: 200px" v-model="form.dgsjjs" clearable
oninput="value=value.replace(/^([0-9-]\d*\.?\d{0,2})?.*$/,'$1')"
@blur="form.dgsjjs = $event.target.value" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="深度零点" prop="sdld">
<el-input style="width: 200px" v-model="form.sdld" clearable />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="定稿设计水平段长度" prop="dgsjspdcd">
<el-input style="width: 200px" v-model="form.dgsjspdcd" clearable
oninput="value=value.replace(/^([0-9-]\d*\.?\d{0,2})?.*$/,'$1')"
@blur="form.dgsjspdcd = $event.target.value" />
</el-form-item>
</el-col>
<!-- <el-col :span="8">
<el-form-item label="目的层" prop="mdc">
<el-input style="width: 180px" v-if="!showCascader" v-model="form.mdc" readonly>
<template slot="append">
<el-button @click="openCascader">选择</el-button>
</template>
</el-input>
<el-cascader v-if="showCascader" ref="cascader" v-model="form.mdc" @change="change" :props="mdc"></el-cascader>
</el-form-item>
</el-col> -->
</el-row>
<!-- <el-row>
</el-row> -->
</div>
</el-form>
<div style="display: flex;justify-content: center;margin: 10px;" slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</div>
</el-dialog>
<!-- <el-dialog title="定稿" :visible.sync="openFinalize" width="300px" append-to-body>
<el-form ref="form" :model="finalizeForm" :rules="rules" label-width="90px">
<el-form-item label="井设计方案" prop="menuType">
<el-select v-model="finalizeForm.wellDesignScheme" placeholder="请选择">
<el-option v-for="item in wellDesignSchemeOptions" :key="item.value" :label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitFinalizeForm">确 定</el-button>
<el-button @click="cancelFinalizeForm">取 消</el-button>
</div>
</el-dialog> -->
</div>
</template>
<script>
import { getQkxl } from "@/api/system/jsaa";
import wellImg from '../assets/images/home/well.png'
import { listFayh, deptTreeSelect, getBasinSelect, getBlockByPdid, deleteJtDjjc, addJtDjjc, getJtDjjc, updateJtDjjc, getMdcByQkid, LockJtDjjc } from "@/api/scientificDrill/schemeOptimization";
import { blob } from 'd3';
import Treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
export default {
name: "tabsCard",
components: { Treeselect },
dicts: ['jt_ytgs', 'jt_jx', 'jt_jb', 'jt_wjfs', 'jt_sdld', 'jt_qklx'],
props: {
tabsId: {
type: String,
default: ''
},
},
watch: {
tabsId: {
handler(val) {
if (val) {
this.getList();
}
},
immediate: true,
},
},
data() {
return {
blockOptions: [],
showCascader: false,
mdcOptions: [],
zjgs: "",
jh: "",
jhdm: "",
wellImg: wellImg,
ytLists: [],
// 显示搜索条件
showSearch: true,
// 查询参数
queryParams: {
jh: undefined,
pageNum: 1,
pageSize: 10
},
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
openFinalize: false,
// 表单参数
form: {
remark: "",
jh: "",
pd: "",
ytgs: "",
zjgs: "",
jd: "",
qkdm: "",
qkmc: "",
qkid: "",
jx: "",
sjzyzq: "",
jb: "",
jkzbx: "",
jkzby: "",
rbdazb: "",
rbdbzb: "",
dmhb: "",
wjfs: "",
dgsjjs: "",
sdld: "",
dgsjspdcd: "",
zjgs: "",
mdcid: "",
mdc: "",
},
finalizeForm: {},
// 表单校验
rules: {
jh: [
{ required: true, message: "井号不能为空", trigger: "blur" }
],
ytgs: [
{ required: true, message: "油田公司不能为空", trigger: "blur" }
],
zjgs: [
{ required: true, message: "钻井公司不能为空", trigger: "blur" }
],
jd: [
{ required: true, message: "井队不能为空", trigger: "blur" }
],
},
basinValue: "",
selectedBasinId: "",
basinOptions: [],
deptOptions: undefined,
blockOptions: [],
oilOptions: [],
wellTypeOptions: [],
wellClassifyOptions: [],
completionMethodOptions: [],
zeroDepthOptions: [],
wellDesignSchemeOptions: [],
pd: "",
mdc: {
lazy: true,
lazyLoad: this.loadTreeNode // 直接将 loadTreeNode 方法作为 lazyLoad 属性传递
},
selectedBasinId: null, // 添加一个用于存储选中盆地的变量
oilOptions: [] // 用于存储 MDC 数据
}
},
computed: {
filteredBlockOptions() {
if (!Array.isArray(this.blockOptions)) return []
return this.blockOptions
.filter(item => item && (item.qk || item.qkmc))
.map(item => ({ label: item.qk || item.qkmc, value: item.qk || item.qkmc }))
}
},
mounted() {
console.log('this.tabsId', this.tabsId);
this.getList();
this.getDeptTree();
this.getBlockOptions();
this.getBasinSelect();
},
methods: {
onBlockChange(val) {
this.form.qkmc = val || '';
},
/** 获取区块下拉选项 */
getBlockOptions() {
getQkxl().then(response => {
// 过滤掉无效的选项
this.blockOptions = response.data.filter(item => item && item.qk);
console.log(this.blockOptions, ' this.blockOptions ');
});
},
/** 搜索按钮操作 */
handleQuery() {
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.queryParams.jh = ''
this.resetForm("queryForm");
this.queryParams.pageNum = 1;
this.getList()
},
openCascader() {
this.showCascader = true;
},
change(val) {
this.showCascader = false; // 选择后关闭级联选择器
let nodesObj = this.$refs['cascader'].getCheckedNodes();
console.log(nodesObj, 'nodesObj');
if (nodesObj && nodesObj.length > 0) {
const lastNode = nodesObj[nodesObj.length - 1];
this.form.mdc = lastNode.label; // 设置选中层的 mc 到表单中
console.log(this.form.mdc, 'this.form.mdc ');
this.form.mdcid = lastNode.value; // 设置选中层的 id 到表单中
console.log(this.form.mdcid, 'this.form.mdcid ');
}
},
// 查询
getList() {
// 创建一个本地变量,初始值为 props 中的 tabsId
let localTabsId = this.tabsId;
// 如果 localTabsId 为 "所有油田",则将其设置为空字符串
if (localTabsId === "所有油田") {
localTabsId = "";
}
const val = {
ytgs: localTabsId,
jh: this.queryParams.jh
}
listFayh(val).then(response => {
console.log('this.tabsId', localTabsId);
console.log('查询');
this.ytLists = response.rows;
console.log(this.ytLists, 'this.ytLists');
});
},
/** 查询部门树结构 */
getDeptTree() {
deptTreeSelect({ level: "3" }).then(response => {
this.deptOptions = response.data;
console.log(this.deptOptions, 'this.deptOptions');
});
},
// 查询盆地结构
getBasinSelect() {
getBasinSelect().then(response => {
this.basinOptions = response;
console.log(this.basinOptions, ' this.basinOptions');
// 不要在这里清空或覆盖 blockOptions,避免和区块下拉数据竞争
// 仅在用户选择盆地后,再通过 basinChange 主动更新区块选项
});
},
// 盆地选项改变时触发 区块
basinChange() {
const selectedBasinName = this.form.pd; // 获取选中的盆地名称
console.log(selectedBasinName, 'selectedBasinName');
const selectedBasin = this.basinOptions.find(item => item.pdmc === selectedBasinName); // 根据名称获取相应的盆地对象
console.log(selectedBasin, 'selectedBasin');
if (selectedBasin) {
getBlockByPdid(selectedBasin.id).then(response => { // 使用盆地对象的ID来获取区块
this.blockOptions = response.rows;
});
}
},
// 获取 MDC 数据
// getMdc() {
// const params = {
// dclevel: '1',
// pid: ''
// };
// getMdcByQkid(params).then(response => {
// const oilOptions = response.data; // 保存获取到的数据
// console.log(oilOptions, 'this.oilOptions');
// this.oilOptions = oilOptions; // 将数据存储到组件的属性中
// this.loadTreeNode((nodes) => {
// this.mdcOptions = nodes;
// }, oilOptions);
// }).catch(error => {
// console.error('Error fetching MDC data:', error);
// });
// },
loadTreeNode(node, resolve) {
console.log(node, 'node');
console.log(resolve, 'resolve');
const params = {
dclevel: node.level + 1,
pid: node.value,
};
console.log(params, 'params');
// 发起请求获取节点数据
getMdcByQkid(params).then(response => {
const oilOptions = response.data; // 获取到的数据
console.log(oilOptions, 'this.oilOptions');
if (Array.isArray(oilOptions)) {
// 将数据转换为符合 Cascader 要求的格式
const nodes = oilOptions.map(item => ({
value: item.id,
label: item.mc,
leaf: item.dclevel >= 5
}));
console.log(nodes, 'nodes');
resolve(nodes);
} else {
// 处理异常情况
}
}).catch(error => {
// 处理请求失败的情况
});
},
//点击卡片
toDesignScheme(item) {
this.$store.dispatch('setJh', item.jh);
this.$store.dispatch('setJhdm', item.jhdm);
console.log(item, 'item');
this.$router.push({
path: "/wellDesign",
query: {
jhdm: item.jhdm,
jh: item.jh,
qkmc: item.qkmc
}
})
},
//新建井
handleAdd() {
this.reset();
this.open = true;
this.title = "新增井设计"
},
//编辑
handleEdit(item) {
this.reset();
this.open = true;
console.log(item, 'item');
const jhdm = item.jhdm;
getJtDjjc(jhdm).then(response => {
this.form = response.data;
// this.form.mdc = response.data.mdcid;
console.log(response, 'response');
this.open = true;
this.title = "修改";
});
this.title = "修改井设计"
},
/** 提交按钮 */
submitForm() {
// const pd = this.form.pd
// this.form.pdid = pd == '' ? '' : this.basinOptions.find(item => item.pdmc == pd).pdid
// const qkmc = this.form.qkmc
// this.form.qkdm = qkmc == '' ? '' : this.blockOptions.find(item => item.qkmc == qkmc).qkdm
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.jhdm != null && this.form.jhdm !== '') {
// 如果 jhdm 存在且不为空,则执行更新操作
updateJtDjjc(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
// 否则执行新增操作
addJtDjjc(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
// 表单重置
reset() {
this.form = {
};
this.resetForm("form");
},
cancel() {
this.open = false;
},
/** 删除按钮操作 */
handleDelete(item) {
const jhdms = item.jhdm;
this.$modal.confirm('是否确认删除编号为"' + jhdms + '"的数据项?').then(function () {
return deleteJtDjjc(jhdms);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => { });
},
/** 锁定按钮操作 */
handleLock(item) {
const jhdm = item.jhdm;
this.$modal.confirm('是否确认锁定?').then(function () {
return LockJtDjjc(jhdm);
}).then(() => {
this.getList();
this.$modal.msgSuccess("已锁定");
}).catch(() => { });
},
submitFinalizeForm() {
this.openFinalize = false
},
cancelFinalizeForm() {
this.openFinalize = false
},
}
}
</script>
<style scoped lang="scss">
.cardStyle {
// border: 1px solid red;
border: 1px solid #dedede;
background-color: #fff;
border-radius: 5px;
box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.1);
padding: 12px;
}
.cardStyle:hover {
box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.2);
}
::v-deep .el-form-item {
margin-bottom: 10px;
}
.text {
font-size: 14px;
}
.clearfix:before,
.clearfix:after {
display: table;
content: "";
}
.clearfix:after {
clear: both
}
.card-container {
display: flex;
flex-wrap: wrap;
/* 当空间不足时换行 */
justify-content: flex-start;
/* 卡片从左向右排列 */
align-items: flex-start;
/* 卡片顶部对齐 */
margin-top: -10px;
}
.box-card {
flex: 0 0 auto;
width: 300px;
}
.header-content {
display: flex;
align-items: center;
/* 垂直居中对齐 */
}
.left {
margin-right: 10px;
/* 左右间距 */
}
.right {
flex-grow: 1;
/* 右侧内容占据剩余空间 */
}
::v-deep .el-card__body {
padding: 0 !important;
}
</style>
<style lang="scss" scoped></style>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment