Browse Source

feat(invite): 重构邀请设计师页面

- 添加自定义导航栏和底部应用栏组件
- 优化海报生成逻辑,使用 Canvas工具类简化绘制过程
- 新增 ImageEvo 组件用于海报显示和懒加载
- 调整页面布局和样式,提升用户体验
EvilDragon 5 months ago
parent
commit
fa62b517d9

+ 23 - 0
packages/app/src/components/image-evo.vue

@@ -0,0 +1,23 @@
+<script setup lang="ts">
+withDefaults(defineProps<{ src?: string }>(), {})
+const emits = defineEmits<{ displayed: [] }>()
+const visible = ref(false)
+const handleLoad = async () => {
+  visible.value = true
+  nextTick(() => {
+    emits('displayed')
+  })
+}
+</script>
+<template>
+  <!-- <div> -->
+  <wd-img
+    class="w-full h-full"
+    width="100%"
+    height="100%"
+    :style="{ visibility: visible ? 'visible' : 'hidden' }"
+    :src="src"
+    @load="handleLoad"
+  ></wd-img>
+  <!-- </div> -->
+</template>

+ 67 - 0
packages/app/src/core/utils/canvas.ts

@@ -0,0 +1,67 @@
+export const CircleImage = (ctx, img, x, y, r) => {
+  ctx.save()
+  ctx.beginPath()
+  ctx.arc(x + r, y + r, r, 0, 2 * Math.PI)
+  ctx.clip()
+  ctx.drawImage(img, x, y, 2 * r, 2 * r)
+  ctx.restore()
+}
+export class Canvas {
+  context: UniApp.CanvasContext
+  size: { width: number; height: number }
+  designSize: { width?: number; height?: number }
+  constructor(
+    ctx: UniApp.CanvasContext,
+    size: { width: number; height: number },
+    designSize: { width?: number; height?: number },
+  ) {
+    this.context = ctx
+    this.size = size
+    this.designSize = designSize
+  }
+
+  px(designPx) {
+    return (this.size.width / this.designSize.width) * designPx
+  }
+
+  CircleImage(img, designX, designY, designR) {
+    this.context.save()
+    this.context.beginPath()
+    this.context.arc(
+      this.px(designX) + this.px(designR),
+      this.px(designY) + this.px(designR),
+      this.px(designR),
+      0,
+      2 * Math.PI,
+    )
+    this.context.clip()
+    this.context.drawImage(
+      img,
+      this.px(designX),
+      this.px(designY),
+      2 * this.px(designR),
+      2 * this.px(designR),
+    )
+    this.context.restore()
+  }
+
+  Image(img, designX, designY, designW, designH) {
+    this.context.drawImage(
+      img,
+      this.px(designX),
+      this.px(designY),
+      this.px(designW),
+      this.px(designH),
+    )
+  }
+
+  FillImage(img) {
+    this.context.drawImage(img, 0, 0, this.size.width, this.size.height)
+  }
+
+  FillText(text, color, designFontSize, designX, designY) {
+    this.context.setFillStyle(color)
+    this.context.setFontSize(this.px(designFontSize))
+    this.context.fillText(text, this.px(designX), this.px(designY))
+  }
+}

+ 2 - 1
packages/app/src/pages.json

