Commit dfd5cc55 by jiang'yun

修改

parent fa53ed97
<template>
<div>
<el-tooltip content="设置" placement="bottom" effect="light">
<el-button @click="editProperties" :class="{ 'active': showLineStylePanel }">
<i class="el-icon-setting"></i>
</el-button>
</el-tooltip>
<el-tooltip content="上传文件" placement="bottom" effect="light">
<el-button @click="triggerFileUpload">
<i class="el-icon-upload"></i>
</el-button>
</el-tooltip>
<el-dialog title="设置" :visible.sync="showPropertiesDialog" width="100%">
<el-form ref="form" label-width="130px">
<el-row>
<el-col :span="5">
<el-form-item label="颜色集合">
<el-select v-model="colorMapSelect" placeholder="请选择颜色集合" @change="changeColor">
<el-option v-for="item in listNameColorMaps" :label="item" :value="item"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="归一化类型">
<el-select v-model="NormalizationType" @change="changeNor" placeholder="请选择归一化类型">
<el-option v-for="item in NormalizationTypeData" :label="item.label" :value="item.value"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="归一化比例">
<el-slider @change="changeNor" style="width: 180px;" v-model="NormalizationBl" :min="0.1" :max="5"
:step="0.1" show-stops>
</el-slider>
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="每英寸道数">
<el-input v-model="mycds" @blur="changeMycds"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="5">
<el-form-item label="每秒英寸数">
<el-input v-model="msycs" @blur="changeMycds"></el-input>
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="采样插值类型">
<el-select v-model="samplesType" @change="changeInt" placeholder="请选择采样插值类型">
<el-option v-for="item in InterpolationType" :label="item.label" :value="item.value"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="采样插值边缘">
<el-select v-model="samplesEdge" @change="changeInt" placeholder="请选择采样插值边缘">
<el-option v-for="item in InterpolationEdge" :label="item.label" :value="item.value"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="道插值类型">
<el-select v-model="tracesEdge" @change="changeInt" placeholder="请选择道插值类型">
<el-option v-for="item in InterpolationType" :label="item.label" :value="item.value"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="5">
<el-form-item label="道插值边缘">
<el-select v-model="tracesType" @change="changeInt" placeholder="请选择道插值边缘">
<el-option v-for="item in InterpolationEdge" :label="item.label" :value="item.value"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item label="绘图类型">
<el-checkbox v-model="Wiggle" @change="changePlotType">Wiggle</el-checkbox>
<el-checkbox v-model="Reversed" @change="changePlotType">Reversed</el-checkbox>
<el-checkbox v-model="PositiveFill" @change="changePlotType">Positive fill</el-checkbox>
<el-checkbox v-model="NegativeFill" @change="changePlotType">Negative fill</el-checkbox>
<el-checkbox v-model="PositiveColorFill" @change="changePlotType">Positive color fill</el-checkbox>
<el-checkbox v-model="NegativeColorFill" @change="changePlotType">Negative color fill</el-checkbox>
<el-checkbox v-model="SimpleDensity" @change="changePlotType">Simple density</el-checkbox>
<el-checkbox v-model="InterpolatedDensity" @change="changePlotType">Interpolated density</el-checkbox>
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="Wiggle-裁剪因子">
<el-input v-model="ClippingFactor" @blur="changePlotType"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="5">
<el-form-item label="Wiggle-抽取间距">
<el-input v-model="DecimationSpacing" @blur="changePlotType"></el-input>
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="Wiggle-密度抽取">
<el-checkbox v-model="densityDecimation" @change="changePlotType">Density decimation(密度抽取)</el-checkbox>
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="Clipping mode(裁剪模式)">
<el-select v-model="ClippingMode" @change="changePlotType" placeholder="请选择裁剪模式">
<el-option v-for="item in ClippingModeData" :label="item.label" :value="item.value"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="3">
<el-form-item label="TaperFilter(滤波)">
<el-checkbox v-model="TaperFilterEnbled" @change="changePlotType">启用滤波</el-checkbox>
</el-form-item>
</el-col>
<el-col :span="2">
<el-form-item label="f1">
<el-input v-model="f1" style="width: 80px" @blur="changePlotType"></el-input>
</el-form-item>
</el-col>
<el-col :span="2">
<el-form-item label="f2">
<el-input v-model="f2" style="width: 80px" @blur="changePlotType"></el-input>
</el-form-item>
</el-col>
<el-col :span="2">
<el-form-item label="f3">
<el-input v-model="f3" style="width: 80px" @blur="changePlotType"></el-input>
</el-form-item>
</el-col>
<el-col :span="2">
<el-form-item label="f4">
<el-input v-model="f4" style="width: 80px" @blur="changePlotType"></el-input>
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item label="采样率">
<el-input v-model="sampleRate" style="width: 80px" @blur="changePlotType"></el-input>
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item label="带通模式">
<el-checkbox v-model="passFlag" @change="changePlotType">带通模式</el-checkbox>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="3">
<el-form-item label="AGC">
<el-checkbox v-model="AGCEnbled" @change="changePlotType">启用AGC</el-checkbox>
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item label="AGC length">
<el-input v-model="AGCLength" style="width: 80px" @blur="changePlotType"></el-input>
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item label="Desired average">
<el-input v-model="DesiredAverage" style="width: 80px" @blur="changePlotType"></el-input>
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item label="Noise reduction">
<el-select v-model="NoiseReduction" @change="changePlotType" placeholder="请选择降噪">
<el-option v-for="item in NoiseReductionData" :label="item.label" :value="item.value"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item label="Noise reduction percentage">
<el-input v-model="NoiseReductionPercentage" style="width: 80px" @blur="changePlotType"></el-input>
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item label="Start sample">
<el-input v-model="StartSample" style="width: 80px" @blur="changePlotType"></el-input>
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item label="Step">
<el-input v-model="Step" style="width: 80px" @blur="changePlotType"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="4">
<el-form-item label="Units">
<el-select v-model="Units" @change="changePlotType" placeholder="请选择单位">
<el-option v-for="item in UnitsData" :label="item.label" :value="item.value"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item label="Window length">
<el-input v-model="WindowLength" style="width: 80px" @blur="changePlotType"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="3">
<el-form-item label="Reverse">
<el-checkbox v-model="ReverseEnbled" @change="changePlotType">启用Reverse</el-checkbox>
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item label="inverted">
<el-checkbox v-model="inverted" @change="changePlotType">启用inverted</el-checkbox>
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item label="reversed">
<el-checkbox v-model="reversed" @change="changePlotType">启用reversed</el-checkbox>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="showPropertiesDialog = false">取 消</el-button>
<el-button type="primary" @click="showPropertiesDialog = false">确 定</el-button>
</div>
</el-dialog>
<canvas ref="plot" id="canvas" />
<!-- <PropertiesDialog-->
<!-- :show-dialog="showPropertiesDialog"-->
<!-- :node-props="nodeProps"-->
<!-- :ajv="ajv"-->
<!-- :schema="schema"-->
<!-- @close="applyProps"-->
<!-- />-->
</div>
</template>
<script>
import { SeismicPlot } from './App.js';
import PropertiesDialog from './ui/PropertiesDialog.vue';
import { getAjv, setNodeProps, getNodeProps } from './ui/DialogPropertyUtils';
import { getToken } from "@/utils/auth";
import { SegyReader } from '@int/geotoolkit/seismic/data/SegyReader';
import { LocalFile } from '@int/geotoolkit/seismic/data/LocalFile';
import { Reverse } from '@int/geotoolkit/seismic/pipeline/processor/Reverse';
import { AGC } from '@int/geotoolkit/seismic/pipeline/processor/AGC';
import { TaperFilterProcess } from '@int/geotoolkit/seismic/analysis/filters/TaperFilterProcess';
import { NormalizationType } from '@int/geotoolkit/seismic/pipeline/NormalizationType';
import { SeismicPipeline } from '@int/geotoolkit/seismic/pipeline/SeismicPipeline';
import { SeismicColors } from '@int/geotoolkit/seismic/util/SeismicColors';
let seismicPlot = null;
export default {
name: "index",
components: {
PropertiesDialog
},
data() {
return {
showLineStylePanel: false,
showPropertiesDialog: false,
nodeProps: null,
ajv: null,
schema: null,
chartIsActive: false,
tableIsActive: false,
listNameColorMaps: [
"WhiteBlack",
"RedWhiteBlack",
"RedWhiteBlue",
"Saddleback",
"Angles5color",
"BlackRedYellowWhite",
"GreyOrange",
"IntervalVelocity",
"IntervalVelocity16",
"IntervalVelocity32",
"Rainbow",
"RedGreenBlue",
"RedWhiteBlueExtremes",
"RedWhiteBlueHot",
"RedYellowBlue",
"SaddlebackHot",
"Spectrum"
],
colorMapSelect: "",
_colorMap: "",
pipeline: null,
_seismicWidget: null,
NormalizationType: null,
NormalizationBl: 0.1,
NormalizationTypeData: [
{
"label": "None",
"value": 0,
},
{
"label": "Maximum",
"value": 1,
},
{
"label": "TraceMaximum",
"value": 2,
},
{
"label": "Average",
"value": 3,
},
{
"label": "TraceAverage",
"value": 4,
}, {
"label": "RMS",
"value": 5,
}, {
"label": "TraceRMS",
"value": 6,
}, {
"label": "Limits",
"value": 7,
}
],
mycds: null,
msycs: null,
samplesType: null,
samplesEdge: null,
tracesType: null,
tracesEdge: null,
InterpolationType: [
{
"label": "Linear",
"value": 1,
}, {
"label": "Quadratic",
"value": 2,
}, {
"label": "Step",
"value": 3,
}, {
"label": "CenteredStep",
"value": 4,
}, {
"label": "Cubic",
"value": 5,
}, {
"label": "Logarithmic",
"value": 6,
},
],
InterpolationEdge: [
{
"label": "Zero",
"value": 0,
}, {
"label": "Duplicate",
"value": 1,
}
],
Wiggle: false,
Reversed: false,
PositiveFill: false,
NegativeFill: false,
PositiveColorFill: false,
NegativeColorFill: false,
SimpleDensity: false,
InterpolatedDensity: true,
ClippingFactor: 4,
DecimationSpacing: 5,
densityDecimation: false,
ClippingMode: null,
ClippingModeData: [
{
"label": "Connected",
"value": "Connected"
}, {
"label": "Disconnected",
"value": "Disconnected"
},
],
TaperFilterEnbled: false,
f1: 10,
f2: 20,
f3: 60,
f4: 70,
sampleRate: 0,
passFlag: false,
AGCEnbled: false,
AGCLength: 0,
DesiredAverage: 1,
NoiseReductionPercentage: 3,
NoiseReduction: null,
StartSample: 0,
Step: 1,
Units: "",
WindowLength: 250,
UnitsData: [
{
"label": "Sample",
"value": 0
}, {
"label": "Time",
"value": 1
},
],
NoiseReductionData: [
{
"label": "disable",
"value": "disable"
}, {
"label": "enable",
"value": "enable"
}, {
"label": "auto",
"value": "auto"
},
],
ReverseEnbled: false,
inverted: false,
reversed: false,
// 文件上传相关
uploadUrl: process.env.VUE_APP_BASE_API + '/ndy/dz/upload',
uploadHeaders: {
Authorization: "Bearer " + getToken()
},
fileList: []
}
},
mounted() {
// this.init()
},
methods: {
init() {
//创建
seismicPlot = new SeismicPlot({
'canvas': this.$refs.plot,
'errorCallback': this.onCreateWidgetError,
'fileOpenedCallback': this.onFileOpen
});
// 不再自动加载默认文件,等待用户上传文件
},
handleFileSelect() {
let url = process.env.VUE_APP_BASE_API + "/ndy/dz/getSegyDataFile3?fileName=density.segy";
let fileName = 'density.segy';
return fetch(url, {
headers: {
Authorization: "Bearer " + getToken()
}
}).then((response) => {
if (!response.ok) {
throw new Error(`网络请求失败: ${response.status} ${response.statusText}`);
}
return response.blob();
}).then((blob) => {
if (!blob || blob.size === 0) {
throw new Error('获取到的文件数据为空');
}
let fileInput = new File([blob], fileName, { type: blob.type, lastModified: Date.now() });
// seismicPlot.createWidgetFromFile(filesObj)
const file = new LocalFile(fileInput);
this._fileSize = file.fileSize;
const segyReader = new SegyReader(file);
segyReader.loadMetaData((reader) => {
if (reader instanceof Error && this.errorCallback != null) {
this.errorCallback(reader.message);
return;
}
// this.createRemotePipeline(reader)
reader.readDataSetStatistics((reader, statistics) => {
if (reader.getModelLimits().getHeight() === 0 && this.warningCallback != null) {
return this.$message.error("加载失败");
}
// return this.createPipelineFromFile(file.getFileName(), reader, statistics);
this.pipeline = new SeismicPipeline(file.getFileName(), reader, statistics)
seismicPlot.createPipelineFromFile2(this.pipeline, file.getFileName(), reader, statistics);
console.log(this.pipeline)
});
});
})
},
onCreateWidgetError(errorMsg) {
this.$message.error(errorMsg);
},
editProperties() {
// console.log(seismicPlot)
// this.ajv = getAjv({chartNames: seismicPlot.getChartNames()});
// console.log(this.ajv)
// this.schema = this.ajv.getSchema(`/${seismicPlot.getWidget().getClassName()}`).schema;
// console.log(this.schema)
// this.nodeProps = getNodeProps(seismicPlot);
// console.log(this.nodeProps)
this.showPropertiesDialog = true;
},
applyProps(props) {
if (props) {
setNodeProps(seismicPlot, props);
}
this.showPropertiesDialog = false;
},
onFileOpen() {
},
changeColor(val) {
var colorMap = SeismicColors.getDefault().createNamedColorMap(val, 256)
this.pipeline.setColorMap(colorMap);
if (seismicPlot._seismicWidget) {
seismicPlot._seismicWidget.invalidate(); // 使组件重新渲染
seismicPlot._seismicWidget.fitToBounds(); // 调整视图以适应数据
} else {
console.error('Widget不可用,无法更新视图');
}
// 如果plots对象存在redraw方法,使用它来触发重绘
if (seismicPlot.plots && typeof seismicPlot.plots.redraw === 'function') {
seismicPlot.plots.redraw();
}
},
//归一化
changeNor(val) {
if (this.NormalizationType == 7) {
this.pipeline.setOptions({
'normalization': {
'type': this.NormalizationType,
'scale': this.NormalizationBl
}
})
} else {
this.pipeline.setOptions({
'normalization': {
'type': this.NormalizationType,
'scale': this.NormalizationBl
}
})
}
},
changeMycds() {
seismicPlot.openPipeline(this.pipeline, {
'tracescale': this.mycds,
'samplescale': this.msycs,
'deviceunit': 'in',
'sampleunit': 's',
});
},
changeInt() {
console.log(this.pipeline.getOptions())
this.pipeline.setOptions({
'interpolation': {
'samples': {
"type": this.samplesType,//采样插值类型,
"edge": this.samplesEdge,//采样插值边缘,
},
'traces': {
"type": this.tracesType,//道插值类型
"edge": this.tracesEdge,//道插值边缘
},
}
})
},
changePlotType() {
this.pipeline.setOptions({
'plot': {
'type': {
"Wiggle": this.Wiggle,
"Reversed": this.Reversed,
"PositiveFill": this.PositiveFill,
"NegativeFill": this.NegativeFill,
"PositiveColorFill": this.PositiveColorFill,
"NegativeColorFill": this.NegativeColorFill,
"SimpleDensity": this.SimpleDensity,
"InterpolatedDensity": this.InterpolatedDensity,
},
"clippingFactor": this.ClippingFactor,
"decimationSpacing": this.DecimationSpacing,
"densityDecimation": this.densityDecimation,
},
"clippingmode": this.ClippingMode,
"dataProcessors": {
"TaperFilter": {
"apply": this.TaperFilterEnbled,
"f1": Number(this.f1),
"f2": Number(this.f2),
"f3": Number(this.f3),
"f4": Number(this.f4),
"sampleRate": Number(this.sampleRate),
"passFlag": this.passFlag,
},
"AGC": {
"agcLength": this.AGCLength,
"apply": this.AGCEnbled,
"desiredAverage": this.DesiredAverage,
"noiseReduction": this.NoiseReduction,
"noiseReductionPercentage": this.NoiseReductionPercentage,
"startSample": this.StartSample,
"step": this.Step,
"units": this.Units,
"windowLength": this.WindowLength,
},
"Reverse": {
"apply": this.ReverseEnbled,
"inverted": this.inverted,
"reversed": this.reversed,
}
}
})
console.log(this.pipeline.getOptions())
},
// 文件上传相关方法
triggerFileUpload() {
// 创建隐藏的文件输入元素
const input = document.createElement('input');
input.type = 'file';
input.accept = '.segy,.sgy';
input.style.display = 'none';
input.onchange = (event) => {
const file = event.target.files[0];
if (file) {
this.handleFileUpload(file);
}
};
document.body.appendChild(input);
input.click();
document.body.removeChild(input);
},
handleFileUpload(file) {
// 检查文件类型
const allowedTypes = ['.segy', '.sgy'];
const fileName = file.name.toLowerCase();
const isValidType = allowedTypes.some(type => fileName.endsWith(type));
if (!isValidType) {
this.$message.error('只能上传segy、sgy文件');
return;
}
// 检查文件大小 (例如限制为100MB)
const maxSize = 100 * 1024 * 1024; // 100MB
if (file.size > maxSize) {
this.$message.error('文件大小不能超过100MB');
return;
}
// 创建FormData
const formData = new FormData();
formData.append('file', file);
// 上传文件
this.$message.info('正在上传文件...');
fetch(this.uploadUrl, {
method: 'POST',
headers: this.uploadHeaders,
body: formData
})
.then(response => {
if (!response.ok) {
throw new Error(`上传失败: ${response.status} ${response.statusText}`);
}
return response.json();
})
.then(data => {
this.$message.success('文件上传成功');
console.log('上传成功:', data);
// 这里可以处理上传成功后的逻辑,比如重新加载数据
this.loadNewFile(data.fileName);
this.init()
})
.catch(error => {
console.error('上传失败:', error);
this.$message.error('文件上传失败: ' + error.message);
});
},
loadNewFile(fileName) {
// 加载新文件的逻辑
let url = process.env.VUE_APP_BASE_API + "/ndy/dz/getSegyDataFile3?fileName=" + fileName;
return fetch(url, {
headers: {
Authorization: "Bearer " + getToken()
}
}).then((response) => {
if (!response.ok) {
throw new Error(`网络请求失败: ${response.status} ${response.statusText}`);
}
return response.blob();
}).then((blob) => {
if (!blob || blob.size === 0) {
throw new Error('获取到的文件数据为空');
}
let fileInput = new File([blob], fileName, { type: blob.type, lastModified: Date.now() });
const file = new LocalFile(fileInput);
this._fileSize = file.fileSize;
const segyReader = new SegyReader(file);
segyReader.loadMetaData((reader) => {
if (reader instanceof Error && this.errorCallback != null) {
this.errorCallback(reader.message);
return;
}
reader.readDataSetStatistics((reader, statistics) => {
if (reader.getModelLimits().getHeight() === 0 && this.warningCallback != null) {
return this.$message.error("加载失败");
}
this.pipeline = new SeismicPipeline(fileName, reader, statistics)
seismicPlot.createPipelineFromFile2(this.pipeline, fileName, reader, statistics);
console.log(this.pipeline)
});
});
}).catch(error => {
console.error('加载文件失败:', error);
this.$message.error('加载文件失败: ' + error.message);
});
}
}
}
</script>
<style scoped>
#canvas {
width: 100% !important;
height: calc(85vh - 35px) !important;
margin-top: 20px;
}
</style>
<!-- 成果列表 -->
<template>
<div class="app-container">
<!-- 左右分栏布局 -->
<div class="main-layout" :class="{ 'viewer-mode': isViewerRole }">
<!-- 左侧:文件上传情况 -->
<div class="left-panel">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<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-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>
<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 v-if="!isViewerRole" type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['yscgWjscqk:wjscqk:add']">新增</el-button>
<el-button v-if="!isViewerRole" type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate"
v-hasPermi="['yscgWjscqk:wjscqk:edit']">修改</el-button>
<el-button v-if="!isViewerRole" type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete"
v-hasPermi="['yscgWjscqk:wjscqk:remove']">删除</el-button>
<el-button v-if="!isViewerRole" type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
v-hasPermi="['yscgWjscqk:wjscqk:export']">导出</el-button>
</el-form-item>
</el-form>
<el-table border v-loading="loading" :data="wjscqkList" @selection-change="handleSelectionChange">
<el-table-column v-if="!isViewerRole" type="selection" width="55" align="center" />
<el-table-column label="文件名称" align="center" prop="wjmc" min-width="120" show-overflow-tooltip>
<template slot-scope="scope">
<el-button type="text" @click="handleDownload(scope.row)" style="padding: 0; color: #409EFF;">
{{ scope.row.wjmc }}
</el-button>
</template>
</el-table-column>
<el-table-column label="资料类型" align="center" prop="zllx" min-width="100" show-overflow-tooltip />
<el-table-column label="上传时间" align="center" prop="createdTime" min-width="170" show-overflow-tooltip />
<el-table-column v-if="!isViewerRole" 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)"
v-hasPermi="['yscgWjscqk:wjscqk:edit']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['yscgWjscqk:wjscqk:remove']">删除</el-button>
<el-button size="mini" type="text" icon="el-icon-share" @click="handleShare(scope.row)"
v-hasPermi="['yscgWjscqk:wjscqk:share']">分享</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>
<!-- 右侧:项目成员 -->
<div v-if="!isViewerRole" class="right-panel">
<div class="xmcy-panel">
<!-- 标题和操作栏 -->
<div class="xmcy-header">
<h3 class="xmcy-title">项目成员 ({{ xmcyTotal }})</h3>
<div class="xmcy-actions">
<el-button
type="primary"
icon="el-icon-plus"
size="small"
@click="handleXmcyAdd"
class="invite-btn"
>邀请作成员</el-button>
<el-button
type="text"
icon="el-icon-search"
size="small"
@click="toggleSearch"
class="search-btn"
></el-button>
</div>
</div>
<!-- 搜索框 -->
<div v-show="showSearchBox" class="xmcy-search">
<el-input
v-model="xmcyQueryParams.nickName"
placeholder="搜索成员"
clearable
prefix-icon="el-icon-search"
@keyup.enter.native="handleXmcyQuery"
@clear="handleXmcyQuery"
size="small"
>
</el-input>
</div>
<!-- 项目成员列表 -->
<div class="xmcy-list" v-loading="xmcyLoading">
<div
v-for="item in xmcyList"
:key="item.cyid"
class="xmcy-item"
>
<div class="xmcy-item-left">
<!-- 头像图标 -->
<div class="xmcy-avatar avatar-member">
<i class="el-icon-user"></i>
</div>
<div class="xmcy-info">
<!-- 角色标签 -->
<div class="xmcy-role-tag">
<dict-tag :options="dict.type.xm_js" :value="item.xmjs"/>
</div>
<!-- 用户姓名 -->
<div class="xmcy-name">{{ item.nickName }}</div>
</div>
</div>
<div class="xmcy-item-right">
<el-dropdown trigger="click" @command="handleCommand">
<span class="el-dropdown-link">
<i class="el-icon-arrow-down"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item :command="{action: 'edit', row: item}">
<i class="el-icon-edit"></i> 修改
</el-dropdown-item>
<el-dropdown-item divided>
<el-popconfirm
title="是否确认删除该成员?"
@confirm="handleXmcyDelete(item)"
placement="top"
>
<span slot="reference" style="display: block; width: 100%;">
<i class="el-icon-delete"></i> 删除
</span>
</el-popconfirm>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
<!-- 空状态 -->
<div v-if="!xmcyLoading && xmcyList.length === 0" class="xmcy-empty">
<p>暂无成员</p>
</div>
</div>
</div>
</div>
</div>
<!-- 添加或修改成果管理对话框 -->
<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="资料类型" 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>
<el-button @click="cancel">取 消</el-button>
</div>
</el-dialog>
<!-- 添加或修改项目成员对话框 -->
<el-dialog :title="xmcyTitle" :visible.sync="xmcyOpen" width="500px" append-to-body>
<el-form ref="xmcyForm" :model="xmcyForm" :rules="xmcyRules" label-width="80px">
<el-form-item label="用户姓名" prop="nickName">
<el-input v-model="xmcyForm.nickName" placeholder="请选择用户" readonly>
<el-button slot="append" icon="el-icon-search" @click="openUserDialog"></el-button>
</el-input>
</el-form-item>
<el-form-item label="项目角色" prop="xmjs">
<el-select style="width: 100%;" v-model="xmcyForm.xmjs" placeholder="请选择项目角色">
<el-option
v-for="dict in dict.type.xm_js"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitXmcyForm">确 定</el-button>
<el-button @click="cancelXmcy">取 消</el-button>
</div>
</el-dialog>
<!-- 选择用户对话框 -->
<el-dialog title="选择用户" :visible.sync="userDialogVisible" width="800px" append-to-body>
<el-form :model="userQueryParams" ref="userQueryForm" size="small" :inline="true" label-width="80px">
<el-form-item label="统一账号" prop="userName">
<el-input
v-model="userQueryParams.userName"
placeholder="请输入统一账号"
clearable
size="small"
style="width: 200px"
@keyup.enter.native="handleUserSearch"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleUserSearch">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetUserQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="userLoading"
:data="userList"
@row-click="handleUserSelect"
highlight-current-row
style="cursor: pointer;"
>
<el-table-column label="统一账号" align="center" prop="userName" />
<el-table-column label="用户姓名" align="center" prop="nickName" />
<el-table-column label="部门" align="center" prop="dept.deptName" />
</el-table>
<pagination
v-show="userTotal>0"
:total="userTotal"
:page.sync="userQueryParams.pageNum"
:limit.sync="userQueryParams.pageSize"
@pagination="getUserList"
/>
</el-dialog>
</div>
</template>
<script>
import { listWjscqk, getWjscqk, delWjscqk, addWjscqk, updateWjscqk } from "@/api/yscgWjscqk/wjscqk"
import { selectZllx } from "@/api/yscgZllx/zllx"
import { getToken } from "@/utils/auth"
import { listXmcy, getXmcy, delXmcy, addXmcy, updateXmcy } from "@/api/ysqqXmxxXmcy/xmcy"
import { listUser } from "@/api/system/user"
export default {
name: "Wjscqk",
dicts: ['xm_js'],
data() {
return {
// 遮罩层
loading: true,
// 选中数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 成果管理表格数据
wjscqkList: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
id: null,
xmid: null,
wjmc: null,
zllx: null,
wjlj: null,
createdBy: null,
createdTime: null,
ext1: null,
ext2: null,
ext3: null
},
// 表单参数
form: {},
// 表单校验
rules: {
},
// 资料类型选项
zllxOptions: [],
// 文件列表
fileList: [],
// 上传配置
uploadUrl: process.env.VUE_APP_BASE_API + "/common/upload",
uploadHeaders: {
Authorization: "Bearer " + getToken()
},
uploadData: {},
// 项目成员相关数据
// 项目成员列表加载状态
xmcyLoading: false,
// 项目成员列表
xmcyList: [],
// 项目成员总条数
xmcyTotal: 0,
// 项目成员查询参数
xmcyQueryParams: {
pageNum: 1,
pageSize: 9999,
nickName: null,
zbid: null
},
// 项目成员表单参数
xmcyForm: {},
// 项目成员表单校验
xmcyRules: {
nickName: [
{ required: true, message: "用户姓名不能为空", trigger: "change" }
],
xmjs: [
{ required: true, message: "项目角色不能为空", trigger: "change" }
]
},
// 项目成员弹出层标题
xmcyTitle: "",
// 是否显示项目成员弹出层
xmcyOpen: false,
// 选择用户对话框
userDialogVisible: false,
// 用户列表加载状态
userLoading: false,
// 用户列表
userList: [],
// 用户列表总条数
userTotal: 0,
// 用户查询参数
userQueryParams: {
pageNum: 1,
pageSize: 10,
userName: null
},
// 是否显示搜索框
showSearchBox: false,
// 当前用户的项目角色
currentUserRole: null,
// 是否为查看者角色
isViewerRole: false
}
},
created() {
// 获取路由参数中的id和角色
if (this.$route.query.xmid) {
this.queryParams.xmid = this.$route.query.xmid
// 设置项目成员查询的zbid(xmid对应zbid)
this.xmcyQueryParams.zbid = this.$route.query.xmid
}
// 获取路由参数中的项目角色
if (this.$route.query.xmjs) {
this.currentUserRole = this.$route.query.xmjs
}
this.getList()
this.getZllxOptions()
},
mounted() {
// 在字典加载完成后判断角色
this.$nextTick(() => {
if (this.dict && this.dict.type && this.dict.type.xm_js) {
this.checkViewerRole()
}
// 加载项目成员列表
if (this.xmcyQueryParams.zbid && !this.isViewerRole) {
this.getXmcyList()
}
})
},
watch: {
// 监听路由参数变化
'$route.query.xmid'(newId) {
if (newId) {
this.queryParams.xmid = newId
this.xmcyQueryParams.zbid = newId
this.getList()
if (!this.isViewerRole) {
this.getXmcyList()
}
}
},
// 监听路由参数中的角色变化
'$route.query.xmjs'(newRole) {
if (newRole) {
this.currentUserRole = newRole
}
},
// 监听字典数据变化,字典加载完成后再判断角色
'dict.type.xm_js'() {
if (this.dict && this.dict.type && this.dict.type.xm_js) {
this.checkViewerRole()
}
},
// 监听当前用户角色变化
currentUserRole() {
this.checkViewerRole()
}
},
methods: {
/** 查询成果管理列表 */
getList() {
this.loading = true
listWjscqk(this.queryParams).then(response => {
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
this.reset()
},
// 表单重置
reset() {
this.form = {
id: null,
xmid: null,
wjmc: null,
zllx: null,
wjlj: null,
createdBy: null,
createdTime: null,
updateBy: null,
updateTime: null,
ext1: null,
ext2: null,
ext3: null
}
this.fileList = []
this.resetForm("form")
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm")
this.handleQuery()
},
// 多选框选中数据
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length !== 1
this.multiple = !selection.length
},
/** 新增按钮操作 */
handleAdd() {
this.reset()
// 设置xmid参数
if (this.queryParams.xmid) {
this.form.xmid = this.queryParams.xmid
}
this.open = true
this.title = "添加成果管理"
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset()
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 = "修改成果管理"
})
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.id != null) {
updateWjscqk(this.form).then(response => {
this.$modal.msgSuccess("修改成功")
this.open = false
this.getList()
})
} else {
addWjscqk(this.form).then(response => {
this.$modal.msgSuccess("新增成功")
this.open = false
this.getList()
})
}
}
})
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids
this.$modal.confirm('是否确认删除成果管理编号为"' + ids + '"的数据项?').then(function () {
return delWjscqk(ids)
}).then(() => {
this.getList()
this.$modal.msgSuccess("删除成功")
}).catch(() => { })
},
/** 分享按钮操作 */
handleShare(row) {
// 跳转到文件权限管理页面,传递文件ID
const fileId = row.id
this.$router.push({
path: '/yscgFileRole/filerole',
query: {
fileId: fileId,
wjmc: row.wjmc
}
})
},
/** 导出按钮操作 */
handleExport() {
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
}
},
/** 下载文件 */
handleDownload(row) {
// 检查是否有文件链接
if (!row.wjlj) {
this.$modal.msgWarning('该文件没有下载链接')
return
}
// 处理多个文件的情况(逗号分隔)
const fileNames = row.wjmc ? String(row.wjmc).split(',').filter(Boolean) : []
const fileLinks = String(row.wjlj).split(',').filter(Boolean)
if (fileLinks.length === 0) {
this.$modal.msgWarning('该文件没有下载链接')
return
}
// 如果只有一个文件,直接下载
if (fileLinks.length === 1) {
this.downloadFile(fileLinks[0], fileNames[0] || '文件')
return
}
// 如果有多个文件,显示选择列表
const fileOptions = fileLinks.map((link, index) => {
return {
name: fileNames[index] || `文件${index + 1}`,
link: link
}
})
// 使用MessageBox选择要下载的文件
const h = this.$createElement
const fileListHtml = fileOptions.map((file, index) => {
return h('div', {
style: {
padding: '8px 0',
cursor: 'pointer',
color: '#409EFF'
},
on: {
click: () => {
this.downloadFile(file.link, file.name)
this.$msgbox.close()
}
}
}, file.name)
})
this.$msgbox({
title: '请选择要下载的文件',
message: h('div', null, fileListHtml),
showCancelButton: true,
confirmButtonText: '取消',
cancelButtonText: '',
showConfirmButton: true,
showCancelButton: false
}).catch(() => {})
},
/** 执行文件下载 */
downloadFile(url, fileName) {
if (!url) {
this.$modal.msgWarning('文件链接无效')
return
}
// 创建一个a标签进行下载
const link = document.createElement('a')
link.style.display = 'none'
link.href = url
link.setAttribute('download', fileName)
link.setAttribute('target', '_blank')
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
this.$modal.msgSuccess('开始下载文件')
},
/** 查询项目成员列表 */
getXmcyList() {
if (!this.xmcyQueryParams.zbid) {
return
}
this.xmcyLoading = true
listXmcy(this.xmcyQueryParams).then(response => {
this.xmcyList = response.rows || []
this.xmcyTotal = response.total || 0
this.xmcyLoading = false
}).catch(() => {
this.xmcyLoading = false
})
},
/** 搜索项目成员 */
handleXmcyQuery() {
this.xmcyQueryParams.pageNum = 1
this.getXmcyList()
},
/** 重置项目成员查询 */
resetXmcyQuery() {
this.xmcyQueryParams = {
pageNum: 1,
pageSize: 9999,
nickName: null,
zbid: this.queryParams.xmid
}
this.getXmcyList()
},
/** 项目成员表单重置 */
resetXmcyForm() {
this.xmcyForm = {
cyid: null,
zbid: this.queryParams.xmid,
userId: null,
nickName: null,
xmjs: null,
createdBy: null,
createdTime: null,
updateBy: null,
updateTime: null
}
this.resetForm("xmcyForm")
},
/** 新增项目成员按钮操作 */
handleXmcyAdd() {
this.resetXmcyForm()
this.xmcyOpen = true
this.xmcyTitle = "添加项目成员"
},
/** 修改项目成员按钮操作 */
handleXmcyUpdate(row) {
this.resetXmcyForm()
const cyid = row.cyid
getXmcy(cyid).then(response => {
this.xmcyForm = response.data
// 确保zbid被设置
this.xmcyForm.zbid = this.queryParams.xmid
this.xmcyOpen = true
this.xmcyTitle = "修改项目成员"
})
},
/** 提交项目成员表单 */
submitXmcyForm() {
this.$refs["xmcyForm"].validate(valid => {
if (valid) {
if (this.xmcyForm.cyid != null) {
updateXmcy(this.xmcyForm).then(response => {
this.$modal.msgSuccess("修改成功")
this.xmcyOpen = false
this.getXmcyList()
})
} else {
addXmcy(this.xmcyForm).then(response => {
this.$modal.msgSuccess("新增成功")
this.xmcyOpen = false
this.getXmcyList()
})
}
}
})
},
/** 删除项目成员按钮操作 */
handleXmcyDelete(row) {
const cyid = row.cyid
delXmcy(cyid).then(() => {
this.getXmcyList()
this.$modal.msgSuccess("删除成功")
}).catch(() => {})
},
/** 取消项目成员表单 */
cancelXmcy() {
this.xmcyOpen = false
this.resetXmcyForm()
},
/** 打开选择用户对话框 */
openUserDialog() {
// 重置查询参数
this.userQueryParams = {
pageNum: 1,
pageSize: 10,
userName: null
}
this.userList = []
this.userTotal = 0
this.userDialogVisible = true
this.getUserList()
},
/** 搜索用户 */
handleUserSearch() {
this.userQueryParams.pageNum = 1
this.getUserList()
},
/** 查询用户列表 */
getUserList() {
this.userLoading = true
listUser(this.userQueryParams).then(response => {
this.userList = response.rows || []
this.userTotal = response.total || 0
this.userLoading = false
}).catch(() => {
this.userLoading = false
})
},
/** 重置用户查询 */
resetUserQuery() {
this.userQueryParams = {
pageNum: 1,
pageSize: 10,
userName: null
}
this.userList = []
this.userTotal = 0
},
/** 选择用户 */
handleUserSelect(row) {
this.xmcyForm.userId = row.userId
this.xmcyForm.nickName = row.nickName
this.xmcyForm.phonenumber = row.phonenumber
this.userDialogVisible = false
},
/** 切换搜索框显示 */
toggleSearch() {
this.showSearchBox = !this.showSearchBox
if (this.showSearchBox) {
this.$nextTick(() => {
const searchInput = this.$el.querySelector('.xmcy-search input')
if (searchInput) {
searchInput.focus()
}
})
} else {
// 隐藏搜索框时重置查询
this.resetXmcyQuery()
}
},
/** 下拉菜单命令处理 */
handleCommand(command) {
if (command.action === 'edit') {
this.handleXmcyUpdate(command.row)
}
},
/** 获取头像样式类 */
getAvatarClass(item) {
// 根据角色返回不同的样式类,管理员用绿色,其他用蓝色
if (this.isOwner(item.xmjs)) {
return 'avatar-admin'
}
return 'avatar-member'
},
/** 获取头像图标 */
getAvatarIcon(item) {
if (this.isOwner(item.xmjs)) {
return 'el-icon-user-solid'
}
return 'el-icon-user'
},
/** 判断是否为所有者/管理员 */
isOwner(xmjs) {
// 查找字典中标签包含"管理员"或"所有者"的角色
if (!this.dict || !this.dict.type || !this.dict.type.xm_js) {
return false
}
const role = this.dict.type.xm_js.find(item => item.value === xmjs)
if (role && (role.label.includes('管理员') || role.label.includes('所有者'))) {
return true
}
// 如果字典值为'1'或'admin'等,也认为是管理员
return xmjs === '1' || xmjs === 'admin'
},
/** 判断是否为查看者角色 */
checkViewerRole() {
if (!this.currentUserRole || !this.dict || !this.dict.type || !this.dict.type.xm_js) {
this.isViewerRole = false
return
}
// 查找字典中标签包含"查看者"的角色
const role = this.dict.type.xm_js.find(item => item.value === this.currentUserRole)
if (role && role.label.includes('查看者')) {
this.isViewerRole = true
} else {
this.isViewerRole = false
}
}
}
}
</script>
<style lang="scss" scoped>
.main-layout {
display: flex;
gap: 20px;
height: calc(100vh - 150px);
}
.left-panel {
flex: 1;
min-width: 0;
overflow: hidden;
display: flex;
flex-direction: column;
}
// 查看者模式下,左侧面板占满整个宽度
.main-layout.viewer-mode .left-panel {
width: 100%;
}
.right-panel {
width: 410px;
flex-shrink: 0;
border-left: 1px solid #e8eaed;
padding-left: 20px;
padding-right: 20px;
}
.xmcy-panel {
height: 100%;
display: flex;
flex-direction: column;
}
.xmcy-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 14px;
padding-bottom: 10px;
border-bottom: 1px solid #e8eaed;
.xmcy-title {
margin: 0;
font-size: 15px;
font-weight: 600;
color: #212121;
letter-spacing: 0.1px;
}
.xmcy-actions {
display: flex;
align-items: center;
gap: 8px;
.invite-btn {
height: 28px;
padding: 0 12px;
font-size: 12px;
border-radius: 4px;
font-weight: 500;
}
.search-btn {
padding: 4px;
font-size: 16px;
color: #757575;
border-radius: 4px;
transition: all 0.2s;
&:hover {
color: #212121;
background: rgba(0, 0, 0, 0.04);
}
}
}
}
.xmcy-search {
margin-bottom: 12px;
}
.xmcy-list {
flex: 1;
overflow-y: auto;
padding-right: 6px;
padding-top: 2px;
border: 1px solid #e8eaed;
}
.xmcy-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 12px;
margin-bottom: 8px;
background: #fafafa;
border-radius: 8px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
transition: all 0.2s ease;
&:hover {
background: #f5f5f5;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.xmcy-item-left {
display: flex;
align-items: center;
flex: 1;
min-width: 0;
}
.xmcy-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 10px;
flex-shrink: 0;
background: #e3f2fd;
color: #1976d2;
i {
font-size: 18px;
}
&.avatar-admin {
background: #e3f2fd;
color: #1976d2;
}
&.avatar-member {
background: #e3f2fd;
color: #1976d2;
}
}
.xmcy-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
.xmcy-role-tag {
margin: 0;
line-height: 1.2;
::v-deep .el-tag {
margin: 0;
border: none;
font-size: 10px;
padding: 0;
background: transparent;
color: #999;
height: auto;
line-height: 1.2;
font-weight: 400;
}
}
.xmcy-name {
font-size: 13px;
font-weight: 500;
color: #424242;
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
line-height: 1.3;
}
.xmcy-item-right {
flex-shrink: 0;
margin-left: 8px;
.el-dropdown-link {
cursor: pointer;
color: #bbb;
font-size: 14px;
padding: 4px;
transition: color 0.2s;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
&:hover {
color: #666;
background: rgba(0, 0, 0, 0.04);
}
i {
font-size: 14px;
}
}
}
}
.xmcy-empty {
text-align: center;
padding: 60px 20px;
color: #9aa0a6;
p {
margin: 0;
font-size: 14px;
}
}
.xmcy-pagination {
margin-top: 14px;
padding-top: 12px;
border-top: 1px solid #e8eaed;
}
// 滚动条样式
.xmcy-list::-webkit-scrollbar {
width: 6px;
}
.xmcy-list::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.xmcy-list::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
&:hover {
background: #a8a8a8;
}
}
</style>
<!-- 成果列表 -->
<template>
<div class="app-container">
<!-- 左右分栏布局 -->
<div class="main-layout" :class="{ 'viewer-mode': isViewerRole }">
<!-- 左侧:文件上传情况 -->
<div class="left-panel">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<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-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>
<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 v-if="!isViewerRole" type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['yscgWjscqk:wjscqk:add']">新增</el-button>
<el-button v-if="!isViewerRole" type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate"
v-hasPermi="['yscgWjscqk:wjscqk:edit']">修改</el-button>
<el-button v-if="!isViewerRole" type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete"
v-hasPermi="['yscgWjscqk:wjscqk:remove']">删除</el-button>
<el-button v-if="!isViewerRole" type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
v-hasPermi="['yscgWjscqk:wjscqk:export']">导出</el-button>
</el-form-item>
</el-form>
<el-table border v-loading="loading" :data="wjscqkList" @selection-change="handleSelectionChange">
<el-table-column v-if="!isViewerRole" type="selection" width="55" align="center" />
<el-table-column label="文件名称" align="center" prop="wjmc" min-width="120" show-overflow-tooltip>
<template slot-scope="scope">
<el-button type="text" @click="handleDownload(scope.row)" style="padding: 0; color: #409EFF;">
{{ scope.row.wjmc }}
</el-button>
</template>
</el-table-column>
<el-table-column label="资料类型" align="center" prop="zllx" min-width="100" show-overflow-tooltip />
<el-table-column label="上传时间" align="center" prop="createdTime" min-width="170" show-overflow-tooltip />
<el-table-column v-if="!isViewerRole" 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)"
v-hasPermi="['yscgWjscqk:wjscqk:edit']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['yscgWjscqk:wjscqk:remove']">删除</el-button>
<el-button size="mini" type="text" icon="el-icon-share" @click="handleShare(scope.row)"
v-hasPermi="['yscgWjscqk:wjscqk:share']">分享</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>
<!-- 右侧:项目成员 -->
<div v-if="!isViewerRole" class="right-panel">
<div class="xmcy-panel">
<!-- 标题和操作栏 -->
<div class="xmcy-header">
<h3 class="xmcy-title">项目成员 ({{ xmcyTotal }})</h3>
<div class="xmcy-actions">
<el-button
type="primary"
icon="el-icon-plus"
size="small"
@click="handleXmcyAdd"
class="invite-btn"
>邀请作成员</el-button>
<el-button
type="text"
icon="el-icon-search"
size="small"
@click="toggleSearch"
class="search-btn"
></el-button>
</div>
</div>
<!-- 搜索框 -->
<div v-show="showSearchBox" class="xmcy-search">
<el-input
v-model="xmcyQueryParams.nickName"
placeholder="搜索成员"
clearable
prefix-icon="el-icon-search"
@keyup.enter.native="handleXmcyQuery"
@clear="handleXmcyQuery"
size="small"
>
</el-input>
</div>
<!-- 项目成员列表 -->
<div class="xmcy-list" v-loading="xmcyLoading">
<div
v-for="item in xmcyList"
:key="item.cyid"
class="xmcy-item"
>
<div class="xmcy-item-left">
<!-- 头像图标 -->
<div class="xmcy-avatar avatar-member">
<i class="el-icon-user"></i>
</div>
<div class="xmcy-info">
<!-- 角色标签 -->
<div class="xmcy-role-tag">
<dict-tag :options="dict.type.xm_js" :value="item.xmjs"/>
</div>
<!-- 用户姓名 -->
<div class="xmcy-name">{{ item.nickName }}</div>
</div>
</div>
<div class="xmcy-item-right">
<el-dropdown trigger="click" @command="handleCommand">
<span class="el-dropdown-link">
<i class="el-icon-arrow-down"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item :command="{action: 'edit', row: item}">
<i class="el-icon-edit"></i> 修改
</el-dropdown-item>
<el-dropdown-item divided>
<el-popconfirm
title="是否确认删除该成员?"
@confirm="handleXmcyDelete(item)"
placement="top"
>
<span slot="reference" style="display: block; width: 100%;">
<i class="el-icon-delete"></i> 删除
</span>
</el-popconfirm>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
<!-- 空状态 -->
<div v-if="!xmcyLoading && xmcyList.length === 0" class="xmcy-empty">
<p>暂无成员</p>
</div>
</div>
</div>
</div>
</div>
<!-- 添加或修改成果管理对话框 -->
<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="资料类型" 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 格式文件<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>
<el-button @click="cancel">取 消</el-button>
</div>
</el-dialog>
<!-- 添加或修改项目成员对话框 -->
<el-dialog :title="xmcyTitle" :visible.sync="xmcyOpen" width="500px" append-to-body>
<el-form ref="xmcyForm" :model="xmcyForm" :rules="xmcyRules" label-width="80px">
<el-form-item label="用户姓名" prop="nickName">
<el-input v-model="xmcyForm.nickName" placeholder="请选择用户" readonly>
<el-button slot="append" icon="el-icon-search" @click="openUserDialog"></el-button>
</el-input>
</el-form-item>
<el-form-item label="项目角色" prop="xmjs">
<el-select style="width: 100%;" v-model="xmcyForm.xmjs" placeholder="请选择项目角色">
<el-option
v-for="dict in dict.type.xm_js"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitXmcyForm">确 定</el-button>
<el-button @click="cancelXmcy">取 消</el-button>
</div>
</el-dialog>
<!-- 选择用户对话框 -->
<el-dialog title="选择用户" :visible.sync="userDialogVisible" width="800px" append-to-body>
<el-form :model="userQueryParams" ref="userQueryForm" size="small" :inline="true" label-width="80px">
<el-form-item label="统一账号" prop="userName">
<el-input
v-model="userQueryParams.userName"
placeholder="请输入统一账号"
clearable
size="small"
style="width: 200px"
@keyup.enter.native="handleUserSearch"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleUserSearch">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetUserQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="userLoading"
:data="userList"
@row-click="handleUserSelect"
highlight-current-row
style="cursor: pointer;"
>
<el-table-column label="统一账号" align="center" prop="userName" />
<el-table-column label="用户姓名" align="center" prop="nickName" />
<el-table-column label="部门" align="center" prop="dept.deptName" />
</el-table>
<pagination
v-show="userTotal>0"
:total="userTotal"
:page.sync="userQueryParams.pageNum"
:limit.sync="userQueryParams.pageSize"
@pagination="getUserList"
/>
</el-dialog>
</div>
</template>
<script>
import { listWjscqk, getWjscqk, delWjscqk, addWjscqk, updateWjscqk } from "@/api/yscgWjscqk/wjscqk"
import { selectZllx } from "@/api/yscgZllx/zllx"
import { getToken } from "@/utils/auth"
import { listXmcy, getXmcy, delXmcy, addXmcy, updateXmcy } from "@/api/ysqqXmxxXmcy/xmcy"
import { listUser } from "@/api/system/user"
export default {
name: "Wjscqk",
dicts: ['xm_js'],
data() {
return {
// 遮罩层
loading: true,
// 选中数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 成果管理表格数据
wjscqkList: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
id: null,
xmid: null,
wjmc: null,
zllx: null,
wjlj: null,
createdBy: null,
createdTime: null,
ext1: null,
ext2: null,
ext3: null
},
// 表单参数
form: {},
// 表单校验
rules: {
},
// 资料类型选项
zllxOptions: [],
// 文件列表
fileList: [],
// 上传配置
uploadUrl: process.env.VUE_APP_BASE_API + "/common/upload",
uploadHeaders: {
Authorization: "Bearer " + getToken()
},
uploadData: {},
// 项目成员相关数据
// 项目成员列表加载状态
xmcyLoading: false,
// 项目成员列表
xmcyList: [],
// 项目成员总条数
xmcyTotal: 0,
// 项目成员查询参数
xmcyQueryParams: {
pageNum: 1,
pageSize: 9999,
nickName: null,
zbid: null
},
// 项目成员表单参数
xmcyForm: {},
// 项目成员表单校验
xmcyRules: {
nickName: [
{ required: true, message: "用户姓名不能为空", trigger: "change" }
],
xmjs: [
{ required: true, message: "项目角色不能为空", trigger: "change" }
]
},
// 项目成员弹出层标题
xmcyTitle: "",
// 是否显示项目成员弹出层
xmcyOpen: false,
// 选择用户对话框
userDialogVisible: false,
// 用户列表加载状态
userLoading: false,
// 用户列表
userList: [],
// 用户列表总条数
userTotal: 0,
// 用户查询参数
userQueryParams: {
pageNum: 1,
pageSize: 10,
userName: null
},
// 是否显示搜索框
showSearchBox: false,
// 当前用户的项目角色
currentUserRole: null,
// 是否为查看者角色
isViewerRole: false
}
},
created() {
// 获取路由参数中的id和角色
if (this.$route.query.xmid) {
this.queryParams.xmid = this.$route.query.xmid
// 设置项目成员查询的zbid(xmid对应zbid)
this.xmcyQueryParams.zbid = this.$route.query.xmid
}
// 获取路由参数中的项目角色
if (this.$route.query.xmjs) {
this.currentUserRole = this.$route.query.xmjs
}
this.getList()
this.getZllxOptions()
},
mounted() {
// 在字典加载完成后判断角色
this.$nextTick(() => {
if (this.dict && this.dict.type && this.dict.type.xm_js) {
this.checkViewerRole()
}
// 加载项目成员列表
if (this.xmcyQueryParams.zbid && !this.isViewerRole) {
this.getXmcyList()
}
})
},
watch: {
// 监听路由参数变化
'$route.query.xmid'(newId) {
if (newId) {
this.queryParams.xmid = newId
this.xmcyQueryParams.zbid = newId
this.getList()
if (!this.isViewerRole) {
this.getXmcyList()
}
}
},
// 监听路由参数中的角色变化
'$route.query.xmjs'(newRole) {
if (newRole) {
this.currentUserRole = newRole
}
},
// 监听字典数据变化,字典加载完成后再判断角色
'dict.type.xm_js'() {
if (this.dict && this.dict.type && this.dict.type.xm_js) {
this.checkViewerRole()
}
},
// 监听当前用户角色变化
currentUserRole() {
this.checkViewerRole()
}
},
methods: {
/** 查询成果管理列表 */
getList() {
this.loading = true
listWjscqk(this.queryParams).then(response => {
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
this.reset()
},
// 表单重置
reset() {
this.form = {
id: null,
xmid: null,
wjmc: null,
zllx: null,
wjlj: null,
createdBy: null,
createdTime: null,
updateBy: null,
updateTime: null,
ext1: null,
ext2: null,
ext3: null
}
this.fileList = []
this.resetForm("form")
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm")
this.handleQuery()
},
// 多选框选中数据
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length !== 1
this.multiple = !selection.length
},
/** 新增按钮操作 */
handleAdd() {
this.reset()
// 设置xmid参数
if (this.queryParams.xmid) {
this.form.xmid = this.queryParams.xmid
}
this.open = true
this.title = "添加成果管理"
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset()
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 = "修改成果管理"
})
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.id != null) {
updateWjscqk(this.form).then(response => {
this.$modal.msgSuccess("修改成功")
this.open = false
this.getList()
})
} else {
addWjscqk(this.form).then(response => {
this.$modal.msgSuccess("新增成功")
this.open = false
this.getList()
})
}
}
})
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids
this.$modal.confirm('是否确认删除成果管理编号为"' + ids + '"的数据项?').then(function () {
return delWjscqk(ids)
}).then(() => {
this.getList()
this.$modal.msgSuccess("删除成功")
}).catch(() => { })
},
/** 分享按钮操作 */
handleShare(row) {
// 跳转到文件权限管理页面,传递文件ID
const fileId = row.id
this.$router.push({
path: '/yscgFileRole/filerole',
query: {
fileId: fileId,
wjmc: row.wjmc
}
})
},
/** 导出按钮操作 */
handleExport() {
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
}
},
/** 下载文件 */
handleDownload(row) {
// 检查是否有文件链接
if (!row.wjlj) {
this.$modal.msgWarning('该文件没有下载链接')
return
}
// 处理多个文件的情况(逗号分隔)
const fileNames = row.wjmc ? String(row.wjmc).split(',').filter(Boolean) : []
const fileLinks = String(row.wjlj).split(',').filter(Boolean)
if (fileLinks.length === 0) {
this.$modal.msgWarning('该文件没有下载链接')
return
}
// 如果只有一个文件,直接下载
if (fileLinks.length === 1) {
this.downloadFile(fileLinks[0], fileNames[0] || '文件')
return
}
// 如果有多个文件,显示选择列表
const fileOptions = fileLinks.map((link, index) => {
return {
name: fileNames[index] || `文件${index + 1}`,
link: link
}
})
// 使用MessageBox选择要下载的文件
const h = this.$createElement
const fileListHtml = fileOptions.map((file, index) => {
return h('div', {
style: {
padding: '8px 0',
cursor: 'pointer',
color: '#409EFF'
},
on: {
click: () => {
this.downloadFile(file.link, file.name)
this.$msgbox.close()
}
}
}, file.name)
})
this.$msgbox({
title: '请选择要下载的文件',
message: h('div', null, fileListHtml),
showCancelButton: true,
confirmButtonText: '取消',
cancelButtonText: '',
showConfirmButton: true,
showCancelButton: false
}).catch(() => {})
},
/** 执行文件下载 */
downloadFile(url, fileName) {
if (!url) {
this.$modal.msgWarning('文件链接无效')
return
}
// 创建一个a标签进行下载
const link = document.createElement('a')
link.style.display = 'none'
link.href = url
link.setAttribute('download', fileName)
link.setAttribute('target', '_blank')
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
this.$modal.msgSuccess('开始下载文件')
},
/** 查询项目成员列表 */
getXmcyList() {
if (!this.xmcyQueryParams.zbid) {
return
}
this.xmcyLoading = true
listXmcy(this.xmcyQueryParams).then(response => {
this.xmcyList = response.rows || []
this.xmcyTotal = response.total || 0
this.xmcyLoading = false
}).catch(() => {
this.xmcyLoading = false
})
},
/** 搜索项目成员 */
handleXmcyQuery() {
this.xmcyQueryParams.pageNum = 1
this.getXmcyList()
},
/** 重置项目成员查询 */
resetXmcyQuery() {
this.xmcyQueryParams = {
pageNum: 1,
pageSize: 9999,
nickName: null,
zbid: this.queryParams.xmid
}
this.getXmcyList()
},
/** 项目成员表单重置 */
resetXmcyForm() {
this.xmcyForm = {
cyid: null,
zbid: this.queryParams.xmid,
userId: null,
nickName: null,
xmjs: null,
createdBy: null,
createdTime: null,
updateBy: null,
updateTime: null
}
this.resetForm("xmcyForm")
},
/** 新增项目成员按钮操作 */
handleXmcyAdd() {
this.resetXmcyForm()
this.xmcyOpen = true
this.xmcyTitle = "添加项目成员"
},
/** 修改项目成员按钮操作 */
handleXmcyUpdate(row) {
this.resetXmcyForm()
const cyid = row.cyid
getXmcy(cyid).then(response => {
this.xmcyForm = response.data
// 确保zbid被设置
this.xmcyForm.zbid = this.queryParams.xmid
this.xmcyOpen = true
this.xmcyTitle = "修改项目成员"
})
},
/** 提交项目成员表单 */
submitXmcyForm() {
this.$refs["xmcyForm"].validate(valid => {
if (valid) {
if (this.xmcyForm.cyid != null) {
updateXmcy(this.xmcyForm).then(response => {
this.$modal.msgSuccess("修改成功")
this.xmcyOpen = false
this.getXmcyList()
})
} else {
addXmcy(this.xmcyForm).then(response => {
this.$modal.msgSuccess("新增成功")
this.xmcyOpen = false
this.getXmcyList()
})
}
}
})
},
/** 删除项目成员按钮操作 */
handleXmcyDelete(row) {
const cyid = row.cyid
delXmcy(cyid).then(() => {
this.getXmcyList()
this.$modal.msgSuccess("删除成功")
}).catch(() => {})
},
/** 取消项目成员表单 */
cancelXmcy() {
this.xmcyOpen = false
this.resetXmcyForm()
},
/** 打开选择用户对话框 */
openUserDialog() {
// 重置查询参数
this.userQueryParams = {
pageNum: 1,
pageSize: 10,
userName: null
}
this.userList = []
this.userTotal = 0
this.userDialogVisible = true
this.getUserList()
},
/** 搜索用户 */
handleUserSearch() {
this.userQueryParams.pageNum = 1
this.getUserList()
},
/** 查询用户列表 */
getUserList() {
this.userLoading = true
listUser(this.userQueryParams).then(response => {
this.userList = response.rows || []
this.userTotal = response.total || 0
this.userLoading = false
}).catch(() => {
this.userLoading = false
})
},
/** 重置用户查询 */
resetUserQuery() {
this.userQueryParams = {
pageNum: 1,
pageSize: 10,
userName: null
}
this.userList = []
this.userTotal = 0
},
/** 选择用户 */
handleUserSelect(row) {
this.xmcyForm.userId = row.userId
this.xmcyForm.nickName = row.nickName
this.xmcyForm.phonenumber = row.phonenumber
this.userDialogVisible = false
},
/** 切换搜索框显示 */
toggleSearch() {
this.showSearchBox = !this.showSearchBox
if (this.showSearchBox) {
this.$nextTick(() => {
const searchInput = this.$el.querySelector('.xmcy-search input')
if (searchInput) {
searchInput.focus()
}
})
} else {
// 隐藏搜索框时重置查询
this.resetXmcyQuery()
}
},
/** 下拉菜单命令处理 */
handleCommand(command) {
if (command.action === 'edit') {
this.handleXmcyUpdate(command.row)
}
},
/** 获取头像样式类 */
getAvatarClass(item) {
// 根据角色返回不同的样式类,管理员用绿色,其他用蓝色
if (this.isOwner(item.xmjs)) {
return 'avatar-admin'
}
return 'avatar-member'
},
/** 获取头像图标 */
getAvatarIcon(item) {
if (this.isOwner(item.xmjs)) {
return 'el-icon-user-solid'
}
return 'el-icon-user'
},
/** 判断是否为所有者/管理员 */
isOwner(xmjs) {
// 查找字典中标签包含"管理员"或"所有者"的角色
if (!this.dict || !this.dict.type || !this.dict.type.xm_js) {
return false
}
const role = this.dict.type.xm_js.find(item => item.value === xmjs)
if (role && (role.label.includes('管理员') || role.label.includes('所有者'))) {
return true
}
// 如果字典值为'1'或'admin'等,也认为是管理员
return xmjs === '1' || xmjs === 'admin'
},
/** 判断是否为查看者角色 */
checkViewerRole() {
if (!this.currentUserRole || !this.dict || !this.dict.type || !this.dict.type.xm_js) {
this.isViewerRole = false
return
}
// 查找字典中标签包含"查看者"的角色
const role = this.dict.type.xm_js.find(item => item.value === this.currentUserRole)
if (role && role.label.includes('查看者')) {
this.isViewerRole = true
} else {
this.isViewerRole = false
}
}
}
}
</script>
<style lang="scss" scoped>
.main-layout {
display: flex;
gap: 20px;
height: calc(100vh - 150px);
}
.left-panel {
flex: 1;
min-width: 0;
overflow: hidden;
display: flex;
flex-direction: column;
}
// 查看者模式下,左侧面板占满整个宽度
.main-layout.viewer-mode .left-panel {
width: 100%;
}
.right-panel {
width: 410px;
flex-shrink: 0;
border-left: 1px solid #e8eaed;
padding-left: 20px;
padding-right: 20px;
}
.xmcy-panel {
height: 100%;
display: flex;
flex-direction: column;
}
.xmcy-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 14px;
padding-bottom: 10px;
border-bottom: 1px solid #e8eaed;
.xmcy-title {
margin: 0;
font-size: 15px;
font-weight: 600;
color: #212121;
letter-spacing: 0.1px;
}
.xmcy-actions {
display: flex;
align-items: center;
gap: 8px;
.invite-btn {
height: 28px;
padding: 0 12px;
font-size: 12px;
border-radius: 4px;
font-weight: 500;
}
.search-btn {
padding: 4px;
font-size: 16px;
color: #757575;
border-radius: 4px;
transition: all 0.2s;
&:hover {
color: #212121;
background: rgba(0, 0, 0, 0.04);
}
}
}
}
.xmcy-search {
margin-bottom: 12px;
}
.xmcy-list {
flex: 1;
overflow-y: auto;
padding-right: 6px;
padding-top: 2px;
border: 1px solid #e8eaed;
}
.xmcy-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 12px;
margin-bottom: 8px;
background: #fafafa;
border-radius: 8px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
transition: all 0.2s ease;
&:hover {
background: #f5f5f5;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.xmcy-item-left {
display: flex;
align-items: center;
flex: 1;
min-width: 0;
}
.xmcy-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 10px;
flex-shrink: 0;
background: #e3f2fd;
color: #1976d2;
i {
font-size: 18px;
}
&.avatar-admin {
background: #e3f2fd;
color: #1976d2;
}
&.avatar-member {
background: #e3f2fd;
color: #1976d2;
}
}
.xmcy-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
.xmcy-role-tag {
margin: 0;
line-height: 1.2;
::v-deep .el-tag {
margin: 0;
border: none;
font-size: 10px;
padding: 0;
background: transparent;
color: #999;
height: auto;
line-height: 1.2;
font-weight: 400;
}
}
.xmcy-name {
font-size: 13px;
font-weight: 500;
color: #424242;
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
line-height: 1.3;
}
.xmcy-item-right {
flex-shrink: 0;
margin-left: 8px;
.el-dropdown-link {
cursor: pointer;
color: #bbb;
font-size: 14px;
padding: 4px;
transition: color 0.2s;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
&:hover {
color: #666;
background: rgba(0, 0, 0, 0.04);
}
i {
font-size: 14px;
}
}
}
}
.xmcy-empty {
text-align: center;
padding: 60px 20px;
color: #9aa0a6;
p {
margin: 0;
font-size: 14px;
}
}
.xmcy-pagination {
margin-top: 14px;
padding-top: 12px;
border-top: 1px solid #e8eaed;
}
// 滚动条样式
.xmcy-list::-webkit-scrollbar {
width: 6px;
}
.xmcy-list::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.xmcy-list::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
&:hover {
background: #a8a8a8;
}
}
</style>
......@@ -24,8 +24,8 @@
<!-- </el-select>-->
<!-- </div>-->
<toolbar-controls :plots="plots" :data-loaded="isWidgetReady" :show-line-style-panel="showLineStylePanel"
@toggleLineStylePanel="toggleLineStylePanel" @exporting="exporting = $event"
@closePrintDialog="showPrintDialog = false" />
@toggleLineStylePanel="toggleLineStylePanel" @exporting="exporting = $event"
@closePrintDialog="showPrintDialog = false" />
<el-tooltip content="设置" placement="bottom" effect="light">
<el-button @click="editProperties" :class="{ 'active': showLineStylePanel }">
......@@ -55,11 +55,11 @@
</div>
<!-- 缩略图 start-->
<div v-if="shouldShowThumb" ref="thumbContainer" @click="handleThumbClick" @mousedown="startDrag"
class="right-thumb draggable-thumb" :style="thumbPosition">
class="right-thumb draggable-thumb" :style="thumbPosition">
<!-- <div class="thumb-title">缩略图</div> -->
<div ref="thumbHolder" class="thumb-holder">
<YsgcIndex v-if="thumbReady" :key="'thumb-' + routeId + '-' + thumbReady" :compact="true" :width="260"
:height="160" :id-override="routeId" :active-segy-index="currentSegyIndex" />
:height="160" :id-override="routeId" :active-segy-index="currentSegyIndex" />
</div>
<!-- 拖动指示器 -->
<div class="drag-handle">
......@@ -68,17 +68,15 @@
</div>
<!-- 缩略图 end-->
<!-- 缩略图弹窗 start -->
<!-- 缩略图弹窗 start -->
<el-dialog v-if="shouldShowThumb" title="缩略图预览" :visible.sync="showThumbDialog" width="80%" append-to-body
:z-index="9999999" :modal="true" :close-on-click-modal="true" :close-on-press-escape="true"
custom-class="thumbnail-dialog">
:z-index="9999999" :modal="true" :close-on-click-modal="true" :close-on-press-escape="true"
custom-class="thumbnail-dialog">
<div style="height: 100%;">
<YsgcIndex :key="'dialog-thumb-' + routeId" :id-override="routeId" :active-segy-index="currentSegyIndex"
@segyLinePick="onThumbLinePick" />
@segyLinePick="onThumbLinePick" />
</div>
</el-dialog>
<!-- 缩略图弹窗 end -->
<!-- 缩略图弹窗 end -->
</div>
<!-- 添加加载状态和错误提示 -->
......@@ -90,16 +88,16 @@
</div>
</div>
<div class="split-container" :class="layoutMode === 'vertical' ? 'layout-vertical' : 'layout-horizontal'"
v-loading="isLoading" element-loading-text="加载中...">
v-loading="isLoading" element-loading-text="加载中...">
<div class="sync-section" ref="topScroll" @scroll="onScroll('top')">
<div class="zoom-wrapper" ref="topWrapper"
:style="{ width: baseCanvasWidth + 'px', height: baseCanvasHeight + 'px' }">
:style="{ width: baseCanvasWidth + 'px', height: baseCanvasHeight + 'px' }">
<canvas ref="plot" id="canvasTop" class="plot-canvas" />
</div>
</div>
<div class="sync-section" ref="bottomScroll" @scroll="onScroll('bottom')">
<div class="zoom-wrapper" ref="bottomWrapper"
:style="{ width: baseCanvasWidth + 'px', height: baseCanvasHeight + 'px' }">
:style="{ width: baseCanvasWidth + 'px', height: baseCanvasHeight + 'px' }">
<canvas ref="plot2" id="canvasBottom" class="plot-canvas" />
</div>
</div>
......@@ -113,7 +111,7 @@
<div v-if="item.type === 'separator'" class="context-menu-separator">
</div>
<div v-else class="context-menu-item" :class="{ disabled: !isMenuItemEnabled(item.action) }"
@click="handleMenuAction(item.action)">
@click="handleMenuAction(item.action)">
<span>
<i :class="getElementIcon(item.icon)" class="mr-2"></i>
{{ item.text }}
......@@ -182,7 +180,7 @@
<span class="style-label">字体:</span>
<el-select v-model="textStyle.font" placeholder="选择字体" @change="handleFontChange">
<el-option v-for="(font, index) in Object.keys(TEXT_PATTERNS)" :key="index" :label="font" :value="font"
:style="{ fontFamily: font }">
:style="{ fontFamily: font }">
{{ font }}
</el-option>
</el-select>
......@@ -192,25 +190,25 @@
<div class="text-style-buttons">
<el-button-group style="width: 100%">
<el-button style="min-width: 20%; max-width: 20%" :type="textStyle.align === 'left' ? 'primary' : ''"
@click="textStyle.align = 'left'; updateTextStyle()" title="左对齐">
@click="textStyle.align = 'left'; updateTextStyle()" title="左对齐">
<i class="el-icon-s-fold"></i>
</el-button>
<el-button style="min-width: 20%; max-width: 20%" :type="textStyle.align === 'center' ? 'primary' : ''"
@click="textStyle.align = 'center'; updateTextStyle()" title="居中对齐">
@click="textStyle.align = 'center'; updateTextStyle()" title="居中对齐">
<i class="el-icon-menu"></i>
</el-button>
<el-button style="min-width: 20%; max-width: 20%" :type="textStyle.align === 'right' ? 'primary' : ''"
@click="textStyle.align = 'right'; updateTextStyle()" title="右对齐">
@click="textStyle.align = 'right'; updateTextStyle()" title="右对齐">
<i class="el-icon-s-unfold"></i>
</el-button>
<!-- 粗体按钮 -->
<el-button style="min-width: 20%; max-width: 20%" :type="textStyle.bold ? 'primary' : ''"
@click="textStyle.bold = !textStyle.bold; updateTextStyle()" title="粗体">
@click="textStyle.bold = !textStyle.bold; updateTextStyle()" title="粗体">
<span style="font-weight: bold">B</span>
</el-button>
<!-- 斜体按钮 -->
<el-button style="min-width: 20%; max-width: 20%" :type="textStyle.italic ? 'primary' : ''"
@click="textStyle.italic = !textStyle.italic; updateTextStyle()" title="斜体">
@click="textStyle.italic = !textStyle.italic; updateTextStyle()" title="斜体">
<span style="font-style: italic">I</span>
</el-button>
</el-button-group>
......@@ -222,7 +220,7 @@
<div class="style-item">
<span class="style-label">边框圆角:</span>
<el-input-number v-model="textStyle.borderRadius" :min="0" :max="30" size="small"
@change="handleBorderRadiusChange">
@change="handleBorderRadiusChange">
</el-input-number>
</div>
<div class="style-item">
......@@ -284,7 +282,7 @@
<el-col :span="5">
<el-form-item label="归一化比例">
<el-slider @change="changeNor" style="width: 180px;" v-model="NormalizationBl" :min="0.1" :max="5"
:step="0.1" show-stops>
:step="0.1" show-stops>
</el-slider>
</el-form-item>
</el-col>
......@@ -813,6 +811,14 @@ export default {
// height: 0.1452145214521452
// }
// }],
// 添加鼠标悬停相关的数据
cursorInfo: {
depth: null,
trace: null,
value: null
},
showCursorInfo: true,
isHovering: false,
// 添加画笔工具相关属性
pencilTool: null,
selectedShape: null,
......@@ -881,6 +887,9 @@ export default {
'DashDot': [20, 4, 2, 4],
'DashDotDot': [20, 4, 2, 4, 2, 4]
},
isWidgetReady: false,
loadingError: null,
pipeline: null,
colorProvider: null,
// 右键菜单相关数据
contextMenu: {
......@@ -1154,7 +1163,7 @@ export default {
},
// 监听routeId变化
routeId(newId, oldId) {
// console.log('[index2] routeId变化:', newId, oldId);
//console.log('[index2] routeId变化:', newId, oldId);
if (newId !== oldId) {
this.handleRouteChange();
// 强制刷新缩略图
......@@ -1162,69 +1171,66 @@ export default {
}
}
},
// mounted() {
//
// console.log(1111)
// // 初始化时强制设置为初始状态
// this.resetToInitialState();
//
// // 立即禁用滚动条工具
// this.disableScrollbarTools();
//
// // 添加强制隐藏滚动条的CSS
// this.addForceHideScrollbarCSS();
//
// // 关闭强力滚动条监控,避免影响全局布局
// // this.startAggressiveScrollbarMonitor();
//
// this.$nextTick(async () => {
// ['thumb-test-container', 'thumb-chart-container', 'super-visible-thumb'].forEach(id => {
// const el = document.getElementById(id);
// if (el) try { el.remove(); } catch (e) { }
// });
// // 保持缩略图容器在组件内部,避免在DOM树中难以定位
// // 初始化场景时不自动加载演示数据,等待接口结果决定
// this.shouldLoadDemo = false;
// this.createScene(this.$refs.plot, { autoloadDemo: false });
// // 等DOM稳定后再渲染缩略图组件
// setTimeout(() => {
// this.thumbReady = true;
// // 再兜底:确保右上角出现内容,并直接绘制缩略图
// setTimeout(() => {
// this.ensureThumbRendered();
// this.drawThumbDirect();
// }, 200);
// }, 100);
// // 已改为内嵌 YsgcIndex 缩略图,不再走旧的 ECharts 缩略图初始化
//
// // 自动拉取接口数据并加载第一组上/下部 SEGY(jbsegy -> 顶部,xbsegy -> 底部)
// try {
// await this.loadThumbData();
// if (Array.isArray(this.segyList) && this.segyList.length > 0) {
// await this.loadCurrentSegy();
// } else {
// // 无接口数据:若没有路由id,允许加载演示数据;若有id但无数据,保持空白
// const hasId = !!this.routeId;
// if (!hasId) {
// this.shouldLoadDemo = true;
// try { await this.handleFileSelect(this.plots); } catch (e) { }
// } else {
// this.clearCurrentPlots();
// console.info('[index2] 该项目无可用SEGY数据');
// }
// }
// } catch (e) {
// console.warn('[index2] 自动加载数据失败:', e);
// // 加载失败时,如果没有路由ID,尝试加载演示数据
// if (!this.routeId) {
// this.shouldLoadDemo = true;
// try { await this.handleFileSelect(this.plots); } catch (e) { }
// }
// }
// });
// },
mounted() {
console.log(2222)
// 初始化时强制设置为初始状态
this.resetToInitialState();
// 立即禁用滚动条工具
this.disableScrollbarTools();
// 添加强制隐藏滚动条的CSS
this.addForceHideScrollbarCSS();
// 关闭强力滚动条监控,避免影响全局布局
// this.startAggressiveScrollbarMonitor();
this.$nextTick(async () => {
['thumb-test-container', 'thumb-chart-container', 'super-visible-thumb'].forEach(id => {
const el = document.getElementById(id);
if (el) try { el.remove(); } catch (e) { }
});
// 保持缩略图容器在组件内部,避免在DOM树中难以定位
// 初始化场景时不自动加载演示数据,等待接口结果决定
this.shouldLoadDemo = false;
this.createScene(this.$refs.plot, { autoloadDemo: false });
// 等DOM稳定后再渲染缩略图组件
setTimeout(() => {
this.thumbReady = true;
// 再兜底:确保右上角出现内容,并直接绘制缩略图
setTimeout(() => {
this.ensureThumbRendered();
this.drawThumbDirect();
}, 200);
}, 100);
// 已改为内嵌 YsgcIndex 缩略图,不再走旧的 ECharts 缩略图初始化
// 自动拉取接口数据并加载第一组上/下部 SEGY(jbsegy -> 顶部,xbsegy -> 底部)
try {
await this.loadThumbData();
if (Array.isArray(this.segyList) && this.segyList.length > 0) {
await this.loadCurrentSegy();
} else {
// 无接口数据:若没有路由id,允许加载演示数据;若有id但无数据,保持空白
const hasId = !!this.routeId;
if (!hasId) {
this.shouldLoadDemo = true;
try { await this.handleFileSelect(this.plots); } catch (e) { }
} else {
this.clearCurrentPlots();
console.info('[index2] 该项目无可用SEGY数据');
}
}
} catch (e) {
console.warn('[index2] 自动加载数据失败:', e);
// 加载失败时,如果没有路由ID,尝试加载演示数据
if (!this.routeId) {
this.shouldLoadDemo = true;
try { await this.handleFileSelect(this.plots); } catch (e) { }
}
}
});
},
mounted() {
// 将缩略图容器移动到body,确保fixed基于视口
if (this.$refs.thumbContainer && this.$refs.thumbContainer.parentNode !== document.body) {
document.body.appendChild(this.$refs.thumbContainer);
......@@ -2180,8 +2186,6 @@ export default {
//console.log('[index2] 加载segy文件 Top/Bottom:', (currentSegy.jbsegyName || currentSegy.jbsegy), (currentSegy.xbsegyName || currentSegy.xbsegy));
//console.log('[index2] segy文件路径 Top/Bottom:', currentSegy.jbsegy, currentSegy.xbsegy);
//
try {
// 双图加载:jbsegy -> 顶部;xbsegy -> 底部
// 根据后台返回的 ysqqXmxxSegy 数据,jbsegy 顶部展示,xbsegy 底部展示
......@@ -2205,9 +2209,6 @@ export default {
try { this._seismicWidget && this._seismicWidget.invalidate && this._seismicWidget.invalidate(); } catch (e) { }
// 关键修复:切换下一道后,强制将底部 widget 绑定为当前 pipeline,并重新插入绘制工具,防止短暂显示上一道
try {
// 确保 pipeline/路径键为当前
// await this.assertBottomPipelineCurrent();
this.refreshBottomDisplay();
// 确保 pipeline/路径键为当前(只在必要时调用,避免频繁刷新)
await this.assertBottomPipelineCurrent();
// 移除重复的 refreshBottomDisplay 调用,避免闪烁
......@@ -2467,7 +2468,7 @@ export default {
}
console.log('[renderAnnotationsFromXbbznr] 开始解析标注数据,数量:', annotationList.length);
// console.log('[renderAnnotationsFromXbbznr] annotationList 详细内容:', JSON.stringify(annotationList, null, 2));
console.log('[renderAnnotationsFromXbbznr] annotationList 详细内容:', JSON.stringify(annotationList, null, 2));
// 清空现有的底部 savedAnnotations 和 savedLineAnnotations
this.savedAnnotationsBottom = [];
......@@ -2878,143 +2879,116 @@ export default {
this.isHovering = false;
// 打开双图布局
// this.showDualChart();
this.showDualChart();
// 不再隐藏容器,避免首次绘制依赖鼠标事件触发
const topScroll = this.$refs.topScroll;
const bottomScroll = this.$refs.bottomScroll;
// if (this.annotationTool){
// // if (this.annotationTool.isEnabled() && this.annotationTool.getMode() === PaintMode.Edit && this.annotationTool.getShape() != null) {
// if (Array.isArray(this.annotationTool.getShape())) {
// this.annotationTool.getShape().forEach((shape) => shape.dispose());
// } else {
// this.annotationTool.getShape().dispose();
// }
// this.annotationTool.editNode(null);
// // }
// }
var theCanvas = document.getElementById("canvasTop");
var w = theCanvas.width;
var h = theCanvas.height;
var context = theCanvas.getContext("2d");
context.clearRect(0, 0, w, h);
if(this.plots){
this.plots.dispose();
}
if(this.plotsBottom){
this.plotsBottom.dispose();
}
// 清空现有图表
this.clearCurrentPlots();
//
// 清空 pipeline 引用,确保旧数据被清除
this.pipeline = null;
this.pipelineBottom = null;
this.plots = null;
this.plotsBottom = null;
// // 确保两个画布已初始化(如果widget不存在,才创建新的)
// if (this.$refs.plot && !this._seismicWidget) {
// this.createScene(this.$refs.plot, { autoloadDemo: false });
// }
// if (this.$refs.plot2 && !this._seismicWidgetBottom) {
// this.initSecondWidget(this.$refs.plot2);
// }
// // 重新绑定事件监听器
// this.$nextTick(() => {
// this.bindContextMenuListeners();
// });
//
// // 等待一小段时间,确保清空操作完成
// await new Promise(resolve => setTimeout(resolve, 50));
//
// // 顺序加载,分别统计成功与失败,但不立即刷新
// let topOk = false;
// let bottomOk = false;
// try {
// if (jbPath) {
// const topTitle = jbName || this.extractFileName(jbPath) || 'jbsegy';
// await this.loadSegyIntoWidget('top', jbPath, topTitle, false); // 不立即刷新
// topOk = true;
// }
// } catch (e) {
// console.warn('加载上部SEGY失败:', e);
// }
//
// try {
// if (xbPath) {
// const bottomTitle = xbName || this.extractFileName(xbPath) || 'xbsegy';
// await this.loadSegyIntoWidget('bottom', xbPath, bottomTitle, false); // 不立即刷新
// bottomOk = true;
// }
// } catch (e) {
// console.warn('加载下部SEGY失败:', e);
// }
//
// // 等待所有数据加载完成后再统一刷新
// if (topOk || bottomOk) {
// // 使用 requestAnimationFrame 确保在下一帧统一刷新
// await this.$nextTick();
// requestAnimationFrame(() => {
// try {
// if (topOk && this._seismicWidget) {
// if (this._seismicWidget.invalidate) {
// this._seismicWidget.invalidate();
// }
// this.setScrollbarCSS(this._seismicWidget);
// this.forceDisableInternalScrollbars();
// setTimeout(() => {
// try {
// if (this._seismicWidget && this._seismicWidget.fitToBounds) {
// this._seismicWidget.fitToBounds();
// }
// } catch (e) { }
// }, 100);
// }
// if (bottomOk && this._seismicWidgetBottom) {
// if (this._seismicWidgetBottom.invalidate) {
// this._seismicWidgetBottom.invalidate();
// }
// this.setScrollbarCSS(this._seismicWidgetBottom);
// this.forceDisableInternalScrollbars();
// setTimeout(() => {
// try {
// if (this._seismicWidgetBottom && this._seismicWidgetBottom.fitToBounds) {
// this._seismicWidgetBottom.fitToBounds();
// }
// // 额外在显示前进行一次尺寸同步与重绘
// try { this.updateBaseCanvasSize(); } catch (e) { }
// try { this.plotsBottom && typeof this.plotsBottom.redraw === 'function' && this.plotsBottom.redraw(); } catch (e) { }
// } catch (e) { }
// }, 100);
// }
// } catch (e) {
// console.warn('最终刷新widget时出错:', e);
// }
// });
//
// // 刷新完成后,额外进行一次尺寸同步与重绘,确保无悬浮也正确
// setTimeout(() => {
// try { this.updateBaseCanvasSize(); } catch (e) { }
// try { this._seismicWidget && this._seismicWidget.invalidate && this._seismicWidget.invalidate(); } catch (e) { }
// try { this._seismicWidgetBottom && this._seismicWidgetBottom.invalidate && this._seismicWidgetBottom.invalidate(); } catch (e) { }
// requestAnimationFrame(() => {
// try { this._seismicWidget && this._seismicWidget.invalidate && this._seismicWidget.invalidate(); } catch (e) { }
// try { this._seismicWidgetBottom && this._seismicWidgetBottom.invalidate && this._seismicWidgetBottom.invalidate(); } catch (e) { }
// });
// }, 200);
// } else {
// // 加载失败也做一次重绘尝试
// try { this._seismicWidget && this._seismicWidget.invalidate && this._seismicWidget.invalidate(); } catch (e) { }
// try { this._seismicWidgetBottom && this._seismicWidgetBottom.invalidate && this._seismicWidgetBottom.invalidate(); } catch (e) { }
// }
// 确保两个画布已初始化(如果widget不存在,才创建新的)
if (this.$refs.plot && !this._seismicWidget) {
this.createScene(this.$refs.plot, { autoloadDemo: false });
}
if (this.$refs.plot2 && !this._seismicWidgetBottom) {
this.initSecondWidget(this.$refs.plot2);
}
// 重新绑定事件监听器
this.$nextTick(() => {
this.bindContextMenuListeners();
});
// 等待一小段时间,确保清空操作完成
await new Promise(resolve => setTimeout(resolve, 50));
// 顺序加载,分别统计成功与失败,但不立即刷新
let topOk = false;
let bottomOk = false;
try {
if (jbPath) {
const topTitle = jbName || this.extractFileName(jbPath) || 'jbsegy';
await this.loadSegyIntoWidget('top', jbPath, topTitle, false); // 不立即刷新
topOk = true;
}
} catch (e) {
console.warn('加载上部SEGY失败:', e);
}
try {
if (xbPath) {
const bottomTitle = xbName || this.extractFileName(xbPath) || 'xbsegy';
await this.loadSegyIntoWidget('bottom', xbPath, bottomTitle, false); // 不立即刷新
bottomOk = true;
}
} catch (e) {
console.warn('加载下部SEGY失败:', e);
}
// 等待所有数据加载完成后再统一刷新
if (topOk || bottomOk) {
// 使用 requestAnimationFrame 确保在下一帧统一刷新
await this.$nextTick();
requestAnimationFrame(() => {
try {
if (topOk && this._seismicWidget) {
if (this._seismicWidget.invalidate) {
this._seismicWidget.invalidate();
}
this.setScrollbarCSS(this._seismicWidget);
this.forceDisableInternalScrollbars();
setTimeout(() => {
try {
if (this._seismicWidget && this._seismicWidget.fitToBounds) {
this._seismicWidget.fitToBounds();
}
} catch (e) { }
}, 100);
}
if (bottomOk && this._seismicWidgetBottom) {
if (this._seismicWidgetBottom.invalidate) {
this._seismicWidgetBottom.invalidate();
}
this.setScrollbarCSS(this._seismicWidgetBottom);
this.forceDisableInternalScrollbars();
setTimeout(() => {
try {
if (this._seismicWidgetBottom && this._seismicWidgetBottom.fitToBounds) {
this._seismicWidgetBottom.fitToBounds();
}
// 额外在显示前进行一次尺寸同步与重绘
try { this.updateBaseCanvasSize(); } catch (e) { }
try { this.plotsBottom && typeof this.plotsBottom.redraw === 'function' && this.plotsBottom.redraw(); } catch (e) { }
} catch (e) { }
}, 100);
}
} catch (e) {
console.warn('最终刷新widget时出错:', e);
}
});
// 刷新完成后,额外进行一次尺寸同步与重绘,确保无悬浮也正确
setTimeout(() => {
try { this.updateBaseCanvasSize(); } catch (e) { }
try { this._seismicWidget && this._seismicWidget.invalidate && this._seismicWidget.invalidate(); } catch (e) { }
try { this._seismicWidgetBottom && this._seismicWidgetBottom.invalidate && this._seismicWidgetBottom.invalidate(); } catch (e) { }
requestAnimationFrame(() => {
try { this._seismicWidget && this._seismicWidget.invalidate && this._seismicWidget.invalidate(); } catch (e) { }
try { this._seismicWidgetBottom && this._seismicWidgetBottom.invalidate && this._seismicWidgetBottom.invalidate(); } catch (e) { }
});
}, 200);
} else {
// 加载失败也做一次重绘尝试
try { this._seismicWidget && this._seismicWidget.invalidate && this._seismicWidget.invalidate(); } catch (e) { }
try { this._seismicWidgetBottom && this._seismicWidgetBottom.invalidate && this._seismicWidgetBottom.invalidate(); } catch (e) { }
}
},
// 将指定segy加载到顶部/底部widget
// skipRefresh: 如果为 true,则不立即刷新,等待统一刷新
......@@ -3184,7 +3158,7 @@ export default {
// 关键修复:如果 pipeline、路径和名称都没有变化,不需要重新设置,避免不必要的刷新
if (this._seismicWidgetBottom.getPipeline && this._seismicWidgetBottom.getPipeline() === pipeline &&
String(this._bottomSegyPath) === String(newPath) && String(this._bottomSegyKey) === String(newKey)) {
String(this._bottomSegyPath) === String(newPath) && String(this._bottomSegyKey) === String(newKey)) {
// 完全一致,不需要做任何操作
return;
}
......@@ -4681,11 +4655,11 @@ export default {
// 检查节点是否是文本节点
const isTextNode = (selectedNode.text !== undefined && selectedNode.text !== null) ||
(typeof selectedNode.getText === 'function');
(typeof selectedNode.getText === 'function');
// 检查节点是否是路径节点(线条)
const isPathNode = selectedNode.getType && selectedNode.getType() === 'Path' ||
(selectedNode.getProperties && selectedNode.getProperties().linestyle);
(selectedNode.getProperties && selectedNode.getProperties().linestyle);
if (isTextNode && this.annotationToolBottom) {
// 文本节点:使用 annotationToolBottom 编辑
......@@ -6286,9 +6260,6 @@ export default {
}
},
async handleFileSelect(plot) {
let segys = [];
......@@ -6411,7 +6382,7 @@ export default {
});
// Set the pipeline first
this.pipeline= pipeline;
this.pipeline = pipeline;
this._seismicWidget.setPipeline(pipeline);
// Then set widget options
......@@ -6517,11 +6488,11 @@ export default {
});
},
// handleMouseMove(event) {
// if (!this._seismicWidget || !this.isHovering) return;
//
// // 使用内置状态栏,不需要额外的处理逻辑
// },
handleMouseMove(event) {
if (!this._seismicWidget || !this.isHovering) return;
// 使用内置状态栏,不需要额外的处理逻辑
},
onFileOpen(evt, plot, fileInfo) {
evt.stopPropagation();
......@@ -6600,11 +6571,11 @@ export default {
// 检查节点是否是文本节点(通过检查是否有 text 属性或 getText 方法)
const isTextNode = (selectedNode.text !== undefined && selectedNode.text !== null) ||
(typeof selectedNode.getText === 'function');
(typeof selectedNode.getText === 'function');
// 检查节点是否是路径节点(线条)
const isPathNode = selectedNode.getType && selectedNode.getType() === 'Path' ||
(selectedNode.getProperties && selectedNode.getProperties().linestyle);
(selectedNode.getProperties && selectedNode.getProperties().linestyle);
if (isTextNode && this.annotationTool) {
// 文本节点:使用 annotationTool 编辑
......@@ -6888,185 +6859,185 @@ export default {
// 添加事件监听器来打印文本绘制数据
this.annotationTool.addListener(EditEvents.Start, (tool, command) => {
const node = command.getNode();
const node = command.getNode();
const textStyle = node.getTextStyle ? node.getTextStyle() : null;
// console.log('开始绘制文本:', {
// tool: tool.getMode(),
// node: {
// type: node.getType ? node.getType() : 'unknown',
// properties: node.getProperties(),
// bounds: node.getBounds ? node.getBounds() : null
// },
// textStyle: {
// font: textStyle ? textStyle.getFont() : null,
// color: textStyle ? textStyle.getColor() : null,
// size: textStyle ? textStyle.size || this.textStyle.size : this.textStyle.size,
// alignment: textStyle ? textStyle.getAlignment() : null
// },
// currentToolConfig: {
// mode: tool.getMode(),
// editMode: tool.getEditMode(),
// isEnabled: tool.isEnabled()
// }
// });
});
this.annotationTool.addListener(EditEvents.Change, (tool, event) => {
const node = tool.getShape();
if (node) {
const textStyle = node.getTextStyle ? node.getTextStyle() : null;
// console.log('开始绘制文本:', {
// tool: tool.getMode(),
// node: {
// type: node.getType ? node.getType() : 'unknown',
const previousState = node._previousState || {};
//console.log('文本绘制更新:', {
// previous: {
// properties: previousState.properties || {},
// textStyle: previousState.textStyle || {}
// },
// current: {
// properties: node.getProperties(),
// bounds: node.getBounds ? node.getBounds() : null
// bounds: node.getBounds ? node.getBounds() : null,
// textStyle: {
// font: textStyle ? textStyle.getFont() : null,
// color: textStyle ? textStyle.getColor() : null,
// size: textStyle ? textStyle.size || this.textStyle.size : this.textStyle.size,
// alignment: textStyle ? textStyle.getAlignment() : null
// }
// },
// textStyle: {
// font: textStyle ? textStyle.getFont() : null,
// color: textStyle ? textStyle.getColor() : null,
// size: textStyle ? textStyle.size || this.textStyle.size : this.textStyle.size,
// alignment: textStyle ? textStyle.getAlignment() : null
// },
// currentToolConfig: {
// mode: tool.getMode(),
// editMode: tool.getEditMode(),
// isEnabled: tool.isEnabled()
// }
// changes: this.detectChanges(previousState, node)
// });
});
this.annotationTool.addListener(EditEvents.Change, (tool, event) => {
const node = tool.getShape();
if (node) {
const textStyle = node.getTextStyle ? node.getTextStyle() : null;
const previousState = node._previousState || {};
//console.log('文本绘制更新:', {
// previous: {
// properties: previousState.properties || {},
// textStyle: previousState.textStyle || {}
// },
// current: {
// properties: node.getProperties(),
// bounds: node.getBounds ? node.getBounds() : null,
// textStyle: {
// font: textStyle ? textStyle.getFont() : null,
// color: textStyle ? textStyle.getColor() : null,
// size: textStyle ? textStyle.size || this.textStyle.size : this.textStyle.size,
// alignment: textStyle ? textStyle.getAlignment() : null
// }
// },
// changes: this.detectChanges(previousState, node)
// });
// 保存当前状态用于下次比较
node._previousState = {
properties: { ...node.getProperties() },
textStyle: textStyle ? {
font: textStyle.getFont(),
color: textStyle.getColor(),
size: textStyle.size,
alignment: textStyle.getAlignment()
} : {}
};
}
});
// 保存当前状态用于下次比较
node._previousState = {
properties: { ...node.getProperties() },
textStyle: textStyle ? {
font: textStyle.getFont(),
color: textStyle.getColor(),
size: textStyle.size,
alignment: textStyle.getAlignment()
} : {}
};
}
});
this.annotationTool.addListener(EditEvents.End, (tool, node) => {
if (node) {
// 修复:按优先级提取文本,即使前面的方法返回空也要继续尝试
let text = '';
// 方法1: 优先从 node.text 直接属性获取(最可靠)
if (node.text !== undefined && node.text !== null && String(node.text).trim() !== '') {
text = String(node.text);
console.log('[annotationTool][End] ✓ 从 node.text 获取文本:', text);
}
if (node) {
// 修复:按优先级提取文本,即使前面的方法返回空也要继续尝试
let text = '';
// 方法2: 如果 node.text 为空,尝试 getText() 方法
if (!text || text.trim() === '') {
try {
if (typeof node.getText === 'function') {
const methodText = node.getText();
if (methodText !== undefined && methodText !== null && String(methodText).trim() !== '') {
text = String(methodText);
console.log('[annotationTool][End] ✓ 从 getText() 获取文本:', text);
}
// 方法1: 优先从 node.text 直接属性获取(最可靠)
if (node.text !== undefined && node.text !== null && String(node.text).trim() !== '') {
text = String(node.text);
console.log('[annotationTool][End] ✓ 从 node.text 获取文本:', text);
}
// 方法2: 如果 node.text 为空,尝试 getText() 方法
if (!text || text.trim() === '') {
try {
if (typeof node.getText === 'function') {
const methodText = node.getText();
if (methodText !== undefined && methodText !== null && String(methodText).trim() !== '') {
text = String(methodText);
console.log('[annotationTool][End] ✓ 从 getText() 获取文本:', text);
}
} catch (e) {
console.warn('[annotationTool][End] getText() 调用失败:', e);
}
} catch (e) {
console.warn('[annotationTool][End] getText() 调用失败:', e);
}
}
// 方法3: 如果还是为空,尝试从 properties 中获取
if (!text || text.trim() === '') {
try {
if (node.getProperties && typeof node.getProperties === 'function') {
const props = node.getProperties();
if (props && props.text !== undefined && props.text !== null && String(props.text).trim() !== '') {
text = String(props.text);
console.log('[annotationTool][End] ✓ 从 properties.text 获取文本:', text);
}
// 方法3: 如果还是为空,尝试从 properties 中获取
if (!text || text.trim() === '') {
try {
if (node.getProperties && typeof node.getProperties === 'function') {
const props = node.getProperties();
if (props && props.text !== undefined && props.text !== null && String(props.text).trim() !== '') {
text = String(props.text);
console.log('[annotationTool][End] ✓ 从 properties.text 获取文本:', text);
}
} catch (e) {
console.warn('[annotationTool][End] getProperties() 调用失败:', e);
}
} catch (e) {
console.warn('[annotationTool][End] getProperties() 调用失败:', e);
}
}
// 如果文本为空,记录警告
if (!text || text.trim() === '') {
console.warn('[annotationTool][End] ⚠️ 警告:文本节点但 text 为空!', {
node,
nodeText: node.text,
hasGetText: typeof node.getText === 'function',
getTextResult: typeof node.getText === 'function' ? node.getText() : 'N/A',
properties: node.getProperties ? node.getProperties() : null
});
}
const textStyle = node.getTextStyle ? node.getTextStyle() : null;
console.log('[annotationTool][End] 绘制文本节点', {
// 如果文本为空,记录警告
if (!text || text.trim() === '') {
console.warn('[annotationTool][End] ⚠️ 警告:文本节点但 text 为空!', {
node,
text,
nodeText: node.text,
properties: node.getProperties && node.getProperties(),
textStyle: textStyle ? {
font: textStyle.getFont && textStyle.getFont(),
color: textStyle.getColor && textStyle.getColor(),
size: textStyle.size,
alignment: textStyle.getAlignment && textStyle.getAlignment()
} : null
hasGetText: typeof node.getText === 'function',
getTextResult: typeof node.getText === 'function' ? node.getText() : 'N/A',
properties: node.getProperties ? node.getProperties() : null
});
}
// 关键修复:如果获取到文本,立即缓存并同步到节点
if (text && text.trim() !== '') {
try {
// 1. 同步文本到 properties
const props = node.getProperties ? node.getProperties() : {};
if (!props.text || String(props.text).trim() === '') {
if (typeof node.setProperties === 'function') {
node.setProperties({ ...props, text: text });
console.log('[annotationTool][End] ✓ 已同步文本到 properties.text:', text);
}
const textStyle = node.getTextStyle ? node.getTextStyle() : null;
console.log('[annotationTool][End] 绘制文本节点', {
node,
text,
nodeText: node.text,
properties: node.getProperties && node.getProperties(),
textStyle: textStyle ? {
font: textStyle.getFont && textStyle.getFont(),
color: textStyle.getColor && textStyle.getColor(),
size: textStyle.size,
alignment: textStyle.getAlignment && textStyle.getAlignment()
} : null
});
// 关键修复:如果获取到文本,立即缓存并同步到节点
if (text && text.trim() !== '') {
try {
// 1. 同步文本到 properties
const props = node.getProperties ? node.getProperties() : {};
if (!props.text || String(props.text).trim() === '') {
if (typeof node.setProperties === 'function') {
node.setProperties({ ...props, text: text });
console.log('[annotationTool][End] ✓ 已同步文本到 properties.text:', text);
}
}
// 2. 立即缓存到 collectedAnnotations(使用节点引用作为标识)
try {
// 检查是否已存在相同节点的缓存
const existingIndex = this.collectedAnnotations.findIndex(item => item._nodeRef === node);
const cacheItem = {
type: 'text',
text: text,
properties: node.getProperties ? node.getProperties() : {},
textStyle: textStyle ? {
font: textStyle.getFont && textStyle.getFont(),
color: textStyle.getColor && textStyle.getColor(),
size: textStyle.size,
alignment: textStyle.getAlignment && textStyle.getAlignment()
} : null,
panel: 'top',
_nodeRef: node // 保存节点引用用于后续匹配
};
// 2. 立即缓存到 collectedAnnotations(使用节点引用作为标识)
try {
// 检查是否已存在相同节点的缓存
const existingIndex = this.collectedAnnotations.findIndex(item => item._nodeRef === node);
const cacheItem = {
type: 'text',
text: text,
properties: node.getProperties ? node.getProperties() : {},
textStyle: textStyle ? {
font: textStyle.getFont && textStyle.getFont(),
color: textStyle.getColor && textStyle.getColor(),
size: textStyle.size,
alignment: textStyle.getAlignment && textStyle.getAlignment()
} : null,
panel: 'top',
_nodeRef: node // 保存节点引用用于后续匹配
};
if (existingIndex >= 0) {
// 更新现有缓存
this.collectedAnnotations[existingIndex] = cacheItem;
console.log('[annotationTool][End] ✓ 已更新缓存项 #' + existingIndex, text);
} else {
// 添加新缓存
this.collectedAnnotations.push(cacheItem);
console.log('[annotationTool][End] ✓ 已缓存文本到 collectedAnnotations:', text, '总数:', this.collectedAnnotations.length);
}
} catch (e) {
console.warn('[annotationTool][End] 缓存失败:', e);
if (existingIndex >= 0) {
// 更新现有缓存
this.collectedAnnotations[existingIndex] = cacheItem;
console.log('[annotationTool][End] ✓ 已更新缓存项 #' + existingIndex, text);
} else {
// 添加新缓存
this.collectedAnnotations.push(cacheItem);
console.log('[annotationTool][End] ✓ 已缓存文本到 collectedAnnotations:', text, '总数:', this.collectedAnnotations.length);
}
} catch (e) {
console.warn('[annotationTool][End] 同步文本到 properties 失败:', e);
console.warn('[annotationTool][End] 缓存失败:', e);
}
} catch (e) {
console.warn('[annotationTool][End] 同步文本到 properties 失败:', e);
}
}
// 确保节点被添加到图层
if (this.annotations.indexOfChild(node) === -1) {
this.annotations.addChild(node);
console.log('[annotationTool][End] ✓ 已添加节点到图层');
}
// 确保节点被添加到图层
if (this.annotations.indexOfChild(node) === -1) {
this.annotations.addChild(node);
console.log('[annotationTool][End] ✓ 已添加节点到图层');
}
this.requestRepaint();
});
}
this.requestRepaint();
});
this.annotationTool.setEnabled(true);
......@@ -7947,7 +7918,8 @@ export default {
if (!this.checkWidgetStatus() || !this.isWidgetReady) return;
const widget = this.plots.getRoot();
// 修复:如果 plots 为 null,直接使用 _seismicWidget
const widget = (this.plots && this.plots.getRoot) ? this.plots.getRoot() : this._seismicWidget;
if (!widget) return;
// 确保注释层存在
......@@ -8014,9 +7986,42 @@ export default {
if (node && this.annotations.indexOfChild(node) === -1) {
this.annotations.addChild(node);
}
tool.setEditMode(EditMode.EditNode);
tool.editNode(node);
this.selectedShape = node;
// 关键修复:绘制完成后,确保节点保持可编辑属性
try {
const props = typeof node.getProperties === 'function' ? node.getProperties() : {};
if (!props.selectable || !props.movable || !props.editable) {
// 如果属性丢失,重新设置
const currentLineStyle = props.linestyle || new LineStyle({
color: this.lineStyle?.color || '#0351ad',
width: this.lineStyle?.width || 2,
pattern: this.getProcessedLinePattern ? this.getProcessedLinePattern(this.lineStyle?.pattern) : []
});
node.setProperties({
...props,
linestyle: currentLineStyle,
selectable: true,
movable: true,
editable: true
});
}
} catch (e) {
console.warn('设置线条节点编辑属性失败:', e);
}
// 修复:绘制完成后,保持编辑模式,让用户可以立即编辑刚绘制的线条
// 用户可以通过点击空白区域或按 ESC 来取消编辑,然后继续绘制新线条
try {
if (this.pencilTool && this.pencilTool.isEnabled() && this.showLineStylePanel) {
// 设置编辑模式,让用户可以选择和编辑刚绘制的线条
this.pencilTool.setEditMode(EditMode.EditNode);
this.pencilTool.editNode(node);
this.selectedShape = node;
}
} catch (e) {
console.warn('设置线条编辑模式失败:', e);
}
this.requestRepaint();
});
}
......@@ -9455,7 +9460,7 @@ export default {
border: none;
background: linear-gradient(145deg, #f8f9fa 0%, #e9ecef 100%);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08),
inset 0 1px 0 rgba(255, 255, 255, 0.8);
inset 0 1px 0 rgba(255, 255, 255, 0.8);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
......@@ -9476,7 +9481,7 @@ export default {
.toolbar-right .el-button:has(.el-icon-edit-outline):hover {
background: linear-gradient(145deg, #ff8c00 0%, #ffa500 100%);
box-shadow: 0 4px 12px rgba(255, 140, 0, 0.25),
0 2px 4px rgba(0, 0, 0, 0.1);
0 2px 4px rgba(0, 0, 0, 0.1);
}
.toolbar-right .el-button:has(.el-icon-edit-outline):hover [class^="el-icon-"] {
......@@ -9486,7 +9491,7 @@ export default {
.toolbar-right .el-button:has(.el-icon-edit-outline).active {
background: linear-gradient(145deg, #ff8c00 0%, #ffa500 100%);
box-shadow: 0 6px 16px rgba(255, 140, 0, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
inset 0 1px 0 rgba(255, 255, 255, 0.2);
}
.toolbar-right .el-button:has(.el-icon-edit-outline).active [class^="el-icon-"] {
......@@ -9501,7 +9506,7 @@ export default {
.toolbar-right .el-button:has(.el-icon-edit):hover {
background: linear-gradient(145deg, #28a745 0%, #34ce57 100%);
box-shadow: 0 4px 12px rgba(40, 167, 69, 0.25),
0 2px 4px rgba(0, 0, 0, 0.1);
0 2px 4px rgba(0, 0, 0, 0.1);
}
.toolbar-right .el-button:has(.el-icon-edit):hover [class^="el-icon-"] {
......@@ -9511,7 +9516,7 @@ export default {
.toolbar-right .el-button:has(.el-icon-edit).active {
background: linear-gradient(145deg, #28a745 0%, #34ce57 100%);
box-shadow: 0 6px 16px rgba(40, 167, 69, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
inset 0 1px 0 rgba(255, 255, 255, 0.2);
}
.toolbar-right .el-button:has(.el-icon-edit).active [class^="el-icon-"] {
......@@ -9530,7 +9535,7 @@ export default {
.toolbar-right .el-button:has(.el-icon-s-operation):hover {
background: linear-gradient(145deg, #9c27b0 0%, #ba68c8 100%);
box-shadow: 0 4px 12px rgba(156, 39, 176, 0.25),
0 2px 4px rgba(0, 0, 0, 0.1);
0 2px 4px rgba(0, 0, 0, 0.1);
}
.toolbar-right .el-button:has(.el-icon-s-operation):hover [class^="el-icon-"] {
......@@ -9540,7 +9545,7 @@ export default {
.toolbar-right .el-button:has(.el-icon-s-operation).active {
background: linear-gradient(145deg, #9c27b0 0%, #ba68c8 100%);
box-shadow: 0 6px 16px rgba(156, 39, 176, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
inset 0 1px 0 rgba(255, 255, 255, 0.2);
}
.toolbar-right .el-button:has(.el-icon-s-operation).active [class^="el-icon-"] {
......@@ -9563,7 +9568,7 @@ export default {
.toolbar-right .el-button:hover {
background: linear-gradient(145deg, #409EFF 0%, #66b1ff 100%);
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.25),
0 2px 4px rgba(0, 0, 0, 0.1);
0 2px 4px rgba(0, 0, 0, 0.1);
transform: translateY(-1px);
}
......@@ -9579,7 +9584,7 @@ export default {
.toolbar-right .el-button.active {
background: linear-gradient(145deg, #409EFF 0%, #66b1ff 100%);
box-shadow: 0 6px 16px rgba(64, 158, 255, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
inset 0 1px 0 rgba(255, 255, 255, 0.2);
transform: translateY(-1px);
}
......@@ -9879,27 +9884,27 @@ export default {
.CustomGrayScale {
background: linear-gradient(to right,
rgb(255, 255, 255) 50%,
/* -4000到181保持白色 */
rgb(253, 253, 253) 55%,
/* 181开始的颜色 */
rgb(240, 240, 240) 65%,
/* 到566的颜色 */
rgb(180, 180, 180) 80%,
/* 566后的过渡色 */
rgb(6, 6, 6) 100%
/* 最终颜色 */
);
rgb(255, 255, 255) 50%,
/* -4000到181保持白色 */
rgb(253, 253, 253) 55%,
/* 181开始的颜色 */
rgb(240, 240, 240) 65%,
/* 到566的颜色 */
rgb(180, 180, 180) 80%,
/* 566后的过渡色 */
rgb(6, 6, 6) 100%
/* 最终颜色 */
);
}
.CustomRedScale {
background: linear-gradient(to right,
#f62814,
#dd8b66,
#d7c692,
#fbfaff,
#6e6e8a,
#323136);
#f62814,
#dd8b66,
#d7c692,
#fbfaff,
#6e6e8a,
#323136);
}
.BlueWhiteRed {
......@@ -9908,25 +9913,25 @@ export default {
.Rainbow {
background: linear-gradient(to right,
#ff0000,
#ffff00,
#00ff00,
#00ffff,
#0000ff,
#ff00ff,
#ff0000);
#ff0000,
#ffff00,
#00ff00,
#00ffff,
#0000ff,
#ff00ff,
#ff0000);
}
.Jet {
background: linear-gradient(to right,
#00008f,
#0000ff,
#00ffff,
#00ff00,
#ffff00,
#ff7f00,
#ff0000,
#7f0000);
#00008f,
#0000ff,
#00ffff,
#00ff00,
#ffff00,
#ff7f00,
#ff0000,
#7f0000);
}
.Hot {
......@@ -10041,7 +10046,7 @@ export default {
.scrollbar-track,
.gt-scrollbar-thumb,
.gt-scrollbar-track,
/* 仅在地震图可滚动区域内隐藏内部滚动条相关"thumb/track",避免误伤缩略图 */
/* 仅在地震图可滚动区域内隐藏内部滚动条相关"thumb/track",避免误伤缩略图 */
.sync-section [class*="thumb"]:not(.thumb-holder):not(.thumb-title):not(.right-thumb),
.sync-section [class*="track"] {
display: none !important;
......
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