index.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. <template>
  2. <FlexCol :gap="20" :padding="30" :innerStyle="{
  3. marginTop: '-130px',
  4. backgroundImage: 'url(https://xy.wenlvti.net/app_static/images/home/BannerHome.png)',
  5. backgroundSize: '100% auto',
  6. backgroundRepeat: 'no-repeat',
  7. backgroundPosition: 'top center',
  8. backgroundColor: themeContext.resolveThemeColor('background.primary'),
  9. }">
  10. <FlexCol position="absolute" :left="0" :top="0">
  11. <StatusBarSpace />
  12. <Button
  13. @click="showCityPopup = true"
  14. icon="https://xy.wenlvti.net/app_static/images/home/IconSwitch.png"
  15. :text="currentCity"
  16. type="custom"
  17. color="transparent"
  18. />
  19. </FlexCol>
  20. <Height height="200px" />
  21. <FlexCol :gap="20" align="center">
  22. <SearchBar v-model="searchKeywords" placeholder="搜索" :innerStyle="{
  23. backgroundColor: 'white',
  24. borderRadius: '20rpx',
  25. borderWidth: '1px',
  26. borderStyle: 'solid',
  27. borderColor: themeContext.resolveThemeColor('primary'),
  28. width: '650rpx',
  29. }" />
  30. <Image
  31. src="https://xy.wenlvti.net/app_static/images/home/BannerIndex.png"
  32. width="700rpx"
  33. height="200px"
  34. mode="aspectFill"
  35. :innerStyle="{
  36. borderRadius: '20rpx',
  37. clipPath: 'ellipse(100% 90% at 50% 0%)'
  38. }"
  39. />
  40. </FlexCol>
  41. <LightMap
  42. small
  43. :city="currentCity"
  44. :lonlat="currentLocation.currentLonlat.value"
  45. @getCurrentLonlat="currentLocation.getCurrentExactLocation"
  46. @selectVillage="goDetails"
  47. @regionChanged="currentRegion=$event"
  48. >
  49. <NoticeBar
  50. v-if="currentNoticeContent"
  51. :content="currentNoticeContent"
  52. :innerStyle="{
  53. position: 'absolute',
  54. top: '20rpx',
  55. left: '20rpx',
  56. right: '20rpx',
  57. zIndex: 100,
  58. borderRadius: '30rpx',
  59. }"
  60. :textStyle="{
  61. fontSize: '26rpx',
  62. }"
  63. icon="https://xy.wenlvti.net/app_static/images/home/IconLightActive.png"
  64. :iconProps="{
  65. size: 34,
  66. }"
  67. textColor="#C9211F"
  68. backgroundColor="#D9492E10"
  69. />
  70. </LightMap>
  71. <FlexRow justify="space-between" :padding="[10, 16]" gap="gap.md">
  72. <Button
  73. icon="https://xy.wenlvti.net/app_static/images/home/IconSwitch.png"
  74. radius="radius.lg"
  75. :padding="[10, 30]"
  76. @click="showCityPopup = true"
  77. >
  78. 切换城市
  79. </Button>
  80. <Button
  81. icon="https://xy.wenlvti.net/app_static/images/home/IconFollow.png"
  82. radius="radius.lg" :padding="[10, 30]"
  83. @click="showMyFollowPopup = true"
  84. >
  85. 我的关注
  86. </Button>
  87. <Button
  88. icon="https://xy.wenlvti.net/app_static/images/home/IconLight.png"
  89. radius="radius.lg" :padding="[10, 30]"
  90. @click="navTo('/pages/home/light/submit-map', { city: currentCity })"
  91. >
  92. 点亮村社
  93. </Button>
  94. </FlexRow>
  95. <HomeTitle title="乡村排名" showMore @moreClicked="navTo('/pages/home/village/rank/village', {
  96. regionId: currentRegion ?? undefined,
  97. })" />
  98. <VillageRankList :list="villageRankListLoader.content.value ?? []" :jumpToSingle="false" @goDetails="goDetails" />
  99. <HomeTitle title="志愿者排名" showMore :lightCount="3" @moreClicked="navTo('/pages/home/village/rank/volunteer', {
  100. regionId: currentRegion ?? undefined,
  101. })" />
  102. <VillageUserRankList :list="villageUserRankListLoader.content.value ?? []" />
  103. <HomeTitle title="精选记忆" showMore />
  104. <MasonryGrid>
  105. <MasonryGridItem
  106. v-for="(item, i) in recommendLoader.list.value"
  107. :key="i"
  108. :width="340"
  109. >
  110. <IndexCommonImageItem
  111. :image="item.image"
  112. :title="item.title"
  113. :desc="item.desc"
  114. :userName="item.userName"
  115. :likes="item.likes"
  116. :isLike="item.isLike"
  117. @click="goMessageDetails(item)"
  118. />
  119. </MasonryGridItem>
  120. </MasonryGrid>
  121. <Loadmore status="nomore" />
  122. <Height :height="150" />
  123. <Popup
  124. v-model:show="showCityPopup"
  125. closeable
  126. position="top"
  127. size="80vh"
  128. >
  129. <CitySelect @selectCity="handleSelectCity" />
  130. </Popup>
  131. <Popup
  132. v-model:show="showMyFollowPopup"
  133. closeable
  134. position="bottom"
  135. round
  136. size="80vh"
  137. >
  138. <VillageMyFollow @goDetails="goDetails" />
  139. </Popup>
  140. </FlexCol>
  141. </template>
  142. <script setup lang="ts">
  143. import { onMounted, ref, watch } from 'vue';
  144. import { useTheme } from '@/components/theme/ThemeDefine';
  145. import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
  146. import { useStorageVar } from '@/components/composeabe/StorageVar';
  147. import { useVillageStore } from '@/store/village';
  148. import { useGetCurrentLocation } from './composeabe/GetCurrentLocation';
  149. import { useSimplePageListLoader } from '@/components/composeabe/loader/SimplePageListLoader';
  150. import { ArrayUtils, waitTimeOut } from '@imengyu/imengyu-utils';
  151. import { toast } from '@/components/utils/DialogAction';
  152. import { navTo } from '@/components/utils/PageAction';
  153. import Image from '@/components/basic/Image.vue';
  154. import Loadmore from '@/components/display/loading/Loadmore.vue';
  155. import FlexCol from '@/components/layout/FlexCol.vue';
  156. import FlexRow from '@/components/layout/FlexRow.vue';
  157. import Height from '@/components/layout/space/Height.vue';
  158. import SearchBar from '@/components/form/SearchBar.vue';
  159. import Button from '@/components/basic/Button.vue';
  160. import HomeTitle from '@/common/components/parts/HomeTitle.vue';
  161. import VillageRankList from './components/VillageRankList.vue';
  162. import VillageUserRankList from './components/VillageUserRankList.vue';
  163. import MasonryGrid from '@/components/layout/masonry/MasonryGrid.vue';
  164. import MasonryGridItem from '@/components/layout/masonry/MasonryGridItem.vue';
  165. import IndexCommonImageItem from '@/common/components/parts/IndexCommonImageItem.vue';
  166. import Popup from '@/components/dialog/Popup.vue';
  167. import CitySelect from './components/CitySelect.vue';
  168. import VillageMyFollow from './components/VillageMyFollow.vue';
  169. import MapApi from '@/api/map/MapApi';
  170. import LightMap from './components/LightMap.vue';
  171. import NoticeBar from '@/components/display/NoticeBar.vue';
  172. import StatusBarSpace from '@/components/layout/space/StatusBarSpace.vue';
  173. import FollowVillageApi from '@/api/light/FollowVillageApi';
  174. import LightVillageApi, { VillageListItem } from '@/api/light/LightVillageApi';
  175. import type { CityItem } from '@/api/map/MapApi';
  176. const emit = defineEmits(['goVillage']);
  177. const villageStore = useVillageStore();
  178. const themeContext = useTheme();
  179. const searchKeywords = ref('');
  180. const showCityPopup = ref(false);
  181. const showMyFollowPopup = ref(false);
  182. const currentRegion = ref<number | null>(null);
  183. const { value: currentCity } = useStorageVar('currentCityName', '');
  184. const currentLocation = useGetCurrentLocation({
  185. onCityChanged: (city) => {
  186. currentCity.value = city;
  187. },
  188. });
  189. const currentNoticeContent = ref('目前厦门市已被点亮一个社区,刚刚小亮贡献了10个光源,让湖里区点亮了一个社区');
  190. const villageRankListLoader = useSimpleDataLoader(async () => {
  191. const res = await LightVillageApi.getVillageRankList({ region_id: currentRegion.value ?? undefined, num: 3 });
  192. return res.map((item, i) => ({
  193. image: item.image ?? '',
  194. title: item.name,
  195. rank: i + 1,
  196. id: item.id,
  197. }));
  198. });
  199. const villageUserRankListLoader = useSimpleDataLoader(async () => {
  200. const res = (await LightVillageApi.getVolunteerRankList({ region_id: currentRegion.value ?? undefined, num: 3 }))
  201. .map((item, i) => ({
  202. id: item.id,
  203. image: item.image ?? '',
  204. title: item.name,
  205. rank: i + 1,
  206. score: item.points,
  207. }));
  208. if (res.length >= 3) {
  209. //移动第一名到中间
  210. const first = res[0];
  211. ArrayUtils.removeAt(res, 0);
  212. ArrayUtils.insert(res, 1, first);
  213. }
  214. return res
  215. });
  216. const recommendLoader = useSimplePageListLoader(20, async (page, pageSize, params) => {
  217. const res = await LightVillageApi.getMessages(page, pageSize, params?.keywords ?? '');
  218. return {
  219. list: res.data.map((item) => ({
  220. image: item.image ?? '',
  221. title: item.title,
  222. desc: item.content ?? '',
  223. userName: item.nick_name ?? '',
  224. likes: item.like_count,
  225. isLike: false,
  226. })),
  227. total: res.total,
  228. };
  229. }, true);
  230. watch(currentRegion, async (newVal) => {
  231. await villageRankListLoader.reload();
  232. await villageUserRankListLoader.reload();
  233. });
  234. async function goDetails(item: VillageListItem) {
  235. showMyFollowPopup.value = false;
  236. const details = await LightVillageApi.getVillageDetails(item.id);
  237. villageStore.setCurrentVillage(details);
  238. await waitTimeOut(100);
  239. emit('goVillage')
  240. }
  241. function goMessageDetails(item: any) {
  242. uni.openOfficialAccountArticle({
  243. url: item.jump_url,
  244. fail: (err) => {
  245. console.error(err);
  246. },
  247. })
  248. }
  249. async function handleChangedCity(city: string) {
  250. currentCity.value = city;
  251. try {
  252. const res = (await MapApi.simpleGetRegion(city)).requireData();
  253. currentLocation.currentLonlat.value = {
  254. longitude: Number(res.center.split(',')[0]),
  255. latitude: Number(res.center.split(',')[1]),
  256. };
  257. console.log('currentLocation.currentLonlat.value', currentLocation.currentLonlat.value);
  258. } catch (error) {
  259. console.error(error);
  260. return;
  261. }
  262. }
  263. function handleSelectCity(city: CityItem) {
  264. currentCity.value = city.name;
  265. showCityPopup.value = false;
  266. handleChangedCity(city.name);
  267. }
  268. onMounted(async () => {
  269. try {
  270. if (currentCity.value) {
  271. await currentLocation.setCurrentLocationWithCity(currentCity.value);
  272. } else {
  273. await currentLocation.getCurrentFuzzyLocation();
  274. }
  275. } catch (error) {
  276. console.error(error);
  277. toast('获取当前位置失败,现在显示的是');
  278. }
  279. const res = await FollowVillageApi.getFollowVillageList({ page: 1, pageSize: 200 });
  280. villageStore.setMyFollowVillages(res.list);
  281. if (res.list.length > 0) {
  282. const currentVillage = villageStore.loadCurrentVillage();
  283. if (currentVillage) {
  284. villageStore.setCurrentVillage(res.list.find(p => p.id === currentVillage) as VillageListItem);
  285. } else {
  286. villageStore.setCurrentVillage(res.list[0]);
  287. }
  288. }
  289. });
  290. </script>