Procházet zdrojové kódy

闲置厂房地图功能、修改密码

yin_yu820 před 2 dny
rodič
revize
e3c819a284

+ 1 - 1
jp-mobile/common/config.js

@@ -3,7 +3,7 @@ let APP_SERVER_URL = ""
 if(process.env.NODE_ENV === 'development'){
     // 开发环境    http://47.97.69.114:8088/#/login 
     //APP_SERVER_URL = 'http://47.97.69.114:8088/panhuangly/'
-	APP_SERVER_URL = 'http://192.168.139.34:8072'
+	APP_SERVER_URL = 'http://192.168.139.62:8072'
 	//APP_SERVER_URL = 'https://ydwqfw.com.cn/yd_qycpfbWeb/yd_qycpfb'
 }else{
     // 生产环境

+ 4 - 1
jp-mobile/manifest.json

@@ -1,7 +1,7 @@
 {
     "name" : "huidu",
     "appid" : "__UNI__09A1C76",
-    "description" : "大走访+都企供需线上平台",
+    "description" : "",
     "versionName" : "huidu mobile 1.0",
     "versionCode" : 1,
     "transformPx" : false,
@@ -101,6 +101,9 @@
             "maps" : {
                 "qqmap" : {
                     "key" : "4OWBZ-KZICJ-PCPFK-XWKUM-FLKFJ-C5FI2"
+                },
+                "tencent" : {
+                    "key" : "N4YBZ-EB2WZ-JCIXK-7G6R5-6NJNV-7OFNK"
                 }
             }
         }

+ 5 - 1
jp-mobile/pages.json

@@ -65,6 +65,10 @@
 			"path": "pages/index/factoryindex5",
 			"style": {}
 		},
+		{
+			"path": "pages/index/factoryindex6",
+			"style": {}
+		},
 		{
 			"path": "pages/factoryBuildings/factoryBuildingsList",
 			"style": {}
@@ -557,7 +561,7 @@
 				"ly-tree-node": "/components/ly-tree/ly-tree-node"
 			},
 		"navigationBarBackgroundColor": "#0081ff",
-		"navigationBarTitleText": "大走访+都企供需线上平台",
+		"navigationBarTitleText": "",
 		"navigationStyle": "custom",
 		"navigationBarTextStyle": "white"
 	},

+ 4 - 4
jp-mobile/pages/factoryBuildings/factoryBuildingsInfo.vue

@@ -159,7 +159,7 @@
 		
 		data() {
 			return {
-				title: '闲置厂房详情',
+				title: '厂房资源详情',
 				isBack: true, // 是否显示返回按钮
 			    backText: '返回', // 返回按钮的文本
 			    bgColor: 'bg-blue', // 背景颜色
@@ -258,11 +258,11 @@
 			    // 字典到位后再回显详情
 			    if (query.id) {                 // 有 id → 编辑/查看
 				  this.isAdd = false
-				  this.title = '闲置厂房详情'
+				  this.title = '厂房资源详情'
 				  return loginService.factoryqueryById(query.id)
 				} else {                        // 无 id → 新增
 				  this.isAdd = true
-				  this.title = '新增闲置厂房'
+				  this.title = '新增厂房资源'
 				  return Promise.resolve({ data: {} })   // 空对象,走 setData 不会报错
 				}
 			  }).then(({ data }) => {
@@ -335,7 +335,7 @@
 				}) => {
 					uni.hideLoading();
 					uni.showToast({
-						title: "闲置厂房提交成功!",
+						title: "厂房资源提交成功!",
 						icon: "success"
 					});
 					uni.navigateTo({

+ 8 - 6
jp-mobile/pages/factoryBuildings/factoryBuildingsList.vue

@@ -119,7 +119,7 @@
 				pageInfo: {},
 				canAdd:false,
 				loginid:"",
-				title:"闲置厂房",
+				title:"厂房资源",
 				isBack: true, // 是否显示返回按钮
 				bgColor: 'bg-blue', // 背景颜色
 				stype: "", // 企业 => 3  楼宇 => 2  园区 => 1  zfadmin => 4  admin=> 5
@@ -136,10 +136,10 @@
 				loading: false,
 				parkList: [{label: '全部',value: ''},{label: '0-500㎡',value: '1'},{label: '500-1000㎡',value: '2'},
 				{label: '1000-3000㎡',value: '3'},{label: '3000-5000㎡',value: '4'},{label: '大于5000㎡',value: '5'}],
-				parkList2: [{label: '全部',value: ''},{label: '台创园',value: '1'},{label: '大冈',value: '2'},
-				{label: '大纵湖',value: '3'},{label: '学富',value: '4'},{label: '尚庄',value: '5'},{label: '张庄',value: '6'},
-				{label: '楼王',value: '7'},{label: '潘黄',value: '8'},{label: '盐渎',value: '9'},{label: '秦南',value: '10'},
-				{label: '郭猛',value: '11'},{label: '高新区',value: '12'},{label: '龙冈',value: '13'}],
+				parkList2: [{label: '全部',value: ''},{label: '高新区',value: '12'},{label: '盐渎',value: '9'},{label: '潘黄',value: '8'},
+				{label: '龙冈',value: '13'},{label: '张庄',value: '6'},{label: '大冈',value: '2'},{label: '郭猛',value: '11'},
+				{label: '大纵湖',value: '3'},{label: '秦南',value: '10'},{label: '学富',value: '4'},{label: '楼王',value: '7'},
+				{label: '尚庄',value: '5'},{label: '台创园',value: '1'},{label: '大纵湖度假区',value: '14'}],
 
 			};
 		},
@@ -154,7 +154,7 @@
 		        this.searchForm.park = '1';
 		      } else if (query.parkid === '大冈镇'|| query.parkid === '大冈') {
 		        this.searchForm.park = '2';
-		      } else if (query.parkid === '大纵湖镇' || query.parkid === '大纵湖旅游度假区'|| query.parkid === '大纵湖') {
+		      } else if (query.parkid === '大纵湖镇' || query.parkid === '大纵湖') {
 		        this.searchForm.park = '3';
 		      } else if (query.parkid === '学富镇'|| query.parkid === '学富') {
 		        this.searchForm.park = '4';
@@ -176,6 +176,8 @@
 		        this.searchForm.park = '12';
 		      } else if (query.parkid === '龙冈镇'|| query.parkid === '龙冈') {
 		        this.searchForm.park = '13';
+		      } else if (query.parkid === '大纵湖度假区') {
+		        this.searchForm.park = '14';
 		      }
 		    });
 		  }

