提交 2f0b73350bd1fbf20af5ec1a1644c5321da046d1

作者 愚道
1 个父辈 a8b4380c

init pro block account setting layout

  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 React, { PureComponent } from 'react';
  2 +import { connect } from 'dva';
  3 +import styles from './GridContent.less';
  4 +
  5 +class GridContent extends PureComponent {
  6 + render() {
  7 + const { contentWidth, children } = this.props;
  8 + let className = `${styles.main}`;
  9 + if (contentWidth === 'Fixed') {
  10 + className = `${styles.main} ${styles.wide}`;
  11 + }
  12 + return <div className={className}>{children}</div>;
  13 + }
  14 +}
  15 +
  16 +export default connect(({ setting }) => ({
  17 + contentWidth: setting.contentWidth,
  18 +}))(GridContent);
... ...
  1 +.main {
  2 + width: 100%;
  3 + height: 100%;
  4 + min-height: 100%;
  5 + transition: 0.3s;
  6 + &.wide {
  7 + max-width: 1200px;
  8 + margin: 0 auto;
  9 + }
  10 +}
... ...
  1 +import request from '@/utils/request';
  2 +
  3 +export async function query() {
  4 + return request('/api/users');
  5 +}
  6 +
  7 +export async function queryCurrent() {
  8 + return request('/api/currentUser');
  9 +}
... ...
  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/accountsettings
  2 +
  3 +AccountSettings
  4 +
  5 +## Usage
  6 +
  7 +```sh
  8 +umi block https://github.com/umijs/umi-blocks/tree/master/accountsettings
  9 +```
  10 +
  11 +## LICENSE
  12 +
  13 +MIT
... ...
  1 +{
  2 + "name": "@umi-block/accountsettings",
  3 + "version": "0.0.1",
  4 + "description": "AccountSettings",
  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/accountsettings"
  12 + },
  13 + "dependencies": {
  14 + "react": "^16.6.3",
  15 + "dva": "^2.4.0",
  16 + "antd": "^3.10.9",
  17 + "hash.js": "^1.1.5",
  18 + "moment": "^2.22.2",
  19 + "nzh": "^1.0.3",
  20 + "qs": "^6.6.0"
  21 + },
  22 + "devDependencies": {
  23 + "umi": "^2.3.0-beta.1",
  24 + "umi-plugin-react": "^1.3.0-beta.1",
  25 + "umi-plugin-block-dev": "^1.0.0"
  26 + },
  27 + "license": "ISC"
  28 +}
... ...
  1 +@import '~antd/lib/style/themes/default.less';
  2 +
  3 +.main {
  4 + width: 100%;
  5 + height: 100%;
  6 + background-color: @body-background;
  7 + display: flex;
  8 + padding-top: 16px;
  9 + padding-bottom: 16px;
  10 + overflow: auto;
  11 + .leftmenu {
  12 + width: 224px;
  13 + border-right: @border-width-base @border-style-base @border-color-split;
  14 + :global {
  15 + .ant-menu-inline {
  16 + border: none;
  17 + }
  18 + .ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {
  19 + font-weight: bold;
  20 + }
  21 + }
  22 + }
  23 + .right {
  24 + flex: 1;
  25 + padding-left: 40px;
  26 + padding-right: 40px;
  27 + padding-top: 8px;
  28 + padding-bottom: 8px;
  29 + .title {
  30 + font-size: 20px;
  31 + color: @heading-color;
  32 + line-height: 28px;
  33 + font-weight: 500;
  34 + margin-bottom: 12px;
  35 + }
  36 + }
  37 + :global {
  38 + .ant-list-split .ant-list-item:last-child {
  39 + border-bottom: 1px solid #e8e8e8;
  40 + }
  41 + .ant-list-item {
  42 + padding-top: 14px;
  43 + padding-bottom: 14px;
  44 + }
  45 + }
  46 +}
  47 +:global {
  48 + .ant-list-item-meta {
  49 + // 账号绑定图标
  50 + .taobao {
  51 + color: #ff4000;
  52 + display: block;
  53 + font-size: 48px;
  54 + line-height: 48px;
  55 + border-radius: @border-radius-base;
  56 + }
  57 + .dingding {
  58 + background-color: #2eabff;
  59 + color: #fff;
  60 + font-size: 32px;
  61 + line-height: 32px;
  62 + padding: 6px;
  63 + margin: 2px;
  64 + border-radius: @border-radius-base;
  65 + }
  66 + .alipay {
  67 + color: #2eabff;
  68 + font-size: 48px;
  69 + line-height: 48px;
  70 + border-radius: @border-radius-base;
  71 + }
  72 + }
  73 +
  74 + // 密码强度
  75 + font.strong {
  76 + color: @success-color;
  77 + }
  78 + font.medium {
  79 + color: @warning-color;
  80 + }
  81 + font.weak {
  82 + color: @error-color;
  83 + }
  84 +}
  85 +
  86 +@media screen and (max-width: @screen-md) {
  87 + .main {
  88 + flex-direction: column;
  89 + .leftmenu {
  90 + width: 100%;
  91 + border: none;
  92 + }
  93 + .right {
  94 + padding: 40px;
  95 + }
  96 + }
  97 +}
