Browse Source

feat(app): 优化商城功能和页面样式

- 添加新的组件:ImgBtnEvo、InputNumberEvo、ProgressEvo、SwiperEvo
- 更新活动详情页面布局和样式
- 实现购物车商品数量增减功能
- 优化商城首页布局和样式- 添加倒计时功能
- 调整主题样式
EvilDragon 3 months ago
parent
commit
bf1b42a45b

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

@@ -0,0 +1,25 @@
+<script setup lang="ts">
+const props = withDefaults(defineProps<{ size?: 'large' | '110x44' }>(), { size: 'large' })
+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', src: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTA1IiBoZWlnaHQ9IjQ0IiB2aWV3Qm94PSIwIDAgMTA1IDQ0IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMTMuMzM1MSA1LjY4MzU2QzE0Ljk5NyAyLjIxMDQ4IDE4LjUwNTMgMCAyMi4zNTU1IDBIOTVDMTAwLjUyMyAwIDEwNSA0LjQ3NzE1IDEwNSAxMFYzNEMxMDUgMzkuNTIyOCAxMDAuNTIzIDQ0IDk1IDQ0SDkuMjg0QzIuNjYxNTYgNDQgLTEuNjkyOTMgMzcuMDg4OSAxLjE2NTYgMzEuMTE1MkwxMy4zMzUxIDUuNjgzNTZaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjg1Ii8+Cjwvc3ZnPgo=' },
+]
+</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,
+      height,
+      backgroundImage: `url(${types.find((it) => props.size === it.size).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>

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

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

@@ -424,6 +424,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)
@@ -457,9 +458,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: {

+ 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',
 }

+ 4 - 4
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',
@@ -255,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>
@@ -368,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>

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

@@ -73,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>

+ 90 - 55
packages/app/src/pages/home/mall/index.vue

@@ -28,6 +28,10 @@ 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 { diff } from 'radash'
+import dayjs from 'dayjs'
 
 const router = useRouter()
 const userStore = useUserStore()
@@ -50,6 +54,13 @@ const { data: favourableProducts, run: setFavourableProducts } = useRequest(
 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,
@@ -58,6 +69,9 @@ const query = computed(() => ({
 const handleChange = async () => {
   setCarts()
 }
+const handleSwiperChange = async (e) => {
+  console.log(e)
+}
 onMounted(async () => {
   await Promise.all([setProductCategories(), setBanners(), setCarts(), setFavourableProducts()])
   // await setProductCategories()
@@ -77,74 +91,87 @@ onMounted(async () => {
           <wd-img width="95" height="25" :src="textGreatMoney"></wd-img>
           <div class="flex-1"></div>
           <div
-            class="text-center text-white text-base font-normal font-['PingFang_SC'] leading-relaxed"
+            class="flex gap-1 text-center text-white text-base font-normal font-['PingFang_SC'] leading-relaxed"
           >
-            17:02:18 结束
+            <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">
-        <swiper class="w-full h-full" v-model:value="current">
-          <template v-for="(it, i) in favourableProducts" :key="i">
-            <swiper-item>
-              <div class="w-full h-full px-4 flex items-center gap-3">
-                <wd-img
-                  width="114"
-                  height="114"
-                  custom-class="rounded-2xl overflow-hidden"
-                  :src="it.productCoverImgUrl"
-                />
-                <div>
+        <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="w-[178px] text-black/90 text-base font-normal font-['PingFang SC'] leading-normal"
+                    class="text-black/40 text-[10px] font-normal font-['PingFang_SC'] leading-normal"
                   >
-                    <!-- 海蓝之谜精华面霜60ml -->
-                    {{ it.prodcutName }}
+                    还剩{{ 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="flex items-center gap-2.5">
-                    <div class="flex-1">
-                      <wd-progress
-                        :percentage="(it.exchangeCount || 0 / it.productRepertory || 0) * 100"
-                        hide-text
-                        color="black"
-                      ></wd-progress>
-                    </div>
-                    <div
-                      class="text-black/40 text-[10px] font-normal font-['PingFang_SC'] leading-normal"
-                    >
-                      还剩{{ it.productRepertory - it.exchangeCount }}件
-                    </div>
+                  <div class="text-black/60 text-sm font-normal font-['PingFang_SC'] pb-3">
+                    积分
                   </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 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 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 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>
-            </swiper-item>
+            </div>
           </template>
-        </swiper>
+        </SwiperEvo>
       </div>
     </div>
     <div class="w-full inline-flex gap-2">
@@ -183,7 +210,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')">

+ 35 - 3
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()
@@ -63,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' })
@@ -125,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"
                     >
@@ -137,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>

+ 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