+ 26 - 2
jp-mobile/pages/index/factoryindex.vue

@@ -4,12 +4,12 @@
 	<view class="header">
 	  <text class="title">{{title}}</text>
 	  <!-- 新增地图按钮 -->
-	  <image
+	  <!-- <image
 	    class="map-btn"
 	    src="/static/img/map.png"
 	    mode="aspectFit"
 	    @tap="goMap"
-	  />
+	  /> -->
 	</view>
 
     <!-- 2. 轮播图 -->
@@ -25,6 +25,16 @@
         <image :src="item" class="swiper-item" mode="aspectFill" />
       </swiper-item>
     </swiper>
+	
+	<view style="display: flex;width: 100%;flex-direction:row-reverse;background: white;">
+	<text class="title2" @tap="goMap">地图</text>
+	  <!-- <image
+	    class="map-btn2"
+	    src="/static/img/tab4.png"
+	    mode="aspectFit"
+	    @tap="goMap"
+	  /> -->
+	</view>
 
     <!-- 3. 九宫格导航 -->
 <!--    <view class="grid-box">
@@ -207,6 +217,20 @@ export default {
 .grid-label {
   font-size: 30rpx;
   font-weight: 600;
+  padding-left: 8rpx;
+  padding-right: 8rpx;
+  text-align: center;
   color: #fff;
 }
+.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>

+ 23 - 18
jp-mobile/pages/index/factoryindex2.vue

@@ -25,7 +25,7 @@
         v-for="(item, index) in parkList2"
         :key="index"
         class="grid-item"
-        @tap="goList(item.value)"
+        @tap="goList(item.label)"
       >
         <image class="grid-icon" :src="item.icon" mode="aspectFit" />
         <text class="grid-label">{{ item.label }}</text>
@@ -37,10 +37,11 @@
 </template>
 
 <script>
+	import loginService from "@/api/auth/loginService";
 export default {
   data() {
     return {
-      title: '盐都区闲置厂房分布图',
+      title: '盐都区厂房资源分布图',
       picture: [
         'https://ydwqfw.com.cn/yd_qycpfbH5/bg1.jpg',
         'https://ydwqfw.com.cn/yd_qycpfbH5/bg2.jpg',
@@ -48,24 +49,28 @@ export default {
         'https://ydwqfw.com.cn/yd_qycpfbH5/bg4.jpg'
       ],
       // 九宫格数据
-      parkList2: [
-        { label: '全部',   value: '', icon: '/static/icon/all.png' },
-        { label: '台创园', value: '台创园', icon: '/static/icon/taichuang.png' },
-        { label: '大冈',   value: '大冈镇', icon: '/static/icon/dagang.png' },
-        { label: '大纵湖', value: '大纵湖镇', icon: '/static/icon/dazonghu.png' },
-        { label: '学富',   value: '学富镇', icon: '/static/icon/xuefu.png' },
-        { label: '尚庄',   value: '尚庄镇', icon: '/static/icon/shangzhuang.png' },
-        { label: '张庄',   value: '张庄街道', icon: '/static/icon/zhangzhuang.png' },
-        { label: '楼王',   value: '楼王镇', icon: '/static/icon/louwang.png' },
-        { label: '潘黄',   value: '潘黄街道', icon: '/static/icon/panhuang.png' },
-        { label: '盐渎',   value: '盐渎街道', icon: '/static/icon/yandu.png' },
-        { label: '秦南',   value: '秦南镇', icon: '/static/icon/qinnan.png' },
-        { label: '郭猛',   value: '郭猛镇', icon: '/static/icon/guomeng.png' },
-        { label: '高新区', value: '盐龙街道', icon: '/static/icon/gxq.png' },
-        { label: '龙冈',   value: '龙冈镇', icon: '/static/icon/longgang.png' }
-      ]
+      parkList2: []
     }
   },
+  
+  onShow() {
+  	loginService.getParkList().then(({
+  		data
+  	}) => {
+  		let alls=0;
+  		for(var i = 0; i < data.length; i++){
+  			alls=alls+data[i].building_cnt;
+  		}
+  		
+  		this.allcount=alls;		
+  		this.parkList2=data;
+  		this.parkList2.unshift({total_area: 0, total_idle_area: 0, building_cnt: alls, label: "全部"})
+  		
+  	}).catch(e => {
+  		
+  	})
+  	
+  },
   methods: {
     // 与之前保持一致
     goList(parkid) {

+ 2 - 2
jp-mobile/pages/index/factoryindex3.vue

@@ -37,7 +37,7 @@
           <view class="card-mask" />
           <view class="card-content">
             <text class="card-name">{{ item.label }}</text>
-            <text class="card-count">闲置厂房 {{ item.building_cnt||0 }} 处</text>
+            <text class="card-count">厂房资源 {{ item.building_cnt||0 }} 处</text>
           </view>
         </view>
 
@@ -63,7 +63,7 @@
 export default {
   data() {
     return {
-      title: '盐都区闲置厂房分布图',
+      title: '盐都区厂房资源分布图',
       defaultBg: 'https://ydwqfw.com.cn/yd_qycpfbH5/bg2.jpg', // 默认封面
       picture: [
         'https://ydwqfw.com.cn/yd_qycpfbH5/bg1.jpg',

+ 231 - 137
jp-mobile/pages/index/factoryindex4.vue

@@ -4,12 +4,12 @@
     <view class="header">
       <text class="title">{{title}}</text>
 	  <!-- 新增地图按钮 -->
-	  <image
+	  <!-- <image
 	    class="map-btn"
-	    src="/static/img/tab.png"
+	    src="/static/img/tab4.png"
 	    mode="aspectFit"
 	    @tap="goMap"
-	  />
+	  /> -->
     </view>
 
     <!-- 2. 轮播图 -->
@@ -25,6 +25,17 @@
         <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">
@@ -43,6 +54,7 @@ export default {
   data() {
     return {
       chart: null,
+	  inited: false,   // 防止重复 init 标志
 	  title:'盐都区闲置厂房分布图',
       /* 轮播图示例,换成你的真实图片即可 */
       picture:["https://ydwqfw.com.cn/yd_qycpfbH5/bg1.jpg",
@@ -50,35 +62,84 @@ export default {
       "https://ydwqfw.com.cn/yd_qycpfbH5/bg3.jpg",
       "https://ydwqfw.com.cn/yd_qycpfbH5/bg4.jpg"],
 	  parkList2: [],
-	  buildingMap: new Map()   // 新增
+	  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() {
-    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%
-    })
-	this.getList()    
+    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
@@ -89,36 +150,7 @@ export default {
 			  
 			  for(var i = 0; i < data.length; i++){
 			  	alls=alls+data[i].building_cnt;
-				
-				if(data[i].label.indexOf("高新区") != -1){
-					data[i].label='盐龙'
-				}
-				// if(data[i].label.indexOf("大冈") != -1){
-				// 	data[i].label='大冈镇'
-				// }else if(data[i].label.indexOf("大纵湖") != -1){
-				// 	data[i].label='大纵湖镇'
-				// }else if(data[i].label.indexOf("学富") != -1){
-				// 	data[i].label='学富镇'
-				// }else if(data[i].label.indexOf("尚庄") != -1){
-				// 	data[i].label='尚庄镇'
-				// }else if(data[i].label.indexOf("张庄") != -1){
-				// 	data[i].label='张庄镇'
-				// }else if(data[i].label.indexOf("楼王") != -1){
-				// 	data[i].label='楼王镇'
-				// }else if(data[i].label.indexOf("潘黄") != -1){
-				// 	data[i].label='潘黄街道'
-				// }else if(data[i].label.indexOf("盐渎") != -1){
-				// 	data[i].label='盐渎街道'
-				// }else if(data[i].label.indexOf("秦南") != -1){
-				// 	data[i].label='秦南镇'
-				// }else if(data[i].label.indexOf("郭猛") != -1){
-				// 	data[i].label='郭猛镇'
-				// }else if(data[i].label.indexOf("高新区") != -1){
-				// 	data[i].label='盐龙街道'
-				// }else if(data[i].label.indexOf("龙冈") != -1){
-				// 	data[i].label='龙冈镇'
-				// }
-				
+												
 			  }
 			  
 		
@@ -149,7 +181,7 @@ export default {
       })
     },
 	goList(parkName) {
-
+		this.touchMoved = false;
 	    // 真正跳转
 	    uni.navigateTo({
 	      url: `/pages/factoryBuildings/factoryBuildingsList?parkid=${parkName}`
@@ -161,7 +193,8 @@ export default {
 		})
 	},
 	onTouchEnd(e) {
-		
+	  if (this.touchMoved) return
+		  
 	  const touch = e.changedTouches[0]
 	  uni.createSelectorQuery()
 	    .in(this)
@@ -195,88 +228,121 @@ export default {
 
 	},
 	
+	
+	
+	
+    /* 统一手势:双指缩放 + 单指平移 */
+    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 = []
-		/* 如果你知道所有镇名,可以写死;否则把 geoJSON 里的名字读出来即可 */
-		const townNames = ['台创园', '大冈镇', '大纵湖镇', '学富镇', '尚庄镇', '张庄街道', '楼王镇','潘黄街道','盐渎街道', '秦南镇', '郭猛镇', '盐龙街道', '龙冈镇']
-		townNames.forEach(name => {
-		  mapData.push({
-			name,
-			value: this.buildingMap.get(name) || 0   // 没有就按 0 处理
-		  })
-		})
-		
-      const option = {
-         geo: [{
-              map: 'yanduqu',
-              roam: true,
-              aspectScale: 1.2,
-              zoom: 1.3,
-              itemStyle: {
-				normal: {
-				  areaColor: {
-					type: 'linear-gradient',
-					x: 0,
-					y: 400,
-					x2: 0,
-					y2: 0,
-					colorStops: [{
-					  offset: 0,
-					  color: 'rgba(37,108,190,0.8)' // 0% 处的颜色
-					}, {
-					  offset: 1,
-					  color: 'rgba(15,169,195,0.8)' // 50% 处的颜色
-					}],
-					global: true // 缺省为 false
-				  },
-				  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)' // 0% 处的颜色
-					}, {
-					  offset: 1,
-					  color: 'rgba(15,169,195,1)' // 50% 处的颜色
-					}],
-					global: false // 缺省为 false
-				  },
-				}
-			  },
-              emphasis: {
-                itemStyle: { areaColor: '#ffca28' }
+      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
               },
-              // 关键:打开标签
-              label: {
-                show: true,          // 默认显示名称
-                color: '#fff',       // 文字颜色
-                fontSize: 12,        // 字号
-                fontWeight: 'bold',
-				/* 关键:让标签显示两行 */
-				  formatter: params => {
-					const cnt = this.buildingMap.get(params.name) || 0
-					return `${params.name} ${cnt} 处`
-					//return `${params.name}`
-				  }
+              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
               }
-            }],
-            series: [{ type: 'map', geoIndex: 0, data: mapData    },]
-      }
-      this.chart.setOption(option)
-
-      this.chart.on('click', params => {
-		uni.navigateTo({
-			url: `/pages/factoryBuildings/factoryBuildingsList?parkid=${params.name}`
-		})
-
-      })
+            }
+          },
+          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);
+	      }
+	    });
+	  
     }
   }
 }
