实现户外媒介管理界面

This commit is contained in:
wangchengming 2025-09-01 21:33:21 +08:00
parent 9941b39d0f
commit a54c7ef921
10 changed files with 719 additions and 0 deletions

View File

@ -16,6 +16,7 @@
"url": "https://gitee.com/y_project/RuoYi-Vue.git"
},
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"@element-plus/icons-vue": "2.3.1",
"@vueup/vue-quill": "1.2.0",
"@vueuse/core": "13.3.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,718 @@
<template>
<div class="app-container">
<div class="searchPanel">
<div class="more-search-pane">
<div class="search-where-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" class="searchPanelForm">
<el-form-item label="媒体类型:">
<el-select v-model="queryParams.mediaTypeOne" placeholder="请选择" @change="getMediaTypeTwo"
clearable style="min-width: 30px">
<el-option v-for="item in mediaTypeOne" :key="item.id" :label="item.name"
:value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="媒体大类">
<el-select v-model="queryParams.mediaTypeTwo" placeholder="请选择" @change="getMediaTypeThree"
clearable style="min-width: 30px">
<el-option v-for="item in mediaTypeTwo" :key="item.id" :label="item.name"
:value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="展示形式">
<el-select v-model="queryParams.mediaTypeThree" placeholder="请选择" clearable
style="min-width: 30px">
<el-option v-for="item in mediaTypeThree" :key="item.id" :label="item.name"
:value="item.id" />
</el-select>
</el-form-item>
</el-form>
</div>
<div class="search-more-button">
<el-button v-if="!unfoldFlag" text class="foladText" @click="handleFlod">展开
<svg-icon icon-class="unfold" class="ml10" />
</el-button>
<el-button v-else text class="foladText" @click="handleFlod">收起
<svg-icon icon-class="packUp" class="ml10" />
</el-button>
</div>
</div>
</div>
<div class="searchSmallPanel" v-show="unfoldFlag">
<el-form :model="queryParams" ref="queryRef" :inline="true" class="searchSmallPanelForm">
<el-form-item label="城市:">
<el-select v-model="queryParams.provinceId" placeholder="请选择" @change="getCityList" clearable
style="min-width: 30px">
<el-option v-for="item in province" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="">
<el-select v-model="queryParams.cityId" placeholder="请选择" @change="getCountyList" clearable
style="min-width: 30px">
<el-option v-for="item in city" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="">
<el-select v-model="queryParams.countyId" placeholder="请选择" @change="getTownList" clearable
style="min-width: 30px">
<el-option v-for="item in county" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="">
<el-select v-model="queryParams.townId" placeholder="请选择" clearable style="min-width: 30px">
<el-option v-for="item in town" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="商圈">
<el-select v-model="queryParams.approval_document_status" placeholder="请选择" clearable
style="min-width: 30px;">
<el-option v-for="dict in approval_document_status" :key="dict.value" :label="dict.label"
:value="dict.value" />
</el-select>
</el-form-item>
</el-form>
</div>
<div class="choseResultPanel">
<el-form :inline="true" class="searchSmallPanelForm">
<el-form-item label="已选媒体:">
<div v-if="multipleChoseArr.length == 0" class="noChoseLabel">未选择媒体</div>
<template v-else>
<el-tag v-for="tag in multipleChoseArr" :key="tag.id" class="choseResultTag"
@close="handleCloseTag(tag)" closable>
{{ tag.mediaName }}
</el-tag>
</template>
</el-form-item>
</el-form>
</div>
<el-card class="mt10">
<el-row :gutter="10" class="my_row">
<el-col :span="8">
<el-form :model="queryParams" ref="queryRef" :inline="true" class="searchInputForm">
<el-form-item label="">
<el-input v-model="queryParams.postCode" placeholder="请输入媒体名称/媒体编号" :prefix-icon="Search"
style="width: 400px;" />
</el-form-item>
</el-form>
</el-col>
<el-col :span="16" style="text-align: right;">
<el-button type="primary" class="primaryBtn" @click="handleQuery">查询</el-button>
<el-button type="primary" class="primaryBtn" @click="resetQuery">重置</el-button>
<el-button type="primary" class="primaryBtn">PPT批量导出</el-button>
<el-button type="primary" class="primaryBtn">清空所选媒体</el-button>
</el-col>
</el-row>
<el-row :gutter="10" class="my_row">
<el-col :span="12">
<el-table v-loading="loading" :data="outdoorMediaList" @selection-change="handleSelectionChange"
:height="unfoldFlag ? 'calc(100vh - 388px)' : 'calc(100vh - 354px)'">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="实景图片" align="left" prop="mediaName" width="170">
<template #default="scope">
<img :src="otherbg" fit="fill" style="width: 138px;height: 78px; border-radius: 4px" />
</template>
</el-table-column>
<el-table-column label="媒体名称" align="left" prop="mediaName" width="150">
<template #default="scope">
<el-popover popper-class="myImg_popover" placement="right-start">
<img :src="otherbg" />
<template #reference>
<span class="mediaNameLabel" @click="handleOpenDetail">{{ scope.row.mediaName
}}</span>
</template>
</el-popover>
</template>
</el-table-column>
<el-table-column label="商圈" align="left" prop="postCode" width="150" />
<el-table-column label="媒体大类" align="left" prop="postCode" width="150" />
<el-table-column label="展示形式" align="left" prop="postCode" width="150" />
<el-table-column label="操作" width="58" align="center" fixed="right">
<template #default="scope">
<el-popover popper-class="my_popover" placement="left-start">
<div class="popBtns" @click="handleLogs(scope.row)"
v-hasPermi="['problemFeedback:edit']">查看
</div>
<div class="popBtns" @click="handleDownFiles(scope.row)"
v-hasPermi="['problemFeedback:edit']">下载</div>
<template #reference>
<img style="cursor: pointer;" :src="scope.row.currentImageSrc"
@mouseenter="scope.row.currentImageSrc = hoverImageSrc"
@mouseleave="scope.row.currentImageSrc = defaultImageSrc" />
</template>
</el-popover>
</template>
</el-table-column>
</el-table>
<pagination :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
@pagination="getList" />
</el-col>
<el-col :span="12">
<div id="mapContainer" class="mapContainer"></div>
</el-col>
</el-row>
</el-card>
</div>
</template>
<script setup name="Post">
import { onMounted, ref } from 'vue';
import { Search } from '@element-plus/icons-vue'
import { sysRegionListByPid } from "@/api/system/administrativeRegion"
import { sysMediaTypeListByPid } from "@/api/system/mediaType"
import AMapLoader from "@amap/amap-jsapi-loader"; //
import optionIcon from '@/assets/images/optionIcon.png'
import optionIconHover from '@/assets/images/optionIconHover.png'
import { useBackgroundStore } from '@/store/modules/background'
import otherbg from '@/assets/images/otherbg.png'
const bgStore = useBackgroundStore()
const { proxy } = getCurrentInstance()
const { approval_document_status } = proxy.useDict("approval_document_status")
//
const province = ref([])
const city = ref([])
const county = ref([])
const town = ref([])
//
const mediaTypeOne = ref([])
const mediaTypeTwo = ref([])
const mediaTypeThree = ref([])
//
const supplierList = ref([])
//
const multipleChoseArr = ref([])
const loading = ref(true)
const total = ref(0)
const data = reactive({
queryParams: {
pageNum: 1,
pageSize: 10,
}
})
const { queryParams } = toRefs(data)
//
const defaultImageSrc = ref(optionIcon);
const hoverImageSrc = ref(optionIconHover);
//
const outdoorMediaList = ref([
{ id: 1, mediaName: '遵义市海风井铁路桥', currentImageSrc: defaultImageSrc.value },
{ id: 2, mediaName: '遵义市海风井铁路桥', currentImageSrc: defaultImageSrc.value },
{ id: 3, mediaName: '遵义市海风井铁路桥', currentImageSrc: defaultImageSrc.value },
{ id: 4, mediaName: '遵义市海风井铁路桥', currentImageSrc: defaultImageSrc.value },
{ id: 5, mediaName: '遵义市海风井铁路桥', currentImageSrc: defaultImageSrc.value },
{ id: 6, mediaName: '遵义市海风井铁路桥', currentImageSrc: defaultImageSrc.value },
{ id: 7, mediaName: '遵义市海风井铁路桥', currentImageSrc: defaultImageSrc.value },
{ id: 8, mediaName: '遵义市海风井铁路桥', currentImageSrc: defaultImageSrc.value },
{ id: 9, mediaName: '遵义市海风井铁路桥', currentImageSrc: defaultImageSrc.value },
{ id: 10, mediaName: '遵义市海风井铁路桥', currentImageSrc: defaultImageSrc.value }
])
//
const unfoldFlag = ref(false)
const handleFlod = () => {
unfoldFlag.value = !unfoldFlag.value
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
proxy.resetForm("queryRef")
handleQuery()
}
//
const handleSelectionChange = (selection) => {
multipleChoseArr.value = selection
}
//
const handleCloseTag = (tag) => {
}
//
const getList = () => {
loading.value = false
// listPost(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
// postList.value = response.rows
// total.value = response.total
// loading.value = false
// })
}
//
const getMediaTypeOne = () => {
sysMediaTypeListByPid({ parentId: 0 }).then(res => {
mediaTypeOne.value = res.data
})
}
//
const getMediaTypeTwo = (value) => {
sysMediaTypeListByPid({ parentId: value }).then(res => {
queryParams.value.mediaTypeTwo = undefined
queryParams.value.mediaTypeThree = undefined
mediaTypeTwo.value = res.data
})
}
//
const getMediaTypeThree = (value) => {
sysMediaTypeListByPid({ parentId: value }).then(res => {
queryParams.value.mediaTypeThree = undefined
mediaTypeThree.value = res.data
})
}
// /
const getProvinceList = () => {
sysRegionListByPid({ parentId: '0' }).then(res => {
province.value = res.data
})
}
// /
const getCityList = (value) => {
sysRegionListByPid({ parentId: value }).then(res => {
queryParams.value.cityId = undefined
queryParams.value.countyId = undefined
queryParams.value.townId = undefined
city.value = res.data
})
}
// /
const getCountyList = (value) => {
sysRegionListByPid({ parentId: value }).then(res => {
queryParams.value.countyId = undefined
queryParams.value.townId = undefined
county.value = res.data
})
}
//
const getTownList = (value) => {
sysRegionListByPid({ parentId: value }).then(res => {
queryParams.value.townId = undefined
town.value = res.data
})
}
// map
const mapInstance = ref(null)
const massMarks = ref(null)
// 2D/3D
const mapMode = ref('2D')
//
const isFullscreen = ref(false)
//
const loadMap = () => {
return new Promise((resolve, reject) => {
//
window._AMapSecurityConfig = {
securityJsCode: 'a157b9d8963b598f40023f5469d3e73c'
};
AMapLoader.load({
key: "f5b170e2332903225896a7290b90793a",
plugins: [],
AMapUI: {
version: "1.1",
plugins: []
}
}).then(AMap => {
//
const initialPitch = mapMode.value === '3D' ? 65 : 0;
const initialRotation = mapMode.value === '3D' ? -15 : 0;
mapInstance.value = new AMap.Map("mapContainer", {
resizeEnable: true,
zoom: 14,
center: [116.397428, 39.90923],
pitch: initialPitch, // 2D/3D
rotation: initialRotation, //
buildingAnimation: true,
expandZoomRange: true,
zooms: [3, 20],
viewMode: mapMode.value // 3D
});
//
mapInstance.value.setZoom(14);
//
addCustomControls(AMap);
// Logo
setTimeout(() => {
hideAmapLogo();
}, 1000);
//
mapInstance.value.on('render', hideAmapLogo);
//
renderMassMarks();
resolve();
}).catch(e => {
console.log(e, "高德地图加载失败");
reject(e);
});
});
}
//
const addCustomControls = (AMap) => {
//
const controlContainer = document.createElement('div');
controlContainer.className = 'custom-map-controls';
controlContainer.style.position = 'absolute';
controlContainer.style.top = '20px';
controlContainer.style.right = '20px';
controlContainer.style.zIndex = '1000';
controlContainer.style.display = 'flex';
controlContainer.style.gap = '10px';
controlContainer.style.flexDirection = 'column';
// 2D/3D
const toggle2D3DBtn = document.createElement('div');
toggle2D3DBtn.className = mapMode.value === '2D' ? 'map-control-btn map-control-3dbtn' : 'map-control-btn map-control-2dbtn';
toggle2D3DBtn.onclick = toggle2D3DMode;
//
const fullscreenBtn = document.createElement('div');
fullscreenBtn.className = isFullscreen.value === true ? 'map-control-btn map-full-screen' : 'map-control-btn map-nofull-screen';
fullscreenBtn.onclick = toggleFullscreen;
controlContainer.appendChild(toggle2D3DBtn);
controlContainer.appendChild(fullscreenBtn);
//
document.getElementById('mapContainer').appendChild(controlContainer);
}
// 2D/3D
const toggle2D3DMode = () => {
if (!mapInstance.value) return;
const currentPitch = mapInstance.value.getPitch();
if (currentPitch === 0) {
// 3D
mapInstance.value.setPitch(65);
mapInstance.value.setRotation(-15);
mapMode.value = '3D';
loadMap()
} else {
// 2D
mapInstance.value.setPitch(0);
mapInstance.value.setRotation(0);
mapMode.value = '2D';
loadMap()
}
}
//
const toggleFullscreen = () => {
const mapContainer = document.getElementById('mapContainer');
if (!document.fullscreenElement) {
//
if (mapContainer.requestFullscreen) {
mapContainer.requestFullscreen().then(() => {
isFullscreen.value = true;
const buttons = document.querySelectorAll('.map-control-btn');
buttons[1].className = 'map-control-btn map-full-screen';
}).catch(err => {
console.error('全屏模式错误:', err);
});
}
} else {
// 退
if (document.exitFullscreen) {
document.exitFullscreen().then(() => {
isFullscreen.value = false;
const buttons = document.querySelectorAll('.map-control-btn');
buttons[1].className = 'map-control-btn map-nofull-screen';
});
}
}
}
//
document.addEventListener('fullscreenchange', () => {
if (!document.fullscreenElement) {
isFullscreen.value = false;
}
});
//
const createPointStyle = (color, size, styleType) => {
const canvas = document.createElement('canvas');
canvas.width = size * 2;
canvas.height = size * 2;
const ctx = canvas.getContext('2d');
//
switch (styleType) {
case 'circle':
ctx.beginPath();
ctx.arc(size, size, size, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
break;
case 'ring':
ctx.beginPath();
ctx.arc(size, size, size, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
ctx.fill();
ctx.beginPath();
ctx.arc(size, size, size * 0.7, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
break;
case 'square':
ctx.fillStyle = color;
ctx.fillRect(0, 0, size * 2, size * 2);
break;
case 'star':
ctx.fillStyle = color;
ctx.beginPath();
for (let i = 0; i < 5; i++) {
const angle = (i * 2 * Math.PI / 5) - Math.PI / 2;
const x = size + size * Math.cos(angle);
const y = size + size * Math.sin(angle);
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
const innerAngle = angle + Math.PI / 5;
const innerX = size + size * 0.4 * Math.cos(innerAngle);
const innerY = size + size * 0.4 * Math.sin(innerAngle);
ctx.lineTo(innerX, innerY);
}
ctx.closePath();
ctx.fill();
break;
}
return canvas;
}
//
const renderMassMarks = () => {
//
if (massMarks.value) {
massMarks.value.clear();
}
//
const styles = [
{
url: createPointStyle('#EC1B60', 18, 'circle').toDataURL(),
anchor: new AMap.Pixel(18, 18),
size: new AMap.Size(18, 18)
},
{
url: createPointStyle('#058DED', 18, 'circle').toDataURL(),
anchor: new AMap.Pixel(18, 18),
size: new AMap.Size(18, 18)
},
{
url: createPointStyle('#FFA200', 18, 'circle').toDataURL(),
anchor: new AMap.Pixel(18, 18),
size: new AMap.Size(18, 18)
},
{
url: createPointStyle('#00C272', 18, 'circle').toDataURL(),
anchor: new AMap.Pixel(18, 18),
size: new AMap.Size(18, 18)
}
];
//
const points = [
{ "lnglat": [116.258446, 37.686622], "name": "景县", "style": 1 },
{ "lnglat": [113.559954, 22.124049], "name": "圣方济各堂区", "style": 1 },
{ "lnglat": [116.366794, 39.915309], "name": "西城区", "style": 1 },
{ "lnglat": [116.486409, 39.921489], "name": "朝阳区", "style": 1 },
{ "lnglat": [116.286968, 39.863642], "name": "丰台区", "style": 1 },
{ "lnglat": [116.195445, 39.914601], "name": "石景山区", "style": 2 },
{ "lnglat": [116.310316, 39.956074], "name": "海淀区", "style": 2 },
{ "lnglat": [116.105381, 39.937183], "name": "门头沟区", "style": 2 },
{ "lnglat": [116.139157, 39.735535], "name": "房山区", "style": 2 },
{ "lnglat": [116.658603, 39.902486], "name": "通州区", "style": 2 },
{ "lnglat": [116.653525, 40.128936], "name": "顺义区", "style": 2 },
{ "lnglat": [116.235906, 40.218085], "name": "昌平区", "style": 2 },
{ "lnglat": [116.338033, 39.728908], "name": "大兴区", "style": 2 },
{ "lnglat": [116.637122, 40.324272], "name": "怀柔区", "style": 2 },
{ "lnglat": [117.112335, 40.144783], "name": "平谷区", "style": 3 },
{ "lnglat": [116.843352, 40.377362], "name": "密云区", "style": 3 },
{ "lnglat": [115.985006, 40.465325], "name": "延庆区", "style": 3 },
{ "lnglat": [113.56925, 22.136546], "name": "路凼填海区", "style": 3 },
{ "lnglat": [117.195907, 39.118327], "name": "和平区", "style": 2 },
{ "lnglat": [117.226568, 39.122125], "name": "河东区", "style": 2 },
{ "lnglat": [117.217536, 39.101897], "name": "河西区", "style": 2 },
{ "lnglat": [117.164143, 39.120474], "name": "南开区", "style": 2 },
{ "lnglat": [117.201569, 39.156632], "name": "河北区", "style": 2 },
{ "lnglat": [117.163301, 39.175066], "name": "红桥区", "style": 2 },
{ "lnglat": [117.313967, 39.087764], "name": "东丽区", "style": 2 },
{ "lnglat": [117.012247, 39.139446], "name": "西青区", "style": 0 },
{ "lnglat": [117.382549, 38.989577], "name": "津南区", "style": 0 },
{ "lnglat": [117.13482, 39.225555], "name": "北辰区", "style": 0 },
{ "lnglat": [117.057959, 39.376925], "name": "武清区", "style": 0 },
{ "lnglat": [116.405285, 39.904989], "name": "北京市", "style": 0 }
]
// MassMarks
massMarks.value = new AMap.MassMarks(points, {
opacity: 1,
zIndex: 111,
cursor: 'pointer',
style: styles
});
//
massMarks.value.setMap(mapInstance.value);
//
massMarks.value.on('click', function (e) {
console.log('点击了节点', e.data)
});
}
// Logo
const hideAmapLogo = () => {
const logos = document.querySelectorAll('.amap-logo, .amap-copyright');
logos.forEach(logo => {
logo.style.display = 'none';
logo.style.visibility = 'hidden';
logo.style.opacity = '0';
});
}
//
onMounted(() => {
bgStore.setBgImage(otherbg)
getList()
getMediaTypeOne()
getProvinceList()
loadMap()
});
</script>
<style lang="scss">
.noChoseLabel {
height: 24px;
line-height: 24px;
font-family: Microsoft YaHei;
font-weight: 400;
font-size: 16px;
color: #ffffff90;
}
.mapContainer {
width: 100%;
height: 100%;
background: #3f8bff;
}
.custom-map-controls {
position: absolute;
top: 20px;
right: 20px;
z-index: 1000;
display: flex;
flex-direction: column;
gap: 10px;
}
.map-control-btn {
cursor: pointer;
}
.map-control-2dbtn {
width: 40px;
height: 40px;
background: url('../../assets/images/icon-2D-btn.png');
}
.map-control-2dbtn:hover {
background: url('../../assets/images/icon-2D-active-btn.png');
}
.map-control-3dbtn {
width: 40px;
height: 40px;
background: url('../../assets/images/icon-3D-btn.png');
}
.map-control-3dbtn:hover {
background: url('../../assets/images/icon-3D-active-btn.png');
}
.map-nofull-screen {
width: 40px;
height: 40px;
background: url('../../assets/images/icon-full-screen.png');
}
.map-nofull-screen:hover {
background: url('../../assets/images/icon-full-screen-active.png');
}
.map-full-screen {
width: 40px;
height: 40px;
background: url('../../assets/images/icon-map-nofull-screen.png');
}
.map-full-screen:hover {
background: url('../../assets/images/icon-map-nofull-screen-active.png');
}
// .map-control-btn {
// padding: 8px 12px;
// background: white;
// border: 1px solid #ccc;
// border-radius: 4px;
// cursor: pointer;
// font-size: 12px;
// box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
// transition: all 0.3s ease;
// }
// .map-control-btn:hover {
// background: #f5f5f5;
// box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
// }
// .map-control-btn:active {
// transform: translateY(1px);
// }
/* 全屏样式 */
.mapContainer:-webkit-full-screen {
width: 100% !important;
height: 100% !important;
}
.mapContainer:-moz-full-screen {
width: 100% !important;
height: 100% !important;
}
.mapContainer:-ms-fullscreen {
width: 100% !important;
height: 100% !important;
}
.mapContainer:fullscreen {
width: 100% !important;
height: 100% !important;
}
</style>