factoryindex6.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. <template>
  2. <view class="page">
  3. <!-- 1. 顶部标题 -->
  4. <view class="header">
  5. <text class="title">{{title}}</text>
  6. <!-- 新增地图按钮 -->
  7. <!-- <image
  8. class="map-btn"
  9. src="/static/img/tab4.png"
  10. mode="aspectFit"
  11. @tap="goMap"
  12. /> -->
  13. </view>
  14. <!-- 2. 轮播图 -->
  15. <swiper
  16. class="swiper"
  17. circular
  18. :indicator-dots="true"
  19. :autoplay="true"
  20. :interval="3000"
  21. :duration="800"
  22. >
  23. <swiper-item v-for="(item, index) in picture" :key="index">
  24. <image :src="item" class="swiper-item" mode="aspectFill" />
  25. </swiper-item>
  26. </swiper>
  27. <view style="display: flex;width: 100%;flex-direction:row-reverse;">
  28. <text class="title2" @tap="goMap">列表</text>
  29. <!-- <image
  30. class="map-btn2"
  31. src="/static/img/tab4.png"
  32. mode="aspectFit"
  33. @tap="goMap"
  34. /> -->
  35. </view>
  36. <!-- 3. 地图 -->
  37. <view class="map-box">
  38. <view @touchend="onTouchEnd" class="map-wrap">
  39. <canvas canvas-id="ydChart" id="ydChart" class="map-canvas" />
  40. </view>
  41. </view>
  42. </view>
  43. </template>
  44. <script>
  45. import * as echarts from 'echarts'
  46. import loginService from "@/api/auth/loginService";
  47. export default {
  48. data() {
  49. return {
  50. chart: null,
  51. title:'盐都区闲置厂房分布图',
  52. /* 轮播图示例,换成你的真实图片即可 */
  53. picture:["https://ydwqfw.com.cn/yd_qycpfbH5/bg1.jpg",
  54. "https://ydwqfw.com.cn/yd_qycpfbH5/bg2.jpg",
  55. "https://ydwqfw.com.cn/yd_qycpfbH5/bg3.jpg",
  56. "https://ydwqfw.com.cn/yd_qycpfbH5/bg4.jpg"],
  57. parkList2: [],
  58. buildingMap: new Map(), // 新增
  59. /* ---- 手势相关 ---- */
  60. zoom: 1.3, // 初始缩放,与 geo.zoom 保持一致
  61. center: [119.95504644775392, 33.239221832141594], // 盐都中心经纬度,按需改119.97104644775392,
  62. //33.239261832141594
  63. lastTouch: null, // 单指上次位置
  64. startCenter: null, // 平移起始中心
  65. touchMoved: false
  66. }
  67. },
  68. async onReady() {
  69. const json = await this.loadGeoJSON()
  70. echarts.registerMap('yanduqu', json)
  71. let canvasNode
  72. // #ifdef H5
  73. canvasNode = document.getElementById('ydChart')
  74. // #endif
  75. // #ifndef H5
  76. canvasNode = await new Promise(resolve => {
  77. uni.createSelectorQuery()
  78. .in(this)
  79. .select('#ydChart')
  80. .node(res => resolve(res.node))
  81. .exec()
  82. })
  83. // #endif
  84. this.chart = echarts.init(canvasNode, null, {
  85. width : uni.getSystemInfoSync().windowWidth,
  86. height: uni.getSystemInfoSync().windowHeight * 0.6, // 地图占屏幕 50%
  87. useCoarsePointer: true,
  88. // 可选:让鼠标也能滚轮缩放
  89. useNativePointer: false,
  90. devicePixelRatio: uni.getSystemInfoSync().pixelRatio
  91. })
  92. await this.getList()
  93. this.bindTouch() // ✅ 关键:绑定手势
  94. },
  95. methods: {
  96. getList() {
  97. loginService.getParkList().then(({
  98. data
  99. }) => {
  100. let alls = 0
  101. data.forEach(item => { alls += item.building_cnt })
  102. for(var i = 0; i < data.length; i++){
  103. alls=alls+data[i].building_cnt;
  104. }
  105. this.allcount = alls
  106. this.parkList2 = data
  107. /* 构造 name -> building_cnt 映射表 */
  108. this.buildingMap = new Map(data.map(item => [item.label, item.building_cnt]))
  109. /* 插入“全部”汇总行 */
  110. this.parkList2.unshift({ total_area: 0, total_idle_area: 0, building_cnt: alls, label: '全部' })
  111. this.renderMap()
  112. }).catch(e => {
  113. })
  114. },
  115. loadGeoJSON() {
  116. return new Promise((resolve, reject) => {
  117. uni.request({
  118. url: './static/yandu.json',
  119. method: 'GET',
  120. success: res => resolve(res.data),
  121. fail: reject
  122. })
  123. })
  124. },
  125. goList(parkName) {
  126. // 真正跳转
  127. uni.navigateTo({
  128. url: `/pages/factoryBuildings/factoryBuildingsList?parkid=${parkName}`
  129. })
  130. },
  131. goMap() {
  132. uni.navigateTo({
  133. url: `/pages/index/factoryindex` // 换成你的地图页面路径
  134. })
  135. },
  136. onTouchEnd(e) {
  137. if (this.touchMoved) return
  138. const touch = e.changedTouches[0]
  139. uni.createSelectorQuery()
  140. .in(this)
  141. .select('.map-wrap')
  142. .boundingClientRect(rect => {
  143. const dpr = uni.getSystemInfoSync().pixelRatio // 关键1:拿到 dpr
  144. const xCss = touch.clientX - rect.left // CSS 像素
  145. const yCss = touch.clientY - rect.top
  146. const xCanvas = xCss ; // 关键2:转成 canvas 物理像素
  147. const yCanvas = yCss ;
  148. // 用物理像素判断
  149. const inGeo = this.chart.containPixel('geo', [xCanvas, yCanvas])
  150. if (inGeo) {
  151. // 拿到逻辑坐标(供 emit 用)
  152. const [lng, lat] = this.chart.convertFromPixel('geo', [xCanvas, yCanvas])
  153. // 直接 emit 一个 click 事件,把 name 传出去
  154. // 先用 chart.getOption().series[0].data 里匹配,这里简化:用 convert 回来的坐标反查
  155. const geoModel = this.chart.getModel().getComponent('geo', 0)
  156. const region = geoModel.coordinateSystem.getRegionByCoord([lng, lat])
  157. const name = region ? region.name : ''
  158. if (region) {
  159. this.goList(name)
  160. }
  161. }
  162. })
  163. .exec()
  164. },
  165. /* 统一手势:双指缩放 + 单指平移 */
  166. bindTouch() {
  167. const canvasNode = this.chart.getDom(); // 取 canvas 节点
  168. let start = 0, startZoom = this.zoom; // 缩放缓存
  169. const getDistance = (t1, t2) => {
  170. const dx = t1.clientX - t2.clientX;
  171. const dy = t1.clientY - t2.clientY;
  172. return Math.hypot(dx, dy);
  173. };
  174. canvasNode.addEventListener('touchstart', (e) => {
  175. if (e.touches.length === 2) {
  176. start = getDistance(e.touches[0], e.touches[1]);
  177. startZoom = this.zoom;
  178. } else if (e.touches.length === 1) {
  179. this.lastTouch = { x: e.touches[0].clientX, y: e.touches[0].clientY };
  180. this.startCenter = [...this.center];
  181. }
  182. });
  183. canvasNode.addEventListener('touchmove', (e) => {
  184. e.preventDefault(); // 阻止微信整页缩放
  185. this.touchMoved = true
  186. if (e.touches.length === 2) {
  187. const move = getDistance(e.touches[0], e.touches[1]);
  188. this.zoom = Math.max(0.8, Math.min(startZoom * (move / start), 7));
  189. this.renderMap();
  190. } else if (e.touches.length === 1) {
  191. const touch = e.touches[0];
  192. const dx = touch.clientX - this.lastTouch.x;
  193. const dy = touch.clientY - this.lastTouch.y;
  194. const scale = 0.0006 * this.zoom; // 灵敏度
  195. this.center = [
  196. this.startCenter[0] - dx * scale,
  197. this.startCenter[1] + dy * scale // 注意 Y 方向取反
  198. ];
  199. this.renderMap();
  200. }
  201. });
  202. canvasNode.addEventListener('touchend', e => {
  203. // 先等 30ms,防止 click 事件立即触发
  204. setTimeout(() => this.touchMoved = false, 30)
  205. });
  206. // canvasNode.addEventListener('touchend', () => {
  207. // this.lastTouch = null;
  208. // this.startCenter = null;
  209. // });
  210. },
  211. /* 重新渲染(zoom/center 已更新) */
  212. renderMap() {
  213. const mapData = [];
  214. const townNames = ['台创园', '大冈镇', '大纵湖镇', '学富镇', '尚庄镇', '张庄街道', '楼王镇', '潘黄街道', '盐渎街道', '秦南镇', '郭猛镇', '盐龙街道', '龙冈镇'];
  215. townNames.forEach(name => {
  216. mapData.push({ name, value: this.buildingMap.get(name) || 0 });
  217. });
  218. this.chart.setOption({
  219. geo: [{
  220. map: 'yanduqu',
  221. roam: false, // ✅ 关键:关闭 echarts 自带 roam
  222. zoom: this.zoom,
  223. center: this.center,
  224. aspectScale: 1.2,
  225. itemStyle: { // 你原来的样式
  226. normal: {
  227. areaColor: {
  228. type: 'linear-gradient',
  229. x: 0, y: 400, x2: 0, y2: 0,
  230. colorStops: [
  231. { offset: 0, color: 'rgba(37,108,190,0.8)' },
  232. { offset: 1, color: 'rgba(15,169,195,0.8)' }
  233. ],
  234. global: true
  235. },
  236. borderColor: '#4ecee6', borderWidth: 1
  237. },
  238. emphasis: {
  239. areaColor: {
  240. type: 'linear-gradient',
  241. x: 0, y: 300, x2: 0, y2: 0,
  242. colorStops: [
  243. { offset: 0, color: 'rgba(37,108,190,1)' },
  244. { offset: 1, color: 'rgba(15,169,195,1)' }
  245. ],
  246. global: false
  247. }
  248. }
  249. },
  250. emphasis: { itemStyle: { areaColor: '#ffca28' } },
  251. label: {
  252. show: true,
  253. color: '#fff',
  254. fontSize: 12,
  255. fontWeight: 'bold',
  256. formatter: p => `${p.name} ${this.buildingMap.get(p.name) || 0} 处`
  257. }
  258. }],
  259. series: [{ type: 'map', geoIndex: 0, data: mapData }]
  260. });
  261. }
  262. }
  263. }
  264. </script>
  265. <style scoped>
  266. .page {
  267. display: flex;
  268. flex-direction: column;
  269. height: 100vh;
  270. background: #f5f5f5;
  271. }
  272. /* 顶部标题 */
  273. .header {
  274. position: relative; /* 让子元素绝对定位参照它 */
  275. padding: 20rpx 0;
  276. text-align: center;
  277. background: #fff;
  278. }
  279. .title {
  280. font-size: 36rpx;
  281. font-weight: bold;
  282. color: #333;
  283. }
  284. /* 右上角地图按钮 */
  285. .map-btn {
  286. position: absolute;
  287. right: 30rpx;
  288. top: 50%;
  289. transform: translateY(-50%);
  290. width: 48rpx;
  291. height: 48rpx;
  292. }
  293. /* 轮播图 */
  294. .swiper {
  295. width: 100%;
  296. height: 300rpx;
  297. }
  298. .swiper-item {
  299. width: 100%;
  300. height: 100%;
  301. }
  302. /* 地图容器 */
  303. .map-box {
  304. flex: 1;
  305. width: 100%;
  306. /* background: #003366; */ /* 任意你想要的深色 */
  307. }
  308. .map-box,
  309. .map-wrap {
  310. width: 100%;
  311. height: 100%;
  312. touch-action: none; /* 交给 echarts 自己处理 */
  313. -webkit-user-select: none; /* 禁止选中文本,但允许手势 */
  314. user-select: none;
  315. }
  316. .map-canvas {
  317. width: 100%;
  318. height: 100%;
  319. touch-action: pinch-zoom pan-x pan-y; /* 明确声明可以双指缩放+平移 */
  320. pointer-events: auto;
  321. }
  322. .map-btn2 {
  323. padding: 6rpx;
  324. width: 90rpx;
  325. height: 90rpx;
  326. }
  327. .title2{
  328. background: #1296db;
  329. border-radius: 15rpx;
  330. color: white;
  331. margin-top: 8rpx;
  332. margin-right: 30rpx;
  333. padding-top: 8rpx;
  334. padding-bottom: 8rpx;
  335. padding-right: 40rpx;
  336. padding-left: 40rpx;
  337. }
  338. </style>