Xiao_123 5 달 전
부모
커밋
7d3b5fdeff
5개의 변경된 파일238개의 추가작업 그리고 7개의 파일을 삭제
  1. 17 0
      src/api/mall/index.js
  2. 1 1
      src/layout/index.vue
  3. 1 1
      src/views/mall/home/components/hotGoods.vue
  4. 182 2
      src/views/mall/home/components/pointExchange/index.vue
  5. 37 3
      src/views/mall/home/index.vue

+ 17 - 0
src/api/mall/index.js

@@ -12,4 +12,21 @@ export const getProductByIds = async (ids) => {
   return request.get({
     url: `/app-api/product/spu/list-by-ids?ids=${ids}`
   })
+}
+
+// 提交积分商品兑换
+export const redeemSubmit = async (data) => {
+  return request.post({
+    url: '/app-api/menduner/system/redeem/submit',
+    openEncryption: true,
+    data
+  })
+}
+
+// 获取积分兑换记录
+export const getRedeemPage = async (params) => {
+  return request.get({
+    url: '/app-api/menduner/system/redeem/page',
+    params
+  })
 }

+ 1 - 1
src/layout/index.vue

@@ -10,7 +10,7 @@
         <component :is="Component" :key="router.currentRoute.value.path" v-if="!router.currentRoute.value.meta?.keepAlive"/>
       </router-view>  -->
     </div>
-    <Footers v-if="footerWhiteList.indexOf(router.currentRoute.value.path) === -1" class="mt-10"></Footers>
+    <Footers v-if="footerWhiteList.indexOf(router.currentRoute.value.path) === -1" :class="{'mt-10': !router.currentRoute.value.path.includes('/mall')}"></Footers>
     <Slider v-if="whiteList.indexOf(router.currentRoute.value.path) === -1" class="slider"></Slider>
   </div>
 </template>

+ 1 - 1
src/views/mall/home/components/hotGoods.vue

@@ -2,7 +2,7 @@
   <div>
     <div class="d-flex justify-space-between color-666">
       <div class="color-primary" style="font-size: 25px;">热门商品</div>
-      <!-- <div>查看更多</div> -->
+      <div class="cursor-pointer">查看更多</div>
     </div>
     <div class="goods-box mt-5">
       <v-card v-for="val in goodList" :key="val.id" class="goods-box-item" hover elevation="2" @click="handleClickGood(val)">

+ 182 - 2
src/views/mall/home/components/pointExchange/index.vue

@@ -1,11 +1,191 @@
 <template>
-  <div>xxx</div>
+  <div>
+    <div class="d-flex justify-space-between color-666">
+      <div class="color-primary" style="font-size: 25px;">积分兑换</div>
+      <div>
+        <span class="cursor-pointer active" @click="router.push('/recruit/personal/personalCenter/wallet')">当前账户积分:{{ point }}</span>
+        <span class="septal-line"></span>
+        <span class="cursor-pointer active">兑换记录</span>
+      </div>
+    </div>
+    <div class="goods-box mt-5">
+      <v-card v-for="val in dataList" :key="val.id" class="goods-box-item" elevation="2" @click="handleShowDetail(val)">
+        <v-img :src="val.url" width="100%" height="68%"></v-img>
+        <div class="pa-3">
+          <p class="ellipsis color-333">{{ val.name }}</p>
+          <div class="mt-1">
+            <div class="font-size-15 color-666">
+              消耗积分
+              <span class="color-primary font-size-20">{{ val.point }}</span>
+            </div>
+          </div>
+        </div>
+      </v-card>
+    </div>
+  </div>
+
+  <CtDialog :visible="showDetail" titleClass="text-h6" :footer="point >= detailItem?.point" :widthType="3" title="详情说明" @submit="handleSubmit" @close="showDetail = false">
+    <div class="color-primary font-size-20">{{ detailItem.name }}</div>
+    <div class="tips">
+      <div>使用说明:积分一经兑换概不退回,敬请谅解。</div>
+    </div>
+    <div class="d-flex align-center my-5">
+      <div class="mr-1">消耗积分</div>
+      <div class="color-primary">{{ detailItem.point }}</div>
+    </div>
+    <div v-if="point >= detailItem.point">
+      <div class="color-666 mb-5 mt-10">{{ detailItem?.type  ? '收货人信息填写' : '联系电话填写' }}</div>
+      <CtForm ref="CtFormRef" :items="formItems"></CtForm>
+    </div>
+    <div v-if="point < detailItem.point" class="text-end color-error font-size-14 cursor-pointer text-decoration-underline" @click="router.push('/recruit/personal/personalCenter/memberBenefits/taskCenter')">
+      积分不足,快去赚取积分吧
+    </div>
+  </CtDialog>
 </template>
 
 <script setup>
 defineOptions({ name: 'mall-point-exchange'})
