Commit a8a4c625 by cat

zd 首页样式修改 缩略图弹窗显示 成果管理

parent 66a45d27
import request from '@/utils/request'
// 查询成果管理列表
// 查询成果列表列表
export function listWjscqk(query) {
return request({
url: '/yscgWjscqk/wjscqk/list',
method: 'get',
params: query
params: query,
})
}
// 查询成果管理详细
// 查询成果列表详细
export function getWjscqk(id) {
return request({
url: '/yscgWjscqk/wjscqk/' + id,
method: 'get'
method: 'get',
})
}
// 新增成果管理
// 新增成果列表
export function addWjscqk(data) {
return request({
url: '/yscgWjscqk/wjscqk',
method: 'post',
data: data
data: data,
})
}
// 修改成果管理
// 修改成果列表
export function updateWjscqk(data) {
return request({
url: '/yscgWjscqk/wjscqk',
method: 'put',
data: data
data: data,
})
}
// 删除成果管理
// 删除成果列表
export function delWjscqk(id) {
return request({
url: '/yscgWjscqk/wjscqk/' + id,
method: 'delete'
method: 'delete',
})
}
// 查询成果管理列表
export function listYscgList(query) {
return request({
url: '/ysqqXmxx/ysqqXmxx/yscgList',
method: 'get',
params: query,
})
}
......@@ -5,7 +5,7 @@ export function listZllx(query) {
return request({
url: '/yscgZllx/zllx/list',
method: 'get',
params: query
params: query,
})
}
......@@ -13,7 +13,7 @@ export function listZllx(query) {
export function getZllx(id) {
return request({
url: '/yscgZllx/zllx/' + id,
method: 'get'
method: 'get',
})
}
......@@ -22,7 +22,7 @@ export function addZllx(data) {
return request({
url: '/yscgZllx/zllx',
method: 'post',
data: data
data: data,
})
}
......@@ -31,7 +31,7 @@ export function updateZllx(data) {
return request({
url: '/yscgZllx/zllx',
method: 'put',
data: data
data: data,
})
}
......@@ -39,6 +39,14 @@ export function updateZllx(data) {
export function delZllx(id) {
return request({
url: '/yscgZllx/zllx/' + id,
method: 'delete'
method: 'delete',
})
}
// 查询资料类型下拉选项
export function selectZllx() {
return request({
url: '/yscgZllx/zllx/selectZllx',
method: 'get',
})
}

79.2 KB | W: | H:

1.65 KB | W: | H:

src/assets/images/profile.jpg
src/assets/images/profile.jpg
src/assets/images/profile.jpg
src/assets/images/profile.jpg
  • 2-up
  • Swipe
  • Onion skin
......@@ -225,17 +225,30 @@ export const dynamicRoutes = [
},
],
},
{
path: '/yscgWjscqk/wjscqk',
component: Layout,
hidden: true,
children: [
{
path: 'index',
component: () => import('@/views/yscgWjscqk/wjscqk/index'),
name: 'WjscqkIndex',
meta: { title: '成果管理', activeMenu: '/yscgWjscqk/yscg' },
},
],
},
];
// 防止连续点击多次路由报错
let routerPush = Router.prototype.push;
let routerReplace = Router.prototype.replace;
// push
Router.prototype.push = function push(location) {
Router.prototype.push = function push (location) {
return routerPush.call(this, location).catch((err) => err);
};
// replace
Router.prototype.replace = function push(location) {
Router.prototype.replace = function push (location) {
return routerReplace.call(this, location).catch((err) => err);
};
......
......@@ -19,11 +19,11 @@
<el-tab-pane label="待验收" name="pending">
<div class="card-container">
<el-row :gutter="16" v-loading="pendingLoading">
<el-col :span="6" v-for="item in pendingList" :key="item.id" class="card-item">
<el-col :span="4" v-for="item in pendingList" :key="item.id" class="card-item">
<el-card class="project-card" shadow="hover">
<div class="card-header" @click="goNav2(item)" style="cursor: pointer;">
<h3 class="project-title">{{ item.xmmc }}</h3>
<el-tag type="warning" size="small">待验收</el-tag>
<el-tag size="small" class="status-tag status-pending">待验收</el-tag>
</div>
<div class="card-content">
<div class="content-wrapper">
......@@ -46,7 +46,7 @@
</div>
</div>
<div class="button-section">
<el-button size="small" @click="handleComplete(item)">完成</el-button>
<el-button size="mini" @click="handleComplete(item)">完成</el-button>
</div>
</div>
</div>
......@@ -66,7 +66,7 @@
<el-tab-pane label="已验收" name="completed">
<div class="card-container">
<el-row :gutter="16" v-loading="completedLoading">
<el-col :span="6" v-for="item in completedList" :key="item.id" class="card-item">
<el-col :span="4" v-for="item in completedList" :key="item.id" class="card-item">
<el-card class="project-card" shadow="hover">
<div class="card-header">
<h3 class="project-title">{{ item.xmmc }}</h3>
......@@ -314,6 +314,7 @@ export default {
padding: 3px 0;
border-radius: 4px;
transition: background-color 0.2s ease;
align-items: center;
}
.info-item:hover {
......@@ -325,6 +326,7 @@ export default {
min-width: 70px;
flex-shrink: 0;
font-weight: 500;
white-space: nowrap;
}
.value {
......@@ -334,6 +336,7 @@ export default {
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 400;
min-width: 0;
}
.button-section {
......@@ -346,11 +349,47 @@ export default {
}
.button-section .el-button {
width: 70px;
height: 28px;
width: 50px;
height: 25px;
font-size: 12px;
font-weight: 500;
border-radius: 14px;
border-radius: 20px;
background: linear-gradient(135deg, #409EFF 0%, #66b1ff 100%);
border: none;
color: #ffffff !important;
box-shadow: 0 1px 4px rgba(64, 158, 255, 0.2);
transition: all 0.2s ease-out;
position: relative;
overflow: hidden;
letter-spacing: 0.5px;
}
.button-section .el-button:hover {
background: linear-gradient(135deg, #337ecc 0%, #5a9eff 100%);
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.25);
transform: translateY(-0.5px);
color: #ffffff !important;
}
.button-section .el-button:active {
transform: translateY(0);
box-shadow: 0 1px 3px rgba(64, 158, 255, 0.2);
color: #ffffff !important;
}
.button-section .el-button::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.15), transparent);
transition: left 0.3s ease-out;
}
.button-section .el-button:hover::before {
left: 100%;
}
/* Safety: if any global rule hid buttons inside cards on non-hover, override */
......@@ -367,6 +406,17 @@ export default {
visibility: visible;
}
/* 状态标签:待验收(蓝色系,贴合系统主色) */
.status-tag {
border: none !important;
font-weight: 600;
}
.status-pending {
color: #1f3b64;
background: rgba(64, 158, 255, 0.12);
}
/* 分页固定在右下角 */
.pagination-wrapper {
position: fixed;
......@@ -377,13 +427,13 @@ export default {
/* 默认大屏下每行5列 */
.el-col {
width: 25% !important;
width: 20% !important;
}
/* 响应式设计 */
@media (max-width: 1400px) {
.el-col {
width: 25% !important;
width: 20% !important;
}
}
......
<!-- 成果列表 -->
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<!-- <el-form-item label="项目id" prop="xmid">
<el-input
v-model="queryParams.xmid"
placeholder="请输入项目id"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item> -->
<el-form-item label="文件名称" prop="wjmc">
<el-input v-model="queryParams.wjmc" placeholder="请输入文件名称" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="资料类型" prop="zllx">
<el-input v-model="queryParams.zllx" placeholder="请输入资料类型" clearable @keyup.enter.native="handleQuery" />
<el-select v-model="queryParams.zllx" placeholder="请选择资料类型" clearable>
<el-option
v-for="item in zllxOptions"
:key="item.id"
:label="item.lxmc"
:value="item.lxmc">
</el-option>
</el-select>
</el-form-item>
<!-- <el-form-item label="文件路径" prop="wjlj">
<el-input
v-model="queryParams.wjlj"
placeholder="请输入文件路径"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="创建人" prop="createdBy">
<el-input
v-model="queryParams.createdBy"
placeholder="请输入创建人"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="创建时间" prop="createdTime">
<el-date-picker clearable
v-model="queryParams.createdTime"
type="date"
value-format="yyyy-MM-dd"
placeholder="请选择创建时间">
</el-date-picker>
</el-form-item>
<el-form-item label="备用1" prop="ext1">
<el-input
v-model="queryParams.ext1"
placeholder="请输入备用1"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="备用2" prop="ext2">
<el-input
v-model="queryParams.ext2"
placeholder="请输入备用2"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="备用3" prop="ext3">
<el-input
v-model="queryParams.ext3"
placeholder="请输入备用3"
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>
......@@ -77,34 +30,12 @@
</el-form-item>
</el-form>
<!-- <el-row :gutter="10" class="mb8">
<el-col :span="1.5">
</el-col>
<el-col :span="1.5">
</el-col>
<el-col :span="1.5">
</el-col>
<el-col :span="1.5">
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row> -->
<el-table border v-loading="loading" :data="wjscqkList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<!-- <el-table-column label="主键" align="center" prop="id" />
<el-table-column label="项目id" align="center" prop="xmid" /> -->
<el-table-column label="文件名称" align="center" prop="wjmc" min-width="120" show-overflow-tooltip />
<el-table-column label="资料类型" align="center" prop="zllx" min-width="100" show-overflow-tooltip />
<el-table-column label="文件路径" align="center" prop="wjlj" min-width="170" show-overflow-tooltip />
<!-- <el-table-column label="创建人" align="center" prop="createdBy" />
<el-table-column label="创建时间" align="center" prop="createdTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createdTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="备用1" align="center" prop="ext1" />
<el-table-column label="备用2" align="center" prop="ext2" />
<el-table-column label="备用3" align="center" prop="ext3" /> -->
<el-table-column label="上传时间" align="center" prop="createdTime" min-width="170" show-overflow-tooltip />
<el-table-column label="操作" min-width="110" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
......@@ -121,35 +52,42 @@
<!-- 添加或修改成果管理对话框 -->
<el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="89px">
<!-- <el-form-item label="项目id" prop="xmid">
<el-input v-model="form.xmid" placeholder="请输入项目id" />
</el-form-item> -->
<el-form-item label="文件名称" prop="wjmc">
<el-input v-model="form.wjmc" placeholder="请输入文件名称" />
</el-form-item>
<el-form-item label="资料类型" prop="zllx">
<el-input v-model="form.zllx" placeholder="请输入资料类型" />
</el-form-item>
<el-form-item label="文件路径" prop="wjlj">
<el-input v-model="form.wjlj" placeholder="请输入文件路径" />
</el-form-item>
<!-- <el-form-item label="创建人" prop="createdBy">
<el-input v-model="form.createdBy" placeholder="请输入创建人" />
</el-form-item>
<el-form-item label="创建时间" prop="createdTime">
<el-date-picker clearable v-model="form.createdTime" type="date" value-format="yyyy-MM-dd"
placeholder="请选择创建时间">
</el-date-picker>
</el-form-item>
<el-form-item label="备用1" prop="ext1">
<el-input v-model="form.ext1" placeholder="请输入备用1" />
</el-form-item>
<el-form-item label="备用2" prop="ext2">
<el-input v-model="form.ext2" placeholder="请输入备用2" />
</el-form-item>
<el-form-item label="备用3" prop="ext3">
<el-input v-model="form.ext3" placeholder="请输入备用3" />
</el-form-item> -->
<el-form-item label="资料类型" prop="zllx">
<el-select v-model="form.zllx" placeholder="请选择资料类型" clearable>
<el-option
v-for="item in zllxOptions"
:key="item.id"
:label="item.lxmc"
:value="item.lxmc">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="文件上传" prop="fileList">
<el-upload
ref="fileUpload"
:action="uploadUrl"
:headers="uploadHeaders"
:file-list="fileList"
:before-upload="handleBeforeUpload"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
:on-remove="handleRemove"
:on-exceed="handleExceed"
:on-change="handleFileChange"
:data="uploadData"
drag
class="upload-dragger"
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">
支持 doc、docx、pdf、xls、xlsx、ppt、pptx、txt、jpg、jpeg、png、gif 格式文件,单个文件不超过50MB<br/>
<span style="color: #409EFF;">上传新文件将自动替换已有文件</span>
</div>
</el-upload>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
......@@ -161,6 +99,8 @@
<script>
import { listWjscqk, getWjscqk, delWjscqk, addWjscqk, updateWjscqk } from "@/api/yscgWjscqk/wjscqk"
import { selectZllx } from "@/api/yscgZllx/zllx"
import { getToken } from "@/utils/auth"
export default {
name: "Wjscqk",
......@@ -188,6 +128,7 @@ export default {
queryParams: {
pageNum: 1,
pageSize: 10,
id: null,
xmid: null,
wjmc: null,
zllx: null,
......@@ -202,22 +143,81 @@ export default {
form: {},
// 表单校验
rules: {
}
},
// 资料类型选项
zllxOptions: [],
// 文件列表
fileList: [],
// 上传配置
uploadUrl: process.env.VUE_APP_BASE_API + "/common/upload",
uploadHeaders: {
Authorization: "Bearer " + getToken()
},
uploadData: {}
}
},
created() {
// 获取路由参数中的id
if (this.$route.query.xmid) {
this.queryParams.xmid = this.$route.query.xmid
}
this.getList()
this.getZllxOptions()
},
watch: {
// 监听路由参数变化
'$route.query.xmid'(newId) {
if (newId) {
this.queryParams.xmid = newId
this.getList()
}
}
},
methods: {
/** 查询成果管理列表 */
getList() {
console.log(this.queryParams.xmid,6666);
this.loading = true
listWjscqk(this.queryParams).then(response => {
this.wjscqkList = response.rows
this.total = response.total
const mergedRows = this.mergeRowsById(response.rows || [])
this.wjscqkList = mergedRows
this.total = mergedRows.length
this.loading = false
})
},
/** 将相同id的记录进行合并(合并文件名与文件路径) */
mergeRowsById(rows) {
const idToRowMap = {}
const mergeCommaSeparated = (first, second) => {
const parts = []
if (first) parts.push(...String(first).split(',').filter(Boolean))
if (second) parts.push(...String(second).split(',').filter(Boolean))
const seen = new Set()
return parts.filter(item => {
if (seen.has(item)) return false
seen.add(item)
return true
}).join(',') || null
}
rows.forEach(row => {
const key = row.id
if (key == null) {
// 无id的记录直接保留为独立项
const tempKey = `__no_id__${Math.random()}_${Date.now()}`
idToRowMap[tempKey] = { ...row }
return
}
if (!idToRowMap[key]) {
idToRowMap[key] = { ...row }
} else {
const existing = idToRowMap[key]
existing.wjmc = mergeCommaSeparated(existing.wjmc, row.wjmc)
existing.wjlj = mergeCommaSeparated(existing.wjlj, row.wjlj)
// 其它字段保持首次出现的值,或可在此按需处理
}
})
return Object.values(idToRowMap)
},
// 取消按钮
cancel() {
this.open = false
......@@ -239,6 +239,7 @@ export default {
ext2: null,
ext3: null
}
this.fileList = []
this.resetForm("form")
},
/** 搜索按钮操作 */
......@@ -260,6 +261,10 @@ export default {
/** 新增按钮操作 */
handleAdd() {
this.reset()
// 设置xmid参数
if (this.queryParams.xmid) {
this.form.xmid = this.queryParams.xmid
}
this.open = true
this.title = "添加成果管理"
},
......@@ -269,6 +274,19 @@ export default {
const id = row.id || this.ids
getWjscqk(id).then(response => {
this.form = response.data
// 确保xmid参数被保持
if (this.queryParams.xmid) {
this.form.xmid = this.queryParams.xmid
}
// 如果有文件信息,加载到文件列表中(单文件)
if (this.form.wjmc && this.form.wjlj) {
this.fileList = [{
name: this.form.wjmc,
url: this.form.wjlj,
status: 'success',
uid: Date.now()
}]
}
this.open = true
this.title = "修改成果管理"
})
......@@ -308,6 +326,75 @@ export default {
this.download('yscgWjscqk/wjscqk/export', {
...this.queryParams
}, `wjscqk_${new Date().getTime()}.xlsx`)
},
/** 获取资料类型选项 */
getZllxOptions() {
selectZllx().then(response => {
this.zllxOptions = response.rows || []
}).catch(() => {
this.zllxOptions = []
})
},
/** 上传前校验 */
handleBeforeUpload(file) {
// 校验文件类型
const allowedTypes = ['doc', 'docx', 'pdf', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'jpg', 'jpeg', 'png', 'gif']
const fileExt = file.name.split('.').pop().toLowerCase()
if (!allowedTypes.includes(fileExt)) {
this.$modal.msgError('文件格式不正确,请上传支持的文件格式!')
return false
}
// 校验文件大小
const isLt50M = file.size / 1024 / 1024 < 50
if (!isLt50M) {
this.$modal.msgError('上传文件大小不能超过 50MB!')
return false
}
return true
},
/** 文件状态改变 */
handleFileChange(file, fileList) {
// 如果文件列表超过1个,只保留最新的文件
if (fileList.length > 1) {
this.fileList = [fileList[fileList.length - 1]]
} else {
this.fileList = fileList
}
this.updateFormFiles()
},
/** 上传成功 */
handleUploadSuccess(response, file, fileList) {
this.fileList = fileList
this.updateFormFiles()
this.$modal.msgSuccess('文件上传成功')
},
/** 上传失败 */
handleUploadError(err, file, fileList) {
this.$modal.msgError('文件上传失败')
},
/** 删除文件 */
handleRemove(file, fileList) {
this.fileList = fileList
this.updateFormFiles()
},
/** 文件数量超出限制 */
handleExceed(files, fileList) {
// 由于已实现自动替换,此方法不再需要显示错误提示
// 新文件会自动替换旧文件
},
/** 更新表单文件信息 */
updateFormFiles() {
if (this.fileList && this.fileList.length > 0) {
// 单文件上传,直接取第一个文件
const file = this.fileList[0]
this.form.wjmc = file.name
this.form.wjlj = file.response ? file.response.url : file.url
} else {
this.form.wjmc = null
this.form.wjlj = null
}
}
}
}
......
<!-- 成果管理 -->
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="项目名称" prop="xmmc">
<el-input v-model="queryParams.xmmc" placeholder="请输入项目名称" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="区块" prop="qk">
<el-input v-model="queryParams.qk" 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-form-item>
</el-form>
<el-table border v-loading="loading" :data="yscgList" :span-method="spanMethod">
<el-table-column label="项目名称" align="center" prop="xmmc" min-width="150" show-overflow-tooltip />
<el-table-column label="区块" align="center" prop="qk" min-width="120" show-overflow-tooltip />
<el-table-column label="资料类型" align="center" prop="zllxmc" min-width="120" show-overflow-tooltip />
<el-table-column label="上传情况" align="center" min-width="120">
<template slot-scope="scope">
<i :class="scope.row.zlsl > 0 ? 'el-icon-check' : 'el-icon-close'"
:style="{ color: scope.row.zlsl > 0 ? '#67C23A' : '#F56C6C', fontSize: '18px' }">
</i>
</template>
</el-table-column>
<el-table-column label="成果" align="center" prop="id" min-width="100" show-overflow-tooltip>
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)">查看成果</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
@pagination="getList" />
</div>
</template>
<script>
import { listYscgList } from "@/api/yscgWjscqk/wjscqk"
export default {
name: "Yscg",
data() {
return {
// 遮罩层
loading: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 成果管理表格数据
yscgList: [],
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
xmmc: null,
qk: null
}
}
},
created() {
this.getList()
},
methods: {
/** 查询成果管理列表 */
getList() {
this.loading = true
listYscgList(this.queryParams).then(response => {
this.yscgList = response.rows
this.total = response.total
this.loading = false
})
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm")
this.handleQuery()
},
/** 成果按钮操作 */
handleView(row) {
// 跳转到成果详情页面,传id
this.$router.push({
path: '/yscgWjscqk/wjscqk/index',
query: {
xmid: row.id
}
})
},
/** 表格行合并方法 */
spanMethod({ row, column, rowIndex, columnIndex }) {
// 项目名称列(第0列)、区块列(第1列)和成果列(第3列)需要合并
if (columnIndex === 0 || columnIndex === 1 || columnIndex === 4) {
let fieldName, currentValue
if (columnIndex === 0) {
fieldName = 'xmmc'
currentValue = row[fieldName]
} else if (columnIndex === 1) {
fieldName = 'qk'
currentValue = row[fieldName]
} else if (columnIndex === 4) {
fieldName = 'id'
currentValue = row[fieldName]
}
// 如果当前值为空,不合并
if (!currentValue) {
return [1, 1]
}
// 如果当前值与上一行相同,则隐藏当前行
if (rowIndex > 0 && this.yscgList[rowIndex - 1][fieldName] === currentValue) {
return [0, 0]
}
// 计算需要合并的行数
let rowspan = 1
for (let i = rowIndex + 1; i < this.yscgList.length; i++) {
if (this.yscgList[i][fieldName] === currentValue) {
rowspan++
} else {
break
}
}
return [rowspan, 1]
}
// 其他列不合并
return [1, 1]
}
}
}
</script>
<template>
<div class="ysgc-container" :class="{ compact }">
<!-- <el-form :inline="true" size="small" class="toolbar" label-width="80px">
<el-form-item label="项目名称">
<el-input v-model="filters.projectName" placeholder="请输入" class="input" />
</el-form-item>
<el-form-item label="区块">
<el-input v-model="filters.block" placeholder="请输入" class="input" />
</el-form-item>
</el-form> -->
<div ref="chartRef" class="chart"
:style="compact ? { width: (width || 260) + 'px', height: (height || 160) + 'px' } : {}"></div>
</div>
<div class="ysgc-container" :class="{ compact }">
<!-- <el-form :inline="true" size="small" class="toolbar" label-width="80px">
<el-form-item label="项目名称">
<el-input v-model="filters.projectName" placeholder="请输入" class="input" />
</el-form-item>
<el-form-item label="区块">
<el-input v-model="filters.block" placeholder="请输入" class="input" />
</el-form-item>
</el-form> -->
<div ref="chartRef" class="chart"
:style="compact ? { width: (width || 260) + 'px', height: (height || 160) + 'px' } : {}"></div>
</div>
</template>
......@@ -19,356 +19,388 @@ import * as echarts from 'echarts'
import { toDht } from '@/api/ysqqXmxx/ysqqXmxx'
export default {
name: 'YsgcIndex',
props: {
compact: { type: Boolean, default: false },
width: { type: Number, default: 0 },
height: { type: Number, default: 0 },
idOverride: { type: [String, Number], default: null }
},
data() {
return {
chart: null,
resizeObserver: null,
loadingDht: false,
dhtResult: null,
segyLines: [],
filters: {
projectName: '',
block: ''
},
// 示例数据,后续可由接口/路由参数加载
xMin: null,
xMax: null,
yMin: null,
yMax: null,
verticalLineX: null,
points: []
}
},
mounted() {
// 仅使用延迟初始化,确保容器有尺寸后再 init
this.deferInit()
window.addEventListener('resize', this.resizeChart)
// 若路由中带有 id,则尝试调用接口
this.loadDht()
// 兜底:在下一帧强制渲染一次(compact 时保证首屏显示)
this.$nextTick(() => setTimeout(() => {
if (this.chart) {
this.renderChart()
this.chart.resize()
}
}, 50))
// 对于缩略图模式,确保即使在隐藏状态下也能渲染
name: 'YsgcIndex',
props: {
compact: { type: Boolean, default: false },
width: { type: Number, default: 0 },
height: { type: Number, default: 0 },
idOverride: { type: [String, Number], default: null }
},
data () {
return {
chart: null,
resizeObserver: null,
loadingDht: false,
dhtResult: null,
segyLines: [],
filters: {
projectName: '',
block: ''
},
// 示例数据,后续可由接口/路由参数加载
xMin: null,
xMax: null,
yMin: null,
yMax: null,
verticalLineX: null,
points: []
}
},
mounted () {
// 仅使用延迟初始化,确保容器有尺寸后再 init
this.deferInit()
window.addEventListener('resize', this.resizeChart)
// 若路由中带有 id,则尝试调用接口
this.loadDht()
// 兜底:在下一帧强制渲染一次(compact 时保证首屏显示)
this.$nextTick(() => setTimeout(() => {
if (this.chart) {
this.renderChart()
this.chart.resize()
}
}, 50))
// 对于缩略图模式,确保即使在隐藏状态下也能渲染
if (this.compact) {
// 强制初始化并渲染
this.$nextTick(() => {
this.initChart()
this.renderChart()
})
}
},
created () {
// 尽早触发接口调用,进入页面即发起请求
this.loadDht()
},
activated () {
// 当页面被 keep-alive 缓存后再次进入时,确保尺寸和图表刷新
this.deferInit()
this.resizeChart()
},
beforeDestroy () {
window.removeEventListener('resize', this.resizeChart)
if (this.chart) {
this.chart.dispose()
this.chart = null
}
if (this.resizeObserver) {
try { this.resizeObserver.disconnect() } catch (e) { }
this.resizeObserver = null
}
},
methods: {
// 等待容器有有效尺寸再初始化,避免 echarts 容器宽高为 0 报错
deferInit () {
const tryInit = () => {
const el = this.$refs.chartRef
if (!el) return
// 缩略图模式:尺寸已知,直接初始化
if (this.compact) {
// 强制初始化并渲染
this.$nextTick(() => {
this.initChart()
this.renderChart()
})
this.initChart()
return
}
let w = el.clientWidth
let h = el.clientHeight
console.log('[ysgc] before size', w, h)
if (w === 0) {
el.style.width = '800px'
w = el.clientWidth
}
if (h === 0) {
// 若元素当前不可见(display: none)或父容器高度为 0,clientHeight 依然为 0
// 使用 ResizeObserver 监听直到有高度后再初始化
if (!this.resizeObserver) {
this.resizeObserver = new ResizeObserver(entries => {
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)
if (this.resizeObserver) {
try { this.resizeObserver.disconnect() } catch (e) { }
this.resizeObserver = null
}
this.initChart()
}
}
})
this.resizeObserver.observe(el)
}
}
console.log('[ysgc] after size', w, h)
// 即使高度为 0,也先用回退尺寸初始化,待可见后再 resize
this.initChart()
// 兜底:稍后再尝试一次 resize
setTimeout(() => this.resizeChart(), 150)
}
this.$nextTick(tryInit)
},
created() {
// 尽早触发接口调用,进入页面即发起请求
this.loadDht()
resizeChart () {
if (this.chart) this.chart.resize()
},
activated() {
// 当页面被 keep-alive 缓存后再次进入时,确保尺寸和图表刷新
this.deferInit()
this.resizeChart()
initChart () {
const el = this.$refs.chartRef
if (!el) return
if (this.chart) {
this.chart.dispose()
this.chart = null
}
// 当容器高度为 0 时,先用显式宽高初始化,等可见后再 resize
const initWidth = this.compact ? (this.width || 260) : (el.clientWidth || 800)
const initHeight = this.compact ? (this.height || 160) : (el.clientHeight || 600)
this.chart = echarts.init(el, null, { width: initWidth, height: initHeight })
// 初始渲染
this.renderChart()
this.resizeChart()
this.bindChartEvents()
// 对于缩略图模式,确保渲染成功
if (this.compact) {
console.log('[ysgc] 缩略图初始化完成', initWidth, initHeight)
}
},
beforeDestroy() {
window.removeEventListener('resize', this.resizeChart)
if (this.chart) {
this.chart.dispose()
this.chart = null
// 数据变化后自动刷新(确保缩略图及时绘制)
_requestRender () {
if (!this.chart) return
this.$nextTick(() => {
this.renderChart()
this.chart.resize()
// 确保缩略图正确显示
if (this.compact) {
console.log('[ysgc] 缩略图渲染完成')
}
if (this.resizeObserver) {
try { this.resizeObserver.disconnect() } catch (e) { }
this.resizeObserver = null
})
},
// 调用地层/断层图接口(示例),优先从路由参数获取 id
loadDht () {
if (this.loadingDht || this.dhtResult) return
try {
const route = this.$route || {}
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
this.loadingDht = true
toDht(id)
.then(res => {
// 根据后端返回结构做适配:优先取 data 字段
this.dhtResult = (res && (res.data !== undefined ? res.data : res)) || null
this.applyDhtResult()
if (this.chart) this._requestRender()
})
.catch(err => {
// 保留错误日志,避免打断现有流程
console.error('[ysgc] toDht error:', err)
})
.finally(() => {
this.loadingDht = false
})
} catch (e) {
console.error('[ysgc] loadDht failed:', e)
}
},
// 将接口返回的数据应用到图表状态
applyDhtResult () {
const data = this.dhtResult || {}
// 边界
this.xMin = data.xmin != null ? Number(data.xmin) : this.xMin
this.xMax = data.xmax != null ? Number(data.xmax) : this.xMax
this.yMin = data.ymin != null ? Number(data.ymin) : this.yMin
this.yMax = data.ymax != null ? Number(data.ymax) : this.yMax
// 井点
const list = Array.isArray(data.ysqqXmxxJxxList) ? data.ysqqXmxxJxxList : []
if (list.length) {
this.points = list
.filter(it => it && it.x != null && it.y != null)
.map(it => ({ name: String(it.jh || ''), x: Number(it.x), y: Number(it.y) }))
}
// 线(SEGY)
const segys = Array.isArray(data.ysqqXmxxSegy) ? data.ysqqXmxxSegy : []
console.log(segys, 'segys');
const lines = segys.map((seg, idx) => {
try {
const arr = typeof seg.x === 'string' ? JSON.parse(seg.x) : (Array.isArray(seg.x) ? seg.x : [])
// 若存在有效的 sxh,则按 sxh 升序连线;否则按原始顺序
const enriched = arr
.filter(p => p && p.x != null && p.y != null)
.map(p => ({ sxh: Number(p.sxh), x: Number(p.x), y: Number(p.y) }))
.filter(p => Number.isFinite(p.x) && Number.isFinite(p.y))
const hasSxh = enriched.every(p => Number.isFinite(p.sxh))
const ordered = hasSxh ? enriched.sort((a, b) => a.sxh - b.sxh) : enriched
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 })
} catch (e) { }
return pairs
} catch (e) {
console.error('[ysgc] segy parse error', { index: idx, id: seg && seg.id, err: e })
return []
}
}).filter(line => line.length > 0)
this.segyLines = lines
try {
console.log('[ysgc] segyLines summary', { lines: this.segyLines.length, lengths: this.segyLines.map(l => l.length) })
} catch (e) { }
},
methods: {
// 等待容器有有效尺寸再初始化,避免 echarts 容器宽高为 0 报错
deferInit() {
const tryInit = () => {
const el = this.$refs.chartRef
if (!el) return
// 缩略图模式:尺寸已知,直接初始化
if (this.compact) {
this.initChart()
return
}
let w = el.clientWidth
let h = el.clientHeight
console.log('[ysgc] before size', w, h)
if (w === 0) {
el.style.width = '800px'
w = el.clientWidth
}
if (h === 0) {
// 若元素当前不可见(display: none)或父容器高度为 0,clientHeight 依然为 0
// 使用 ResizeObserver 监听直到有高度后再初始化
if (!this.resizeObserver) {
this.resizeObserver = new ResizeObserver(entries => {
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)
if (this.resizeObserver) {
try { this.resizeObserver.disconnect() } catch (e) { }
this.resizeObserver = null
}
this.initChart()
}
}
})
this.resizeObserver.observe(el)
}
}
console.log('[ysgc] after size', w, h)
// 即使高度为 0,也先用回退尺寸初始化,待可见后再 resize
this.initChart()
// 兜底:稍后再尝试一次 resize
setTimeout(() => this.resizeChart(), 150)
}
this.$nextTick(tryInit)
},
resizeChart() {
if (this.chart) this.chart.resize()
},
initChart() {
const el = this.$refs.chartRef
if (!el) return
if (this.chart) {
this.chart.dispose()
this.chart = null
}
// 当容器高度为 0 时,先用显式宽高初始化,等可见后再 resize
const initWidth = this.compact ? (this.width || 260) : (el.clientWidth || 800)
const initHeight = this.compact ? (this.height || 160) : (el.clientHeight || 600)
this.chart = echarts.init(el, null, { width: initWidth, height: initHeight })
// 生成图表配置
buildOption () {
// 若未提供边界,则根据数据自动估算
const allXs = []
const allYs = []
for (const p of this.points) { allXs.push(p.x); allYs.push(p.y) }
for (const line of this.segyLines) {
for (const [lx, ly] of line) { allXs.push(lx); allYs.push(ly) }
}
if (allXs.length && (this.xMin == null || this.xMax == null)) {
const padX = Math.max(1, Math.round((Math.max(...allXs) - Math.min(...allXs)) * 0.02))
this.xMin = this.xMin != null ? this.xMin : Math.min(...allXs) - padX
this.xMax = this.xMax != null ? this.xMax : Math.max(...allXs) + padX
}
if (allYs.length && (this.yMin == null || this.yMax == null)) {
const padY = Math.max(1, Math.round((Math.max(...allYs) - Math.min(...allYs)) * 0.02))
this.yMin = this.yMin != null ? this.yMin : Math.min(...allYs) - padY
this.yMax = this.yMax != null ? this.yMax : Math.max(...allYs) + padY
}
// 初始渲染
this.renderChart()
this.resizeChart()
// 对于缩略图模式,确保渲染成功
if (this.compact) {
console.log('[ysgc] 缩略图初始化完成', initWidth, initHeight)
}
},
// 数据变化后自动刷新(确保缩略图及时绘制)
_requestRender() {
if (!this.chart) return
this.$nextTick(() => {
this.renderChart()
this.chart.resize()
// 确保缩略图正确显示
if (this.compact) {
console.log('[ysgc] 缩略图渲染完成')
}
})
},
// 调用地层/断层图接口(示例),优先从路由参数获取 id
loadDht() {
if (this.loadingDht || this.dhtResult) return
try {
const route = this.$route || {}
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
this.loadingDht = true
toDht(id)
.then(res => {
// 根据后端返回结构做适配:优先取 data 字段
this.dhtResult = (res && (res.data !== undefined ? res.data : res)) || null
this.applyDhtResult()
if (this.chart) this._requestRender()
})
.catch(err => {
// 保留错误日志,避免打断现有流程
console.error('[ysgc] toDht error:', err)
})
.finally(() => {
this.loadingDht = false
})
} catch (e) {
console.error('[ysgc] loadDht failed:', e)
}
},
// 将接口返回的数据应用到图表状态
applyDhtResult() {
const data = this.dhtResult || {}
// 边界
this.xMin = data.xmin != null ? Number(data.xmin) : this.xMin
this.xMax = data.xmax != null ? Number(data.xmax) : this.xMax
this.yMin = data.ymin != null ? Number(data.ymin) : this.yMin
this.yMax = data.ymax != null ? Number(data.ymax) : this.yMax
// 井点
const list = Array.isArray(data.ysqqXmxxJxxList) ? data.ysqqXmxxJxxList : []
if (list.length) {
this.points = list
.filter(it => it && it.x != null && it.y != null)
.map(it => ({ name: String(it.jh || ''), x: Number(it.x), y: Number(it.y) }))
}
// 线(SEGY)
const segys = Array.isArray(data.ysqqXmxxSegy) ? data.ysqqXmxxSegy : []
console.log(segys, 'segys');
const lines = segys.map((seg, idx) => {
try {
const arr = typeof seg.x === 'string' ? JSON.parse(seg.x) : (Array.isArray(seg.x) ? seg.x : [])
// 若存在有效的 sxh,则按 sxh 升序连线;否则按原始顺序
const enriched = arr
.filter(p => p && p.x != null && p.y != null)
.map(p => ({ sxh: Number(p.sxh), x: Number(p.x), y: Number(p.y) }))
.filter(p => Number.isFinite(p.x) && Number.isFinite(p.y))
const hasSxh = enriched.every(p => Number.isFinite(p.sxh))
const ordered = hasSxh ? enriched.sort((a, b) => a.sxh - b.sxh) : enriched
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 })
} catch (e) { }
return pairs
} catch (e) {
console.error('[ysgc] segy parse error', { index: idx, id: seg && seg.id, err: e })
return []
}
}).filter(line => line.length > 0)
const series = []
// 井点散点图
if (this.points && this.points.length) {
series.push({
type: 'scatter',
symbolSize: this.compact ? 8 : 12,
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) {
series.push({
type: 'line',
coordinateSystem: 'cartesian2d',
data: line,
showSymbol: false,
connectNulls: true,
smooth: false,
emphasis: { scale: false },
encode: { x: 0, y: 1 },
lineStyle: { color: '#00aa66', width: 2 },
tooltip: { show: false }
})
// 调试:在每条线之上叠加小点,便于核对路径
series.push({
type: 'scatter',
data: line,
symbolSize: 3,
itemStyle: { color: '#888' },
tooltip: { show: false }
})
}
// 垂直参考线(可选)
if (this.verticalLineX != null && this.yMin != null && this.yMax != null) {
series.push({
type: 'line',
data: [[this.verticalLineX, this.yMin], [this.verticalLineX, this.yMax]],
showSymbol: false,
lineStyle: { color: '#222', width: 2 },
tooltip: { show: false }
})
}
this.segyLines = lines
try {
console.log('[ysgc] segyLines summary', { lines: this.segyLines.length, lengths: this.segyLines.map(l => l.length) })
} catch (e) { }
},
// 生成图表配置
buildOption() {
// 若未提供边界,则根据数据自动估算
const allXs = []
const allYs = []
for (const p of this.points) { allXs.push(p.x); allYs.push(p.y) }
for (const line of this.segyLines) {
for (const [lx, ly] of line) { allXs.push(lx); allYs.push(ly) }
}
if (allXs.length && (this.xMin == null || this.xMax == null)) {
const padX = Math.max(1, Math.round((Math.max(...allXs) - Math.min(...allXs)) * 0.02))
this.xMin = this.xMin != null ? this.xMin : Math.min(...allXs) - padX
this.xMax = this.xMax != null ? this.xMax : Math.max(...allXs) + padX
}
if (allYs.length && (this.yMin == null || this.yMax == null)) {
const padY = Math.max(1, Math.round((Math.max(...allYs) - Math.min(...allYs)) * 0.02))
this.yMin = this.yMin != null ? this.yMin : Math.min(...allYs) - padY
this.yMax = this.yMax != null ? this.yMax : Math.max(...allYs) + padY
}
return {
backgroundColor: this.compact ? '#f2f2f2' : '#ffffff',
grid: this.compact ? { top: 6, left: 6, right: 6, bottom: 6, containLabel: false } : { top: 40, left: 50, right: 40, bottom: 50, containLabel: true },
xAxis: this.compact ? { type: 'value', min: this.xMin, max: this.xMax, scale: true, axisLine: { show: false }, axisTick: { show: false }, axisLabel: { show: false }, splitLine: { show: false } } : { type: 'value', min: this.xMin, max: this.xMax, scale: false, axisLine: { show: true }, axisTick: { show: true }, axisLabel: { color: '#666' }, splitLine: { show: true, lineStyle: { color: '#9bb3e7', opacity: 0.6 } } },
yAxis: this.compact ? { type: 'value', min: this.yMin, max: this.yMax, scale: true, axisLine: { show: false }, axisTick: { show: false }, axisLabel: { show: false }, splitLine: { show: false } } : { type: 'value', min: this.yMin, max: this.yMax, scale: false, axisLine: { show: true }, axisTick: { show: true }, axisLabel: { color: '#666' }, splitLine: { show: true, lineStyle: { color: '#9bb3e7', opacity: 0.6 } } },
series,
tooltip: { trigger: 'item' }
}
},
// 渲染或更新图表
renderChart () {
if (!this.chart) return
const option = this.buildOption()
this.chart.setOption(option, true)
// 确保事件绑定有效
this.bindChartEvents()
},
// 绑定点击/双击事件,把被点击的绿色线条映射为 segy 下标并上报
bindChartEvents () {
if (!this.chart) return
// 解除旧绑定避免重复
this.chart.off('click')
this.chart.off('dblclick')
const series = []
// 井点散点图
if (this.points && this.points.length) {
series.push({
type: 'scatter',
symbolSize: this.compact ? 8 : 12,
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) {
series.push({
type: 'line',
coordinateSystem: 'cartesian2d',
data: line,
showSymbol: false,
connectNulls: true,
smooth: false,
emphasis: { scale: false },
encode: { x: 0, y: 1 },
lineStyle: { color: '#00aa66', width: 2 },
tooltip: { show: false }
})
// 调试:在每条线之上叠加小点,便于核对路径
series.push({
type: 'scatter',
data: line,
symbolSize: 3,
itemStyle: { color: '#888' },
tooltip: { show: false }
})
}
// 垂直参考线(可选)
if (this.verticalLineX != null && this.yMin != null && this.yMax != null) {
series.push({
type: 'line',
data: [[this.verticalLineX, this.yMin], [this.verticalLineX, this.yMax]],
showSymbol: false,
lineStyle: { color: '#222', width: 2 },
tooltip: { show: false }
})
}
const getLineIndexBySeries = (seriesIndex) => {
// 如果有井点散点,最前面占 1 个 series
const hasPoints = Array.isArray(this.points) && this.points.length > 0
const base = hasPoints ? 1 : 0
// 每条线我们 push 了两个 series:line 和一个灰色散点
if (seriesIndex < base) return -1
const offset = seriesIndex - base
// 偶数 -> 折线;奇数 -> 叠加的灰色散点
return Math.floor(offset / 2)
}
return {
backgroundColor: this.compact ? '#f2f2f2' : '#ffffff',
grid: this.compact ? { top: 6, left: 6, right: 6, bottom: 6, containLabel: false } : { top: 40, left: 50, right: 40, bottom: 50, containLabel: true },
xAxis: this.compact ? { type: 'value', min: this.xMin, max: this.xMax, scale: true, axisLine: { show: false }, axisTick: { show: false }, axisLabel: { show: false }, splitLine: { show: false } } : { type: 'value', min: this.xMin, max: this.xMax, scale: false, axisLine: { show: true }, axisTick: { show: true }, axisLabel: { color: '#666' }, splitLine: { show: true, lineStyle: { color: '#9bb3e7', opacity: 0.6 } } },
yAxis: this.compact ? { type: 'value', min: this.yMin, max: this.yMax, scale: true, axisLine: { show: false }, axisTick: { show: false }, axisLabel: { show: false }, splitLine: { show: false } } : { type: 'value', min: this.yMin, max: this.yMax, scale: false, axisLine: { show: true }, axisTick: { show: true }, axisLabel: { color: '#666' }, splitLine: { show: true, lineStyle: { color: '#9bb3e7', opacity: 0.6 } } },
series,
tooltip: { trigger: 'item' }
}
},
// 渲染或更新图表
renderChart() {
if (!this.chart) return
const option = this.buildOption()
this.chart.setOption(option, true)
const emitPick = (params) => {
if (!params || (params.seriesType !== 'line' && params.seriesType !== 'scatter')) return
const idx = getLineIndexBySeries(params.seriesIndex)
if (idx >= 0 && idx < this.segyLines.length) {
this.$emit('segyLinePick', { index: idx })
}
}
this.chart.on('click', emitPick)
this.chart.on('dblclick', emitPick)
}
}
}
</script>
<style lang="scss" scoped>
.ysgc-container {
display: flex;
flex-direction: column;
height: 88vh;
overflow: hidden;
/* 避免出现滚动条 */
display: flex;
flex-direction: column;
height: 88vh;
overflow: hidden;
/* 避免出现滚动条 */
}
.toolbar {
display: flex;
align-items: center;
gap: 80px;
padding: 10px 0 8px 10px;
display: flex;
align-items: center;
gap: 80px;
padding: 10px 0 8px 10px;
}
.field {
display: flex;
align-items: center;
display: flex;
align-items: center;
}
.label {
font-size: 20px;
font-weight: 600;
margin-right: 16px;
font-size: 20px;
font-weight: 600;
margin-right: 16px;
}
.input {
width: 180px;
width: 180px;
}
.chart {
flex: 1;
min-height: 0;
/* 让 flex 子项在容器内收缩,避免溢出 */
width: 100%;
margin: 0;
/* 取消外边距,防止产生滚动条 */
background: #fff;
border: 1px solid #e0e0e0;
flex: 1;
min-height: 0;
/* 让 flex 子项在容器内收缩,避免溢出 */
width: 100%;
margin: 0;
/* 取消外边距,防止产生滚动条 */
background: #fff;
border: 1px solid #e0e0e0;
}
</style>
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
<template>
<template>
<div class="ysgc-container"
:class="{ compact }">
<!-- <el-form :inline="true" size="small" class="toolbar" label-width="80px">
<el-form-item label="项目名称">
<el-input v-model="filters.projectName" placeholder="请输入" class="input" />
</el-form-item>
<el-form-item label="区块">
<el-input v-model="filters.block" placeholder="请输入" class="input" />
</el-form-item>
</el-form> -->
<div ref="chartRef"
class="chart"
:style="compact ? { width: (width || 260) + 'px', height: (height || 160) + 'px' } : {}"></div>
</div>
</template>
<script>
import * as echarts from 'echarts'
import { toDht } from '@/api/ysqqXmxx/ysqqXmxx'
export default {
name: 'YsgcIndex',
props: {
compact: { type: Boolean, default: false },
width: { type: Number, default: 0 },
height: { type: Number, default: 0 },
idOverride: { type: [String, Number], default: null }
},
data () {
return {
chart: null,
resizeObserver: null,
loadingDht: false,
dhtResult: null,
segyLines: [],
filters: {
projectName: '',
block: ''
},
// 示例数据,后续可由接口/路由参数加载
xMin: null,
xMax: null,
yMin: null,
yMax: null,
verticalLineX: null,
points: []
}
},
mounted () {
// 仅使用延迟初始化,确保容器有尺寸后再 init
this.deferInit()
window.addEventListener('resize', this.resizeChart)
// 若路由中带有 id,则尝试调用接口
this.loadDht()
// 兜底:在下一帧强制渲染一次(compact 时保证首屏显示)
this.$nextTick(() => setTimeout(() => {
if (this.chart) {
this.renderChart()
this.chart.resize()
}
}, 50))
// 对于缩略图模式,确保即使在隐藏状态下也能渲染
if (this.compact) {
// 强制初始化并渲染
this.$nextTick(() => {
this.initChart()
this.renderChart()
})
}
},
created () {
// 尽早触发接口调用,进入页面即发起请求
this.loadDht()
},
activated () {
// 当页面被 keep-alive 缓存后再次进入时,确保尺寸和图表刷新
this.deferInit()
this.resizeChart()
},
beforeDestroy () {
window.removeEventListener('resize', this.resizeChart)
if (this.chart) {
this.chart.dispose()
this.chart = null
}
if (this.resizeObserver) {
try { this.resizeObserver.disconnect() } catch (e) { }
this.resizeObserver = null
}
},
methods: {
// 等待容器有有效尺寸再初始化,避免 echarts 容器宽高为 0 报错
deferInit () {
const tryInit = () => {
const el = this.$refs.chartRef
if (!el) return
// 缩略图模式:尺寸已知,直接初始化
if (this.compact) {
this.initChart()
return
}
let w = el.clientWidth
let h = el.clientHeight
console.log('[ysgc] before size', w, h)
if (w === 0) {
el.style.width = '800px'
w = el.clientWidth
}
if (h === 0) {
// 若元素当前不可见(display: none)或父容器高度为 0,clientHeight 依然为 0
// 使用 ResizeObserver 监听直到有高度后再初始化
if (!this.resizeObserver) {
this.resizeObserver = new ResizeObserver(entries => {
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)
if (this.resizeObserver) {
try { this.resizeObserver.disconnect() } catch (e) { }
this.resizeObserver = null
}
this.initChart()
}
}
})
this.resizeObserver.observe(el)
}
}
console.log('[ysgc] after size', w, h)
// 即使高度为 0,也先用回退尺寸初始化,待可见后再 resize
this.initChart()
// 兜底:稍后再尝试一次 resize
setTimeout(() => this.resizeChart(), 150)
}
this.$nextTick(tryInit)
},
resizeChart () {
if (this.chart) this.chart.resize()
},
initChart () {
const el = this.$refs.chartRef
if (!el) return
if (this.chart) {
this.chart.dispose()
this.chart = null
}
// 当容器高度为 0 时,先用显式宽高初始化,等可见后再 resize
const initWidth = this.compact ? (this.width || 260) : (el.clientWidth || 800)
const initHeight = this.compact ? (this.height || 160) : (el.clientHeight || 600)
this.chart = echarts.init(el, null, { width: initWidth, height: initHeight })
// 初始渲染
this.renderChart()
this.resizeChart()
// 对于缩略图模式,确保渲染成功
if (this.compact) {
console.log('[ysgc] 缩略图初始化完成', initWidth, initHeight)
}
},
// 数据变化后自动刷新(确保缩略图及时绘制)
_requestRender () {
if (!this.chart) return
this.$nextTick(() => {
this.renderChart()
this.chart.resize()
// 确保缩略图正确显示
if (this.compact) {
console.log('[ysgc] 缩略图渲染完成')
}
})
},
// 调用地层/断层图接口(示例),优先从路由参数获取 id
loadDht () {
if (this.loadingDht || this.dhtResult) return
try {
const route = this.$route || {}
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
this.loadingDht = true
toDht(id)
.then(res => {
// 根据后端返回结构做适配:优先取 data 字段
this.dhtResult = (res && (res.data !== undefined ? res.data : res)) || null
this.applyDhtResult()
if (this.chart) this._requestRender()
})
.catch(err => {
// 保留错误日志,避免打断现有流程
console.error('[ysgc] toDht error:', err)
})
.finally(() => {
this.loadingDht = false
})
} catch (e) {
console.error('[ysgc] loadDht failed:', e)
}
},
// 将接口返回的数据应用到图表状态
applyDhtResult () {
const data = this.dhtResult || {}
// 边界
this.xMin = data.xmin != null ? Number(data.xmin) : this.xMin
this.xMax = data.xmax != null ? Number(data.xmax) : this.xMax
this.yMin = data.ymin != null ? Number(data.ymin) : this.yMin
this.yMax = data.ymax != null ? Number(data.ymax) : this.yMax
// 井点
const list = Array.isArray(data.ysqqXmxxJxxList) ? data.ysqqXmxxJxxList : []
if (list.length) {
this.points = list
.filter(it => it && it.x != null && it.y != null)
.map(it => ({ name: String(it.jh || ''), x: Number(it.x), y: Number(it.y) }))
}
// 线(SEGY)
const segys = Array.isArray(data.ysqqXmxxSegy) ? data.ysqqXmxxSegy : []
console.log(segys, 'segys');
const lines = segys.map((seg, idx) => {
try {
const arr = typeof seg.x === 'string' ? JSON.parse(seg.x) : (Array.isArray(seg.x) ? seg.x : [])
// 若存在有效的 sxh,则按 sxh 升序连线;否则按原始顺序
const enriched = arr
.filter(p => p && p.x != null && p.y != null)
.map(p => ({ sxh: Number(p.sxh), x: Number(p.x), y: Number(p.y) }))
.filter(p => Number.isFinite(p.x) && Number.isFinite(p.y))
const hasSxh = enriched.every(p => Number.isFinite(p.sxh))
const ordered = hasSxh ? enriched.sort((a, b) => a.sxh - b.sxh) : enriched
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 })
} catch (e) { }
return pairs
} catch (e) {
console.error('[ysgc] segy parse error', { index: idx, id: seg && seg.id, err: e })
return []
}
}).filter(line => line.length > 0)
this.segyLines = lines
try {
console.log('[ysgc] segyLines summary', { lines: this.segyLines.length, lengths: this.segyLines.map(l => l.length) })
} catch (e) { }
},
// 生成图表配置
buildOption () {
// 若未提供边界,则根据数据自动估算
const allXs = []
const allYs = []
for (const p of this.points) { allXs.push(p.x); allYs.push(p.y) }
for (const line of this.segyLines) {
for (const [lx, ly] of line) { allXs.push(lx); allYs.push(ly) }
}
if (allXs.length && (this.xMin == null || this.xMax == null)) {
const padX = Math.max(1, Math.round((Math.max(...allXs) - Math.min(...allXs)) * 0.02))
this.xMin = this.xMin != null ? this.xMin : Math.min(...allXs) - padX
this.xMax = this.xMax != null ? this.xMax : Math.max(...allXs) + padX
}
if (allYs.length && (this.yMin == null || this.yMax == null)) {
const padY = Math.max(1, Math.round((Math.max(...allYs) - Math.min(...allYs)) * 0.02))
this.yMin = this.yMin != null ? this.yMin : Math.min(...allYs) - padY
this.yMax = this.yMax != null ? this.yMax : Math.max(...allYs) + padY
}
const series = []
// 井点散点图
if (this.points && this.points.length) {
series.push({
type: 'scatter',
symbolSize: this.compact ? 8 : 12,
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) {
series.push({
type: 'line',
coordinateSystem: 'cartesian2d',
data: line,
showSymbol: false,
connectNulls: true,
smooth: false,
emphasis: { scale: false },
encode: { x: 0, y: 1 },
lineStyle: { color: '#00aa66', width: 2 },
tooltip: { show: false }
})
// 调试:在每条线之上叠加小点,便于核对路径
series.push({
type: 'scatter',
data: line,
symbolSize: 3,
itemStyle: { color: '#888' },
tooltip: { show: false }
})
}
// 垂直参考线(可选)
if (this.verticalLineX != null && this.yMin != null && this.yMax != null) {
series.push({
type: 'line',
data: [[this.verticalLineX, this.yMin], [this.verticalLineX, this.yMax]],
showSymbol: false,
lineStyle: { color: '#222', width: 2 },
tooltip: { show: false }
})
}
return {
backgroundColor: this.compact ? '#f2f2f2' : '#ffffff',
grid: this.compact ? { top: 6, left: 6, right: 6, bottom: 6, containLabel: false } : { top: 40, left: 50, right: 40, bottom: 50, containLabel: true },
xAxis: this.compact ? { type: 'value', min: this.xMin, max: this.xMax, scale: true, axisLine: { show: false }, axisTick: { show: false }, axisLabel: { show: false }, splitLine: { show: false } } : { type: 'value', min: this.xMin, max: this.xMax, scale: false, axisLine: { show: true }, axisTick: { show: true }, axisLabel: { color: '#666' }, splitLine: { show: true, lineStyle: { color: '#9bb3e7', opacity: 0.6 } } },
yAxis: this.compact ? { type: 'value', min: this.yMin, max: this.yMax, scale: true, axisLine: { show: false }, axisTick: { show: false }, axisLabel: { show: false }, splitLine: { show: false } } : { type: 'value', min: this.yMin, max: this.yMax, scale: false, axisLine: { show: true }, axisTick: { show: true }, axisLabel: { color: '#666' }, splitLine: { show: true, lineStyle: { color: '#9bb3e7', opacity: 0.6 } } },
series,
tooltip: { trigger: 'item' }
}
},
// 渲染或更新图表
renderChart () {
if (!this.chart) return
const option = this.buildOption()
this.chart.setOption(option, true)
}
}
}
</script>
<style lang="scss" scoped>
.ysgc-container {
display: flex;
flex-direction: column;
height: 88vh;
overflow: hidden;
/* 避免出现滚动条 */
}
.toolbar {
display: flex;
align-items: center;
gap: 80px;
padding: 10px 0 8px 10px;
}
.field {
display: flex;
align-items: center;
}
.label {
font-size: 20px;
font-weight: 600;
margin-right: 16px;
}
.input {
width: 180px;
}
.chart {
flex: 1;
min-height: 0;
/* 让 flex 子项在容器内收缩,避免溢出 */
width: 100%;
margin: 0;
/* 取消外边距,防止产生滚动条 */
background: #fff;
border: 1px solid #e0e0e0;
}
</style>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment