Jake 3 months ago
parent
commit
5da6a81823
57 changed files with 10393 additions and 1837 deletions
  1. 2 2
      packages/app/env/.env.development
  2. 1 0
      packages/app/package.json
  3. 1 0
      packages/app/pages.config.ts
  4. 23 0
      packages/app/src/components/image-evo.vue
  5. 37 0
      packages/app/src/components/img-btn-evo.vue
  6. 12 0
      packages/app/src/components/input-number-evo.vue
  7. 21 9
      packages/app/src/components/moment-item.vue
  8. 36 1
      packages/app/src/components/navbar-evo.vue
  9. 27 2
      packages/app/src/components/page-helper.vue
  10. 16 0
      packages/app/src/components/progress-evo.vue
  11. 16 0
      packages/app/src/components/swiper-evo.vue
  12. 41 14
      packages/app/src/composables/permissions.ts
  13. 17 0
      packages/app/src/core/libs/actions.ts
  14. 1 0
      packages/app/src/core/libs/messages.ts
  15. 37 2
      packages/app/src/core/libs/models.ts
  16. 2 0
      packages/app/src/core/libs/net-images.ts
  17. 69 3
      packages/app/src/core/libs/requests.ts
  18. 6 0
      packages/app/src/core/themes/default.scss
  19. 2 0
      packages/app/src/core/themes/default.ts
  20. 67 0
      packages/app/src/core/utils/canvas.ts
  21. 3 0
      packages/app/src/core/utils/common.ts
  22. 7 2
      packages/app/src/core/utils/router.ts
  23. 13 0
      packages/app/src/pages.json
  24. 11 6
      packages/app/src/pages/home/activity/detail/index.vue
  25. 62 0
      packages/app/src/pages/home/components/home-banner.vue
  26. 6 3
      packages/app/src/pages/home/components/register-card.vue
  27. 39 18
      packages/app/src/pages/home/index.vue
  28. 3 2
      packages/app/src/pages/home/mall/components/product.vue
  29. 50 22
      packages/app/src/pages/home/mall/confirm-order/index.vue
  30. 3 6
      packages/app/src/pages/home/mall/detail/index.vue
  31. 135 12
      packages/app/src/pages/home/mall/index.vue
  32. 37 4
      packages/app/src/pages/home/mall/shopping-cart/index.vue
  33. 104 0
      packages/app/src/pages/home/study-tour/components/register-card.vue
  34. 5 2
      packages/app/src/pages/home/study-tour/components/study-tour-card.vue
  35. 44 13
      packages/app/src/pages/home/study-tour/index.vue
  36. 1 1
      packages/app/src/pages/home/study-tour/list.vue
  37. 99 11
      packages/app/src/pages/messages/index.vue
  38. 60 24
      packages/app/src/pages/mine/coupons/index.vue
  39. 36 2
      packages/app/src/pages/mine/homepage/index.vue
  40. 1 1
      packages/app/src/pages/mine/homepage/statistics/index.vue
  41. 18 6
      packages/app/src/pages/mine/index.vue
  42. 179 0
      packages/app/src/pages/mine/invite/index.vue
  43. 4 5
      packages/app/src/pages/mine/levels/index.vue
  44. 1 1
      packages/app/src/pages/mine/orders/index.vue
  45. 13 6
      packages/app/src/pages/mine/scan/result/index.vue
  46. 2 3
      packages/app/src/pages/mine/setting/index.vue
  47. 6 0
      packages/app/src/pages/publish/moment/index.vue
  48. 1 0
      packages/app/src/types/uni-pages.d.ts
  49. 1 1
      packages/app/src/utils/index.ts
  50. 11 0
      packages/assets/src/assets/grab-now.svg
  51. 3 0
      packages/assets/src/assets/lightning.svg
  52. 1 0
      packages/assets/src/assets/text-great-money.svg
  53. 2 0
      packages/assets/src/libs/assets/grabNow.ts
  54. 2 0
      packages/assets/src/libs/assets/imgBtnBlack-110x44.ts
  55. 2 0
      packages/assets/src/libs/assets/lightning.ts
  56. 2 0
      packages/assets/src/libs/assets/textGreatMoney.ts
  57. 8992 1653
      pnpm-lock.yaml

+ 2 - 2
packages/app/env/.env.development

@@ -5,14 +5,14 @@ VITE_DELETE_CONSOLE = false
 # 是否开启sourcemap
 VITE_SHOW_SOURCEMAP = true
 
-VITE_SERVER_BASEURL = 'https://www.zhuchaohui.com'
+# VITE_SERVER_BASEURL = 'https://www.zhuchaohui.com'
 # VITE_SERVER_BASEURL = 'http://39.106.91.179:48080'
 # VITE_SERVER_BASEURL = 'http://192.168.2.34:48080'
 # 王超
 # VITE_SERVER_BASEURL = 'http://192.168.2.39:48080'
 # VITE_SERVER_BASEURL = 'http://192.168.0.157:48080'
 # 刘岁成
-# VITE_SERVER_BASEURL = 'http://192.168.2.38:48080'
+VITE_SERVER_BASEURL = 'http://192.168.2.38:48080'
 # 赵要军
 # VITE_SERVER_BASEURL = 'http://192.168.2.41:48080'
 # 姚逊涛

+ 1 - 0
packages/app/package.json

@@ -111,6 +111,7 @@
     "radash": "^12.1.0",
     "sentry-uniapp": "^1.0.12",
     "uqrcodejs": "^4.0.7",
+    "validator": "^13.12.0",
     "vue": "^3.4.21",
     "vue-component-type-helpers": "^2.1.8",
     "vue-i18n": "^9.1.9",

+ 1 - 0
packages/app/pages.config.ts

@@ -61,6 +61,7 @@ export default defineUniPages({
       },
       { name: '商品兑换成功', path: '/pages/home/mall/purchased/success/index' },
       { name: '购物车', path: '/pages/home/mall/shopping-cart/index' },
+      { name: '支付成功', path: '/pages/mine/scan/pay/success/index' },
     ],
   },
 })

+ 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>

+ 37 - 0
packages/app/src/components/img-btn-evo.vue

@@ -0,0 +1,37 @@
+<script setup lang="ts">
+const props = withDefaults(defineProps<{ size?: 'large' | '110x44'; color?: 'black' }>(), {
+  size: 'large',
+  color: 'black',
+})
+const width = computed(() => Number(props.size.split('x')[0]) * 2 + 'rpx')
+const height = computed(() => Number(props.size.split('x')[1]) * 2 + 'rpx')
+const types = [
+  {
+    color: 'black',
+    size: '110x44',
+    src: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTA1IiBoZWlnaHQ9IjQ0IiB2aWV3Qm94PSIwIDAgMTA1IDQ0IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMTMuMzM1MSA1LjY4MzU2QzE0Ljk5NyAyLjIxMDQ4IDE4LjUwNTMgMCAyMi4zNTU1IDBIOTVDMTAwLjUyMyAwIDEwNSA0LjQ3NzE1IDEwNSAxMFYzNEMxMDUgMzkuNTIyOCAxMDAuNTIzIDQ0IDk1IDQ0SDkuMjg0QzIuNjYxNTYgNDQgLTEuNjkyOTMgMzcuMDg4OSAxLjE2NTYgMzEuMTE1MkwxMy4zMzUxIDUuNjgzNTZaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjg1Ii8+Cjwvc3ZnPgo=',
+  },
+  {
+    size: 'large',
+    color: 'black',
+    width: 110,
+    height: 44,
+    src: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTA1IiBoZWlnaHQ9IjQ0IiB2aWV3Qm94PSIwIDAgMTA1IDQ0IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMTMuMzM1MSA1LjY4MzU2QzE0Ljk5NyAyLjIxMDQ4IDE4LjUwNTMgMCAyMi4zNTU1IDBIOTVDMTAwLjUyMyAwIDEwNSA0LjQ3NzE1IDEwNSAxMFYzNEMxMDUgMzkuNTIyOCAxMDAuNTIzIDQ0IDk1IDQ0SDkuMjg0QzIuNjYxNTYgNDQgLTEuNjkyOTMgMzcuMDg4OSAxLjE2NTYgMzEuMTE1MkwxMy4zMzUxIDUuNjgzNTZaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjg1Ii8+Cjwvc3ZnPgo=',
+  },
+]
+const type = computed(() =>
+  types.find(({ size, color }) => props.size === size && props.color === color),
+)
+</script>
+<template>
+  <div
+    class="flex items-center justify-center bg-[length:100%_100%] text-white pl-4 pr-2 box-border text-white text-base font-normal font-['PingFang_SC'] leading-tight"
+    :style="{
+      width: type.width * 2 + 'rpx',
+      height: type.height * 2 + 'rpx',
+      backgroundImage: `url(${type.src})`,
+    }"
+  >
+    <slot></slot>
+  </div>
+</template>

+ 12 - 0
packages/app/src/components/input-number-evo.vue

@@ -0,0 +1,12 @@
+<script setup lang="ts">
+import { ConfigProviderThemeVars } from 'wot-design-uni'
+const modelValue = defineModel({ type: Number, default: 0 })
+const themeVars = computed<ConfigProviderThemeVars>(() => ({
+  //   progressHeight: typeof props.height === 'number' ? `${props.height}px` : props.height,
+}))
+</script>
+<template>
+  <wd-config-provider :themeVars="themeVars">
+    <wd-input-number class="" v-model="modelValue" />
+  </wd-config-provider>
+</template>

+ 21 - 9
packages/app/src/components/moment-item.vue

@@ -9,6 +9,7 @@ import { useRouter } from '../core/utils/router'
 import { likeActived, likeBlack } from '@designer-hub/assets/src/icons'
 import { NetImages } from '../core/libs/net-images'
 import { currRoute } from '../utils'
