index.vue 14 KB

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