index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. <template>
  2. <FlexCol>
  3. <FlexCol :gap="20" :padding="30" :innerStyle="{
  4. marginTop: '-130px',
  5. backgroundImage: 'url(https://xy.wenlvti.net/app_static/images/home/BannerHomeNew.png)',
  6. backgroundSize: '100% auto',
  7. backgroundRepeat: 'no-repeat',
  8. backgroundPosition: 'top center',
  9. backgroundColor: themeContext.resolveThemeColor('background.primary'),
  10. }">
  11. <FlexCol position="absolute" :left="0" :top="0" :right="0">
  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. :text="currentCity"
  18. type="custom"
  19. color="transparent"
  20. />
  21. <Image
  22. src="https://xy.wenlvti.net/app_static/images/home/BannerHomeTitle.png"
  23. :width="140"
  24. :height="75"
  25. />
  26. <Width :width="150" />
  27. </FlexRow>
  28. </FlexCol>
  29. <Height height="150px" />
  30. <FlexCol :gap="20" align="center">
  31. <FlexRow justify="space-between" align="center" width="100%">
  32. <SearchBar v-model="searchKeywords" placeholder="搜索" :innerStyle="{
  33. backgroundColor: 'white',
  34. borderRadius: '20rpx',
  35. borderWidth: '1px',
  36. borderStyle: 'solid',
  37. borderColor: themeContext.resolveThemeColor('primary'),
  38. width: '490rpx',
  39. }" />
  40. <Button icon="ai-thinking" @click="handleGoAI" text="问AI" />
  41. </FlexRow>
  42. <Image
  43. src="https://xy.wenlvti.net/app_static/images/home/BannerIndex.png"
  44. width="700rpx"
  45. height="200px"
  46. mode="aspectFit"
  47. :innerStyle="{
  48. border: '2px solid #fff',
  49. borderRadius: '20rpx',
  50. clipPath: 'ellipse(100% 90% at 50% 0%)'
  51. }"
  52. />
  53. </FlexCol>
  54. <LightMap
  55. small
  56. :city="currentCity"
  57. :lonlat="currentLocation.currentLonlat.value"
  58. @getCurrentLonlat="currentLocation.getCurrentExactLocation"
  59. @selectVillage="goDetails"
  60. @regionChanged="currentRegion=$event"
  61. >
  62. <NoticeBar
  63. v-if="currentNoticeContent"
  64. :content="currentNoticeContent"
  65. :innerStyle="{
  66. position: 'absolute',
  67. top: '20rpx',
  68. left: '20rpx',
  69. right: '20rpx',
  70. zIndex: 100,
  71. borderRadius: '30rpx',
  72. }"
  73. :textStyle="{
  74. fontSize: '26rpx',
  75. }"
  76. icon="https://xy.wenlvti.net/app_static/images/home/IconLightActive.png"
  77. :iconProps="{
  78. size: 34,
  79. }"
  80. textColor="#C9211F"
  81. backgroundColor="#D9492E10"
  82. />
  83. </LightMap>
  84. <FlexRow justify="space-between" :padding="[10, 16]" gap="gap.md">
  85. <Button
  86. icon="https://xy.wenlvti.net/app_static/images/home/IconSwitch.png"
  87. radius="radius.lg"
  88. :padding="[10, 30]"
  89. @click="showCityPopup = true"
  90. >
  91. 切换城市
  92. </Button>
  93. <Button
  94. icon="https://xy.wenlvti.net/app_static/images/home/IconFollow.png"
  95. radius="radius.lg" :padding="[10, 30]"
  96. @click="showMyFollowPopup = true"
  97. >
  98. 我的关注
  99. </Button>
  100. <Button
  101. icon="https://xy.wenlvti.net/app_static/images/home/IconLight.png"
  102. radius="radius.lg" :padding="[10, 30]"
  103. @click="handleLightVillage"
  104. >
  105. 点亮村社
  106. </Button>
  107. </FlexRow>
  108. <!-- <HomeTitle title="最新动态" />
  109. <Construction text="测试数据,没有接口!">
  110. <FlexCol gap="gap.lg">
  111. <FlexRow
  112. v-for="item in activityLoader.content.value" :key="item.id"
  113. backgroundColor="background.tertiary"
  114. radius="radius.md"
  115. :padding="[20, 30]"
  116. gap="gap.lg"
  117. align="center"
  118. >
  119. <Avatar
  120. :url="item.head"
  121. :size="80"
  122. :round="false"
  123. :radius="10"
  124. />
  125. <Text :text="item.content" fontConfig="contentText" :innerStyle="{ flex: 1 }" />
  126. </FlexRow>
  127. </FlexCol>
  128. </Construction> -->
  129. <HomeTitle title="乡村排名" showMore @moreClicked="navTo('/pages/home/village/rank/village', {
  130. regionId: currentRegion ?? undefined,
  131. })" />
  132. <VillageRankList :list="villageRankListLoader.content.value ?? []" :jumpToSingle="false" @goDetails="goDetails" />
  133. <HomeTitle title="志愿者排名" showMore :lightCount="3" @moreClicked="navTo('/pages/home/village/rank/volunteer', {
  134. regionId: currentRegion ?? undefined,
  135. })" />
  136. <VillageUserRankList
  137. :list="villageUserRankListLoader.content.value ?? []"
  138. @goDetails="navTo('/pages/home/village/volunteer/detail', { id: $event.id })"
  139. />
  140. <HomeTitle title="精选记忆" />
  141. </FlexCol>
  142. <OfficialAccountPublishWrap
  143. topic="亮乡源"
  144. @publishsuccess="onPublishSuccess"
  145. />
  146. <Height :height="200" />
  147. <Popup
  148. v-model:show="showCityPopup"
  149. closeable
  150. position="top"
  151. size="80vh"
  152. >
  153. <CitySelect @selectCity="handleSelectCity" />
  154. </Popup>
  155. <Popup
  156. v-model:show="showMyFollowPopup"
  157. closeable
  158. position="bottom"
  159. round
  160. size="80vh"
  161. >
  162. <VillageMyFollow @goDetails="goDetails" />
  163. </Popup>
  164. <IntroClamTip ref="introClamTipRef" @apply="handleLightVillage" />
  165. </FlexCol>
  166. </template>
  167. <script setup lang="ts">
  168. import { onMounted, ref, watch } from 'vue';
  169. import { useTheme } from '@/components/theme/ThemeDefine';
  170. import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
  171. import { useStorageVar } from '@/components/composeabe/StorageVar';
  172. import { useVillageStore } from '@/store/village';
  173. import { useGetCurrentLocation } from './composeabe/GetCurrentLocation';
  174. import { useAuthStore } from '@/store/auth';
  175. import { useReqireLogin } from '@/common/composeabe/RequireLogin';
  176. import { useOfficialAccount } from './composeabe/OfficialAccount';
  177. import { useUserTools } from '@/common/composeabe/UserTools';
  178. import { ArrayUtils, waitTimeOut } from '@imengyu/imengyu-utils';
  179. import { toast } from '@/components/utils/DialogAction';
  180. import { navTo } from '@/components/utils/PageAction';
  181. import Image from '@/components/basic/Image.vue';
  182. import FlexCol from '@/components/layout/FlexCol.vue';
  183. import FlexRow from '@/components/layout/FlexRow.vue';
  184. import Height from '@/components/layout/space/Height.vue';
  185. import SearchBar from '@/components/form/SearchBar.vue';
  186. import Button from '@/components/basic/Button.vue';
  187. import HomeTitle from '@/common/components/parts/HomeTitle.vue';
  188. import VillageRankList from './components/VillageRankList.vue';
  189. import VillageUserRankList from './components/VillageUserRankList.vue';
  190. import Popup from '@/components/dialog/Popup.vue';
  191. import CitySelect from './components/CitySelect.vue';
  192. import VillageMyFollow from './components/VillageMyFollow.vue';
  193. import MapApi from '@/api/map/MapApi';
  194. import LightMap from './components/LightMap.vue';
  195. import NoticeBar from '@/components/display/NoticeBar.vue';
  196. import StatusBarSpace from '@/components/layout/space/StatusBarSpace.vue';
  197. import LightVillageApi, { VillageListItem } from '@/api/light/LightVillageApi';
  198. import type { CityItem } from '@/api/map/MapApi';
  199. import Width from '@/components/layout/space/Width.vue';
  200. import OfficialAccountPublishWrap from '@/common/components/OfficialAccountPublishWrap.vue';
  201. import IntroClamTip from './village/dialogs/IntroClamTip.vue';
  202. import MemoryTimeOut from '@/components/composeabe/MemoryTimeOut';
  203. const emit = defineEmits(['goVillage']);
  204. const { onPublishSuccess } = useOfficialAccount();
  205. const authStore = useAuthStore();
  206. const villageStore = useVillageStore();
  207. const themeContext = useTheme();
  208. const { requireLogin } = useReqireLogin();
  209. const { getIsVolunteer } = useUserTools();
  210. const searchKeywords = ref('');
  211. const showCityPopup = ref(false);
  212. const showMyFollowPopup = ref(false);
  213. const introClamTipRef = ref();
  214. const introClamTipTimeout = new MemoryTimeOut('IntroClamTip', 1000 * 3600 * 30);//30h
  215. const currentRegion = ref<number | null>(null);
  216. const { value: currentCity } = useStorageVar('currentCityName', '');
  217. const currentLocation = useGetCurrentLocation({
  218. onCityChanged: (city) => {
  219. currentCity.value = city;
  220. },
  221. });
  222. const currentNoticeContent = ref('目前厦门市已被点亮一个社区,刚刚小亮贡献了10个光源,让湖里区点亮了一个社区');
  223. const villageRankListLoader = useSimpleDataLoader(async () => {
  224. const res = await LightVillageApi.getVillageRankList({ region_id: currentRegion.value ?? undefined, num: 3 });
  225. return res.map((item, i) => ({
  226. image: item.image ?? '',
  227. title: item.name,
  228. rank: i + 1,
  229. id: item.id,
  230. }));
  231. });
  232. const villageUserRankListLoader = useSimpleDataLoader(async () => {
  233. const res = (await LightVillageApi.getVolunteerRankList({ region_id: currentRegion.value ?? undefined, num: 3 }))
  234. .map((item, i) => ({
  235. id: item.id,
  236. image: item.image ?? '',
  237. title: item.name,
  238. rank: i + 1,
  239. score: item.points,
  240. }));
  241. if (res.length >= 3) {
  242. //移动第一名到中间
  243. const first = res[0];
  244. ArrayUtils.removeAt(res, 0);
  245. ArrayUtils.insert(res, 1, first);
  246. }
  247. return res
  248. });
  249. const activityLoader = useSimpleDataLoader(async () => {
  250. return [
  251. {
  252. id: 1,
  253. head: 'https://mn.wenlvti.net/app_static/minnan/images/test/ImageTest1.png',
  254. content: '测试数据,没有接口!:福泽乡里 为全村加成+10%乡源果,可持续24小时',
  255. levelText: '一级',
  256. },
  257. {
  258. id: 2,
  259. head: 'https://mn.wenlvti.net/app_static/minnan/images/test/ImageTest2.png',
  260. content: '福泽乡里 为全村加成+10%乡源果,可持续24小时',
  261. levelText: '五级',
  262. },
  263. {
  264. id: 3,
  265. head: 'https://mn.wenlvti.net/app_static/minnan/images/test/ImageTest3.png',
  266. content: '福泽乡里 为全村加成+10%乡源果,可持续24小时',
  267. levelText: '十级',
  268. },
  269. ];
  270. });
  271. watch(currentRegion, async (newVal) => {
  272. await villageRankListLoader.reload();
  273. await villageUserRankListLoader.reload();
  274. });
  275. async function goDetails(item: VillageListItem) {
  276. showMyFollowPopup.value = false;
  277. const details = await LightVillageApi.getVillageDetails(item.id);
  278. villageStore.setCurrentVillage(details);
  279. await waitTimeOut(100);
  280. emit('goVillage')
  281. }
  282. async function handleChangedCity(city: string) {
  283. currentCity.value = city;
  284. try {
  285. const res = (await MapApi.simpleGetRegion(city)).requireData();
  286. currentLocation.currentLonlat.value = {
  287. longitude: Number(res.center.split(',')[0]),
  288. latitude: Number(res.center.split(',')[1]),
  289. };
  290. console.log('currentLocation.currentLonlat.value', currentLocation.currentLonlat.value);
  291. } catch (error) {
  292. console.error(error);
  293. return;
  294. }
  295. }
  296. function handleSelectCity(city: CityItem) {
  297. currentCity.value = city.name;
  298. showCityPopup.value = false;
  299. handleChangedCity(city.name);
  300. }
  301. function handleGoAI() {
  302. requireLogin(async () => {
  303. navTo('/pages/chat/index');
  304. }, '暂时需要登录后才能使用AI助手');
  305. }
  306. async function handleLightVillage() {
  307. requireLogin(async () => navTo('/pages/home/light/submit-map', { city: currentCity.value }), '登录后才能点亮村社哦!');
  308. }
  309. async function loadInfo() {
  310. //如果不是志愿者,则显示认领村庄提示
  311. const isVolunteer = await getIsVolunteer();
  312. if (!isVolunteer) {
  313. if (introClamTipTimeout.isTimeout()) {
  314. introClamTipRef.value?.show();
  315. introClamTipTimeout.recordTime();
  316. }
  317. }
  318. //加载我的关注村庄
  319. await villageStore.loadMyFollowVillages();
  320. if (villageStore.myFollowVillages.length > 0) {
  321. const currentVillage = villageStore.loadCurrentVillage();
  322. if (currentVillage) {
  323. villageStore.setCurrentVillage(villageStore.myFollowVillages.find(p => p.id === currentVillage) as VillageListItem || villageStore.myFollowVillages[0]);
  324. } else {
  325. villageStore.setCurrentVillage(villageStore.myFollowVillages[0] as VillageListItem);
  326. }
  327. }
  328. }
  329. watch(() => authStore.isLogged, async (newVal) => {
  330. if (newVal) {
  331. introClamTipTimeout.reset();
  332. await loadInfo();
  333. }
  334. });
  335. onMounted(async () => {
  336. try {
  337. if (currentCity.value) {
  338. await currentLocation.setCurrentLocationWithCity(currentCity.value);
  339. } else {
  340. await currentLocation.getCurrentFuzzyLocation();
  341. }
  342. } catch (error) {
  343. console.error(error);
  344. toast('获取当前位置失败,您可以手动选择城市');
  345. }
  346. await loadInfo();
  347. });
  348. </script>