Browse Source

feat(app): 实现商城产品列表和详情页面

EvilDragon 4 months ago
parent
commit
75e5726fc9

+ 100 - 55
packages/app/src/components/data-form.vue

@@ -3,6 +3,7 @@ import WdButton from 'wot-design-uni/components/wd-button/wd-button.vue'
 import WdInput from 'wot-design-uni/components/wd-input/wd-input.vue'
 import WdPicker from 'wot-design-uni/components/wd-picker/wd-picker.vue'
 import { ref } from 'vue'
+import { ConfigProviderThemeVars } from 'wot-design-uni'
 
 const modelValue = defineModel({
   type: Object,
@@ -53,6 +54,24 @@ const verticalDefaultProps = {
     block: true,
   },
 }
+const horizontalDefaultProps = {
+  TextField: {
+    customClass: 'text-red!',
+    placeholderClass: 'text-black/30',
+  },
+  Select: {
+    customClass: 'text-black/30! border-b-1 border-b-[#e1e1e1] border-b-solid',
+  },
+  Radio: {
+    customClass: 'my--4!',
+  },
+}
+const themeVars: ConfigProviderThemeVars = {
+  cellPadding: '0',
+  cellWrapperPadding: '10rpx',
+  radioButtonRadius: '8rpx',
+  radioButtonBg: 'transparent',
+}
 const submit = () => {
   emits('submit', modelValue)
 }
@@ -62,60 +81,86 @@ defineExpose({
 })
 </script>
 <template>
-  <wd-form ref="form" :model="modelValue">
-    <template
-      v-for="([prop, { type, label, props }], index) in Object.entries(schema)"
-      :key="index"
-    >
-      <div class="grid mb-4">
-        <label
-          v-if="type !== 'Submit' && direction === 'vertical'"
-          class="text-black/40 text-sm font-normal font-['PingFang SC'] leading-relaxed mb-1"
-          :for="prop"
-        >
-          {{ 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="{
-            ...(direction === 'vertical' ? verticalDefaultProps[type] : {}),
-            ...props,
-          }"
-          v-model="modelValue[prop]"
-        ></wd-input>
-        <wd-picker
-          v-if="type === 'Select'"
-          v-bind="{ label, ...props }"
-          v-model="modelValue[prop]"
-        ></wd-picker>
-        <wd-radio-group
-          v-if="type === 'Radio'"
-          v-bind="{ label, ...props, cell: true, shape: 'button' }"
-          v-model="modelValue[prop]"
-        >
-          <template v-for="{ label, value } of props.columns" :key="value">
-            <wd-radio :value="value">{{ label }}</wd-radio>
-          </template>
-        </wd-radio-group>
-        <wd-button
-          v-if="type === 'Submit'"
-          v-bind="{
-            ...(direction === 'vertical' ? verticalDefaultProps[type] : {}),
-            ...props,
-            formType: 'submit',
-          }"
-          @click="submit"
+  <wd-config-provider :theme-vars="themeVars">
+    <wd-form ref="form" :model="modelValue">
+      <template
+        v-for="([prop, { type, label, props }], index) in Object.entries(schema)"
+        :key="index"
+      >
+        <div
+          class="grid mb-4"
+          :class="[direction === 'horizontal' ? 'items-center' : '']"
+          :style="
+            direction === 'horizontal'
+              ? { 'grid-template-columns': `${props.labelWidth} auto` }
+              : {}
+          "
         >
-          <span v-if="type === 'Submit'">提交</span>
-        </wd-button>
-        <!-- #endif -->
-      </div>
-    </template>
-  </wd-form>
+          <label
+            v-if="type !== 'Submit'"
+            class="text-sm font-normal leading-relaxed"
+            :class="[direction === 'horizontal' ? 'text-black/60' : 'mb-1 text-black/40']"
+            :for="prop"
+          >
+            {{ 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="{
+              ...(direction === 'vertical'
+                ? verticalDefaultProps[type]
+                : horizontalDefaultProps[type]),
+              ...props,
+            }"
+            v-model="modelValue[prop]"
+          ></wd-input>
+          <wd-picker
+            v-if="type === 'Select'"
+            v-bind="{
+              ...(direction === 'vertical'
+                ? verticalDefaultProps[type]
+                : horizontalDefaultProps[type]),
+              cell: false,
+              ...props,
+            }"
+            v-model="modelValue[prop]"
+          ></wd-picker>
+          <wd-radio-group
+            v-if="type === 'Radio'"
+            v-bind="{
+              ...(direction === 'vertical'
+                ? verticalDefaultProps[type]
+                : horizontalDefaultProps[type]),
+              ...props,
+              cell: true,
+              shape: 'button',
+            }"
+            v-model="modelValue[prop]"
+          >
+            <template v-for="{ label, value } of props.columns" :key="value">
+              <wd-radio :value="value">{{ label }}</wd-radio>
+            </template>
+          </wd-radio-group>
+          <wd-button
+            v-if="type === 'Submit'"
+            v-bind="{
+              ...(direction === 'vertical' ? verticalDefaultProps[type] : {}),
+              ...props,
+              formType: 'submit',
+            }"
+            @click="submit"
+          >
+            <span v-if="type === 'Submit'">提交</span>
+          </wd-button>
+          <!-- #endif -->
+        </div>
+      </template>
+    </wd-form>
+  </wd-config-provider>
 </template>

