
yarn add vant@latest-v2
import Vant from 'vant'
import 'vant/lib/index.css'
// 把vant中所有的组件都导入、注册
Vue.use(Vant)
主要按钮
信息按钮
yarn add vant@latest-v2
yarn add babel-plugin-import -D
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
// 需要添加的配置
plugins: [
['import', {
libraryName: 'vant',
libraryDirectory: 'es',
style: true
}, 'vant']
]
}
import Vue from 'vue'
import { Button, Switch, Step, Steps, Tabbar, TabbarItem, NavBar, Toast, Search, Swipe, SwipeItem, Grid, GridItem, Icon, Lazyload, Rate, ActionSheet, Dialog, Checkbox, CheckboxGroup, Tab, Tabs } from 'vant'
// 按钮
Vue.use(Button)
// 开关
Vue.use(Switch)
// 步骤条
Vue.use(Step)
Vue.use(Steps)
// 标签栏
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)
// Vant 图标
Vue.use(Icon)
// 懒加载
Vue.use(Lazyload)
// 评分
Vue.use(Rate)
// 反馈组件
Vue.use(ActionSheet)
// 弹窗组件
Vue.use(Dialog)
// 复选框
Vue.use(Checkbox)
Vue.use(CheckboxGroup)
// Tab 标签页
Vue.use(Tab)
Vue.use(Tabs)
import '@/plugins/vant-ui'
主要按钮
信息按钮
yarn add [email protected] -D
module.exports = {
plugins: {
'postcss-px-to-viewport': {
viewportWidth: 375
}
}
}
import { Tabbar, TabbarItem } from 'vant'
Vue.use(Tabbar)
Vue.use(TabbarItem)
首页
分类
购物车
我的
import Vue from 'vue'
import { Button, Toast } from 'vant'
Vue.use(Button)
Vue.use(Toast)
import Vue from 'vue'
import VueRouter from 'vue-router'
import Layout from '@/views/layout'
import Login from '@/views/login'
import MyOrder from '@/views/myOrder'
import Pay from '@/views/pay'
import ProDetail from '@/views/proDetail'
import Search from '@/views/search'
import SearchList from '@/views/search/list'
import Home from '@/views/layout/home'
import Category from '@/views/layout/category'
import Cart from '@/views/layout/cart'
import User from '@/views/layout/user'
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{
path: '/',
component: Layout,
redirect: '/home',
children: [
{ path: '/home', component: Home },
{ path: '/category', component: Category },
{ path: '/cart', component: Cart },
{ path: '/user', component: User }
]
},
{ path: '/login', component: Login },
{ path: '/myOrder', component: MyOrder },
{ path: '/pay', component: Pay },
{ path: '/proDetail/:id', component: ProDetail },
{ path: '/search', component: Search },
{ path: '/searchList', component: SearchList }
]
})
export default router
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
Vue.use(Vuex)
export default new Vuex.Store({
getters: {
token (state) {
return state.user.userInfo.token
}
},
modules: {
user
}
})
import Vue from 'vue'
import VueRouter from 'vue-router'
import store from '@/store'
Vue.use(VueRouter)
// 定义一个数组,存放所以需要权限的页面
const authUrls = ['/user', '/myOrder', '/pay']
// 全局前置导航守卫
// to:往哪里去, 到哪去的路由信息对象
// from:从哪里来, 从哪来的路由信息对象
// 3. next() 是否放行
// 如果next()调用,就是放行
// next(路径) 拦截到某个路径页面, 跳转到该页面
router.beforeEach((to, from, next) => {
console.log(to, from, next)
// 获取 to.path 判断是否存在于 需要权限的页面
if (!authUrls.includes(to.path)) {
// 放行
next()
} else {
const token = store.getters.token
if (token) {
// 已经登录,放行
next()
} else {
// 未登录,跳转登陆页
next('/login')
}
}
})
export default router
yarn add axios
import axios from 'axios'
// 创建 axios 实例,将来对创建出来的实例进行自定义配置
// 好处:不会污染原始的 axios 实例
const instance = axios.create({
baseURL: 'http://cba.itlike.com/public/index.php?s=/api/',
timeout: 5000
})
// 自定义配置:请求/响应 拦截器
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error)
})
// 添加响应拦截器
instance.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response.data
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error)
})
// 导出配置好的实例
export default instance
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', // 加载图标类型,支持 spinner loading circle
duration: 0 // 持续展示时间(ms),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)
// 抛出错误提示
return Promise.reject(res.message)
} else {
// 正确情况,走业务核心逻辑,清除 loading 效果
Toast.clear()
}
return res
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error)
})
// 导出配置好的实例
export default instance
import request from '@/utils/request'
// 用于存放所有登录相关的接口请求
// 1. 获取图形验证码
export const getPicCode = () => {
return request({
url: '/captcha/image',
method: 'get'
})
}
// 自定义配置:请求/响应 拦截器
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
// 开启loading,禁止背景点击(节流处理,防止多次无效触发)
Toast.loading({
message: '加载中...',
forbidClick: true, // 禁止背景点击
loadingType: 'spinner', // 加载图标类型,支持 spinner loading circle
duration: 0 // 持续展示时间(ms),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)
// 抛出错误提示
return Promise.reject(res.message)
} else {
// 正确情况,走业务核心逻辑,清除 loading 效果
Toast.clear()
}
return res
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error)
})
// 自定义配置:请求/响应 拦截器
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
// 开启loading,禁止背景点击(节流处理,防止多次无效触发)
Toast.loading({
message: '加载中...',
forbidClick: true, // 禁止背景点击
loadingType: 'spinner', // 加载图标类型,支持 spinner loading circle
duration: 0 // 持续展示时间(ms),0 表示永久展示
})
// 只要有 token, 就在请求的时候携带, 便于 请求需要授权的接口
const token = store.getters.token
if (token) {
config.headers['Access-Token'] = token
// 设置平台
config.headers.platform = 'H5'
}
return config
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error)
})
// 约定一个通用的键名
const INFO_KEY = 'shopping_info'
// 获取个人信息
export const getUserInfo = () => {
const defaultInfo = { tonken: '', userId: '' }
const result = localStorage.getItem(INFO_KEY)
return result ? JSON.parse(result) : defaultInfo
}
// 设置个人信息
export const setUserInfo = (info) => {
localStorage.setItem(INFO_KEY, JSON.stringify(info))
}
// 移除个人信息
export const removeUserInfo = () => {
localStorage.removeItem(INFO_KEY)
}
import { getUserInfo, setUserInfo } from '@/utils/storage'
export default {
namespaced: true,
state () {
return {
userInfo: getUserInfo()
}
},
mutations: {
setUserInfo (state, userInfo) {
state.userInfo = userInfo
setUserInfo(userInfo)
}
},
actions: {},
getters: {}
}
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
user
}
})
import { getCartList, updateCartNum, deleteCartGoods } from '@/api/cart'
import { Toast } from 'vant'
export default {
namespaced: true,
state () {
return {
cartTotal: 0, // 购物车总数
cartList: [] // 购物车列表
}
},
mutations: {
// 设置购物车总数
setcartTotal (state, cartTotal) {
state.cartTotal = cartTotal
},
// 设置购物车列表
setCartList (state, cartList) {
state.cartList = cartList
},
// 切换商品选中状态
toggleCheck (state, goodsId) {
state.cartList.forEach(item => {
if (item.goods_id === goodsId) {
item.is_checked = !item.is_checked
}
})
},
// 切换全选状态
toggleAllCheck (state, flag) {
state.cartList.forEach(item => {
item.is_checked = flag
})
}
},
actions: {
// 获取购物车列表
async getCartListAction (context) {
const { data: { cartTotal, list } } = await getCartList()
// 默认选中
list.forEach(item => {
item.is_checked = true
})
context.commit('setCartList', list)
context.commit('setcartTotal', cartTotal)
},
// 修改商品数量
async changeCountAction (context, obj) {
const { goodsNum, goodsId, goodsSkuId } = obj
// 修改后台数量
await updateCartNum(goodsNum, goodsId, goodsSkuId)
// 更新数据
context.dispatch('getCartListAction')
},
// 删除选中的商品
async deleteCheckedAction (context) {
const cartIds = context.getters.getCheckedList.map(item => item.id)
await deleteCartGoods(cartIds)
// 更新数据
await context.dispatch('getCartListAction')
context.commit('toggleAllCheck', false)
Toast.success('删除成功!')
}
},
getters: {
// 获取选中商品总数
getCheckedTotal (state) {
return state.cartList.reduce((sum, item) => sum + (item.is_checked ? item.goods_num : 0), 0)
},
// 获取选中商品列表
getCheckedList (state) {
return state.cartList.filter(item => item.is_checked)
},
// 获取选中商品总价
getCheckedTotalPrice (state, getters) {
return getters.getCheckedList.reduce((sum, item) => {
return sum + item.goods.goods_price_min * item.goods_num
}, 0).toFixed(2)
},
// 判断是否全选
isAllChecked (state) {
// return getters.getCheckedList.length === state.cartList.length && getters.getCheckedList.length !== 0
return state.cartList.every(item => item.is_checked) && state.cartList.length !== 0
}
}
}
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import cart from './modules/cart'
Vue.use(Vuex)
export default new Vuex.Store({
strict: true,
state: {
},
getters: {
token (state) {
return state.user.userInfo.token
}
},
mutations: {
},
actions: {
},
modules: {
user,
cart
}
})
import request from '@/utils/request'
// 用于存放所有登录相关的接口请求
// 1. 获取图形验证码
export const getPicCode = () => {
return request({
url: '/captcha/image',
method: 'get'
})
}
import request from '@/utils/request'
// 2. 获取短信验证码
export const getSmsCode = (captchaCode, captchaKey, mobile) => {
return request.post('/captcha/sendSmsCaptcha', {
form: {
captchaCode, // 图形验证码
captchaKey, // 图形验证码key
mobile // 手机号
}
})
}
import request from '@/utils/request'
// 3. 登录
export const login = (mobile, smsCode) => {
return request.post('/passport/login', {
form: {
mobile, // 手机号 必需
smsCode, // 短信验证码 必需
isParty: false, // 是否存在第三方用户信息 必需
partyData: {} // 三方登录信息,默认为:{} 可选
}
})
}
登录
import Vue from 'vue'
import { Search, Swipe, SwipeItem, Grid, GridItem } from 'vant'
// 搜索框
Vue.use(Search)
// 轮播图
Vue.use(Swipe)
Vue.use(SwipeItem)
// 布局
Vue.use(Grid)
Vue.use(GridItem)
import request from '@/utils/request'
// 1.获取首页数据
export const getHomeData = () => {
return request.get('/page/detail', {
params: {
pageId: 0
}
})
}
{{ item.goods_name }}
已售{{ item.goods_sales}}件
¥{{ item.goods_price_min }}
¥{{ item.goods_price_max }}
—— 猜你喜欢 ——
搜索
最近搜索
{{ item }}
// 约定一个通用的键名
const HISTORY_KEY = 'shopping_history_search'
// 获取搜索历史
export const getSearchHistory = () => {
const history = localStorage.getItem(HISTORY_KEY)
return history ? JSON.parse(history) : []
}
// 设置搜索历史
export const setSearchHistory = (history) => {
localStorage.setItem(HISTORY_KEY, JSON.stringify(history))
}
// 移除搜索历史
export const removeSearchHistory = () => {
localStorage.removeItem(HISTORY_KEY)
}
import request from '@/utils/request'
// 1.获取搜索数据
export const getSearchData = (searchObj) => {
const { sortType, sortPrice, categoryId, goodsName, page } = searchObj
return request.get('/goods/list', {
params: {
sortType, // all-按综合搜索(默认),sales-按销量搜索,price-按价格搜索
sortPrice, // 0-价格从低到高, 1-价格从高到低
categoryId,
goodsName, // 商品名称
page// 页码
}
})
}
综合
销量
价格
import request from '@/utils/request'
// 1.获取搜索数据
export const getSearchData = (searchObj) => {
const { sortType, sortPrice, categoryId, goodsName, page } = searchObj
return request.get('/goods/list', {
params: {
sortType, // all-按综合搜索(默认),sales-按销量搜索,price-按价格搜索
sortPrice, // 0-价格从低到高, 1-价格从高到低
categoryId,
goodsName, // 商品名称
page// 页码
}
})
}
// 2.根据商品id获取商品详情
export const getGoodsDetail = (goodsId) => {
return request.get('/goods/detail', {
params: {
goodsId
}
})
}
// 3.根据商品id获取评价总数
export const getCommentTotal = (goodsId) => {
return request.get('/comment/total', {
params: {
goodsId
}
})
}
// 4.根据商品id获取评价
export const getComments = (goodsId, limit) => {
return request.get('/comment/listRows', {
params: {
goodsId,
limit
}
})
}
// 5.根据商品id获取评价
export const getGoodsService = (goodsId) => {
return request.get('/goods.service/list', {
params: {
goodsId
}
})
}
{{ current + 1 }} / {{ images.length }}
¥{{ product.goods_price_min }}
¥{{ product.goods_price_max }}
已售{{ product.goods_sales }}件
{{ product.goods_name }}
{{ item.name }}
商品评价 ({{ commentTotal.all }}条)
查看更多
{{ item.user.nick_name }}
{{ item.content }}
{{ item.create_time }}
{{ current + 1 }} / {{ images.length }}
¥{{ product.goods_price_min }}
¥{{ product.goods_price_max }}
已售{{ product.goods_sales }}件
{{ product.goods_name }}
{{ item.name }}
商品评价 ({{ commentTotal.all }}条)
查看更多
{{ item.user.nick_name }}
{{ item.content }}
{{ item.create_time }}
¥
{{ product.goods_price_min }}
库存
{{ product.stock_total }}
数量
加入购物车
立刻购买
该商品已抢完
import request from '@/utils/request'
// 添加购物车
export const addCart = (goodsId, goodsNum, goodsSkuId) => {
return request.post('/cart/add', {
goodsId,
goodsNum,
goodsSkuId
})
}
// 获取购物车商品总数量
export const getCartTotalCount = () => {
return request.get('/cart/total')
}
// 获取购物车商品列表
export const getCartList = () => {
return request.get('/cart/list')
}
// 更新购物车商品数量
export const updateCartNum = (goodsNum, goodsId, goodsSkuId) => {
return request.post('/cart/update', {
goodsNum,
goodsId,
goodsSkuId
})
}
// 删除购物车商品
export const deleteCartGoods = (cartIds) => {
return request.post('/cart/clear', {
cartIds
})
}
共{{ cartTotal }}件商品
编辑
{{ item.goods.goods_name }}
¥ {{ item.goods.goods_price_min }}
changeCount(value, item.goods_id, item.goods_sku_id)" :value="item.goods_num" >
您的购物车是空的, 快去逛逛吧
去逛逛
import request from '@/utils/request'
// 1.获取收货地址
export const getAddress = () => {
return request.get('/address/list')
}
// 2.获取默认收货地址
export const getDefaultAddress = () => {
return request.get('/address/defaultId')
}
// 3.订单结算
// mode: cart => obj: { cartId }
// mode: buyNow => obj: { goodsId, goodsNum, goodsSkuId }
export const checkoutOrder = (mode, obj) => {
return request.get('/checkout/order', {
params: {
delivery: 10, // 10 快递 20 自提
couponId: 0, // 优惠券id, 0:不适用优惠
isUsePoints: 0, // 是否使用积分, 0:不使用
mode, // cart/buyNow
...obj // 将传递过来的参数对象动态展开
}
})
}
{{ selectedAddress.name }}
{{ selectedAddress.phone }}
{{ this.address }}
请选择配送地址
{{ item.goods_name }}
x{{ item.total_num }}
¥{{ item.total_pay_price }}
共 {{ order.orderTotalNum }} 件商品,合计:
¥{{ order.orderPrice }}
订单总金额:
¥{{ order.orderTotalPrice }}
优惠券:
{{ order.isUsePoints === '0' ? '无优惠券可用' : ''}}
配送费用:
请先选择配送地址
+¥0.00
支付方式
余额支付(可用 ¥ {{ personal.balance }} 元)
// 登录确认
export default {
methods: {
// 登录确认
loginConfirm () {
if (!this.$store.getters.token) {
// 未登录,引导前往登陆页面
this.$dialog.confirm({
title: '温馨提示',
message: '此操作只有登陆才能进行!',
confirmButtonText: '去登录',
cancelButtonText: '再逛逛'
}).then(() => {
// 确定前往登录页(replace:跳转页面不会添加到历史记录)
this.$router.replace({
path: '/login',
query: {
// 登录成功后跳转回来
backUrl: this.$route.fullPath
}
})
}).catch(() => {})
return true
}
return false
}
}
}
¥
{{ product.goods_price_min }}
库存
{{ product.stock_total }}
数量
加入购物车
立刻购买
该商品已抢完
import request from '@/utils/request'
// 4.订单提交
// mode: cart => obj: { cartId, remark }
// mode: buyNow => obj: { goodsId, goodsNum, goodsSkuId, remark }
export const submitOrder = (mode, obj) => {
return request.get('/checkout/submit', {
params: {
delivery: 10, // 10 快递 20 自提
couponId: 0, // 优惠券id, 0:不适用优惠
isUsePoints: 0, // 是否使用积分, 0:不使用
payType: 10, // 10:余额支付
mode, // cart/buyNow
...obj // 将传递过来的参数对象动态展开
}
})
}
import request from '@/utils/request'
// 5. 我的订单
export const getOrderList = (dataType, page) => {
return request.get('/order/list', {
params: {
dataType, // 订单类型,all-全部,payment-待支付,delivery-待发货,received-待收货,comment-待评价
page
}
})
}
{{ value.create_time }}
{{ value.state_text }}
{{ item.goods_name }}
¥ {{ item.total_pay_price }}
x {{ item.total_num }}
共{{ totalNum }}件商品,总金额 ¥{{ value.total_price }}
立刻付款
申请取消
确认收货
评价
import request from '@/utils/request'
// 获取个人信息
export const getUserInfoDetail = () => {
return request.get('/user/info')
}
{{ detail.mobile }}
普通会员
未登录
点击登录账号
{{ detail.balance || 0 }}
账户余额
0
积分
0
优惠券
我的钱包
我的服务
收货地址
领券中心
优惠券
我的帮助
我的积分
退换/售后
import { getUserInfo, setUserInfo } from '@/utils/storage'
export default {
namespaced: true,
state () {
return {
userInfo: getUserInfo()
}
},
mutations: {
setUserInfo (state, userInfo) {
state.userInfo = userInfo
// 缓存用户信息
setUserInfo(userInfo)
}
},
actions: {
logout (context) {
// 清除用户信息
context.commit('setUserInfo', {})
// 清除购物车信息
context.commit('cart/setCartList', [], { root: true })
}
},
getters: {}
}
yarn build
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
publicPath: './',
transpileDependencies: true
})
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '@/views/layout/home'
import Category from '@/views/layout/category'
import Cart from '@/views/layout/cart'
import User from '@/views/layout/user'
import Layout from '@/views/layout'
const Login = () => import('@/views/login')
const MyOrder = () => import('@/views/myOrder')
const Pay = () => import('@/views/pay')
const ProDetail = () => import('@/views/proDetail')
const Search = () => import('@/views/search')
const SearchList = () => import('@/views/search/list')
// import store from '@/store'
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{
path: '/',
component: Layout,
redirect: '/home',
children: [
{ path: '/home', component: Home },
{ path: '/category', component: Category },
{ path: '/cart', component: Cart },
{ path: '/user', component: User }
]
},
{ path: '/login', component: Login },
{ path: '/myOrder', component: MyOrder },
{ path: '/pay', component: Pay },
{ path: '/proDetail/:id', component: ProDetail },
{ path: '/search', component: Search },
{ path: '/searchList', component: SearchList }
]
})
// 定义一个数组,存放所以需要权限的页面
const authUrls = ['/myOrder', '/pay']
// 全局前置导航守卫
// to:往哪里去, 到哪去的路由信息对象
// from:从哪里来, 从哪来的路由信息对象
// 3. next() 是否放行
// 如果next()调用,就是放行
// next(路径) 拦截到某个路径页面, 跳转到该页面
router.beforeEach((to, from, next) => {
// 获取 to.path 判断是否存在于 需要权限的页面
if (!authUrls.includes(to.path)) {
// 放行
next()
} else {
// const token = store.getters.token
if (store.getters.token) {
// 已经登录,放行
next()
} else {
// 未登录,跳转登陆页
next('/login')
}
}
})
export default router