index.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. <route lang="json">
  2. {
  3. "style": {
  4. "navigationBarTitleText": "详情",
  5. "navigationBarBackgroundColor": "#fff",
  6. "navigationStyle": "custom"
  7. }
  8. }
  9. </route>
  10. <script setup lang="ts">
  11. import Tag from '@/components/tag.vue'
  12. import {
  13. createCircleReview,
  14. getCircle,
  15. getCircleReviews,
  16. getCircleUpvotes,
  17. shareCircle,
  18. } from '../../../core/libs/requests'
  19. import { handleShareClick, handleUpvoteClick } from '../../../core/libs/actions'
  20. import CommentItem from '../components/comment-item.vue'
  21. import { useUserStore } from '../../../store'
  22. import { storeToRefs } from 'pinia'
  23. import { isImageOrVideo } from '../../../core/utils/common'
  24. import dayjs from 'dayjs'
  25. import SectionHeading from '@/components/section-heading.vue'
  26. import BottomAppBar from '@/components/bottom-app-bar.vue'
  27. import { likeActived, likeBlack } from '@designer-hub/assets/src/icons'
  28. import NavBarEvo from '@/components/navbar-evo.vue'
  29. import { useRouter } from '../../../core/utils/router'
  30. import { usePermissions } from '../../../composables/permissions'
  31. const { features } = usePermissions()
  32. const userStore = useUserStore()
  33. const { userInfo } = storeToRefs(userStore)
  34. const router = useRouter()
  35. const id = ref()
  36. const isShared = ref(false)
  37. const { data, run } = useRequest(() => getCircle(id.value), { initialData: {} })
  38. const { data: reviews, run: runGetReviews } = useRequest(
  39. () => getCircleReviews({ circleId: id.value }),
  40. {
  41. initialData: {
  42. list: [],
  43. },
  44. },
  45. )
  46. const { data: circleUpvotes, run: setCircleUpvotes } = useRequest(
  47. () => getCircleUpvotes(id.value),
  48. { initialData: { list: [], total: 0 } },
  49. )
  50. const swiperSizes = ref()
  51. const swiperStyle = ref()
  52. const reviewContent = ref('')
  53. const isVideo = ref(false)
  54. const handleChange = ({ detail: { current } }) => {
  55. swiperStyle.value = {
  56. height: swiperSizes.value[current].height + 'px',
  57. }
  58. }
  59. const setSwiperStyle = async () => {
  60. if (!data.value.bannerUrls.length) return
  61. const { screenWidth } = await uni.getSystemInfo()
  62. if (data.value.bannerUrls.length === 1 && isImageOrVideo(data.value.bannerUrls[0]) === 'video') {
  63. isVideo.value = true
  64. return
  65. }
  66. swiperSizes.value = (
  67. await Promise.all(data.value.bannerUrls.map((src) => uni.getImageInfo({ src })))
  68. ).map(({ width, height }) => ({ width: screenWidth, height: height / (width / screenWidth) }))
  69. swiperStyle.value = {
  70. height: swiperSizes.value[0].height + 'px',
  71. }
  72. }
  73. const handleSend = async () => {
  74. const { code, msg } = await createCircleReview({
  75. circleId: id.value,
  76. userId: userInfo.value.userId,
  77. userName: userInfo.value.nickname,
  78. reviewContent: reviewContent.value,
  79. })
  80. if (code !== 0) {
  81. uni.showToast({ title: msg, icon: 'none' })
  82. } else {
  83. reviewContent.value = ''
  84. uni.showToast({ title: '评论成功', icon: 'none' })
  85. await runGetReviews()
  86. }
  87. }
  88. onMounted(async () => {})
  89. onLoad(async (query: { id: string; isShared?: boolean }) => {
  90. id.value = query.id
  91. isShared.value = query.isShared
  92. await run()
  93. await runGetReviews()
  94. await setSwiperStyle()
  95. await setCircleUpvotes()
  96. })
  97. // onShareAppMessage(async () => {
  98. // await shareCircle(id.value)
  99. // return { title: data.value?.circleDesc }
  100. // })
  101. onShareAppMessage(async ({ from, target }) => {
  102. console.log('from', from)
  103. console.log('target', target)
  104. if (!features.value.shareMoment) {
  105. return handleShareClick()
  106. }
  107. const res: Page.CustomShareContent = {}
  108. await shareCircle(id.value)
  109. res.path = `/pages/home/moment/index?id=${id.value}&isShared=true`
  110. res.imageUrl = data.value?.bannerUrls[0]
  111. res.title = `${data.value?.stylistName}: ${data.value?.circleDesc}`
  112. return res
  113. })
  114. </script>
  115. <template>
  116. <view class="bg-white flex-grow">
  117. <NavBarEvo placeholder :isShowBack="!isShared">
  118. <template #prepend>
  119. <div
  120. class="flex items-center gap-2"
  121. @click="() => router.push(`/pages/mine/homepage/index?id=${data.stylistId}`)"
  122. >
  123. <wd-img width="24" height="24" round :src="data.headUrl"></wd-img>
  124. <div class="text-black/90 text-sm font-normal font-['PingFang_SC'] leading-[10.18px]">
  125. {{ data.stylistName }}
  126. </div>
  127. </div>
  128. </template>
  129. </NavBarEvo>
  130. <!-- <div class="my-4 text-black/90 text-lg font-normal font-['PingFang_SC'] leading-[10.18px]">
  131. {{ data?.detailsDesc }}
  132. </div> -->
  133. <template v-if="swiperSizes && !isVideo">
  134. <div>
  135. <swiper class="" :style="swiperStyle" @change="handleChange">
  136. <template v-for="it of data?.bannerUrls" :key="it">
  137. <swiper-item>
  138. <wd-img width="100%" :src="it" mode="widthFix"></wd-img>
  139. </swiper-item>
  140. </template>
  141. </swiper>
  142. </div>
  143. </template>
  144. <template v-if="isVideo">
  145. <video width="100%" class="w-full aspect-[1.64/1]" :src="data?.bannerUrls[0]"></video>
  146. </template>
  147. <!-- <wd-swiper
  148. v-model="current"
  149. custom-class="my-1"
  150. autoplay="false"
  151. :list="data?.images"
  152. :indicator="{ type: 'fraction' }"
  153. indicatorPosition="top-right"
  154. imageMode="widthFix"
  155. ></wd-swiper> -->
  156. <view class="m-3.5">
  157. <div class="text-black/90 text-base font-normal font-['PingFang_SC'] leading-[10.18px]">
  158. {{ data?.circleDesc }}
  159. </div>
  160. <view class="my-5.5 flex gap-3.5 flex-wrap">
  161. <!-- <TiltedButton>按钮</TiltedButton> -->
  162. <template v-if="data?.tagName !== ''">
  163. <template v-for="it of data?.tagName?.split(',')" :key="it">
  164. <Tag>{{ it }}</Tag>
  165. </template>
  166. </template>
  167. </view>
  168. <div class="text-black/30 text-xs font-normal font-['PingFang_SC'] leading-[10.18px]">
  169. {{ dayjs(data.createTime).format('YYYY-MM-DD HH:mm') }}
  170. </div>
  171. <!-- <view class="flex items-center my-4">
  172. <view class="flex items-center">
  173. <avatar-group-casual
  174. :show-number="3"
  175. :urls="[
  176. 'https://via.placeholder.com/20x20',
  177. 'https://via.placeholder.com/20x20',
  178. 'https://via.placeholder.com/20x20',
  179. ]"
  180. ></avatar-group-casual>
  181. <div
  182. class="ml-1 text-black/60 text-sm font-normal font-['PingFang_SC'] leading-[10.18px]"
  183. >
  184. {{ circleUpvotes.total }}人赞过
  185. </div>
  186. </view>
  187. <view class="flex-1"></view>
  188. <view><wd-icon class="text-black/65" name="arrow-right" size="22px"></wd-icon></view>
  189. </view> -->
  190. <div class="h-0.25 bg-[#dadada] my-7"></div>
  191. <SectionHeading :title="`评论`" size="base">
  192. <template #append>
  193. <view v-if="reviews?.list" class="flex">
  194. <div class="text-black/90 text-xs font-normal font-['PingFang_SC'] leading-[10.18px]">
  195. 按热度
  196. </div>
  197. <div
  198. class="mx-2 text-black/40 text-xs font-normal font-['PingFang_SC'] leading-[10.18px]"
  199. >
  200. |
  201. </div>
  202. <div class="text-black/40 text-xs font-normal font-['PingFang_SC'] leading-[10.18px]">
  203. 按时间
  204. </div>
  205. </view>
  206. </template>
  207. </SectionHeading>
  208. <view clas="mt-8.25">
  209. <template v-if="reviews?.list.length">
  210. <template v-for="it of reviews?.list" :key="it.id">
  211. <CommentItem
  212. :options="it"
  213. :isChild="false"
  214. @upvote="runGetReviews()"
  215. @delete="runGetReviews()"
  216. ></CommentItem>
  217. <!-- <template v-for="child of it.childrens" :key="child.id">
  218. <CommentItem :options="child" :isChild="true"></CommentItem>
  219. </template> -->
  220. </template>
  221. </template>
  222. <template v-else>
  223. <view class="flex items-center justify-center mt-26 mb-36">
  224. <div class="text-black/40 text-xs font-normal font-['PingFang_SC'] leading-[10.18px]">
  225. 这里空空的
  226. </div>
  227. <div
  228. class="ml-1.5 text-[#2f4471]/90 text-xs font-normal font-['PingFang_SC'] leading-[10.18px]"
  229. >
  230. 点击评论~
  231. </div>
  232. </view>
  233. </template>
  234. </view>
  235. </view>
  236. <BottomAppBar fixed placeholder border custom-class="">
  237. <div class="bg-white flex items-center">
  238. <div class="w-[168px] bg-[#f6f6f6] rounded-[60px] px-3.5 py-2 flex items-center">
  239. <wd-input
  240. custom-class="bg-transparent!"
  241. no-border
  242. confirm-type="send"
  243. v-model="reviewContent"
  244. placeholder="说点什么..."
  245. :cursor-spacing="140"
  246. @confirm="handleSend"
  247. ></wd-input>
  248. </div>
  249. <view class="flex justify-around flex-1">
  250. <div>
  251. <button open-type="share" class="bg-transparent! p-0!">
  252. <view
  253. class="flex flex-col items-center text-[rgba(0,0,0,0.85)] text-3.5 font-400 line-height-5.5"
  254. >
  255. <div class="w-4.5 h-4.5 flex items-center justify-center">
  256. <wd-img width="15" height="15" src="/static/svgs/share.svg"></wd-img>
  257. </div>
  258. <view class="">{{ data?.shareCount || 0 }}</view>
  259. </view>
  260. </button>
  261. </div>
  262. <view
  263. class="flex flex-col items-center text-[rgba(0,0,0,0.85)] text-3.5 font-400 line-height-5.5"
  264. >
  265. <div class="w-4.5 h-4.5 flex items-center justify-center">
  266. <wd-img width="15" height="15" src="/static/svgs/comment.svg"></wd-img>
  267. </div>
  268. <view class="">{{ data?.reviewCount }}</view>
  269. </view>
  270. <view
  271. class="flex flex-col items-center text-[rgba(0,0,0,0.85)] text-3.5 font-400 line-height-5.5"
  272. @click="
  273. handleUpvoteClick(
  274. {
  275. upvote: data.ownUpvote,
  276. circleId: data.id,
  277. userId: userInfo.userId,
  278. userName: userInfo.nickname,
  279. },
  280. () => run(),
  281. )
  282. "
  283. >
  284. <template v-if="data.ownUpvote">
  285. <wd-img width="18" height="18" :src="likeActived"></wd-img>
  286. </template>
  287. <template v-else>
  288. <wd-img width="18" height="18" :src="likeBlack"></wd-img>
  289. </template>
  290. <view>{{ data.upvoteCount }}</view>
  291. </view>
  292. </view>
  293. </div>
  294. </BottomAppBar>
  295. </view>
  296. </template>