factoryindex4.vue 12 KB

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