| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429 |
- <template>
- <view class="page">
- <!-- 1. 顶部标题 -->
- <view class="header">
- <text class="title">{{title}}</text>
- <!-- 新增地图按钮 -->
- <!-- <image
- class="map-btn"
- src="/static/img/tab4.png"
- mode="aspectFit"
- @tap="goMap"
- /> -->
- </view>
- <!-- 2. 轮播图 -->
- <swiper
- class="swiper"
- circular
- :indicator-dots="true"
- :autoplay="true"
- :interval="3000"
- :duration="800"
- >
- <swiper-item v-for="(item, index) in picture" :key="index">
- <image :src="item" class="swiper-item" mode="aspectFill" />
- </swiper-item>
- </swiper>
-
- <view style="display: flex;width: 100%;flex-direction:row-reverse;">
-
- <text class="title2" @tap="goMap">列表</text>
- <!-- <image
- class="map-btn2"
- src="/static/img/tab4.png"
- mode="aspectFit"
- @tap="goMap"
- /> -->
- </view>
- <!-- 3. 地图 -->
- <view class="map-box">
- <view @touchend="onTouchEnd" class="map-wrap">
- <canvas canvas-id="ydChart" id="ydChart" class="map-canvas" />
- </view>
- </view>
- </view>
- </template>
- <script>
- import * as echarts from 'echarts'
- import loginService from "@/api/auth/loginService";
- export default {
- data() {
- return {
- chart: null,
- inited: false, // 防止重复 init 标志
- title:'盐都区闲置厂房分布图',
- /* 轮播图示例,换成你的真实图片即可 */
- picture:["https://ydwqfw.com.cn/yd_qycpfbH5/bg1.jpg",
- "https://ydwqfw.com.cn/yd_qycpfbH5/bg2.jpg",
- "https://ydwqfw.com.cn/yd_qycpfbH5/bg3.jpg",
- "https://ydwqfw.com.cn/yd_qycpfbH5/bg4.jpg"],
- parkList2: [],
- buildingMap: new Map(), // 新增
- /* ---- 手势相关 ---- */
- zoom: 1.5, // 初始缩放,与 geo.zoom 保持一致
- center: [119.95504644775392, 33.239221832141594], // 盐都中心经纬度,按需改119.97104644775392,
- //33.239261832141594
- lastTouch: null, // 单指上次位置
- startCenter: null, // 平移起始中心
- touchMoved: false
- }
- },
- async onReady() {
- this.reInitChart()
- },
- async onShow() {
- // 返回该页时若未初始化或实例已死,重新走一遍
- if (!this.chart || this.chart.isDisposed?.()) {
- await this.reInitChart()
- }
- },
-
- onUnload() {
- // 页面卸载必须销毁,否则返回再进会重复 init
- this.disposeChart()
- },
-
- onHide() {
- // 隐藏时也可以销毁,看业务需要
- this.disposeChart()
- },
-
- methods: {
- /* 把 onReady 里初始化逻辑抽出来,方便复用 */
- async reInitChart() {
- if (this.inited) return
- this.inited = true
-
-
- const json = await this.loadGeoJSON()
- echarts.registerMap('yanduqu', json)
-
- let canvasNode
- // #ifdef H5
- canvasNode = document.getElementById('ydChart')
- // #endif
- // #ifndef H5
- canvasNode = await new Promise(resolve => {
- uni.createSelectorQuery()
- .in(this)
- .select('#ydChart')
- .node(res => resolve(res.node))
- .exec()
- })
- // #endif
-
- this.chart = echarts.init(canvasNode, null, {
- width : uni.getSystemInfoSync().windowWidth,
- height: uni.getSystemInfoSync().windowHeight * 0.6, // 地图占屏幕 50%
- useCoarsePointer: true,
- // 可选:让鼠标也能滚轮缩放
- useNativePointer: false,
- devicePixelRatio: uni.getSystemInfoSync().pixelRatio
- })
-
-
- await this.getList()
- this.bindTouch() // ✅ 关键:绑定手势
-
- },
-
- /* 统一销毁 */
- disposeChart() {
- if (this.chart) {
- this.chart.dispose()
- this.chart = null
- }
- this.inited = false
- },
-
- getList() {
- loginService.getParkList().then(({
- data
- }) => {
-
- let alls = 0
- data.forEach(item => { alls += item.building_cnt })
-
- for(var i = 0; i < data.length; i++){
- alls=alls+data[i].building_cnt;
-
- }
-
-
- this.allcount = alls
- this.parkList2 = data
-
- /* 构造 name -> building_cnt 映射表 */
- this.buildingMap = new Map(data.map(item => [item.label, item.building_cnt]))
-
- /* 插入“全部”汇总行 */
- this.parkList2.unshift({ total_area: 0, total_idle_area: 0, building_cnt: alls, label: '全部' })
-
- this.renderMap()
-
- }).catch(e => {
-
- })
-
- },
- loadGeoJSON() {
- return new Promise((resolve, reject) => {
- uni.request({
- url: './static/yandu.json',
- method: 'GET',
- success: res => resolve(res.data),
- fail: reject
- })
- })
- },
- goList(parkName) {
- this.touchMoved = false;
- // 真正跳转
- uni.navigateTo({
- url: `/pages/factoryBuildings/factoryBuildingsList?parkid=${parkName}`
- })
- },
- goMap() {
- uni.navigateTo({
- url: `/pages/index/factoryindex` // 换成你的地图页面路径
- })
- },
- onTouchEnd(e) {
- if (this.touchMoved) return
-
- const touch = e.changedTouches[0]
- uni.createSelectorQuery()
- .in(this)
- .select('.map-wrap')
- .boundingClientRect(rect => {
- const dpr = uni.getSystemInfoSync().pixelRatio // 关键1:拿到 dpr
- const xCss = touch.clientX - rect.left // CSS 像素
- const yCss = touch.clientY - rect.top
- const xCanvas = xCss ; // 关键2:转成 canvas 物理像素
- const yCanvas = yCss ;
- // 用物理像素判断
- const inGeo = this.chart.containPixel('geo', [xCanvas, yCanvas])
- if (inGeo) {
- // 拿到逻辑坐标(供 emit 用)
- const [lng, lat] = this.chart.convertFromPixel('geo', [xCanvas, yCanvas])
- // 直接 emit 一个 click 事件,把 name 传出去
- // 先用 chart.getOption().series[0].data 里匹配,这里简化:用 convert 回来的坐标反查
- const geoModel = this.chart.getModel().getComponent('geo', 0)
- const region = geoModel.coordinateSystem.getRegionByCoord([lng, lat])
- const name = region ? region.name : ''
-
- if (region) {
- this.goList(name)
- }
- }
- })
- .exec()
- },
-
-
-
-
- /* 统一手势:双指缩放 + 单指平移 */
- bindTouch() {
- const canvasNode = this.chart.getDom(); // 取 canvas 节点
- let start = 0, startZoom = this.zoom; // 缩放缓存
-
- const getDistance = (t1, t2) => {
- const dx = t1.clientX - t2.clientX;
- const dy = t1.clientY - t2.clientY;
- return Math.hypot(dx, dy);
- };
-
- canvasNode.addEventListener('touchstart', (e) => {
- if (e.touches.length === 2) {
- start = getDistance(e.touches[0], e.touches[1]);
- startZoom = this.zoom;
- } else if (e.touches.length === 1) {
- this.lastTouch = { x: e.touches[0].clientX, y: e.touches[0].clientY };
- this.startCenter = [...this.center];
- }
- });
-
- canvasNode.addEventListener('touchmove', (e) => {
- e.preventDefault(); // 阻止微信整页缩放
- this.touchMoved = true
- if (e.touches.length === 2) {
- const move = getDistance(e.touches[0], e.touches[1]);
- this.zoom = Math.max(0.8, Math.min(startZoom * Math.pow(move / start, 0.55), 3));
- this.renderMap();
- } else if (e.touches.length === 1) {
- const touch = e.touches[0];
- const dx = touch.clientX - this.lastTouch.x;
- const dy = touch.clientY - this.lastTouch.y;
- const scale = 0.0006 * this.zoom; // 灵敏度
- this.center = [
- this.startCenter[0] - dx * scale,
- this.startCenter[1] + dy * scale // 注意 Y 方向取反
- ];
- this.renderMap();
- }
- });
-
- canvasNode.addEventListener('touchend', e => {
- // 先等 30ms,防止 click 事件立即触发
- setTimeout(() => this.touchMoved = false, 30)
- });
- // canvasNode.addEventListener('touchend', () => {
- // this.lastTouch = null;
- // this.startCenter = null;
- // });
- },
-
- /* 重新渲染(zoom/center 已更新) */
- renderMap() {
- const mapData = [];
- const townNames = ['台创园', '大冈镇', '大纵湖镇', '学富镇', '尚庄镇', '张庄街道', '楼王镇', '潘黄街道', '盐渎街道', '秦南镇', '郭猛镇', '盐龙街道', '龙冈镇'];
- townNames.forEach(name => {
- mapData.push({ name, value: this.buildingMap.get(name) || 0 });
- });
-
- this.chart.setOption({
- geo: [{
- map: 'yanduqu',
- roam: true, // ✅ 关键:关闭 echarts 自带 roam
- zoom: this.zoom,
- center: this.center,
- aspectScale: 1.2,
- itemStyle: { // 你原来的样式
- normal: {
- areaColor: {
- type: 'linear-gradient',
- x: 0, y: 400, x2: 0, y2: 0,
- colorStops: [
- { offset: 0, color: 'rgba(37,108,190,0.8)' },
- { offset: 1, color: 'rgba(15,169,195,0.8)' }
- ],
- global: true
- },
- borderColor: '#4ecee6', borderWidth: 1
- },
- emphasis: {
- areaColor: {
- type: 'linear-gradient',
- x: 0, y: 300, x2: 0, y2: 0,
- colorStops: [
- { offset: 0, color: 'rgba(37,108,190,1)' },
- { offset: 1, color: 'rgba(15,169,195,1)' }
- ],
- global: false
- }
- }
- },
- emphasis: { itemStyle: { areaColor: '#ffca28' } },
- label: {
- show: true,
- color: '#fff',
- fontSize: 12,
- fontWeight: 'bold',
- formatter: p => `${p.name} ${this.buildingMap.get(p.name) || 0} 处`
- }
- }],
- series: [{ type: 'map', geoIndex: 0, data: mapData }]
- });
-
- /* 2. 统一点击事件(PC 鼠标、移动端手指) */
- this.chart.off('click'); // 防止重复注册
- this.chart.on('click', params => {
- // params.name 就是点击的乡镇名称
- if (params.componentType === 'series' && params.name) {
- this.goList(params.name);
- }
- });
-
- }
- }
- }
- </script>
- <style scoped>
- .page {
- display: flex;
- flex-direction: column;
- height: 100vh;
- background: #f5f5f5;
- }
- /* 顶部标题 */
- .header {
- position: relative; /* 让子元素绝对定位参照它 */
- padding: 20rpx 0;
- text-align: center;
- background: #fff;
- }
- .title {
- font-size: 36rpx;
- font-weight: bold;
- color: #333;
- }
- /* 右上角地图按钮 */
- .map-btn {
- position: absolute;
- right: 30rpx;
- top: 50%;
- transform: translateY(-50%);
- width: 48rpx;
- height: 48rpx;
- }
- /* 轮播图 */
- .swiper {
- width: 100%;
- height: 300rpx;
- }
- .swiper-item {
- width: 100%;
- height: 100%;
- }
- /* 地图容器 */
- .map-box {
- flex: 1;
- width: 100%;
- /* background: #003366; */ /* 任意你想要的深色 */
- }
- .map-box,
- .map-wrap {
- width: 100%;
- height: 100%;
- touch-action: none; /* 交给 echarts 自己处理 */
- -webkit-user-select: none; /* 禁止选中文本,但允许手势 */
- user-select: none;
- }
- .map-canvas {
- width: 100%;
- height: 100%;
- touch-action: pinch-zoom pan-x pan-y; /* 明确声明可以双指缩放+平移 */
- pointer-events: auto;
- }
- .map-btn2 {
- padding: 6rpx;
- width: 90rpx;
- height: 90rpx;
- }
- .title2{
- background: #1296db;
- border-radius: 15rpx;
- color: white;
- margin-top: 8rpx;
- margin-right: 30rpx;
- padding-top: 8rpx;
- padding-bottom: 8rpx;
- padding-right: 40rpx;
- padding-left: 40rpx;
- }
- </style>
|