【小程序】多端框架Taro实现外卖首页

全局安装Taro命令行工具

npm install -g @tarojs/cli 【Taro v3.0.18】

创建模板项目

taro init myApp

安装其他第三方依赖

npm install --save taro-ui@next 安装失败,改用cnpm install --save taro-ui@next 【taro-ui@3.0.0-alpha.9】。
npm install --save events 安装失败,改用cnpm install --save events【events@3.2.0】。

代码目录

在这里插入图片描述

详细代码
修改index.jsx
import React, { Component } from 'react';
import { View} from '@tarojs/components';
import './index.scss';
import Head from "../../components/head/head.jsx";
import Food from "../../components/food/food.jsx";
import Bottom from "../../components/bottom/bottom.jsx";

class Index extends Component {

  render () {
    return (
      <View className='index'>
        <Head/>
        <Food/>
        <Bottom/>
      </View>
    )
  }
}

export default Index;
新增目录 assets/images

请移步此处下载图片。

新增commons/utils/utils.js
import Taro from "@tarojs/taro";
import EventEmitter  from "events";

function getTotal(){
    let buffer = Taro.getStorageSync("buffer");
    let total = 0,sum = 0;
    Object.keys(buffer).map(item => {
        let {price,quantity} = buffer[item];
        sum += quantity;
        total += price*quantity;
    })
    return {sum,total};
}

const Event = new EventEmitter();

export {
    getTotal,
    Event
}
新增目录components/bottom
bottom.jsx
import React,{Component} from "react";
import {View,Image,Text} from "@tarojs/components";
import "./bottom.scss";
import {Event,getTotal} from "../../common/utils/utils.js";

class Bottom extends Component{
    constructor(){
        super(...arguments);
        this.state = {
            total:0,
            sum:0,
            delivery:15,
            starting:20
        }
    }
    componentDidMount(){
        let {total,sum} = getTotal();
        this.setState({
            total:total,
            sum:sum
        })
        Event.on("change",() => {
            let {total,sum} = getTotal();
            this.setState({
                total:total,
                sum:sum
            })
        })
    }
    render(){
        let {total,sum,delivery,starting} = this.state;
        return (
            <View className="bottom">
                <View className="bottom-content">
                    {
                        (sum>0) 
                        ? <><Text className="sum">{sum}</Text><Image className="expressman" src={require("../../assets/images/expressman_light.png")}></Image></>
                        : <Image className="expressman" src={require("../../assets/images/expressman_dark.png")}></Image>
                    }
                    <View className="cost">
                        <Text className="total"><Text className="value">{total}</Text></Text>
                        <Text className="delivery">预估送配送费¥{total<starting?delivery:0}</Text>
                    </View>
                    {(total<starting)
                        ?<View className="starting"><Text>{starting}起送</Text></View>
                        :<View className="gotoPay">去结算</View>
                    }       
                </View>
            </View>
        )
    }
}

export default Bottom;
bottom.scss
.bottom{
    padding: 0 24px;
    box-sizing: border-box;
    width: 100%;
    height: 100px;
    position: fixed;
    bottom: 0;
    .bottom-content{
        height: inherit;
        background-color: #020001;
        border-radius: 60px;
        display: flex;
        color: #6a6a6a;
        justify-content: space-between;
        align-items: center;
        font-size: 26px;
        .sum{
            width: 36px;
            height: 36px;
            line-height: 36px;
            display: inline-block;
            background-color: #fa4e3f;
            color: #fff;
            text-align: center;
            border-radius: 50%;
            position: absolute;
            bottom: 10px;
            left: 150px;
        }
        .expressman{
            width: 128px;
            height: 128px;
            margin-top: -30px;
            margin-left:50px;
        }
        .cost{
            margin-left: -30px;
            display: flex;
            align-items: center;
        }
        .total{
            color: #fff;
            font-size: 28px;
            padding-right: 10px;
        }
        .value{
            font-size: 42px;
        }
        .delivery{
            padding-left: 10px;
            border-left: 1px solid #666;
        }
        .starting{
            margin-right: 50px;
        }
        .gotoPay{
            width: 150px;
            height: 100px;
            line-height: 100px;
            text-align: center;
            border-top-right-radius: 60px;
            border-bottom-right-radius: 60px;
            background-color: #ffc236;
            box-shadow: 1px 0 0 #ffc236;
        }
    }

}
新增目录components/food
category.jsx
import React,{Component} from "react";
import {View,Text} from "@tarojs/components";
import "./category.scss";

