index.vue 11 KB

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