factoryindex4.vue 12 KB

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