map-view.uvue 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769
  1. <template>
  2. <view class="map-view-container">
  3. <!-- Leaflet WebView 地图 -->
  4. <leaflet-map
  5. ref="leafletMapRef"
  6. :latitude="centerLatitude"
  7. :longitude="centerLongitude"
  8. :zoom="props.zoom"
  9. :markers="markers"
  10. :layer-type="currentLayerType"
  11. :tianditu-key="tiandituKey"
  12. @markerTap="handleMarkerTap"
  13. @regionChange="handleRegionChange"
  14. @layerChange="handleLayerChange"
  15. @ready="handleMapReady"
  16. @error="handleMapError"
  17. />
  18. <!-- 返回按钮 -->
  19. <view class="back-btn" @click="handleBack">
  20. <text class="back-icon">←</text>
  21. </view>
  22. <!-- 操作按钮 -->
  23. <view class="map-controls">
  24. <view class="control-btn" @click="showLayerPopup">
  25. <image class="control-icon" src="/static/images/map/1.png" mode="aspectFit"></image>
  26. </view>
  27. <view class="control-btn" @click="handleZoomIn">
  28. <image class="control-icon" src="/static/images/map/3.png" mode="aspectFit"></image>
  29. </view>
  30. <view class="control-btn" @click="handleZoomOut">
  31. <image class="control-icon" src="/static/images/map/4.png" mode="aspectFit"></image>
  32. </view>
  33. <view class="control-btn" @click="handleLocation">
  34. <image class="control-icon" src="/static/images/map/2.png" mode="aspectFit"></image>
  35. </view>
  36. </view>
  37. <!-- 图层切换弹窗 - 自定义实现 -->
  38. <!-- 遮罩层 -->
  39. <view v-if="layerPopupVisible" class="popup-mask" @click="closeLayerPopup"></view>
  40. <!-- 弹窗内容 -->
  41. <view class="layer-popup" :class="{ 'layer-popup-show': layerPopupVisible }">
  42. <view class="popup-header">
  43. <text class="popup-title">图层选择</text>
  44. <view class="popup-close" @click="closeLayerPopup">
  45. <text class="close-icon">×</text>
  46. </view>
  47. </view>
  48. <scroll-view class="popup-scroll" :scroll-y="true" :show-scrollbar="true">
  49. <!-- 地图主题 -->
  50. <view class="layer-section">
  51. <text class="section-title">{{ baseLayerConfig.name }}</text>
  52. <view class="layer-list">
  53. <view v-for="(item, index) in baseLayerConfig.list" :key="index" class="layer-item" :class="{ 'layer-item-active': item.active }" @click="handleBaseLayerChange(index)">
  54. <image class="layer-image" :src="item.icon" mode="aspectFill"></image>
  55. <text class="layer-name">{{ item.name }}</text>
  56. <view v-if="item.active" class="layer-check">
  57. <text class="check-icon">✓</text>
  58. </view>
  59. </view>
  60. </view>
  61. </view>
  62. <!-- 基础数据 -->
  63. <view class="layer-section">
  64. <text class="section-title">{{ businessLayerConfig.name }}</text>
  65. <view class="layer-list">
  66. <view v-for="(item, index) in businessLayerConfig.list" :key="index" class="layer-item" :class="{ 'layer-item-active': item.active }" @click="handleBusinessLayerChange(index)">
  67. <image class="layer-image" :src="item.icon" mode="aspectFill"></image>
  68. <text class="layer-name">{{ item.name }}</text>
  69. <view v-if="item.active" class="layer-check">
  70. <text class="check-icon">✓</text>
  71. </view>
  72. </view>
  73. </view>
  74. </view>
  75. </scroll-view>
  76. </view>
  77. </view>
  78. </template>
  79. <script setup lang="uts">
  80. import { ref, reactive, type ComponentPublicInstance } from 'vue'
  81. import type { MapMarker, MapLayerType } from '../../types/map'
  82. import { ARCGIS_CONFIG } from '../../config/map.config'
  83. // Props
  84. type Props = {
  85. latitude?: number
  86. longitude?: number
  87. zoom?: number
  88. markers?: MapMarker[]
  89. showLocation?: boolean
  90. enableZoom?: boolean
  91. layerType?: MapLayerType
  92. tiandituKey?: string
  93. }
  94. const props = withDefaults(defineProps<Props>(), {
  95. latitude: 32.556211,
  96. longitude: 111.494811,
  97. zoom: 13,
  98. markers: () => [],
  99. showLocation: true,
  100. enableZoom: true,
  101. layerType: 'tianditu-img',
  102. tiandituKey: '4c9c8a818d3cb25bf5ccbd749e7e67c8'
  103. })
  104. // Emits
  105. const emit = defineEmits<{
  106. (e: 'markerTap', event: any): void
  107. (e: 'regionChange', region: any): void
  108. (e: 'locationUpdate', location: any): void
  109. (e: 'layerChange', layerType: MapLayerType): void
  110. (e: 'layerChangeError', error: string): void
  111. }>()
  112. // Leaflet 地图引用
  113. const leafletMapRef = ref<ComponentPublicInstance | null>(null)
  114. // 地图中心点
  115. const centerLatitude = ref<number>(props.latitude)
  116. const centerLongitude = ref<number>(props.longitude)
  117. const scale = ref<number>(16)
  118. // 当前图层类型(使用 as string 转换,让 ref 自动推断类型)
  119. const currentLayerType = ref(props.layerType as string)
  120. // 地图是否已准备好
  121. const isMapReady = ref(false)
  122. // 图层弹窗显示状态
  123. const layerPopupVisible = ref(false)
  124. // 当前位置标记点 ID
  125. const locationMarkerId = ref<number>(999999)
  126. // 图层配置类型定义
  127. type LayerItem = {
  128. code: string
  129. name: string
  130. icon: string
  131. url?: string
  132. active: boolean
  133. }
  134. type LayerConfig = {
  135. name: string
  136. list: LayerItem[]
  137. }
  138. // 图层配置(使用 ref 包装)
  139. const baseLayerConfig = ref<LayerConfig>({
  140. name: '地图主题',
  141. list: [
  142. {
  143. code: 'tianditu-vec',
  144. name: '天地图-矢量',
  145. icon: '/static/images/map/map1.jpg',
  146. active: false
  147. },
  148. {
  149. code: 'tianditu-img',
  150. name: '天地图-影像',
  151. icon: '/static/images/map/map2.jpg',
  152. active: true
  153. },
  154. {
  155. code: 'tianditu-ter',
  156. name: '天地图-地形',
  157. icon: '/static/images/map/map3.jpg',
  158. active: false
  159. }
  160. ]
  161. })
  162. const businessLayerConfig = ref<LayerConfig>({
  163. name: '基础数据',
  164. list: [
  165. {
  166. code: 'boundary',
  167. name: '界桩',
  168. icon: '/static/images/map/map4.jpg',
  169. url: '/arcgis/rest/services/水利专题图层/界桩点/MapServer/tile/{z}/{y}/{x}',
  170. active: false
  171. },
  172. {
  173. code: 'landLine',
  174. name: '管理线',
  175. icon: '/static/images/map/map5.jpg',
  176. url: '/arcgis/rest/services/管理和保护范围/管理范围/MapServer/tile/{z}/{y}/{x}',
  177. active: true
  178. },
  179. {
  180. code: 'protectLine',
  181. name: '保护线',
  182. icon: '/static/images/map/map6.jpg',
  183. url: '/arcgis/rest/services/管理和保护范围/保护范围/MapServer/tile/{z}/{y}/{x}',
  184. active: false
  185. }
  186. ]
  187. })
  188. /**
  189. * 获取图层显示名称
  190. */
  191. const getLayerName = (layerType: MapLayerType): string => {
  192. if (layerType == 'tianditu-vec') {
  193. return '天地图矢量'
  194. } else if (layerType == 'tianditu-img') {
  195. return '天地图影像'
  196. } else if (layerType == 'tianditu-ter') {
  197. return '天地图地形'
  198. }
  199. return '未知图层'
  200. }
  201. /**
  202. * 初始化业务图层(加载默认激活的图层)
  203. * 必须在 handleMapReady 之前定义
  204. */
  205. const initBusinessLayers = (): void => {
  206. console.log('initBusinessLayers 开始执行')
  207. console.log('leafletMapRef.value:', leafletMapRef.value)
  208. // 获取 token
  209. const token = uni.getStorageSync('accessToken') as string | null
  210. // const token = "eyJhbGciOiJIUzI1NiJ9.eyJyZWFsTmFtZSI6IueOi-itpuS9jSIsInN1YiI6IndhbmdqaW5nd2VpIiwiZGVwdElkIjoiMTAwMDA3IiwiZXhwIjoxNzYyOTMwMTAwLCJ1c2VySWQiOjI2OCwiaWF0IjoxNzYyODQzNzAwLCJqdGkiOiI3YzhhNmVmYi0xMDQyLTQ0NjAtOWNlMi1iZGMwMTVjNmZlMDYiLCJ1c2VybmFtZSI6IndhbmdqaW5nd2VpIn0.BlEnejBXkemhZY1nXd5xSAo8t5XoFSTnQVD09CQlVcI"
  211. console.log('token:', token != null ? 'exists' : 'null')
  212. for (let i = 0; i < businessLayerConfig.value.list.length; i++) {
  213. const item = businessLayerConfig.value.list[i]
  214. console.log(`检查图层 ${i}:`, item.name, 'active:', item.active)
  215. if (item.active) {
  216. const layerUrl = item.url as string | null
  217. console.log('layerUrl:', layerUrl)
  218. if (layerUrl != null && leafletMapRef.value != null) {
  219. leafletMapRef.value.$callMethod('addBusinessLayer', item.code, layerUrl, ARCGIS_CONFIG.baseUrl, token)
  220. console.log('✓ 初始化业务图层:', item.name)
  221. } else {
  222. console.log('✗ 未能添加图层,layerUrl:', layerUrl, 'leafletMapRef:', leafletMapRef.value)
  223. }
  224. }
  225. }
  226. }
  227. /**
  228. * 获取当前位置
  229. * 必须在 handleMapReady 之前定义
  230. *
  231. * 注意:
  232. * 1. manifest.json 中已配置定位权限,首次调用时系统会自动请求权限
  233. * 2. 使用系统定位(system provider),只支持 wgs84 坐标系
  234. * 3. 如需使用 gcj02 坐标,需配置腾讯定位 provider: 'tencent'
  235. */
  236. const handleLocation = (): void => {
  237. uni.getLocation({
  238. type: 'wgs84',
  239. success: (res) => {
  240. const latitude = res.latitude as number
  241. const longitude = res.longitude as number
  242. const accuracy = res.accuracy as number
  243. centerLatitude.value = latitude
  244. centerLongitude.value = longitude
  245. // 等待地图完全准备好再设置中心和添加标记点
  246. if (isMapReady.value && leafletMapRef.value != null) {
  247. leafletMapRef.value.$callMethod('setCenter', latitude, longitude, null)
  248. // 先移除旧的位置标记点
  249. leafletMapRef.value.$callMethod('removeMarker', locationMarkerId.value)
  250. // 添加位置标记点
  251. const locationMarker: MapMarker = {
  252. id: locationMarkerId.value,
  253. latitude: latitude,
  254. longitude: longitude,
  255. title: '我的位置',
  256. iconPath: '/static/images/map/location.png',
  257. width: 48,
  258. height: 48
  259. }
  260. leafletMapRef.value.$callMethod('addMarker', locationMarker)
  261. }
  262. emit('locationUpdate', {
  263. latitude: latitude,
  264. longitude: longitude,
  265. accuracy: accuracy
  266. })
  267. },
  268. fail: (err) => {
  269. console.log('定位失败', err)
  270. // #ifdef WEB
  271. // Web 平台降级方案:使用默认位置(props 中的初始位置)
  272. const latitude = props.latitude
  273. const longitude = props.longitude
  274. centerLatitude.value = latitude
  275. centerLongitude.value = longitude
  276. // 等待地图完全准备好再设置中心和添加标记点
  277. if (isMapReady.value && leafletMapRef.value != null) {
  278. leafletMapRef.value.$callMethod('setCenter', latitude, longitude, null)
  279. // 先移除旧的位置标记点
  280. leafletMapRef.value.$callMethod('removeMarker', locationMarkerId.value)
  281. // 添加位置标记点(使用默认位置)
  282. const locationMarker: MapMarker = {
  283. id: locationMarkerId.value,
  284. latitude: latitude,
  285. longitude: longitude,
  286. title: '默认位置',
  287. iconPath: '/static/images/map/location.png',
  288. width: 48,
  289. height: 48
  290. }
  291. leafletMapRef.value.$callMethod('addMarker', locationMarker)
  292. }
  293. emit('locationUpdate', {
  294. latitude: latitude,
  295. longitude: longitude,
  296. accuracy: 0
  297. })
  298. console.log('Web平台定位失败,使用默认位置')
  299. // #endif
  300. // #ifndef WEB
  301. // App 平台直接提示定位失败
  302. uni.showToast({
  303. title: '定位失败',
  304. icon: 'none',
  305. duration: 2000
  306. })
  307. // #endif
  308. }
  309. })
  310. }
  311. // 地图准备完成
  312. const handleMapReady = (): void => {
  313. isMapReady.value = true
  314. console.log('地图准备完成')
  315. // 延迟初始化业务图层,确保 JavaScript 完全准备好
  316. setTimeout(() => {
  317. console.log('准备初始化业务图层...')
  318. initBusinessLayers()
  319. }, 500)
  320. // 自动定位当前位置
  321. if (props.showLocation) {
  322. setTimeout(() => {
  323. console.log('开始自动定位...')
  324. handleLocation()
  325. }, 800)
  326. }
  327. }
  328. // 地图错误
  329. const handleMapError = (error: string): void => {
  330. console.error('地图错误:', error)
  331. }
  332. // 返回上一页
  333. const handleBack = (): void => {
  334. uni.navigateBack()
  335. }
  336. // 放大地图
  337. const handleZoomIn = (): void => {
  338. if (scale.value < 20) {
  339. scale.value++
  340. if (leafletMapRef.value != null) {
  341. leafletMapRef.value.$callMethod('setZoom', scale.value)
  342. }
  343. }
  344. }
  345. // 缩小地图
  346. const handleZoomOut = (): void => {
  347. if (scale.value > 5) {
  348. scale.value--
  349. if (leafletMapRef.value != null) {
  350. leafletMapRef.value.$callMethod('setZoom', scale.value)
  351. }
  352. }
  353. }
  354. // 标记点点击
  355. const handleMarkerTap = (e: any): void => {
  356. emit('markerTap', e)
  357. }
  358. // 地图区域变化
  359. const handleRegionChange = (e: any): void => {
  360. emit('regionChange', e)
  361. }
  362. // 图层变化
  363. const handleLayerChange = (layerType: MapLayerType): void => {
  364. currentLayerType.value = layerType as string
  365. emit('layerChange', layerType)
  366. }
  367. /**
  368. * 切换地图图层(核心方法,必须在 handleSwitchLayer 之前定义)
  369. */
  370. const switchLayer = (layerType: MapLayerType): void => {
  371. try {
  372. // 验证图层类型
  373. if (currentLayerType.value == (layerType as string)) {
  374. return
  375. }
  376. // 更新图层
  377. currentLayerType.value = layerType as string
  378. if (leafletMapRef.value != null) {
  379. leafletMapRef.value.$callMethod('switchLayer', layerType)
  380. }
  381. } catch (e: any) {
  382. const message = e.message as string | null
  383. const errorMsg: string = message != null ? message : '图层切换失败'
  384. emit('layerChangeError', errorMsg)
  385. }
  386. }
  387. /**
  388. * 显示图层选择弹窗
  389. */
  390. const showLayerPopup = (): void => {
  391. layerPopupVisible.value = true
  392. }
  393. /**
  394. * 关闭图层选择弹窗
  395. */
  396. const closeLayerPopup = (): void => {
  397. layerPopupVisible.value = false
  398. }
  399. /**
  400. * 切换基础图层(地图主题)
  401. */
  402. const handleBaseLayerChange = (index: number): void => {
  403. // 取消其他图层的选中状态
  404. for (let i = 0; i < baseLayerConfig.value.list.length; i++) {
  405. baseLayerConfig.value.list[i].active = (i == index)
  406. }
  407. // 获取选中的图层
  408. const selectedLayer = baseLayerConfig.value.list[index]
  409. const layerType = selectedLayer.code as MapLayerType
  410. // 切换图层
  411. switchLayer(layerType)
  412. }
  413. /**
  414. * 切换业务图层(基础数据)
  415. */
  416. const handleBusinessLayerChange = (index: number): void => {
  417. // 切换选中状态
  418. const item = businessLayerConfig.value.list[index]
  419. item.active = !item.active
  420. // 获取图层 URL
  421. const layerUrl = item.url as string | null
  422. if (layerUrl == null) {
  423. console.error('业务图层 URL 为空:', item.name)
  424. return
  425. }
  426. // 根据 active 状态添加或移除图层
  427. if (leafletMapRef.value != null) {
  428. if (item.active) {
  429. // 获取 token
  430. // const token = uni.getStorageSync('accessToken') as string | null
  431. const token = "eyJhbGciOiJIUzI1NiJ9.eyJyZWFsTmFtZSI6IueOi-itpuS9jSIsInN1YiI6IndhbmdqaW5nd2VpIiwiZGVwdElkIjoiMTAwMDA3IiwiZXhwIjoxNzYyOTMwMTAwLCJ1c2VySWQiOjI2OCwiaWF0IjoxNzYyODQzNzAwLCJqdGkiOiI3YzhhNmVmYi0xMDQyLTQ0NjAtOWNlMi1iZGMwMTVjNmZlMDYiLCJ1c2VybmFtZSI6IndhbmdqaW5nd2VpIn0.BlEnejBXkemhZY1nXd5xSAo8t5XoFSTnQVD09CQlVcI"
  432. // 添加图层
  433. leafletMapRef.value.$callMethod('addBusinessLayer', item.code, layerUrl, ARCGIS_CONFIG.baseUrl, token)
  434. console.log('添加业务图层:', item.name, ARCGIS_CONFIG.baseUrl, layerUrl )
  435. } else {
  436. // 移除图层
  437. leafletMapRef.value.$callMethod('removeBusinessLayer', item.code)
  438. console.log('移除业务图层:', item.name, ARCGIS_CONFIG.baseUrl, layerUrl)
  439. }
  440. }
  441. }
  442. // 批量添加标记点
  443. const addMarkers = (markers: MapMarker[]): void => {
  444. if (leafletMapRef.value != null) {
  445. leafletMapRef.value.$callMethod('addMarkers', markers)
  446. }
  447. }
  448. // 清除所有标记点
  449. const clearMarkers = (): void => {
  450. if (leafletMapRef.value != null) {
  451. leafletMapRef.value.$callMethod('clearMarkers')
  452. }
  453. }
  454. // 添加路线
  455. const addPolyline = (points: any[], color: string | null, width: number | null): void => {
  456. if (leafletMapRef.value != null) {
  457. leafletMapRef.value.$callMethod('addPolyline', points, color, width)
  458. }
  459. }
  460. // 添加圆形
  461. const addCircle = (
  462. latitude: number,
  463. longitude: number,
  464. radius: number,
  465. strokeColor: string | null,
  466. fillColor: string | null
  467. ): void => {
  468. if (leafletMapRef.value != null) {
  469. leafletMapRef.value.$callMethod('addCircle', latitude, longitude, radius, strokeColor, fillColor)
  470. }
  471. }
  472. // 添加多边形
  473. const addPolygon = (
  474. points: any[],
  475. strokeColor: string | null,
  476. fillColor: string | null
  477. ): void => {
  478. if (leafletMapRef.value != null) {
  479. leafletMapRef.value.$callMethod('addPolygon', points, strokeColor, fillColor)
  480. }
  481. }
  482. // 清除所有覆盖物
  483. const clearOverlays = (): void => {
  484. if (leafletMapRef.value != null) {
  485. leafletMapRef.value.$callMethod('clearOverlays')
  486. }
  487. }
  488. /**
  489. * 获取当前图层类型
  490. */
  491. const getCurrentLayerType = (): string => {
  492. return currentLayerType.value
  493. }
  494. /**
  495. * 获取图层配置信息
  496. */
  497. const getLayerConfig = (): any => {
  498. return {
  499. currentLayerType: currentLayerType.value,
  500. layerName: getLayerName(currentLayerType.value as MapLayerType),
  501. isReady: isMapReady.value
  502. }
  503. }
  504. // 暴露方法给父组件
  505. defineExpose({
  506. addMarkers,
  507. clearMarkers,
  508. addPolyline,
  509. addCircle,
  510. addPolygon,
  511. clearOverlays,
  512. handleLocation,
  513. switchLayer,
  514. getCurrentLayerType,
  515. getLayerConfig
  516. })
  517. </script>
  518. <style lang="scss">
  519. .map-view-container {
  520. position: relative;
  521. width: 100%;
  522. height: 100%;
  523. }
  524. .back-btn {
  525. position: absolute;
  526. left: 20rpx;
  527. top: 80rpx;
  528. width: 80rpx;
  529. height: 80rpx;
  530. background-color: #ffffff;
  531. border-radius: 40rpx;
  532. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
  533. justify-content: center;
  534. align-items: center;
  535. .back-icon {
  536. font-size: 36rpx;
  537. color: #333333;
  538. font-weight: bold;
  539. line-height: 80rpx;
  540. }
  541. }
  542. .map-controls {
  543. position: absolute;
  544. right: 20rpx;
  545. top: 80rpx;
  546. flex-direction: column;
  547. }
  548. .control-btn {
  549. width: 80rpx;
  550. height: 80rpx;
  551. margin-bottom: 15rpx;
  552. background-color: #ffffff;
  553. border-radius: 8rpx;
  554. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
  555. justify-content: center;
  556. align-items: center;
  557. .control-icon {
  558. width: 48rpx;
  559. height: 48rpx;
  560. }
  561. }
  562. /* 自定义图层弹窗样式 */
  563. .popup-mask {
  564. position: fixed;
  565. top: 0;
  566. left: 0;
  567. right: 0;
  568. bottom: 0;
  569. background-color: rgba(0, 0, 0, 0.5);
  570. z-index: 998;
  571. }
  572. .layer-popup {
  573. position: fixed;
  574. top: 0;
  575. right: 0;
  576. bottom: 0;
  577. width: 70%;
  578. background-color: #ffffff;
  579. flex-direction: column;
  580. z-index: 999;
  581. transform: translateX(100%);
  582. transition-property: transform;
  583. transition-duration: 300ms;
  584. transition-timing-function: ease;
  585. .popup-header {
  586. height: 180rpx;
  587. padding: 80rpx 30rpx 0 30rpx;
  588. border-bottom: 1rpx solid #f0f0f0;
  589. flex-direction: row;
  590. justify-content: space-between;
  591. align-items: center;
  592. .popup-title {
  593. font-size: 32rpx;
  594. font-weight: bold;
  595. color: #333333;
  596. }
  597. .popup-close {
  598. width: 60rpx;
  599. height: 60rpx;
  600. justify-content: center;
  601. align-items: center;
  602. .close-icon {
  603. font-size: 48rpx;
  604. color: #666666;
  605. line-height: 60rpx;
  606. }
  607. }
  608. }
  609. .popup-scroll {
  610. flex: 1;
  611. padding: 30rpx;
  612. }
  613. }
  614. .layer-popup-show {
  615. transform: translateX(0) !important;
  616. }
  617. .layer-section {
  618. margin-bottom: 40rpx;
  619. .section-title {
  620. font-size: 28rpx;
  621. color: #666666;
  622. margin-bottom: 20rpx;
  623. }
  624. .layer-list {
  625. flex-direction: row;
  626. flex-wrap: wrap;
  627. justify-content: flex-start;
  628. .layer-item {
  629. position: relative;
  630. width: 200rpx;
  631. height: 200rpx;
  632. margin-bottom: 20rpx;
  633. margin-right: 20rpx;
  634. border-radius: 8rpx;
  635. overflow: hidden;
  636. border: 2rpx solid transparent;
  637. .layer-image {
  638. width: 200rpx;
  639. height: 200rpx;
  640. }
  641. .layer-name {
  642. position: absolute;
  643. bottom: 0;
  644. left: 0;
  645. right: 0;
  646. padding: 10rpx;
  647. background-color: rgba(0, 0, 0, 0.6);
  648. font-size: 24rpx;
  649. color: #ffffff;
  650. }
  651. .layer-check {
  652. position: absolute;
  653. top: 10rpx;
  654. right: 10rpx;
  655. width: 44rpx;
  656. height: 44rpx;
  657. background-color: #007aff;
  658. border-radius: 22rpx;
  659. justify-content: center;
  660. align-items: center;
  661. .check-icon {
  662. font-size: 28rpx;
  663. color: #ffffff;
  664. font-weight: bold;
  665. }
  666. }
  667. }
  668. .layer-item-active {
  669. border-color: #007aff;
  670. }
  671. }
  672. }
  673. </style>