+import { usePermissions } from '../composables/permissions'
 
 const props = withDefaults(
   defineProps<{
@@ -39,8 +40,9 @@ const props = withDefaults(
   }>(),
   {},
 )
-const emits = defineEmits<{ delete: [id: number] }>()
+const emits = defineEmits<{ delete: [id: number]; like: [options: any] }>()
 const router = useRouter()
+const { features } = usePermissions()
 const imgClass = ref('')
 const isVideo = ref(false)
 const toDetail = () => {
@@ -52,9 +54,6 @@ const handleDelete = async () => {
   emits('delete', props.options.id)
 }
 onMounted(async () => {
-  // console.log('加载')
-  // console.log(currRoute().path)
-
   if (
     props.options.bannerUrls?.length === 1 &&
     isImageOrVideo(props.options.bannerUrls[0]) === 'image'
@@ -84,7 +83,8 @@ onMounted(async () => {
         <view
           class="overflow-hidden rounded-full mr-2"
           @click.stop="
-            currRoute().path !== '/pages/mine/homepage/index' &&
+            features.toDesignerHomePage &&
+              currRoute().path !== '/pages/mine/homepage/index' &&
               router.push(`/pages/mine/homepage/index?id=${options.stylistId}`)
           "
         >
@@ -139,10 +139,21 @@ onMounted(async () => {
         </template>
       </view>
       <view class="flex justify-between">
-        <view class="flex items-center text-[rgba(0,0,0,0.85)] text-3.5 font-400 line-height-5.5">
-          <wd-img width="15" height="15" src="/static/svgs/share.svg"></wd-img>
-          <view class="ml-1">{{ props.options.shareCount }}</view>
-        </view>
+        <div>
+          <button
+            open-type="share"
+            class="bg-transparent! p-0!"
+            :data-options="options"
+            @click.stop
+          >
+            <view
+              class="flex items-center text-[rgba(0,0,0,0.85)] text-3.5 font-400 line-height-5.5"
+            >
+              <wd-img width="15" height="15" src="/static/svgs/share.svg"></wd-img>
+              <view class="ml-1">{{ props.options.shareCount }}</view>
+            </view>
+          </button>
+        </div>
         <view class="flex items-center text-[rgba(0,0,0,0.85)] text-3.5 font-400 line-height-5.5">
           <wd-img width="15" height="15" src="/static/svgs/comment.svg"></wd-img>
           <view class="ml-1">{{ props.options.reviewCount }}</view>
@@ -150,6 +161,7 @@ onMounted(async () => {
         <view
           class="flex items-center text-3.5 font-400 line-height-5.5"
           :class="[options.ownUpvote ? 'text-[#ca5141]' : 'text-[rgba(0,0,0,0.85)]']"
+          @click.stop="emits('like', { upvote: options.ownUpvote, circleId: options.id })"
         >
           <template v-if="options.ownUpvote">
             <wd-img width="18" height="18" :src="likeActived"></wd-img>

+ 36 - 1
packages/app/src/components/navbar-evo.vue

@@ -10,9 +10,18 @@ const props = defineProps<{
   placeholder?: boolean
 }>()
 const router = useRouter()
+const pages = computed(() => getCurrentPages())
 const themeVars = computed<ConfigProviderThemeVars>(() => ({
   navbarColor: props.dark ? 'white' : 'black',
+  // buttonIconSize: '100rpx',
 }))
+const handleToHome = () => {
+  uni.reLaunch({ url: '/pages/home/index' })
+}
+onMounted(() => {
+  // getCurrentPages()
+  // console.log(getCurrentPages())
+})
 </script>
 <template>
   <div>
@@ -25,7 +34,33 @@ const themeVars = computed<ConfigProviderThemeVars>(() => ({
         :custom-class="`${transparent ? 'bg-transparent!' : ''} `"
         v-bind="{ title }"
         @click-left="router.back()"
-      ></wd-navbar>
+      >
+        <!-- {{ pages }} -->
+        <template #capsule>
+          <div class="w-full h-full">
+            <!-- <wd-button
+              type="icon"
+              icon="home"
+              :custom-style="`color: ${dark ? 'white' : 'black'}`"
+              @click="handleToHome"
+            ></wd-button> -->
+            <wd-icon
+              v-if="pages.length === 1"
+              name="home"
+              size="50rpx"
+              :color="dark ? 'white' : 'blacak'"
+              @click="handleToHome"
+            ></wd-icon>
+            <wd-icon
+              v-else
+              name="arrow-left"
+              size="48rpx"
+              :color="dark ? 'white' : 'blacak'"
+              @click="router.back()"
+            ></wd-icon>
+          </div>
+        </template>
+      </wd-navbar>
       <template v-if="props.placeholder">
         <wd-navbar
           safe-area-inset-top

+ 27 - 2
packages/app/src/components/page-helper.vue

@@ -26,7 +26,8 @@ const { data, run: setData } = useRequest(
   () => props.request({ pageNo: pageNo.value, pageSize: pageSize.value, ...props.query }),
   { immediate: false },
 )
-const dataList = ref([])
+const dataList = ref<S[]>([])
+const source = computed(() => ({ list: dataList.value }))
 const queryList = (no, size) => {
   console.log(no, size)
 
@@ -72,6 +73,8 @@ defineExpose({
       :default-page-size="10"
       :default-page-no="1"
       :use-page-scroll="true"
+      :auto-full-height="true"
+      custom-class="h-full"
       @query="queryList"
     >
       <template #top>
@@ -80,7 +83,29 @@ defineExpose({
       <template #empty>
         <wd-status-tip :image="NetImages.NotContent" tip="暂无内容"></wd-status-tip>
       </template>
-      <slot :source="{ list: dataList } as T" :data="dataList as unknown as Ref<S>[]"></slot>
+      <slot :source="{ list: dataList }"></slot>
     </z-paging>
   </div>
 </template>
+<style lang="scss">
+// .z-paging {
+// .z-paging-content,
+// .zp-view-super,
+// .zp-scroll-view-container,
+// .zp-scroll-view,
+// .uni-scroll-view,
+// .uni-scroll-view-content {
+//   display: flex;
+//   flex-direction: column;
+//   flex-grow: 1;
+// }
+// .zp-view-super {
+//   // flex: 1;
+//   flex-direction: column !important;
+// }
+// .zp-scroll-view-container {
+//   flex: 1;
+// }
+
+// }
+</style>

+ 16 - 0
packages/app/src/components/progress-evo.vue

@@ -0,0 +1,16 @@
+<script lang="ts" setup>
+import { ConfigProviderThemeVars } from 'wot-design-uni'
+const props = withDefaults(defineProps<{ height?: number | string; color?: string }>(), {
+  height: 4,
+  color: 'black',
+})
+const modelValue = defineModel({ type: Number, default: 30 })
+const themeVars = computed<ConfigProviderThemeVars>(() => ({
+  progressHeight: typeof props.height === 'number' ? `${props.height}px` : props.height,
+}))
+</script>
+<template>
+  <wd-config-provider :themeVars="themeVars">
+    <wd-progress :percentage="modelValue" hide-text :color="color"></wd-progress>
+  </wd-config-provider>
+</template>

+ 16 - 0
packages/app/src/components/swiper-evo.vue

@@ -0,0 +1,16 @@
+<script setup lang="ts" generic="T extends Object">
+withDefaults(defineProps<{ items?: T[] }>(), { items: () => [] })
+const modelValue = defineModel({ type: Number, default: 0 })
+const handleSwiperChange = async ({ detail: { current } }) => {
+  modelValue.value = current
+}
+</script>
+<template>
+  <swiper class="w-full h-full" :current="modelValue" @change="handleSwiperChange">
+    <template v-for="(it, i) in items" :key="i">
+      <swiper-item>
+        <div class="w-full h-full"><slot :item="it"></slot></div>
+      </swiper-item>
+    </template>
+  </swiper>
+</template>

+ 41 - 14
packages/app/src/composables/permissions.ts

@@ -3,35 +3,62 @@ import { storeToRefs } from 'pinia'
 
 export const usePermissions = () => {
   const userStore = useUserStore()
-  const { isLogined, isDesigner } = storeToRefs(userStore)
+  const { isLogined, isDesigner, userInfo } = storeToRefs(userStore)
   const routes = [
-    { path: '/pages/mine/homepage/index', meta: { canNotLogin: false } },
-    { path: '/pages/material/mini-class/index', meta: { canNotLogin: false, showToast: true } },
-    { path: '/pages/material/recommend/index', meta: { canNotLogin: false, showToast: true } },
-    { path: '/pages/publish/moment/index', meta: { canNotLogin: false, showToast: true } },
-    { path: '/pages/publish/moment/index', meta: { canNotLogin: false, showToast: true } },
-    { path: '/pages/messages/index', meta: { canNotLogin: false, showToast: true } },
-    { path: '/pages/mine/setting/index', meta: { canNotLogin: false, showToast: true } },
+    { path: '/pages/mine/homepage/index', meta: { canNotLogin: false, canNotDesigner: true } },
+    {
+      path: '/pages/material/mini-class/index',
+      meta: { canNotLogin: false, canNotDesigner: true, toLogin: true },
+    },
+    {
+      path: '/pages/material/recommend/index',
+      meta: { canNotLogin: false, canNotDesigner: false, toLogin: true },
+    },
+    {
+      path: '/pages/publish/moment/index',
+      meta: { canNotLogin: false, canNotDesigner: false, toLogin: true },
+    },
+    {
+      path: '/pages/messages/index',
+      meta: { canNotLogin: false, canNotDesigner: true, toLogin: true },
+    },
+    {
+      path: '/pages/mine/setting/index',
+      meta: { canNotLogin: false, canNotDesigner: true, toLogin: true },
+    },
     {
       path: '/pages/mine/homepage/statistics/index',
-      meta: { canNotLogin: false, showToast: true },
+      meta: { canNotLogin: false, canNotDesigner: true, toLogin: true },
     },
     {
       path: '/pages/mine/points/index',
-      meta: { canNotLogin: false, showToast: true },
+      meta: { canNotLogin: false, canNotDesigner: true, toLogin: true },
     },
     {
       path: '/pages/mine/coupons/index',
-      meta: { canNotLogin: false, showToast: true },
+      meta: { canNotLogin: false, canNotDesigner: true, toLogin: true },
     },
     {
       path: '/pages/mine/orders/index',
-      meta: { canNotLogin: false, showToast: true },
+      meta: { canNotLogin: false, canNotDesigner: true, toLogin: true },
     },
     {
       path: '/pages/mine/agents/index',
-      meta: { canNotLogin: false, showToast: true },
+      meta: { canNotLogin: false, canNotDesigner: true, toLogin: true },
+    },
+    {
+      path: '/pages/mine/invite/index',
+      meta: { canNotLogin: false, canNotDesigner: false, toLogin: true },
     },
   ]
-  return { isLogined, isDesigner, routes }
+  const features = computed(() => ({
+    /**
+     * 1分钟了解筑巢荟
+     */
+    about: isDesigner.value,
+    toDesignerHomePage: isLogined.value,
+    checkInAtStoreTask: isDesigner.value,
+    // shareMoment: isLogined.value && isDesigner.value && userInfo.value.level.level > 1,
+  }))
+  return { isLogined, isDesigner, routes, features }
 }

+ 17 - 0
packages/app/src/core/libs/actions.ts

@@ -1,5 +1,7 @@
 import { title } from 'radash'
 import { cancelCircleReviewUpvote, cancelCircleUpvote, createCircleUpvote } from './requests'
+import { useUserStore } from '@/store'
+import { useRouter } from '../utils/router'
 
 const toast = (title: string) => uni.showToast({ title, icon: 'none' })
 export const handleUpvoteClick = async (
@@ -25,3 +27,18 @@ export const handleUpvoteClick = async (
   }
   callback && callback()
 }
+/**
+ * 分享圈子
+ */
+export const handleShareClick = () => {
+  const userStore = useUserStore()
+  if (!userStore.isLogined) {
+    return useRouter().push('/pages/login/index')
+  }
+  if (!userStore.isDesigner) {
+    return useRouter().push('/pages/mine/authentication/index')
+  }
+  if (userStore.userInfo.level?.level < 2) {
+    return toast('请先升级为设计师')
+  }
+}

+ 1 - 0
packages/app/src/core/libs/messages.ts

@@ -0,0 +1 @@
+export const messages = { home: { shareTitle: '筑巢荟' }, moment: { imageNotExist: '请上传图片' } }

+ 37 - 2
packages/app/src/core/libs/models.ts

@@ -451,11 +451,11 @@ export interface Message {
   /**
    * 消息类型
    */
-  messageType: string
+  messageType: number
   /**
    * 消息子类型
    */
-  messageSubType: string
+  messageSubType: number
   /**
    * 发送会员等级(多个枚举值逗号拼接,选项值包括全部会员等级、普通会员等级、白银会员等级、黄金会员等级、白金会员等级)
    */
@@ -492,6 +492,41 @@ export interface Message {
   viewCount: any
   viewTime: any
 }
+export interface Coupon {
+  id: number
+  couponId: number
+  userId: number
+  sendTime: string
+  /**
+   * 优惠卷有效开始时间
+   */
+  validityStartDate: string
+  /**
+   * 优惠卷有效结束时间
+   */
+  validityEndDate: string
+  /**
+   * 是否有效
+   */
+  isValid: number
+  isUse: number
+  useTime: string
+  /**
+   * 材料商id
+   */
+  material: number
+  brandId: number
+  buinessId: number
+  productId: number
+  createTime: string
+  brandPoints: number
+  couponName: string
+  couponType: number
+  brandIds: string
+  productIds: string
+  materialName: string
+  couponImgUrl: string
+}
 export enum DictType {
   /**
    *  擅长空间类型

+ 2 - 0
packages/app/src/core/libs/net-images.ts

@@ -6,4 +6,6 @@ export enum NetImages {
   ConsultDefaultBg = 'https://image.zhuchaohui.com/zhucaohui/7e98b5995902cd9484a6baecfc1219420cbcc30d8ae11e058af0c140a3c11137.png',
   StudyTourItemBg = 'https://image.zhuchaohui.com/zhucaohui/254b7ea7fdecaba8e318a7f50e292d036cafe49904fc7fc160a5477ce921e261.png',
   DefaultAvatar = 'https://image.zhuchaohui.com/zhucaohui/0b57771c2fbe60157e592a5b0e51a2b2b6c5263300663ad33efd55b235a2402a.png',
+  InviteBg = 'https://image.zhuchaohui.com/zhucaohui/b8f5350f9c09c1210adfa1417aaba4160b583928765df14194be2227ee928305.png',
+  Logo = 'https://image.zhuchaohui.com/zhucaohui/186acb6fdb8c8499c5ea77af317bfdab426edea94a009f8575877ab5179092d0.png',
 }

+ 69 - 3
packages/app/src/core/libs/requests.ts

@@ -17,6 +17,7 @@ import {
   UserAuthInfo,
   ResPageData,
   Message,
+  Coupon,
 } from './models'
 import dayjs from 'dayjs'
 import { pointsCancel } from '../../../../merchant/node_modules/@designer-hub/app/src/core/libs/requests'
@@ -327,6 +328,42 @@ export const getProducts = (query: { oneCategory?: string; secondCategory?: stri
       secondCategory: string
     }[]
   }>('/app-api/member/product/page', query)
+/**
+ * 获取超值划算商品
+ */
+export const getFavourableProducts = () =>
+  httpPost<
+    {
+      id: number
+      prodcutName: string
+      productId: string
+      oneCategory: string
+      oneCategoryName: string
+      secondCategory: string
+      secondCategoryName: string
+      isRestrict: number
+      productRepertory: number
+      productPrice: string
+      productType: number
+      vendorId: number
+      vendorName: string
+      needPoints: number
+      points: number
+      gainType: number
+      exchangeDesc: string
+      productCoverImgUrl: string
+      productDetailsImgUrl: string
+      contentDesc: string
+      status: number
+      exchangeCount: number
+      memberLevelIds: string
+      memberLevelName: string
+      favourablePoints: number
+      favourableEndDate: string
+      createTime: string
+      updateTime: string
+    }[]
+  >('/app-api/member/product/listFavourableProduct')
 export const getProduct = (id: string) =>
   httpGet<{
     id: number
@@ -388,6 +425,7 @@ export const getProductItemBuy = (query: { userId: number }) =>
       count: number
       createTime: any
       userId: number
+      nums: number
     }>[]
     total: number
   }>('/app-api/member/product-item-buy/select', query)
@@ -421,9 +459,18 @@ export const deleteProductItemBuy = (data: {
     id?: number
     productId?: string
     userId?: number
+    nums?: number
   }[]
 }) => httpPost('/app-api/member/product-item-buy/delete', data)
 /**
+ * 购物车商品数量增减
+ */
+// export const updateProductItemNums = (data: {
+//   userId: number
+//   productId: string
+//   changingNum: number
+// }) => httpPost('/app-api/member/product-item-buy/increase-or-decrease/product/num', data)
+/**
  * 商城下单
  */
 export const productPlacing = (data: {
@@ -436,6 +483,10 @@ export const productPlacing = (data: {
     nums?: number
     productName: string
     orderImgUrl: string
+    /**
+     * 商家id
+     */
+    vendorId: string | number
   }[]
   couponList: {
     couponId: number
@@ -578,12 +629,12 @@ export const updateMessage = (data: Partial<Message>) =>
  * 积分订单取消
  */
 export const orderPointsCancel = (query: { id: string; cancelReason: string }) =>
-  httpGet('/app-api/member/message-manage/points-cancel', query)
+  httpGet('/app-api/member/points-details/points-cancel', query)
 /**
  * 积分订单确认
  */
-export const orderPointsSubmit = (query: { id: string }) =>
-  httpGet('/app-api/member/message-manage/order-points-confirm', query)
+export const orderPointsSubmit = (data: { id: number; userId: number; couponUserId: number }) =>
+  httpPost('/app-api/member/points-details/confirm', data)
 /**
  * 获取Banner
  */
@@ -799,6 +850,21 @@ export const storeAndPunchIn = (data: { id: number }) =>
     hideErrorToast: true,
     method: 'POST',
   })
+/**
+ * 获取优惠券
+ */
+export const getCoupons = (query) =>
+  httpGet<ResPageData<Coupon>>('/app-api/member/coupon-user/page', query)
+/**
+ * 获取商品优惠券
+ */
+export const getProductCoupons = (query) =>
+  httpGet<Coupon[]>('/app-api/member/coupon-user/coupon-user-by-product-ids-page', query)
+/**
+ * 获取积分券
+ */
+export const getPointsCoupons = (query) =>
+  httpGet<Coupon[]>('/app-api/member/coupon-user/coupon-user-by-point-ids-page', query)
 export const refreshToken = (refreshToken: string) =>
   httpPost<any>('/app-api/member/auth/refresh-token', {}, { refreshToken })
 export const httpGetMock = <T>(data: T) =>

+ 6 - 0
packages/app/src/core/themes/default.scss

@@ -7,3 +7,9 @@ page {
 .wd-tabs__nav {
   background-color: transparent !important;
 }
+.wd-input-number__action {
+  width: 44rpx;
+  height: 44rpx;
+  background-color: #f3f3f3;
+  border-radius: 100px;
+}

+ 2 - 0
packages/app/src/core/themes/default.ts

@@ -15,4 +15,6 @@ export const defaultThemeVars: ConfigProviderThemeVars = {
   colorTheme: 'black',
   // navbarTitleColor: '#fff',
   tabsNavBg: 'red',
+  inputNumberBorderColor: 'transparent',
+  inputNumberBtnWidth: '44rpx',
 }

+ 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))
+  }
+}

+ 3 - 0
packages/app/src/core/utils/common.ts

@@ -12,6 +12,9 @@ export const isImageOrVideo = (url) => {
     return 'unknown'
   }
 }
+export const toast = (msg: string, icon = 'none') => {
+  uni.showToast({ title: msg, icon })
+}
 export const requestToast = async <T>(
   func: () => Promise<IResData<T>>,
   options: { error?: boolean; errorTitle?: string; success?: boolean; successTitle?: string } = {

+ 7 - 2
packages/app/src/core/utils/router.ts

@@ -4,17 +4,22 @@ export const back = () => {
   uni.navigateBack()
 }
 export const useRouter = () => {
-  const { routes, isLogined } = usePermissions()
+  const { routes, isLogined, isDesigner } = usePermissions()
   const push = async (url: string, switchTab = false) => {
-    // console.log(url)
     const path = url.split('?')[0]
     const route = routes.find((it) => it.path === path)
     if (route && !route.meta.canNotLogin && !isLogined.value) {
       if (route.meta.showToast) {
         uni.showToast({ title: '暂无权限', icon: 'none' })
       }
+      if (route.meta.toLogin) {
+        push('/pages/login/index')
+      }
       return false
     }
+    if (route && !route.meta.canNotDesigner && !isDesigner.value) {
+      return push('/pages/mine/authentication/index')
+    }
     if (switchTab) {
       uni.switchTab({ url })
     } else {

+ 13 - 0
packages/app/src/pages.json

@@ -77,6 +77,10 @@
       {
         "name": "购物车",
         "path": "/pages/home/mall/shopping-cart/index"
+      },
+      {
+        "name": "支付成功",
+        "path": "/pages/mine/scan/pay/success/index"
       }
     ]
   },
@@ -308,6 +312,15 @@
       }
     },
     {
+      "path": "pages/mine/invite/index",
+      "type": "page",
+      "style": {
+        "navigationBarTitleText": "邀请设计师",
+        "navigationBarBackgroundColor": "#fff",
+        "navigationStyle": "custom"
+      }
+    },
+    {
       "path": "pages/mine/levels/index",
       "type": "page",
       "style": {

+ 11 - 6
packages/app/src/pages/home/activity/detail/index.vue

@@ -35,6 +35,7 @@ import { replace, sort } from 'radash'
 import { Activity, StudyTour } from '../../../../core/libs/models'
 import mapLocation from '@designer-hub/assets/src/libs/assets/mapLocation'
 import cameraWhite from '@designer-hub/assets/src/libs/assets/cameraWhite'
+import ImgBtnEvo from '@/components/img-btn-evo.vue'
 
 const themeVars = ref<ConfigProviderThemeVars>({
   tableBorderColor: 'white',
@@ -208,9 +209,14 @@ onLoad(async (query: { id: string; type: 'activity' | 'studyTour' }) => {
     }"
   >
     <NavbarEvo transparent dark></NavbarEvo>
-    <div class="aspect-[1.26/1] relative mx--3.5">
+    <div class="aspect-[1.26/1] relative mx--3.5 relative">
       <!-- <wd-img width="100%" height="100%" :src="data.bannerUrl?.at(0)"></wd-img> -->
-      <canvas class="w-full h-full" canvas-id="firstCanvas" id="firstCanvas"></canvas>
+      <canvas
+        class="w-full h-full absolute top--1000"
+        canvas-id="firstCanvas"
+        id="firstCanvas"
+      ></canvas>
+      <wd-img width="100%" height="100%" :src="data?.backgroundUrl"></wd-img>
       <div class="absolute left-3.5 bottom-3" @click="listShow = true">
         <div
           v-if="isStudyTour"
@@ -250,9 +256,7 @@ onLoad(async (query: { id: string; type: 'activity' | 'studyTour' }) => {
       <!-- 日本研学·东京艺术大学设计游学 -->
       <div class="inline-block">{{ data?.name }}</div>
       <div class="inline-block py-1.5 px-4 bg-white rounded-[20px] backdrop-blur-[15px]">
-        <div
-          class="w-[42px] h-[21px] text-[#a60707] text-sm font-normal font-['PingFang_SC'] leading-relaxed"
-        >
+        <div class="text-[#a60707] text-sm font-normal font-['PingFang_SC'] leading-relaxed">
           {{ getActivityStatusText(data?.applyStartTime, data?.applyEndTime) }}
         </div>
       </div>
@@ -363,7 +367,8 @@ onLoad(async (query: { id: string; type: 'activity' | 'studyTour' }) => {
         </div>
         <div class="flex-1"></div>
         <div @click="show = true">
-          <TiltedButton size="large">{{ data?.ifSingnUp ? '已报名' : '立即报名' }}</TiltedButton>
+          <ImgBtnEvo>{{ data?.ifSingnUp ? '已报名' : '立即报名' }}</ImgBtnEvo>
+          <!-- <TiltedButton size="large">{{ data?.ifSingnUp ? '已报名' : '立即报名' }}</TiltedButton> -->
         </div>
       </div>
     </BottomAppBar>

+ 62 - 0
packages/app/src/pages/home/components/home-banner.vue

@@ -0,0 +1,62 @@
+<script setup lang="ts">
+import { unix } from 'dayjs'
+
+const props = withDefaults(
+  defineProps<{
+    id: string | number
+    url: string
+    cover: string
+  }>(),
+  {},
+)
+// const emits = defineEmits()
+const instance = getCurrentInstance()
+const playing = ref(false)
+const duration = ref(0)
+const durationText = computed(() => unix(duration.value).format('mm:ss'))
+const videoContext = ref<UniNamespace.VideoContext>()
+const handlePlay = () => {
+  videoContext.value?.play()
+  playing.value = true
+}
+const handleLoadedMetaData = ({ detail }) => {
+  duration.value = detail.duration
+}
+onMounted(async () => {
+  videoContext.value = uni.createVideoContext(`video-${props.id}`, instance)
+})
+</script>
+<template>
+  <div class="w-full h-full relative">
+    <video
+      class="w-full h-full"
+      :id="`video-${id}`"
+      :src="url"
+      :controls="'contimg'"
+      :show-center-play-btn="false"
+      @loadedmetadata="handleLoadedMetaData"
+    ></video>
+    <div v-if="!playing" class="absolute left-0 top-0 w-full h-full bg-black">
+      <wd-img width="100%" height="100%" :src="cover" />
+    </div>
+    <div
+      v-if="!playing"
+      class="w-[375px] h-[90px] bg-gradient-to-t from-black to-black/0 absolute left-0 bottom-0 w-full flex items-center"
+    >
+      <view class="mx-7">
+        <wd-button
+          plain
+          custom-class="bg-transparent! text-white! w-20.5! min-w-20! h-8.25! border! border-solid! border-white!"
+          @click="handlePlay()"
+        >
+          <div class="flex items-center">
+            <wd-icon name="play" size="22px"></wd-icon>
+            <div class="text-white text-[13px] font-['Gotham'] leading-[10.18px]">
+              {{ durationText }}
+            </div>
+          </div>
+        </wd-button>
+      </view>
+    </div>
+  </div>
+</template>

+ 6 - 3
packages/app/src/pages/home/components/register-card.vue

@@ -1,6 +1,6 @@
 <script lang="ts" setup>
 import Card from '@/components/card.vue'
-import { Activity, DictType } from '../../../core/libs/models'
+import { Activity, DictType, StudyTour } from '../../../core/libs/models'
 import dayjs from 'dayjs'
 import TiltedButton from '@/components/tilted-button.vue'
 import { getActivitySignups, getByDictType } from '../../../core/libs/requests'
@@ -8,7 +8,10 @@ import { NetImages } from '../../../core/libs/net-images'
 import { useRouter } from '../../../core/utils/router'
 import { getActivityStatusButtonText, getActivityStatusText } from '../../../core/utils/common'
 
-const props = defineProps<{ customClass?: string; options?: Activity }>()
+const props = defineProps<{
+  customClass?: string
+  options?: Activity & StudyTour
+}>()
 const router = useRouter()
 const { data: signups, run: setSignups } = useRequest(
   () => getActivitySignups({ activityId: props.options!.id.toString() }),
@@ -87,7 +90,7 @@ onMounted(async () => {
               {{
                 options?.ifSingnUp
                   ? '已报名'
-                  : getActivityStatusButtonText(options?.applyStartTime, options?.activityEndTime)
+                  : getActivityStatusButtonText(options?.applyStartTime, options?.applyEndTime)
               }}
             </tilted-button>
           </view>

+ 39 - 18
packages/app/src/pages/home/index.vue

@@ -13,26 +13,31 @@
 import Card from '@/components/card.vue'
 import HotActivity from '@/components/hot-activity.vue'
 import MomentItem from '@/components/moment-item.vue'
+import HomeBanner from './components/home-banner.vue'
 import useRequest from '../../hooks/useRequest'
 import Menus from './components/menus.vue'
-import { getCircles, getSetIndexConfigs } from '../../core/libs/requests'
+import { getCircles, getSetIndexConfigs, shareCircle } from '../../core/libs/requests'
 import { logo } from '../../core/libs/svgs'
 import PageHelper from '@/components/page-helper.vue'
 import { ComponentExposed } from 'vue-component-type-helpers'
 import { usePermissions } from '../../composables/permissions'
 import { storeToRefs } from 'pinia'
+import { messages } from '../../core/libs/messages'
+import { handleUpvoteClick, handleShareClick } from '../../core/libs/actions'
+import { useUserStore } from '../../store'
 
 defineOptions({
   name: 'Home',
 })
-const instance = getCurrentInstance()
-const { isLogined, isDesigner } = usePermissions()
+const userStore = useUserStore()
+const { userInfo } = storeToRefs(userStore)
+const { features } = usePermissions()
 const pageHelperRef = ref<ComponentExposed<typeof PageHelper>>()
 const { data: indexConfigsData, run: setIndexConfigsData } = useRequest(
   () => getSetIndexConfigs(),
   { initialData: { list: [] } },
 )
-const swiperData = ref<{ data: any; videoContext: UniNamespace.VideoContext; playing: boolean }[]>()
+const swiperData = ref<{ data: any }[]>()
 const swiperCurrent = ref(0)
 
 onShow(async () => {
@@ -42,8 +47,6 @@ onLoad(async () => {
   await setIndexConfigsData()
   swiperData.value = indexConfigsData.value.list.map((it) => ({
     data: it,
-    videoContext: uni.createVideoContext(`video-${it.id}`, instance),
-    playing: false,
   }))
 })
 const toAbout = () => {
@@ -52,11 +55,31 @@ const toAbout = () => {
 const handleSwiperChange = ({ detail: { current } }) => {
   swiperCurrent.value = current
 }
-const handlePlay = () => {
-  swiperData.value[swiperCurrent.value].videoContext.play()
-  swiperData.value[swiperCurrent.value].playing = true
+const handleLike = async (options) => {
+  await handleUpvoteClick({
+    ...options,
+    userId: userInfo.value.userId,
+    userName: userInfo.value.nickname,
+  })
+  pageHelperRef.value?.refresh()
 }
-onShareAppMessage(() => ({}))
+onShareAppMessage(async ({ from, target }) => {
+  console.log('from', from)
+  console.log('target', target)
+  const res: Page.CustomShareContent = {}
+  if (from === 'button') {
+    handleShareClick()
+    console.log('分享')
+    await shareCircle(target.dataset.options.id)
+    res.path = `/pages/home/moment/index?id=${target.dataset.options.id}`
+    res.imageUrl = target.dataset.options.bannerUrls[0]
+    res.title = `${target.dataset.options.stylistName}: ${target.dataset.options.circleDesc}`
+  }
+  if (from === 'menu') {
+    res.title = messages.home.shareTitle
+  }
+  return res
+})
 </script>
 
 <template>
@@ -64,14 +87,12 @@ onShareAppMessage(() => ({}))
     <view class="bg-black w-full relative aspect-[1.26/1]">
       <swiper @change="handleSwiperChange">
         <template
-          v-for="{
-            data: { id, coverVideoImage, indexPromotionalVideoImage },
-            playing,
-          } of swiperData"
+          v-for="{ data: { id, coverVideoImage, indexPromotionalVideoImage } } of swiperData"
           :key="id"
         >
           <swiper-item>
-            <div class="w-full h-full relative">
+            <HomeBanner :id="id" :url="indexPromotionalVideoImage" :cover="coverVideoImage" />
+            <!-- <div class="w-full h-full relative">
               <video
                 class="w-full h-full"
                 :id="`video-${id}`"
@@ -95,7 +116,7 @@ onShareAppMessage(() => ({}))
                   </wd-button>
                 </view>
               </div>
-            </div>
+            </div> -->
           </swiper-item>
         </template>
       </swiper>
@@ -106,7 +127,7 @@ onShareAppMessage(() => ({}))
       <!-- <view class="my-6 mx-3.5">
         <HotActivity></HotActivity>
       </view> -->
-      <view v-if="isDesigner" class="my-6 mx-3.5" @click="toAbout()">
+      <view v-if="features.about" class="my-6 mx-3.5" @click="toAbout()">
         <Card>
           <div class="flex items-center gap-2">
             <wd-img width="28" height="28" :src="logo"></wd-img>
@@ -124,7 +145,7 @@ onShareAppMessage(() => ({}))
           <template #default="{ source }">
             <template v-for="it of source.list" :key="it.id">
               <view class="my-6">
-                <MomentItem :options="it"></MomentItem>
+                <MomentItem :options="it" @like="handleLike"></MomentItem>
               </view>
             </template>
           </template>

+ 3 - 2
packages/app/src/pages/home/mall/components/product.vue

@@ -1,5 +1,4 @@
 <script lang="ts" setup>
-import { publish } from '../../../../core/libs/svgs'
 import { addBlack } from '@designer-hub/assets/src/assets/svgs'
 import { useRouter } from '../../../../core/utils/router'
 import { PropType } from 'vue'
@@ -14,6 +13,7 @@ const props = defineProps({
     default: () => {},
   },
 })
+const emits = defineEmits<{ change: [] }>()
 const userStore = useUserStore()
 const { userInfo } = storeToRefs(userStore)
 
@@ -33,6 +33,7 @@ const handleAddToCart = async () => {
       }),
     { success: true, successTitle: '加入购物车成功' },
   )
+  emits('change')
 }
 </script>
 <template>
@@ -72,7 +73,7 @@ const handleAddToCart = async () => {
       </div>
       <div class="flex-1"></div>
       <div class="" @click.stop="handleAddToCart">
-        <wd-img width="24" height="24" :src="addBlack"></wd-img>
+        <wd-img width="32" height="32" :src="addBlack"></wd-img>
       </div>
     </div>
   </div>

+ 50 - 22
packages/app/src/pages/home/mall/confirm-order/index.vue

@@ -17,10 +17,12 @@ import Card from '@/components/card.vue'
 import SectionHeading from '@/components/section-heading.vue'
 import BottomAppBar from '@/components/bottom-app-bar.vue'
 import { requestToast } from '../../../../core/utils/common'
-import { orderPay } from '../../../../core/libs/requests'
+import { getProductCoupons, orderPay } from '../../../../core/libs/requests'
 import { useUserStore } from '../../../../store'
 import { storeToRefs } from 'pinia'
 import { useRouter } from '../../../../core/utils/router'
+import dayjs from 'dayjs'
+import { Coupon } from '../../../../core/libs/models'
 
 const router = useRouter()
 const userStore = useUserStore()
@@ -28,11 +30,26 @@ const { userInfo } = storeToRefs(userStore)
 const show = ref(false)
 const a = ref(1)
 const data = ref()
+const selectedCoupon = ref<Coupon>()
+const { data: coupons, run: setCoupons } = useRequest(() =>
+  getProductCoupons({
+    userId: userInfo.value.userId,
+    productIds: data.value.list.map((it) => it.productId).join(','),
+    isUse: 0,
+  }),
+)
 const handlePay = async () => {
   const { code } = await requestToast(
     () =>
       orderPay({
         ...data?.value,
+        couponList: [
+          {
+            couponId: selectedCoupon.value.id,
+            projectIds: selectedCoupon.value.productIds,
+            buinessId: selectedCoupon.value.buinessId,
+          },
+        ],
       }),
     { success: true, successTitle: '兑换成功' },
   )
@@ -40,6 +57,10 @@ const handlePay = async () => {
     router.replace('/pages/home/mall/purchased/success/index')
   }
 }
+const handleQ = async () => {
+  await setCoupons()
+  show.value = true
+}
 onLoad(async (query: { data: string }) => {
   data.value = JSON.parse(query.data)
 })
@@ -74,9 +95,9 @@ onLoad(async (query: { data: string }) => {
     <Card>
       <div class="flex flex-col gap-8">
         <SectionHeading title="总积分" :end-text="data?.totalsPoints" size="sm"></SectionHeading>
-        <!-- <div @click="show = true">
+        <div @click="handleQ">
           <SectionHeading title="优惠券" end-text="已选2张" end-arrow size="sm"></SectionHeading>
-        </div> -->
+        </div>
         <SectionHeading
           title="实付积分"
           :end-text="data?.totalsCurrPoints"
@@ -96,11 +117,9 @@ onLoad(async (query: { data: string }) => {
         </div>
         <div class="">
           <TrapeziumButton size="large">
-            <div
-              class="w-[49px] h-[22px] text-white text-base font-normal font-['PingFang_SC'] leading-tight"
-            >
+            <div class="text-white text-base font-normal font-['PingFang_SC'] leading-tight">
               <div
-                class="w-[65px] h-[22px] text-white text-base font-normal font-['PingFang_SC'] leading-tight"
+                class="text-white text-base font-normal font-['PingFang_SC'] leading-tight"
                 @click="handlePay"
               >
                 确认兑换
@@ -116,24 +135,33 @@ onLoad(async (query: { data: string }) => {
           <wd-tab title="可用优惠券"></wd-tab>
           <wd-tab title="不可用优惠券"></wd-tab>
         </wd-tabs>
-        <div class="bg-[#f6f6f6] p-3.5">
-          <div class="bg-white rounded-2xl p-3.5 flex gap-2.5">
-            <div class="w-[94px] h-[94px] bg-[#f6f6f6] rounded-[10px]"></div>
-            <div class="flex flex-col justify-around flex-1">
-              <div class="text-black text-sm font-normal font-['PingFang_SC'] leading-normal">
-                GELATO咖啡兑换券
-              </div>
-              <div class="text-black/40 text-xs font-normal font-['PingFang_SC'] leading-normal">
-                2024/04/01-2024/05/30
+        <div class="bg-[#f6f6f6] p-3.5 flex flex-col gap-4">
+          <template v-for="(it, i) in coupons" :key="i">
+            <div
+              class="bg-white rounded-2xl p-3.5 flex gap-2.5"
+              @click="(selectedCoupon = it), (show = false)"
+            >
+              <div class="w-[94px] h-[94px] bg-[#f6f6f6] rounded-[10px]"></div>
+              <div class="flex flex-col justify-around flex-1">
+                <div class="text-black text-sm font-normal font-['PingFang_SC'] leading-normal">
+                  <!-- GELATO咖啡兑换券 -->
+                  {{ it.couponName }}
+                </div>
+                <div class="text-black/40 text-xs font-normal font-['PingFang_SC'] leading-normal">
+                  <!-- 2024/04/01-2024/05/30 -->
+                  {{ dayjs(it.validityStartDate).format('YYYY/MM/DD') }}-{{
+                    dayjs(it.validityEndDate).format('YYYY/MM/DD')
+                  }}
+                </div>
+                <div class="text-black/40 text-xs font-normal font-['PingFang_SC'] leading-normal">
+                  使用说明
+                </div>
               </div>
-              <div class="text-black/40 text-xs font-normal font-['PingFang_SC'] leading-normal">
-                使用说明
+              <div class="flex items-center">
+                <div class="w-4 h-4 rounded-full border border-black/60 border-solid"></div>
               </div>
             </div>
-            <div class="flex items-center">
-              <div class="w-4 h-4 rounded-full border border-black/60 border-solid"></div>
-            </div>
-          </div>
+          </template>
         </div>
         <!-- <wd-button block :round="false">确认</wd-button> -->
       </view>

+ 3 - 6
packages/app/src/pages/home/mall/detail/index.vue

@@ -43,6 +43,7 @@ const handleConfirm = async () => {
             nums: 1,
             productName: data.value.prodcutName,
             orderImgUrl: data.value.productCoverImgUrl,
+            vendorId: data.value.vendorId,
           },
         ],
         couponList: [],
@@ -128,12 +129,8 @@ onLoad(async (query: { id: string }) => {
         </div>
         <div class="" @click="(show = true), (type = 'orderNow')">
           <TrapeziumButton size="large">
-            <div
-              class="w-[49px] h-[22px] text-white text-base font-normal font-['PingFang_SC'] leading-tight"
-            >
-              <div
-                class="w-[65px] h-[22px] text-white text-base font-normal font-['PingFang_SC'] leading-tight"
-              >
+            <div class="text-white text-base font-normal font-['PingFang_SC'] leading-tight">
+              <div class="text-white text-base font-normal font-['PingFang_SC'] leading-tight">
                 立即兑换
               </div>
             </div>

+ 135 - 12
packages/app/src/pages/home/mall/index.vue

@@ -14,6 +14,7 @@ import { shoppingBag } from '@designer-hub/assets/src/assets/svgs/index'
 import { useRouter } from '../../../core/utils/router'
 import {
   getBanners,
+  getFavourableProducts,
   getProductCategories,
   getProductItemBuy,
   getProducts,
@@ -24,6 +25,12 @@ import Banner from '../components/banner.vue'
 import BottomAppBar from '@/components/bottom-app-bar.vue'
 import { useUserStore } from '../../../store'
 import { storeToRefs } from 'pinia'
+import lightning from '@designer-hub/assets/src/libs/assets/lightning'
+import textGreatMoney from '@designer-hub/assets/src/libs/assets/textGreatMoney'
+import grabNow from '@designer-hub/assets/src/libs/assets/grabNow'
+import ProgressEvo from '@/components/progress-evo.vue'
+import SwiperEvo from '@/components/swiper-evo.vue'
+import dayjs from 'dayjs'
 
 const router = useRouter()
 const userStore = useUserStore()
@@ -39,14 +46,36 @@ const { data: carts, run: setCarts } = useRequest(
   () => getProductItemBuy({ userId: userInfo.value.userId }),
   { initialData: { list: [], total: 0 } },
 )
+const { data: favourableProducts, run: setFavourableProducts } = useRequest(
+  () => getFavourableProducts(),
+  { initialData: [] },
+)
 const current = ref<number>(0)
 const swiperList = computed(() => banners.value.map((it) => it.bannerImgUrl))
 const categories = computed(() => productCategories.value.find(({ id }) => id === 1)?.children)
+const time = computed(
+  () =>
+    dayjs(favourableProducts.value[current.value].favourableEndDate).diff(
+      dayjs(),
+      'milliseconds',
+    ) || 0,
+)
 const category = ref()
+const query = computed(() => ({
+  oneCategory: category.value?.parentId,
+  secondCategory: category.value?.id,
+}))
+const handleChange = async () => {
+  setCarts()
+}
+const handleSwiperChange = async (e) => {
+  console.log(e)
+}
 onMounted(async () => {
-  await setProductCategories()
-  await setBanners()
-  await setCarts()
+  await Promise.all([setProductCategories(), setBanners(), setCarts(), setFavourableProducts()])
+  // await setProductCategories()
+  // await setBanners()
+  // await setCarts()
   category.value = categories.value[0]
 })
 </script>
@@ -54,8 +83,97 @@ onMounted(async () => {
 <template>
   <view class="bg-white flex-grow flex flex-col px-3.5 py-5.5 gap-5.5">
     <Banner :mode="BannerMode.Mall"></Banner>
+    <div v-if="favourableProducts.length" class="relative aspect-[1.47/1]">
+      <div class="aspect-[2.99/1] bg-black rounded-2xl">
+        <div class="px-3 h-11 flex items-center">
+          <wd-img width="22" height="22" :src="lightning"></wd-img>
+          <wd-img width="95" height="25" :src="textGreatMoney"></wd-img>
+          <div class="flex-1"></div>
+          <div
+            class="flex gap-1 text-center text-white text-base font-normal font-['PingFang_SC'] leading-relaxed"
+          >
+            <wd-count-down :time="time">
+              <template #default="{ current }">
+                <span
+                  class="text-center text-white text-base font-normal font-['PingFang_SC'] leading-relaxed"
+                >
+                  <span class="custom-count-down">{{ current.hours }}</span>
+                  <span class="custom-count-down-colon">:</span>
+                  <span class="custom-count-down">{{ current.minutes }}</span>
+                  <span class="custom-count-down-colon">:</span>
+                  <span class="custom-count-down">{{ current.seconds }}</span>
+                </span>
+              </template>
+            </wd-count-down>
+            <!-- 17:02:18 -->
+            结束
+          </div>
+        </div>
+      </div>
+      <div class="absolute top-11 left-0 right-0 bottom-0 bg-white rounded-2xl shadow">
+        <SwiperEvo v-model="current" :items="favourableProducts">
+          <template #default="{ item: it }">
+            <div class="w-full h-full px-4 flex items-center gap-3 box-border">
+              <wd-img
+                width="114"
+                height="114"
+                custom-class="rounded-2xl overflow-hidden"
+                :src="it.productCoverImgUrl"
+              />
+              <div class="flex-1">
+                <div
+                  class="w-[178px] text-black/90 text-base font-normal font-['PingFang_SC'] leading-normal"
+                >
+                  <!-- 海蓝之谜精华面霜60ml -->
+                  {{ it.prodcutName }}
+                </div>
+                <div class="flex items-center gap-2.5">
+                  <div class="flex-1">
+                    <!-- {{ (it.exchangeCount || 0 / it.productRepertory || 0) * 100 }} -->
+                    <ProgressEvo
+                      :height="6"
+                      :model-value="(it.exchangeCount || 0 / it.productRepertory || 0) * 100"
+                      color="black"
+                    ></ProgressEvo>
+                  </div>
+                  <div
+                    class="text-black/40 text-[10px] font-normal font-['PingFang_SC'] leading-normal"
+                  >
+                    还剩{{ it.productRepertory - it.exchangeCount }}件
+                  </div>
+                </div>
+                <div class="flex items-end gap-1 mt-5">
+                  <div class="text-[#ef4343] text-[22px] font-normal font-['D-DIN_Exp'] pb-3">
+                    <!-- 1600 -->
+                    {{ it.favourablePoints }}
+                  </div>
+                  <div class="text-black/60 text-sm font-normal font-['PingFang_SC'] pb-3">
+                    积分
+                  </div>
+                  <div class="flex-1"></div>
+                  <div @click="router.push(`/pages/home/mall/detail/index?id=${it.productId}`)">
+                    <wd-img width="106" height="40" :src="grabNow"></wd-img>
+                  </div>
+                </div>
+                <div class="flex gap-4">
+                  <div
+                    class="text-black/30 text-[10px] font-normal font-['PingFang_SC'] line-through leading-normal"
+                  >
+                    {{ it.points }}积分
+                  </div>
+                  <div
+                    class="text-black/30 text-[10px] font-normal font-['PingFang_SC'] line-through leading-normal"
+                  >
+                    ¥{{ it.productPrice }}
+                  </div>
+                </div>
+              </div>
+            </div>
+          </template>
+        </SwiperEvo>
+      </div>
+    </div>
     <div class="w-full inline-flex gap-2">
-      <!-- <div><wd-button type="primary" size="small">GELATO专区</wd-button></div> -->
       <template v-for="(it, i) in categories" :key="i">
         <div>
           <wd-button
@@ -71,21 +189,18 @@ onMounted(async () => {
     </div>
     <PageHelper
       v-if="category"
-      v-slot="{ data }"
+      v-slot="{ source }"
       class="flex-grow flex flex-col"
       :request="getProducts"
-      :query="{
-        oneCategory: category?.parentId,
-        secondCategory: category?.id,
-      }"
+      :query="query"
     >
       <!-- <wd-skeleton
         :row-col="[[{ width: 100, height: 100 }, , { width: 100, height: 100 }]]"
         :loading="true"
       > -->
       <div class="grid grid-cols-2 gap-2.5">
-        <template v-for="(it, i) of data" :key="i">
-          <Product :options="it"></Product>
+        <template v-for="(it, i) of source.list" :key="i">
+          <Product :options="it" @change="handleChange"></Product>
         </template>
       </div>
       <!-- </wd-skeleton> -->
@@ -94,7 +209,15 @@ onMounted(async () => {
       <div class="h-16 bg-white flex items-center justify-between px-3.5">
         <!-- <div> -->
         <!-- <wd-button type="text" size="small"> -->
-        <wd-img :round="false" width="32" height="32" :src="shoppingBag"></wd-img>
+        <wd-badge :modelValue="carts.total || 0">
+          <wd-img
+            :round="false"
+            width="32"
+            height="32"
+            :src="shoppingBag"
+            @click="router.push('/pages/home/mall/shopping-cart/index')"
+          ></wd-img>
+        </wd-badge>
         <!-- </wd-button> -->
         <!-- </div> -->
         <div @click="router.push('/pages/home/mall/shopping-cart/index')">

+ 37 - 4
packages/app/src/pages/home/mall/shopping-cart/index.vue

@@ -14,9 +14,11 @@ import { shoppingBag } from '@designer-hub/assets/src/assets/svgs/index'
 import InvertedTrapezoidButton from '@/components/inverted-trapezoid-button.vue'
 import TrapeziumButton from '@/components/trapezium-button.vue'
 import {
+  createProductItemBuy,
   deleteProductItemBuy,
   getProductItemBuy,
   productPlacing,
+  updateProductItemNums,
 } from '../../../../core/libs/requests'
 import PageHelper from '@/components/page-helper.vue'
 import BottomAppBar from '@/components/bottom-app-bar.vue'
@@ -25,6 +27,7 @@ import { useRouter } from '../../../../core/utils/router'
 import { storeToRefs } from 'pinia'
 import { requestToast } from '../../../../core/utils/common'
 import type { ComponentExposed } from 'vue-component-type-helpers'
+import InputNumberEvo from '@/components/input-number-evo.vue'
 
 const pageHelperRef = ref<ComponentExposed<typeof PageHelper>>()
 const userStore = useUserStore()
@@ -35,6 +38,7 @@ const selected = ref([])
 const points = computed(() =>
   selected.value.reduce((acc, item) => acc + item.points * item.nums, 0),
 )
+const query = ref({ userId: userInfo.value?.userId })
 const handleSelect = (product) => {
   if (selected.value.map((it) => it.productId).includes(product.productId)) {
     selected.value = selected.value.filter(({ productId }) => productId !== product.productId)
@@ -62,6 +66,32 @@ const handleDelete = async (product: any) => {
   )
   await pageHelperRef.value?.refresh()
 }
+const handleProductNumsChange = async (nums, product) => {
+  const changeNums = nums - product.nums
+  if (changeNums > 0) {
+    await createProductItemBuy({
+      doList: [
+        {
+          userId: userInfo.value.userId,
+          productId: product.productId,
+          points: product.points,
+          nums: Math.abs(changeNums),
+        },
+      ],
+    })
+  } else {
+    await deleteProductItemBuy({
+      doList: [
+        {
+          productId: product.productId,
+          userId: userInfo.value.userId,
+          nums: Math.abs(changeNums),
+        },
+      ],
+    })
+  }
+  await pageHelperRef.value?.refresh()
+}
 const handlePlaceOrder = async () => {
   if (!selected.value.length) {
     uni.showToast({ title: '请选择商品', icon: 'none' })
@@ -100,7 +130,7 @@ const handlePlaceOrder = async () => {
     <PageHelper
       ref="pageHelperRef"
       :request="getProductItemBuy"
-      :query="{ userId: userInfo.userId }"
+      :query="query"
       class="flex-grow flex flex-col"
     >
       <template #default="{ source }">
@@ -124,7 +154,7 @@ const handlePlaceOrder = async () => {
                   >
                     {{ it.prodcutName }}
                   </div>
-                  <div class="flex items-center">
+                  <div class="flex items-center gap-1.25">
                     <div
                       class="text-[#ef4343] text-[22px] font-normal font-['D-DIN Exp'] leading-normal"
                     >
@@ -136,8 +166,11 @@ const handlePlaceOrder = async () => {
                       积分
                     </div>
                     <div class="flex-1"></div>
-                    {{ it.nums }}
-                    <!-- <wd-input-number v-model="it.nums" /> -->
+                    <!-- {{ it.nums }} -->
+                    <wd-input-number
+                      :model-value="Number(it.nums)"
+                      @update:model-value="(e) => handleProductNumsChange(e, it)"
+                    />
                   </div>
                 </div>
               </div>

+ 104 - 0
packages/app/src/pages/home/study-tour/components/register-card.vue

@@ -0,0 +1,104 @@
+<script lang="ts" setup>
+import Card from '@/components/card.vue'
+import { Activity, StudyTour } from '../../../../core/libs/models'
+import dayjs from 'dayjs'
+import TiltedButton from '@/components/tilted-button.vue'
+import { getActivitySignups, getStudyTourSignups } from '../../../../core/libs/requests'
+import { NetImages } from '../../../../core/libs/net-images'
+import { useRouter } from '../../../../core/utils/router'
+import { getActivityStatusButtonText, getActivityStatusText } from '../../../../core/utils/common'
+
+const props = defineProps<{
+  customClass?: string
+  options?: StudyTour & { levelsByMemberLevel }
+}>()
+const router = useRouter()
+const { data: signups, run: setSignups } = useRequest(
+  () => getStudyTourSignups({ studyId: props.options!.id.toString() }),
+  { initialData: { list: [], total: 0 } },
+)
+onMounted(async () => {
+  await setSignups()
+})
+</script>
+<template>
+  <div @click="router.push(`/pages/home/activity/detail/index?id=${options?.id}&type=studyTour`)">
+    <Card custom-class="w-full p-0! relative aspect-[0.75/1]">
+      <div class="absolute left-0 top-0 right-0 bottom-0">
+        <wd-img
+          custom-class="vertical-bottom"
+          width="100%"
+          height="100%"
+          :src="options?.bannerUrl"
+        ></wd-img>
+      </div>
+      <div
+        class="w-[63px] h-[29px] bg-black/60 rounded-[20px] backdrop-blur-[15px] absolute top-5 right-3.5 flex items-center justify-center"
+      >
+        <div class="text-white text-sm font-normal font-['PingFang_SC'] leading-relaxed">
+          {{ getActivityStatusText(options?.applyStartTime, options?.applyEndTime) }}
+        </div>
+      </div>
+      <view class="absolute bottom-0 left-0 right-0">
+        <view class="flex items-center mx-4 my-2.5 gap-1">
+          <avatar-group-casual
+            :show-number="3"
+            :urls="signups.list.map((it) => it.avatar || NetImages.DefaultAvatar)"
+          ></avatar-group-casual>
+          <div
+            class="ml-1 text-white/60 text-sm font-normal font-['PingFang_SC'] leading-[10.18px]"
+          >
+            {{ signups.total }}人已报名
+          </div>
+        </view>
+        <div class="bg-[#27130d]/50 rounded-bl-2xl rounded-br-2xl backdrop-blur-[20px] p-3.5">
+          <div
+            class="w-[293px] text-white text-xl font-normal font-['PingFang_SC'] leading-relaxed"
+          >
+            {{ options?.name }}
+          </div>
+          <view class="flex items-center">
+            <div class="text-white/60 text-sm font-normal font-['PingFang_SC'] leading-[34px]">
+              游学时间:
+            </div>
+            <div
+              class="text-white/60 text-base font-normal font-['PingFang_SC'] leading-[34px] flex items-center gap-1"
+            >
+              <div class="text-center">
+                {{ dayjs(options?.studyStartTime || options?.planStudyStartTime).format('MM.DD') }}
+              </div>
+              <wd-icon name="play" size="22px"></wd-icon>
+              <div class="text-center">
+                {{ dayjs(options?.studyEndTime || options?.planStudyEndTime).format('MM.DD') }}
+              </div>
+            </div>
+          </view>
+          <div
+            class="text-justify text-white/60 text-sm font-normal font-['PingFang_SC'] leading-relaxed"
+          >
+            等级限制:{{
+              options?.memberLevel
+                ?.map((it) => options?.levelsByMemberLevel[String(it)]?.memberLevelName)
+                ?.join('、')
+            }}
+          </div>
+          <view class="flex items-center justify-between">
+            <view class="flex items-end">
+              <div class="text-white text-3xl font-bold font-['D-DIN_Exp']">
+                {{ options?.needPointsCount }}
+              </div>
+              <div class="ml-1 text-white/60 text-sm font-normal font-['PingFang_SC']">积分</div>
+            </view>
+            <tilted-button custom-class="" size="large" color="white">
+              {{
+                options?.ifSingnUp
+                  ? '已报名'
+                  : getActivityStatusButtonText(options?.applyStartTime, options?.applyEndTime)
+              }}
+            </tilted-button>
+          </view>
+        </div>
+      </view>
+    </Card>
+  </div>
+</template>

+ 5 - 2
packages/app/src/pages/home/study-tour/components/study-tour-card.vue

@@ -8,7 +8,10 @@ import { NetImages } from '../../../../core/libs/net-images'
 import ActivityCountDown from '../../components/activity-count-down.vue'
 import { getActivityStatusButtonText, getActivityStatusText } from '../../../../core/utils/common'
 
-const props = defineProps<{ customClass?: string; options?: StudyTour & { levelsByMemberLevel } }>()
+const props = defineProps<{
+  customClass?: string
+  options?: StudyTour & { levelsByMemberLevel; index }
+}>()
 
 const router = useRouter()
 const toDetail = () => {
@@ -26,7 +29,7 @@ const toDetail = () => {
       <div class="flex gap-1">
         <wd-img width="23" height="23" :src="map"></wd-img>
         <div class="text-white text-base font-normal font-['PingFang_SC'] leading-relaxed">
-          第
+          第{{ options?.index + 1 }}
         </div>
         <div class="flex-1"></div>
         <div class="bg-[#f3f3f3] rounded-[20px] backdrop-blur-[15px] px-4 py-1.5">

+ 44 - 13
packages/app/src/pages/home/study-tour/index.vue

@@ -12,14 +12,23 @@ import MomentItem from '@/components/moment-item.vue'
 import SectionHeading from '@/components/section-heading.vue'
 import ClassItem from '../components/class-item.vue'
 import TimeLine from './components/time-line.vue'
-import { getBanners, getCircles, getContents } from '../../../core/libs/requests'
+import {
+  getAppMemberLevelConfigs,
+  getBanners,
+  getCircles,
+  getContents,
+  getStudyTours,
+} from '../../../core/libs/requests'
 import { NetImages } from '../../../core/libs/net-images'
 import { BannerMode } from '../../../core/libs/models'
 import { useRouter } from '../../../core/utils/router'
 import PageHelper from '@/components/page-helper.vue'
+import RegisterCard from './components/register-card.vue'
 
 const router = useRouter()
-const { data: studyTours, run: setStudyTours } = useRequest(() => getContents({ contentType: '1' }))
+const { data: studyTours, run: setStudyTours } = useRequest(() =>
+  getStudyTours({ headRecommend: 1 }),
+)
 const { data: classmates, run: setClassmates } = useRequest(
   () => getContents({ contentCategory: '101', pageSize: '2' }),
   { initialData: { list: [] } },
@@ -28,10 +37,17 @@ const { data: banners, run: setBanners } = useRequest(
   () => getBanners({ mode: BannerMode.StudyTour }),
   { initialData: [] },
 )
+const { data: levels, run: setLevels } = useRequest(() => getAppMemberLevelConfigs(), {
+  initialData: [],
+})
+const levelsByMemberLevel = computed(() =>
+  levels.value.reduce((acc, item) => {
+    acc[item.memberLevel] = item
+    return acc
+  }, {}),
+)
 onMounted(async () => {
-  await setStudyTours()
-  await setClassmates()
-  await setBanners()
+  await Promise.all([setLevels(), setStudyTours(), setClassmates(), setBanners()])
 })
 </script>
 <template>
@@ -39,15 +55,30 @@ onMounted(async () => {
     <div class="">
       <!-- <TimeLine></TimeLine> -->
       <template v-if="banners.length">
-        <wd-img
-          width="100%"
-          height="100%"
-          custom-class="aspect-[1.73/1]"
-          :src="banners[0].bannerImgUrl"
-          @click="router.push(`/pages/home/study-tour/list`)"
-        ></wd-img>
+        <div class="aspect-[1.73/1]">
+          <wd-img
+            width="100%"
+            height="100%"
+            custom-class="aspect-[1.73/1]"
+            :src="banners[0].bannerImgUrl"
+            @click="router.push(`/pages/home/study-tour/list`)"
+          ></wd-img>
+        </div>
       </template>
     </div>
+    <!-- <PageHelper :request="getActivities" :query="{ headRecommend: 1 }">
+      <template #default="{ source }"> -->
+    <template v-if="studyTours.list.length">
+      <swiper class="aspect-[0.75/1] rounded-[20px] overflow-hidden">
+        <template v-for="(it, i) in studyTours.list" :key="i">
+          <swiper-item>
+            <RegisterCard :options="{ ...it, levelsByMemberLevel }"></RegisterCard>
+          </swiper-item>
+        </template>
+      </swiper>
+    </template>
+    <!-- </template>
+    </PageHelper> -->
     <!-- <card custom-class="p-0!">
       <view class="relative">
         <wd-img
@@ -112,7 +143,7 @@ onMounted(async () => {
         筑巢荟-设计游学
       </div>
       <div
-        class="w-[319px] h-[264px] text-justify text-black/40 text-base font-normal font-['PingFang_SC'] leading-relaxed"
+        class="text-justify text-black/40 text-base font-normal font-['PingFang_SC'] leading-relaxed"
       >
         我们为您精心打造了一个独特且极具价值的游学项目。这个项目的核心旨在全方位提升您作为设计师的能力。
         在这里,您将拥有无比优质的游学资源。我们与全球知名的设计学府、顶尖设计工作室以及具有代表性的经典建筑和室内空间建立了紧密合作。您将有机会深入这些卓越的场所,亲身体验最前沿的设计理念和实践。

+ 1 - 1
packages/app/src/pages/home/study-tour/list.vue

@@ -42,7 +42,7 @@ onMounted(async () => {
             <div class="mx--2.5 my--2.5">
               <StudyTourCard
                 custom-class=""
-                :options="{ ...it, levelsByMemberLevel }"
+                :options="{ ...it, levelsByMemberLevel, index }"
               ></StudyTourCard>
             </div>
           </template>

+ 99 - 11
packages/app/src/pages/messages/index.vue

@@ -14,26 +14,36 @@ import PageHelper from '@/components/page-helper.vue'
 import {
   deleteMessage,
   getMessages,
+  getPointsCoupons,
   orderPointsCancel,
   orderPointsSubmit,
   updateMessage,
 } from '../../core/libs/requests'
-import { integral, interact, system } from '../../core/libs/svgs'
+import { integral, interact, message, system } from '../../core/libs/svgs'
 import { beforeNow } from '../../utils/date-util'
 import dayjs from 'dayjs'
 import { MessageType } from '../../core/libs/enums'
-import { group } from 'radash'
 import { ComponentExposed } from 'vue-component-type-helpers'
 import { Message } from '../../core/libs/models'
 import { requestToast } from '../../core/utils/common'
+import { useUserStore } from '../../store'
+import { storeToRefs } from 'pinia'
 
 const pageHelperRef = ref<ComponentExposed<typeof PageHelper>>()
+const userStore = useUserStore()
+const { userInfo } = storeToRefs(userStore)
+const show = ref(false)
+const businessId = ref()
 const tab = ref(0)
 const tabs = ref([
   { label: '积分消息', value: MessageType.Integral },
   { label: '系统消息', value: MessageType.System },
   { label: '互动消息', value: MessageType.Interact },
 ])
+const selectedCoupon = ref()
+const { data: coupons, run: setCoupons } = useRequest(() =>
+  getPointsCoupons({ userId: userInfo.value.userId, businessId: businessId.value }),
+)
 onShow(async () => {
   nextTick(() => {
     pageHelperRef.value?.refresh()
@@ -49,14 +59,32 @@ const handleCancel = async (message: Message) => {
   await pageHelperRef.value?.refresh()
 }
 const handleSubmit = async (message: Message) => {
-  await requestToast(() => orderPointsSubmit({ id: message.businessId.toString() }), {
-    success: true,
-    successTitle: '积分已确认',
-  })
+  await requestToast(
+    () =>
+      orderPointsSubmit({
+        id: message.businessId,
+        userId: message.designerId,
+        couponUserId: 19,
+      }),
+    {
+      success: true,
+      successTitle: '积分已确认',
+    },
+  )
   // await deleteMessage(message.id.toString())
   await updateMessage({ id: message.id, isRead: '1' })
   await pageHelperRef.value?.refresh()
 }
+const handleQ = async (message: Message) => {
+  // const { data } = await getPointsCoupons({
+  //   userId: message.designerId,
+  //   businessId: message.businessId,
+  // })
+  // console.log(data)
+  businessId.value = message.businessId
+  await setCoupons()
+  show.value = true
+}
 </script>
 
 <template>
@@ -70,7 +98,7 @@ const handleSubmit = async (message: Message) => {
       ref="pageHelperRef"
       :automatic="false"
       :request="getMessages"
-      :query="{ messageType: tabs[tab]?.value.toString() }"
+      :query="{ messageType: tabs[tab]?.value }"
       class="flex-grow flex flex-col"
     >
       <template #default="{ source }">
@@ -108,9 +136,30 @@ const handleSubmit = async (message: Message) => {
                     class="my-3 text-black/40 text-sm font-normal font-['PingFang_SC'] leading-[25px]"
                   >
                     <div
-                      v-if="it.messageType === MessageType.Integral.toString()"
+                      v-if="it.messageType === MessageType.Integral"
                       v-html="it.detailBody"
                     ></div>
+                    <div class="grid grid-cols-[auto_1fr] gap-x-5 gap-y-4.5">
+                      <template v-if="true">
+                        <div
+                          class="text-black/40 text-sm font-normal font-['PingFang_SC'] h-5.5 flex items-center"
+                        >
+                          积分券
+                        </div>
+                        <div class="flex items-center">
+                          <div class="text-black/60 text-sm font-normal font-['PingFang_SC']">
+                            无可用
+                          </div>
+                          <div class="h-5.5 overflow-hidden">
+                            <wd-icon
+                              name="chevron-right"
+                              custom-class="text-black/60!"
+                              size="20"
+                            ></wd-icon>
+                          </div>
+                        </div>
+                      </template>
+                    </div>
                     <!-- {{ it.detailBody }} -->
                   </div>
                 </div>
@@ -125,7 +174,7 @@ const handleSubmit = async (message: Message) => {
                     <template
                       v-if="
                         [MessageType.Integral].includes(Number(it.messageType)) &&
-                        it.messageSubType === '31' &&
+                        it.messageSubType === 31 &&
                         it.isRead !== '1'
                       "
                     >
@@ -145,7 +194,7 @@ const handleSubmit = async (message: Message) => {
                     <template
                       v-else-if="
                         [MessageType.Integral].includes(Number(it.messageType)) &&
-                        it.messageSubType === '22'
+                        it.messageSubType === 22
                       "
                     >
                       如有问题请您联系官方客服!
@@ -162,7 +211,7 @@ const handleSubmit = async (message: Message) => {
                   class="row-start-6 col-start-1 col-end-4 my-1"
                   v-if="
                     [MessageType.Integral].includes(Number(it.messageType)) &&
-                    it.messageSubType === '31' &&
+                    it.messageSubType === 31 &&
                     it.isRead !== '1'
                   "
                 >
@@ -173,6 +222,9 @@ const handleSubmit = async (message: Message) => {
                       </wd-button>
                     </div>
                     <div class="flex-1">
+                      <wd-button block :round="false" @click="handleQ(it)">积分券</wd-button>
+                    </div>
+                    <div class="flex-1">
                       <wd-button block :round="false" @click="handleSubmit(it)">确认</wd-button>
                     </div>
                   </div>
@@ -183,6 +235,42 @@ const handleSubmit = async (message: Message) => {
         </div>
       </template>
     </PageHelper>
+    <wd-action-sheet title="优惠券" v-model="show">
+      <view class="">
+        <wd-tabs>
+          <wd-tab title="可用优惠券"></wd-tab>
+          <wd-tab title="不可用优惠券"></wd-tab>
+        </wd-tabs>
+        <div class="bg-[#f6f6f6] p-3.5">
+          <template v-for="(it, i) in coupons" :key="i">
+            <div
+              class="bg-white rounded-2xl p-3.5 flex gap-2.5"
+              @click="(selectedCoupon = it), (show = false)"
+            >
+              <div class="w-[94px] h-[94px] bg-[#f6f6f6] rounded-[10px]"></div>
+              <div class="flex flex-col justify-around flex-1">
+                <div class="text-black text-sm font-normal font-['PingFang_SC'] leading-normal">
+                  {{ it.couponName }}
+                </div>
+                <div class="text-black/40 text-xs font-normal font-['PingFang_SC'] leading-normal">
+                  <!-- 2024/04/01-2024/05/30 -->
+                  {{ dayjs(it.validityStartDate).format('YYYY/MM/DD') }}-{{
+                    dayjs(it.validityEndDate).format('YYYY/MM/DD')
+                  }}
+                </div>
+                <div class="text-black/40 text-xs font-normal font-['PingFang_SC'] leading-normal">
+                  使用说明
+                </div>
+              </div>
+              <div class="flex items-center">
+                <div class="w-4 h-4 rounded-full border border-black/60 border-solid"></div>
+              </div>
+            </div>
+          </template>
+        </div>
+        <!-- <wd-button block :round="false">确认</wd-button> -->
+      </view>
+    </wd-action-sheet>
   </view>
 </template>
 

+ 60 - 24
packages/app/src/pages/mine/coupons/index.vue

@@ -3,37 +3,73 @@
 </route>
 <script setup lang="ts">
 import Card from '@/components/card.vue'
+import { getCoupons } from '../../../core/libs/requests'
+import PageHelper from '@/components/page-helper.vue'
+import dayjs from 'dayjs'
 
-const tab = ref()
-const tabs = ref([{ label: '商品优惠券' }, { label: '销售积分券' }])
-const data = ref([{}])
+const tab = ref(2)
+const tabs = ref([
+  { label: '商品优惠券', value: 2 },
+  { label: '销售积分券', value: 1 },
+])
+const query = computed(() => ({ couponType: tab.value }))
+// const {data, run: set} = useRequest(() => getCoupons({}))
 </script>
 <template>
   <div class="flex-grow flex flex-col gap-4">
     <wd-tabs v-model="tab">
-      <block v-for="({ label }, i) in tabs" :key="i">
-        <wd-tab :title="label"></wd-tab>
+      <block v-for="({ label, value }, i) in tabs" :key="i">
+        <wd-tab :title="label" :name="value"></wd-tab>
       </block>
     </wd-tabs>
-    <template v-for="(it, i) of data" :key="i">
-      <Card custom-class="mx-3.5">
-        <div class="flex gap-3">
-          <div class="w-[94px] h-[94px] bg-[#f6f6f6] rounded-[10px]"></div>
-          <div class="flex flex-col justify-around">
-            <div class="text-black text-sm font-normal font-['PingFang_SC'] leading-normal">
-              GELATO咖啡兑换券
-            </div>
-            <div
-              class="text-center text-black/40 text-xs font-normal font-['PingFang_SC'] leading-normal"
-            >
-              有效期:2024/04/01-2024/05/30
-            </div>
-            <div class="text-black/40 text-xs font-normal font-['PingFang_SC'] leading-normal">
-              使用说明
-            </div>
-          </div>
+    <PageHelper :request="getCoupons" :query="query">
+      <template #default="{ source }">
+        <div class="flex flex-col gap-4">
+          <template v-for="(it, i) of source.list" :key="i">
+            <Card custom-class="mx-3.5">
+              <div class="flex gap-3">
+                <div class="w-[94px] h-[94px] bg-[#f6f6f6] rounded-2.5 overflow-hidden">
+                  <template v-if="it.couponType === 1">
+                    <div
+                      class="bg-[#fff8f8] w-full h-full flex flex-col items-center justify-center"
+                    >
+                      <div class="text-[#ff7878] text-[26px] font-normal font-['PingFang_SC']">
+                        {{ it.brandPoints }}
+                      </div>
+                      <div class="text-[#ff7878] text-base font-normal font-['PingFang_SC']">
+                        积分
+                      </div>
+                    </div>
+                  </template>
+                  <template v-else>
+                    <wd-img width="100%" height="100%" :src="it.couponImgUrl"></wd-img>
+                  </template>
+                </div>
+                <div class="flex flex-col justify-around">
+                  <div class="text-black text-sm font-normal font-['PingFang_SC'] leading-normal">
+                    <!-- GELATO咖啡兑换券 -->
+                    {{ it.couponName }}
+                  </div>
+                  <div
+                    class="text-center text-black/40 text-xs font-normal font-['PingFang_SC'] leading-normal"
+                  >
+                    有效期:
+                    <!-- 2024/04/01-2024/05/30 -->
+                    {{ dayjs(it.validityStartDate).format('YYYY/MM/DD') }}-{{
+                      dayjs(it.validityEndDate).format('YYYY/MM/DD')
+                    }}
+                  </div>
+                  <div
+                    class="text-black/40 text-xs font-normal font-['PingFang_SC'] leading-normal"
+                  >
+                    使用说明
+                  </div>
+                </div>
+              </div>
+            </Card>
+          </template>
         </div>
-      </Card>
-    </template>
+      </template>
+    </PageHelper>
   </div>
 </template>

+ 36 - 2
packages/app/src/pages/mine/homepage/index.vue

@@ -14,6 +14,7 @@ import {
   getDesignerInfo,
   getUserInfoById,
   updateDesignerInfo,
+  shareCircle,
 } from '../../../core/libs/requests'
 import { useUserStore } from '../../../store'
 import { storeToRefs } from 'pinia'
@@ -27,6 +28,7 @@ import { requestToast } from '../../../core/utils/common'
 import { ComponentExposed } from 'vue-component-type-helpers'
 import dayjs from 'dayjs'
 import wechatChannels from '@designer-hub/assets/src/libs/assets/wechatChannels'
+import { handleUpvoteClick } from '@/core/libs/actions'
 
 const { alert, confirm } = useMessage()
 const router = useRouter()
@@ -83,6 +85,14 @@ const handleMomentDelete = async (id) => {
     },
   })
 }
+const handleLike = async (options) => {
+  await handleUpvoteClick({
+    ...options,
+    userId: userInfo.value.userId,
+    userName: userInfo.value.nickname,
+  })
+  pageHelperRef.value?.refresh()
+}
 const handle2Video = () => {
   try {
     uni.openChannelsUserProfile({ finderUserName: designerInfo.value?.videoNumber })
@@ -138,7 +148,27 @@ onUnload(async () => {
     })
   }
 })
-onShareAppMessage(() => ({ title: `${userInfo.value.nickname}` }))
+// onShareAppMessage(() => ({
+//   title: `${userInfo.value.nickname}: “${designerInfo.value.designDesc}”`,
+//   imageUrl: designerInfo.value?.sharePageUrl,
+//   path: `/pages/mine/homepage/index?id=${id.value}`,
+// }))
+onShareAppMessage(async ({ from, target }) => {
+  const res: Page.CustomShareContent = {}
+  if (from === 'button') {
+    await shareCircle(target.id)
+    res.path = `/pages/home/moment/index?id=${target.id}`
+    res.imageUrl = target.dataset.options.bannerUrls[0]
+    res.title = `${target.dataset.options.stylistName}: ${target.dataset.options.circleDesc}`
+  }
+  if (from === 'menu') {
+    res.title = `${userInfo.value.nickname}: “${designerInfo.value.designDesc}”`
+    res.imageUrl = designerInfo.value?.sharePageUrl
+    res.path = `/pages/mine/homepage/index?id=${id.value}`
+  }
+  return res
+})
+onShareTimeline(() => ({}))
 defineExpose({
   navBarFixed: false,
 })
@@ -218,7 +248,10 @@ defineExpose({
           <div class="w-[37.01px] h-[37.01px] bg-[#fa9d3b] rounded-lg">
             <wd-img width="100%" height="100%" :src="wechatChannels"></wd-img>
           </div>
-          <div v-if="(designerInfo?.videoNumber ?? '') !== ''" @click.stop="handleUnbundle">
+          <div
+            v-if="isOwn && (designerInfo?.videoNumber ?? '') !== ''"
+            @click.stop="handleUnbundle"
+          >
             <div
               class="text-[#da7e1e] text-[9px] font-normal font-['PingFang_SC'] leading-normal flex items-center"
             >
@@ -280,6 +313,7 @@ defineExpose({
                     :options="it"
                     :is-own="userInfo.userId === it.stylistId"
                     @delete="handleMomentDelete"
+                    @like="handleLike"
                   ></MomentItem>
                 </view>
               </template>

+ 1 - 1
packages/app/src/pages/mine/homepage/statistics/index.vue

@@ -73,7 +73,7 @@ onMounted(async () => {
                   class="flex-1 text-black text-sm font-normal font-['PingFang_SC'] leading-normal"
                 >
                   <!-- 银色飞行船 -->
-                  {{ it.stylistName }}
+                  {{ it.creatorName }}
                 </div>
                 <div class="text-black/40 text-xs font-normal font-['PingFang SC'] leading-normal">
                   浏览时长:{{ (Number(it.duration) / 60).toFixed(2) }}分钟

+ 18 - 6
packages/app/src/pages/mine/index.vue

@@ -24,10 +24,12 @@ import { useRouter } from '../../core/utils/router'
 import { NetImages } from '../../core/libs/net-images'
 import { qrCodeString2Object, requestToast, toQrCodeString } from '../../core/utils/common'
 import { QrCodeBusinessType } from '../../core/libs/enums'
+import { usePermissions } from '@/composables/permissions'
 
 const router = useRouter()
 const userStore = useUserStore()
 const { isLogined, userInfo } = storeToRefs(userStore)
+const { features } = usePermissions()
 const { setUserInfo } = userStore
 const { data, run } = useRequest(getMemberUserInfo)
 const { data: taskData, run: getTaskData } = useRequest(() => getTasks({}), {
@@ -47,13 +49,16 @@ const { data: taskStatusData, run: setTaskStatus } = useRequest(
       {
         btnProps: {
           content: '去打卡',
-          onClick: () => uni.showToast({ title: '请到店扫码完成', icon: 'none' }),
+          onClick: () =>
+            features.value.checkInAtStoreTask
+              ? uni.showToast({ title: '请到店扫码完成', icon: 'none' })
+              : router.push(`/pages/mine/authentication/index`),
         },
       },
       {
         btnProps: {
           content: '去邀请',
-          onClick: () => uni.showToast({ title: '敬请期待', icon: 'none' }),
+          onClick: () => router.push('/pages/mine/invite/index'),
         },
       },
       {
@@ -152,8 +157,8 @@ const nickNameClickHandle = async () => {
   uni.navigateTo({ url: '/pages/login/index' })
 }
 const handleToAuthentication = () => {
-  if (!isLogined.value) return
-  uni.navigateTo({ url: '/pages/mine/authentication/index' })
+  if (!isLogined.value) return router.push('/pages/login/index')
+  router.push('/pages/mine/authentication/index')
 }
 const handleToHomepage = () => {
   uni.navigateTo({ url: '/pages/mine/homepage/index' })
@@ -170,6 +175,7 @@ const handleClickScan = async () => {
   // console.log(qrCodeString2Object(toQrCodeString('到店', { a: 1, orderId: 2222 })))
   const { type, options } = qrCodeString2Object(result)
   if (type === QrCodeBusinessType.InStoreClockIn) {
+    if (!features.value.checkInAtStoreTask) return router.push('/pages/mine/authentication/index')
     try {
       await storeAndPunchIn({ id: options.id })
       router.push(`/pages/mine/scan/result/index?result=${result}`)
@@ -282,8 +288,9 @@ onPageScroll(({ scrollTop }: { scrollTop: number }) => {
 
         <div class="flex items-center">
           <div class="text-white text-base font-normal font-['PingFang_SC'] leading-normal mr-1">
-            0
+            <!-- 0 -->
             <!-- {{designerInfo.c}} -->
+            {{ designerInfo?.winCustomerCount || 0 }}
           </div>
           <div
             class="text-center text-[#e9e7e4] text-xs font-normal font-['PingFang_SC'] leading-normal"
@@ -325,11 +332,16 @@ onPageScroll(({ scrollTop }: { scrollTop: number }) => {
             @click="handleToAuthentication()"
           >
             <div
-              class="text-[#9e5934] text-[13px] font-normal font-['PingFang_SC'] leading-relaxed"
+              class="text-[#9e5934] text-[13px] font-normal font-['PingFang_SC'] leading-relaxed flex items-center gap-1"
             >
               <!-- 去认证 -->
               <!-- {{ !isCertified ? '去认证' : certificationStatusText }} -->
               {{ certificationBtnText }}
+              <div
+                class="w-3.5 h-3.5 bg-gradient-to-tl from-[#773b19] to-[#9e5a34] rounded-full flex items-center justify-center"
+              >
+                <wd-icon name="play" size="12" color="#ffead2"></wd-icon>
+              </div>
             </div>
           </div>
         </div>

+ 179 - 0
packages/app/src/pages/mine/invite/index.vue

@@ -0,0 +1,179 @@
+<route lang="json">
+{
+  "style": {
+    "navigationBarTitleText": "邀请设计师",
+    "navigationBarBackgroundColor": "#fff",
+    "navigationStyle": "custom"
+  }
+}
+</route>
+<script setup lang="ts">
+import SectionHeading from '@/components/section-heading.vue'
+import { getDesignerInfo, updateDesignerInfo } from '../../../core/libs/requests'
+import { useUserStore } from '../../../store'
+import { storeToRefs } from 'pinia'
+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 } from '../../../core/utils/canvas'
+
+const { alert } = useMessage()
+const userStore = useUserStore()
+const { userInfo } = storeToRefs(userStore)
+const form = ref<{
+  userId?: number
+  videoNumber?: string
+}>()
+const { data, run: setData } = useRequest(() => getDesignerInfo(userInfo.value.userId))
+const { loading, run: submiting } = useRequest(() => updateDesignerInfo(form.value))
+const posterUrl = ref()
+const canvasHidden = ref(false)
+const createPoster = () => {
+  return new Promise((resolve, reject) => {
+    ;(async () => {
+      uni.showLoading({ title: '生成中' })
+      const [path, bgPath, logoPath, avatarPath, icon1, icon2, icon3, icon4] = await Promise.all(
+        [
+          data.value.sharePageUrl,
+          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
+      }
+      uni
+        .createSelectorQuery()
+        .select('#firstCanvas')
+        .fields({ size: true }, async ({ width: w, height: h }: any) => {
+          const canvas = new Canvas(ctx, { width: w, height: h }, { width: 319 })
+          canvas.FillImage(bgPath)
+          ctx.drawImage(path, 0, 0, w, w / 1.56)
+          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)
+
+          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({
+              canvasId: 'firstCanvas',
+              width: 300,
+              height: 460,
+              success: (res) => {
+                // console.log('生成海报', res)
+                uni.hideLoading()
+                resolve(res.tempFilePath)
+              },
+              fail: (err) => {
+                // uni.hideLoading()
+                reject(err)
+              },
+            })
+          })
+        })
+        .exec()
+    })()
+  })
+}
+const save = async () => {
+  await uni.saveImageToPhotosAlbum({ filePath: posterUrl.value })
+  uni.showToast({ title: '已保存相册', icon: 'none' })
+}
+const share = () => {
+  // uni.share({
+  //   provider: 'weixin',
+  //   scene: 'WXSceneSession',
+  //   type: 2,
+  //   // title: '你好呀',
+  //   // href: 'https://www.baidu.com/',
+  //   // summary: '我是图文描述',
+  //   imageUrl: posterUrl.value,
+  //   success: function (res) {
+  //     console.log('success:' + JSON.stringify(res))
+  //   },
+  //   fail: function (err) {
+  //     console.log('fail:' + JSON.stringify(err))
+  //   },
+  // })
+}
+onMounted(async () => {
+  await setData()
+  form.value = pick(data.value, ['id', 'userId', 'videoNumber'])
+  posterUrl.value = await createPoster()
+})
+// onAppShare(() => {})
+</script>
+<template>
+  <div
+    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="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>
+      </div>
+    </div>
+    <!-- <SectionHeading title="如何关联视频号?" size="sm"></SectionHeading>
+    <img class="w-[347px] h-[186px] rounded-2xl" src="https://via.placeholder.com/347x186" />
+    <SectionHeading title="视频号ID" size="sm"></SectionHeading>
+    <div class="bg-[#f6f6f6] rounded-lg px-3.5 py-2.5">
+      <wd-input
+        v-model="form.videoNumber"
+        placeholder="请输入视频号ID"
+        no-border
+        custom-class="bg-[#f6f6f6]!"
+      ></wd-input>
+    </div>
+    <BottomAppBar fixed :border="false">
+      <div>
+        <div class="text-center mb-5.5">
+          <span class="text-black/40 text-xs font-normal font-['PingFang SC'] leading-tight">
+            点击确认关联即表示同意
+          </span>
+          <span class="text-[#0cbe7c] text-xs font-normal font-['PingFang SC'] leading-tight">
+            《个人微信视频号授权使用协议》
+          </span>
+        </div>
+        <wd-button :round="false" block :loading="loading" @click="handleSubmit">
+          确定关联
+        </wd-button>
+      </div>
+    </BottomAppBar> -->
+  </div>
+</template>

+ 4 - 5
packages/app/src/pages/mine/levels/index.vue

@@ -10,6 +10,7 @@ import LevelCard from './components/level-card.vue'
 import { getAppMemberLevelConfigs } from '../../../core/libs/requests'
 import { useUserStore } from '../../../store'
 import { storeToRefs } from 'pinia'
+import { sort } from 'radash'
 
 const router = useRouter()
 const userStore = useUserStore()
@@ -24,7 +25,7 @@ const handleSwiperChange = ({ detail }) => {
 }
 onMounted(async () => {
   await setLevelConfigs()
-  current.value = levelConfigs.value?.findIndex(
+  current.value = sort(levelConfigs.value, (it) => it.memberLevel)?.findIndex(
     (it) => it.memberLevel === userInfo.value?.level?.level,
   )
 })
@@ -60,7 +61,7 @@ onMounted(async () => {
           previous-margin="24"
           next-margin="24"
         >
-          <template v-for="(it, i) in levelConfigs" :key="i">
+          <template v-for="(it, i) in sort(levelConfigs, (it) => it.memberLevel)" :key="i">
             <swiper-item class="">
               <div
                 class="px-1.25 h-full box-border"
@@ -70,9 +71,7 @@ onMounted(async () => {
                   class="w-full h-full bg-[length:100%_100%]"
                   :style="{ backgroundImage: `url(${it.memberBgImage})` }"
                 >
-                  <LevelCard
-                    :is-current="userInfo.level?.level === levelConfigs[i].memberLevel"
-                  ></LevelCard>
+                  <LevelCard :is-current="userInfo.level?.level === it.memberLevel"></LevelCard>
                 </div>
               </div>
             </swiper-item>

+ 1 - 1
packages/app/src/pages/mine/orders/index.vue

@@ -54,7 +54,7 @@ const handleClick = (order: PointsOrder) => {
                   </div>
                   <div>
                     <div
-                      v-if="it.gainType === 1"
+                      v-if="it.gainType === 1 && it.orderStatus === '0'"
                       class="w-[58px] h-[26px] bg-[#020202] rounded-[20px] backdrop-blur-[10px] flex items-center justify-center"
                       @click.stop="router.push(`/pages/mine/orders/code/index?id=${it.id}`)"
                     >

+ 13 - 6
packages/app/src/pages/mine/scan/result/index.vue

@@ -11,6 +11,8 @@ import { useRouter } from '../../../../core/utils/router'
 import { QrCodeBusinessType } from '../../../../core/libs/enums'
 import { success } from '../../../../core/libs/svgs'
 import SectionHeading from '@/components/section-heading.vue'
+import BottomAppBar from '@/components/bottom-app-bar.vue'
+import { NetImages } from '@/core/libs/net-images'
 
 const router = useRouter()
 const userStore = useUserStore()
@@ -71,32 +73,37 @@ onLoad(async (query: { result: string }) => {
 })
 </script>
 <template>
-  <div class="flex-grow flex flex-col items-center justify-center">
+  <div class="flex-grow flex flex-col items-center justify-center bg-white">
     <template v-if="isPointsCheckout">
-      <div class="flex items-center">
+      <div class="w-full flex items-center box-border py-5.5 px-3.5 gap-2.5">
         <wd-img
           class="rounded-full"
           width="45"
           height="45"
           custom-class="border border-[#f2f2f2] border-solid"
-          :src="data?.avatar"
+          :src="data?.avatar ? 'https://' + data?.avatar : NetImages.DefaultAvatar"
         />
         <div class="text-black/90 text-base font-normal font-['PingFang_SC'] leading-[10.18px]">
           {{ data?.name }}
         </div>
       </div>
-      <div>
+      <div class="flex items-end">
         <div class="text-black/90 text-3xl font-normal font-['PingFang_SC'] leading-none">¥</div>
         <div class="text-black/90 text-[50px] font-medium font-['DIN'] leading-none">
           {{ data?.amount }}
         </div>
       </div>
-      <div>
+      <div class="mt-8">
         <div class="text-black/40 text-base font-normal font-['PingFang_SC'] leading-none">
           积分:{{ data?.points }}
         </div>
       </div>
-      <div><wd-button @click="handleSubmit">确认付款</wd-button></div>
+      <div class="flex-1"></div>
+      <BottomAppBar fixed safe-area-inset-bottom>
+        <div class="w-full">
+          <wd-button :round="false" block @click="handleSubmit">确认付款</wd-button>
+        </div>
+      </BottomAppBar>
     </template>
     <template v-else-if="isInStoreClockIn">
       <div class="w-full box-border px-8.25 flex flex-col items-center gap-7">

+ 2 - 3
packages/app/src/pages/mine/setting/index.vue

@@ -6,6 +6,7 @@ import SectionHeading from '@/components/section-heading.vue'
 import { useUserStore } from '../../../store'
 import { storeToRefs } from 'pinia'
 import { updateMemberUserInfo } from '../../../core/libs/requests'
+import { NetImages } from '../../../core/libs/net-images'
 
 const userStore = useUserStore()
 const { userInfo } = storeToRefs(userStore)
@@ -56,9 +57,7 @@ const handleLogout = () => {
           round
           width="97"
           height="97"
-          :src="
-            (userInfo.avatar ?? '') === '' ? 'https://via.placeholder.com/97x97' : userInfo.avatar
-          "
+          :src="(userInfo.avatar ?? '') === '' ? NetImages.DefaultAvatar : userInfo.avatar"
           custom-class="border border-white border-solid"
         ></wd-img>
       </button>

+ 6 - 0
packages/app/src/pages/publish/moment/index.vue

@@ -17,6 +17,8 @@ import DataForm from '@/components/data-form.vue'
 import { zipToObject } from 'radash'
 import { useRouter } from '../../../core/utils/router'
 import BottomAppBar from '@/components/bottom-app-bar.vue'
+import { toast } from '../../../core/utils/common'
+import { messages } from '../../../core/libs/messages'
 
 const router = useRouter()
 const userStore = useUserStore()
@@ -42,6 +44,10 @@ const handleChange = ({ fileList: files }) => {
 }
 const handleSubmit = async () => {
   publishing.value = true
+  if (!fileList.value.length) {
+    toast(messages.moment.imageNotExist)
+    return false
+  }
   const { code, msg } = await createCircle({
     stylistId: userInfo.value.userId,
     stylistName: userInfo.value.nickname,

+ 1 - 0
packages/app/src/types/uni-pages.d.ts

@@ -33,6 +33,7 @@ interface NavigateToOptions {
        "/pages/mine/convention/index" |
        "/pages/mine/coupons/index" |
        "/pages/mine/homepage/index" |
+       "/pages/mine/invite/index" |
        "/pages/mine/levels/index" |
        "/pages/mine/orders/index" |
        "/pages/mine/points/index" |

+ 1 - 1
packages/app/src/utils/index.ts

@@ -1,5 +1,5 @@
 import { pages, subPackages, tabBar } from '@/pages.json'
-import { path } from 'node:path'
+
 const getLastPage = () => {
   // getCurrentPages() 至少有1个元素,所以不再额外判断
   // const lastPage = getCurrentPages().at(-1)

+ 11 - 0
packages/assets/src/assets/grab-now.svg

@@ -0,0 +1,11 @@
+<svg width="106" height="40" viewBox="0 0 106 40" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M14.9799 5.47537C16.6841 2.11642 20.1311 0 23.8977 0H97L82 35H13.0297C7.06212 35 3.19535 28.7021 5.89547 23.3803L14.9799 5.47537Z" fill="black" fill-opacity="0.85"/>
+<path d="M27.98 19.144H36.814V20.446H27.98V19.144ZM36.464 12.284H29.072V10.982H38.004L37.318 16.302H40.048C39.8987 19.578 39.6233 21.5473 39.222 22.21C38.83 22.8727 37.9993 23.204 36.73 23.204L35.134 23.148L34.784 21.86C35.484 21.944 36.1327 21.986 36.73 21.986C37.402 21.986 37.8593 21.7947 38.102 21.412C38.3353 21.02 38.508 19.7507 38.62 17.604H29.8L30.738 12.984L32.096 13.096L31.494 16.302H35.876L36.464 12.284ZM46.656 10.52H48.028V14.734H53.208V16.022H48.028V21.65H54.286V22.938H41.742V21.65H46.656V10.52ZM64.716 10.632C65.444 12.2467 66.6993 13.6607 68.482 14.874L67.754 15.98C67.4087 15.756 67.0867 15.518 66.788 15.266V18.724C66.7787 19.4893 66.3633 19.9 65.542 19.956C65.1687 19.956 64.6647 19.9373 64.03 19.9L63.694 18.696C64.254 18.7427 64.702 18.766 65.038 18.766C65.3553 18.766 65.514 18.6027 65.514 18.276V16.19H62.728V21.272C62.728 21.748 62.9473 21.986 63.386 21.986H65.78C66.3587 21.986 66.7227 21.8693 66.872 21.636C67.012 21.384 67.124 20.8707 67.208 20.096L68.454 20.502C68.314 21.7993 68.0993 22.5787 67.81 22.84C67.5207 23.0827 66.97 23.204 66.158 23.204H62.826C61.8833 23.204 61.412 22.7187 61.412 21.748V15.518C61.1507 15.742 60.8567 15.9707 60.53 16.204L59.816 15.112C61.3933 13.9733 62.6207 12.48 63.498 10.632H64.716ZM66.494 15.014C65.4767 14.118 64.6787 13.1007 64.1 11.962C63.5027 13.1287 62.784 14.146 61.944 15.014H66.494ZM60.11 17.268C59.662 17.5013 59.214 17.7253 58.766 17.94V21.93C58.766 22.854 58.304 23.316 57.38 23.316H56.148L55.854 22.028C56.274 22.0747 56.6287 22.098 56.918 22.098C57.2633 22.098 57.436 21.93 57.436 21.594V18.5C56.82 18.7333 56.2647 18.9293 55.77 19.088L55.448 17.828C56.176 17.6413 56.8387 17.436 57.436 17.212V14.37H55.728V13.096H57.436V10.562H58.766V13.096H60.012V14.37H58.766V16.652C59.3633 16.3533 59.8113 16.12 60.11 15.952V17.268Z" fill="white"/>
+<path d="M82.2462 17.1393L90.6384 0H105.854L98.8591 10.9846H105.854L77 40L88.3667 17.1393H82.2462Z" fill="url(#paint0_linear_460_358)"/>
+<defs>
+<linearGradient id="paint0_linear_460_358" x1="101.516" y1="-1.81577" x2="77.1782" y2="40.1035" gradientUnits="userSpaceOnUse">
+<stop stop-color="#F17112"/>
+<stop offset="1" stop-color="#F0D194"/>
+</linearGradient>
+</defs>
+</svg>

+ 3 - 0
packages/assets/src/assets/lightning.svg

@@ -0,0 +1,3 @@
+<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M9.34359 20.6726C9.29019 20.6726 9.24998 20.6583 9.22329 20.6323C9.03607 20.5781 8.96932 20.472 9.02272 20.3115L10.2662 12.9716H4.85136C4.71771 12.9716 4.6241 12.918 4.57052 12.8111C4.49043 12.705 4.50377 12.5847 4.61073 12.4501L12.9535 1.54044C13.0604 1.37993 13.1807 1.35407 13.3144 1.46017C13.448 1.48686 13.515 1.59468 13.515 1.78104L12.2715 9.1211H17.0446C17.1782 9.1211 17.2718 9.17536 17.3254 9.28146C17.3521 9.38841 17.3521 9.50872 17.3254 9.64254L9.62443 20.5522C9.54416 20.6323 9.45055 20.6726 9.34359 20.6726Z" fill="white"/>
+</svg>

File diff suppressed because it is too large
+ 1 - 0
packages/assets/src/assets/text-great-money.svg


+ 2 - 0
packages/assets/src/libs/assets/grabNow.ts

@@ -0,0 +1,2 @@
+import grabNow from '../../assets/grab-now.svg' 
+ export default grabNow

+ 2 - 0
packages/assets/src/libs/assets/imgBtnBlack-110x44.ts

@@ -0,0 +1,2 @@
+import imgBtnBlack-110x44 from '../../assets/img-btn-black-110x44.svg' 
+ export default imgBtnBlack-110x44

+ 2 - 0
packages/assets/src/libs/assets/lightning.ts

@@ -0,0 +1,2 @@
+import lightning from '../../assets/lightning.svg' 
+ export default lightning

+ 2 - 0
packages/assets/src/libs/assets/textGreatMoney.ts

@@ -0,0 +1,2 @@
+import textGreatMoney from '../../assets/text-great-money.svg' 
+ export default textGreatMoney

File diff suppressed because it is too large
+ 8992 - 1653
pnpm-lock.yaml


Some files were not shown because too many files changed in this diff