商家展示功能的开发

具体重要功能

(1)封装一个功能函数
把有多个界面要使用的函数封装起来
commonCartEffects.js

import { useStore } from "vuex"
import { computed } from "vue";

export const useCommonCartEffect = (shopId) => {
    const store = useStore();
    const cartList = store.state.cartList;
    const changeCartItemInfo = (shopId, productId, productInfo, num) => {
        // console.log(shopId,productId,productInfo,num)
        store.commit('changeCartItemInfo', { 
            shopId, productId, productInfo, num 
        });
    };
    const productList = computed(()=>{
        const productList = cartList[shopId]?.productList || [];
        return productList
    })
    
    return { changeCartItemInfo,cartList,productList}
}

(2)要用到vuex中的数据
store文件夹下的index.js代码

import { createStore } from 'vuex'

//实现本地存储
const setLocalCartList = (state)=>{
  const { cartList } = state;
  const cartListString = JSON.stringify(cartList);
  localStorage.cartList = cartListString;
}
const getLocalCartList = ()=>{
  try{
    return JSON.parse(localStorage.cartList) || {}
  }catch(e){
    return {}
  }
}

export default createStore({
  state: {
    // cartList:{
    //   shopId:{
    //     shopNme:'沃尔玛',
    //     productList:{
    //        productId:{
    //          _id: "1",
    //          name: "番茄 250g / 份",
    //          imgUrl: "http://www.dell-lee.com/imgs/vue3/tomato.png",
    //          sales: 10,
    //          price: 33.6,
    //          oldPrice: 39.6,
    //          count:2
    //        } 
    //     }
    // }
    // cartList:{}
    cartList:getLocalCartList()
  },
  getters: {
  },
  mutations: {
    changeCartItemInfo(state,pyload){
      const {shopId,productId,productInfo} = pyload;
      let shopInfo = state.cartList[shopId] || {
        shopName:'',
        productList:{}
      };
      // if(!shopInfo) {shopInfo={}}
      let product = shopInfo.productList[productId];
      if(!product) { 
        productInfo.count = 0;
        product=productInfo;
        // product.count = 0;
      }
      product.count = product.count +pyload.num;

      if(pyload.num > 0){product.check = true;}
      // 等价于
      // (pyload.num > 0) && (product.check = true);
    
      if(product.count < 0) {product.count = 0;}
      // 等价于
      // (product.count < 0) && (product.count = 0);

      shopInfo.productList[productId] = product;
      state.cartList[shopId] = shopInfo;

      setLocalCartList(state);
    },

    changeShopName(state,pyload){
      const {shopId,shopName} = pyload;
      const shopInfo = state.cartList[shopId] || {
        shopName:'',
        productList:{}
      }
      shopInfo.shopName=shopName;
      state.cartList[shopId] = shopInfo

      setLocalCartList(state);
    },

    changeCartItemChecked(state,pyload){
      const {shopId,productId} = pyload;
      const product = state.cartList[shopId].productList[productId];
      //console.log(product)
       product.check = !product.check;

       setLocalCartList(state);
    },

    cleanCartProducts(state,pyload){
      const {shopId} = pyload;
      state.cartList[shopId].productList={};
    },

    setCartItemsChecked(state,pyload){
      const {shopId} = pyload;
      const products = state.cartList[shopId].productList;
      if(products){
        for(let i in products){
          const product =products[i];
          product.check = true;
          
        }
      }
      setLocalCartList(state);
    },

    clearCartData(state,pyload){
      const {shopId} = pyload
      state.cartList[shopId].productList=[];
    }
  },
})

(3)路由注册功能router文件夹下的index.js

