feat: pages > page and h5 scroll

This commit is contained in:
bac-joker 2020-12-27 19:57:02 +08:00
parent 5beef07281
commit 552bc4e9ad
22 changed files with 222 additions and 133 deletions

View File

@ -8,6 +8,7 @@ module.exports = {
// 这里值为 false 表示这个全局变量不允许被重新赋值,比如:
//
// Vue: false
__DEV__: false
},
rules: {
'vue/comment-directive': 'off',

View File

@ -13,7 +13,7 @@ const headPkgs = [
"fes-plugin-access",
"fes-plugin-model",
"fes-plugin-layout",
"fes-plugin-icon",
"fes-plugin-icon"
];
const tailPkgs = [];
// const otherPkgs = readdirSync(join(__dirname, 'packages')).filter(

View File

@ -0,0 +1,12 @@
# request 封装
为了尽可能减少包的,提供可定制化能力
## TODO 待实现能力
* formData 控制
* 轮询
* 并行请求 >> 通过定义 key 区分
* 防抖 & 节流
* 缓存 & SWR & 预加载
* loadingDelay

View File

@ -14,10 +14,7 @@
"author": "",
"license": "MIT",
"peerDependencies": {
"axios": "0.21.1",
"@webank/fes": "^2.0.0"
},
"dependencies": {
"axios": "^0.20.0",
"throttle-debounce": "^2.3.0"
}
}

View File

@ -13,7 +13,8 @@ export function typeOf(obj) {
'[object RegExp]': 'regExp',
'[object Undefined]': 'undefined',
'[object Null]': 'null',
'[object Object]': 'object'
'[object Object]': 'object',
'[object URLSearchParams]': 'URLSearchParams'
};
return map[Object.prototype.toString.call(obj)];
}
@ -38,13 +39,14 @@ export function isObject(obj) {
return typeOf(obj) === 'object';
}
export function isHtmlElement(node) {
return node && node.nodeType === Node.ELEMENT_NODE;
export function isURLSearchParams(obj) {
return typeOf(obj) === 'URLSearchParams';
}
// eslint-disable-next-line
export const isUndefined = val => val === undefined;
export const isDefined = val => val !== undefined && val !== null;
export const isDefined = val => val != null;
export function checkHttpRequestHasBody(method) {
@ -88,3 +90,16 @@ export function trimObj(obj) {
});
}
}
/**
* 唯一定位一个请求url, data | params, method
* 其中请求参数(data, params)根据请求方法,只使用其中一个
* 一个请求同时包含 data | params 参数的设计本身不合理
* 不对这种情况进行兼容
*/
export function genRequestKey(url, data, method) {
if (isURLSearchParams(data)) {
return `${url}${data.toString()}${method}`;
}
return `${url}${JSON.stringify(data)}${method}`;
}

View File

@ -0,0 +1,12 @@
import { checkHttpRequestHasBody, trimObj } from 'helpers';
export default (instance) => {
instance.interceptors.request.use((config) => {
if (checkHttpRequestHasBody(config.method)) {
config.data = trimObj(config.data);
} else {
config.params = trimObj(config.params);
}
return config;
});
};

View File

