本文参考github上的项目抽取出来的,原文地址:https://github.com/bailicangdu/vue2-elm
1.配置全局的线上环境和编译环境的切换
/**
* 配置编译环境和线上环境之间的切换
*
* baseUrl: 域名地址
* routerMode: 路由模式
* imgBaseUrl: 图片所在域名地址
*
*/
let baseUrl = '';
let routerMode = 'hash';
let imgBaseUrl = '';
if (process.env.NODE_ENV == 'development') {
imgBaseUrl = '/img/';
}else if(process.env.NODE_ENV == 'production'){
baseUrl = '//elm.cangdu.org';
imgBaseUrl = '//elm.cangdu.org/img/';
}
export {
baseUrl,
routerMode,
imgBaseUrl,
}
2.封装请求
import { baseUrl } from './env'
export default async(url = '', data = {}, type = 'GET', method = 'fetch') => {
type = type.toUpperCase();
url = baseUrl + url;
if (type == 'GET') {
let dataStr = ''; //数据拼接字符串
Object.keys(data).forEach(key => {
dataStr += key + '=' + data[key] + '&';
})
if (dataStr !== '') {
dataStr = dataStr.substr(0, dataStr.lastIndexOf('&'));
url = url + '?' + dataStr;
}
}
if (window.fetch && method == 'fetch') {
let requestConfig = {
credentials: 'include',
method: type,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
mode: "cors",
cache: "force-cache"
}
if (type == 'POST') {
Object.defineProperty(requestConfig, 'body', {
value: JSON.stringify(data)
})
}
try {
const response = await fetch(url, requestConfig);
const responseJson = await response.json();
return responseJson
} catch (error) {
throw new Error(error)
}
} else {
return new Promise((resolve, reject) => {
let requestObj;
if (window.XMLHttpRequest) {
requestObj = new XMLHttpRequest();
} else {
requestObj = new ActiveXObject;
}
let sendData = '';
if (type == 'POST') {
sendData = JSON.stringify(data);
}
requestObj.open(type, url, true);
requestObj.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
requestObj.send(sendData);
requestObj.onreadystatechange = () => {
if (requestObj.readyState == 4) {
if (requestObj.status == 200) {
let obj = requestObj.response
if (typeof obj !== 'object') {
obj = JSON.parse(obj);
}
resolve(obj)
} else {
reject(requestObj)
}
}
}
})
}
}
3.调用封装的请求
import fetch from '../config/fetch'
import {getStore} from '../config/mUtils'
/**
* 获取首页默认地址
*/
export const cityGuess = () => fetch('/v1/cities', {
type: 'guess'
});
/**
* 获取首页热门城市
*/
export const hotcity = () => fetch('/v1/cities', {
type: 'hot'
});
/**
* 获取首页所有城市
*/
export const groupcity = () => fetch('/v1/cities', {
type: 'group'
});
/**
* 获取当前所在城市
*/
export const currentcity = number => fetch('/v1/cities/' + number);
/**
* 获取搜索地址
*/
export const searchplace = (cityid, value) => fetch('/v1/pois', {
type: 'search',
city_id: cityid,
keyword: value
});
/**
* 获取msite页面地址信息
*/
export const msiteAddress = geohash => fetch('/v2/pois/' + geohash);
/**
* 获取msite页面食品分类列表
*/
export const msiteFoodTypes = geohash => fetch('/v2/index_entry', {
geohash,
group_type: '1',
'flags[]': 'F'
});
/**
* 获取msite商铺列表
*/
export const shopList = (latitude, longitude, offset, restaurant_category_id = '', restaurant_category_ids = '', order_by = '', delivery_mode = '', support_ids = []) => {
let supportStr = '';
support_ids.forEach(item => {
if (item.status) {
supportStr += '&support_ids[]=' + item.id;
}
});
let data = {
latitude,
longitude,
offset,
limit: '20',
'extras[]': 'activities',
keyword: '',
restaurant_category_id,
'restaurant_category_ids[]': restaurant_category_ids,
order_by,
'delivery_mode[]': delivery_mode + supportStr
};
return fetch('/shopping/restaurants', data);
};
/**
* 获取search页面搜索结果
*/
export const searchRestaurant = (geohash, keyword) => fetch('/v4/restaurants', {
'extras[]': 'restaurant_activity',
geohash,
keyword,
type: 'search'
});
/**
* 获取food页面的 category 种类列表
*/
export const foodCategory = (latitude, longitude) => fetch('/shopping/v2/restaurant/category', {
latitude,
longitude
});
/**
* 获取food页面的配送方式
*/
export const foodDelivery = (latitude, longitude) => fetch('/shopping/v1/restaurants/delivery_modes', {
latitude,
longitude,
kw: ''
});
/**
* 获取food页面的商家属性活动列表
*/
export const foodActivity = (latitude, longitude) => fetch('/shopping/v1/restaurants/activity_attributes', {
latitude,
longitude,
kw: ''
});
/**
* 获取shop页面商铺详情
*/
export const shopDetails = (shopid, latitude, longitude) => fetch('/shopping/restaurant/' + shopid, {
latitude,
longitude: longitude + '&extras[]=activities&extras[]=album&extras[]=license&extras[]=identification&extras[]=statistics'
});
/**
* 获取shop页面菜单列表
*/
export const foodMenu = restaurant_id => fetch('/shopping/v2/menu', {
restaurant_id
});
/**
* 获取商铺评价列表
*/
export const getRatingList = (shopid, offset, tag_name = '') => fetch('/ugc/v2/restaurants/' + shopid + '/ratings', {
has_content: true,
offset,
limit: 10,
tag_name
});
/**
* 获取商铺评价分数
*/
export const ratingScores = shopid => fetch('/ugc/v2/restaurants/' + shopid + '/ratings/scores');
/**
* 获取商铺评价分类
*/
export const ratingTags = shopid => fetch('/ugc/v2/restaurants/' + shopid + '/ratings/tags');
/**
* 获取短信验证码
*/
export const mobileCode = phone => fetch('/v4/mobile/verify_code/send', {
mobile: phone,
scene: 'login',
type: 'sms'
}, 'POST');
/**
* 获取图片验证码
*/
export const getcaptchas = () => fetch('/v1/captchas', {},'POST');
/**
* 检测帐号是否存在
*/
export const checkExsis = (checkNumber, type) => fetch('/v1/users/exists', {
[type]: checkNumber,
type
});
/**
* 发送帐号
*/
export const sendMobile = (sendData, captcha_code, type, password) => fetch('/v1/mobile/verify_code/send', {
action: "send",
captcha_code,
[type]: sendData,
type: "sms",
way: type,
password,
}, 'POST');
/**
* 确认订单
*/
export const checkout = (geohash, entities, shopid) => fetch('/v1/carts/checkout', {
come_from: "web",
geohash,
entities,
restaurant_id: shopid,
}, 'POST');
/**
* 获取快速备注列表
*/
export const getRemark = (id, sig) => fetch('/v1/carts/' + id + '/remarks', {
sig
});
/**
* 获取地址列表
*/
export const getAddress = (id, sig) => fetch('/v1/carts/' + id + '/addresses', {
sig
});
/**
* 搜索地址
*/
export const searchNearby = keyword => fetch('/v1/pois', {
type: 'nearby',
keyword
});
/**
* 添加地址
*/
export const postAddAddress = (userId, address, address_detail, geohash, name, phone, phone_bk, poi_type, sex, tag, tag_type) => fetch('/v1/users/' + userId + '/addresses', {
address,
address_detail,
geohash,
name,
phone,
phone_bk,
poi_type,
sex,
tag,
tag_type,
}, 'POST');
/**
* 下订单
*/
export const placeOrders = (user_id, cart_id, address_id, description, entities, geohash, sig) => fetch('/v1/users/' + user_id + '/carts/' + cart_id + '/orders', {
address_id,
come_from: "mobile_web",
deliver_time: "",
description,
entities,
geohash,
paymethod_id: 1,
sig,
}, 'POST');
/**
* 重新发送订单验证码
*/
export const rePostVerify = (cart_id, sig, type) => fetch('/v1/carts/' + cart_id + '/verify_code', {
sig,
type,
}, 'POST');
/**
* 下订单
*/
export const validateOrders = ({
user_id,
cart_id,
address_id,
description,
entities,
geohash,
sig,
validation_code,
validation_token
}) => fetch('/v1/users/' + user_id + '/carts/' + cart_id + '/orders', {
address_id,
come_from: "mobile_web",
deliver_time: "",
description,
entities,
geohash,
paymethod_id: 1,
sig,
validation_code,
validation_token,
}, 'POST');
/**
* 重新发送订单验证码
*/
export const payRequest = (merchantOrderNo, userId) => fetch('/payapi/payment/queryOrder', {
merchantId: 5,
merchantOrderNo,
source: 'MOBILE_WAP',
userId,
version: '1.0.0',
});
/**
* 获取服务中心信息
*/
export const getService = () => fetch('/v3/profile/explain');
/**
*兑换会员卡
*/
export const vipCart = (id, number, password) => fetch('/member/v1/users/' + id + '/delivery_card/physical_card/bind',{
number,
password
}, 'POST')
/**
* 获取红包
*/
export const getHongbaoNum = id => fetch('/promotion/v2/users/' + id + '/hongbaos?limit=20&offset=0');
/**
* 获取过期红包
*/
export const getExpired = id => fetch('/promotion/v2/users/' + id + '/expired_hongbaos?limit=20&offset=0');
/**
* 兑换红包
*/
export const exChangeHongbao = (id, exchange_code, captcha_code) => fetch('/v1/users/' + id + '/hongbao/exchange',{
exchange_code,
captcha_code,
}, 'POST');
/**
* 获取用户信息
*/
export const getUser = () => fetch('/v1/user', {user_id: getStore('user_id')});
/**
* 手机号登录
*/
var sendLogin = (code, mobile, validate_token) => fetch('/v1/login/app_mobile', {
code,
mobile,
validate_token
}, 'POST');
/**
* 获取订单列表
*/
export const getOrderList = (user_id, offset) => fetch('/bos/v2/users/' + user_id + '/orders', {
limit: 10,
offset,
});
/**
* 获取订单详情
*/
export const getOrderDetail = (user_id, orderid) => fetch('/bos/v1/users/' + user_id + '/orders/' + orderid + '/snapshot');
/**
*个人中心里编辑地址
*/
export const getAddressList = (user_id) => fetch('/v1/users/'+user_id+'/addresses')
/**
*个人中心里搜索地址
*/
export const getSearchAddress = (keyword) => fetch('v1/pois',{
keyword:keyword,
type:'nearby'
})
/**
* 删除地址
*/
export const deleteAddress = (userid, addressid) => fetch( '/v1/users/' + userid + '/addresses/' + addressid, {}, 'DELETE')
/**
* 账号密码登录
*/
export const accountLogin = (username, password, captcha_code) => fetch('/v2/login', {username, password, captcha_code}, 'POST');
/**
* 退出登录
*/
export const signout = () => fetch('/v2/signout');
/**
* 改密码
*/
export const changePassword = (username, oldpassWord, newpassword, confirmpassword, captcha_code) => fetch('/v2/changepassword', {username, oldpassWord, newpassword, confirmpassword, captcha_code}, 'POST');
4.配置常用的js方法
/**
* 存储localStorage
*/
export const setStore = (name, content) => {
if (!name) return;
if (typeof content !== 'string') {
content = JSON.stringify(content);
}
window.localStorage.setItem(name, content);
}
/**
* 获取localStorage
*/
export const getStore = name => {
if (!name) return;
return window.localStorage.getItem(name);
}
/**
* 删除localStorage
*/
export const removeStore = name => {
if (!name) return;
window.localStorage.removeItem(name);
}
/**
* 获取style样式
*/
export const getStyle = (element, attr, NumberMode = 'int') => {
let target;
// scrollTop 获取方式不同,没有它不属于style,而且只有document.body才能用
if (attr === 'scrollTop') {
target = element.scrollTop;
}else if(element.currentStyle){
target = element.currentStyle[attr];
}else{
target = document.defaultView.getComputedStyle(element,null)[attr];
}
//在获取 opactiy 时需要获取小数 parseFloat
return NumberMode == 'float'? parseFloat(target) : parseInt(target);
}
/**
* 页面到达底部,加载更多
*/
export const loadMore = (element, callback) => {
let windowHeight = window.screen.height;
let height;
let setTop;
let paddingBottom;
let marginBottom;
let requestFram;
let oldScrollTop;
document.body.addEventListener('scroll',() => {
loadMore();
}, false)
//运动开始时获取元素 高度 和 offseTop, pading, margin
element.addEventListener('touchstart',() => {
height = element.offsetHeight;
setTop = element.offsetTop;
paddingBottom = getStyle(element,'paddingBottom');
marginBottom = getStyle(element,'marginBottom');
},{passive: true})
//运动过程中保持监听 scrollTop 的值判断是否到达底部
element.addEventListener('touchmove',() => {
loadMore();
},{passive: true})
//运动结束时判断是否有惯性运动,惯性运动结束判断是非到达底部
element.addEventListener('touchend',() => {
oldScrollTop = document.body.scrollTop;
moveEnd();
},{passive: true})
const moveEnd = () => {
requestFram = requestAnimationFrame(() => {
if (document.body.scrollTop != oldScrollTop) {
oldScrollTop = document.body.scrollTop;
loadMore();
moveEnd();
}else{
cancelAnimationFrame(requestFram);
//为了防止鼠标抬起时已经渲染好数据从而导致重获取数据,应该重新获取dom高度
height = element.offsetHeight;
loadMore();
}
})
}
const loadMore = () => {
if (document.body.scrollTop + windowHeight >= height + setTop + paddingBottom + marginBottom) {
callback();
}
}
}
/**
* 显示返回顶部按钮,开始、结束、运动 三个过程中调用函数判断是否达到目标点
*/
export const showBack = callback => {
let requestFram;
let oldScrollTop;
document.addEventListener('scroll',() => {
showBackFun();
}, false)
document.addEventListener('touchstart',() => {
showBackFun();
},{passive: true})
document.addEventListener('touchmove',() => {
showBackFun();
},{passive: true})
document.addEventListener('touchend',() => {
oldScrollTop = document.body.scrollTop;
moveEnd();
},{passive: true})
const moveEnd = () => {
requestFram = requestAnimationFrame(() => {
if (document.body.scrollTop != oldScrollTop) {
oldScrollTop = document.body.scrollTop;
moveEnd();
}else{
cancelAnimationFrame(requestFram);
}
showBackFun();
})
}
//判断是否达到目标点
const showBackFun = () => {
if (document.body.scrollTop > 500) {
callback(true);
}else{
callback(false);
}
}
}
/**
* 运动效果
* @param {HTMLElement} element 运动对象,必选
* @param {JSON} target 属性:目标值,必选
* @param {number} duration 运动时间,可选
* @param {string} mode 运动模式,可选
* @param {function} callback 可选,回调函数,链式动画
*/
export const animate = (element, target, duration = 400, mode = 'ease-out', callback) => {
clearInterval(element.timer);
//判断不同参数的情况
if (duration instanceof Function) {
callback = duration;
duration = 400;
}else if(duration instanceof String){
mode = duration;
duration = 400;
}
//判断不同参数的情况
if (mode instanceof Function) {
callback = mode;
mode = 'ease-out';
}
//获取dom样式
const attrStyle = attr => {
if (attr === "opacity") {
return Math.round(getStyle(element, attr, 'float') * 100);
} else {
return getStyle(element, attr);
}
}
//根字体大小,需要从此将 rem 改成 px 进行运算
const rootSize = parseFloat(document.documentElement.style.fontSize);
const unit = {};
const initState = {};
//获取目标属性单位和初始样式值
Object.keys(target).forEach(attr => {
if (/[^\d^\.]+/gi.test(target[attr])) {
unit[attr] = target[attr].match(/[^\d^\.]+/gi)[0] || 'px';
}else{
unit[attr] = 'px';
}
initState[attr] = attrStyle(attr);
});
//去掉传入的后缀单位
Object.keys(target).forEach(attr => {
if (unit[attr] == 'rem') {
target[attr] = Math.ceil(parseInt(target[attr])*rootSize);
}else{
target[attr] = parseInt(target[attr]);
}
});
let flag = true; //假设所有运动到达终点
const remberSpeed = {};//记录上一个速度值,在ease-in模式下需要用到
element.timer = setInterval(() => {
Object.keys(target).forEach(attr => {
let iSpeed = 0; //步长
let status = false; //是否仍需运动
let iCurrent = attrStyle(attr) || 0; //当前元素属性址
let speedBase = 0; //目标点需要减去的基础值,三种运动状态的值都不同
let intervalTime; //将目标值分为多少步执行,数值越大,步长越小,运动时间越长
switch(mode){
case 'ease-out':
speedBase = iCurrent;
intervalTime = duration*5/400;
break;
case 'linear':
speedBase = initState[attr];
intervalTime = duration*20/400;
break;
case 'ease-in':
let oldspeed = remberSpeed[attr] || 0;
iSpeed = oldspeed + (target[attr] - initState[attr])/duration;
remberSpeed[attr] = iSpeed
break;
default:
speedBase = iCurrent;
intervalTime = duration*5/400;
}
if (mode !== 'ease-in') {
iSpeed = (target[attr] - speedBase) / intervalTime;
iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed);
}
//判断是否达步长之内的误差距离,如果到达说明到达目标点
switch(mode){
case 'ease-out':
status = iCurrent != target[attr];
break;
case 'linear':
status = Math.abs(Math.abs(iCurrent) - Math.abs(target[attr])) > Math.abs(iSpeed);
break;
case 'ease-in':
status = Math.abs(Math.abs(iCurrent) - Math.abs(target[attr])) > Math.abs(iSpeed);
break;
default:
status = iCurrent != target[attr];
}
if (status) {
flag = false;
//opacity 和 scrollTop 需要特殊处理
if (attr === "opacity") {
element.style.filter = "alpha(opacity:" + (iCurrent + iSpeed) + ")";
element.style.opacity = (iCurrent + iSpeed) / 100;
} else if (attr === 'scrollTop') {
element.scrollTop = iCurrent + iSpeed;
}else{
element.style[attr] = iCurrent + iSpeed + 'px';
}
} else {
flag = true;
}
if (flag) {
clearInterval(element.timer);
if (callback) {
callback();
}
}
})
}, 20);
}
5.样式
$blue: #3190e8;
$bc: #e4e4e4;
$fc:#fff;
// 背景图片地址和大小
@mixin bis($url) {
background-image: url($url);
background-repeat: no-repeat;
background-size: 100% 100%;
}
@mixin borderRadius($radius) {
-webkit-border-radius: $radius;
-moz-border-radius: $radius;
-ms-border-radius: $radius;
-o-border-radius: $radius;
border-radius: $radius;
}
//定位全屏
@mixin allcover{
position:absolute;
top:0;
right:0;
}
//定位上下左右居中
@mixin center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
//定位上下居中
@mixin ct {
position: absolute;
top: 50%;
transform: translateY(-50%);
}
//定位左右居中
@mixin cl {
position: absolute;
left: 50%;
transform: translateX(-50%);
}
//宽高
@mixin wh($width, $height){
width: $width;
height: $height;
}
//字体大小、行高、字体
@mixin font($size, $line-height, $family: 'Microsoft YaHei') {
font: #{$size}/#{$line-height} $family;
}
//字体大小,颜色
@mixin sc($size, $color){
font-size: $size;
color: $color;
}
//flex 布局和 子元素 对其方式
@mixin fj($type: space-between){
display: flex;
justify-content: $type;
}