import { createRouter, createWebHashHistory } from 'vue-router'
const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import(/* webpackChunkName: "home" */ '../views/home/HomeV')
  },
  {
    path: '/cartList',
    name: 'CartList',
    component: () => import(/* webpackChunkName: "cartList" */ '../views/cartList/CartList')
  },
  {
    path: '/orderConfirmation/:id',
    name: 'OrderConfirmation',
    component: () => import(/* webpackChunkName: "orderConfirmation" */ '../views/orderConfirmation/OrderConfirmation')
  },
  {
    path: '/orderList',
    name: 'OrderList',
    component: () => import(/* webpackChunkName: "orderList" */ '../views/orderList/OrderList')
  },
  {
    path: '/shop/:id',
    name: 'Shop',
    component: () => import(/* webpackChunkName: "shop" */ '../views/shop/ShopV')
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import(/* webpackChunkName: "login" */ '../views/login/LoginV'),
    beforeEnter(to,from,next){
      const {isLogin} =localStorage;
      isLogin ? next({name:"Home"}) : next();
    },
  },
  {
    path: '/register',
    name: 'Register',
    component: () => import(/* webpackChunkName: "register" */ '../views/register/RegisterV'),
    beforeEnter(to,from,next){
      const {isLogin} =localStorage;
      isLogin ? next({name:"Home"}) : next();
    },
  },
  // {
  //   path: '/about',
  //   name: 'about',
  //   // route level code-splitting
  //   // this generates a separate chunk (about.[hash].js) for this route
  //   // which is lazy-loaded when the route is visited.
  //   component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
  // }
]

const router = createRouter({
  history: createWebHashHistory(),
  routes
})

router.beforeEach((to,from,next) => {
  const {isLogin }= localStorage;
  const {name} = to;
  const isLoginOrRegister = (name === "Login" || name === "Register");

  (isLogin || isLoginOrRegister) ? next() : next({name:'Login'});
})

export default router

商家详情页总体代码ShopV.vue

<template>
<div class="wrapper">
    <div class="search">
        <div
         class="search__back iconfont"
         @click="handleBackClick"
         >&#xe6db;</div>
        <div class="search__content">
            <span class="search__content__icon iconfont">&#xe615;</span>
            <input
             class="search__content__input"
             placeholder="请输入商品名称"
            />
        </div>
    </div>
    <ShopInfo
        :item="item"
        :hindeBoder="true"
        v-show="item.imgUrl"
    />
    <Content :shopName="item.name" />
    <CartV />
</div>
</template>

<script>
import {reactive, toRefs} from 'vue'
import {useRouter,useRoute} from "vue-router"
import {get} from '../../utils/request'
import ShopInfo from '../../components/ShopInfo.vue'
import Content from './Content.vue'
import CartV from './CartV.vue'

//获取当前商铺信息
const useShopInfoEffect = ()=>{
    const route = useRoute();
    const data = reactive({ item:{} });
    const getItemData = async()=>{
        const result = await get(`api/shop/${route.params.id}`);
        if(result.errno===0 && result?.data){
            data.item = result.data
        }
    }
    const {item} = toRefs(data);
    return {item,getItemData}
}
//点击回退逻辑
const useBackRoterEffect = ()=>{
    const router = useRouter();
    const handleBackClick = ()=>{
            router.back();
    }
    return {handleBackClick}
} 
export default {
    name:'ShopV',
    components:{ ShopInfo,Content,CartV },
    setup() {
        const {item,getItemData} = useShopInfoEffect();
        const {handleBackClick} = useBackRoterEffect();
        getItemData(); 
        return { item,handleBackClick }
    },
}
</script>

<style lang="scss" scoped>
@import '../../style/viriables.scss';
.wrapper {
    padding: 0 .18rem;
}
.search{
    margin: .14rem 0 .04rem 0;
    display: flex;
    line-height: .32rem;
    &__back{
        font-size: .24rem;
        height: .3rem;
        color: #b6b6b6;
    }
    &__content{
        display: flex;
        flex: 1;
        background: $search-bgColor;
        border-radius: .16rem;
        &__icon{
            width: .44rem;
            text-align: center;
            color: $search-fontColor;
        }
        &__input{
            display: block;
            width: 100%;
            padding-right: .2rem;
            border: none;
            outline: none;
            background: none;
            height: .32rem;
            font-size: .14rem;
            color: $content-fontcolor;
            &::placeholder{
                color: $content-fontcolor;
            }
        }
    }
}
</style>

商品信息代码ShopInfo.vue

<template>
    <div class="shop">
        <img :src="item.imgUrl" class="shop__img" />
        <div
          :class="{shop__content:true,'shop__content--bordered':hindeBoder?false:true}"
        >
          <div class="shop__content__title">{{item.name}}</div>
          <div class="shop__content__tags">
            <span class="shop__content__tag">月售: {{item.sales}}</span>
            <span class="shop__content__tag">起送: {{item.expressLimit}}</span>
            <span class="shop__content__tag">基础运费: {{item.expressPrice}}</span>
          </div>
          <p class="shop__content__highlight">{{item.slogan}}</p>
        </div>
      </div>