class Catetory extends Component{
    constructor(){
        super(...arguments);
    }
    handleClick(item){
        let {selectedCategory,changeCategory} = this.props;
        if(selectedCategory.id!==item.id){
            changeCategory(item);
        }
    }
    render(){
        const {categories,selectedCategory} = this.props;
        return (
            <View className="category">
                {
                    categories.map(item => (
                        <Text 
                        className={"category-title "+((selectedCategory.id===item.id)?"selected":"")} 
                        key={item.id}
                        onClick={this.handleClick.bind(this,item)}
                        >{item.title}</Text>
                    ))
                }
            </View>
        )
    }
}

export default Catetory;
category.scss
.category{
    .category-title{
        display: flex;
        align-items: center;
        width: 100px;
        height: 64px;
        text-align: left;
        font-size: 24px;
        padding: 28px;
        background-color: #f7f8fa;
    }
    .selected{
        background-color: #fff;
    }
}
food.jsx
import React,{Component} from "react";
import {AtTabs,AtTabsPane} from "taro-ui";
import Taro from "@tarojs/taro";
import {View,ScrollView} from "@tarojs/components";
import Category from "./category.jsx";
import FoodList from "./foodlist.jsx";
import "./food.scss";

const categories = [
    {title:"推荐",id:"recommend"},
    {title:"折扣",id:"discount"},
    {title:"新品上市",id:"new"},
    {title:"超值套餐",id:"value"},
    {title:"爱吃饭",id:"love"},
    {title:"一桶美味",id:"delicious"},
    {title:"美味卷堡",id:"roll"},
    {title:"休闲小食",id:"snack"}
];

const foods = [
    {title:"小食盒套餐1份",sales:10,price:25,imgUrl:"1",pid:"recommend",id:"recomment#1"},
    {title:"全明星炸鸡桶",sales:30,price:49,imgUrl:"2",pid:"recommend",id:"recomment#2"},
    {title:"香辣鸡翅5对",sales:45,price:39,imgUrl:"3",pid:"recommend",id:"recomment#3"},
    {title:"圣诞四味拼盘",sales:17,price:39,imgUrl:"4",pid:"recommend",id:"recomment#4"},
    {title:"鸡腿汉堡双人餐1份",sales:28,price:49,imgUrl:"5",pid:"discount",id:"discount#1"},
    {title:"双层鸡排堡单人餐1份",sales:14,price:39,imgUrl:"6",pid:"discount",id:"discount#2"},
    {title:"德克士一桶都是小食B",sales:28,price:42,imgUrl:"7",pid:"discount",id:"discount#3"},
    {title:"香酥双鸡堡",sales:30,price:14,imgUrl:"8",pid:"discount",id:"discount#4"},
    {title:"南洋鸡肉卷单人餐",sales:25,price:25,imgUrl:"9",pid:"new",id:"new#1"},
    {title:"堡卷双人餐",sales:12,price:49,imgUrl:"10",pid:"new",id:"new#2"},
    {title:"脆皮鸡腿",sales:23,price:20.7,imgUrl:"11",pid:"new",id:"new#3"},
    {title:"多人餐",sales:18,price:89,imgUrl:"12",pid:"new",id:"new#4"}
]

