You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
729 lines
21 KiB
729 lines
21 KiB
<template> |
|
<div class="app-container"> |
|
<div class="el-header"> |
|
<el-form :inline="true" ref="mainForm" |
|
:model="formData" |
|
:rules="formRules" |
|
class="demo-form-inline"> |
|
<!-- 服务器配置部分 --> |
|
<el-form-item label="数据服务器IP:" prop="sqlseverIp"> |
|
<el-input v-model="formData.sqlseverIp"></el-input> |
|
</el-form-item> |
|
|
|
<el-form-item label="登录名" prop="userName"> |
|
<el-input v-model="formData.userName"></el-input> |
|
</el-form-item> |
|
|
|
<el-form-item label="密码" prop="password"> |
|
<el-input v-model="formData.password" type="password"></el-input> |
|
</el-form-item> |
|
|
|
<el-form-item label="数据库名称:" prop="dataBase"> |
|
<el-input v-model="formData.dataBase"></el-input> |
|
</el-form-item> |
|
|
|
<el-form-item> |
|
<el-button type="primary" @click="testConnection">测试连接</el-button> |
|
</el-form-item> |
|
|
|
|
|
</el-form> |
|
</div> |
|
<div class="el-left"> |
|
<div class="table-title">南方片数据交互</div> |
|
<el-form> |
|
<el-form-item style="margin-left: 15%" label="交互类型:" prop="softTypeValue"> |
|
<el-select style="margin-left: 5%" v-model="formData.softTypeValue" clearable placeholder="请选择交互类型"> |
|
<el-option |
|
v-for="item in softType" |
|
:key="item.value" |
|
:label="item.label" |
|
:value="item.value"> |
|
</el-option> |
|
</el-select> |
|
</el-form-item> |
|
<el-form-item style="margin-left: 15%" label="开始时间:" prop="startTime"> |
|
<el-date-picker |
|
style="margin-left: 5%" |
|
v-model="formData.startTime" |
|
type="month" |
|
placeholder="选择日期"> |
|
</el-date-picker> |
|
</el-form-item> |
|
|
|
<el-form-item style="margin-left: 15%" label="结束时间:" prop="endTime"> |
|
<el-date-picker |
|
style="margin-left: 5%" |
|
v-model="formData.endTime" |
|
type="month" |
|
placeholder="选择日期"> |
|
</el-date-picker> |
|
</el-form-item> |
|
|
|
<el-form-item label="类型:" style="margin-left: 15%" prop="stationTypeValue"> |
|
<el-select style="margin-left: 12%" v-model="formData.stationTypeValue" clearable placeholder="请选择类型"> |
|
<el-option |
|
v-for="item in stationType" |
|
:key="item.value" |
|
:label="item.label" |
|
:value="item.value"> |
|
</el-option> |
|
</el-select> |
|
</el-form-item> |
|
|
|
<el-form-item> |
|
<el-button style="float: right" type="primary" @click="submitForm">提交</el-button> |
|
</el-form-item> |
|
</el-form> |
|
</div> |
|
<div class="el-middle"> |
|
<div class="table-title">可选测站</div> |
|
<div style="width: 60%; margin-bottom: 5px; margin-left: 22%"> |
|
<el-input placeholder="请输入站点名称" v-model="filterText" clearable /> |
|
</div> |
|
<!-- 新增滚动容器 --> |
|
<div style="margin-left: 20%" class="tree-scroll-container"> |
|
<el-tree |
|
ref="tree" |
|
:data="options" |
|
:props="props" |
|
node-key="value" |
|
:filter-node-method="filterNode" |
|
@check-change="handleCheckChange" |
|
:highlight-current="true" |
|
default-expand-all |
|
show-checkbox |
|
/> |
|
</div> |
|
</div> |
|
<div class="el-main"> |
|
<el-table |
|
:data="tableData" |
|
ref="treeTable" |
|
:row-class-name="getRowClassName" |
|
row-key="id" |
|
:tree-props="{children: 'children'}" |
|
@select="handleSelect" |
|
@select-all="handleSelectAll" |
|
default-expand-all |
|
border |
|
> |
|
<!-- 多选列 --> |
|
<el-table-column |
|
type="selection" |
|
width="55" |
|
:reserve-selection="true"> |
|
</el-table-column> |
|
|
|
<!-- 树形内容列 --> |
|
<el-table-column label="所有表项"> |
|
<template slot-scope="{ row }"> |
|
<span :style="{ paddingLeft: (row.level * 18) + 'px' }"> |
|
{{ row.label }} |
|
</span> |
|
</template> |
|
</el-table-column> |
|
</el-table> |
|
</div> |
|
</div> |
|
</template> |
|
|
|
<script> |
|
import {treeselect} from "@/api/system/dept"; |
|
import Treeselect from "@riophae/vue-treeselect"; |
|
import "@riophae/vue-treeselect/dist/vue-treeselect.css"; |
|
import stationApi from "@/api/basic/station"; |
|
import dayRainApi from "@/api/day/dayRain"; |
|
|
|
|
|
export default { |
|
data() { |
|
return { |
|
formData: { |
|
sqlseverIp: '', |
|
userName: '', |
|
password: '', |
|
dataBase: '', |
|
softTypeValue: '', |
|
startTime: '', |
|
endTime: '', |
|
stationTypeValue: '', |
|
selectedValues: [] |
|
}, |
|
// 表单验证规则 |
|
formRules: { |
|
sqlseverIp: [ |
|
{required: true, message: '请输入数据服务器IP', trigger: 'blur'} |
|
], |
|
userName: [ |
|
{required: true, message: '请输入登录名', trigger: 'blur'} |
|
], |
|
password: [ |
|
{required: true, message: '请输入密码', trigger: 'blur'} |
|
], |
|
dataBase: [ |
|
{required: true, message: '请输入数据库名称', trigger: 'blur'} |
|
], |
|
softTypeValue: [ |
|
{required: true, message: '请选择交互类型', trigger: 'change'} |
|
], |
|
startTime: [ |
|
{required: true, message: '请选择开始时间', trigger: 'change'} |
|
], |
|
endTime: [ |
|
{required: true, message: '请选择结束时间', trigger: 'change'} |
|
], |
|
stationTypeValue: [ |
|
{required: true, message: '请选择类型', trigger: 'change'} |
|
], |
|
selectedValues: [ |
|
{required: true, message: '请选择站点', trigger: 'change'} |
|
] |
|
}, |
|
isAllSelected: false, |
|
tableData: [ |
|
{ |
|
id: 1, |
|
label: '基本信息', |
|
parentId: null, |
|
level: 0, |
|
checked: false, |
|
indeterminate: false, // 添加半选状态标识 |
|
children: [ |
|
{id: 11, parentId: 1, label: '测站信息一览表', level: 1, checked: false}, |
|
] |
|
}, |
|
{ |
|
id: 2, |
|
label: '雨量资料', |
|
level: 0, |
|
parentId: null, |
|
checked: false, |
|
indeterminate: false, // 添加半选状态标识 |
|
children: [ |
|
{id: 21, parentId: 2, label: '逐日降水表', level: 1, checked: false}, |
|
{id: 22, parentId: 2, label: '各时段最大降水表(1)', level: 1, checked: false}, |
|
{id: 23, parentId: 2, label: '各时段最大降水表(2)', level: 1, checked: false}, |
|
{id: 24, parentId: 2, label: '降水量摘录表', level: 1, checked: false}, |
|
] |
|
}, |
|
{ |
|
id: 3, |
|
label: '水位资料', |
|
level: 0, |
|
parentId: null, |
|
checked: false, |
|
indeterminate: false, // 添加半选状态标识 |
|
children: [ |
|
{id: 31, parentId: 3, label: '逐日平均水位表', level: 1, checked: false}, |
|
{id: 32, parentId: 3, label: '水闸洪水水文要素摘录表', level: 1, checked: false}, |
|
{id: 33, parentId: 4, label: '逐潮高低潮位表', level: 1, checked: false}, |
|
{id: 34, parentId: 4, label: '潮位月年统计表', level: 1, checked: false}, |
|
] |
|
}, |
|
{ |
|
id: 5, |
|
label: '流量资料', |
|
level: 0, |
|
parentId: null, |
|
checked: false, |
|
indeterminate: false, // 添加半选状态标识 |
|
children: [ |
|
{id: 51, parentId: 5, label: '逐日平均流量表', level: 1, checked: false}, |
|
] |
|
}, |
|
], |
|
softType: [{ |
|
value: '0', |
|
label: '导出数据到云服务数据库' |
|
}, { |
|
value: '1', |
|
label: '导入数据到南方片数据库' |
|
}], |
|
softTypeValue: '', |
|
stationType: [ |
|
{ |
|
value: 'P', |
|
label: '雨量站' |
|
}, |
|
{ |
|
value: 'H', |
|
label: '水文站' |
|
}, |
|
{ |
|
value: 'Q', |
|
label: '水位站' |
|
} |
|
], |
|
stationTypeValue: '', |
|
props: { |
|
multiple: true, |
|
checkStrictly: true,// 允许独立选择节点 |
|
value: 'value', // 确保与数据源字段名一致 |
|
label: 'label', |
|
disabled: 'disabled' |
|
}, |
|
componentKey: 0, |
|
options: [], |
|
stcds: '', |
|
filterText:"", |
|
selectedValues: [], // 绑定选中值 |
|
// 遮罩层 |
|
loading: true, |
|
// 显示搜索条件 |
|
showSearch: true, |
|
// 总条数 |
|
total: 0, |
|
// 弹出层标题 |
|
title: "", |
|
// 表单参数 |
|
form: {}, |
|
// 表单校验 |
|
rules: {}, |
|
dateYear: [], |
|
deptOptions: undefined, |
|
isHandlingCheckChange: false |
|
}; |
|
}, |
|
created() { |
|
}, |
|
watch: { |
|
filterText(val) { |
|
this.$refs['tree'].filter(val); |
|
}, |
|
'formData.startTime': { |
|
handler(newVal) { |
|
this.checkAndFetchData(newVal, this.formData.endTime, this.formData.stationTypeValue); |
|
}, |
|
immediate: true // 初始立即执行 |
|
}, |
|
'formData.endTime'(newVal) { |
|
this.checkAndFetchData(this.formData.startTime, newVal, this.formData.stationTypeValue); |
|
}, |
|
'formData.stationTypeValue'(newVal) { |
|
this.checkAndFetchData(this.formData.startTime, this.formData.endTime, newVal); |
|
} |
|
}, |
|
methods: { |
|
filterNode(value, data) { |
|
if (!value) return true; |
|
return data.label.indexOf(value) !== -1; |
|
}, |
|
handleCheckChange(data, checked, indeterminate) { |
|
// 防止递归调用 |
|
if (this.isHandlingCheckChange) return; |
|
this.isHandlingCheckChange = true; |
|
|
|
try { |
|
// 获取当前所有选中的节点(包括非叶子节点) |
|
const checkedKeys = this.$refs.tree.getCheckedKeys(); |
|
|
|
// 情况1:当前选中了"all"节点 |
|
if (data.value === 'all' && checked) { |
|
// 只保留"all"节点 |
|
this.$refs.tree.setCheckedKeys(['all']); |
|
this.stcds = 'all'; |
|
} |
|
// 情况2:当前选中了其他节点(非all) |
|
else if (checked && data.value !== 'all') { |
|
// 如果已存在"all"节点,先移除 |
|
if (checkedKeys.includes('all')) { |
|
const newCheckedKeys = checkedKeys.filter(key => key !== 'all'); |
|
this.$refs.tree.setCheckedKeys(newCheckedKeys); |
|
this.stcds = newCheckedKeys.join(","); |
|
} else { |
|
// 直接更新为当前选中的节点 |
|
this.stcds = this.$refs.tree.getCheckedKeys(true).join(","); |
|
} |
|
} |
|
// 情况3:取消选中操作 |
|
else { |
|
this.stcds = this.$refs.tree.getCheckedKeys(true).join(","); |
|
} |
|
} finally { |
|
this.isHandlingCheckChange = false; |
|
} |
|
}, |
|
getRowClassName({row}) { |
|
if (row.indeterminate) { |
|
return 'indeterminate-row'; // 应用半选样式 |
|
} |
|
return ''; |
|
}, |
|
// 查找父节点 |
|
findParent(parentId) { |
|
if (!parentId) return null; |
|
|
|
// 广度优先搜索父节点 |
|
const queue = [...this.tableData]; |
|
while (queue.length) { |
|
const node = queue.shift(); |
|
if (node.id === parentId) return node; |
|
if (node.children) queue.push(...node.children); |
|
} |
|
return null; |
|
}, |
|
|
|
// 1. 单行选择处理(父子联动) |
|
handleSelect(selection, row) { |
|
if (row.indeterminate && !selection.includes(row)) { |
|
this.clearIndeterminateState(row); // 新增方法 |
|
} else { |
|
row.checked = selection.includes(row); |
|
|
|
// 处理子节点 |
|
if (row.children) { |
|
this.toggleChildren(row, row.checked); |
|
} |
|
} |
|
// 更新父节点状态 |
|
this.updateParentStatus(row); |
|
}, |
|
// 新增:清除半选节点及子节点状态 |
|
clearIndeterminateState(node) { |
|
// 重置当前节点状态 |
|
node.checked = false; |
|
node.indeterminate = false; |
|
this.$refs.treeTable.toggleRowSelection(node, false); |
|
|
|
// 递归清除子节点 |
|
if (node.children) { |
|
node.children.forEach(child => { |
|
this.clearIndeterminateState(child); |
|
}); |
|
} |
|
}, |
|
// 2. 递归设置子节点状态 |
|
toggleChildren(node, isSelected) { |
|
if (node.children) { |
|
node.children.forEach(child => { |
|
// 设置子节点选中状态 |
|
this.$refs.treeTable.toggleRowSelection(child, isSelected); |
|
child.checked = isSelected; |
|
|
|
// 递归处理孙子节点 |
|
if (child.children) this.toggleChildren(child, isSelected); |
|
}); |
|
} |
|
}, |
|
|
|
// 3. 更新父节点状态(关键修复) |
|
// 在 updateParentStatus 中强化半选处理 |
|
updateParentStatus(childNode) { |
|
const parent = this.findParent(childNode.parentId); |
|
if (!parent) return; |
|
|
|
const checkedCount = parent.children.filter(c => c.checked).length; |
|
const indeterminateCount = parent.children.filter(c => c.indeterminate).length; // 新增 |
|
|
|
// 强化半选判断逻辑 |
|
parent.checked = checkedCount === parent.children.length; |
|
parent.indeterminate = |
|
(checkedCount > 0 && checkedCount < parent.children.length) || |
|
indeterminateCount > 0; // 关键修复[7](@ref) |
|
|
|
// 更新UI |
|
this.$refs.treeTable.toggleRowSelection(parent, parent.checked); |
|
this.$set(parent, 'indeterminate', parent.indeterminate); |
|
|
|
// 半选状态特殊处理 |
|
if (parent.indeterminate && !parent.checked) { |
|
// 强制清除可能残留的半选状态 |
|
parent.children.forEach(child => { |
|
if (child.indeterminate) { |
|
this.clearIndeterminateState(child); |
|
} |
|
}); |
|
} |
|
|
|
}, |
|
|
|
// 4. 全选/全不选处理(修复版) |
|
handleSelectAll(selection) { |
|
const isAllSelected = selection.length > 0; |
|
|
|
// 深度递归设置所有节点状态 |
|
const setAllSelection = (nodes, status) => { |
|
nodes.forEach(node => { |
|
// 设置当前节点状态 |
|
node.checked = status; |
|
node.indeterminate = false; // 清除半选状态 |
|
this.$refs.treeTable.toggleRowSelection(node, status); |
|
|
|
// 递归处理子节点 |
|
if (node.children && node.children.length) { |
|
setAllSelection(node.children, status); |
|
} |
|
}); |
|
}; |
|
|
|
setAllSelection(this.tableData, isAllSelected); |
|
|
|
// 特殊处理:取消全选时强制清空选中项 |
|
if (!isAllSelected) { |
|
this.$nextTick(() => { |
|
this.$refs.treeTable.clearSelection(); // 关键修复[1,3](@ref) |
|
}); |
|
} |
|
}, |
|
formatDateToYearMonth(date) { |
|
if (date == null) { |
|
return ''; |
|
} |
|
const d = new Date(date); |
|
if (isNaN(d.getTime())) { |
|
return ''; |
|
} |
|
const year = d.getFullYear(); |
|
const month = (d.getMonth() + 1).toString().padStart(2, '0'); |
|
return `${year}-${month}`; |
|
}, |
|
// 检查是否满足查询条件 |
|
checkAndFetchData(start, end, stationTypeValue) { |
|
if (start && end && stationTypeValue) { |
|
this.componentKey++; |
|
this.options = [] |
|
this.formData.selectedValues = [] |
|
this.getStaionList(start, end, stationTypeValue); |
|
|
|
} |
|
}, |
|
async testConnection() { |
|
const res = await stationApi.testSqlConnection(this.formData) |
|
if (res.code === 0) { |
|
this.$modal.msgSuccess(res.data) |
|
} else { |
|
this.$modal.msgError("链接失败") |
|
} |
|
}, |
|
async getStaionList(start, end, type) { |
|
const res = await stationApi.getStnmAndStcdByType(this.formatDateToYearMonth(start), |
|
this.formatDateToYearMonth(end), type); |
|
// 新增数据处理逻辑 |
|
const processTree = (nodes) => { |
|
return nodes.map(node => { |
|
const hasChildren = node.children && node.children.length > 0; |
|
return { |
|
...node, |
|
disabled: hasChildren, // 有子节点的父类自动禁用 |
|
children: hasChildren ? processTree(node.children) : null |
|
}; |
|
}); |
|
}; |
|
this.options = processTree([res.data]); // 注意保持数组结构 |
|
}, |
|
|
|
|
|
submitForm() { |
|
this.$refs.mainForm.validate(valid => { |
|
if (valid) { |
|
// 所有验证通过,执行提交操作 |
|
this.executeSubmit(this.formData); |
|
} else { |
|
// 验证失败,显示错误提示 |
|
this.$message.error('请填写所有必填项'); |
|
return false; |
|
} |
|
}); |
|
// this.executeSubmit(this.formData); |
|
}, |
|
getFilteredSelection() { |
|
const allSelected = this.$refs.treeTable.selection; // 获取所有选中节点[1](@ref) |
|
const result = []; |
|
|
|
// 遍历选中节点,按规则过滤 |
|
allSelected.forEach(node => { |
|
const isParent = node.children && node.children.length > 0; |
|
|
|
// 规则1:父节点被选中时,忽略其所有子节点 |
|
if (isParent && allSelected.some(n => n.parentId === node.id)) { |
|
return; // 跳过子节点 |
|
} |
|
|
|
// 规则2:子节点被选中时保留 |
|
result.push(node); |
|
}); |
|
|
|
return result; |
|
}, |
|
executeSubmit(formData) { |
|
const filteredData = this.getFilteredSelection(); |
|
const ids = filteredData.length |
|
? filteredData.map(item => item.id).join(',') |
|
: ''; |
|
const params = { |
|
sqlseverIp: this.formData.sqlseverIp, |
|
userName: this.formData.userName, |
|
password: this.formData.password, |
|
dataBase: this.formData.dataBase, |
|
startTime: this.formatDateToYearMonth(this.formData.startTime), |
|
endTime: this.formatDateToYearMonth(this.formData.endTime), |
|
softTypeValue: this.formData.softTypeValue, |
|
stationTypeValue: this.formData.stationTypeValue, |
|
stcds: this.stcds, |
|
ids: ids |
|
} |
|
stationApi.importDateToSoftOrExportSoft(params).then(res => { |
|
if (res.code === 0) { |
|
this.$modal.msgSuccess(res.msg); |
|
} else { |
|
this.$modal.msgError(res.msg); |
|
} |
|
}); |
|
}, |
|
// 取消按钮 |
|
cancel() { |
|
this.open = false; |
|
this.softOpen = false; |
|
this.reset(); |
|
}, |
|
// 表单重置 |
|
reset() { |
|
this.form = { |
|
startTime: '', |
|
endTime: '' |
|
} |
|
this.resetForm("form"); |
|
}, |
|
/** 搜索按钮操作 */ |
|
handleQuery() { |
|
this.queryParams.page = 1; |
|
this.getList(); |
|
}, |
|
/** 重置按钮操作 */ |
|
resetQuery() { |
|
this.resetForm("queryForm"); |
|
this.handleQuery(); |
|
}, |
|
}, |
|
mounted() { |
|
} |
|
}; |
|
</script> |
|
<style scoped lang="scss"> |
|
.app-container { |
|
background: #F2F2F2; |
|
|
|
.el-header { |
|
border-radius: 4px; |
|
background: #FFFFFF; |
|
color: #333; |
|
padding: 10px; |
|
height: 55px; |
|
box-shadow: 0 0 10px 1px rgba(123, 123, 123, 0.4); |
|
} |
|
|
|
//.el-header { |
|
// border-radius: 4px; |
|
// background: #FFFFFF; |
|
// color: #333; |
|
// padding: 10px; |
|
// height: 100px; |
|
// box-shadow: 0 0 10px 1px rgba(123, 123, 123, 0.4); |
|
//} |
|
|
|
|
|
.el-middle { |
|
.table-title{ |
|
font-size: 25px; |
|
font-weight: bold; |
|
width: 100%; |
|
height: 60px; |
|
// line-height: 30px; |
|
text-align: center; |
|
} |
|
border-radius: 4px; |
|
color: #333; |
|
overflow: hidden; /* 改为允许滚动条 */ |
|
background: #FFFFFF; |
|
text-align: center; |
|
height: calc(95vh - 120px); |
|
padding: 5px !important; |
|
float: left; |
|
width: 30%; |
|
margin-left: 20px; |
|
margin-top: 20px; |
|
padding-bottom: 15px !important; |
|
box-shadow: 0 0 10px 1px rgba(123, 123, 123, 0.4); |
|
/* 专用滚动容器样式 */ |
|
.tree-scroll-container { |
|
height: calc(100% - 40px); /* 减去输入框高度 */ |
|
overflow: auto; /* 双向滚动 */ |
|
} |
|
/* 修复横向滚动 */ |
|
.tree-scroll-container .el-tree { |
|
min-width: 100%; |
|
} |
|
.el-table .el-checkbox__input.is-indeterminate .el-checkbox__inner { |
|
background-color: #409EFF; |
|
border-color: #409EFF; |
|
} |
|
} |
|
|
|
.el-left { |
|
.table-title{ |
|
font-size: 25px; |
|
font-weight: bold; |
|
width: 100%; |
|
height: 60px; |
|
// line-height: 30px; |
|
text-align: center; |
|
} |
|
border-radius: 4px; |
|
color: #333; |
|
background: #FFFFFF; |
|
text-align: center; |
|
height: calc(95vh - 120px); |
|
padding: 5px !important; |
|
float: left; |
|
width: 25%; |
|
margin-top: 20px; |
|
padding-bottom: 15px !important; |
|
box-shadow: 0 0 10px 1px rgba(123, 123, 123, 0.4); |
|
|
|
.el-table .el-checkbox__input.is-indeterminate .el-checkbox__inner { |
|
background-color: #409EFF; |
|
border-color: #409EFF; |
|
} |
|
} |
|
|
|
.el-main { |
|
border-radius: 4px; |
|
color: #333; |
|
background: #FFFFFF; |
|
text-align: center; |
|
height: calc(95vh - 120px); |
|
padding: 5px !important; |
|
float: right; |
|
width: 43%; |
|
margin-top: 20px; |
|
padding-bottom: 15px !important; |
|
box-shadow: 0 0 10px 1px rgba(123, 123, 123, 0.4); |
|
|
|
.el-table .el-checkbox__input.is-indeterminate .el-checkbox__inner { |
|
background-color: #409EFF; |
|
border-color: #409EFF; |
|
} |
|
} |
|
} |
|
</style> |
|
<!-- 在组件中添加CSS样式 --> |
|
<style> |
|
/* 半选状态样式 */ |
|
.el-table .indeterminate-row .el-checkbox__inner { |
|
background-color: #409EFF; |
|
border-color: #409EFF; |
|
} |
|
|
|
.el-table .indeterminate-row .el-checkbox__inner::after { |
|
content: ""; |
|
position: absolute; |
|
display: block; |
|
background-color: white; |
|
height: 2px; |
|
width: 55%; |
|
top: 50%; |
|
left: 20%; |
|
transform: translateY(-50%); |
|
} |
|
</style>
|
|
|