YouKeChuanMei_VUE/src/views/outdoorMedia/index.vue
2025-09-14 00:06:28 +08:00

708 lines
25 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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.mediaType" 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.mediaCategory" 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.displayForm" 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.provinceCode" 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.cityCode" 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.areaCode" 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.townCode" placeholder="请选择" @change="getbusinessAreaList" 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.businessDistrictId" placeholder="请选择" clearable
style="min-width: 30px;">
<el-option v-for="item in businessAreaList" :key="item.id" :label="item.name"
:value="item.id" />
</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.keyword" 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" @click="handleClearChoseMedia">清空所选媒体</el-button>
</el-col>
</el-row>
<el-row :gutter="10" class="my_row" style="margin-bottom: 0;">
<el-col :span="12">
<el-table v-loading="loading" :data="outdoorMediaList" @selection-change="handleSelectionChange"
:height="unfoldFlag ? 'calc(100vh - 372px)' : 'calc(100vh - 338px)'">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="实景图片" align="left" prop="mediaFileList" width="170">
<template #default="scope">
<img v-if="scope.row.mediaFileList.length > 0"
:src="baseUrl + scope.row.mediaFileList[0].fileName" fit="fill"
style="width: 138px;height: 78px; border-radius: 4px" />
</template>
</el-table-column>
<el-table-column label="媒体名称" align="left" prop="mediaName" width="150" />
<el-table-column label="商圈" align="left" prop="businessDistrictName" width="150" />
<el-table-column label="媒体大类" align="left" prop="mediaCategoryStr" width="150" />
<el-table-column label="展示形式" align="left" prop="displayFormStr" 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="handleViewMedia(scope.row.id)"
v-hasPermi="['problemFeedback:edit']">查看
</div>
<div class="popBtns" @click="handleDownFiles(scope.row)"
v-hasPermi="['problemFeedback:edit']">下载PPT</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="getOutMediaPageList" />
</el-col>
<el-col :span="12">
<div id="mapContainer" class="mapContainer"></div>
</el-col>
</el-row>
</el-card>
<detail ref="detailFormRef" />
</div>
</template>
<script setup name="Post">
import { onMounted, onUnmounted, ref } from 'vue';
import { Search } from '@element-plus/icons-vue'
import { sysRegionListByPid } from "@/api/system/administrativeRegion"
import { sysMediaTypeListByPid } from "@/api/system/mediaType"
import { busTradingAreaPage } from "@/api/system/businessArea"
import { outMediaPageList } from "@/api/mediaLibrary"
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'
import detail from './detail.vue';
const bgStore = useBackgroundStore()
const { proxy } = getCurrentInstance()
const baseUrl = import.meta.env.VITE_APP_BASE_API
const { apiKey, secretKey } = window._CONFIG
const detailFormRef = ref(null)
// 操作图标
const defaultImageSrc = ref(optionIcon);
const hoverImageSrc = ref(optionIconHover);
// 省、市、县、镇数据
const province = ref([])
const city = ref([])
const county = ref([])
const town = ref([])
// 商圈信息列表
const businessAreaList = ref([])
// 媒体类型数据
const mediaTypeOne = ref([])
const mediaTypeTwo = ref([])
const mediaTypeThree = ref([])
// 媒体多选数据
const multipleChoseArr = ref([])
// 户外媒体数据
const outdoorMediaList = ref([])
// 是否折叠
const unfoldFlag = ref(false)
const loading = ref(true)
const total = ref(0)
const data = reactive({
queryParams: {
pageNum: 1,
pageSize: 10,
keyword: undefined,
mediaType: undefined,
mediaCategory: undefined,
displayForm: undefined,
provinceCode: undefined,
cityCode: undefined,
areaCode: undefined,
townCode: undefined,
businessDistrictId: undefined
}
})
const { queryParams } = toRefs(data)
// map实例
const mapInstance = ref(null)
const massMarks = ref(null)
// 当前地图模式2D/3D
const mapMode = ref('2D')
// 是否全屏状态
const isFullscreen = ref(false)
// 地图数据点
const points = ref([])
// 获取一级媒体类型
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
})
getbusinessAreaList()
}
// 获取区/县数据
const getCountyList = (value) => {
sysRegionListByPid({ parentId: value }).then(res => {
queryParams.value.countyId = undefined
queryParams.value.townId = undefined
county.value = res.data
})
getbusinessAreaList()
}
// 获取镇数据
const getTownList = (value) => {
sysRegionListByPid({ parentId: value }).then(res => {
queryParams.value.townId = undefined
town.value = res.data
})
getbusinessAreaList()
}
// 依据省市县镇查询商圈
const getbusinessAreaList = (val) => {
const _params = {
pageIndex: 1,
pageSize: 200,
provinceId: queryParams.value.provinceCode,
cityId: queryParams.value.cityCode,
countyId: queryParams.value.areaCode,
townId: queryParams.value.townCode,
}
busTradingAreaPage(_params).then(res => {
if (res.code == 200) {
businessAreaList.value = res.data.list
}
})
}
// 折叠展开
const handleFlod = () => {
unfoldFlag.value = !unfoldFlag.value
}
// 获取户外媒介列表数据
const getOutMediaPageList = () => {
loading.value = true
outMediaPageList(queryParams.value).then(res => {
res.data.rows.forEach(element => {
element.currentImageSrc = defaultImageSrc.value;
});
outdoorMediaList.value = res.data.rows
total.value = res.data.total
loading.value = false
// 在地图完全加载后执行点数据处理
renderMassMarks();
})
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1
getOutMediaPageList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryParams.value = {
pageNum: 1,
pageSize: 10,
keyword: undefined,
mediaType: undefined,
mediaCategory: undefined,
displayForm: undefined,
provinceCode: undefined,
cityCode: undefined,
areaCode: undefined,
townCode: undefined,
businessDistrictId: undefined
}
handleQuery()
}
// 选择媒体事件
const handleSelectionChange = (selection) => {
multipleChoseArr.value = selection
}
// 移除选择项
const handleCloseTag = (tag) => {
const rowIndex = multipleChoseArr.value.findIndex(item => item.id == tag.id)
multipleChoseArr.value.splice(rowIndex, 1)
}
// 清空已选媒体
const handleClearChoseMedia = () => {
multipleChoseArr.value = []
}
// 查看媒体详情
const handleViewMedia = (_mediaId) => {
detailFormRef.value.initMediaDetail(_mediaId)
}
// 初始化地图
const loadMap = () => {
return new Promise((resolve, reject) => {
// 设置安全密钥
window._AMapSecurityConfig = {
securityJsCode: secretKey
};
AMapLoader.load({
key: apiKey,
plugins: ["AMap.PlaceSearch"],
AMapUI: {
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,
pitch: initialPitch, // 倾斜角度决定2D/3D模式
rotation: initialRotation, // 旋转角度
zoom: 16,
zooms: [3, 16],
viewMode: mapMode.value // 启用3D视图
});
// // 先添加基本控件
mapInstance.value.setZoom(16);
// 添加自定义控件容器
addCustomControls(AMap);
// 地图加载完成后隐藏Logo
setTimeout(() => {
hideAmapLogo();
}, 1000);
// 监听地图渲染完成事件
mapInstance.value.on('render', hideAmapLogo);
// 添加缩放变化监听
mapInstance.value.on('zoomchange', handleZoomChange);
//// 在地图完全加载后执行点数据处理
//renderMassMarks();
resolve();
}).catch(e => {
console.log(e, "高德地图加载失败");
reject(e);
});
});
}
// 处理地图缩放事件
const handleZoomChange = () => {
const currentZoomLevel = mapInstance.value.getZoom();
// 如果缩放级别超过16自动调整回16
if (currentZoomLevel > 16) {
mapInstance.value.setZoom(16);
return;
}
}
// 添加自定义控件
const addCustomControls = (AMap) => {
// 创建控件容器
const controlContainer = document.createElement('div');
controlContainer.className = 'custom-map-controls';
// 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)
},
];
// 调用接口,获取数据点
points.value = []
outdoorMediaList.value.forEach(itemPoint => {
console.log('itemPoint', itemPoint)
if (itemPoint.businessType == 1) points.value.push({ "lnglat": [itemPoint.mapX, itemPoint.mapY], "name": itemPoint.mediaName, "mediaId": itemPoint.id, "style": 0 })
if (itemPoint.businessType == 2) points.value.push({ "lnglat": [itemPoint.mapX, itemPoint.mapY], "name": itemPoint.mediaName, "mediaId": itemPoint.id, "style": 1 })
});
console.log('points', points.value)
// 创建MassMarks对象
massMarks.value = new AMap.MassMarks(points.value, {
opacity: 1,
zIndex: 111,
cursor: 'pointer',
style: styles
});
// 将海量点添加到地图
massMarks.value.setMap(mapInstance.value);
// 添加点击事件
massMarks.value.on('click', function (e) {
console.log('点击了节点', e.data)
handleViewMedia(e.data.mediaId)
});
}
// 隐藏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';
});
}
onUnmounted(() => {
if (mapInstance.value) {
mapInstance.value.destroy();
}
})
// 初始化
onMounted(() => {
bgStore.setBgImage(otherbg)
getOutMediaPageList()
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: 5px;
right: 5px;
z-index: 1000;
display: flex;
flex-direction: column;
gap: 0px;
}
.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');
}
/* 全屏样式 */
.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>