@@ -328,8 +394,36 @@ export default {
   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>

+ 119 - 326
jp-mobile/pages/index/factoryindex5.vue

@@ -1,11 +1,12 @@
 <template>
   <view class="page">
-    <!-- 1. 顶部标题 -->
+    <!-- 顶部标题 -->
     <view class="header">
       <text class="title">{{ title }}</text>
+      <text class="list-btn" @tap="goList('全部')">列表</text>
     </view>
 
-    <!-- 2. 轮播图 -->
+    <!-- 轮播图 -->
     <swiper
       class="swiper"
       circular
@@ -14,236 +15,146 @@
       :interval="3000"
       :duration="800"
     >
-      <swiper-item v-for="(item, index) in picture" :key="index">
-        <image :src="item" class="swiper-item" mode="aspectFill" />
+      <swiper-item v-for="(src, i) in pictures" :key="i">
+        <image :src="src" mode="aspectFill" class="swiper-item" />
       </swiper-item>
     </swiper>
 
-    <!-- 3. 全屏地图 -->
-    <view class="map-box">
-      <view class="map-wrap">
-        <canvas canvas-id="ydChart" id="ydChart" class="map-canvas" />
-      </view>
-    </view>
-
-    <!-- 4. 悬浮按钮 -->
-    <view class="fab" @tap="openDrawer">
-      <text class="fab-txt">园区</text>
-    </view>
-
-    <!-- 5. 侧边抽屉 -->
-    <view class="drawer-mask" :class="{show: drawerOpen}" @tap="closeDrawer" />
-    <view class="drawer" :class="{show: drawerOpen}">
-      <view class="drawer-header">
-        <input
-          v-model="keyword"
-          class="search-input"
-          placeholder="搜索园区"
-          @input="onSearch"
-        />
-        <view class="mode-toggle" @tap="toggleMode">
-          <text :class="{active: mode==='grid'}">宫格</text>
-          <text :class="{active: mode==='list'}">列表</text>
-        </view>
-      </view>
-
-      <!-- 宫格模式 -->
-      <scroll-view v-if="mode==='grid'" scroll-y class="drawer-body">
-        <view class="grid-box">
-          <view
-            v-for="(item,index) in filteredList"
-            :key="index"
-            class="grid-card"
-            @tap="onSelectPark(item)"
-          >
-            <text class="grid-label">{{ item.label }}</text>
-          </view>
-        </view>
-      </scroll-view>
-
-      <!-- 列表模式 -->
-      <scroll-view v-else scroll-y class="drawer-body">
-        <view
-          v-for="(item,index) in filteredList"
-          :key="index"
-          class="list-item"
-          @tap="onSelectPark(item)"
-        >
-          <text class="list-label">{{ item.label }}</text>
-          <text class="list-count">{{ item.count||0 }} 处</text>
-        </view>
-      </scroll-view>
-    </view>
+    <!-- 腾讯地图 -->
+    <map
+      id="tencentMap"
+      class="tmap"
+      :longitude="centerLng"
+      :latitude="centerLat"
+      :scale="scale"
+      :subkey="mapKey"
+      :show-location="false"
+      :enable-zoom="true"
+      :enable-scroll="true"
+      @tap="mapTap"
+      @regionchange="regionChange"
+    >
+      <!-- 各镇覆盖物(polygon) -->
+      <polygon
+        v-for="item in townPolygons"
+        :key="item.name"
+        :points="item.points"
+        :strokeWidth="2"
+        :strokeColor="item.stroke"
+        :fillColor="item.fill"
+        @tap="tapTown(item.name)"
+      />
+    </map>
   </view>
 </template>
 
 <script>
-import * as echarts from 'echarts'
+import loginService from '@/api/auth/loginService'
+const qqMap = require('../../libs/qqmap-wx-jssdk.min.js')   // 官方下载
+const qqmapsdk = new qqMap({ key: 'N4YBZ-EB2WZ-JCIXK-7G6R5-6NJNV-7OFNK' })
 
 export default {
   data() {
     return {
       title: '盐都区闲置厂房分布图',
-      picture: [
+      pictures: [
         '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'
       ],
-      chart: null,
-      drawerOpen: false,
-      mode: 'grid',               // grid | list
-      keyword: '',
-      parkList2: [
-        { label: '全部', value: '', count: 99 },
-        { label: '台创园', value: '1', count: 12 },
-        { label: '大冈', value: '2', count: 8 },
-        { label: '大纵湖', value: '3', count: 15 },
-        { label: '学富', value: '4', count: 6 },
-        { label: '尚庄', value: '5', count: 9 },
-        { label: '张庄', value: '6', count: 11 },
-        { label: '楼王', value: '7', count: 7 },
-        { label: '潘黄', value: '8', count: 13 },
-        { label: '盐渎', value: '9', count: 18 },
-        { label: '秦南', value: '10', count: 5 },
-        { label: '郭猛', value: '11', count: 10 },
-        { label: '高新区', value: '12', count: 22 },
-        { label: '龙冈', value: '13', count: 14 }
-      ]
+      mapKey: 'N4YBZ-EB2WZ-JCIXK-7G6R5-6NJNV-7OFNK',
+      centerLng: 120.156,   // 盐都区政府大致经度
+      centerLat: 33.108,
+      scale: 10,             // 初始缩放
+      townCntMap: new Map(), // name -> building_cnt
+      townPolygons: []       // 供给 <polygon> 组件
     }
   },
-  computed: {
-    filteredList() {
-      if (!this.keyword) return this.parkList2
-      const kw = this.keyword.toLowerCase()
-      return this.parkList2.filter(i => i.label.toLowerCase().includes(kw))
-    }
-  },
-  async onReady() {
-    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 - uni.upx2px(300) // 扣掉轮播图高度
-    })
-    this.renderMap()
+  async onLoad() {
+    await this.getList()     // 先拉取各镇厂房数量
+    await this.loadDistrictBorder() // 再画行政区
   },
   methods: {
-    loadGeoJSON() {
-      return new Promise((resolve, reject) => {
-        uni.request({
-          url: './static/yandu.json',
-          method: 'GET',
-          success: res => resolve(res.data),
-          fail: reject
-        })
+    /* 1. 获取各镇厂房数,与之前逻辑完全一致 */
+    getList() {
+      return loginService.getParkList().then(({ data }) => {
+        let all = 0
+        data.forEach(item => { all += item.building_cnt })
+        this.townCntMap = new Map(data.map(item => [item.label, item.building_cnt]))
       })
     },
-    renderMap() {
-      const option = {
-        geo: [{
-          map: 'yanduqu',
-          roam: true,
-          aspectScale: 1.2,
-          zoom: 1.3,
-          itemStyle: {
-            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: 'rgba(255,202,40,.9)'
-          },
-          label: { show: true, color: '#fff', fontSize: 12, fontWeight: 'bold' }
-        }],
-        series: [{ type: 'map', geoIndex: 0, data: [] }]
-      }
-      this.chart.setOption(option)
-
-      this.chart.on('click', params => {
-        uni.navigateTo({
-          url: `/pages/factoryBuildings/factoryBuildingsList?parkid=${params.name}`
-        })
-      })
-    },
-    // 抽屉交互
-    openDrawer() {
-      this.drawerOpen = true
-    },
-    closeDrawer() {
-      this.drawerOpen = false
+    /* 2. 拉取盐都区边界并拆成各镇 */
+   /* 2. 通过 WebService 拿盐都区各镇边界 */
+   loadDistrictBorder() {
+     return new Promise((resolve, reject) => {
+       uni.request({
+         url: 'https://apis.map.qq.com/ws/district/v1/getchildren',
+         method: 'GET',
+         data: {
+           keyword: '盐都区',
+           key: this.mapKey,          // 你申请的 key
+           output: 'json',
+           extensions: 'all'          // 返回边界 polyline
+         },
+         success: res => {
+           const data = res.data
+           if (data.status !== 0) {
+             console.error('district api error:', data.message)
+             reject(data.message)
+             return
+           }
+           // data.result[0] 就是盐都区下属镇/街道
+           const towns = data.result[0]
+           const polygons = []
+           towns.forEach(t => {
+             // 边界字符串 -> 经纬度数组
+             const path = []
+             if (t.polyline) {
+               t.polyline.split(';').forEach(ll => {
+                 const [lat, lng] = ll.split(',').map(Number)
+                 path.push({ latitude: lat, longitude: lng })
+               })
+             }
+             const cnt = this.townCntMap.get(t.fullname) || 0
+             polygons.push({
+               name: t.fullname,
+               points: path,
+               fill: cnt > 0 ? '#4A90E2AA' : '#E0E0E0AA',
+               stroke: cnt > 0 ? '#ffffff' : '#cccccc'
+             })
+           })
+           this.townPolygons = polygons
+           resolve()
+         },
+         fail: reject
+       })
+     })
+   },
+    /* 3. 点击镇面 -> 跳转列表 */
+    tapTown(name) {
+      uni.navigateTo({ url: `/pages/factoryBuildings/factoryBuildingsList?parkid=${name}` })
     },
-    toggleMode() {
-      this.mode = this.mode === 'grid' ? 'list' : 'grid'
+    /* 4. 右上角列表按钮 */
+    goList() {
+      uni.navigateTo({ url: '/pages/factoryBuildings/factoryBuildingsList?parkid=全部' })
     },
-    onSearch() {}, // 已用 computed 实时过滤
-    // 选中园区
-    onSelectPark(item) {
-      this.closeDrawer()
-      // 1. 地图高亮
-      if (this.chart && item.value) {
-        this.chart.dispatchAction({
-          type: 'highlight',
-          seriesIndex: 0,
-          name: item.label
-        })
-        setTimeout(() => {
-          this.chart.dispatchAction({
-            type: 'downplay',
-            seriesIndex: 0,
-            name: item.label
-          })
-        }, 1200)
-      }
-      // 2. 跳转列表
-      uni.navigateTo({
-        url: `/pages/factoryBuildings/factoryBuildingsList?parkid=${item.value}`
-      })
-    }
+    /* 5. 地图空白处点一下,关闭气泡(这里没气泡,可留空) */
+    mapTap(e) {},
+    regionChange(e) {}
   }
 }
 </script>
 
 <style scoped>
-/* CSS 变量,一键换色 */
-:root {
-  --theme: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-  --card-bg: #f7f9fc;
-  --text: #333;
-  --shadow: 0 6rpx 24rpx rgba(0,0,0,.08);
-}
-
 .page {
   display: flex;
   flex-direction: column;
   height: 100vh;
   background: #f5f5f5;
 }
-
 .header {
+  position: relative;
   padding: 20rpx 0;
   text-align: center;
   background: #fff;
@@ -251,9 +162,19 @@ export default {
 .title {
   font-size: 36rpx;
   font-weight: bold;
-  color: var(--text);
+  color: #333;
+}
+.list-btn {
+  position: absolute;
+  right: 30rpx;
+  top: 50%;
+  transform: translateY(-50%);
+  background: #1296db;
+  color: #fff;
+  padding: 8rpx 30rpx;
+  border-radius: 15rpx;
+  font-size: 28rpx;
 }
-
 .swiper {
   width: 100%;
   height: 300rpx;
@@ -262,136 +183,8 @@ export default {
   width: 100%;
   height: 100%;
 }
-
-.map-box {
+.tmap {
   flex: 1;
   width: 100%;
-  position: relative;
-}
-.map-canvas {
-  width: 100%;
-  height: 100%;
-}
-
-/* 悬浮按钮 */
-.fab {
-  position: fixed;
-  right: 40rpx;
-  bottom: 80rpx;
-  width: 100rpx;
-  height: 100rpx;
-  border-radius: 50%;
-  background: var(--theme);
-  box-shadow: var(--shadow);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  z-index: 9;
-}
-.fab-txt {
-  font-size: 28rpx;
-  color: #fff;
-  font-weight: 600;
-}
-
-/* 抽屉 */
-.drawer-mask {
-  position: fixed;
-  left: 0;
-  top: 0;
-  right: 0;
-  bottom: 0;
-  background: rgba(0,0,0,.45);
-  opacity: 0;
-  visibility: hidden;
-  transition: all .3s;
-  z-index: 10;
-}
-.drawer-mask.show {
-  opacity: 1;
-  visibility: visible;
-}
-.drawer {
-  position: fixed;
-  right: 0;
-  top: 0;
-  bottom: 0;
-  width: 75vw;
-  background: #fff;
-  transform: translateX(100%);
-  transition: transform .3s;
-  display: flex;
-  flex-direction: column;
-  z-index: 11;
-}
-.drawer.show {
-  transform: translateX(0);
-}
-.drawer-header {
-  padding: 30rpx;
-  border-bottom: 1rpx solid #eee;
-}
-.search-input {
-  height: 64rpx;
-  padding: 0 20rpx;
-  border: 1rpx solid #ddd;
-  border-radius: 8rpx;
-  font-size: 28rpx;
-}
-.mode-toggle {
-  display: flex;
-  margin-top: 20rpx;
-  justify-content: space-around;
-}
-.mode-toggle text {
-  padding: 8rpx 24rpx;
-  border-radius: 8rpx;
-  font-size: 26rpx;
-  color: #666;
-  background: var(--card-bg);
-}
-.mode-toggle text.active {
-  background: var(--theme);
-  color: #fff;
-}
-.drawer-body {
-  flex: 1;
-  padding: 20rpx 30rpx;
-}
-
-/* 宫格 */
-.grid-box {
-  display: grid;
-  grid-template-columns: repeat(3, 1fr);
-  gap: 20rpx;
-}
-.grid-card {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  height: 140rpx;
-  border-radius: 12rpx;
-  background: var(--card-bg);
-  box-shadow: var(--shadow);
-  font-size: 28rpx;
-  font-weight: 600;
-  color: var(--text);
-}
-
-/* 列表 */
-.list-item {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  padding: 24rpx 0;
-  border-bottom: 1rpx solid #f0f0f0;
-}
-.list-label {
-  font-size: 30rpx;
-  color: var(--text);
-}
-.list-count {
-  font-size: 26rpx;
-  color: #999;
 }
 </style>

+ 383 - 0
jp-mobile/pages/index/factoryindex6.vue

@@ -0,0 +1,383 @@
+<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,
+	  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.3,                 // 初始缩放,与 geo.zoom 保持一致
+	  center: [119.95504644775392, 33.239221832141594],   // 盐都中心经纬度,按需改119.97104644775392,
+                            //33.239261832141594
+	  lastTouch: null,           // 单指上次位置
+	  startCenter: null,          // 平移起始中心
+	  touchMoved: false
+    }
+  },
+  async onReady() {
+    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()   // ✅ 关键:绑定手势
+  },
+  
+  methods: {
+	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) {
+
+	    // 真正跳转
+	    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 * (move / start), 7));
+          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: false,          // ✅ 关键:关闭 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 }]
+      });
+    }
+  }
+}
+</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>

binární
jp-mobile/static/img/tab.png


binární
jp-mobile/static/img/tab2.png


binární
jp-mobile/static/img/tab3.png


binární
jp-mobile/static/img/tab4.png


+ 2 - 2
jp-mobile/static/yandu.json

@@ -2546,7 +2546,7 @@
         },
         {
             "properties": {
-                "name": "大纵湖旅游度假区",
+                "name": "大纵湖度假区",
                 "id": "12b56a4b6f500346ba8dc65d8c95be9f",
                 "type": "wangge",
                 "color": "#e6f6ff",
@@ -7266,7 +7266,7 @@
         },
         {
             "properties": {
-                "name": "盐龙",
+                "name": "高新区",
                 "id": "aa9a4915432685f50edf75a10330b6d0",
                 "type": "wangge",
                 "color": "#e6f6ff",

+ 17 - 5
jp-ui/src/views/layout/UpdatePassword.vue

@@ -19,7 +19,7 @@
         <el-input type="password" size="small" show-password v-model="dataForm.confirmPassword"></el-input>
       </el-form-item>
 
-      <div style="padding-left: 40px;padding-bottom: 20px;">注意:新密码长度必须在6-16位,并且同时包含字母和数字。</div>
+      <div style="padding-left: 40px;padding-bottom: 20px;">注意:新密码长度必须在8-20位,须同时包含大小写字母、数字及特殊字符。</div>
     </el-form>
     <span slot="footer" class="dialog-footer">
       <el-button size="small" @click="visible = false" icon="el-icon-circle-close">关闭</el-button>
@@ -36,8 +36,8 @@
       let validateConfirmPassword = (rule, value, callback) => {
         if (this.dataForm.newPassword !== value) {
           callback(new Error('确认密码与新密码不一致'))
-        } else if (value && (value.length < 6 || value.length > 16)) {
-          callback(new Error('密码长度为6-16位'))
+        } else if (value && (value.length < 8 || value.length > 20)) {
+          callback(new Error('密码长度为8-20位'))
         } else if (!(/\d/.test(value) && /[a-zA-Z]/.test(value))) {
           callback(new Error('密码必须包含字母+数字'))
         } else {
@@ -56,8 +56,20 @@
             {required: true, message: '原密码不能为空', trigger: 'blur'}
           ],
           newPassword: [
-            {required: true, message: '新密码不能为空', trigger: 'blur'}
-          ],
+          { required: true, message: '新密码不能为空', trigger: 'blur' },
+          {
+                      validator: (rule, value, callback) => {
+                        // 8-20 位,必须同时包含:大写、小写、数字、特殊字符
+                        const reg = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]).{8,20}$/
+                        if (!reg.test(value)) {
+                          callback(new Error('密码须同时包含大小写字母、数字及特殊字符,长度 8-20 位'))
+                        } else {
+                          callback()
+                        }
+                      },
+                      trigger: 'blur'
+                    }
+        ],
           confirmPassword: [
             {required: true, message: '确认密码不能为空', trigger: 'blur'},
             {validator: validateConfirmPassword, trigger: 'blur'}

+ 33 - 1
jp-ui/src/views/modules/sys/user/UserForm.vue

@@ -13,11 +13,43 @@
                 <el-input v-model="inputForm.loginName" maxlength="50" placeholder=""></el-input>
               </el-form-item>
           </el-col>
-          <el-col :span="12">
+          <!-- <el-col :span="12">
             <el-form-item label="密码:" prop="newPassword" :rules="inputForm.id?[]:[{required: true, message:'密码不能为空', trigger:'blur'}]">
                 <el-input v-model="inputForm.newPassword" maxlength="50" placeholder="若不修改,请留空" show-password></el-input>
               </el-form-item>
+          </el-col> -->
+
+          <!-- 密码 -->
+          <el-col :span="12">
+            <el-form-item
+              label="密码:"
+              prop="newPassword"
+              :rules="
+                inputForm.id
+                  ? [
+                      {
+                        pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,20}$/,
+                        message: '密码须同时包含大小写字母、数字及特殊字符,长度 8-20 位',
+                        trigger: 'blur'
+                      }
+                    ]
+                  : [
+                      { required: true, message: '密码不能为空', trigger: 'blur' },
+                      {
+                        pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,20}$/,
+                        message: '密码须同时包含大小写字母、数字及特殊字符,长度 8-20 位',
+                        trigger: 'blur'
+                      }
+                    ]
+              ">
+              <el-input
+                v-model="inputForm.newPassword"
+                maxlength="50"
+                placeholder="若不修改,请留空"
+                show-password />
+            </el-form-item>
           </el-col>
+
           <el-col :span="12">
             <el-form-item label="确认密码" prop="confirmNewPassword" :rules="inputForm.id?[{validator: validatePass2, trigger: 'blur'}]:[{required: true, message:'确认密码不能为空', trigger:'blur'},{validator: validatePass2, trigger: 'blur'}]">
               <el-input v-model="inputForm.confirmNewPassword" maxlength="50" placeholder="" show-password></el-input>