menu init config

This commit is contained in:
silenceqi 2020-03-09 09:28:32 +08:00
parent a145c1c229
commit ba3dabd3ca
23 changed files with 1124 additions and 0 deletions

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="200px" height="200px" viewBox="0 0 200 200" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 47.1 (45422) - http://www.bohemiancoding.com/sketch -->
<title>Group 28 Copy 5</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="62.1023273%" y1="0%" x2="108.19718%" y2="37.8635764%" id="linearGradient-1">
<stop stop-color="#4285EB" offset="0%"></stop>
<stop stop-color="#2EC7FF" offset="100%"></stop>
</linearGradient>
<linearGradient x1="69.644116%" y1="0%" x2="54.0428975%" y2="108.456714%" id="linearGradient-2">
<stop stop-color="#29CDFF" offset="0%"></stop>
<stop stop-color="#148EFF" offset="37.8600687%"></stop>
<stop stop-color="#0A60FF" offset="100%"></stop>
</linearGradient>
<linearGradient x1="69.6908165%" y1="-12.9743587%" x2="16.7228981%" y2="117.391248%" id="linearGradient-3">
<stop stop-color="#FA816E" offset="0%"></stop>
<stop stop-color="#F74A5C" offset="41.472606%"></stop>
<stop stop-color="#F51D2C" offset="100%"></stop>
</linearGradient>
<linearGradient x1="68.1279872%" y1="-35.6905737%" x2="30.4400914%" y2="114.942679%" id="linearGradient-4">
<stop stop-color="#FA8E7D" offset="0%"></stop>
<stop stop-color="#F74A5C" offset="51.2635191%"></stop>
<stop stop-color="#F51D2C" offset="100%"></stop>
</linearGradient>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="logo" transform="translate(-20.000000, -20.000000)">
<g id="Group-28-Copy-5" transform="translate(20.000000, 20.000000)">
<g id="Group-27-Copy-3">
<g id="Group-25" fill-rule="nonzero">
<g id="2">
<path d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C99.2571609,26.9692191 101.032305,26.9692191 102.20193,28.1378823 L129.985225,55.8983314 C134.193707,60.1033528 141.017005,60.1033528 145.225487,55.8983314 C149.433969,51.69331 149.433969,44.8756232 145.225487,40.6706018 L108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z" id="Shape" fill="url(#linearGradient-1)"></path>
<path d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C100.999864,25.6271836 105.751642,20.541824 112.729652,19.3524487 C117.915585,18.4685261 123.585219,20.4140239 129.738554,25.1889424 C125.624663,21.0784292 118.571995,14.0340304 108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z" id="Shape" fill="url(#linearGradient-2)"></path>
</g>
<path d="M153.685633,135.854579 C157.894115,140.0596 164.717412,140.0596 168.925894,135.854579 L195.959977,108.842726 C200.659183,104.147384 200.659183,96.5636133 195.960527,91.8688194 L168.690777,64.7181159 C164.472332,60.5180858 157.646868,60.5241425 153.435895,64.7316526 C149.227413,68.936674 149.227413,75.7543607 153.435895,79.9593821 L171.854035,98.3623765 C173.02366,99.5310396 173.02366,101.304724 171.854035,102.473387 L153.685633,120.626849 C149.47715,124.83187 149.47715,131.649557 153.685633,135.854579 Z" id="Shape" fill="url(#linearGradient-3)"></path>
</g>
<ellipse id="Combined-Shape" fill="url(#linearGradient-4)" cx="100.519339" cy="100.436681" rx="23.6001926" ry="23.580786"></ellipse>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -0,0 +1,9 @@
module.exports = {
navTheme: 'dark', // theme for nav menu
primaryColor: '#1890FF', // primary color of ant design
layout: 'sidemenu', // nav menu position: sidemenu or topmenu
contentWidth: 'Fluid', // layout of content: Fluid or Fixed, only works when layout is topmenu
fixedHeader: false, // sticky header
autoHideHeader: false, // auto hide header
fixSiderbar: false, // sticky siderbar
};

View File

@ -0,0 +1,67 @@
import { queryNotices } from '@/services/api';
export default {
namespace: 'global',
state: {
collapsed: false,
notices: [],
},
effects: {
*fetchNotices(_, { call, put }) {
const data = yield call(queryNotices);
yield put({
type: 'saveNotices',
payload: data,
});
yield put({
type: 'user/changeNotifyCount',
payload: data.length,
});
},
*clearNotices({ payload }, { put, select }) {
yield put({
type: 'saveClearedNotices',
payload,
});
const count = yield select(state => state.global.notices.length);
yield put({
type: 'user/changeNotifyCount',
payload: count,
});
},
},
reducers: {
changeLayoutCollapsed(state, { payload }) {
return {
...state,
collapsed: payload,
};
},
saveNotices(state, { payload }) {
return {
...state,
notices: payload,
};
},
saveClearedNotices(state, { payload }) {
return {
...state,
notices: state.notices.filter(item => item.type !== payload),
};
},
},
subscriptions: {
setup({ history }) {
// Subscribe history(url) change, trigger `load` action if pathname is `/`
return history.listen(({ pathname, search }) => {
if (typeof window.ga !== 'undefined') {
window.ga('send', 'pageview', pathname + search);
}
});
},
},
};

View File

@ -0,0 +1,123 @@
import { message } from 'antd';
import defaultSettings from '../defaultSettings';
let lessNodesAppended;
const updateTheme = primaryColor => {
// Don't compile less in production!
if (APP_TYPE !== 'site') {
return;
}
// Determine if the component is remounted
if (!primaryColor) {
return;
}
const hideMessage = message.loading('正在编译主题!', 0);
function buildIt() {
if (!window.less) {
return;
}
setTimeout(() => {
window.less
.modifyVars({
'@primary-color': primaryColor,
})
.then(() => {
hideMessage();
})
.catch(() => {
message.error('Failed to update theme');
hideMessage();
});
}, 200);
}
if (!lessNodesAppended) {
// insert less.js and color.less
const lessStyleNode = document.createElement('link');
const lessConfigNode = document.createElement('script');
const lessScriptNode = document.createElement('script');
lessStyleNode.setAttribute('rel', 'stylesheet/less');
lessStyleNode.setAttribute('href', '/color.less');
lessConfigNode.innerHTML = `
window.less = {
async: true,
env: 'production',
javascriptEnabled: true
};
`;
lessScriptNode.src = 'https://gw.alipayobjects.com/os/lib/less.js/3.8.1/less.min.js';
lessScriptNode.async = true;
lessScriptNode.onload = () => {
buildIt();
lessScriptNode.onload = null;
};
document.body.appendChild(lessStyleNode);
document.body.appendChild(lessConfigNode);
document.body.appendChild(lessScriptNode);
lessNodesAppended = true;
} else {
buildIt();
}
};
const updateColorWeak = colorWeak => {
document.body.className = colorWeak ? 'colorWeak' : '';
};
export default {
namespace: 'setting',
state: defaultSettings,
reducers: {
getSetting(state) {
const setting = {};
const urlParams = new URL(window.location.href);
Object.keys(state).forEach(key => {
if (urlParams.searchParams.has(key)) {
const value = urlParams.searchParams.get(key);
setting[key] = value === '1' ? true : value;
}
});
const { primaryColor, colorWeak } = setting;
if (state.primaryColor !== primaryColor) {
updateTheme(primaryColor);
}
updateColorWeak(colorWeak);
return {
...state,
...setting,
};
},
changeSetting(state, { payload }) {
const urlParams = new URL(window.location.href);
Object.keys(defaultSettings).forEach(key => {
if (urlParams.searchParams.has(key)) {
urlParams.searchParams.delete(key);
}
});
Object.keys(payload).forEach(key => {
if (key === 'collapse') {
return;
}
let value = payload[key];
if (value === true) {
value = 1;
}
if (defaultSettings[key] !== value) {
urlParams.searchParams.set(key, value);
}
});
const { primaryColor, colorWeak, contentWidth } = payload;
if (state.primaryColor !== primaryColor) {
updateTheme(primaryColor);
}
if (state.contentWidth !== contentWidth && window.dispatchEvent) {
window.dispatchEvent(new Event('resize'));
}
updateColorWeak(colorWeak);
window.history.replaceState(null, 'setting', urlParams.href);
return {
...state,
...payload,
};
},
},
};

View File

@ -0,0 +1,51 @@
import { query as queryUsers, queryCurrent } from '@/services/user';
export default {
namespace: 'user',
state: {
list: [],
currentUser: {},
},
effects: {
*fetch(_, { call, put }) {
const response = yield call(queryUsers);
yield put({
type: 'save',
payload: response,
});
},
*fetchCurrent(_, { call, put }) {
const response = yield call(queryCurrent);
yield put({
type: 'saveCurrentUser',
payload: response,
});
},
},
reducers: {
save(state, action) {
return {
...state,
list: action.payload,
};
},
saveCurrentUser(state, action) {
return {
...state,
currentUser: action.payload || {},
};
},
changeNotifyCount(state, action) {
return {
...state,
currentUser: {
...state.currentUser,
notifyCount: action.payload,
},
};
},
},
};

View File

@ -0,0 +1,15 @@
import React from 'react';
import { formatMessage } from 'umi';
import {Link} from 'umi';
import Exception from '@/components/Exception';
const Exception403 = () => (
<Exception
type="403"
desc={formatMessage({ id: 'app.exception.description.403' })}
linkElement={Link}
backText={formatMessage({ id: 'app.exception.back' })}
/>
);
export default Exception403;

View File

@ -0,0 +1,15 @@
import React from 'react';
import { formatMessage } from 'umi/locale';
import Link from 'umi/link';
import Exception from '@/components/Exception';
const Exception404 = () => (
<Exception
type="404"
desc={formatMessage({ id: 'app.exception.description.404' })}
linkElement={Link}
backText={formatMessage({ id: 'app.exception.back' })}
/>
);
export default Exception404;

View File

@ -0,0 +1,15 @@
import React from 'react';
import { formatMessage } from 'umi/locale';
import Link from 'umi/link';
import Exception from '@/components/Exception';
const Exception500 = () => (
<Exception
type="500"
desc={formatMessage({ id: 'app.exception.description.500' })}
linkElement={Link}
backText={formatMessage({ id: 'app.exception.back' })}
/>
);
export default Exception500;

View File

@ -0,0 +1,50 @@
import React, { PureComponent } from 'react';
import { Button, Spin, Card } from 'antd';
import { connect } from 'dva';
import styles from './style.less';
@connect(state => ({
isloading: state.error.isloading,
}))
class TriggerException extends PureComponent {
state = {
isloading: false,
};
triggerError = code => {
this.setState({
isloading: true,
});
const { dispatch } = this.props;
dispatch({
type: 'error/query',
payload: {
code,
},
});
};
render() {
const { isloading } = this.state;
return (
<Card>
<Spin spinning={isloading} wrapperClassName={styles.trigger}>
<Button type="danger" onClick={() => this.triggerError(401)}>
触发401
</Button>
<Button type="danger" onClick={() => this.triggerError(403)}>
触发403
</Button>
<Button type="danger" onClick={() => this.triggerError(500)}>
触发500
</Button>
<Button type="danger" onClick={() => this.triggerError(404)}>
触发404
</Button>
</Spin>
</Card>
);
}
}
export default TriggerException;

View File

@ -0,0 +1,28 @@
import queryError from '@/services/error';
export default {
namespace: 'error',
state: {
error: '',
isloading: false,
},
effects: {
*query({ payload }, { call, put }) {
yield call(queryError, payload.code);
yield put({
type: 'trigger',
payload: payload.code,
});
},
},
reducers: {
trigger(state, action) {
return {
error: action.payload,
};
},
},
};

View File

@ -0,0 +1,7 @@
.trigger {
background: 'red';
:global(.ant-btn) {
margin-right: 8px;
margin-bottom: 12px;
}
}

View File

@ -0,0 +1,22 @@
import {Component} from 'react';
import {Card} from 'antd';
class Helloworld extends Component {
render() {
return (
<Card>
<Card.Meta
avatar={<img
alt=""
style={{ width: '64px', height: '64px', borderRadius: '32px' }}
src="https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png"
/>}
title="Alipay"
description="在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。"
/>
</Card>
);
}
}
export default Helloworld;

126
app/web/src/services/api.js Normal file
View File

@ -0,0 +1,126 @@
//import { stringify } from 'qs';
import request from '@/utils/request';
export async function queryProjectNotice() {
return request('/api/project/notice');
}
export async function queryActivities() {
return request('/api/activities');
}
export async function queryRule(params) {
return request(`/api/rule?${stringify(params)}`);
}
export async function removeRule(params) {
return request('/api/rule', {
method: 'POST',
body: {
...params,
method: 'delete',
},
});
}
export async function addRule(params) {
return request('/api/rule', {
method: 'POST',
body: {
...params,
method: 'post',
},
});
}
export async function updateRule(params) {
return request('/api/rule', {
method: 'POST',
body: {
...params,
method: 'update',
},
});
}
export async function fakeSubmitForm(params) {
return request('/api/forms', {
method: 'POST',
body: params,
});
}
export async function fakeChartData() {
return request('/api/fake_chart_data');
}
export async function queryTags() {
return request('/api/tags');
}
export async function queryBasicProfile() {
return request('/api/profile/basic');
}
export async function queryAdvancedProfile() {
return request('/api/profile/advanced');
}
export async function queryFakeList(params) {
return request(`/api/fake_list?${stringify(params)}`);
}
export async function removeFakeList(params) {
const { count = 5, ...restParams } = params;
return request(`/api/fake_list?count=${count}`, {
method: 'POST',
body: {
...restParams,
method: 'delete',
},
});
}
export async function addFakeList(params) {
const { count = 5, ...restParams } = params;
return request(`/api/fake_list?count=${count}`, {
method: 'POST',
body: {
...restParams,
method: 'post',
},
});
}
export async function updateFakeList(params) {
const { count = 5, ...restParams } = params;
return request(`/api/fake_list?count=${count}`, {
method: 'POST',
body: {
...restParams,
method: 'update',
},
});
}
export async function fakeAccountLogin(params) {
return request('/api/login/account', {
method: 'POST',
body: params,
});
}
export async function fakeRegister(params) {
return request('/api/register', {
method: 'POST',
body: params,
});
}
export async function queryNotices() {
return request('/api/notices');
}
export async function getFakeCaptcha(mobile) {
return request(`/api/captcha?mobile=${mobile}`);
}

View File

@ -0,0 +1,5 @@
import request from '@/utils/request';
export default async function queryError(code) {
return request(`/api/${code}`);
}

View File

@ -0,0 +1,9 @@
import request from '@/utils/request';
export async function query() {
return request('/api/users');
}
export async function queryCurrent() {
return request('/api/currentUser');
}

View File

@ -0,0 +1,12 @@
import RenderAuthorized from '@/components/Authorized';
import { getAuthority } from './authority';
let Authorized = RenderAuthorized(getAuthority()); // eslint-disable-line
// Reload the rights component
const reloadAuthorized = () => {
Authorized = RenderAuthorized(getAuthority());
};
export { reloadAuthorized };
export default Authorized;

31
app/web/src/utils/Yuan.js Normal file
View File

@ -0,0 +1,31 @@
import React from 'react';
import { yuan } from '@/components/Charts';
/**
* 减少使用 dangerouslySetInnerHTML
*/
export default class Yuan extends React.PureComponent {
componentDidMount() {
this.rendertoHtml();
}
componentDidUpdate() {
this.rendertoHtml();
}
rendertoHtml = () => {
const { children } = this.props;
if (this.main) {
this.main.innerHTML = yuan(children);
}
};
render() {
return (
<span
ref={ref => {
this.main = ref;
}}
/>
);
}
}

View File

@ -0,0 +1,22 @@
// use localStorage to store the authority info, which might be sent from server in actual project.
export function getAuthority(str) {
// return localStorage.getItem('antd-pro-authority') || ['admin', 'user'];
const authorityString =
typeof str === 'undefined' ? localStorage.getItem('antd-pro-authority') : str;
// authorityString could be admin, "admin", ["admin"]
let authority;
try {
authority = JSON.parse(authorityString);
} catch (e) {
authority = authorityString;
}
if (typeof authority === 'string') {
return [authority];
}
return authority || ['admin'];
}
export function setAuthority(authority) {
const proAuthority = typeof authority === 'string' ? [authority] : authority;
return localStorage.setItem('antd-pro-authority', JSON.stringify(proAuthority));
}

View File

@ -0,0 +1,19 @@
import { getAuthority } from './authority';
describe('getAuthority should be strong', () => {
it('empty', () => {
expect(getAuthority(null)).toEqual(['admin']); // default value
});
it('string', () => {
expect(getAuthority('admin')).toEqual(['admin']);
});
it('array with double quotes', () => {
expect(getAuthority('"admin"')).toEqual(['admin']);
});
it('array with single item', () => {
expect(getAuthority('["admin"]')).toEqual(['admin']);
});
it('array with multiple items', () => {
expect(getAuthority('["admin", "guest"]')).toEqual(['admin', 'guest']);
});
});

View File

@ -0,0 +1,158 @@
import fetch from 'dva/fetch';
import { notification } from 'antd';
import {history} from 'umi';
import hash from 'hash.js';
import { isAntdPro } from './utils';
const codeMessage = {
200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。',
202: '一个请求已经进入后台排队(异步任务)。',
204: '删除数据成功。',
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
401: '用户没有权限(令牌、用户名、密码错误)。',
403: '用户得到授权,但是访问是被禁止的。',
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
406: '请求的格式不可得。',
410: '请求的资源被永久删除,且不会再得到的。',
422: '当创建一个对象时,发生一个验证错误。',
500: '服务器发生错误,请检查服务器。',
502: '网关错误。',
503: '服务不可用,服务器暂时过载或维护。',
504: '网关超时。',
};
const checkStatus = response => {
if (response.status >= 200 && response.status < 300) {
return response;
}
const errortext = codeMessage[response.status] || response.statusText;
notification.error({
message: `请求错误 ${response.status}: ${response.url}`,
description: errortext,
});
const error = new Error(errortext);
error.name = response.status;
error.response = response;
throw error;
};
const cachedSave = (response, hashcode) => {
/**
* Clone a response data and store it in sessionStorage
* Does not support data other than json, Cache only json
*/
const contentType = response.headers.get('Content-Type');
if (contentType && contentType.match(/application\/json/i)) {
// All data is saved as text
response
.clone()
.text()
.then(content => {
sessionStorage.setItem(hashcode, content);
sessionStorage.setItem(`${hashcode}:timestamp`, Date.now());
});
}
return response;
};
/**
* Requests a URL, returning a promise.
*
* @param {string} url The URL we want to request
* @param {object} [option] The options we want to pass to "fetch"
* @return {object} An object containing either "data" or "err"
*/
export default function request(
url,
option,
) {
const options = {
expirys: isAntdPro(),
...option,
};
/**
* Produce fingerprints based on url and parameters
* Maybe url has the same parameters
*/
const fingerprint = url + (options.body ? JSON.stringify(options.body) : '');
const hashcode = hash
.sha256()
.update(fingerprint)
.digest('hex');
const defaultOptions = {
credentials: 'include',
};
const newOptions = { ...defaultOptions, ...options };
if (
newOptions.method === 'POST' ||
newOptions.method === 'PUT' ||
newOptions.method === 'DELETE'
) {
if (!(newOptions.body instanceof FormData)) {
newOptions.headers = {
Accept: 'application/json',
'Content-Type': 'application/json; charset=utf-8',
...newOptions.headers,
};
newOptions.body = JSON.stringify(newOptions.body);
} else {
// newOptions.body is FormData
newOptions.headers = {
Accept: 'application/json',
...newOptions.headers,
};
}
}
const expirys = options.expirys && 60;
// options.expirys !== false, return the cache,
if (options.expirys !== false) {
const cached = sessionStorage.getItem(hashcode);
const whenCached = sessionStorage.getItem(`${hashcode}:timestamp`);
if (cached !== null && whenCached !== null) {
const age = (Date.now() - whenCached) / 1000;
if (age < expirys) {
const response = new Response(new Blob([cached]));
return response.json();
}
sessionStorage.removeItem(hashcode);
sessionStorage.removeItem(`${hashcode}:timestamp`);
}
}
return fetch(url, newOptions)
.then(checkStatus)
.then(response => cachedSave(response, hashcode))
.then(response => {
// DELETE and 204 do not return data by default
// using .json will report an error.
if (newOptions.method === 'DELETE' || response.status === 204) {
return response.text();
}
return response.json();
})
.catch(e => {
const status = e.name;
if (status === 401) {
// @HACK
/* eslint-disable no-underscore-dangle */
window.g_app._store.dispatch({
type: 'login/logout',
});
return;
}
// environment should not be used
if (status === 403) {
history.push('/exception/403');
return;
}
if (status <= 504 && status >= 500) {
history.push('/exception/500');
return;
}
if (status >= 404 && status < 422) {
history.push('/exception/404');
}
});
}

183
app/web/src/utils/utils.js Normal file
View File

@ -0,0 +1,183 @@
import moment from 'moment';
import React from 'react';
import nzh from 'nzh/cn';
import { parse, stringify } from 'qs';
export function fixedZero(val) {
return val * 1 < 10 ? `0${val}` : val;
}
export function getTimeDistance(type) {
const now = new Date();
const oneDay = 1000 * 60 * 60 * 24;
if (type === 'today') {
now.setHours(0);
now.setMinutes(0);
now.setSeconds(0);
return [moment(now), moment(now.getTime() + (oneDay - 1000))];
}
if (type === 'week') {
let day = now.getDay();
now.setHours(0);
now.setMinutes(0);
now.setSeconds(0);
if (day === 0) {
day = 6;
} else {
day -= 1;
}
const beginTime = now.getTime() - day * oneDay;
return [moment(beginTime), moment(beginTime + (7 * oneDay - 1000))];
}
if (type === 'month') {
const year = now.getFullYear();
const month = now.getMonth();
const nextDate = moment(now).add(1, 'months');
const nextYear = nextDate.year();
const nextMonth = nextDate.month();
return [
moment(`${year}-${fixedZero(month + 1)}-01 00:00:00`),
moment(moment(`${nextYear}-${fixedZero(nextMonth + 1)}-01 00:00:00`).valueOf() - 1000),
];
}
const year = now.getFullYear();
return [moment(`${year}-01-01 00:00:00`), moment(`${year}-12-31 23:59:59`)];
}
export function getPlainNode(nodeList, parentPath = '') {
const arr = [];
nodeList.forEach(node => {
const item = node;
item.path = `${parentPath}/${item.path || ''}`.replace(/\/+/g, '/');
item.exact = true;
if (item.children && !item.component) {
arr.push(...getPlainNode(item.children, item.path));
} else {
if (item.children && item.component) {
item.exact = false;
}
arr.push(item);
}
});
return arr;
}
export function digitUppercase(n) {
return nzh.toMoney(n);
}
function getRelation(str1, str2) {
if (str1 === str2) {
console.warn('Two path are equal!'); // eslint-disable-line
}
const arr1 = str1.split('/');
const arr2 = str2.split('/');
if (arr2.every((item, index) => item === arr1[index])) {
return 1;
}
if (arr1.every((item, index) => item === arr2[index])) {
return 2;
}
return 3;
}
function getRenderArr(routes) {
let renderArr = [];
renderArr.push(routes[0]);
for (let i = 1; i < routes.length; i += 1) {
// 去重
renderArr = renderArr.filter(item => getRelation(item, routes[i]) !== 1);
// 是否包含
const isAdd = renderArr.every(item => getRelation(item, routes[i]) === 3);
if (isAdd) {
renderArr.push(routes[i]);
}
}
return renderArr;
}
/**
* Get router routing configuration
* { path:{name,...param}}=>Array<{name,path ...param}>
* @param {string} path
* @param {routerData} routerData
*/
export function getRoutes(path, routerData) {
let routes = Object.keys(routerData).filter(
routePath => routePath.indexOf(path) === 0 && routePath !== path
);
// Replace path to '' eg. path='user' /user/name => name
routes = routes.map(item => item.replace(path, ''));
// Get the route to be rendered to remove the deep rendering
const renderArr = getRenderArr(routes);
// Conversion and stitching parameters
const renderRoutes = renderArr.map(item => {
const exact = !routes.some(route => route !== item && getRelation(route, item) === 1);
return {
exact,
...routerData[`${path}${item}`],
key: `${path}${item}`,
path: `${path}${item}`,
};
});
return renderRoutes;
}
export function getPageQuery() {
return parse(window.location.href.split('?')[1]);
}
export function getQueryPath(path = '', query = {}) {
const search = stringify(query);
if (search.length) {
return `${path}?${search}`;
}
return path;
}
/* eslint no-useless-escape:0 */
const reg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/;
export function isUrl(path) {
return reg.test(path);
}
export function formatWan(val) {
const v = val * 1;
if (!v || Number.isNaN(v)) return '';
let result = val;
if (val > 10000) {
result = Math.floor(val / 10000);
result = (
<span>
{result}
<span
styles={{
position: 'relative',
top: -2,
fontSize: 14,
fontStyle: 'normal',
lineHeight: 20,
marginLeft: 2,
}}
>
</span>
</span>
);
}
return result;
}
export function isAntdPro() {
return window.location.hostname === 'preview.pro.ant.design';
}

View File

@ -0,0 +1,50 @@
.textOverflow() {
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
white-space: nowrap;
}
.textOverflowMulti(@line: 3, @bg: #fff) {
overflow: hidden;
position: relative;
line-height: 1.5em;
max-height: @line * 1.5em;
text-align: justify;
margin-right: -1em;
padding-right: 1em;
&:before {
background: @bg;
content: '...';
padding: 0 1px;
position: absolute;
right: 14px;
bottom: 0;
}
&:after {
background: white;
content: '';
margin-top: 0.2em;
position: absolute;
right: 14px;
width: 1em;
height: 1em;
}
}
// mixins for clearfix
// ------------------------
.clearfix() {
zoom: 1;
&:before,
&:after {
content: ' ';
display: table;
}
&:after {
clear: both;
visibility: hidden;
font-size: 0;
height: 0;
}
}

View File

@ -0,0 +1,64 @@
import { fixedZero, isUrl } from './utils';
describe('fixedZero tests', () => {
it('should not pad large numbers', () => {
expect(fixedZero(10)).toEqual(10);
expect(fixedZero(11)).toEqual(11);
expect(fixedZero(15)).toEqual(15);
expect(fixedZero(20)).toEqual(20);
expect(fixedZero(100)).toEqual(100);
expect(fixedZero(1000)).toEqual(1000);
});
it('should pad single digit numbers and return them as string', () => {
expect(fixedZero(0)).toEqual('00');
expect(fixedZero(1)).toEqual('01');
expect(fixedZero(2)).toEqual('02');
expect(fixedZero(3)).toEqual('03');
expect(fixedZero(4)).toEqual('04');
expect(fixedZero(5)).toEqual('05');
expect(fixedZero(6)).toEqual('06');
expect(fixedZero(7)).toEqual('07');
expect(fixedZero(8)).toEqual('08');
expect(fixedZero(9)).toEqual('09');
});
});
describe('isUrl tests', () => {
it('should return false for invalid and corner case inputs', () => {
expect(isUrl([])).toBeFalsy();
expect(isUrl({})).toBeFalsy();
expect(isUrl(false)).toBeFalsy();
expect(isUrl(true)).toBeFalsy();
expect(isUrl(NaN)).toBeFalsy();
expect(isUrl(null)).toBeFalsy();
expect(isUrl(undefined)).toBeFalsy();
expect(isUrl()).toBeFalsy();
expect(isUrl('')).toBeFalsy();
});
it('should return false for invalid URLs', () => {
expect(isUrl('foo')).toBeFalsy();
expect(isUrl('bar')).toBeFalsy();
expect(isUrl('bar/test')).toBeFalsy();
expect(isUrl('http:/example.com/')).toBeFalsy();
expect(isUrl('ttp://example.com/')).toBeFalsy();
});
it('should return true for valid URLs', () => {
expect(isUrl('http://example.com/')).toBeTruthy();
expect(isUrl('https://example.com/')).toBeTruthy();
expect(isUrl('http://example.com/test/123')).toBeTruthy();
expect(isUrl('https://example.com/test/123')).toBeTruthy();
expect(isUrl('http://example.com/test/123?foo=bar')).toBeTruthy();
expect(isUrl('https://example.com/test/123?foo=bar')).toBeTruthy();
expect(isUrl('http://www.example.com/')).toBeTruthy();
expect(isUrl('https://www.example.com/')).toBeTruthy();
expect(isUrl('http://www.example.com/test/123')).toBeTruthy();
expect(isUrl('https://www.example.com/test/123')).toBeTruthy();
expect(isUrl('http://www.example.com/test/123?foo=bar')).toBeTruthy();
expect(isUrl('https://www.example.com/test/123?foo=bar')).toBeTruthy();
});
});