class Food extends Component{
    constructor(){
        super(...arguments);
        this.state = {
            current:0,
            tabList:[{title:"点菜"},{title:"评价"},{title:"商家"}],
            categories:categories,
            selectedCategory:categories[0],
            list:[]
        }
        this.handleClick = this.handleClick.bind(this);
        this.changeCategory = this.changeCategory.bind(this);
    }
    componentDidMount(){
        let {selectedCategory} = this.state;
        this.setState({
            list:foods.filter(item => item.pid === selectedCategory.id)
        });
        if(!Taro.getStorageSync("buffer")){
            let buffer = {};
            for(let food of foods){
                let obj  = {};
                obj[food.id] = {
                    quantity:0,
                    price:food.price
                };
                Object.assign(buffer,obj);
            }
            Taro.setStorageSync("buffer",buffer);
        }

    }
    handleClick(value){
        this.setState({
            current:value
        })
    }
    changeCategory(selectedCategory){
        this.setState({
            selectedCategory:selectedCategory,
            list:foods.filter(item => selectedCategory.id === item.pid)
        })

    }
    render(){
        const {current,tabList,categories,selectedCategory,list} = this.state;
        return (
            <AtTabs current={current} tabList={tabList} onClick={this.handleClick}>
                <AtTabsPane>
                    <View  className="food-body">
                        <Category categories={categories} selectedCategory={selectedCategory} changeCategory={this.changeCategory}/>
                        <FoodList selectedCategory={selectedCategory} list={list}/>
                    </View>
                </AtTabsPane>
                <AtTabsPane>评价</AtTabsPane>
                <AtTabsPane>商家</AtTabsPane>
            </AtTabs>
        )
    }
}

export default Food;
food.scss
@import "taro-ui/dist/style/components/tabs.scss";

.food-body{
    display: flex;
    margin-bottom: 100px;
}
foodlist.jsx
import React,{Component} from "react";
import {View,Image,Text} from "@tarojs/components";
import Operation from "./operation.jsx";
import "./foodlist.scss";

class FoodList extends Component{
    render(){
        const {selectedCategory,list} = this.props;
        return (
            <View className="list">
                <View className="category-title">{selectedCategory.title}</View>
                {
                    list.map(item => (
                        <View key={item.id} className="item">
                            <Image className="item-image" src={require("../../assets/images/"+item.imgUrl+".jpg")}></Image>
                            <View className="item-info">
                                <View>
                                    <View className="item-title">{item.title}</View>
                                    <View className="item-sales">月售{item.sales}</View>
                                </View>
                                <View className="item-price"><Text className="value">{item.price}</Text></View>
                                <Operation item={item} id={item.id}/>
                            </View>
                        </View>
                    ))
                }
            </View>
        );
    }
}

export default FoodList;
foodlist.scss
.list{
    margin: 10px 0 0 20px;
    flex-grow: 1;
    .category-title{
        font-size: 26px;
        margin: 10px 0;

    }
    .item{
        margin-bottom:30px;
        display: flex;
        .item-image{
            width: 200px;
            height: 200px;
            border-radius: 10px;
            margin-right: 20px;
        }
        .item-info{
            flex-grow: 1;
            display: flex;
            flex-direction: column;
            justify-content: space-between;
            font-size: 28px;
            position: relative;
            .item-title{
                margin-bottom: 5px;
            }
            .item-sales{
                font-size: 24px;
                color: #696969;
            }
            .item-price{
                color: #f60;
                .value{
                    font-size: 48px;
                }
            }
        }
    }
}
operation.jsx
import React,{Component} from "react";
import {View,Text} from "@tarojs/components";
import Taro from "@tarojs/taro";
import "./operation.scss";
import {Event} from "../../common/utils/utils.js";


class Operation extends Component{
    constructor(){
        super(...arguments);
        this.state = {
            quantity:this.getBuffer()[this.props.id].quantity
        }
        this.onSub = this.onSub.bind(this);
        this.onAdd = this.onAdd.bind(this);

    }
    onSub(){
        this.setState(state => {
            if(state.quantity>0){
                return {
                    quantity:state.quantity-1
                }
            }
        },() => {
            let buffer = this.getBuffer();
            buffer[this.props.id].quantity--;
            Taro.setStorageSync("buffer",buffer);
            Event.emit("change");
        })
    }
    onAdd(){
        this.setState(state => ({
            quantity:state.quantity+1
        }),() => {
            let buffer = this.getBuffer();
            buffer[this.props.id].quantity++;
            Taro.setStorageSync("buffer",buffer);
            Event.emit("change");
        })
    }
    getBuffer(){
        return Taro.getStorageSync("buffer");
    }
    render(){
         const {quantity} = this.state;
        return (
            <View className="operation">
                {
                    (quantity>0) && 
                    <>
                    <Text className="substract button" onClick={this.onSub}>-</Text>
                    <Text className="quantity">{quantity}</Text>
                    </>
                }
                <Text className="add button" onClick={this.onAdd}>+</Text>
            </View> 
        );
    }
}

