Commit 283cbc36 by LJM

css

parent 674e0222
Showing with 6 additions and 7404 deletions
......@@ -3,4 +3,5 @@
/unpackage
/*.hbuilderx
/.DS_Store
/node_modules
\ No newline at end of file
/node_modules
/node_modules/query-string
export interface ParseOptions {
/**
Decode the keys and values. URI components are decoded with [`decode-uri-component`](https://github.com/SamVerschueren/decode-uri-component).
@default true
*/
readonly decode?: boolean;
/**
@default 'none'
- `bracket`: Parse arrays with bracket representation:
```
import queryString = require('query-string');
queryString.parse('foo[]=1&foo[]=2&foo[]=3', {arrayFormat: 'bracket'});
//=> {foo: ['1', '2', '3']}
```
- `index`: Parse arrays with index representation:
```
import queryString = require('query-string');
queryString.parse('foo[0]=1&foo[1]=2&foo[3]=3', {arrayFormat: 'index'});
//=> {foo: ['1', '2', '3']}
```
- `comma`: Parse arrays with elements separated by comma:
```
import queryString = require('query-string');
queryString.parse('foo=1,2,3', {arrayFormat: 'comma'});
//=> {foo: ['1', '2', '3']}
```
- `separator`: Parse arrays with elements separated by a custom character:
```
import queryString = require('query-string');
queryString.parse('foo=1|2|3', {arrayFormat: 'separator', arrayFormatSeparator: '|'});
//=> {foo: ['1', '2', '3']}
```
- `none`: Parse arrays with elements using duplicate keys:
```
import queryString = require('query-string');
queryString.parse('foo=1&foo=2&foo=3');
//=> {foo: ['1', '2', '3']}
```
*/
readonly arrayFormat?: 'bracket' | 'index' | 'comma' | 'separator' | 'none';
/**
The character used to separate array elements when using `{arrayFormat: 'separator'}`.
@default ,
*/
readonly arrayFormatSeparator?: string;
/**
Supports both `Function` as a custom sorting function or `false` to disable sorting.
If omitted, keys are sorted using `Array#sort`, which means, converting them to strings and comparing strings in Unicode code point order.
@default true
@example
```
import queryString = require('query-string');
const order = ['c', 'a', 'b'];
queryString.parse('?a=one&b=two&c=three', {
sort: (itemLeft, itemRight) => order.indexOf(itemLeft) - order.indexOf(itemRight)
});
//=> {c: 'three', a: 'one', b: 'two'}
```
@example
```
import queryString = require('query-string');
queryString.parse('?a=one&c=three&b=two', {sort: false});
//=> {a: 'one', c: 'three', b: 'two'}
```
*/
readonly sort?: ((itemLeft: string, itemRight: string) => number) | false;
/**
Parse the value as a number type instead of string type if it's a number.
@default false
@example
```
import queryString = require('query-string');
queryString.parse('foo=1', {parseNumbers: true});
//=> {foo: 1}
```
*/
readonly parseNumbers?: boolean;
/**
Parse the value as a boolean type instead of string type if it's a boolean.
@default false
@example
```
import queryString = require('query-string');
queryString.parse('foo=true', {parseBooleans: true});
//=> {foo: true}
```
*/
readonly parseBooleans?: boolean;
/**
Parse the fragment identifier from the URL and add it to result object.
@default false
@example
```
import queryString = require('query-string');
queryString.parseUrl('https://foo.bar?foo=bar#xyz', {parseFragmentIdentifier: true});
//=> {url: 'https://foo.bar', query: {foo: 'bar'}, fragmentIdentifier: 'xyz'}
```
*/
readonly parseFragmentIdentifier?: boolean;
}
export interface ParsedQuery<T = string> {
[key: string]: T | T[] | null;
}
/**
Parse a query string into an object. Leading `?` or `#` are ignored, so you can pass `location.search` or `location.hash` directly.
The returned object is created with [`Object.create(null)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create) and thus does not have a `prototype`.
@param query - The query string to parse.
*/
export function parse(query: string, options: {parseBooleans: true, parseNumbers: true} & ParseOptions): ParsedQuery<string | boolean | number>;
export function parse(query: string, options: {parseBooleans: true} & ParseOptions): ParsedQuery<string | boolean>;
export function parse(query: string, options: {parseNumbers: true} & ParseOptions): ParsedQuery<string | number>;
export function parse(query: string, options?: ParseOptions): ParsedQuery;
export interface ParsedUrl {
readonly url: string;
readonly query: ParsedQuery;
/**
The fragment identifier of the URL.
Present when the `parseFragmentIdentifier` option is `true`.
*/
readonly fragmentIdentifier?: string;
}
/**
Extract the URL and the query string as an object.
If the `parseFragmentIdentifier` option is `true`, the object will also contain a `fragmentIdentifier` property.
@param url - The URL to parse.
@example
```
import queryString = require('query-string');
queryString.parseUrl('https://foo.bar?foo=bar');
//=> {url: 'https://foo.bar', query: {foo: 'bar'}}
queryString.parseUrl('https://foo.bar?foo=bar#xyz', {parseFragmentIdentifier: true});
//=> {url: 'https://foo.bar', query: {foo: 'bar'}, fragmentIdentifier: 'xyz'}
```
*/
export function parseUrl(url: string, options?: ParseOptions): ParsedUrl;
export interface StringifyOptions {
/**
Strictly encode URI components with [`strict-uri-encode`](https://github.com/kevva/strict-uri-encode). It uses [`encodeURIComponent`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) if set to `false`. You probably [don't care](https://github.com/sindresorhus/query-string/issues/42) about this option.
@default true
*/
readonly strict?: boolean;
/**
[URL encode](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) the keys and values.
@default true
*/
readonly encode?: boolean;
/**
@default 'none'
- `bracket`: Serialize arrays using bracket representation:
```
import queryString = require('query-string');
queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'bracket'});
//=> 'foo[]=1&foo[]=2&foo[]=3'
```
- `index`: Serialize arrays using index representation:
```
import queryString = require('query-string');
queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'index'});
//=> 'foo[0]=1&foo[1]=2&foo[2]=3'
```
- `comma`: Serialize arrays by separating elements with comma:
```
import queryString = require('query-string');
queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'comma'});
//=> 'foo=1,2,3'
```
- `separator`: Serialize arrays by separating elements with character:
```
import queryString = require('query-string');
queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'separator', arrayFormatSeparator: '|'});
//=> 'foo=1|2|3'
```
- `none`: Serialize arrays by using duplicate keys:
```
import queryString = require('query-string');
queryString.stringify({foo: [1, 2, 3]});
//=> 'foo=1&foo=2&foo=3'
```
*/
readonly arrayFormat?: 'bracket' | 'index' | 'comma' | 'separator' | 'none';
/**
The character used to separate array elements when using `{arrayFormat: 'separator'}`.
@default ,
*/
readonly arrayFormatSeparator?: string;
/**
Supports both `Function` as a custom sorting function or `false` to disable sorting.
If omitted, keys are sorted using `Array#sort`, which means, converting them to strings and comparing strings in Unicode code point order.
@default true
@example
```
import queryString = require('query-string');
const order = ['c', 'a', 'b'];
queryString.stringify({a: 1, b: 2, c: 3}, {
sort: (itemLeft, itemRight) => order.indexOf(itemLeft) - order.indexOf(itemRight)
});
//=> 'c=3&a=1&b=2'
```
@example
```
import queryString = require('query-string');
queryString.stringify({b: 1, c: 2, a: 3}, {sort: false});
//=> 'b=1&c=2&a=3'
```
*/
readonly sort?: ((itemLeft: string, itemRight: string) => number) | false;
/**
Skip keys with `null` as the value.
Note that keys with `undefined` as the value are always skipped.
@default false
@example
```
import queryString = require('query-string');
queryString.stringify({a: 1, b: undefined, c: null, d: 4}, {
skipNull: true
});
//=> 'a=1&d=4'
queryString.stringify({a: undefined, b: null}, {
skipNull: true
});
//=> ''
```
*/
readonly skipNull?: boolean;
/**
Skip keys with an empty string as the value.
@default false
@example
```
import queryString = require('query-string');
queryString.stringify({a: 1, b: '', c: '', d: 4}, {
skipEmptyString: true
});
//=> 'a=1&d=4'
```
@example
```
import queryString = require('query-string');
queryString.stringify({a: '', b: ''}, {
skipEmptyString: true
});
//=> ''
```
*/
readonly skipEmptyString?: boolean;
}
export type Stringifiable = string | boolean | number | null | undefined;
export type StringifiableRecord = Record<
string,
Stringifiable | readonly Stringifiable[]
>;
/**
Stringify an object into a query string and sort the keys.
*/
export function stringify(
// TODO: Use the below instead when the following TS issues are fixed:
// - https://github.com/microsoft/TypeScript/issues/15300
// - https://github.com/microsoft/TypeScript/issues/42021
// Context: https://github.com/sindresorhus/query-string/issues/298
// object: StringifiableRecord,
object: Record<string, any>,
options?: StringifyOptions
): string;
/**
Extract a query string from a URL that can be passed into `.parse()`.
Note: This behaviour can be changed with the `skipNull` option.
*/
export function extract(url: string): string;
export interface UrlObject {
readonly url: string;
/**
Overrides queries in the `url` property.
*/
readonly query?: StringifiableRecord;
/**
Overrides the fragment identifier in the `url` property.
*/
readonly fragmentIdentifier?: string;
}
/**
Stringify an object into a URL with a query string and sorting the keys. The inverse of [`.parseUrl()`](https://github.com/sindresorhus/query-string#parseurlstring-options)
Query items in the `query` property overrides queries in the `url` property.
The `fragmentIdentifier` property overrides the fragment identifier in the `url` property.
@example
```
queryString.stringifyUrl({url: 'https://foo.bar', query: {foo: 'bar'}});
//=> 'https://foo.bar?foo=bar'
queryString.stringifyUrl({url: 'https://foo.bar?foo=baz', query: {foo: 'bar'}});
//=> 'https://foo.bar?foo=bar'
queryString.stringifyUrl({
url: 'https://foo.bar',
query: {
top: 'foo'
},
fragmentIdentifier: 'bar'
});
//=> 'https://foo.bar?top=foo#bar'
```
*/
export function stringifyUrl(
object: UrlObject,
options?: StringifyOptions
): string;
/**
Pick query parameters from a URL.
@param url - The URL containing the query parameters to pick.
@param keys - The names of the query parameters to keep. All other query parameters will be removed from the URL.
@param filter - A filter predicate that will be provided the name of each query parameter and its value. The `parseNumbers` and `parseBooleans` options also affect `value`.
@returns The URL with the picked query parameters.
@example
```
queryString.pick('https://foo.bar?foo=1&bar=2#hello', ['foo']);
//=> 'https://foo.bar?foo=1#hello'
queryString.pick('https://foo.bar?foo=1&bar=2#hello', (name, value) => value === 2, {parseNumbers: true});
//=> 'https://foo.bar?bar=2#hello'
```
*/
export function pick(
url: string,
keys: readonly string[],
options?: ParseOptions & StringifyOptions
): string
export function pick(
url: string,
filter: (key: string, value: string | boolean | number) => boolean,
options?: {parseBooleans: true, parseNumbers: true} & ParseOptions & StringifyOptions
): string
export function pick(
url: string,
filter: (key: string, value: string | boolean) => boolean,
options?: {parseBooleans: true} & ParseOptions & StringifyOptions
): string
export function pick(
url: string,
filter: (key: string, value: string | number) => boolean,
options?: {parseNumbers: true} & ParseOptions & StringifyOptions
): string
/**
Exclude query parameters from a URL. Like `.pick()` but reversed.
@param url - The URL containing the query parameters to exclude.
@param keys - The names of the query parameters to remove. All other query parameters will remain in the URL.
@param filter - A filter predicate that will be provided the name of each query parameter and its value. The `parseNumbers` and `parseBooleans` options also affect `value`.
@returns The URL without the excluded the query parameters.
@example
```
queryString.exclude('https://foo.bar?foo=1&bar=2#hello', ['foo']);
//=> 'https://foo.bar?bar=2#hello'
queryString.exclude('https://foo.bar?foo=1&bar=2#hello', (name, value) => value === 2, {parseNumbers: true});
//=> 'https://foo.bar?foo=1#hello'
```
*/
export function exclude(
url: string,
keys: readonly string[],
options?: ParseOptions & StringifyOptions
): string
export function exclude(
url: string,
filter: (key: string, value: string | boolean | number) => boolean,
options?: {parseBooleans: true, parseNumbers: true} & ParseOptions & StringifyOptions
): string
export function exclude(
url: string,
filter: (key: string, value: string | boolean) => boolean,
options?: {parseBooleans: true} & ParseOptions & StringifyOptions
): string
export function exclude(
url: string,
filter: (key: string, value: string | number) => boolean,
options?: {parseNumbers: true} & ParseOptions & StringifyOptions
): string
'use strict';
const strictUriEncode = require('strict-uri-encode');
const decodeComponent = require('decode-uri-component');
const splitOnFirst = require('split-on-first');
const filterObject = require('filter-obj');
const isNullOrUndefined = value => value === null || value === undefined;
function encoderForArrayFormat(options) {
switch (options.arrayFormat) {
case 'index':
return key => (result, value) => {
const index = result.length;
if (
value === undefined ||
(options.skipNull && value === null) ||
(options.skipEmptyString && value === '')
) {
return result;
}
if (value === null) {
return [...result, [encode(key, options), '[', index, ']'].join('')];
}
return [
...result,
[encode(key, options), '[', encode(index, options), ']=', encode(value, options)].join('')
];
};
case 'bracket':
return key => (result, value) => {
if (
value === undefined ||
(options.skipNull && value === null) ||
(options.skipEmptyString && value === '')
) {
return result;
}
if (value === null) {
return [...result, [encode(key, options), '[]'].join('')];
}
return [...result, [encode(key, options), '[]=', encode(value, options)].join('')];
};
case 'comma':
case 'separator':
return key => (result, value) => {
if (value === null || value === undefined || value.length === 0) {
return result;
}
if (result.length === 0) {
return [[encode(key, options), '=', encode(value, options)].join('')];
}
return [[result, encode(value, options)].join(options.arrayFormatSeparator)];
};
default:
return key => (result, value) => {
if (
value === undefined ||
(options.skipNull && value === null) ||
(options.skipEmptyString && value === '')
) {
return result;
}
if (value === null) {
return [...result, encode(key, options)];
}
return [...result, [encode(key, options), '=', encode(value, options)].join('')];
};
}
}
function parserForArrayFormat(options) {
let result;
switch (options.arrayFormat) {
case 'index':
return (key, value, accumulator) => {
result = /\[(\d*)\]$/.exec(key);
key = key.replace(/\[\d*\]$/, '');
if (!result) {
accumulator[key] = value;
return;
}
if (accumulator[key] === undefined) {
accumulator[key] = {};
}
accumulator[key][result[1]] = value;
};
case 'bracket':
return (key, value, accumulator) => {
result = /(\[\])$/.exec(key);
key = key.replace(/\[\]$/, '');
if (!result) {
accumulator[key] = value;
return;
}
if (accumulator[key] === undefined) {
accumulator[key] = [value];
return;
}
accumulator[key] = [].concat(accumulator[key], value);
};
case 'comma':
case 'separator':
return (key, value, accumulator) => {
const isArray = typeof value === 'string' && value.includes(options.arrayFormatSeparator);
const isEncodedArray = (typeof value === 'string' && !isArray && decode(value, options).includes(options.arrayFormatSeparator));
value = isEncodedArray ? decode(value, options) : value;
const newValue = isArray || isEncodedArray ? value.split(options.arrayFormatSeparator).map(item => decode(item, options)) : value === null ? value : decode(value, options);
accumulator[key] = newValue;
};
default:
return (key, value, accumulator) => {
if (accumulator[key] === undefined) {
accumulator[key] = value;
return;
}
accumulator[key] = [].concat(accumulator[key], value);
};
}
}
function validateArrayFormatSeparator(value) {
if (typeof value !== 'string' || value.length !== 1) {
throw new TypeError('arrayFormatSeparator must be single character string');
}
}
function encode(value, options) {
if (options.encode) {
return options.strict ? strictUriEncode(value) : encodeURIComponent(value);
}
return value;
}
function decode(value, options) {
if (options.decode) {
return decodeComponent(value);
}
return value;
}
function keysSorter(input) {
if (Array.isArray(input)) {
return input.sort();
}
if (typeof input === 'object') {
return keysSorter(Object.keys(input))
.sort((a, b) => Number(a) - Number(b))
.map(key => input[key]);
}
return input;
}
function removeHash(input) {
const hashStart = input.indexOf('#');
if (hashStart !== -1) {
input = input.slice(0, hashStart);
}
return input;
}
function getHash(url) {
let hash = '';
const hashStart = url.indexOf('#');
if (hashStart !== -1) {
hash = url.slice(hashStart);
}
return hash;
}
function extract(input) {
input = removeHash(input);
const queryStart = input.indexOf('?');
if (queryStart === -1) {
return '';
}
return input.slice(queryStart + 1);
}
function parseValue(value, options) {
if (options.parseNumbers && !Number.isNaN(Number(value)) && (typeof value === 'string' && value.trim() !== '')) {
value = Number(value);
} else if (options.parseBooleans && value !== null && (value.toLowerCase() === 'true' || value.toLowerCase() === 'false')) {
value = value.toLowerCase() === 'true';
}
return value;
}
function parse(query, options) {
options = Object.assign({
decode: true,
sort: true,
arrayFormat: 'none',
arrayFormatSeparator: ',',
parseNumbers: false,
parseBooleans: false
}, options);
validateArrayFormatSeparator(options.arrayFormatSeparator);
const formatter = parserForArrayFormat(options);
// Create an object with no prototype
const ret = Object.create(null);
if (typeof query !== 'string') {
return ret;
}
query = query.trim().replace(/^[?#&]/, '');
if (!query) {
return ret;
}
for (const param of query.split('&')) {
if (param === '') {
continue;
}
let [key, value] = splitOnFirst(options.decode ? param.replace(/\+/g, ' ') : param, '=');
// Missing `=` should be `null`:
// http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters
value = value === undefined ? null : ['comma', 'separator'].includes(options.arrayFormat) ? value : decode(value, options);
formatter(decode(key, options), value, ret);
}
for (const key of Object.keys(ret)) {
const value = ret[key];
if (typeof value === 'object' && value !== null) {
for (const k of Object.keys(value)) {
value[k] = parseValue(value[k], options);
}
} else {
ret[key] = parseValue(value, options);
}
}
if (options.sort === false) {
return ret;
}
return (options.sort === true ? Object.keys(ret).sort() : Object.keys(ret).sort(options.sort)).reduce((result, key) => {
const value = ret[key];
if (Boolean(value) && typeof value === 'object' && !Array.isArray(value)) {
// Sort object keys, not values
result[key] = keysSorter(value);
} else {
result[key] = value;
}
return result;
}, Object.create(null));
}
exports.extract = extract;
exports.parse = parse;
exports.stringify = (object, options) => {
if (!object) {
return '';
}
options = Object.assign({
encode: true,
strict: true,
arrayFormat: 'none',
arrayFormatSeparator: ','
}, options);
validateArrayFormatSeparator(options.arrayFormatSeparator);
const shouldFilter = key => (
(options.skipNull && isNullOrUndefined(object[key])) ||
(options.skipEmptyString && object[key] === '')
);
const formatter = encoderForArrayFormat(options);
const objectCopy = {};
for (const key of Object.keys(object)) {
if (!shouldFilter(key)) {
objectCopy[key] = object[key];
}
}
const keys = Object.keys(objectCopy);
if (options.sort !== false) {
keys.sort(options.sort);
}
return keys.map(key => {
const value = object[key];
if (value === undefined) {
return '';
}
if (value === null) {
return encode(key, options);
}
if (Array.isArray(value)) {
return value
.reduce(formatter(key), [])
.join('&');
}
return encode(key, options) + '=' + encode(value, options);
}).filter(x => x.length > 0).join('&');
};
exports.parseUrl = (url, options) => {
options = Object.assign({
decode: true
}, options);
const [url_, hash] = splitOnFirst(url, '#');
return Object.assign(
{
url: url_.split('?')[0] || '',
query: parse(extract(url), options)
},
options && options.parseFragmentIdentifier && hash ? {fragmentIdentifier: decode(hash, options)} : {}
);
};
exports.stringifyUrl = (object, options) => {
options = Object.assign({
encode: true,
strict: true
}, options);
const url = removeHash(object.url).split('?')[0] || '';
const queryFromUrl = exports.extract(object.url);
const parsedQueryFromUrl = exports.parse(queryFromUrl, {sort: false});
const query = Object.assign(parsedQueryFromUrl, object.query);
let queryString = exports.stringify(query, options);
if (queryString) {
queryString = `?${queryString}`;
}
let hash = getHash(object.url);
if (object.fragmentIdentifier) {
hash = `#${encode(object.fragmentIdentifier, options)}`;
}
return `${url}${queryString}${hash}`;
};
exports.pick = (input, filter, options) => {
options = Object.assign({
parseFragmentIdentifier: true
}, options);
const {url, query, fragmentIdentifier} = exports.parseUrl(input, options);
return exports.stringifyUrl({
url,
query: filterObject(query, filter),
fragmentIdentifier
}, options);
};
exports.exclude = (input, filter, options) => {
const exclusionFilter = Array.isArray(filter) ? key => !filter.includes(key) : (key, value) => !filter(key, value);
return exports.pick(input, exclusionFilter, options);
};
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (http://sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
var token = '%[a-f0-9]{2}';
var singleMatcher = new RegExp('(' + token + ')|([^%]+?)', 'gi');
var multiMatcher = new RegExp('(' + token + ')+', 'gi');
function decodeComponents(components, split) {
try {
// Try to decode the entire string first
return [decodeURIComponent(components.join(''))];
} catch (err) {
// Do nothing
}
if (components.length === 1) {
return components;
}
split = split || 1;
// Split the array in 2 parts
var left = components.slice(0, split);
var right = components.slice(split);
return Array.prototype.concat.call([], decodeComponents(left), decodeComponents(right));
}
function decode(input) {
try {
return decodeURIComponent(input);
} catch (err) {
var tokens = input.match(singleMatcher) || [];
for (var i = 1; i < tokens.length; i++) {
input = decodeComponents(tokens, i).join('');
tokens = input.match(singleMatcher) || [];
}
return input;
}
}
function customDecodeURIComponent(input) {
// Keep track of all the replacements and prefill the map with the `BOM`
var replaceMap = {
'%FE%FF': '\uFFFD\uFFFD',
'%FF%FE': '\uFFFD\uFFFD'
};
var match = multiMatcher.exec(input);
while (match) {
try {
// Decode as big chunks as possible
replaceMap[match[0]] = decodeURIComponent(match[0]);
} catch (err) {
var result = decode(match[0]);
if (result !== match[0]) {
replaceMap[match[0]] = result;
}
}
match = multiMatcher.exec(input);
}
// Add `%C2` at the end of the map to make sure it does not replace the combinator before everything else
replaceMap['%C2'] = '\uFFFD';
var entries = Object.keys(replaceMap);
for (var i = 0; i < entries.length; i++) {
// Replace all decoded components
var key = entries[i];
input = input.replace(new RegExp(key, 'g'), replaceMap[key]);
}
return input;
}
module.exports = function (encodedURI) {
if (typeof encodedURI !== 'string') {
throw new TypeError('Expected `encodedURI` to be of type `string`, got `' + typeof encodedURI + '`');
}
try {
encodedURI = encodedURI.replace(/\+/g, ' ');
// Try the built in decoder first
return decodeURIComponent(encodedURI);
} catch (err) {
// Fallback to a more advanced decoder
return customDecodeURIComponent(encodedURI);
}
};
The MIT License (MIT)
Copyright (c) 2017, Sam Verschueren <sam.verschueren@gmail.com> (github.com/SamVerschueren)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
{
"_from": "decode-uri-component@0.2.2",
"_id": "decode-uri-component@0.2.2",
"_inBundle": false,
"_integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
"_location": "/query-string/decode-uri-component",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "decode-uri-component@0.2.2",
"name": "decode-uri-component",
"escapedName": "decode-uri-component",
"rawSpec": "0.2.2",
"saveSpec": null,
"fetchSpec": "0.2.2"
},
"_requiredBy": [
"/query-string"
],
"_resolved": "https://repo.huaweicloud.com/repository/npm/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
"_shasum": "e69dbe25d37941171dd540e024c444cd5188e1e9",
"_spec": "decode-uri-component@0.2.2",
"_where": "E:\\H5_2.0\\node_modules\\query-string",
"author": {
"name": "Sam Verschueren",
"email": "sam.verschueren@gmail.com",
"url": "github.com/SamVerschueren"
},
"bugs": {
"url": "https://github.com/SamVerschueren/decode-uri-component/issues"
},
"bundleDependencies": false,
"deprecated": false,
"description": "A better decodeURIComponent",
"devDependencies": {
"ava": "^0.17.0",
"coveralls": "^2.13.1",
"nyc": "^10.3.2",
"xo": "^0.16.0"
},
"engines": {
"node": ">=0.10"
},
"files": [
"index.js"
],
"homepage": "https://github.com/SamVerschueren/decode-uri-component#readme",
"keywords": [
"decode",
"uri",
"component",
"decodeuricomponent",
"components",
"decoder",
"url"
],
"license": "MIT",
"name": "decode-uri-component",
"repository": {
"type": "git",
"url": "git+https://github.com/SamVerschueren/decode-uri-component.git"
},
"scripts": {
"coveralls": "nyc report --reporter=text-lcov | coveralls",
"test": "xo && nyc ava"
},
"version": "0.2.2"
}
# decode-uri-component
![CI](https://github.com/SamVerschueren/decode-uri-component/workflows/CI/badge.svg) [![Coverage Status](https://coveralls.io/repos/SamVerschueren/decode-uri-component/badge.svg?branch=master&service=github)](https://coveralls.io/github/SamVerschueren/decode-uri-component?branch=master)
> A better [decodeURIComponent](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent)
## Why?
- Decodes `+` to a space.
- Converts the [BOM](https://en.wikipedia.org/wiki/Byte_order_mark) to a [replacement character](https://en.wikipedia.org/wiki/Specials_(Unicode_block)#Replacement_character) `�`.
- Does not throw with invalid encoded input.
- Decodes as much of the string as possible.
## Install
```
$ npm install --save decode-uri-component
```
## Usage
```js
const decodeUriComponent = require('decode-uri-component');
decodeUriComponent('%25');
//=> '%'
decodeUriComponent('%');
//=> '%'
decodeUriComponent('st%C3%A5le');
//=> 'ståle'
decodeUriComponent('%st%C3%A5le%');
//=> '%ståle%'
decodeUriComponent('%%7Bst%C3%A5le%7D%');
//=> '%{ståle}%'
decodeUriComponent('%7B%ab%%7C%de%%7D');
//=> '{%ab%|%de%}'
decodeUriComponent('%FE%FF');
//=> '\uFFFD\uFFFD'
decodeUriComponent('%C2');
//=> '\uFFFD'
decodeUriComponent('%C2%B5');
//=> 'µ'
```
## API
### decodeUriComponent(encodedURI)
#### encodedURI
Type: `string`
An encoded component of a Uniform Resource Identifier.
## License
MIT © [Sam Verschueren](https://github.com/SamVerschueren)
---
<div align="center">
<b>
<a href="https://tidelift.com/subscription/pkg/npm-decode-uri-component?utm_source=npm-decode-uri-component&utm_medium=referral&utm_campaign=readme">Get professional support for this package with a Tidelift subscription</a>
</b>
<br>
<sub>
Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
</sub>
</div>
'use strict';
module.exports = function (obj, predicate) {
var ret = {};
var keys = Object.keys(obj);
var isArr = Array.isArray(predicate);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var val = obj[key];
if (isArr ? predicate.indexOf(key) !== -1 : predicate(key, val, obj)) {
ret[key] = val;
}
}
return ret;
};
The MIT License (MIT)
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
{
"_from": "filter-obj@1.1.0",
"_id": "filter-obj@1.1.0",
"_inBundle": false,
"_integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==",
"_location": "/query-string/filter-obj",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "filter-obj@1.1.0",
"name": "filter-obj",
"escapedName": "filter-obj",
"rawSpec": "1.1.0",
"saveSpec": null,
"fetchSpec": "1.1.0"
},
"_requiredBy": [
"/query-string"
],
"_resolved": "https://repo.huaweicloud.com/repository/npm/filter-obj/-/filter-obj-1.1.0.tgz",
"_shasum": "9b311112bc6c6127a16e016c6c5d7f19e0805c5b",
"_spec": "filter-obj@1.1.0",
"_where": "E:\\H5_2.0\\node_modules\\query-string",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
"bugs": {
"url": "https://github.com/sindresorhus/filter-obj/issues"
},
"bundleDependencies": false,
"deprecated": false,
"description": "Filter object keys and values into a new object",
"devDependencies": {
"ava": "0.0.4",
"xo": "*"
},
"engines": {
"node": ">=0.10.0"
},
"files": [
"index.js"
],
"homepage": "https://github.com/sindresorhus/filter-obj#readme",
"keywords": [
"filter",
"obj",
"object",
"key",
"keys",
"value",
"values",
"val",
"iterate",
"iterator"
],
"license": "MIT",
"name": "filter-obj",
"repository": {
"type": "git",
"url": "git+https://github.com/sindresorhus/filter-obj.git"
},
"scripts": {
"test": "xo && node test.js"
},
"version": "1.1.0"
}
# filter-obj [![Build Status](https://travis-ci.org/sindresorhus/filter-obj.svg?branch=master)](https://travis-ci.org/sindresorhus/filter-obj)
> Filter object keys and values into a new object
## Install
```
$ npm install --save filter-obj
```
## Usage
```js
var filterObj = require('filter-obj');
var obj = {
foo: true,
bar: false
};
var newObject = filterObj(obj, function (key, value, object) {
return value === true;
});
//=> {foo: true}
var newObject2 = filterObj(obj, ['bar']);
//=> {bar: true}
```
## Related
- [map-obj](https://github.com/sindresorhus/map-obj) - Map object keys and values into a new object
- [object-assign](https://github.com/sindresorhus/object-assign) - Copy enumerable own properties from one or more source objects to a target object
## License
MIT © [Sindre Sorhus](http://sindresorhus.com)
/**
Split a string on the first occurrence of a given separator.
@param string - The string to split.
@param separator - The separator to split on.
@example
```
import splitOnFirst = require('split-on-first');
splitOnFirst('a-b-c', '-');
//=> ['a', 'b-c']
splitOnFirst('key:value:value2', ':');
//=> ['key', 'value:value2']
splitOnFirst('a---b---c', '---');
//=> ['a', 'b---c']
splitOnFirst('a-b-c', '+');
//=> ['a-b-c']
```
*/
declare function splitOnFirst(
string: string,
separator: string
): [string, string?];
export = splitOnFirst;
'use strict';
module.exports = (string, separator) => {
if (!(typeof string === 'string' && typeof separator === 'string')) {
throw new TypeError('Expected the arguments to be of type `string`');
}
if (separator === '') {
return [string];
}
const separatorIndex = string.indexOf(separator);
if (separatorIndex === -1) {
return [string];
}
return [
string.slice(0, separatorIndex),
string.slice(separatorIndex + separator.length)
];
};
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
{
"_from": "split-on-first@1.1.0",
"_id": "split-on-first@1.1.0",
"_inBundle": false,
"_integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==",
"_location": "/query-string/split-on-first",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "split-on-first@1.1.0",
"name": "split-on-first",
"escapedName": "split-on-first",
"rawSpec": "1.1.0",
"saveSpec": null,
"fetchSpec": "1.1.0"
},
"_requiredBy": [
"/query-string"
],
"_resolved": "https://repo.huaweicloud.com/repository/npm/split-on-first/-/split-on-first-1.1.0.tgz",
"_shasum": "f610afeee3b12bce1d0c30425e76398b78249a5f",
"_spec": "split-on-first@1.1.0",
"_where": "E:\\H5_2.0\\node_modules\\query-string",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
"bugs": {
"url": "https://github.com/sindresorhus/split-on-first/issues"
},
"bundleDependencies": false,
"deprecated": false,
"description": "Split a string on the first occurance of a given separator",
"devDependencies": {
"ava": "^1.4.1",
"tsd": "^0.7.2",
"xo": "^0.24.0"
},
"engines": {
"node": ">=6"
},
"files": [
"index.js",
"index.d.ts"
],
"homepage": "https://github.com/sindresorhus/split-on-first#readme",
"keywords": [
"split",
"string",
"first",
"occurrence",
"separator",
"delimiter",
"text"
],
"license": "MIT",
"name": "split-on-first",
"repository": {
"type": "git",
"url": "git+https://github.com/sindresorhus/split-on-first.git"
},
"scripts": {
"test": "xo && ava && tsd"
},
"version": "1.1.0"
}
# split-on-first [![Build Status](https://travis-ci.com/sindresorhus/split-on-first.svg?branch=master)](https://travis-ci.com/sindresorhus/split-on-first)
> Split a string on the first occurrence of a given separator
This is similar to [`String#split()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split), but that one splits on all the occurrences, not just the first one.
## Install
```
$ npm install split-on-first
```
## Usage
```js
const splitOnFirst = require('split-on-first');
splitOnFirst('a-b-c', '-');
//=> ['a', 'b-c']
splitOnFirst('key:value:value2', ':');
//=> ['key', 'value:value2']
splitOnFirst('a---b---c', '---');
//=> ['a', 'b---c']
splitOnFirst('a-b-c', '+');
//=> ['a-b-c']
```
## API
### splitOnFirst(string, separator)
#### string
Type: `string`
The string to split.
#### separator
Type: `string`
The separator to split on.
## Related
- [split-at](https://github.com/sindresorhus/split-at) - Split a string at one or more indices
## License
MIT © [Sindre Sorhus](https://sindresorhus.com)
'use strict';
module.exports = str => encodeURIComponent(str).replace(/[!'()*]/g, x => `%${x.charCodeAt(0).toString(16).toUpperCase()}`);
The MIT License (MIT)
Copyright (c) Kevin Martensson <kevinmartensson@gmail.com> (github.com/kevva)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
{
"_from": "strict-uri-encode@2.0.0",
"_id": "strict-uri-encode@2.0.0",
"_inBundle": false,
"_integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==",
"_location": "/query-string/strict-uri-encode",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "strict-uri-encode@2.0.0",
"name": "strict-uri-encode",
"escapedName": "strict-uri-encode",
"rawSpec": "2.0.0",
"saveSpec": null,
"fetchSpec": "2.0.0"
},
"_requiredBy": [
"/query-string"
],
"_resolved": "https://repo.huaweicloud.com/repository/npm/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
"_shasum": "b9c7330c7042862f6b142dc274bbcc5866ce3546",
"_spec": "strict-uri-encode@2.0.0",
"_where": "E:\\H5_2.0\\node_modules\\query-string",
"author": {
"name": "Kevin Mårtensson",
"email": "kevinmartensson@gmail.com",
"url": "github.com/kevva"
},
"bugs": {
"url": "https://github.com/kevva/strict-uri-encode/issues"
},
"bundleDependencies": false,
"deprecated": false,
"description": "A stricter URI encode adhering to RFC 3986",
"devDependencies": {
"ava": "*",
"xo": "*"
},
"engines": {
"node": ">=4"
},
"files": [
"index.js"
],
"homepage": "https://github.com/kevva/strict-uri-encode#readme",
"keywords": [
"component",
"encode",
"RFC3986",
"uri"
],
"license": "MIT",
"name": "strict-uri-encode",
"repository": {
"type": "git",
"url": "git+https://github.com/kevva/strict-uri-encode.git"
},
"scripts": {
"test": "xo && ava"
},
"version": "2.0.0"
}
# strict-uri-encode [![Build Status](https://travis-ci.org/kevva/strict-uri-encode.svg?branch=master)](https://travis-ci.org/kevva/strict-uri-encode)
> A stricter URI encode adhering to [RFC 3986](http://tools.ietf.org/html/rfc3986)
## Install
```
$ npm install --save strict-uri-encode
```
## Usage
```js
const strictUriEncode = require('strict-uri-encode');
strictUriEncode('unicorn!foobar');
//=> 'unicorn%21foobar'
strictUriEncode('unicorn*foobar');
//=> 'unicorn%2Afoobar'
```
## API
### strictUriEncode(string)
#### string
Type: `string`, `number`
String to URI encode.
## License
MIT © [Kevin Mårtensson](http://github.com/kevva)
{
"name": "query-string",
"version": "6.14.1",
"description": "Parse and stringify URL query strings",
"license": "MIT",
"repository": "sindresorhus/query-string",
"funding": "https://github.com/sponsors/sindresorhus",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "https://sindresorhus.com"
},
"engines": {
"node": ">=6"
},
"scripts": {
"benchmark": "node benchmark.js",
"test": "xo && ava && tsd"
},
"files": [
"index.js",
"index.d.ts"
],
"keywords": [
"browser",
"querystring",
"query",
"string",
"qs",
"param",
"parameter",
"url",
"parse",
"stringify",
"encode",
"decode",
"searchparams",
"filter"
],
"dependencies": {
"decode-uri-component": "^0.2.0",
"filter-obj": "^1.1.0",
"split-on-first": "^1.0.0",
"strict-uri-encode": "^2.0.0"
},
"devDependencies": {
"ava": "^1.4.1",
"benchmark": "^2.1.4",
"deep-equal": "^1.0.1",
"fast-check": "^1.5.0",
"tsd": "^0.7.3",
"xo": "^0.24.0"
},
"__npminstall_done": true,
"_from": "query-string@6.14.1",
"_resolved": "https://registry.npmmirror.com/query-string/-/query-string-6.14.1.tgz"
}
\ No newline at end of file
# query-string
> Parse and stringify URL [query strings](https://en.wikipedia.org/wiki/Query_string)
<br>
---
<div align="center">
<p>
<p>
<sup>
<a href="https://github.com/sponsors/sindresorhus">My open source work is supported by the community</a>
</sup>
</p>
<sup>Special thanks to:</sup>
<br>
<br>
<a href="https://standardresume.co/tech">
<img src="https://sindresorhus.com/assets/thanks/standard-resume-logo.svg" width="200"/>
</a>
</p>
</div>
---
<br>
## Install
```
$ npm install query-string
```
This module targets Node.js 6 or later and the latest version of Chrome, Firefox, and Safari. If you want support for older browsers, or, if your project is using create-react-app v1, use version 5: `npm install query-string@5`.
## Usage
```js
const queryString = require('query-string');
console.log(location.search);
//=> '?foo=bar'
const parsed = queryString.parse(location.search);
console.log(parsed);
//=> {foo: 'bar'}
console.log(location.hash);
//=> '#token=bada55cafe'
const parsedHash = queryString.parse(location.hash);
console.log(parsedHash);
//=> {token: 'bada55cafe'}
parsed.foo = 'unicorn';
parsed.ilike = 'pizza';
const stringified = queryString.stringify(parsed);
//=> 'foo=unicorn&ilike=pizza'
location.search = stringified;
// note that `location.search` automatically prepends a question mark
console.log(location.search);
//=> '?foo=unicorn&ilike=pizza'
```
## API
### .parse(string, options?)
Parse a query string into an object. Leading `?` or `#` are ignored, so you can pass `location.search` or `location.hash` directly.
The returned object is created with [`Object.create(null)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create) and thus does not have a `prototype`.
#### options
Type: `object`
##### decode
Type: `boolean`\
Default: `true`
Decode the keys and values. URL components are decoded with [`decode-uri-component`](https://github.com/SamVerschueren/decode-uri-component).
##### arrayFormat
Type: `string`\
Default: `'none'`
- `'bracket'`: Parse arrays with bracket representation:
```js
const queryString = require('query-string');
queryString.parse('foo[]=1&foo[]=2&foo[]=3', {arrayFormat: 'bracket'});
//=> {foo: ['1', '2', '3']}
```
- `'index'`: Parse arrays with index representation:
```js
const queryString = require('query-string');
queryString.parse('foo[0]=1&foo[1]=2&foo[3]=3', {arrayFormat: 'index'});
//=> {foo: ['1', '2', '3']}
```
- `'comma'`: Parse arrays with elements separated by comma:
```js
const queryString = require('query-string');
queryString.parse('foo=1,2,3', {arrayFormat: 'comma'});
//=> {foo: ['1', '2', '3']}
```
- `'separator'`: Parse arrays with elements separated by a custom character:
```js
const queryString = require('query-string');
queryString.parse('foo=1|2|3', {arrayFormat: 'separator', arrayFormatSeparator: '|'});
//=> {foo: ['1', '2', '3']}
```
- `'none'`: Parse arrays with elements using duplicate keys:
```js
const queryString = require('query-string');
queryString.parse('foo=1&foo=2&foo=3');
//=> {foo: ['1', '2', '3']}
```
##### arrayFormatSeparator
Type: `string`\
Default: `','`
The character used to separate array elements when using `{arrayFormat: 'separator'}`.
##### sort
Type: `Function | boolean`\
Default: `true`
Supports both `Function` as a custom sorting function or `false` to disable sorting.
##### parseNumbers
Type: `boolean`\
Default: `false`
```js
const queryString = require('query-string');
queryString.parse('foo=1', {parseNumbers: true});
//=> {foo: 1}
```
Parse the value as a number type instead of string type if it's a number.
##### parseBooleans
Type: `boolean`\
Default: `false`
```js
const queryString = require('query-string');
queryString.parse('foo=true', {parseBooleans: true});
//=> {foo: true}
```
Parse the value as a boolean type instead of string type if it's a boolean.
### .stringify(object, options?)
Stringify an object into a query string and sorting the keys.
#### options
Type: `object`
##### strict
Type: `boolean`\
Default: `true`
Strictly encode URI components with [strict-uri-encode](https://github.com/kevva/strict-uri-encode). It uses [encodeURIComponent](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) if set to false. You probably [don't care](https://github.com/sindresorhus/query-string/issues/42) about this option.
##### encode
Type: `boolean`\
Default: `true`
[URL encode](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) the keys and values.
##### arrayFormat
Type: `string`\
Default: `'none'`
- `'bracket'`: Serialize arrays using bracket representation:
```js
const queryString = require('query-string');
queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'bracket'});
//=> 'foo[]=1&foo[]=2&foo[]=3'
```
- `'index'`: Serialize arrays using index representation:
```js
const queryString = require('query-string');
queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'index'});
//=> 'foo[0]=1&foo[1]=2&foo[2]=3'
```
- `'comma'`: Serialize arrays by separating elements with comma:
```js
const queryString = require('query-string');
queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'comma'});
//=> 'foo=1,2,3'
```
- `'none'`: Serialize arrays by using duplicate keys:
```js
const queryString = require('query-string');
queryString.stringify({foo: [1, 2, 3]});
//=> 'foo=1&foo=2&foo=3'
```
##### arrayFormatSeparator
Type: `string`\
Default: `','`
The character used to separate array elements when using `{arrayFormat: 'separator'}`.
##### sort
Type: `Function | boolean`
Supports both `Function` as a custom sorting function or `false` to disable sorting.
```js
const queryString = require('query-string');
const order = ['c', 'a', 'b'];
queryString.stringify({a: 1, b: 2, c: 3}, {
sort: (a, b) => order.indexOf(a) - order.indexOf(b)
});
//=> 'c=3&a=1&b=2'
```
```js
const queryString = require('query-string');
queryString.stringify({b: 1, c: 2, a: 3}, {sort: false});
//=> 'b=1&c=2&a=3'
```
If omitted, keys are sorted using `Array#sort()`, which means, converting them to strings and comparing strings in Unicode code point order.
##### skipNull
Skip keys with `null` as the value.
Note that keys with `undefined` as the value are always skipped.
Type: `boolean`\
Default: `false`
```js
const queryString = require('query-string');
queryString.stringify({a: 1, b: undefined, c: null, d: 4}, {
skipNull: true
});
//=> 'a=1&d=4'
```
```js
const queryString = require('query-string');
queryString.stringify({a: undefined, b: null}, {
skipNull: true
});
//=> ''
```
##### skipEmptyString
Skip keys with an empty string as the value.
Type: `boolean`\
Default: `false`
```js
const queryString = require('query-string');
queryString.stringify({a: 1, b: '', c: '', d: 4}, {
skipEmptyString: true
});
//=> 'a=1&d=4'
```
```js
const queryString = require('query-string');
queryString.stringify({a: '', b: ''}, {
skipEmptyString: true
});
//=> ''
```
### .extract(string)
Extract a query string from a URL that can be passed into `.parse()`.
Note: This behaviour can be changed with the `skipNull` option.
### .parseUrl(string, options?)
Extract the URL and the query string as an object.
Returns an object with a `url` and `query` property.
If the `parseFragmentIdentifier` option is `true`, the object will also contain a `fragmentIdentifier` property.
```js
const queryString = require('query-string');
queryString.parseUrl('https://foo.bar?foo=bar');
//=> {url: 'https://foo.bar', query: {foo: 'bar'}}
queryString.parseUrl('https://foo.bar?foo=bar#xyz', {parseFragmentIdentifier: true});
//=> {url: 'https://foo.bar', query: {foo: 'bar'}, fragmentIdentifier: 'xyz'}
```
#### options
Type: `object`
The options are the same as for `.parse()`.
Extra options are as below.
##### parseFragmentIdentifier
Parse the fragment identifier from the URL.
Type: `boolean`\
Default: `false`
```js
const queryString = require('query-string');
queryString.parseUrl('https://foo.bar?foo=bar#xyz', {parseFragmentIdentifier: true});
//=> {url: 'https://foo.bar', query: {foo: 'bar'}, fragmentIdentifier: 'xyz'}
```
### .stringifyUrl(object, options?)
Stringify an object into a URL with a query string and sorting the keys. The inverse of [`.parseUrl()`](https://github.com/sindresorhus/query-string#parseurlstring-options)
The `options` are the same as for `.stringify()`.
Returns a string with the URL and a query string.
Query items in the `query` property overrides queries in the `url` property.
The `fragmentIdentifier` property overrides the fragment identifier in the `url` property.
```js
queryString.stringifyUrl({url: 'https://foo.bar', query: {foo: 'bar'}});
//=> 'https://foo.bar?foo=bar'
queryString.stringifyUrl({url: 'https://foo.bar?foo=baz', query: {foo: 'bar'}});
//=> 'https://foo.bar?foo=bar'
queryString.stringifyUrl({
url: 'https://foo.bar',
query: {
top: 'foo'
},
fragmentIdentifier: 'bar'
});
//=> 'https://foo.bar?top=foo#bar'
```
#### object
Type: `object`
##### url
Type: `string`
The URL to stringify.
##### query
Type: `object`
Query items to add to the URL.
### .pick(url, keys, options?)
### .pick(url, filter, options?)
Pick query parameters from a URL.
Returns a string with the new URL.
```js
const queryString = require('query-string');
queryString.pick('https://foo.bar?foo=1&bar=2#hello', ['foo']);
//=> 'https://foo.bar?foo=1#hello'
queryString.pick('https://foo.bar?foo=1&bar=2#hello', (name, value) => value === 2, {parseNumbers: true});
//=> 'https://foo.bar?bar=2#hello'
```
### .exclude(url, keys, options?)
### .exclude(url, filter, options?)
Exclude query parameters from a URL.
Returns a string with the new URL.
```js
const queryString = require('query-string');
queryString.exclude('https://foo.bar?foo=1&bar=2#hello', ['foo']);
//=> 'https://foo.bar?bar=2#hello'
queryString.exclude('https://foo.bar?foo=1&bar=2#hello', (name, value) => value === 2, {parseNumbers: true});
//=> 'https://foo.bar?foo=1#hello'
```
#### url
Type: `string`
The URL containing the query parameters to filter.
#### keys
Type: `string[]`
The names of the query parameters to filter based on the function used.
#### filter
Type: `(key, value) => boolean`
A filter predicate that will be provided the name of each query parameter and its value. The `parseNumbers` and `parseBooleans` options also affect `value`.
#### options
Type: `object`
[Parse options](#options) and [stringify options](#options-1).
## Nesting
This module intentionally doesn't support nesting as it's not spec'd and varies between implementations, which causes a lot of [edge cases](https://github.com/visionmedia/node-querystring/issues).
You're much better off just converting the object to a JSON string:
```js
const queryString = require('query-string');
queryString.stringify({
foo: 'bar',
nested: JSON.stringify({
unicorn: 'cake'
})
});
//=> 'foo=bar&nested=%7B%22unicorn%22%3A%22cake%22%7D'
```
However, there is support for multiple instances of the same key:
```js
const queryString = require('query-string');
queryString.parse('likes=cake&name=bob&likes=icecream');
//=> {likes: ['cake', 'icecream'], name: 'bob'}
queryString.stringify({color: ['taupe', 'chartreuse'], id: '515'});
//=> 'color=taupe&color=chartreuse&id=515'
```
## Falsy values
Sometimes you want to unset a key, or maybe just make it present without assigning a value to it. Here is how falsy values are stringified:
```js
const queryString = require('query-string');
queryString.stringify({foo: false});
//=> 'foo=false'
queryString.stringify({foo: null});
//=> 'foo'
queryString.stringify({foo: undefined});
//=> ''
```
## query-string for enterprise
Available as part of the Tidelift Subscription.
The maintainers of query-string and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-query-string?utm_source=npm-query-string&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
# uni-simple-router
> 一个更为简洁的[Vue-router](https://router.vuejs.org/zh/),专为 [uni-app](https://uniapp.dcloud.io/) 量身打造
## 介绍
`uni-simple-router` 是专为 [uni-app](https://uniapp.dcloud.io/) 打造的路由器。它与 [uni-app](https://uniapp.dcloud.io/) 核心深度集成,使使用 [uni-app](https://uniapp.dcloud.io/) 轻松构建单页应用程序变得轻而易举。功能包括:
* `H5端` 能完全使用 `vue-router` 进行开发。
* 模块化,基于组件的路由器配置。
* 路由参数,查询,通配符。
* `H5端` 查看由 `uni-simple-router` 过渡系统提供动力的过渡效果。
* 更细粒度的导航控制。
* `H端`自动控制活动的CSS类链接。
* 通配小程序端、APP端、H5端。
开始使用 [查看文档](http://hhyang.cn),或 [使用示例](https://github.com/SilurianYang/uni-simple-router/tree/master/examples)(请参见下面的示例)。
## 问题
在提交问题的之前,请确保阅读 [“问题报告清单”](https://github.com/SilurianYang/uni-simple-router/issues/new?assignees=&labels=&template=bug_report.md&title=) 。不符合准则的问题可能会立即被解决。
## 贡献
提出拉取请求之前,请务必先阅读 [查看文档](http://hhyang.cn)(请参见下面的示例)。。
## 变更日志
[发行说明](https://github.com/SilurianYang/uni-simple-router/releases) 中记录了每个发行版的详细信息更改。
## 特别感谢
特别感谢 [markrgba](https://github.com/markrgba) 一直以来对文档和相关测试的维护。
## 技术交流
<a target="_blank" href="//shang.qq.com/wpa/qunwpa?idkey=0f4d7f38e5d15dd49bf7c3032c80ed3f54ecfa3dd800053d6ae145c869f9eb47"><img border="0" src="http://pub.idqqimg.com/wpa/images/group.png" alt="uni-app 插件" title="uni-app 插件"></a>
import { uniAppHook, Global } from '../helpers/config';
import {
callAppHook, getPages, getPageVmOrMp, ruleToUniNavInfo, formatTo, formatFrom, APPGetPageRoute, getPageOnBeforeBack,
} from './util';
import { noop } from '../helpers/util';
import { warn } from '../helpers/warn';
import uniPushTo from './uniNav';
let startBack = false; // 主要是兼容低端手机返回卡 然后多次返回直接提示退出的问题
/**
* 还原并执行所有 拦截下来的生命周期 app.vue 及 index 下的生命周期
* @param {Boolean} callHome // 是否触发首页的生命周期
*
* this 为当前 page 对象
*/
const callwaitHooks = function (callHome) {
return new Promise(async (resolve) => {
const variation = []; // 存储一下在uni-app上的变异生命钩子 奇葩的要死
const {
appVue, indexVue, onLaunch, onShow, waitHooks, variationFuns, indexCallHooks,
} = uniAppHook;
const app = appVue.$options;
await onLaunch.fun[onLaunch.fun.length - 1].call(appVue, onLaunch.args); // 确保只执行最后一个 并且强化异步操作
onShow.fun[onShow.fun.length - 1].call(appVue, onShow.args); // onshow 不保证异步 直接确保执行最后一个
if (callHome) { // 触发首页生命周期
// eslint-disable-next-line
for (const key in waitHooks) {
if (indexCallHooks.includes(key)) { // 只有在被包含的情况下才执行
callAppHook.call(this, waitHooks[key].fun);
}
}
}
if (onLaunch.isHijack) { // 还原 onLaunch生命钩子
app.onLaunch.splice(app.onLaunch.length - 1, 1, onLaunch.fun[0]);
}
if (onShow.isHijack) { // 继续还原 onShow
app.onShow.splice(app.onShow.length - 1, 1, onShow.fun[0]);
}
// eslint-disable-next-line
for (const key in waitHooks) { // 还原 首页下的生命钩子
const item = waitHooks[key];
if (item.isHijack) {
if (variationFuns.includes(key)) { // 变异方法
variation.push({ key, fun: item.fun[0] });
} else {
const indeHooks = indexVue[key];
// 修复 https://github.com/SilurianYang/uni-simple-router/issues/76
setTimeout(() => { // 异步延迟还原 不然 uni-app 给给触发了
indeHooks.splice(indeHooks.length - 1, 1, item.fun[0]);
}, 50);
}
}
}
resolve(variation);
});
};
/**
* 还原剩下的奇葩生命钩子
* @param {Object} variation 当前uni-app中的一些变异方法 奇葩生命钩子
*/
const callVariationHooks = function (variation) {
for (let i = 0; i < variation.length; i += 1) {
const { key, fun } = variation[i];
const indeHooks = uniAppHook.indexVue[key];
indeHooks.splice(indeHooks.length - 1, 1, fun);
}
};
/**
* 主要是对app.vue下onLaunch和onShow生命周期进行劫持
*
* this 为当前 page 对象
*/
export const proxyLaunchHook = function () {
const {
onLaunch,
onShow,
} = this.$options;
uniAppHook.appVue = this; // 缓存 当前app.vue组件对象
if (onLaunch.length > 1) { // 确保有写 onLaunch 可能有其他混入 那也办法
uniAppHook.onLaunch.isHijack = true;
uniAppHook.onLaunch.fun = onLaunch.splice(onLaunch.length - 1, 1, (arg) => {
uniAppHook.onLaunch.args = arg;
}); // 替换uni-app自带的生命周期
}
if (onShow.length > 0) {
uniAppHook.onShow.isHijack = true;
uniAppHook.onShow.fun = onShow.splice(onShow.length - 1, 1, (arg) => {
uniAppHook.onShow.args = arg;
if (uniAppHook.pageReady) { // 因为还有app切前台后台的操作
callAppHook.call(this, uniAppHook.onShow.fun, arg);
}
}); // 替换替换 都替换
}
};
/**
* 把指定页面的生命钩子函数保存并替换
* this 为当前 page 对象
*/
export const proxyIndexHook = function (Router) {
const { needHooks, waitHooks } = uniAppHook;
const options = this.$options;
uniAppHook.indexVue = options;
for (let i = 0; i < needHooks.length; i += 1) {
const key = needHooks[i];
if (options[key] != null) { // 只劫持开发者声明的生命周期
const { length } = options[key];
// eslint-disable-next-line
const whObject= waitHooks[key]={};
whObject.fun = options[key].splice(length - 1, 1, noop); // 把实际的页面生命钩子函数缓存起来,替换原有的生命钩子
whObject.isHijack = true;
}
}
// eslint-disable-next-line
triggerLifeCycle.call(this, Router); // 接着 主动我们触发导航守卫
};
/**
* 触发全局beforeHooks 生命钩子
* @param {Object} _from // from 参数
* @param {Object} _to // to 参数
*
* this 为当前 Router 对象
*/
const beforeHooks = function (_from, _to) {
return new Promise(async (resolve) => {
const beforeHooksFun = this.lifeCycle.beforeHooks[0];
if (beforeHooksFun == null) {
return resolve();
}
await beforeHooksFun.call(this, _to, _from, resolve);
});
};
/**
* 触发全局afterEachHooks 生命钩子
* @param {Object} _from // from 参数
* @param {Object} _to // to 参数
*
* this 为当前 Router 对象
*/
const afterEachHooks = function (_from, _to) {
const afterHooks = this.lifeCycle.afterHooks[0];
if (afterHooks != null && afterHooks.constructor === Function) {
afterHooks.call(this, _to, _from);
}
};
/**
* 触发全局 beforeEnter 生命钩子
* @param {Object} finalRoute // 当前格式化后的路由参数
* @param {Object} _from // from 参数
* @param {Object} _to // to 参数
*
* this 为当前 Router 对象
*/
const beforeEnterHooks = function (finalRoute, _from, _to) {
return new Promise(async (resolve) => {
const { beforeEnter } = finalRoute.route;
if (beforeEnter == null || beforeEnter.constructor !== Function) { // 当前这个beforeEnter不存在 或者类型错误
return resolve();
}
await beforeEnter.call(this, _to, _from, resolve);
});
};
/**
* 触发返回事件公共方法
* @param {Object} page 用getPages获取到的页面栈对象
* @param {Object} options 当前vue页面对象
* @param {Object} backLayerC 需要返回页面的层级
*
* this 为当前 Router 对象
*/
const backCallHook = function (page, options, backLayerC = 1) {
const route = APPGetPageRoute([page]);
const NAVTYPE = 'RouterBack';
// eslint-disable-next-line
transitionTo.call(this, { path: route.path, query: route.query }, NAVTYPE, (finalRoute, fnType) => {
if (fnType != NAVTYPE) { // 返回时的api如果有next到其他页面 那么必须带上NAVTYPE 不相同则表示需要跳转到其他页面
return uniPushTo(finalRoute, fnType);
}
if (startBack) { // 如果当前处于正在返回的状态
return warn('当前处于正在返回的状态,请稍后再试!');
}
startBack = true; // 标记开始返回
options.onBackPress = [noop]; // 改回uni-app可执行的状态
setTimeout(() => {
this.back(backLayerC, undefined, true); // 越过加锁验证
startBack = false; // 返回结束
});
});
};
/**
* 处理返回按钮的生命钩子
* @param {Object} options 当前 vue 组件对象下的$options对象
* @param {Array} args 当前页面是点击头部返回还是底部返回
*
* this 为当前 Router 对象
*/
export const beforeBackHooks = async function (options, args) {
const isNext = await getPageOnBeforeBack(args); // 执行onBeforeBack
if (isNext === false) { // onBeforeBack 返回了true 阻止了跳转
Global.LockStatus = false; // 也需要解锁
return false;
}
const page = getPages(-3); // 上一个页面对象
backCallHook.call(this, page, options);
};
/**
* 处理back api的生命钩子
* @param {Object} options 当前 vue 组件对象下的$options对象
* @param {Array} args 当前页面是点击头部返回还是底部返回
*
* this 为当前 Router 对象
*/
export const backApiCallHook = async function (options, args) {
await getPageOnBeforeBack(args);
const { backLayerC } = Global;
const pages = getPages();
let page = null;
if (backLayerC > pages.length - 1 || backLayerC == pages.length - 1) { // 返回的首页 我们需要显示tabbar拦截
// eslint-disable-next-line
page = pages[0];
} else {
page = pages[pages.length - 2];
}
backCallHook.call(this, page, options, backLayerC);
};
/**
* v1.5.4+
* beforeRouteLeave 生命周期
* @param {Object} to 将要去的那个页面 to对象
* @param {Object} from 从那个页面触发的 from对象
* @param {Boolean} leaveHook:? 是否为 beforeRouteLeave 触发的next 到别处 如果是则不再触发 beforeRouteLeave 生命钩子
* this 为当前 Router 对象
*/
const beforeRouteLeaveHooks = function (from, to, leaveHook) {
return new Promise((resolve) => {
if (leaveHook) { // 我们知道这个是来自页面beforeRouteLeave next到其他地方,所有不必再执行啦
warn('beforeRouteLeave next到其他地方,无须再执行!');
return resolve();
}
if (from.path == to.path) { // 进入首页的时候不触发
return resolve();
}
const currentPage = getPages(-2); // 获取到全部的页面对象
const callThis = getPageVmOrMp(currentPage); // 获取到页面的 $vm 对象 及 page页面的this对象
const { beforeRouteLeave } = callThis.$options; // 查看当前是否有开发者声明
if (beforeRouteLeave == null) {
warn('当前页面下无 beforeRouteLeave 钩子声明,无须执行!');
return resolve();
}
if (beforeRouteLeave != null && beforeRouteLeave.constructor !== Function) {
warn('beforeRouteLeave 生命钩子声明错误,必须是一个函数!');
return resolve();
}
beforeRouteLeave.call(callThis, to, from, resolve); // 执行生命钩子
});
};
/**
* 验证当前 next() 管道函数是否支持下一步
*
* @param {Object} Intercept 拦截到的新路由规则
* @param {Object} fnType 跳转页面的类型方法 原始的
* @param {Object} navCB 回调函数 原始的
* @param {Boolean} leaveHookCall:? 是否为 beforeRouteLeave 触发的next 做拦截判断
* this 为当前 Router 对象
*
*/
const isNext = function (Intercept, fnType, navCB, leaveHookCall = false) {
return new Promise((resolve, reject) => {
if (Intercept == null) { // 什么也不做 直接执行下一个钩子
return resolve();
}
if (Intercept === false) { // 路由中断
Global.LockStatus = false; // 解锁跳转状态
return reject('路由终止');
}
if (Intercept.constructor === String) { // 说明 开发者直接传的path 并且没有指定 NAVTYPE 那么采用原来的navType
reject('next到其他页面');
// eslint-disable-next-line
return transitionTo.call(this, Intercept, fnType, navCB,leaveHookCall);
}
if (Intercept.constructor === Object) { // 有一系列的配置 包括页面切换动画什么的
reject('next到其他页面');
// eslint-disable-next-line
return transitionTo.call(this, Intercept, Intercept.NAVTYPE || fnType, navCB,leaveHookCall);
}
});
};
/**
* 核心方法 处理一系列的跳转配置
* @param {Object} rule 当前跳转规则
* @param {Object} fnType 跳转页面的类型方法
* @param {Object} navCB:? 回调函数
* @param {Boolean} leaveHook:? 是否为 beforeRouteLeave 触发的next 到别处 如果是则不再触发 beforeRouteLeave 生命钩子
*
* this 为当前 Router 对象
*/
export const transitionTo = async function (rule, fnType, navCB, leaveHook = false) {
await this.lifeCycle.routerbeforeHooks[0].call(this); // 触发内部跳转前的生命周期
const finalRoute = ruleToUniNavInfo(rule, this.CONFIG.routes); // 获得到最终的 route 对象
const _from = formatFrom(this.CONFIG.routes); // 先根据跳转类型获取 from 数据
const _to = formatTo(finalRoute); // 再根据跳转类型获取 to 数据
try {
const leaveResult = await beforeRouteLeaveHooks.call(this, _from, _to, leaveHook); // 执行页面中的 beforeRouteLeave 生命周期 v1.5.4+
await isNext.call(this, leaveResult, fnType, navCB, true); // 验证当前是否继续 可能需要递归 那么 我们把参数传递过去
const beforeResult = await beforeHooks.call(this, _from, _to); // 执行 beforeEach 生命周期
await isNext.call(this, beforeResult, fnType, navCB); // 验证当前是否继续 可能需要递归 那么 我们把参数传递过去
const enterResult = await beforeEnterHooks.call(this, finalRoute, _from, _to); // 接着执行 beforeEnter 生命周期
await isNext.call(this, enterResult, fnType, navCB); // 再次验证 如果生命钩子多的话应该写成递归或者循环
} catch (e) {
warn(e); // 打印开发者操作的日志
return false;
}
if (navCB) {
navCB.call(this, finalRoute, fnType); // 执行当前回调生命周期
}
afterEachHooks.call(this, _from, _to);
await this.lifeCycle.routerAfterHooks[0].call(this); // 触发内部跳转前的生命周期
};
/**
* 主动触发导航守卫
* @param {Object} Router 当前路由对象
*
* this 当前vue页面组件对象
*/
export const triggerLifeCycle = function (Router) {
const topPage = getCurrentPages()[0];
if (topPage == null) {
return warn('打扰了,当前一个页面也没有 这不是官方的bug是什么??');
}
const { query, page } = getPageVmOrMp(topPage, false);
transitionTo.call(Router, { path: page.route, query }, 'push', async (finalRoute, fnType) => {
let variation = [];
if (`/${page.route}` == finalRoute.route.path) { // 在首页不动的情况下
uniAppHook.pageReady = true; // 标致着路由已经就绪 可能准备起飞
await callwaitHooks.call(this, true);
} else { // 需要跳转
variation = await callwaitHooks.call(this, false); // 只触发app.vue中的生命周期
await uniPushTo(finalRoute, fnType);
}
plus.nativeObj.View.getViewById('router-loadding').close();
callVariationHooks(variation);
uniAppHook.pageReady = true; // 标致着路由已经就绪 可能准备起飞
});
};
/**
* 处理tabbar点击拦截事件
* @param {Object} path 当前需要跳转的tab页面路径
*
* this 为当前 Router 对象
*/
export const beforeTabHooks = function (path) {
transitionTo.call(this, { path: `/${path}`, query: {} }, 'pushTab', (finalRoute, fnType) => {
uniPushTo(finalRoute, fnType);
});
};
import {
proxyLaunchHook, beforeBackHooks, beforeTabHooks, backApiCallHook,
} from './hooks';
import { Global, uniAppHook } from '../helpers/config';
import { assertCanBack } from './util';
import { warn } from '../helpers/warn';
/**
* 重写掉uni-app的 uni.getLocation 和 uni.chooseLocation APi
* @param {Object} Router 当前路由对象
*/
export const rewriteUniFun = function (Router) {
const oldSwitchTab = uni.switchTab; // 缓存 跳转到 tabBar 页面
uni.switchTab = function ({ url, ...args }, normal = false) {
if (normal === true || uniAppHook.pageReady === false) { // 调用原始的uni-app api
oldSwitchTab({
url,
...args,
});
} else {
if (uniAppHook.pageReady) { // 只有在路由守卫等 处理完所有操作后才能触发
const { path } = Router.$Route; // 获取当前路径
if (path == url) { // 路径相同不执行
return warn(`当前跳转路径:${url} 已在本页面无须跳转`);
}
beforeTabHooks.call(Router, url.substring(1)); // 不要 /
} else {
warn('路由守卫正在忙碌中 不允许执行 ‘uni.switchTab’');
}
}
};
};
/**
* 对当前app做一个动画页面 用来过渡首次next 等待时间过长的尴尬
* @param {Object} Router 当前路由对象
*/
export const registerLoddingPage = function (Router) {
const { loddingPageHook, loddingPageStyle } = Router.CONFIG.APP; // 获取app所有配置
const view = new plus.nativeObj.View('router-loadding', {
top: '0px',
left: '0px',
height: '100%',
width: '100%',
...loddingPageStyle.call(Router),
});
loddingPageHook.call(Router, view); // 触发等待页面生命周期
};
/**
* 移除当前 页面上 非router 声明的 onBackPress 事件
* @param {Object} page 当前 vue 组件对象
* @param {Object} options 当前page对象的 $options
* 修复 https://github.com/SilurianYang/uni-simple-router/issues/106
*/
export const removeBackPressEvent = function (page, options) {
const isBack = assertCanBack(page);
if (isBack) { // 可返回
options.onBackPress = [options.onBackPress[0]]; // 路由混入的都干掉
}
};
/**
* 判断当前页面是否需要拦截返回
*
* @param {Object} page 当前 vue 组件对象
* @param {Object} options 当前 vue 组件对象下的$options对象
* @param {Array} args 当前页面是点击头部返回还是底部返回
* 修复 https://github.com/SilurianYang/uni-simple-router/issues/66
*
* this 为当前 Router 对象
*/
export const pageIsHeadBack = function (page, options, args) {
if (args[0].from == 'navigateBack') { // 调用api返回
if (Global.LockStatus) { // 正在跳转的时候 返回按键按的太快啦
warn('当前页面正在处于跳转状态,请稍后再进行跳转....');
return true;
}
Global.LockStatus = true; // 设置为锁住状态
backApiCallHook.call(this, options, args);
return true;
}
const isBack = assertCanBack(page);
if (isBack) { // 可返回
if (Global.LockStatus) { // 正在跳转的时候 返回按键按的太快啦
warn('当前页面正在处于跳转状态,请稍后再进行跳转....');
return true;
}
Global.LockStatus = true; // 设置为锁住状态
beforeBackHooks.call(this, options, args);
return true;
}
return false;
};
/**
* 开始初始化app端路由配置
*
* @param {Object} Router
*
* this 为当前 page 对象
*/
export const appInit = function (Router) {
proxyLaunchHook.call(this);
const { holdTabbar } = Router.CONFIG.APP;
if (holdTabbar) { // 开启tab拦截时
rewriteUniFun(Router);
}
registerLoddingPage(Router);
};
import { methods, baseConfig, Global } from '../helpers/config';
import { noop, formatURLQuery } from '../helpers/util';
let stop = null;
/**
* @param {Object} finalRoute 格式化后的路由跳转规则
* @param {Object} NAVTYPE 需要调用的跳转方法
*/
const uniPushTo = function (finalRoute, NAVTYPE) {
return new Promise((resolve) => {
const query = formatURLQuery(`?${finalRoute.uniRoute.query}`);
const { APP } = baseConfig;
const { url } = finalRoute.uniRoute;
stop = setTimeout(() => {
resolve(url);
resolve = noop; // 执行完了就没了 确保不会被下一次执行
Global.LockStatus = false; // 跳转完成解锁状态
}, APP.switchPageOutTime);
uni[methods[NAVTYPE]]({
url: url + query,
...finalRoute.route.animation,
complete: () => {
clearTimeout(stop);
resolve(url);
resolve = noop; // 执行完了就没了 确保不会被下一次执行
Global.LockStatus = false; // 跳转完成解锁状态
},
}, true); // 这里传递true 主要是兼容重写 uni.switchTab
});
};
export default uniPushTo;
import { err } from '../helpers/warn';
import { copyObject, parseQuery } from '../helpers/util';
import { Global, route as mergeRoute } from '../helpers/config';
/**
* 触发指定生命钩子
* @param {Array} funList //需要执行的方法列表
* @param {Object} args //触发生命钩子传递的参数
*/
export const callAppHook = function (funList = [], args) {
for (let i = 0; i < funList.length; i += 1) {
funList[i].call(this, args);
}
};
/**
* @param {Number} index //需要获取的页面下标 -2:表示获取最后一个即当前页面 -1:表示全部 -3:当前页面的前一个页面
* @param {Boolean} all //是否获取全部的页面
*/
export const getPages = function (index = -1, all) {
const pages = getCurrentPages(all);
if (index === -1) {
return pages;
}
if (index === -2) {
return pages[pages.length - 1];
}
if (index === -3) {
return pages[pages.length - 2];
}
return pages[index];
};
/**
* 验证当前页面是否为nvue页面
* @param {Object} page 当前页面对象
*/
export const isNvuePage = function (page) {
const cstr = page.constructor.name;
const pageType = {
s: true,
z: false,
};
return pageType[cstr];
};
/**
* @param {Object} page //当前顶级页面对象
* @param {Object} vim:? //是否获取 $vm 对象还是 $mp 对象
*/
export const getPageVmOrMp = function (page, vim = true) {
if (vim) {
return page.$vm;
}
if (page.$vm.$mp) {
return page.$vm.$mp;
}
if (isNvuePage(page)) { // nvue 页面
return {
page,
query: page.__displayReporter.query,
};
}
};
/**
* 获取 to 的配置参数
* @param {Object} rule 当前跳转的规则
*/
export const formatTo = function (finalRoute) {
const route = copyObject(finalRoute.route);
const { rule } = finalRoute;
route.query = rule.query || rule.params || {};
return route;
};
/**
* 通过一个未知的路径或者名称 在路由表中查找指定路由表 并返回
* @param {string} type //path 或者 name
* @param {Object} routes //当前对象的所有路由表
*/
export const pathOrNameToRoute = function (type, routes = Global.Router.CONFIG.routes) {
const routesKeys = Object.keys(routes);
for (let i = 0; i < routesKeys.length; i += 1) {
const key = routesKeys[i];
const item = routes[key];
if (item.path === `/${type}`) {
return mergeRoute(item); // 合并一下对象,主要是合并 query:{} 及 params:{}
}
if (item.path === type) {
return mergeRoute(item); // 合并一下对象,主要是合并 query:{} 及 params:{}
}
if (item.name == type) {
return mergeRoute(item); // 合并一下对象,主要是合并 query:{} 及 params:{}
}
}
err(`当前 '${type}' 在路由表中没有找到匹配的 name 或者 path`);
};
/**
* 统一格式话 路由传递的参数 看看是编码还是非编码 做相应的对策
*
* @param {Object} query 当前的路由参数
* @param {Boolean} getter 是从页面获取 route 对象下的参数 还是编码后传输
*/
export const getFormatQuery = function (query = {}) {
if (Global.Router.CONFIG.encodeURI) {
try {
query = JSON.parse(decodeURIComponent(query.query || encodeURIComponent('{}')));
} catch (e) {
query = JSON.parse(query.query);
}
}
return query;
};
/**
* 获取 from 的配置参数 from 页面永远都是站在当前页面忘其它地方走 所以都是最后一个页面
*
* @param {Object} routes //当前对象的所有路由表
*/
export const formatFrom = function (routes) {
const topPage = getPages(-2);
const { page, query } = getPageVmOrMp(topPage, false);
const route = pathOrNameToRoute(page.route, routes); // 获取到当前路由表下的 route
route.query = getFormatQuery(query); // 不管是编码传输还是非编码 最后都得在 to/from 中换成json对象
return route;
};
/**
*
* 把用户的跳转路由规则格式化成uni-app可用的路由跳转规则
*
* @param {Object} rule //当前用户跳转的路由规则
* @param {Object} routes //当前simple-router 下的路由表
*/
export const ruleToUniNavInfo = function (rule, routes) {
if (rule == null) {
return err('当前跳转规则为空,请检查跳转代码');
}
// eslint-disable-next-line
let [navType, route, query, animation] = ['path', null, {}, {}];
if (rule.constructor === String) { // 是字符串类型 那当前就是路径啦
route = pathOrNameToRoute(rule, routes); // 直接把 rule 当 path 传递 完事
} else if (rule.constructor === Object) { // 对象类型 可以是 path 或者 name
route = pathOrNameToRoute(rule.path || (navType = 'name', rule.name), routes); // 两则必有其一 报错自己处理
query = rule.query || rule.params || {};
animation = rule.animation || {};
} else {
return err('传的什么乱七八糟的类型?路由跳转规则只认字符串 \'path\' , 对象 \'path\' , 对象 \'name\' ');
}
animation = { ...Global.Router.CONFIG.APP.animation, ...route.animation || {}, ...animation }; // 合并多种方式声明的动画效果
route.animation = animation; // 这才是最终的页面切换效果
// 路径处理完后 开始格式化参数
const uniRoute = parseQuery(route.path, query); // uni-app 需要的跳转规则
return {
rule,
route,
uniRoute,
};
};
/**
* 获取当前页面下的 Route 信息
*
* @param {Object} pages 获取页面对象集合
* @param {Object} Vim 用户传递的当前页面对象
*/
export const APPGetPageRoute = function (pages, Vim) {
let [query, path] = [{}, ''];
const page = pages[pages.length - 1]; // 获取到当前页面
if (pages.length > 0) {
query = getFormatQuery(page.options, true);
path = page.route;
} else if (Vim != null) {
query = getFormatQuery(Vim.$mp.page.options, true);
path = page.route;
}
const route = pathOrNameToRoute(path);
route.query = query;
return route;
};
/**
* 获取当前页面下的 onBeforeBack 生命周期并执行
*
* @param {Object} args 当前返回页面时uni-app传递的参数
*/
export const getPageOnBeforeBack = function (args) {
return new Promise(async (resolve) => {
const currPage = getPages(-2); // 获取到当前页面
const { onBeforeBack } = currPage.$vm.$options;
if (onBeforeBack != null && onBeforeBack.constructor === Function) {
const isNext = await onBeforeBack.call(currPage.$vm, args);
if (isNext === true) {
return resolve(false);
}
}
return resolve(true);
});
};
/**
* 断言当前页面是否可返回上一级
* @param {Object} page 当前页面webview对象
*/
export const assertCanBack = function (page) {
const pageStyle = page.$getAppWebview().getStyle();
if (pageStyle.titleNView != null && pageStyle.titleNView.autoBackButton) { // 只有处理有带返回按钮的页面
return true;
}
// 两种情况 1.真的是顶级页面时 2.自定义头部
const { $page } = page;
if ($page && $page.meta.isQuit === false) { // 自定义头部 不是顶级页面
return true;
}
return false; // 不可返回 真的是顶级页面时 返回就直接退出app了
};
import { methods, Global } from '../helpers/config';
import { formatURLQuery } from '../helpers/util';
/**
* @param {Object} finalRoute 格式化后的路由跳转规则
* @param {Object} NAVTYPE 需要调用的跳转方法
*/
const appletsUniPushTo = function (finalRoute, NAVTYPE) {
return new Promise((resolve) => {
const query = formatURLQuery(`?${finalRoute.uniRoute.query}`);
const { url } = finalRoute.uniRoute;
uni[methods[NAVTYPE]]({
url: url + query,
complete: () => {
resolve(url);
Global.LockStatus = false; // 跳转完成解锁状态
},
});
});
};
export default appletsUniPushTo;
import { uniAppHook, Global } from '../helpers/config';
import {
callAppHook, getPageVmOrMp, ruleToUniNavInfo, formatTo, formatFrom, getPages,
} from './util';
import appletsUniPushTo from './appletsNav';
import { noop } from '../helpers/util';
import { warn } from '../helpers/warn';
/**
*
* @param {String} key
* @param {Function} hook 需要执行及还原的生命周期函数
*/
const toutiaoIndexHookCall = function (key, hook) {
const { indexVue } = uniAppHook;
const indeHooks = indexVue[key];
indeHooks.splice(indeHooks.length - 1, 1, hook);
};
/**
* 还原并执行所有 拦截下来的生命周期 app.vue 及 index 下的生命周期
* @param {Boolean} callHome // 是否触发首页的生命周期
*
* this 为当前 page 对象
*/
const callwaitHooks = function (callHome) {
return new Promise(async (resolve) => {
const variation = []; // 存储一下在uni-app上的变异生命钩子 奇葩的要死
const {
appVue, onLaunch, onShow, waitHooks, variationFuns, indexCallHooks,
} = uniAppHook;
const app = appVue.$options;
await onLaunch.fun[onLaunch.fun.length - 1].call(appVue, onLaunch.args); // 确保只执行最后一个 并且强化异步操作
onShow.fun[onShow.fun.length - 1].call(appVue, onShow.args); // onshow 不保证异步 直接确保执行最后一个
if (callHome) { // 触发首页生命周期
// eslint-disable-next-line
for (const key in waitHooks) {
if (indexCallHooks.includes(key)) { // 只有在被包含的情况下才执行
callAppHook.call(this, waitHooks[key].fun);
}
}
}
if (onLaunch.isHijack) { // 还原 onLaunch生命钩子
app.onLaunch.splice(app.onLaunch.length - 1, 1, onLaunch.fun[0]);
}
if (onShow.isHijack) { // 继续还原 onShow
app.onShow.splice(app.onShow.length - 1, 1, onShow.fun[0]);
}
// eslint-disable-next-line
for (const key in waitHooks) { // 还原 首页下的生命钩子
const item = waitHooks[key];
if (item.isHijack) {
if (variationFuns.includes(key)) { // 变异方法
variation.push({ key, fun: item.fun[0] });
} else {
toutiaoIndexHookCall(key, item.fun[0]);
}
}
}
resolve(variation);
});
};
/**
* 还原剩下的奇葩生命钩子
* @param {Object} variation 当前uni-app中的一些变异方法 奇葩生命钩子
*/
const callVariationHooks = function (variation) {
for (let i = 0; i < variation.length; i += 1) {
const { key, fun } = variation[i];
toutiaoIndexHookCall(key, fun);
}
};
/**
* 主要是对app.vue下onLaunch和onShow生命周期进行劫持
*
* this 为当前 page 对象
*/
export const proxyLaunchHook = function () {
const {
onLaunch,
onShow,
} = this.$options;
uniAppHook.appVue = this; // 缓存 当前app.vue组件对象
if (onLaunch.length > 1) { // 确保有写 onLaunch 可能有其他混入 那也办法
uniAppHook.onLaunch.isHijack = true;
uniAppHook.onLaunch.fun = onLaunch.splice(onLaunch.length - 1, 1, (arg) => {
uniAppHook.onLaunch.args = arg;
}); // 替换uni-app自带的生命周期
}
if (onShow.length > 0) {
uniAppHook.onShow.isHijack = true;
uniAppHook.onShow.fun = onShow.splice(onShow.length - 1, 1, (arg) => {
uniAppHook.onShow.args = arg;
if (uniAppHook.pageReady) { // 因为还有app切前台后台的操作
callAppHook.call(this, uniAppHook.onShow.fun, arg);
}
}); // 替换替换 都替换
}
};
/**
* 触发全局beforeHooks 生命钩子
* @param {Object} _from // from 参数
* @param {Object} _to // to 参数
*
* this 为当前 Router 对象
*/
const beforeHooks = function (_from, _to) {
return new Promise(async (resolve) => {
const beforeHooksFun = this.lifeCycle.beforeHooks[0];
if (beforeHooksFun == null) {
return resolve();
}
await beforeHooksFun.call(this, _to, _from, resolve);
});
};
/**
* 触发全局afterEachHooks 生命钩子
* @param {Object} _from // from 参数
* @param {Object} _to // to 参数
*
* this 为当前 Router 对象
*/
const afterEachHooks = function (_from, _to) {
const afterHooks = this.lifeCycle.afterHooks[0];
if (afterHooks != null && afterHooks.constructor === Function) {
afterHooks.call(this, _to, _from);
}
};
/**
* 触发全局 beforeEnter 生命钩子
* @param {Object} finalRoute // 当前格式化后的路由参数
* @param {Object} _from // from 参数
* @param {Object} _to // to 参数
*
* this 为当前 Router 对象
*/
const beforeEnterHooks = function (finalRoute, _from, _to) {
return new Promise(async (resolve) => {
const { beforeEnter } = finalRoute.route;
if (beforeEnter == null || beforeEnter.constructor !== Function) { // 当前这个beforeEnter不存在 或者类型错误
return resolve();
}
await beforeEnter.call(this, _to, _from, resolve);
});
};
/**
* v1.5.4+
* beforeRouteLeave 生命周期
* @param {Object} to 将要去的那个页面 to对象
* @param {Object} from 从那个页面触发的 from对象
* @param {Boolean} leaveHook:? 是否为 beforeRouteLeave 触发的next 到别处 如果是则不再触发 beforeRouteLeave 生命钩子
* this 为当前 Router 对象
*/
const beforeRouteLeaveHooks = function (from, to, leaveHook) {
return new Promise(async (resolve) => {
if (leaveHook) { // 我们知道这个是来自页面beforeRouteLeave next到其他地方,所有不必再执行啦
warn('beforeRouteLeave next到其他地方,无须再执行!');
return resolve();
}
if (from.path == to.path) { // 进入首页的时候不触发
return resolve();
}
const currentPage = getPages(-2); // 获取到全部的页面对象
const callThis = getPageVmOrMp(currentPage); // 获取到页面的 $vm 对象 及 page页面的this对象
const { beforeRouteLeave } = callThis.$options; // 查看当前是否有开发者声明
if (beforeRouteLeave == null) {
warn('当前页面下无 beforeRouteLeave 钩子声明,无须执行!');
return resolve();
}
if (beforeRouteLeave != null && beforeRouteLeave.constructor !== Function) {
warn('beforeRouteLeave 生命钩子声明错误,必须是一个函数!');
return resolve();
}
await beforeRouteLeave.call(callThis, to, from, resolve); // 执行生命钩子
});
};
/**
* 核心方法 处理一系列的跳转配置
* @param {Object} rule 当前跳转规则
* @param {Object} fnType 跳转页面的类型方法
* @param {Object} navCB:? 回调函数
* @param {Boolean} leaveHook:? 是否为 beforeRouteLeave 触发的next 到别处 如果是则不再触发 beforeRouteLeave 生命钩子
* this 为当前 Router 对象
*
*/
export const appletsTransitionTo = async function (rule, fnType, navCB, leaveHook = false) {
await this.lifeCycle.routerbeforeHooks[0].call(this); // 触发内部跳转前的生命周期
const finalRoute = ruleToUniNavInfo(rule, this.CONFIG.routes); // 获得到最终的 route 对象
const _from = formatFrom(this.CONFIG.routes); // 先根据跳转类型获取 from 数据
const _to = formatTo(finalRoute); // 再根据跳转类型获取 to 数据
try {
const leaveResult = await beforeRouteLeaveHooks.call(this, _from, _to, leaveHook); // 执行页面中的 beforeRouteLeave 生命周期 v1.5.4+
// eslint-disable-next-line
await isNext.call(this, leaveResult, fnType, navCB,true); // 验证当前是否继续 可能需要递归 那么 我们把参数传递过去
const beforeResult = await beforeHooks.call(this, _from, _to); // 执行 beforeEach 生命周期
// eslint-disable-next-line
await isNext.call(this, beforeResult, fnType, navCB); // 验证当前是否继续 可能需要递归 那么 我们把参数传递过去
const enterResult = await beforeEnterHooks.call(this, finalRoute, _from, _to); // 接着执行 beforeEnter 生命周期
// eslint-disable-next-line
await isNext.call(this, enterResult, fnType, navCB); // 再次验证 如果生命钩子多的话应该写成递归或者循环
} catch (e) {
warn(e); // 打印开发者操作的日志
return false;
}
if (navCB) {
navCB.call(this, finalRoute, fnType); // 执行当前回调生命周期
}
afterEachHooks.call(this, _from, _to);
await this.lifeCycle.routerAfterHooks[0].call(this); // 触发内部跳转前的生命周期
};
/**
* 触发全局 返回事件
* @param {Number} backLayer 需要返回的页面层级
* @param {Function} next 正真的回调函数
*
* this 为当前 Router 对象
*/
export const backCallHook = function (backLayer, next) {
const pages = getPages(); // 获取到全部的页面对象
const toPage = pages.reverse()[backLayer];
if (toPage == null) { // 没有匹配到的时候
return warn('亲爱的开发者,你确定页面栈中有这么多历史记录给你返回?');
}
const { query, page } = getPageVmOrMp(toPage, false);
const beforeFntype = 'RouterBack';
appletsTransitionTo.call(this, { path: page.route, query }, beforeFntype, (finalRoute, fnType) => {
const toPath = finalRoute.uniRoute.url;
if (`/${page.route}` == toPath || page.route == toPath) { // 直接调用返回api
next();
} else { // 有拦截到其他页面时
if (fnType == beforeFntype) {
return warn('调用返回api被拦截到其他页面需要指定合理的 ‘NAVTYPE’ ');
}
appletsUniPushTo(finalRoute, fnType);
}
});
};
/**
* 主动触发导航守卫
* @param {Object} Router 当前路由对象
*
*/
export const triggerLifeCycle = function (Router) {
const topPage = getCurrentPages()[0];
if (topPage == null) {
return warn('打扰了,当前一个页面也没有 这不是官方的bug是什么??');
}
const { query, page } = getPageVmOrMp(topPage, false);
appletsTransitionTo.call(Router, { path: page.route, query }, 'push', async (finalRoute, fnType) => {
let variation = [];
if (`/${page.route}` == finalRoute.route.path || page.route == finalRoute.route.path) { // 在首页不动的情况下
uniAppHook.pageReady = true; // 标致着路由已经就绪 可能准备起飞
await callwaitHooks.call(this, true);
} else { // 需要跳转
variation = await callwaitHooks.call(this, false); // 只触发app.vue中的生命周期
await appletsUniPushTo(finalRoute, fnType);
}
uniAppHook.pageReady = true; // 标致着路由已经就绪 可能准备起飞
callVariationHooks(variation);
});
};
/**
* 把指定页面的生命钩子函数保存并替换
* this 为当前 page 对象
*/
export const appletsProxyIndexHook = function (Router) {
if (process.env.VUE_APP_PLATFORM == 'mp-toutiao') { // 头条小程序首页生命周期由我们手动触发,缓存this
uniAppHook.toutiaoIndexThis = this;
}
const { needHooks, waitHooks } = uniAppHook;
const options = this.$options;
uniAppHook.indexVue = options;
for (let i = 0; i < needHooks.length; i += 1) {
const key = needHooks[i];
if (options[key] != null) { // 只劫持开发者声明的生命周期
const { length } = options[key];
// eslint-disable-next-line
const whObject= waitHooks[key]={};
whObject.fun = options[key].splice(length - 1, 1, noop); // 把实际的页面生命钩子函数缓存起来,替换原有的生命钩子
whObject.isHijack = true;
}
}
triggerLifeCycle.call(this, Router); // 接着 主动我们触发导航守卫
};
/**
* 验证当前 next() 管道函数是否支持下一步
*
* @param {Object} Intercept 拦截到的新路由规则
* @param {Object} fnType 跳转页面的类型方法 原始的
* @param {Object} navCB 回调函数 原始的
* @param {Boolean} leaveHookCall:? 是否为 beforeRouteLeave 触发的next 做拦截判断
* this 为当前 Router 对象
*
*/
const isNext = function (Intercept, fnType, navCB, leaveHookCall = false) {
return new Promise((resolve, reject) => {
if (Intercept == null) { // 什么也不做 直接执行下一个钩子
return resolve();
}
if (Intercept === false) { // 路由中断 我们需要把防抖设置为false
Global.LockStatus = false; // 解锁跳转状态
return reject('路由终止');
}
if (Intercept.constructor === String) { // 说明 开发者直接传的path 并且没有指定 NAVTYPE 那么采用原来的navType
reject('next到其他页面');
return appletsTransitionTo.call(this, Intercept, fnType, navCB, leaveHookCall);
}
if (Intercept.constructor === Object) { // 有一系列的配置 包括页面切换动画什么的
reject('next到其他页面');
return appletsTransitionTo.call(this, Intercept, Intercept.NAVTYPE || fnType, navCB, leaveHookCall);
}
});
};
import { proxyLaunchHook } from './hooks';
/**
* 开始初始化app端路由配置
*
* @param {Object} Router 当前Router对象
*
* this 为当前 page 对象
*/
const appletsInit = function () {
proxyLaunchHook.call(this);
};
export default appletsInit;
import { Global, route as mergeRoute } from '../helpers/config';
import { copyObject, parseQuery } from '../helpers/util';
import { err } from '../helpers/warn';
import { baiduApple, touTiao } from '../helpers/compile';
/**
* 触发指定生命钩子
* @param {Array} funList //需要执行的方法列表
* @param {Object} args //触发生命钩子传递的参数
*/
export const callAppHook = function (funList, args) {
for (let i = 0; i < funList.length; i += 1) {
funList[i].call(this, args);
}
};
/**
* @param {Object} page //当前顶级页面对象
* @param {Object} vim:? //是否获取 $vm 对象还是 $mp 对象
*/
export const getPageVmOrMp = function (page, vim = true) {
if (vim) {
return page.$vm;
}
const { $mp } = page.$vm;
baiduApple(() => { // 百度小程序新增一个route属性
$mp.page.route = $mp.page.is;
});
touTiao(() => { // 头条小程序新增一个route属性
$mp.page.route = $mp.page.is;
});
return $mp;
};
/**
* 统一格式话 路由传递的参数 看看是编码还是非编码 做相应的对策
*
* @param {Object} query 当前的路由参数
* @param {Boolean} getter 是从页面获取 route 对象下的参数 还是编码后传输
*/
export const getFormatQuery = function (query = {}, getter = false) {
if (Global.Router.CONFIG.encodeURI) {
if (getter) {
try { // 除去微信小程序都不需要 decodeURIComponent
query = JSON.parse(decodeURIComponent(query.query) || '{}');
} catch (e) { // 其他小程序
query = JSON.parse(query.query || '{}');
}
} else {
try {
query = JSON.parse(decodeURIComponent(query.query || encodeURIComponent('{}')));
} catch (e) {
query = JSON.parse(query.query);
}
}
}
return query;
};
/**
* @param {Number} index //需要获取的页面下标 -2:表示获取最后一个即当前页面 -1:表示全部 -3:当前页面的前一个页面
* @param {Boolean} all //是否获取全部的页面
*/
export const getPages = function (index = -1, all) {
const pages = getCurrentPages(all);
if (index === -1) {
return pages;
}
if (index === -2) {
return pages[pages.length - 1];
}
if (index === -3) {
return pages[pages.length - 2];
}
return pages[index];
};
/**
* 通过一个未知的路径或者名称 在路由表中查找指定路由表 并返回
* @param {string} type //path 或者 name
* @param {Object} routes //当前对象的所有路由表
*/
export const pathOrNameToRoute = function (type, routes = Global.Router.CONFIG.routes) {
const routesKeys = Object.keys(routes);
for (let i = 0; i < routesKeys.length; i += 1) {
const key = routesKeys[i];
const item = routes[key];
if (item.path === `/${type}`) {
return mergeRoute(item); // 合并一下对象,主要是合并 query:{} 及 params:{}
}
if (item.path === type) {
return mergeRoute(item); // 合并一下对象,主要是合并 query:{} 及 params:{}
}
if (item.name == type) {
return mergeRoute(item); // 合并一下对象,主要是合并 query:{} 及 params:{}
}
}
err(`当前 '${type}' 在路由表中没有找到匹配的 name 或者 path`);
};
/**
* 获取 to 的配置参数
* @param {Object} rule 当前跳转的规则
*/
export const formatTo = function (finalRoute) {
const route = copyObject(finalRoute.route);
const { rule } = finalRoute;
route.query = rule.query || rule.params || {};
return route;
};
/**
* 获取 from 的配置参数 from 页面永远都是站在当前页面忘其它地方走 所以都是最后一个页面
*
* @param {Object} routes //当前对象的所有路由表
*/
export const formatFrom = function (routes) {
const topPage = getPages(-2);
const { page, query } = getPageVmOrMp(topPage, false);
const route = pathOrNameToRoute(page.route, routes); // 获取到当前路由表下的 route
route.query = getFormatQuery(query); // 不管是编码传输还是非编码 最后都得在 to/from 中换成json对象
return route;
};
/**
*
* 把用户的跳转路由规则格式化成uni-app可用的路由跳转规则
*
* @param {Object} rule //当前用户跳转的路由规则
* @param {Object} routes //当前simple-router 下的路由表
*/
export const ruleToUniNavInfo = function (rule, routes) {
if (rule == null) {
return err('当前跳转规则为空,请检查跳转代码');
}
// eslint-disable-next-line
let [navType, route, query] = ['path', null, {}];
if (rule.constructor === String) { // 是字符串类型 那当前就是路径啦
route = pathOrNameToRoute(rule, routes); // 直接把 rule 当 path 传递 完事
} else if (rule.constructor === Object) { // 对象类型 可以是 path 或者 name
route = pathOrNameToRoute(rule.path || (navType = 'name', rule.name), routes); // 两则必有其一 报错自己处理
query = rule.query || rule.params || {};
} else {
return err('传的什么乱七八糟的类型?路由跳转规则只认字符串 \'path\' , 对象 \'path\' , 对象 \'name\' ');
}
// 路径处理完后 开始格式化参数
const uniRoute = parseQuery(route.path, query); // uni-app 需要的跳转规则
return {
rule,
route,
uniRoute,
};
};
/**
* 获取当前页面下的 Route 信息
*
* @param {Object} pages 获取页面对象集合
* @param {Object} Vim 用户传递的当前页面对象
*/
export const AppletsPageRoute = function (pages, Vim) {
let [query, path] = [{}, ''];
const page = pages[pages.length - 1]; // 获取到当前页面
if (pages.length > 0) {
const uniQuery = getPageVmOrMp(page, false).query;
query = getFormatQuery(uniQuery, true);
path = page.route;
} else if (Vim != null) {
query = getFormatQuery(Vim.$mp.page.options, true);
path = page.route;
}
const route = pathOrNameToRoute(path);
route.query = query;
return route;
};
const render = function (node) {
if (typeof node == 'string') { // 是一个文本节点
return document.createTextNode(node);
}
if (node instanceof HTMLElement) {
return node;
}
// eslint-disable-next-line
return createElement(node);
};
/**
* 根据标签及属性创建一个dom
*/
const createElement = function ({
tag,
attrs,
children,
} = {}) {
const $el = document.createElement(tag);
// eslint-disable-next-line
for (const [k, v] of Object.entries(attrs)) {
$el.setAttribute(k, v);
}
// eslint-disable-next-line
for (const item of children) {
$el.appendChild(render(item));
}
return $el;
};
const html = createElement({
tag: 'div',
attrs: {
id: 'router-loadding',
},
children: [
createElement({
tag: 'div',
attrs: {
class: 'loadding',
},
children: [],
}),
],
});
/* eslint-disable */
const style = createElement({
tag: 'style',
attrs: {
id: 'HHYANG_style',
},
children: [
`
body{padding:0;margin:0}#router-loadding{position:fixed;width:100%;height:3px;transition:all .05s;top:0;z-index:9999999999999999;}#router-loadding .loadding{position:fixed;top:0;height:3px;background-color:#47b14b;width:0;box-shadow:0 0 15px #4CAF50;transition:all .8s;border-top-right-radius:3px;border-bottom-right-radius:3px}
`,
],
});
const script = createElement({
tag: 'script',
attrs: {
id: 'HHYANG_script',
},
children: [
`
var HHYANG_El=document.querySelector("#router-loadding .loadding"),HHYANG_Pel=document.querySelector("#router-loadding"),w=0,stop=null,WH=window.innerWidth,loop=function(){w=w>=WH-35?w+parseInt(5*Math.random()):w+parseInt(35*Math.random());HHYANG_El.style.cssText="width:"+w+"px";w>=WH&&clearInterval(stop)};window.startLodding=function(a){a=void 0===a?500:a;HHYANG_Pel.style.cssText="display: block;";HHYANG_El.style.cssText="transition: all 0.8s;";w=0;clearInterval(stop);stop=setInterval(function(){loop()},a)};window.stopLodding=function(a){a=void 0===a?200:a;clearInterval(stop);HHYANG_El.style.cssText="width:"+WH+"px;transition: all "+a/1E3+"s;";HHYANG_Pel.style.cssText="opacity: 0;transition: all "+a/1E3+"s;";setTimeout(function(){HHYANG_Pel.style.cssText="display: none;";HHYANG_El.style.cssText="width:0px";w=0},a)};
`,
],
});
export const DOM = {
style,
html,
script,
};
/* eslint-enable */
<template>
<view @click="gotoPage()"><slot></slot></view>
</template>
<script>
const navType = {
push: 'push',
replace: 'replace',
replaceAll: 'replaceAll',
pushTab: 'pushTab'
};
export default {
props: {
to: {
type: [String, Object],
},
stopNavto: {
type: Boolean,
default: false
},
navType: {
type: String,
default: 'push'
},
level: {
type: Number,
default: 1
},
append: {
type: Boolean,
default: false
}
},
methods: {
formatNav(text) {
if (text != null && text.constructor === String) {
text = text.replace(/\'/g, '');
text = text.replace(/(\w+)(?=:)/g, function(val) {
return `"${val}"`;
});
text = text.replace(/:\s*([^,{}\s"]+)/g, function(val) {
const arr = val.split(':');
return `:"${arr[1].trim()}"`;
});
try {
text = JSON.parse(text);
} catch (e) {}
}
if (this.append) {
let pathArr = this.$Route.path.split('/');
pathArr.splice(pathArr.length - this.level, this.level);
pathArr = pathArr.join('/');
if (text.constructor === Object) {
if (text.path) {
text.path = pathArr + text.path;
}
} else {
text = pathArr + text;
}
}
return text;
},
gotoPage() {
if (this.stopNavto) {
return true;
}
const type = navType[this.navType];
if (type == null) {
return console.error(` "navType" unknown type \n\n value:${Object.values(navType).join('、')}`);
}
const navInfo = this.formatNav(this.to);
this.$Router[type](navInfo);
}
}
};
</script>
<style></style>
export const H5 = function (fn) {
// #ifdef H5
fn();
// #endif
};
export const APP = function (fn) {
// #ifdef APP-PLUS
fn();
// #endif
};
export const applets = function (fn) {
// #ifdef MP
fn();
// #endif
};
export const notH5 = function (fn) {
// #ifndef H5
fn();
// #endif
};
export const baiduApple = function (fn) {
// #ifdef MP-BAIDU
fn();
// #endif
};
export const touTiao = function (fn) {
// #ifdef MP-TOUTIAO
fn();
// #endif
};
export const mp = function (fn) {
// #ifdef MP
fn();
// #endif
};
export const baseConfig = {
h5: {
rewriteFun: true, // 是否对uni-app reLaunch/navigateBack 两个方法重写 处理uni刷新直接返回到首页和触发路由守卫
paramsToQuery: false, // h5端上通过params传参时规则是vue-router 刷新会丢失 开启此开关将变成?连接的方式
loading: true, // 是否显示加载动画
hinderTab: false, // 是否拦截uni-app自带底部菜单 TODO
vueRouterDev: false, // 完全使用采用vue-router的开发模式
useUniConfig: true, // 是否采用在pages.json下的所有页面配置信息,false时需开发者自行设置页面
keepUniIntercept: false, // 保留uni-app使用vue-router的拦截器
vueNext: false, // 在next管道函数中是否获取vueRouter next的原本参数
replaceStyle: false, // 是否对resetStyle函数中返回的style节点进行全部替换,否则为追加
resetStyle: () => JSON.parse('{}'), // 自定义加载样式函数 可返回一个包涵 html、style、script 的对象来重置Router内置的加载动画
mode: 'hash',
base: '/',
linkActiveClass: 'router-link-active',
linkExactActiveClass: 'router-link-exact-active',
scrollBehavior: (to, from, savedPostion) => savedPostion,
fallback: true,
},
APP: {
holdTabbar: true, // 是否开启底部菜单拦截
loddingPageStyle: () => JSON.parse('{"backgroundColor":"#FFF"}'), // 当前等待页面的样式 必须返回一个json
loddingPageHook: (view) => { plus.navigator.closeSplashscreen(); view.show(); }, // 刚刚打开页面处于等待状态,会触发此事件
animation: { animationType: 'pop-in', animationDuration: 300 }, // 页面切换动画
switchPageOutTime: 1000, // 最高能忍耐的页面切换时间 达到此时间 不管切换有没有完成都会显示页面出来 这对启动页帮助很大
},
debugger: false, // 是否处于开发阶段 设置为true则打印日志
encodeURI: true, // 是否对url传递的参数进行编码
routerBeforeEach: () => {}, // router 前置路由函数 每次触发跳转前先会触发此函数
routerAfterEach: () => {}, // router 后置路由函数 每次触发跳转后会触发此函数
routes: [],
};
export const methods = {
push: 'navigateTo',
replace: 'redirectTo',
replaceAll: 'reLaunch',
pushTab: 'switchTab',
back: 'navigateBack',
};
export const H5FnTypeToggle = {
push: 'push',
replace: 'replace',
replaceAll: 'replace',
pushTab: 'replace',
};
export const lifeCycle = {
beforeHooks: [],
afterHooks: [],
routerHooks: [],
routerbeforeHooks: [], // 内部跳转前生命周期
routerAfterHooks: [], // 内部跳转后生命周期
};
export const Global = { // 缓存一些必要的对象,作为全局可以访问的参数
$parseQuery: null, // url query 帮助类实例
Router: {},
vueRouter: {},
addedRoutes: [], // 用于缓存用户动态添加的路由
RouterReadyPromise: () => {},
H5RouterReady: null, // 当前router是否就绪
backLayerC: 1, // 返回api调用时开发者传递的 delta
LockStatus: false, // 当前是否正在进行跳转 正在跳转调用api是不给跳转的
};
export const uniAppHook = {
indexVue: {}, // 首页 组件对象
toutiaoIndexThis: {}, // 头条小程序Index this对象
appVue: {}, // 同getApp()获取到的对象一毛一样的 其实就是app.vue组件
onLaunch: { fun: [], args: {}, isHijack: false }, // 这两个是app.vue
onShow: { fun: [], args: {}, isHijack: false },
variationFuns: ['onReady', 'onUnload'], // 一些uni-app的变异方法 需要处理下
waitHooks: {}, // 首页等待中的生命钩子 一些需要等待的Hooks,就是在页面没有进来之前一些提前触发的生命钩子 主要是用户已经声明好的
indexCallHooks: ['onLoad', 'onReady', 'created', 'onShow'], // 在首页首次启动时需要触发的生命周期
needHooks: ['onLoad', 'onReady', 'onShow', 'created', 'onHide', 'onUnload', 'onResize'], // 首页需要拦截的生命钩子
pageReady: false,
onLaunched: false, // 否触发onLaunch事件
};
export const appletsConfig = { // 小程序端的一些路由所需配置
onLaunchEd: false, // 当前小程序端是否触发onLaunch事件
};
export const route = function (object = {}) {
return {
...object,
params: {},
query: {},
};
};
import { uniAppHook } from './config';
import H5init from '../vueRouter/init';
import { appInit, removeBackPressEvent, pageIsHeadBack } from '../appRouter/init';
import appletsInit from '../appletsRouter/init';
import { appPlatform } from './util';
import { proxyIndexHook } from '../appRouter/hooks';
import { appletsProxyIndexHook } from '../appletsRouter/hooks';
/**
* 获取一些需要在各个平台混入的事件
* @param {Object} Router 当前原始路由对象
*/
const getMixins = function (Router) {
return {
H5: {
beforeCreate() {
if (this.$options.router) {
H5init(Router.$root, this.$options.router, this);
}
},
},
APP: {
onLaunch() {
uniAppHook.onLaunched = true; // 标志已经触发了 onLaunch 事件
appInit.call(this, Router.$root);
},
onLoad() {
// 第一个页面 拦截所有生命周期
if (uniAppHook.onLaunched && !uniAppHook.pageReady) {
uniAppHook.onLaunched = false;
proxyIndexHook.call(this, Router.$root);
}
removeBackPressEvent(this.$mp.page, this.$options); // 移除页面的onBackPress事件
},
onBackPress(...args) {
return pageIsHeadBack.call(Router.$root, this.$mp.page, this.$options, args);
},
},
APPLETS: {
onLaunch() {
uniAppHook.onLaunched = true; // 标志已经触发了 onLaunch 事件
appletsInit.call(this, Router.$root);
},
onLoad() {
if (uniAppHook.onLaunched && !uniAppHook.pageReady) { // 必须是第一个页面
uniAppHook.onLaunched = false;
appletsProxyIndexHook.call(this, Router.$root);
}
},
},
};
};
const initMixins = function (Vue, Router) {
Vue.mixin({
...getMixins(Router)[appPlatform(true)],
});
};
export default initMixins;
import { appPlatform } from './util';
import { methods, H5FnTypeToggle, Global } from './config';
import { transitionTo } from '../appRouter/hooks';
import { appletsTransitionTo, backCallHook } from '../appletsRouter/hooks';
import uniPushTo from '../appRouter/uniNav';
import appletsUniPushTo from '../appletsRouter/appletsNav';
import { err, warn } from './warn';
import H5PushTo from '../vueRouter/routerNav';
import * as compile from './compile';
/**
* 返回api 触发的公共函数
* @param {Object/String} rule 当前跳转规则
* @param {String} fnType 跳转页面的类型方法
*
* this 为当前 Router 实例
*/
const isBcakNav = function ({
backLayer,
delta,
H5PATCH,
}) {
compile.H5(() => {
H5PATCH.on('historyBack', {
backLayer,
delta,
});
});
compile.APP(() => {
Global.backLayerC = backLayer; // 告诉路由需要返回几层
uni.navigateBack({
delta: backLayer,
complete: () => {
Global.LockStatus = false; // 跳转完成解锁状态
},
});
});
compile.mp(() => {
backCallHook.call(this, backLayer, () => {
uni.navigateBack({
delta: backLayer,
});
});
});
};
/**
* 非 返回api 触发的公共函数
* @param {Object/String} rule 当前跳转规则
* @param {String} fnType 跳转页面的类型方法
*
* this 为当前 Router 实例
*/
const notBackNav = function (rule, fnType) {
if (rule == null) {
return err('跳转规则为空,不允许这样操作');
}
if (rule.constructor === String) { // 单独 path 的情况 允许?拼接参数
const ruleArray = rule.split('?');
if (ruleArray.length > 1) {
rule = {
path: ruleArray[0],
query: Global.$parseQuery.parse(ruleArray[1]),
};
}
}
switch (appPlatform(true)) {
case 'H5':
return H5PushTo.call(this, H5FnTypeToggle[fnType], rule, methods[fnType]);
case 'APP':
Global.LockStatus = true; // 设置为锁住状态
return transitionTo.call(this, rule, fnType, uniPushTo);
case 'APPLETS':
Global.LockStatus = true; // 设置为锁住状态
return appletsTransitionTo.call(this, rule, fnType, appletsUniPushTo);
default:
err('糟糕!!!还有其他的执行环境???没听说过啊。一脸懵逼???加QQ群问问:769241495');
break;
}
};
/**
* 处理正在跳转的公共api
* @param {Object/String} rule 当前跳转规则
* @param {String} fnType 跳转页面的类型方法
* @param {Boolean} isBack 是否通过 back() api 调用的 默认为false
* @param {Boolean} enforce 是否强制越过跳转加锁检查 默认false 目前只有back() api 传递了
*
* this 为当前 Router 实例
*/
const navjump = function (rule, fnType, isBack = false, enforce = false) {
if (Global.LockStatus && !enforce) { // 正在跳转的状态下 给出提示正在跳转
return warn('当前页面正在处于跳转状态,请稍后再进行跳转....');
}
if (isBack) { // 是返回api触发的
return isBcakNav.call(this, rule, fnType);
}
return notBackNav.call(this, rule, fnType);
};
export default navjump;
import { Global } from './config';
import { warn, err } from './warn';
const nodeURL = require('query-string');
class ParseQuery {
get queryName() {
return nodeURL;
}
/**
* 判断当前这个对象是否为深度对象
* @param {Object} obj
*/
isDepthObject(obj) {
const str = JSON.stringify(obj);
return str.match(/}/g).length > 1;
}
/**
* 从URL中提取查询字符串
* @param {String} url
*/
extract(url) {
return nodeURL.extract(url);
}
/**
* 把一个 key=value&key1=value 的字符串转成对象
* @param {string} strQuery key=value&key1=value 类型的字符串
*/
parse(strQuery) {
return nodeURL.parse(strQuery);
}
/**
* 把一个对象转成 key=value&key1=value 类型的字符串
* @param {Object} ObjQuery 符合js标注的对象
* @param {Boolean} intact 是否在转成的字符串前添加?号
*/
stringify(ObjQuery, intact = true) {
const strQuery = nodeURL.stringify(ObjQuery);
if (intact) {
return `?${strQuery}`;
}
return strQuery;
}
/**
* 把一个对象或者 key=value&key1=value 类型的数据加密成 query=encodeURIComponent(value)
* @param {Object|String} query 符合js标注的对象 或者 key=value&key1=value 字符串
* @param {Boolean} intact 是否在转成的字符串前添加?号
*/
encode(query, intact = true) {
let [strQuery, formatQuery] = ['', ''];
if (query == null) {
warn('加密参数没有传递,你知道?', true);
return '';
}
if (query.constructor === String) { // 字符串 尝试 转成 对象
strQuery = JSON.stringify(this.parse(query));
} else if (query.constructor === Object) { // 直接转成字符串对象即可
if (Object.keys(query).length === 0) {
warn('当前参数不满足加密规范!');
return '';
}
strQuery = JSON.stringify(query);
}
if (intact) {
formatQuery = '?';
}
formatQuery += `query=${encodeURIComponent(strQuery)}`;
return formatQuery;
}
/**
* 把一个已经加密好的字符串 query=encodeURIComponent(value) 解密成 对象
* @param {string} strQuery 已经加密好的字符串 query=encodeURIComponent(value)
*/
decode(strQuery) {
if (strQuery == null) {
warn('解密参数没有传递,你知道?', true);
return {};
}
let jsonQuery = strQuery;
if (strQuery.constructor === Object) { // 如果是对象 看能不能满足要求
jsonQuery = strQuery.query;
if (jsonQuery == null) {
warn('当前解密参数不满足编码规则');
return {};
}
jsonQuery = `query=${jsonQuery}`;
}
let decode = {};
// query 长这个样 query=encodeURIComponent(value)
const decodeStr = decodeURIComponent(jsonQuery);
const { query } = this.parse(decodeStr); // 转成 json 获取到正真的json字符串
if (query == null) {
warn('当前解密参数不满足编码规则');
} else {
try {
decode = JSON.parse(query);
} catch (error) {
warn('当前解密参数不满足编码规则');
}
}
return decode;
}
queryGet(query) {
const { encodeURI } = Global.Router.CONFIG; // 获取到路由配置
let [decode, historyObj, strQuery] = [query, query, ''];
switch (encodeURI) {
case true: { // 加密模式
decode = this.decode(query);
strQuery = this.encode(decode);
historyObj = {
query: encodeURIComponent(JSON.stringify(decode)),
};
break;
}
case false: { // 不加密模式
strQuery = this.stringify(query);
break;
}
default: {
err('未知参数模式,请检查 \'encodeURI\'', true);
}
}
return { strQuery, historyObj, decode };
}
/**
* 对需要传递的参数进行加密解密
* @param {Object|String} query get为false 必须为 Object 类型
* @param {String} get 是取值 还是通过api传值
*/
transfer(query = {}) {
const { encodeURI } = Global.Router.CONFIG; // 获取到路由配置
switch (encodeURI) {
case true: {
// 加密模式
return this.encode(query, false);
}
case false: {
// 不加密模式
return this.stringify(query);
}
default: {
err('未知参数模式,请检查 \'encodeURI\' ', true);
}
}
}
}
export default ParseQuery;
import { route, baseConfig, Global } from './config';
import { builtIn } from '../vueRouter/base';
import { err, log, warn } from './warn';
/**
* 当前是不是H5运行环境
*/
export const isH5 = function () {
return typeof window !== 'undefined' && typeof document !== 'undefined';
};
/**
* 判断当前变量是否为Object
* @param {Object} strObj
*/
export const isObject = function (strObj) {
return strObj.toString() === '[object Object]' && strObj.constructor === Object;
};
/**
* 获取当前运行平台
* @param {Boolean} applets 默认false true时所有小程序平台统一返回 APPLETS
*/
export const appPlatform = function (applets = false) {
let platform = '';
// #ifdef APP-PLUS-NVUE
platform = 'APPNVUE';
// #endif
// #ifdef APP-PLUS
platform = 'APP';
// #endif
// #ifdef H5
platform = 'H5';
// #endif
// #ifdef MP-ALIPAY
platform = 'ALIPAY';
// #endif
// #ifdef MP-BAIDU
platform = 'BAIDU';
// #endif
// #ifdef MP-QQ
platform = 'QQ';
// #endif
// #ifdef MP-WEIXIN
platform = 'WEIXIN';
// #endif
// #ifdef MP-TOUTIAO
platform = 'TOUTIAO';
// #endif
if (applets) {
// #ifdef MP
platform = 'APPLETS';
// #endif
}
return platform;
};
/**
* 定义一个空方法 如果最后一个参数为true则打印所有参数
* @param {...any} args
*/
export const noop = function (...args) {
if (args[args.length - 1] === true) {
log(args);
}
};
/**
* 格式化基础配置信息 通过new Router传递过来的参数
*/
export const formatConfig = function (userConfig) {
if (!userConfig.routes || userConfig.routes.constructor !== Array) {
return err(`路由参数 'routes' 必须传递 \r\n\r\n${JSON.stringify(userConfig)}`);
}
if (userConfig.h5 != null && userConfig.h5.constructor !== Object) {
return err(`h5参数传递错误,应该是一个 'Object' 类型 示例:\r\n\r\n${JSON.stringify(baseConfig.h5)}`);
}
const config = Object.create(null);
const baseConfigKeys = Object.keys(baseConfig);
for (let i = 0; i < baseConfigKeys.length; i += 1) {
const key = baseConfigKeys[i];
if (userConfig[key] != null) {
if (userConfig[key].constructor === Object) {
config[key] = {
...baseConfig[key],
...userConfig[key],
};
} else if (key == 'routes') { // 需要加入已知的白名单
config[key] = [...baseConfig[key], ...userConfig[key], ...builtIn];
} else {
config[key] = userConfig[key];
}
} else {
config[key] = baseConfig[key];
}
}
return config;
};
export const filter = function (str) {
str += '';
str = str.replace(/%/g, '%25');
str = str.replace(/\+/g, '%2B');
str = str.replace(/ /g, '%20');
str = str.replace(/\//g, '%2F');
str = str.replace(/\?/g, '%3F');
str = str.replace(/&/g, '%26');
str = str.replace(/=/g, '%3D');
str = str.replace(/#/g, '%23');
return str;
};
/**
* 使用encodeURI:true的情况 需要进行编码后再传递,解码等等 可以传递深度对象并会在路径后面加入一个query=
*
* @param {String} routerName //路径名称
* @param {JSON} query //需要格式化参数
* @param {Boolean} Encode //是获取还是编码后传递
*/
export const parseQueryN = function (routerName, query, Encode) {
if (Encode) {
return {
url: routerName,
query: JSON.parse(decodeURIComponent(query.replace(/^query=/, ''))),
};
}
return {
url: routerName,
query: `query=${encodeURIComponent(JSON.stringify(query))}`,
};
};
/**
* 使用encodeURI:false的情况 直接格式化为普通的queryURl参数形式传递即可 扁平深度对象
*
* @param {String} routerName //路径名称
* @param {JSON} query //需要格式化参数
* @param {Boolean} Encode //是获取还是编码后传递
*/
export const parseQueryD = function (routerName, query, Encode) {
if (Encode) {
const obj = {};
const reg = /([^=&\s]+)[=\s]*([^&\s]*)/g;
while (reg.exec(query)) {
obj[RegExp.$1] = RegExp.$2;
}
return {
url: routerName,
query: obj,
};
}
const encodeArr = [];
const queryKeys = Object.keys(query);
for (let i = 0; i < queryKeys.length; i += 1) {
const attr = queryKeys[i];
let encodeStr = '';
if (query[attr].constructor == Object) {
encodeStr = parseQueryD(routerName, query[attr], Encode).query;
encodeArr.push(encodeStr);
} else {
encodeStr = filter(query[attr]);
encodeArr.push(`${attr}=${encodeStr}`);
}
}
return {
url: routerName,
query: encodeArr.join('&'),
};
};
/**
* @param {String} routerName //路径名称
* @param {JSON} query //需要格式化参数
* @param {Boolean} Encode //是获取还是编码后传递
*/
export const parseQuery = function (routerName, query, Encode = false) {
if (Global.Router.CONFIG.encodeURI) {
return parseQueryN(routerName, query, Encode);
}
return parseQueryD(routerName, query, Encode);
};
export const exactRule = function (cloneRule, routes, ruleKey, getRule = false) {
const params = {};
let i = 0;
// eslint-disable-next-line
while (true) {
const item = routes[i];
if (item == null) {
if (!getRule) {
err(`路由表中未查找到 '${ruleKey}' 为 '${cloneRule[ruleKey]}'`);
}
return {
path: '',
name: '',
};
}
if (item[ruleKey] != null && item[ruleKey] === cloneRule[ruleKey]) {
if (!getRule) {
params.url = item.path;
params.rule = item;
if (isH5()) { // 如果是h5 则使用优先使用自定义路径名称
params.url = item.aliasPath || item.path;
}
return params;
}
return item;
}
i += 1;
}
};
export const resolveRule = function (router, rule, query = {}, ruleKey = 'path') {
const ruleInfo = route(
exactRule({
...rule,
},
router.CONFIG.routes,
ruleKey,
router),
);
return {
...ruleInfo,
query,
};
};
/**
* 把一些不必要的参数进行格式化掉,完成url的美观
* @param {String} URLQuery URL中传递的参数
*/
export const formatURLQuery = function (URLQuery) {
switch (URLQuery.trim()) {
case 'query=%7B%7D':
case '%7B%7D':
case '?query=%7B%7D':
case '?':
case '?[object Object]':
case '?query={}':
URLQuery = '';
break;
default:
warn('url已经很完美啦,不需要格式化!');
break;
}
return URLQuery;
};
/**
* 拷贝对象
* @param {Object} object
*/
export const copyObject = function (object) {
return JSON.parse(JSON.stringify(object));
};
import { Global } from './config';
const isLog = function (type, errText, enforce) {
if (!enforce) {
const dev = Global.Router.CONFIG.debugger;
const isObject = dev.toString() === '[object Object]';
if (dev === false) {
return false;
} if (dev === false) {
return false;
} if (isObject) {
if (dev[type] === false) {
return false;
}
}
}
/* eslint no-console:"off" */
console[type](errText);
};
export const err = function (errInfo, enforce = false) {
isLog('error', errInfo, enforce);
};
export const warn = function (errInfo, enforce = false) {
isLog('warn', errInfo, enforce);
};
export const log = function (errInfo, enforce = false) {
isLog('log', errInfo, enforce);
};
export const warnLock = function (errInfo) {
console.warn(errInfo);
};
import { isH5, formatConfig, appPlatform } from './helpers/util';
import navjump from './helpers/navJump';
import { H5GetPageRoute } from './vueRouter/util';
import { APPGetPageRoute } from './appRouter/util';
import { AppletsPageRoute } from './appletsRouter/util';
import { lifeCycle, Global } from './helpers/config';
import { warn, err } from './helpers/warn';
import { registerRouterHooks, registerHook } from './lifeCycle/hooks';
import { vueMount } from './vueRouter/base';
import appletsMount from './patch/applets-patch';
import appMount from './patch/app-patch';
import initMixins from './helpers/mixins';
import ParseQuery from './helpers/urlQuery';
// #ifdef H5
import H5 from './patch/h5-patch';
// #endif
let H5PATCH = null;
// #ifdef H5
H5PATCH = new H5(isH5());
// #endif
const parseQuery = new ParseQuery();
Global.H5RouterReady = new Promise((resolve) => Global.RouterReadyPromise = resolve);
class Router {
constructor(arg) {
Router.$root = this;
Global.Router = this; // 全局缓存一个对象,不必使用时都传递
Global.$parseQuery = parseQuery;
this.CONFIG = formatConfig(arg);
this.lifeCycle = lifeCycle;
registerRouterHooks.call(this); // 注册全局Router生命钩子
if (appPlatform() === 'H5') {
H5PATCH.setLoadingStatus(this.CONFIG.h5);
}
}
get $Route() {
return this.getPageRoute();
}
/**
* 获取 url 参数帮助类实例
*/
get $parseQuery() {
return Global.$parseQuery;
}
/**
* 获取当前是否处于正在跳转的状态
* H5 状态下始终为false
*/
get $lockStatus() {
return Global.LockStatus;
}
/**
* 动态设置拦截状态
*/
set $lockStatus(status) {
warn('你确定要这么做?你知道后果?', true);
Global.LockStatus = status;
}
/** 动态的导航到一个新 URL 保留浏览历史
* navigateTo
* @param {Object} rule
*/
push(rule) {
navjump.call(this, rule, 'push');
}
/** 动态的导航到一个新 URL 关闭当前页面,跳转到的某个页面。
* redirectTo
* @param {Object} rule
*/
replace(rule) {
navjump.call(this, rule, 'replace');
}
/** 动态的导航到一个新 URL 关闭所有页面,打开到应用内的某个页面
* reLaunch
* @param {Object} rule
*/
replaceAll(rule) {
navjump.call(this, rule, 'replaceAll');
}
/** 动态的导航到一个新 url 关闭所有页面,打开到应用内的某个tab
* @param {Object} rule
*/
pushTab(rule) {
navjump.call(this, rule, 'pushTab');
}
/**
* 返回到指定层级页面上
* @param {Number} backLayer 需要返回的页面层级 默认 1
* @param {Object} delta 暂时无用
* @param {enforce} 是否强制越过跳转加锁检查 默认 false
*/
back(backLayer = 1, delta, enforce = false) {
if (backLayer.constructor != Number) {
return err(
`返回层级参数必须是一个Number类型且必须大于1:${backLayer}`,
);
}
navjump.call(this, {
backLayer, delta, H5PATCH,
}, 'back', true, enforce);
}
/**
* 获取当前页面下的 Route 信息
*
* @param {Object} Vim 当前开发者可以传递一个 vue 组件对象 来获取当前下的 Route 信息
*/
getPageRoute(Vim) {
const pages = getCurrentPages();
switch (appPlatform(true)) {
case 'H5':
return H5GetPageRoute.call(this, pages, Vim);
case 'APP':
return APPGetPageRoute(pages, Vim);
case 'APPLETS':
return AppletsPageRoute(pages, Vim);
default:
break;
}
}
beforeEach(fn) {
return registerHook(this.lifeCycle.beforeHooks, fn);
}
afterEach(fn) {
return registerHook(this.lifeCycle.afterHooks, fn);
}
}
Router.install = function (Vue) {
initMixins(Vue, Router);
Object.defineProperty(Vue.prototype, '$Router', {
get() {
return Router.$root;
},
});
Object.defineProperty(Vue.prototype, '$Route', {
get() {
return Router.$root.getPageRoute(this);
},
});
};
export default Router;
/**
*
* @param {VueComponent } Vim vue实例对象
* @param {dom} el dom节点选择器
*/
export const RouterMount = function (Vim, el) {
switch (appPlatform(true)) {
case 'APP':
appMount(Vim, el);
break;
case 'APPLETS':
appletsMount(Vim, el);
break;
case 'H5':
vueMount.push({ Vim, el });
break;
default:
warn('糟糕!!!还有其他的执行环境???没听说过啊。一脸懵逼???加QQ群问问:769241495');
break;
}
};
import { appPlatform, isH5 } from '../helpers/util';
// #ifdef H5
import H5 from '../patch/h5-patch';
const H5PATCH = new H5(isH5());
// #endif
export const registerHook = function (list, fn) {
list.push(fn);
return () => {
const i = list.indexOf(fn);
if (i > -1) list.splice(i, 1);
};
};
/**
* 注册全局Router生命钩子
*/
export const registerRouterHooks = function () {
registerHook(this.lifeCycle.routerbeforeHooks, function () {
return new Promise(async (resolve) => {
this.CONFIG.routerBeforeEach(); // 触发暴露给开发者的生命钩子
if (appPlatform(true) === 'H5') {
H5PATCH.on('toogle', 'startLodding');
}
return resolve(true);
});
});
registerHook(this.lifeCycle.routerAfterHooks, function (res = {}) {
if (res.H5Intercept !== true) {
this.CONFIG.routerAfterEach(); // 触发暴露给开发者的生命钩子
}
if (appPlatform(true) === 'H5') {
H5PATCH.on('toogle', 'stopLodding');
}
return true;
});
};
export interface ParseOptions {
/**
Decode the keys and values. URI components are decoded with [`decode-uri-component`](https://github.com/SamVerschueren/decode-uri-component).
@default true
*/
readonly decode?: boolean;
/**
@default 'none'
- `bracket`: Parse arrays with bracket representation:
```
import queryString = require('query-string');
queryString.parse('foo[]=1&foo[]=2&foo[]=3', {arrayFormat: 'bracket'});
//=> {foo: ['1', '2', '3']}
```
- `index`: Parse arrays with index representation:
```
import queryString = require('query-string');
queryString.parse('foo[0]=1&foo[1]=2&foo[3]=3', {arrayFormat: 'index'});
//=> {foo: ['1', '2', '3']}
```
- `comma`: Parse arrays with elements separated by comma:
```
import queryString = require('query-string');
queryString.parse('foo=1,2,3', {arrayFormat: 'comma'});
//=> {foo: ['1', '2', '3']}
```
- `separator`: Parse arrays with elements separated by a custom character:
```
import queryString = require('query-string');
queryString.parse('foo=1|2|3', {arrayFormat: 'separator', arrayFormatSeparator: '|'});
//=> {foo: ['1', '2', '3']}
```
- `none`: Parse arrays with elements using duplicate keys:
```
import queryString = require('query-string');
queryString.parse('foo=1&foo=2&foo=3');
//=> {foo: ['1', '2', '3']}
```
*/
readonly arrayFormat?: 'bracket' | 'index' | 'comma' | 'separator' | 'none';
/**
The character used to separate array elements when using `{arrayFormat: 'separator'}`.
@default ,
*/
readonly arrayFormatSeparator?: string;
/**
Supports both `Function` as a custom sorting function or `false` to disable sorting.
If omitted, keys are sorted using `Array#sort`, which means, converting them to strings and comparing strings in Unicode code point order.
@default true
@example
```
import queryString = require('query-string');
const order = ['c', 'a', 'b'];
queryString.parse('?a=one&b=two&c=three', {
sort: (itemLeft, itemRight) => order.indexOf(itemLeft) - order.indexOf(itemRight)
});
//=> {c: 'three', a: 'one', b: 'two'}
```
@example
```
import queryString = require('query-string');
queryString.parse('?a=one&c=three&b=two', {sort: false});
//=> {a: 'one', c: 'three', b: 'two'}
```
*/
readonly sort?: ((itemLeft: string, itemRight: string) => number) | false;
/**
Parse the value as a number type instead of string type if it's a number.
@default false
@example
```
import queryString = require('query-string');
queryString.parse('foo=1', {parseNumbers: true});
//=> {foo: 1}
```
*/
readonly parseNumbers?: boolean;
/**
Parse the value as a boolean type instead of string type if it's a boolean.
@default false
@example
```
import queryString = require('query-string');
queryString.parse('foo=true', {parseBooleans: true});
//=> {foo: true}
```
*/
readonly parseBooleans?: boolean;
/**
Parse the fragment identifier from the URL and add it to result object.
@default false
@example
```
import queryString = require('query-string');
queryString.parseUrl('https://foo.bar?foo=bar#xyz', {parseFragmentIdentifier: true});
//=> {url: 'https://foo.bar', query: {foo: 'bar'}, fragmentIdentifier: 'xyz'}
```
*/
readonly parseFragmentIdentifier?: boolean;
}
export interface ParsedQuery<T = string> {
[key: string]: T | T[] | null;
}
/**
Parse a query string into an object. Leading `?` or `#` are ignored, so you can pass `location.search` or `location.hash` directly.
The returned object is created with [`Object.create(null)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create) and thus does not have a `prototype`.
@param query - The query string to parse.
*/
export function parse(query: string, options: {parseBooleans: true, parseNumbers: true} & ParseOptions): ParsedQuery<string | boolean | number>;
export function parse(query: string, options: {parseBooleans: true} & ParseOptions): ParsedQuery<string | boolean>;
export function parse(query: string, options: {parseNumbers: true} & ParseOptions): ParsedQuery<string | number>;
export function parse(query: string, options?: ParseOptions): ParsedQuery;
export interface ParsedUrl {
readonly url: string;
readonly query: ParsedQuery;
/**
The fragment identifier of the URL.
Present when the `parseFragmentIdentifier` option is `true`.
*/
readonly fragmentIdentifier?: string;
}
/**
Extract the URL and the query string as an object.
If the `parseFragmentIdentifier` option is `true`, the object will also contain a `fragmentIdentifier` property.
@param url - The URL to parse.
@example
```
import queryString = require('query-string');
queryString.parseUrl('https://foo.bar?foo=bar');
//=> {url: 'https://foo.bar', query: {foo: 'bar'}}
queryString.parseUrl('https://foo.bar?foo=bar#xyz', {parseFragmentIdentifier: true});
//=> {url: 'https://foo.bar', query: {foo: 'bar'}, fragmentIdentifier: 'xyz'}
```
*/
export function parseUrl(url: string, options?: ParseOptions): ParsedUrl;
export interface StringifyOptions {
/**
Strictly encode URI components with [`strict-uri-encode`](https://github.com/kevva/strict-uri-encode). It uses [`encodeURIComponent`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) if set to `false`. You probably [don't care](https://github.com/sindresorhus/query-string/issues/42) about this option.
@default true
*/
readonly strict?: boolean;
/**
[URL encode](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) the keys and values.
@default true
*/
readonly encode?: boolean;
/**
@default 'none'
- `bracket`: Serialize arrays using bracket representation:
```
import queryString = require('query-string');
queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'bracket'});
//=> 'foo[]=1&foo[]=2&foo[]=3'
```
- `index`: Serialize arrays using index representation:
```
import queryString = require('query-string');
queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'index'});
//=> 'foo[0]=1&foo[1]=2&foo[2]=3'
```
- `comma`: Serialize arrays by separating elements with comma:
```
import queryString = require('query-string');
queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'comma'});
//=> 'foo=1,2,3'
```
- `separator`: Serialize arrays by separating elements with character:
```
import queryString = require('query-string');
queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'separator', arrayFormatSeparator: '|'});
//=> 'foo=1|2|3'
```
- `none`: Serialize arrays by using duplicate keys:
```
import queryString = require('query-string');
queryString.stringify({foo: [1, 2, 3]});
//=> 'foo=1&foo=2&foo=3'
```
*/
readonly arrayFormat?: 'bracket' | 'index' | 'comma' | 'separator' | 'none';
/**
The character used to separate array elements when using `{arrayFormat: 'separator'}`.
@default ,
*/
readonly arrayFormatSeparator?: string;
/**
Supports both `Function` as a custom sorting function or `false` to disable sorting.
If omitted, keys are sorted using `Array#sort`, which means, converting them to strings and comparing strings in Unicode code point order.
@default true
@example
```
import queryString = require('query-string');
const order = ['c', 'a', 'b'];
queryString.stringify({a: 1, b: 2, c: 3}, {
sort: (itemLeft, itemRight) => order.indexOf(itemLeft) - order.indexOf(itemRight)
});
//=> 'c=3&a=1&b=2'
```
@example
```
import queryString = require('query-string');
queryString.stringify({b: 1, c: 2, a: 3}, {sort: false});
//=> 'b=1&c=2&a=3'
```
*/
readonly sort?: ((itemLeft: string, itemRight: string) => number) | false;
/**
Skip keys with `null` as the value.
Note that keys with `undefined` as the value are always skipped.
@default false
@example
```
import queryString = require('query-string');
queryString.stringify({a: 1, b: undefined, c: null, d: 4}, {
skipNull: true
});
//=> 'a=1&d=4'
queryString.stringify({a: undefined, b: null}, {
skipNull: true
});
//=> ''
```
*/
readonly skipNull?: boolean;
/**
Skip keys with an empty string as the value.
@default false
@example
```
import queryString = require('query-string');
queryString.stringify({a: 1, b: '', c: '', d: 4}, {
skipEmptyString: true
});
//=> 'a=1&d=4'
```
@example
```
import queryString = require('query-string');
queryString.stringify({a: '', b: ''}, {
skipEmptyString: true
});
//=> ''
```
*/
readonly skipEmptyString?: boolean;
}
export type Stringifiable = string | boolean | number | null | undefined;
export type StringifiableRecord = Record<
string,
Stringifiable | readonly Stringifiable[]
>;
/**
Stringify an object into a query string and sort the keys.
*/
export function stringify(
// TODO: Use the below instead when the following TS issues are fixed:
// - https://github.com/microsoft/TypeScript/issues/15300
// - https://github.com/microsoft/TypeScript/issues/42021
// Context: https://github.com/sindresorhus/query-string/issues/298
// object: StringifiableRecord,
object: Record<string, any>,
options?: StringifyOptions
): string;
/**
Extract a query string from a URL that can be passed into `.parse()`.
Note: This behaviour can be changed with the `skipNull` option.
*/
export function extract(url: string): string;
export interface UrlObject {
readonly url: string;
/**
Overrides queries in the `url` property.
*/
readonly query?: StringifiableRecord;
/**
Overrides the fragment identifier in the `url` property.
*/
readonly fragmentIdentifier?: string;
}
/**
Stringify an object into a URL with a query string and sorting the keys. The inverse of [`.parseUrl()`](https://github.com/sindresorhus/query-string#parseurlstring-options)
Query items in the `query` property overrides queries in the `url` property.
The `fragmentIdentifier` property overrides the fragment identifier in the `url` property.
@example
```
queryString.stringifyUrl({url: 'https://foo.bar', query: {foo: 'bar'}});
//=> 'https://foo.bar?foo=bar'
queryString.stringifyUrl({url: 'https://foo.bar?foo=baz', query: {foo: 'bar'}});
//=> 'https://foo.bar?foo=bar'
queryString.stringifyUrl({
url: 'https://foo.bar',
query: {
top: 'foo'
},
fragmentIdentifier: 'bar'
});
//=> 'https://foo.bar?top=foo#bar'
```
*/
export function stringifyUrl(
object: UrlObject,
options?: StringifyOptions
): string;
/**
Pick query parameters from a URL.
@param url - The URL containing the query parameters to pick.
@param keys - The names of the query parameters to keep. All other query parameters will be removed from the URL.
@param filter - A filter predicate that will be provided the name of each query parameter and its value. The `parseNumbers` and `parseBooleans` options also affect `value`.
@returns The URL with the picked query parameters.
@example
```
queryString.pick('https://foo.bar?foo=1&bar=2#hello', ['foo']);
//=> 'https://foo.bar?foo=1#hello'
queryString.pick('https://foo.bar?foo=1&bar=2#hello', (name, value) => value === 2, {parseNumbers: true});
//=> 'https://foo.bar?bar=2#hello'
```
*/
export function pick(
url: string,
keys: readonly string[],
options?: ParseOptions & StringifyOptions
): string
export function pick(
url: string,
filter: (key: string, value: string | boolean | number) => boolean,
options?: {parseBooleans: true, parseNumbers: true} & ParseOptions & StringifyOptions
): string
export function pick(
url: string,
filter: (key: string, value: string | boolean) => boolean,
options?: {parseBooleans: true} & ParseOptions & StringifyOptions
): string
export function pick(
url: string,
filter: (key: string, value: string | number) => boolean,
options?: {parseNumbers: true} & ParseOptions & StringifyOptions
): string
/**
Exclude query parameters from a URL. Like `.pick()` but reversed.
@param url - The URL containing the query parameters to exclude.
@param keys - The names of the query parameters to remove. All other query parameters will remain in the URL.
@param filter - A filter predicate that will be provided the name of each query parameter and its value. The `parseNumbers` and `parseBooleans` options also affect `value`.
@returns The URL without the excluded the query parameters.
@example
```
queryString.exclude('https://foo.bar?foo=1&bar=2#hello', ['foo']);
//=> 'https://foo.bar?bar=2#hello'
queryString.exclude('https://foo.bar?foo=1&bar=2#hello', (name, value) => value === 2, {parseNumbers: true});
//=> 'https://foo.bar?foo=1#hello'
```
*/
export function exclude(
url: string,
keys: readonly string[],
options?: ParseOptions & StringifyOptions
): string
export function exclude(
url: string,
filter: (key: string, value: string | boolean | number) => boolean,
options?: {parseBooleans: true, parseNumbers: true} & ParseOptions & StringifyOptions
): string
export function exclude(
url: string,
filter: (key: string, value: string | boolean) => boolean,
options?: {parseBooleans: true} & ParseOptions & StringifyOptions
): string
export function exclude(
url: string,
filter: (key: string, value: string | number) => boolean,
options?: {parseNumbers: true} & ParseOptions & StringifyOptions
): string
'use strict';
const strictUriEncode = require('strict-uri-encode');
const decodeComponent = require('decode-uri-component');
const splitOnFirst = require('split-on-first');
const filterObject = require('filter-obj');
const isNullOrUndefined = value => value === null || value === undefined;
function encoderForArrayFormat(options) {
switch (options.arrayFormat) {
case 'index':
return key => (result, value) => {
const index = result.length;
if (
value === undefined ||
(options.skipNull && value === null) ||
(options.skipEmptyString && value === '')
) {
return result;
}
if (value === null) {
return [...result, [encode(key, options), '[', index, ']'].join('')];
}
return [
...result,
[encode(key, options), '[', encode(index, options), ']=', encode(value, options)].join('')
];
};
case 'bracket':
return key => (result, value) => {
if (
value === undefined ||
(options.skipNull && value === null) ||
(options.skipEmptyString && value === '')
) {
return result;
}
if (value === null) {
return [...result, [encode(key, options), '[]'].join('')];
}
return [...result, [encode(key, options), '[]=', encode(value, options)].join('')];
};
case 'comma':
case 'separator':
return key => (result, value) => {
if (value === null || value === undefined || value.length === 0) {
return result;
}
if (result.length === 0) {
return [[encode(key, options), '=', encode(value, options)].join('')];
}
return [[result, encode(value, options)].join(options.arrayFormatSeparator)];
};
default:
return key => (result, value) => {
if (
value === undefined ||
(options.skipNull && value === null) ||
(options.skipEmptyString && value === '')
) {
return result;
}
if (value === null) {
return [...result, encode(key, options)];
}
return [...result, [encode(key, options), '=', encode(value, options)].join('')];
};
}
}
function parserForArrayFormat(options) {
let result;
switch (options.arrayFormat) {
case 'index':
return (key, value, accumulator) => {
result = /\[(\d*)\]$/.exec(key);
key = key.replace(/\[\d*\]$/, '');
if (!result) {
accumulator[key] = value;
return;
}
if (accumulator[key] === undefined) {
accumulator[key] = {};
}
accumulator[key][result[1]] = value;
};
case 'bracket':
return (key, value, accumulator) => {
result = /(\[\])$/.exec(key);
key = key.replace(/\[\]$/, '');
if (!result) {
accumulator[key] = value;
return;
}
if (accumulator[key] === undefined) {
accumulator[key] = [value];
return;
}
accumulator[key] = [].concat(accumulator[key], value);
};
case 'comma':
case 'separator':
return (key, value, accumulator) => {
const isArray = typeof value === 'string' && value.includes(options.arrayFormatSeparator);
const isEncodedArray = (typeof value === 'string' && !isArray && decode(value, options).includes(options.arrayFormatSeparator));
value = isEncodedArray ? decode(value, options) : value;
const newValue = isArray || isEncodedArray ? value.split(options.arrayFormatSeparator).map(item => decode(item, options)) : value === null ? value : decode(value, options);
accumulator[key] = newValue;
};
default:
return (key, value, accumulator) => {
if (accumulator[key] === undefined) {
accumulator[key] = value;
return;
}
accumulator[key] = [].concat(accumulator[key], value);
};
}
}
function validateArrayFormatSeparator(value) {
if (typeof value !== 'string' || value.length !== 1) {
throw new TypeError('arrayFormatSeparator must be single character string');
}
}
function encode(value, options) {
if (options.encode) {
return options.strict ? strictUriEncode(value) : encodeURIComponent(value);
}
return value;
}
function decode(value, options) {
if (options.decode) {
return decodeComponent(value);
}
return value;
}
function keysSorter(input) {
if (Array.isArray(input)) {
return input.sort();
}
if (typeof input === 'object') {
return keysSorter(Object.keys(input))
.sort((a, b) => Number(a) - Number(b))
.map(key => input[key]);
}
return input;
}
function removeHash(input) {
const hashStart = input.indexOf('#');
if (hashStart !== -1) {
input = input.slice(0, hashStart);
}
return input;
}
function getHash(url) {
let hash = '';
const hashStart = url.indexOf('#');
if (hashStart !== -1) {
hash = url.slice(hashStart);
}
return hash;
}
function extract(input) {
input = removeHash(input);
const queryStart = input.indexOf('?');
if (queryStart === -1) {
return '';
}
return input.slice(queryStart + 1);
}
function parseValue(value, options) {
if (options.parseNumbers && !Number.isNaN(Number(value)) && (typeof value === 'string' && value.trim() !== '')) {
value = Number(value);
} else if (options.parseBooleans && value !== null && (value.toLowerCase() === 'true' || value.toLowerCase() === 'false')) {
value = value.toLowerCase() === 'true';
}
return value;
}
function parse(query, options) {
options = Object.assign({
decode: true,
sort: true,
arrayFormat: 'none',
arrayFormatSeparator: ',',
parseNumbers: false,
parseBooleans: false
}, options);
validateArrayFormatSeparator(options.arrayFormatSeparator);
const formatter = parserForArrayFormat(options);
// Create an object with no prototype
const ret = Object.create(null);
if (typeof query !== 'string') {
return ret;
}
query = query.trim().replace(/^[?#&]/, '');
if (!query) {
return ret;
}
for (const param of query.split('&')) {
if (param === '') {
continue;
}
let [key, value] = splitOnFirst(options.decode ? param.replace(/\+/g, ' ') : param, '=');
// Missing `=` should be `null`:
// http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters
value = value === undefined ? null : ['comma', 'separator'].includes(options.arrayFormat) ? value : decode(value, options);
formatter(decode(key, options), value, ret);
}
for (const key of Object.keys(ret)) {
const value = ret[key];
if (typeof value === 'object' && value !== null) {
for (const k of Object.keys(value)) {
value[k] = parseValue(value[k], options);
}
} else {
ret[key] = parseValue(value, options);
}
}
if (options.sort === false) {
return ret;
}
return (options.sort === true ? Object.keys(ret).sort() : Object.keys(ret).sort(options.sort)).reduce((result, key) => {
const value = ret[key];
if (Boolean(value) && typeof value === 'object' && !Array.isArray(value)) {
// Sort object keys, not values
result[key] = keysSorter(value);
} else {
result[key] = value;
}
return result;
}, Object.create(null));
}
exports.extract = extract;
exports.parse = parse;
exports.stringify = (object, options) => {
if (!object) {
return '';
}
options = Object.assign({
encode: true,
strict: true,
arrayFormat: 'none',
arrayFormatSeparator: ','
}, options);
validateArrayFormatSeparator(options.arrayFormatSeparator);
const shouldFilter = key => (
(options.skipNull && isNullOrUndefined(object[key])) ||
(options.skipEmptyString && object[key] === '')
);
const formatter = encoderForArrayFormat(options);
const objectCopy = {};
for (const key of Object.keys(object)) {
if (!shouldFilter(key)) {
objectCopy[key] = object[key];
}
}
const keys = Object.keys(objectCopy);
if (options.sort !== false) {
keys.sort(options.sort);
}
return keys.map(key => {
const value = object[key];
if (value === undefined) {
return '';
}
if (value === null) {
return encode(key, options);
}
if (Array.isArray(value)) {
return value
.reduce(formatter(key), [])
.join('&');
}
return encode(key, options) + '=' + encode(value, options);
}).filter(x => x.length > 0).join('&');
};
exports.parseUrl = (url, options) => {
options = Object.assign({
decode: true
}, options);
const [url_, hash] = splitOnFirst(url, '#');
return Object.assign(
{
url: url_.split('?')[0] || '',
query: parse(extract(url), options)
},
options && options.parseFragmentIdentifier && hash ? {fragmentIdentifier: decode(hash, options)} : {}
);
};
exports.stringifyUrl = (object, options) => {
options = Object.assign({
encode: true,
strict: true
}, options);
const url = removeHash(object.url).split('?')[0] || '';
const queryFromUrl = exports.extract(object.url);
const parsedQueryFromUrl = exports.parse(queryFromUrl, {sort: false});
const query = Object.assign(parsedQueryFromUrl, object.query);
let queryString = exports.stringify(query, options);
if (queryString) {
queryString = `?${queryString}`;
}
let hash = getHash(object.url);
if (object.fragmentIdentifier) {
hash = `#${encode(object.fragmentIdentifier, options)}`;
}
return `${url}${queryString}${hash}`;
};
exports.pick = (input, filter, options) => {
options = Object.assign({
parseFragmentIdentifier: true
}, options);
const {url, query, fragmentIdentifier} = exports.parseUrl(input, options);
return exports.stringifyUrl({
url,
query: filterObject(query, filter),
fragmentIdentifier
}, options);
};
exports.exclude = (input, filter, options) => {
const exclusionFilter = Array.isArray(filter) ? key => !filter.includes(key) : (key, value) => !filter(key, value);
return exports.pick(input, exclusionFilter, options);
};
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (http://sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
{
"_from": "query-string@6.14.1",
"_id": "query-string@6.14.1",
"_inBundle": false,
"_integrity": "sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==",
"_location": "/uni-simple-router/query-string",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "query-string@6.14.1",
"name": "query-string",
"escapedName": "query-string",
"rawSpec": "6.14.1",
"saveSpec": null,
"fetchSpec": "6.14.1"
},
"_requiredBy": [
"/uni-simple-router"
],
"_resolved": "https://repo.huaweicloud.com/repository/npm/query-string/-/query-string-6.14.1.tgz",
"_shasum": "7ac2dca46da7f309449ba0f86b1fd28255b0c86a",
"_spec": "query-string@6.14.1",
"_where": "E:\\H5_2.0\\node_modules\\uni-simple-router",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "https://sindresorhus.com"
},
"bugs": {
"url": "https://github.com/sindresorhus/query-string/issues"
},
"bundleDependencies": false,
"dependencies": {
"decode-uri-component": "^0.2.0",
"filter-obj": "^1.1.0",
"split-on-first": "^1.0.0",
"strict-uri-encode": "^2.0.0"
},
"deprecated": false,
"description": "Parse and stringify URL query strings",
"devDependencies": {
"ava": "^1.4.1",
"benchmark": "^2.1.4",
"deep-equal": "^1.0.1",
"fast-check": "^1.5.0",
"tsd": "^0.7.3",
"xo": "^0.24.0"
},
"engines": {
"node": ">=6"
},
"files": [
"index.js",
"index.d.ts"
],
"funding": "https://github.com/sponsors/sindresorhus",
"homepage": "https://github.com/sindresorhus/query-string#readme",
"keywords": [
"browser",
"querystring",
"query",
"string",
"qs",
"param",
"parameter",
"url",
"parse",
"stringify",
"encode",
"decode",
"searchparams",
"filter"
],
"license": "MIT",
"name": "query-string",
"repository": {
"type": "git",
"url": "git+https://github.com/sindresorhus/query-string.git"
},
"scripts": {
"benchmark": "node benchmark.js",
"test": "xo && ava && tsd"
},
"version": "6.14.1"
}
# query-string
> Parse and stringify URL [query strings](https://en.wikipedia.org/wiki/Query_string)
<br>
---
<div align="center">
<p>
<p>
<sup>
<a href="https://github.com/sponsors/sindresorhus">My open source work is supported by the community</a>
</sup>
</p>
<sup>Special thanks to:</sup>
<br>
<br>
<a href="https://standardresume.co/tech">
<img src="https://sindresorhus.com/assets/thanks/standard-resume-logo.svg" width="200"/>
</a>
</p>
</div>
---
<br>
## Install
```
$ npm install query-string
```
This module targets Node.js 6 or later and the latest version of Chrome, Firefox, and Safari. If you want support for older browsers, or, if your project is using create-react-app v1, use version 5: `npm install query-string@5`.
## Usage
```js
const queryString = require('query-string');
console.log(location.search);
//=> '?foo=bar'
const parsed = queryString.parse(location.search);
console.log(parsed);
//=> {foo: 'bar'}
console.log(location.hash);
//=> '#token=bada55cafe'
const parsedHash = queryString.parse(location.hash);
console.log(parsedHash);
//=> {token: 'bada55cafe'}
parsed.foo = 'unicorn';
parsed.ilike = 'pizza';
const stringified = queryString.stringify(parsed);
//=> 'foo=unicorn&ilike=pizza'
location.search = stringified;
// note that `location.search` automatically prepends a question mark
console.log(location.search);
//=> '?foo=unicorn&ilike=pizza'
```
## API
### .parse(string, options?)
Parse a query string into an object. Leading `?` or `#` are ignored, so you can pass `location.search` or `location.hash` directly.
The returned object is created with [`Object.create(null)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create) and thus does not have a `prototype`.
#### options
Type: `object`
##### decode
Type: `boolean`\
Default: `true`
Decode the keys and values. URL components are decoded with [`decode-uri-component`](https://github.com/SamVerschueren/decode-uri-component).
##### arrayFormat
Type: `string`\
Default: `'none'`
- `'bracket'`: Parse arrays with bracket representation:
```js
const queryString = require('query-string');
queryString.parse('foo[]=1&foo[]=2&foo[]=3', {arrayFormat: 'bracket'});
//=> {foo: ['1', '2', '3']}
```
- `'index'`: Parse arrays with index representation:
```js
const queryString = require('query-string');
queryString.parse('foo[0]=1&foo[1]=2&foo[3]=3', {arrayFormat: 'index'});
//=> {foo: ['1', '2', '3']}
```
- `'comma'`: Parse arrays with elements separated by comma:
```js
const queryString = require('query-string');
queryString.parse('foo=1,2,3', {arrayFormat: 'comma'});
//=> {foo: ['1', '2', '3']}
```
- `'separator'`: Parse arrays with elements separated by a custom character:
```js
const queryString = require('query-string');
queryString.parse('foo=1|2|3', {arrayFormat: 'separator', arrayFormatSeparator: '|'});
//=> {foo: ['1', '2', '3']}
```
- `'none'`: Parse arrays with elements using duplicate keys:
```js
const queryString = require('query-string');
queryString.parse('foo=1&foo=2&foo=3');
//=> {foo: ['1', '2', '3']}
```
##### arrayFormatSeparator
Type: `string`\
Default: `','`
The character used to separate array elements when using `{arrayFormat: 'separator'}`.
##### sort
Type: `Function | boolean`\
Default: `true`
Supports both `Function` as a custom sorting function or `false` to disable sorting.
##### parseNumbers
Type: `boolean`\
Default: `false`
```js
const queryString = require('query-string');
queryString.parse('foo=1', {parseNumbers: true});
//=> {foo: 1}
```
Parse the value as a number type instead of string type if it's a number.
##### parseBooleans
Type: `boolean`\
Default: `false`
```js
const queryString = require('query-string');
queryString.parse('foo=true', {parseBooleans: true});
//=> {foo: true}
```
Parse the value as a boolean type instead of string type if it's a boolean.
### .stringify(object, options?)
Stringify an object into a query string and sorting the keys.
#### options
Type: `object`
##### strict
Type: `boolean`\
Default: `true`
Strictly encode URI components with [strict-uri-encode](https://github.com/kevva/strict-uri-encode). It uses [encodeURIComponent](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) if set to false. You probably [don't care](https://github.com/sindresorhus/query-string/issues/42) about this option.
##### encode
Type: `boolean`\
Default: `true`
[URL encode](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) the keys and values.
##### arrayFormat
Type: `string`\
Default: `'none'`
- `'bracket'`: Serialize arrays using bracket representation:
```js
const queryString = require('query-string');
queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'bracket'});
//=> 'foo[]=1&foo[]=2&foo[]=3'
```
- `'index'`: Serialize arrays using index representation:
```js
const queryString = require('query-string');
queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'index'});
//=> 'foo[0]=1&foo[1]=2&foo[2]=3'
```
- `'comma'`: Serialize arrays by separating elements with comma:
```js
const queryString = require('query-string');
queryString.stringify({foo: [1, 2, 3]}, {arrayFormat: 'comma'});
//=> 'foo=1,2,3'
```
- `'none'`: Serialize arrays by using duplicate keys:
```js
const queryString = require('query-string');
queryString.stringify({foo: [1, 2, 3]});
//=> 'foo=1&foo=2&foo=3'
```
##### arrayFormatSeparator
Type: `string`\
Default: `','`
The character used to separate array elements when using `{arrayFormat: 'separator'}`.
##### sort
Type: `Function | boolean`
Supports both `Function` as a custom sorting function or `false` to disable sorting.
```js
const queryString = require('query-string');
const order = ['c', 'a', 'b'];
queryString.stringify({a: 1, b: 2, c: 3}, {
sort: (a, b) => order.indexOf(a) - order.indexOf(b)
});
//=> 'c=3&a=1&b=2'
```
```js
const queryString = require('query-string');
queryString.stringify({b: 1, c: 2, a: 3}, {sort: false});
//=> 'b=1&c=2&a=3'
```
If omitted, keys are sorted using `Array#sort()`, which means, converting them to strings and comparing strings in Unicode code point order.
##### skipNull
Skip keys with `null` as the value.
Note that keys with `undefined` as the value are always skipped.
Type: `boolean`\
Default: `false`
```js
const queryString = require('query-string');
queryString.stringify({a: 1, b: undefined, c: null, d: 4}, {
skipNull: true
});
//=> 'a=1&d=4'
```
```js
const queryString = require('query-string');
queryString.stringify({a: undefined, b: null}, {
skipNull: true
});
//=> ''
```
##### skipEmptyString
Skip keys with an empty string as the value.
Type: `boolean`\
Default: `false`
```js
const queryString = require('query-string');
queryString.stringify({a: 1, b: '', c: '', d: 4}, {
skipEmptyString: true
});
//=> 'a=1&d=4'
```
```js
const queryString = require('query-string');
queryString.stringify({a: '', b: ''}, {
skipEmptyString: true
});
//=> ''
```
### .extract(string)
Extract a query string from a URL that can be passed into `.parse()`.
Note: This behaviour can be changed with the `skipNull` option.
### .parseUrl(string, options?)
Extract the URL and the query string as an object.
Returns an object with a `url` and `query` property.
If the `parseFragmentIdentifier` option is `true`, the object will also contain a `fragmentIdentifier` property.
```js
const queryString = require('query-string');
queryString.parseUrl('https://foo.bar?foo=bar');
//=> {url: 'https://foo.bar', query: {foo: 'bar'}}
queryString.parseUrl('https://foo.bar?foo=bar#xyz', {parseFragmentIdentifier: true});
//=> {url: 'https://foo.bar', query: {foo: 'bar'}, fragmentIdentifier: 'xyz'}
```
#### options
Type: `object`
The options are the same as for `.parse()`.
Extra options are as below.
##### parseFragmentIdentifier
Parse the fragment identifier from the URL.
Type: `boolean`\
Default: `false`
```js
const queryString = require('query-string');
queryString.parseUrl('https://foo.bar?foo=bar#xyz', {parseFragmentIdentifier: true});
//=> {url: 'https://foo.bar', query: {foo: 'bar'}, fragmentIdentifier: 'xyz'}
```
### .stringifyUrl(object, options?)
Stringify an object into a URL with a query string and sorting the keys. The inverse of [`.parseUrl()`](https://github.com/sindresorhus/query-string#parseurlstring-options)
The `options` are the same as for `.stringify()`.
Returns a string with the URL and a query string.
Query items in the `query` property overrides queries in the `url` property.
The `fragmentIdentifier` property overrides the fragment identifier in the `url` property.
```js
queryString.stringifyUrl({url: 'https://foo.bar', query: {foo: 'bar'}});
//=> 'https://foo.bar?foo=bar'
queryString.stringifyUrl({url: 'https://foo.bar?foo=baz', query: {foo: 'bar'}});
//=> 'https://foo.bar?foo=bar'
queryString.stringifyUrl({
url: 'https://foo.bar',
query: {
top: 'foo'
},
fragmentIdentifier: 'bar'
});
//=> 'https://foo.bar?top=foo#bar'
```
#### object
Type: `object`
##### url
Type: `string`
The URL to stringify.
##### query
Type: `object`
Query items to add to the URL.
### .pick(url, keys, options?)
### .pick(url, filter, options?)
Pick query parameters from a URL.
Returns a string with the new URL.
```js
const queryString = require('query-string');
queryString.pick('https://foo.bar?foo=1&bar=2#hello', ['foo']);
//=> 'https://foo.bar?foo=1#hello'
queryString.pick('https://foo.bar?foo=1&bar=2#hello', (name, value) => value === 2, {parseNumbers: true});
//=> 'https://foo.bar?bar=2#hello'
```
### .exclude(url, keys, options?)
### .exclude(url, filter, options?)
Exclude query parameters from a URL.
Returns a string with the new URL.
```js
const queryString = require('query-string');
queryString.exclude('https://foo.bar?foo=1&bar=2#hello', ['foo']);
//=> 'https://foo.bar?bar=2#hello'
queryString.exclude('https://foo.bar?foo=1&bar=2#hello', (name, value) => value === 2, {parseNumbers: true});
//=> 'https://foo.bar?foo=1#hello'
```
#### url
Type: `string`
The URL containing the query parameters to filter.
#### keys
Type: `string[]`
The names of the query parameters to filter based on the function used.
#### filter
Type: `(key, value) => boolean`
A filter predicate that will be provided the name of each query parameter and its value. The `parseNumbers` and `parseBooleans` options also affect `value`.
#### options
Type: `object`
[Parse options](#options) and [stringify options](#options-1).
## Nesting
This module intentionally doesn't support nesting as it's not spec'd and varies between implementations, which causes a lot of [edge cases](https://github.com/visionmedia/node-querystring/issues).
You're much better off just converting the object to a JSON string:
```js
const queryString = require('query-string');
queryString.stringify({
foo: 'bar',
nested: JSON.stringify({
unicorn: 'cake'
})
});
//=> 'foo=bar&nested=%7B%22unicorn%22%3A%22cake%22%7D'
```
However, there is support for multiple instances of the same key:
```js
const queryString = require('query-string');
queryString.parse('likes=cake&name=bob&likes=icecream');
//=> {likes: ['cake', 'icecream'], name: 'bob'}
queryString.stringify({color: ['taupe', 'chartreuse'], id: '515'});
//=> 'color=taupe&color=chartreuse&id=515'
```
## Falsy values
Sometimes you want to unset a key, or maybe just make it present without assigning a value to it. Here is how falsy values are stringified:
```js
const queryString = require('query-string');
queryString.stringify({foo: false});
//=> 'foo=false'
queryString.stringify({foo: null});
//=> 'foo'
queryString.stringify({foo: undefined});
//=> ''
```
## query-string for enterprise
Available as part of the Tidelift Subscription.
The maintainers of query-string and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-query-string?utm_source=npm-query-string&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
{
"name": "uni-simple-router",
"version": "1.5.5",
"description": "> 一个更为简洁的[Vue-router](https://router.vuejs.org/zh/),专为 [uni-app](https://uniapp.dcloud.io/) 量身打造",
"main": "index.js",
"directories": {
"example": "examples"
},
"scripts": {
"postinstall": "node -e \"console.log('\\x1B[32m \\x1B[1m','\\n 欢迎下载 uni-simple-router ↓↓↓↓ \\n\\n 注意事项:\\n 1: 编译为app端如果开启V3,请关闭fast启动模式 \\n 2: 多看文档,特别是‘快速上手’栏目。文档地址:http://hhyang.cn/ \\n 3: 不要再问路由表是否可以不配置两遍。问就是文档有!\\n 4: 有啥问题解决不了的加群:769241495 \\n 5: 任何时候你都可以在github上提出你的想法及问题,相信我很快会得到回应 \\n\\n showTime:\\n 1:开源不易,需要鼓励。去给 uni-simple-router 项目 点个 star 吧 \\n')\""
},
"dependencies": {
"query-string": "^6.12.1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/SilurianYang/uni-simple-router.git"
},
"keywords": [
"router",
"uni-app-router",
"interceptor",
"uni-app",
"uniapp"
],
"author": "hhyang",
"license": "MIT",
"bugs": {
"url": "https://github.com/SilurianYang/uni-simple-router/issues"
},
"homepage": "https://github.com/SilurianYang/uni-simple-router#readme",
"types": "./types/index.d.ts",
"__npminstall_done": true,
"_from": "uni-simple-router@1.5.5",
"_resolved": "https://registry.npmmirror.com/uni-simple-router/-/uni-simple-router-1.5.5.tgz"
}
\ No newline at end of file
/**
* 截止 1.3.5 版本 不做任何操作
* @param {element} el dom节点
*/
const appMount = function (Vim) {
Vim.$mount();
};
export default appMount;
/**
* 截止 1.3.5 版本 不做任何操作
* @param {element} el dom节点
*/
const appletsMount = function (Vim) {
Vim.$mount();
};
export default appletsMount;
// #ifdef H5
import { DOM } from '../component/h5-dom';
import init from '../vueRouter/init';
// #endif
import { warn } from '../helpers/warn';
class Patch {
constructor(H5) {
this.H5 = H5;
this.isLoading = true;
this.loadingCount = 0; // 在APP.vue中进行跳转时,DOMContentLoaded过慢。使用索引来判断
}
on(fun, args, callback) {
if (this.H5) {
return this[fun](args);
}
if (callback) {
callback();
}
}
/**
*把vueRouter的生命周期代理过来
* @param {Object} Router
* @param {Object} vueRouter
* @param {VueComponent} vueVim
*/
// eslint-disable-next-line
registerHook(Router, vueRouter, vueVim) {
init(Router, vueRouter, vueVim);
}
/**
* H5 专属 history.back API
* @param {Number} backLayer 需要返回的层级必须是正整数
* 2020年1月14日14:39:38 修复 https://github.com/SilurianYang/uni-simple-router/issues/73
*/
// eslint-disable-next-line
historyBack({ backLayer, delta = { from: 'navigateBack' } } = {}) {
const pages = getCurrentPages();
const page = pages[pages.length - 1];
const { onBackPress } = page.$options;
if (onBackPress != null && onBackPress.constructor === Array) {
const callFun = onBackPress[onBackPress.length - 1];
const isNext = callFun.call(page, delta);
if (isNext) {
return true;
}
}
// eslint-disable-next-line
history.go(-backLayer);
}
/**
* 把加载动画添加到dom下面,为什么一定要先添加,后移除。保证动画的连续性
*/
appendHTML({
style,
html,
script,
}) {
window.addEventListener('DOMContentLoaded', () => {
const body = document.querySelector('body');
body.appendChild(style);
body.appendChild(html);
body.appendChild(script);
this.toogle('startLodding', true);
});
}
/**
* 页面是否加载完毕触发对应事件
*/
toogle(toogle, DOMContentLoaded = false) {
if (DOMContentLoaded && this.loadingCount !== 0) {
this.loadingCount += 1;
return false;
}
try {
this.loadingCount += 1;
if (this.isLoading) {
window[toogle]();
}
} catch (error) {
warn('你使用了 addRoutes API 提前进行了生命周期 并触发了startLodding');
}
}
async setLoadingStatus({
loading,
replaceStyle,
resetStyle,
}) {
this.isLoading = loading;
if (loading) { // 确认需要加载样式 开始插入节点
const userStyle = resetStyle();
const userStyleKeys = Object.keys(userStyle);
for (let i = 0; i < userStyleKeys.length; i += 1) {
const key = userStyleKeys[i];
let html = userStyle[key];
if (key === 'style' && !replaceStyle) { // 开发者设置为追加style
html = DOM[key].innerHTML + html;
}
DOM[key].innerHTML = html;
}
this.appendHTML(DOM);
}
}
}
export default Patch;
import {Component,PluginFunction,Location,Route,Animation,H5,APP,RouteConfig,RouterOptions} from './options'
/**
* 路由挂载点
* @param {VueComponent } Vim vue实例对象
* @param {dom} el dom节点选择器
*/
declare const RouterMount: (Vim: any, el: string | Element) => void;
declare class Router {
constructor(options:RouterOptions)
/**
* 当前的 Route
*/
get $Route():Route;
/**
* 获取 url 参数帮助类实例
*/
get $parseQuery():object;
/**
* 获取当前是否处于正在跳转的状态
* H5 状态下始终为false
*/
get $lockStatus():boolean;
/**
* 动态设置拦截状态
*/
set $lockStatus(status:boolean);
/**动态的导航到一个新 URL 保留浏览历史
* navigateTo
* @param {Object} rule
*/
push(rule: Location | string): void;
/**动态的导航到一个新 URL 关闭当前页面,跳转到的某个页面。
* redirectTo
* @param {Object} rule
*/
replace(rule:Location | string):void;
/**动态的导航到一个新 URL 关闭所有页面,打开到应用内的某个页面
* reLaunch
* @param {Object} rule
*/
replaceAll(rule:Location | string):void;
/**动态的导航到一个新 url 关闭所有页面,打开到应用内的某个tab
* @param {Object} rule
*/
pushTab(rule:Location | string) :void;
/**
* 返回到指定层级页面上
*/
back(backLayer?:number,delta?:Object):void
/**
* 获取当前页面下的 Route 信息
* @param {Object} Vim 当前开发者可以传递一个 vue 组件对象 来获取当前下的 Route 信息
*/
getPageRoute(Vim?:Component) : Route
/**
* 注册的全局前置生命周期
* @param hooks 注册的全局前置生命周期函数
*/
beforeEach(hooks:Function) : Function
/**
* 注册的全局后置生命周期
* @param hooks 注册的全局后置生命周期函数
*/
afterEach(hooks:Function) : Function
static install:PluginFunction<never>
}
declare module "vue/types/vue" {
interface Vue {
$Router: Router;
$Route: Route;
}
}
export default Router;
export {
RouterMount,
Location,
Route,
Animation,
H5,
APP,
RouteConfig,
RouterOptions
}
\ No newline at end of file
import Vue, { ComponentOptions,PluginFunction, AsyncComponent } from "vue";
type Component = ComponentOptions<Vue> | typeof Vue | AsyncComponent;
type Dictionary<T> = { [key: string]: T };
type Position = { x: number; y: number };
type PositionResult = Position | { selector: string; offset?: Position } | void;
type NAVTYPE= 'push' | 'replace' | 'replaceAll' | 'pushTab'
interface Location {
name?: string;
path?: string;
query?: Dictionary<string | (string | null)[] | null | undefined>;
params?: Dictionary<string>;
NAVTYPE?: NAVTYPE;
}
interface Route {
path: string;
name?: string;
params?: any;
query?: any;
beforeEnter?:(to:Route, from:Route, next:Function) => void;
meta?: any; //其他格外参数
}
interface Animation{
animationType?:string;
animationDuration?:number;
}
interface H5{
rewriteFun?:boolean; //是否对uni-app reLaunch/navigateBack 两个方法重写 处理uni刷新直接返回到首页和触发路由守卫
paramsToQuery?: boolean; //h5端上通过params传参时规则是vue-router 刷新会丢失 开启此开关将变成?连接的方式
loading?: boolean; //是否显示加载动画
hinderTab?: boolean; //是否拦截uni-app自带底部菜单 TODO
vueRouterDev?: boolean; //完全使用采用vue-router的开发模式
useUniConfig?: boolean; //是否采用在pages.json下的所有页面配置信息,false时需开发者自行设置页面
keepUniIntercept?: boolean; //保留uni-app使用vue-router的拦截器
vueNext?: boolean; //在next管道函数中是否获取vueRouter next的原本参数
replaceStyle?: boolean; //是否对resetStyle函数中返回的style节点进行全部替换,否则为追加
resetStyle?: () => Object; //自定义加载样式函数 可返回一个包涵 html、style、script 的对象来重置Router内置的加载动画
mode?: string;
base?: string;
linkActiveClass?: string;
linkExactActiveClass?: string;
scrollBehavior?: (to:Route, from:Route, savedPostion:Position|void) => PositionResult | Promise<PositionResult>,
fallback?: boolean,
}
interface APP{
holdTabbar?:boolean; //是否开启底部菜单拦截
rewriteFun?:boolean; //是否对uni-app 下的chooseLocation/openLocation 两个方法重写 目的是隐藏和显示拦截tabbar
loddingPageStyle?:()=>Object; //当前等待页面的样式 必须返回一个json
loddingPageHook?:()=>void; //刚刚打开页面处于等待状态,会触发此事件
holdTabbarStyle?:()=>Object;
animation?:Animation; //页面切换动画
switchPageOutTime?:number, //最高能忍耐的页面切换时间 达到此时间 不管切换有没有完成都会显示页面出来 这对启动页帮助很大
}
interface RouteConfig {
path: string; //pages.json中的path 必须加上 '/' 开头
component?: Component; //H5端可用
name?: string; // 命名路由
components?: { [name: string]: Component }; // 命名视图组件,H5端可用
redirect?: string | Location | Function; //H5端可用
props?: boolean | Object | Function; //H5端可用
aliasPath?:string; //h5端 设置一个别名路径来替换 uni-app的默认路径
alias?: string | Array<string>; //H5端可用
children?: Array<RouteConfig>; // 嵌套路由,H5端可用
beforeEnter?: (to: Route, from: Route, next: Function) => void; //路由元守卫
meta?: any; //其他格外参数
}
interface RouterOptions{
h5?:H5;
APP?:APP;
debugger?: boolean; //是否处于开发阶段 设置为true则打印日志
encodeURI?: boolean; //是否对url传递的参数进行编码
routerBeforeEach?: () => Object; //router 前置路由函数 每次触发跳转前先会触发此函数
routerAfterEach?: () => Object; //router 后置路由函数 每次触发跳转后会触发此函数
routes?: RouteConfig[];
}
export {
PluginFunction,
Component,
Location,
Route,
Animation,
H5,
APP,
RouteConfig,
RouterOptions
}
\ No newline at end of file
export const builtIn = [{
path: '/preview-image',
name: 'previewImage',
component: {
render: () => {},
},
}, {
path: '/choose-location',
name: 'chooseLocation',
component: {
render: () => {},
},
}, {
path: '/open-location',
name: 'openLocation',
component: {
render: () => {},
},
}]; // uni-app内置路由
export const vuelifeHooks = { // vueRouter的原始生命周期
beforeHooks: [],
afterHooks: [],
};
export const vueMount = [];// 使用内部对象保留实例化下的appVue,并使用Router进行挂载触发第一次路由钩子
import { warn, err } from '../helpers/warn';
import {
diffRouter, vueDevRouteProxy, getRouterNextInfo, formatUserRule, nameToRute, encodeURLQuery, strPathToObjPath, getPages,
} from './util';
import { formatURLQuery } from '../helpers/util';
import { vuelifeHooks, vueMount } from './base';
import { lifeCycle, Global } from '../helpers/config';
let beforeEachCount = 0;
let afterEachCount = 0;
let resolveLaunch = null;
let beforeEnterDep = [];// 记录当前是否有重复的页面进入 避免重复触发
const beforeEachLaunch = new Promise((resolve) => resolveLaunch = resolve);
/**
* 把vue实例进行挂载到dom下
* @param {Router} Router uni-simple-router实例对象
*/
export const appMount = function () {
if (vueMount.length == 0) {
return err('检测到您未进行dom模型挂载操作,请调用api\r\n\r\n RouterMount(Vim: any, el: any): void');
}
const {
Vim,
el,
} = vueMount[0];
let formatEl = el;
if (el == null) {
formatEl = '#app'; // 这是uni-app在h5中的官方节点
}
try {
Vim.$mount(formatEl);
} catch (error) {
warn(`挂载vue节点时错误啦${error}`);
}
};
/**
* 格式化 next传递过来的参数 作为vue-router可用的
* @param {Object} to//即将跳转到的路由页面
* @param {*} Intercept
* @param {Funtion} next//路由连接管道
* @param {Router} Router//路由对象
*/
export const forMatNext = function (to, Intercept, next, Router) {
const { CONFIG, selfRoutes } = Router;
if (CONFIG.h5.vueRouterDev) { // 完全使用vue-router开发的时候 vueRouterDev:true 不用做啥直接略过
next(Intercept);
return Intercept;
}
if (typeof Intercept === 'object') { // 只有是对象类型的时候 我们才进行格式化
const navType = Reflect.get(Intercept, 'NAVTYPE');
delete Intercept.NAVTYPE;
if (navType == 'push') {
Intercept.replace = false;
Intercept.type = 'navigateTo';
} else {
Intercept.replace = true; // uni-app导航api所谓的NAVTYPE取值在h5都是replace:true
Intercept.type = 'reLaunch';
}
const name = Reflect.get(Intercept, 'name'); // 统一格式化path
Intercept.query = Intercept.params || Intercept.query;
delete Intercept.name;
delete Intercept.params;
if (Intercept.query == null) {
Intercept.query = {};
}
if (name != null) {
const { aliasPath, path } = nameToRute(name, selfRoutes);
Intercept.path = aliasPath || path;
} else { // 当设置别名时可以是别名跳转也可以path跳转
Intercept.path = Reflect.get(Intercept, 'path');
const rute = formatUserRule(Intercept.path, selfRoutes, CONFIG);
if (rute == null) {
return false;
}
Intercept.path = rute;
}
if (CONFIG.encodeURI) { // 如果设置的编码传递则进行编码后传递
const query = encodeURIComponent(JSON.stringify(Intercept.query));
const formatQuery = formatURLQuery(query);
Intercept.query = {};
if (formatQuery != '') {
Intercept.query.query = formatQuery;
}
}
} else if (Intercept != null && Intercept.constructor === String) {
Intercept = formatUserRule(Intercept, selfRoutes, CONFIG);
}
let objPath = Intercept;
if (Intercept != null && Intercept.constructor !== Boolean) {
objPath = strPathToObjPath(Intercept);
if (objPath != null) {
const type = Reflect.get(objPath, 'type');
if (type == null) { // 当next()是一个路径时
objPath.type = 'navigateTo';
}
}
} else if (Intercept === false) {
Router.lifeCycle.routerAfterHooks[0].call(Router, { H5Intercept: true });
}
next(objPath);// 统一格式化为对象的方式传递
return Intercept;
};
/**
* v1.5.4+
* beforeRouteLeave 生命周期
* @param {Object} to 将要去的那个页面 vue-router to对象
* @param {Object} from 从那个页面触发的 vue-router from对象
* @param {Object} next vue-router beforeEach next管道函数
* @param {Object} Router Router路由对象
*/
const beforeRouteLeaveHooks = function (to, from, next, Router) {
return new Promise((resolve) => {
const { currentRoute } = Router.$route;
if (currentRoute.path == to.path) { // 如果是同一个页面直接返回 不执行页面中的Leave事件
return resolve();
}
const page = getPages(); // 获取到当前的页面对象
if (page == null || page._HHYANGbeforeRouteLeaveCalled) {
warn('当前环境下无须执行 beforeRouteLeave。 原因:1.page等于null 2.真的的无须执行');
return resolve();
}
const beforeRouteLeaveArray = page.$options.beforeRouteLeave; // 获取到页面下的 beforeRouteLeave 路由守卫
if (beforeRouteLeaveArray == null) { // 当前页面没有预设 beforeRouteLeave 啥都不做
return resolve();
}
const { toRoute, fromRoute } = getRouterNextInfo(to, from, Router);
const beforeRouteLeave = beforeRouteLeaveArray[0]; // 不管怎么样 只执行第一个钩子 其他都不管
beforeRouteLeave.call(page, toRoute, fromRoute, (Intercept) => { // 开始执行生命周期
if (Intercept == null) { // 放行状态 直接返回
return resolve();
}
page._HHYANGbeforeRouteLeaveCalled = true; // 标记一下当前已经做过 beforeRouteLeave 啦
forMatNext(to, Intercept, next, Router); // 直接交给vue-router 处理
});
});
};
/**
* 修复首页beforeEnter执行两次的问题 https://github.com/SilurianYang/uni-simple-router/issues/67
*
* beforeEnter 生命周期
* @param {Object} to
* @param {Object} from
* @param {Object} next
* @param {Object} userHooks
* @param {Object} Router
*/
export const beforeEnterHooks = function (to, from, next, userHooks, Router) {
return new Promise(async (resolve) => {
// 修复 (#67)
if (beforeEnterDep.includes(to.path)) {
next();
return resolve();
}
beforeEnterDep = [to.path];
if (Reflect.get(Router, 'H5RouterReady')) {
const res = await new Promise(async (resolveNext) => {
const {
toRoute,
fromRoute,
} = getRouterNextInfo(to, from, Router);
await userHooks(toRoute, fromRoute, resolveNext);
});
forMatNext(to, res, next, Router);
} else {
next();
}
resolve();
});
};
/**
* vueAfter 生命周期
* @param {Object} to
* @param {Object} from
* @param {Object} next
* @param {Object} Router
*/
export const afterHooks = async function (to, from, next, Router) {
vuelifeHooks.afterHooks[0](to, from);
if (lifeCycle.afterHooks[0]) {
if (afterEachCount === 0) {
await beforeEachLaunch;
appMount(Router);
}
const {
toRoute,
fromRoute,
} = getRouterNextInfo(to, from, Router);
lifeCycle.afterHooks[0](toRoute, fromRoute);
} else if (afterEachCount === 0) {
appMount(Router);
}
afterEachCount += 1;
Router.lifeCycle.routerAfterHooks[0].call(Router);
};
/**
* vueBefore 生命周期
* @param {Object} to 将要去的那个页面 vue-router to对象
* @param {Object} from 从那个页面触发的 vue-router from对象
* @param {Object} next vue-router beforeEach next管道函数
* @param {Object} H5Config
*/
export const beforeHooks = function (to, from, next, Router) {
return new Promise(async (resolve) => {
await Router.lifeCycle.routerbeforeHooks[0].call(Router); // 触发Router内置前置生命周期
await beforeRouteLeaveHooks(to, from, next, Router); // 执行1.5.4+ beforeRouteLeave生命钩子
const H5 = Router.CONFIG.h5;
vuelifeHooks.beforeHooks[0](to, from, async (Intercept) => {
if (Intercept != null && H5.keepUniIntercept === true && H5.vueRouterDev === false) {
next(Intercept);
warn('uni-app 内部强制触发跳转拦截');
beforeEachCount += 1;
return resolve();
}
// 顺序问题 没有触发uni-app里面的方法 修复[#44](https://github.com/SilurianYang/uni-simple-router/issues/44)
if (!lifeCycle.beforeHooks[0]) {
next();
beforeEachCount += 1;
resolveLaunch();
return resolve();
}
const res = await new Promise(async (resolveNext) => {
const {
toRoute,
fromRoute,
} = getRouterNextInfo(to, from, Router);
await lifeCycle.beforeHooks[0](toRoute, fromRoute, resolveNext);
});
const beforeIntercept = forMatNext(to, res, next, Router);
if (beforeEachCount == 0 && beforeIntercept == null && to.meta.isTabBar) { // 首次触发beforeEach,并且首页没有进行跳转的情况下才触发beforeEnter 主要是keep-alive
const {
selfRoutes,
} = Router;
const beforeEnter = Reflect.get(selfRoutes[`/${to.meta.pagePath}`], 'beforeEnter');
if (beforeEnter) {
await beforeEnterHooks(to, from, next, beforeEnter, Router);
}
}
beforeEachCount += 1;
resolveLaunch();
resolve();
});
});
};
/**
* 通过自动调用router api来完成触发生命周期
* 修复 history 模式下报错的问题 https://github.com/SilurianYang/uni-simple-router/issues/38
* 修复 history 模式下刷新页面参数丢失的问题 https://github.com/SilurianYang/uni-simple-router/issues/45
* 修复 history 模式下首次打开页面url错误时不走 path:* 的匹配项 https://github.com/SilurianYang/uni-simple-router/issues/58
*
* @param {Object} Router //当前simple-router 对象
* @param {Object} vueRouter vue-router对象
*/
export const triggerLifeCycle = function (Router, vueRouter) {
const { CONFIG } = Router;
const currRoute = vueRouter.currentRoute;
if (vueRouter.mode === 'hash') {
const {
query,
path,
} = currRoute;
const URLQuery = encodeURLQuery(CONFIG, query, 'hash');
vueRouter.replace(`${path}${URLQuery}`);
} else {
const {
toRoute,
} = getRouterNextInfo(currRoute, currRoute, Router);
const URLQuery = encodeURLQuery(CONFIG, currRoute.query, 'history');
vueRouter.replace({
path: toRoute.aliasPath || toRoute.path || currRoute.path,
query: URLQuery,
type: 'redirectTo',
});
}
};
/** 注册自定义的路由到vue-router中 前提是必须使用vueRouter开发模式
* @param {Object} Router
* @param {Object} vueRouter
* @param {Boolean} vueRouterDev
*/
export const registerRouter = function (Router, vueRouter, vueRouterDev) {
let routeMap = [];
if (!vueRouterDev) { // 则进行对比两个路由表 主要工作是做路径的优化
routeMap = diffRouter(Router, vueRouter, Router.CONFIG.h5.useUniConfig);
} else { // 完全使用vue-router开发时直接采用开发者的路由表
routeMap = vueDevRouteProxy(Router.CONFIG.routes, Router);
}
const createRouter = () => new vueRouter.constructor({
base: vueRouter.options.base,
mode: vueRouter.options.mode,
routes: routeMap,
});
const router = createRouter();
vueRouter.matcher = router.matcher;
Global.vueRouter = vueRouter; // 把当前vueRouter缓存到全局对象中
Global.RouterReadyPromise(); // router已经准备就绪 调用promise.resolve();
Router.H5RouterReady = true; // 并挂载到Router对象下
// 注册完成所有的钩子及相关参数,手动触发Router的生命周期
setTimeout(() => {
triggerLifeCycle(Router, vueRouter);
});
};
/**
* 实现一个继承的 数组类 代理掉 vue-router 生命钩子的数据
*/
class MyArray extends Array {
constructor(Router, vueOldHooks, hookFun) {
super();
this.Router = Router;
this.vueOldHooks = vueOldHooks;
this.hookFun = hookFun;
}
push(v) {
this.vueOldHooks.splice(0, 1, v);// 把vue-router路由生命钩子保存起来
this[this.length] = (to, from, next) => {
this.hookFun(to, from, next, this.Router);
};
}
}
export default MyArray;
import { afterHooks, beforeHooks, registerRouter } from './concat';
import { fromatRoutes } from './util';
import { err, warn } from '../helpers/warn';
import { proxyEachHooks } from './proxy/proxy';
/**
* 重写掉H5端 uni-app原始存在的bug
*
* @param {Object} Router
*/
const rewriteUniFun = function (Router) {
if (Router.CONFIG.h5.rewriteFun === false) { // 不需要重写
return false;
}
uni.reLaunch = function ({
url,
}) {
if (url === '/') {
warn('H5端 uni.reLaunch(\'/\')时 默认被重写了! 你可以使用 this.$Router.replaceAll() 或者 uni.reLaunch(\'/\'?xxx)');
// eslint-disable-next-line
if (history.length > 1) { // 只有在有历史记录的时候才返回 不然直接返回首页
return Router.back();
}
return Router.replaceAll('/');
}
const path = url.match(/^[^?]+|(\/)/)[0];
try {
const query = {};
url.replace(/([^?&]+)=([^?&]+)/g, (s, v, k) => {
query[v] = decodeURIComponent(k);
return `${k}=${v}`;
});
Router.replaceAll({
path,
query,
});
} catch (e) {
err(`${url}解析失败了.... 试试 this.$Router.replaceAll() 吧`);
}
};
uni.navigateBack = function (delta) {
let backLayer = delta;
if (delta.constructor === Object) { // 这种可能就只是uni-app自带的返回按钮,还有种可能就是开发者另类传递的
backLayer = 1;
}
Router.back(backLayer, delta);
};
};
/**
* 拦截并注册vueRouter中的生命钩子,路由表解析
* @param {Object} Router
* @param {vueRouter} vueRouter
*/
const init = function (Router, vueRouter) {
const CONFIG = Router.CONFIG.h5;
vueRouter.afterHooks = proxyEachHooks(Router, 'afterHooks', afterHooks);
vueRouter.beforeHooks = proxyEachHooks(Router, 'beforeHooks', beforeHooks);
const objVueRoutes = fromatRoutes(vueRouter.options.routes, false, {}); // 返回一个格式化好的routes 键值对的形式
const objSelfRoutes = fromatRoutes(Router.CONFIG.routes, true, CONFIG);
Router.vueRoutes = objVueRoutes; // 挂载vue-routes到当前的路由下
Router.selfRoutes = {
...Router.selfRoutes || {},
...objSelfRoutes,
}; // 挂载self-routes到当前路由下
Router.$route = vueRouter; // 挂载vue-router到$route
rewriteUniFun(Router); // 重新掉uniapp上的一些有异常的方法
registerRouter(Router, vueRouter, CONFIG.vueRouterDev);
};
export default init;
import { beforeEnterHooks } from '../concat';
import { vuelifeHooks } from '../base';
import MyArray from '../extends/myArray';
/**
* 通过 Object.defineProperty 代理一个对象主要是拦截beforeEnter 生命钩子
* @param {Router} Router 路由实例对象
* @param {Object} BeProxy 需要代理的路由表
*/
export const proxyBeforeEnter = function (Router, BeProxy) {
const proxyDc = Object.create(null);// 创建没有继承的属性
const BeProxyKeys = Object.keys(BeProxy);
for (let i = 0; i < BeProxyKeys.length; i += 1) {
const key = BeProxyKeys[i];
Object.defineProperty(proxyDc, key, {
enumerable: true,
configurable: true,
get() {
const value = BeProxy[key];
if (key == 'beforeEnter' && value !== undefined) {
return (to, from, next) => {
beforeEnterHooks(to, from, next, value, Router);
};
}
return value;
},
set(v) {
BeProxy[key] = v;
},
});
}
return proxyDc;
};
/**
* 在uni-app没有注入生命周期时先直接代理相关生命周期数组
* @param {Object} Router
* @param {Object} key
* @param {Funtion} hookFun
*/
export const proxyEachHooks = function (Router, key, hookFun) {
const vueOldHooks = vuelifeHooks[key];
return new MyArray(Router, vueOldHooks, hookFun);
};
import { err } from '../helpers/warn';
import { formatUserRule, strPathToObjPath } from './util';
/**
* @param {Object} replace vue-router的跳转方式
* @param {Object} rule 需要跳转到的路由匹配规则
* @param {Object} type 对应的官方跳转模式
*
* this 为当前 Router 实例
*/
const H5PushTo = function (replace, rule, type) {
if (this.$route == null) {
return err('h5端路由为就绪,请检查调用代码');
}
rule = formatUserRule(rule, this.selfRoutes, this.CONFIG);
const objPath = strPathToObjPath(rule);
objPath.type = type;
this.$route[replace](objPath);
};
export default H5PushTo;
import { warn, err } from '../helpers/warn';
import { isObject, resolveRule, copyObject } from '../helpers/util';
import { proxyBeforeEnter } from './proxy/proxy';
import { Global } from '../helpers/config';
const pagesConfigReg = /props:\s*\(.*\)\s*(\([\s\S]*\))\s*},/;
const pagesConfigRegCli = /props:\s*Object\.assign\s*(\([\s\S]*\))\s*},/; // 脚手架项目
const defRoutersReg = /props:\s*{([\s\S]+)}\s*},/;
/**
* 解析验证当前的 component 选项是否配置正确 只有vueRouterDev:false 才会调用此方法
* @param {Function|Object} component
* @param {Object} item
* @param {Boolean} useUniConfig
*/
export const resolveRender = function ({
component,
components,
}, item, useUniConfig) {
if (components != null) {
warn(`vueRouterDev:false时 路由表配置中 ‘components’ 无效,\r\n\r\n ${JSON.stringify(item)}`);
}
if (useUniConfig == true) { // 采用uni-pages.json中的配置时component可以为空
return false;
}
if (item.path == '*') { // 唯独这个情况在vue-router中可以不用component
return true;
}
if (component == null) {
return err(`vueRouterDev:false时 路由表中 ‘component’ 选项不能为空:\r\n\r\n ${JSON.stringify(item)}`);
}
if (component.constructor === Function) {
item.component = {
render: component,
};
} else if (component.constructor === Object) {
if (component.render == null || component.render.constructor !== Function) {
err(`vueRouterDev:false时 路由表配置中 ‘render’ 函数缺失或类型不正确:\r\n\r\n ${JSON.stringify(item)}`);
}
} else {
err(
`vueRouterDev:false时 路由表配置中 ‘component’ 选项仅支持 Function、Object 类型。并确保 Object 类型时传递了 ‘render’ 函数 :\r\n\r\n ${JSON.stringify(item)}`,
);
}
};
/**
* 递归解析 H5配置中有存在嵌套对象的情况,优先以path为key存储。没有则找aliasPath作为key
* @param {Object} objRoutes
* @param {Array} children
* @param {Boolean} useUniConfig 是否使用pages.json下的页面配置
*/
export const resloveChildrenPath = function (objRoutes, children, useUniConfig) {
for (let i = 0; i < children.length; i += 1) {
const item = children[i];
resolveRender(item, item, useUniConfig);
if (item.path != null) {
objRoutes[item.path] = {
...item,
...{
_ROUTERPATH: true, // 使用page.json中的path为路径
},
};
} else {
objRoutes[item.aliasPath] = {
...item,
...{
_ROUTERPATH: false,
},
};
}
if (item.children && item.children.constructor === Array) {
resloveChildrenPath(objRoutes, item.children, useUniConfig);
}
}
};
/**
* 格式化原始路由表
* @param {Object} routes 路由表
* @param {Boolean} userRoute 是否为用户自己配置的路由表
* @param {Boolean} H5CONFIG
*/
export const fromatRoutes = function (routes, userRoute, {
vueRouterDev,
useUniConfig,
}) {
if (userRoute && vueRouterDev) { // 如果是用户的路由表并且 完全采用vueRouter开发 则不作处理直接返回
return routes;
}
const objRoutes = {};
for (let i = 0; i < routes.length; i += 1) {
const item = routes[i];
const path = item.path === '/' ? item.alias : item.path;
if (userRoute) {
if (item.children && item.children.constructor === Array) {
resloveChildrenPath(objRoutes, item.children, useUniConfig);
}
resolveRender(item, item, useUniConfig); // 是否使用pages.json下的页面配置
}
objRoutes[path] = {
...item,
...{
_PAGEPATH: path.substring(1),
},
};
}
return objRoutes;
};
/**
* 解析vueRouter中 component 下 render函数中的配置信息
* @param {String} FunStr
*/
export const getFuntionConfig = function (FunStr) {
let matchText = FunStr.match(pagesConfigReg);
let prefix = '';
if (matchText == null) { // 是uni-app自带的默认路由及配置 也可能是脚手架项目
matchText = FunStr.match(pagesConfigRegCli);
if (matchText == null) { // 确认不是脚手架项目
try {
// eslint-disable-next-line
matchText = FunStr.match(defRoutersReg)[1];
// eslint-disable-next-line
matchText = eval(`Object.assign({${matchText}})`);
prefix = 'system-';
} catch (error) {
err(`读取uni-app页面构建方法配置错误 \r\n\r\n ${error}`);
}
} else {
// eslint-disable-next-line
matchText = eval(`Object.assign${matchText[1]}`);
}
} else {
// eslint-disable-next-line
matchText = eval(`Object.assign${matchText[1]}`);
}
return {
config: matchText,
prefix,
FunStr,
};
};
/**
* 通过一个未知的路径名称 在路由表中查找指定路由表 并返回
* @param {String} path //不管是aliasPath名的路径还是path名的路径
* @param {Object} routes//当前对象的所有路由表
*/
export const pathToRute = function (path, routes) {
let PATHKEY = '';
let rute = {};
const routeKeys = Object.keys(routes);
for (let i = 0; i < routeKeys.length; i += 1) {
const key = routeKeys[i];
const item = routes[key];
rute = item;
if (item.aliasPath == path) { // path参数是优先采用aliasPath为值得 所以可以先判断是否与aliasPath相同
PATHKEY = 'aliasPath';
break;
}
// eslint-disable-next-line
if (`/${item._PAGEPATH}` == path) { // 路径相同
PATHKEY = 'path';
break;
}
}
return {
PATHKEY: {
[PATHKEY]: path,
},
rute,
};
};
/**
* 通过一个路径name 在路由表中查找指定路由表 并返回
* @param {String} name//实例化路由时传递的路径表中所匹配的对应路由name
* @param {Object} routes//当前对象的所有路由表
*/
export const nameToRute = function (name, routes) {
const routesKeys = Object.keys(routes);
for (let i = 0; i < routesKeys.length; i += 1) {
const key = routesKeys[i];
const item = routes[key];
if (item.name == name) {
return item;
}
}
err(`路由表中没有找到 name为:'${name}' 的路由`);
};
/**
* 根据用户传入的路由规则 格式化成正确的路由规则
* @param {Object} rule 用户需要跳转的路由规则
* @param {Object} selfRoutes simple-router下的所有routes对象
* @param {Object} CONFIG 当前路由下的所有配置信息
*/
export const formatUserRule = function (rule, selfRoutes, CONFIG) {
let type = '';
const ruleQuery = (type = 'query', rule.query || (type = 'params', rule.params)) || (type = '', {});
let rute = {}; // 默认在router中的配置
if (type == '' && rule.name != null) { // 那就是可能没有穿任何值咯
type = 'params';
}
if (type != 'params') {
const route = pathToRute(rule.path || rule, selfRoutes);
if (Object.keys(route.PATHKEY)[0] == '') {
err(`'${route.PATHKEY['']}' 路径在路由表中未找到`);
return null;
}
rute = route.rute;
if (rule.path) {
rule.path = rute.path;
}
}
if (type != '') { // 当然是对象啦 这个主要是首页H5PushTo调用时的
if (type == 'params' && CONFIG.h5.paramsToQuery) { // 如果是name规则并且设置了转query,那么就转path跳转了
const {
aliasPath,
path,
} = nameToRute(rule.name, selfRoutes);
delete rule.name;
delete rule.params;
rule.path = aliasPath || path;
type = 'query';
}
const query = Global.$parseQuery.transfer(ruleQuery);
if (CONFIG.encodeURI) {
if (query != '') {
rule[type] = {
query: query.replace(/^query=/, ''),
};
}
} else {
rule[type] = ruleQuery;
}
} else { // 纯字符串,那就只有是path啦
rule = rute.path;
}
return rule;
};
/**
* 根据是否获取非vue-Router next管道参数,来进行格式化
*
* @param {Object} to
* @param {Object} from
* @param {Router} Router //router当前实例对象
*/
export const getRouterNextInfo = function (to, from, Router) {
let [toRoute, fromRoute] = [to, from];
const H5 = Router.CONFIG.h5;
if (H5.vueNext === false && H5.vueRouterDev === false) { // 不采用vue-router中的to和from,需要格式化成Router中$Route获取的一样一样的
let [toPath, fromPath] = [{}, {}];
toPath[to.meta.PATHKEY] = to.meta.PATHKEY === 'path' ? `/${to.meta.pagePath}` : `${to.path}`;
fromPath[from.meta.PATHKEY] = from.meta.PATHKEY === 'path' ? `/${from.meta.pagePath}` : `${from.path}`;
if (to.meta.PATHKEY == null) { // 未使用uni-pages.json中的配置、通过addRoutes时 meta.PATHKEY 可能未undefined
toPath = pathToRute(to.path, Router.selfRoutes).PATHKEY;
}
if (from.meta.PATHKEY == null) {
fromPath = pathToRute(from.path, Router.selfRoutes).PATHKEY;
}
const isEmptyTo = Object.keys(to.query).length != 0 ? copyObject(to.query) : copyObject(to.params);
const isEmptyFrom = Object.keys(from.query).length != 0 ? copyObject(from.query) : copyObject(from.params);
/* eslint-disable */
delete isEmptyTo.__id__; // 删除uni-app下的内置属性
delete isEmptyFrom.__id__;
/* eslint-enable */
const toQuery = Global.$parseQuery.queryGet(isEmptyTo).decode;
const fromQuery = Global.$parseQuery.queryGet(isEmptyFrom).decode;
toRoute = resolveRule(Router, toPath, toQuery, Object.keys(toPath)[0]);
fromRoute = resolveRule(Router, fromPath, fromQuery, Object.keys(fromPath)[0]);
} else {
if (fromRoute.name == null && toRoute.name != null) { // 这种情况是因为uni-app在使用vue-router时搞了骚操作。
fromRoute = {
...fromRoute,
...{
name: toRoute.name,
},
}; // 这个情况一般出现在首次加载页面
}
}
return {
toRoute,
fromRoute,
};
};
export const vueDevRouteProxy = function (routes, Router) {
const proxyRoutes = [];
for (let i = 0; i < routes.length; i += 1) {
const item = routes[i];
const childrenRoutes = Reflect.get(item, 'children');
if (childrenRoutes != null) {
const childrenProxy = vueDevRouteProxy(childrenRoutes, Router);
item.children = childrenProxy;
}
const ProxyRoute = proxyBeforeEnter(Router, item);
proxyRoutes.push(ProxyRoute);
}
return proxyRoutes;
};
/**
* 组装成编码后的路由query传递信息
* @param {Object} CONFIG simple-router 对象配置
* @param {Object} query 传递的参数
* @param {Object} mode 路由模式
*/
export const encodeURLQuery = function (CONFIG, query, mode) {
if (Object.keys(query).length == 0) { // 没有传值的时候 我们啥都不管
return '';
}
if (CONFIG.h5.vueRouterDev === false) { // 没有采取完全模式开发时 才转换
const { strQuery, historyObj } = Global.$parseQuery.queryGet(query);
if (mode === 'history') {
return historyObj;
}
return strQuery;
} // 完全彩种 vue-router 开发的时候 我们不用管
if (mode === 'history') { // 此模式下 需要的就是对象
return query;
}
return Global.$parseQuery.stringify(query); // hash转成字符串拼接
};
/**
* 把一个未知的路由跳转规则进行格式化为 hash、history 可用的,主要表现在 history模式下直接传入path会报错__id__错误的问题
* @param {*} path 需要判断修改的路径规则
*/
export const strPathToObjPath = function (path) {
if (path == null) { // 我们也不用管啦,这个情况是路由守卫中传递的
return path;
}
if (isObject(path)) { // 是对象我们不用管
return path;
}
return { // 这种情况就是只有path时,直接返回path对象了
path,
};
};
/**
* 通过 getCurrentPages() api 获取指定页面的 page 对象 默认是获取当前页面page对象
* @param {Number} index //需要获取的页面索引
* @param {Boolean} all //是否获取全部的页面
*/
export const getPages = function (index = 0, all) {
const pages = getCurrentPages(all);
return pages.reverse()[index];
};
/**
* 获取当前页面下的 Route 信息
*
* @param {Object} pages 获取页面对象集合
* @param {Object} Vim 用户传递的当前页面对象
*/
export const H5GetPageRoute = function (pages, Vim) {
if (pages.length > 0) { // 直接取当前页面的对象
const currentRoute = pages[pages.length - 1].$route;
return getRouterNextInfo(currentRoute, currentRoute, this).toRoute;
} if (Vim && Vim.$route) {
return getRouterNextInfo(Vim.$route, Vim.$route, this).toRoute;
}
return {};
};
/**
* 在useUniConfig:true 的情况下重新拼装路由表 useUniConfig:false 不需要读取page.json中的数据 直接使用component作为页面组件
* @param {Router} Router//unis-simple-router 路由对象
* @param {vueRouter} vueRouter//vue-router对象
* @param {Boolean} useUniConfig//是否采用uni-page.json中的配置选项
* @param {Array} routes//需要循环的routes表
*/
export const diffRouter = function (Router, vueRouter, useUniConfig, routes) {
const newRouterMap = [];
if (useUniConfig) { // 使用pages.json的样式配置 只是单纯的把url路径改成用户自定义的 保留uni的所以的配置及生命周期、缓存
const Routes = routes || vueRouter.options.routes;
const cloneSelfRoutes = copyObject(Router.selfRoutes); // copy一个对象随便搞xxoo
Routes.forEach(((item) => {
const path = item.path === '/' ? item.alias : item.path;
const vueRoute = (Router.vueRoutes[path] || Router.vueRoutes[item.path]) || Router.selfRoutes[path];
const CselfRoute = Router.selfRoutes[path];
delete cloneSelfRoutes[path]; // 移除已经添加到容器中的路由,用于最后做对比 是否page.json中没有,而实例化时传递了
if (CselfRoute == null) {
return err(
`读取 ‘pages.json’ 中页面配置错误。实例化时传递的路由表中未找到路径为:${path} \r\n\r\n 可以尝试把 ‘useUniConfig’ 设置为 ‘false’。或者配置正确的路径。如果你是动态添加的则不用理会`,
);
}
let pageConfigJson = {
config: {},
};
if (vueRoute.component) {
pageConfigJson = getFuntionConfig(vueRoute.component.render.toString());
CselfRoute.component = {
render: (h) => vueRoute.component.render(h),
};
}
delete CselfRoute.components; // useUniConfig:true 时不允许携带components
delete CselfRoute.children; // useUniConfig:true 时不允许携带children
CselfRoute.meta = {
...pageConfigJson.config,
...item.meta || {},
PATHKEY: CselfRoute.aliasPath ? 'aliasPath' : 'path',
pagePath: CselfRoute.path.substring(1),
};
CselfRoute.path = CselfRoute.aliasPath || (item.path === '/' ? item.path : CselfRoute.path);
item.alias = item.path === '/' ? item.alias : CselfRoute.path; // 重新给vueRouter赋值一个新的路径,欺骗uni-app源码判断
const ProxyRoute = proxyBeforeEnter(Router, CselfRoute);
newRouterMap.push(ProxyRoute);
}));
if (Object.keys(cloneSelfRoutes).length > 0) { // 确实page.json中没有,而实例化时传递了
const testG = cloneSelfRoutes['*']; // 全局通配符,他是个例外'通配符'可以被添加
if (testG && routes == null) {
const ProxyRoute = proxyBeforeEnter(Router, Router.selfRoutes['*']);
newRouterMap.push(ProxyRoute);
}
if (routes == null) { // 非动态添加时才打印警告
const cloneSelfRoutesKeys = Object.keys(cloneSelfRoutes);
for (let i = 0; i < cloneSelfRoutesKeys.length; i += 1) {
const key = cloneSelfRoutesKeys[i];
if (key !== '*') { // 通配符不警告
warn(`实例化时传递的routes参数:\r\n\r\n ${JSON.stringify(cloneSelfRoutes[key])} \r\n\r\n 在pages.json中未找到。自定排除掉,不会添加到路由中`);
}
}
}
}
} else { // 不使用任何的uni配置完全使用 完全使用component作为页面使用
const Routes = routes || Router.selfRoutes;
const RoutesKeys = Object.keys(Routes);
for (let i = 0; i < RoutesKeys.length; i += 1) {
const key = RoutesKeys[i];
const item = Routes[key];
// eslint-disable-next-line
if (item._ROUTERPATH != null) { // 不寻找children下的路径,只取第一层
continue;
}
delete item.components;
delete item.children;
item.path = item.aliasPath || item.path; // 优先获取别名为路径
if (item.path !== '*') {
item.component = item.component.render || item.component; // render可能是用户使用addRoutes api进行动态添加的
}
item.meta = {
...item.meta || {},
PATHKEY: item.aliasPath ? 'aliasPath' : 'path',
pagePath: item.path.substring(1),
};
const ProxyRoute = proxyBeforeEnter(Router, item);
newRouterMap.push(ProxyRoute);
}
}
return newRouterMap;
};
......@@ -91,7 +91,7 @@
</view>
<view class="h2">币种</view>
<view class="tab row">
<view class="box row rowCenter verCenter" v-for="(item, index) in currency_arr" :class="{ curr: currencyIndex == index }" :key="index">{{ item }}</view>
<view class="box row rowCenter verCenter" v-for="(item, index) in currency_arr" :class="{ curr: currencyIndex == index }" :key="index" @click="filterTab(index)">{{ item }}</view>
</view>
</view>
<view class="btn row bothSide verCenter">
......@@ -181,7 +181,9 @@ export default {
/**
* 筛选
*/
filterChange() {},
filterChange(index) {
this.currencyIndex = index;
},
/**
* 立即支付
* @param {Object} order_id
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment