<template>
|
<div class="bg-white rounded-lg p-4 flex flex-col w-[370px] gap-4">
|
<!-- 头部 -->
|
<div class="flex items-center flex-0">
|
<span class="ywifont ywicon-guanbi cursor-pointer mr-1.5" @click="handleClose"></span>
|
|
<span class="text-lg font-bold">对象批量查询</span>
|
</div>
|
|
<!-- 空间范围 -->
|
<div class="flex-0">
|
<div class="flex items-center gap-2 mb-2">
|
<div class="w-1 h-4 bg-blue-500 rounded-sm"></div>
|
<h4 class="font-medium">空间范围</h4>
|
</div>
|
<div class="flex gap-2">
|
<div
|
v-for="item in spatialTypes"
|
:key="item.value"
|
:title="item.label"
|
class="cursor-pointer flex-center w-8 h-8 rounded transition-colors"
|
:class="[selectedSpatialType === item.value ? 'bg-blue-100' : 'hover:bg-gray-100']"
|
style="border: 1px solid #e0e0e0"
|
@click="handleSpatialTypeSelect(item.value)"
|
>
|
<span
|
class="ywifont text-lg"
|
:class="['ywicon-' + item.icon, selectedSpatialType === item.value ? 'text-blue-500' : 'text-gray-600']"
|
></span>
|
</div>
|
</div>
|
</div>
|
|
<!-- 查询条件 -->
|
<div class="flex-0">
|
<div class="flex items-center justify-between mb-2">
|
<div class="flex items-center gap-2">
|
<div class="w-1 h-4 bg-blue-500 rounded-sm"></div>
|
<h4 class="font-medium">查询条件</h4>
|
</div>
|
</div>
|
|
<div class="space-y-3">
|
<el-form
|
ref="tmpConditionFormRef"
|
:model="tmpCondition"
|
label-width="0"
|
inline-message
|
:rules="tmpConditionRules"
|
class="condition-form"
|
>
|
<el-form-item prop="otype" class="!mb-3">
|
<!-- 添加Otype树形选择 -->
|
<el-tree-select
|
v-model="queryForm.otype"
|
:data="otypeList"
|
placeholder="请选择操作otype"
|
class="!w-full"
|
clearable
|
filterable
|
node-key="id"
|
@change="handleOtypeChange"
|
:props="defaultProps"
|
></el-tree-select>
|
</el-form-item>
|
|
<div class="flex gap-2">
|
<!-- 属性选择 -->
|
<el-form-item prop="property" class="!mb-0">
|
<el-tree-select
|
v-model="tmpCondition.property"
|
:data="propertyGroupList"
|
placeholder="属性"
|
class="!w-[120px]"
|
clearable
|
filterable
|
node-key="id"
|
default-expand-all
|
@change="handlePropertyChange"
|
:props="propertyGroupProps"
|
popper-class="custom-popper_w-180"
|
></el-tree-select>
|
</el-form-item>
|
|
<!-- 操作符选择 -->
|
<el-form-item prop="operator" class="!mb-0">
|
<el-select v-model="tmpCondition.operator" placeholder="操作" class="!w-[100px]">
|
<el-option v-for="op in operators" :key="op.value" :label="op.label" :value="op.value" />
|
</el-select>
|
</el-form-item>
|
|
<!-- 值输入 -->
|
<el-form-item prop="value" class="!mb-0">
|
<div class="flex items-center">
|
<el-input v-model="tmpCondition.value" placeholder="值" class="!flex-1" />
|
<span v-if="propertyMapRow?.[tmpCondition?.property]?.info?.unit" class="ml-1 text-gray-500">{{
|
propertyMapRow?.[tmpCondition?.property]?.info?.unit
|
}}</span>
|
</div>
|
</el-form-item>
|
|
<!-- 增加按钮 -->
|
<el-button type="primary" link @click="addCondition" class="!flex !items-center">
|
<span class="ywifont ywicon-jia"></span>
|
</el-button>
|
</div>
|
</el-form>
|
|
<div class="rounded-lg" style="border: 1px solid #e0e0e0">
|
<div
|
v-for="(condition, index) in queryForm.conditions"
|
:key="index"
|
class="flex items-center text-blue-400 py-2 px-4 rounded-md"
|
:style="{ borderBottom: index !== queryForm.conditions.length - 1 ? '1px solid #e0e0e0' : 'none' }"
|
>
|
<span>{{ `"${propertyMapRow[condition.vprop]?.title}"` }}</span>
|
<span> {{ operatorMap[condition.operator]?.label }} </span>
|
<span>{{ `"${condition.value}"` }}</span>
|
<el-button type="danger" link @click="removeCondition(index)" class="!flex !items-center ml-auto">
|
<span class="ywifont ywicon-shanchu"></span>
|
</el-button>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 查询结果 -->
|
<div class="flex-auto flex flex-col gap-2">
|
<div class="flex-0 flex items-center justify-between">
|
<div class="flex items-center gap-2">
|
<div class="w-1 h-4 bg-blue-500 rounded-sm"></div>
|
<h4 class="font-medium">查询结果</h4>
|
</div>
|
<div class="flex gap-2">
|
<el-button type="primary" @click="handleSearch">查询</el-button>
|
<el-button type="primary" @click="handleLocate">高亮</el-button>
|
<el-button type="primary" @click="handleExport">导出</el-button>
|
</div>
|
</div>
|
|
<!-- 结果表格 -->
|
<el-table v-loading="tableLoading" class="flex-auto" :data="tableData" style="width: 100%">
|
<el-table-column prop="OTYPE" label="类型" width="120" show-overflow-tooltip />
|
<el-table-column prop="ONAME" label="名称" show-overflow-tooltip />
|
<el-table-column prop="location" label="定位" width="60" fixed="right">
|
<template #default="{ row }">
|
<el-button type="text" @click="handleRowLocate(row)">
|
<div class="ywifont ywicon !text-[20px] ywicon-dangqianweizhi text-blue-500"></div>
|
</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
|
<!-- 分页 -->
|
<!-- <div class="flex items-center justify-between mt-2 text-sm">
|
<span>共 63 条</span>
|
<el-pagination v-model:current-page="currentPage" :page-size="pageSize" layout="prev, pager, next" :total="63" class="!m-0" />
|
<div class="flex items-center gap-2">
|
<el-select v-model="pageSize" class="!w-[100px]">
|
<el-option :value="10" label="10 条/页" />
|
<el-option :value="20" label="20 条/页" />
|
</el-select>
|
<span>跳至</span>
|
<el-input-number v-model="jumpPage" :min="1" :max="7" controls-position="right" class="!w-[60px]" />
|
<span>页</span>
|
</div>
|
</div> -->
|
</div>
|
</div>
|
</template>
|
|
<script setup lang="ts" name="AdvanceSearch">
|
import { saveAs } from 'file-saver';
|
import type { PropType } from 'vue';
|
import { computed, ref } from 'vue';
|
import * as XLSX from 'xlsx';
|
import { advanceSearchMapElementByPost } from '/@/api/map';
|
import type { OLMap } from '/@/model/map/OLMap';
|
import { formatDate } from '/@/utils/formatTime';
|
import { downloadExcel, travelTree } from '/@/utils/util';
|
import { deepClone } from '/@/utils/other';
|
|
const props = defineProps({
|
olMap: {
|
type: Object as PropType<OLMap>,
|
default: () => {},
|
},
|
propertyMap: {
|
type: Object as PropType<Record<string, any>>,
|
},
|
propertyConfigMap: {
|
type: Object as PropType<Record<string, any>>,
|
},
|
});
|
const layerInfo = computed(() => {
|
// const info = (props.olMap as OLMap)?.layerInfo.value
|
const info = (props.olMap as OLMap)?.layerInfo.value.filter((item) => item.type !== 'equip');
|
|
return info;
|
});
|
|
const otypeList = computed(() => {
|
return Object.keys(props.propertyMap ?? {}).map((key) => {
|
const value = props.propertyMap[key];
|
return {
|
id: key,
|
label: value.title,
|
vprops: value.vprops,
|
};
|
});
|
});
|
|
const propertyMapRow = ref(null);
|
const propertyGroupProps = {
|
label: 'title',
|
children: 'prop_list',
|
};
|
const propertyGroupList = computed(() => {
|
const otype = queryForm.value.otype;
|
if (!otype) return [];
|
const otypeProperty = props.propertyMap?.[otype] ?? {};
|
|
const vpropsMap = otypeProperty.vprops ?? {};
|
|
const config = props.propertyConfigMap?.[otype] ?? {};
|
const vpropsList: any[] = [];
|
|
const groupList = (config?.['sections']?.['WebGIS'] ?? []).map((group) => {
|
group.id = group.type;
|
group.prop_list = (group?.prop_list ?? [])
|
?.filter((item) => !['OTYPE', 'ONAME'].includes(item.vprop))
|
.map((item) => {
|
let info: any = vpropsMap[item.vprop] ?? {};
|
vpropsList.push(item.vprop);
|
const title = info.title;
|
info = {
|
...info,
|
value: null,
|
};
|
const row = {
|
id: `${item.vprop}`,
|
...item,
|
info,
|
title: info.title ?? '-',
|
};
|
if (!propertyMapRow.value) {
|
propertyMapRow.value = {};
|
}
|
propertyMapRow.value[row.id] = row;
|
return row;
|
});
|
return group;
|
});
|
return groupList;
|
});
|
|
const getRowById = (id: string) => {
|
if (!id) return null;
|
let row = null;
|
travelTree(
|
propertyGroupList.value as any[],
|
(value) => {
|
if (value.id === id) {
|
row = value;
|
return true;
|
}
|
},
|
null,
|
false,
|
'prop_list'
|
);
|
return row;
|
};
|
|
// 查询表单
|
const queryForm = ref({
|
otype: '',
|
conditions: [],
|
});
|
const handleOtypeChange = (value: string) => {
|
queryForm.value.conditions = [];
|
tmpCondition.value.property = '';
|
tmpCondition.value.operator = '';
|
tmpCondition.value.value = '';
|
tmpConditionFormRef.value?.resetFields();
|
};
|
|
const handleClose = () => {
|
props.olMap.removeDrawLayer();
|
props.olMap.clearObjectSearch();
|
emit('close');
|
};
|
|
const handlePropertyChange = (value: string) => {};
|
const defaultProps = {
|
children: 'children',
|
label: 'label',
|
};
|
|
const tmpCondition = ref({
|
property: '',
|
operator: '',
|
value: '',
|
});
|
|
const tmpConditionRules = {
|
property: [{ required: true, message: '请选择属性' }],
|
operator: [{ required: true, message: '请选择操作' }],
|
value: [{ required: true, message: '请输入值', trigger: '' }],
|
};
|
|
// 可选字段
|
const fields = [
|
{ label: '管道类型', value: 'pipeType' },
|
{ label: '管径', value: 'diameter' },
|
{ label: '材质', value: 'material' },
|
{ label: '起点', value: 'startPoint' },
|
{ label: '终点', value: 'endPoint' },
|
];
|
|
// 操作符
|
const operators = [
|
{ label: '等于', value: 'eq' },
|
{ label: '不等于', value: 'ne' },
|
{ label: '大于', value: 'gt' },
|
{ label: '大于等于', value: 'gte' },
|
{ label: '小于', value: 'lt' },
|
{ label: '小于等于', value: 'lte' },
|
{ label: '包含', value: 'contains' },
|
];
|
|
const getOperatorMap = () => {
|
const operatorMap = operators.reduce((map, item) => {
|
map[item.value] = item;
|
return map;
|
}, {});
|
return operatorMap;
|
};
|
|
const operatorMap = getOperatorMap();
|
|
const tmpConditionFormRef = ref();
|
|
// 添加条件
|
const addCondition = () => {
|
tmpConditionFormRef.value?.validate().then((isValid) => {
|
if (isValid) {
|
queryForm.value.conditions.push({
|
vprop: tmpCondition.value.property,
|
operator: tmpCondition.value.operator,
|
value: tmpCondition.value.value,
|
});
|
tmpConditionFormRef.value.resetFields();
|
tmpCondition.value = {
|
property: '',
|
operator: '',
|
value: '',
|
};
|
}
|
});
|
};
|
|
// 删除条件
|
const removeCondition = (index: number) => {
|
queryForm.value.conditions.splice(index, 1);
|
};
|
|
// 错误提示
|
const errorMsg = ref('管道口径 大于 100 mm');
|
|
// 表格数据
|
const tableData = ref([]);
|
|
// 分页相关
|
const currentPage = ref(1);
|
const pageSize = ref(10);
|
const jumpPage = ref(1);
|
const tableLoading = ref(false);
|
// 按钮事件处理
|
const handleSearch = async () => {
|
// 实现查询逻辑
|
|
let extent;
|
switch (selectedSpatialType.value) {
|
case 'global':
|
break;
|
case 'currentView':
|
// 获取当前地图视图范围
|
extent = props.olMap.map.getView().calculateExtent();
|
break;
|
case 'rectangle':
|
extent = props.olMap.drawLayer?.getSource().getExtent();
|
break;
|
case 'polygon':
|
break;
|
}
|
const conditions = deepClone(queryForm.value.conditions).map((item) => {
|
const id = item.vprop;
|
const operate = item.operator;
|
const row = getRowById(id);
|
Reflect.deleteProperty(item, 'operator');
|
return {
|
...item,
|
vprop: row?.vprop,
|
operate: operate,
|
};
|
});
|
tableLoading.value = true;
|
const res = await advanceSearchMapElementByPost({
|
extent,
|
max_count: 100,
|
time: formatDate(new Date()),
|
otype: queryForm.value.otype,
|
condtions: JSON.stringify(conditions),
|
})
|
.catch(() => {
|
tableData.value = [];
|
})
|
.finally(() => {
|
tableLoading.value = false;
|
});
|
tableData.value = res.values ?? [];
|
};
|
|
const handleLocate = () => {
|
// 实现高亮定位逻辑
|
props.olMap.clearObjectSearch();
|
const features = tableData.value
|
.filter((item) => item.WKT)
|
.map((item) => {
|
return props.olMap.readWKT(item.WKT);
|
});
|
props.olMap.highlightSearch(features);
|
// props.olMap.zoomToFeatures(features);
|
};
|
|
const handleExport = () => {
|
if (!tableData.value?.length) return;
|
// 准备导出数据
|
const exportData = tableData.value.map((item) => ({
|
类型: item.OTYPE,
|
名称: item.ONAME,
|
位置: item.WKT,
|
}));
|
downloadExcel(exportData, '查询结果');
|
};
|
|
const handleRowLocate = (row: any) => {
|
// 实现单行定位逻辑
|
props.olMap.displaySearchResult(row);
|
};
|
|
// 空间范围类型
|
const spatialTypes = [
|
{ label: '全局', value: 'global', icon: 'diqiu' },
|
{ label: '当前视图', value: 'currentView', icon: 'dangqianshitu' },
|
{ label: '矩形', value: 'rectangle', icon: 'juxing' },
|
// { label: '多边形选择', value: 'polygon', icon: 'bangong' },
|
];
|
|
const selectedSpatialType = ref('global');
|
|
let preSelectedSpatialType = '';
|
// 选择空间范围类型
|
const handleSpatialTypeSelect = (type: string) => {
|
// if (preSelectedSpatialType === type) return;
|
selectedSpatialType.value = type;
|
// 触发选择事件
|
emit('selectSpatial', type);
|
preSelectedSpatialType = type;
|
props.olMap.removeDrawLayer();
|
|
switch (type) {
|
case 'rectangle':
|
props.olMap.drawRectangle();
|
break;
|
case 'polygon':
|
break;
|
}
|
};
|
|
const emit = defineEmits(['close', 'selectSpatial', 'clearSpatial']);
|
</script>
|
|
<style scoped lang="scss">
|
:deep(.el-input-number) {
|
.el-input__wrapper {
|
padding-left: 0.75rem !important;
|
padding-right: 0.75rem !important;
|
}
|
}
|
|
:deep(.el-select) {
|
.el-input__wrapper {
|
padding-left: 0.75rem !important;
|
padding-right: 0.75rem !important;
|
}
|
}
|
|
:deep(.el-tree-select) {
|
.el-input__wrapper {
|
padding-left: 0.75rem !important;
|
padding-right: 0.75rem !important;
|
}
|
}
|
|
.flex-center {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
}
|
|
.condition-form {
|
.el-form-item {
|
margin-bottom: 0;
|
:deep(.el-form-item__content) {
|
flex-direction: column;
|
}
|
}
|
}
|
</style>
|
|
<style lang="scss">
|
.custom-popper_w-180 {
|
.el-tree {
|
width: 180px !important;
|
}
|
}
|
</style>
|