新建项目
使用idea工具,新建空工程,内建springboot模块和vue模块。
使用npm命令,下载安装vant组件库,
npm i vant@latest-v2 -S
可以登录vant官网,查看详细教程,
vant官网 ------https://vant-contrib.gitee.io/vant/v2/#/zh-CN/
按照vant官网提供的步骤,先进行rem适配等操作。
npm install lib-flexible --save
npm install postcss-pxtorem@5.1.1 pxtorem-loader --save
在vue项目根目录中,新建postcss.config.js文件
//rem适配
postcss.config.js
module.exports = () => ({
plugins: [
require("postcss-pxtorem")({
rootValue: 37.5,//结果为:设计稿元素尺寸/16,比如元素宽320px,最终页面会换算成 20rem
propList: ["*"]
})
]
})
对vue项目重置基本样式,下载这个
npm i reset-css --save
下载这个,可以调用异步接口
npm install axios --save
在main.js中,引入
import 'lib-flexible'
import 'reset-css'
以上步骤完成后,可以使用vant全局引入方式,在main.js中添加
import Vue from 'vue';
import Vant from 'vant';
import 'vant/lib/index.css';
Vue.use(Vant);
也可以使用按需引入方式,但是需要下载一个插件
npm i babel-plugin-import -D
在babel.config.js文件中,增加此配置
//按需引入
// 对于使用 babel7 的用户,可以在 babel.config.js 中配置
module.exports = {
plugins: [
['import', {
libraryName: 'vant',
libraryDirectory: 'es',
style: true
}, 'vant']
]
};
如上步骤完成,一个基本的vant项目框架已经搭建完成。
下面我们开始一个vant练习项目,是一个模拟手机商城项目,可以下单购买手机,基本骨架类似在线商城。
项目目录结构
vant前端代码
vue.config.js
module.exports = {
transpileDependencies: true
}
postcss.config.js
module.exports = () => ({
plugins: [
require("postcss-pxtorem")({
rootValue: 37.5,//结果为:设计稿元素尺寸/16,比如元素宽320px,最终页面会换算成 20rem
propList: ["*"]
})
]
})
package.json
{
"name": "vant-web",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"@vant/area-data": "^1.5.1",
"axios": "^1.6.2",
"core-js": "^3.8.3",
"lib-flexible": "^0.3.2",
"postcss-pxtorem": "^5.1.1",
"pxtorem-loader": "^0.1.1",
"reset-css": "^5.0.2",
"vant": "^2.13.2",
"vue": "^2.6.14",
"vue-router": "^3.5.1",
"vuex": "^3.6.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^5.0.0",
"@vue/cli-service": "^5.0.0",
"babel-plugin-import": "^1.13.8",
"less": "^4.0.0",
"less-loader": "^8.0.0",
"vue-template-compiler": "^2.6.14"
}
}
babel.config.js
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
plugins: [
['import', {
libraryName: 'vant',
libraryDirectory: 'es',
style: true
}, 'vant']
]
}
main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import axios from "axios";
import 'lib-flexible'
import 'reset-css'
import '@/vantui/index'
//全部引入
// import Vant from 'vant';
// import 'vant/lib/index.css';
//
// Vue.use(Vant);
axios.defaults.baseURL='http://localhost:8089';
Vue.prototype.$http = axios;
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
router
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
let routes;
routes = [
{
path: '/',
redirect: '/home',
},
{
path: '/home',
name: 'Home',
component: () => import('@/views/HomeView.vue'),
},
{
path:'/addressList',
name:'AddressList',
component: ()=> import('@/views/AddressList.vue')
},
{
path:'/addressNew',
name:'AddressNew',
component: ()=> import('@/views/AddressNew.vue')
},
{
path:'/addressEdit',
name:'AddressEdit',
component: ()=> import('@/views/AddressEdit.vue')
},
{
path:'/detail',
name:'Detail',
component: ()=> import('@/views/Detail.vue')
},
{
path:'/success',
name:'Success',
component: ()=> import('@/views/Success.vue')
},
{
path:'/info',
name:'Info',
component: ()=> import('@/views/InfoView.vue')
},
];
const router = new VueRouter({
routes
})
export default router
store
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
specsId:'',
quantity:'',
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
}
})
vantui.js
import Vue from 'vue';
import { Button } from 'vant';
import { Col, Row } from 'vant';
import { Tab, Tabs } from 'vant';
import { Card } from 'vant';
import { Tag } from 'vant';
import { Sku } from 'vant';
import { AddressList } from 'vant';
import { AddressEdit } from 'vant';
import { Area } from 'vant';
import { Cell, CellGroup } from 'vant';
import { Icon } from 'vant';
import { SubmitBar } from 'vant';
import { Image as VanImage } from 'vant';
Vue.use(VanImage);
Vue.use(SubmitBar);
Vue.use(Icon);
Vue.use(Cell);
Vue.use(CellGroup);
Vue.use(Area);
Vue.use(AddressEdit);
Vue.use(AddressList);
Vue.use(Sku);
Vue.use(Tag);
Vue.use(Card);
Vue.use(Tab);
Vue.use(Tabs);
Vue.use(Col);
Vue.use(Row);
Vue.use(Button);
app.vue
<template>
<div id="app">
<router-view/>
</div>
</template>
<style lang="less">
</style>
addressEdit.vue
<template>
<div>
<van-address-edit
:area-list="areaList"
show-delete
show-set-default
:area-columns-placeholder="['请选择', '请选择', '请选择']"
:address-info="addressInfo"
save-button-text="修 改"
@save="onSave"
@delete="onDelete"
/>
</div>
</template>
<script>
import { Toast } from 'vant';
import { areaList } from '@vant/area-data';
export default {
name:'AddressEdit',
data() {
return {
areaList: areaList,
addressInfo:{},
};
},
methods: {
onSave(obj) {
//Toast('save');
let instance = Toast('修改成功');
setTimeout(()=>{
//关闭提示
instance.close();
this.$router.push('/addressList');
},1000)
},
onDelete() {
//Toast('delete');
this.$router.go(-1);
},
},
created(){
let data = JSON.parse(this.$route.query.item);
this.addressInfo = data;
//截取详细地址
let index = data.address.indexOf('区');
if(index < 0) index = data.address.indexOf('县');
this.addressInfo.addressDetail = data.address.substring(index+1);
}
};
/*
@vant/area-data
Vant 官方提供了一份默认的省市区数据,可以通过 @vant/area-data 引入:
npm i @vant/area-data
import { areaList } from '@vant/area-data';
*/
</script>
<style scoped>
</style>
addressList.vue
<template>
<div>
<van-address-list
v-model="chosenAddressId"
:list="list"
default-tag-text="默认"
@add="onAdd"
@edit="onEdit"
@select="onSelect"
/>
</div>
</template>
<script>
import { Toast } from 'vant';
export default {
name:'AddressList',
data() {
return {
chosenAddressId: '1',
list: [
{
id: '1',
name: '张三',
tel: '13000000000',
address: '浙江省杭州市西湖区文三路 138 号东方通信大厦 7 楼 501 室',
areaCode:'330106',
isDefault: true,
},
{
id: '2',
name: '李四',
tel: '13100000000',
areaCode:'330105',
address: '浙江省杭州市拱墅区莫干山路 50 号',
},
],
};
},
methods: {
onAdd() {
//Toast('新增地址');
this.$router.push('/addressNew');
},
onEdit(item, index) {
let data = JSON.stringify(item);
this.$router.push({path:'/addressEdit',query:{item:data}});
},
onSelect(item, index) {
//Toast('选择地址:' + index);
let instance = Toast('下单成功');
setTimeout(()=>{
//关闭提示
instance.close();
this.$router.push('/detail');
},1000);
},
},
};
</script>
<style scoped>
</style>
addressNew.vue
<template>
<div>
<van-address-edit
:area-list="areaList"
show-delete
show-set-default
:area-columns-placeholder="['请选择', '请选择', '请选择']"
@save="onSave"
@delete="onDelete"
/>
</div>
</template>
<script>
import { Toast } from 'vant';
import { areaList } from '@vant/area-data';
export default {
name:'AddressNew',
data() {
return {
areaList: areaList,
searchResult: [],
};
},
methods: {
onSave(obj) {
//Toast('save');
let instance = Toast('新增成功');
console.log(obj)
setTimeout(()=>{
//关闭提示
instance.close();
this.$router.push('/addressList');
},1000)
},
onDelete() {
//Toast('delete');
this.$router.go(-1);
},
},
};
/*
@vant/area-data
Vant 官方提供了一份默认的省市区数据,可以通过 @vant/area-data 引入:
npm i @vant/area-data
import { areaList } from '@vant/area-data';
*/
</script>
<style scoped>
</style>
detail.vue
<template>
<div class="goods">
<van-cell-group class="goods-cell-group">
<van-cell >
<van-col span="16"><van-icon name="location-o" style="margin-right:15px;"/>收货人:{{data.buyerName }}</van-col>
<van-col span="8">{{data.tel}}</van-col>
<van-col span="21" style="margin-left: 28px;">收货地址:{{data.address}}</van-col>
</van-cell>
</van-cell-group>
<van-card
:num="data.num"
:price="data.price"
:desc="data.specs"
:title="data.phoneName"
:thumb="data.icon"
/>
<van-cell-group class="goods-cell-group">
<van-cell class="goods-express">
<van-col span="20">配送方式</van-col>
<van-col span="4">快递</van-col>
</van-cell>
</van-cell-group>
<van-cell-group class="goods-cell-group">
<van-cell class="goods-express" style="">
<van-col span="20">商品金额</van-col>
<van-col style="" span="4">¥{{data.amount}}</van-col>
</van-cell>
</van-cell-group>
<van-cell-group>
<van-cell class="goods-express" style="">
<van-col span="20">运费</van-col>
<van-col style="" span="4">¥{{data.freight}}</van-col>
</van-cell>
</van-cell-group>
<van-submit-bar
:price="data.amount * 100 + data.freight * 100"
button-text="确认付款"
@submit="onSubmit" />
</div>
</template>
<script>
import {Toast} from 'vant'
export default {
name: "Detail",
data(){
return {
data:{
orderId:'15998',
buyerName:'小明',
phoneName:'华为',
payStatus:0,
freight:10,
tel:'13012345678',
address:'浙江省杭州市西湖区',
num:1,
specs:"32GB",
price: '2800.00',
icon:'https://img01.yzcdn.cn/vant/apple-1.jpg',
amount:2800,
}
}
},
methods:{
onSubmit(){
let instance = Toast('支付成功');
setTimeout(()=>{
//关闭提示
instance.close();
//
this.$router.push('/success?orderId=' + this.data.orderId
+ '&amount=' + (this.data.amount + this.data.freight));
},1000);
}
}
}
</script>
<style scoped>
</style>
home.vue
<template>
<div>
<van-row>
<van-col span="24">
<van-tabs
v-model="active"
@click="onClick"
sticky
title-active-color="#e32dab"
color="#e32dab"
line-width="100"
line-height="2">
<van-tab v-for="item in categories" :title="item.name" :key="item.type">
<van-card
v-for="item in phones"
:key="item.id"
:price="item.price"
:desc="item.desc"
:title="item.title"
:thumb="item.thumb">
<template #tags>
<van-tag v-for="t in item.tag" color="#f2826a" style="margin-left: 5px;">{{t.name}}</van-tag>
</template>
<template #footer>
<van-button type="info" size="mini" @click="buy(item)">购 买</van-button>
</template>
</van-card>
</van-tab>
</van-tabs>
</van-col>
</van-row>
<van-sku
v-model="show"
:sku="sku"
:goods="goods"
:hide-stock="sku.hide_stock"
@buy-clicked="onBuyClicked">
<!-- 自定义 sku actions -->
<template #sku-actions="props">
<div class="van-sku-actions">
<!-- 直接触发 sku 内部事件,通过内部事件执行 onBuyClicked 回调 -->
<van-button
square
size="large"
type="danger"
@click="props.skuEventBus.$emit('sku:buy')"
>
立即购买
</van-button>
</div>
</template>
</van-sku>
</div>
</template>
<script>
let categories = [
{name:'魅焰红',type:0},
{name:'极光蓝',type:1},
{name:'铂光金',type:2},
{name:'幻夜黑',type:3},
];
let phones = [
{ id:1,title:'HUAWEI META60',price:'8999.00',desc:'魅焰红',tag:[{name:'720P珍珠屏'},{name:'Micro USB接口'},],thumb: 'https://img01.yzcdn.cn/vant/apple-1.jpg',},
{ id:2,title:'OPPO R21S',price:'3888.00',desc:'魅焰红',tag:[{name:'720P珍珠屏'},{name:'Micro USB接口'},],thumb: 'https://img01.yzcdn.cn/vant/apple-2.jpg',},
{ id:3,title:'小米青春X9',price:'3199.00',desc:'魅焰红',tag:[{name:'720P珍珠屏'},{name:'Micro USB接口'},],thumb: 'https://img01.yzcdn.cn/vant/apple-3.jpg',},
{ id:4,title:'VIVO X48',price:'3698.00',desc:'魅焰红',tag:[{name:'720P珍珠屏'},{name:'Micro USB接口'},],thumb: 'https://img01.yzcdn.cn/vant/apple-4.jpg',},
{ id:5,title:'HUAWEI META50',price:'7999.00',desc:'极光蓝',tag:[{name:'1080P珍珠屏'},{name:'Micro USB接口'},],thumb: 'https://img01.yzcdn.cn/vant/apple-1.jpg',},
{ id:6,title:'OPPO R22S',price:'3588.00',desc:'极光蓝',tag:[{name:'1080P珍珠屏'},{name:'Micro USB接口'},],thumb: 'https://img01.yzcdn.cn/vant/apple-2.jpg',},
{ id:7,title:'小米青春X8',price:'3099.00',desc:'极光蓝',tag:[{name:'1080P珍珠屏'},{name:'Micro USB接口'},],thumb: 'https://img01.yzcdn.cn/vant/apple-3.jpg',},
{ id:8,title:'VIVO X47',price:'3698.00',desc:'极光蓝',tag:[{name:'1080P珍珠屏'},{name:'Micro USB接口'},],thumb: 'https://img01.yzcdn.cn/vant/apple-4.jpg',},
{ id:9,title:'HUAWEI META60',price:'8999.00',desc:'铂光金',tag:[{name:'720P珍珠屏'},{name:'Micro USB接口'},],thumb: 'https://img01.yzcdn.cn/vant/apple-1.jpg',},
{ id:10,title:'OPPO R22S',price:'3888.00',desc:'铂光金',tag:[{name:'720P珍珠屏'},{name:'Micro USB接口'},],thumb: 'https://img01.yzcdn.cn/vant/apple-2.jpg',},
{ id:11,title:'小米青春X7',price:'2199.00',desc:'铂光金',tag:[{name:'720P珍珠屏'},{name:'Micro USB接口'},],thumb: 'https://img01.yzcdn.cn/vant/apple-3.jpg',},
{ id:12,title:'VIVO X47',price:'2698.00',desc:'铂光金',tag:[{name:'720P珍珠屏'},{name:'Micro USB接口'},],thumb: 'https://img01.yzcdn.cn/vant/apple-4.jpg',},
{ id:13,title:'HUAWEI META60',price:'8099.00',desc:'幻夜黑',tag:[{name:'1080P珍珠屏'},{name:'Micro USB接口'},],thumb: 'https://img01.yzcdn.cn/vant/apple-1.jpg',},
{ id:14,title:'OPPO R11S',price:'3888.00',desc:'幻夜黑',tag:[{name:'1080P珍珠屏'},{name:'Micro USB接口'},],thumb: 'https://img01.yzcdn.cn/vant/apple-2.jpg',},
{ id:15,title:'小米青春X5',price:'1199.00',desc:'幻夜黑',tag:[{name:'720P珍珠屏'},{name:'Micro USB接口'},],thumb: 'https://img01.yzcdn.cn/vant/apple-3.jpg',},
{ id:16,title:'VIVO X46',price:'3098.00',desc:'幻夜黑',tag:[{name:'720P珍珠屏'},{name:'Micro USB接口'},],thumb: 'https://img01.yzcdn.cn/vant/apple-4.jpg',},
];
export default {
name: "HomeView",
data(){
return {
//
goods:{
},
//
sku:{},
//显示购买界面
show:false,
//页签激活索引
active:0,
//分类
categories:[],
//商品
phones:[],
}
},
methods:{
//点击购买
onBuyClicked(item){
//console.log(item)
//规格id
this.$store.state.specsId = item.selectedSkuComb.s1;
//选择数量
this.$store.state.quantity = item.selectedNum;
//
this.$router.push('/addressList');
},
//页签单击
onClick(){
let category = categories.find(item => item.type === this.active);
this.phones = phones.filter(item => item.desc === category.name);
},
//点击购买
buy(obj){
//console.log(obj)
this.show = true;
this.goods = {
// 默认商品 sku 缩略图
picture: 'https://img01.yzcdn.cn/vant/apple-1.jpg',
};
this.sku = {
tree:[
{
k: '规格', // skuKeyName:规格类目名称
k_s: 's1', // skuKeyStr:sku 组合列表(下方 list)中当前类目对应的 key 值,value 值会是从属于当前类目的一个规格值 id
v: [
{
id: '1', // skuValueId:规格值 id
name: '32GB', // skuValueName:规格值名称
imgUrl: 'https://img01.yzcdn.cn/vant/apple-1.jpg', // 规格类目图片,只有第一个规格类目可以定义图片
previewImgUrl: 'https://img01.yzcdn.cn/vant/apple-4.jpg', // 用于预览显示的规格类目图片
},
{
id: '2',
name: '64GB',
imgUrl: 'https://img01.yzcdn.cn/vant/apple-1.jpg',
previewImgUrl: 'https://img01.yzcdn.cn/vant/apple-4.jpg',
}
],
//largeImageMode: true, // 是否展示大图模式
}
],
// 所有 sku 的组合列表,比如红色、M 码为一个 sku 组合,红色、S 码为另一个组合
list: [
{
s1: '1', // 规格类目 k_s 为 s1 的对应规格值 id
price: 380000, // 价格(单位分)
stock_num: 5 // 当前 sku 组合对应的库存
},
{
s1: '2', // 规格类目 k_s 为 s1 的对应规格值 id
price: 400000, // 价格(单位分)
stock_num: 5 // 当前 sku 组合对应的库存
},
],
price: '1800.00', // 默认价格(单位元)
stock_num: 10, // 商品总库存
none_sku: false, // 是否无规格商品
hide_stock: false // 是否隐藏剩余库存
}
}
},
created(){
this.categories = categories;
//
//this.phones = phones;
this.onClick();
}
}
</script>
<style scoped>
</style>
infoview.vue
<template>
<div class="goods">
<van-cell-group class="goods-cell-group">
<van-cell >
<van-col span="16"><van-icon name="location-o" style="margin-right:15px;"/>收货人:{{data.buyerName }}</van-col>
<van-col span="8">{{data.tel}}</van-col>
<van-col span="21" style="margin-left: 28px;">收货地址:{{data.address}}</van-col>
</van-cell>
</van-cell-group>
<van-card
:num="data.num"
:price="data.price"
:desc="data.specs"
:title="data.phoneName"
:thumb="data.icon"
/>
<van-cell-group class="goods-cell-group">
<van-cell class="goods-express">
<van-col span="20">配送方式</van-col>
<van-col span="4">快递</van-col>
</van-cell>
</van-cell-group>
<van-cell-group class="goods-cell-group">
<van-cell class="goods-express" style="">
<van-col span="20">商品金额</van-col>
<van-col style="" span="4">¥{{data.amount}}</van-col>
</van-cell>
</van-cell-group>
<van-cell-group>
<van-cell class="goods-express" style="">
<van-col span="20">运费</van-col>
<van-col style="" span="4">¥{{data.freight}}</van-col>
</van-cell>
</van-cell-group>
<van-cell-group>
<van-cell class="goods-express" style="">
<van-col span="20">订单状态</van-col>
<van-col style="" span="4">{{data.payStatus === 0 ? '未支付' : '已支付'}}</van-col>
</van-cell>
</van-cell-group>
</div>
</template>
<script>
import {Toast} from 'vant'
export default {
name: "InfoView",
data(){
return {
data:{
orderId:'15998',
buyerName:'小明',
phoneName:'华为',
payStatus:1,
freight:10,
tel:'13012345678',
address:'浙江省杭州市西湖区',
num:1,
specs:"32GB",
price: '2800.00',
icon:'https://img01.yzcdn.cn/vant/apple-1.jpg',
amount:2800,
}
}
},
methods:{
}
}
</script>
<style scoped>
</style>
success.vue
<template>
<div>
<div style="text-align: center;margin-top: 50px;">
<!-- <van-image-->
<!-- width="130"-->
<!-- height="115"-->
<!-- src="https://images.669pic.com/element_pic/31/52/13/42/f1d2bc750b1fcd67072cee60b4cabeb7.jpg"-->
<!-- />-->
<van-icon name="wechat-pay" style="font-size:65px;"/>
<p style="margin-top: 20px;margin-bottom: 15px;font-size: 16px;">支付成功</p>
<van-cell :title="amount" style="font-size: 20px;color:red;"/>
</div>
<!--修改到这里了-->
<van-cell-group>
<van-cell>
<van-col span="21">配送方式:</van-col>
<van-col>快递</van-col>
</van-cell>
</van-cell-group>
<van-button type="info" size="large" @click="showInfo">查看订单详情</van-button>
</div>
</template>
<script>
export default {
name: "Success",
data(){
return {
orderId:null,
amount:null,
}
},
methods: {
showInfo(){
this.$router.push('/info?orderId=' + this.orderId);
}
},
created(){
this.orderId = this.$route.query.orderId;
this.amount = this.$route.query.amount;
}
}
</script>
<style scoped>
</style>
测试
首页
购买
地址列表
地区选择
保存地址
编辑地址
确认付款
付款成功
查看订单详情
以上是全部代码,这是一个练习项目,代码只是完成了前端部分,未与后端连通,数据与操作步骤均为虚拟的,后续我完成与后端的连通后,再把代码贴出来,供大家学习参考。