export default Operation;
operation.scss
.operation{
    position: absolute;
    right: 30px;
    bottom: 0;
    .button{
        display: inline-block;
        width: 48px;
        height: 48px;
        font-size: 28px;
        text-align: center;
        font-weight: bolder;
        padding: 4px;
        box-sizing: border-box;
        border-radius: 10px;
        vertical-align: middle;
    }
    .add{
        background-color: #fbcd32;
    }
    .substract{
        border: 1px solid #fbcd32;
    }
    .quantity{
        display: inline-block;
        min-width: 32px;
        max-width: 64px;
        height: 32px;
        line-height: 32px;
        text-align: center;
        margin: 0 16px;
        vertical-align: middle;
    }
}

新增目录components/head
head.jsx
import React,{Component} from 'react';

import {View} from "@tarojs/components";
import Top from "./top.jsx";
import Store from "./store.jsx";
class Head extends Component{
    render(){
        return <View>
            <Top/>
            <Store/>
        </View>
    }
}

export default Head;
store.jsx
import React,{Component} from "react";
import {View,Text,Image} from "@tarojs/components";
import "./store.scss";

class Store extends Component{
    render(){
        return (
        <View className="store-container">
            <View className="store-content">
                <View className="store-name">德克士(王府井店)</View>
                <View className="store-desc">
                    <Text>月售<Text className="sales">1362</Text></Text>
                    <Text>配送约<Text className="time">20</Text>分钟</Text>
                </View>
                <View className="store-comment">
                    <Image className="star" src={require("../../assets/images/star.png")} />
                    <Text className="score">4.7</Text>
                    <Text className="review"></Text>
                    <Text className="tag">"皮子香脆 肉汁四溢 鸡肉很嫩"</Text>
                </View>
                <View className="store-rank">
                    <Text>王府井汉堡人气第2</Text>
                    <Text>点评高分店铺</Text>
                </View>
                <View className="store-ticket">
                    <View className="left"></View>
                    <View className="middle">
                        <View className="wrapper">
                            <Text className="discount">5.2</Text><Text className="text"></Text>
                        </View>
                        <Text>商品券</Text>
                    </View>
                    <View className="right">
                        <Text></Text>
                    </View>
                </View>
                <View className="coupon-list">
                    <View className="coupon-item">新客爆品1元抢</View>
                    <View className="coupon-item">首单减12</View>
                    <View className="coupon-item">4015 | 8030</View>
                    <View className="coupon-item">天天神券</View>
                </View>
                <View className="store-notice">公告:圣诞、下雪、炸鸡...</View>
                <Image className="store-logo" src={require("../../assets/images/dicos_logo.png")} />
            </View>
        </View>
        );


    }
}

