提交 4f9522e8c3ad28b3b9e695bb1a4e189337e9cbe7

作者 愚道
1 个父辈 89874044

init pro block account setting binding

  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 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/accountsettingsbinding
  2 +
  3 +AccountSettingsBinding
  4 +
  5 +## Usage
  6 +
  7 +```sh
  8 +umi block https://github.com/umijs/umi-blocks/tree/master/accountsettingsbinding
  9 +```
  10 +
  11 +## LICENSE
  12 +
  13 +MIT
... ...
  1 +{
  2 + "name": "@umi-block/accountsettingsbinding",
  3 + "version": "0.0.1",
  4 + "description": "AccountSettingsBinding",
  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/accountsettingsbinding"
  12 + },
  13 + "dependencies": {
  14 + "react": "^16.6.3",
  15 + "antd": "^3.10.9",
  16 + "dva": "^2.4.0",
  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 +// 代码中会兼容本地 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, Fragment } from 'react';
  2 +import { formatMessage, FormattedMessage } from 'umi/locale';
  3 +import { Icon, List } from 'antd';
  4 +
  5 +class BindingView extends Component {
  6 + getData = () => [
  7 + {
  8 + title: formatMessage({ id: 'app.settings.binding.taobao' }, {}),
  9 + description: formatMessage({ id: 'app.settings.binding.taobao-description' }, {}),
  10 + actions: [
  11 + <a>
  12 + <FormattedMessage id="app.settings.binding.bind" defaultMessage="Bind" />
  13 + </a>,
  14 + ],
  15 + avatar: <Icon type="taobao" className="taobao" />,
  16 + },
  17 + {
  18 + title: formatMessage({ id: 'app.settings.binding.alipay' }, {}),
  19 + description: formatMessage({ id: 'app.settings.binding.alipay-description' }, {}),
  20 + actions: [
  21 + <a>
  22 + <FormattedMessage id="app.settings.binding.bind" defaultMessage="Bind" />
  23 + </a>,
  24 + ],
  25 + avatar: <Icon type="alipay" className="alipay" />,
  26 + },
  27 + {
  28 + title: formatMessage({ id: 'app.settings.binding.dingding' }, {}),
  29 + description: formatMessage({ id: 'app.settings.binding.dingding-description' }, {}),
  30 + actions: [
  31 + <a>
  32 + <FormattedMessage id="app.settings.binding.bind" defaultMessage="Bind" />
  33 + </a>,
  34 + ],
  35 + avatar: <Icon type="dingding" className="dingding" />,
  36 + },
  37 + ];
  38 +
  39 + render() {
  40 + return (
  41 + <Fragment>
  42 + <List
  43 + itemLayout="horizontal"
  44 + dataSource={this.getData()}
  45 + renderItem={item => (
  46 + <List.Item actions={item.actions}>
  47 + <List.Item.Meta
  48 + avatar={item.avatar}
  49 + title={item.title}
  50 + description={item.description}
  51 + />
  52 + </List.Item>
  53 + )}
  54 + />
  55 + </Fragment>
  56 + );
  57 + }
  58 +}
  59 +
  60 +export default BindingView;
... ...
  1 +export default {
  2 + 'app.settings.menuMap.basic': 'Basic Settings',
  3 + 'app.settings.menuMap.security': 'Security Settings',
  4 + 'app.settings.menuMap.binding': 'Account Binding',
  5 + 'app.settings.menuMap.notification': 'New Message Notification',
  6 + 'app.settings.basic.avatar': 'Avatar',
  7 + 'app.settings.basic.change-avatar': 'Change avatar',
  8 + 'app.settings.basic.email': 'Email',
  9 + 'app.settings.basic.email-message': 'Please input your email!',
  10 + 'app.settings.basic.nickname': 'Nickname',
  11 + 'app.settings.basic.nickname-message': 'Please input your Nickname!',
  12 + 'app.settings.basic.profile': 'Personal profile',
  13 + 'app.settings.basic.profile-message': 'Please input your personal profile!',
  14 + 'app.settings.basic.profile-placeholder': 'Brief introduction to yourself',
  15 + 'app.settings.basic.country': 'Country/Region',
  16 + 'app.settings.basic.country-message': 'Please input your country!',
  17 + 'app.settings.basic.geographic': 'Province or city',
  18 + 'app.settings.basic.geographic-message': 'Please input your geographic info!',
  19 + 'app.settings.basic.address': 'Street Address',
  20 + 'app.settings.basic.address-message': 'Please input your address!',
  21 + 'app.settings.basic.phone': 'Phone Number',
  22 + 'app.settings.basic.phone-message': 'Please input your phone!',
  23 + 'app.settings.basic.update': 'Update Information',
  24 + 'app.settings.security.strong': 'Strong',
  25 + 'app.settings.security.medium': 'Medium',
  26 + 'app.settings.security.weak': 'Weak',
  27 + 'app.settings.security.password': 'Account Password',
  28 + 'app.settings.security.password-description': 'Current password strength:',
  29 + 'app.settings.security.phone': 'Security Phone',
  30 + 'app.settings.security.phone-description': 'Bound phone:',
  31 + 'app.settings.security.question': 'Security Question',
  32 + 'app.settings.security.question-description':
  33 + 'The security question is not set, and the security policy can effectively protect the account security',
  34 + 'app.settings.security.email': 'Backup Email',
  35 + 'app.settings.security.email-description': 'Bound Email:',
  36 + 'app.settings.security.mfa': 'MFA Device',
  37 + 'app.settings.security.mfa-description':
  38 + 'Unbound MFA device, after binding, can be confirmed twice',
  39 + 'app.settings.security.modify': 'Modify',
  40 + 'app.settings.security.set': 'Set',
  41 + 'app.settings.security.bind': 'Bind',
  42 + 'app.settings.binding.taobao': 'Binding Taobao',
  43 + 'app.settings.binding.taobao-description': 'Currently unbound Taobao account',
  44 + 'app.settings.binding.alipay': 'Binding Alipay',
  45 + 'app.settings.binding.alipay-description': 'Currently unbound Alipay account',
  46 + 'app.settings.binding.dingding': 'Binding DingTalk',
  47 + 'app.settings.binding.dingding-description': 'Currently unbound DingTalk account',
  48 + 'app.settings.binding.bind': 'Bind',
  49 + 'app.settings.notification.password': 'Account Password',
  50 + 'app.settings.notification.password-description':
  51 + 'Messages from other users will be notified in the form of a station letter',
  52 + 'app.settings.notification.messages': 'System Messages',
  53 + 'app.settings.notification.messages-description':
  54 + 'System messages will be notified in the form of a station letter',
  55 + 'app.settings.notification.todo': 'To-do Notification',
  56 + 'app.settings.notification.todo-description':
  57 + 'The to-do list will be notified in the form of a letter from the station',
  58 + 'app.settings.open': 'Open',
  59 + 'app.settings.close': 'Close',
  60 +};
... ...
  1 +export default {
  2 + 'app.settings.menuMap.basic': '基本设置',
  3 + 'app.settings.menuMap.security': '安全设置',
  4 + 'app.settings.menuMap.binding': '账号绑定',
  5 + 'app.settings.menuMap.notification': '新消息通知',
  6 + 'app.settings.basic.avatar': '头像',
  7 + 'app.settings.basic.change-avatar': '更换头像',
  8 + 'app.settings.basic.email': '邮箱',
  9 + 'app.settings.basic.email-message': '请输入您的邮箱!',
  10 + 'app.settings.basic.nickname': '昵称',
  11 + 'app.settings.basic.nickname-message': '请输入您的昵称!',
  12 + 'app.settings.basic.profile': '个人简介',
  13 + 'app.settings.basic.profile-message': '请输入个人简介!',
  14 + 'app.settings.basic.profile-placeholder': '个人简介',
  15 + 'app.settings.basic.country': '国家/地区',
  16 + 'app.settings.basic.country-message': '请输入您的国家或地区!',
  17 + 'app.settings.basic.geographic': '所在省市',
  18 + 'app.settings.basic.geographic-message': '请输入您的所在省市!',
  19 + 'app.settings.basic.address': '街道地址',
  20 + 'app.settings.basic.address-message': '请输入您的街道地址!',
  21 + 'app.settings.basic.phone': '联系电话',
  22 + 'app.settings.basic.phone-message': '请输入您的联系电话!',
  23 + 'app.settings.basic.update': '更新基本信息',
  24 + 'app.settings.security.strong': '强',
  25 + 'app.settings.security.medium': '中',
  26 + 'app.settings.security.weak': '弱',
  27 + 'app.settings.security.password': '账户密码',
  28 + 'app.settings.security.password-description': '当前密码强度:',
  29 + 'app.settings.security.phone': '密保手机',
  30 + 'app.settings.security.phone-description': '已绑定手机:',
  31 + 'app.settings.security.question': '密保问题',
  32 + 'app.settings.security.question-description': '未设置密保问题,密保问题可有效保护账户安全',
  33 + 'app.settings.security.email': '备用邮箱',
  34 + 'app.settings.security.email-description': '已绑定邮箱:',
  35 + 'app.settings.security.mfa': 'MFA 设备',
  36 + 'app.settings.security.mfa-description': '未绑定 MFA 设备,绑定后,可以进行二次确认',
  37 + 'app.settings.security.modify': '修改',
  38 + 'app.settings.security.set': '设置',
  39 + 'app.settings.security.bind': '绑定',
  40 + 'app.settings.binding.taobao': '绑定淘宝',
  41 + 'app.settings.binding.taobao-description': '当前未绑定淘宝账号',
  42 + 'app.settings.binding.alipay': '绑定支付宝',
  43 + 'app.settings.binding.alipay-description': '当前未绑定支付宝账号',
  44 + 'app.settings.binding.dingding': '绑定钉钉',
  45 + 'app.settings.binding.dingding-description': '当前未绑定钉钉账号',
  46 + 'app.settings.binding.bind': '绑定',
  47 + 'app.settings.notification.password': '账户密码',
  48 + 'app.settings.notification.password-description': '其他用户的消息将以站内信的形式通知',
  49 + 'app.settings.notification.messages': '系统消息',
  50 + 'app.settings.notification.messages-description': '系统消息将以站内信的形式通知',
  51 + 'app.settings.notification.todo': '待办任务',
  52 + 'app.settings.notification.todo-description': '待办任务将以站内信的形式通知',
  53 + 'app.settings.open': '开',
  54 + 'app.settings.close': '关',
  55 +};
... ...
  1 +export default {
  2 + 'app.settings.menuMap.basic': '基本設置',
  3 + 'app.settings.menuMap.security': '安全設置',
  4 + 'app.settings.menuMap.binding': '賬號綁定',
  5 + 'app.settings.menuMap.notification': '新消息通知',
  6 + 'app.settings.basic.avatar': '頭像',
  7 + 'app.settings.basic.change-avatar': '更換頭像',
  8 + 'app.settings.basic.email': '郵箱',
  9 + 'app.settings.basic.email-message': '請輸入您的郵箱!',
  10 + 'app.settings.basic.nickname': '昵稱',
  11 + 'app.settings.basic.nickname-message': '請輸入您的昵稱!',
  12 + 'app.settings.basic.profile': '個人簡介',
  13 + 'app.settings.basic.profile-message': '請輸入個人簡介!',
  14 + 'app.settings.basic.profile-placeholder': '個人簡介',
  15 + 'app.settings.basic.country': '國家/地區',
  16 + 'app.settings.basic.country-message': '請輸入您的國家或地區!',
  17 + 'app.settings.basic.geographic': '所在省市',
  18 + 'app.settings.basic.geographic-message': '請輸入您的所在省市!',
  19 + 'app.settings.basic.address': '街道地址',
  20 + 'app.settings.basic.address-message': '請輸入您的街道地址!',
  21 + 'app.settings.basic.phone': '聯系電話',
  22 + 'app.settings.basic.phone-message': '請輸入您的聯系電話!',
  23 + 'app.settings.basic.update': '更新基本信息',
  24 + 'app.settings.security.strong': '強',
  25 + 'app.settings.security.medium': '中',
  26 + 'app.settings.security.weak': '弱',
  27 + 'app.settings.security.password': '賬戶密碼',
  28 + 'app.settings.security.password-description': '當前密碼強度:',
  29 + 'app.settings.security.phone': '密保手機',
  30 + 'app.settings.security.phone-description': '已綁定手機:',
  31 + 'app.settings.security.question': '密保問題',
  32 + 'app.settings.security.question-description': '未設置密保問題,密保問題可有效保護賬戶安全',
  33 + 'app.settings.security.email': '備用郵箱',
  34 + 'app.settings.security.email-description': '已綁定郵箱:',
  35 + 'app.settings.security.mfa': 'MFA 設備',
  36 + 'app.settings.security.mfa-description': '未綁定 MFA 設備,綁定後,可以進行二次確認',
  37 + 'app.settings.security.modify': '修改',
  38 + 'app.settings.security.set': '設置',
  39 + 'app.settings.security.bind': '綁定',
  40 + 'app.settings.binding.taobao': '綁定淘寶',
  41 + 'app.settings.binding.taobao-description': '當前未綁定淘寶賬號',
  42 + 'app.settings.binding.alipay': '綁定支付寶',
  43 + 'app.settings.binding.alipay-description': '當前未綁定支付寶賬號',
  44 + 'app.settings.binding.dingding': '綁定釘釘',
  45 + 'app.settings.binding.dingding-description': '當前未綁定釘釘賬號',
  46 + 'app.settings.binding.bind': '綁定',
  47 + 'app.settings.notification.password': '賬戶密碼',
  48 + 'app.settings.notification.password-description': '其他用戶的消息將以站內信的形式通知',
  49 + 'app.settings.notification.messages': '系統消息',
  50 + 'app.settings.notification.messages-description': '系統消息將以站內信的形式通知',
  51 + 'app.settings.notification.todo': '待辦任務',
  52 + 'app.settings.notification.todo-description': '待辦任務將以站內信的形式通知',
  53 + 'app.settings.open': '開',
  54 + 'app.settings.close': '關',
  55 +};
... ...
  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 +};
... ...
注册登录 后发表评论