+ 43 - 0
packages/app/src/components/page-helper.vue

@@ -0,0 +1,43 @@
+<script setup lang="ts" generic="T extends { list: any }">
+import { PropType } from 'vue'
+import { NetImages } from '../core/libs/net-images'
+
+const props = defineProps({
+  request: {
+    type: Function as PropType<(query: any) => Promise<IResData<T>>>,
+  },
+  query: {
+    type: Object as PropType<any>,
+    default: () => {},
+  },
+})
+const slot = defineSlots<{
+  default(props: { data: T }): any
+  // col(props: { row: T; index: number }): any
+}>()
+const { data, run: setData } = useRequest(() => props.request(props.query), { immediate: false })
+// const data = [{}, {}, {}, {}, {}, {}]
+watch(
+  () => props.query,
+  async (e) => {
+    console.log(e)
+    console.log('watch')
+    await setData()
+  },
+  // { deep: true },
+)
+onMounted(async () => {
+  await setData()
+})
+onReachBottom(() => {
+  console.log(1111)
+})
+</script>
+<template>
+  <div class="flex-grow flex flex-col">
+    <div v-if="!data?.list?.length" class="flex-grow flex items-center justify-center">
+      <wd-status-tip :image="NetImages.NotContent" tip="暂无内容"></wd-status-tip>
+    </div>
+    <slot v-else :data="data.list"></slot>
+  </div>
+</template>

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

@@ -285,6 +285,41 @@ export const getAllCategories = () =>
   httpGet<Category[]>('/app-api/member/categories/getAllCategories')
 export const getContent = (query: { id: string }) =>
   httpGet<Content>('/app-api/member/content-manger/get', query)
+export const getProductCategories = () =>
+  httpGet<Category[]>('/app-api/member/product-type/getAllProductCategories', {})
+export const getProducts = (query: { oneCategory?: string; secondCategory?: string }) =>
+  httpPost<{ list: any[] }>('/app-api/member/product/page', {}, query)
+export const getProduct = (id: string) =>
+  httpGet<{
+    id: number
+    prodcutName: string
+    productId: string
+    oneCategory: any
+    oneCategoryName: any
+    secondCategory: string
+    secondCategoryName: any
+    isRestrict: number
+    productRepertory: any
+    productPrice: string
+    productType: number
+    vendorId: any
+    vendorName: string
+    needPoints: number
+    points: number
+    gainType: number
+    exchangeDesc: string
+    productCoverImgUrl: string
+    productDetailsImgUrl: string
+    contentDesc: string
+    status: number
+    exchangeCount: any
+    memberLevelId: any
+    memberLevelName: any
+    favourablePoints: any
+    favourableEndDate: any
+    createTime: number
+    updateTime: number
+  }>('/app-api/member/product/detail', { productId: id })
 export const refreshToken = (refreshToken: string) =>
   httpPost<any>('/app-api/member/auth/refresh-token', {}, { refreshToken })
 export const httpGetMock = <T>(data: T) =>

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

@@ -2,34 +2,48 @@
 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'
 
+const props = defineProps({
+  options: {
+    type: Object as PropType<any>,
+    default: () => {},
+  },
+})
 const router = useRouter()
 </script>
 <template>
-  <div class="w-full flex flex-col gap-2.5" @click="router.push('/pages/home/mall/detail/index')">
+  <div
+    class="w-full flex flex-col gap-2.5"
+    @click="router.push(`/pages/home/mall/detail/index?id=${options.productId}`)"
+  >
     <div class="bg-[#f6f6f6] rounded-2xl w-full aspect-square overflow-hidden">
       <wd-img
         width="100%"
         height="100%"
-        src="https://via.placeholder.com/116x155"
+        :src="options.productCoverImgUrl"
         mode="aspectFill"
+        custom-class="aspect-square"
       ></wd-img>
     </div>
     <div class="flex">
       <div class="text-black/90 text-base font-normal font-['PingFang SC'] leading-normal">
