小米商城
router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import axios from 'axios'
import { Toast } from 'vant'
Vue.use(VueRouter)
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
}
const router = new VueRouter({
routes: [
{
path: '/',
redirect: '/index/content'
},
{
path: '/index',
name: 'Index',
component: () => import('../views/Index'),
children: [
{
path: 'content',
name: 'Content',
component: () => import('../views/Content'),
meta: {
title: '小米商城'
}
},
{
path: 'cart',
name: 'Cart',
component: () => import('../views/Cart'),
beforeEnter: (to, from, next) => {
axios.get("/user/detail", {
params: {
token: localStorage.getItem("miUserToken")
? JSON.parse(localStorage.getItem("miUserToken")).token
: "",
},
}).then((res) => {
if (res.data.code == 2000) {
Toast(res.data.msg);
router.app.$router.push("/login");
} else {
next()
}
})
},
meta: {
title: '购物车'
}
},
{
path: 'home',
name: 'Home',
component: () => import('../views/Home'),
meta: {
title: '个人中心',
keepAlive: true,
},
}
]
},
{
path: '/detail',
name: 'Detail',
component: () => import('../views/Detail'),
meta: {
title: '商品详情'
}
},
{
path: '/login',
name: 'Login',
component: () => import('../views/Login'),
meta: {
title: '登录'
}
},
{
path: '/register',
name: 'Register',
component: () => import('../views/Register'),
meta: {
title: '注册'
}
}
]
})
router.beforeEach((to, from, next) => {
to.meta.title ? document.title = to.meta.title : document.title = 'undefined'
next()
})
export default router
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import local from './local'
Vue.use(Vuex)
import { Toast } from 'vant'
export default new Vuex.Store({
state: {
cartArr: local.get('miCart'),
allChecked: true
},
mutations: {
add2Cart(state, obj) {
obj.checked = true
obj.date = new Date().getTime()
let index = state.cartArr.findIndex(val => val.id == obj.id)
if (index == -1) {
obj.num = 1
state.cartArr.push(obj)
} else {
state.cartArr[index].num < state.cartArr[index].stores ? state.cartArr[index] += 1 : Toast('没货了!')
}
local.set('miCart', state.cartArr)
},
checkAll(state, allChecked) {
state.allChecked = allChecked
state.cartArr.map(val => val.checked = allChecked)
local.set('miCart', state.cartArr)
},
checkItem(state) {
state.cartArr.findIndex(val => val.checked == false) == -1 ? state.allChecked = true : state.allChecked = false
local.set('miCart', state.cartArr)
},
minusItem(state, id) {
let index = state.cartArr.findIndex(val => val.id == id)
state.cartArr[index].num > 1 ? state.cartArr[index].num -= 1 : Toast('如果不要请点删除')
local.set('miCart', state.cartArr)
},
plusItem(state, id) {
let index = state.cartArr.findIndex(val => val.id == id)
state.cartArr[index].num < state.cartArr[index].stores ? state.cartArr[index].num += 1 : Toast('最大库存了')
local.set('miCart', state.cartArr)
},
del(state, id) {
state.cartArr.splice(state.cartArr.findIndex(val => val.id == id), 1)
local.set('miCart', state.cartArr)
}
},
actions: {
},
modules: {
},
getters: {
totalPrice(state) {
let sum = 0
state.cartArr.map(val => val.checked == true ? sum += val.minPrice * val.num : sum)
return sum
}
}
})
store/local.js
export default {
get(key) {
return localStorage.getItem(key) ? JSON.parse(localStorage.getItem(key)) : []
},
set(key, value) {
localStorage.setItem(key, JSON.stringify(value))
}
}
App.vue
<template>
<div id="app">
<router-view v-if="flag" />
</div>
</template>
<script>
export default {
// errorCaptured: (err, vm, info) => {
// return false;
// },
data() {
return {
flag: true,
};
},
// mounted() {
// this.$http
// .get("/list")
// .then((res) => console.log(res));
// },
methods: {
reload() {
this.flag = false;
this.$nextTick(() => {
this.flag = true;
});
},
},
provide() {
return {
reload: this.reload,
};
},
};
</script>
<style>
* {
margin: 0;
padding: 0;
list-style: none;
}
</style>
main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import Vant from 'vant'
import { Lazyload } from 'vant'
import 'vant/lib/index.css'
import axios from 'axios'
axios.defaults.baseURL = 'https://api.it120.cc/xiaochengxu'
Vue.prototype.$http = axios
Vue.use(Vant)
Vue.use(Lazyload)
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
views/Cart.vue
<template>
<div class="cart">
<div>
<input type="checkbox" :checked="allChecked" @change="checkAll" />
<span>全选</span>
</div>
<div class="cart-content">
<div class="cart-item" v-for="item in cartArr" :key="item.id">
<input type="checkbox" v-model="item.checked" @change="checkItem" />
<img :src="item.pic" alt="" />
<div class="cart-item-text">
<p>{{ item.name }}</p>
<p>{{ item.minPrice }}</p>
<p>
<button @click="minusItem(item.id)">-</button>{{ item.num
}}<button @click="plusItem(item.id)">+</button>
</p>
</div>
<button @click="del(item.id)">删除</button>
</div>
</div>
<van-submit-bar :price="totalPrice * 100" button-text="提交订单" />
</div>
</template>
<script>
import { Dialog, Toast } from "vant";
import { mapState, mapMutations, mapGetters } from "vuex";
export default {
computed: {
...mapState({
cartArr: (state) => state.cartArr,
}),
...mapState(["allChecked"]),
...mapGetters(["totalPrice"]),
},
methods: {
checkAll(event) {
this.$store.commit("checkAll", event.target.checked);
},
...mapMutations(["checkItem", "minusItem", "plusItem"]),
...mapMutations({
del: (commit, id) =>
Dialog.confirm({ title: "确定删除" })
.then(() => commit("del", id))
.catch(() => {
return true;
}),
}),
},
};
</script>
<style scoped>
.cart {
height: 100%;
display: flex;
flex-direction: column;
}
.cart-content {
flex: 1;
overflow: auto;
}
.van-submit-bar {
position: static;
}
.cart-item {
display: flex;
justify-content: space-around;
align-items: center;
border: 1px solid rgb(255, 153, 0);
margin: 10px 3px;
height: 20vh;
}
.cart-item img {
height: 100%;
width: auto;
}
.cart-item-text {
width: 30vw;
}
</style>
views/Content.vue
<template>
<div class="content">
<van-swipe :autoplay="3000">
<van-swipe-item v-for="(item, index) in swiperArr" :key="index">
<img
class="img"
v-lazy="item.picUrl"
@click="$router.push('/detail?id=' + item.id)"
/></van-swipe-item>
</van-swipe>
<van-grid :column-num="3">
<van-grid-item
v-for="value in 6"
:key="value"
icon="photo-o"
text="文字"
/>
</van-grid>
<van-divider>推荐</van-divider>
<van-card
v-for="item in hotGoodsArr"
:key="item.id"
:num="item.stores"
:tag="item.recommendStatusStr"
:price="item.minPrice"
:desc="item.characteristic"
:title="item.name"
:thumb="item.pic"
:origin-price="item.originalPrice"
@click="$router.push('/detail?id=' + item.id)"
></van-card>
<van-divider>砍价商品</van-divider>
<van-card
v-for="item in haggleGoodsArr"
:key="item.id"
:num="item.stores"
:tag="item.tags"
:price="item.minPrice"
:desc="item.characteristic"
:title="item.name"
:thumb="item.pic"
:origin-price="item.originalPrice"
@click="$router.push('/detail?id=' + item.id)"
></van-card>
<van-divider>拼团商品</van-divider>
<van-card
v-for="item in groupGoodsArr"
:key="item.id"
:num="item.stores"
:tag="item.recommendStatusStr"
:price="item.minPrice"
:desc="item.characteristic"
:title="item.name"
:thumb="item.pic"
:origin-price="item.originalPrice"
@click="$router.push('/detail?id=' + item.id)"
></van-card>
</div>
</template>
<script>
export default {
data() {
return {
swiperArr: [],
hotGoodsArr: [],
haggleGoodsArr: [],
groupGoodsArr: [],
};
},
mounted() {
this.getSwipe();
this.getHotGoods();
this.getHaggleGoods();
this.getGroupGoods();
},
methods: {
async getSwipe() {
this.swiperArr = (await this.$http.get("/banner/list")).data.data;
},
async getHotGoods() {
this.hotGoodsArr = (
await this.$http.get("/shop/goods/list", {
params: {
kanjia: false,
pingtuan: false,
},
})
).data.data;
},
async getHaggleGoods() {
this.haggleGoodsArr = (
await this.$http.get("/shop/goods/list", {
params: {
kanjia: true,
pingtuan: false,
},
})
).data.data;
},
async getGroupGoods() {
this.groupGoodsArr = (
await this.$http.get("/shop/goods/list", {
params: {
pingtuan: true,
kanjia: false,
},
})
).data.data;
},
},
};
</script>
<style scoped>
.img {
width: 100%;
}
</style>
views/Detail.vue
<template>
<div>
<van-nav-bar
title="商品详情"
left-text="返回"
left-arrow
@click-left="onClickLeft"
></van-nav-bar>
<van-swipe :autoplay="3000">
<van-swipe-item v-for="(image, index) in pics" :key="index"
><img v-lazy="image.pic"
/></van-swipe-item>
</van-swipe>
<van-divider />
<div v-if="basicInfo">
<p>商品名称:{{ basicInfo.name }}</p>
<p>商品价格:{{ basicInfo.minPrice }}</p>
<p>上架时间:{{ basicInfo.dateAdd }}</p>
</div>
<van-divider />
<div v-html="content" class="contentBox"></div>
<div class="box"></div>
<van-goods-action>
<van-goods-action-icon icon="chat-o" text="客服"></van-goods-action-icon>
<van-goods-action-icon
icon="cart-o"
text="购物车"
></van-goods-action-icon>
<van-goods-action-icon icon="shop-o" text="店铺"></van-goods-action-icon>
<van-goods-action-button
type="warning"
text="加入购物车"
@click="add2Cart"
></van-goods-action-button>
</van-goods-action>
</div>
</template>
<script>
import { Toast } from "vant";
export default {
data() {
return {
basicInfo: null,
content: "",
pics: [],
};
},
mounted() {
this.getInfo(this.$route.query.id);
},
methods: {
onClickLeft() {
this.$router.back();
},
async getInfo(id) {
let { basicInfo, content, pics } = (
await this.$http.get("/shop/goods/detail", { params: { id } })
).data.data;
[this.basicInfo, this.content, this.pics] = [basicInfo, content, pics];
},
add2Cart() {
Toast("已添加到购物车");
let obj = {
id: this.basicInfo.id,
pic: this.basicInfo.pic,
name: this.basicInfo.name,
minPrice: this.basicInfo.minPrice,
stores: this.basicInfo.stores,
};
this.$store.commit("add2Cart", obj);
this.$router.push('/index/cart')
},
},
};
</script>
<style scoped>
>>> img {
width: 100%;
height: auto;
}
.box {
height: 50px;
}
p {
text-indent: 2em;
}
</style>
views/Home.vue
<template>
<div>
<header class="hd">
<div class="user ui-flex align-center">
<div class="img">
<img src="https://m.mi.com/static/img/avatar.76a75b8f17.png" alt="" />
</div>
<div class="name">昵称:{{ nick }} <br />手机号:{{ mobile }}</div>
<div class="userType">{{ isSeller ? "金牌卖家" : "金牌买家" }}</div>
</div>
</header>
<van-cell-group>
<van-cell title="用户ID" :value="id" />
<van-cell title="注册时间" :value="dateAdd" />
<van-cell title="注册方式" :value="sourceStr" />
<van-cell title="银行卡号" :value="cardNumber" />
<van-cell title="用户状态" :value="statusStr" />
</van-cell-group>
</div>
</template>
<script>
import { Toast, Cell, CellGroup } from "vant";
export default {
data() {
return {
cardNumber: "",
dateAdd: "",
id: "",
mobile: "",
nick: "",
sourceStr: "",
statusStr: "",
isSeller: "",
};
},
mounted() {
this.userInfo();
},
methods: {
userInfo() {
this.$http
.get("/user/detail", {
params: {
token: localStorage.getItem("miUserToken")
? JSON.parse(localStorage.getItem("miUserToken")).token
: "",
},
})
.then((res) => {
if (res.data.code == 2000) {
Toast(res.data.msg);
this.$router.push("/login");
} else {
let infoObj = res.data.data.base;
this.nick = infoObj.nick;
this.mobile = infoObj.mobile;
this.id = infoObj.id;
this.dateAdd = infoObj.dateAdd;
this.cardNumber = infoObj.cardNumber;
this.statusStr = infoObj.statusStr;
this.sourceStr = infoObj.sourceStr;
this.isSeller = infoObj.isSeller;
}
});
},
},
};
</script>
<style scoped>
.hd {
background: url(https://m.mi.com/static/img/bg.63c8e19851.png) center 0
#f37d0f;
background-size: auto 100%;
padding: 1em 0;
}
.align-center {
align-items: center;
-webkit-box-align: center;
}
.ui-flex {
display: flex;
justify-content: space-around;
}
.img {
width: 4em;
height: 4em;
/* margin: 0 1em; */
overflow: hidden;
border-radius: 100%;
border: 3px solid hsla(0, 0%, 100%, 0.4);
}
img {
width: 100%;
}
.name a,
.name {
margin-left: -3em;
color: blanchedalmond;
}
.userType {
color: rgb(255, 217, 0);
font-weight: bold;
}
</style>
views/Index.vue
<template>
<div class="index">
<div class="index-head">
<h2 @click="$router.push('/')">小米商城</h2>
</div>
<div class="index-content">
<keep-alive>
<router-view></router-view>
</keep-alive>
</div>
<van-tabbar route>
<van-tabbar-item to="/index/content" icon="home-o">首页</van-tabbar-item>
<van-tabbar-item to="/index/cart" icon="shopping-cart-o"
>购物车</van-tabbar-item
>
<van-tabbar-item to="/index/home" icon="manager-o">我的</van-tabbar-item>
</van-tabbar>
</div>
</template>
<script>
export default {};
</script>
<style scoped>
.index {
height: 100vh;
display: flex;
flex-direction: column;
}
.index-content {
flex: 1;
overflow: auto;
}
.index-head > h2 {
line-height: 6vh;
color: white;
background: rgb(253, 104, 33);
text-align: center;
}
.van-tabbar--fixed {
position: static;
}
</style>
views/Login.vue
<template>
<div>
<van-nav-bar title="登录" />
<van-form @submit="onSubmit">
<van-field
v-model="mobile"
name="mobile"
label="手机号"
placeholder="请输入手机号"
:rules="[{ required: true, message: '请输入手机号' }]"
/>
<van-field
v-model="pwd"
label="密码"
name="pwd"
type="password"
placeholder="请输入密码"
:rules="[{ required: true, message: '请输入密码' }]"
/>
<div style="margin: 16px">
<van-button round block type="info" native-type="submit"
>登录</van-button
>
<router-link to="/register">我要注册</router-link>
</div>
</van-form>
</div>
</template>
<script>
import qs from "qs";
import { Toast } from "vant";
export default {
inject: ["reload"],
data() {
return {
mobile: "",
pwd: "",
};
},
methods: {
onSubmit(values) {
values.deviceId = "0529";
values.deviceName = "小米note3";
let parmStr = qs.stringify(values);
this.$http.post("/user/m/login", parmStr).then((res) => {
if (res.data.code == 0) {
Toast("登录成功");
localStorage.setItem("miUserToken", JSON.stringify(res.data.data));
this.$router.push("/index/home");
} else {
Toast(res.data.msg);
this.reload();
}
});
},
},
};
</script>
<style scoped>
a {
line-height: 3em;
color: blue;
font-size: 0.8em;
}
</style>
views/Register.vue
<template>
<div>
<van-nav-bar
title="注册"
left-text="返回"
left-arrow
@click-left="onClickLeft"
/>
<van-form @submit="onSubmit">
<van-field
v-model="mobile"
name="mobile"
label="手机号"
placeholder="请输入手机号"
:rules="[{ required: true }]"
/>
<van-field
v-model="nick"
label="用户名"
name="nick"
placeholder="请输入用户名"
:rules="[{ required: true }]"
/>
<van-field
v-model="code"
center
clearable
label="短信验证码"
name="code"
placeholder="请输入短信验证码"
>
<template #button>
<van-button size="small" type="primary" @click.prevent="sendCode"
>发送验证码</van-button
>
</template>
</van-field>
<van-field
v-model="pwd"
type="password"
name="pwd"
label="密码"
placeholder="请输入密码"
:rules="[{ required: true }]"
/>
<van-field name="autoLogin" label="自动登录">
<template #input>
<van-checkbox v-model="autoLogin" shape="square" />
</template>
</van-field>
<div style="margin: 16px">
<van-button round block type="info" native-type="submit"
>注册</van-button
>
</div>
</van-form>
</div>
</template>
<script>
import qs from "qs";
import { Toast } from "vant";
export default {
inject: ["reload"],
data() {
return {
mobile: "",
pwd: "",
nick: "",
code: "",
autoLogin: "",
};
},
methods: {
onSubmit(values) {
let parmStr = qs.stringify(values);
this.$http.post("/user/m/register", parmStr).then((res) => {
if (res.data.code == 0) {
Toast("注册成功");
this.$router.push("/login");
} else if (res.data.code == 10000) {
Toast("用户已存在,请登录");
this.$router.push("/login");
} else {
Toast(res.data.msg);
this.reload();
}
});
},
onClickLeft() {
this.$router.back();
},
sendCode() {
this.mobile
? this.$http
.get("/verification/sms/get", { params: { mobile: this.mobile } })
.then((res) =>
res.data.code === 0
? Toast("验证码发送成功")
: Toast(res.data.msg)
)
: Toast("请输入手机号");
},
},
};
</script>
<style scoped>
</style>