</template>

<script>
export default {
    name:'ShopInfo',
    props:['item','hindeBoder'],
    setup() {
        
    },
}
</script>

<style lang="scss" scoped>
@import '../style/viriables.scss';
@import '../style/mixins.scss';
.shop{
    display: flex;
    padding-top: .12rem;
    &__img{
      margin-right: .16rem;
      width: .56rem;
      height: .56rem;
    }
    &__content{
        flex: 1;
        padding-bottom: .12rem;
        &--bordered{
          border-bottom:.01rem solid $content-fontcolor ;
        }
        &__title{
        line-height: .22rem;
        font-size: .16rem;
        color: $content-fontcolor;
        }
        &__tags{
        margin-top: .08rem;
        line-height: .18rem;
        font-size: .13rem;
        color: $content-fontcolor;
        }
        &__tag{
        margin-right: .16rem;
        }
        &__highlight{
        margin: .08rem 0 0 0;
        line-height: .18rem;
        font-size: .13rem;
        color: $highlight-fontColor;
        }
    }
}
  
</style>

详情内容部分代码Content.vue

<template>
    <div class="content">
        <div class="category">
            <div
                :class="{ 'category__item': true, 'category__item--active': currentTab === item.tab }"
                v-for="item in categories"
                :key="item.name"
                @click="() => handleTabClick(item.tab)"
            >{{ item.name }}</div>
        </div>
        <div class="product">
            <div class="product__item" v-for="item in list" :key="item._id">
                <img class="product__item__img" :src="item.imgUrl" />
                <div class="product__item__detail">
                    <h4 class="product__item__title">{{ item.name }}</h4>
                    <p class="product__item__sales">月售 {{ item.sales }} 件</p>
                    <p class="product__item__price">
                        <span class="product__item__yen">&yen;{{ item.price }}</span>
                        <span class="product__item__oringin">&yen;{{ item.oldPrice }}</span>
                    </p>
                </div>
                <div class="product__number">
                    <span
                        class="product__number__minus iconfont"
                        @click="changeCartItem(shopId, item._id, item, -1,shopName)"
                    >&#xe66d;</span>
                    {{getProductCartCount(shopId,item._id) }}
                    <span
                        class="product__number__plus iconfont"
                        @click="changeCartItem(shopId, item._id, item, 1,shopName)"
                    >&#xe781;</span>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
import { reactive, toRefs, ref, watchEffect } from 'vue'
import { useRoute } from "vue-router"
import { useStore } from 'vuex'
import { get } from '../../utils/request'
import { useCommonCartEffect } from '../../effects/cartEffects'
const categories = [
    { name: '全部商品', tab: 'all' },
    { name: '秒杀', tab: 'seckill' },
    { name: '新鲜水果', tab: 'fruit' }
]

//和tab切换相关的逻辑
const useTabEffect = () => {
    const currentTab = ref(categories[0].tab);
    const handleTabClick = (tab) => {
        currentTab.value = tab;
    }
    return { currentTab, handleTabClick }
}

//列表内容相关的东西
const useCurrentListEffect = (currentTab, shopId) => {
    const content = reactive({ list: [] });
    const getContentData = async () => {
        const result = await get(`/api/shop/${shopId}/products`, {
            tab: currentTab.value
        });
        if (result?.errno === 0 && result?.data?.length) {
            content.list = result.data;
        }
    }
    watchEffect(() => { getContentData(); })
    const { list } = toRefs(content);
    return { list }
}

//购物车相关逻辑
const useCartEffect = ()=>{
    const store = useStore();
    const { changeCartItemInfo,cartList } = useCommonCartEffect();
    const changeShopName = (shopId,shopName)=>{
        store.commit('changeShopName',{shopId,shopName})
    }
    const changeCartItem = (shopId, productId, item, num,shopName)=>{
        changeCartItemInfo(shopId, productId, item, num);
        changeShopName(shopId,shopName)
    }
    const getProductCartCount =(shopId,productId)=>{
        return cartList?.[shopId]?.productList?.[productId]?.count || 0 
    }
    return {cartList,changeCartItem,getProductCartCount}
}