+import { ref } from 'vue'
+import { getToken } from '@/utils/auth'
+import { redeemSubmit } from '@/api/mall/index.js'
+import { useUserStore } from '@/store/user'
+import { getDict } from '@/hooks/web/useDictionaries'
+import { checkPersonBaseInfo } from '@/utils/check'
+import dialogExtend from '@/plugins/dialogExtend'
+import Snackbar from '@/plugins/snackbar'
+import { useRouter } from 'vue-router'
+
+const emit = defineEmits(['login'])
+defineProps({
+  point: {
+    type: Number,
+    default: 0
+  }
+})
+
+const router = useRouter()
+// 数据
+const dataList = ref([
+  { name: '房券-高端酒店房券', point: 12000, url: 'https://minio.menduner.com/dev/menduner/hotalRoomVoucher.png', type: 1 },
+  { name: '门墩儿酒店英语学习年卡', point: 8000, url: 'https://minio.citupro.com/dev/menduner/englishCourses.jpg', type: 0 },
+  { name: '红酒-经典年份葡萄酒', point: 5000, url: 'https://minio.menduner.com/dev/menduner/redWine.png', type: 1 },
+  { name: '瑞幸咖啡券-瑞幸咖啡精致享受券', point: 2000, url: 'https://minio.menduner.com/dev/menduner/coffee.png', type: 0 },
+  { name: '减压捏捏乐', point: 500, url: 'https://minio.menduner.com/dev/menduner/pinchMusic.png', type: 1 }
+])
+
+const CtFormRef = ref()
+const formItems = ref({
+  options: [
+    {
+      type: 'text',
+      key: 'contactName',
+      value: null,
+      default: localStorage.getItem('baseInfo') ? JSON.parse(localStorage.getItem('baseInfo')).name : '',
+      hide: false,
+      label: '收货人姓名 *',
+      rules: [v => !!v || '请输入收货人姓名'],
+    },
+    {
+      type: 'text',
+      key: 'contactPhone',
+      value: null,
+      default: localStorage.getItem('userInfo') ? JSON.parse(localStorage.getItem('userInfo')).phone : '',
+      label: '收货人联系电话 *',
+      outlined: true,
+      rules: [v => !!v || '请填写收货人联系电话']
+    },
+    {
+      type: 'cascade',
+      key: 'address',
+      value: [],
+      default: [],
+      label: '收货地址',
+      itemText: 'name',
+      itemValue: 'id',
+      required: true,
+      clearable: false,
+      disabled: true,
+      emitPath: true,
+      items: []
+    },
+    {
+      type: 'textarea',
+      key: 'contactAddress',
+      value: '',
+      default: null,
+      hide: false,
+      label: '收货详细地址 *',
+      rules: [ v => !!v || '请填写收货详细地址' ]
+    }
+  ]
+})
+// 期望城市、其它感兴趣的城市
+getDict('areaTreeData', null, 'areaTreeData').then(({ data }) => {
+  data = data?.length && data || []
+  formItems.value.options.find(e => e.key === 'address').items = data
+})
+
+// 详情说明弹窗
+const showDetail = ref(false)
+const detailItem = ref({})
+const handleShowDetail = (item) =>{
+  if (!getToken()) {
+    Snackbar.warning('请先登录')
+    emit('login')
+    return
+  }
+  if (!checkPersonBaseInfo()) { // 强制填写个人信息
+    dialogExtend('necessaryInfoDialog').then(() => {
+      handleShowDetail()
+    })
+    return
+  }
+  detailItem.value = item
+  formItems.value.options.forEach(e => {
+    if (e.key !== 'contactPhone') e.hide = item.type ? false : true
+    e.value = e.default
+  })
+  showDetail.value = true
+}
+
+// 兑换提交
+const handleSubmit = async () =>{
+  const { valid } = await CtFormRef.value.formRef.validate()
+  if (!valid) return
+  const obj = {
+    ...detailItem.value
+  }
+  formItems.value.options.forEach(e => {
+    if (!e.hide) obj[e.key] = e.value
+  })
+  if (obj.type && (!obj.address || !obj.address.length)) return Snackbar.warning('请选择收货地址')
+  if (obj.type) obj.contactAddress = obj.address.join('') + obj.contactAddress
+  delete obj.address 
+  if (!obj.contactName) obj.contactName = localStorage.getItem('baseInfo') ? JSON.parse(localStorage.getItem('baseInfo')).name : '--'
+  await redeemSubmit(obj)
+  Snackbar.success('提交成功')
+  showDetail.value = false
+  detailItem.value = {}
+  await useUserStore().getUserAccountInfo()
+}
+
 </script>
 
 <style scoped lang="scss">
-
+.active:hover {
+  color: var(--v-primary-base) !important;
+}
+.goods-box {
+  width: 100%;
+  display: flex;
+  flex-wrap: wrap;
+  &-item {
+    height: 380px;
+    width: calc((100% - 48px) / 5);
+    margin: 0 12px 12px 0;
+    &:nth-child(5n) {
+      margin-right: 0;
+    }
+  }
+}
 </style>

+ 37 - 3
src/views/mall/home/index.vue

@@ -31,11 +31,19 @@
     </div>
 
     <!-- 轮播图 -->
-    <Carousel :templateData="template" class="mb-10" style="min-height: 370px;" />
+    <Carousel :templateData="template" class="mb-10" />
 
-    <!-- 热门商品 -->
-    <HotGoods :templateData="template" class="default-width" />
+    <div class="default-width">
+      <!-- 热门商品 -->
+      <HotGoods :templateData="template" class="mb-10" />
+
+      <!-- 积分兑换 -->
+      <PointExchange :point="accountData.point" @login="handleLogin" />
+    </div>
   </div>
+
+  <!-- 快速登录 -->
+  <loginPage v-if="showLogin" @loginSuccess="loginSuccess" @close="loginClose"></loginPage>
 </template>
 
 <script setup>
@@ -43,7 +51,10 @@ defineOptions({ name: 'mall-home-index'})
 import { ref, computed } from 'vue'
 import Carousel from './components/carousel.vue'
 import HotGoods from './components/hotGoods.vue'
+import PointExchange from './components/pointExchange'
+import loginPage from '@/views/common/loginDialog.vue'
 import { useMallStore } from '@/store/mall'
+import { useUserStore } from '@/store/user'
 import { useRouter } from 'vue-router'
 
 // 获取装修模版
@@ -60,6 +71,29 @@ useMallStore().$subscribe((mutation, state) => {
   if (state.template && Object.keys(state.template).length) template.value = state?.template
 })
 
+const userStore = useUserStore()
+let accountData = ref(JSON.parse(localStorage.getItem('userAccount')) || {})
+
+userStore.$subscribe((mutation, state) => {
+  if (Object.keys(state.userAccount).length) accountData.value = state.userAccount
+})
+
+const showLogin = ref(false)
+const handleLogin = () => {
+  showLogin.value = true // 打开快速登录弹窗
+}
+
+// 快速登录
+const loginSuccess = () => {
+  showLogin.value = false
+  Snackbar.success('登录成功')
+}
+
+const loginClose = () => {
+  showLogin.value = false
+  Snackbar.warning('您已取消登录')
+}
+
 
 const inputVal = ref('')
 const handleSearch = () => {}