123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752 |
- <template>
- <div class="menu-content">
- <div class="gradient-text title">定点分析</div>
- <div class="scroll-box">
- <div v-if="!tempState.address" class="search-box">
- <div class="text-box">
- <i class="icon-position2" />
- <div class="text1" :title="selectData.event_title">{{ selectData.event_title }}</div>
- </div>
- <div class="common-btn-primary3" @click="toSelect">定点选取</div>
- </div>
- <div v-else class="search-box">
- <div class="text-box">
- <i class="icon-position2" />
- <div class="text1" :title="tempState.address">{{ tempState.address }}</div>
- </div>
- <div class="common-btn-primary4" @click="confirmSelect">确定定位</div>
- </div>
- <div class="search-item">
- <div class="text1">选择救灾资源:</div>
- <el-select
- v-model="queryParams.dataType"
- class="custom-select select-box"
- placeholder="请选择"
- popper-class="custom-select-popper"
- :teleported="false"
- >
- <el-option v-for="item in disaster_relief_material" :key="item.value" :label="item.label" :value="item.value"></el-option>
- </el-select>
- </div>
- <div class="search-item2">
- <el-input v-model="queryParams.keyword" class="custom-input2" placeholder="请输入关键字搜索" />
- <div class="common-btn-primary4" @click="getList">搜索</div>
- </div>
- <div class="search-item3">
- <el-checkbox v-model="checked1">自定义距离</el-checkbox>
- <el-input v-model="distance" class="custom-input2" placeholder="自定义距离(公里)" />
- <div class="common-btn3" @click="getList">确定</div>
- </div>
- <div class="list2">
- <div class="text-box1">
- 总数:
- <div class="gradient-text2">{{ total }}</div>
- 个
- </div>
- <div class="list-content">
- <div v-for="(item, index) in dataList" :key="index" class="list-item" @click="handleRoutes(item)">
- <div class="text-box2">
- <div class="text2">{{ item.name }}</div>
- <div class="text3">{{ item.address }}</div>
- </div>
- <div class="operate">
- <i class="icon2" />
- 路线
- </div>
- </div>
- </div>
- </div>
- <div v-show="showAddress && !!routeData && routeData.length > 0" class="route-box">
- <div class="route-item1">
- <i class="icon-position3" />
- <div class="text1">路线始点:</div>
- <div class="text2" :title="routesAddress">{{ routesAddress }}</div>
- <i class="icon-close2" @click="showAddress = false" />
- </div>
- <div class="route-list">
- <div
- v-for="(item, index) in routeData"
- :key="index"
- :class="index === selectIndex ? 'route-item2 route-item2-active' : 'route-item2'"
- @click="drawRoute(item, index)"
- >
- <div class="text-box1">
- <div :class="'tag tag' + index">{{ getTag(index) }}</div>
- <!-- <div class="text1">{{ item.strategy }}</div>-->
- </div>
- <div class="route-info">
- <div class="text-box2">
- <i class="icon1" />
- <div class="gradient-text2">{{ item.duration[0] }}</div>
- <div class="text2">小时</div>
- <div class="gradient-text2">{{ item.duration[1] }}</div>
- <div class="text2">分钟</div>
- </div>
- <div class="line"></div>
- <div class="text-box2">
- <i class="icon2" />
- <div class="gradient-text2">{{ item.distance / 1000 }}</div>
- <div class="text2">公里</div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script lang="ts" setup name="FixedPointAnalysis">
- import BigNumber from 'bignumber.js';
- import { getEmergencyRescuePointInfoList } from '@/api/globalMap';
- import markImg from '@/assets/images/map/mark.png';
- import startImg from '@/assets/images/map/start.png';
- import endImg from '@/assets/images/map/end.png';
- interface Props {
- location?: string | number[];
- activeMap: string;
- }
- const AMapType = ['vectorgraph', 'satellite'];
- const props = withDefaults(defineProps<Props>(), {});
- const { proxy } = getCurrentInstance() as ComponentInternalInstance;
- const { disaster_relief_material } = toRefs<any>(proxy?.useDict('disaster_relief_material'));
- import gcoord from 'gcoord';
- import Icon from 'ol/style/Icon';
- import Feature from 'ol/Feature';
- import Point from 'ol/geom/Point';
- import Style from 'ol/style/Style';
- import { LineString } from 'ol/geom';
- import { Stroke } from 'ol/style';
- import useMapStore from '@/store/modules/map';
- const mapStore = useMapStore();
- const amapKey = 'e45d4caa2bef3c84714a2ed9b1e27d98';
- let getMapUtils = inject('getMapUtils');
- let showAddress = ref(true);
- let routeData = ref([]);
- let routeLine, startMarker, endMarker;
- let routesAddress = ref('');
- let AMap, map;
- let eventList = ref([]);
- let selectData = ref({
- event_title: '',
- longitude: '',
- latitude: ''
- });
- let selectMarker;
- let tempState = reactive({
- address: '',
- longitude: '',
- latitude: ''
- });
- let checked1 = ref(false);
- let distance = ref('');
- let queryParams = reactive({
- keyword: '',
- dataType: '2'
- });
- let total = ref(0);
- let dataList = ref([]);
- const toSelect = () => {
- map.on('click', handleClickMap);
- mapStore.setIsMapSelect(true);
- showAddress.value = false;
- routeData.value = [];
- clearMarker();
- clearLine();
- };
- const handleClickMap = (e) => {
- if (AMapType.includes(props.activeMap)) {
- map.off('click', handleClickMap);
- } else {
- map.un('click', handleClickMap);
- }
- mapStore.setIsMapSelect(false);
- getAddress([e.lnglat.getLng(), e.lnglat.getLat()]);
- };
- const confirmSelect = () => {
- selectEvent(
- {
- event_title: tempState.address,
- longitude: tempState.longitude,
- latitude: tempState.latitude
- },
- true
- );
- };
- // 选中事件
- const selectEvent = (item, unFitView) => {
- eventList.value.forEach((item2) => {
- if (item2.event_id === item.event_id) {
- item2.checked = true;
- } else {
- item2.checked = false;
- }
- });
- selectData.value = item;
- tempState.address = '';
- tempState.longitude = '';
- tempState.latitude = '';
- getList();
- };
- const createMarks = (item, unFitView) => {
- clearMarker();
- if (AMapType.includes(props.activeMap)) {
- // 以 icon URL 的形式创建一个途经点
- const icon = new AMap.Icon({
- size: new AMap.Size(19, 31),
- image: markImg
- });
- selectMarker = new AMap.Marker({
- position: new AMap.LngLat(item.longitude, item.latitude),
- icon: icon,
- offset: new AMap.Pixel(-13, -30)
- });
- selectMarker.setMap(map);
- if (!unFitView) {
- map.setFitView([selectMarker]);
- }
- } else {
- const icon = new Icon({
- src: markImg,
- width: 19,
- height: 31
- });
- const feature = new Feature({
- geometry: new Point([item.longitude, item.latitude])
- });
- feature.setStyle(
- new Style({
- image: icon
- })
- );
- const vectorLayer = getMapUtils().getVectorLayer();
- vectorLayer.getSource().addFeature(feature);
- if (!unFitView) {
- map.getView().setCenter([item.longitude, item.latitude]);
- }
- }
- };
- let selectIndex = ref(-1);
- const handleRoutes = async (item) => {
- selectIndex.value = -1;
- const lnglat = gcoord.transform([item.longitude, item.latitude], gcoord.WGS84, gcoord.GCJ02);
- const lnglat2 = gcoord.transform([selectData.value.longitude, selectData.value.latitude], gcoord.WGS84, gcoord.GCJ02);
- const start = [lnglat[0].toFixed(6), lnglat[1].toFixed(6)];
- const end = [lnglat2[0].toFixed(6), lnglat2[1].toFixed(6)];
- showAddress.value = true;
- routesAddress.value = item.address;
- const url = `https://restapi.amap.com/v5/direction/driving?origin=${start.toString()}&destination=${end.toString()}&key=${amapKey}`;
- const response = await fetch(url + '&strategy=32&show_fields=polyline,cost');
- if (!!response.ok) {
- const data = await response.json();
- if (data.route && data.route.paths) {
- data.route.paths.forEach((item) => {
- item.duration = formatDate(item.cost?.duration);
- });
- routeData.value = data.route.paths;
- // 默认展示第一条
- drawRoute(routeData.value[0], 0);
- } else {
- routeData.value = [];
- }
- }
- };
- const drawRoute = (route, index) => {
- selectIndex.value = index;
- const path = parseRouteToPath(route);
- clearLine();
- if (AMapType.includes(props.activeMap)) {
- const icon1 = new AMap.Icon({
- size: new AMap.Size(19, 31),
- image: startImg
- });
- const icon2 = new AMap.Icon({
- size: new AMap.Size(19, 31),
- image: endImg
- });
- startMarker = new AMap.Marker({
- position: path[0],
- icon: icon1,
- offset: new AMap.Pixel(-13, -30),
- map: map
- });
- endMarker = new AMap.Marker({
- position: path[path.length - 1],
- icon: icon2,
- offset: new AMap.Pixel(-13, -30),
- map: map
- });
- routeLine = new AMap.Polyline({
- path: path,
- isOutline: true,
- outlineColor: '#ffeeee',
- borderWeight: 2,
- strokeWeight: 5,
- strokeColor: '#0091ff',
- lineJoin: 'round'
- });
- routeLine.setMap(map);
- // 调整视野达到最佳显示区域
- map.setFitView([startMarker, endMarker, routeLine]);
- } else {
- const vectorLayer = getMapUtils().getVectorLayer();
- const source = vectorLayer.getSource();
- startMarker = new Feature({
- geometry: new Point(path[0])
- });
- endMarker = new Feature({
- geometry: new Point(path[path.length - 1])
- });
- startMarker.setStyle(
- new Style({
- image: new Icon({
- src: startImg,
- width: 19,
- height: 31
- })
- })
- );
- endMarker.setStyle(
- new Style({
- image: new Icon({
- src: endImg,
- width: 19,
- height: 31
- })
- })
- );
- routeLine = new Feature({
- geometry: new LineString(path)
- });
- routeLine.setStyle(
- new Style({
- stroke: new Stroke({
- color: '#0091ff', // 主线条颜色
- width: 5, // 主线条宽度
- lineCap: 'round', // 线帽样式(可选)
- lineJoin: 'round' // 线连接样式
- })
- })
- );
- source.addFeature(startMarker);
- source.addFeature(routeLine);
- source.addFeature(endMarker);
- }
- };
- // 解析DrivingRoute对象,构造成AMap.Polyline的path参数需要的格式
- // DrivingResult对象结构参考文档 https://lbs.amap.com/api/javascript-api/reference/route-search#m_DriveRoute
- const parseRouteToPath = (route) => {
- var path = [];
- for (var i = 0, l = route.steps.length; i < l; i++) {
- var step = route.steps[i];
- // 按分号分割字符串,得到一个包含经纬度对的数组
- const coordinatesPairs = step.polyline.split(';');
- // 将每个经纬度对进一步分割成经度和纬度,并转换为一个对象
- path = coordinatesPairs.map((pair) => {
- const [longitude, latitude] = pair.split(',');
- return gcoord.transform([longitude, latitude], gcoord.GCJ02, gcoord.WGS84);
- });
- }
- return path;
- };
- function formatDate(seconds: number) {
- // 将秒数转换为BigNumber
- const secondsBn = new BigNumber(seconds);
- // 计算小时数
- const hoursBn = secondsBn.dividedBy(3600).integerValue(BigNumber.ROUND_DOWN);
- // 计算剩余的秒数,然后转换为分钟数
- const remainingSecondsBn = secondsBn.minus(hoursBn.times(3600));
- const minutesBn = remainingSecondsBn.dividedBy(60).integerValue();
- // 将BigNumber转换为字符串,并使用padStart添加前导零
- const hours = hoursBn.toString();
- const minutes = minutesBn.toString();
- // 返回包含小时和分钟的数组
- return [hours, minutes];
- }
- const getTag = (index) => {
- const arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
- return '路线' + (arr[index] ? arr[index] : index);
- };
- // 搜索列表
- const getList = () => {
- const params = {
- longitude: selectData.value.longitude,
- latitude: selectData.value.latitude,
- dataType: queryParams.dataType,
- keyword: queryParams.keyword,
- radius: '30'
- };
- if (!!checked1.value) {
- params.radius = distance.value;
- }
- getEmergencyRescuePointInfoList(params).then((res) => {
- dataList.value = res.rows;
- total.value = res.total;
- });
- };
- const getAddress = async (location) => {
- const gcj02Coord = gcoord.transform(location, gcoord.WGS84, gcoord.GCJ02);
- tempState.longitude = location[0];
- tempState.latitude = location[1];
- // 调用高德逆向地理编码 API
- const response = await fetch(`https://restapi.amap.com/v3/geocode/regeo?key=${amapKey}&location=${gcj02Coord[0]},${gcj02Coord[1]}`);
- const result = await response.json();
- // 解析地址信息
- if (result.status === '1' && result.regeocode) {
- tempState.address = result.regeocode.formatted_address;
- }
- createMarks({ longitude: location[0], latitude: location[1] }, true);
- };
- const clearMarker = () => {
- if (!selectMarker) return;
- if (AMapType.includes(props.activeMap)) {
- selectMarker.remove();
- selectMarker = null;
- } else {
- const vectorLayer = getMapUtils().getVectorLayer();
- const source = vectorLayer.getSource();
- source.removeFeature(selectMarker);
- selectMarker = null;
- }
- };
- const clearLine = () => {
- if (AMapType.includes(props.activeMap)) {
- if (!!startMarker) {
- startMarker.remove();
- startMarker = null;
- }
- if (!!endMarker) {
- endMarker.remove();
- endMarker = null;
- }
- if (!!routeLine) {
- routeLine.remove();
- routeLine = null;
- }
- } else {
- const vectorLayer = getMapUtils().getVectorLayer();
- const source = vectorLayer.getSource();
- if (!!startMarker) {
- source.removeFeature(startMarker);
- startMarker = null;
- }
- if (!!endMarker) {
- source.removeFeature(endMarker);
- endMarker = null;
- }
- if (!!routeLine) {
- source.removeFeature(routeLine);
- routeLine = null;
- }
- }
- };
- onMounted(() => {
- map = getMapUtils().getMap();
- if (AMapType.includes(props.activeMap)) {
- AMap = getMapUtils().getAMap();
- }
- getAddress(props.location);
- });
- </script>
- <style lang="scss" scoped>
- .menu-content {
- width: 1500px;
- height: 2071px;
- background: url('@/assets/images/map/rightMenu/dialog2.png') no-repeat;
- padding: 130px 45px 20px 50px;
- font-size: 36px;
- position: relative;
- color: #ffffff;
- .scroll-box {
- width: 100%;
- height: 100%;
- position: relative;
- overflow-y: auto;
- }
- .search-box {
- width: 100%;
- height: 150px;
- background: url('@/assets/images/electronicDisasterMapManage/box6.png') no-repeat;
- background-size: 100% 100%;
- padding: 0 10px 0 30px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- .text-box {
- display: flex;
- align-items: center;
- .icon-position2 {
- flex-shrink: 0;
- display: inline-block;
- width: 46px;
- height: 50px;
- background: url('@/assets/images/electronicDisasterMapManage/position2.png') no-repeat;
- background-size: 100% 100%;
- }
- .text1 {
- margin-left: 10px;
- font-size: 38px;
- color: #f7f8fb;
- display: -webkit-box;
- -webkit-box-orient: vertical;
- -webkit-line-clamp: 2;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- }
- .common-btn-primary4 {
- flex-shrink: 0;
- }
- }
- .search-item {
- margin: 6px 0 3px;
- display: flex;
- align-items: center;
- .text1 {
- font-size: 38px;
- flex-shrink: 0;
- }
- .custom-select {
- flex: 1;
- }
- }
- .search-item2 {
- margin: 3px 0;
- display: flex;
- align-items: center;
- .custom-input2 {
- flex: 1;
- }
- .common-btn-primary4 {
- flex-shrink: 0;
- margin-right: -12px;
- }
- }
- .search-item3 {
- margin: 3px 0;
- display: flex;
- align-items: center;
- :deep(.el-checkbox) {
- color: #8fa8be;
- margin-right: 10px;
- flex-shrink: 0;
- .el-checkbox__inner {
- width: 28px;
- height: 28px;
- &::after {
- width: 6px;
- height: 14px;
- left: 8px;
- top: 4px;
- }
- }
- .el-checkbox__label {
- font-size: 38px;
- }
- }
- .custom-input2 {
- flex: 1;
- margin-right: 10px;
- }
- .common-btn3 {
- flex-shrink: 0;
- }
- }
- .list2 {
- padding: 8px;
- border-radius: 2px;
- background: #1c356d;
- color: #ebf5f7;
- .text-box1 {
- font-size: 38px;
- .gradient-text2 {
- font-size: 44px;
- font-family: BEBAS-1;
- }
- }
- .list-content {
- height: 560px;
- overflow-y: auto;
- .list-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 7px 0;
- border-bottom: 1px solid #4574d5;
- cursor: pointer;
- .text-box2 {
- .text2 {
- font-size: 38px;
- line-height: 76pxpx;
- }
- .text3 {
- font-size: 36px;
- line-height: 72px;
- }
- }
- .operate {
- flex-shrink: 0;
- display: flex;
- align-items: center;
- font-size: 38px;
- color: transparent;
- background-image: linear-gradient(to bottom, #ffffff 25%, #2b72d6 100%);
- -webkit-background-clip: text;
- background-clip: text;
- cursor: pointer;
- .icon2 {
- display: inline-block;
- width: 33px;
- height: 33px;
- background: url('@/assets/images/electronicDisasterMapManage/icon2.png') no-repeat;
- background-size: 100% 100%;
- }
- }
- }
- }
- }
- .route-box {
- .route-item1 {
- display: flex;
- align-items: center;
- background-color: #1d366e;
- padding: 0 10px;
- margin-top: 12px;
- .icon-position3 {
- display: inline-block;
- width: 23px;
- height: 26px;
- background: url('@/assets/images/electronicDisasterMapManage/position3.png') no-repeat;
- background-size: 100% 100%;
- flex-shrink: 0;
- }
- .text1 {
- font-size: 38px;
- color: #cfe4fe;
- flex-shrink: 0;
- }
- .text2 {
- font-size: 38px;
- color: #f1fbff;
- flex: 1;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- .icon-close2 {
- flex-shrink: 0;
- width: 30px;
- height: 30px;
- background: url('@/assets/images/electronicDisasterMapManage/close.png') no-repeat;
- background-size: 100% 100%;
- cursor: pointer;
- margin-left: 5px;
- }
- }
- .route-item2 {
- width: 100%;
- background: #1f2c4e;
- padding: 5px;
- margin-top: 15px;
- cursor: pointer;
- &:hover {
- border: 3px solid #01e3fb;
- }
- .text-box1 {
- display: flex;
- padding: 0 15px;
- .tag {
- color: #fff;
- background-color: #2c81ff;
- border-radius: 10px;
- padding: 2px 6px;
- font-size: 36px;
- margin-right: 10px;
- }
- .tag0 {
- color: #fff;
- background-color: #e65d63;
- }
- .tag1 {
- color: #fff;
- background-color: #f0b13c;
- }
- .tag2 {
- color: #fff;
- background-color: #6bc26b;
- }
- .text1 {
- font-size: 38px;
- margin-left: 5px;
- }
- }
- .route-info {
- background-color: #2b3858;
- border: 1px solid #2e5174;
- padding: 5px 15px;
- margin-top: 10px;
- display: flex;
- align-items: center;
- .text-box2 {
- display: flex;
- align-items: center;
- .icon1 {
- display: inline-block;
- width: 38px;
- height: 34px;
- background: url('@/assets/images/electronicDisasterMapManage/icon3.png') no-repeat;
- background-size: 100% 100%;
- margin-right: 15px;
- }
- .icon2 {
- display: inline-block;
- width: 34px;
- height: 42px;
- background: url('@/assets/images/electronicDisasterMapManage/icon4.png') no-repeat;
- background-size: 100% 100%;
- margin-right: 15px;
- }
- .gradient-text2 {
- font-size: 40px;
- font-family: BEBAS-1;
- margin: 0 3px;
- }
- .text2 {
- font-size: 38px;
- color: #95a9c1;
- }
- }
- .line {
- width: 1px;
- height: 10px;
- background-color: #5a6885;
- margin: 0 20px;
- }
- }
- }
- .route-item2-active {
- border: 3px solid #01e3fb;
- }
- }
- }
- .title {
- font-size: 60px;
- position: absolute;
- top: 30px;
- left: 140px;
- }
- </style>
|