全局安装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">40减15 | 80减30</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多端框架开发外卖首页,但该课程挺多内容陈旧过时,学习起来很难受,大家在学习的过程多加注意哈。