Browse Source

feat(mine): 优化会员等级页面

- 重构会员等级页面布局和样式
- 添加会员等级配置请求和处理逻辑
- 实现会员等级卡片组件
- 优化数据加载和状态管理
EvilDragon 4 months ago
parent
commit
6c24a4c8dd

+ 29 - 21
packages/app/src/components/data-form.vue

@@ -8,19 +8,33 @@ const modelValue = defineModel({
   type: Object,
   default: () => ({}),
 })
-defineProps({
-  schema: {
-    type: Object as PropType<{
-      [key: string | symbol]: { type: 'TextField' | 'Submit' | string; label?: string; props?: any }
-    }>,
-    required: true,
-    default: () => ({}),
-  },
-  direction: {
-    type: String as PropType<'horizontal' | 'vertical'>,
-    default: 'vertical',
-  },
-})
+// defineProps({
+//   schema: {
+//     type: Object as PropType<{
+//       [key: string | symbol]: { type: 'TextField' | 'Submit' | string; label?: string; props?: any }
+//     }>,
+//     required: true,
+//     default: () => ({}),
+//   },
+//   direction: {
+//     type: String as PropType<'horizontal' | 'vertical'>,
+//     default: 'vertical',
+//   },
+// })
+withDefaults(
+  defineProps<{
+    schema: {
+      [key: symbol]: {
+        type: 'TextField' | 'Select' | 'Radio' | 'Submit'
+        label?: string
+        existing?: boolean
+        props?: any
+      }
+    }
+    direction?: 'horizontal' | 'vertical'
+  }>(),
+  { direction: 'vertical' },
+)
 const emits = defineEmits(['submit'])
 const form = ref()
 const types = {
@@ -83,10 +97,11 @@ defineExpose({
   <wd-config-provider :theme-vars="themeVars">
     <wd-form ref="form" :model="modelValue">
       <template
-        v-for="([prop, { type, label, props }], index) in Object.entries(schema)"
+        v-for="([prop, { type, label, existing, props }], index) in Object.entries(schema)"
         :key="index"
       >
         <div
+          v-if="existing ?? true"
           class="grid mb-4"
           :class="[direction === 'horizontal' ? 'items-center' : '']"
           :style="
@@ -103,12 +118,6 @@ defineExpose({
           >
             {{ label || prop }}
           </label>
-          <!-- #ifdef H5 -->
-          <component :is="types[type]" :name="prop" v-bind="defaultProps[type]">
-            <span v-if="type === 'Submit'">提交</span>
-          </component>
-          <!-- #endif -->
-          <!-- #ifdef MP-WEIXIN -->
           <wd-input
             v-if="type === 'TextField'"
             v-bind="{
@@ -157,7 +166,6 @@ defineExpose({
           >
             <span v-if="type === 'Submit'">提交</span>
           </wd-button>
-          <!-- #endif -->
         </div>
       </template>
     </wd-form>

+ 26 - 21
packages/app/src/components/navbar-evo.vue

@@ -1,33 +1,38 @@
 <script lang="ts" setup>
 import { ConfigProviderThemeVars } from 'wot-design-uni'
 import { useRouter } from '../core/utils/router'
-import {computed} from 'vue'
+import { computed } from 'vue'
 
-const props = defineProps<{ transparent?: boolean; title?: string; dark?: boolean; placeholder?: boolean }>()
+const props = defineProps<{
+  transparent?: boolean
+  title?: string
+  dark?: boolean
+  placeholder?: boolean
+}>()
 const router = useRouter()
 const themeVars = computed<ConfigProviderThemeVars>(() => ({
   navbarColor: props.dark ? 'white' : 'black',
 }))
 </script>
 <template>
-  <wd-config-provider :themeVars="themeVars">
-    <wd-navbar
-      fixed
-      left-arrow
-      safe-area-inset-top
-      :bordered="false"
-      :custom-class="`${transparent ? 'bg-transparent!' : ''} `"
-      v-bind="{ title }"
-      @click-left="router.back()"
-    ></wd-navbar>
-    <template v-if="props.placeholder">
+  <div>
+    <wd-config-provider :themeVars="themeVars">
       <wd-navbar
-      left-arrow
-      safe-area-inset-top
-      :bordered="false"
-      :custom-class="`${transparent ? 'bg-transparent!' : ''} `"
-      v-bind="{ title }"
-    ></wd-navbar>
-    </template>
-  </wd-config-provider>
+        fixed
+        left-arrow
+        safe-area-inset-top
+        :bordered="false"
+        :custom-class="`${transparent ? 'bg-transparent!' : ''} `"
+        v-bind="{ title }"
+        @click-left="router.back()"
+      ></wd-navbar>
+      <template v-if="props.placeholder">
+        <wd-navbar
+          safe-area-inset-top
+          :bordered="false"
+          :custom-class="`${transparent ? 'bg-transparent!' : ''} `"
+        ></wd-navbar>
+      </template>
+    </wd-config-provider>
+  </div>
 </template>

+ 4 - 4
packages/app/src/components/upload-evo.vue

@@ -16,16 +16,16 @@ const handleChange = ({ fileList: files }) => {
 watch(
   () => fileList,
   () => {
-    console.log(modelValue.value)
+    // console.log(modelValue.value)
   },
 )
 onMounted(() => {
-  console.log(modelValue.value)
+  // console.log(modelValue.value)
 
-  if (typeof modelValue.value === 'string') {
+  if (typeof modelValue.value === 'string' && (modelValue.value ?? '') !== '') {
     fileList.value = [{ url: modelValue.value }]
   }
-  console.log(fileList.value)
+  // console.log(fileList.value)
 })
 </script>
 <template>

+ 34 - 0
packages/app/src/core/libs/requests.ts

@@ -702,6 +702,40 @@ export const getUserInfoById = (id) =>
       experience: number
     }>
   >('/app-api/member/user/getByUserId', { id })
+/**
+ * 获取会员等级配置
+ */
+export const getAppMemberLevelConfigs = () =>
+  httpGet<
+    {
+      id: number
+      memberLevel: number
+      memberLevelName: string
+      upgradeCriteria: number
+      points: number
+      retentionDaysCriteria: number
+      retentionDays: number
+      memberBgImage: string
+      associatedMemberRights: string
+      associatedMemberRightsName: string
+      status: number
+      createTime: string
+      setMemberRights: {
+        createTime: string
+        updateTime: string
+        creator: string
+        updater: string
+        deleted: boolean
+        id: number
+        rightsName: string
+        rightsType: number
+        rightsImage: string
+        rightsDescription: string
+        associatedMemberRightsLevel: string
+        status: number
+      }[]
+    }[]
+  >('/app-api/basicsetting/app-set-member-level-config/listAndRights')
 export const refreshToken = (refreshToken: string) =>
   httpPost<any>('/app-api/member/auth/refresh-token', {}, { refreshToken })
 export const httpGetMock = <T>(data: T) =>

+ 20 - 8
packages/app/src/pages/mine/authentication/index.vue

@@ -36,9 +36,9 @@ const { error } = useToast()
 const formData = ref<any>({})
 const attachment = ref()
 const formInited = ref(false)
-// const a = async () => ({ data: null, code: 0, msg: '' })
+const a = async () => ({ data: null, code: 0, msg: '' })
 const { data: userAuthInfo, run: setUserAuthInfo } = useRequest(() => getUserAuthInfo())
-// const { data: userAuthInfo, run: setUserAuthInfo } = useRequest(() => ())
+// const { data: userAuthInfo, run: setUserAuthInfo } = useRequest(() => a())
 const schema = ref({
   channelSource: {
     type: 'Select',
@@ -48,11 +48,21 @@ const schema = ref({
       placeholder: '请选择通过哪个渠道入驻的筑巢荟',
       columns: [],
       disabled: userAuthInfo.value != null,
+      'onUpdate:modelValue': (value) => {
+        console.log(value)
+
+        if (value === '4') {
+          schema.value.referrer.existing = false
+        } else {
+          schema.value.referrer.existing = true
+        }
+      },
     },
   },
   referrer: {
     type: 'TextField',
     label: '推荐人',
+    existing: true,
     props: {
       labelWidth: '126rpx',
       placeholder: '请如实填写推荐人编号,设计师会员编号或渠道编号',
@@ -96,12 +106,14 @@ const schema = ref({
 const handleSubmit = async () => {
   console.log(formData.value)
   if (!userAuthInfo.value) {
-    const { data, code: status } = await requestToast(() =>
-      validateReferrerCode({ code: formData.value.referrer }),
-    )
-    if (data === false || status !== 0) {
-      uni.showToast({ title: '推荐人编号不正确', icon: 'none' })
-      return
+    if (formData.value.channelSource !== '4') {
+      const { data, code: status } = await requestToast(() =>
+        validateReferrerCode({ code: formData.value.referrer }),
+      )
+      if (data === false || status !== 0) {
+        uni.showToast({ title: '推荐人编号不正确', icon: 'none' })
+        return
+      }
     }
     const { code, msg } = await createUserAuthInfo({
       gender: userInfo.value.sex,

+ 19 - 0
packages/app/src/pages/mine/levels/components/level-card.vue

@@ -0,0 +1,19 @@
+<script setup lang="ts">
+withDefaults(defineProps<{ isCurrent: boolean }>(), { isCurrent: false })
+</script>
+<template>
+  <div class="w-full h-full box-border p-4 flex flex-col">
+    <div class="flex justify-end pt-2">
+      <div class="w-[66px] h-[26px] relative" v-if="isCurrent">
+        <div class="w-[66px] h-[22px] left-0 absolute bg-[#3c556b] rounded-md"></div>
+        <div
+          class="left-[10px] top-0.25 absolute text-center text-[#dfdfdf] text-xs font-normal font-['PingFang_SC'] leading-relaxed"
+        >
+          当前等级
+        </div>
+      </div>
+    </div>
+    <div class="flex-1"></div>
+    <wd-progress :percentage="30" hide-text />
+  </div>
+</template>

+ 61 - 39
packages/app/src/pages/mine/levels/index.vue

@@ -6,50 +6,39 @@ import NavbarEvo from '@/components/navbar-evo.vue'
 import { levels } from '../../../core/libs/levels'
 import { useRouter } from '../../../core/utils/router'
 import { notify } from '@designer-hub/assets/src/assets/svgs'
+import LevelCard from './components/level-card.vue'
+import { getAppMemberLevelConfigs } from '../../../core/libs/requests'
+import { useUserStore } from '../../../store'
+import { storeToRefs } from 'pinia'
+
 const router = useRouter()
+const userStore = useUserStore()
+const { userInfo } = storeToRefs(userStore)
 const current = ref(0)
 const swiperList = ref(levels.map(({ bgImg }) => bgImg))
-const items = ref([
-  {
-    icon: 'https://image.zhuchaohui.com/zhucaohui/95aa37b07a32a571fda86e8f8b89a2e54d1dd36d1250190ed8cfc68d638c3d43.png',
-    title: '会员专属活动',
-    subTitle: '参与平台内的会员活动',
-  },
-  {
-    icon: 'https://image.zhuchaohui.com/zhucaohui/95aa37b07a32a571fda86e8f8b89a2e54d1dd36d1250190ed8cfc68d638c3d43.png',
-    title: '国内外游学项目',
-    subTitle: '参与平台国内外设计游学项目',
-  },
-  {
-    icon: 'https://image.zhuchaohui.com/zhucaohui/95aa37b07a32a571fda86e8f8b89a2e54d1dd36d1250190ed8cfc68d638c3d43.png',
-    title: '专属经纪人',
-    subTitle: '认证成功即可拥有专属经纪人',
-  },
-  {
-    icon: 'https://image.zhuchaohui.com/zhucaohui/95aa37b07a32a571fda86e8f8b89a2e54d1dd36d1250190ed8cfc68d638c3d43.png',
-    title: '销售积分券',
-    subTitle: '得1张200积分的销售积分券,使用时不限制...',
-  },
-])
+const { data: levelConfigs, run: setLevelConfigs } = useRequest(() => getAppMemberLevelConfigs(), {
+  initialData: [],
+})
+const handleSwiperChange = ({ detail }) => {
+  current.value = detail.current
+}
+onMounted(async () => {
+  await setLevelConfigs()
+  current.value = levelConfigs.value?.findIndex(
+    (it) => it.memberLevel === userInfo.value?.level?.level,
+  )
+})
 </script>
 <template>
-  <div class="flex-grow flex flex-col gap-4 bg-gradient-to-b from-[#312c38] to-[#171322] px-3.5">
-    <!-- <wd-navbar
-      left-arrow
-      safe-area-inset-top
-      custom-class="mx--3.5 bg-transparent!"
-      title="会员等级"
-      :bordered="false"
-      @left-click="router.back()"
-    ></wd-navbar> -->
-    <NavbarEvo fixed transparent title="会员等级" dark></NavbarEvo>
+  <div class="flex-grow flex flex-col gap-4 bg-gradient-to-b from-[#312c38] to-[#171322] p-3.5">
+    <NavbarEvo transparent title="会员等级" dark placeholder></NavbarEvo>
     <div class="flex gap-2 bg-gradient-to-r from-[#292331] to-[#35303b] rounded-md px-5 py-1">
       <wd-img width="22" height="22" :src="notify"></wd-img>
       <div class="text-[#f1d2c5] text-sm font-normal font-['PingFang_SC'] leading-normal">
         会员成长等级保级规则通知
       </div>
     </div>
-    <view class="card-swiper mx--3.5">
+    <!-- <view class="card-swiper mx--3.5">
       <wd-swiper
         :autoplay="false"
         v-model:current="current"
@@ -57,23 +46,51 @@ const items = ref([
         custom-image-class="custom-image"
         custom-next-image-class="custom-image-prev"
         custom-prev-image-class="custom-image-prev"
-        :indicator="{ type: 'dots' }"
         :list="swiperList"
         previousMargin="24px"
         nextMargin="24px"
       ></wd-swiper>
-    </view>
-    <template v-for="({ icon, title, subTitle }, index) in items" :key="index">
+    </view> -->
+    <div class="mx--3.5">
+      <div class="my-swiper w-full aspect-[2.22/1] relative">
+        <swiper
+          :current="current"
+          @change="handleSwiperChange"
+          circular
+          previous-margin="24"
+          next-margin="24"
+        >
+          <template v-for="(it, i) in levelConfigs" :key="i">
+            <swiper-item class="">
+              <div
+                class="px-1.25 h-full box-border"
+                :style="{ padding: current !== i ? '10rpx' : '0 10rpx' }"
+              >
+                <div
+                  class="w-full h-full bg-[length:100%_100%]"
+                  :style="{ backgroundImage: `url(${it.memberBgImage})` }"
+                >
+                  <LevelCard
+                    :is-current="userInfo.level?.level === levelConfigs[i].memberLevel"
+                  ></LevelCard>
+                </div>
+              </div>
+            </swiper-item>
+          </template>
+        </swiper>
+      </div>
+    </div>
+    <template v-for="(it, index) in levelConfigs[current].setMemberRights" :key="index">
       <div
         class="bg-[#201d28] rounded-2xl shadow border border-[#504951] border-solid flex gap-4 px-2.5 py-5.5"
       >
-        <wd-img width="50" height="50" :src="icon"></wd-img>
+        <wd-img width="50" height="50" :src="it.rightsImage"></wd-img>
         <div class="flex flex-col justify-between flex-1">
           <div class="text-[#f1d2c5] text-base font-normal font-['PingFang_SC']">
-            {{ title }}
+            {{ it.rightsName }}
           </div>
           <div class="text-white/40 text-xs font-normal font-['PingFang_SC']">
-            {{ subTitle }}
+            {{ it.rightsDescription }}
           </div>
         </div>
         <div></div>
@@ -98,4 +115,9 @@ const items = ref([
     height: 168px !important;
   }
 }
+.my-swiper {
+  :deep(.uni-swiper-slides) {
+    inset: 0 48rpx;
+  }
+}
 </style>