@ -1,31 +1,14 @@
import axios from 'axios';
// import { debounce } from 'throttle-debounce';
import { ApplyPluginsType, plugin, router } from '@webank/fes';
import { ApplyPluginsType, plugin } from '@webank/fes';
import {
checkHttpRequestHasBody,
trimObj,
isFunction,
isObject
isFunction
} from './helpers';
/**
* 统一错误处理
* @param {object | string function} errorStruct
* {
* errorMessage: '', // 错误地址
* errorPage: '', // 错误页面地址
* }
*/
function _errorHandler(error, customerErrorHandler) {
if (isFunction(error)) {
error();
} else if (error.errorPage) {
router.replace(error.errorPage);
} else {
customerErrorHandler && customerErrorHandler(error);
}
}
import setDataField from './setDataField';
import paramsProcess from './paramsProcess';
import resDataAdaptor from './resDataAdaptor';
import resErrorProcess from './resErrorProcess';
function addInterceptors(instance, interceptors, type = 'request') {
interceptors.forEach((fn) => {
@ -59,90 +42,27 @@ function getRequestInstance() {
initialValue: {}
});
const _errorConfig = Object.assign({
401: {
showType: 9,
errorPage: '/login'
},
403: '用户得到授权,但访问是禁止的'
}, errorConfig);
const _requestInterceptors = [].concat([
(config) => {
config.method = config.method.toUpperCase();
return config;
},
(config) => {
if (checkHttpRequestHasBody(config.method)) {
config.data = trimObj(config.data);
} else {
config.params = trimObj(config.params);
}
return config;
}
], requestInterceptors);
const _responseInterceptors = [].concat([
[
function (response) {
if (isObject(response.data) && response.data.code !== '0') {
_errorHandler(_errorConfig[response.data.code] || response.data.msg || response.data.errorMessage || response.data.errorMsg || '服务异常', errorHandler);
return Promise.reject(response);
}
return response;
}, function (error) {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
if (_errorConfig[error.response.status]) {
_errorHandler(_errorConfig[error.response.status], errorHandler);
}
}
return Promise.reject(error);
}
]
], responseInterceptors);
if (responseDataAdaptor && isFunction(responseDataAdaptor)) {
_responseInterceptors.unshift((response) => {
// 响应内容可能是个文件流 or 普通文本
if (isObject(response.data)) {
response.data = responseDataAdaptor(response.data);
}
return response;
});
}
// 只把响应数据暴露出去
_responseInterceptors.push((response) => {
// eslint-disable-next-line
const dataField = REPLACE_DATA_FIELD;
if (isObject(response.data) && dataField) {
return response.data[dataField];
}
return response;
});
const instance = axios.create(Object.assign({
timeout: 10000,
withCredentials: true
}, otherConfigs));
addRequestInterceptors(instance, _requestInterceptors);
addResponseInterceptors(instance, _responseInterceptors);
// eslint-disable-next-line
const dataField = REPLACE_DATA_FIELD;
addRequestInterceptors(requestInterceptors);
addResponseInterceptors(responseInterceptors);
paramsProcess(instance);
resDataAdaptor(instance, { responseDataAdaptor });
resErrorProcess(instance, { errorConfig, errorHandler });
setDataField(instance, dataField);
return {
instance
};
}
// TODO 待实现能力
// formData 控制
// 轮询
// 并行请求 >> 通过定义 key 区分
// 防抖 & 节流
// 缓存 & SWR & 预加载
// loadingDelay
let currentRequestInstance = null;
export const request = (url, data, options = {}) => {
if (!currentRequestInstance) {
@ -150,7 +70,7 @@ export const request = (url, data, options = {}) => {
currentRequestInstance = instance;
}
options.url = url;
options.method = options.method || 'post';
options.method = (options.method || 'post').toUpperCase();
if (checkHttpRequestHasBody(options.method)) {
options.data = data;
} else {

View File

@ -0,0 +1,11 @@
import { isFunction, isObject, isString } from './helpers';
export default (instance, { responseDataAdaptor }) => {
instance.interceptors.response.use((response) => {
// 响应内容可能是个文件流 or 普通文本
if (isFunction(responseDataAdaptor) && (isObject(response.data) || isString(response.data))) {
response.data = responseDataAdaptor(response.data);
}
return response;
});
};

View File

@ -0,0 +1,37 @@
import { isObject, isFunction } from 'helpers';
function resErrorProcess(error, customerErrorHandler) {
if (isFunction(error)) {
error();
} else {
customerErrorHandler && customerErrorHandler(error);
}
}
export default (instance, { errorConfig, errorHandler }) => {
const _errorConfig = Object.assign({
401: {
showType: 9,
errorPage: '/login'
},
403: '用户得到授权,但访问是禁止的'
}, errorConfig);
instance.interceptors.response.use((response) => {
if (isObject(response.data) && response.data.code !== '0') {
resErrorProcess(_errorConfig[response.data.code] || response.data.msg || response.data.errorMessage || response.data.errorMsg || '服务异常', errorHandler);
return Promise.reject(response);
}
return response;
}, (error) => {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
if (_errorConfig[error.response.status]) {
resErrorProcess(_errorConfig[error.response.status], errorHandler);
}
}
return Promise.reject(error);
});
};

View File

@ -0,0 +1,10 @@
import { isObject } from './helpers';
export default (instance, { dataField }) => {
instance.interceptors.response.use((response) => {
if (isObject(response.data) && dataField) {
return response.data[dataField];
}
return response;
});
};

View File

@ -1,5 +1 @@
# fes vue3 模版
## TODO
* 屏幕适配

View File

@ -1,19 +0,0 @@
import { requestWrap } from '@webank/fes';
// TODO
// 响应体控制
// formData 控制
// 错误控制
// 跳错误页面 || 或者重新登录
// 段时间内不能重复发送的请求
// or
export default requestWrap({
login: {
url: '',
throttle: 300,
options: {
method: 'get'
}
}
});

View File

@ -0,0 +1,66 @@
// TODO
// 时间格式化
// js 数字精度计算
// 手机号、身份证号 等的校验
// 数字分割
export function resetContainerHeight(dom) {
const originalHeight = document.body.clientHeight || document.documentElement.clientHeight;
window.onresize = function () {
const resizeHeight = document.documentElement.clientHeight || document.body.clientHeight;
if (resizeHeight < originalHeight) {
// 恢复内容区域高度
const container = document.querySelector(dom);
container.style.height = originalHeight;
}
};
}
export function resetInputBlur() {
const isWechat = window.navigator.userAgent.match(/MicroMessenger\/([\d.]+)/i);
if (!isWechat) return;
const wechatVersion = isWechat[1];
const version = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/);
// 如果设备类型为iOS 12+ 和wechat 6.7.4+,恢复成原来的视口
if (+wechatVersion.replace(/\./g, '') >= 674 && +version[1] >= 12) {
window.scrollTo(0, Math.max(document.body.clientHeight, document.documentElement.clientHeight));
}
}
export function getQueryString(name) {
const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`, 'i');
const r = window.location.search.substr(1).match(reg);
if (r != null) {
return decodeURIComponent(r[2]);
}
return null;
}
export function simpleRequest(options) {
const xhr = new XMLHttpRequest();
xhr.timeout = 3000;
if (options.type === 'GET') {
xhr.open(options.type, options.url, options.async || true);
xhr.send(null);
} else if (options.type === 'POST') {
xhr.open(options.type, options.url, options.async || true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(options.data || {}));
}
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
options.successed(xhr.responseText);
} else {
options.failed && options.failed(xhr);
}
}
};
xhr.ontimeout = function () {
options.failed && options.failed(xhr);
};
}

View File

@ -57,6 +57,6 @@ div {
}
.onepiece {
.hairline("top");
// background: url('../images/male.png');
background: url('../images/male.png');
}
</style>

View File

@ -3,6 +3,9 @@ img {
-webkit-touch-callout: none;
}
html {
touch-action: manipulation; // 处理 IOS10+click点击 300ms 问题
}
body {
background-color: #f7f7f7;
}
@ -20,3 +23,23 @@ a {
* {
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* 适配 iPhone X 顶部填充*/
@supports (top: env(safe-area-inset-top)){
body,
.alien-screen-header {
padding-top: constant(safe-area-inset-top, 40px);
padding-top: env(safe-area-inset-top, 40px);
padding-top: var(safe-area-inset-top, 40px);
}
}
/* 判断iPhoneX 将 footer 的 padding-bottom 填充到最底部 */
@supports (bottom: env(safe-area-inset-bottom)){
body,
.alien-screen-footer {
padding-bottom: constant(safe-area-inset-bottom, 20px);
padding-bottom: env(safe-area-inset-bottom, 20px);
padding-top: var(safe-area-inset-bottom, 20px);
}
}

View File

@ -0,0 +1,4 @@
.scroll() {
-webkit-overflow-scrolling: touch;
overflow-y: auto;
}

View File

@ -4,7 +4,7 @@
export default {
base: '/foo/',
define: {
FOO: 'bar'
__DEV__: false
},
publicPath: '/',
access: {

View File

@ -1,6 +1,6 @@
import { access } from '@webank/fes';
import PageLoading from '@/components/PageLoading.vue';
import UserCenter from '@/components/UserCenter.vue';
import PageLoading from '@/components/PageLoading';
import UserCenter from '@/components/UserCenter';
export const beforeRender = {
loading: <PageLoading />,

View File

@ -3,3 +3,7 @@
export function main() {
console.log('hello world');
}
export function isHtmlElement(node) {
return node && node.nodeType === Node.ELEMENT_NODE;
}