Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
pdczt_qd
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
zhaopanyu
pdczt_qd
Commits
9142da4f
Commit
9142da4f
authored
Nov 20, 2025
by
zhaopanyu
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
zpy
parent
08340864
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
2583 additions
and
905 deletions
+2583
-905
src/router/index.js
+1
-1
src/views/efficiencyAnalysis/djxx/detail/components/HistogramGraph copy 2.vue
+1612
-0
src/views/efficiencyAnalysis/djxx/detail/components/HistogramGraph.vue
+122
-156
src/views/efficiencyAnalysis/jcxx/components/HistogramGraph.vue
+155
-69
src/views/index copy 2.vue
+689
-0
src/views/index.vue
+4
-679
No files found.
src/router/index.js
View file @
9142da4f
...
...
@@ -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
},
},
],
},
...
...
src/views/efficiencyAnalysis/djxx/detail/components/HistogramGraph copy 2.vue
0 → 100644
View file @
9142da4f
<
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
src/views/efficiencyAnalysis/djxx/detail/components/HistogramGraph.vue
View file @
9142da4f
<
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
];
},
legend
Items
()
{
return
Array
.
isArray
(
this
.
tlList
)
?
this
.
tlList
:
[]
;
legend
PanelWidth
()
{
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
2
0px
;
min-height
:
calc
(
100vh
-
140px
)
;
padding
:
0
1
0px
;
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
:
2
2
0px
;
min-width
:
2
0
0px
;
.
strata-legend
{
width
:
2
4
0px
;
min-width
:
2
4
0px
;
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
:
1
5
px
;
.legend-
header
{
font-size
:
1
6
px
;
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
;
}
}
/* 深色模式支持 */
...
...
src/views/efficiencyAnalysis/jcxx/components/HistogramGraph.vue
View file @
9142da4f
<
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
src/views/index copy 2.vue
0 → 100644
View file @
9142da4f
<
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
>
src/views/index.vue
View file @
9142da4f
<
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
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment