UNIAPP实战项目笔记59 NodeJS后端生成token 和 修复一些bug
后端保持数据时往数据库写入token
修复一些前面遗留的问题bug
实际案例图片
后端接口文件 index.js
var express = require('express');
var router = express.Router();
var connection = require('../db/sql.js');
var user = require('../db/UserSql.js');
// 验证码
let code = '';
// 接入短信的sdk
// var QcloudSms = require('qcloudsms_js');
//设置跨域访问(设置在所有的请求前面即可)
router.all("*", function (req, res, next) {
//设置允许跨域的域名,*代表允许任意域名跨域
res.header("Access-Control-Allow-Origin", "*");
//允许的header类型
res.header("Access-Control-Allow-Headers", "content-type");
//跨域允许的请求方式
res.header("Access-Control-Allow-Methods", "DELETE,PUT,POST,GET,OPTIONS");
if (req.method == 'OPTIONS')
res.sendStatus(200); //让options尝试请求快速结束
else
next();
});
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
/* 注册->增加一条数据. */
router.post('/api/addUser', function(req, res, next) {
// 前端给后端的数据
let params = {
userName:req.body.userName,
userCode:req.body.code
};
if( params.userCode == code ){
connection.query( user.insertData( params ),function(error,results, fields){
res.send({
data:{
success:true,
msg:'注册成功'
}
});
});
}
});
/* 发送验证码 */
router.post('/api/code', function(req, res, next) {
// 前端给后端的数据
let params = {
userName : req.body.userName
}
// 短信SDK 可以使用阿里云或腾讯云的,具体接入方式以官方NodeJS代码案例
// ....
// 阿里云 官方代码 https://help.aliyun.com/document_detail/112185.html
// 腾讯云 官方代码 https://cloud.tencent.com/developer/article/1987501
// ....
// ....
var paramss = [ Math.floor( Math.random()*(9999-1000)+1000 ) ] // 要发送的验证码
// 模拟以获取到验证码
code = paramss[0];
console.log(code);
// 模拟成功返回验证码
res.send({
data:{
success:true,
code : paramss[0]
}
})
})
/* 注册验证手机号是否存在 */
router.post('/api/registered', function(req, res, next) {
// 前端给后端的数据
let params = {
userName : req.body.phone
}
// 查询手机号是否存在
connection.query(user.queryUserName(params),function(error,results,fields){
if(results.length > 0){
res.send({
data:{
success:false,
msg:"手机号已经存在!"
}
});
}else{
res.send({
data:{
success:true
}
});
}
})
});
/* 用户登录 */
router.post('/api/login', function(req, res, next) {
// 前端给后端的数据
let params = {
userName : req.body.userName,
userPwd : req.body.userPwd
}
// 查询用户名或手机号是否存在
connection.query(user.queryUserName(params),function(error,results,fields){
if(results.length > 0){
connection.query(user.queryUserPwd(params),function(erro,result){
if(result.length > 0){
res.send({
data:{
success:true,
msg:"登录成功!",
data:result[0]
}
});
}else{
res.send({
data:{
success:false,
msg:"密码不正确!"
}
});
}
})
}else{
res.send({
data:{
success:false,
msg:"用户名或手机号不存在!"
}
});
}
})
});
/* GET databases goods Detail. */
router.get('/api/goods/id', function(req, res, next) {
let id = req.query.id;
connection.query("select * from goods_search where id='"+id+"'",function(error,result,fields){
if(error) throw error;
res.send({
code:"0",
data:result
})
})
});
/* GET List Page */
router.get('/api/goods/list', function(req, res, next) {
res.send({
code:0,
name:"家居家纺",
data:[
{
id:1,
name:"家纺",
data:[
{
name:"家纺",
list:[
{
id:1,
name:"毛巾/浴巾",
imgUrl:"/static/logo.png"
},
{
id:2,
name:"枕头",
imgUrl:"/static/logo.png"
}
]
},
{
name:"生活用品",
list:[
{
id:1,
name:"浴室用品",
imgUrl:"/static/logo.png"
},
{
id:2,
name:"洗晒",
imgUrl:"/static/logo.png"
}
]
}
]
},
{
id:2,
name:"女装",
data:[
{
name:"裙装",
list:[
{
id:1,
name:"连衣裙",
imgUrl:"/static/logo.png"
},
{
id:2,
name:"半身裙",
imgUrl:"/static/logo.png"
}
]
},
{
name:"上衣",
list:[
{
id:1,
name:"T恤",
imgUrl:"/static/logo.png"
},
{
id:2,
name:"衬衫",
imgUrl:"/static/logo.png"
}
]
}
]
}
]
});
});
/* GET databases goods. */
router.get('/api/goods/search', function(req, res, next) {
/*
desc 降序 asc 升序
*/
// 获取对象的key
let [goodsName,orderName] = Object.keys(req.query);
// name参数的值
let name = req.query.name;
// orderName的key值
let order = req.query[orderName];
let sql = "select * from goods_search";
if(!(name == undefined || orderName == undefined || order == undefined)){
sql = "select * from goods_search where name like '%"+name+"%' order by "+orderName+" "+order;
}
connection.query(sql,function(error,results,fields){
res.send({
code:"0",
data:results
});
})
});
/* 首页第一次触底的数据 */
router.get('/api/index_list/1/data/2', function(req, res, next) {
res.send({
code:"0",
data:[
{
type:"commodityList",
data:[
{
id:1,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},
{
id:2,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},{
id:3,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},
{
id:4,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},
]
},
]
});
});
/* 运动户外第二次触底的数据 */
router.get('/api/index_list/2/data/3', function(req, res, next) {
res.send({
code:"0",
data:[
{
type:"commodityList",
data:[
{
id:1,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},
{
id:2,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},{
id:3,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},
{
id:4,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},
]
},
]
});
});
/* 运动户外第一次触底的数据 */
router.get('/api/index_list/2/data/2', function(req, res, next) {
res.send({
code:"0",
data:[
{
type:"commodityList",
data:[
{
id:1,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},
{
id:2,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},{
id:3,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},
{
id:4,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},
]
},
]
});
});
/* 运动户外第一次加载的数据 */
router.get('/api/index_list/2/data/1', function(req, res, next) {
res.send({
code:"0",
data:[
{
type:"bannerList",
imgUrl:"../../static/img/b3.jpg",
},
{
type:"iconsList",
data:[
{imgUrl:"../../static/logo.png",name:"运动户外"},
{imgUrl:"../../static/logo.png",name:"运动户外"},
{imgUrl:"../../static/logo.png",name:"运动户外"},
{imgUrl:"../../static/logo.png",name:"运动户外"},
{imgUrl:"../../static/logo.png",name:"运动户外"},
{imgUrl:"../../static/logo.png",name:"运动户外"},
{imgUrl:"../../static/logo.png",name:"运动户外"},
{imgUrl:"../../static/logo.png",name:"运动户外"}
]
},
{
type:"hotList",
data:[
{
id:1,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},
{
id:2,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},{
id:3,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
}
]
},
{
type:"shopList",
data:[
{
bigUrl:"../../static/img/b3.jpg",
data:[
{
id:1,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},
{
id:2,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},{
id:3,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},
{
id:4,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
}
]
}
],
},
{
type:"commodityList",
data:[
{
id:1,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},
{
id:2,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},{
id:3,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},
{
id:4,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},
]
},
]
});
});
/* 服饰内衣第一次加载的数据 */
router.get('/api/index_list/3/data/1', function(req, res, next) {
res.send({
code:"0",
data:[
{
type:"bannerList",
imgUrl:"../../static/img/b3.jpg",
},
{
type:"iconsList",
data:[
{imgUrl:"../../static/logo.png",name:"服饰内衣"},
{imgUrl:"../../static/logo.png",name:"服饰内衣"},
{imgUrl:"../../static/logo.png",name:"服饰内衣"},
{imgUrl:"../../static/logo.png",name:"服饰内衣"},
{imgUrl:"../../static/logo.png",name:"服饰内衣"},
{imgUrl:"../../static/logo.png",name:"服饰内衣"},
{imgUrl:"../../static/logo.png",name:"服饰内衣"},
{imgUrl:"../../static/logo.png",name:"服饰内衣"}
]
},
{
type:"hotList",
data:[
{
id:1,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},
{
id:2,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},{
id:3,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
}
]
},
{
type:"shopList",
data:[
{
bigUrl:"../../static/img/b3.jpg",
data:[
{
id:1,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},
{
id:2,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},{
id:3,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},
{
id:4,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
}
]
}
],
},
{
type:"commodityList",
data:[
{
id:1,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},
{
id:2,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},{
id:3,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},
{
id:4,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},
]
},
]
});
});
/* 首页推荐数据 */
router.get('/api/index_list/data', function(req, res, next) {
res.send({
"code":0,
"data":{
topBar:[
{id:1,name:'推荐'},
{id:2,name:'运动户外'},
{id:3,name:'服饰内衣'},
{id:4,name:'鞋靴箱包'},
{id:5,name:'美妆个护'},
{id:6,name:'家居数码'},
{id:7,name:'食品母婴'}
],
data:[
{
type:"swiperList",
data:[
{imgUrl:'/static/img/b3.jpg'},
{imgUrl:'/static/img/b3.jpg'},
{imgUrl:'/static/img/b3.jpg'}
]
},{
type:"recommendList",
data:[
{
bigUrl:"../../static/img/b3.jpg",
data:[
{imgUrl:'../../static/logo.png'},
{imgUrl:'../../static/logo.png'},
{imgUrl:'../../static/logo.png'}
]
},{
bigUrl:"../../static/img/b3.jpg",
data:[
{imgUrl:'../../static/logo.png'},
{imgUrl:'../../static/logo.png'},
{imgUrl:'../../static/logo.png'}
]
}
]
},{
type:"commodityList",
data:[
{
id:1,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},
{
id:2,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},{
id:3,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},
{
id:4,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},
]
},
]
}
})
});
module.exports = router;
后端接口文件 UserSql.js
let User = {
// 查询用户名或手机号
queryUserName( param ){
return "select * from user where userName = '"+param.userName+"' or phone ='"+param.userName+"'";
},
// 查询用户名和密码
queryUserPwd( param ){
return "select * from user where (userName = '"+param.userName+"' or phone ='"+param.userName+"') and userPwd ='"+param.userPwd+"'";
},
// 增加一条用户数据
insertData( param ){
// 生成token秘钥
const jwt = require('jsonwebtoken');
let payload = {name:param.userName};//用户名
let secret = 'alalal';//自定义口令
let token = jwt.sign(payload,secret);
return "insert into user (userName,userPwd,phone,imgUrl,nickName,token) values ('','a1a1a1','"+param.userName+"','../../static/logo.png','默认昵称','"+token+"')";
}
}
exports = module.exports = User;
验证验证码页面 login-code.vue
<template>
<view>
<Lines></Lines>
<view class="login-tel">
<view class="tel-main">
<view class="login-from">
<view class="login-user">
<text class="user-text">验证码</text>
<input type="text" focus="true" v-model="userCode" placeholder="请输入验证码">
<button plain="true" size="mini" :disabled="disabled" @tap="sendCode">{{codeMsg}}</button>
</view>
</view>
<view class="tel" @click="getNextIndex">下一步</view>
</view>
</view>
</view>
</template>
<script>
import $http from '@/common/api/request.js'
import Lines from '@/components/common/Lines.vue'
export default {
data() {
return {
//用户输入的验证码
userCode:"",
// 倒计时时间
codeNum:60,
// 显示的文本
codeMsg:"",
// 按钮是否禁用
disabled:false,
// 手机号
phone:'',
// 得到的验证码
getCode:"",
};
},
components:{
Lines
},
onReady() {
this.codeMsg = '重新发送('+this.codeNum+')';
this.sendCode();
},
onLoad(e) {
this.phone = e.phone;
},
methods:{
sendCode(){
// 请求接口返回验证码
$http.request({
url:'/code',
method:"POST",
data:{
userName: this.phone
}
}).then((res)=>{
console.log(res.code);
this.getCode = res.code;
}).catch(()=>{
uni.showToast({
title:'请求失败',
icon:'none'
})
})
this.disabled = true;
let timer = setInterval(()=>{
--this.codeNum;
this.codeMsg = '重新发送('+this.codeNum+')';
},1000);
setTimeout(()=>{
clearInterval(timer);
this.codeNum = 60;
this.disabled = false;
this.codeMsg = '重新发送';
},60000)
},
// 点击下一步
getNextIndex(){
if( this.getCode == this.userCode ){
// 请求后端接口 => 往数据库增加一条数据
$http.request({
url:'/addUser',
method:"POST",
data:{
userName: this.phone,
code:this.userCode
}
}).then((res)=>{
console.log(res);
// 注册成功
if( res.success ){
uni.showToast({
title:res.msg,
icon:'none'
})
setTimeout(()=>{
uni.redirectTo({
url:"../index/index"
})
},2000)
}
}).catch(()=>{
uni.showToast({
title:'请求失败',
icon:'none'
})
})
}else{
uni.showToast({
title:"验证码错误",
icon:"none"
})
}
// uni.switchTab({
// url:'/pages/index/index'
// })
}
},
}
</script>
<style lang="scss">
.login-tel{
width: 100vw;
height: 100vh;
}
.tel-main{
padding: 0 20rpx;
}
.login-from{
padding: 30rpx 0;
}
.tel{
width: 100%;
height: 80rpx;
line-height: 80rpx;
text-align: center;
color: #fff;
background-color: #40bde8;
border-radius: 40rpx;
}
.login-user{
font-size: 40rpx;
padding: 10rpx 0 ;
display: flex;
align-items: center;
border-bottom: 2rpx solid #f7f7f7;
}
.user-text{
padding-right: 10rpx;
}
</style>
手机登录页面 login.vue
<template>
<view class="login">
<swiper vertical="true" style="height: 100vh;">
<swiper-item>
<scroll-view>
<view class="login-tel">
<view class="tel-main">
<view class="close" @tap="goBack">
<image class="close-img" src="../../static/img/close-bold.png" mode=""></image>
</view>
<view class="logo">
<image class="logo-img" src="../../static/logo.png" mode=""></image>
</view>
<view class="tel" @tap="goLoginTel">手机号注册</view>
<LoginOther></LoginOther>
<view class="login-go">
<view class="">已有账号,去登录</view>
<image src="../../static/img/arrow-down.png" mode=""></image>
</view>
</view>
</view>
</scroll-view>
</swiper-item>
<swiper-item>
<scroll-view>
<view class="login-tel">
<view class="tel-main">
<view class="close close-center">
<view class="" @tap="goBack">
<image class="close-img" src="../../static/img/close-bold.png" mode=""></image>
</view>
<view class="login-go">
<image class="close-img" src="../../static/img/up.png" mode=""></image>
<view class="">没账号,去注册</view>
</view>
<view class=""></view>
</view>
<view class="login-form">
<view class="login-user">
<text class='user-text'>账号</text>
<input type="text" v-model="userName" value="" placeholder="请输入手机号/昵称"/>
</view>
<view class="login-user">
<text class='user-text'>密码</text>
<input type="safe-password" v-model="userPwd" value="" placeholder="6-16位字符"/>
</view>
</view>
<view class="login-quick">
<view class="">忘记密码</view>
<view class="">免密登录</view>
</view>
<view class="tel" @tap="submit">登录</view>
<view class="reminder">温馨提示,您可以选择免密登录,更加方便</view>
<LoginOther></LoginOther>
</view>
</view>
</scroll-view>
</swiper-item>
</swiper>
</view>
</template>
<script>
import $http from '@/common/api/request.js'
import LoginOther from '@/components/login/login-other.vue'
import {mapMutations} from 'vuex'
export default {
data() {
return {
userName:"",
userPwd:"",
rules:{
userName:{
rule:/\S/,
msg:"账号不能为空 "
},
userPwd:{
rule:/^[0-9a-zA-Z]{6,16}$/,
msg:"密码应该为6-16位字符"
}
}
};
},
components:{
LoginOther
},
methods:{
...mapMutations(['login']),
goBack(){
uni.navigateBack();
},
submit(){
if( !this.validate('userName') ) return ;
if( !this.validate('userPwd') ) return ;
uni.showLoading({
title:"登录中..."
});
// setTimeout(()=>{
// uni.hideLoading();
// uni.navigateBack();
// },2000)
$http.request({
url:'/login',
method:"POST",
data:{
userName: this.userName,
userPwd : this.userPwd
}
}).then((res)=>{
if(res.success){
// 保存用户信息
this.login(res.data);
console.log(res.data);
uni.showToast({
title:res.msg,
icon:"none"
})
uni.hideLoading();
setTimeout(()=>{
uni.navigateBack();
},1000)
}else{
uni.showToast({
title:res.msg,
icon:"none"
})
}
}).catch(()=>{
uni.showToast({
title:'请求失败',
icon:'none'
})
})
},
// 判断验证是否符合要求
validate(key){
let bool = true;
if( !this.rules[key].rule.test(this[key]) ){
uni.showToast({
title:this.rules[key].msg,
icon:'none'
});
bool = false;
return false;
}
return bool;
},
// 进入手机号注册页面
goLoginTel(){
uni.navigateTo({
url:"/pages/login-tel/login-tel"
})
}
}
}
</script>
<style lang="scss">
.login-tel{
width: 100vw;
height: 100vh;
}
.tel-main{
padding: 0 20rpx;
}
.close{
padding: 120rpx 0;
}
.close-img{
width: 60rpx;
height: 60rpx;
}
.logo{
padding: 0 100rpx;
padding-bottom: 100rpx;
display: flex;
justify-content: center;
}
.logo-img{
width: 200rpx;
height: 200rpx;
}
.tel{
width: 100%;
height: 80rpx;
line-height: 80rpx;
text-align: center;
color: #fff;
background-color: #40bde8;
border-radius: 40rpx;
}
.login-go{
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.login-go image{
width: 60rpx;
height: 60rpx;
}
// 第二屏
.close-center{
display: flex;
}
.close-center >view{
flex: 1;
}
.login-form{
padding-top: 100rpx;
}
.login-user{
font-size: 40rpx;
padding: 10rpx 0 ;
display: flex;
align-items: center;
border-bottom: 2rpx solid #f7f7f7;
}
.user-text{
padding-right: 10rpx;
}
.login-quick{
display: flex;
padding: 20rpx 0;
justify-content: space-between;
}
.reminder{
color: #ccc;
font-size: 32rpx;
padding: 20rpx 0;
text-align: center;
}
</style>
商品详情页面 details.vue
<template>
<view class="details">
<!-- 商品图 -->
<swiper :indicator-dots="true" :autoplay="true" :interval="3000" :duration="1000">
<swiper-item>
<view class="swiper-item">
<image class="swiper-img" :src="goodsContent.imgUrl" mode=""></image>
</view>
</swiper-item>
</swiper>
<!-- 价格和名称 -->
<view class="details-goods">
<view class="goods-pprice">¥{{goodsContent.pprice}} </view>
<view class="goods-oprice">¥{{goodsContent.oprice}} </view>
<view class="goods-name">{{goodsContent.name}} </view>
</view>
<!-- 商品详情图 -->
<view class="">
<view class=""><image class="details-img" src="../../static/img/b3.jpg" mode=""></image></view>
<view class=""><image class="details-img" src="../../static/img/b3.jpg" mode=""></image></view>
<view class=""><image class="details-img" src="../../static/img/b3.jpg" mode=""></image></view>
<view class=""><image class="details-img" src="../../static/img/b3.jpg" mode=""></image></view>
<view class=""><image class="details-img" src="../../static/img/b3.jpg" mode=""></image></view>
<view class=""><image class="details-img" src="../../static/img/b3.jpg" mode=""></image></view>
</view>
<!-- 商品列表 -->
<Card cardTitle="看了又看"></Card>
<CommodityList :dataList="dataList"></CommodityList>
<!-- 底部 -->
<view class="details-foot">
<view class="iconfont icon-xiaoxi"></view>
<view class="iconfont icon-31gouwuche" @tap="goShopCart"></view>
<view class="add-shopcart" @tap="showPop">加入购物车</view>
<view class="purchase" @tap="showPop">立刻购买</view>
</view>
<!-- 底部弹出层 -->
<view class="pop" v-show="isShow" @touchmove.stop.prevent="">
<!-- 蒙层 -->
<view class="pop-mask" @tap="hidePop"> </view>
<!-- 内容块 -->
<view class="pop-box" :animation="animationData">
<view class="">
<image class="pop-img" :src="goodsContent.imgUrl" mode=""></image>
</view>
<view class="pop-num">
<view class="">购买数量</view>
<UniNumberBox
:min=1
:value="num"
@change="changeNumber"
></UniNumberBox>
</view>
<view class="pop-sub" @tap="addCart">确定</view>
</view>
</view>
</view>
</template>
<script>
import $http from '@/common/api/request.js'
import Card from '@/components/common/Card.vue';
import CommodityList from '@/components/common/CommodityList.vue';
import UniNumberBox from '@/components/uni/uni-number-box/uni-number-box.vue';
import {mapMutations} from 'vuex'
export default {
data() {
return {
isShow:false,
goodsContent:{},
animationData:{},
num:1,
swiperList:[
{imgUrl:"../../static/img/b3.jpg"},
{imgUrl:"../../static/img/b3.jpg"},
{imgUrl:"../../static/img/b3.jpg"}
],
dataList:[{
id:1,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},
{
id:2,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
},{
id:3,
imgUrl:"../../static/logo.png",
name:"迪奥绒毛大衣,今年必抢,错过瞬时不爽了,爆款疯狂销售",
pprice:"299",
oprice:"659",
discount:"5.2"
}]
};
},
components:{
Card,
CommodityList,
UniNumberBox
},
onLoad(e) {
// console.log(e.id);
// 设置默认id=1
if(!e.id)
e.id = 1;
this.getData(e.id);
},
// 修改返回默认行为
onBackPress(){
if (this.isShow) {
this.hidePop();
return true;
}
},
// 点击分享
onNavigationBarButtonTap(e) {
if(e.type==='share'){
uni.share({
provider:"weixin",
type:0,
scene:"WXSceneSession",
title:this.goodsContent.name,
href:"http://127.0.0.1:8080/#/pages/details/details?id="+this.goodsContent.id,
imageUrl:this.goodsContent.imageUrl,
success:function(res){
uni.showTabBar({
title:"分享成功"
})
},
fail: (err) => {
console.log("fail:"+ JSON.stringify(err));
}
})
}
},
methods:{
...mapMutations(['addShopCart']),
// 改变商品数量
changeNumber(index,value){
this.num[index] = value;
},
// 请求商品
getData(id){
$http.request({
url:"/goods/id",
data:{
id:id
}
}).then((res)=>{
this.goodsContent = res[0];
}).catch(()=>{
uni.showToast({
title:'请求失败',
icon:'none'
})
})
},
showPop(){
// 初始化一个动画
var animation = uni.createAnimation({
duration:200 // 动画时间
});
// 定义动画内容
animation.translateY(600).step();
// 导出动画数据传递给data层
this.animationData = animation.export();
setTimeout(()=>{
this.isShow = true;
animation.translateY(0).step();
this.animationData = animation.export();
},200)
},
hidePop(){
var animation = uni.createAnimation({
duration:200
});
animation.translateY(600).step();
this.animationData = animation.export();
this.isShow = true;
setTimeout(()=>{
animation.translateY(0).step();
this.animationData = animation.export();
this.isShow = false;
},200)
},
// 跳转到购物车页面
goShopCart(){
uni.navigateTo({
url:'../shopcart/shopcart'
})
},
// 加入购物车
addCart(){
let goods = this.goodsContent;
this.goodsContent['checked'] = false;
this.goodsContent['num'] = this.num;
// 加入购物车
this.addShopCart(goods);
// 隐藏弹出框
this.hidePop();
// 提示信息
uni.showToast({
title:"成功加入购物车",
icon:"none"
})
},
}
}
</script>
<style lang="scss">
swiper{
width: 100%;
height: 700rpx;
}
.swiper-img{
width: 100%;
height: 700rpx;
}
.details{
padding-bottom: 90rpx;
}
.details-goods{
text-align: center;
font-weight: bold;
font-size: 36rpx;
padding: 10rpx 0;
}
.details-img{
width: 100%;
}
.details-foot{
position: fixed;
left: 0;
bottom: 0;
width: 100%;
height: 90rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #FFFFFF;
}
.details-foot .iconfont{
width: 60rpx;
height: 60rpx;
border-radius: 100%;
background-color: #000000;
color: #FFFFFF;
text-align: center;
margin: 0 10rpx;
line-height: 60rpx;
}
.add-shopcart{
margin: 0 40rpx;
padding: 6rpx 30rpx;
background-color: #000000;
color: #FFFFFF;
border-radius: 40rpx;
}
.purchase{
margin: 0 40rpx;
padding: 6rpx 30rpx;
background-color: #49BDFB;
color: #FFFFFF;
border-radius: 40rpx;
}
.pop{
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 9999;
}
.pop-mask{
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.3);
}
.pop-box{
position: absolute;
left: 0;
bottom: 0;
width: 100%;
background-color: #FFFFFF;
}
.pop-img{
width: 260rpx;
height: 260rpx;
top:-130rpx;
border-radius:20rpx 20rpx 0 0;
margin: 30rpx;
}
.pop-sub{
line-height: 80rpx;
background-color: #49BDFB;
color: #FFFFFF;
text-align: center;
}
.pop-num{
padding: 20rpx;
display: flex;
justify-content: space-between;
}
</style>
购物车页面 shopcart.vue
<template>
<view class="shop-cart">
<template v-if=" list.length > 0 ">
<!-- 自定义导航栏 -->
<uniNavBar
title="购物车"
:rightText=" isNavBar ? '完成' : '编辑'"
fixed="true"
statusBar="true"
@clickRight=" isNavBar = !isNavBar"
></uniNavBar>
<!-- 商品内容 -->
<view class="shop-item" v-for="(item,index) in list" :key="index">
<label for="" class="radio" @tap="selectedItem(index)">
<radio value="" color="#FF3333" :checked="item.checked" /> <text></text>
</label>
<image class="shop-img" :src="item.imgUrl" mode=""></image>
<view class="shop-text">
<view class="shop-name">
{{item.name}}
</view>
<view class="shop-color f-color">
{{item.color}}
</view>
<view class="shop-price">
<view class="">
¥{{item.pprice}}
</view>
<template v-if="!isNavBar">
<view>x {{item.num}}</view>
</template>
<template v-else>
<uniNumberBox
:value="item.num"
min="1"
@change="changeNumber($event,index)"
></uniNumberBox>
</template>
</view>
</view>
</view>
<!-- 底部 -->
<view class="shop-foot">
<label for="" class="radio foot-radio" @tap='checkedAllFn'>
<radio value="" color="#FF3333" :checked="checkedAll"></radio><text>全选</text>
</label>
<template v-if="!isNavBar">
<view class="foot-total">
<view class="foot-count">
合计:
<text class="f-active-color">
¥{{totalCount.pprice}}
</text>
</view>
<view class="foot-num" @tap="goConfirmOrder">
结算({{totalCount.num}})
</view>
</view>
</template>
<template v-else>
<view class="foot-total">
<view class="foot-num" style="background-color: black;">
移入收藏夹
</view>
<view class="foot-num" @tap="delGoodsFn">
删除
</view>
</view>
</template>
</view>
</template>
<template v-else>
<uniNavBar
title="购物车"
fixed="true"
statusBar="true"
></uniNavBar>
<view class="shop-else-list">
<text>囧~ 购物车还是空的~</text>
</view>
</template>
<Tabbar currentPage='shopcart'></Tabbar>
</view>
</template>
<script>
import uniNavBar from '@/components/uni/uni-nav-bar/uni-nav-bar.vue'
import uniNumberBox from '@/components/uni/uni-number-box/uni-number-box.vue'
import Tabbar from '@/components/common/Tabbar.vue';//引入
// 状态机引入
import {mapState,mapActions,mapGetters,mapMutations} from 'vuex'
export default {
data() {
return {
isNavBar:false,
}
},
computed:{
// 状态机数据处理
...mapState({
list:state=>state.cart.list
}),
...mapGetters(['checkedAll','totalCount'])
},
components:{
uniNavBar,uniNumberBox,Tabbar
},
methods: {
...mapActions(['checkedAllFn','delGoodsFn']),
...mapMutations(['selectedItem']),
changeNumber(value,index){
this.list[index].num = value;
},
// 进入确认订单
goConfirmOrder(){
uni.navigateTo({
url:'/pages/confirm-order/confirm-order'
})
}
}
}
</script>
<style lang="scss">
.shop-list{
padding-bottom: 100rpx;
}
.shop-else-list{
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: #f7f7f7;
display: flex;
align-items: center;
justify-content: center;
}
.shop-item{
display: flex;
padding: 20rpx;
align-items: center;
background-color: #f7f7f7;
margin-bottom: 10rpx;
}
.shop-img{
width: 200rpx;
height: 200rpx;
}
.shop-text{
flex: 1;
padding-left: 20rpx;
}
.shop-color{
font-size: 24rpx;
}
.shop-price{
display: flex;
justify-content: space-between;
}
.shop-foot{
border-top: 2rpx solid #f7f7f7;
background-color: #FFFFFF;
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 100rpx;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 120rpx;
}
.foot-radio{
padding-left: 20rpx;
}
.foot-total{
display: flex;
}
.foot-count{
line-height: 100rpx;
padding: 0 20rpx;
font-size: 32rpx;
}
.foot-num{
background-color: #49bdfb;
color: #FFFFFF;
padding: 0 60rpx;
line-height: 100rpx;
}
</style>
目录结构
前端目录结构
-
manifest.json 配置文件: appid、logo…
-
pages.json 配置文件: 导航、 tabbar、 路由
-
main.js vue初始化入口文件
-
App.vue 全局配置:样式、全局监视
-
static 静态资源:图片、字体图标
-
page 页面
- index
- index.vue
- list
- list.vue
- my
- my.vue
- my-config
- my-config.vue
- my-config
- my-config.vue
- my-add-path
- my-add-path.vue
- my-path-list
- my-path-list.vue
- search
- search.vue
- search-list
- search-list.vue
- shopcart
- shopcart.vue
- details
- details.vue
- my-order
- my-order.vue
- confirm-order
- confirm-order.vue
- payment
- payment.vue
- payment-success
- payment-success.vue
- login
- login.vue
- login-tel
login-tel.vue
- login-code
login-code.vue
- index
-
components 组件
- index
- Banner.vue
- Hot.vue
- Icons.vue
- indexSwiper.vue
- Recommend.vue
- Shop.vue
Tabbar.vue
- common
- Card.vue
- Commondity.vue
- CommondityList.vue
- Line.vue
- ShopList.vue
- order
- order-list.vue
- uni
- uni-number-box
- uni-number-box.vue
- uni-icons
- uni-icons.vue
- uni-nav-bar
- uni-nav-bar.vue
- mpvue-citypicker
- mpvueCityPicker.vue
- uni-number-box
- index
-
common 公共文件:全局css文件 || 全局js文件
- api
- request.js
- common.css
- uni.css
- api
-
store vuex状态机文件
- modules
- cart.js
- path.js
- user.js
- index.js
- modules