export default {
    // eslint-disable-next-line vue/multi-word-component-names
    name: 'Content',
    props:['shopName'],
    setup() {
        const route = useRoute();
        const shopId = route.params.id;
        const { currentTab, handleTabClick } = useTabEffect();
        const { list } = useCurrentListEffect(currentTab, shopId);
        const {cartList,changeCartItem,getProductCartCount} = useCartEffect();
        return {
            list, currentTab, categories, handleTabClick,
            shopId, changeCartItem,cartList,getProductCartCount
        }
    },
}
</script>

<style lang="scss" scoped>
@import "../../style/mixins.scss";
@import "../../style/viriables.scss";


.content {
    position: absolute;
    display: flex;
    left: 0;
    right: 0;
    top: 1.5rem;
    bottom: 0.5rem;
}
.category {
    overflow-y: scroll;
    height: 100%;
    width: 0.76rem;
    background: $search-bgColor;
    &__item {
        line-height: 0.4rem;
        text-align: center;
        font-size: 0.14rem;
        color: #333;
        &--active {
            background: $bgColor;
        }
    }
}
.product {
    overflow-y: scroll;
    flex: 1;
    &__item {
        position: relative;
        display: flex;
        padding: 0.12rem 0;
        margin: 0 0.16rem;
        border-bottom: 0.01rem solid $content-bgcolor;
        &__detail {
            overflow: hidden;
        }
        &__img {
            width: 0.68rem;
            height: 0.68rem;
            margin-right: 0.16rem;
        }
        &__title {
            margin: 0;
            line-height: 0.2rem;
            font-size: 0.14rem;
            color: $content-fontcolor;
            @include ellipsis;
        }
        &__sales {
            margin: 0.06rem 0;
            line-height: 0.16rem;
            font-size: 0.12rem;
            color: $content-fontcolor;
        }
        &__price {
            margin: 0;
            line-height: 0.2rem;
            font-size: 0.14rem;
            color: $highlight-fontColor;
        }
        &__yen {
            font-size: 0.12rem;
        }
        &__oringin {
            margin-left: 0.06rem;
            line-height: 0.2rem;
            font-size: 0.12rem;
            color: $light-fontColor;
            text-decoration: line-through;
        }
        .product__number {
            position: absolute;
            right: 0;
            bottom: 0.12rem;
            line-height: .18rem;
            &__minus {
                position: relative;
                top: .02rem;
                color: $medium-fontColor;
                margin-right: 0.05rem;
            }
            &__plus {
                position: relative;
                top: .02rem;
                color: #0091ff;
                margin-left: 0.05rem;
            }
        }
    }
}
</style>

购物车部分代码CartV.vue

<template>
<div 
    class="mask" 
    v-if="showCart && calculations.total>0"
    @click="handleCartShowChange"
/>
<div class="cart">
    <div class="product" v-if="showCart && calculations.total>0"> 
        <div class="product__header">
            <div 
                class="product__header__all"
                @click="() => setCartItemsChecked(shopId)"    
            >
                <span 
                    class="product__header__icon iconfont"
                    v-html="calculations.allChecked ?'&#xe652;':'&#xe619;'"
                ></span>
                全选</div>
            <div  class="product__header__clear">   
                <span 
                    class="product__header__clear__btn"
                    @click="() => cleanCartProducts(shopId)"
                >清空购物车</span>
            </div>
        </div>
        <div 
            class="product__item"
            v-for="item in productList"
            :key="item._id"
        >
            <div 
                class="product__item__checked iconfont"
                v-html="item.check ? '&#xe652;':'&#xe619;'"
                @click="()=>{changeCartItemChecked(shopId,item._id)}"
            />    
            <img
                class="product__item__img"
                :src="item.imgUrl" 
            />
            <div class="product__item__detail">
                <h4 class="product__item__title">{{item.name}}</h4>
                <p class="product__item__price">
                    <span class="product__item__yen">&yen;{{item.price}}</span>
                    <span class="product__item__oringin">&yen;{{item.oldPrice}}</span>
                </p>
            </div>
            <div class="product__number">
                <span 
                    class="product__number__minus iconfont"
                    @click="()=>{changeCartItemInfo(shopId,item._id,item,-1)}"
                >&#xe66d;</span>
                {{item.count || 0}}
                <span 
                    class="product__number__plus iconfont"
                    @click="()=>{changeCartItemInfo(shopId,item._id,item,1)}"
                >&#xe781;</span> 
            </div>
        </div>
    </div>
    <div class="check">
        <div class="check__icon">
            <img
                src="http://www.dell-lee.com/imgs/vue3/basket.png"
                class="check__icon__img"
                @click="handleCartShowChange"
            />
            <div class="check__icon__tag">{{calculations.total}}</div>
        </div>
        <div class="check__info">
            总计:<span class="check__info__price">&yen; {{calculations.price}}</span>
        </div>
        <div class="check__button" v-show="calculations.total>0">
            <router-link :to="{path:`/orderConfirmation/${shopId}`}">
                去结算
            </router-link>
        </div>
    </div>
</div>
   
</template>

<script>
import { ref} from 'vue'
import {useRoute} from 'vue-router'
import {useStore} from "vuex"
import { useCommonCartEffect } from '../../effects/cartEffects'

//获取购物车信息逻辑
const useCartEffect = (shopId)=>{
    const store = useStore();
    const {calculations,changeCartItemInfo,productList} = useCommonCartEffect(shopId);
    
    

    const changeCartItemChecked = (shopId,productId)=>{
        store.commit('changeCartItemChecked', { 
            shopId, productId
        })
    }

    const cleanCartProducts = (shopId)=>{
        store.commit('cleanCartProducts',{ shopId })
    }

    const setCartItemsChecked = (shopId)=>{
        store.commit('setCartItemsChecked',{shopId})
    }

    
    return {
        calculations,productList,changeCartItemInfo,
        changeCartItemChecked,cleanCartProducts,
        setCartItemsChecked
    }
}


    //展示隐藏购物车逻辑
const toggleCartEffect = ()=>{
    const showCart = ref(false);
    const handleCartShowChange = ()=>{
        showCart.value=!showCart.value;
    }
    return {showCart,handleCartShowChange}
}
export default {
    name:'CartV',
    setup() {
        const route = useRoute();
        const shopId = route.params.id;
       
        const {
            calculations,productList,changeCartItemInfo,
            changeCartItemChecked,cleanCartProducts,
            setCartItemsChecked
        } = useCartEffect(shopId);
        const {showCart,handleCartShowChange} = toggleCartEffect();
        return {
            calculations,productList,changeCartItemInfo,
            changeCartItemChecked,shopId,cleanCartProducts,
            setCartItemsChecked,showCart,handleCartShowChange
        }
    },   
}
</script>

<style lang="scss" scoped>
@import '../../style/viriables.scss';
@import '../../style/mixins.scss';

.mask{
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    top: 0;
    background: rgba(0,0,0,.5);
    z-index: 1;
}
.cart{
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 2;
    background: $bgColor;
}

.product{
    overflow-y: scroll;
    flex: 1;
    background:$bgColor;
    &__header{
        display: flex;
        line-height: .52rem;
        border-bottom: .01rem solid $content-bgcolor;
        font-size: .14rem;
        color: $content-fontcolor;
        &__all{
            width: .64rem;
            margin-left:.18rem;
        }
        &__icon{
            display: inline-block;
            vertical-align: top;
            color: $btn-bgColor;
            font-size: .2rem;
        }
        &__clear{
            flex: 1;
            margin-right: .16rem;
            text-align: right; 
            &__btn{
                display: inline-block;
            }
        }
    }
    &__item{
        position: relative;
        display: flex;
        padding: .12rem 0;
        margin: 0 .16rem;
        border-bottom: .01rem solid $content-bgcolor;
        &__detail{
            overflow: hidden;
        }
        &__checked{
            line-height: .5rem;
            margin-right: .2rem;
            color:$btn-bgColor;
            font-size: .2rem;
        }
        &__img{
            width: .46rem;
            height: .46rem;
            margin-right: .16rem;
        }
        &__title{
            margin: 0;
            line-height: .2rem;
            font-size: .14rem;
            color: $content-fontcolor;
            @include ellipsis;
        }
        &__price{
            margin: .06rem 0 0 0 ;
            line-height: .2rem;
            font-size: .14rem;
            color: $highlight-fontColor;
        }
        &__yen{
            font-size: .12rem;
        }
        &__oringin{
            margin-left: .06rem;
            line-height: .2rem;
            font-size: .12rem;
            color: $light-fontColor;
            text-decoration: line-through;
        }
        .product__number{
            position: absolute;
            right: 0;
            bottom: .26rem;
            &__minus{
                position: relative;
                top: .02rem;
                color: $medium-fontColor;
                margin-right:.05rem ;
            }
            &__plus{
                position: relative;
                top: .02rem;
                color: #0091ff;
                margin-left: .05rem;
            }
        }
    }
}
.check{
    display: flex;
    line-height: .49rem;
    height: .49rem;
    border-top: .01rem solid $content-bgcolor;
    &__icon{
        position: relative;
        width: .84rem;
        &__img{
            display: block;
            margin: .12rem auto;
            width: .28rem;
            height: .26rem;
        }
        &__tag{
            position: absolute;
            left: .46rem;
            top: .04rem;
            padding: 0 .04rem;
            min-width: .2rem;
            height: .2rem;
            line-height: .2rem;
            background-color: $highlight-fontColor;
            border-radius: .1rem;
            font-size: .12rem;
            text-align: center;
            color: $bgColor;
            transform: scale(.5);
            transform-origin: left centet;//做缩小的时候,以左侧中心的位置为中心
        }
    }
    &__info{
        flex: 1;
        color: $content-fontcolor;
        font-size: .12rem;
        &__price{
            line-height: .49rem;
            color: $highlight-fontColor;
            font-size: .18rem;
        }
    }

    &__button{
        width: .98rem;
        background-color: #4fb0f9;
        color: $content-bgcolor;
        font-size: .14rem;
        text-align: center;
        a{
            color: $bgColor;
            text-decoration: none;
        }
    }
}

</style>

具体实现界面

详情1.png
详情2.png
详情3.png
详情4.png
详情5.png

(2)点击清空购物车可清空购物车内容

点击详情5图片中去结算可跳转到新的页面,确认订单界面

确认订单界面开发

详细代码

(1)确认订单总体代码OrderConformation.vue
<template>
<div class="wrapper">
    <TopArea />
    <ProductList />
    <OrderV />
</div>
</template>

<script>
import TopArea from './TopArea.vue';
import ProductList from './ProductList.vue' 
import OrderV from './OrderV.vue'
export default {
    name:'OrderConfirmation',
    components:{TopArea,ProductList,OrderV},
}
</script>

<style lang="scss" scoped>
.wrapper{
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    background-color: #eee;
    overflow-y: scroll;
}
</style>
(2)顶部区域代码TopArea.vue
<template>
<div class="top">
    <div class="top__header">
        <div 
            class="iconfont top__header__back"
            @click="handleBackClick"
        >&#xe6db;</div>
        确认订单
    </div>
    <div class="top__receiver">
        <div class="top__receiver__title">收货地址</div>
        <div class="top__receiver__address">南华大学雨母校区三省园5栋</div>
        <div class="top__receiver__info">
            <span class="top__receiver__info__name">彭美女</span>
            <span class="top__receiver__info__name">15080846751</span>
        </div>
        <div class="top__receiver__icon iconfont">&#xe6db;</div>
    </div>
</div>
</template>

<script>
import { useRouter } from 'vue-router';
export default {
    name:'TopArea',
    setup() {
        const router = useRouter()
        const handleBackClick = ()=>{router.back()}
        return {handleBackClick}
    },
}
</script>

<style lang="scss" scoped>
@import "../../style/viriables.scss";
.top{
    height: 1.96rem;
    background-size: 100% 1.59rem ;
    background-image: linear-gradient(0deg,rgba(0,145,255,0.00) 4%,#0091ff 50%);
    background-repeat: no-repeat;
    &__header{
        position: relative;
        padding-top: .2rem;
        line-height: .24rem;
        color: $bgColor;
        text-align: center;
        font-size: .16rem;
        &__back{
            position: absolute;
            left: .18rem;
            font-size: .22rem;
        }
    }
    &__receiver{
        position: absolute;
        left: .18rem;
        right: .18rem;
        top: .66rem;
        height: 1.11rem;
        background: $bgColor;
        border-radius: .04rem;
        &__title{
            line-height: .22rem;
            padding: .16rem 0 .14rem .16rem;
            font-size: .16rem;
            color: $content-fontcolor;
        }
        &__address{
            line-height: .2rem;
            padding: 0 .4rem 0 .16rem;
            font-size: .14rem;
            color: $content-fontcolor;
        }
        &__info{
            padding: .06rem 0 0 .16rem;
            &__name{
                margin-right: .06rem;
                line-height: .18rem;
                font-size: .12rem;
                color: $medium-fontColor;
            }
        }
        &__icon{
            transform: rotate(180deg);
            position: absolute;
            right: .16rem;
            top: .5rem;
            color: $medium-fontColor;
            font-size: .2rem;
        }
    }
}
</style>
(3)下单部分代码OrderV.vue
<template>
    <div class="order">
        <div class="order__price">实付金额 ¥<b>{{calculations.price}}</b></div>
        <div 
            class="order__btn" 
            @click="()=>handleShowConfirmChange(true)"
        >提交订单</div>
    </div>
    <div 
        class="mask" 
        v-show="showConfirm"
        @click="()=>handleShowConfirmChange(false)"
    >
        <div class="mask__content" @click.stop>
            <h3 class="mask__content__title">确认离开收银台</h3>
            <p class="mask__content__desc">请尽快完成支付,否则30分钟后自动取消</p>
            <div class="mask__content__btns">
                <button 
                    class="mask__content__btn mask__content__btn--first"
                    @click="()=>handleConfirmOrder(true)"
                >取消订单</button>
                <button 
                    class="mask__content__btn mask__content__btn--second"
                    @click="()=>handleConfirmOrder(false)"
                >确认支付</button>
            </div>
        </div>
    </div>
</template>

<script>
import { ref } from '@vue/reactivity';
import { useRoute,useRouter } from 'vue-router';
import { useStore } from 'vuex';
import { useCommonCartEffect } from '../../effects/cartEffects'
import {post} from '../../utils/request'

//下单相关逻辑
const useMakeOrderEffect = (shopName,productList,shopId)=>{
    const store = useStore()
    const router =useRouter()
    
    const handleConfirmOrder= async (isCancled)=>{
        const products = []
        for(let i in productList.value){
            const product = productList.value[i]
            products.push({id:parseInt(product._id,10),num:product.count})
        }
        try{
            const result = await post('/api/order',{
                addressId:1,
                shopId,
                shopName:shopName.value,
                isCancled,
                products
            })
            if(result?.errno === 0){
                store.commit('clearCartData',{shopId})
                router.push({name:'OrderList'});
            }
        }catch(e){
            //提示下单失败
            //showToast("请求失败")
        }         
    }
    return {handleConfirmOrder}
}

//蒙层展示相关逻辑
const useShowMaskEffect = ()=>{
    const showConfirm = ref(false)
    const handleShowConfirmChange = (status)=>{
        showConfirm.value=status
    }
    return {showConfirm,handleShowConfirmChange}
}
export default {
    name:'OrderV',
    setup() {
        const route = useRoute()
        const shopId = parseInt(route.params.id,10)
        const {calculations,shopName,productList} = useCommonCartEffect(shopId)
        const {handleConfirmOrder} = useMakeOrderEffect(shopName,productList,shopId);
        const {showConfirm,handleShowConfirmChange} = useShowMaskEffect()
        
        return {showConfirm,calculations,
            handleConfirmOrder,handleShowConfirmChange
        }
    },
}
</script>

<style lang="scss" scoped>
@import "../../style/viriables.scss";
.order{
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    display: flex;
    height: .49rem;
    line-height: .49rem;
    background: $bgColor;
    &__price{
        flex: 1;
        text-indent: .24rem;
        font-size: .14rem;
        color: $content-fontcolor;
    }
    &__btn{
        width: .98rem;
        background: #4fb0f9;
        color: $bgColor;
        text-align: center;
        font-size: .14rem;
    }
}
.mask{
    z-index: 1;
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    background: rgba(0,0,0,0.50);
    &__content{
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%,-50%);
        width: 3rem;
        height: 1.56rem;
        background: #fff;
        border-radius: .04rem;
        text-align: center;
        &__title{
            margin: .24rem 0 0 0;
            line-height: .26rem;
            font-size: .18rem;
            color: #333;
        }
        &__desc{
            margin: .08rem 0 0 0 ;
            font-size: .14rem;
            color: #666;
            
        }
        &__btns{
            display: flex;
            margin: .24rem .58rem ;
        }
        &__btn{
            flex: 1;
            width: .8rem;
            line-height: .32rem;
            border-radius: .16rem;
            font-size: .14rem;
            &--first{
                margin-right: .12rem;
                border: .01rem solid #4fb0f9;
                color: #4fb0f9;
            }
            &--second{
                margin-left: .12rem;
                background: #4fb0f9;
                color: #fff;
            }
        }
    }
}
</style>
(4)下单界面商品显示下单商品ProductList.vue
<template>
    <div class="products">
        <div class="products__title">
            {{shopName}}
        </div>
        <div class="products__wrapper">
            <div class="products__list">     
                <div  
                    class="products__item"
                    v-for="item in productList"
                    :key="item._id"
                >
                    <img class="products__item__img" :src="item.imgUrl" />
                    <div class="products__item__detail">
                        <h4 class="products__item__title">{{item.name}}</h4>
                        <p class="products__item__price">
                            <span>
                                <span class="products__item__yen">&yen;</span>
                                {{item.price}} × {{item.count}}
                            </span>
                            <span class="products__item__total">
                                <span class="products__item__yen">&yen;</span>
                                {{(item.price*item.count).toFixed(2)}}
                            </span>
                        </p>
                    </div>
                </div>
            </div>  
        </div>        
    </div>
