index.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. <route lang="json">
  2. {
  3. "style": {
  4. "navigationBarTitleText": "我的荣誉",
  5. "navigationStyle": "custom"
  6. }
  7. }
  8. </route>
  9. <script setup lang="ts">
  10. import NavbarEvo from '@/components/navbar-evo.vue'
  11. import { getBanners, getDesignerInfo } from '../../../../core/libs/requests'
  12. import { Badge, BannerMode } from '../../../../core/libs/models'
  13. import { NetImages } from '../../../../core/libs/net-images'
  14. import ImageEvo from '@/components/image-evo.vue'
  15. import dayjs from 'dayjs'
  16. import { storeToRefs } from 'pinia'
  17. import { useUserStore } from '@/store'
  18. import { Canvas } from '@/core/utils/canvas'
  19. const show = ref<boolean>(false)
  20. const id = ref<string | number>('')
  21. const canvasHidden = ref<boolean>(false)
  22. const posterUrl = ref<string | any>()
  23. const userStore = useUserStore()
  24. const { userInfo } = storeToRefs(userStore)
  25. const badgePath = ref<any[]>([
  26. { badgeType: '游学徽章', path: '/pages-sub/home/study-tour/index' },
  27. { badgeType: '课程徽章', path: '/pages-sub/home/offline-activity/index' },
  28. { badgeType: '传播徽章', path: `/pages-sub/mine/homepage/index?id=${id.value}` },
  29. { badgeType: '积分徽章', path: '/pages-sub/home/mall/index' },
  30. { badgeType: '打卡徽章', path: '/pages/mine/index' },
  31. { badgeType: '活动徽章', path: '/pages-sub/home/offline-activity/index' },
  32. ])
  33. const panels = ref([
  34. {
  35. iconUrl: NetImages.savePosterIcon,
  36. title: '保存到本地',
  37. index: 0,
  38. },
  39. ])
  40. const { data: designer, run: setData } = useRequest(() => getDesignerInfo(id.value))
  41. /**
  42. * 生成分享海拔
  43. * */
  44. const createPoster = () => {
  45. return new Promise((resolve, reject) => {
  46. ;(async () => {
  47. uni.showLoading({ title: '生成中' })
  48. const [bgPath, badgePath, avatarPath, icon1, icon2] = await Promise.all(
  49. [
  50. NetImages.TopSpotlight,
  51. data.value.quantity ? data.value.activedImage : data.value.image,
  52. designer.value.headImgUrl || userInfo.value?.avatar || NetImages.DefaultAvatar,
  53. NetImages.Logo,
  54. designer.value?.homeUrl,
  55. ].map((it) => it && uni.getImageInfo({ src: it }).then(({ path }) => path)),
  56. )
  57. const ctx = uni.createCanvasContext('firstCanvas')
  58. ctx.fillStyle = '#141823'
  59. ctx.fillRect(0, 0, 345, 500)
  60. uni
  61. .createSelectorQuery()
  62. .select('#firstCanvas')
  63. .fields({ size: true }, async ({ width: w, height: h }: any) => {
  64. const canvas = new Canvas(ctx, { width: w, height: h }, { width: 345 })
  65. canvas.FillImage(bgPath)
  66. // ctx.drawImage(path, 0, 0, 16, 18)
  67. canvas.CircleImage(avatarPath, 17, 20, 20)
  68. canvas.Image(badgePath, 93, 74, 170, 180)
  69. canvas.FillText(userInfo.value?.nickname, '#ffffff', 16, 63, 46)
  70. const textNameWidth = ctx.measureText(data.value.name).width
  71. const textDesWidth = ctx.measureText(subTitle.value).width
  72. console.log(textDesWidth)
  73. canvas.FillText(data.value?.name, '#ffffff', 18, (345 - textNameWidth) / 2, 291)
  74. canvas.FillText(subTitle.value, '#ffffff', 14, (365 - textDesWidth) / 2, 326)
  75. // canvas.Image(icon1, 17, 415, 28, 28)
  76. // canvas.FillText('筑巢荟', '#ffffff', 18, 63, 436)
  77. canvas.Image(icon2, 250, 393, 80, 80)
  78. ctx.draw(true, () => {
  79. uni.canvasToTempFilePath({
  80. canvasId: 'firstCanvas',
  81. width: 345,
  82. height: 500,
  83. success: (res) => {
  84. // console.log('生成海报', res)
  85. uni.hideLoading()
  86. resolve(res.tempFilePath)
  87. },
  88. fail: (err) => {
  89. uni.hideLoading()
  90. reject(err)
  91. },
  92. })
  93. })
  94. })
  95. .exec()
  96. })()
  97. })
  98. }
  99. const save = async () => {
  100. await uni.saveImageToPhotosAlbum({ filePath: posterUrl.value })
  101. uni.showToast({ title: '已保存相册', icon: 'none' })
  102. }
  103. const cancelSaveImage = () => {
  104. uni.showToast({ title: '取消保存', icon: 'none' })
  105. show.value = false
  106. }
  107. const data = ref<{
  108. name: string
  109. userId: number
  110. quantity: number
  111. // 已获取的图片和未获取的图片
  112. image: string
  113. activedImage: string
  114. badgeType?: string
  115. badgeDescription?: string
  116. createTime?: number | Date | string
  117. }>({
  118. name: '',
  119. quantity: 0,
  120. image: '',
  121. activedImage: '',
  122. createTime: '',
  123. })
  124. const image = computed(() => (data.value.quantity ? data.value.activedImage : data.value.image))
  125. const subTitle = computed(() =>
  126. data.value?.quantity > 1
  127. ? `${dayjs(data.value.createTime).format('YYYY年MM月DD日')} 首次获得`
  128. : `${dayjs(data.value.createTime).format('YYYY年MM月DD日')}获得`,
  129. )
  130. const setBadgesPath = (type: string) => {
  131. if (type === '游学徽章' || type === '典藏勋章') {
  132. if (banners.value?.length > 0) {
  133. // url: `/pages-sub/home/study-tour/list?designStudyAbroadYear=${banners.value[0].designStudyAbroadYear}&designDesc=${banners.value[0].designDesc}`,
  134. uni.redirectTo({
  135. url: '/pages-sub/home/study-tour/index',
  136. })
  137. } else {
  138. uni.showToast({
  139. title: '暂无游学计划',
  140. icon: 'none',
  141. })
  142. }
  143. } else {
  144. if (type === '打卡徽章') {
  145. uni.switchTab({
  146. url: badgePath.value.find((it) => it.badgeType === type)?.path,
  147. })
  148. } else {
  149. uni.redirectTo({
  150. url: badgePath.value.find((it) => it.badgeType === type)?.path,
  151. })
  152. }
  153. }
  154. }
  155. const showActions = () => {
  156. show.value = true
  157. }
  158. const close = () => {
  159. show.value = false
  160. }
  161. const select = (item: any) => {
  162. console.log(item, '分享选择')
  163. let { index } = item
  164. if (index === 0) {
  165. save()
  166. }
  167. }
  168. const { data: banners, run: setBanners } = useRequest(
  169. () => getBanners({ mode: BannerMode.StudyTour }),
  170. { initialData: [] },
  171. )
  172. onLoad(async (query: { type: 'badge' | 'certificate'; data: string; id: number }) => {
  173. console.log(query, 'query2')
  174. if (query.type === 'badge') {
  175. id.value = query?.id
  176. const badge = JSON.parse(query.data) as Badge
  177. data.value = {
  178. name: badge.badgeName,
  179. userId: query.id,
  180. quantity: badge.quantity,
  181. image: badge.badgeNotObtainedImage,
  182. activedImage: badge.badgeYesObtainedImage,
  183. badgeType: badge.badgeType,
  184. badgeDescription: badge.badgeDescription,
  185. createTime: badge.createTime,
  186. }
  187. badgePath.value = badgePath.value.map((m) => {
  188. if (m.badgeType === '传播徽章') {
  189. m.path = `/pages-sub/mine/homepage/index?id=${id.value}`
  190. }
  191. return { ...m }
  192. })
  193. console.log('=======================', data)
  194. await setBanners()
  195. if (data.value.quantity > 0) {
  196. await setData()
  197. if (data.value.userId == userInfo.value.userId) {
  198. nextTick(async () => {
  199. posterUrl.value = await createPoster()
  200. })
  201. }
  202. }
  203. }
  204. })
  205. </script>
  206. <template>
  207. <div
  208. class="flex-grow bg-[#100f18] backdrop-blur-2xl flex flex-col items-center justify-between py-32 gap-4 relative"
  209. >
  210. <div class="absolute top-0 left-0 right-0">
  211. <wd-img
  212. width="100%"
  213. mode="widthFix"
  214. custom-clas="vertical-bottom"
  215. :src="NetImages.TopSpotlight"
  216. ></wd-img>
  217. </div>
  218. <div class="absolute top-20 left-0 right-0 flex flex-col items-center">
  219. <wd-img
  220. width="40%"
  221. mode="widthFix"
  222. custom-clas="vertical-bottom mx-auto"
  223. :src="NetImages.Stars"
  224. ></wd-img>
  225. </div>
  226. <NavbarEvo fixed dark transparent></NavbarEvo>
  227. <wd-img width="42%" mode="widthFix" :src="image"></wd-img>
  228. <div class="flex flex-col items-center gap-1">
  229. <div class="text-white text-[26px] font-normal font-['PingFang_SC'] uppercase">
  230. <!-- 东方研习营 -->
  231. {{ data.name }}
  232. </div>
  233. <div class="text-center text-white text-sm font-normal font-['PingFang_SC'] uppercase">
  234. <!-- 参加东方艺术设计研学营 -->
  235. {{ `${data.badgeDescription}` }}
  236. </div>
  237. <div class="mt-6 flex items-center gap-4">
  238. <div class="w-4 h-0.25 bg-white"></div>
  239. <div
  240. class="text-center text-white text-sm font-normal font-['PingFang_SC'] uppercase"
  241. v-if="data.quantity > 0"
  242. >
  243. {{ dayjs(data.createTime).format('YYYY年MM月DD日')
  244. }}{{ data.quantity > 1 ? '首次' : '' }}获得
  245. </div>
  246. <div
  247. class="text-center text-white text-sm font-normal font-['PingFang_SC'] uppercase"
  248. v-else
  249. >
  250. 暂未获得该徽章
  251. </div>
  252. <div class="w-4 h-0.25 bg-white"></div>
  253. </div>
  254. <div
  255. v-if="data.quantity > 1"
  256. class="text-center text-white text-sm font-normal font-['PingFang_SC'] uppercase"
  257. >
  258. 累计参与{{ data.quantity }}次
  259. </div>
  260. </div>
  261. <template v-if="data.userId == userInfo.userId">
  262. <wd-button
  263. custom-class="w-[161px] h-12 bg-[#0cbe7c]! rounded-[30px]"
  264. @click="showActions"
  265. v-if="data.quantity > 0"
  266. >
  267. 去分享
  268. </wd-button>
  269. <wd-button
  270. custom-class="w-[161px] h-12 bg-[#0cbe7c]! rounded-[30px]"
  271. @click="setBadgesPath(data.badgeType)"
  272. v-else
  273. >
  274. 去获得
  275. </wd-button>
  276. </template>
  277. <canvas
  278. class="w-[347px] h-[494px] absolute rounded-[20px] top--1000"
  279. canvas-id="firstCanvas"
  280. id="firstCanvas"
  281. ></canvas>
  282. <wd-popup
  283. v-model="show"
  284. custom-class="!bg-[rgba(255,255,255,0)] !backdrop-blur-2xl"
  285. position="bottom"
  286. :safe-area-inset-bottom="true"
  287. custom-style="height: 690px;"
  288. @close="close"
  289. >
  290. <div class="rounded-[20px] mx-[14px]">
  291. <div class="flex h-[500px]">
  292. <ImageEvo
  293. class="w-full h-full rounded-[20px] overflow-hidden"
  294. :src="posterUrl"
  295. @displayed="canvasHidden = true"
  296. ></ImageEvo>
  297. </div>
  298. </div>
  299. <div class="bg-[#ffffff] rounded-[20px] mx-[14px] mt-[20px] py-4 px-6">
  300. <div class="flex items-center gap-4">
  301. <div
  302. class="text-white text-[26px] font-normal font-['PingFang_SC'] uppercase text-center"
  303. v-for="(item, index) in panels"
  304. :key="index"
  305. @click="select(item)"
  306. >
  307. <!-- 分享到 -->
  308. <wd-img :src="item.iconUrl" width="40px" mode="widthFix"></wd-img>
  309. <div class="text-center text-black text-sm font-normal font-['PingFang_SC'] uppercase">
  310. {{ item.title }}
  311. </div>
  312. </div>
  313. </div>
  314. <div
  315. @click="cancelSaveImage"
  316. class="bg-[rgb(240,240,240)] line-height-[40px] h-[40px] text-center rounded-[50px] mx-[4px] mt-[20px] text-black font-normal font-['PingFang_SC']"
  317. >
  318. 取消
  319. </div>
  320. </div>
  321. </wd-popup>
  322. </div>
  323. </template>