| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616 |
- <template>
- <FlexCol>
- <FlexCol :gap="20" :padding="[30,30,0,30]" :innerStyle="{
- marginTop: '-80px',
- backgroundImage: `url(${appConfiguration?.banners.homeTop})`,
- backgroundSize: '100% auto',
- backgroundRepeat: 'no-repeat',
- backgroundPosition: 'top center',
- backgroundColor: themeContext.resolveThemeColor('background.primary'),
- }">
- <FlexCol position="absolute" :left="30" :top="0" :right="30">
- <StatusBarSpace />
- <FlexRow justify="space-between" align="center">
- <Button
- @click="showCityPopup = true"
- icon="https://xy.wenlvti.net/app_static/images/home/IconSwitch.png"
- size="medium"
- :text="currentCity"
- />
- <Image
- :src="appConfiguration?.banners.homeTitle.url"
- :width="appConfiguration?.banners.homeTitle.width || 140"
- :height="appConfiguration?.banners.homeTitle.height || 75"
- :innerStyle="appConfiguration?.banners.homeTitle.innerStyle"
- />
- <Width :width="150" />
- </FlexRow>
- </FlexCol>
- <SimpleTransition name="fade" :show="currentScrollTop > 1000" :duration="300">
- <template #show="{ classNames }">
- <view
- :class="['top-nav-fade ',...classNames]"
- :style="{ backgroundColor: themeContext.resolveThemeColor('background.primary') }"
- :left="0" :top="0" :right="0" :zIndex="200"
- >
- <StatusBarSpace />
- </view>
- </template>
- </SimpleTransition>
- <Height height="150px" />
- <FlexCol :gap="20" align="center">
- <FlexRow justify="space-between" align="center" width="100%">
- <SearchBar
- v-model="searchKeywords"
- placeholder="搜索"
- :innerStyle="{
- backgroundColor: 'white',
- borderRadius: '20rpx',
- borderWidth: '1px',
- borderStyle: 'solid',
- borderColor: themeContext.resolveThemeColor('primary'),
- width: '630rpx',//490rpx
- }"
- @search="handleSearch"
- />
- <!-- <Button icon="ai-thinking" @click="handleGoAI" text="问AI" /> -->
- </FlexRow>
- <ImageSwiper
- :images="bannerLoader.content.value || []"
- :height="appConfiguration?.banners.home.height"
- :width="700"
- radius="radius.md"
- :innerStyle="{
- border: '1px solid #fff',
- overflow: 'hidden',
- clipPath: 'ellipse(100% 90% at 50% 0%)'
- }"
- />
- </FlexCol>
- <LightMap
- small
- :city="currentCity"
- :cityCode="currentCityCode"
- :district="currentDistrict"
- :lonlat="currentLocation.currentLonlat.value"
- @getCurrentLonlat="currentLocation.getCurrentExactLocation"
- @mapClick="handleGoMap"
- >
- <NoticeBar
- v-if="currentNoticeContent"
- :content="currentNoticeContent"
- :innerStyle="{
- position: 'absolute',
- top: '20rpx',
- left: '20rpx',
- right: '20rpx',
- zIndex: 100,
- borderRadius: '30rpx',
- }"
- :textStyle="{
- fontSize: '26rpx',
- }"
- icon="https://xy.wenlvti.net/app_static/images/home/IconLightActive.png"
- :iconProps="{
- size: 34,
- }"
- textColor="#C9211F"
- backgroundColor="#D9492E10"
- />
- </LightMap>
- <FlexRow justify="space-between" :padding="[10, 16]" gap="gap.md">
- <Button
- icon="https://xy.wenlvti.net/app_static/images/home/IconSwitch.png"
- radius="radius.lg"
- :padding="[10, 30]"
- text="切换城市"
- @click="showCityPopup = true"
- />
- <Button
- icon="https://xy.wenlvti.net/app_static/images/home/IconFollow.png"
- radius="radius.lg" :padding="[10, 30]"
- @click="showMyFollowPopup = true"
- text="我的关注"
- />
- <BubbleTip
- v-model:show="showLightTip"
- content="没有加入村社?点击这里点亮村落"
- position="bottom"
- crossPosition="right"
- :arrowOffsetX="130"
- @contentClick="handleLightVillage"
- @close="handleCloseLightTip"
- >
- <Button
- icon="https://xy.wenlvti.net/app_static/images/home/IconLight.png"
- radius="radius.lg" :padding="[10, 30]"
- text="点亮村社"
- @click="handleLightVillage"
- />
- </BubbleTip>
- </FlexRow>
- <HomeTitle title="乡村排名" showMore @moreClicked="navTo('/pages/home/village/rank/village', {})" />
- <VillageRankList :list="villageRankListLoader.content.value ?? []" :jumpToSingle="false" @goDetails="handleGoVillageDetails" />
- <HomeTitle title="志愿者排名" showMore :lightCount="3" @moreClicked="navTo('/pages/home/village/rank/volunteer', {})" />
- <VillageUserRankList
- :list="villageUserRankListLoader.content.value ?? []"
- scoreSuffix="积分"
- @goDetails="navTo('/pages/home/village/volunteer/detail', { id: $event.id })"
- />
- </FlexCol>
- <view class="recommmend-header" :style="stickyStyle">
- <FlexCol :padding="[0,30]">
- <HomeTitle title="精选记忆">
- <template #right>
- <FrameButton
- size="small"
- icon="https://xy.wenlvti.net/app_static/images/village/IconLargeHistory.png"
- text="我也来写"
- @click="handleGoPublish()"
- />
- </template>
- </HomeTitle>
- </FlexCol>
- <Tabs
- v-model:currentIndex="recommendTabCurrent"
- :tabs="recommendTabs"
- />
- </view>
- <FlexCol padding="padding.md">
- <SimplePageListLoader v-if="recommendTabCurrent === 0" :loader="recommendLoader">
- <MasonryGrid :items="recommendLoader.list.value" itemKey="id">
- <template #default="{ item }">
- <IndexCommonImageItem
- :width="354"
- :image="item.image"
- :title="item.title"
- :desc="item.content ?? ''"
- :userName="item.villageVolunteerName ?? ''"
- :userAvatar="item.villageVolunteerAvatar"
- :likes="item.likeNum"
- :isLike="item.likeType === 1"
- @click="handleGoRecommendDetails(item)"
- />
- </template>
- </MasonryGrid>
- </SimplePageListLoader>
- <SimplePageListLoader v-else-if="recommendTabCurrent === 1" :loader="goodsLoader">
- <FlexRow wrap justify="space-around" gap="gap.sm">
- <BoxMid
- v-for="good in goodsLoader.list.value" :key="good.id"
- :innerStyle="{
- width: 'calc(50% - 70rpx)',
- }"
- >
- <Touchable direction="column" gap="gap.lg" @click="handleGoRecommendDetails(good)">
- <Image
- :src="good.image"
- :radius="20"
- width="100%"
- height="300rpx"
- mode="aspectFill"
- />
- <Text :text="good.title" fontConfig="titleText" />
- <FlexRow align="center" gap="gap.md">
- <Avatar
- :src="good.villageVolunteerAvatar"
- :size="40"
- defaultAvatar="https://xy.wenlvti.net/app_static/images/mine/DefaultAvatar.png"
- />
- <Text :text="`发布人: ${good.villageVolunteerName}`" fontConfig="contentText" />
- </FlexRow>
- </Touchable>
- </BoxMid>
- <FlexCol v-if="goodsLoader.list.value && goodsLoader.list.value.length % 2 !== 0" width="calc(50%)" />
- </FlexRow>
- </SimplePageListLoader>
- <SimplePageListLoader v-else-if="recommendTabCurrent === 2" :loader="travelLoader">
- <FlexRow wrap justify="space-between" gap="gap.sm">
- <IndexCommonImageItem
- v-for="item in travelLoader.list.value" :key="item.id"
- :width="354"
- fixHeight
- :image="item.image"
- :title="item.title"
- :desc="item.content ?? ''"
- :userName="item.villageVolunteerName ?? ''"
- :userAvatar="item.villageVolunteerAvatar"
- :likes="item.likeNum"
- :isLike="item.likeType === 1"
- @click="handleGoRecommendDetails(item)"
- />
- </FlexRow>
- </SimplePageListLoader>
- <SimplePageListLoader v-else-if="recommendTabCurrent === 3" :loader="culturalLoader">
- <FlexRow wrap justify="space-between" gap="gap.sm">
- <IndexCommonImageItem
- v-for="item in culturalLoader.list.value" :key="item.id"
- :width="354"
- fixHeight
- :image="item.image"
- :title="item.title"
- :desc="item.content ?? ''"
- :userName="item.villageVolunteerName ?? ''"
- :userAvatar="item.villageVolunteerAvatar"
- :likes="item.likeNum"
- :isLike="item.likeType === 1"
- @click="handleGoRecommendDetails(item)"
- />
- </FlexRow>
- </SimplePageListLoader>
- </FlexCol>
- <Height :height="200" />
- <Popup
- v-model:show="showCityPopup"
- closeable
- position="top"
- size="60vh"
- >
- <CitySelect @selectCity="handleSelectCity" />
- </Popup>
- <Popup
- v-model:show="showMyFollowPopup"
- closeable
- position="bottom"
- round
- size="80vh"
- >
- <VillageMyFollow @goDetails="handleGoVillageDetails" />
- </Popup>
- <PostIndex
- ref="postIndexRef"
- @goDig="emit('goDig')"
- />
- <IntroClamTip ref="introClamTipRef" @apply="handleLightVillage" />
- </FlexCol>
- </template>
- <script setup lang="ts">
- import { onMounted, ref, watch } from 'vue';
- import { useTheme } from '@/components/theme/ThemeDefine';
- import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
- import { useStorageVar } from '@/components/composeabe/StorageVar';
- import { useVillageStore } from '@/store/village';
- import { useGetCurrentLocation } from './composeabe/GetCurrentLocation';
- import { useAuthStore } from '@/store/auth';
- import { useRequireLogin } from '@/common/composeabe/RequireLogin';
- import { useUserTools } from '@/common/composeabe/UserTools';
- import { useSimplePageListLoader } from '@/components/composeabe/loader/SimplePageListLoader';
- import { useGetNotice } from './village/composeabe/GetNotice';
- import { waitTimeOut } from '@imengyu/imengyu-utils';
- import { toast, loading, hideLoading, confirm } from '@/components/utils/DialogAction';
- import { navTo } from '@/components/utils/PageAction';
- import { injectAppConfiguration } from '@/api/system/useAppConfiguration';
- import { CollectableModulesIdMap } from '@/common/data/CollectableModulesIdMap';
- import Image from '@/components/basic/Image.vue';
- import FlexCol from '@/components/layout/FlexCol.vue';
- import FlexRow from '@/components/layout/FlexRow.vue';
- import Height from '@/components/layout/space/Height.vue';
- import SearchBar from '@/components/form/SearchBar.vue';
- import Button from '@/components/basic/Button.vue';
- import HomeTitle from '@/common/components/parts/HomeTitle.vue';
- import VillageRankList from './components/VillageRankList.vue';
- import VillageUserRankList from './components/VillageUserRankList.vue';
- import Popup from '@/components/dialog/Popup.vue';
- import CitySelect from './components/CitySelect.vue';
- import VillageMyFollow from './components/VillageMyFollow.vue';
- import LightMap from './components/LightMap.vue';
- import NoticeBar from '@/components/display/NoticeBar.vue';
- import StatusBarSpace from '@/components/layout/space/StatusBarSpace.vue';
- import LightVillageApi, { VillageListItem, type VillageMapItem } from '@/api/light/LightVillageApi';
- import Width from '@/components/layout/space/Width.vue';
- import IndexCommonImageItem from '@/common/components/parts/IndexCommonImageItem.vue';
- import IntroClamTip from './village/dialogs/IntroClamTip.vue';
- import MemoryTimeOut from '@/components/composeabe/MemoryTimeOut';
- import ImageSwiper from '@/common/components/parts/ImageSwiper.vue';
- import VillageInfoApi, { type CommonInfoModel } from '@/api/inhert/VillageInfoApi';
- import SimplePageListLoader from '@/components/loader/SimplePageListLoader.vue';
- import PostIndex from './village/dialogs/PostIndex.vue';
- import FrameButton from '@/common/components/FrameButton.vue';
- import type { RegionItem } from '@/api/map/RegionApi';
- import BubbleTip from '@/components/feedback/BubbleTip.vue';
- import BannerApi from '@/api/system/Banner';
- import Tabs from '@/components/nav/Tabs.vue';
- import BoxMid from '@/common/components/box/BoxMid.vue';
- import Touchable from '@/components/feedback/Touchable.vue';
- import Text from '@/components/basic/Text.vue';
- import Avatar from '@/components/display/Avatar.vue';
- import MasonryGrid from '@/components/layout/masonry/MasonryGrid.vue';
- import { DynamicColor, statusBarHeight } from '@/components/theme/ThemeTools';
- import SimpleTransition from '@/components/anim/SimpleTransition.vue';
- const props = defineProps({
- currentScrollTop: {
- type: Number,
- default: 0,
- },
- });
- const emit = defineEmits(['goVillage','goDig']);
- const authStore = useAuthStore();
- const villageStore = useVillageStore();
- const themeContext = useTheme();
- const { requireLogin } = useRequireLogin();
- const { getIsVolunteer } = useUserTools();
- const searchKeywords = ref('');
- const showCityPopup = ref(false);
- const showMyFollowPopup = ref(false);
- const introClamTipRef = ref();
- const introClamTipTimeout = new MemoryTimeOut('IntroClamTip', 1000 * 3600 * 30);//30h
- const postIndexRef = ref<InstanceType<typeof PostIndex>>();
- const appConfiguration = injectAppConfiguration();
- const { value: currentCity } = useStorageVar('currentCityName', '');
- const { value: currentCityCode } = useStorageVar('currentCityCode', 0);
- const { value: currentDistrict } = useStorageVar('currentDistrict', '');
- const currentLocation = useGetCurrentLocation({
- onCityChanged: (city, code, district) => {
- currentCity.value = city;
- currentCityCode.value = code;
- currentDistrict.value = district || '';
- },
- });
- const { currentNoticeContent } = useGetNotice();
- const bannerLoader = useSimpleDataLoader(async () => {
- return (await BannerApi.getBannerList({
- keywords: '亮乡源大众版首页头部',
- })).map(p => ({
- image: p.image,
- url: p.url,
- }));
- });
- const villageRankListLoader = useSimpleDataLoader(async () => {
- const res = await LightVillageApi.getVillageRankList({ num: 3 });
- return res.map((item, i) => ({
- image: item.image ?? '',
- title: item.name,
- rank: i + 1,
- id: item.id,
- }));
- });
- const villageUserRankListLoader = useSimpleDataLoader(async () => {
- const res = (await LightVillageApi.getVolunteerRankList({ num: 3 }))
- .map((item, i) => ({
- id: item.id,
- image: item.avatar ?? '',
- title: item.name,
- rank: i + 1,
- score: item.points,
- }));
- return res
- });
- const showLightTipTimeout = new MemoryTimeOut('LightTip', 1000 * 3600 * 2);//2h
- const showLightTip = ref(false);
- function handleShowLightTip() {
- if (showLightTipTimeout.isTimeout())
- showLightTip.value = true;
- }
- function handleCloseLightTip() {
- showLightTipTimeout.recordTime();
- }
- const recommendTabCurrent = ref(0);
- const recommendTabs = ref([
- {
- text: '推荐',
- value: 0,
- },
- {
- text: '好物',
- value: 1,
- },
- {
- text: '乡游',
- value: 2,
- },
- {
- text: '文化',
- value: 3,
- },
- ]);
- const recommendLoader = useSimplePageListLoader(20, async (page, pageSize) => {
- return await VillageInfoApi.getListForDiscover({ page, pageSize });
- }, true);
- const goodsLoader = useSimplePageListLoader(20, async (page, pageSize) => {
- return await VillageInfoApi.getListForDiscover({
- page,
- pageSize,
- collectModuleId: CollectableModulesIdMap['goods'],
- });
- }, false);
- const travelLoader = useSimplePageListLoader(20, async (page, pageSize) => {
- return await VillageInfoApi.getMiltListForDiscover({
- page,
- pageSize,
- collectModuleIds: [
- CollectableModulesIdMap['travel_guide'],
- CollectableModulesIdMap['route'],
- CollectableModulesIdMap['environment'],
- CollectableModulesIdMap['relic'],
- CollectableModulesIdMap['spots'],
- ],
- });
- }, true);
- const culturalLoader = useSimplePageListLoader(20, async (page, pageSize) => {
- return await VillageInfoApi.getMiltListForDiscover({
- page,
- pageSize,
- collectModuleIds: [
- CollectableModulesIdMap['cultural'],
- CollectableModulesIdMap['folk_culture'],
- CollectableModulesIdMap['story'],
- ],
- });
- }, true);
- const stickyStyle = themeContext.useThemeStyle({
- position: 'sticky',
- top: (statusBarHeight - 10) + 'px',
- left: 0,
- right: 0,
- zIndex: '100',
- backgroundColor: DynamicColor('background.primary'),
- });
- function handleGoRecommendDetails(item: CommonInfoModel) {
- navTo(`/pages/home/discover/details`, { id: item.id });
- }
- async function handleGoVillageDetails(item: VillageListItem) {
- showMyFollowPopup.value = false;
- const details = await LightVillageApi.getVillageDetails(item.id);
- villageStore.setCurrentVillage(details);
- await waitTimeOut(100);
- emit('goVillage')
- }
- function handleSelectCity(city: RegionItem) {
- currentCity.value = city.name;
- currentCityCode.value = city.areaCode;
- showCityPopup.value = false;
- currentLocation.currentLonlat.value = {
- longitude: Number(city.longitude),
- latitude: Number(city.latitude),
- };
- }
- function handleGoAI() {
- requireLogin(async () => {
- navTo('/pages/chat/index');
- }, '暂时需要登录后才能使用AI助手');
- }
- async function handleGoPublish() {
- requireLogin(async () => {
- postIndexRef.value?.show();
- }, '欢迎您发布内容,登录后以便使用更多功能哦!');
- }
- async function handleLightVillage() {
- requireLogin(async () => navTo('/pages/home/light/create-village'), '登录后才能点亮村社哦!');
- }
- function handleGoMap() {
- navTo('/pages/home/light/map', {
- city: currentCity.value,
- cityCode: currentCityCode.value,
- district: currentDistrict.value,
- });
- }
- function handleSearch() {
- if (!searchKeywords.value) {
- toast('请输入搜索关键词');
- return;
- }
- navTo('/pages/home/search/index', {
- city: currentCity.value,
- cityCode: currentCityCode.value,
- searchValue: searchKeywords.value,
- });
- }
- async function loadInfo() {
- loading('载入数据中');
- //如果不是志愿者,则显示认领村庄提示
- const isVolunteer = await getIsVolunteer();
- if (!isVolunteer) {
- if (introClamTipTimeout.isTimeout()) {
- introClamTipRef.value?.show();
- introClamTipTimeout.recordTime();
- }
- }
- //加载我的关注村庄
- try {
- await villageStore.loadMyFollowVillages();
- } catch (error) {
- console.error(error);
- }
- try {
- await villageStore.loadMyJoinedVillages();
- } catch (error) {
- console.error(error);
- }
- handleShowLightTip();
- try {
- if (!villageStore.currentVillage && villageStore.myFollowVillages.length > 0) {
- const currentVillage = villageStore.loadCurrentVillage();
- const id = (villageStore.myFollowVillages.find(p => p.id === currentVillage) as VillageListItem || villageStore.myFollowVillages[0]).id;
- const res = await LightVillageApi.getVillageDetails(id)
- villageStore.setCurrentVillage(res);
- }
- } finally {
- hideLoading();
- }
- }
- watch(() => authStore.isLogged, async (newVal) => {
- if (newVal) {
- await loadInfo();
- }
- }, { immediate: true });
- onMounted(async () => {
- try {
- if (currentCity.value) {
- await currentLocation.setCurrentLocationWithCity(currentCity.value, currentDistrict.value);
- } else {
- await currentLocation.getCurrentFuzzyLocation();
- }
- } catch (error) {
- console.error(error);
- toast('获取当前位置失败,您可以手动选择城市');
- }
- });
- defineExpose({
- onPageBack: (name: string, data: Record<string, unknown>) => {
- if (name === 'changeCurrentCity') {
- currentCity.value = data.city as string;
- currentCityCode.value = data.code as number;
- currentDistrict.value = '';
- currentLocation.currentLonlat.value = {
- longitude: Number(data.longitude),
- latitude: Number(data.latitude),
- };
- }
- }
- })
- </script>
- <style lang="scss">
- .top-nav-fade {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- z-index: 200;
- transition: opacity 0.3s ease-in-out;
- &.fade-enter-active,
- &.fade-leave-active {
- opacity: 1;
- }
- &.fade-enter-from,
- &.fade-leave-to {
- opacity: 0;
- }
- }
- </style>
|