</template>

<script>
import { useRoute } from 'vue-router';
import { useCommonCartEffect } from '../../effects/cartEffects'
export default {
    name:'ProductList',
    setup() {
        const route = useRoute()
        const shopId = route.params.id
        const {productList,shopName} = useCommonCartEffect(shopId)
        return {productList,shopName}
    },
}
</script>

<style lang="scss" scoped>
@import "../../style/mixins.scss";
@import "../../style/viriables.scss";
.products{
    margin: .16rem .18rem .1rem .18rem;
    background: $bgColor;
    &__title{
        padding: .16rem;
        font-size: .16rem;
        color: $content-fontcolor;
    }
    &__wrapper{
        overflow-y: scroll;
        margin: 0 .18rem;
        position: absolute;
        left: 0;
        right: 0;
        bottom: .6rem;
        top: 2.6rem;
    }
    &__list{
        background: $bgColor;
    }
    &__item {
        position: relative;
        display: flex;
        padding: 0 .16rem .16rem .16rem;
        &__img {
            width: 0.46rem;
            height: 0.46rem;
            margin-right: 0.16rem;
        }
        &__detail {
            flex: 1;
        }
        &__title {
            margin: 0;
            line-height: 0.2rem;
            font-size: 0.14rem;
            color: $content-fontcolor;
            @include ellipsis;
        }
        &__price {
            display: flex;
            margin: .06rem 0 0 0;
            line-height: 0.2rem;
            font-size: 0.14rem;
            color: $highlight-fontColor;
        }
        &__total{
            text-align: right;
            flex: 1;
            color: $dark-fontColor;
        }
        &__yen {
            font-size: 0.12rem;
        }
    }
}
</style>

下单界面具体实现界面

下单1.png
下单2.png

**点击确认支付和取消订单均跳转到首页,因为没有做支付界面

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,914评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,935评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,531评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,309评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,381评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,730评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,882评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,643评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,095评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,448评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,566评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,253评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,829评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,715评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,945评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,248评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,440评论 2 348

推荐阅读更多精彩内容

  • boku88.com 本套课程ThinkPHP5 查看更多关于 ThinkPHP5 的文章 (tp5)第四季:实战...
    博库吧阅读 1,238评论 0 2
  • 36 APP常用功能设计 36.1启动页面设计 启动页面的图片设计成动态配置的,可以显示不同的广告。 当服务器更新...
    xjbclz阅读 1,182评论 0 1
  • 前言 这里筑梦师,是一名正在努力学习的iOS开发工程师,目前致力于全栈方向的学习,希望可以和大家一起交流技术,共同...
    筑梦师Winston阅读 25,956评论 80 514
  • 0、杂记 0.1、在实际的开发中,图片资源不会存储在小程序的目录中,因为小程序的大小不能超过1MB(现在改为2M)...
    it筱竹阅读 5,318评论 0 10
  • 前沿 置身世外只为暗中观察!!!Hello大家好,我是魔王哪吒!重学巩固你的Vuejs知识体系,如果有哪些知识点遗...
    lessonSam阅读 1,172评论 0 13