index.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616
  1. <template>
  2. <FlexCol>
  3. <FlexCol :gap="20" :padding="[30,30,0,30]" :innerStyle="{
  4. marginTop: '-80px',
  5. backgroundImage: `url(${appConfiguration?.banners.homeTop})`,
  6. backgroundSize: '100% auto',
  7. backgroundRepeat: 'no-repeat',
  8. backgroundPosition: 'top center',
  9. backgroundColor: themeContext.resolveThemeColor('background.primary'),
  10. }">
  11. <FlexCol position="absolute" :left="30" :top="0" :right="30">
  12. <StatusBarSpace />
  13. <FlexRow justify="space-between" align="center">
  14. <Button
  15. @click="showCityPopup = true"
  16. icon="https://xy.wenlvti.net/app_static/images/home/IconSwitch.png"
  17. size="medium"
  18. :text="currentCity"
  19. />
  20. <Image
  21. :src="appConfiguration?.banners.homeTitle.url"
  22. :width="appConfiguration?.banners.homeTitle.width || 140"
  23. :height="appConfiguration?.banners.homeTitle.height || 75"
  24. :innerStyle="appConfiguration?.banners.homeTitle.innerStyle"
  25. />
  26. <Width :width="150" />
  27. </FlexRow>
  28. </FlexCol>
  29. <SimpleTransition name="fade" :show="currentScrollTop > 1000" :duration="300">
  30. <template #show="{ classNames }">
  31. <view
  32. :class="['top-nav-fade ',...classNames]"
  33. :style="{ backgroundColor: themeContext.resolveThemeColor('background.primary') }"
  34. :left="0" :top="0" :right="0" :zIndex="200"
  35. >
  36. <StatusBarSpace />
  37. </view>
  38. </template>
  39. </SimpleTransition>
  40. <Height height="150px" />
  41. <FlexCol :gap="20" align="center">
  42. <FlexRow justify="space-between" align="center" width="100%">
  43. <SearchBar
  44. v-model="searchKeywords"
  45. placeholder="搜索"
  46. :innerStyle="{
  47. backgroundColor: 'white',
  48. borderRadius: '20rpx',
  49. borderWidth: '1px',
  50. borderStyle: 'solid',
  51. borderColor: themeContext.resolveThemeColor('primary'),
  52. width: '630rpx',//490rpx
  53. }"
  54. @search="handleSearch"
  55. />
  56. <!-- <Button icon="ai-thinking" @click="handleGoAI" text="问AI" /> -->
  57. </FlexRow>
  58. <ImageSwiper
  59. :images="bannerLoader.content.value || []"
  60. :height="appConfiguration?.banners.home.height"
  61. :width="700"
  62. radius="radius.md"
  63. :innerStyle="{
  64. border: '1px solid #fff',
  65. overflow: 'hidden',
  66. clipPath: 'ellipse(100% 90% at 50% 0%)'
  67. }"
  68. />
  69. </FlexCol>
  70. <LightMap
  71. small
  72. :city="currentCity"
  73. :cityCode="currentCityCode"
  74. :district="currentDistrict"
  75. :lonlat="currentLocation.currentLonlat.value"
  76. @getCurrentLonlat="currentLocation.getCurrentExactLocation"
  77. @mapClick="handleGoMap"
  78. >
  79. <NoticeBar
  80. v-if="currentNoticeContent"
  81. :content="currentNoticeContent"
  82. :innerStyle="{
  83. position: 'absolute',
  84. top: '20rpx',
  85. left: '20rpx',
  86. right: '20rpx',
  87. zIndex: 100,
  88. borderRadius: '30rpx',
  89. }"
  90. :textStyle="{
  91. fontSize: '26rpx',
  92. }"
  93. icon="https://xy.wenlvti.net/app_static/images/home/IconLightActive.png"
  94. :iconProps="{
  95. size: 34,
  96. }"
  97. textColor="#C9211F"
  98. backgroundColor="#D9492E10"
  99. />
  100. </LightMap>
  101. <FlexRow justify="space-between" :padding="[10, 16]" gap="gap.md">
  102. <Button
  103. icon="https://xy.wenlvti.net/app_static/images/home/IconSwitch.png"
  104. radius="radius.lg"
  105. :padding="[10, 30]"
  106. text="切换城市"
  107. @click="showCityPopup = true"
  108. />
  109. <Button
  110. icon="https://xy.wenlvti.net/app_static/images/home/IconFollow.png"
  111. radius="radius.lg" :padding="[10, 30]"
  112. @click="showMyFollowPopup = true"
  113. text="我的关注"
  114. />
  115. <BubbleTip
  116. v-model:show="showLightTip"
  117. content="没有加入村社?点击这里点亮村落"
  118. position="bottom"
  119. crossPosition="right"
  120. :arrowOffsetX="130"
  121. @contentClick="handleLightVillage"
  122. @close="handleCloseLightTip"
  123. >
  124. <Button
  125. icon="https://xy.wenlvti.net/app_static/images/home/IconLight.png"
  126. radius="radius.lg" :padding="[10, 30]"
  127. text="点亮村社"
  128. @click="handleLightVillage"
  129. />
  130. </BubbleTip>
  131. </FlexRow>
  132. <HomeTitle title="乡村排名" showMore @moreClicked="navTo('/pages/home/village/rank/village', {})" />
  133. <VillageRankList :list="villageRankListLoader.content.value ?? []" :jumpToSingle="false" @goDetails="handleGoVillageDetails" />
  134. <HomeTitle title="志愿者排名" showMore :lightCount="3" @moreClicked="navTo('/pages/home/village/rank/volunteer', {})" />
  135. <VillageUserRankList
  136. :list="villageUserRankListLoader.content.value ?? []"
  137. scoreSuffix="积分"
  138. @goDetails="navTo('/pages/home/village/volunteer/detail', { id: $event.id })"
  139. />
  140. </FlexCol>
  141. <view class="recommmend-header" :style="stickyStyle">
  142. <FlexCol :padding="[0,30]">
  143. <HomeTitle title="精选记忆">
  144. <template #right>
  145. <FrameButton
  146. size="small"
  147. icon="https://xy.wenlvti.net/app_static/images/village/IconLargeHistory.png"
  148. text="我也来写"
  149. @click="handleGoPublish()"
  150. />
  151. </template>
  152. </HomeTitle>
  153. </FlexCol>
  154. <Tabs
  155. v-model:currentIndex="recommendTabCurrent"
  156. :tabs="recommendTabs"
  157. />
  158. </view>
  159. <FlexCol padding="padding.md">
  160. <SimplePageListLoader v-if="recommendTabCurrent === 0" :loader="recommendLoader">
  161. <MasonryGrid :items="recommendLoader.list.value" itemKey="id">
  162. <template #default="{ item }">
  163. <IndexCommonImageItem
  164. :width="354"
  165. :image="item.image"
  166. :title="item.title"
  167. :desc="item.content ?? ''"
  168. :userName="item.villageVolunteerName ?? ''"
  169. :userAvatar="item.villageVolunteerAvatar"
  170. :likes="item.likeNum"
  171. :isLike="item.likeType === 1"
  172. @click="handleGoRecommendDetails(item)"
  173. />
  174. </template>
  175. </MasonryGrid>
  176. </SimplePageListLoader>
  177. <SimplePageListLoader v-else-if="recommendTabCurrent === 1" :loader="goodsLoader">
  178. <FlexRow wrap justify="space-around" gap="gap.sm">
  179. <BoxMid
  180. v-for="good in goodsLoader.list.value" :key="good.id"
  181. :innerStyle="{
  182. width: 'calc(50% - 70rpx)',
  183. }"
  184. >
  185. <Touchable direction="column" gap="gap.lg" @click="handleGoRecommendDetails(good)">
  186. <Image
  187. :src="good.image"
  188. :radius="20"
  189. width="100%"
  190. height="300rpx"
  191. mode="aspectFill"
  192. />
  193. <Text :text="good.title" fontConfig="titleText" />
  194. <FlexRow align="center" gap="gap.md">
  195. <Avatar
  196. :src="good.villageVolunteerAvatar"
  197. :size="40"
  198. defaultAvatar="https://xy.wenlvti.net/app_static/images/mine/DefaultAvatar.png"
  199. />
  200. <Text :text="`发布人: ${good.villageVolunteerName}`" fontConfig="contentText" />
  201. </FlexRow>
  202. </Touchable>
  203. </BoxMid>
  204. <FlexCol v-if="goodsLoader.list.value && goodsLoader.list.value.length % 2 !== 0" width="calc(50%)" />
  205. </FlexRow>
  206. </SimplePageListLoader>
  207. <SimplePageListLoader v-else-if="recommendTabCurrent === 2" :loader="travelLoader">
  208. <FlexRow wrap justify="space-between" gap="gap.sm">
  209. <IndexCommonImageItem
  210. v-for="item in travelLoader.list.value" :key="item.id"
  211. :width="354"
  212. fixHeight
  213. :image="item.image"
  214. :title="item.title"
  215. :desc="item.content ?? ''"
  216. :userName="item.villageVolunteerName ?? ''"
  217. :userAvatar="item.villageVolunteerAvatar"
  218. :likes="item.likeNum"
  219. :isLike="item.likeType === 1"
  220. @click="handleGoRecommendDetails(item)"
  221. />
  222. </FlexRow>
  223. </SimplePageListLoader>
  224. <SimplePageListLoader v-else-if="recommendTabCurrent === 3" :loader="culturalLoader">
  225. <FlexRow wrap justify="space-between" gap="gap.sm">
  226. <IndexCommonImageItem
  227. v-for="item in culturalLoader.list.value" :key="item.id"
  228. :width="354"
  229. fixHeight
  230. :image="item.image"
  231. :title="item.title"
  232. :desc="item.content ?? ''"
  233. :userName="item.villageVolunteerName ?? ''"
  234. :userAvatar="item.villageVolunteerAvatar"
  235. :likes="item.likeNum"
  236. :isLike="item.likeType === 1"
  237. @click="handleGoRecommendDetails(item)"
  238. />
  239. </FlexRow>
  240. </SimplePageListLoader>
  241. </FlexCol>
  242. <Height :height="200" />
  243. <Popup
  244. v-model:show="showCityPopup"
  245. closeable
  246. position="top"
  247. size="60vh"
  248. >
  249. <CitySelect @selectCity="handleSelectCity" />
  250. </Popup>
  251. <Popup
  252. v-model:show="showMyFollowPopup"
  253. closeable
  254. position="bottom"
  255. round
  256. size="80vh"
  257. >
  258. <VillageMyFollow @goDetails="handleGoVillageDetails" />
  259. </Popup>
  260. <PostIndex
  261. ref="postIndexRef"
  262. @goDig="emit('goDig')"
  263. />
  264. <IntroClamTip ref="introClamTipRef" @apply="handleLightVillage" />
  265. </FlexCol>
  266. </template>
  267. <script setup lang="ts">
  268. import { onMounted, ref, watch } from 'vue';
  269. import { useTheme } from '@/components/theme/ThemeDefine';
  270. import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
  271. import { useStorageVar } from '@/components/composeabe/StorageVar';
  272. import { useVillageStore } from '@/store/village';
  273. import { useGetCurrentLocation } from './composeabe/GetCurrentLocation';
  274. import { useAuthStore } from '@/store/auth';
  275. import { useRequireLogin } from '@/common/composeabe/RequireLogin';
  276. import { useUserTools } from '@/common/composeabe/UserTools';
  277. import { useSimplePageListLoader } from '@/components/composeabe/loader/SimplePageListLoader';
  278. import { useGetNotice } from './village/composeabe/GetNotice';
  279. import { waitTimeOut } from '@imengyu/imengyu-utils';
  280. import { toast, loading, hideLoading, confirm } from '@/components/utils/DialogAction';
  281. import { navTo } from '@/components/utils/PageAction';
  282. import { injectAppConfiguration } from '@/api/system/useAppConfiguration';
  283. import { CollectableModulesIdMap } from '@/common/data/CollectableModulesIdMap';
  284. import Image from '@/components/basic/Image.vue';
  285. import FlexCol from '@/components/layout/FlexCol.vue';
  286. import FlexRow from '@/components/layout/FlexRow.vue';
  287. import Height from '@/components/layout/space/Height.vue';
  288. import SearchBar from '@/components/form/SearchBar.vue';
  289. import Button from '@/components/basic/Button.vue';
  290. import HomeTitle from '@/common/components/parts/HomeTitle.vue';
  291. import VillageRankList from './components/VillageRankList.vue';
  292. import VillageUserRankList from './components/VillageUserRankList.vue';
  293. import Popup from '@/components/dialog/Popup.vue';
  294. import CitySelect from './components/CitySelect.vue';
  295. import VillageMyFollow from './components/VillageMyFollow.vue';
  296. import LightMap from './components/LightMap.vue';
  297. import NoticeBar from '@/components/display/NoticeBar.vue';
  298. import StatusBarSpace from '@/components/layout/space/StatusBarSpace.vue';
  299. import LightVillageApi, { VillageListItem, type VillageMapItem } from '@/api/light/LightVillageApi';
  300. import Width from '@/components/layout/space/Width.vue';
  301. import IndexCommonImageItem from '@/common/components/parts/IndexCommonImageItem.vue';
  302. import IntroClamTip from './village/dialogs/IntroClamTip.vue';
  303. import MemoryTimeOut from '@/components/composeabe/MemoryTimeOut';
  304. import ImageSwiper from '@/common/components/parts/ImageSwiper.vue';
  305. import VillageInfoApi, { type CommonInfoModel } from '@/api/inhert/VillageInfoApi';
  306. import SimplePageListLoader from '@/components/loader/SimplePageListLoader.vue';
  307. import PostIndex from './village/dialogs/PostIndex.vue';
  308. import FrameButton from '@/common/components/FrameButton.vue';
  309. import type { RegionItem } from '@/api/map/RegionApi';
  310. import BubbleTip from '@/components/feedback/BubbleTip.vue';
  311. import BannerApi from '@/api/system/Banner';
  312. import Tabs from '@/components/nav/Tabs.vue';
  313. import BoxMid from '@/common/components/box/BoxMid.vue';
  314. import Touchable from '@/components/feedback/Touchable.vue';
  315. import Text from '@/components/basic/Text.vue';
  316. import Avatar from '@/components/display/Avatar.vue';
  317. import MasonryGrid from '@/components/layout/masonry/MasonryGrid.vue';
  318. import { DynamicColor, statusBarHeight } from '@/components/theme/ThemeTools';
  319. import SimpleTransition from '@/components/anim/SimpleTransition.vue';
  320. const props = defineProps({
  321. currentScrollTop: {
  322. type: Number,
  323. default: 0,
  324. },
  325. });
  326. const emit = defineEmits(['goVillage','goDig']);
  327. const authStore = useAuthStore();
  328. const villageStore = useVillageStore();
  329. const themeContext = useTheme();
  330. const { requireLogin } = useRequireLogin();
  331. const { getIsVolunteer } = useUserTools();
  332. const searchKeywords = ref('');
  333. const showCityPopup = ref(false);
  334. const showMyFollowPopup = ref(false);
  335. const introClamTipRef = ref();
  336. const introClamTipTimeout = new MemoryTimeOut('IntroClamTip', 1000 * 3600 * 30);//30h
  337. const postIndexRef = ref<InstanceType<typeof PostIndex>>();
  338. const appConfiguration = injectAppConfiguration();
  339. const { value: currentCity } = useStorageVar('currentCityName', '');
  340. const { value: currentCityCode } = useStorageVar('currentCityCode', 0);
  341. const { value: currentDistrict } = useStorageVar('currentDistrict', '');
  342. const currentLocation = useGetCurrentLocation({
  343. onCityChanged: (city, code, district) => {
  344. currentCity.value = city;
  345. currentCityCode.value = code;
  346. currentDistrict.value = district || '';
  347. },
  348. });
  349. const { currentNoticeContent } = useGetNotice();
  350. const bannerLoader = useSimpleDataLoader(async () => {
  351. return (await BannerApi.getBannerList({
  352. keywords: '亮乡源大众版首页头部',
  353. })).map(p => ({
  354. image: p.image,
  355. url: p.url,
  356. }));
  357. });
  358. const villageRankListLoader = useSimpleDataLoader(async () => {
  359. const res = await LightVillageApi.getVillageRankList({ num: 3 });
  360. return res.map((item, i) => ({
  361. image: item.image ?? '',
  362. title: item.name,
  363. rank: i + 1,
  364. id: item.id,
  365. }));
  366. });
  367. const villageUserRankListLoader = useSimpleDataLoader(async () => {
  368. const res = (await LightVillageApi.getVolunteerRankList({ num: 3 }))
  369. .map((item, i) => ({
  370. id: item.id,
  371. image: item.avatar ?? '',
  372. title: item.name,
  373. rank: i + 1,
  374. score: item.points,
  375. }));
  376. return res
  377. });
  378. const showLightTipTimeout = new MemoryTimeOut('LightTip', 1000 * 3600 * 2);//2h
  379. const showLightTip = ref(false);
  380. function handleShowLightTip() {
  381. if (showLightTipTimeout.isTimeout())
  382. showLightTip.value = true;
  383. }
  384. function handleCloseLightTip() {
  385. showLightTipTimeout.recordTime();
  386. }
  387. const recommendTabCurrent = ref(0);
  388. const recommendTabs = ref([
  389. {
  390. text: '推荐',
  391. value: 0,
  392. },
  393. {
  394. text: '好物',
  395. value: 1,
  396. },
  397. {
  398. text: '乡游',
  399. value: 2,
  400. },
  401. {
  402. text: '文化',
  403. value: 3,
  404. },
  405. ]);
  406. const recommendLoader = useSimplePageListLoader(20, async (page, pageSize) => {
  407. return await VillageInfoApi.getListForDiscover({ page, pageSize });
  408. }, true);
  409. const goodsLoader = useSimplePageListLoader(20, async (page, pageSize) => {
  410. return await VillageInfoApi.getListForDiscover({
  411. page,
  412. pageSize,
  413. collectModuleId: CollectableModulesIdMap['goods'],
  414. });
  415. }, false);
  416. const travelLoader = useSimplePageListLoader(20, async (page, pageSize) => {
  417. return await VillageInfoApi.getMiltListForDiscover({
  418. page,
  419. pageSize,
  420. collectModuleIds: [
  421. CollectableModulesIdMap['travel_guide'],
  422. CollectableModulesIdMap['route'],
  423. CollectableModulesIdMap['environment'],
  424. CollectableModulesIdMap['relic'],
  425. CollectableModulesIdMap['spots'],
  426. ],
  427. });
  428. }, true);
  429. const culturalLoader = useSimplePageListLoader(20, async (page, pageSize) => {
  430. return await VillageInfoApi.getMiltListForDiscover({
  431. page,
  432. pageSize,
  433. collectModuleIds: [
  434. CollectableModulesIdMap['cultural'],
  435. CollectableModulesIdMap['folk_culture'],
  436. CollectableModulesIdMap['story'],
  437. ],
  438. });
  439. }, true);
  440. const stickyStyle = themeContext.useThemeStyle({
  441. position: 'sticky',
  442. top: (statusBarHeight - 10) + 'px',
  443. left: 0,
  444. right: 0,
  445. zIndex: '100',
  446. backgroundColor: DynamicColor('background.primary'),
  447. });
  448. function handleGoRecommendDetails(item: CommonInfoModel) {
  449. navTo(`/pages/home/discover/details`, { id: item.id });
  450. }
  451. async function handleGoVillageDetails(item: VillageListItem) {
  452. showMyFollowPopup.value = false;
  453. const details = await LightVillageApi.getVillageDetails(item.id);
  454. villageStore.setCurrentVillage(details);
  455. await waitTimeOut(100);
  456. emit('goVillage')
  457. }
  458. function handleSelectCity(city: RegionItem) {
  459. currentCity.value = city.name;
  460. currentCityCode.value = city.areaCode;
  461. showCityPopup.value = false;
  462. currentLocation.currentLonlat.value = {
  463. longitude: Number(city.longitude),
  464. latitude: Number(city.latitude),
  465. };
  466. }
  467. function handleGoAI() {
  468. requireLogin(async () => {
  469. navTo('/pages/chat/index');
  470. }, '暂时需要登录后才能使用AI助手');
  471. }
  472. async function handleGoPublish() {
  473. requireLogin(async () => {
  474. postIndexRef.value?.show();
  475. }, '欢迎您发布内容,登录后以便使用更多功能哦!');
  476. }
  477. async function handleLightVillage() {
  478. requireLogin(async () => navTo('/pages/home/light/create-village'), '登录后才能点亮村社哦!');
  479. }
  480. function handleGoMap() {
  481. navTo('/pages/home/light/map', {
  482. city: currentCity.value,
  483. cityCode: currentCityCode.value,
  484. district: currentDistrict.value,
  485. });
  486. }
  487. function handleSearch() {
  488. if (!searchKeywords.value) {
  489. toast('请输入搜索关键词');
  490. return;
  491. }
  492. navTo('/pages/home/search/index', {
  493. city: currentCity.value,
  494. cityCode: currentCityCode.value,
  495. searchValue: searchKeywords.value,
  496. });
  497. }
  498. async function loadInfo() {
  499. loading('载入数据中');
  500. //如果不是志愿者,则显示认领村庄提示
  501. const isVolunteer = await getIsVolunteer();
  502. if (!isVolunteer) {
  503. if (introClamTipTimeout.isTimeout()) {
  504. introClamTipRef.value?.show();
  505. introClamTipTimeout.recordTime();
  506. }
  507. }
  508. //加载我的关注村庄
  509. try {
  510. await villageStore.loadMyFollowVillages();
  511. } catch (error) {
  512. console.error(error);
  513. }
  514. try {
  515. await villageStore.loadMyJoinedVillages();
  516. } catch (error) {
  517. console.error(error);
  518. }
  519. handleShowLightTip();
  520. try {
  521. if (!villageStore.currentVillage && villageStore.myFollowVillages.length > 0) {
  522. const currentVillage = villageStore.loadCurrentVillage();
  523. const id = (villageStore.myFollowVillages.find(p => p.id === currentVillage) as VillageListItem || villageStore.myFollowVillages[0]).id;
  524. const res = await LightVillageApi.getVillageDetails(id)
  525. villageStore.setCurrentVillage(res);
  526. }
  527. } finally {
  528. hideLoading();
  529. }
  530. }
  531. watch(() => authStore.isLogged, async (newVal) => {
  532. if (newVal) {
  533. await loadInfo();
  534. }
  535. }, { immediate: true });
  536. onMounted(async () => {
  537. try {
  538. if (currentCity.value) {
  539. await currentLocation.setCurrentLocationWithCity(currentCity.value, currentDistrict.value);
  540. } else {
  541. await currentLocation.getCurrentFuzzyLocation();
  542. }
  543. } catch (error) {
  544. console.error(error);
  545. toast('获取当前位置失败,您可以手动选择城市');
  546. }
  547. });
  548. defineExpose({
  549. onPageBack: (name: string, data: Record<string, unknown>) => {
  550. if (name === 'changeCurrentCity') {
  551. currentCity.value = data.city as string;
  552. currentCityCode.value = data.code as number;
  553. currentDistrict.value = '';
  554. currentLocation.currentLonlat.value = {
  555. longitude: Number(data.longitude),
  556. latitude: Number(data.latitude),
  557. };
  558. }
  559. }
  560. })
  561. </script>
  562. <style lang="scss">
  563. .top-nav-fade {
  564. position: fixed;
  565. top: 0;
  566. left: 0;
  567. right: 0;
  568. z-index: 200;
  569. transition: opacity 0.3s ease-in-out;
  570. &.fade-enter-active,
  571. &.fade-leave-active {
  572. opacity: 1;
  573. }
  574. &.fade-enter-from,
  575. &.fade-leave-to {
  576. opacity: 0;
  577. }
  578. }
  579. </style>