提交 b01c12f30a4c1bb6c298bbe60bb1c022c191ca99

作者 愚道
1 个父辈 22f683f3

init pro block user login

  1 +/yarn.lock
  2 +/package-lock.json
  3 +/dist
  4 +/node_modules
  5 +
  6 +.umi
  7 +.umi-production
  1 +export default {
  2 + plugins: [
  3 + ['umi-plugin-block-dev', {}],
  4 + ['umi-plugin-react', {
  5 + dva: true,
  6 + locale: true,
  7 + antd: true,
  8 + }]
  9 + ],
  10 +}
  1 +import { stringify } from 'qs';
  2 +import request from '@/utils/request';
  3 +
  4 +export async function queryProjectNotice() {
  5 + return request('/api/project/notice');
  6 +}
  7 +
  8 +export async function queryActivities() {
  9 + return request('/api/activities');
  10 +}
  11 +
  12 +export async function queryRule(params) {
  13 + return request(`/api/rule?${stringify(params)}`);
  14 +}
  15 +
  16 +export async function removeRule(params) {
  17 + return request('/api/rule', {
  18 + method: 'POST',
  19 + body: {
  20 + ...params,
  21 + method: 'delete',
  22 + },
  23 + });
  24 +}
  25 +
  26 +export async function addRule(params) {
  27 + return request('/api/rule', {
  28 + method: 'POST',
  29 + body: {
  30 + ...params,
  31 + method: 'post',
  32 + },
  33 + });
  34 +}
  35 +
  36 +export async function updateRule(params) {
  37 + return request('/api/rule', {
  38 + method: 'POST',
  39 + body: {
  40 + ...params,
  41 + method: 'update',
  42 + },
  43 + });
  44 +}
  45 +
  46 +export async function fakeSubmitForm(params) {
  47 + return request('/api/forms', {
  48 + method: 'POST',
  49 + body: params,
  50 + });
  51 +}
  52 +
  53 +export async function fakeChartData() {
  54 + return request('/api/fake_chart_data');
  55 +}
  56 +
  57 +export async function queryTags() {
  58 + return request('/api/tags');
  59 +}
  60 +
  61 +export async function queryBasicProfile() {
  62 + return request('/api/profile/basic');
  63 +}
  64 +
  65 +export async function queryAdvancedProfile() {
  66 + return request('/api/profile/advanced');
  67 +}
  68 +
  69 +export async function queryFakeList(params) {
  70 + return request(`/api/fake_list?${stringify(params)}`);
  71 +}
  72 +
  73 +export async function removeFakeList(params) {
  74 + const { count = 5, ...restParams } = params;
  75 + return request(`/api/fake_list?count=${count}`, {
  76 + method: 'POST',
  77 + body: {
  78 + ...restParams,
  79 + method: 'delete',
  80 + },
  81 + });
  82 +}
  83 +
  84 +export async function addFakeList(params) {
  85 + const { count = 5, ...restParams } = params;
  86 + return request(`/api/fake_list?count=${count}`, {
  87 + method: 'POST',
  88 + body: {
  89 + ...restParams,
  90 + method: 'post',
  91 + },
  92 + });
  93 +}
  94 +
  95 +export async function updateFakeList(params) {
  96 + const { count = 5, ...restParams } = params;
  97 + return request(`/api/fake_list?count=${count}`, {
  98 + method: 'POST',
  99 + body: {
  100 + ...restParams,
  101 + method: 'update',
  102 + },
  103 + });
  104 +}
  105 +
  106 +export async function fakeAccountLogin(params) {
  107 + return request('/api/login/account', {
  108 + method: 'POST',
  109 + body: params,
  110 + });
  111 +}
  112 +
  113 +export async function fakeRegister(params) {
  114 + return request('/api/register', {
  115 + method: 'POST',
  116 + body: params,
  117 + });
  118 +}
  119 +
  120 +export async function queryNotices() {
  121 + return request('/api/notices');
  122 +}
  123 +
  124 +export async function getFakeCaptcha(mobile) {
  125 + return request(`/api/captcha?mobile=${mobile}`);
  126 +}
  1 +import RenderAuthorized from 'ant-design-pro/lib/Authorized';
  2 +import { getAuthority } from './authority';
  3 +
  4 +let Authorized = RenderAuthorized(getAuthority()); // eslint-disable-line
  5 +
  6 +// Reload the rights component
  7 +const reloadAuthorized = () => {
  8 + Authorized = RenderAuthorized(getAuthority());
  9 +};
  10 +
  11 +export { reloadAuthorized };
  12 +export default Authorized;
  1 +// use localStorage to store the authority info, which might be sent from server in actual project.
  2 +export function getAuthority(str) {
  3 + // return localStorage.getItem('antd-pro-authority') || ['admin', 'user'];
  4 + const authorityString =
  5 + typeof str === 'undefined' ? localStorage.getItem('antd-pro-authority') : str;
  6 + // authorityString could be admin, "admin", ["admin"]
  7 + let authority;
  8 + try {
  9 + authority = JSON.parse(authorityString);
  10 + } catch (e) {
  11 + authority = authorityString;
  12 + }
  13 + if (typeof authority === 'string') {
  14 + return [authority];
  15 + }
  16 + return authority || ['admin'];
  17 +}
  18 +
  19 +export function setAuthority(authority) {
  20 + const proAuthority = typeof authority === 'string' ? [authority] : authority;
  21 + return localStorage.setItem('antd-pro-authority', JSON.stringify(proAuthority));
  22 +}
  1 +import fetch from 'dva/fetch';
  2 +import { notification } from 'antd';
  3 +import router from 'umi/router';
  4 +import hash from 'hash.js';
  5 +import { isAntdPro } from './utils';
  6 +
  7 +const codeMessage = {
  8 + 200: '服务器成功返回请求的数据。',
  9 + 201: '新建或修改数据成功。',
  10 + 202: '一个请求已经进入后台排队(异步任务)。',
  11 + 204: '删除数据成功。',
  12 + 400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
  13 + 401: '用户没有权限(令牌、用户名、密码错误)。',
  14 + 403: '用户得到授权,但是访问是被禁止的。',
  15 + 404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
  16 + 406: '请求的格式不可得。',
  17 + 410: '请求的资源被永久删除,且不会再得到的。',
  18 + 422: '当创建一个对象时,发生一个验证错误。',
  19 + 500: '服务器发生错误,请检查服务器。',
  20 + 502: '网关错误。',
  21 + 503: '服务不可用,服务器暂时过载或维护。',
  22 + 504: '网关超时。',
  23 +};
  24 +
  25 +const checkStatus = response => {
  26 + if (response.status >= 200 && response.status < 300) {
  27 + return response;
  28 + }
  29 + const errortext = codeMessage[response.status] || response.statusText;
  30 + notification.error({
  31 + message: `请求错误 ${response.status}: ${response.url}`,
  32 + description: errortext,
  33 + });
  34 + const error = new Error(errortext);
  35 + error.name = response.status;
  36 + error.response = response;
  37 + throw error;
  38 +};
  39 +
  40 +const cachedSave = (response, hashcode) => {
  41 + /**
  42 + * Clone a response data and store it in sessionStorage
  43 + * Does not support data other than json, Cache only json
  44 + */
  45 + const contentType = response.headers.get('Content-Type');
  46 + if (contentType && contentType.match(/application\/json/i)) {
  47 + // All data is saved as text
  48 + response
  49 + .clone()
  50 + .text()
  51 + .then(content => {
  52 + sessionStorage.setItem(hashcode, content);
  53 + sessionStorage.setItem(`${hashcode}:timestamp`, Date.now());
  54 + });
  55 + }
  56 + return response;
  57 +};
  58 +
  59 +/**
  60 + * Requests a URL, returning a promise.
  61 + *
  62 + * @param {string} url The URL we want to request
  63 + * @param {object} [option] The options we want to pass to "fetch"
  64 + * @return {object} An object containing either "data" or "err"
  65 + */
  66 +export default function request(url, option) {
  67 + const options = {
  68 + expirys: isAntdPro(),
  69 + ...option,
  70 + };
  71 + /**
  72 + * Produce fingerprints based on url and parameters
  73 + * Maybe url has the same parameters
  74 + */
  75 + const fingerprint = url + (options.body ? JSON.stringify(options.body) : '');
  76 + const hashcode = hash
  77 + .sha256()
  78 + .update(fingerprint)
  79 + .digest('hex');
  80 +
  81 + const defaultOptions = {
  82 + credentials: 'include',
  83 + };
  84 + const newOptions = { ...defaultOptions, ...options };
  85 + if (
  86 + newOptions.method === 'POST' ||
  87 + newOptions.method === 'PUT' ||
  88 + newOptions.method === 'DELETE'
  89 + ) {
  90 + if (!(newOptions.body instanceof FormData)) {
  91 + newOptions.headers = {
  92 + Accept: 'application/json',
  93 + 'Content-Type': 'application/json; charset=utf-8',
  94 + ...newOptions.headers,
  95 + };
  96 + newOptions.body = JSON.stringify(newOptions.body);
  97 + } else {
  98 + // newOptions.body is FormData
  99 + newOptions.headers = {
  100 + Accept: 'application/json',
  101 + ...newOptions.headers,
  102 + };
  103 + }
  104 + }
  105 +
  106 + const expirys = options.expirys && 60;
  107 + // options.expirys !== false, return the cache,
  108 + if (options.expirys !== false) {
  109 + const cached = sessionStorage.getItem(hashcode);
  110 + const whenCached = sessionStorage.getItem(`${hashcode}:timestamp`);
  111 + if (cached !== null && whenCached !== null) {
  112 + const age = (Date.now() - whenCached) / 1000;
  113 + if (age < expirys) {
  114 + const response = new Response(new Blob([cached]));
  115 + return response.json();
  116 + }
  117 + sessionStorage.removeItem(hashcode);
  118 + sessionStorage.removeItem(`${hashcode}:timestamp`);
  119 + }
  120 + }
  121 + return fetch(url, newOptions)
  122 + .then(checkStatus)
  123 + .then(response => cachedSave(response, hashcode))
  124 + .then(response => {
  125 + // DELETE and 204 do not return data by default
  126 + // using .json will report an error.
  127 + if (newOptions.method === 'DELETE' || response.status === 204) {
  128 + return response.text();
  129 + }
  130 + return response.json();
  131 + })
  132 + .catch(e => {
  133 + const status = e.name;
  134 + if (status === 401) {
  135 + // @HACK
  136 + /* eslint-disable no-underscore-dangle */
  137 + window.g_app._store.dispatch({
  138 + type: 'login/logout',
  139 + });
  140 + return;
  141 + }
  142 + // environment should not be used
  143 + if (status === 403) {
  144 + router.push('/exception/403');
  145 + return;
  146 + }
  147 + if (status <= 504 && status >= 500) {
  148 + router.push('/exception/500');
  149 + return;
  150 + }
  151 + if (status >= 404 && status < 422) {
  152 + router.push('/exception/404');
  153 + }
  154 + });
  155 +}
  1 +import moment from 'moment';
  2 +import React from 'react';
  3 +import nzh from 'nzh/cn';
  4 +import { parse, stringify } from 'qs';
  5 +
  6 +export function fixedZero(val) {
  7 + return val * 1 < 10 ? `0${val}` : val;
  8 +}
  9 +
  10 +export function getTimeDistance(type) {
  11 + const now = new Date();
  12 + const oneDay = 1000 * 60 * 60 * 24;
  13 +
  14 + if (type === 'today') {
  15 + now.setHours(0);
  16 + now.setMinutes(0);
  17 + now.setSeconds(0);
  18 + return [moment(now), moment(now.getTime() + (oneDay - 1000))];
  19 + }
  20 +
  21 + if (type === 'week') {
  22 + let day = now.getDay();
  23 + now.setHours(0);
  24 + now.setMinutes(0);
  25 + now.setSeconds(0);
  26 +
  27 + if (day === 0) {
  28 + day = 6;
  29 + } else {
  30 + day -= 1;
  31 + }
  32 +
  33 + const beginTime = now.getTime() - day * oneDay;
  34 +
  35 + return [moment(beginTime), moment(beginTime + (7 * oneDay - 1000))];
  36 + }
  37 +
  38 + if (type === 'month') {
  39 + const year = now.getFullYear();
  40 + const month = now.getMonth();
  41 + const nextDate = moment(now).add(1, 'months');
  42 + const nextYear = nextDate.year();
  43 + const nextMonth = nextDate.month();
  44 +
  45 + return [
  46 + moment(`${year}-${fixedZero(month + 1)}-01 00:00:00`),
  47 + moment(moment(`${nextYear}-${fixedZero(nextMonth + 1)}-01 00:00:00`).valueOf() - 1000),
  48 + ];
  49 + }
  50 +
  51 + const year = now.getFullYear();
  52 + return [moment(`${year}-01-01 00:00:00`), moment(`${year}-12-31 23:59:59`)];
  53 +}
  54 +
  55 +export function getPlainNode(nodeList, parentPath = '') {
  56 + const arr = [];
  57 + nodeList.forEach(node => {
  58 + const item = node;
  59 + item.path = `${parentPath}/${item.path || ''}`.replace(/\/+/g, '/');
  60 + item.exact = true;
  61 + if (item.children && !item.component) {
  62 + arr.push(...getPlainNode(item.children, item.path));
  63 + } else {
  64 + if (item.children && item.component) {
  65 + item.exact = false;
  66 + }
  67 + arr.push(item);
  68 + }
  69 + });
  70 + return arr;
  71 +}
  72 +
  73 +export function digitUppercase(n) {
  74 + return nzh.toMoney(n);
  75 +}
  76 +
  77 +function getRelation(str1, str2) {
  78 + if (str1 === str2) {
  79 + console.warn('Two path are equal!'); // eslint-disable-line
  80 + }
  81 + const arr1 = str1.split('/');
  82 + const arr2 = str2.split('/');
  83 + if (arr2.every((item, index) => item === arr1[index])) {
  84 + return 1;
  85 + }
  86 + if (arr1.every((item, index) => item === arr2[index])) {
  87 + return 2;
  88 + }
  89 + return 3;
  90 +}
  91 +
  92 +function getRenderArr(routes) {
  93 + let renderArr = [];
  94 + renderArr.push(routes[0]);
  95 + for (let i = 1; i < routes.length; i += 1) {
  96 + // 去重
  97 + renderArr = renderArr.filter(item => getRelation(item, routes[i]) !== 1);
  98 + // 是否包含
  99 + const isAdd = renderArr.every(item => getRelation(item, routes[i]) === 3);
  100 + if (isAdd) {
  101 + renderArr.push(routes[i]);
  102 + }
  103 + }
  104 + return renderArr;
  105 +}
  106 +
  107 +/**
  108 + * Get router routing configuration
  109 + * { path:{name,...param}}=>Array<{name,path ...param}>
  110 + * @param {string} path
  111 + * @param {routerData} routerData
  112 + */
  113 +export function getRoutes(path, routerData) {
  114 + let routes = Object.keys(routerData).filter(
  115 + routePath => routePath.indexOf(path) === 0 && routePath !== path
  116 + );
  117 + // Replace path to '' eg. path='user' /user/name => name
  118 + routes = routes.map(item => item.replace(path, ''));
  119 + // Get the route to be rendered to remove the deep rendering
  120 + const renderArr = getRenderArr(routes);
  121 + // Conversion and stitching parameters
  122 + const renderRoutes = renderArr.map(item => {
  123 + const exact = !routes.some(route => route !== item && getRelation(route, item) === 1);
  124 + return {
  125 + exact,
  126 + ...routerData[`${path}${item}`],
  127 + key: `${path}${item}`,
  128 + path: `${path}${item}`,
  129 + };
  130 + });
  131 + return renderRoutes;
  132 +}
  133 +
  134 +export function getPageQuery() {
  135 + return parse(window.location.href.split('?')[1]);
  136 +}
  137 +
  138 +export function getQueryPath(path = '', query = {}) {
  139 + const search = stringify(query);
  140 + if (search.length) {
  141 + return `${path}?${search}`;
  142 + }
  143 + return path;
  144 +}
  145 +
  146 +/* eslint no-useless-escape:0 */
  147 +const reg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/;
  148 +
  149 +export function isUrl(path) {
  150 + return reg.test(path);
  151 +}
  152 +
  153 +export function formatWan(val) {
  154 + const v = val * 1;
  155 + if (!v || Number.isNaN(v)) return '';
  156 +
  157 + let result = val;
  158 + if (val > 10000) {
  159 + result = Math.floor(val / 10000);
  160 + result = (
  161 + <span>
  162 + {result}
  163 + <span
  164 + style={{
  165 + position: 'relative',
  166 + top: -2,
  167 + fontSize: 14,
  168 + fontStyle: 'normal',
  169 + marginLeft: 2,
  170 + }}
  171 + >
  172 +
  173 + </span>
  174 + </span>
  175 + );
  176 + }
  177 + return result;
  178 +}
  179 +
  180 +// 给官方演示站点用,用于关闭真实开发环境不需要使用的特性
  181 +export function isAntdPro() {
  182 + return window.location.hostname === 'preview.pro.ant.design';
  183 +}
  1 +# @umi-material/userlogin
  2 +
  3 +UserLogin
  4 +
  5 +## Usage
  6 +
  7 +```sh
  8 +umi block https://github.com/umijs/umi-blocks/tree/master/userlogin
  9 +```
  10 +
  11 +## LICENSE
  12 +
  13 +MIT
  1 +{
  2 + "name": "@umi-block/userlogin",
  3 + "version": "0.0.1",
  4 + "description": "UserLogin",
  5 + "main": "src/index.js",
  6 + "scripts": {
  7 + "dev": "umi dev"
  8 + },
  9 + "repository": {
  10 + "type": "git",
  11 + "url": "https://github.com/umijs/umi-blocks/userlogin"
  12 + },
  13 + "dependencies": {
  14 + "react": "^16.6.3",
  15 + "dva": "^2.4.0",
  16 + "antd": "^3.10.9",
  17 + "ant-design-pro": "^2.1.1",
  18 + "qs": "^6.6.0",
  19 + "hash.js": "^1.1.5",
  20 + "moment": "^2.22.2",
  21 + "nzh": "^1.0.3"
  22 + },
  23 + "devDependencies": {
  24 + "umi": "^2.3.0-beta.1",
  25 + "umi-plugin-react": "^1.3.0-beta.1",
  26 + "umi-plugin-block-dev": "^1.0.0"
  27 + },
  28 + "license": "ISC"
  29 +}
  1 +@import '~antd/lib/style/themes/default.less';
  2 +
  3 +.main {
  4 + width: 368px;
  5 + margin: 0 auto;
  6 + @media screen and (max-width: @screen-sm) {
  7 + width: 95%;
  8 + }
  9 +
  10 + .icon {
  11 + font-size: 24px;
  12 + color: rgba(0, 0, 0, 0.2);
  13 + margin-left: 16px;
  14 + vertical-align: middle;
  15 + cursor: pointer;
  16 + transition: color 0.3s;
  17 +
  18 + &:hover {
  19 + color: @primary-color;
  20 + }
  21 + }
  22 +
  23 + .other {
  24 + text-align: left;
  25 + margin-top: 24px;
  26 + line-height: 22px;
  27 +
  28 + .register {
  29 + float: right;
  30 + }
  31 + }
  32 +}
  1 +// 代码中会兼容本地 service mock 以及部署站点的静态数据
  2 +export default {
  3 + // 支持值为 Object 和 Array
  4 + 'GET /api/currentUser': {
  5 + name: 'Serati Ma',
  6 + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
  7 + userid: '00000001',
  8 + email: 'antdesign@alipay.com',
  9 + signature: '海纳百川,有容乃大',
  10 + title: '交互专家',
  11 + group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED',
  12 + tags: [
  13 + {
  14 + key: '0',
  15 + label: '很有想法的',
  16 + },
  17 + {
  18 + key: '1',
  19 + label: '专注设计',
  20 + },
  21 + {
  22 + key: '2',
  23 + label: '辣~',
  24 + },
  25 + {
  26 + key: '3',
  27 + label: '大长腿',
  28 + },
  29 + {
  30 + key: '4',
  31 + label: '川妹子',
  32 + },
  33 + {
  34 + key: '5',
  35 + label: '海纳百川',
  36 + },
  37 + ],
  38 + notifyCount: 12,
  39 + unreadCount: 11,
  40 + country: 'China',
  41 + geographic: {
  42 + province: {
  43 + label: '浙江省',
  44 + key: '330000',
  45 + },
  46 + city: {
  47 + label: '杭州市',
  48 + key: '330100',
  49 + },
  50 + },
  51 + address: '西湖区工专路 77 号',
  52 + phone: '0752-268888888',
  53 + },
  54 + // GET POST 可省略
  55 + 'GET /api/users': [
  56 + {
  57 + key: '1',
  58 + name: 'John Brown',
  59 + age: 32,
  60 + address: 'New York No. 1 Lake Park',
  61 + },
  62 + {
  63 + key: '2',
  64 + name: 'Jim Green',
  65 + age: 42,
  66 + address: 'London No. 1 Lake Park',
  67 + },
  68 + {
  69 + key: '3',
  70 + name: 'Joe Black',
  71 + age: 32,
  72 + address: 'Sidney No. 1 Lake Park',
  73 + },
  74 + ],
  75 + 'POST /api/login/account': (req, res) => {
  76 + const { password, userName, type } = req.body;
  77 + if (password === 'ant.design' && userName === 'admin') {
  78 + res.send({
  79 + status: 'ok',
  80 + type,
  81 + currentAuthority: 'admin',
  82 + });
  83 + return;
  84 + }
  85 + if (password === 'ant.design' && userName === 'user') {
  86 + res.send({
  87 + status: 'ok',
  88 + type,
  89 + currentAuthority: 'user',
  90 + });
  91 + return;
  92 + }
  93 + res.send({
  94 + status: 'error',
  95 + type,
  96 + currentAuthority: 'guest',
  97 + });
  98 + },
  99 + 'POST /api/register': (req, res) => {
  100 + res.send({ status: 'ok', currentAuthority: 'user' });
  101 + },
  102 + 'GET /api/500': (req, res) => {
  103 + res.status(500).send({
  104 + timestamp: 1513932555104,
  105 + status: 500,
  106 + error: 'error',
  107 + message: 'error',
  108 + path: '/base/category/list',
  109 + });
  110 + },
  111 + 'GET /api/404': (req, res) => {
  112 + res.status(404).send({
  113 + timestamp: 1513932643431,
  114 + status: 404,
  115 + error: 'Not Found',
  116 + message: 'No message available',
  117 + path: '/base/category/list/2121212',
  118 + });
  119 + },
  120 + 'GET /api/403': (req, res) => {
  121 + res.status(403).send({
  122 + timestamp: 1513932555104,
  123 + status: 403,
  124 + error: 'Unauthorized',
  125 + message: 'Unauthorized',
  126 + path: '/base/category/list',
  127 + });
  128 + },
  129 + 'GET /api/401': (req, res) => {
  130 + res.status(401).send({
  131 + timestamp: 1513932555104,
  132 + status: 401,
  133 + error: 'Unauthorized',
  134 + message: 'Unauthorized',
  135 + path: '/base/category/list',
  136 + });
  137 + },
  138 +};
  1 +module.exports = {
  2 + navTheme: 'dark', // theme for nav menu
  3 + primaryColor: '#1890FF', // primary color of ant design
  4 + layout: 'sidemenu', // nav menu position: sidemenu or topmenu
  5 + contentWidth: 'Fluid', // layout of content: Fluid or Fixed, only works when layout is topmenu
  6 + fixedHeader: false, // sticky header
  7 + autoHideHeader: false, // auto hide header
  8 + fixSiderbar: false, // sticky siderbar
  9 +};
  1 +import React, { Component } from 'react';
  2 +import { connect } from 'dva';
  3 +import { formatMessage, FormattedMessage } from 'umi/locale';
  4 +import Link from 'umi/link';
  5 +import { Checkbox, Alert, Icon } from 'antd';
  6 +import Login from 'ant-design-pro/lib/Login';
  7 +import styles from './Login.less';
  8 +
  9 +const { Tab, UserName, Password, Mobile, Captcha, Submit } = Login;
  10 +
  11 +@connect(({ login, loading }) => ({
  12 + login,
  13 + submitting: loading.effects['login/login'],
  14 +}))
  15 +class LoginPage extends Component {
  16 + state = {
  17 + type: 'account',
  18 + autoLogin: true,
  19 + };
  20 +
  21 + onTabChange = type => {
  22 + this.setState({ type });
  23 + };
  24 +
  25 + onGetCaptcha = () =>
  26 + new Promise((resolve, reject) => {
  27 + this.loginForm.validateFields(['mobile'], {}, (err, values) => {
  28 + if (err) {
  29 + reject(err);
  30 + } else {
  31 + const { dispatch } = this.props;
  32 + dispatch({
  33 + type: 'login/getCaptcha',
  34 + payload: values.mobile,
  35 + })
  36 + .then(resolve)
  37 + .catch(reject);
  38 + }
  39 + });
  40 + });
  41 +
  42 + handleSubmit = (err, values) => {
  43 + const { type } = this.state;
  44 + if (!err) {
  45 + const { dispatch } = this.props;
  46 + dispatch({
  47 + type: 'login/login',
  48 + payload: {
  49 + ...values,
  50 + type,
  51 + },
  52 + });
  53 + }
  54 + };
  55 +
  56 + changeAutoLogin = e => {
  57 + this.setState({
  58 + autoLogin: e.target.checked,
  59 + });
  60 + };
  61 +
  62 + renderMessage = content => (
  63 + <Alert style={{ marginBottom: 24 }} message={content} type="error" showIcon />
  64 + );
  65 +
  66 + render() {
  67 + const { login, submitting } = this.props;
  68 + const { type, autoLogin } = this.state;
  69 + return (
  70 + <div className={styles.main}>
  71 + <Login
  72 + defaultActiveKey={type}
  73 + onTabChange={this.onTabChange}
  74 + onSubmit={this.handleSubmit}
  75 + ref={form => {
  76 + this.loginForm = form;
  77 + }}
  78 + >
  79 + <Tab key="account" tab={formatMessage({ id: 'app.login.tab-login-credentials' })}>
  80 + {login.status === 'error' &&
  81 + login.type === 'account' &&
  82 + !submitting &&
  83 + this.renderMessage(formatMessage({ id: 'app.login.message-invalid-credentials' }))}
  84 + <UserName
  85 + name="userName"
  86 + placeholder={`${formatMessage({ id: 'app.login.userName' })}: admin or user`}
  87 + rules={[
  88 + {
  89 + required: true,
  90 + message: formatMessage({ id: 'validation.userName.required' }),
  91 + },
  92 + ]}
  93 + />
  94 + <Password
  95 + name="password"
  96 + placeholder={`${formatMessage({ id: 'app.login.password' })}: ant.design`}
  97 + rules={[
  98 + {
  99 + required: true,
  100 + message: formatMessage({ id: 'validation.password.required' }),
  101 + },
  102 + ]}
  103 + onPressEnter={() => this.loginForm.validateFields(this.handleSubmit)}
  104 + />
  105 + </Tab>
  106 + <Tab key="mobile" tab={formatMessage({ id: 'app.login.tab-login-mobile' })}>
  107 + {login.status === 'error' &&
  108 + login.type === 'mobile' &&
  109 + !submitting &&
  110 + this.renderMessage(
  111 + formatMessage({ id: 'app.login.message-invalid-verification-code' })
  112 + )}
  113 + <Mobile
  114 + name="mobile"
  115 + placeholder={formatMessage({ id: 'form.phone-number.placeholder' })}
  116 + rules={[
  117 + {
  118 + required: true,
  119 + message: formatMessage({ id: 'validation.phone-number.required' }),
  120 + },
  121 + {
  122 + pattern: /^1\d{10}$/,
  123 + message: formatMessage({ id: 'validation.phone-number.wrong-format' }),
  124 + },
  125 + ]}
  126 + />
  127 + <Captcha
  128 + name="captcha"
  129 + placeholder={formatMessage({ id: 'form.verification-code.placeholder' })}
  130 + countDown={120}
  131 + onGetCaptcha={this.onGetCaptcha}
  132 + getCaptchaButtonText={formatMessage({ id: 'form.get-captcha' })}
  133 + getCaptchaSecondText={formatMessage({ id: 'form.captcha.second' })}
  134 + rules={[
  135 + {
  136 + required: true,
  137 + message: formatMessage({ id: 'validation.verification-code.required' }),
  138 + },
  139 + ]}
  140 + />
  141 + </Tab>
  142 + <div>
  143 + <Checkbox checked={autoLogin} onChange={this.changeAutoLogin}>
  144 + <FormattedMessage id="app.login.remember-me" />
  145 + </Checkbox>
  146 + <a style={{ float: 'right' }} href="">
  147 + <FormattedMessage id="app.login.forgot-password" />
  148 + </a>
  149 + </div>
  150 + <Submit loading={submitting}>
  151 + <FormattedMessage id="app.login.login" />
  152 + </Submit>
  153 + <div className={styles.other}>
  154 + <FormattedMessage id="app.login.sign-in-with" />
  155 + <Icon type="alipay-circle" className={styles.icon} theme="outlined" />
  156 + <Icon type="taobao-circle" className={styles.icon} theme="outlined" />
  157 + <Icon type="weibo-circle" className={styles.icon} theme="outlined" />
  158 + <Link className={styles.register} to="/user/register">
  159 + <FormattedMessage id="app.login.signup" />
  160 + </Link>
  161 + </div>
  162 + </Login>
  163 + </div>
  164 + );
  165 + }
  166 +}
  167 +
  168 +export default LoginPage;
  1 +export default {
  2 + 'app.login.userName': 'userName',
  3 + 'app.login.password': 'password',
  4 + 'app.login.message-invalid-credentials': 'Invalid username or password(admin/ant.design)',
  5 + 'app.login.message-invalid-verification-code': 'Invalid verification code',
  6 + 'app.login.tab-login-credentials': 'Credentials',
  7 + 'app.login.tab-login-mobile': 'Mobile number',
  8 + 'app.login.remember-me': 'Remember me',
  9 + 'app.login.forgot-password': 'Forgot your password?',
  10 + 'app.login.sign-in-with': 'Sign in with',
  11 + 'app.login.signup': 'Sign up',
  12 + 'app.login.login': 'Login',
  13 + 'app.register.register': 'Register',
  14 + 'app.register.get-verification-code': 'Get code',
  15 + 'app.register.sign-in': 'Already have an account?',
  16 + 'app.register-result.msg': 'Account:registered at {email}',
  17 + 'app.register-result.activation-email':
  18 + 'The activation email has been sent to your email address and is valid for 24 hours. Please log in to the email in time and click on the link in the email to activate the account.',
  19 + 'app.register-result.back-home': 'Back to home',
  20 + 'app.register-result.view-mailbox': 'View mailbox',
  21 + 'validation.email.required': 'Please enter your email!',
  22 + 'validation.email.wrong-format': 'The email address is in the wrong format!',
  23 + 'validation.userName.required': 'Please enter your userName!',
  24 + 'validation.password.required': 'Please enter your password!',
  25 + 'validation.password.twice': 'The passwords entered twice do not match!',
  26 + 'validation.password.strength.msg':
  27 + "Please enter at least 6 characters and don't use passwords that are easy to guess.",
  28 + 'validation.password.strength.strong': 'Strength: strong',
  29 + 'validation.password.strength.medium': 'Strength: medium',
  30 + 'validation.password.strength.short': 'Strength: too short',
  31 + 'validation.confirm-password.required': 'Please confirm your password!',
  32 + 'validation.phone-number.required': 'Please enter your phone number!',
  33 + 'validation.phone-number.wrong-format': 'Malformed phone number!',
  34 + 'validation.verification-code.required': 'Please enter the verification code!',
  35 + 'validation.title.required': 'Please enter a title',
  36 + 'validation.date.required': 'Please select the start and end date',
  37 + 'validation.goal.required': 'Please enter a description of the goal',
  38 + 'validation.standard.required': 'Please enter a metric',
  39 +};
  1 +export default {
  2 + 'app.login.userName': '用户名',
  3 + 'app.login.password': '密码',
  4 + 'app.login.message-invalid-credentials': '账户或密码错误(admin/ant.design)',
  5 + 'app.login.message-invalid-verification-code': '验证码错误',
  6 + 'app.login.tab-login-credentials': '账户密码登录',
  7 + 'app.login.tab-login-mobile': '手机号登录',
  8 + 'app.login.remember-me': '自动登录',
  9 + 'app.login.forgot-password': '忘记密码',
  10 + 'app.login.sign-in-with': '其他登录方式',
  11 + 'app.login.signup': '注册账户',
  12 + 'app.login.login': '登录',
  13 + 'app.register.register': '注册',
  14 + 'app.register.get-verification-code': '获取验证码',
  15 + 'app.register.sign-in': '使用已有账户登录',
  16 + 'app.register-result.msg': '你的账户:{email} 注册成功',
  17 + 'app.register-result.activation-email':
  18 + '激活邮件已发送到你的邮箱中,邮件有效期为24小时。请及时登录邮箱,点击邮件中的链接激活帐户。',
  19 + 'app.register-result.back-home': '返回首页',
  20 + 'app.register-result.view-mailbox': '查看邮箱',
  21 + 'validation.email.required': '请输入邮箱地址!',
  22 + 'validation.email.wrong-format': '邮箱地址格式错误!',
  23 + 'validation.userName.required': '请输入用户名!',
  24 + 'validation.password.required': '请输入密码!',
  25 + 'validation.password.twice': '两次输入的密码不匹配!',
  26 + 'validation.password.strength.msg': '请至少输入 6 个字符。请不要使用容易被猜到的密码。',
  27 + 'validation.password.strength.strong': '强度:强',
  28 + 'validation.password.strength.medium': '强度:中',
  29 + 'validation.password.strength.short': '强度:太短',
  30 + 'validation.confirm-password.required': '请确认密码!',
  31 + 'validation.phone-number.required': '请输入手机号!',
  32 + 'validation.phone-number.wrong-format': '手机号格式错误!',
  33 + 'validation.verification-code.required': '请输入验证码!',
  34 + 'validation.title.required': '请输入标题',
  35 + 'validation.date.required': '请选择起止日期',
  36 + 'validation.goal.required': '请输入目标描述',
  37 + 'validation.standard.required': '请输入衡量标准',
  38 +};
  1 +export default {
  2 + 'app.login.userName': '賬戶',
  3 + 'app.login.password': '密碼',
  4 + 'app.login.message-invalid-credentials': '賬戶或密碼錯誤(admin/ant.design)',
  5 + 'app.login.message-invalid-verification-code': '驗證碼錯誤',
  6 + 'app.login.tab-login-credentials': '賬戶密碼登錄',
  7 + 'app.login.tab-login-mobile': '手機號登錄',
  8 + 'app.login.remember-me': '自動登錄',
  9 + 'app.login.forgot-password': '忘記密碼',
  10 + 'app.login.sign-in-with': '其他登錄方式',
  11 + 'app.login.signup': '註冊賬戶',
  12 + 'app.login.login': '登錄',
  13 + 'app.register.register': '註冊',
  14 + 'app.register.get-verification-code': '獲取驗證碼',
  15 + 'app.register.sign-in': '使用已有賬戶登錄',
  16 + 'app.register-result.msg': '妳的賬戶:{email} 註冊成功',
  17 + 'app.register-result.activation-email':
  18 + '激活郵件已發送到妳的郵箱中,郵件有效期為24小時。請及時登錄郵箱,點擊郵件中的鏈接激活帳戶。',
  19 + 'app.register-result.back-home': '返回首頁',
  20 + 'app.register-result.view-mailbox': '查看郵箱',
  21 + 'validation.email.required': '請輸入郵箱地址!',
  22 + 'validation.email.wrong-format': '郵箱地址格式錯誤!',
  23 + 'validation.userName.required': '請輸入賬戶!',
  24 + 'validation.password.required': '請輸入密碼!',
  25 + 'validation.password.twice': '兩次輸入的密碼不匹配!',
  26 + 'validation.password.strength.msg': '請至少輸入 6 個字符。請不要使用容易被猜到的密碼。',
  27 + 'validation.password.strength.strong': '強度:強',
  28 + 'validation.password.strength.medium': '強度:中',
  29 + 'validation.password.strength.short': '強度:太短',
  30 + 'validation.confirm-password.required': '請確認密碼!',
  31 + 'validation.phone-number.required': '請輸入手機號!',
  32 + 'validation.phone-number.wrong-format': '手機號格式錯誤!',
  33 + 'validation.verification-code.required': '請輸入驗證碼!',
  34 + 'validation.title.required': '請輸入標題',
  35 + 'validation.date.required': '請選擇起止日期',
  36 + 'validation.goal.required': '請輸入目標描述',
  37 + 'validation.standard.required': '請輸入衡量標淮',
  38 +};
  1 +import { routerRedux } from 'dva/router';
  2 +import { stringify } from 'qs';
  3 +import { fakeAccountLogin, getFakeCaptcha } from '@/services/api';
  4 +import { setAuthority } from '@/utils/authority';
  5 +import { getPageQuery } from '@/utils/utils';
  6 +import { reloadAuthorized } from '@/utils/Authorized';
  7 +
  8 +export default {
  9 + namespace: 'login',
  10 +
  11 + state: {
  12 + status: undefined,
  13 + },
  14 +
  15 + effects: {
  16 + *login({ payload }, { call, put }) {
  17 + const response = yield call(fakeAccountLogin, payload);
  18 + yield put({
  19 + type: 'changeLoginStatus',
  20 + payload: response,
  21 + });
  22 + // Login successfully
  23 + if (response.status === 'ok') {
  24 + reloadAuthorized();
  25 + const urlParams = new URL(window.location.href);
  26 + const params = getPageQuery();
  27 + let { redirect } = params;
  28 + if (redirect) {
  29 + const redirectUrlParams = new URL(redirect);
  30 + if (redirectUrlParams.origin === urlParams.origin) {
  31 + redirect = redirect.substr(urlParams.origin.length);
  32 + if (redirect.match(/^\/.*#/)) {
  33 + redirect = redirect.substr(redirect.indexOf('#') + 1);
  34 + }
  35 + } else {
  36 + window.location.href = redirect;
  37 + return;
  38 + }
  39 + }
  40 + yield put(routerRedux.replace(redirect || '/'));
  41 + }
  42 + },
  43 +
  44 + *getCaptcha({ payload }, { call }) {
  45 + yield call(getFakeCaptcha, payload);
  46 + },
  47 +
  48 + *logout(_, { put }) {
  49 + yield put({
  50 + type: 'changeLoginStatus',
  51 + payload: {
  52 + status: false,
  53 + currentAuthority: 'guest',
  54 + },
  55 + });
  56 + reloadAuthorized();
  57 + yield put(
  58 + routerRedux.push({
  59 + pathname: '/user/login',
  60 + search: stringify({
  61 + redirect: window.location.href,
  62 + }),
  63 + })
  64 + );
  65 + },
  66 + },
  67 +
  68 + reducers: {
  69 + changeLoginStatus(state, { payload }) {
  70 + setAuthority(payload.currentAuthority);
  71 + return {
  72 + ...state,
  73 + status: payload.status,
  74 + type: payload.type,
  75 + };
  76 + },
  77 + },
  78 +};
  1 +import { message } from 'antd';
  2 +import defaultSettings from '../defaultSettings';
  3 +
  4 +let lessNodesAppended;
  5 +const updateTheme = primaryColor => {
  6 + // Don't compile less in production!
  7 + if (APP_TYPE !== 'site') {
  8 + return;
  9 + }
  10 + // Determine if the component is remounted
  11 + if (!primaryColor) {
  12 + return;
  13 + }
  14 + const hideMessage = message.loading('正在编译主题!', 0);
  15 + function buildIt() {
  16 + if (!window.less) {
  17 + return;
  18 + }
  19 + setTimeout(() => {
  20 + window.less
  21 + .modifyVars({
  22 + '@primary-color': primaryColor,
  23 + })
  24 + .then(() => {
  25 + hideMessage();
  26 + })
  27 + .catch(() => {
  28 + message.error('Failed to update theme');
  29 + hideMessage();
  30 + });
  31 + }, 200);
  32 + }
  33 + if (!lessNodesAppended) {
  34 + // insert less.js and color.less
  35 + const lessStyleNode = document.createElement('link');
  36 + const lessConfigNode = document.createElement('script');
  37 + const lessScriptNode = document.createElement('script');
  38 + lessStyleNode.setAttribute('rel', 'stylesheet/less');
  39 + lessStyleNode.setAttribute('href', '/color.less');
  40 + lessConfigNode.innerHTML = `
  41 + window.less = {
  42 + async: true,
  43 + env: 'production',
  44 + javascriptEnabled: true
  45 + };
  46 + `;
  47 + lessScriptNode.src = 'https://gw.alipayobjects.com/os/lib/less.js/3.8.1/less.min.js';
  48 + lessScriptNode.async = true;
  49 + lessScriptNode.onload = () => {
  50 + buildIt();
  51 + lessScriptNode.onload = null;
  52 + };
  53 + document.body.appendChild(lessStyleNode);
  54 + document.body.appendChild(lessConfigNode);
  55 + document.body.appendChild(lessScriptNode);
  56 + lessNodesAppended = true;
  57 + } else {
  58 + buildIt();
  59 + }
  60 +};
  61 +
  62 +const updateColorWeak = colorWeak => {
  63 + document.body.className = colorWeak ? 'colorWeak' : '';
  64 +};
  65 +
  66 +export default {
  67 + namespace: 'setting',
  68 + state: defaultSettings,
  69 + reducers: {
  70 + getSetting(state) {
  71 + const setting = {};
  72 + const urlParams = new URL(window.location.href);
  73 + Object.keys(state).forEach(key => {
  74 + if (urlParams.searchParams.has(key)) {
  75 + const value = urlParams.searchParams.get(key);
  76 + setting[key] = value === '1' ? true : value;
  77 + }
  78 + });
  79 + const { primaryColor, colorWeak } = setting;
  80 + if (state.primaryColor !== primaryColor) {
  81 + updateTheme(primaryColor);
  82 + }
  83 + updateColorWeak(colorWeak);
  84 + return {
  85 + ...state,
  86 + ...setting,
  87 + };
  88 + },
  89 + changeSetting(state, { payload }) {
  90 + const urlParams = new URL(window.location.href);
  91 + Object.keys(defaultSettings).forEach(key => {
  92 + if (urlParams.searchParams.has(key)) {
  93 + urlParams.searchParams.delete(key);
  94 + }
  95 + });
  96 + Object.keys(payload).forEach(key => {
  97 + if (key === 'collapse') {
  98 + return;
  99 + }
  100 + let value = payload[key];
  101 + if (value === true) {
  102 + value = 1;
  103 + }
  104 + if (defaultSettings[key] !== value) {
  105 + urlParams.searchParams.set(key, value);
  106 + }
  107 + });
  108 + const { primaryColor, colorWeak, contentWidth } = payload;
  109 + if (state.primaryColor !== primaryColor) {
  110 + updateTheme(primaryColor);
  111 + }
  112 + if (state.contentWidth !== contentWidth && window.dispatchEvent) {
  113 + window.dispatchEvent(new Event('resize'));
  114 + }
  115 + updateColorWeak(colorWeak);
  116 + window.history.replaceState(null, 'setting', urlParams.href);
  117 + return {
  118 + ...state,
  119 + ...payload,
  120 + };
  121 + },
  122 + },
  123 +};
注册登录 后发表评论