这是一个基于Vue实现开箱即用H5移动端商城的单页应用
作者的开源地址是:https://github.com/yrinleung/openshopping-vue
我们一起来欣赏页面吧
看看代码有什么乾坤
看了下package.json发现有axios和vue-lazyload
先看代码一层一层进去里面
//app.vue
先看config中的router.js
虽说是普通的router页面,不过对路由的处理很有意思
//router.js
import Vue from 'vue';
import Router from 'vue-router';
Vue.use(Router);
const routes = [
{
path: '*',
redirect: '/home'
},
{
name: 'home',
component: () => import('../page/index'),
meta: {
title: '首页'
}
},
{
path: '/login',
component: () => import('../page/account/login'),
meta: {
title: '登录'
}
},
{
path: '/login/password',
component: () => import('../page/account/password'),
meta: {
title: '登录'
}
},
{
path: '/login/phone',
component: () => import('../page/account/phonelogin'),
meta: {
title: '手机号登录'
}
},
{
path: '/login/register',
component: () => import('../page/account/register'),
meta: {
title: '注册'
}
},
{
path: '/user/index',
component: () => import('../page/user/index'),
name: 'user',
meta: {
title: '会员中心'
}
},
{
path: '/user/info',
component: () => import('../page/user/info/detail'),
name: 'user',
meta: {
title: '账号管理'
}
},
{
path: '/user/address',
component: () => import('../page/user/address/list'),
meta: {
title: '我的地址'
}
},
{
path: '/user/address/edit',
component: () => import('../page/user/address/edit'),
meta: {
title: '修改地址'
}
},
{
path: '/user/favorite',
component: () => import('../page/user/favorite/list'),
meta: {
title: '我的收藏'
}
},
{
path: '/user/coupon',
component: () => import('../page/user/coupon/list'),
meta: {
title: '我的优惠券'
}
},
{
path: '/user/order',
component: () => import('../page/user/order/list'),
meta: {
title: '我的订单'
}
},
{
path: '/user/order/:id',
component: () => import('../page/user/order/list'),
meta: {
title: '我的订单'
}
},
{
path: '/user/order/info/:id',
component: () => import('../page/user/order/info'),
meta: {
title: '我的订单'
}
},
{
path: '/user/order/logistics/:id',
component: () => import('../page/user/order/logistics'),
meta: {
title: '订单追踪'
}
},
{
path: '/user/aftersale',
component: () => import('../page/user/aftersale/list'),
meta: {
title: '售后'
}
},
{
path: '/user/aftersale/apply',
component: () => import('../page/user/aftersale/apply'),
meta: {
title: '申请售后'
}
},
{
path: '/user/aftersale/detail',
component: () => import('../page/user/aftersale/detail'),
meta: {
title: '服务单详情'
}
},
{
path: '/user/aftersale/track/:id',
component: () => import('../page/user/aftersale/track'),
meta: {
title: '进度详情'
}
},
{
path: '/product/:id',
component: () => import('../page/product/detail'),
meta: {
title: '商品详情'
}
},
{
path: '/search',
component: () => import('../page/product/list'),
meta: {
title: '商品列表'
}
},
{
name: 'cart',
component: () => import('../page/cart/index'),
meta: {
title: '购物车'
}
},
{
path: '/order',
component: () => import('../page/shipping/order'),
meta: {
title: '确认订单'
}
},
{
name: 'category',
component: () => import('../page/category/index'),
meta: {
title: '分类'
}
},
];
// add route path
routes.forEach(route => {
route.path = route.path || '/' + (route.name || '');
});
const router = new Router({ routes });
// 这里对title的处理也挺有意思的
router.beforeEach((to, from, next) => {
const title = to.meta && to.meta.title;
if (title) {
document.title = title;
}
next();
});
export {
router
};
我们再看src\config\components.js的components.js
代码如下
import headerNav from '../components/header/nav';
import navigate from '../components/footer/navigate.vue'
import productcard from '../components/common/productcard.vue'
import {
Tag,
Col,
Icon,
Cell,
CellGroup,
Swipe,
Toast,
SwipeItem,
GoodsAction,
GoodsActionBigBtn,
GoodsActionMiniBtn,
Actionsheet,
Sku,
Card,Button,SwipeCell,Dialog,Tab, Tabs,Row,Checkbox, CheckboxGroup, SubmitBar,NavBar,Tabbar, TabbarItem,Panel,List,Step, Steps,Field ,
Badge, BadgeGroup,Popup,Stepper,RadioGroup, Radio,Picker,Uploader,Info
} from 'vant';
const components=[
Tag,
Col,
Icon,
Cell,
CellGroup,
Swipe,
SwipeItem,
GoodsAction,
GoodsActionBigBtn,
GoodsActionMiniBtn,
Actionsheet,
Sku,
Card,
Button,
SwipeCell ,
Dialog ,
headerNav,
Tab, Tabs,Toast,Row,Checkbox, CheckboxGroup, SubmitBar,NavBar ,Tabbar, TabbarItem,navigate,Panel,List ,Step, Steps,Field ,
Badge, BadgeGroup,Popup,productcard,Stepper,RadioGroup, Radio,Picker,Uploader,Info
]
export default (Vue)=>{
components.forEach(Component => {
Vue.component(Component.name, Component)
});
}
上面的对组件的处理挺有意思的
看一下移动端这边的适配
//rem.js
//src\config\rem.js
(function(d, w) {
const doc = d.documentElement;
function rem() {
const width = Math.min(doc.getBoundingClientRect().width, 768);
doc.style.fontSize = width / 7.5 + 'px';
}
rem();
w.addEventListener('resize', rem);
})(document, window);
接下来我们来根据router.js,结合路由来看我们的页面内容
//src\page\index.vue
引入了page.vue和navigate.vue
//navigate.vue
首页
分类
购物车
我的
//page.vue
上面的page页面实际上是由很多组件构成的,我们可以一个一个去分析组件
//src\components\page\whitespace.vue
//src\components\page\line.vue
//src\components\page\text.vue
{{data.value}}
//src\components\page\notice.vue
//src\components\page\cube.vue
//src\components\page\imageAd.vue
//src\components\page\imageText.vue
//src\components\page\product.vue
-
-
{{item.title}}
¥ {{item.price}}
//src\api\page.js
import request from "../config/request";
export function GetPage() {
return request({
url: '/Page/GetPage',
method: 'get',
})
}
export function getProduct(id) {
return request({
url: '/Page/Product',
method: 'get',
params: { id }
})
}
感觉好神奇,上面的那些就处理完毕数据了吗?
看第二个页面吧
这个页面就跟首页不一样了,所有的东西是写死的可是也暴露出来了,第一个home页面都没有看到axiox
//src\page\category\index.vue
这个里面就是很普通的布局方式,静态文件,如果要点击进去下一个路由,应该在点击的时候,进行一次数据的请求
看一下对cart的处理
cart页面也是前端写死数据进行渲染的,我们来看看代码
//src\page\cart\index.vue
全选
促销
满60元减5元
京东自营
促销
满60元减5元
满减 购满60元,可减5元
促销
满60元减5元
全选
选择地址
张三 138****6520
广东省深圳市南山区科技园
//src\page\user\address\list.vue
//src\page\user\address\edit.vue
//src\page\user\index.vue
//点击不同的路由跳转到不同的页面
待付款
待发货
已完成
售后
我的优惠券
我的收藏
收货地址
//login.vue
手机号登录
密码登录
手机号一键注册
或用以下方式登录
发送验证码
注册
登录
我觉得这个里面的很大的看点来自作者对组件的封装吧
{{ formatPrice(goods.price) }}
{{ formatPrice(goods.market_price) }}
{{ goods.title }}
{{goods.subtitle}}
挪威品牌
跨境商品
次日达
自提
闪电退款
前海保税仓
七天无理由退货(拆封后不支持)
领券
满180减30
满300减100
促销
多买优惠
满减
限购
已选
10件装
图文详情
收藏
购物车
加入购物车
立即购买
多买优惠
满2件,总价打9折
满减
满100元减50元
限购
购买不超过5件时享受单件价¥8.00,超出数量以结算价为准
次日达
指定时间前下单,次日送达
自提
我们提供多种自提服务,包括自提点、自助提货柜、移动自提车等服务
闪电退款
签收7天内退货的,提供先退款后退货服务。
七天无理由退货(拆封后不支持)
七天无理由退货(拆封后不支持)
前海保税仓
本商品由前海保税仓发货
//user/order
待付款
待发货
已完成
售后
我的优惠券
我的收藏
收货地址
看了下作者封装的config里面的内容,还有两个觉得很有意思
//src\config\env.js
/**
* 配置编译环境和线上环境之间的切换
*
* baseUrl: 域名地址
* routerMode: 路由模式
* dataSources:数据源
*/
let baseUrl = '';
let routerMode = 'hash';
let dataSources='local';//local=本地,其他值代表非本地
if (process.env.NODE_ENV == 'development') {
baseUrl='';
}else if(process.env.NODE_ENV == 'production'){
baseUrl = '';
}
export {
baseUrl,
routerMode,
dataSources,
}
//src\config\request.js
import axios from 'axios'
import {baseUrl,dataSources} from './env';
import datas from '../data/data';
const service =axios.create({
baseURL: baseUrl, // api 的 base_url
timeout: 5000, // request timeout
});
const servicef =function(parameter){
if(dataSources=='local'){
//定义回调函数和axios一致
const promist = new Promise(function(resolve,reject){
var data=datas[parameter.url];
if(typeof data=='string'){
data= JSON.parse(data);
}
resolve(data);
})
return promist;
}
return service(parameter);
}
service.interceptors.request.use(
config => {
// Do something before request is sent
// if (store.getters.token) {
// // 让每个请求携带token-- ['X-Token']为自定义key 请根据实际情况自行修改
// config.headers['X-Token'] = getToken()
// }
return config
},
error => {
// Do something with request error
console.log(error) // for debug
Promise.reject(error)
}
)
// response interceptor
service.interceptors.response.use(
//response => response,
/**
* 下面的注释为通过在response里,自定义code来标示请求状态
* 当code返回如下情况则说明权限有问题,登出并返回到登录页
* 如想通过 xmlhttprequest 来状态码标识 逻辑可写在下面error中
* 以下代码均为样例,请结合自生需求加以修改,若不需要,则可删除
*/
response => {
const res = response.data;
if (res.ResultCode !== 200) {
// Message({
// message: res.message,
// type: 'error',
// duration: 5 * 1000
// })
// // 50008:非法的token; 50012:其他客户端登录了; 50014:Token 过期了;
// if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
// // 请自行在引入 MessageBox
// // import { Message, MessageBox } from 'element-ui'
// MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', {
// confirmButtonText: '重新登录',
// cancelButtonText: '取消',
// type: 'warning'
// }).then(() => {
// store.dispatch('FedLogOut').then(() => {
// location.reload() // 为了重新实例化vue-router对象 避免bug
// })
// })
// }
console.log(1);
return Promise.reject('error')
} else {
if(typeof response.data.Tag=='string'){
return JSON.parse(response.data.Tag);
}else{
return response.data.Tag;
}
}
},
error => {
return Promise.reject(error)
}
)
export default servicef
关于我上面说的数据渲染部分的疑问,我也在向作者提issue,作者大大人很好,很热心回答我
https://github.com/yrinleung/openshopping-vue/issues/1
看了这个项目倒是没有别的感受,只能说,埋着头写,写,写