index.vue 16 KB

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