新建vue项目
main.js
import Vue from 'vue'
import App from './App.vue'
import store from "@/store";
import router from "@/router";
import '@/utils/vant-ui';
import '@/styles/common.css'
Vue.config.productionTip = false;
new Vue({
render: h => h(App),
router,
store,
}).$mount('#app');
app.vue
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style>
</style>
search list.vue
<template>
<div class="search">
<van-nav-bar title="商品列表" fixed left-arrow @click-left="$router.go(-1)"></van-nav-bar>
<van-search
readonly
shape="round"
background="#ffffff"
:value="querySearch || '搜索商品'"
show-action
@click="$router.push('/search')">
<template #action>
<van-icon class="tool" name="apps-o"></van-icon>
</template>
</van-search>
<!-- 排序按钮 -->
<div class="sort-btns">
<div class="srot-item">综合</div>
<div class="srot-item">销量</div>
<div class="srot-item">价格</div>
</div>
<div class="goods-list">
<goods-item v-for="item in proList" :key="item.goods_id" :item="item"></goods-item>
</div>
</div>
</template>
<script>
import GoodsItem from "@/components/GoodsItem";
import { getProductList } from '@/api/product';
export default {
name: "ListIndex",
data(){
return {
page:1,
proList:[]
}
},
components:{
GoodsItem
},
computed:{
//获取地址栏的搜索关键字
querySearch(){
return this.$route.query.search;
}
},
async created(){
const { data:{ list }} = await getProductList({
categoryId: this.$route.query.categoryId,
goodsName: this.querySearch,
page: this.page
});
this.proList = list.data;
}
}
</script>
<style scoped>
.search{
padding-top: 46px;
}
.search ::v-deep .van-icon-arrow-left{
color: #333333;
}
.search .tool{
font-size: 24px;
height: 40px;
line-height: 40px;
}
.sort-btns{
display: flex;
height: 36px;
line-height: 36px;
}
.sort-btns .srot-item{
text-align: center;
flex: 1;
font-size: 16px;
}
.goods-list{
background-color: #f6f6f6;
}
</style>
search index.vue
<template>
<div class="search">
<van-nav-bar title="商品搜索" left-arrow @click-left="$router.go(-1)"></van-nav-bar>
<van-search v-model="search" show-action placeholder="请输入搜索关键字">
<template #action>
<div @click="goSearch(search)">搜索</div>
</template>
</van-search>
<!-- 搜索历史 -->
<div class="search-history" v-if="history.length >0 ">
<div class="title">
<span>最近搜索</span>
<van-icon name="delete-o" size="16" @click="clear"/>
</div>
<div class="list">
<div class="list-item" v-for="item in history" :key="item" @click="goSearch(item)">{{ item}}</div>
</div>
</div>
</div>
</template>
<script>
import { getHistoryList, setHistoryList} from '@/utils/storage'
export default {
name: "SearchIndex",
data(){
return {
search: '', //输入框的值
history: getHistoryList() //历史记录
}
},
methods:{
goSearch(key){
//查下标
const index = this.history.indexOf(key);
if(index !== -1){
//存在相同项,将原有关键词删除
this.history.splice(index,1);
}
//在把关键词添加在首位
this.history.unshift(key);
this.search = '';
//存储本地化
setHistoryList(this.history);
//跳转到搜索列表页
this.$router.push(`/searchList?search=${key}`);
},
//清空按钮
clear(){
this.history = [];
//清除本地化
setHistoryList([]);
}
}
}
</script>
<style scoped>
.search .searchBtn{
background-color: #fa2209;
color: #ffffff;
}
.search ::v-deep .van-search__action{
background-color: #c21401;
color: #ffffff;
padding: 0 20px;
border-radius: 0 5px 5px 0;
margin-right: 10px;
}
.search ::v-deep .van-icon-arrow-left{
color: #333333;
}
.search-history .title{
height: 40px;
line-height: 40px;
font-size: 14px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 15px;
}
.list{
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
padding: 0 10px;
gap: 5%;
}
.list-item{
width: 30%;
text-align: center;
padding: 7px;
line-height: 15px;
border-radius: 50px;
background-color: #ffffff;
font-size: 13px;
border: 1px solid #efefef;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin-bottom: 10px;
}
</style>
productDetails index.vue
<template>
<div class="product-details">
<van-nav-bar title="商品详情页" fixed left-arrow @click-left="$router.go(-1)"></van-nav-bar>
<van-swipe :autoplay="4000" @change="onChange">
<van-swipe-item v-for="(image,index) in images" :key="index">
<img :src="image.external_url">
</van-swipe-item>
<template #indicate>
<div class="custom-indicator">{{ current + 1}} / {{ images.length }}</div>
</template>
</van-swipe>
<!-- 商品说明 -->
<div class="info">
<div class="title">
<div class="price">
<span class="now">¥{{ details.goods_price_min}}</span>
<span class="old-price">¥{{ details.goods_price_max }}}</span>
</div>
<div class="sell-count">已售{{ details.goods_sales }}件</div>
</div>
<div class="msg text-ellipsis-2">
{{ details.goods_name }}
</div>
<div class="service">
<div class="left-words">
<span><van-icon name="passed"/>七天无理由退货</span>
<span><van-icon name="passed">48小时发货</van-icon></span>
</div>
<div class="right-icon">
<van-icon name="arrow"/>
</div>
</div>
</div>
<!-- 商品评价 -->
<div class="comment">
<div class="comment-title">
<div class="left">商品评价{{ commentTotal }}条</div>
<div class="right">查看更多 <van-icon name="arrow"/></div>
</div>
<div class="comment-list">
<div class="comment-item" v-for="item in commentList" :key="item.comment_id">
<div class="top">
<img :src="item.user.avatar_url || defaultImg" alt="">
<div class="name">{{ item.user.nick_name }}</div>
<van-rate :size="16" :value="item.score / 2" color="#ffd21e" void-icon="star"></van-rate>
</div>
<div class="content">
{{ item.content }}
</div>
<div class="time">
{{ item.create_time }}
</div>
</div>
</div>
</div>
<!-- 商品描述 -->
<div class="desc" v-html="details.content">
</div>
<div class="footer">
<div class="icon-home">
<van-icon name="wap-home-o"/>
<span>首页</span>
</div>
<div class="icon-cart">
<van-icon name="shopping-cart-o"/>
<span>购物车</span>
</div>
<div class="btn-add">加入购物车</div>
<div class="btn-buy">立即购买</div>
</div>
</div>
</template>
<script>
import { getProductDetails, getProductComments } from '@/api/product'
import defaultImg from '@/assets/default-avatar.png'
export default {
name: "ProductDetailsIndex",
data(){
return {
images:[],
current:0,
details:{},
commentList:[], //评价数组
commentTotal:0, // 评价总数
defaultImg
}
},
methods:{
onChange(index){
this.current = index;
},
async getDetails(){
const { data: { detail }} = await getProductDetails(this.goodsId);
//console.log(res);
this.details = detail;
this.images = detail.goods_images;
},
async getComments(){
const { data: {list,total}} = await getProductComments(this.goodsId,3);
//console.log(res);
this.commentList = list;
this.commentTotal = total;
}
},
computed:{
goodsId(){
return this.$route.params.id;
}
},
created(){
this.getDetails();
this.getComments();
}
}
</script>
<style scoped>
.product-details{
padding-top: 46px;
}
.product-details ::v-deep .van-icon-arrow-left{
color: #333333;
}
.product-details img{
display: block;
width: 100%;
}
.product-details .custom-indicator{
position: absolute;
right: 10px;
bottom: 10px;
padding: 5px 10px;
font-size: 12px;
background: rgba(0,0.0,.1);
border-radius: 15px;
}
.product-details .desc{
width: 100%;
overflow: scroll;
}
.product-details .desc ::v-deep img{
display: block;
width: 100%!important;
}
.product-details .info{
padding: 10px;
}
.product-details .info .title{
display: flex;
justify-content: space-between;
}
.product-details .info .now{
color: #fa2209;
font-size: 20px;
}
.product-details .info .old-price{
color: #959595;
font-size: 16px;
text-decoration: line-through;
margin-left: 5px;
}
.product-details .info .sell-count{
color: #959595;
font-size: 16px;
position: relative;
top: 4px;
}
.product-details .info .msg{
font-size: 16px;
line-height: 24px;
margin-top: 5px;
}
.info .service{
display: flex;
justify-content: space-between;
line-height: 40px;
margin-top: 10px;
font-size: 16px;
background-color: #fafafa;
}
.info .service .left-words span{
margin-right: 10px;
}
.info .service .left-words .van-icon{
margin-right: 4px;
color: #fa2209;
}
.comment{
padding: 10px;
}
.comment .comment-title{
display: flex;
justify-content: space-between;
}
.comment .comment-title .right{
color: #959595;
}
.comment .comment-item {
font-size: 16px;
line-height: 30px;
}
.comment .comment-item .top{
height: 30px;
display: flex;
align-items: center;
margin-top: 20px;
}
.comment .comment-item img{
width: 20px;
height: 20px;
}
.comment .comment-item .name{
margin: 0 10px;
}
.comment .comment-item .time{
color: #999999;
}
.footer{
position: fixed;
left: 0;
bottom: 0;
width: 100%;
height: 55px;
background-color: #ffffff;
border-top: 1px solid #cccccc;
display: flex;
justify-content: space-evenly;
align-items: center;
}
.footer .icon-home,.icon-cart{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 14px;
}
.footer .van-icon{
font-size: 24px;
}
.footer .btn-add,.btn-buy{
height: 36px;
line-height: 36px;
width: 120px;
border-radius: 18px;
background-color: #ffa900;
text-align: center;
color: #ffffff;
font-size: 14px;
}
.footer .btn-buy{
background-color: #fe5630;
}
.tips{
padding: 10px;
}
</style>
pay index.vue
<template>
<div>我是Pay</div>
</template>
<script>
export default {
name: "PayIndex"
}
</script>
<style scoped>
</style>
myorder index.vue
<template>
<div>我是MyOrder</div>
</template>
<script>
export default {
name: "MyOrderIndex"
}
</script>
<style scoped>
</style>
login index.vue
<template>
<div class="login">
<!-- 头部 -->
<van-nav-bar
title="会员登录"
left-arrow
@click-left="$router.go(-1)"></van-nav-bar>
<!-- 底部 -->
<div class="container">
<div class="title">
<h3>手机号登录</h3>
<p>未注册的手机号登录后自动注册</p>
</div>
<div class="form">
<div class="form-item">
<input type="text" v-model="mobile" class="inp" placeholder="请输入手机号码" maxlength="11">
</div>
<div class="form-item">
<input type="text" v-model="picCode" class="inp" placeholder="请输入图形验证码" max="5">
<img v-if="picUrl" :src="picUrl" alt="" @click="getPicCode">
</div>
<div class="form-item">
<input type="text" v-model="smsCode" class="inp" placeholder="请输入短信验证码" >
<button @click="handleCode">
{{ currentSecond === totalSecond ? '获取验证码' : currentSecond + '秒之后重新发送'}}
</button>
</div>
</div>
<div class="login-btn" @click="handleLogin">登录</div>
</div>
</div>
</template>
<script>
//按需导入
import { getRandomPicCode, getMsgCode, doLogin } from '@/api/login'
//import { Toast } from 'vant'
export default {
name: "LoginIndex",
data(){
return {
picKey:'', //请求后台时的图形验证码唯一标识
picUrl:'', //存储请求渲染的图片地址,
totalSecond: 60, //总秒数
currentSecond: 60, //当前数秒
timer: null, //定时器
mobile:'', //手机号
picCode:'', //用户输入的图形验证码
smsCode:'' //短信验证码
}
},
created(){
//页面一加载,就请求验证码
this.getPicCode()
},
methods:{
//获取图形验证码
async getPicCode(){
//展开分解
const { data:{ base64, key }} = await getRandomPicCode();
this.picUrl = base64; //存储地址
this.picKey = key; // 存储唯一标识
//Toast('获取图形验证码成功!');
this.$toast('获取验证码成功');
},
//获取短信验证码
async handleCode(){
//校验没通过,不发送短信验证码
if(!this.validFn()){
return;
}
//当前没有开启定时器,且totalSecond==CurrentSecond时,
if (!this.timer && this.currentSecond === this.totalSecond){
//发送请求,
const res = await getMsgCode(this.picCode,this.picKey,this.mobile);
//console.log(res);
this.$toast('短信发送成功,请注意查收!' + res.message);
this.timer = setInterval( ()=>{
console.log('正在倒计时...');
//开始数秒
this.currentSecond--;
//如果当前秒 = 0时,关闭清除定时器,恢复当前秒
if(this.currentSecond <= 0){
clearInterval(this.timer);
this.timer = null;
this.currentSecond = this.totalSecond;
}
},1000);
}
},
//校验 手机号和图形验证码是否合法
validFn(){
//手机号正则表达式
if(!(/^1[3-9]\d{9}$/.test(this.mobile))){
this.$toast('请输入正确的手机号码');
return false;
}
//图形验证码正则表达式
if(!(/^\w{4}$/.test(this.picCode))){
this.$toast('请输入正确的图形验证码');
return false;
}
return true;
},
//登录
async handleLogin(){
if(!this.validFn()){
return;
}
if(!(/^\d{6}$/.test(this.smsCode))){
this.$toast('请输入正确的短信验证码');
return;
}
const res = await doLogin(this.mobile,this.smsCode);
//console.log(res);
if(res.status === 200){
//存入vuex
this.$store.commit('user/setUserInfo',res.data);
this.$toast(res.message);
await this.$router.push('/');
}
}
},
destroyed(){
//离开页面,清除定时器
clearInterval(this.timer);
}
}
</script>
<style scoped>
.container{
padding: 49px 29px;
}
.container .title{
margin-bottom: 20px;
}
.title h3{
font-size: 26px;
font-weight: normal;
}
.title p{
line-height: 40px;
font-size: 14px;
color: #b8b8b8;
}
.form-item{
border-bottom: 1px solid #f3f1f2;
padding: 8px;
margin-bottom: 14px;
display: flex;
align-items: center;
}
.form-item .inp{
display: block;
border:none;
outline: none;
height: 32px;
font-size: 14px;
flex: 1;
}
.form-item img{
width: 94px;
height: 31px;
}
.form-item button{
height: 31px;
border: none;
font-size: 13px;
color: #cea26a;
background-color: transparent;
padding-right: 9px;
}
.login-btn{
width: 100%;
height: 42px;
margin-top: 39px;
background: linear-gradient(90deg,#ecb53c,#ff9211,#ff9211);
color: #ffffff;
border-radius: 39px;
box-shadow: 0 10px 20px 0 rgba(0,0,0,.1);
letter-spacing: 2px;
display: flex;
justify-content: center;
align-items: center;
}
</style>
layout user.vue
<template>
<div>我是user</div>
</template>
<script>
export default {
name: "UserPage"
}
</script>
<style scoped>
</style>
layout index.vue
<template>
<div class="layout-index">
<!-- 二级路由出口,二级组件展示的位置 -->
<router-view></router-view>
<van-tabbar route active-color="#ee0a24" inactive-color="#000">
<van-tabbar-item to="/home" icon="wap-home-o">首页</van-tabbar-item>
<van-tabbar-item to="/category" icon="apps-o">分类页</van-tabbar-item>
<van-tabbar-item to="/cart" icon="shopping-cart-o">购物车</van-tabbar-item>
<van-tabbar-item to="/user" icon="user-o">我的</van-tabbar-item>
</van-tabbar>
</div>
</template>
<script>
export default {
name: "LayoutIndex"
}
</script>
<style scoped>
</style>
layout home.vue
<template>
<div class="home">
<!-- 导航条 -->
<van-nav-bar title="模拟商城" fixed/>
<!-- 搜索框 -->
<van-search readonly shape="round" background="#f1f1f2"
placeholder="请在此输入搜索关键词" @click="$router.push('/search')" />
<!-- 轮播图 -->
<van-swipe class="my-swipe" :autoplay="3000" indicator-color="white">
<van-swipe-item v-for="item in bannerList" :key="item.imgUrl">
<img :src="item.imgUrl" alt="">
</van-swipe-item>
</van-swipe>
<!--导航 -->
<van-grid column-num="5" icon-size="40">
<van-grid-item v-for="item in navList" :key="item.imgUrl"
:icon = "item.imgUrl"
:text="item.text"
@click="$router.push('/category')"></van-grid-item>
</van-grid>
<!-- 主会场 -->
<div class="main">
<img src="" alt="">
</div>
<!-- 猜你喜欢 -->
<div class="guess">
<p class="guess-title">--猜你喜欢--</p>
<div class="goods-list">
<GoodsItem v-for="item in productList" :key="item.goods_id" :item="item"></GoodsItem>
</div>
</div>
</div>
</template>
<script>
import GoodsItem from "@/components/GoodsItem";
import { getHomeData } from '@/api/home'
export default {
name: "HomePage",
data(){
return{
bannerList:[], //轮播图
navList:[], // 导航
productList:[], //商品
}
},
components:{
GoodsItem
},
async created(){
const { data:{ pageData }} = await getHomeData();
console.log(pageData);
this.bannerList = pageData.items[1].data;
this.navList = pageData.items[3].data;
this.productList = pageData.items[6].data;
}
}
</script>
<style scoped>
.home {
padding-top: 100px;
padding-bottom: 50px;
}
.van-nav-bar{
z-index: 999;
background-color: #c21401;
}
.home .van-nav-bar__title{
color: #ffffff;
}
.van-search{
position: fixed;
width: 100%;
top: 46px;
z-index: 999;
}
.my-swipe .van-swipe-item{
height: 185px;
color: #ffffff;
font-size: 20px;
text-align: center;
background-color: #39a9ed;
}
.my-swipe .van-swipe-item img{
width: 100%;
height: 185px;
}
/*主会场*/
.main img{
display: block;
width: 100%;
}
/*猜你喜欢*/
.guess .guess-title{
height: 40px;
line-height: 40px;
text-align: center;
}
/*商品样式*/
.goods-list{
background-color: #f6f6f6;
}
</style>
layout category.vue
<template>
<div class="category">
<!-- 分类 -->
<van-nav-bar title="全场分类" fixed></van-nav-bar>
<!-- 搜索框 -->
<van-search
readonly
shape="round"
background="#f1f1f2"
placeholder="请输入搜索关键词"
@click="$router.push('/search')">
</van-search>
<!-- 分类列表 -->
<div class="list-box">
<div class="left">
<ul>
<li v-for="(item,index) in list" :key="item.category_id">
<a :class="{ active: index === activeIndex}" @click="activeIndex = index" href="javascript:;">{{ item.name }}</a>
</li>
</ul>
</div>
<div class="right">
<div @click="$router.push(`/searchList?categoryId=${item.category_id}`)" v-for="item in list[activeIndex]?.children" :key="item.category_id" class="cate-goods">
<img :src="item.image?.external_url" alt="">
<p>{{ item.name }}</p>
</div>
</div>
</div>
</div>
</template>
<script>
import { getCategoryData } from '@/api/category'
export default {
name: "CategoryPage",
data(){
return {
list: [],
active: false,
activeIndex:0
}
},
async created(){
const res = await getCategoryData();
console.log(res);
this.list = res.data.list;
}
}
</script>
<style scoped>
.list-box{
padding-top: 100px;
}
.list-box .left{
width: 100px;
float: left;
background-color: #f6f6f6;
}
.left .active{
background-color: white;
}
.left a{
display: block;
height: 45px;
line-height: 45px;
text-align: center;
color: #444444;
font-size: 12px;
}
.left a&.active{
color: #fb442f;
background-color: #ffffff;
}
.right {
flex: 1;
height: 100%;
background-color: #ffffff;
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
align-content: flex-start;
padding: 10px 0;
overflow: auto;
}
.right .cate-goods {
width: 33.3%;
margin-bottom: 10px;
}
.cate-goods img{
width: 70px;
height: 70px;
display: block;
margin: 5px auto;
}
.cate-goods p{
text-align: center;
font-size: 12px;
}
.van-nav-bar {
z-index: 999;
}
.van-search{
position: fixed;
width: 100%;
top: 46px;
z-index: 999;
}
</style>
layout cart.vue
<template>
<div>我是cart</div>
</template>
<script>
export default {
name: "CartPage"
}
</script>
<style scoped>
</style>
vant-ui.js
import Vue from 'vue'
//Vant组件库,全部导入方式
// npm i vant@latest-v2 -s
// import Vant from 'vant'
// import 'vant/lib/index.css'
// Vue.use(Vant);
//按需导入
import {
Button,
Tabbar,
TabbarItem,
NavBar,
Toast,
Search,
Swipe,
SwipeItem,
Grid,
GridItem,
Icon,
Rate
} from 'vant'
//注册使用
Vue.use(Button);
//底部菜单栏
Vue.use(Tabbar);
Vue.use(TabbarItem);
//登录页顶部导航栏
Vue.use(NavBar);
//轻提示
Vue.use(Toast);
//搜索框
Vue.use(Search);
//轮播图
Vue.use(Swipe);
Vue.use(SwipeItem);
//网格
Vue.use(Grid);
Vue.use(GridItem);
//图标
Vue.use(Icon);
//5星评价
Vue.use(Rate);
storage.js
//约定一个通用的键名
const INFO_KEY = 'xx_shopping_info';
//搜索关键字的键名
const HISTORY_KEY = 'xx_history_list';
//获取个人登录信息
export const getInfo = () => {
const defaultObj = { token:'',userId:''};
const result = localStorage.getItem(INFO_KEY);
return result ? JSON.parse(result) : defaultObj;
};
//设置个人信息
export const setInfo = (obj) => {
localStorage.setItem(INFO_KEY,JSON.stringify(obj));
};
//删除个人信息
export const removeInfo = () => {
localStorage.removeItem(INFO_KEY);
};
//获取搜索历史
export const getHistoryList = () => {
const result = localStorage.getItem(HISTORY_KEY);
return result ? JSON.parse(result) : [];
};
//设置搜索历史
export const setHistoryList = (arr) => {
localStorage.setItem(HISTORY_KEY,JSON.stringify(arr));
};
request.js
import axios from "axios";
import { Toast } from 'vant'
//创建 axios 实例,将来对创建出来的实例,进行自定义配置
//好处,不会污染原始的axios
const instance = axios.create({
baseURL:'http://cba.itlike.com/public/index.php?s=/api/',
timeout:5000
});
//自定义配置 请求-响应 拦截器
//添加请求拦截器
instance.interceptors.request.use(function (config) {
//在发送请求之前做些什么
// 开启loading,禁止背景点击,节流处理,防止用户多次无效触发
Toast.loading({
message:'加载中...',
forbidClick:true, //是否禁止点击背景功能
loadingType: "spinner", //配置loading图标
duration: 0, //图标不会自动消失
});
return config;
},function (error) {
//对请求错误做些什么
return Promise.reject(error);
});
//添加响应拦截器
instance.interceptors.response.use(function (response) {
//2xx 范围内的状态码都会触发该函数
//对响应数据做点什么
const res = response.data;
if(res.status !== 200){
//提示
Toast(res.message);
//抛出一个错误的Promise
return Promise.reject(res.message);
}
else{
//正确情况下,清除loading效果
Toast.clear();
}
return res;
},function (error) {
//超出2xx 范围的状态码都会触发该函数
//对响应错误做点什么
return Promise.reject(error);
});
//导出配置好的实例
export default instance;
common.css
/*重置默认样式*/
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/*文字溢出省略号*/
.text-ellipsis-2{
overflow: hidden;
-webkit-line-clamp:2;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient:vertical;
}
/*
添加导航的通用样式
*/
.van-nav-bar .van-nav-bar__arrow{
color: #333333;
}
store index.js
import Vue from 'vue'
import Vuex from 'vuex'
import user from "@/store/modules/user";
Vue.use(Vuex);
const store = new Vuex.Store({
getters:{
//获取tosken
token(state){
return state.user.userInfo.token;
}
},
modules:{
user
}
});
export default store;
store user.js
import { getInfo, setInfo } from '@/utils/storage'
export default {
//开启命名空间
namespaced:true,
state(){
return {
//个人权证相关
userInfo: getInfo()
}
},
mutations:{
//所有mutation的参数都是state
setUserInfo(state,obj){
state.userInfo = obj;
setInfo(obj);
}
},
actions:{
},
getters:{
}
}
router index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '@/views/login'
import Layout from '@/views/layout'
import MyOrder from '@/views/myorder'
import Pay from '@/views/pay'
import ProductDetails from '@/views/productdetails'
import Search from '@/views/search'
import SearchList from '@/views/search/list'
import Home from "@/views/layout/home";
import Cart from "@/views/layout/cart";
import Category from "@/views/layout/category";
import User from "@/views/layout/user";
import store from "@/store";
Vue.use(VueRouter);
const router = new VueRouter({
routes:[
{ path:'/login',component:Login},
{
path:'/',
component: Layout,
redirect:'/home',
children:[
{ path:'/home',component:Home},
{ path:'/category',component:Category},
{ path:'/cart',component: Cart},
{ path:'/user',component:User}
]
},
{ path:'/search',component:Search},
{ path:'/searchList',component: SearchList},
{ path:'/productDetails/:id',component:ProductDetails},
{ path:'/pay',component:Pay},
{ path:'/myOrder',component:MyOrder}
]
});
//全局前置导航守卫
// 所有的路径在其被真正访问之前,解析渲染对应的组件页面前,
// 都会完整经过全部前置守卫,只有全局前置守卫放行了,
//才会到达对应的页面
// to 到哪里去
// from 从哪里来得
// next 是否放行 next()直接放行,next(路径),进行拦截
//定义一个数组,专用存放用户需要拦截的页面路径
const authUrl = ['/pay','/myOrder'];
router.beforeEach((to, from, next) => {
//看to.path 是否在authUrl中出现
if(!authUrl.includes(to.path)){
//非权限页面,无须登录,直接放行
next();
return;
}
//是权限页面,需要判断token
const token = store.getters.token;
if(token){
next();
}
else{
next('/login');
}
});
export default router;
goodsitem.vue
<template>
<div class="goods-item" v-if="item.goods_id" @click="$router.push(`/productDetails/${item.goods_id}`)">
<div class="left">
<img :src="item.goods_image" alt="">
</div>
<div class="right">
<p class="tit text-ellipsis-2">
{{ item.goods_name }}
</p>
<p class="count">{{ item.goods_sales }}</p>
<p class="price">
<span class="new">¥{{ item.goods_price_min }}</span>
<span class="old">¥{{ item.goods_price_max }}</span>
</p>
</div>
</div>
</template>
<script>
export default {
name: "GoodsItem",
props:{
item:{
type: Object,
//默认值
default: () => {
return {}
}
}
}
}
</script>
<style scoped>
.goods-item{
height: 148px;
margin-bottom: 6px;
padding: 10px;
background-color: #ffffff;
display: flex;
}
.goods-item .left{
width: 127px;
}
.left img{
display: block;
width: 100%;
}
.goods-item .right{
flex: 1;
font-size: 14px;
line-height: 1.3;
padding: 10px;
display: flex;
flex-direction: column;
justify-content: space-evenly;
}
.right .count{
color: #999999;
font-size: 12px;
}
.right .price{
color: #999999;
font-size: 16px;
}
.right .new{
color: #f03c3c;
margin-right: 10px;
}
.right .old{
text-decoration: line-through;
font-size: 12px;
}
</style>
api product.js
import requset from '@/utils/request'
//获取搜索商品列表的数据
export const getProductList = (obj) => {
const { categoryId, goodsName, page} = obj;
return requset.get('/goods/list',{
params:{
categoryId,
goodsName,
page
}
})
};
//获取商品详情数据
export const getProductDetails = (goodsId) => {
return requset.get('/goods/detail',{
params:{
goodsId
}
})
};
//获取商品评价
export const getProductComments = (goodsId,limit) => {
return requset.get('/comment/listRows',{
params:{
goodsId,
limit
}
})
};
api login.js
//用于存放所有登录有关的请求接口
import request from '@/utils/request'
//1 获取图形验证码
export const getRandomPicCode = () =>{
return request.get('/captcha/image');
};
//2 获取短信验证码
export const getMsgCode = (captchaCode,captchaKey,mobile) => {
return request.post('/captcha/sendSmsCaptcha',{
form:{
captchaCode, //输入的验证码
captchaKey, //
mobile //手机号
}
});
};
//3. 登录接口
export const doLogin = (mobile,smsCode) => {
return request.post('/passport/login',{
form:{
isParty:false, //是否第三方登录
partyData:{}, //第三方的登录信息
mobile, //手机号
smsCode // 短信验证码
}
})
};
api home.js
import request from '@/utils/request'
//获取首页数据
export const getHomeData = () => {
return request.get('/page/detail',{
params:{
pageId:0
}
})
};
api home.js
import requset from '@/utils/request'
//获取分类页数据
export const getCategoryData = () => {
return requset.get('/category/list');
};