@@ -316,7 +316,8 @@
       "type": "page",
       "style": {
         "navigationBarTitleText": "邀请设计师",
-        "navigationBarBackgroundColor": "#fff"
+        "navigationBarBackgroundColor": "#fff",
+        "navigationStyle": "custom"
       }
     },
     {

+ 56 - 69
packages/app/src/pages/mine/invite/index.vue

@@ -1,5 +1,11 @@
 <route lang="json">
-{ "style": { "navigationBarTitleText": "邀请设计师", "navigationBarBackgroundColor": "#fff" } }
+{
+  "style": {
+    "navigationBarTitleText": "邀请设计师",
+    "navigationBarBackgroundColor": "#fff",
+    "navigationStyle": "custom"
+  }
+}
 </route>
 <script setup lang="ts">
 import SectionHeading from '@/components/section-heading.vue'
@@ -10,6 +16,9 @@ import { pick } from 'radash'
 import BottomAppBar from '@/components/bottom-app-bar.vue'
 import { useMessage } from 'wot-design-uni'
 import { NetImages } from '../../../core/libs/net-images'
+import NavbarEvo from '@/components/navbar-evo.vue'
+import ImageEvo from '@/components/image-evo.vue'
+import { Canvas, CircleImage } from '@/core/utils/canvas'
 
 const { alert } = useMessage()
 const userStore = useUserStore()
@@ -21,38 +30,23 @@ const form = ref<{
 const { data, run: setData } = useRequest(() => getDesignerInfo(userInfo.value.userId))
 const { loading, run: submiting } = useRequest(() => updateDesignerInfo(form.value))
 const posterUrl = ref()
-const handleSubmit = async () => {
-  await submiting()
-  await setData()
-  alert({ title: '关联成功', confirmButtonText: '我知道了' })
-}
+const canvasHidden = ref(false)
 const createPoster = () => {
   return new Promise((resolve, reject) => {
     ;(async () => {
-      const { path } = await uni.getImageInfo({ src: data.value.homePageUrl })
-      // console.log(path)
-      // console.log(logoPath)
-      const { path: bgPath } = await uni.getImageInfo({ src: NetImages.InviteBg })
-      // console.log(bgPath)
-      const { path: logoPath } = await uni.getImageInfo({ src: NetImages.Logo })
-      const { path: avatarPath } = await uni.getImageInfo({
-        src: data.value.headImgUrl || userInfo.value?.avatar || NetImages.DefaultAvatar,
-      })
-      const [{ path: icon1 }, { path: icon2 }, { path: icon3 }, { path: icon4 }] =
-        await Promise.all([
-          uni.getImageInfo({
-            src: 'https://image.zhuchaohui.com/zhucaohui/315c5ca680bc6b47a5344dbff701cf1e6802d8a240754e25ef3d4308bc1deef6.png',
-          }),
-          uni.getImageInfo({
-            src: 'https://image.zhuchaohui.com/zhucaohui/ee83d30d553feb1482920e49c28824d73bb33838d996ec2f6c646ae6deab0330.png',
-          }),
-          uni.getImageInfo({
-            src: 'https://image.zhuchaohui.com/zhucaohui/9f89604f285c33200b7e6fda9acc82e130f34c2f5687cfeb78f3541e1fabcc28.png',
-          }),
-          uni.getImageInfo({
-            src: 'https://image.zhuchaohui.com/zhucaohui/2467be55426018856150bd94bbd21865df3d73ce982bc1c82b7585cf26c822f0.png',
-          }),
-        ])
+      uni.showLoading({ title: '生成中' })
+      const [path, bgPath, logoPath, avatarPath, icon1, icon2, icon3, icon4] = await Promise.all(
+        [
+          data.value.homePageUrl,
+          NetImages.InviteBg,
+          NetImages.Logo,
+          data.value.headImgUrl || userInfo.value?.avatar || NetImages.DefaultAvatar,
+          'https://image.zhuchaohui.com/zhucaohui/315c5ca680bc6b47a5344dbff701cf1e6802d8a240754e25ef3d4308bc1deef6.png',
+          'https://image.zhuchaohui.com/zhucaohui/ee83d30d553feb1482920e49c28824d73bb33838d996ec2f6c646ae6deab0330.png',
+          'https://image.zhuchaohui.com/zhucaohui/9f89604f285c33200b7e6fda9acc82e130f34c2f5687cfeb78f3541e1fabcc28.png',
+          'https://image.zhuchaohui.com/zhucaohui/2467be55426018856150bd94bbd21865df3d73ce982bc1c82b7585cf26c822f0.png',
+        ].map((it) => uni.getImageInfo({ src: it }).then(({ path }) => path)),
+      )
       const ctx = uni.createCanvasContext('firstCanvas')
       const getPx = (width, designPx) => {
         return (width / 319) * designPx
@@ -61,41 +55,22 @@ const createPoster = () => {
         .createSelectorQuery()
         .select('#firstCanvas')
         .fields({ size: true }, async ({ width: w, height: h }: any) => {
-          console.log(w, h)
-
-          ctx.drawImage(bgPath, 0, 0, w, h)
+          const canvas = new Canvas(ctx, { width: w, height: h }, { width: 319 })
+          canvas.FillImage(bgPath)
           ctx.drawImage(path, 0, 0, w, w / 1.56)
-          ctx.drawImage(avatarPath, getPx(w, 17), getPx(w, 21), getPx(w, 28), getPx(w, 28))
-          ctx.drawImage(logoPath, getPx(w, 17), getPx(w, 230), getPx(w, 24), getPx(w, 24))
-          // 绘制昵称
-          ctx.setFillStyle('#ffffff')
-          ctx.setFontSize(getPx(w, 14))
-          ctx.fillText(userInfo.value?.nickname, getPx(w, 53), getPx(w, 40))
-          // 绘制文字
-          ctx.setFillStyle('#ffffff')
-          ctx.setFontSize(getPx(w, 18))
-          ctx.fillText('筑巢荟—助力设计师成长平台', getPx(w, 53), getPx(w, 248))
-
-          ctx.setFillStyle('#ffffff')
-          ctx.setFontSize(getPx(w, 12))
-          ctx.fillText('国内外设计游学', getPx(w, 62), getPx(w, 303))
-
-          ctx.setFillStyle('#ffffff')
-          ctx.setFontSize(getPx(w, 12))
-          ctx.fillText('设计赋能项目', getPx(w, 207), getPx(w, 303))
-
-          ctx.setFillStyle('#ffffff')
-          ctx.setFontSize(getPx(w, 12))
-          ctx.fillText('线上获客工具', getPx(w, 62), getPx(w, 339))
-
-          ctx.setFillStyle('#ffffff')
-          ctx.setFontSize(getPx(w, 12))
-          ctx.fillText('丰富线下活动', getPx(w, 207), getPx(w, 339))
+          canvas.CircleImage(avatarPath, 17, 21, 14)
+          canvas.Image(logoPath, 17, 230, 24, 24)
+          canvas.FillText(userInfo.value?.nickname, '#ffffff', 14, 53, 40)
+          canvas.FillText('筑巢荟—助力设计师成长平台', '#ffffff', 18, 53, 248)
+          canvas.FillText('国内外设计游学', '#ffffff', 12, 62, 303)
+          canvas.FillText('设计赋能项目', '#ffffff', 12, 207, 303)
+          canvas.FillText('线上获客工具', '#ffffff', 12, 62, 339)
+          canvas.FillText('丰富线下活动', '#ffffff', 12, 207, 339)
 
-          ctx.drawImage(icon1, getPx(w, 41), getPx(w, 290), getPx(w, 18), getPx(w, 18))
-          ctx.drawImage(icon2, getPx(w, 189), getPx(w, 290), getPx(w, 18), getPx(w, 18))
-          ctx.drawImage(icon3, getPx(w, 41), getPx(w, 325), getPx(w, 18), getPx(w, 18))
-          ctx.drawImage(icon4, getPx(w, 189), getPx(w, 325), getPx(w, 18), getPx(w, 18))
+          canvas.Image(icon1, 41, 290, 18, 18)
+          canvas.Image(icon2, 189, 290, 18, 18)
+          canvas.Image(icon3, 41, 325, 18, 18)
+          canvas.Image(icon4, 189, 325, 18, 18)
 
           ctx.draw(true, () => {
             uni.canvasToTempFilePath({
@@ -104,6 +79,7 @@ const createPoster = () => {
               height: 460,
               success: (res) => {
                 // console.log('生成海报', res)
+                uni.hideLoading()
                 resolve(res.tempFilePath)
               },
               fail: (err) => {
@@ -150,13 +126,24 @@ onMounted(async () => {
     class="flex-grow flex flex-col justify-center gap-5 px-3.5 py-6 bg-black bg-[length:100%_100%]"
     :style="{ backgroundImage: `url(${NetImages.InviteBg})` }"
   >
+    <NavbarEvo fixed transparent dark></NavbarEvo>
     <div class="block aspect-[0.69/1]">
-      <div class="bg-white mx-7 aspect-[0.69/1] relative rounded-2xl overflow-hidden">
-        <canvas class="w-full h-full" canvas-id="firstCanvas" id="firstCanvas"></canvas>
-        <div class="absolute bottom-5.5 left-5.5">
-          <cover-view>
-            <wd-button custom-class="bg-white/10!" @click="save">保存到相册</wd-button>
-          </cover-view>
+      <div class="mx-7 aspect-[0.69/1] relative rounded-2xl overflow-hidden">
+        <canvas
+          class="w-full h-full absolute top--1000"
+          canvas-id="firstCanvas"
+          id="firstCanvas"
+        ></canvas>
+        <!-- <wd-img width="100%" height="100%" :src="posterUrl"></wd-img> -->
+        <ImageEvo
+          class="w-full h-full"
+          :src="posterUrl"
+          @displayed="canvasHidden = true"
+        ></ImageEvo>
+        <div class="absolute bottom-5.5 left-5.5" v-if="canvasHidden">
+          <!-- <cover-view> -->
+          <wd-button custom-class="bg-white/10!" @click="save">保存到相册</wd-button>
+          <!-- </cover-view> -->
           <!-- <wd-button @click="share">微信</wd-button>
         <wd-button @click="share">朋友圈</wd-button> -->
         </div>