Commit fa6919bf by cat

zd

parent 6688d535
......@@ -69,3 +69,11 @@ export function startConvert(id) {
method: 'post',
})
}
// 导出Word(转录文本)
export function exportWordYY(id) {
return request({
url: '/hyjyxx/hyjyxx/exportWord_YY',
method: 'post',
data: { id },
})
}
......@@ -7,10 +7,14 @@
</div>
<el-row :gutter="20">
<el-col :span="16">
<el-col :span="12">
<el-card shadow="never"
body-style="padding: 12px; height: calc(100vh - 220px); overflow: hidden; display: flex; flex-direction: column;background-color:#f6f8fa ">
<div style="font-weight: 600; margin-bottom: 8px;">转录文本</div>
<div style="display:flex; align-items:center; justify-content: space-between; margin-bottom: 8px;">
<div style="font-weight: 600;">转录文本</div>
<el-button type="primary" size="small" icon="el-icon-download" @click="handleExportWord">导出</el-button>
</div>
<div class="transcript-area flex-scroll" style="margin-left: 10px;">
<div style="padding-bottom: 10px;">
<div v-for="(segment, index) in transcriptList" :key="index" placement="top" style="margin-bottom: 15px;">
......@@ -62,7 +66,7 @@
</div>
</el-card>
</el-col>
<el-col :span="8">
<el-col :span="12">
<el-card shadow="never" body-style="padding: 12px; height: calc(100vh - 220px); overflow: auto;">
<div
style="display:flex; align-items:center; justify-content: space-between; margin-bottom: 12px;">
......@@ -79,6 +83,7 @@
<script>
import { getHyjyxx } from '@/api/hyjyxx/hyjyxx'
import { download } from '@/utils/request'
import icon1 from '@/assets/images/个人1.svg'
import icon2 from '@/assets/images/个人2.svg'
import icon3 from '@/assets/images/个人3.svg'
......@@ -237,7 +242,6 @@ export default {
const parsed = JSON.parse(bzh)
if (Array.isArray(parsed)) bzh = parsed
} catch (e) {
console.warn('Failed to parse ywBzh string:', e)
}
}
if (Array.isArray(bzh)) {
......@@ -314,6 +318,16 @@ export default {
},
reload() {
this.load()
},
/**
* 导出Word(转录文本)
*/
handleExportWord() {
if (!this.id) {
this.$message.warning('缺少会议ID')
return
}
download('hyjyxx/hyjyxx/exportWord_YY', { id: this.id }, `会议转录文本_${this.detail.ypwjName || this.id}_${new Date().getTime()}.docx`)
}
}
}
......
......@@ -24,7 +24,13 @@ export default {
compact: { type: Boolean, default: false },
width: { type: Number, default: 0 },
height: { type: Number, default: 0 },
idOverride: { type: [String, Number], default: null }
idOverride: { type: [String, Number], default: null },
// 外部传入需要高亮的 SEGY 线下标(来自 index2.vue 的当前显示/选中)
activeSegyIndex: { type: Number, default: null },
// 支持多条同时高亮
activeSegyIndices: { type: Array, default: () => [] },
// 点击线后跳转路由;传字符串为 path,或路由对象;不传则仅抛事件
navigateTo: { type: [String, Object], default: null }
},
data() {
return {
......@@ -33,6 +39,8 @@ export default {
loadingDht: false,
dhtResult: null,
segyLines: [],
// 与 segyLines 同步的元信息(如 id、名称)
segyMetas: [],
filters: {
projectName: '',
block: ''
......@@ -43,7 +51,11 @@ export default {
yMin: null,
yMax: null,
verticalLineX: null,
points: []
points: [],
// 防抖定时器,避免频繁渲染
renderTimer: null,
// 标记是否正在渲染,避免重复渲染
isRendering: false
}
},
mounted() {
......@@ -82,6 +94,11 @@ export default {
},
beforeDestroy() {
window.removeEventListener('resize', this.resizeChart)
// 清除防抖定时器
if (this.renderTimer) {
clearTimeout(this.renderTimer)
this.renderTimer = null
}
if (this.chart) {
this.chart.dispose()
this.chart = null
......@@ -94,11 +111,24 @@ export default {
watch: {
// 监听idOverride变化,当路由参数变化时重新加载数据
idOverride(newId, oldId) {
if (newId !== oldId && newId) {
console.log('[ysgc] idOverride变化,重新加载数据:', newId);
// 严格验证:只有当 newId 有有效值且与旧值不同时才触发
const newIdValid = newId != null && newId !== 'null' && newId !== 'undefined' && String(newId).trim() !== ''
if (newId !== oldId && newIdValid) {
this.dhtResult = null; // 清除缓存
this.loadDht(true);
}
},
// 外部高亮线变化,刷新图表
activeSegyIndex(newVal, oldVal) {
console.log('[ysgc] activeSegyIndex watch 触发:', { oldVal, newVal, equal: newVal === oldVal })
// 只有当值真正改变时才触发渲染,避免重复触发
if (newVal === oldVal) return
this._requestRender()
},
activeSegyIndices(newVal, oldVal) {
// 只有当值真正改变时才触发渲染,避免重复触发
if (JSON.stringify(newVal) === JSON.stringify(oldVal)) return
this._requestRender()
}
},
methods: {
......@@ -114,7 +144,7 @@ export default {
}
let w = el.clientWidth
let h = el.clientHeight
console.log('[ysgc] before size', w, h)
//console.log('[ysgc] before size', w, h)
if (w === 0) {
el.style.width = '800px'
w = el.clientWidth
......@@ -127,7 +157,7 @@ export default {
for (const entry of entries) {
const cr = entry.contentRect
if (cr.height > 0 && cr.width > 0) {
console.log('[ysgc] ResizeObserver visible', cr.width, cr.height)
//console.log('[ysgc] ResizeObserver visible', cr.width, cr.height)
if (this.resizeObserver) {
try { this.resizeObserver.disconnect() } catch (e) { }
this.resizeObserver = null
......@@ -139,7 +169,7 @@ export default {
this.resizeObserver.observe(el)
}
}
console.log('[ysgc] after size', w, h)
//console.log('[ysgc] after size', w, h)
// 即使高度为 0,也先用回退尺寸初始化,待可见后再 resize
this.initChart()
// 兜底:稍后再尝试一次 resize
......@@ -148,7 +178,10 @@ export default {
this.$nextTick(tryInit)
},
resizeChart() {
if (this.chart) this.chart.resize()
if (this.chart && !this.isRendering) {
// resize 时不要触发渲染,避免循环
this.chart.resize()
}
},
initChart() {
const el = this.$refs.chartRef
......@@ -157,6 +190,8 @@ export default {
this.chart.dispose()
this.chart = null
}
// 重置事件绑定标记,允许重新绑定事件
this._eventsBound = false
// 当容器高度为 0 时,先用显式宽高初始化,等可见后再 resize
const initWidth = this.compact ? (this.width || 260) : (el.clientWidth || 800)
const initHeight = this.compact ? (this.height || 160) : (el.clientHeight || 600)
......@@ -169,21 +204,41 @@ export default {
// 对于缩略图模式,确保渲染成功
if (this.compact) {
console.log('[ysgc] 缩略图初始化完成', initWidth, initHeight)
//console.log('[ysgc] 缩略图初始化完成', initWidth, initHeight)
}
},
// 数据变化后自动刷新(确保缩略图及时绘制)
_requestRender() {
if (!this.chart) return
// 如果正在渲染,取消之前的定时器
if (this.renderTimer) {
clearTimeout(this.renderTimer)
this.renderTimer = null
}
// 如果正在渲染中,直接返回,避免重复渲染
if (this.isRendering) return
// 使用防抖机制,避免频繁触发
this.renderTimer = setTimeout(() => {
this.renderTimer = null
if (!this.chart) return
this.isRendering = true
this._internalRender = true
this.$nextTick(() => {
try {
this.renderChart()
// resize 可能会触发事件,延迟执行避免循环
setTimeout(() => {
if (this.chart && !this.isRendering) {
this.chart.resize()
// 确保缩略图正确显示
if (this.compact) {
console.log('[ysgc] 缩略图渲染完成')
}
}, 10)
} finally {
this.isRendering = false
this._internalRender = false
}
})
}, 50)
},
// 调用地层/断层图接口(示例),优先从路由参数获取 id
loadDht(forceReload = false) {
......@@ -193,7 +248,10 @@ export default {
const params = route.params || {}
const query = route.query || {}
const id = this.idOverride != null ? this.idOverride : (params.zbid || query.zbid || params.id || query.id)
if (!id) return
// 严格验证 id:排除 null、undefined、空字符串、字符串 "null"、"undefined"
if (!id || id === 'null' || id === 'undefined' || String(id).trim() === '') {
return
}
this.loadingDht = true
toDht(id)
.then(res => {
......@@ -204,13 +262,13 @@ export default {
})
.catch(err => {
// 保留错误日志,避免打断现有流程
console.error('[ysgc] toDht error:', err)
//console.error('[ysgc] toDht error:', err)
})
.finally(() => {
this.loadingDht = false
})
} catch (e) {
console.error('[ysgc] loadDht failed:', e)
//console.error('[ysgc] loadDht failed:', e)
}
},
// 将接口返回的数据应用到图表状态
......@@ -230,8 +288,8 @@ export default {
}
// 线(SEGY)
const segys = Array.isArray(data.ysqqXmxxSegy) ? data.ysqqXmxxSegy : []
console.log(segys, 'segys');
const lines = segys.map((seg, idx) => {
//console.log(segys, 'segys');
const linesWithMeta = segys.map((seg, idx) => {
try {
const arr = typeof seg.x === 'string' ? JSON.parse(seg.x) : (Array.isArray(seg.x) ? seg.x : [])
// 若存在有效的 sxh,则按 sxh 升序连线;否则按原始顺序
......@@ -244,21 +302,37 @@ export default {
const pairs = ordered.map(p => [p.x, p.y])
try {
const cloneForLog = pairs.map(it => [it[0], it[1]])
console.log('[ysgc] segy parsed', { index: idx, id: seg && seg.id, points: cloneForLog.length, sample: cloneForLog.slice(0, 5) })
console.log('[ysgc] segy full points', { index: idx, id: seg && seg.id, pointsArray: cloneForLog })
//console.log('[ysgc] segy parsed', { index: idx, id: seg && seg.id, points: cloneForLog.length, sample: cloneForLog.slice(0, 5) })
//console.log('[ysgc] segy full points', { index: idx, id: seg && seg.id, pointsArray: cloneForLog })
} catch (e) { }
return pairs
return {
line: pairs,
meta: { id: seg && seg.id, name: seg && (seg.name || seg.segyName || seg.jbsegyName || seg.xbsegyName), originalIndex: idx }
}
} catch (e) {
console.error('[ysgc] segy parse error', { index: idx, id: seg && seg.id, err: e })
return []
//console.error('[ysgc] segy parse error', { index: idx, id: seg && seg.id, err: e })
return { line: [], meta: { id: seg && seg.id, originalIndex: idx } }
}
}).filter(line => line.length > 0)
}).filter(item => item.line.length > 0)
this.segyLines = lines
this.segyLines = linesWithMeta.map(item => item.line)
// 保存元信息(保持与 lines 的顺序一致,只包含有效线条)
this.segyMetas = linesWithMeta.map(item => item.meta)
try {
console.log('[ysgc] segyLines summary', { lines: this.segyLines.length, lengths: this.segyLines.map(l => l.length) })
//console.log('[ysgc] segyLines summary', { lines: this.segyLines.length, lengths: this.segyLines.map(l => l.length) })
//console.log('[ysgc] segyMetas summary', { metas: this.segyMetas.length, ids: this.segyMetas.map(m => m.id) })
} catch (e) { }
},
// 对外方法:设置/清除高亮(便于通过 ref 调用)
setActiveSegyIndex(index) {
this.$emit('update:activeSegyIndex', index)
// 注意:不要直接修改 props,Vue 不允许直接修改 props
// 如果父组件使用了 v-model,会自动更新;否则通过事件传递
this._requestRender()
},
clearActiveSegy() {
this.setActiveSegyIndex(null)
},
// 生成图表配置
buildOption() {
// 若未提供边界,则根据数据自动估算
......@@ -284,25 +358,69 @@ export default {
if (this.points && this.points.length) {
series.push({
type: 'scatter',
symbolSize: this.compact ? 8 : 12,
symbolSize: this.compact ? 4 : 6,
data: this.points.map(p => [p.x, p.y, p.name]),
itemStyle: { color: '#2d72ff', borderColor: '#2d72ff' },
label: this.compact ? { show: false } : { show: true, position: 'top', color: '#333', formatter: params => params.data[2] }
})
}
// 叠加 SEGY 线
for (const line of this.segyLines) {
// 统一计算选中集合(基于原始索引)
const activeOriginalIndices = new Set(
Array.isArray(this.activeSegyIndices) && this.activeSegyIndices.length
? this.activeSegyIndices
: (this.activeSegyIndex != null ? [this.activeSegyIndex] : [])
)
// console.log('[ysgc] buildOption 高亮调试:', {
// activeSegyIndex: this.activeSegyIndex,
// activeSegyIndices: this.activeSegyIndices,
// activeOriginalIndices: Array.from(activeOriginalIndices),
// segyLinesLength: this.segyLines.length,
// segyMetasLength: this.segyMetas ? this.segyMetas.length : 0,
// segyMetas: this.segyMetas
// })
for (let li = 0; li < this.segyLines.length; li++) {
const line = this.segyLines[li]
// 获取当前线条的元信息,包括原始索引
const meta = this.segyMetas && this.segyMetas[li] ? this.segyMetas[li] : {}
// 使用原始索引来判断是否高亮
const originalIdx = meta.originalIndex != null ? meta.originalIndex : li
const isLineActive = activeOriginalIndices.has(originalIdx)
// //console.log(`[ysgc] 线条 ${li}:`, {
// originalIdx,
// isLineActive,
// meta,
// activeOriginalIndices: Array.from(activeOriginalIndices),
// hasOriginalIdx: activeOriginalIndices.has(originalIdx)
// })
series.push({
type: 'line',
coordinateSystem: 'cartesian2d',
data: line,
showSymbol: false,
silent: false,
connectNulls: true,
smooth: false,
emphasis: { scale: false },
encode: { x: 0, y: 1 },
lineStyle: { color: '#00aa66', width: 2 },
tooltip: { show: false }
z: 10,
zlevel: 1,
lineStyle: {
color: isLineActive ? '#FF6B6B' : '#409EFF',
width: isLineActive ? 4 : 2,
opacity: 1
},
// 提升可点击体验
cursor: 'pointer',
emphasis: { lineStyle: { width: 6 } },
tooltip: {
show: true,
formatter: (p) => {
const v = p && p.data
return v ? `X:${v[0]}<br/>Y:${v[1]}` : ''
}
}
})
}
// 垂直参考线(可选)
......@@ -390,17 +508,32 @@ export default {
// 渲染或更新图表
renderChart() {
if (!this.chart) return
try {
const option = this.buildOption()
this.chart.setOption(option, true)
// 确保事件绑定有效
// 确保事件绑定有效(只在必要时绑定)
this.bindChartEvents()
} catch (e) {
// 渲染失败时重置状态
this.isRendering = false
this._internalRender = false
//console.error('[ysgc] renderChart error:', e)
}
},
// 绑定点击/双击事件,把被点击的绿色线条映射为 segy 下标并上报
bindChartEvents() {
if (!this.chart) return
// 使用标记避免重复绑定
if (this._eventsBound) return
this._eventsBound = true
// 解除旧绑定避免重复
this.chart.off('click')
this.chart.off('dblclick')
if (this._zrClick) {
try { this.chart.getZr().off('click', this._zrClick) } catch (e) {}
this._zrClick = null
}
const getLineIndexBySeries = (seriesIndex) => {
// 如果有井点散点,最前面占 1 个 series
......@@ -409,20 +542,135 @@ export default {
// 现在每条 SEGY 线只对应一个 line series
if (seriesIndex < base) return -1
const offset = seriesIndex - base
// console.log('[ysgc] getLineIndexBySeries 计算:', {
// seriesIndex,
// hasPoints,
// base,
// offset,
// segyLinesLength: this.segyLines.length
// })
if (offset < 0 || offset >= this.segyLines.length) return -1
return offset
}
const emitPick = (params) => {
if (!params || params.seriesType !== 'line') return
//console.log('[ysgc] emitPick 被调用:', params)
if (!params) {
//console.log('[ysgc] emitPick: params 为空,返回')
return
}
// 允许 componentType 不存在或为 'series'
if (params.componentType && params.componentType !== 'series') {
//console.log('[ysgc] emitPick: componentType 不匹配', params.componentType)
return
}
if (params.seriesType !== 'line') {
//console.log('[ysgc] emitPick: seriesType 不是 line', params.seriesType)
return
}
const idx = getLineIndexBySeries(params.seriesIndex)
//console.log('[ysgc] emitPick: 计算出的 idx', { seriesIndex: params.seriesIndex, idx, segyLinesLength: this.segyLines.length })
if (idx >= 0 && idx < this.segyLines.length) {
this.$emit('segyLinePick', { index: idx })
const meta = this.segyMetas && this.segyMetas[idx] ? this.segyMetas[idx] : {}
const originalIdx = meta.originalIndex != null ? meta.originalIndex : idx
const payload = {
index: idx,
originalIndex: originalIdx,
id: meta.id,
meta
}
// console.log('[ysgc] emitPick 发送事件:', {
// filteredIndex: idx,
// originalIndex: originalIdx,
// id: meta.id,
// meta,
// payload
// })
this.$emit('segyLinePick', payload)
if (this.navigateTo) {
try {
const currentRoute = this.$route || {}
const routeId = this.idOverride != null ? this.idOverride : (currentRoute.params && (currentRoute.params.zbid || currentRoute.params.id)) || (currentRoute.query && (currentRoute.query.zbid || currentRoute.query.id))
const base = typeof this.navigateTo === 'string' ? { path: this.navigateTo } : { ...(this.navigateTo || {}) }
const next = {
...base,
query: {
...(base.query || {}),
segyIndex: idx,
segyId: meta.id,
id: routeId
}
}
// 延迟执行,确保事件先处理完;如果当前路由相同,使用 replace
this.$nextTick(() => {
try {
const currentPath = this.$route && this.$route.path
const isSamePath = currentPath === next.path || currentPath === base.path
if (isSamePath) {
this.$router && this.$router.replace(next)
} else {
this.$router && this.$router.push(next)
}
} catch (err) {
//console.error('[ysgc] navigation error:', err)
}
})
} catch (e) {
//console.error('[ysgc] navigation error:', e)
}
}
}
}
this.chart.on('click', emitPick)
this.chart.on('dblclick', emitPick)
// ZRender 兜底命中:即使未命中 series,也能根据像素距离选线
const zrClick = (e) => {
if (!e) return
if (!Array.isArray(this.segyLines) || this.segyLines.length === 0) return
const pt = [e.offsetX, e.offsetY]
const toPixel = (xy) => this.chart.convertToPixel({ xAxisIndex: 0, yAxisIndex: 0 }, xy)
const distPointSeg = (p, a, b) => {
const vx = b[0] - a[0], vy = b[1] - a[1]
const wx = p[0] - a[0], wy = p[1] - a[1]
const c1 = vx*wx + vy*wy
if (c1 <= 0) return Math.hypot(p[0]-a[0], p[1]-a[1])
const c2 = vx*vx + vy*vy
if (c2 <= 0) return Math.hypot(p[0]-a[0], p[1]-a[1])
const t = Math.min(1, Math.max(0, c1 / c2))
const proj = [a[0] + t*vx, a[1] + t*vy]
return Math.hypot(p[0]-proj[0], p[1]-proj[1])
}
let bestIdx = -1
let bestDist = Infinity
for (let li = 0; li < this.segyLines.length; li++) {
const line = this.segyLines[li]
for (let j = 1; j < line.length; j++) {
const a = toPixel(line[j-1])
const b = toPixel(line[j])
const d = distPointSeg(pt, a, b)
if (d < bestDist) { bestDist = d; bestIdx = li }
}
}
const threshold = 8 // 像素阈值
//console.log('[ysgc] zrClick 检测结果:', { bestIdx, bestDist, threshold, hasPoints: Array.isArray(this.points) && this.points.length > 0 })
if (bestIdx >= 0 && bestDist <= threshold) {
// bestIdx 是 segyLines 的索引(过滤后的索引)
// 需要转换为 echarts series 的索引
const hasPoints = Array.isArray(this.points) && this.points.length > 0
const base = hasPoints ? 1 : 0
const seriesIndex = base + bestIdx
// 构造与 series click 相同的负载
const fake = { seriesType: 'line', seriesIndex: seriesIndex }
//console.log('[ysgc] zrClick 构造 fake params:', { bestIdx, seriesIndex, fake })
emitPick(fake)
}
}
this.chart.getZr().on('click', zrClick)
this._zrClick = zrClick
}
}
}
......
......@@ -3238,7 +3238,7 @@ export default {
}, 100);
},
// 处理缩略图(绿色线)点击/双击事件
onThumbLinePick(payload) {
async onThumbLinePick(payload) {
try {
if (!payload) return;
......@@ -3278,15 +3278,29 @@ export default {
const seg = this.segyList[lineIdx];
console.log('[index2] 设置 currentSegyIndex:', { lineIdx, segId: seg && seg.id, segName: seg && (seg.name || seg.segyName || seg.jbsegyName || seg.xbsegyName) });
this.currentSegyIndex = lineIdx;
if (seg) {
this.loadDualSegyFiles(seg.jbsegy, seg.xbsegy, seg.jbsegyName, seg.xbsegyName);
}
// 如果弹窗是打开的,点击后关闭弹窗
// 立即关闭弹窗,给用户反馈
if (this.showThumbDialog) {
this.showThumbDialog = false;
}
// 设置加载状态,避免显示错误的图
if (seg) {
this.isLoading = true;
try {
// 等待数据加载完成
await this.loadDualSegyFiles(seg.jbsegy, seg.xbsegy, seg.jbsegyName, seg.xbsegyName);
} catch (error) {
console.error('[index2] loadDualSegyFiles 加载失败:', error);
} finally {
// 数据加载完成后关闭加载状态
this.isLoading = false;
}
}
} catch (e) {
console.error('[index2] onThumbLinePick error:', e);
// 确保出错时也关闭加载状态
this.isLoading = false;
}
},
// 确保缩略图渲染
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment