card.vue 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764
  1. <template>
  2. <SimplePageContentLoader :loader="pageThemeLoader">
  3. <ProvideVar
  4. v-if="pageThemeLoader.content.value"
  5. :vars="pageThemeLoader.content.value.props?.varOvrride"
  6. :colors="pageThemeLoader.content.value.props?.colorOvrride"
  7. >
  8. <FlexRow center :padding="[0,30]">
  9. <HomeLargeTitle title="村社名片" :active="true" />
  10. <Width :width="30" />
  11. <HomeLargeTitle title="乡源树" titleColorAnim @click="emit('goTree')">
  12. <template #icon>
  13. <Image src="https://xy.wenlvti.net/app_static/images/village/TreeIconAmin2.gif" :width="34" :height="50" mode="heightFix" />
  14. </template>
  15. </HomeLargeTitle>
  16. <Width :width="30" />
  17. </FlexRow>
  18. <DynamicX
  19. :page="pageThemeLoader.content.value"
  20. :injectScriptGlobal="pageScriptGlobal"
  21. >
  22. <template #default="{ page, context }">
  23. <DynamicXNodeChild :node="page">
  24. <template #nodeRender="{ node }">
  25. <FlexCol v-if="node.type === 'Card:Basic'" gap="gap.md">
  26. <!-- 标题 -->
  27. <FlexRow justify="space-between" gap="gap.lg" wrap align="center" width="100%">
  28. <FlexRow gap="gap.md">
  29. <Text
  30. :text="title"
  31. :lines="1"
  32. :fontSize="40"
  33. wordBreak="break-all"
  34. fontConfig="primaryTitle"
  35. />
  36. </FlexRow>
  37. <FlexRow flex="1" justify="space-between" align="center" gap="gap.lg">
  38. <IconButton icon="cascades" :size="40" @click="shareQRDialog?.show()" />
  39. <FlexRow align="center" gap="gap.lg">
  40. <BubbleTip
  41. v-model:show="showFollowTip"
  42. content="关注我,方便下次进入"
  43. @contentClick="isFollowed ? undefined : onFollow()"
  44. @close="handleCloseFollowTip"
  45. >
  46. <Button
  47. :icon="isFollowed ? 'success' : 'https://xy.wenlvti.net/app_static/images/village/IconJoin.png'"
  48. radius="radius.lgr"
  49. :text="isFollowed ? (title.length < 5 ? '已关注' : '') : '关注'"
  50. :padding="[15, 10]"
  51. :iconProps="{ size: 38 }"
  52. @click="isFollowed ? onUnFollow() : onFollow()"
  53. />
  54. </BubbleTip>
  55. <FlexRow
  56. v-if="isJoined"
  57. gap="gap.md"
  58. radius="radius.lgr"
  59. backgroundColor="background.tertiary"
  60. >
  61. <Avatar
  62. :src="authStore.userInfo?.avatar"
  63. :size="65"
  64. defaultAvatar="https://xy.wenlvti.net/app_static/images/mine/DefaultAvatar.png"
  65. />
  66. <FlexCol position="relative" gap="gap.sm" :padding="[0,30,0,0]">
  67. <FlexRow align="center" :gap="10">
  68. <Text :text="displayMyName" fontSize="23" :lines="1" :maxWidth="150" fontConfig="lightImportantTitle" />
  69. <Button
  70. v-if="isStaff"
  71. :innerStyle="{ position: 'absolute', right: 0, top: 0, transform: 'translate(5%,-50%)' }"
  72. text="管理" size="mini" radius="radius.md" type="danger"
  73. @click="staffInfoRef?.show()"
  74. />
  75. </FlexRow>
  76. <IconButton icon="edit-filling" size="26" @click="changeNickRef?.show()">
  77. <Text text="改昵称" :wrap="false" fontSize="22" fontConfig="subText" />
  78. </IconButton>
  79. </FlexCol>
  80. </FlexRow>
  81. <Button
  82. v-else
  83. icon="https://xy.wenlvti.net/app_static/images/village/IconFollow.png"
  84. radius="radius.lgr"
  85. text="加入"
  86. @click="handleGoJoin()"
  87. />
  88. </FlexRow>
  89. </FlexRow>
  90. </FlexRow>
  91. <!-- 简介 -->
  92. <TextEllipsis
  93. fontConfig="secondText"
  94. :outerProps="{ direction: 'row', width: '490rpx' }"
  95. :lines="1"
  96. :expandable="(villageInfoLoader.content.value?.desc as string || '').length > 20"
  97. :text="(villageInfoLoader.content.value?.desc as string) || '暂无简介,欢迎您来编写完善!'"
  98. wordBreak="break-all"
  99. @expand="descOpen = true;"
  100. @collapse="descOpen = false;"
  101. />
  102. </FlexCol>
  103. <template v-else-if="node.type === 'Card:Level'">
  104. <!-- 状态与申请 -->
  105. <FlexRow
  106. backgroundColor="background.tertiary"
  107. radius="radius.md"
  108. padding="space.md"
  109. justify="space-between"
  110. >
  111. <FlexRow align="center">
  112. <FlexCol gap="gap.md">
  113. <FlexRow gap="gap.lg" align="center">
  114. <Icon v-if="villageInfoLoader.content.value?.vipLevel === 4" name="https://xy.wenlvti.net/app_static/images/village/IconGov.png" :size="70" />
  115. <Text :text="`${villageInfoLoader.content.value?.vipLevelText || '默认级别'}`" fontConfig="secondText" />
  116. </FlexRow>
  117. <Text v-if="villageInfoLoader.content.value?.sizeLimit" :text="`存储空间:${villageInfoLoader.content.value?.sizeText || ''}`" :fontSize="22" fontConfig="secondText" />
  118. <Text v-else text="无限存储空间" :fontSize="22" fontConfig="secondText" />
  119. </FlexCol>
  120. </FlexRow>
  121. <FlexRow align="center" gap="gap.md">
  122. <IconButton icon="help-filling" @click="navTo('/pages/article/details', { id: 7021, modelId: 18, showRecommend: false })" />
  123. <Button
  124. v-if="(villageInfoLoader.content.value?.vipLevel || 0) < 4"
  125. icon="https://xy.wenlvti.net/app_static/images/village/IconUser.png"
  126. radius="radius.lgr"
  127. :padding="[10, 30]"
  128. backgroundColor="white"
  129. text="升级村社"
  130. @click="upgradeRef?.show()"
  131. />
  132. </FlexRow>
  133. </FlexRow>
  134. </template>
  135. <template v-else-if="node.type === 'Card:Gallery'">
  136. <!-- 图片 -->
  137. <VillageGallery
  138. v-if="villageInfoLoader.content.value"
  139. ref="villageGalleryRef"
  140. :villageId="villageStore.currentVillage?.id ?? 0"
  141. @goGallery="handleGoGallery"
  142. />
  143. </template>
  144. <template v-else-if="node.type === 'Card:AddressAndMap'">
  145. <!-- 地图 -->
  146. <VillageMiniMap
  147. v-if="villageInfoLoader.content.value"
  148. :lonlat="{
  149. longitude: villageInfoLoader.content.value.longitude,
  150. latitude: villageInfoLoader.content.value.latitude
  151. }"
  152. :currentNoticeContent="currentNoticeContent"
  153. >
  154. <FlexRow position="absolute" :inset="{ l: 13, r: 13, b: 13 }" center :zIndex="100">
  155. <FlexRow align="center" gap="gap.sm" backgroundColor="background.tertiary" radius="radius.lg" padding="padding.sm">
  156. <Icon name="https://xy.wenlvti.net/app_static/images/village/IconMap.png" size="fontSize.md" />
  157. <Text :text="villageInfoLoader.content.value?.address" :maxWidth="550" fontConfig="contentText" fontSize="fontSize.sm" />
  158. </FlexRow>
  159. </FlexRow>
  160. </VillageMiniMap>
  161. </template>
  162. <template v-else-if="node.type === 'Card:Static'">
  163. <!-- 村社状态信息 -->
  164. <FlexRow justify="space-between" align="center">
  165. <FlexRow center gap="gap.lg" flexBasis="50%">
  166. <Text text="村社排名" fontConfig="contentText" />
  167. <Text text="No." fontConfig="lightTitle" />
  168. <Text :text="villageInfoLoader.content.value?.rankText" fontConfig="primaryTitle" />
  169. </FlexRow>
  170. <FlexRow center gap="gap.lg" flexBasis="50%">
  171. <Text text="村社等级" fontConfig="contentText" />
  172. <Text :text="`${villageInfoLoader.content.value?.level}级`" fontConfig="primaryTitle" />
  173. </FlexRow>
  174. </FlexRow>
  175. <Height height="gap.md" />
  176. <FlexRow backgroundColor="background.tertiary" radius="radius.md" :padding="[30, 20]">
  177. <Touchable direction="column" center gap="gap.sm" flexBasis="25%" @click="navTo('/pages/home/village/task/index')">
  178. <Text text="乡源光" fontConfig="secondText" fontSize="fontSize.sm" />
  179. <Text :text="villageInfoLoader.content.value?.light || '0'" fontConfig="importantTitle" />
  180. <Button type="text" size="mini" text="做任务" @click="navTo('/pages/home/village/task/index')" />
  181. </Touchable>
  182. <Divider type="vertical" />
  183. <FlexCol center gap="gap.sm" flexBasis="25%">
  184. <Touchable direction="column" center @click="navTo('/pages/home/village/volunteer/list', { villageId: villageStore.currentVillage?.id ?? undefined })">
  185. <Text text="乡源人数" fontConfig="contentText" fontSize="fontSize.sm" />
  186. <Text :text="villageInfoLoader.content.value?.memberCount|| '0'" fontConfig="importantTitle" />
  187. </Touchable>
  188. <WxButton openType="share">
  189. <Button type="text" size="mini" text="邀请加入" @click="navTo('/pages/home/village/task/index')" />
  190. </WxButton>
  191. </FlexCol>
  192. <Divider type="vertical" />
  193. <Touchable direction="column" center gap="gap.sm" flexBasis="25%" @click="navTo('/pages/home/village/follow/list', {
  194. villageId: villageStore.currentVillage?.id ?? undefined,
  195. })">
  196. <Text text="关注人数" fontConfig="contentText" fontSize="fontSize.sm" />
  197. <Text :text="villageInfoLoader.content.value?.followerCount|| '0'" fontConfig="importantTitle" />
  198. <Height :size="36" />
  199. </Touchable>
  200. <Divider type="vertical" />
  201. <Button
  202. :padding="0"
  203. type="text"
  204. size="small"
  205. textColor="text.title"
  206. rightIcon="arrow-right"
  207. @click="handleGoNew()"
  208. >
  209. <FlexCol>
  210. <Text text="新手" fontConfig="contentText" />
  211. <Text text="上路" fontConfig="contentText" />
  212. </FlexCol>
  213. </Button>
  214. </FlexRow>
  215. </template>
  216. <template v-else-if="node.type === 'Block:Rank'">
  217. <!-- 排行榜 -->
  218. <HomeTitle
  219. :title="node.props?.title"
  220. showMore
  221. @moreClicked="navTo('/pages/home/village/rank/volunteer', {
  222. villageId: villageStore.currentVillage?.id ?? undefined,
  223. })"
  224. />
  225. <Height height="gap.md" />
  226. <VillageUserRankList
  227. :list="villageUserRankListLoader.content.value ?? []"
  228. scoreSuffix="文化积分"
  229. @goDetails="navTo('/pages/home/village/volunteer/detail', { id: $event.id })"
  230. />
  231. </template>
  232. <template v-else-if="node.type === 'Block:Collect'">
  233. <!-- 魅力乡源 -->
  234. <HomeTitle
  235. :title="node.props?.title"
  236. showMore
  237. @moreClicked="handleGoCollect()"
  238. >
  239. <template #right>
  240. <FlexRow align="center" gap="gap.md">
  241. <FrameButton
  242. text="管理"
  243. size="small"
  244. primary
  245. @click="handleGoDigManage()"
  246. />
  247. <FrameButton
  248. text="共编村史"
  249. icon="https://xy.wenlvti.net/app_static/images/village/IconHistory.png"
  250. size="small"
  251. @click="handleGoCollect()"
  252. />
  253. <Width :size="15" />
  254. </FlexRow>
  255. </template>
  256. </HomeTitle>
  257. <Height height="gap.md" />
  258. <ProvideVar :vars="{
  259. GridItemIconSize: 90,
  260. GridItemBackgroundColor: 'transparent',
  261. GridItemPaddingHorizontal: 0,
  262. GridItemPaddingVertical: 8,
  263. }">
  264. <Grid :borderGrid="false" :mainAxisCount="4">
  265. <GridItem title="村社概况" icon="https://xy.wenlvti.net/app_static/images/village/IconLargeIntrod.png" touchable @click="handleGoCollect(11, '村社概况')" />
  266. <GridItem title="自然风光" icon="https://xy.wenlvti.net/app_static/images/village/IconLargeEnvirounment.png" touchable @click="handleGoCollect(13, '自然风光')" />
  267. <GridItem title="历史沿革" icon="https://xy.wenlvti.net/app_static/images/village/IconLargeHistory.png" touchable @click="handleGoCollect(2, '历史沿革')" />
  268. <GridItem title="特色产业" icon="https://xy.wenlvti.net/app_static/images/village/IconLargeIndustry.png" touchable @click="handleGoCollect(9, '特色产业' )" />
  269. <GridItem title="文艺活动" icon="https://xy.wenlvti.net/app_static/images/village/IconLargeActivity.png" touchable @click="handleGoCollect(12, '文艺活动')" />
  270. <GridItem title="非遗展示" icon="https://xy.wenlvti.net/app_static/images/village/IconLargeShow.png" touchable @click="handleGoCollect(10, '非遗展示')" />
  271. <GridItem title="民俗风采" icon="https://xy.wenlvti.net/app_static/images/village/IconLargeFolkloreVibe.png" touchable @click="handleGoCollect(8, '民俗风采')" />
  272. <GridItem title="历史人物" icon="https://xy.wenlvti.net/app_static/images/village/IconGoods.png" touchable @click="handleGoCollect(7, '历史人物')" />
  273. </Grid>
  274. </ProvideVar>
  275. </template>
  276. <template v-else-if="node.type === 'Block:Games'">
  277. <!-- 活力乡源 -->
  278. <HomeTitle :title="node.props?.title" />
  279. <Height height="gap.md" />
  280. <ProvideVar :vars="{
  281. GridItemIconSize: 90,
  282. GridItemBackgroundColor: 'transparent',
  283. GridItemPaddingHorizontal: 0,
  284. GridItemPaddingVertical: 8,
  285. }">
  286. <Grid :borderGrid="false" :mainAxisCount="4">
  287. <GridItem
  288. v-for="item in node.props?.items"
  289. :key="item.title"
  290. :title="item.title"
  291. :icon="item.icon"
  292. touchable
  293. @click="() => context.getScriptEngine().execute(item.handler, node.getNodeScriptContext?.())"
  294. />
  295. </Grid>
  296. </ProvideVar>
  297. </template>
  298. <template v-else-if="node.type === 'Block:Official'">
  299. <!-- 文脉乡源 -->
  300. <HomeTitle :title="node.props?.title">
  301. <template #right>
  302. <FlexRow align="center" gap="gap.md">
  303. <BubbleTip
  304. v-model:show="showManageTip"
  305. position="bottom"
  306. crossPosition="right"
  307. arrowOffsetX="180rpx"
  308. content="村社贴图太乱?点这里整理"
  309. @contentClick="handleCloseManageTip(true)"
  310. @close="handleCloseManageTip(false)"
  311. >
  312. <FrameButton
  313. text="管理"
  314. size="small"
  315. primary
  316. @click="handleGoOfficalManage()"
  317. />
  318. </BubbleTip>
  319. <FrameButton
  320. text="乡源AI帮你写"
  321. size="small"
  322. icon="https://xy.wenlvti.net/app_static/images/village/IconLargeHistory.png"
  323. @click="handleGoPublish()"
  324. />
  325. </FlexRow>
  326. </template>
  327. </HomeTitle>
  328. <Height height="gap.md" />
  329. <RoundTags
  330. v-model:active="listActiveTag"
  331. :tags="tagsLoader.content.value || []"
  332. />
  333. <Height height="gap.md" />
  334. <OfficialAccountPublishWrap
  335. :topic="recommendTagName"
  336. :path="recommendPath"
  337. @publishsuccess="onPublishSuccess"
  338. @empty="isOfficialEmpty=true"
  339. />
  340. </template>
  341. </template>
  342. </DynamicXNodeChild>
  343. </template>
  344. </DynamicX>
  345. </ProvideVar>
  346. </SimplePageContentLoader>
  347. <UpgradeDialog
  348. ref="upgradeRef"
  349. v-if="villageStore.currentVillage"
  350. :villageId="villageStore.currentVillage.id"
  351. />
  352. <ChangeNickDialog
  353. v-if="villageStore.currentVillage"
  354. :villageId="villageStore.currentVillage.id"
  355. :currentNick="displayMyName"
  356. ref="changeNickRef"
  357. />
  358. <StaffInfoDialog
  359. v-if="villageStore.currentVillage && staffInfo"
  360. :villageId="villageStore.currentVillage.id"
  361. :staffEndTime="staffInfo.staffEndTime"
  362. :staffSource="staffInfo.staffSource"
  363. :staffTimeType="staffInfo.staffTimeType"
  364. ref="staffInfoRef"
  365. />
  366. <ShareQRDialog
  367. v-if="villageStore.currentVillage"
  368. :villageId="villageStore.currentVillage.id"
  369. ref="shareQRDialog"
  370. />
  371. <Popup
  372. :show="showUnLight"
  373. :zIndex="100"
  374. size="45%"
  375. position="bottom"
  376. backgroundColor="transparent"
  377. >
  378. <FlexCol center>
  379. <BackgroundBox
  380. backgroundImage="https://xy.wenlvti.net/app_static/images/village/BoxDialog.png"
  381. :backgroundCutBorder="48"
  382. :backgroundCutBorderSize="48"
  383. :padding="40"
  384. direction="column"
  385. center
  386. width="600rpx"
  387. gap="gap.md"
  388. >
  389. <Image src="https://xy.wenlvti.net/app_static/images/home/BadgeNew.png" :width="320" mode="widthFix" />
  390. <Text :text="`${villageStore.currentVillage?.name}暂未点亮`" fontConfig="primaryTitle" />
  391. <Text text="等你的加入,快来为家乡建设做贡献吧" fontConfig="contentText" />
  392. <Height :height="20" />
  393. <FlexRow gap="gap.lg">
  394. <FrameButton :text="isFollowed ? '已关注' : '先关注村社'" @click="isFollowed ? onUnFollow() : onFollow()" />
  395. <FrameButton text="去申请点亮" @click="handleGoJoin" primary />
  396. </FlexRow>
  397. </BackgroundBox>
  398. <Height :size="300" />
  399. </FlexCol>
  400. </Popup>
  401. </template>
  402. <script setup lang="ts">
  403. import { computed, inject, reactive, ref, watch, type Ref } from 'vue';
  404. import { useUserTools } from '@/common/composeabe/UserTools';
  405. import { useOfficialAccount } from '../../composeabe/OfficialAccount';
  406. import { useAuthStore } from '@/store/auth';
  407. import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
  408. import { useVillageStore } from '@/store/village';
  409. import { useRequireLogin } from '@/common/composeabe/RequireLogin';
  410. import { useFollow } from '../composeabe/Follow';
  411. import { useGetNotice } from '../composeabe/GetNotice';
  412. import { assertNotNull, FormatUtils, waitTimeOut } from '@imengyu/imengyu-utils';
  413. import { navTo } from '@/components/utils/PageAction';
  414. import { alert, confirm } from '@/components/dialog/CommonRoot';
  415. import { isDevEnv } from '@/common/config/AppCofig';
  416. import { defaultTags } from './data/DefaultTag';
  417. import DefaultCard from './data/DefaultCard.json';
  418. import HomeTitle from '@/common/components/parts/HomeTitle.vue';
  419. import Icon from '@/components/basic/Icon.vue';
  420. import Text from '@/components/basic/Text.vue';
  421. import BackgroundBox from '@/components/display/block/BackgroundBox.vue';
  422. import Touchable from '@/components/feedback/Touchable.vue';
  423. import FlexCol from '@/components/layout/FlexCol.vue';
  424. import FlexRow from '@/components/layout/FlexRow.vue';
  425. import VillageMiniMap from '../../components/VillageMiniMap.vue';
  426. import Divider from '@/components/display/Divider.vue';
  427. import RoundTags from '@/common/components/parts/RoundTags.vue';
  428. import VillageUserRankList from '../../components/VillageUserRankList.vue';
  429. import ProvideVar from '@/components/theme/ProvideVar.vue';
  430. import Grid from '@/components/layout/grid/Grid.vue';
  431. import GridItem from '@/components/layout/grid/GridItem.vue';
  432. import LightVillageApi from '@/api/light/LightVillageApi';
  433. import OfficialAccountPublishWrap from '@/common/components/OfficialAccountPublishWrap.vue';
  434. import UpgradeDialog from '../dialogs/UpgradeDialog.vue';
  435. import VillageGallery from '../components/VillageGallery.vue';
  436. import WxButton from '@/components/basic/WxButton.vue';
  437. import Height from '@/components/layout/space/Height.vue';
  438. import IconButton from '@/components/basic/IconButton.vue';
  439. import Button from '@/components/basic/Button.vue';
  440. import BubbleTip from '@/components/feedback/BubbleTip.vue';
  441. import MemoryTimeOut from '@/components/composeabe/MemoryTimeOut';
  442. import TextEllipsis from '@/components/display/TextEllipsis.vue';
  443. import OfficialApi from '@/api/light/OfficialApi';
  444. import FrameButton from '@/common/components/FrameButton.vue';
  445. import Width from '@/components/layout/space/Width.vue';
  446. import Popup from '@/components/dialog/Popup.vue';
  447. import Image from '@/components/basic/Image.vue';
  448. import Avatar from '@/components/display/Avatar.vue';
  449. import ChangeNickDialog from '../dialogs/ChangeNickDialog.vue';
  450. import StaffInfoDialog from '../dialogs/StaffInfoDialog.vue';
  451. import ShareQRDialog from '../dialogs/ShareQRDialog.vue';
  452. import DynamicX from '@/components/dynamic/DynamicX.vue';
  453. import type { DynamicXPage } from '@/components/dynamic/DynamicX';
  454. import DynamicXNodeChild from '@/components/dynamic/x/DynamicXNodeChild.vue';
  455. import SimplePageContentLoader from '@/components/loader/SimplePageContentLoader.vue';
  456. import HomeLargeTitle from '@/common/components/parts/HomeLargeTitle.vue';
  457. const emit = defineEmits<{
  458. (e: 'goTree'): void;
  459. (e: 'goJoin'): void;
  460. }>();
  461. const authStore = useAuthStore();
  462. const { getIsVolunteer } = useUserTools();
  463. const { requireLogin } = useRequireLogin();
  464. const { onPublishSuccess, makeOfficialPublishLinkPathAndParams } = useOfficialAccount(() => {
  465. return {
  466. villageId: villageStore.currentVillage?.id ?? 0,
  467. userId: authStore.userId,
  468. tag: recommendTagName.value,
  469. };
  470. });
  471. const villageStore = useVillageStore();
  472. const { isFollowed, onFollow, onUnFollow } = useFollow();
  473. const { getIsJoinedVillage } = useUserTools();
  474. const isOfficialEmpty = ref(false);
  475. const isJoined = ref(false);
  476. const villageInfoLoader = useSimpleDataLoader(async () => {
  477. const village = villageStore.currentVillage;
  478. try {
  479. if (village)
  480. isJoined.value = await getIsJoinedVillage(village.id);
  481. } catch (e) {
  482. console.error(e);
  483. isJoined.value = false;
  484. }
  485. handleShowFollowTip();
  486. isOfficialEmpty.value = false;
  487. showUnLight.value = !villageStore.currentVillage?.isLight;
  488. setTimeout(() => {
  489. handleManageFollowTip();
  490. }, 3000);
  491. return {
  492. title: village?.name || '',
  493. desc: village?.desc || '',
  494. address: village?.address,
  495. sizeText: `${FormatUtils.formatSize(village?.storageUsed || 0)}/${FormatUtils.formatSize(village?.storageLimit || 0)}`,
  496. sizeLimit: (village?.storageLimit || 0) >= 0,
  497. level: village?.level || 1,
  498. vipLevel: village?.vipLevel || 0,
  499. vipLevelText: village?.vipName ? `已升级 · ${village?.vipName}` : '',
  500. rankText: village?.rank.toString() || '',
  501. light: village?.light || 0,
  502. memberCount: village?.volunteerCount || 0,
  503. followerCount: village?.followCount || 0,
  504. images: village?.images || [],
  505. longitude: village?.longitude || 0,
  506. latitude: village?.latitude || 0,
  507. };
  508. });
  509. const isLight = computed(() => villageStore.currentVillage?.isLight ?? false);
  510. const title = computed(() => villageInfoLoader.content.value?.title || '');
  511. const extraInfoInList = computed(() => villageStore.myJoinedVillages.find(v => v.id === villageStore.currentVillage?.id));
  512. const displayMyName = computed(() => extraInfoInList.value?.villageNickname || authStore.userInfo?.nickname || authStore.userInfo?.username || '暂无昵称');
  513. const staffInfo = computed(() => extraInfoInList.value ? {
  514. type: extraInfoInList.value.type as string,
  515. staffEndTime: extraInfoInList.value.staffEndTime as Date,
  516. staffSource: extraInfoInList.value.staffSource as number,
  517. staffTimeType: extraInfoInList.value.staffTimeType as number,
  518. } : null);
  519. const isStaff = computed(() => staffInfo.value?.type === 'staff');
  520. const shareQRDialog = ref<InstanceType<typeof ShareQRDialog>>();
  521. const descOpen = ref(false);
  522. const listActiveTag = ref('广场');
  523. const villageUserRankListLoader = useSimpleDataLoader(async () => {
  524. const res = (await LightVillageApi.getVolunteerRankList({
  525. num: 3,
  526. village_id: villageStore.currentVillage?.id ?? undefined,
  527. }))
  528. .map((item, i) => ({
  529. id: item.id,
  530. image: item.avatar ?? '',
  531. title: item.name,
  532. rank: i + 1,
  533. score: item.points,
  534. }));
  535. return res
  536. });
  537. const showFollowTipTimeout = new MemoryTimeOut('FollowTip', 1000 * 3600);//1h
  538. const showFollowTip = ref(false);
  539. function handleShowFollowTip() {
  540. if (!isFollowed.value && isLight.value && showFollowTipTimeout.isTimeout()) {
  541. showFollowTip.value = true;
  542. }
  543. }
  544. function handleCloseFollowTip() {
  545. showFollowTipTimeout.recordTime();
  546. }
  547. const showUnLight = ref(false);
  548. const showManageTipTimeout = new MemoryTimeOut('VillageManageTip', 1000 * 3600 * 20);//30h
  549. const showManageTip = ref(false);
  550. function handleManageFollowTip() {
  551. if (showManageTipTimeout.isTimeout() && !isOfficialEmpty.value)
  552. showManageTip.value = true;
  553. }
  554. function handleCloseManageTip(goComp: boolean) {
  555. showManageTipTimeout.recordTime();
  556. }
  557. const recommendTagName = computed(() => {
  558. return '亮乡源·' + villageInfoLoader.content.value?.title + '·' + listActiveTag.value;
  559. });
  560. const recommendPath = computed(() => {
  561. return makeOfficialPublishLinkPathAndParams('/pages/index');
  562. });
  563. function handleGoJoin() {
  564. if (isJoined.value)
  565. return;
  566. requireLogin(async () => emit('goJoin'), '登录后就可以加入村社啦');
  567. }
  568. function handleGoNew() {
  569. navTo('/pages/article/common/list', {
  570. modelId: 18,
  571. mainBodyColumnId: 356,
  572. title: '新手上路',
  573. });
  574. }
  575. function handleGoCollect(collectModuleId?: number, title?: string) {
  576. if (!collectModuleId) {
  577. navTo('/pages/dig/details', {
  578. villageId: villageStore.currentVillage?.id ?? undefined,
  579. name: villageInfoLoader.content.value?.title,
  580. });
  581. return;
  582. }
  583. navTo('/pages/dig/forms/list-ordinary', {
  584. villageId: villageStore.currentVillage?.id ?? undefined,
  585. collectModuleId: collectModuleId,
  586. title: title,
  587. });
  588. }
  589. function handleGoPublish() {
  590. requireLogin(async () => {
  591. navTo('/pages/home/chat/dependent/post/publish', {
  592. tag: recommendTagName.value,
  593. villageId: villageStore.currentVillage?.id ?? undefined,
  594. });
  595. }, '登录后以便发布文章使用更多功能哦!');
  596. }
  597. function handleGoGallery() {
  598. navTo('/pages/home/village/gallery/index', {
  599. villageId: villageStore.currentVillage?.id ?? 0 ,
  600. maxCount: villageStore.currentVillage?.imageLimit || 3,
  601. });
  602. }
  603. function handleGoSkin() {
  604. navTo('/pages/home/village/introd/config', {
  605. villageId: villageStore.currentVillage?.id ?? 0 ,
  606. });
  607. }
  608. const staffInfoRef = ref<InstanceType<typeof StaffInfoDialog>>();
  609. async function checkIsStaff(needTopic?: boolean) {
  610. assertNotNull(villageStore.currentVillage?.id)
  611. if (isDevEnv)
  612. return true;
  613. const isAdmin = needTopic ?
  614. await OfficialApi.checkTopicRule(villageStore.currentVillage.id)
  615. : (authStore.isAdmin || isStaff.value);
  616. if (!isAdmin) {
  617. if (needTopic && (isStaff.value || authStore.isAdmin)) {
  618. alert({
  619. title: '提示',
  620. content: '您暂无管理贴图权限,无法管理贴图哦。可联系客服询问原因。',
  621. });
  622. } else {
  623. const goUpgrade = await confirm({
  624. title: '提示',
  625. content: '您还不是管理员,无法管理'
  626. + (needTopic ? '贴图' : '内容')
  627. + '。您可以联系村社管理员将你添加为管理员,若当前村社暂无管理员,您可以点击“去升级”将您升级为管理员。',
  628. confirmText: '去升级',
  629. cancelText: '取消',
  630. });
  631. if (goUpgrade) {
  632. navTo('/pages/home/village/upgrade/my-upgrade-management', {
  633. villageId: villageStore.currentVillage.id,
  634. });
  635. }
  636. }
  637. return false;
  638. }
  639. return true;
  640. }
  641. async function handleGoOfficalManage() {
  642. assertNotNull(villageStore.currentVillage?.id)
  643. if (!await checkIsStaff(true))
  644. return;
  645. navTo('/pages/home/village/post/management-list', {
  646. villageId: villageStore.currentVillage.id,
  647. topic: recommendTagName.value,
  648. });
  649. }
  650. async function handleGoEdit() {
  651. assertNotNull(villageStore.currentVillage?.id)
  652. if (!await checkIsStaff())
  653. return;
  654. navTo('/pages/home/village/introd/edit', {
  655. villageId: villageStore.currentVillage.id,
  656. });
  657. }
  658. async function handleGoDigManage() {
  659. assertNotNull(villageStore.currentVillage?.id)
  660. if (!await checkIsStaff())
  661. return;
  662. navTo('/pages/dig/details', {
  663. name: villageStore.currentVillage.title,
  664. villageId: villageStore.currentVillage.id,
  665. });
  666. }
  667. const { currentNoticeContent, noticeListLoader } = useGetNotice(() => villageStore.currentVillage?.id || 0);
  668. const upgradeRef = ref<InstanceType<typeof UpgradeDialog>>();
  669. const villageGalleryRef = ref<InstanceType<typeof VillageGallery>>();
  670. const changeNickRef = ref<InstanceType<typeof ChangeNickDialog>>();
  671. const pageScriptGlobal = reactive({
  672. handleGoGallery,
  673. handleGoSkin,
  674. handleGoEdit,
  675. handleGoDigManage,
  676. handleGoCollect,
  677. getVillageId: () => villageStore.currentVillage?.id ?? 0,
  678. getDefaultTags: () => defaultTags,
  679. navTo,
  680. emit,
  681. });
  682. const topBanner = inject('topBanner') as Ref<string>;
  683. const pageThemeLoader = useSimpleDataLoader(async () => {
  684. //TODO: 加载村社主题
  685. const page = DefaultCard as DynamicXPage;
  686. topBanner.value = page.props?.topBanner || 'https://xy.wenlvti.net/app_static/images/home/BannerHome.png';
  687. return page;
  688. });
  689. const tagsLoader = useSimpleDataLoader(async () => {
  690. //TODO: 加载村社话题
  691. return defaultTags;
  692. });
  693. watch(() => villageStore.currentVillage, async () => {
  694. await waitTimeOut(100);
  695. await villageInfoLoader.reload();
  696. await waitTimeOut(100);
  697. await villageUserRankListLoader.reload();
  698. await waitTimeOut(100);
  699. await noticeListLoader.reload();
  700. }, { immediate: true });
  701. defineExpose({
  702. onPageBack: (name: string, data: Record<string, unknown>) => {
  703. if (name === 'refreshVillageGrallery') {
  704. villageGalleryRef.value?.refresh();
  705. } else if (name === 'refreshOfficialAccount') {
  706. const tag = listActiveTag.value;
  707. listActiveTag.value = '';
  708. setTimeout(() => listActiveTag.value = tag, 100);
  709. } else if (name === 'showVillageUpgrade') {
  710. upgradeRef.value?.show();
  711. } else if (name === 'backShowVillageJoin') {
  712. handleGoJoin();
  713. }
  714. },
  715. });
  716. </script>