export default Store;
store.scss
.store-container{
    background-color: #fff;
    margin-top: -30px;
    border-radius: 20px;
    .store-content{
        margin-left: 40px;
        padding-top: 30px;
        position: relative;
        .store-name{
            font-size: 48px;
            font-weight: bolder;    
            margin-bottom: 10px;
        }
        .store-desc{
            font-size: 24px;
            color: #9f9f9f;
            margin-bottom: 10px;
            .sales,.time{
                color: #848484;
            }
            .sales{
                margin-right: 20px;
            }
        }
        .store-comment{
            margin-bottom: 16px;
            display: flex;
            align-items: center;
            .star{
                width: 26px;
                height: 26px;
                margin-right: 6px;
            }
            .score{
                color: #FD8000;
                margin-right: 10px;
                font-size: 36px;
            }
            .review{
                color: #1d1d1d;
                font-size: 26px;
                font-weight: bolder;
                margin-right: 20px;
            }
            .tag{
                padding-left: 20px;
                border-left: 1px solid #d9d9d9;
                font-size: 26px;
                font-weight: 500;
                color: #191919;
            }
        }
        .store-rank{
            font-size: 24px;
            margin-bottom: 24px;
            Text{
                margin-right: 10px;
                background-color: #f5f5f5;
                padding: 6px 8px;
                border-radius: 4px;
            }
        }
        .store-ticket{
            display: flex;
            margin-bottom: 12px;
            height: 50px;
            .left,.middle,.right{
                height: 50px;
            }
            .left{
                width: 50px;
                background: blanchedalmond url(../../assets/images/ticket.png) no-repeat center;
                border-bottom-right-radius: 15px;
                box-shadow: 15px 0 0 coral;
            }
            .middle{
                width: 200px;
                background: radial-gradient(circle at top right,white 4px, transparent 0) top right,
                radial-gradient(circle at bottom right,white 4px,transparent 0) bottom right;
                background-color: coral;
                color: white;
                font-size: 24px;
                display: flex;
                align-items: center;
                justify-content: space-evenly;
            }
            .discount{
                font-size: 32px;
                font-weight: bolder;
            }
            .wrapper{
                display: flex;
                align-items: center;
            }
            .right{
                width: 60px;
                line-height: 50px;
                text-align: center;
                background: radial-gradient(circle at top left,white 4px,transparent 0) top left,
                radial-gradient(circle at bottom left,white 4px,transparent 0) bottom left;
                background-color: coral;
                font-size: 24px;
                color: white;
                border-radius: 0 5px 5px 0;
                position: relative;
            }
            .right::before{
                position: absolute;
                left: 0;
                content: "";
                height: 40px;
                margin-top: 5px;
                border-left: 2px dashed white;
            }
        }
        .coupon-list{
            display: flex;
            margin-bottom: 20px;
            .coupon-item{
                margin-right: 8px;
                font-size: 24px;
                padding: 3px 5px;
                border-radius: 5px;
                color: coral;
                border: 1px solid coral;
            }
        }
        .store-notice{
            font-size: 24px;
            color: #888;
        }
        .store-logo{
            width: 200px;
            height: 150px;
            position: absolute;
            top: -10px;
            right: 40px;
            border-radius: 10px;
            z-index: 2;
        }



    }
}
top.jsx
import React,{Component} from "react";
import {View,Image} from "@tarojs/components";
import "./top.scss";
class Top extends Component{
    render(){
        return <View className="top">
            <View className="left">
                <Image className="img" src={require("../../assets/images/back.png")} />
            </View>
            <View className="right">
                <Image className="img" src={require("../../assets/images/search.png")} />
                <Image className="img" src={require("../../assets/images/collect.png")} />
                <Image className="img" src={require("../../assets/images/more.png")} />
            </View>
        </View>
    }
}

export default Top;
top.scss
.top{
    background: url(../../assets/images/dicos_store.png) no-repeat;
    background-size: 100%;
    height: 180px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    align-content: space-between;
    .img{
        width: 48px;
        height: 48px;
    }
    .left{
        display: flex;
    }
    .right{
        display: flex;
        .img{
            margin-right: 15px;
        }
    }

}
编译打包,生成dist目录

npm run dev:weapp

将dist目录导入微信开发者工具

在这里插入图片描述

遇到的问题及解决方法
Taro CLI 多版本共存

全局安装了Taro CLI 旧版本:npm install -g @tarojs/cli@2.26,但是现在新的项目需要使用Taro CLI的最新版本@tarojs.cli@3.0.23,如何解决多版本共存的问题?
那就在新项目本地安装Taro CLI的最新版本:npm install --save-dev @tarojs/cli@tarojs/cli@3.0.23),这样一来,本地编译时使用的依然是Taro CLI最新版本。
在这里插入图片描述

全局安装的Taro CLI无法全局卸载

npm install -g @tarojs/cli全局安装了Taro CLI,但cnpm uninstall -g @tarojs/cli无法卸载。
解决方法是,到npm包默认安装路径中C:\Users\admin\AppData\Roaming\npm\node_modules,删除相应目录即可。
在这里插入图片描述

PS:本例参考Taro多端框架开发外卖首页,但该课程挺多内容陈旧过时,学习起来很难受,大家在学习的过程多加注意哈。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值