card.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. <template>
  2. <FlexCol :padding="30" gap="gap.lg">
  3. <!-- 卡片背景 -->
  4. <BackgroundBox
  5. color1="#eecaa0"
  6. color2="white"
  7. color2Position="45%"
  8. color3="white"
  9. radius="radius.lg"
  10. direction="column"
  11. :padding="[35,30]"
  12. gap="gap.lg"
  13. >
  14. <!-- 标题 -->
  15. <FlexRow justify="space-between" width="100%">
  16. <FlexCol gap="gap.md">
  17. <Text :text="villageInfoLoader.content.value?.title" fontConfig="primaryTitle" />
  18. <Text :text="villageInfoLoader.content.value?.address" fontConfig="contentText" />
  19. </FlexCol>
  20. <FlexCol gap="gap.md">
  21. <Button icon="https://xy.wenlvti.net/app_static/images/village/IconUser.png" radius="radius.lgr" :padding="[10, 30]" backgroundColor="white">申请管理者</Button>
  22. <Text :text="`${villageInfoLoader.content.value?.applyCount} 个乡源果可申请`" fontConfig="secondText" />
  23. </FlexCol>
  24. </FlexRow>
  25. <!-- 图片 -->
  26. <FlexRow v-if="villageInfoLoader.content.value" justify="space-between">
  27. <Image
  28. v-for="index of 3"
  29. :key="index"
  30. :src="villageInfoLoader.content.value.images[index - 1]"
  31. radius="radius.md"
  32. :width="200"
  33. :height="140"
  34. mode="aspectFill"
  35. defaultImage=""
  36. touchable
  37. >
  38. <template #empty>
  39. <Touchable
  40. direction="column"
  41. position="absolute"
  42. inset="0"
  43. center
  44. backgroundColor="background.primary"
  45. >
  46. <Icon name="add" size="fontSize.md" />
  47. <Text text="上传封面" fontConfig="secondText" />
  48. </Touchable>
  49. </template>
  50. <FlexCol
  51. v-if="index === 3 && villageInfoLoader.content.value.images.length > 3"
  52. position="absolute" inset="0"
  53. center
  54. backgroundColor="mask.default"
  55. >
  56. <Text :text="`+${villageInfoLoader.content.value.images.length - 3}`" fontSize="fontSize.lg" color="white" />
  57. </FlexCol>
  58. </Image>
  59. </FlexRow>
  60. <!-- 地址 -->
  61. <FlexRow align="center" gap="gap.sm">
  62. <Icon name="https://xy.wenlvti.net/app_static/images/village/IconMap.png" size="fontSize.md" />
  63. <Text :text="villageInfoLoader.content.value?.address" fontConfig="contentText" />
  64. </FlexRow>
  65. <VillageMiniMap
  66. v-if="villageInfoLoader.content.value"
  67. :lonlat="{
  68. longitude: villageInfoLoader.content.value.longitude,
  69. latitude: villageInfoLoader.content.value.latitude
  70. }"
  71. />
  72. <FlexRow center gap="gap.lg">
  73. <Button
  74. icon="https://xy.wenlvti.net/app_static/images/village/IconJoin.png"
  75. radius="radius.lgr"
  76. size="large"
  77. :innerStyle="{ flexBasis: '25%' }"
  78. @click="isFollowed ? onUnFollow() : onFollow()"
  79. >
  80. {{ isFollowed ? '已关注' : '关注' }}
  81. </Button>
  82. <Button
  83. icon="https://xy.wenlvti.net/app_static/images/village/IconFollow.png"
  84. size="large"
  85. radius="radius.lgr"
  86. :innerStyle="{ flexBasis: '25%' }"
  87. >
  88. 加入
  89. </Button>
  90. </FlexRow>
  91. <FlexRow justify="space-between" align="center">
  92. <FlexRow center gap="gap.lg" flexBasis="50%">
  93. <Text text="村社排名" fontConfig="contentText" />
  94. <Text text="No." fontConfig="lightTitle" />
  95. <Text :text="villageInfoLoader.content.value?.rankText" fontConfig="primaryTitle" />
  96. </FlexRow>
  97. <FlexRow center gap="gap.lg" flexBasis="50%">
  98. <Text text="村社等级" fontConfig="contentText" />
  99. <Text :text="villageInfoLoader.content.value?.levelText" fontConfig="primaryTitle" />
  100. </FlexRow>
  101. </FlexRow>
  102. <FlexRow backgroundColor="background.tertiary" radius="radius.md" :padding="[30, 20]">
  103. <FlexCol center gap="gap.sm" flexBasis="25%">
  104. <Text text="乡源光" fontConfig="secondText" />
  105. <Text :text="villageInfoLoader.content.value?.light" fontConfig="importantTitle" />
  106. </FlexCol>
  107. <Divider type="vertical" />
  108. <FlexCol center gap="gap.sm" flexBasis="25%">
  109. <Text text="乡源人数" fontConfig="contentText" />
  110. <Text :text="villageInfoLoader.content.value?.memberCount" fontConfig="importantTitle" />
  111. </FlexCol>
  112. <Divider type="vertical" />
  113. <FlexCol center gap="gap.sm" flexBasis="25%">
  114. <Text text="关注人数" fontConfig="contentText" />
  115. <Text :text="villageInfoLoader.content.value?.followerCount" fontConfig="importantTitle" />
  116. </FlexCol>
  117. <Divider type="vertical" />
  118. <Button :padding="0" type="text" size="small" textColor="text.title" text="新手上路" rightIcon="arrow-right" />
  119. </FlexRow>
  120. </BackgroundBox>
  121. <!-- 排行榜 -->
  122. <HomeTitle title="排行榜" showMore @moreClicked="navTo('/pages/home/village/rank/volunteer', {
  123. villageId: villageStore.currentVillage?.id ?? undefined,
  124. })" />
  125. <RoundTags v-model:active="rankActiveTag" :tags="['乡源果', '志愿者', '乡源光']" />
  126. <VillageUserRankList :list="villageUserRankListLoader.content.value ?? []" />
  127. <!-- 魅力乡源 -->
  128. <HomeTitle title="魅力乡源" showMore>
  129. <template #right>
  130. <BackgroundImageButton
  131. backgroundImage="https://xy.wenlvti.net/app_static/images/village/TagActive.png"
  132. :backgroundCutBorder="[10, 10, 10, 10]"
  133. :backgroundCutBorderSize="[10, 10, 10, 10]"
  134. :padding="[15, 20]"
  135. :innerStyle="{ marginRight: '20rpx' }"
  136. >
  137. <Text text="120乡源果兑换" fontConfig="contentText" color="white" />
  138. </BackgroundImageButton>
  139. </template>
  140. </HomeTitle>
  141. <ProvideVar :vars="{
  142. GridItemIconSize: 90,
  143. GridItemBackgroundColor: 'transparent',
  144. GridItemPaddingHorizontal: 0,
  145. GridItemPaddingVertical: 8,
  146. }">
  147. <Grid :borderGrid="false" :mainAxisCount="4">
  148. <GridItem title="村庄概况" icon="https://xy.wenlvti.net/app_static/images/village/IconLargeIntrod.png" touchable @click="handleGoCollect('overview')" />
  149. <GridItem title="自然风光" icon="https://xy.wenlvti.net/app_static/images/village/IconLargeEnvirounment.png" touchable @click="handleGoCollect('environment')" />
  150. <GridItem title="历史沿革" icon="https://xy.wenlvti.net/app_static/images/village/IconLargeHistory.png" touchable @click="handleGoCollect('history')" />
  151. <GridItem title="特色产业" icon="https://xy.wenlvti.net/app_static/images/village/IconLargeIndustry.png" touchable @click="handleGoCollect('product')" />
  152. <GridItem title="文艺活动" icon="https://xy.wenlvti.net/app_static/images/village/IconLargeActivity.png" touchable @click="handleGoCollect('trip')" />
  153. <GridItem title="非遗展示" icon="https://xy.wenlvti.net/app_static/images/village/IconLargeShow.png" touchable @click="handleGoCollect('ich')" />
  154. <GridItem title="民俗风采" icon="https://xy.wenlvti.net/app_static/images/village/IconLargeFolkloreVibe.png" touchable @click="handleGoCollect('custom')" />
  155. <GridItem title="文化志愿者" icon="https://xy.wenlvti.net/app_static/images/village/IconLargeVolunteer.png" touchable @click="toast('TODO')" />
  156. </Grid>
  157. </ProvideVar>
  158. <!-- 活力乡源 -->
  159. <HomeTitle title="活力乡源" />
  160. <ProvideVar :vars="{
  161. GridItemIconSize: 90,
  162. GridItemBackgroundColor: 'transparent',
  163. GridItemPaddingHorizontal: 0,
  164. GridItemPaddingVertical: 8,
  165. }">
  166. <Grid :borderGrid="false" :mainAxisCount="4">
  167. <GridItem title="乡源荣光" icon="https://xy.wenlvti.net/app_static/images/village/IconLargeHornor.png" touchable />
  168. <GridItem title="乡源好物" icon="https://xy.wenlvti.net/app_static/images/village/IconLargeGoods.png" touchable />
  169. <GridItem title="乡源树" icon="https://xy.wenlvti.net/app_static/images/village/IconLargeTree.png" touchable />
  170. <GridItem title="共编村史" icon="https://xy.wenlvti.net/app_static/images/village/IconLargeIndustry.png" touchable />
  171. <GridItem title="互动游戏" icon="https://xy.wenlvti.net/app_static/images/village/IconLargeGame.png" touchable />
  172. </Grid>
  173. </ProvideVar>
  174. <!-- 文脉乡源 -->
  175. <HomeTitle title="文脉乡源">
  176. <template #right>
  177. <BackgroundImageButton
  178. backgroundImage="https://xy.wenlvti.net/app_static/images/village/TagActive.png"
  179. :backgroundCutBorder="[10, 10, 10, 10]"
  180. :backgroundCutBorderSize="[10, 10, 10, 10]"
  181. :padding="[15, 20]"
  182. :innerStyle="{ marginRight: '20rpx' }"
  183. @click="handleGoPublish()"
  184. >
  185. <Text text="去发布" fontConfig="contentText" color="white" />
  186. </BackgroundImageButton>
  187. </template>
  188. </HomeTitle>
  189. <RoundTags v-model:active="listActiveTag" :tags="['广场', '老味道', '老手艺', '老物件', '老故事']" />
  190. <SimplePageListLoader :loader="recommendLoader">
  191. <MasonryGrid>
  192. <MasonryGridItem
  193. v-for="(item, i) in recommendLoader.list.value"
  194. :key="i"
  195. :width="340"
  196. >
  197. <IndexCommonImageItem
  198. :image="item.image"
  199. :title="item.title"
  200. :desc="item.content ?? ''"
  201. :userName="item.nickName ?? ''"
  202. :likes="item.likeCount"
  203. :isLike="false"
  204. @click="handleGoPost(item.id)"
  205. />
  206. </MasonryGridItem>
  207. </MasonryGrid>
  208. </SimplePageListLoader>
  209. </FlexCol>
  210. </template>
  211. <script setup lang="ts">
  212. import { computed, ref, watch } from 'vue';
  213. import { useSimplePageListLoader } from '@/components/composeabe/loader/SimplePageListLoader';
  214. import { useAuthStore } from '@/store/auth';
  215. import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
  216. import { useVillageStore } from '@/store/village';
  217. import { confirm, toast } from '@/components/utils/DialogAction';
  218. import { ArrayUtils } from '@imengyu/imengyu-utils';
  219. import { navTo } from '@/components/utils/PageAction';
  220. import HomeTitle from '@/common/components/parts/HomeTitle.vue';
  221. import Button from '@/components/basic/Button.vue';
  222. import Icon from '@/components/basic/Icon.vue';
  223. import Image from '@/components/basic/Image.vue';
  224. import Text from '@/components/basic/Text.vue';
  225. import BackgroundBox from '@/components/display/block/BackgroundBox.vue';
  226. import Touchable from '@/components/feedback/Touchable.vue';
  227. import FlexCol from '@/components/layout/FlexCol.vue';
  228. import FlexRow from '@/components/layout/FlexRow.vue';
  229. import VillageMiniMap from '../../components/VillageMiniMap.vue';
  230. import Divider from '@/components/display/Divider.vue';
  231. import RoundTags from '@/common/components/parts/RoundTags.vue';
  232. import VillageUserRankList from '../../components/VillageUserRankList.vue';
  233. import BackgroundImageButton from '@/components/basic/BackgroundImageButton.vue';
  234. import ProvideVar from '@/components/theme/ProvideVar.vue';
  235. import Grid from '@/components/layout/grid/Grid.vue';
  236. import GridItem from '@/components/layout/grid/GridItem.vue';
  237. import MasonryGrid from '@/components/layout/masonry/MasonryGrid.vue';
  238. import MasonryGridItem from '@/components/layout/masonry/MasonryGridItem.vue';
  239. import IndexCommonImageItem from '@/common/components/parts/IndexCommonImageItem.vue';
  240. import FollowVillageApi from '@/api/light/FollowVillageApi';
  241. import LightVillageApi from '@/api/light/LightVillageApi';
  242. import SimplePageListLoader from '@/components/loader/SimplePageListLoader.vue';
  243. import { useReqireLogin } from '@/common/composeabe/RequireLogin';
  244. const authStore = useAuthStore();
  245. const { requireLogin } = useReqireLogin();
  246. const villageStore = useVillageStore();
  247. const villageInfoLoader = useSimpleDataLoader(async () => {
  248. const village = villageStore.currentVillage;
  249. return {
  250. title: village?.name || '',
  251. desc: village?.desc || '',
  252. address: village?.address,
  253. applyCount: village?.applyCount || 0,
  254. levelText: village?.levelText as string || '',
  255. rankText: village?.rankText as string || '',
  256. light: village?.light as number || 0,
  257. memberCount: village?.memberCount as number || 0,
  258. followerCount: village?.followerCount as number || 0,
  259. images: village?.images || [],
  260. longitude: village?.longitude as number || 0,
  261. latitude: village?.latitude as number || 0,
  262. };
  263. });
  264. watch(() => villageStore.currentVillage, () => {
  265. villageInfoLoader.reload();
  266. recommendLoader.reload();
  267. villageUserRankListLoader.reload();
  268. }, { immediate: true });
  269. const rankActiveTag = ref('乡源果');
  270. const listActiveTag = ref('广场');
  271. const villageUserRankListLoader = useSimpleDataLoader(async () => {
  272. const res = (await LightVillageApi.getVolunteerRankList({
  273. num: 3,
  274. village_id: villageStore.currentVillage?.id ?? undefined,
  275. }))
  276. .map((item, i) => ({
  277. id: item.id,
  278. image: item.image ?? '',
  279. title: item.name,
  280. rank: i + 1,
  281. score: item.points,
  282. }));
  283. if (res.length >= 3) {
  284. //移动第一名到中间
  285. const first = res[0];
  286. ArrayUtils.removeAt(res, 0);
  287. ArrayUtils.insert(res, 1, first);
  288. }
  289. return res
  290. });
  291. watch(rankActiveTag, () => {
  292. villageUserRankListLoader.reload();
  293. });
  294. const isFollowed = computed(() => {
  295. return villageStore.myFollowVillages.some(p => p.id === villageStore.currentVillage?.id);
  296. });
  297. const isJoined = computed(() => {
  298. return false;
  299. });
  300. async function onFollow() {
  301. if (!villageStore.currentVillage)
  302. return;
  303. requireLogin(async () => {
  304. try {
  305. await FollowVillageApi.followVillage(villageStore.currentVillage!.id);
  306. villageStore.myFollowVillages.push(villageStore.currentVillage!);
  307. toast('关注成功');
  308. } catch {
  309. toast('关注失败');
  310. }
  311. }, '登录后才能关注村庄哦');
  312. }
  313. function onUnFollow() {
  314. if (!villageStore.currentVillage) return;
  315. confirm({
  316. title: '取消关注',
  317. content: '确定取消关注该村庄吗?',
  318. confirmText: '取消关注',
  319. cancelText: '取消',
  320. }).then(async (res) => {
  321. if (res) {
  322. try {
  323. await FollowVillageApi.unfollowVillage(villageStore.currentVillage!.id);
  324. villageStore.myFollowVillages = villageStore.myFollowVillages.filter(p => p.id !== villageStore.currentVillage!.id);
  325. toast('取消关注成功');
  326. } catch {
  327. toast('取消关注失败');
  328. }
  329. }
  330. });
  331. }
  332. const recommendLoader = useSimplePageListLoader(20, async (page, pageSize) => {
  333. return await LightVillageApi.getMessages(page, pageSize, {
  334. villageId: villageStore.currentVillage?.id ?? undefined,
  335. keywords: listActiveTag.value,
  336. });
  337. });
  338. watch(listActiveTag, () => {
  339. recommendLoader.reload();
  340. });
  341. function handleGoCollect(taskName: string) {
  342. navTo('/pages/dig/forms/task', {
  343. villageId: villageStore.currentVillage?.id ?? undefined,
  344. taskName: taskName,
  345. taskPid: -1,
  346. });
  347. }
  348. function handleGoPublish() {
  349. if (!authStore.isLogged) {
  350. confirm({
  351. title: '提示',
  352. content: '登录后就可以发布村落贴图了',
  353. confirmText: '去登录',
  354. }).then((res) => {
  355. if (res) {
  356. navTo('/pages/user/login');
  357. }
  358. });
  359. return;
  360. }
  361. navTo('/pages/chat/dependent/post/publish', {
  362. tag: listActiveTag.value,
  363. villageId: villageStore.currentVillage?.id ?? undefined,
  364. });
  365. }
  366. function handleGoPost(id: number) {
  367. navTo('/pages/home/post/detail', {
  368. id: id,
  369. });
  370. }
  371. </script>