index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. <route lang="json">
  2. {
  3. "style": {
  4. "navigationStyle": "custom"
  5. }
  6. }
  7. </route>
  8. <script setup lang="ts">
  9. import MomentItem from '@/components/moment-item.vue'
  10. import {
  11. createBrowseHistory,
  12. deleteCircle,
  13. getCircles,
  14. getDesignerInfo,
  15. getUserInfoById,
  16. updateDesignerInfo,
  17. shareCircle,
  18. getOwnBadges,
  19. } from '../../../core/libs/requests'
  20. import { useUserStore } from '../../../store'
  21. import { storeToRefs } from 'pinia'
  22. import { NetImages } from '../../../core/libs/net-images'
  23. import PageHelper from '@/components/page-helper.vue'
  24. import BottomAppBar from '@/components/bottom-app-bar.vue'
  25. import { useRouter } from '../../../core/utils/router'
  26. import NavbarEvo from '@/components/navbar-evo.vue'
  27. import { useMessage } from 'wot-design-uni'
  28. import { requestToast } from '../../../core/utils/common'
  29. import { ComponentExposed } from 'vue-component-type-helpers'
  30. import dayjs from 'dayjs'
  31. import wechatChannels from '@designer-hub/assets/src/libs/assets/wechatChannels'
  32. import { handleUpvoteClick } from '../../../core/libs/actions'
  33. import { usePermissions } from '../../../composables/permissions'
  34. import ImageEvo from '@/components/image-evo.vue'
  35. import more from '@designer-hub/assets/src/libs/assets/more'
  36. import qrCode from '@designer-hub/assets/src/libs/assets/qrCode'
  37. import { useShare } from '@/composables/share'
  38. const { features, clickByPermission } = usePermissions()
  39. const { shareAppMessage } = useShare()
  40. const { alert, confirm } = useMessage()
  41. const router = useRouter()
  42. const userStore = useUserStore()
  43. const { userInfo } = storeToRefs(userStore)
  44. const pageHelperRef = ref<ComponentExposed<typeof PageHelper>>()
  45. const id = ref()
  46. const isShared = ref(false)
  47. const tab = ref('2')
  48. const tabs = ref([
  49. { label: '案例', value: '2' },
  50. { label: '动态', value: '1' },
  51. // { label: '视频', value: '0' },
  52. ])
  53. const viewDuration = ref(0)
  54. const viewStartAt = ref<Date>()
  55. const { data: memberInfo, run: setMemberInfo } = useRequest(() => getUserInfoById(id.value), {
  56. initialData: {},
  57. })
  58. const { data: designerInfo, run: setDesignerInfo } = useRequest(() => getDesignerInfo(id.value), {
  59. initialData: {},
  60. })
  61. const { data: badges, run: setBadges } = useRequest(() => getOwnBadges({ userId: id.value }))
  62. const isOwn = computed(() => String(userInfo.value?.userId) === id.value)
  63. const skills = computed(() =>
  64. [
  65. {
  66. label: '从业年限',
  67. value: designerInfo.value?.serviceYears,
  68. show: designerInfo.value?.serviceYears,
  69. },
  70. {
  71. label: '客户',
  72. value: designerInfo.value?.serviceCustomerCount,
  73. show: designerInfo.value?.serviceCustomerCount,
  74. },
  75. {
  76. label: '设计费',
  77. value: `${designerInfo.value?.designFee}元/㎡`,
  78. show: designerInfo.value?.designFee,
  79. },
  80. ].filter(({ show }) => show),
  81. )
  82. const query = computed(() => ({
  83. circleType: tab.value,
  84. stylistId: id.value,
  85. }))
  86. const handleMomentDelete = async (id) => {
  87. confirm({
  88. title: '警告',
  89. msg: '确定要删除吗?',
  90. beforeConfirm: async ({ resolve }) => {
  91. await requestToast(() => deleteCircle(id))
  92. await pageHelperRef.value?.refresh()
  93. resolve(true)
  94. },
  95. })
  96. }
  97. const handleLike = async (options) => {
  98. await handleUpvoteClick({
  99. ...options,
  100. userId: userInfo.value.userId,
  101. userName: userInfo.value.nickname,
  102. })
  103. await pageHelperRef.value?.refresh()
  104. }
  105. const handle2Video = () => {
  106. try {
  107. uni.openChannelsUserProfile({ finderUserName: designerInfo.value?.videoNumber })
  108. } catch (e) {
  109. uni.showToast({
  110. title: '打开失败',
  111. icon: 'none',
  112. })
  113. }
  114. }
  115. const handleUnbundle = async () => {
  116. confirm({
  117. title: '警告',
  118. msg: '确定要解绑吗?',
  119. beforeConfirm: async ({ resolve }) => {
  120. await requestToast(
  121. () =>
  122. updateDesignerInfo({
  123. id: designerInfo.value.id,
  124. userId: designerInfo.value.userId,
  125. videoNumber: '',
  126. }),
  127. { success: true, successTitle: '解绑成功' },
  128. )
  129. await setDesignerInfo()
  130. resolve(true)
  131. },
  132. })
  133. }
  134. onLoad(async (query: { id: string; isShared?: string }) => {
  135. if (query.id) {
  136. id.value = query.id
  137. } else {
  138. id.value = userInfo.value.userId
  139. // memberInfo.value =
  140. }
  141. if (query.isShared) {
  142. isShared.value = true
  143. }
  144. if (!isOwn.value) {
  145. viewStartAt.value = new Date()
  146. }
  147. await Promise.all([setMemberInfo(), setBadges()])
  148. })
  149. onShow(async () => {
  150. await setDesignerInfo()
  151. })
  152. onUnload(async () => {
  153. if (!isOwn.value) {
  154. viewDuration.value = dayjs().diff(viewStartAt.value, 'seconds')
  155. const { data, code } = await createBrowseHistory({
  156. stylistId: id.value,
  157. bizType: 3,
  158. // bizId: '1',
  159. duration: viewDuration.value.toString(),
  160. })
  161. }
  162. })
  163. onShareAppMessage(shareAppMessage)
  164. onShareTimeline(() => ({}))
  165. defineExpose({
  166. navBarFixed: false,
  167. })
  168. </script>
  169. <template>
  170. <div class="flex-grow flex flex-col">
  171. <NavbarEvo transparent dark :isShowBack="!isShared"></NavbarEvo>
  172. <div class="relative">
  173. <!-- <wd-img width="100%" custom-class="aspect-[1.14/1]" /> -->
  174. <div class="aspect-[1.14/1]">
  175. <ImageEvo
  176. :src="designerInfo?.homePageUrl || NetImages.DesignerHomepageDefaultBg"
  177. mode="aspectFill"
  178. ></ImageEvo>
  179. </div>
  180. <div class="absolute bottom-0 left-0 right-0">
  181. <div class="bg-gradient-to-t from-black to-transparent">
  182. <div class="flex min-h-27 px-3.5 gap-3.5">
  183. <div class="w-18 h-18 border-white border border-solid rounded-full overflow-hidden">
  184. <wd-img
  185. width="100%"
  186. height="100%"
  187. :src="memberInfo?.avatar || NetImages.DefaultAvatar"
  188. ></wd-img>
  189. </div>
  190. <div class="pb-8 flex-1 overflow-hidden">
  191. <div class="flex items-center justify-between">
  192. <div class="text-white text-2xl font-normal font-['PingFang_SC'] leading-normal">
  193. {{ designerInfo.homePageName || memberInfo.nickname }}
  194. </div>
  195. <div
  196. v-if="isOwn && features.personalCode"
  197. class="flex items-center"
  198. @click="router.push(`/pages/mine/homepage/qr-code/index`)"
  199. >
  200. <wd-img width="22" height="22" :src="qrCode"></wd-img>
  201. <wd-icon name="chevron-right" color="white" size="16"></wd-icon>
  202. </div>
  203. </div>
  204. <div
  205. class="mt-2.5 flex gap-4 overflow-x-auto whitespace-nowrap"
  206. v-if="designerInfo?.personalIdentity != ''"
  207. >
  208. <template v-for="(it, i) in designerInfo?.personalIdentity?.split('、')" :key="i">
  209. <div
  210. class="inline-block h-6 px-2 bg-black/10 rounded-[30px] border border-solid border-white/60 justify-center items-center box-border inline-flex"
  211. >
  212. <div
  213. class="text-center text-white text-[10px] font-normal font-['PingFang_SC']"
  214. >
  215. {{ it }}
  216. </div>
  217. </div>
  218. </template>
  219. </div>
  220. </div>
  221. </div>
  222. </div>
  223. </div>
  224. </div>
  225. <div class="flex-grow flex flex-col bg-white rounded-t-2xl relative bottom-4 gap-5 px-3.5 pt-5">
  226. <div class="flex gap-4" v-if="skills?.length">
  227. <template v-for="(it, i) in skills" :key="i">
  228. <div>
  229. <span
  230. class="mr-0.575 text-black/90 text-base font-normal font-['PingFang_SC'] leading-[26.98px]"
  231. >
  232. {{ it.value }}
  233. </span>
  234. <span
  235. class="text-center text-black/40 text-sm font-normal font-['PingFang_SC'] leading-[26.98px]"
  236. >
  237. {{ it.label }}
  238. </span>
  239. </div>
  240. <div v-if="i < skills?.length - 1" class="leading-[26.98px] text-black/60">|</div>
  241. </template>
  242. </div>
  243. <div class="text-black/80 text-sm font-normal font-['PingFang_SC'] leading-normal">
  244. {{ designerInfo?.designDesc }}
  245. </div>
  246. <div v-if="badges?.length" class="h-[42px] relative mr--3.5">
  247. <div
  248. class="h-full left-0 pl-20 pr-6 right-20 top-0 bottom-0 absolute bg-gradient-to-r from-[#ffe9e9] via-[#fff7f7] to-[#fff8f8] rounded-tl-md rounded-bl-md flex flex-col justify-center"
  249. >
  250. <div class="">
  251. <div class="flex items-center gap-4">
  252. <template v-for="(it, i) in badges?.slice(0, badges?.length > 5 ? 3 : 4)" :key="i">
  253. <!-- <div class="bg-[#fa9d3b]"> -->
  254. <wd-img width="26" mode="widthFix" :src="it.badgeYesObtainedImage"></wd-img>
  255. <!-- </div> -->
  256. </template>
  257. <div v-if="badges?.length > 5" class="flex">
  258. <wd-img custom-class="m-a" width="26" mode="widthFix" :src="more"></wd-img>
  259. </div>
  260. </div>
  261. </div>
  262. </div>
  263. <div
  264. class="w-[61px] h-2 left-[14px] top-[23px] absolute bg-gradient-to-r from-[#ff9e91] via-[#ffe5d7] to-[#ffe4d6] rounded-tl-[20px] rounded-bl-[20px]"
  265. ></div>
  266. <div
  267. class="w-12 h-[19px] left-[14px] top-[9px] absolute text-center text-black text-xs font-normal font-['PingFang_SC'] leading-normal"
  268. >
  269. 荣誉徽章
  270. </div>
  271. <div
  272. class="w-[76px] h-[30px] px-3 right-0 top-1.5 absolute bg-black/90 rounded-tl-[30px] rounded-bl-[30px] justify-center items-center gap-2.5 inline-flex"
  273. @click="
  274. router.push(`/pages/mine/honors/index?id=${id}${isShared ? '&isShared=true' : ''}`)
  275. "
  276. >
  277. <div class="text-center text-white text-xs font-normal font-['PingFang_SC']">
  278. 查看荣誉
  279. </div>
  280. </div>
  281. </div>
  282. <div
  283. v-if="isOwn || designerInfo?.videoNumber"
  284. class="bg-gradient-to-t from-[#fdf6ee] to-[#fefdfc] rounded-[10px] border border-[#fff4e6] border-solid flex items-center px-3.5 py-5 gap-3"
  285. @click="handle2Video"
  286. >
  287. <div>
  288. <div class="w-[37.01px] h-[37.01px] bg-[#fa9d3b] rounded-lg">
  289. <wd-img width="100%" height="100%" :src="wechatChannels"></wd-img>
  290. </div>
  291. <div
  292. v-if="isOwn && (designerInfo?.videoNumber ?? '') !== ''"
  293. @click.stop="handleUnbundle"
  294. >
  295. <div
  296. class="text-[#da7e1e] text-[9px] font-normal font-['PingFang_SC'] leading-normal flex items-center"
  297. >
  298. 解绑
  299. <wd-icon name="arrow-right" size="12"></wd-icon>
  300. </div>
  301. </div>
  302. </div>
  303. <div class="flex-1">
  304. <div class="text-black/90 text-sm font-normal font-['PingFang_SC'] leading-normal">
  305. {{ designerInfo?.videoNumber ? '个人视频号' : '视频号' }}
  306. </div>
  307. <div class="text-black/40 text-xs font-normal font-['PingFang_SC'] leading-normal">
  308. {{
  309. designerInfo?.videoNumber
  310. ? '案例分享,打造专属生活美学空间'
  311. : '您还没有关联视频号 , 快去关联吧~'
  312. }}
  313. </div>
  314. </div>
  315. <div>
  316. <div
  317. v-if="designerInfo?.videoNumber"
  318. class="text-[#e08e38] text-xs font-normal font-['PingFang_SC'] leading-normal"
  319. >
  320. 去看看
  321. </div>
  322. <div
  323. v-else
  324. class="h-7 px-4 py-0.5 bg-[#fa9d3b] rounded-[20px] justify-center items-center gap-2.5 inline-flex"
  325. @click.stop="router.push('/pages/mine/homepage/channels/index')"
  326. >
  327. <div
  328. class="text-center text-white text-xs font-normal font-['PingFang_SC'] leading-normal"
  329. >
  330. 去关联
  331. </div>
  332. </div>
  333. </div>
  334. </div>
  335. <div>
  336. <wd-tabs v-model="tab" custom-class="bg-transparent!">
  337. <template v-for="({ label, value }, index) in tabs" :key="index">
  338. <wd-tab :title="label" :name="value"></wd-tab>
  339. </template>
  340. </wd-tabs>
  341. <PageHelper
  342. ref="pageHelperRef"
  343. class="flex-grow flex flex-col bg-[#f6f6f6] mx--3.5"
  344. custom-class=""
  345. :request="getCircles"
  346. :query="query"
  347. >
  348. <template #default="{ source }">
  349. <div class="p-3.5 flex flex-col bg-[#f6f6f6] gap-3.5">
  350. <template v-for="it of source.list" :key="it.id">
  351. <view class="">
  352. <MomentItem
  353. :options="it"
  354. :is-own="userInfo.userId === it.stylistId"
  355. :is-shared="isShared"
  356. @delete="handleMomentDelete"
  357. @like="handleLike"
  358. ></MomentItem>
  359. </view>
  360. </template>
  361. </div>
  362. </template>
  363. </PageHelper>
  364. </div>
  365. </div>
  366. <BottomAppBar fixed placeholder>
  367. <div class="flex gap-7.5">
  368. <div class="flex-1" v-if="isOwn && !isShared">
  369. <wd-button block :round="false" @click="router.push(`/pages/mine/homepage/edit/index`)">
  370. 编辑
  371. </wd-button>
  372. </div>
  373. <div class="flex-1" v-if="isOwn && !isShared">
  374. <button
  375. v-if="features.shareMoment"
  376. class="p-0 after:b-none"
  377. block
  378. :round="false"
  379. :open-type="features.shareMoment ? 'share' : ''"
  380. :data-type="'homepage'"
  381. :data-share-content="{
  382. title: `${userInfo.nickname}: “${designerInfo.designDesc}”`,
  383. imageUrl: designerInfo.sharePageUrl,
  384. path: `/pages/mine/homepage/index?id=${id}&isShared=true`,
  385. }"
  386. :data-options="{
  387. homepageId: id,
  388. userId: userInfo.userId,
  389. }"
  390. >
  391. <wd-button block :round="false">分享</wd-button>
  392. </button>
  393. <template v-else>
  394. <!-- 1-->
  395. <wd-button block :round="false" @click="clickByPermission('share', () => {})">
  396. 分享
  397. </wd-button>
  398. </template>
  399. </div>
  400. <div class="flex-1" v-if="!isOwn || isShared">
  401. <wd-button
  402. block
  403. :round="false"
  404. @click="router.push(`/pages/mine/homepage/consult/index?id=${id}`)"
  405. >
  406. 预约咨询
  407. </wd-button>
  408. </div>
  409. </div>
  410. </BottomAppBar>
  411. </div>
  412. </template>