map.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828
  1. <template>
  2. <view class="container">
  3. <navigation-bar @ready="handleNavReady"></navigation-bar>
  4. <view class="dialog" v-if="styleData.dialog" :style="styleData.dialog">
  5. <view class="height-88 flex-align-center bg-fff">
  6. <image
  7. mode="widthFix"
  8. src="/static/images/map-logo.png"
  9. style="width: 156rpx"
  10. class="ml-24"
  11. ></image>
  12. <image
  13. mode="widthFix"
  14. src="/static/images/map-input.png"
  15. class="ml-24"
  16. style="width: 352rpx"
  17. @click="toSearch"
  18. ></image>
  19. </view>
  20. <view class="height-72 flex" style="background-color: #f9f9f9">
  21. <view class="width-half flex-center" @click.stop="checkFilterDistance">
  22. <view class="fs-26 color-000-6 mr-8">距离</view>
  23. <view
  24. class="width-20 height-20 br-round bg-000-08 lh-10 text-center transition"
  25. :style="{
  26. transform: filterDialog.distanceSelector ? 'rotate(180deg)' : '',
  27. }"
  28. >
  29. <uni-icons
  30. type="bottom"
  31. size="8"
  32. color="rgba(0,0,0,0.4)"
  33. ></uni-icons>
  34. </view>
  35. </view>
  36. <view class="width-half flex-center" @click.stop="checkDiscounts">
  37. <view
  38. class="width-32 height-32 br-round lh-20 text-center"
  39. :style="{
  40. border: filterDialog.discounts
  41. ? '1px solid var(--color-primary)'
  42. : '1px solid rgba(0, 0, 0, 0.4)',
  43. backgroundColor: filterDialog.discounts
  44. ? 'var(--color-primary)'
  45. : '#fff',
  46. }"
  47. >
  48. <uni-icons
  49. type="checkmarkempty"
  50. size="10"
  51. color="#ffffff"
  52. ></uni-icons>
  53. </view>
  54. <view
  55. :class="[
  56. 'fs-26',
  57. 'ml-8',
  58. filterDialog.discounts ? 'color-primary' : 'color-000-6',
  59. ]"
  60. >支持省钱充电</view
  61. >
  62. </view>
  63. </view>
  64. <view
  65. class="dialog_selector"
  66. :style="{
  67. height: filterDialog.distanceSelector ? '190rpx' : '0rpx',
  68. borderTop: `1rpx solid rgba(0, 0, 0, ${
  69. filterDialog.distanceSelector ? '0.1' : '0'
  70. })`,
  71. }"
  72. >
  73. <view class="flex-center flex-wrap pb-14">
  74. <view
  75. :class="[
  76. 'type',
  77. 'flex-shrink',
  78. 'flex-center',
  79. 'mt-20',
  80. (index + 1) % 5 === 0 ? 'mr-0' : 'mr-20',
  81. `type-${
  82. item.value === filterDialog.options.distance ? 'active' : ''
  83. }`,
  84. ]"
  85. v-for="(item, index) in filterDialog.distanceRange"
  86. :key="index"
  87. @click.stop="changeFilterDistance(index)"
  88. style="width: 117rpx"
  89. >{{ item.value }}km</view
  90. >
  91. </view>
  92. </view>
  93. <!-- <view class="dialog_event" v-if="ready && !filterDialog.distanceSelector">
  94. // TODO 接入活动接口
  95. <swiper
  96. class="swiper"
  97. circular
  98. :indicator-dots="true"
  99. :autoplay="true"
  100. :interval="3000"
  101. >
  102. <swiper-item class="full-percent">
  103. <view class="full-percent bg-666"></view>
  104. </swiper-item>
  105. <swiper-item class="full-percent">
  106. <view class="full-percent bg-fff"></view>
  107. </swiper-item>
  108. </swiper>
  109. </view> -->
  110. </view>
  111. <view
  112. v-if="styleData.dialogPlaceHolderHeight"
  113. :style="{ height: `${styleData.dialogPlaceHolderHeight}px` }"
  114. ></view>
  115. <map
  116. id="map"
  117. :style="{
  118. width: '100%',
  119. height: `calc(100vh - ${styleData.dialogHeight}px - ${
  120. styleData.cardHeight - 24
  121. }px)`,
  122. zIndex: 1,
  123. pointerEvents: loading || !mapMode ? 'none' : 'auto',
  124. }"
  125. :latitude="mapProps.latitude"
  126. :longitude="mapProps.longitude"
  127. :markers="markers"
  128. min-scale="1"
  129. :scale="mapProps.scale"
  130. @regionchange="mapChange"
  131. @updated="mapUpdated"
  132. @labeltap="tapMarker"
  133. @markertap="tapMarker"
  134. :show-scale="true"
  135. ></map>
  136. <view
  137. class="card"
  138. :style="{
  139. height: mapMode
  140. ? `${styleData.cardHeight}px`
  141. : `calc(100vh - ${styleData.dialogHeight}px)`,
  142. bottom: '0px',
  143. borderRadius: mapMode ? '16rpx 16rpx 0px 0px' : '0px',
  144. }"
  145. @touchstart.stop="touchCardStart"
  146. @touchmove.stop="touchCardMove"
  147. >
  148. <view
  149. v-if="mapMode"
  150. class="card_location height-64 width-64 bg-fff flex-center"
  151. @click="resetLocation"
  152. >
  153. <image
  154. src="/static/images/map-re-location.png"
  155. mode="widthFix"
  156. class="width-48"
  157. ></image>
  158. </view>
  159. <block v-if="station.length">
  160. <!-- 列表 -->
  161. <block v-if="!mapMode">
  162. <view class="station" v-for="(item, index) in station" :key="index">
  163. <view v-if="index !== 0" class="card_line"></view>
  164. <charge-station
  165. :title="item.stationName"
  166. :address="item.address"
  167. :price="item.totalFee"
  168. :fast="item.fastEquipmentInfos"
  169. :slow="item.slowEquipmentInfos"
  170. :sId="item.StationID"
  171. :distance="item.stationLatDistance"
  172. :latitude="item.location.stationLat"
  173. :longitude="item.location.stationLng"
  174. :fromMap="true"
  175. ></charge-station>
  176. </view>
  177. </block>
  178. <block v-else>
  179. <!-- 单个 -->
  180. <view class="station">
  181. <charge-station
  182. :title="station[markersIndex].stationName"
  183. :address="station[markersIndex].address"
  184. :price="station[markersIndex].totalFee"
  185. :fast="station[markersIndex].fastEquipmentInfos"
  186. :slow="station[markersIndex].slowEquipmentInfos"
  187. :sId="station[markersIndex].StationID"
  188. :distance="station[markersIndex].stationLatDistance"
  189. :latitude="station[markersIndex].location.stationLat"
  190. :longitude="station[markersIndex].location.stationLng"
  191. :fromMap="true"
  192. ></charge-station>
  193. </view>
  194. </block>
  195. </block>
  196. <block v-else>
  197. <view class="card_empty flex-column flex-align-center pt-20">
  198. <view v-if="loading" class="mt-60 animation-loading"
  199. ><uni-icons
  200. type="spinner-cycle"
  201. size="20"
  202. color="rgba(0, 0, 0, 0.5)"
  203. ></uni-icons
  204. ></view>
  205. <image
  206. class="image"
  207. v-else
  208. src="/static/images/map-empty.png"
  209. mode="widthFix"
  210. />
  211. <view class="fs-22 mt-14 color-000-5">{{
  212. loading ? "加载中" : "暂无充电站信息"
  213. }}</view>
  214. </view>
  215. </block>
  216. </view>
  217. </view>
  218. <view class="login-mask" v-if="!token">
  219. <button open-type="getPhoneNumber" @getphonenumber="loginMask" class="full">
  220. 登录按钮
  221. </button>
  222. </view>
  223. </template>
  224. <script setup lang="ts">
  225. const defaulDistance = 3;
  226. const defaultScale = 12;
  227. const pointSize = {
  228. width: 34,
  229. height: 58,
  230. fontSize: 10,
  231. iconPath: "/static/images/map-point.png",
  232. currentWidth: 52,
  233. currentHeight: 86,
  234. currentFontSize: 11,
  235. currentIconPath: "/static/images/map-point-current.png",
  236. androidX: -14,
  237. androidCurrentX: -20,
  238. };
  239. import { deCode } from "../../utils/code";
  240. import { rpxToPx } from "../../utils/device";
  241. import { fetchToken, login, onLogin } from "@/api/auth";
  242. import { fetchStations } from "@/api/charge";
  243. import { fetchCollectList } from "@/api/user";
  244. import { fetchLocation } from "@/utils/location";
  245. import { onLoad } from "@dcloudio/uni-app";
  246. import { ref } from "vue";
  247. // TODO 支持省钱充电
  248. // TODO 充电中
  249. const isIOS = ref(false);
  250. const token = ref<string>();
  251. const ready = ref(false);
  252. const loading = ref(false);
  253. const styleData = ref({
  254. dialog: "",
  255. dialogHeight: 0,
  256. dialogPlaceHolderHeight: 0,
  257. cardHeight: 0,
  258. });
  259. const filterDialog = ref({
  260. discounts: false,
  261. distanceSelector: false,
  262. distanceRange: [
  263. {
  264. value: 1,
  265. scale: defaultScale + 3,
  266. },
  267. {
  268. value: 2,
  269. scale: defaultScale + 1,
  270. },
  271. {
  272. value: 3,
  273. scale: defaultScale,
  274. },
  275. {
  276. value: 5,
  277. scale: defaultScale - 0.2,
  278. },
  279. {
  280. value: 10,
  281. scale: defaultScale - 0.5,
  282. },
  283. {
  284. value: 20,
  285. scale: defaultScale - 1.5,
  286. },
  287. {
  288. value: 30,
  289. scale: defaultScale - 2,
  290. },
  291. {
  292. value: 50,
  293. scale: defaultScale - 2.5,
  294. },
  295. {
  296. value: 100,
  297. scale: defaultScale - 3.5,
  298. },
  299. {
  300. value: 200,
  301. scale: defaultScale - 4.5,
  302. },
  303. ],
  304. options: {
  305. distance: defaulDistance,
  306. status: 0,
  307. },
  308. status: [
  309. {
  310. title: "全部",
  311. },
  312. {
  313. title: "空闲",
  314. },
  315. {
  316. title: "忙碌",
  317. },
  318. ],
  319. });
  320. const stationPage = ref({
  321. page: 1,
  322. pageSize: 6,
  323. hasNext: false,
  324. });
  325. const station = ref<any[]>([]);
  326. const mapMode = ref(true);
  327. const mapProps = ref({
  328. latitude: 23.098994,
  329. longitude: 113.32252,
  330. selflatitude: 23.098994,
  331. selflongitude: 113.32252,
  332. scale: defaultScale,
  333. });
  334. const markersIndex = ref(-1);
  335. const markers = ref<any[]>([]);
  336. let isIgnoreChangeLocation = false;
  337. const refreshStation = (location: any) => {
  338. let length = 0;
  339. let available = 0;
  340. const { latitude, longitude } = location;
  341. if (!token.value) {
  342. return;
  343. }
  344. return fetchStations(
  345. stationPage.value.page,
  346. stationPage.value.pageSize,
  347. latitude,
  348. longitude,
  349. mapProps.value.selflatitude,
  350. mapProps.value.selflongitude,
  351. {
  352. distance: filterDialog.value.options.distance,
  353. status: filterDialog.value.options.status,
  354. }
  355. ).then((res) => {
  356. const _markersIndex = stationPage.value.page === 1 ? 0 : markersIndex.value;
  357. const _markers: any[] = res.map((item, index) => {
  358. length = 0;
  359. available = 0;
  360. item.equipmentInfos &&
  361. item.equipmentInfos.forEach((eq: any) => {
  362. eq.connectorInfos &&
  363. eq.connectorInfos.forEach((co: any) => {
  364. length += 1;
  365. if (
  366. co.connectorStatusInfo &&
  367. co.connectorStatusInfo.status === 1
  368. ) {
  369. available += 1;
  370. }
  371. });
  372. });
  373. return {
  374. id: Number(item.StationID),
  375. latitude: item.location.stationLat,
  376. longitude: item.location.stationLng,
  377. iconPath:
  378. index === _markersIndex
  379. ? pointSize.currentIconPath
  380. : pointSize.iconPath,
  381. width:
  382. index === _markersIndex ? pointSize.currentWidth : pointSize.width,
  383. height:
  384. index === _markersIndex ? pointSize.currentHeight : pointSize.height,
  385. label: {
  386. content: `${available}/${length}`,
  387. color: "#ffffff",
  388. fontSize:
  389. index === _markersIndex
  390. ? pointSize.currentFontSize
  391. : pointSize.fontSize,
  392. textAlign: isIOS.value ? "center" : "left",
  393. anchorX: isIOS.value
  394. ? 0
  395. : index === _markersIndex
  396. ? pointSize.androidCurrentX
  397. : pointSize.androidX,
  398. anchorY: -(
  399. (index === _markersIndex
  400. ? pointSize.currentHeight
  401. : pointSize.height) - 3
  402. ),
  403. },
  404. };
  405. });
  406. if (stationPage.value.page === 1) {
  407. _markers.push({
  408. id: -1,
  409. latitude: mapProps.value.selflatitude,
  410. longitude: mapProps.value.selflongitude,
  411. iconPath: "/static/images/map-current.png",
  412. width: 34,
  413. height: 34,
  414. });
  415. }
  416. isIgnoreChangeLocation = markers.value.length !== _markers.length;
  417. stationPage.value.hasNext = res.length >= stationPage.value.pageSize;
  418. // empty.value = stationPage.value.page === 1 ? res.length <= 0 : false;
  419. station.value =
  420. stationPage.value.page === 1 ? res : [...station.value, ...res];
  421. markersIndex.value = _markersIndex;
  422. markers.value = _markers;
  423. return res;
  424. });
  425. };
  426. const refresh = () => {
  427. if (loading.value) {
  428. return;
  429. }
  430. console.log("刷新电站");
  431. loading.value = true;
  432. stationPage.value.page = 1;
  433. stationPage.value.hasNext = false;
  434. station.value = [];
  435. markers.value = [];
  436. markersIndex.value = 0;
  437. fetchLocation()
  438. .then((res: any) => {
  439. mapProps.value.latitude = res.latitude;
  440. mapProps.value.longitude = res.longitude;
  441. mapProps.value.selflatitude = res.latitude;
  442. mapProps.value.selflongitude = res.longitude;
  443. isIgnoreChangeLocation = true;
  444. return refreshStation(res);
  445. })
  446. .then(() => {
  447. loading.value = false;
  448. ready.value = true;
  449. })
  450. .catch((err) => {
  451. console.log(err);
  452. loading.value = false;
  453. uni.showModal({
  454. content: `${err.errMsg},请重试`,
  455. });
  456. });
  457. };
  458. const handleNavReady = (e: any) => {
  459. styleData.value.dialog = `padding-top:${e.detail.statusBarHeight - 6}px;`;
  460. const searchHeight = rpxToPx(88);
  461. const filterHeight = rpxToPx(72);
  462. styleData.value.dialogHeight =
  463. e.detail.statusBarHeight - 6 + searchHeight + filterHeight;
  464. styleData.value.dialogPlaceHolderHeight =
  465. styleData.value.dialogHeight - e.detail.navigationBarHeight;
  466. styleData.value.cardHeight = rpxToPx(410);
  467. };
  468. const toSearch = () => {
  469. uni.navigateTo({
  470. url: "/pages-charge/search/search",
  471. });
  472. };
  473. onLoad((query: any) => {
  474. // 只为了打包进tab-bar使用
  475. console.log(fetchToken, login, onLogin);
  476. // 扫普通码
  477. if (query.q) {
  478. console.log("扫普通码", decodeURIComponent(query.q));
  479. getApp<any>().globalData.normalCode = decodeURIComponent(query.q); // 获取到二维码原始链接内容
  480. }
  481. const device = uni.getSystemInfoSync();
  482. isIOS.value = device.osName === "ios";
  483. setTimeout(() => {
  484. token.value = getApp<any>().globalData.token || "";
  485. if (!token.value) {
  486. isIgnoreChangeLocation = true;
  487. fetchLocation().then((res: any) => {
  488. mapProps.value.latitude = res.latitude;
  489. mapProps.value.longitude = res.longitude;
  490. mapProps.value.selflatitude = res.latitude;
  491. mapProps.value.selflongitude = res.longitude;
  492. markers.value = [
  493. {
  494. id: -1,
  495. latitude: res.latitude,
  496. longitude: res.longitude,
  497. iconPath: "/static/images/map-current.png",
  498. width: 34,
  499. height: 34,
  500. },
  501. ];
  502. });
  503. onLogin((_token) => {
  504. if (getApp<any>().globalData.normalCode) {
  505. const code: string = getApp<any>().globalData.normalCode;
  506. getApp<any>().globalData.normalCode = "";
  507. deCode(code);
  508. }
  509. token.value = _token;
  510. fetchCollectList().then(() => {
  511. refresh();
  512. });
  513. });
  514. return;
  515. }
  516. fetchCollectList().then(() => {
  517. if (getApp<any>().globalData.normalCode) {
  518. const code: string = getApp<any>().globalData.normalCode;
  519. getApp<any>().globalData.normalCode = "";
  520. deCode(code);
  521. }
  522. refresh();
  523. });
  524. }, 300);
  525. });
  526. const checkDiscounts = () => {
  527. filterDialog.value.discounts = !filterDialog.value.discounts;
  528. };
  529. const checkFilterDistance = () => {
  530. if (!mapMode.value) {
  531. mapMode.value = true
  532. }
  533. filterDialog.value.distanceSelector = !filterDialog.value.distanceSelector;
  534. };
  535. const changeFilterDistance = (index: number) => {
  536. filterDialog.value.options.distance =
  537. filterDialog.value.distanceRange[index].value;
  538. if (mapProps.value.scale === filterDialog.value.distanceRange[index].scale) {
  539. refresh();
  540. return;
  541. }
  542. mapProps.value.scale = filterDialog.value.distanceRange[index].scale;
  543. checkFilterDistance();
  544. };
  545. const resetLocation = () => {
  546. if (loading.value) {
  547. return;
  548. }
  549. // eslint-disable-next-line promise/catch-or-return
  550. fetchLocation().then((res: any) => {
  551. const mapCtx = uni.createMapContext("map");
  552. const { latitude, longitude } = res;
  553. mapCtx.moveToLocation({
  554. latitude,
  555. longitude,
  556. });
  557. mapProps.value.scale = defaultScale;
  558. });
  559. };
  560. const mapUpdated = (e: any) => {
  561. // console.log('map updated', isIgnoreChangeLocation)
  562. setTimeout(() => {
  563. isIgnoreChangeLocation = false;
  564. }, 500);
  565. };
  566. const mapChange = (e: any) => {
  567. if (isIgnoreChangeLocation) {
  568. return;
  569. }
  570. if (e.type === "end" && markers.value.length) {
  571. console.log("map change end", {
  572. ...e.detail.centerLocation,
  573. });
  574. const current = e.target.centerLocation;
  575. const { latitude, longitude } = current;
  576. stationPage.value.page = 1;
  577. refreshStation({
  578. latitude,
  579. longitude,
  580. });
  581. }
  582. };
  583. const _changeMarker = (current: number) => {
  584. const _markers = JSON.parse(JSON.stringify(markers.value));
  585. const markersNewIndex = current;
  586. _markers[markersIndex.value].iconPath = pointSize.iconPath;
  587. _markers[markersIndex.value].width = pointSize.width;
  588. _markers[markersIndex.value].height = pointSize.height;
  589. _markers[markersIndex.value].label.fontSize = pointSize.fontSize;
  590. _markers[markersIndex.value].label.anchorY = -(pointSize.height - 3);
  591. if (!isIOS) {
  592. _markers[markersIndex.value].label.anchorX = pointSize.androidX;
  593. }
  594. _markers[markersNewIndex].iconPath = pointSize.currentIconPath;
  595. _markers[markersNewIndex].width = pointSize.currentWidth;
  596. _markers[markersNewIndex].height = pointSize.currentHeight;
  597. _markers[markersNewIndex].label.fontSize = pointSize.currentFontSize;
  598. _markers[markersNewIndex].label.anchorY = -(pointSize.currentHeight - 3);
  599. if (!isIOS) {
  600. _markers[markersNewIndex].label.anchorX = pointSize.androidCurrentX;
  601. }
  602. isIgnoreChangeLocation = true;
  603. markers.value = _markers;
  604. markersIndex.value = markersNewIndex;
  605. if (stationPage.value.hasNext && markersNewIndex >= _markers.length - 2) {
  606. stationPage.value.page += 1;
  607. refreshStation({
  608. latitude: mapProps.value.selflatitude,
  609. longitude: mapProps.value.selflongitude,
  610. });
  611. }
  612. };
  613. const tapMarker = (e: any) => {
  614. if (e.detail.markerId === -1) {
  615. return;
  616. }
  617. const findIndex = station.value.findIndex(
  618. (item) => Number(item.StationID) === Number(e.detail.markerId)
  619. );
  620. if (findIndex >= 0) {
  621. _changeMarker(findIndex);
  622. }
  623. };
  624. const loginMask = (e: any) => {
  625. login(e);
  626. };
  627. let startpageY = 0;
  628. const touchCardStart = (e: any) => {
  629. if (loading.value || station.value.length <= 0) {
  630. return;
  631. }
  632. if (e.touches && e.touches.length) {
  633. startpageY = e.touches[0].pageY;
  634. }
  635. };
  636. const touchCardMove = (e: any) => {
  637. if (!startpageY) {
  638. return;
  639. }
  640. const threshold = 150;
  641. if (e.touches && e.touches.length) {
  642. if (mapMode.value && startpageY - e.touches[0].pageY > threshold) {
  643. mapMode.value = false;
  644. }
  645. if (!mapMode.value && e.touches[0].pageY - startpageY > threshold) {
  646. mapMode.value = true;
  647. }
  648. }
  649. };
  650. // TORM
  651. // const changeMarker = (e: any) => {
  652. // _changeMarker(e.detail.current);
  653. // };
  654. // const emptyTap = () => {};
  655. // const changeFilterStatus = (index: number) => {
  656. // filterDialog.value.options.status = index;
  657. // };
  658. // const resetFilter = () => {
  659. // filterDialog.value.options.distance = defaulDistance;
  660. // filterDialog.value.options.status = 0;
  661. // refresh();
  662. // };
  663. </script>
  664. <style lang="scss">
  665. page {
  666. background-color: #ffffff;
  667. }
  668. .container {
  669. position: relative;
  670. height: 100vh;
  671. width: 100vw;
  672. background-color: #ffffff;
  673. }
  674. .login-mask {
  675. position: fixed;
  676. left: 0;
  677. top: 0;
  678. width: 100%;
  679. height: 100%;
  680. z-index: 999999;
  681. opacity: 0;
  682. .full {
  683. width: 100%;
  684. height: 100%;
  685. }
  686. }
  687. .dialog {
  688. position: fixed;
  689. top: 0;
  690. left: 0;
  691. width: 100%;
  692. z-index: 999;
  693. &_event {
  694. margin-top: 20rpx;
  695. padding: 0rpx 32rpx;
  696. .swiper {
  697. height: 176rpx;
  698. width: 100%;
  699. border-radius: 16rpx;
  700. overflow: hidden;
  701. }
  702. }
  703. &_selector {
  704. background-color: #f9f9f9;
  705. transition: all 0.3s;
  706. overflow: hidden;
  707. .type {
  708. width: 160rpx;
  709. height: 60rpx;
  710. background: var(--color-sec);
  711. border-radius: 4rpx;
  712. color: #666;
  713. font-size: 26rpx;
  714. border: 1px solid var(--color-sec);
  715. }
  716. .type-active {
  717. border: 1px solid var(--color-primary);
  718. color: var(--color-primary);
  719. }
  720. }
  721. /* .slider {
  722. position: relative;
  723. height: 12rpx;
  724. width: 100%;
  725. border-radius: 8rpx;
  726. background-color: var(--color-sec);
  727. margin-top: 10rpx;
  728. &_active {
  729. position: absolute;
  730. left: 0;
  731. top: 0;
  732. background-color: var(--color-primary);
  733. border-radius: 8rpx;
  734. height: 12rpx;
  735. width: 0%;
  736. }
  737. &_block {
  738. position: absolute;
  739. width: 40rpx;
  740. height: 40rpx;
  741. border-radius: 50%;
  742. top: -15rpx;
  743. left: 0;
  744. background: rgba(255, 255, 255, 1);
  745. box-shadow: 0px 4rpx 6rpx rgba(52, 125, 255, 0.4);
  746. }
  747. &_wx {
  748. position: absolute;
  749. width: 100%;
  750. left: 0px;
  751. top: -5px;
  752. margin: 0;
  753. opacity: 0;
  754. }
  755. } */
  756. }
  757. .card {
  758. position: fixed;
  759. width: 100%;
  760. left: 0px;
  761. z-index: 999;
  762. transition: all 0.3s;
  763. box-shadow: 0px 8rpx 20rpx rgba(0, 0, 0, 0.2);
  764. background-color: #fff;
  765. margin-bottom: constant(safe-area-inset-bottom);
  766. margin-bottom: env(safe-area-inset-bottom);
  767. overflow-y: auto;
  768. &_location {
  769. position: absolute;
  770. border-radius: 16rpx;
  771. box-shadow: 0px 8rpx 20rpx 0px rgba(0, 0, 0, 0.2);
  772. right: 20rpx;
  773. top: -104rpx;
  774. }
  775. &_line {
  776. height: 1rpx;
  777. background-color: rgba(0, 0, 0, 0.1);
  778. margin-left: 30rpx;
  779. margin-right: 30rpx;
  780. }
  781. &_empty {
  782. width: 100%;
  783. height: 100%;
  784. .image {
  785. width: 160rpx;
  786. }
  787. }
  788. }
  789. </style>