... ...
  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 router from 'umi/router';
  4 +import { FormattedMessage } from 'umi/locale';
  5 +import { Menu } from 'antd';
  6 +import GridContent from '@/components/PageHeaderWrapper/GridContent';
  7 +import styles from './Info.less';
  8 +
  9 +const { Item } = Menu;
  10 +
  11 +@connect(({ user }) => ({
  12 + currentUser: user.currentUser,
  13 +}))
  14 +class Info extends Component {
  15 + constructor(props) {
  16 + super(props);
  17 + const { match, location } = props;
  18 + const menuMap = {
  19 + base: <FormattedMessage id="app.settings.menuMap.basic" defaultMessage="Basic Settings" />,
  20 + security: (
  21 + <FormattedMessage id="app.settings.menuMap.security" defaultMessage="Security Settings" />
  22 + ),
  23 + binding: (
  24 + <FormattedMessage id="app.settings.menuMap.binding" defaultMessage="Account Binding" />
  25 + ),
  26 + notification: (
  27 + <FormattedMessage
  28 + id="app.settings.menuMap.notification"
  29 + defaultMessage="New Message Notification"
  30 + />
  31 + ),
  32 + };
  33 + const key = location.pathname.replace(`${match.path}/`, '');
  34 + this.state = {
  35 + mode: 'inline',
  36 + menuMap,
  37 + selectKey: menuMap[key] ? key : 'base',
  38 + };
  39 + }
  40 +
  41 + static getDerivedStateFromProps(props, state) {
  42 + const { match, location } = props;
  43 + let selectKey = location.pathname.replace(`${match.path}/`, '');
  44 + selectKey = state.menuMap[selectKey] ? selectKey : 'base';
  45 + if (selectKey !== state.selectKey) {
  46 + return { selectKey };
  47 + }
  48 + return null;
  49 + }
  50 +
  51 + componentDidMount() {
  52 + const { dispatch } = this.props;
  53 + dispatch({
  54 + type: 'user/fetchCurrent',
  55 + });
  56 + window.addEventListener('resize', this.resize);
  57 + this.resize();
  58 + }
  59 +
  60 + componentWillUnmount() {
  61 + window.removeEventListener('resize', this.resize);
  62 + }
  63 +
  64 + getmenu = () => {
  65 + const { menuMap } = this.state;
  66 + return Object.keys(menuMap).map(item => <Item key={item}>{menuMap[item]}</Item>);
  67 + };
  68 +
  69 + getRightTitle = () => {
  70 + const { selectKey, menuMap } = this.state;
  71 + return menuMap[selectKey];
  72 + };
  73 +
  74 + selectKey = ({ key }) => {
  75 + router.push(`/account/settings/${key}`);
  76 + this.setState({
  77 + selectKey: key,
  78 + });
  79 + };
  80 +
  81 + resize = () => {
  82 + if (!this.main) {
  83 + return;
  84 + }
  85 + requestAnimationFrame(() => {
  86 + let mode = 'inline';
  87 + const { offsetWidth } = this.main;
  88 + if (this.main.offsetWidth < 641 && offsetWidth > 400) {
  89 + mode = 'horizontal';
  90 + }
  91 + if (window.innerWidth < 768 && offsetWidth > 400) {
  92 + mode = 'horizontal';
  93 + }
  94 + this.setState({
  95 + mode,
  96 + });
  97 + });
  98 + };
  99 +
  100 + render() {
  101 + const { children, currentUser } = this.props;
  102 + if (!currentUser.userid) {
  103 + return '';
  104 + }
  105 + const { mode, selectKey } = this.state;
  106 + return (
  107 + <GridContent>
  108 + <div
  109 + className={styles.main}
  110 + ref={ref => {
  111 + this.main = ref;
  112 + }}
  113 + >
  114 + <div className={styles.leftmenu}>
  115 + <Menu mode={mode} selectedKeys={[selectKey]} onClick={this.selectKey}>
  116 + {this.getmenu()}
  117 + </Menu>
  118 + </div>
  119 + <div className={styles.right}>
  120 + <div className={styles.title}>{this.getRightTitle()}</div>
  121 + {children}
  122 + </div>
  123 + </div>
  124 + </GridContent>
  125 + );
  126 + }
  127 +}
  128 +
  129 +export default Info;
... ...
  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 +};
... ...
  1 +import { query as queryUsers, queryCurrent } from '@/services/user';
  2 +
  3 +export default {
  4 + namespace: 'user',
  5 +
  6 + state: {
  7 + list: [],
  8 + currentUser: {},
  9 + },
  10 +
  11 + effects: {
  12 + *fetch(_, { call, put }) {
  13 + const response = yield call(queryUsers);
  14 + yield put({
  15 + type: 'save',
  16 + payload: response,
  17 + });
  18 + },
  19 + *fetchCurrent(_, { call, put }) {
  20 + const response = yield call(queryCurrent);
  21 + yield put({
  22 + type: 'saveCurrentUser',
  23 + payload: response,
  24 + });
  25 + },
  26 + },
  27 +
  28 + reducers: {
  29 + save(state, action) {
  30 + return {
  31 + ...state,
  32 + list: action.payload,
  33 + };
  34 + },
  35 + saveCurrentUser(state, action) {
  36 + return {
  37 + ...state,
  38 + currentUser: action.payload || {},
  39 + };
  40 + },
  41 + changeNotifyCount(state, action) {
  42 + return {
  43 + ...state,
  44 + currentUser: {
  45 + ...state.currentUser,
  46 + notifyCount: action.payload.totalCount,
  47 + unreadCount: action.payload.unreadCount,
  48 + },
  49 + };
  50 + },
  51 + },
  52 +};
... ...
注册登录 后发表评论