-        阿芙佳朵
+        <!-- 阿芙佳朵 -->
+        {{ options.prodcutName }}
       </div>
       <div class="flex-1"></div>
       <div
         class="w-[26px] text-black/30 text-xs font-normal font-['PingFang SC'] line-through leading-normal"
       >
-        ¥60
+        <!-- ¥60 -->
+        ¥{{ options.productPrice }}
       </div>
     </div>
     <div class="flex items-center mb-6">
       <div class="flex items-end gap-1">
         <div class="text-[#ef4343] text-[22px] font-normal font-['D-DIN Exp'] leading-5.5">
-          1000
+          <!-- 1000 -->
+          {{ options.points }}
         </div>
         <div class="text-black/60 text-sm font-normal font-['PingFang SC']">积分</div>
       </div>

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

@@ -14,26 +14,34 @@ 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 { useRouter } from '../../../../core/utils/router'
+import { getProduct } from '../../../../core/libs/requests'
 
 const router = useRouter()
 
-const data = ref(['https://via.placeholder.com/347x128'])
+const id = ref()
+// const data = ref(['https://via.placeholder.com/347x128'])
 const products = ref([{}, {}, {}])
 const show = ref(false)
 const a = ref(1)
+const { data, run: setData } = useRequest(() => getProduct(id.value))
+onLoad(async (query: { id: string }) => {
+  id.value = query.id
+  await setData()
+})
 </script>
 
 <template>
   <view class="flex-grow flex flex-col">
     <div class="aspect-[1.34/1] relative">
       <div class="absolute aspect-[1.26/1] top-0 w-full">
-        <wd-img width="100%" height="100%" src="https://via.placeholder.com/375x297" />
+        <wd-img width="100%" height="100%" :src="data.productCoverImgUrl" />
       </div>
     </div>
     <div class="relative flex-1 bg-white p-4 flex flex-col gap-4 rounded-tl-2xl rounded-tr-2xl">
       <div class="flex items-center gap-1">
         <div class="text-[#ef4343] text-[26px] font-normal font-['D-DIN Exp'] leading-normal">
-          1000
+          <!-- 1000 -->
+          {{ data.points }}
         </div>
         <div class="text-black/60 text-base font-normal font-['PingFang SC'] leading-[34px]">
           积分
@@ -41,17 +49,20 @@ const a = ref(1)
         <div
           class="w-[66px] text-black/30 text-xs font-normal font-['PingFang SC'] line-through leading-normal"
         >
-          ¥60
+          <!-- ¥60 -->
+          ¥{{ data.productPrice }}
         </div>
         <div class="flex-1"></div>
         <div class="text-[#999999] text-xs font-normal font-['PingFang SC'] leading-[10.18px]">
-          已售5件
+          <!-- 已售5件 -->
+          已售{{ data.exchangeCount || 0 }}件
         </div>
       </div>
       <div
         class="w-[90px] h-4 text-black text-xl font-normal font-['PingFang SC'] leading-[10.18px]"
       >
-        阿芙佳朵
+        <!-- 阿芙佳朵 -->
+        {{ data.prodcutName }}
       </div>
       <div class="h-0.25 bg-[#f6f6f6]"></div>
       <div class="text-black/90 text-base font-normal font-['PingFang SC'] leading-normal">
@@ -76,6 +87,7 @@ const a = ref(1)
           商品详情
         </div>
       </wd-divider>
+      <wd-img width="100%" mode="widthFix" :src="data.productDetailsImgUrl"></wd-img>
     </div>
     <div class="h-[63px] bg-white backdrop-blur-[20px] flex px-3.5 items-center justify-between">
       <div @click="show = true">

+ 47 - 11
packages/app/src/pages/home/mall/index.vue

@@ -12,30 +12,66 @@ import TiltedButton from '@/components/tilted-button.vue'
 import Product from './components/product.vue'
 import { shoppingBag } from '@designer-hub/assets/src/assets/svgs/index'
 import { useRouter } from '../../../core/utils/router'
+import { getProductCategories, getProducts } from '../../../core/libs/requests'
+import PageHelper from '@/components/page-helper.vue'
 
 const router = useRouter()
 
+const { data: productCategories, run: setProductCategories } = useRequest(
+  () => getProductCategories(),
+  { initialData: [] },
+)
+const categories = computed(() => productCategories.value.find(({ id }) => id === 1)?.children)
+const category = ref()
 const data = ref(['https://via.placeholder.com/347x128'])
-const products = ref([{}, {}, {}])
+onMounted(async () => {
+  await setProductCategories()
+  category.value = categories.value[0]
+})
 </script>
 
 <template>
   <view class="bg-white flex-grow flex flex-col px-3.5 py-5.5 gap-5.5">
     <wd-swiper :list="data" autoplay height="128px"></wd-swiper>
-    <div class="bg-black rounded-2xl pt-11">
+    <!-- <div class="bg-black rounded-2xl pt-11">
       <div class="bg-white rounded-2xl shadow p-3.5"></div>
-    </div>
+    </div> -->
     <div class="w-full inline-flex gap-2">
-      <div><wd-button type="primary" size="small">GELATO专区</wd-button></div>
-      <div>
-        <wd-button plain size="small">设计周边</wd-button>
-      </div>
-    </div>
-    <div class="grid grid-cols-2 gap-2.5">
-      <template v-for="({}, i) of products" :key="i">
-        <Product></Product>
+      <!-- <div><wd-button type="primary" size="small">GELATO专区</wd-button></div> -->
+      <template v-for="(it, i) in categories" :key="i">
+        <div>
+          <wd-button
+            :type="it.id === category?.id ? 'primary' : 'default'"
+            :plain="it.id !== category?.id"
+            size="small"
+            @click="category = it"
+          >
+            {{ it.name }}
+          </wd-button>
+        </div>
       </template>
     </div>
+    <PageHelper
+      v-if="category"
+      v-slot="{ data }"
+      class="flex-grow flex flex-col"
+      :request="getProducts"
+      :query="{
+        oneCategory: category?.parentId,
+        secondCategory: category?.id,
+      }"
+    >
+      <!-- <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>
+      </div>
+      <!-- </wd-skeleton> -->
+    </PageHelper>
     <div class="h-16">
       <div class="fixed bottom-0 left-0 right-0">
         <div class="h-16 bg-white flex items-center justify-between px-7">

+ 25 - 11
packages/app/src/pages/mine/authentication/index.vue

@@ -1,7 +1,10 @@
-<route lang="yaml">
-style:
-  navigationBarTitleText: 设计师认证
-  navigationStyle: custom
+<route lang="json">
+{
+  "style": {
+    "navigationBarTitleText": "设计师认证",
+    "navigationStyle": "custom"
+  }
+}
 </route>
 <script lang="ts" setup>
 import Card from '@/components/card.vue'
@@ -14,11 +17,12 @@ import { useUserStore } from '../../../store'
 import pageHeaderBg from '@designer-hub/assets/src/assets/svgs/pageHeaderBg'
 import pageHeaderFilter from '@designer-hub/assets/src/assets/svgs/pageHeaderFilter'
 import { storeToRefs } from 'pinia'
-import { useMessage, useToast } from 'wot-design-uni'
+import { useToast } from 'wot-design-uni'
+import { useRouter } from '../../../core/utils/router'
 
+const router = useRouter()
 const userStore = useUserStore()
 const { userInfo } = storeToRefs(userStore)
-const { alert } = useMessage()
 const { error } = useToast()
 const formData = ref({})
 const formInited = ref(false)
@@ -68,6 +72,7 @@ const schema = ref({
     type: 'Radio',
     label: '擅长空间类型',
     props: {
+      labelWidth: '170rpx',
       placeholder: ' ',
       columns: [],
     },
@@ -89,13 +94,15 @@ const handleSubmit = async () => {
   }
 }
 onMounted(async () => {
+  if (userInfo.value.userStatusEnabled) {
+    // router.back()
+  }
   const { data } = await getByDictType('member_channel_source')
   const { data: res } = await getByDictType(DictType.memberSpatialExpertiseType)
   console.log(res)
   schema.value.channelSource.props.columns = data
   schema.value.spatialExpertiseType.props.columns = res
   formInited.value = true
-  // alert({ title: '提示', msg: '您的认证申请已提交,请耐心等待审核,审核通过后您将获得通知' })
 })
 defineExpose({})
 </script>
@@ -112,12 +119,19 @@ defineExpose({})
     <NavBarEvo title="设计师认证"></NavBarEvo>
     <div class="flex-grow flex flex-col p-3.5 gap-3.5 relative">
       <Card>
-        <SectionHeading size="base" title="基本信息"></SectionHeading>
-        <template v-if="formInited"></template>
-        <DataForm v-model="formData" :schema="schema" direction="horizontal"></DataForm>
+        <SectionHeading size="base" title="基本信息" custom-class="mb-4"></SectionHeading>
+        <template v-if="formInited">
+          <DataForm v-model="formData" :schema="schema" direction="horizontal"></DataForm>
+        </template>
       </Card>
       <Card>
-        <SectionHeading size="base" title="上传附件"></SectionHeading>
+        <SectionHeading
+          size="base"
+          title="上传附件"
+          subtitle="请上传名片或者获奖信息凭证"
+          custom-class="mb-4"
+        ></SectionHeading>
+        <div class="h-0.25 bg-[#e1e1e1] mb-5"></div>
         <wd-upload></wd-upload>
       </Card>
       <div class="flex-1"></div>