ant design pro v5 发布 (#8642)
* init * remove Authorized * remove login * remove request * remove path-to-regexp" * remove create-umi * clean dependencies * remove unused less * better login mock * add ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION=site env * support settings dynamic * support umi-plugin-setting-drawer * 🎨 format: clean code * remove clean fetch:blocks * add NoticeIcon * add NoticeIcon * use "@ant-design/pro-layout": "6.0.0-1" * support errorHandler * remove SelectLang * update version * PageHeaderWrapper to PageContainer * PageHeaderWrapper to PageContainer (#6622) * 📌 versions: use alpha version (#6634) * use alpha version 5.0.0 no yet * less test * fix lint error * rename setting Co-authored-by: chenshuai2144 <qixian.cs@outlook.com> * fix 401 do not to login error * 405 start:no-mock * siderWidth: 208 * side * false * username * remove resolutions * update list demo * correct the text * fix tabs style in antd@4.3 * fix login error * use "@ant-design/pro-layout": "6.0.0-5" * eslint * access * pro-layout 6.0.0-7 * merge master * up layout version * 💄 UI: fix login style warning * upgrade dependency * delete throw error * versions: up @umijs/fabric * change login * do not open defaultOpenAll * rebase error * update demos * deps upgreade * 🐛 修复切换不同权限的账户登录后,access的鉴权不生效问题 (#6997) * 🐛 修复切换不同权限的账户登录后,access的鉴权不生效问题 * 🎨 prettier code * 修改hash路由下search参数在hash之前,search最后一个参数错误bug (#6908) * 🐛 bug: fix tsc error * config.js里面 history.type 为 hash时,退出重登录后,拼接返回路径时,#号前路径会重复 * prettier all code * merge master * 🐛 bug: fix the problem that modifying URL can bypass permissions * reset hash * fix infinite loop of login page (#7095) * merge master * rm unuse code * rm unuse code * remove unuse code * 🐛 bug: fix redirect error in hash mode * fix redirect * 💥 feat: support defaultSettings close #7407 * sort * sorter * default close umi ui * goto reload window.location.href * add setupTests.js * add localStorageMock init * open esbuild * add exportStatic * better code * add initialStateConfig * remove unuse code * login 翻译 * add key * update siderWidth close #7585 * fix redirect error close #7632 * add childrenRender type * better styles * better styles * remove unuse code close #7648 * default use browser history * close * support unAccessible * up @umijs/preset-react version * merge master * support dumi function * ✨ feat: add react-dev-inspector plugins * 🚑 hotfix: support InspectorWrapper * 🚑 hotfix: support InspectorWrapper * add dumi docs href * 👷 CI: fix Deploy CI * 👷 CI : fix azure-pipelines * fix keys error * fix: version (#7789) * fix lint * fix lint * fix typo * fix type * better code * add openAPI plugins * use openapi plugins * support schemaPath = url * add umi-plugins * add dumi doc * add plugin doc * add config doc * fix: outLogin request method (#8059) (#8061) Co-authored-by: luqili <a@luqili.com> * 非 dev 环境不展示 openapi * locale pt_br Pages added (#8088) * revert surge preview * test: update rebase action * add persian local and fix menu direction for rtl display (#8079) * up @umijs/preset-dumi version * up typescript verison * add waterMark * default open webpack5 * default open webpack5 * support webpack5 * support openAPI is array * upgrade deps * dir * 页面代码结构推荐 * Update global.tsx * Update index.tsx * refactor(app.tsx): dev links use Link * loginPath * fix ci * fix tsc * fix tsc * fix(app.tsx): dev links use umi Link * fix tes * fix warn * refactor(app.tsx): optimization import Link * resetv5-app-tsx-links #8226 * use link to hash * default open fastRefresh * Update README.md (#8356) Ant Design Pro V5 is not released yet. `trial` is often used for released paid product, and `preview` is the regularly used word for pre-release version. * Fixing a few issues with internationalization on the login (#7945) Co-authored-by: chenshuai2144 <qixian.cs@outlook.com> * Update api.ts (#8424) * chore: support verify commit * fix locale error * support all block * locale: 增加标记,以便删除国际化脚本识别 * fix(locale): 删除request错误捕捉 * chore: remove tag * chore: use fabric verify-commit * chore: up to 5.0.0-beta.3 * chore: remove home page png * use latest * merge master * fix conflict type and eslint waring! (#8644) * chore: open mfsu and webpack5 * try fix ci * fix ci * try fix ci * add test * add test * 增加加载时间 * fix test * prettier all code * docs: change oneapi doc * chore: add umi * rmeove unuse code Co-authored-by: lijiehua <41830859@qq.com> Co-authored-by: 偏右 <afc163@gmail.com> Co-authored-by: Jerry <510846@qq.com> Co-authored-by: Vern Brandl <tkvern@users.noreply.github.com> Co-authored-by: Lee Fan <535536456@qq.com> Co-authored-by: zhangshuling <Zsl0516> Co-authored-by: Justin <zcodeworld@gmail.com> Co-authored-by: Jeff Tian <jeff.tian@outlook.com> Co-authored-by: luqili <33475522+luqil@users.noreply.github.com> Co-authored-by: luqili <a@luqili.com> Co-authored-by: Thiago Tognoli <thiagotognoli@users.noreply.github.com> Co-authored-by: abolfazl <Abolfazl.rajabpour@gmail.com> Co-authored-by: HouKunLin <houkunlin@aliyun.com> Co-authored-by: SHINCHVEN <shinchven@gmail.com> Co-authored-by: Scott Goci <scottjg@gmail.com> Co-authored-by: _XiaoTian <istianlei@qq.com> Co-authored-by: 丁田秀 <295434665@qq.com> Co-authored-by: Jiankian <i@anline.cn>
This commit is contained in:
parent
09ca467444
commit
a2d434f023
|
|
@ -7,7 +7,7 @@ jobs:
|
|||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
node_version: [10.x, 12.x]
|
||||
node_version: [12.x, 14.x]
|
||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
with:
|
||||
node-version: ${{ matrix.node_version }}
|
||||
- run: echo ${{github.ref}}
|
||||
- run: npm install
|
||||
- run: yarn
|
||||
- run: yarn run lint
|
||||
- run: yarn run tsc
|
||||
- run: yarn run build
|
||||
|
|
|
|||
|
|
@ -12,10 +12,13 @@ jobs:
|
|||
uses: actions/checkout@master
|
||||
|
||||
- name: install
|
||||
run: npm install
|
||||
run: yarn
|
||||
|
||||
- name: plugins
|
||||
run: yarn add umi-plugin-antd-theme umi-plugin-pro
|
||||
run: yarn add umi-plugin-antd-theme umi-plugin-pro umi-plugin-setting-drawer
|
||||
|
||||
- name: fetch-blocks
|
||||
run: yarn run pro fetch-blocks --branch=v5
|
||||
|
||||
- name: site
|
||||
run: npm run site
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ jobs:
|
|||
|
||||
- name: build
|
||||
run: |
|
||||
npm install
|
||||
npm install umi-plugin-pro --save
|
||||
npm run build
|
||||
yarn
|
||||
yarn add umi-plugin-pro --save
|
||||
yarn build
|
||||
|
||||
- name: upload dist artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ name: Preview Deploy
|
|||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Preview Build"]
|
||||
workflows: ['Preview Build']
|
||||
types:
|
||||
- completed
|
||||
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ jobs:
|
|||
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Automatic Rebase
|
||||
uses: cirrus-actions/rebase@1.3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: actions/checkout@master
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Automatic Rebase
|
||||
uses: cirrus-actions/rebase@1.3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ An out-of-box UI solution for enterprise applications as a React boilerplate.
|
|||
- FAQ: http://pro.ant.design/docs/faq
|
||||
- Mirror Site in China: http://ant-design-pro.gitee.io
|
||||
|
||||
## 5.0 is ready for trial! 🎉🎉🎉
|
||||
## 5.0 is ready for preview! 🎉🎉🎉
|
||||
|
||||
[Try Ant Design Pro 5.0.0](https://beta-pro.ant.design/docs/upgrade-v5-cn)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,4 +12,8 @@ export default defineConfig({
|
|||
babelPlugins: [],
|
||||
babelOptions: {},
|
||||
},
|
||||
// mfsu: {},
|
||||
// webpack5: {
|
||||
// lazyCompilation: {},
|
||||
// },
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
// https://umijs.org/config/
|
||||
import { defineConfig } from 'umi';
|
||||
import { join } from 'path';
|
||||
|
||||
import defaultSettings from './defaultSettings';
|
||||
import proxy from './proxy';
|
||||
import routes from './routes';
|
||||
|
|
@ -12,9 +14,13 @@ export default defineConfig({
|
|||
dva: {
|
||||
hmr: true,
|
||||
},
|
||||
history: {
|
||||
type: 'browser',
|
||||
layout: {
|
||||
// https://umijs.org/zh-CN/plugins/plugin-layout
|
||||
locale: true,
|
||||
siderWidth: 208,
|
||||
...defaultSettings,
|
||||
},
|
||||
// https://umijs.org/zh-CN/plugins/plugin-locale
|
||||
locale: {
|
||||
// default zh-CN
|
||||
default: 'zh-CN',
|
||||
|
|
@ -23,7 +29,7 @@ export default defineConfig({
|
|||
baseNavigator: true,
|
||||
},
|
||||
dynamicImport: {
|
||||
loading: '@/components/PageLoading/index',
|
||||
loading: '@ant-design/pro-layout/es/PageLoading',
|
||||
},
|
||||
targets: {
|
||||
ie: 11,
|
||||
|
|
@ -34,14 +40,33 @@ export default defineConfig({
|
|||
theme: {
|
||||
'primary-color': defaultSettings.primaryColor,
|
||||
},
|
||||
// esbuild is father build tools
|
||||
// https://umijs.org/plugins/plugin-esbuild
|
||||
esbuild: {},
|
||||
title: false,
|
||||
ignoreMomentLocale: true,
|
||||
proxy: proxy[REACT_APP_ENV || 'dev'],
|
||||
manifest: {
|
||||
basePath: '/',
|
||||
},
|
||||
// 快速刷新功能 https://umijs.org/config#fastrefresh
|
||||
// Fast Refresh 热更新
|
||||
fastRefresh: {},
|
||||
esbuild: {},
|
||||
openAPI: [
|
||||
{
|
||||
requestLibPath: "import { request } from 'umi'",
|
||||
// 或者使用在线的版本
|
||||
// schemaPath: "https://gw.alipayobjects.com/os/antfincdn/M%24jrzTTYJN/oneapi.json"
|
||||
schemaPath: join(__dirname, 'oneapi.json'),
|
||||
mock: false,
|
||||
},
|
||||
{
|
||||
requestLibPath: "import { request } from 'umi'",
|
||||
schemaPath: 'https://gw.alipayobjects.com/os/antfincdn/CA1dOm%2631B/openapi.json',
|
||||
projectName: 'swagger',
|
||||
},
|
||||
],
|
||||
nodeModulesTransform: { type: 'none' },
|
||||
mfsu: {},
|
||||
webpack5: {},
|
||||
exportStatic: {},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,23 +1,21 @@
|
|||
import { Settings as ProSettings } from '@ant-design/pro-layout';
|
||||
import { Settings as LayoutSettings } from '@ant-design/pro-layout';
|
||||
|
||||
type DefaultSettings = Partial<ProSettings> & {
|
||||
pwa: boolean;
|
||||
};
|
||||
|
||||
const proSettings: DefaultSettings = {
|
||||
navTheme: 'dark',
|
||||
const Settings: LayoutSettings & {
|
||||
pwa?: boolean;
|
||||
logo?: string;
|
||||
} = {
|
||||
navTheme: 'light',
|
||||
// 拂晓蓝
|
||||
primaryColor: '#1890ff',
|
||||
layout: 'side',
|
||||
layout: 'mix',
|
||||
contentWidth: 'Fluid',
|
||||
fixedHeader: false,
|
||||
fixSiderbar: true,
|
||||
colorWeak: false,
|
||||
title: 'Ant Design Pro',
|
||||
pwa: false,
|
||||
logo: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
|
||||
iconfontUrl: '',
|
||||
};
|
||||
|
||||
export type { DefaultSettings };
|
||||
|
||||
export default proSettings;
|
||||
export default Settings;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,593 @@
|
|||
{
|
||||
"openapi": "3.0.1",
|
||||
"info": {
|
||||
"title": "Ant Design Pro",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "http://localhost:8000/"
|
||||
},
|
||||
{
|
||||
"url": "https://localhost:8000/"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"/api/currentUser": {
|
||||
"get": {
|
||||
"tags": ["api"],
|
||||
"description": "获取当前的用户",
|
||||
"operationId": "currentUser",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CurrentUser"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-swagger-router-controller": "api"
|
||||
},
|
||||
"/api/login/captcha": {
|
||||
"post": {
|
||||
"description": "发送验证码",
|
||||
"operationId": "getFakeCaptcha",
|
||||
"tags": ["login"],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "phone",
|
||||
"in": "query",
|
||||
"description": "手机号",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/FakeCaptcha"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/login/outLogin": {
|
||||
"post": {
|
||||
"description": "登录接口",
|
||||
"operationId": "outLogin",
|
||||
"tags": ["login"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-swagger-router-controller": "api"
|
||||
},
|
||||
"/api/login/account": {
|
||||
"post": {
|
||||
"tags": ["login"],
|
||||
"description": "登录接口",
|
||||
"operationId": "login",
|
||||
"requestBody": {
|
||||
"description": "登录系统",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LoginParams"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LoginResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-codegen-request-body-name": "body"
|
||||
},
|
||||
"x-swagger-router-controller": "api"
|
||||
},
|
||||
"/api/notices": {
|
||||
"summary": "getNotices",
|
||||
"description": "NoticeIconItem",
|
||||
"get": {
|
||||
"tags": ["api"],
|
||||
"operationId": "getNotices",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/NoticeIconList"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/rule": {
|
||||
"get": {
|
||||
"tags": ["rule"],
|
||||
"description": "获取规则列表",
|
||||
"operationId": "rule",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "current",
|
||||
"in": "query",
|
||||
"description": "当前的页码",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "pageSize",
|
||||
"in": "query",
|
||||
"description": "页面的容量",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/RuleList"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"tags": ["rule"],
|
||||
"description": "新建规则",
|
||||
"operationId": "addRule",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/RuleListItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"tags": ["rule"],
|
||||
"description": "新建规则",
|
||||
"operationId": "updateRule",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/RuleListItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"tags": ["rule"],
|
||||
"description": "删除规则",
|
||||
"operationId": "removeRule",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-swagger-router-controller": "api"
|
||||
},
|
||||
"/swagger": {
|
||||
"x-swagger-pipe": "swagger_raw"
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"CurrentUser": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"avatar": {
|
||||
"type": "string"
|
||||
},
|
||||
"userid": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"signature": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"group": {
|
||||
"type": "string"
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"notifyCount": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"unreadCount": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"country": {
|
||||
"type": "string"
|
||||
},
|
||||
"access": {
|
||||
"type": "string"
|
||||
},
|
||||
"geographic": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"province": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"city": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"address": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"LoginResult": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"currentAuthority": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"PageParams": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"current": {
|
||||
"type": "number"
|
||||
},
|
||||
"pageSize": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"RuleListItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"disabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"href": {
|
||||
"type": "string"
|
||||
},
|
||||
"avatar": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"owner": {
|
||||
"type": "string"
|
||||
},
|
||||
"desc": {
|
||||
"type": "string"
|
||||
},
|
||||
"callNo": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"status": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string",
|
||||
"format": "datetime"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
"format": "datetime"
|
||||
},
|
||||
"progress": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
},
|
||||
"RuleList": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/RuleListItem"
|
||||
}
|
||||
},
|
||||
"total": {
|
||||
"type": "integer",
|
||||
"description": "列表的内容总数",
|
||||
"format": "int32"
|
||||
},
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"FakeCaptcha": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"LoginParams": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"username": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"autoLogin": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ErrorResponse": {
|
||||
"required": ["errorCode"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"errorCode": {
|
||||
"type": "string",
|
||||
"description": "业务约定的错误码"
|
||||
},
|
||||
"errorMessage": {
|
||||
"type": "string",
|
||||
"description": "业务上的错误信息"
|
||||
},
|
||||
"success": {
|
||||
"type": "boolean",
|
||||
"description": "业务上的请求是否成功"
|
||||
}
|
||||
}
|
||||
},
|
||||
"NoticeIconList": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/NoticeIconItem"
|
||||
}
|
||||
},
|
||||
"total": {
|
||||
"type": "integer",
|
||||
"description": "列表的内容总数",
|
||||
"format": "int32"
|
||||
},
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"NoticeIconItemType": {
|
||||
"title": "NoticeIconItemType",
|
||||
"description": "已读未读列表的枚举",
|
||||
"type": "string",
|
||||
"properties": {},
|
||||
"enum": ["notification", "message", "event"]
|
||||
},
|
||||
"NoticeIconItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"extra": {
|
||||
"type": "string",
|
||||
"format": "any"
|
||||
},
|
||||
"key": { "type": "string" },
|
||||
"read": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"avatar": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"datetime": {
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"extensions": {
|
||||
"x-is-enum": true
|
||||
},
|
||||
"$ref": "#/components/schemas/NoticeIconItemType"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,72 +1,51 @@
|
|||
export default [
|
||||
{
|
||||
path: '/',
|
||||
component: '../layouts/BlankLayout',
|
||||
path: '/user',
|
||||
layout: false,
|
||||
routes: [
|
||||
{
|
||||
path: '/user',
|
||||
component: '../layouts/UserLayout',
|
||||
routes: [
|
||||
{
|
||||
name: 'login',
|
||||
path: '/user/login',
|
||||
component: './User/login',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
component: '../layouts/SecurityLayout',
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
component: '../layouts/BasicLayout',
|
||||
authority: ['admin', 'user'],
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/welcome',
|
||||
},
|
||||
{
|
||||
path: '/welcome',
|
||||
name: 'welcome',
|
||||
icon: 'smile',
|
||||
component: './Welcome',
|
||||
},
|
||||
{
|
||||
path: '/admin',
|
||||
name: 'admin',
|
||||
icon: 'crown',
|
||||
component: './Admin',
|
||||
authority: ['admin'],
|
||||
routes: [
|
||||
{
|
||||
path: '/admin/sub-page',
|
||||
name: 'sub-page',
|
||||
icon: 'smile',
|
||||
component: './Welcome',
|
||||
authority: ['admin'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'list.table-list',
|
||||
icon: 'table',
|
||||
path: '/list',
|
||||
component: './TableList',
|
||||
},
|
||||
{
|
||||
component: './404',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
component: './404',
|
||||
component: './user/Login',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/welcome',
|
||||
name: 'welcome',
|
||||
icon: 'smile',
|
||||
component: './Welcome',
|
||||
},
|
||||
{
|
||||
path: '/admin',
|
||||
name: 'admin',
|
||||
icon: 'crown',
|
||||
access: 'canAdmin',
|
||||
component: './Admin',
|
||||
routes: [
|
||||
{
|
||||
path: '/admin/sub-page',
|
||||
name: 'sub-page',
|
||||
icon: 'smile',
|
||||
component: './Welcome',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'list.table-list',
|
||||
icon: 'table',
|
||||
path: '/list',
|
||||
component: './TableList',
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/welcome',
|
||||
},
|
||||
{
|
||||
component: './404',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ module.exports = {
|
|||
testURL: 'http://localhost:8000',
|
||||
testEnvironment: './tests/PuppeteerEnvironment',
|
||||
verbose: false,
|
||||
extraSetupFiles: ['./tests/setupTests.js'],
|
||||
globals: {
|
||||
ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: false,
|
||||
localStorage: null,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { Request, Response } from 'express';
|
||||
import { TableListItem, TableListParams } from '@/pages/TableList/data';
|
||||
import moment from 'moment';
|
||||
import { parse } from 'url';
|
||||
|
||||
// mock tableListDataSource
|
||||
const genList = (current: number, pageSize: number) => {
|
||||
const tableListDataSource: TableListItem[] = [];
|
||||
const tableListDataSource: API.RuleListItem[] = [];
|
||||
|
||||
for (let i = 0; i < pageSize; i += 1) {
|
||||
const index = (current - 1) * 10 + i;
|
||||
|
|
@ -21,8 +22,8 @@ const genList = (current: number, pageSize: number) => {
|
|||
desc: '这是一段描述',
|
||||
callNo: Math.floor(Math.random() * 1000),
|
||||
status: Math.floor(Math.random() * 10) % 4,
|
||||
updatedAt: new Date(),
|
||||
createdAt: new Date(),
|
||||
updatedAt: moment().format('YYYY-MM-DD'),
|
||||
createdAt: moment().format('YYYY-MM-DD'),
|
||||
progress: Math.ceil(Math.random() * 100),
|
||||
});
|
||||
}
|
||||
|
|
@ -38,13 +39,17 @@ function getRule(req: Request, res: Response, u: string) {
|
|||
realUrl = req.url;
|
||||
}
|
||||
const { current = 1, pageSize = 10 } = req.query;
|
||||
const params = (new URLSearchParams(realUrl.split('?')[1]) as unknown) as TableListParams;
|
||||
const params = parse(realUrl, true).query as unknown as API.PageParams &
|
||||
API.RuleListItem & {
|
||||
sorter: any;
|
||||
filter: any;
|
||||
};
|
||||
|
||||
let dataSource = [...tableListDataSource].slice(
|
||||
((current as number) - 1) * (pageSize as number),
|
||||
(current as number) * (pageSize as number),
|
||||
);
|
||||
const sorter = JSON.parse(params.sorter as any);
|
||||
const sorter = JSON.parse(params.sorter || ('{}' as any));
|
||||
if (sorter) {
|
||||
dataSource = dataSource.sort((prev, next) => {
|
||||
let sortNumber = 0;
|
||||
|
|
@ -86,14 +91,14 @@ function getRule(req: Request, res: Response, u: string) {
|
|||
}
|
||||
|
||||
if (params.name) {
|
||||
dataSource = dataSource.filter((data) => data.name.includes(params.name || ''));
|
||||
dataSource = dataSource.filter((data) => data?.name?.includes(params.name || ''));
|
||||
}
|
||||
const result = {
|
||||
data: dataSource,
|
||||
total: tableListDataSource.length,
|
||||
success: true,
|
||||
pageSize,
|
||||
current: parseInt(`${params.currentPage}`, 10) || 1,
|
||||
current: parseInt(`${params.current}`, 10) || 1,
|
||||
};
|
||||
|
||||
return res.json(result);
|
||||
|
|
@ -116,7 +121,7 @@ function postRule(req: Request, res: Response, u: string, b: Request) {
|
|||
case 'post':
|
||||
(() => {
|
||||
const i = Math.ceil(Math.random() * 10000);
|
||||
const newRule = {
|
||||
const newRule: API.RuleListItem = {
|
||||
key: tableListDataSource.length,
|
||||
href: 'https://ant.design',
|
||||
avatar: [
|
||||
|
|
@ -128,8 +133,8 @@ function postRule(req: Request, res: Response, u: string, b: Request) {
|
|||
desc,
|
||||
callNo: Math.floor(Math.random() * 1000),
|
||||
status: Math.floor(Math.random() * 10) % 2,
|
||||
updatedAt: new Date(),
|
||||
createdAt: new Date(),
|
||||
updatedAt: moment().format('YYYY-MM-DD'),
|
||||
createdAt: moment().format('YYYY-MM-DD'),
|
||||
progress: Math.ceil(Math.random() * 100),
|
||||
};
|
||||
tableListDataSource.unshift(newRule);
|
||||
|
|
|
|||
196
mock/notices.ts
196
mock/notices.ts
|
|
@ -1,103 +1,105 @@
|
|||
import { Request, Response } from 'express';
|
||||
|
||||
const getNotices = (req: Request, res: Response) => {
|
||||
res.json([
|
||||
{
|
||||
id: '000000001',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
|
||||
title: '你收到了 14 份新周报',
|
||||
datetime: '2017-08-09',
|
||||
type: 'notification',
|
||||
},
|
||||
{
|
||||
id: '000000002',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
|
||||
title: '你推荐的 曲妮妮 已通过第三轮面试',
|
||||
datetime: '2017-08-08',
|
||||
type: 'notification',
|
||||
},
|
||||
{
|
||||
id: '000000003',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png',
|
||||
title: '这种模板可以区分多种通知类型',
|
||||
datetime: '2017-08-07',
|
||||
read: true,
|
||||
type: 'notification',
|
||||
},
|
||||
{
|
||||
id: '000000004',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
|
||||
title: '左侧图标用于区分不同的类型',
|
||||
datetime: '2017-08-07',
|
||||
type: 'notification',
|
||||
},
|
||||
{
|
||||
id: '000000005',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
|
||||
title: '内容不要超过两行字,超出时自动截断',
|
||||
datetime: '2017-08-07',
|
||||
type: 'notification',
|
||||
},
|
||||
{
|
||||
id: '000000006',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
|
||||
title: '曲丽丽 评论了你',
|
||||
description: '描述信息描述信息描述信息',
|
||||
datetime: '2017-08-07',
|
||||
type: 'message',
|
||||
clickClose: true,
|
||||
},
|
||||
{
|
||||
id: '000000007',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
|
||||
title: '朱偏右 回复了你',
|
||||
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
|
||||
datetime: '2017-08-07',
|
||||
type: 'message',
|
||||
clickClose: true,
|
||||
},
|
||||
{
|
||||
id: '000000008',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
|
||||
title: '标题',
|
||||
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
|
||||
datetime: '2017-08-07',
|
||||
type: 'message',
|
||||
clickClose: true,
|
||||
},
|
||||
{
|
||||
id: '000000009',
|
||||
title: '任务名称',
|
||||
description: '任务需要在 2017-01-12 20:00 前启动',
|
||||
extra: '未开始',
|
||||
status: 'todo',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
id: '000000010',
|
||||
title: '第三方紧急代码变更',
|
||||
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
|
||||
extra: '马上到期',
|
||||
status: 'urgent',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
id: '000000011',
|
||||
title: '信息安全考试',
|
||||
description: '指派竹尔于 2017-01-09 前完成更新并发布',
|
||||
extra: '已耗时 8 天',
|
||||
status: 'doing',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
id: '000000012',
|
||||
title: 'ABCD 版本发布',
|
||||
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
|
||||
extra: '进行中',
|
||||
status: 'processing',
|
||||
type: 'event',
|
||||
},
|
||||
]);
|
||||
res.json({
|
||||
data: [
|
||||
{
|
||||
id: '000000001',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
|
||||
title: '你收到了 14 份新周报',
|
||||
datetime: '2017-08-09',
|
||||
type: 'notification',
|
||||
},
|
||||
{
|
||||
id: '000000002',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
|
||||
title: '你推荐的 曲妮妮 已通过第三轮面试',
|
||||
datetime: '2017-08-08',
|
||||
type: 'notification',
|
||||
},
|
||||
{
|
||||
id: '000000003',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png',
|
||||
title: '这种模板可以区分多种通知类型',
|
||||
datetime: '2017-08-07',
|
||||
read: true,
|
||||
type: 'notification',
|
||||
},
|
||||
{
|
||||
id: '000000004',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
|
||||
title: '左侧图标用于区分不同的类型',
|
||||
datetime: '2017-08-07',
|
||||
type: 'notification',
|
||||
},
|
||||
{
|
||||
id: '000000005',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
|
||||
title: '内容不要超过两行字,超出时自动截断',
|
||||
datetime: '2017-08-07',
|
||||
type: 'notification',
|
||||
},
|
||||
{
|
||||
id: '000000006',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
|
||||
title: '曲丽丽 评论了你',
|
||||
description: '描述信息描述信息描述信息',
|
||||
datetime: '2017-08-07',
|
||||
type: 'message',
|
||||
clickClose: true,
|
||||
},
|
||||
{
|
||||
id: '000000007',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
|
||||
title: '朱偏右 回复了你',
|
||||
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
|
||||
datetime: '2017-08-07',
|
||||
type: 'message',
|
||||
clickClose: true,
|
||||
},
|
||||
{
|
||||
id: '000000008',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
|
||||
title: '标题',
|
||||
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
|
||||
datetime: '2017-08-07',
|
||||
type: 'message',
|
||||
clickClose: true,
|
||||
},
|
||||
{
|
||||
id: '000000009',
|
||||
title: '任务名称',
|
||||
description: '任务需要在 2017-01-12 20:00 前启动',
|
||||
extra: '未开始',
|
||||
status: 'todo',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
id: '000000010',
|
||||
title: '第三方紧急代码变更',
|
||||
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
|
||||
extra: '马上到期',
|
||||
status: 'urgent',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
id: '000000011',
|
||||
title: '信息安全考试',
|
||||
description: '指派竹尔于 2017-01-09 前完成更新并发布',
|
||||
extra: '已耗时 8 天',
|
||||
status: 'doing',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
id: '000000012',
|
||||
title: 'ABCD 版本发布',
|
||||
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
|
||||
extra: '进行中',
|
||||
status: 'processing',
|
||||
type: 'event',
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
|
|
|
|||
139
mock/user.ts
139
mock/user.ts
|
|
@ -13,58 +13,85 @@ async function getFakeCaptcha(req: Request, res: Response) {
|
|||
return res.json('captcha-xxx');
|
||||
}
|
||||
|
||||
const { ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION } = process.env;
|
||||
|
||||
/**
|
||||
* 当前用户的权限,如果为空代表没登录
|
||||
* current user access, if is '', user need login
|
||||
* 如果是 pro 的预览,默认是有权限的
|
||||
*/
|
||||
let access = ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site' ? 'admin' : '';
|
||||
|
||||
const getAccess = () => {
|
||||
return access;
|
||||
};
|
||||
|
||||
// 代码中会兼容本地 service mock 以及部署站点的静态数据
|
||||
export default {
|
||||
// 支持值为 Object 和 Array
|
||||
'GET /api/currentUser': {
|
||||
name: 'Serati Ma',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png',
|
||||
userid: '00000001',
|
||||
email: 'antdesign@alipay.com',
|
||||
signature: '海纳百川,有容乃大',
|
||||
title: '交互专家',
|
||||
group: '蚂蚁集团-某某某事业群-某某平台部-某某技术部-UED',
|
||||
tags: [
|
||||
{
|
||||
key: '0',
|
||||
label: '很有想法的',
|
||||
'GET /api/currentUser': (req: Request, res: Response) => {
|
||||
if (!getAccess()) {
|
||||
res.status(401).send({
|
||||
data: {
|
||||
isLogin: false,
|
||||
},
|
||||
errorCode: '401',
|
||||
errorMessage: '请先登录!',
|
||||
success: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
res.send({
|
||||
name: 'Serati Ma',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png',
|
||||
userid: '00000001',
|
||||
email: 'antdesign@alipay.com',
|
||||
signature: '海纳百川,有容乃大',
|
||||
title: '交互专家',
|
||||
group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED',
|
||||
tags: [
|
||||
{
|
||||
key: '0',
|
||||
label: '很有想法的',
|
||||
},
|
||||
{
|
||||
key: '1',
|
||||
label: '专注设计',
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
label: '辣~',
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
label: '大长腿',
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
label: '川妹子',
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
label: '海纳百川',
|
||||
},
|
||||
],
|
||||
notifyCount: 12,
|
||||
unreadCount: 11,
|
||||
country: 'China',
|
||||
access: getAccess(),
|
||||
geographic: {
|
||||
province: {
|
||||
label: '浙江省',
|
||||
key: '330000',
|
||||
},
|
||||
city: {
|
||||
label: '杭州市',
|
||||
key: '330100',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: '1',
|
||||
label: '专注设计',
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
label: '辣~',
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
label: '大长腿',
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
label: '川妹子',
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
label: '海纳百川',
|
||||
},
|
||||
],
|
||||
notifyCount: 12,
|
||||
unreadCount: 11,
|
||||
country: 'China',
|
||||
geographic: {
|
||||
province: {
|
||||
label: '浙江省',
|
||||
key: '330000',
|
||||
},
|
||||
city: {
|
||||
label: '杭州市',
|
||||
key: '330100',
|
||||
},
|
||||
},
|
||||
address: '西湖区工专路 77 号',
|
||||
phone: '0752-268888888',
|
||||
address: '西湖区工专路 77 号',
|
||||
phone: '0752-268888888',
|
||||
});
|
||||
},
|
||||
// GET POST 可省略
|
||||
'GET /api/users': [
|
||||
|
|
@ -88,22 +115,24 @@ export default {
|
|||
},
|
||||
],
|
||||
'POST /api/login/account': async (req: Request, res: Response) => {
|
||||
const { password, userName, type } = req.body;
|
||||
const { password, username, type } = req.body;
|
||||
await waitTime(2000);
|
||||
if (password === 'ant.design' && userName === 'admin') {
|
||||
if (password === 'ant.design' && username === 'admin') {
|
||||
res.send({
|
||||
status: 'ok',
|
||||
type,
|
||||
currentAuthority: 'admin',
|
||||
});
|
||||
access = 'admin';
|
||||
return;
|
||||
}
|
||||
if (password === 'ant.design' && userName === 'user') {
|
||||
if (password === 'ant.design' && username === 'user') {
|
||||
res.send({
|
||||
status: 'ok',
|
||||
type,
|
||||
currentAuthority: 'user',
|
||||
});
|
||||
access = 'user';
|
||||
return;
|
||||
}
|
||||
if (type === 'mobile') {
|
||||
|
|
@ -112,6 +141,7 @@ export default {
|
|||
type,
|
||||
currentAuthority: 'admin',
|
||||
});
|
||||
access = 'admin';
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -120,9 +150,14 @@ export default {
|
|||
type,
|
||||
currentAuthority: 'guest',
|
||||
});
|
||||
access = 'guest';
|
||||
},
|
||||
'POST /api/login/outLogin': (req: Request, res: Response) => {
|
||||
access = '';
|
||||
res.send({ data: {}, success: true });
|
||||
},
|
||||
'POST /api/register': (req: Request, res: Response) => {
|
||||
res.send({ status: 'ok', currentAuthority: 'user' });
|
||||
res.send({ status: 'ok', currentAuthority: 'user', success: true });
|
||||
},
|
||||
'GET /api/500': (req: Request, res: Response) => {
|
||||
res.status(500).send({
|
||||
|
|
|
|||
52
package.json
52
package.json
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "ant-design-pro",
|
||||
"version": "4.5.0",
|
||||
"version": "5.0.0-beta.3",
|
||||
"private": true,
|
||||
"description": "An out-of-box UI solution for enterprise applications",
|
||||
"scripts": {
|
||||
|
|
@ -15,16 +15,17 @@
|
|||
"docker:dev": "docker-compose -f ./docker/docker-compose.dev.yml up",
|
||||
"docker:push": "npm run docker-hub:build && npm run docker:tag && docker push antdesign/ant-design-pro",
|
||||
"docker:tag": "docker tag ant-design-pro antdesign/ant-design-pro",
|
||||
"fetch:blocks": "pro fetch-blocks && npm run prettier",
|
||||
"gh-pages": "gh-pages -d dist",
|
||||
"i18n-remove": "pro i18n-remove --locale=zh-CN --write",
|
||||
"postinstall": "umi g tmp",
|
||||
"lint": "umi g tmp && npm run lint:js && npm run lint:style && npm run lint:prettier",
|
||||
"lint-staged": "lint-staged",
|
||||
"lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx ",
|
||||
"lint:fix": "eslint --fix --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src && npm run lint:style",
|
||||
"lint:js": "eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src",
|
||||
"lint:prettier": "prettier --check \"src/**/*\" --end-of-line auto",
|
||||
"lint:prettier": "prettier -c --write \"src/**/*\" --end-of-line auto",
|
||||
"lint:style": "stylelint --fix \"src/**/*.less\" --syntax less",
|
||||
"openapi": "umi openapi",
|
||||
"precommit": "lint-staged",
|
||||
"prettier": "prettier -c --write \"src/**/*\"",
|
||||
"site": "npm run fetch:blocks && npm run build",
|
||||
|
|
@ -38,6 +39,7 @@
|
|||
"test": "umi test",
|
||||
"test:all": "node ./tests/run-tests.js",
|
||||
"test:component": "umi test ./src/components",
|
||||
"serve": "umi-serve",
|
||||
"tsc": "tsc --noEmit"
|
||||
},
|
||||
"lint-staged": {
|
||||
|
|
@ -53,26 +55,26 @@
|
|||
"not ie <= 10"
|
||||
],
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^4.0.0",
|
||||
"@ant-design/pro-descriptions": "^1.2.0",
|
||||
"@ant-design/pro-form": "^1.3.0",
|
||||
"@ant-design/pro-layout": "^6.9.0",
|
||||
"@ant-design/pro-table": "^2.17.0",
|
||||
"@umijs/route-utils": "^1.0.33",
|
||||
"antd": "^4.15.0",
|
||||
"@ant-design/icons": "^4.5.0",
|
||||
"@ant-design/pro-descriptions": "^1.6.8",
|
||||
"@ant-design/pro-form": "^1.18.3",
|
||||
"@ant-design/pro-layout": "^6.15.3",
|
||||
"@ant-design/pro-table": "^2.30.8",
|
||||
"@umijs/route-utils": "^1.0.36",
|
||||
"antd": "^4.14.0",
|
||||
"classnames": "^2.2.6",
|
||||
"lodash": "^4.17.11",
|
||||
"moment": "^2.25.3",
|
||||
"omit.js": "^2.0.2",
|
||||
"react": "^16.14.0",
|
||||
"react": "^17.0.0",
|
||||
"react-dev-inspector": "^1.1.1",
|
||||
"react-dom": "^17.0.0",
|
||||
"react-helmet-async": "^1.0.4",
|
||||
"umi": "^3.4.1",
|
||||
"umi-request": "^1.0.8"
|
||||
"umi": "^3.5.0",
|
||||
"umi-serve": "^1.9.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ant-design/pro-cli": "^1.0.28",
|
||||
"@ant-design/pro-cli": "^2.0.2",
|
||||
"@types/classnames": "^2.2.7",
|
||||
"@types/express": "^4.17.0",
|
||||
"@types/history": "^4.7.2",
|
||||
|
|
@ -81,14 +83,16 @@
|
|||
"@types/react": "^17.0.0",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"@types/react-helmet": "^6.1.0",
|
||||
"@umijs/fabric": "^2.5.1",
|
||||
"@umijs/fabric": "^2.6.2",
|
||||
"@umijs/openapi": "^1.1.14",
|
||||
"@umijs/plugin-blocks": "^2.0.5",
|
||||
"@umijs/plugin-esbuild": "^1.0.1",
|
||||
"@umijs/plugin-openapi": "^1.2.0",
|
||||
"@umijs/preset-ant-design-pro": "^1.2.0",
|
||||
"@umijs/preset-react": "^1.4.8",
|
||||
"@umijs/preset-dumi": "^1.1.7",
|
||||
"@umijs/preset-react": "^1.7.4",
|
||||
"@umijs/yorkie": "^2.0.3",
|
||||
"carlo": "^0.9.46",
|
||||
"chalk": "^4.0.0",
|
||||
"cross-env": "^7.0.0",
|
||||
"cross-port-killer": "^1.1.1",
|
||||
"detect-installer": "^1.0.1",
|
||||
|
|
@ -99,21 +103,14 @@
|
|||
"jsdom-global": "^3.0.2",
|
||||
"lint-staged": "^10.0.0",
|
||||
"mockjs": "^1.0.1-beta3",
|
||||
"prettier": "^2.0.1",
|
||||
"prettier": "^2.3.2",
|
||||
"puppeteer-core": "^8.0.0",
|
||||
"stylelint": "^13.0.0",
|
||||
"typescript": "^4.0.3"
|
||||
"typescript": "^4.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"checkFiles": [
|
||||
"src/**/*.js*",
|
||||
"src/**/*.ts*",
|
||||
"src/**/*.less",
|
||||
"config/**/*.js*",
|
||||
"scripts/**/*.js"
|
||||
],
|
||||
"create-umi": {
|
||||
"ignoreScript": [
|
||||
"docker*",
|
||||
|
|
@ -142,5 +139,8 @@
|
|||
"CNAME",
|
||||
"create-umi"
|
||||
]
|
||||
},
|
||||
"gitHooks": {
|
||||
"commit-msg": "fabric verify-commit"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 199 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* @see https://umijs.org/zh-CN/plugins/plugin-access
|
||||
* */
|
||||
export default function access(initialState: { currentUser?: API.CurrentUser | undefined }) {
|
||||
const { currentUser } = initialState || {};
|
||||
return {
|
||||
canAdmin: currentUser && currentUser.access === 'admin',
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
import type { Settings as LayoutSettings } from '@ant-design/pro-layout';
|
||||
import { PageLoading } from '@ant-design/pro-layout';
|
||||
import { notification } from 'antd';
|
||||
import type { RequestConfig, RunTimeLayoutConfig } from 'umi';
|
||||
import { history, Link } from 'umi';
|
||||
import RightContent from '@/components/RightContent';
|
||||
import Footer from '@/components/Footer';
|
||||
import { currentUser as queryCurrentUser } from './services/ant-design-pro/api';
|
||||
import { BookOutlined, LinkOutlined } from '@ant-design/icons';
|
||||
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
const loginPath = '/user/login';
|
||||
|
||||
/** 获取用户信息比较慢的时候会展示一个 loading */
|
||||
export const initialStateConfig = {
|
||||
loading: <PageLoading />,
|
||||
};
|
||||
|
||||
/**
|
||||
* @see https://umijs.org/zh-CN/plugins/plugin-initial-state
|
||||
* */
|
||||
export async function getInitialState(): Promise<{
|
||||
settings?: Partial<LayoutSettings>;
|
||||
currentUser?: API.CurrentUser;
|
||||
fetchUserInfo?: () => Promise<API.CurrentUser | undefined>;
|
||||
}> {
|
||||
const fetchUserInfo = async () => {
|
||||
try {
|
||||
const currentUser = await queryCurrentUser();
|
||||
return currentUser;
|
||||
} catch (error) {
|
||||
history.push(loginPath);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
// 如果是登录页面,不执行
|
||||
if (history.location.pathname !== loginPath) {
|
||||
const currentUser = await fetchUserInfo();
|
||||
return {
|
||||
fetchUserInfo,
|
||||
currentUser,
|
||||
settings: {},
|
||||
};
|
||||
}
|
||||
return {
|
||||
fetchUserInfo,
|
||||
settings: {},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 异常处理程序
|
||||
200: '服务器成功返回请求的数据。',
|
||||
201: '新建或修改数据成功。',
|
||||
202: '一个请求已经进入后台排队(异步任务)。',
|
||||
204: '删除数据成功。',
|
||||
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
|
||||
401: '用户没有权限(令牌、用户名、密码错误)。',
|
||||
403: '用户得到授权,但是访问是被禁止的。',
|
||||
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
|
||||
405: '请求方法不被允许。',
|
||||
406: '请求的格式不可得。',
|
||||
410: '请求的资源被永久删除,且不会再得到的。',
|
||||
422: '当创建一个对象时,发生一个验证错误。',
|
||||
500: '服务器发生错误,请检查服务器。',
|
||||
502: '网关错误。',
|
||||
503: '服务不可用,服务器暂时过载或维护。',
|
||||
504: '网关超时。',
|
||||
//-----English
|
||||
200: The server successfully returned the requested data. ',
|
||||
201: New or modified data is successful. ',
|
||||
202: A request has entered the background queue (asynchronous task). ',
|
||||
204: Data deleted successfully. ',
|
||||
400: 'There was an error in the request sent, and the server did not create or modify data. ',
|
||||
401: The user does not have permission (token, username, password error). ',
|
||||
403: The user is authorized, but access is forbidden. ',
|
||||
404: The request sent was for a record that did not exist. ',
|
||||
405: The request method is not allowed. ',
|
||||
406: The requested format is not available. ',
|
||||
410':
|
||||
'The requested resource is permanently deleted and will no longer be available. ',
|
||||
422: When creating an object, a validation error occurred. ',
|
||||
500: An error occurred on the server, please check the server. ',
|
||||
502: Gateway error. ',
|
||||
503: The service is unavailable. ',
|
||||
504: The gateway timed out. ',
|
||||
* @see https://beta-pro.ant.design/docs/request-cn
|
||||
*/
|
||||
export const request: RequestConfig = {
|
||||
errorHandler: (error: any) => {
|
||||
const { response } = error;
|
||||
|
||||
if (!response) {
|
||||
notification.error({
|
||||
description: '您的网络发生异常,无法连接服务器',
|
||||
message: '网络异常',
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
},
|
||||
};
|
||||
|
||||
// ProLayout 支持的api https://procomponents.ant.design/components/layout
|
||||
export const layout: RunTimeLayoutConfig = ({ initialState }) => {
|
||||
return {
|
||||
rightContentRender: () => <RightContent />,
|
||||
disableContentMargin: false,
|
||||
waterMarkProps: {
|
||||
content: initialState?.currentUser?.name,
|
||||
},
|
||||
footerRender: () => <Footer />,
|
||||
onPageChange: () => {
|
||||
const { location } = history;
|
||||
// 如果没有登录,重定向到 login
|
||||
if (!initialState?.currentUser && location.pathname !== loginPath) {
|
||||
history.push(loginPath);
|
||||
}
|
||||
},
|
||||
links: isDev
|
||||
? [
|
||||
<Link to="/umi/plugin/openapi" target="_blank">
|
||||
<LinkOutlined />
|
||||
<span>OpenAPI 文档</span>
|
||||
</Link>,
|
||||
<Link to="/~docs">
|
||||
<BookOutlined />
|
||||
<span>业务组件文档</span>
|
||||
</Link>,
|
||||
]
|
||||
: [],
|
||||
menuHeaderRender: undefined,
|
||||
// 自定义 403 页面
|
||||
// unAccessible: <div>unAccessible</div>,
|
||||
...initialState?.settings,
|
||||
};
|
||||
};
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Result } from 'antd';
|
||||
import check from './CheckPermissions';
|
||||
import type { IAuthorityType } from './CheckPermissions';
|
||||
import type AuthorizedRoute from './AuthorizedRoute';
|
||||
import type Secured from './Secured';
|
||||
|
||||
type AuthorizedProps = {
|
||||
authority: IAuthorityType;
|
||||
noMatch?: React.ReactNode;
|
||||
};
|
||||
|
||||
type IAuthorizedType = React.FunctionComponent<AuthorizedProps> & {
|
||||
Secured: typeof Secured;
|
||||
check: typeof check;
|
||||
AuthorizedRoute: typeof AuthorizedRoute;
|
||||
};
|
||||
|
||||
const Authorized: React.FunctionComponent<AuthorizedProps> = ({
|
||||
children,
|
||||
authority,
|
||||
noMatch = (
|
||||
<Result
|
||||
status="403"
|
||||
title="403"
|
||||
subTitle="Sorry, you are not authorized to access this page."
|
||||
/>
|
||||
),
|
||||
}) => {
|
||||
const childrenRender: React.ReactNode = typeof children === 'undefined' ? null : children;
|
||||
const dom = check(authority, childrenRender, noMatch);
|
||||
return <>{dom}</>;
|
||||
};
|
||||
|
||||
export default Authorized as IAuthorizedType;
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import { Redirect, Route } from 'umi';
|
||||
|
||||
import React from 'react';
|
||||
import Authorized from './Authorized';
|
||||
import type { IAuthorityType } from './CheckPermissions';
|
||||
|
||||
type AuthorizedRouteProps = {
|
||||
currentAuthority: string;
|
||||
component: React.ComponentClass<any, any>;
|
||||
render: (props: any) => React.ReactNode;
|
||||
redirectPath: string;
|
||||
authority: IAuthorityType;
|
||||
};
|
||||
|
||||
const AuthorizedRoute: React.SFC<AuthorizedRouteProps> = ({
|
||||
component: Component,
|
||||
render,
|
||||
authority,
|
||||
redirectPath,
|
||||
...rest
|
||||
}) => (
|
||||
<Authorized
|
||||
authority={authority}
|
||||
noMatch={<Route {...rest} render={() => <Redirect to={{ pathname: redirectPath }} />} />}
|
||||
>
|
||||
<Route
|
||||
{...rest}
|
||||
render={(props: any) => (Component ? <Component {...props} /> : render(props))}
|
||||
/>
|
||||
</Authorized>
|
||||
);
|
||||
|
||||
export default AuthorizedRoute;
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
import React from 'react';
|
||||
import { CURRENT } from './renderAuthorize';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import PromiseRender from './PromiseRender';
|
||||
|
||||
export type IAuthorityType =
|
||||
| undefined
|
||||
| string
|
||||
| string[]
|
||||
| Promise<boolean>
|
||||
| ((currentAuthority: string | string[]) => IAuthorityType);
|
||||
|
||||
/**
|
||||
* @en-US
|
||||
* General permission check method
|
||||
* Common check permissions method
|
||||
* @param {Permission judgment} authority
|
||||
* @param {Your permission | Your permission description} currentAuthority
|
||||
* @param {Passing components} target
|
||||
* @param {no pass components | no pass components} Exception
|
||||
* -------------------------------------------------------
|
||||
* @zh-CN
|
||||
* 通用权限检查方法 Common check permissions method
|
||||
*
|
||||
* @param { 权限判定 | Permission judgment } authority
|
||||
* @param { 你的权限 | Your permission description } currentAuthority
|
||||
* @param { 通过的组件 | Passing components } target
|
||||
* @param { 未通过的组件 | no pass components } Exception
|
||||
*/
|
||||
const checkPermissions = <T, K>(
|
||||
authority: IAuthorityType,
|
||||
currentAuthority: string | string[],
|
||||
target: T,
|
||||
Exception: K,
|
||||
): T | K | React.ReactNode => {
|
||||
// No judgment permission. View all by default
|
||||
// Retirement authority, return target;
|
||||
if (!authority) {
|
||||
return target;
|
||||
}
|
||||
// Array processing
|
||||
if (Array.isArray(authority)) {
|
||||
if (Array.isArray(currentAuthority)) {
|
||||
if (currentAuthority.some((item) => authority.includes(item))) {
|
||||
return target;
|
||||
}
|
||||
} else if (authority.includes(currentAuthority)) {
|
||||
return target;
|
||||
}
|
||||
return Exception;
|
||||
}
|
||||
// Deal with string
|
||||
if (typeof authority === 'string') {
|
||||
if (Array.isArray(currentAuthority)) {
|
||||
if (currentAuthority.some((item) => authority === item)) {
|
||||
return target;
|
||||
}
|
||||
} else if (authority === currentAuthority) {
|
||||
return target;
|
||||
}
|
||||
return Exception;
|
||||
}
|
||||
// Deal with promise
|
||||
if (authority instanceof Promise) {
|
||||
return <PromiseRender<T, K> ok={target} error={Exception} promise={authority} />;
|
||||
}
|
||||
// Deal with function
|
||||
if (typeof authority === 'function') {
|
||||
const bool = authority(currentAuthority);
|
||||
// The return value after the function is executed is Promise
|
||||
if (bool instanceof Promise) {
|
||||
return <PromiseRender<T, K> ok={target} error={Exception} promise={bool} />;
|
||||
}
|
||||
if (bool) {
|
||||
return target;
|
||||
}
|
||||
return Exception;
|
||||
}
|
||||
throw new Error('unsupported parameters');
|
||||
};
|
||||
|
||||
export { checkPermissions };
|
||||
|
||||
function check<T, K>(authority: IAuthorityType, target: T, Exception: K): T | K | React.ReactNode {
|
||||
return checkPermissions<T, K>(authority, CURRENT, target, Exception);
|
||||
}
|
||||
|
||||
export default check;
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Spin } from 'antd';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import { isComponentClass } from './Secured';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
|
||||
type PromiseRenderProps<T, K> = {
|
||||
ok: T;
|
||||
error: K;
|
||||
promise: Promise<boolean>;
|
||||
};
|
||||
|
||||
type PromiseRenderState = {
|
||||
component: React.ComponentClass | React.FunctionComponent;
|
||||
};
|
||||
|
||||
export default class PromiseRender<T, K> extends React.Component<
|
||||
PromiseRenderProps<T, K>,
|
||||
PromiseRenderState
|
||||
> {
|
||||
state: PromiseRenderState = {
|
||||
component: () => null,
|
||||
};
|
||||
|
||||
componentDidMount(): void {
|
||||
this.setRenderComponent(this.props);
|
||||
}
|
||||
|
||||
shouldComponentUpdate = (
|
||||
nextProps: PromiseRenderProps<T, K>,
|
||||
nextState: PromiseRenderState,
|
||||
): boolean => {
|
||||
const { component } = this.state;
|
||||
if (!isEqual(nextProps, this.props)) {
|
||||
this.setRenderComponent(nextProps);
|
||||
}
|
||||
if (nextState.component !== component) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
// set render Component : ok or error
|
||||
setRenderComponent(props: PromiseRenderProps<T, K>): void {
|
||||
const ok = this.checkIsInstantiation(props.ok);
|
||||
const error = this.checkIsInstantiation(props.error);
|
||||
props.promise
|
||||
.then(() => {
|
||||
this.setState({
|
||||
component: ok,
|
||||
});
|
||||
return true;
|
||||
})
|
||||
.catch(() => {
|
||||
this.setState({
|
||||
component: error,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Determine whether the incoming component has been instantiated
|
||||
// AuthorizedRoute is already instantiated
|
||||
// Authorized render is already instantiated, children is no instantiated
|
||||
// Secured is not instantiated
|
||||
checkIsInstantiation = (
|
||||
target: React.ReactNode | React.ComponentClass,
|
||||
): React.FunctionComponent => {
|
||||
if (isComponentClass(target)) {
|
||||
const Target = target as React.ComponentClass;
|
||||
return (props: any) => <Target {...props} />;
|
||||
}
|
||||
if (React.isValidElement(target)) {
|
||||
return (props: any) => React.cloneElement(target, props);
|
||||
}
|
||||
return () => target as React.ReactNode & null;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { component: Component } = this.state;
|
||||
const { ok, error, promise, ...rest } = this.props;
|
||||
|
||||
return Component ? (
|
||||
<Component {...rest} />
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
margin: 'auto',
|
||||
paddingTop: 50,
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
import React from 'react';
|
||||
import CheckPermissions from './CheckPermissions';
|
||||
|
||||
/**
|
||||
* @en-US No pages can be accessed by default,default is "NULL"
|
||||
* @zh-CN 默认不能访问任何页面 default is "NULL"
|
||||
* */
|
||||
const Exception403 = () => 403;
|
||||
|
||||
export const isComponentClass = (component: React.ComponentClass | React.ReactNode): boolean => {
|
||||
if (!component) return false;
|
||||
const proto = Object.getPrototypeOf(component);
|
||||
if (proto === React.Component || proto === Function.prototype) return true;
|
||||
return isComponentClass(proto);
|
||||
};
|
||||
|
||||
// Determine whether the incoming component has been instantiated
|
||||
// AuthorizedRoute is already instantiated
|
||||
// Authorized render is already instantiated, children is no instantiated
|
||||
// Secured is not instantiated
|
||||
const checkIsInstantiation = (target: React.ComponentClass | React.ReactNode) => {
|
||||
if (isComponentClass(target)) {
|
||||
const Target = target as React.ComponentClass;
|
||||
return (props: any) => <Target {...props} />;
|
||||
}
|
||||
if (React.isValidElement(target)) {
|
||||
return (props: any) => React.cloneElement(target, props);
|
||||
}
|
||||
return () => target;
|
||||
};
|
||||
|
||||
/**
|
||||
* @en-US
|
||||
* Used to determine whether you have permission to access this view permission
|
||||
* authority supports incoming string, () => boolean | Promise
|
||||
* e.g.'user' Only user user can access
|
||||
* e.g.'user,admin' user and admin can access
|
||||
* e.g. ()=>boolean return true to access, return false to not access
|
||||
* e.g. Promise then can be accessed, catch can not be accessed
|
||||
* e.g. authority support incoming string, () => boolean | Promise
|
||||
* e.g.'user' only user user can access
|
||||
* e.g.'user, admin' user and admin can access
|
||||
* e.g. () => boolean true to be able to visit, return false can not be accessed
|
||||
* e.g. Promise then can not access the visit to catch
|
||||
*-------------------------------------------------------------
|
||||
* @zh-CN
|
||||
* 用于判断是否拥有权限访问此 view 权限 authority 支持传入 string, () => boolean | Promise e.g. 'user' 只有 user 用户能访问
|
||||
* e.g. 'user,admin' user 和 admin 都能访问 e.g. ()=>boolean 返回true能访问,返回false不能访问 e.g. Promise then 能访问
|
||||
* catch不能访问 e.g. authority support incoming string, () => boolean | Promise e.g. 'user' only user
|
||||
* user can access e.g. 'user, admin' user and admin can access e.g. () => boolean true to be able
|
||||
* to visit, return false can not be accessed e.g. Promise then can not access the visit to catch
|
||||
*
|
||||
* @param {string | function | Promise} authority
|
||||
* @param {ReactNode} error non-required parameter
|
||||
*/
|
||||
const authorize = (authority: string, error?: React.ReactNode) => {
|
||||
/**
|
||||
* @en-US
|
||||
* conversion into a class
|
||||
* Prevent the staticContext from being found to cause an error when the string is passed in
|
||||
* String parameters can cause staticContext not found error
|
||||
*-------------------------------------------------------------
|
||||
* @zh-CN
|
||||
* Conversion into a class 防止传入字符串时找不到staticContext造成报错 String parameters can cause staticContext
|
||||
* not found error
|
||||
*/
|
||||
let classError: boolean | React.FunctionComponent = false;
|
||||
if (error) {
|
||||
classError = (() => error) as React.FunctionComponent;
|
||||
}
|
||||
if (!authority) {
|
||||
throw new Error('authority is required');
|
||||
}
|
||||
return function decideAuthority(target: React.ComponentClass | React.ReactNode) {
|
||||
const component = CheckPermissions(authority, target, classError || Exception403);
|
||||
return checkIsInstantiation(component);
|
||||
};
|
||||
};
|
||||
|
||||
export default authorize;
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
import Authorized from './Authorized';
|
||||
import Secured from './Secured';
|
||||
import check from './CheckPermissions';
|
||||
import renderAuthorize from './renderAuthorize';
|
||||
|
||||
Authorized.Secured = Secured;
|
||||
Authorized.check = check;
|
||||
|
||||
const RenderAuthorize = renderAuthorize(Authorized);
|
||||
|
||||
export default RenderAuthorize;
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable import/no-mutable-exports */
|
||||
let CURRENT: string | string[] = 'NULL';
|
||||
|
||||
type CurrentAuthorityType = string | string[] | (() => typeof CURRENT);
|
||||
/**
|
||||
* Use authority or getAuthority
|
||||
*
|
||||
* @param {string|()=>String} currentAuthority
|
||||
*/
|
||||
const renderAuthorize = <T>(Authorized: T): ((currentAuthority: CurrentAuthorityType) => T) => (
|
||||
currentAuthority: CurrentAuthorityType,
|
||||
): T => {
|
||||
if (currentAuthority) {
|
||||
if (typeof currentAuthority === 'function') {
|
||||
CURRENT = currentAuthority();
|
||||
}
|
||||
if (
|
||||
Object.prototype.toString.call(currentAuthority) === '[object String]' ||
|
||||
Array.isArray(currentAuthority)
|
||||
) {
|
||||
CURRENT = currentAuthority as string[];
|
||||
}
|
||||
} else {
|
||||
CURRENT = 'NULL';
|
||||
}
|
||||
return Authorized;
|
||||
};
|
||||
|
||||
export { CURRENT };
|
||||
export default <T>(Authorized: T) => renderAuthorize<T>(Authorized);
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
import { useIntl } from 'umi';
|
||||
import { GithubOutlined } from '@ant-design/icons';
|
||||
import { DefaultFooter } from '@ant-design/pro-layout';
|
||||
|
||||
export default () => {
|
||||
const intl = useIntl();
|
||||
const defaultMessage = intl.formatMessage({
|
||||
id: 'app.copyright.produced',
|
||||
defaultMessage: '蚂蚁集团体验技术部出品',
|
||||
});
|
||||
|
||||
return (
|
||||
<DefaultFooter
|
||||
copyright={`2020 ${defaultMessage}`}
|
||||
links={[
|
||||
{
|
||||
key: 'Ant Design Pro',
|
||||
title: 'Ant Design Pro',
|
||||
href: 'https://pro.ant.design',
|
||||
blankTarget: true,
|
||||
},
|
||||
{
|
||||
key: 'github',
|
||||
title: <GithubOutlined />,
|
||||
href: 'https://github.com/ant-design/ant-design-pro',
|
||||
blankTarget: true,
|
||||
},
|
||||
{
|
||||
key: 'Ant Design',
|
||||
title: 'Ant Design',
|
||||
href: 'https://ant.design',
|
||||
blankTarget: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import { Avatar, Menu, Spin } from 'antd';
|
||||
import React from 'react';
|
||||
import type { ConnectProps } from 'umi';
|
||||
import { history, connect } from 'umi';
|
||||
import type { ConnectState } from '@/models/connect';
|
||||
import type { CurrentUser } from '@/models/user';
|
||||
import HeaderDropdown from '../HeaderDropdown';
|
||||
import styles from './index.less';
|
||||
|
||||
export type GlobalHeaderRightProps = {
|
||||
currentUser?: CurrentUser;
|
||||
menu?: boolean;
|
||||
} & Partial<ConnectProps>;
|
||||
|
||||
class AvatarDropdown extends React.Component<GlobalHeaderRightProps> {
|
||||
onMenuClick = (event: { key: React.Key; keyPath: React.Key[]; item: React.ReactInstance }) => {
|
||||
const { key } = event;
|
||||
|
||||
if (key === 'logout') {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
if (dispatch) {
|
||||
dispatch({
|
||||
type: 'login/logout',
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
history.push(`/account/${key}`);
|
||||
};
|
||||
|
||||
render(): React.ReactNode {
|
||||
const {
|
||||
currentUser = {
|
||||
avatar: '',
|
||||
name: '',
|
||||
},
|
||||
menu,
|
||||
} = this.props;
|
||||
const menuHeaderDropdown = (
|
||||
<Menu className={styles.menu} selectedKeys={[]} onClick={this.onMenuClick}>
|
||||
{menu && (
|
||||
<Menu.Item key="center">
|
||||
<UserOutlined />
|
||||
个人中心
|
||||
</Menu.Item>
|
||||
)}
|
||||
{menu && (
|
||||
<Menu.Item key="settings">
|
||||
<SettingOutlined />
|
||||
个人设置
|
||||
</Menu.Item>
|
||||
)}
|
||||
{menu && <Menu.Divider />}
|
||||
|
||||
<Menu.Item key="logout">
|
||||
<LogoutOutlined />
|
||||
退出登录
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
return currentUser && currentUser.name ? (
|
||||
<HeaderDropdown overlay={menuHeaderDropdown}>
|
||||
<span className={`${styles.action} ${styles.account}`}>
|
||||
<Avatar size="small" className={styles.avatar} src={currentUser.avatar} alt="avatar" />
|
||||
<span className={`${styles.name} anticon`}>{currentUser.name}</span>
|
||||
</span>
|
||||
</HeaderDropdown>
|
||||
) : (
|
||||
<span className={`${styles.action} ${styles.account}`}>
|
||||
<Spin
|
||||
size="small"
|
||||
style={{
|
||||
marginLeft: 8,
|
||||
marginRight: 8,
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(({ user }: ConnectState) => ({
|
||||
currentUser: user.currentUser,
|
||||
}))(AvatarDropdown);
|
||||
|
|
@ -1,168 +0,0 @@
|
|||
import { Component } from 'react';
|
||||
import type { ConnectProps } from 'umi';
|
||||
import { connect } from 'umi';
|
||||
import { Tag, message } from 'antd';
|
||||
import groupBy from 'lodash/groupBy';
|
||||
import moment from 'moment';
|
||||
import type { NoticeItem } from '@/models/global';
|
||||
import type { CurrentUser } from '@/models/user';
|
||||
import type { ConnectState } from '@/models/connect';
|
||||
import NoticeIcon from '../NoticeIcon';
|
||||
import styles from './index.less';
|
||||
|
||||
export type GlobalHeaderRightProps = {
|
||||
notices?: NoticeItem[];
|
||||
currentUser?: CurrentUser;
|
||||
fetchingNotices?: boolean;
|
||||
onNoticeVisibleChange?: (visible: boolean) => void;
|
||||
onNoticeClear?: (tabName?: string) => void;
|
||||
} & Partial<ConnectProps>;
|
||||
|
||||
class GlobalHeaderRight extends Component<GlobalHeaderRightProps> {
|
||||
componentDidMount() {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
if (dispatch) {
|
||||
dispatch({
|
||||
type: 'global/fetchNotices',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
changeReadState = (clickedItem: NoticeItem): void => {
|
||||
const { id } = clickedItem;
|
||||
const { dispatch } = this.props;
|
||||
|
||||
if (dispatch) {
|
||||
dispatch({
|
||||
type: 'global/changeNoticeReadState',
|
||||
payload: id,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleNoticeClear = (title: string, key: string) => {
|
||||
const { dispatch } = this.props;
|
||||
message.success(`${'Emptied'} ${title}`);
|
||||
|
||||
if (dispatch) {
|
||||
dispatch({
|
||||
type: 'global/clearNotices',
|
||||
payload: key,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
getNoticeData = (): Record<string, NoticeItem[]> => {
|
||||
const { notices = [] } = this.props;
|
||||
|
||||
if (!notices || notices.length === 0 || !Array.isArray(notices)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const newNotices = notices.map((notice) => {
|
||||
const newNotice = { ...notice };
|
||||
|
||||
if (newNotice.datetime) {
|
||||
newNotice.datetime = moment(notice.datetime as string).fromNow();
|
||||
}
|
||||
|
||||
if (newNotice.id) {
|
||||
newNotice.key = newNotice.id;
|
||||
}
|
||||
|
||||
if (newNotice.extra && newNotice.status) {
|
||||
const color = {
|
||||
todo: '',
|
||||
processing: 'blue',
|
||||
urgent: 'red',
|
||||
doing: 'gold',
|
||||
}[newNotice.status];
|
||||
newNotice.extra = (
|
||||
<Tag
|
||||
color={color}
|
||||
style={{
|
||||
marginRight: 0,
|
||||
}}
|
||||
>
|
||||
{newNotice.extra}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
return newNotice;
|
||||
});
|
||||
return groupBy(newNotices, 'type');
|
||||
};
|
||||
|
||||
getUnreadData = (noticeData: Record<string, NoticeItem[]>) => {
|
||||
const unreadMsg: Record<string, number> = {};
|
||||
Object.keys(noticeData).forEach((key) => {
|
||||
const value = noticeData[key];
|
||||
|
||||
if (!unreadMsg[key]) {
|
||||
unreadMsg[key] = 0;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
unreadMsg[key] = value.filter((item) => !item.read).length;
|
||||
}
|
||||
});
|
||||
return unreadMsg;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { currentUser, fetchingNotices, onNoticeVisibleChange } = this.props;
|
||||
const noticeData = this.getNoticeData();
|
||||
const unreadMsg = this.getUnreadData(noticeData);
|
||||
return (
|
||||
<NoticeIcon
|
||||
className={styles.action}
|
||||
count={currentUser && currentUser.unreadCount}
|
||||
onItemClick={(item) => {
|
||||
this.changeReadState(item as NoticeItem);
|
||||
}}
|
||||
loading={fetchingNotices}
|
||||
clearText="Empty"
|
||||
viewMoreText="See more"
|
||||
onClear={this.handleNoticeClear}
|
||||
onPopupVisibleChange={onNoticeVisibleChange}
|
||||
onViewMore={() => message.info('Click on view more')}
|
||||
clearClose
|
||||
>
|
||||
<NoticeIcon.Tab
|
||||
tabKey="notification"
|
||||
count={unreadMsg.notification}
|
||||
list={noticeData.notification}
|
||||
title="Notification"
|
||||
emptyText="You have viewed all notifications"
|
||||
showViewMore
|
||||
/>
|
||||
<NoticeIcon.Tab
|
||||
tabKey="message"
|
||||
count={unreadMsg.message}
|
||||
list={noticeData.message}
|
||||
title="Message"
|
||||
emptyText="You have read all messages"
|
||||
showViewMore
|
||||
/>
|
||||
<NoticeIcon.Tab
|
||||
tabKey="event"
|
||||
title="To do"
|
||||
emptyText="You have completed all to-dos"
|
||||
count={unreadMsg.event}
|
||||
list={noticeData.event}
|
||||
showViewMore
|
||||
/>
|
||||
</NoticeIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(({ user, global, loading }: ConnectState) => ({
|
||||
currentUser: user.currentUser,
|
||||
collapsed: global.collapsed,
|
||||
fetchingMoreNotices: loading.effects['global/fetchMoreNotices'],
|
||||
fetchingNotices: loading.effects['global/fetchNotices'],
|
||||
notices: global.notices,
|
||||
}))(GlobalHeaderRight);
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
import { Tooltip, Tag } from 'antd';
|
||||
import type { Settings as ProSettings } from '@ant-design/pro-layout';
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import React from 'react';
|
||||
import type { ConnectProps } from 'umi';
|
||||
import { connect, SelectLang } from 'umi';
|
||||
import type { ConnectState } from '@/models/connect';
|
||||
import Avatar from './AvatarDropdown';
|
||||
import HeaderSearch from '../HeaderSearch';
|
||||
import styles from './index.less';
|
||||
|
||||
export type GlobalHeaderRightProps = {
|
||||
theme?: ProSettings['navTheme'] | 'realDark';
|
||||
} & Partial<ConnectProps> &
|
||||
Partial<ProSettings>;
|
||||
|
||||
const ENVTagColor = {
|
||||
dev: 'orange',
|
||||
test: 'green',
|
||||
pre: '#87d068',
|
||||
};
|
||||
|
||||
const GlobalHeaderRight: React.SFC<GlobalHeaderRightProps> = (props) => {
|
||||
const { theme, layout } = props;
|
||||
let className = styles.right;
|
||||
|
||||
if (theme === 'dark' && layout === 'top') {
|
||||
className = `${styles.right} ${styles.dark}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<HeaderSearch
|
||||
className={`${styles.action} ${styles.search}`}
|
||||
placeholder="Site Search"
|
||||
defaultValue="umi ui"
|
||||
options={[
|
||||
{ label: <a href="https://umijs.org/zh/guide/umi-ui.html">umi ui</a>, value: 'umi ui' },
|
||||
{
|
||||
label: <a href="next.ant.design">Ant Design</a>,
|
||||
value: 'Ant Design',
|
||||
},
|
||||
{
|
||||
label: <a href="https://protable.ant.design/">Pro Table</a>,
|
||||
value: 'Pro Table',
|
||||
},
|
||||
{
|
||||
label: <a href="https://prolayout.ant.design/">Pro Layout</a>,
|
||||
value: 'Pro Layout',
|
||||
},
|
||||
]}
|
||||
// onSearch={value => {
|
||||
// //console.log('input', value);
|
||||
// }}
|
||||
/>
|
||||
<Tooltip title="Use documentation">
|
||||
<a
|
||||
style={{
|
||||
color: 'inherit',
|
||||
}}
|
||||
target="_blank"
|
||||
href="https://pro.ant.design/docs/getting-started"
|
||||
rel="noopener noreferrer"
|
||||
className={styles.action}
|
||||
>
|
||||
<QuestionCircleOutlined />
|
||||
</a>
|
||||
</Tooltip>
|
||||
<Avatar />
|
||||
{REACT_APP_ENV && (
|
||||
<span>
|
||||
<Tag color={ENVTagColor[REACT_APP_ENV]}>{REACT_APP_ENV}</Tag>
|
||||
</span>
|
||||
)}
|
||||
<SelectLang className={styles.action} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(({ settings }: ConnectState) => ({
|
||||
theme: settings.navTheme,
|
||||
layout: settings.layout,
|
||||
}))(GlobalHeaderRight);
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
@import '~antd/es/style/themes/default.less';
|
||||
|
||||
.headerSearch {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
.input {
|
||||
width: 0;
|
||||
min-width: 0;
|
||||
|
|
@ -12,16 +14,9 @@
|
|||
background: transparent;
|
||||
}
|
||||
input {
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
border: 0;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
&,
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-bottom: 1px solid @border-color-base;
|
||||
}
|
||||
|
||||
&.show {
|
||||
width: 210px;
|
||||
margin-left: 8px;
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ export type HeaderSearchProps = {
|
|||
className?: string;
|
||||
placeholder?: string;
|
||||
options: AutoCompleteProps['options'];
|
||||
defaultOpen?: boolean;
|
||||
open?: boolean;
|
||||
defaultVisible?: boolean;
|
||||
visible?: boolean;
|
||||
defaultValue?: string;
|
||||
value?: string;
|
||||
};
|
||||
|
|
@ -26,8 +26,8 @@ const HeaderSearch: React.FC<HeaderSearchProps> = (props) => {
|
|||
defaultValue,
|
||||
onVisibleChange,
|
||||
placeholder,
|
||||
open,
|
||||
defaultOpen,
|
||||
visible,
|
||||
defaultVisible,
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
|
|
@ -38,15 +38,14 @@ const HeaderSearch: React.FC<HeaderSearchProps> = (props) => {
|
|||
onChange: props.onChange,
|
||||
});
|
||||
|
||||
const [searchMode, setSearchMode] = useMergedState(defaultOpen ?? false, {
|
||||
value: props.open,
|
||||
const [searchMode, setSearchMode] = useMergedState(defaultVisible ?? false, {
|
||||
value: props.visible,
|
||||
onChange: onVisibleChange,
|
||||
});
|
||||
|
||||
const inputClass = classNames(styles.input, {
|
||||
[styles.show]: searchMode,
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(className, styles.headerSearch)}
|
||||
|
|
@ -74,14 +73,11 @@ const HeaderSearch: React.FC<HeaderSearchProps> = (props) => {
|
|||
key="AutoComplete"
|
||||
className={inputClass}
|
||||
value={value}
|
||||
style={{
|
||||
height: 28,
|
||||
marginTop: -6,
|
||||
}}
|
||||
options={restProps.options}
|
||||
onChange={setValue}
|
||||
>
|
||||
<Input
|
||||
size="small"
|
||||
ref={inputRef}
|
||||
defaultValue={defaultValue}
|
||||
aria-label={placeholder}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,126 @@
|
|||
import { BellOutlined } from '@ant-design/icons';
|
||||
import { Badge, Spin, Tabs } from 'antd';
|
||||
import useMergedState from 'rc-util/es/hooks/useMergedState';
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import type { NoticeIconTabProps } from './NoticeList';
|
||||
import NoticeList from './NoticeList';
|
||||
import HeaderDropdown from '../HeaderDropdown';
|
||||
import styles from './index.less';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
export type NoticeIconProps = {
|
||||
count?: number;
|
||||
bell?: React.ReactNode;
|
||||
className?: string;
|
||||
loading?: boolean;
|
||||
onClear?: (tabName: string, tabKey: string) => void;
|
||||
onItemClick?: (item: API.NoticeIconItem, tabProps: NoticeIconTabProps) => void;
|
||||
onViewMore?: (tabProps: NoticeIconTabProps, e: MouseEvent) => void;
|
||||
onTabChange?: (tabTile: string) => void;
|
||||
style?: React.CSSProperties;
|
||||
onPopupVisibleChange?: (visible: boolean) => void;
|
||||
popupVisible?: boolean;
|
||||
clearText?: string;
|
||||
viewMoreText?: string;
|
||||
clearClose?: boolean;
|
||||
emptyImage?: string;
|
||||
children?: React.ReactElement<NoticeIconTabProps>[];
|
||||
};
|
||||
|
||||
const NoticeIcon: React.FC<NoticeIconProps> & {
|
||||
Tab: typeof NoticeList;
|
||||
} = (props) => {
|
||||
const getNotificationBox = (): React.ReactNode => {
|
||||
const {
|
||||
children,
|
||||
loading,
|
||||
onClear,
|
||||
onTabChange,
|
||||
onItemClick,
|
||||
onViewMore,
|
||||
clearText,
|
||||
viewMoreText,
|
||||
} = props;
|
||||
if (!children) {
|
||||
return null;
|
||||
}
|
||||
const panes: React.ReactNode[] = [];
|
||||
React.Children.forEach(children, (child: React.ReactElement<NoticeIconTabProps>): void => {
|
||||
if (!child) {
|
||||
return;
|
||||
}
|
||||
const { list, title, count, tabKey, showClear, showViewMore } = child.props;
|
||||
const len = list && list.length ? list.length : 0;
|
||||
const msgCount = count || count === 0 ? count : len;
|
||||
const tabTitle: string = msgCount > 0 ? `${title} (${msgCount})` : title;
|
||||
panes.push(
|
||||
<TabPane tab={tabTitle} key={tabKey}>
|
||||
<NoticeList
|
||||
clearText={clearText}
|
||||
viewMoreText={viewMoreText}
|
||||
list={list}
|
||||
tabKey={tabKey}
|
||||
onClear={(): void => onClear && onClear(title, tabKey)}
|
||||
onClick={(item): void => onItemClick && onItemClick(item, child.props)}
|
||||
onViewMore={(event): void => onViewMore && onViewMore(child.props, event)}
|
||||
showClear={showClear}
|
||||
showViewMore={showViewMore}
|
||||
title={title}
|
||||
/>
|
||||
</TabPane>,
|
||||
);
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<Spin spinning={loading} delay={300}>
|
||||
<Tabs className={styles.tabs} onChange={onTabChange}>
|
||||
{panes}
|
||||
</Tabs>
|
||||
</Spin>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const { className, count, bell } = props;
|
||||
|
||||
const [visible, setVisible] = useMergedState<boolean>(false, {
|
||||
value: props.popupVisible,
|
||||
onChange: props.onPopupVisibleChange,
|
||||
});
|
||||
const noticeButtonClass = classNames(className, styles.noticeButton);
|
||||
const notificationBox = getNotificationBox();
|
||||
const NoticeBellIcon = bell || <BellOutlined className={styles.icon} />;
|
||||
const trigger = (
|
||||
<span className={classNames(noticeButtonClass, { opened: visible })}>
|
||||
<Badge count={count} style={{ boxShadow: 'none' }} className={styles.badge}>
|
||||
{NoticeBellIcon}
|
||||
</Badge>
|
||||
</span>
|
||||
);
|
||||
if (!notificationBox) {
|
||||
return trigger;
|
||||
}
|
||||
|
||||
return (
|
||||
<HeaderDropdown
|
||||
placement="bottomRight"
|
||||
overlay={notificationBox}
|
||||
overlayClassName={styles.popover}
|
||||
trigger={['click']}
|
||||
visible={visible}
|
||||
onVisibleChange={setVisible}
|
||||
>
|
||||
{trigger}
|
||||
</HeaderDropdown>
|
||||
);
|
||||
};
|
||||
|
||||
NoticeIcon.defaultProps = {
|
||||
emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg',
|
||||
};
|
||||
|
||||
NoticeIcon.Tab = NoticeList;
|
||||
|
||||
export default NoticeIcon;
|
||||
|
|
@ -2,28 +2,25 @@ import { Avatar, List } from 'antd';
|
|||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import type { NoticeIconData } from './index';
|
||||
import styles from './NoticeList.less';
|
||||
|
||||
export type NoticeIconTabProps = {
|
||||
count?: number;
|
||||
name?: string;
|
||||
showClear?: boolean;
|
||||
showViewMore?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
title: string;
|
||||
tabKey: string;
|
||||
data?: NoticeIconData[];
|
||||
onClick?: (item: NoticeIconData) => void;
|
||||
tabKey: API.NoticeIconItemType;
|
||||
onClick?: (item: API.NoticeIconItem) => void;
|
||||
onClear?: () => void;
|
||||
emptyText?: string;
|
||||
clearText?: string;
|
||||
viewMoreText?: string;
|
||||
list: NoticeIconData[];
|
||||
list: API.NoticeIconItem[];
|
||||
onViewMore?: (e: any) => void;
|
||||
};
|
||||
const NoticeList: React.SFC<NoticeIconTabProps> = ({
|
||||
data = [],
|
||||
const NoticeList: React.FC<NoticeIconTabProps> = ({
|
||||
list = [],
|
||||
onClick,
|
||||
onClear,
|
||||
title,
|
||||
|
|
@ -34,7 +31,7 @@ const NoticeList: React.SFC<NoticeIconTabProps> = ({
|
|||
viewMoreText,
|
||||
showViewMore = false,
|
||||
}) => {
|
||||
if (!data || data.length === 0) {
|
||||
if (!list || list.length === 0) {
|
||||
return (
|
||||
<div className={styles.notFound}>
|
||||
<img
|
||||
|
|
@ -47,9 +44,9 @@ const NoticeList: React.SFC<NoticeIconTabProps> = ({
|
|||
}
|
||||
return (
|
||||
<div>
|
||||
<List<NoticeIconData>
|
||||
<List<API.NoticeIconItem>
|
||||
className={styles.list}
|
||||
dataSource={data}
|
||||
dataSource={list}
|
||||
renderItem={(item, i) => {
|
||||
const itemCls = classNames(styles.item, {
|
||||
[styles.read]: item.read,
|
||||
|
|
|
|||
|
|
@ -1,142 +1,152 @@
|
|||
import { BellOutlined } from '@ant-design/icons';
|
||||
import { Badge, Spin, Tabs } from 'antd';
|
||||
import useMergedState from 'rc-util/es/hooks/useMergedState';
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import type { NoticeIconTabProps } from './NoticeList';
|
||||
import NoticeList from './NoticeList';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Tag, message } from 'antd';
|
||||
import { groupBy } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { useModel } from 'umi';
|
||||
import { getNotices } from '@/services/ant-design-pro/api';
|
||||
|
||||
import HeaderDropdown from '../HeaderDropdown';
|
||||
import NoticeIcon from './NoticeIcon';
|
||||
import styles from './index.less';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
export type NoticeIconData = {
|
||||
avatar?: string | React.ReactNode;
|
||||
title?: React.ReactNode;
|
||||
description?: React.ReactNode;
|
||||
datetime?: React.ReactNode;
|
||||
extra?: React.ReactNode;
|
||||
style?: React.CSSProperties;
|
||||
key?: string | number;
|
||||
read?: boolean;
|
||||
export type GlobalHeaderRightProps = {
|
||||
fetchingNotices?: boolean;
|
||||
onNoticeVisibleChange?: (visible: boolean) => void;
|
||||
onNoticeClear?: (tabName?: string) => void;
|
||||
};
|
||||
|
||||
export type NoticeIconProps = {
|
||||
count?: number;
|
||||
bell?: React.ReactNode;
|
||||
className?: string;
|
||||
loading?: boolean;
|
||||
onClear?: (tabName: string, tabKey: string) => void;
|
||||
onItemClick?: (item: NoticeIconData, tabProps: NoticeIconTabProps) => void;
|
||||
onViewMore?: (tabProps: NoticeIconTabProps, e: MouseEvent) => void;
|
||||
onTabChange?: (tabTile: string) => void;
|
||||
style?: React.CSSProperties;
|
||||
onPopupVisibleChange?: (visible: boolean) => void;
|
||||
popupVisible?: boolean;
|
||||
clearText?: string;
|
||||
viewMoreText?: string;
|
||||
clearClose?: boolean;
|
||||
emptyImage?: string;
|
||||
children: React.ReactElement<NoticeIconTabProps>[];
|
||||
};
|
||||
const getNoticeData = (notices: API.NoticeIconItem[]): Record<string, API.NoticeIconItem[]> => {
|
||||
if (!notices || notices.length === 0 || !Array.isArray(notices)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const NoticeIcon: React.FC<NoticeIconProps> & {
|
||||
Tab: typeof NoticeList;
|
||||
} = (props) => {
|
||||
const getNotificationBox = (): React.ReactNode => {
|
||||
const {
|
||||
children,
|
||||
loading,
|
||||
onClear,
|
||||
onTabChange,
|
||||
onItemClick,
|
||||
onViewMore,
|
||||
clearText,
|
||||
viewMoreText,
|
||||
} = props;
|
||||
if (!children) {
|
||||
return null;
|
||||
const newNotices = notices.map((notice) => {
|
||||
const newNotice = { ...notice };
|
||||
|
||||
if (newNotice.datetime) {
|
||||
newNotice.datetime = moment(notice.datetime as string).fromNow();
|
||||
}
|
||||
const panes: React.ReactNode[] = [];
|
||||
React.Children.forEach(children, (child: React.ReactElement<NoticeIconTabProps>): void => {
|
||||
if (!child) {
|
||||
return;
|
||||
}
|
||||
const { list, title, count, tabKey, showClear, showViewMore } = child.props;
|
||||
const len = list && list.length ? list.length : 0;
|
||||
const msgCount = count || count === 0 ? count : len;
|
||||
const tabTitle: string = msgCount > 0 ? `${title} (${msgCount})` : title;
|
||||
panes.push(
|
||||
<TabPane tab={tabTitle} key={tabKey}>
|
||||
<NoticeList
|
||||
{...child.props}
|
||||
clearText={clearText}
|
||||
viewMoreText={viewMoreText}
|
||||
data={list}
|
||||
onClear={(): void => {
|
||||
onClear?.(title, tabKey);
|
||||
}}
|
||||
onClick={(item): void => {
|
||||
onItemClick?.(item, child.props);
|
||||
}}
|
||||
onViewMore={(event): void => {
|
||||
onViewMore?.(child.props, event);
|
||||
}}
|
||||
showClear={showClear}
|
||||
showViewMore={showViewMore}
|
||||
title={title}
|
||||
/>
|
||||
</TabPane>,
|
||||
);
|
||||
});
|
||||
return (
|
||||
<Spin spinning={loading} delay={300}>
|
||||
<Tabs className={styles.tabs} onChange={onTabChange}>
|
||||
{panes}
|
||||
</Tabs>
|
||||
</Spin>
|
||||
|
||||
if (newNotice.id) {
|
||||
newNotice.key = newNotice.id;
|
||||
}
|
||||
|
||||
if (newNotice.extra && newNotice.status) {
|
||||
const color = {
|
||||
todo: '',
|
||||
processing: 'blue',
|
||||
urgent: 'red',
|
||||
doing: 'gold',
|
||||
}[newNotice.status];
|
||||
newNotice.extra = (
|
||||
<Tag
|
||||
color={color}
|
||||
style={{
|
||||
marginRight: 0,
|
||||
}}
|
||||
>
|
||||
{newNotice.extra}
|
||||
</Tag>
|
||||
) as any;
|
||||
}
|
||||
|
||||
return newNotice;
|
||||
});
|
||||
return groupBy(newNotices, 'type');
|
||||
};
|
||||
|
||||
const getUnreadData = (noticeData: Record<string, API.NoticeIconItem[]>) => {
|
||||
const unreadMsg: Record<string, number> = {};
|
||||
Object.keys(noticeData).forEach((key) => {
|
||||
const value = noticeData[key];
|
||||
|
||||
if (!unreadMsg[key]) {
|
||||
unreadMsg[key] = 0;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
unreadMsg[key] = value.filter((item) => !item.read).length;
|
||||
}
|
||||
});
|
||||
return unreadMsg;
|
||||
};
|
||||
|
||||
const NoticeIconView = () => {
|
||||
const { initialState } = useModel('@@initialState');
|
||||
const { currentUser } = initialState || {};
|
||||
const [notices, setNotices] = useState<API.NoticeIconItem[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
getNotices().then(({ data }) => setNotices(data || []));
|
||||
}, []);
|
||||
|
||||
const noticeData = getNoticeData(notices);
|
||||
const unreadMsg = getUnreadData(noticeData || {});
|
||||
|
||||
const changeReadState = (id: string) => {
|
||||
setNotices(
|
||||
notices.map((item) => {
|
||||
const notice = { ...item };
|
||||
if (notice.id === id) {
|
||||
notice.read = true;
|
||||
}
|
||||
return notice;
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const { className, count, bell } = props;
|
||||
|
||||
const [visible, setVisible] = useMergedState<boolean>(false, {
|
||||
value: props.popupVisible,
|
||||
onChange: props.onPopupVisibleChange,
|
||||
});
|
||||
const noticeButtonClass = classNames(className, styles.noticeButton);
|
||||
const notificationBox = getNotificationBox();
|
||||
const NoticeBellIcon = bell || <BellOutlined className={styles.icon} />;
|
||||
const trigger = (
|
||||
<span className={classNames(noticeButtonClass, { opened: visible })}>
|
||||
<Badge count={count} style={{ boxShadow: 'none' }} className={styles.badge}>
|
||||
{NoticeBellIcon}
|
||||
</Badge>
|
||||
</span>
|
||||
);
|
||||
if (!notificationBox) {
|
||||
return trigger;
|
||||
}
|
||||
const clearReadState = (title: string, key: string) => {
|
||||
setNotices(
|
||||
notices.map((item) => {
|
||||
const notice = { ...item };
|
||||
if (notice.type === key) {
|
||||
notice.read = true;
|
||||
}
|
||||
return notice;
|
||||
}),
|
||||
);
|
||||
message.success(`${'清空了'} ${title}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<HeaderDropdown
|
||||
placement="bottomRight"
|
||||
overlay={notificationBox}
|
||||
overlayClassName={styles.popover}
|
||||
trigger={['click']}
|
||||
visible={visible}
|
||||
onVisibleChange={setVisible}
|
||||
<NoticeIcon
|
||||
className={styles.action}
|
||||
count={currentUser && currentUser.unreadCount}
|
||||
onItemClick={(item) => {
|
||||
changeReadState(item.id!);
|
||||
}}
|
||||
onClear={(title: string, key: string) => clearReadState(title, key)}
|
||||
loading={false}
|
||||
clearText="清空"
|
||||
viewMoreText="查看更多"
|
||||
onViewMore={() => message.info('Click on view more')}
|
||||
clearClose
|
||||
>
|
||||
{trigger}
|
||||
</HeaderDropdown>
|
||||
<NoticeIcon.Tab
|
||||
tabKey="notification"
|
||||
count={unreadMsg.notification}
|
||||
list={noticeData.notification}
|
||||
title="通知"
|
||||
emptyText="你已查看所有通知"
|
||||
showViewMore
|
||||
/>
|
||||
<NoticeIcon.Tab
|
||||
tabKey="message"
|
||||
count={unreadMsg.message}
|
||||
list={noticeData.message}
|
||||
title="消息"
|
||||
emptyText="您已读完所有消息"
|
||||
showViewMore
|
||||
/>
|
||||
<NoticeIcon.Tab
|
||||
tabKey="event"
|
||||
title="待办"
|
||||
emptyText="你已完成所有待办"
|
||||
count={unreadMsg.event}
|
||||
list={noticeData.event}
|
||||
showViewMore
|
||||
/>
|
||||
</NoticeIcon>
|
||||
);
|
||||
};
|
||||
|
||||
NoticeIcon.defaultProps = {
|
||||
emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg',
|
||||
};
|
||||
|
||||
NoticeIcon.Tab = NoticeList;
|
||||
|
||||
export default NoticeIcon;
|
||||
export default NoticeIconView;
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
import { PageLoading } from '@ant-design/pro-layout';
|
||||
|
||||
// loading components from code split
|
||||
// https://umijs.org/plugin/umi-plugin-react.html#dynamicimport
|
||||
export default PageLoading;
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import { Avatar, Menu, Spin } from 'antd';
|
||||
import { history, useModel } from 'umi';
|
||||
import { stringify } from 'querystring';
|
||||
import HeaderDropdown from '../HeaderDropdown';
|
||||
import styles from './index.less';
|
||||
import { outLogin } from '@/services/ant-design-pro/api';
|
||||
import type { MenuInfo } from 'rc-menu/lib/interface';
|
||||
|
||||
export type GlobalHeaderRightProps = {
|
||||
menu?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* 退出登录,并且将当前的 url 保存
|
||||
*/
|
||||
const loginOut = async () => {
|
||||
await outLogin();
|
||||
const { query = {}, pathname } = history.location;
|
||||
const { redirect } = query;
|
||||
// Note: There may be security issues, please note
|
||||
if (window.location.pathname !== '/user/login' && !redirect) {
|
||||
history.replace({
|
||||
pathname: '/user/login',
|
||||
search: stringify({
|
||||
redirect: pathname,
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => {
|
||||
const { initialState, setInitialState } = useModel('@@initialState');
|
||||
|
||||
const onMenuClick = useCallback(
|
||||
(event: MenuInfo) => {
|
||||
const { key } = event;
|
||||
if (key === 'logout' && initialState) {
|
||||
setInitialState({ ...initialState, currentUser: undefined });
|
||||
loginOut();
|
||||
return;
|
||||
}
|
||||
history.push(`/account/${key}`);
|
||||
},
|
||||
[initialState, setInitialState],
|
||||
);
|
||||
|
||||
const loading = (
|
||||
<span className={`${styles.action} ${styles.account}`}>
|
||||
<Spin
|
||||
size="small"
|
||||
style={{
|
||||
marginLeft: 8,
|
||||
marginRight: 8,
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
|
||||
if (!initialState) {
|
||||
return loading;
|
||||
}
|
||||
|
||||
const { currentUser } = initialState;
|
||||
|
||||
if (!currentUser || !currentUser.name) {
|
||||
return loading;
|
||||
}
|
||||
|
||||
const menuHeaderDropdown = (
|
||||
<Menu className={styles.menu} selectedKeys={[]} onClick={onMenuClick}>
|
||||
{menu && (
|
||||
<Menu.Item key="center">
|
||||
<UserOutlined />
|
||||
个人中心
|
||||
</Menu.Item>
|
||||
)}
|
||||
{menu && (
|
||||
<Menu.Item key="settings">
|
||||
<SettingOutlined />
|
||||
个人设置
|
||||
</Menu.Item>
|
||||
)}
|
||||
{menu && <Menu.Divider />}
|
||||
|
||||
<Menu.Item key="logout">
|
||||
<LogoutOutlined />
|
||||
退出登录
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
return (
|
||||
<HeaderDropdown overlay={menuHeaderDropdown}>
|
||||
<span className={`${styles.action} ${styles.account}`}>
|
||||
<Avatar size="small" className={styles.avatar} src={currentUser.avatar} alt="avatar" />
|
||||
<span className={`${styles.name} anticon`}>{currentUser.name}</span>
|
||||
</span>
|
||||
</HeaderDropdown>
|
||||
);
|
||||
};
|
||||
|
||||
export default AvatarDropdown;
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
.action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
height: 48px;
|
||||
padding: 0 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
|
@ -42,7 +42,6 @@
|
|||
}
|
||||
.account {
|
||||
.avatar {
|
||||
margin: ~'calc((@{layout-header-height} - 24px) / 2)' 0;
|
||||
margin-right: 8px;
|
||||
color: @primary-color;
|
||||
vertical-align: top;
|
||||
|
|
@ -53,30 +52,11 @@
|
|||
|
||||
.dark {
|
||||
.action {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
> span {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
&:hover {
|
||||
background: #252a3d;
|
||||
}
|
||||
&:hover,
|
||||
&:global(.opened) {
|
||||
background: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global(.ant-pro-global-header) {
|
||||
.dark {
|
||||
.action {
|
||||
color: @text-color;
|
||||
> span {
|
||||
color: @text-color;
|
||||
}
|
||||
&:hover {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
> span {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
}
|
||||
background: #252a3d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
import { Space } from 'antd';
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import React from 'react';
|
||||
import { useModel, SelectLang } from 'umi';
|
||||
import Avatar from './AvatarDropdown';
|
||||
import HeaderSearch from '../HeaderSearch';
|
||||
import styles from './index.less';
|
||||
|
||||
export type SiderTheme = 'light' | 'dark';
|
||||
|
||||
const GlobalHeaderRight: React.FC = () => {
|
||||
const { initialState } = useModel('@@initialState');
|
||||
|
||||
if (!initialState || !initialState.settings) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { navTheme, layout } = initialState.settings;
|
||||
let className = styles.right;
|
||||
|
||||
if ((navTheme === 'dark' && layout === 'top') || layout === 'mix') {
|
||||
className = `${styles.right} ${styles.dark}`;
|
||||
}
|
||||
return (
|
||||
<Space className={className}>
|
||||
<HeaderSearch
|
||||
className={`${styles.action} ${styles.search}`}
|
||||
placeholder="站内搜索"
|
||||
defaultValue="umi ui"
|
||||
options={[
|
||||
{ label: <a href="https://umijs.org/zh/guide/umi-ui.html">umi ui</a>, value: 'umi ui' },
|
||||
{
|
||||
label: <a href="next.ant.design">Ant Design</a>,
|
||||
value: 'Ant Design',
|
||||
},
|
||||
{
|
||||
label: <a href="https://protable.ant.design/">Pro Table</a>,
|
||||
value: 'Pro Table',
|
||||
},
|
||||
{
|
||||
label: <a href="https://prolayout.ant.design/">Pro Layout</a>,
|
||||
value: 'Pro Layout',
|
||||
},
|
||||
]}
|
||||
// onSearch={value => {
|
||||
// console.log('input', value);
|
||||
// }}
|
||||
/>
|
||||
<span
|
||||
className={styles.action}
|
||||
onClick={() => {
|
||||
window.open('https://pro.ant.design/docs/getting-started');
|
||||
}}
|
||||
>
|
||||
<QuestionCircleOutlined />
|
||||
</span>
|
||||
<Avatar />
|
||||
<SelectLang className={styles.action} />
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
export default GlobalHeaderRight;
|
||||
|
|
@ -0,0 +1,272 @@
|
|||
---
|
||||
title: 业务组件
|
||||
sidemenu: false
|
||||
---
|
||||
|
||||
> 此功能由[dumi](https://d.umijs.org/zh-CN/guide/advanced#umi-%E9%A1%B9%E7%9B%AE%E9%9B%86%E6%88%90%E6%A8%A1%E5%BC%8F)提供,dumi 是一个 📖 为组件开发场景而生的文档工具,用过的都说好。
|
||||
|
||||
# 业务组件
|
||||
|
||||
这里列举了 Pro 中所有用到的组件,这些组件不适合作为组件库,但是在业务中却真实需要。所以我们准备了这个文档,来指导大家是否需要使用这个组件。
|
||||
|
||||
## Footer 页脚组件
|
||||
|
||||
这个组件自带了一些 Pro 的配置,你一般都需要改掉它的信息。
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* background: '#f0f2f5'
|
||||
*/
|
||||
import React from 'react';
|
||||
import Footer from '@/components/Footer';
|
||||
|
||||
export default () => <Footer />;
|
||||
```
|
||||
|
||||
## HeaderDropdown 头部下拉列表
|
||||
|
||||
HeaderDropdown 是 antd Dropdown 的封装,但是增加了移动端的特殊处理,用法也是相同的。
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* background: '#f0f2f5'
|
||||
*/
|
||||
import { Button, Menu } from 'antd';
|
||||
import React from 'react';
|
||||
import HeaderDropdown from '@/components/HeaderDropdown';
|
||||
|
||||
export default () => {
|
||||
const menuHeaderDropdown = (
|
||||
<Menu selectedKeys={[]}>
|
||||
<Menu.Item key="center">个人中心</Menu.Item>
|
||||
<Menu.Item key="settings">个人设置</Menu.Item>
|
||||
<Menu.Divider />
|
||||
<Menu.Item key="logout">退出登录</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
return (
|
||||
<HeaderDropdown overlay={menuHeaderDropdown}>
|
||||
<Button>hover 展示菜单</Button>
|
||||
</HeaderDropdown>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## HeaderSearch 头部搜索框
|
||||
|
||||
一个带补全数据的输入框,支持收起和展开 Input
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* background: '#f0f2f5'
|
||||
*/
|
||||
import { Button, Menu } from 'antd';
|
||||
import React from 'react';
|
||||
import HeaderSearch from '@/components/HeaderSearch';
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<HeaderSearch
|
||||
placeholder="站内搜索"
|
||||
defaultValue="umi ui"
|
||||
options={[
|
||||
{ label: 'Ant Design Pro', value: 'Ant Design Pro' },
|
||||
{
|
||||
label: 'Ant Design',
|
||||
value: 'Ant Design',
|
||||
},
|
||||
{
|
||||
label: 'Pro Table',
|
||||
value: 'Pro Table',
|
||||
},
|
||||
{
|
||||
label: 'Pro Layout',
|
||||
value: 'Pro Layout',
|
||||
},
|
||||
]}
|
||||
onSearch={(value) => {
|
||||
console.log('input', value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### API
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| --------------- | ---------------------------------- | ---------------------------- | ------ |
|
||||
| value | 输入框的值 | `string` | - |
|
||||
| onChange | 值修改后触发 | `(value?: string) => void` | - |
|
||||
| onSearch | 查询后触发 | `(value?: string) => void` | - |
|
||||
| options | 选项菜单的的列表 | `{label,value}[]` | - |
|
||||
| defaultVisible | 输入框默认是否显示,只有第一次生效 | `boolean` | - |
|
||||
| visible | 输入框是否显示 | `boolean` | - |
|
||||
| onVisibleChange | 输入框显示隐藏的回调函数 | `(visible: boolean) => void` | - |
|
||||
|
||||
## NoticeIcon 通知工具
|
||||
|
||||
通知工具提供一个展示多种通知信息的界面。
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* background: '#f0f2f5'
|
||||
*/
|
||||
import { message } from 'antd';
|
||||
import React from 'react';
|
||||
import NoticeIcon from '@/components/NoticeIcon/NoticeIcon';
|
||||
|
||||
export default () => {
|
||||
const list = [
|
||||
{
|
||||
id: '000000001',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
|
||||
title: '你收到了 14 份新周报',
|
||||
datetime: '2017-08-09',
|
||||
type: 'notification',
|
||||
},
|
||||
{
|
||||
id: '000000002',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
|
||||
title: '你推荐的 曲妮妮 已通过第三轮面试',
|
||||
datetime: '2017-08-08',
|
||||
type: 'notification',
|
||||
},
|
||||
];
|
||||
return (
|
||||
<NoticeIcon
|
||||
count={10}
|
||||
onItemClick={(item) => {
|
||||
message.info(`${item.title} 被点击了`);
|
||||
}}
|
||||
onClear={(title: string, key: string) => message.info('点击了清空更多')}
|
||||
loading={false}
|
||||
clearText="清空"
|
||||
viewMoreText="查看更多"
|
||||
onViewMore={() => message.info('点击了查看更多')}
|
||||
clearClose
|
||||
>
|
||||
<NoticeIcon.Tab
|
||||
tabKey="notification"
|
||||
count={2}
|
||||
list={list}
|
||||
title="通知"
|
||||
emptyText="你已查看所有通知"
|
||||
showViewMore
|
||||
/>
|
||||
<NoticeIcon.Tab
|
||||
tabKey="message"
|
||||
count={2}
|
||||
list={list}
|
||||
title="消息"
|
||||
emptyText="您已读完所有消息"
|
||||
showViewMore
|
||||
/>
|
||||
<NoticeIcon.Tab
|
||||
tabKey="event"
|
||||
title="待办"
|
||||
emptyText="你已完成所有待办"
|
||||
count={2}
|
||||
list={list}
|
||||
showViewMore
|
||||
/>
|
||||
</NoticeIcon>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### NoticeIcon API
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| count | 有多少未读通知 | `number` | - |
|
||||
| bell | 铃铛的图表 | `ReactNode` | - |
|
||||
| onClear | 点击清空数据按钮 | `(tabName: string, tabKey: string) => void` | - |
|
||||
| onItemClick | 未读消息列被点击 | `(item: API.NoticeIconData, tabProps: NoticeIconTabProps) => void` | - |
|
||||
| onViewMore | 查看更多的按钮点击 | `(tabProps: NoticeIconTabProps, e: MouseEvent) => void` | - |
|
||||
| onTabChange | 通知 Tab 的切换 | `(tabTile: string) => void;` | - |
|
||||
| popupVisible | 通知显示是否展示 | `boolean` | - |
|
||||
| onPopupVisibleChange | 通知信息显示隐藏的回调函数 | `(visible: boolean) => void` | - |
|
||||
| clearText | 清空按钮的文字 | `string` | - |
|
||||
| viewMoreText | 查看更多的按钮文字 | `string` | - |
|
||||
| clearClose | 展示清空按钮 | `boolean` | - |
|
||||
| emptyImage | 列表为空时的兜底展示 | `ReactNode` | - |
|
||||
|
||||
### NoticeIcon.Tab API
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| ------------ | ------------------ | ------------------------------------ | ------ |
|
||||
| count | 有多少未读通知 | `number` | - |
|
||||
| title | 通知 Tab 的标题 | `ReactNode` | - |
|
||||
| showClear | 展示清除按钮 | `boolean` | `true` |
|
||||
| showViewMore | 展示加载更 | `boolean` | `true` |
|
||||
| tabKey | Tab 的唯一 key | `string` | - |
|
||||
| onClick | 子项的单击事件 | `(item: API.NoticeIconData) => void` | - |
|
||||
| onClear | 清楚按钮的点击 | `()=>void` | - |
|
||||
| emptyText | 为空的时候测试 | `()=>void` | - |
|
||||
| viewMoreText | 查看更多的按钮文字 | `string` | - |
|
||||
| onViewMore | 查看更多的按钮点击 | `( e: MouseEvent) => void` | - |
|
||||
| list | 通知信息的列表 | `API.NoticeIconData` | - |
|
||||
|
||||
### NoticeIconData
|
||||
|
||||
```tsx | pure
|
||||
export interface NoticeIconData {
|
||||
id: string;
|
||||
key: string;
|
||||
avatar: string;
|
||||
title: string;
|
||||
datetime: string;
|
||||
type: string;
|
||||
read?: boolean;
|
||||
description: string;
|
||||
clickClose?: boolean;
|
||||
extra: any;
|
||||
status: string;
|
||||
}
|
||||
```
|
||||
|
||||
## RightContent
|
||||
|
||||
RightContent 是以上几个组件的组合,同时新增了 plugins 的 `SelectLang` 插件。
|
||||
|
||||
```tsx | pure
|
||||
<Space>
|
||||
<HeaderSearch
|
||||
placeholder="站内搜索"
|
||||
defaultValue="umi ui"
|
||||
options={[
|
||||
{ label: <a href="https://umijs.org/zh/guide/umi-ui.html">umi ui</a>, value: 'umi ui' },
|
||||
{
|
||||
label: <a href="next.ant.design">Ant Design</a>,
|
||||
value: 'Ant Design',
|
||||
},
|
||||
{
|
||||
label: <a href="https://protable.ant.design/">Pro Table</a>,
|
||||
value: 'Pro Table',
|
||||
},
|
||||
{
|
||||
label: <a href="https://prolayout.ant.design/">Pro Layout</a>,
|
||||
value: 'Pro Layout',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Tooltip title="使用文档">
|
||||
<span
|
||||
className={styles.action}
|
||||
onClick={() => {
|
||||
window.location.href = 'https://pro.ant.design/docs/getting-started';
|
||||
}}
|
||||
>
|
||||
<QuestionCircleOutlined />
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Avatar />
|
||||
{REACT_APP_ENV && (
|
||||
<span>
|
||||
<Tag color={ENVTagColor[REACT_APP_ENV]}>{REACT_APP_ENV}</Tag>
|
||||
</span>
|
||||
)}
|
||||
<SelectLang className={styles.action} />
|
||||
</Space>
|
||||
```
|
||||
|
|
@ -1 +0,0 @@
|
|||
export default undefined;
|
||||
|
|
@ -1,15 +1,18 @@
|
|||
const { uniq } = require('lodash');
|
||||
const RouterConfig = require('../../config/config').default.routes;
|
||||
|
||||
const BASE_URL = `http://localhost:${process.env.PORT || 8000}`;
|
||||
const BASE_URL = `http://localhost:${process.env.PORT || 8001}`;
|
||||
|
||||
function formatter(routes, parentPath = '') {
|
||||
const fixedParentPath = parentPath.replace(/\/{1,}/g, '/');
|
||||
let result = [];
|
||||
routes.forEach((item) => {
|
||||
if (item.path) {
|
||||
if (item.path && !item.path.startsWith('/')) {
|
||||
result.push(`${fixedParentPath}/${item.path}`.replace(/\/{1,}/g, '/'));
|
||||
}
|
||||
if (item.path && item.path.startsWith('/')) {
|
||||
result.push(`${item.path}`.replace(/\/{1,}/g, '/'));
|
||||
}
|
||||
if (item.routes) {
|
||||
result = result.concat(
|
||||
formatter(item.routes, item.path ? `${fixedParentPath}/${item.path}` : parentPath),
|
||||
|
|
@ -49,6 +52,7 @@ describe('Ant Design Pro E2E test', () => {
|
|||
await page.waitForSelector('footer', {
|
||||
timeout: 2000,
|
||||
});
|
||||
|
||||
const haveFooter = await page.evaluate(
|
||||
() => document.getElementsByTagName('footer').length > 0,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ body,
|
|||
.ant-layout {
|
||||
min-height: 100vh;
|
||||
}
|
||||
.ant-pro-sider.ant-layout-sider.ant-pro-sider-fixed {
|
||||
left: unset;
|
||||
}
|
||||
|
||||
canvas {
|
||||
display: block;
|
||||
|
|
|
|||
|
|
@ -73,8 +73,7 @@ if (pwa) {
|
|||
});
|
||||
|
||||
// remove all caches
|
||||
// @ts-ignore
|
||||
if (window.caches && window.caches.keys) {
|
||||
if (window.caches && window.caches.keys()) {
|
||||
caches.keys().then((keys) => {
|
||||
keys.forEach((key) => {
|
||||
caches.delete(key);
|
||||
|
|
|
|||
|
|
@ -1,183 +0,0 @@
|
|||
/**
|
||||
* Ant Design Pro v4 use `@ant-design/pro-layout` to handle Layout.
|
||||
*
|
||||
* @see You can view component api by: https://github.com/ant-design/ant-design-pro-layout
|
||||
*/
|
||||
import type {
|
||||
MenuDataItem,
|
||||
BasicLayoutProps as ProLayoutProps,
|
||||
Settings,
|
||||
} from '@ant-design/pro-layout';
|
||||
import ProLayout, { DefaultFooter } from '@ant-design/pro-layout';
|
||||
import React, { useEffect, useMemo, useRef } from 'react';
|
||||
import type { Dispatch } from 'umi';
|
||||
import { Link, useIntl, connect, history } from 'umi';
|
||||
import { GithubOutlined } from '@ant-design/icons';
|
||||
import { Result, Button } from 'antd';
|
||||
import Authorized from '@/utils/Authorized';
|
||||
import RightContent from '@/components/GlobalHeader/RightContent';
|
||||
import type { ConnectState } from '@/models/connect';
|
||||
import { getMatchMenu } from '@umijs/route-utils';
|
||||
import logo from '../assets/logo.svg';
|
||||
|
||||
const noMatch = (
|
||||
<Result
|
||||
status={403}
|
||||
title="403"
|
||||
subTitle="Sorry, you are not authorized to access this page."
|
||||
extra={
|
||||
<Button type="primary">
|
||||
<Link to="/user/login">Go Login</Link>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
export type BasicLayoutProps = {
|
||||
breadcrumbNameMap: Record<string, MenuDataItem>;
|
||||
route: ProLayoutProps['route'] & {
|
||||
authority: string[];
|
||||
};
|
||||
settings: Settings;
|
||||
dispatch: Dispatch;
|
||||
} & ProLayoutProps;
|
||||
export type BasicLayoutContext = { [K in 'location']: BasicLayoutProps[K] } & {
|
||||
breadcrumbNameMap: Record<string, MenuDataItem>;
|
||||
};
|
||||
/** Use Authorized check all menu item */
|
||||
|
||||
const menuDataRender = (menuList: MenuDataItem[]): MenuDataItem[] =>
|
||||
menuList.map((item) => {
|
||||
const localItem = {
|
||||
...item,
|
||||
children: item.children ? menuDataRender(item.children) : undefined,
|
||||
};
|
||||
return Authorized.check(item.authority, localItem, null) as MenuDataItem;
|
||||
});
|
||||
|
||||
const defaultFooterDom = (
|
||||
<DefaultFooter
|
||||
copyright={`${new Date().getFullYear()} Produced by Ant Group Experience Technology Department`}
|
||||
links={[
|
||||
{
|
||||
key: 'Ant Design Pro',
|
||||
title: 'Ant Design Pro',
|
||||
href: 'https://pro.ant.design',
|
||||
blankTarget: true,
|
||||
},
|
||||
{
|
||||
key: 'github',
|
||||
title: <GithubOutlined />,
|
||||
href: 'https://github.com/ant-design/ant-design-pro',
|
||||
blankTarget: true,
|
||||
},
|
||||
{
|
||||
key: 'Ant Design',
|
||||
title: 'Ant Design',
|
||||
href: 'https://ant.design',
|
||||
blankTarget: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
const BasicLayout: React.FC<BasicLayoutProps> = (props) => {
|
||||
const {
|
||||
dispatch,
|
||||
children,
|
||||
settings,
|
||||
location = {
|
||||
pathname: '/',
|
||||
},
|
||||
} = props;
|
||||
|
||||
const menuDataRef = useRef<MenuDataItem[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (dispatch) {
|
||||
dispatch({
|
||||
type: 'user/fetchCurrent',
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
/** Init variables */
|
||||
|
||||
const handleMenuCollapse = (payload: boolean): void => {
|
||||
if (dispatch) {
|
||||
dispatch({
|
||||
type: 'global/changeLayoutCollapsed',
|
||||
payload,
|
||||
});
|
||||
}
|
||||
};
|
||||
// get children authority
|
||||
const authorized = useMemo(
|
||||
() =>
|
||||
getMatchMenu(location.pathname || '/', menuDataRef.current).pop() || {
|
||||
authority: undefined,
|
||||
},
|
||||
[location.pathname],
|
||||
);
|
||||
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<ProLayout
|
||||
logo={logo}
|
||||
formatMessage={formatMessage}
|
||||
{...props}
|
||||
{...settings}
|
||||
onCollapse={handleMenuCollapse}
|
||||
onMenuHeaderClick={() => history.push('/')}
|
||||
menuItemRender={(menuItemProps, defaultDom) => {
|
||||
if (
|
||||
menuItemProps.isUrl ||
|
||||
!menuItemProps.path ||
|
||||
location.pathname === menuItemProps.path
|
||||
) {
|
||||
return defaultDom;
|
||||
}
|
||||
return <Link to={menuItemProps.path}>{defaultDom}</Link>;
|
||||
}}
|
||||
breadcrumbRender={(routers = []) => [
|
||||
{
|
||||
path: '/',
|
||||
breadcrumbName: formatMessage({ id: 'menu.home' }),
|
||||
},
|
||||
...routers,
|
||||
]}
|
||||
itemRender={(route, params, routes, paths) => {
|
||||
const first = routes.indexOf(route) === 0;
|
||||
return first ? (
|
||||
<Link to={paths.join('/')}>{route.breadcrumbName}</Link>
|
||||
) : (
|
||||
<span>{route.breadcrumbName}</span>
|
||||
);
|
||||
}}
|
||||
footerRender={() => {
|
||||
if (settings.footerRender || settings.footerRender === undefined) {
|
||||
return defaultFooterDom;
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
menuDataRender={menuDataRender}
|
||||
rightContentRender={() => <RightContent />}
|
||||
postMenuData={(menuData) => {
|
||||
menuDataRef.current = menuData || [];
|
||||
return menuData || [];
|
||||
}}
|
||||
waterMarkProps={{
|
||||
content: 'Ant Design Pro',
|
||||
fontColor: 'rgba(24,144,255,0.15)',
|
||||
}}
|
||||
>
|
||||
<Authorized authority={authorized!.authority} noMatch={noMatch}>
|
||||
{children}
|
||||
</Authorized>
|
||||
</ProLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(({ global, settings }: ConnectState) => ({
|
||||
collapsed: global.collapsed,
|
||||
settings,
|
||||
}))(BasicLayout);
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Inspector } from 'react-dev-inspector';
|
||||
|
||||
const InspectorWrapper = process.env.NODE_ENV === 'development' ? Inspector : React.Fragment;
|
||||
|
||||
const Layout: React.FC = ({ children }) => {
|
||||
return <InspectorWrapper>{children}</InspectorWrapper>;
|
||||
};
|
||||
|
||||
export default Layout;
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
import React from 'react';
|
||||
import { PageLoading } from '@ant-design/pro-layout';
|
||||
import type { ConnectProps } from 'umi';
|
||||
import { Redirect, connect } from 'umi';
|
||||
import { stringify } from 'querystring';
|
||||
import type { ConnectState } from '@/models/connect';
|
||||
import type { CurrentUser } from '@/models/user';
|
||||
|
||||
type SecurityLayoutProps = {
|
||||
loading?: boolean;
|
||||
currentUser?: CurrentUser;
|
||||
} & ConnectProps;
|
||||
|
||||
type SecurityLayoutState = {
|
||||
isReady: boolean;
|
||||
};
|
||||
|
||||
class SecurityLayout extends React.Component<SecurityLayoutProps, SecurityLayoutState> {
|
||||
state: SecurityLayoutState = {
|
||||
isReady: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.setState({
|
||||
isReady: true,
|
||||
});
|
||||
const { dispatch } = this.props;
|
||||
if (dispatch) {
|
||||
dispatch({
|
||||
type: 'user/fetchCurrent',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isReady } = this.state;
|
||||
const { children, loading, currentUser } = this.props;
|
||||
// You can replace it to your authentication rule (such as check token exists)
|
||||
// You can replace it with your own login authentication rules (such as judging whether the token exists)
|
||||
const isLogin = currentUser && currentUser.userid;
|
||||
const queryString = stringify({
|
||||
redirect: window.location.href,
|
||||
});
|
||||
|
||||
if ((!isLogin && loading) || !isReady) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
if (!isLogin && window.location.pathname !== '/user/login') {
|
||||
return <Redirect to={`/user/login?${queryString}`} />;
|
||||
}
|
||||
return children;
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(({ user, loading }: ConnectState) => ({
|
||||
currentUser: user.currentUser,
|
||||
loading: loading.models.user,
|
||||
}))(SecurityLayout);
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
import type { MenuDataItem } from '@ant-design/pro-layout';
|
||||
import { DefaultFooter, getMenuData, getPageTitle } from '@ant-design/pro-layout';
|
||||
import { Helmet, HelmetProvider } from 'react-helmet-async';
|
||||
import type { ConnectProps } from 'umi';
|
||||
import { Link, SelectLang, useIntl, connect, FormattedMessage } from 'umi';
|
||||
import React from 'react';
|
||||
import type { ConnectState } from '@/models/connect';
|
||||
import logo from '../assets/logo.svg';
|
||||
import styles from './UserLayout.less';
|
||||
|
||||
export type UserLayoutProps = {
|
||||
breadcrumbNameMap: Record<string, MenuDataItem>;
|
||||
} & Partial<ConnectProps>;
|
||||
|
||||
const UserLayout: React.FC<UserLayoutProps> = (props) => {
|
||||
const {
|
||||
route = {
|
||||
routes: [],
|
||||
},
|
||||
} = props;
|
||||
const { routes = [] } = route;
|
||||
const {
|
||||
children,
|
||||
location = {
|
||||
pathname: '',
|
||||
},
|
||||
} = props;
|
||||
const { formatMessage } = useIntl();
|
||||
const { breadcrumb } = getMenuData(routes);
|
||||
const title = getPageTitle({
|
||||
pathname: location.pathname,
|
||||
formatMessage,
|
||||
breadcrumb,
|
||||
...props,
|
||||
});
|
||||
return (
|
||||
<HelmetProvider>
|
||||
<Helmet>
|
||||
<title>{title}</title>
|
||||
<meta name="description" content={title} />
|
||||
</Helmet>
|
||||
|
||||
<div className={styles.container}>
|
||||
<div className={styles.lang}>
|
||||
<SelectLang />
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.top}>
|
||||
<div className={styles.header}>
|
||||
<Link to="/">
|
||||
<img alt="logo" className={styles.logo} src={logo} />
|
||||
<span className={styles.title}>Ant Design</span>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={styles.desc}>
|
||||
<FormattedMessage
|
||||
id="pages.layouts.userLayout.title"
|
||||
defaultMessage="Ant Design. The most influential Web design specification in Xihu District."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
<DefaultFooter />
|
||||
</div>
|
||||
</HelmetProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(({ settings }: ConnectState) => ({ ...settings }))(UserLayout);
|
||||
|
|
@ -1,16 +1,17 @@
|
|||
import component from './en-US/component';
|
||||
import globalHeader from './en-US/globalHeader';
|
||||
import menu from './en-US/menu';
|
||||
import pages from './en-US/pages';
|
||||
import pwa from './en-US/pwa';
|
||||
import settingDrawer from './en-US/settingDrawer';
|
||||
import settings from './en-US/settings';
|
||||
import pages from './en-US/pages';
|
||||
|
||||
export default {
|
||||
'navBar.lang': 'Languages',
|
||||
'layout.user.link.help': 'Help',
|
||||
'layout.user.link.privacy': 'Privacy',
|
||||
'layout.user.link.terms': 'Terms',
|
||||
'app.copyright.produced': 'Produced by Ant Financial Experience Department',
|
||||
'app.preview.down.block': 'Download this page to your local project',
|
||||
'app.welcome.link.fetch-blocks': 'Get all block',
|
||||
'app.welcome.link.block-list': 'Quickly build standard, pages based on `block` development',
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ export default {
|
|||
'Ant Design is the most influential web design specification in Xihu district',
|
||||
'pages.login.accountLogin.tab': 'Account Login',
|
||||
'pages.login.accountLogin.errorMessage': 'Incorrect username/password(admin/ant.design)',
|
||||
'pages.login.failure': 'Login failed, please try again!',
|
||||
'pages.login.success': 'Login successful!',
|
||||
'pages.login.username.placeholder': 'Username: admin or user',
|
||||
'pages.login.username.required': 'Please input your username!',
|
||||
'pages.login.password.placeholder': 'Password: ant.design',
|
||||
|
|
@ -18,7 +20,7 @@ export default {
|
|||
'pages.getCaptchaSecondText': 'sec(s)',
|
||||
'pages.login.rememberMe': 'Remember me',
|
||||
'pages.login.forgotPassword': 'Forgot Password ?',
|
||||
'pages.login.submit': 'Submit',
|
||||
'pages.login.submit': 'Login',
|
||||
'pages.login.loginWith': 'Login with :',
|
||||
'pages.login.registerAccount': 'Register Account',
|
||||
'pages.welcome.advancedComponent': 'Advanced Component',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
import component from './fa-IR/component';
|
||||
import globalHeader from './fa-IR/globalHeader';
|
||||
import menu from './fa-IR/menu';
|
||||
import pwa from './fa-IR/pwa';
|
||||
import settingDrawer from './fa-IR/settingDrawer';
|
||||
import settings from './fa-IR/settings';
|
||||
import pages from './fa-IR/pages';
|
||||
|
||||
export default {
|
||||
'navBar.lang': 'زبان ها ',
|
||||
'layout.user.link.help': 'کمک',
|
||||
'layout.user.link.privacy': 'حریم خصوصی',
|
||||
'layout.user.link.terms': 'مقررات',
|
||||
'app.preview.down.block': 'این صفحه را در پروژه محلی خود بارگیری کنید',
|
||||
'app.welcome.link.fetch-blocks': 'دریافت تمام بلوک',
|
||||
'app.welcome.link.block-list': 'به سرعت صفحات استاندارد مبتنی بر توسعه "بلوک" را بسازید',
|
||||
...globalHeader,
|
||||
...menu,
|
||||
...settingDrawer,
|
||||
...settings,
|
||||
...pwa,
|
||||
...component,
|
||||
...pages,
|
||||
};
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
export default {
|
||||
'component.tagSelect.expand': 'باز',
|
||||
'component.tagSelect.collapse': 'بسته ',
|
||||
'component.tagSelect.all': 'همه',
|
||||
};
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
export default {
|
||||
'component.globalHeader.search': 'جستجو ',
|
||||
'component.globalHeader.search.example1': 'مثال 1 را جستجو کنید',
|
||||
'component.globalHeader.search.example2': 'مثال 2 را جستجو کنید',
|
||||
'component.globalHeader.search.example3': 'مثال 3 را جستجو کنید',
|
||||
'component.globalHeader.help': 'کمک',
|
||||
'component.globalHeader.notification': 'اعلان',
|
||||
'component.globalHeader.notification.empty': 'شما همه اعلان ها را مشاهده کرده اید.',
|
||||
'component.globalHeader.message': 'پیام',
|
||||
'component.globalHeader.message.empty': 'شما همه پیام ها را مشاهده کرده اید.',
|
||||
'component.globalHeader.event': 'رویداد',
|
||||
'component.globalHeader.event.empty': 'شما همه رویدادها را مشاهده کرده اید.',
|
||||
'component.noticeIcon.clear': 'پاک کردن',
|
||||
'component.noticeIcon.cleared': 'پاک شد',
|
||||
'component.noticeIcon.empty': 'بدون اعلان',
|
||||
'component.noticeIcon.view-more': 'نمایش بیشتر',
|
||||
};
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
export default {
|
||||
'menu.welcome': 'خوش آمدید',
|
||||
'menu.more-blocks': 'بلوک های بیشتر',
|
||||
'menu.home': 'خانه',
|
||||
'menu.admin': 'مدیر',
|
||||
'menu.admin.sub-page': 'زیر صفحه',
|
||||
'menu.login': 'ورود',
|
||||
'menu.register': 'ثبت نام',
|
||||
'menu.register.result': 'ثبت نام نتیجه',
|
||||
'menu.dashboard': 'داشبورد',
|
||||
'menu.dashboard.analysis': 'تحلیل و بررسی',
|
||||
'menu.dashboard.monitor': 'نظارت',
|
||||
'menu.dashboard.workplace': 'محل کار',
|
||||
'menu.exception.403': '403',
|
||||
'menu.exception.404': '404',
|
||||
'menu.exception.500': '500',
|
||||
'menu.form': 'فرم',
|
||||
'menu.form.basic-form': 'فرم اساسی',
|
||||
'menu.form.step-form': 'فرم مرحله',
|
||||
'menu.form.step-form.info': 'فرم مرحله (نوشتن اطلاعات انتقال)',
|
||||
'menu.form.step-form.confirm': 'فرم مرحله (تأیید اطلاعات انتقال)',
|
||||
'menu.form.step-form.result': 'فرم مرحله (تمام شده)',
|
||||
'menu.form.advanced-form': 'فرم پیشرفته',
|
||||
'menu.list': 'لیست',
|
||||
'menu.list.table-list': 'جدول جستجو',
|
||||
'menu.list.basic-list': 'لیست اصلی',
|
||||
'menu.list.card-list': 'لیست کارت',
|
||||
'menu.list.search-list': 'لیست جستجو',
|
||||
'menu.list.search-list.articles': 'لیست جستجو (مقالات)',
|
||||
'menu.list.search-list.projects': 'لیست جستجو (پروژه ها)',
|
||||
'menu.list.search-list.applications': 'لیست جستجو (برنامه ها)',
|
||||
'menu.profile': 'مشخصات',
|
||||
'menu.profile.basic': 'مشخصات عمومی',
|
||||
'menu.profile.advanced': 'مشخصات پیشرفته',
|
||||
'menu.result': 'نتیجه',
|
||||
'menu.result.success': 'موفق',
|
||||
'menu.result.fail': 'ناموفق',
|
||||
'menu.exception': 'استثنا',
|
||||
'menu.exception.not-permission': '403',
|
||||
'menu.exception.not-find': '404',
|
||||
'menu.exception.server-error': '500',
|
||||
'menu.exception.trigger': 'راه اندازی',
|
||||
'menu.account': 'حساب',
|
||||
'menu.account.center': 'مرکز حساب',
|
||||
'menu.account.settings': 'تنظیمات حساب',
|
||||
'menu.account.trigger': 'خطای راه اندازی',
|
||||
'menu.account.logout': 'خروج',
|
||||
'menu.editor': 'ویرایشگر گرافیک',
|
||||
'menu.editor.flow': 'ویرایشگر جریان',
|
||||
'menu.editor.mind': 'ویرایشگر ذهن',
|
||||
'menu.editor.koni': 'ویرایشگر Koni',
|
||||
};
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
export default {
|
||||
'pages.layouts.userLayout.title': 'طراحی مورچه تأثیرگذارترین مشخصات طراحی وب در منطقه Xihu است',
|
||||
'pages.login.accountLogin.tab': 'ورود به حساب کاربری',
|
||||
'pages.login.accountLogin.errorMessage': 'نام کاربری / رمزعبور نادرست (مدیر / ant.design)',
|
||||
'pages.login.username.placeholder': 'نام کاربری: مدیر یا کاربر',
|
||||
'pages.login.username.required': 'لطفا نام کاربری خود را وارد کنید!',
|
||||
'pages.login.password.placeholder': 'رمز عبور: ant.design',
|
||||
'pages.login.password.required': 'لطفاً رمز ورود خود را وارد کنید!',
|
||||
'pages.login.phoneLogin.tab': 'ورود به سیستم تلفن',
|
||||
'pages.login.phoneLogin.errorMessage': 'خطای کد تأیید',
|
||||
'pages.login.phoneNumber.placeholder': 'شماره تلفن',
|
||||
'pages.login.phoneNumber.required': 'لطفاً شماره تلفن خود را وارد کنید!',
|
||||
'pages.login.phoneNumber.invalid': 'شماره تلفن نامعتبر است!',
|
||||
'pages.login.captcha.placeholder': 'کد تایید',
|
||||
'pages.login.captcha.required': 'لطفا کد تأیید را وارد کنید!',
|
||||
'pages.login.phoneLogin.getVerificationCode': 'دریافت کد',
|
||||
'pages.getCaptchaSecondText': 'ثانیه',
|
||||
'pages.login.rememberMe': 'مرا به خاطر بسپار',
|
||||
'pages.login.forgotPassword': 'رمز عبور را فراموش کرده اید ?',
|
||||
'pages.login.submit': 'ارسال',
|
||||
'pages.login.loginWith': 'وارد شوید با :',
|
||||
'pages.login.registerAccount': 'ثبت نام',
|
||||
'pages.welcome.advancedComponent': 'مولفه پیشرفته',
|
||||
'pages.welcome.link': 'خوش آمدید',
|
||||
'pages.welcome.advancedLayout': 'چیدمان پیشرفته',
|
||||
'pages.welcome.alertMessage': 'اجزای سنگین تر سریعتر و قوی تر آزاد شده اند.',
|
||||
'pages.admin.subPage.title': 'این صفحه فقط توسط مدیر قابل مشاهده است',
|
||||
'pages.admin.subPage.alertMessage':
|
||||
'رابط کاربری Umi اکنون منتشر شده است ، برای شروع تجربه استفاده از npm run ui خوش آمدید.',
|
||||
'pages.searchTable.createForm.newRule': 'قانون جدید',
|
||||
'pages.searchTable.updateForm.ruleConfig': 'پیکربندی قانون',
|
||||
'pages.searchTable.updateForm.basicConfig': 'اطلاعات اولیه',
|
||||
'pages.searchTable.updateForm.ruleName.nameLabel': ' نام قانون',
|
||||
'pages.searchTable.updateForm.ruleName.nameRules': 'لطفاً نام قانون را وارد کنید!',
|
||||
'pages.searchTable.updateForm.ruleDesc.descLabel': 'شرح قانون',
|
||||
'pages.searchTable.updateForm.ruleDesc.descPlaceholder': 'لطفاً حداقل پنج حرف وارد کنید',
|
||||
'pages.searchTable.updateForm.ruleDesc.descRules':
|
||||
'لطفاً حداقل یک قانون حاوی پنج کاراکتر شرح دهید!',
|
||||
'pages.searchTable.updateForm.ruleProps.title': 'پیکربندی خصوصیات',
|
||||
'pages.searchTable.updateForm.object': 'نظارت بر شی',
|
||||
'pages.searchTable.updateForm.ruleProps.templateLabel': 'الگوی قانون',
|
||||
'pages.searchTable.updateForm.ruleProps.typeLabel': 'نوع قانون',
|
||||
'pages.searchTable.updateForm.schedulingPeriod.title': 'تنظیم دوره زمان بندی',
|
||||
'pages.searchTable.updateForm.schedulingPeriod.timeLabel': 'زمان شروع',
|
||||
'pages.searchTable.updateForm.schedulingPeriod.timeRules': 'لطفاً زمان شروع را انتخاب کنید!',
|
||||
'pages.searchTable.titleDesc': 'شرح',
|
||||
'pages.searchTable.ruleName': 'نام قانون لازم است',
|
||||
'pages.searchTable.titleCallNo': 'تعداد تماس های خدماتی',
|
||||
'pages.searchTable.titleStatus': 'وضعیت',
|
||||
'pages.searchTable.nameStatus.default': 'پیش فرض',
|
||||
'pages.searchTable.nameStatus.running': 'در حال دویدن',
|
||||
'pages.searchTable.nameStatus.online': 'برخط',
|
||||
'pages.searchTable.nameStatus.abnormal': 'غیرطبیعی',
|
||||
'pages.searchTable.titleUpdatedAt': 'آخرین برنامه ریزی در',
|
||||
'pages.searchTable.exception': 'لطفا دلیل استثنا را وارد کنید!',
|
||||
'pages.searchTable.titleOption': 'گزینه',
|
||||
'pages.searchTable.config': 'پیکربندی',
|
||||
'pages.searchTable.subscribeAlert': 'مشترک شدن در هشدارها',
|
||||
'pages.searchTable.title': 'فرم درخواست',
|
||||
'pages.searchTable.new': 'جدید',
|
||||
'pages.searchTable.chosen': 'انتخاب شده',
|
||||
'pages.searchTable.item': 'مورد',
|
||||
'pages.searchTable.totalServiceCalls': 'تعداد کل تماس های خدماتی',
|
||||
'pages.searchTable.tenThousand': '0000',
|
||||
'pages.searchTable.batchDeletion': 'حذف دسته ای',
|
||||
'pages.searchTable.batchApproval': 'تصویب دسته ای',
|
||||
};
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
export default {
|
||||
'app.pwa.offline': 'شما اکنون آفلاین هستید',
|
||||
'app.pwa.serviceworker.updated': 'مطالب جدید در دسترس است',
|
||||
'app.pwa.serviceworker.updated.hint':
|
||||
'لطفاً برای بارگیری مجدد صفحه فعلی ، دکمه "تازه سازی" را فشار دهید',
|
||||
'app.pwa.serviceworker.updated.ok': 'تازه سازی',
|
||||
};
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
export default {
|
||||
'app.setting.pagestyle': 'تنظیم نوع صفحه',
|
||||
'app.setting.pagestyle.dark': 'سبک تیره',
|
||||
'app.setting.pagestyle.light': 'سبک سبک',
|
||||
'app.setting.content-width': 'عرض محتوا',
|
||||
'app.setting.content-width.fixed': 'ثابت',
|
||||
'app.setting.content-width.fluid': 'شناور',
|
||||
'app.setting.themecolor': 'رنگ تم',
|
||||
'app.setting.themecolor.dust': 'گرد و غبار قرمز',
|
||||
'app.setting.themecolor.volcano': 'آتشفشان',
|
||||
'app.setting.themecolor.sunset': 'غروب نارنجی',
|
||||
'app.setting.themecolor.cyan': 'فیروزه ای',
|
||||
'app.setting.themecolor.green': 'سبز قطبی',
|
||||
'app.setting.themecolor.daybreak': 'آبی روشن(پیشفرض)',
|
||||
'app.setting.themecolor.geekblue': 'چسب گیک',
|
||||
'app.setting.themecolor.purple': 'بنفش طلایی',
|
||||
'app.setting.navigationmode': 'حالت پیمایش',
|
||||
'app.setting.sidemenu': 'طرح منوی کناری',
|
||||
'app.setting.topmenu': 'طرح منوی بالایی',
|
||||
'app.setting.fixedheader': 'سرصفحه ثابت',
|
||||
'app.setting.fixedsidebar': 'نوار کناری ثابت',
|
||||
'app.setting.fixedsidebar.hint': 'کار بر روی منوی کناری',
|
||||
'app.setting.hideheader': 'هدر پنهان هنگام پیمایش',
|
||||
'app.setting.hideheader.hint': 'وقتی Hidden Header فعال باشد کار می کند',
|
||||
'app.setting.othersettings': 'تنظیمات دیگر',
|
||||
'app.setting.weakmode': 'حالت ضعیف',
|
||||
'app.setting.copy': 'تنظیمات کپی',
|
||||
'app.setting.copyinfo':
|
||||
'موفقیت در کپی کردن , لطفا defaultSettings را در src / models / setting.js جایگزین کنید',
|
||||
'app.setting.production.hint':
|
||||
'صفحه تنظیم فقط در محیط توسعه نمایش داده می شود ، لطفاً دستی تغییر دهید',
|
||||
};
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
export default {
|
||||
'app.settings.menuMap.basic': 'تنظیمات پایه ',
|
||||
'app.settings.menuMap.security': 'تنظیمات امنیتی',
|
||||
'app.settings.menuMap.binding': 'صحافی حساب',
|
||||
'app.settings.menuMap.notification': 'اعلان پیام جدید',
|
||||
'app.settings.basic.avatar': 'آواتار',
|
||||
'app.settings.basic.change-avatar': 'آواتار را تغییر دهید',
|
||||
'app.settings.basic.email': 'ایمیل',
|
||||
'app.settings.basic.email-message': 'لطفا ایمیل خود را وارد کنید!',
|
||||
'app.settings.basic.nickname': 'نام مستعار',
|
||||
'app.settings.basic.nickname-message': 'لطفاً نام مستعار خود را وارد کنید!',
|
||||
'app.settings.basic.profile': 'پروفایل شخصی',
|
||||
'app.settings.basic.profile-message': 'لطفاً مشخصات شخصی خود را وارد کنید!',
|
||||
'app.settings.basic.profile-placeholder': 'معرفی مختصر خودتان',
|
||||
'app.settings.basic.country': 'کشور / منطقه',
|
||||
'app.settings.basic.country-message': 'لطفاً کشور خود را وارد کنید!',
|
||||
'app.settings.basic.geographic': 'استان یا شهر',
|
||||
'app.settings.basic.geographic-message': 'لطفاً اطلاعات جغرافیایی خود را وارد کنید!',
|
||||
'app.settings.basic.address': 'آدرس خیابان',
|
||||
'app.settings.basic.address-message': 'لطفا آدرس خود را وارد کنید!',
|
||||
'app.settings.basic.phone': 'شماره تلفن',
|
||||
'app.settings.basic.phone-message': 'لطفاً تلفن خود را وارد کنید!',
|
||||
'app.settings.basic.update': 'به روز رسانی اطلاعات',
|
||||
'app.settings.security.strong': 'قوی',
|
||||
'app.settings.security.medium': 'متوسط',
|
||||
'app.settings.security.weak': 'ضعیف',
|
||||
'app.settings.security.password': 'رمز عبور حساب کاربری',
|
||||
'app.settings.security.password-description': 'قدرت رمز عبور فعلی',
|
||||
'app.settings.security.phone': 'تلفن امنیتی',
|
||||
'app.settings.security.phone-description': 'تلفن مقید',
|
||||
'app.settings.security.question': 'سوال امنیتی',
|
||||
'app.settings.security.question-description':
|
||||
'سوال امنیتی تنظیم نشده است و سیاست امنیتی می تواند به طور موثر از امنیت حساب محافظت کند',
|
||||
'app.settings.security.email': 'ایمیل پشتیبان',
|
||||
'app.settings.security.email-description': 'ایمیل مقید',
|
||||
'app.settings.security.mfa': 'دستگاه MFA',
|
||||
'app.settings.security.mfa-description':
|
||||
'دستگاه MFA بسته نشده ، پس از اتصال ، می تواند دو بار تأیید شود',
|
||||
'app.settings.security.modify': 'تغییر',
|
||||
'app.settings.security.set': 'تنظیم',
|
||||
'app.settings.security.bind': 'بستن',
|
||||
'app.settings.binding.taobao': 'اتصال Taobao',
|
||||
'app.settings.binding.taobao-description': 'حساب Taobao در حال حاضر بسته نشده است',
|
||||
'app.settings.binding.alipay': 'اتصال Alipay',
|
||||
'app.settings.binding.alipay-description': 'حساب Alipay در حال حاضر بسته نشده است',
|
||||
'app.settings.binding.dingding': 'اتصال DingTalk',
|
||||
'app.settings.binding.dingding-description': 'حساب DingTalk در حال حاضر محدود نشده است',
|
||||
'app.settings.binding.bind': 'بستن',
|
||||
'app.settings.notification.password': 'رمز عبور حساب کاربری',
|
||||
'app.settings.notification.password-description':
|
||||
'پیام های سایر کاربران در قالب یک نامه ایستگاهی اعلام خواهد شد',
|
||||
'app.settings.notification.messages': 'پیام های سیستم',
|
||||
'app.settings.notification.messages-description':
|
||||
'پیام های سیستم به صورت نامه ایستگاه مطلع می شوند',
|
||||
'app.settings.notification.todo': 'اعلان کارها',
|
||||
'app.settings.notification.todo-description':
|
||||
'لیست کارها به صورت نامه ای از ایستگاه اطلاع داده می شود',
|
||||
'app.settings.open': 'باز کن',
|
||||
'app.settings.close': 'بستن',
|
||||
};
|
||||
|
|
@ -4,6 +4,7 @@ import menu from './pt-BR/menu';
|
|||
import pwa from './pt-BR/pwa';
|
||||
import settingDrawer from './pt-BR/settingDrawer';
|
||||
import settings from './pt-BR/settings';
|
||||
import pages from './pt-BR/pages';
|
||||
|
||||
export default {
|
||||
'navBar.lang': 'Idiomas',
|
||||
|
|
@ -17,4 +18,5 @@ export default {
|
|||
...settings,
|
||||
...pwa,
|
||||
...component,
|
||||
...pages,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
export default {
|
||||
'pages.layouts.userLayout.title':
|
||||
'Ant Design é a especificação de web design mais influente no distrito de Xihu',
|
||||
'pages.login.accountLogin.tab': 'Login da conta',
|
||||
'pages.login.accountLogin.errorMessage': 'usuário/senha incorreto(admin/ant.design)',
|
||||
'pages.login.username.placeholder': 'Usuário: admin or user',
|
||||
'pages.login.username.required': 'Por favor insira seu usuário!',
|
||||
'pages.login.password.placeholder': 'Senha: ant.design',
|
||||
'pages.login.password.required': 'Por favor insira sua senha!',
|
||||
'pages.login.phoneLogin.tab': 'Login com Telefone',
|
||||
'pages.login.phoneLogin.errorMessage': 'Erro de Código de Verificação',
|
||||
'pages.login.phoneNumber.placeholder': 'Telefone',
|
||||
'pages.login.phoneNumber.required': 'Por favor entre com seu telefone!',
|
||||
'pages.login.phoneNumber.invalid': 'Telefone é inválido!',
|
||||
'pages.login.captcha.placeholder': 'Código de Verificação',
|
||||
'pages.login.captcha.required': 'Por favor entre com o código de verificação!',
|
||||
'pages.login.phoneLogin.getVerificationCode': 'Obter Código',
|
||||
'pages.getCaptchaSecondText': 'seg(s)',
|
||||
'pages.login.rememberMe': 'Lembre-me',
|
||||
'pages.login.forgotPassword': 'Perdeu a Senha ?',
|
||||
'pages.login.submit': 'Enviar',
|
||||
'pages.login.loginWith': 'Login com :',
|
||||
'pages.login.registerAccount': 'Registra Conta',
|
||||
'pages.welcome.advancedComponent': 'Componente Avançado',
|
||||
'pages.welcome.link': 'Bem-vindo',
|
||||
'pages.welcome.advancedLayout': 'Layout Avançado',
|
||||
'pages.welcome.alertMessage': 'Componentes pesados mais rápidos e mais fortes foram lançados.',
|
||||
'pages.admin.subPage.title': 'Esta página só pode ser vista pelo Admin',
|
||||
'pages.admin.subPage.alertMessage':
|
||||
'O Umi ui foi lançado, bem-vindo ao usar o npm run ui para iniciar a experiência.',
|
||||
'pages.searchTable.createForm.newRule': 'Neva Regra',
|
||||
'pages.searchTable.updateForm.ruleConfig': 'Configuração de Regra',
|
||||
'pages.searchTable.updateForm.basicConfig': 'Informação básica',
|
||||
'pages.searchTable.updateForm.ruleName.nameLabel': 'Nome da Regra',
|
||||
'pages.searchTable.updateForm.ruleName.nameRules': 'Por favor entre com o nome da regra!',
|
||||
'pages.searchTable.updateForm.ruleDesc.descLabel': 'Descrição da Regra',
|
||||
'pages.searchTable.updateForm.ruleDesc.descPlaceholder':
|
||||
'Por favor insira ao menos cinco caracteres',
|
||||
'pages.searchTable.updateForm.ruleDesc.descRules':
|
||||
'Insira uma descrição de regra de pelo menos cinco caracteres!',
|
||||
'pages.searchTable.updateForm.ruleProps.title': 'Configurar Propriedades',
|
||||
'pages.searchTable.updateForm.object': 'Objeto de Monitoramento',
|
||||
'pages.searchTable.updateForm.ruleProps.templateLabel': 'Modelo de Regra',
|
||||
'pages.searchTable.updateForm.ruleProps.typeLabel': 'Tipo de Regra',
|
||||
'pages.searchTable.updateForm.schedulingPeriod.title': 'Definir Período de Agendamento',
|
||||
'pages.searchTable.updateForm.schedulingPeriod.timeLabel': 'Hora de Início',
|
||||
'pages.searchTable.updateForm.schedulingPeriod.timeRules':
|
||||
'Por favor selecione um horáriod e início!',
|
||||
'pages.searchTable.titleDesc': 'Descrição',
|
||||
'pages.searchTable.ruleName': 'O nome da regra é obrigatório',
|
||||
'pages.searchTable.titleCallNo': 'Número de chamadas de serviço',
|
||||
'pages.searchTable.titleStatus': 'Status',
|
||||
'pages.searchTable.nameStatus.default': 'padrão',
|
||||
'pages.searchTable.nameStatus.running': 'executando',
|
||||
'pages.searchTable.nameStatus.online': 'online',
|
||||
'pages.searchTable.nameStatus.abnormal': 'anormal',
|
||||
'pages.searchTable.titleUpdatedAt': 'Última programação em',
|
||||
'pages.searchTable.exception': 'Por favor, indique o motivo da exceção!',
|
||||
'pages.searchTable.titleOption': 'Opção',
|
||||
'pages.searchTable.config': 'Configuração',
|
||||
'pages.searchTable.subscribeAlert': 'Inscreva-se para receber alertas',
|
||||
'pages.searchTable.title': 'Formulário de Consulta',
|
||||
'pages.searchTable.new': 'Novo',
|
||||
'pages.searchTable.chosen': 'selecionado',
|
||||
'pages.searchTable.item': 'item',
|
||||
'pages.searchTable.totalServiceCalls': 'Número total de chamadas de serviço',
|
||||
'pages.searchTable.tenThousand': '0000',
|
||||
'pages.searchTable.batchDeletion': 'deleção em lote',
|
||||
'pages.searchTable.batchApproval': 'aprovação em lote',
|
||||
};
|
||||
|
|
@ -11,6 +11,7 @@ export default {
|
|||
'layout.user.link.help': '帮助',
|
||||
'layout.user.link.privacy': '隐私',
|
||||
'layout.user.link.terms': '条款',
|
||||
'app.copyright.produced': '蚂蚁集团体验技术部出品',
|
||||
'app.preview.down.block': '下载此页面到本地项目',
|
||||
'app.welcome.link.fetch-blocks': '获取全部区块',
|
||||
'app.welcome.link.block-list': '基于 block 开发,快速构建标准页面',
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ export default {
|
|||
'pages.layouts.userLayout.title': 'Ant Design 是西湖区最具影响力的 Web 设计规范',
|
||||
'pages.login.accountLogin.tab': '账户密码登录',
|
||||
'pages.login.accountLogin.errorMessage': '错误的用户名和密码(admin/ant.design)',
|
||||
'pages.login.failure': '登录失败,请重试!',
|
||||
'pages.login.success': '登录成功!',
|
||||
'pages.login.username.placeholder': '用户名: admin or user',
|
||||
'pages.login.username.required': '用户名是必填项!',
|
||||
'pages.login.password.placeholder': '密码: ant.design',
|
||||
|
|
@ -17,7 +19,7 @@ export default {
|
|||
'pages.getCaptchaSecondText': '秒后重新获取',
|
||||
'pages.login.rememberMe': '自动登录',
|
||||
'pages.login.forgotPassword': '忘记密码 ?',
|
||||
'pages.login.submit': '提交',
|
||||
'pages.login.submit': '登录',
|
||||
'pages.login.loginWith': '其他登录方式 :',
|
||||
'pages.login.registerAccount': '注册账户',
|
||||
'pages.welcome.advancedComponent': '高级表格',
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
import type { MenuDataItem, Settings as ProSettings } from '@ant-design/pro-layout';
|
||||
import { GlobalModelState } from './global';
|
||||
import { UserModelState } from './user';
|
||||
import type { StateType } from './login';
|
||||
|
||||
export { GlobalModelState, UserModelState };
|
||||
|
||||
export type Loading = {
|
||||
global: boolean;
|
||||
effects: Record<string, boolean | undefined>;
|
||||
models: {
|
||||
global?: boolean;
|
||||
menu?: boolean;
|
||||
setting?: boolean;
|
||||
user?: boolean;
|
||||
login?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export type ConnectState = {
|
||||
global: GlobalModelState;
|
||||
loading: Loading;
|
||||
settings: ProSettings;
|
||||
user: UserModelState;
|
||||
login: StateType;
|
||||
};
|
||||
|
||||
export type Route = {
|
||||
routes?: Route[];
|
||||
} & MenuDataItem;
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
import type { Reducer, Effect } from 'umi';
|
||||
|
||||
import type { NoticeIconData } from '@/components/NoticeIcon';
|
||||
import { queryNotices } from '@/services/user';
|
||||
import type { ConnectState } from './connect.d';
|
||||
|
||||
export type NoticeItem = {
|
||||
id: string;
|
||||
type: string;
|
||||
status: string;
|
||||
} & NoticeIconData;
|
||||
|
||||
export type GlobalModelState = {
|
||||
collapsed: boolean;
|
||||
notices: NoticeItem[];
|
||||
};
|
||||
|
||||
export type GlobalModelType = {
|
||||
namespace: 'global';
|
||||
state: GlobalModelState;
|
||||
effects: {
|
||||
fetchNotices: Effect;
|
||||
clearNotices: Effect;
|
||||
changeNoticeReadState: Effect;
|
||||
};
|
||||
reducers: {
|
||||
changeLayoutCollapsed: Reducer<GlobalModelState>;
|
||||
saveNotices: Reducer<GlobalModelState>;
|
||||
saveClearedNotices: Reducer<GlobalModelState>;
|
||||
};
|
||||
};
|
||||
|
||||
const GlobalModel: GlobalModelType = {
|
||||
namespace: 'global',
|
||||
|
||||
state: {
|
||||
collapsed: false,
|
||||
notices: [],
|
||||
},
|
||||
|
||||
effects: {
|
||||
*fetchNotices(_, { call, put, select }) {
|
||||
const data = yield call(queryNotices);
|
||||
yield put({
|
||||
type: 'saveNotices',
|
||||
payload: data,
|
||||
});
|
||||
const unreadCount: number = yield select(
|
||||
(state: ConnectState) => state.global.notices.filter((item) => !item.read).length,
|
||||
);
|
||||
yield put({
|
||||
type: 'user/changeNotifyCount',
|
||||
payload: {
|
||||
totalCount: data.length,
|
||||
unreadCount,
|
||||
},
|
||||
});
|
||||
},
|
||||
*clearNotices({ payload }, { put, select }) {
|
||||
yield put({
|
||||
type: 'saveClearedNotices',
|
||||
payload,
|
||||
});
|
||||
const count: number = yield select((state: ConnectState) => state.global.notices.length);
|
||||
const unreadCount: number = yield select(
|
||||
(state: ConnectState) => state.global.notices.filter((item) => !item.read).length,
|
||||
);
|
||||
yield put({
|
||||
type: 'user/changeNotifyCount',
|
||||
payload: {
|
||||
totalCount: count,
|
||||
unreadCount,
|
||||
},
|
||||
});
|
||||
},
|
||||
*changeNoticeReadState({ payload }, { put, select }) {
|
||||
const notices: NoticeItem[] = yield select((state: ConnectState) =>
|
||||
state.global.notices.map((item) => {
|
||||
const notice = { ...item };
|
||||
if (notice.id === payload) {
|
||||
notice.read = true;
|
||||
}
|
||||
return notice;
|
||||
}),
|
||||
);
|
||||
|
||||
yield put({
|
||||
type: 'saveNotices',
|
||||
payload: notices,
|
||||
});
|
||||
|
||||
yield put({
|
||||
type: 'user/changeNotifyCount',
|
||||
payload: {
|
||||
totalCount: notices.length,
|
||||
unreadCount: notices.filter((item) => !item.read).length,
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
reducers: {
|
||||
changeLayoutCollapsed(state = { notices: [], collapsed: true }, { payload }): GlobalModelState {
|
||||
return {
|
||||
...state,
|
||||
collapsed: payload,
|
||||
};
|
||||
},
|
||||
saveNotices(state, { payload }): GlobalModelState {
|
||||
return {
|
||||
collapsed: false,
|
||||
...state,
|
||||
notices: payload,
|
||||
};
|
||||
},
|
||||
saveClearedNotices(state = { notices: [], collapsed: true }, { payload }): GlobalModelState {
|
||||
return {
|
||||
...state,
|
||||
collapsed: false,
|
||||
notices: state.notices.filter((item): boolean => item.type !== payload),
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default GlobalModel;
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
import { stringify } from 'querystring';
|
||||
import type { Reducer, Effect } from 'umi';
|
||||
import { history } from 'umi';
|
||||
|
||||
import { fakeAccountLogin } from '@/services/login';
|
||||
import { setAuthority } from '@/utils/authority';
|
||||
import { getPageQuery } from '@/utils/utils';
|
||||
import { message } from 'antd';
|
||||
|
||||
export type StateType = {
|
||||
status?: 'ok' | 'error';
|
||||
type?: string;
|
||||
currentAuthority?: 'user' | 'guest' | 'admin';
|
||||
};
|
||||
|
||||
export type LoginModelType = {
|
||||
namespace: string;
|
||||
state: StateType;
|
||||
effects: {
|
||||
login: Effect;
|
||||
logout: Effect;
|
||||
};
|
||||
reducers: {
|
||||
changeLoginStatus: Reducer<StateType>;
|
||||
};
|
||||
};
|
||||
|
||||
const Model: LoginModelType = {
|
||||
namespace: 'login',
|
||||
|
||||
state: {
|
||||
status: undefined,
|
||||
},
|
||||
|
||||
effects: {
|
||||
*login({ payload }, { call, put }) {
|
||||
const response = yield call(fakeAccountLogin, payload);
|
||||
yield put({
|
||||
type: 'changeLoginStatus',
|
||||
payload: response,
|
||||
});
|
||||
// Login successfully
|
||||
if (response.status === 'ok') {
|
||||
const urlParams = new URL(window.location.href);
|
||||
const params = getPageQuery();
|
||||
message.success('🎉 🎉 🎉 登录成功!');
|
||||
let { redirect } = params as { redirect: string };
|
||||
if (redirect) {
|
||||
const redirectUrlParams = new URL(redirect);
|
||||
if (redirectUrlParams.origin === urlParams.origin) {
|
||||
redirect = redirect.substr(urlParams.origin.length);
|
||||
if (window.routerBase !== '/') {
|
||||
redirect = redirect.replace(window.routerBase, '/');
|
||||
}
|
||||
if (redirect.match(/^\/.*#/)) {
|
||||
redirect = redirect.substr(redirect.indexOf('#') + 1);
|
||||
}
|
||||
} else {
|
||||
window.location.href = '/';
|
||||
return;
|
||||
}
|
||||
}
|
||||
history.replace(redirect || '/');
|
||||
}
|
||||
},
|
||||
|
||||
logout() {
|
||||
const { redirect } = getPageQuery();
|
||||
// Note: There may be security issues, please note
|
||||
if (window.location.pathname !== '/user/login' && !redirect) {
|
||||
history.replace({
|
||||
pathname: '/user/login',
|
||||
search: stringify({
|
||||
redirect: window.location.href,
|
||||
}),
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
reducers: {
|
||||
changeLoginStatus(state, { payload }) {
|
||||
setAuthority(payload.currentAuthority);
|
||||
return {
|
||||
...state,
|
||||
status: payload.status,
|
||||
type: payload.type,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default Model;
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
import type { Reducer } from 'umi';
|
||||
import type { DefaultSettings } from '../../config/defaultSettings';
|
||||
import defaultSettings from '../../config/defaultSettings';
|
||||
|
||||
export type SettingModelType = {
|
||||
namespace: 'settings';
|
||||
state: DefaultSettings;
|
||||
reducers: {
|
||||
changeSetting: Reducer<DefaultSettings>;
|
||||
};
|
||||
};
|
||||
|
||||
const updateColorWeak: (colorWeak: boolean) => void = (colorWeak) => {
|
||||
const root = document.getElementById('root');
|
||||
if (root) {
|
||||
root.className = colorWeak ? 'colorWeak' : '';
|
||||
}
|
||||
};
|
||||
|
||||
const SettingModel: SettingModelType = {
|
||||
namespace: 'settings',
|
||||
state: defaultSettings,
|
||||
reducers: {
|
||||
changeSetting(state = defaultSettings, { payload }) {
|
||||
const { colorWeak, contentWidth } = payload;
|
||||
|
||||
if (state.contentWidth !== contentWidth && window.dispatchEvent) {
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
}
|
||||
updateColorWeak(!!colorWeak);
|
||||
return {
|
||||
...state,
|
||||
...payload,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
export default SettingModel;
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
import type { Effect, Reducer } from 'umi';
|
||||
|
||||
import { queryCurrent, query as queryUsers } from '@/services/user';
|
||||
|
||||
export type CurrentUser = {
|
||||
avatar?: string;
|
||||
name?: string;
|
||||
title?: string;
|
||||
group?: string;
|
||||
signature?: string;
|
||||
tags?: {
|
||||
key: string;
|
||||
label: string;
|
||||
}[];
|
||||
userid?: string;
|
||||
unreadCount?: number;
|
||||
};
|
||||
|
||||
export type UserModelState = {
|
||||
currentUser?: CurrentUser;
|
||||
};
|
||||
|
||||
export type UserModelType = {
|
||||
namespace: 'user';
|
||||
state: UserModelState;
|
||||
effects: {
|
||||
fetch: Effect;
|
||||
fetchCurrent: Effect;
|
||||
};
|
||||
reducers: {
|
||||
saveCurrentUser: Reducer<UserModelState>;
|
||||
changeNotifyCount: Reducer<UserModelState>;
|
||||
};
|
||||
};
|
||||
|
||||
const UserModel: UserModelType = {
|
||||
namespace: 'user',
|
||||
|
||||
state: {
|
||||
currentUser: {},
|
||||
},
|
||||
|
||||
effects: {
|
||||
*fetch(_, { call, put }) {
|
||||
const response = yield call(queryUsers);
|
||||
yield put({
|
||||
type: 'save',
|
||||
payload: response,
|
||||
});
|
||||
},
|
||||
*fetchCurrent(_, { call, put }) {
|
||||
const response = yield call(queryCurrent);
|
||||
yield put({
|
||||
type: 'saveCurrentUser',
|
||||
payload: response,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
reducers: {
|
||||
saveCurrentUser(state, action) {
|
||||
return {
|
||||
...state,
|
||||
currentUser: action.payload || {},
|
||||
};
|
||||
},
|
||||
changeNotifyCount(
|
||||
state = {
|
||||
currentUser: {},
|
||||
},
|
||||
action,
|
||||
) {
|
||||
return {
|
||||
...state,
|
||||
currentUser: {
|
||||
...state.currentUser,
|
||||
notifyCount: action.payload.totalCount,
|
||||
unreadCount: action.payload.unreadCount,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default UserModel;
|
||||
|
|
@ -10,21 +10,19 @@ import {
|
|||
} from '@ant-design/pro-form';
|
||||
import { useIntl, FormattedMessage } from 'umi';
|
||||
|
||||
import type { TableListItem } from '../data.d';
|
||||
|
||||
export type FormValueType = {
|
||||
target?: string;
|
||||
template?: string;
|
||||
type?: string;
|
||||
time?: string;
|
||||
frequency?: string;
|
||||
} & Partial<TableListItem>;
|
||||
} & Partial<API.RuleListItem>;
|
||||
|
||||
export type UpdateFormProps = {
|
||||
onCancel: (flag?: boolean, formVals?: FormValueType) => void;
|
||||
onSubmit: (values: FormValueType) => Promise<void>;
|
||||
updateModalVisible: boolean;
|
||||
values: Partial<TableListItem>;
|
||||
values: Partial<API.RuleListItem>;
|
||||
};
|
||||
|
||||
const UpdateForm: React.FC<UpdateFormProps> = (props) => {
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
export type TableListItem = {
|
||||
key: number;
|
||||
disabled?: boolean;
|
||||
href: string;
|
||||
avatar: string;
|
||||
name: string;
|
||||
owner: string;
|
||||
desc: string;
|
||||
callNo: number;
|
||||
status: number;
|
||||
updatedAt: Date;
|
||||
createdAt: Date;
|
||||
progress: number;
|
||||
};
|
||||
|
||||
export type TableListPagination = {
|
||||
total: number;
|
||||
pageSize: number;
|
||||
current: number;
|
||||
};
|
||||
|
||||
export type TableListData = {
|
||||
list: TableListItem[];
|
||||
pagination: Partial<TableListPagination>;
|
||||
};
|
||||
|
||||
export type TableListParams = {
|
||||
status?: string;
|
||||
name?: string;
|
||||
desc?: string;
|
||||
key?: number;
|
||||
pageSize?: number;
|
||||
currentPage?: number;
|
||||
filter?: Record<string, any[]>;
|
||||
sorter?: Record<string, any>;
|
||||
};
|
||||
|
|
@ -10,16 +10,15 @@ import type { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
|||
import ProDescriptions from '@ant-design/pro-descriptions';
|
||||
import type { FormValueType } from './components/UpdateForm';
|
||||
import UpdateForm from './components/UpdateForm';
|
||||
import type { TableListItem } from './data.d';
|
||||
import { queryRule, updateRule, addRule, removeRule } from './service';
|
||||
import { rule, addRule, updateRule, removeRule } from '@/services/ant-design-pro/api';
|
||||
|
||||
/**
|
||||
* @en-US Add node
|
||||
* @zh-CN 添加节点
|
||||
* @param fields
|
||||
*/
|
||||
const handleAdd = async (fields: TableListItem) => {
|
||||
const hide = message.loading('Adding');
|
||||
const handleAdd = async (fields: API.RuleListItem) => {
|
||||
const hide = message.loading('正在添加');
|
||||
try {
|
||||
await addRule({ ...fields });
|
||||
hide();
|
||||
|
|
@ -63,8 +62,8 @@ const handleUpdate = async (fields: FormValueType) => {
|
|||
*
|
||||
* @param selectedRows
|
||||
*/
|
||||
const handleRemove = async (selectedRows: TableListItem[]) => {
|
||||
const hide = message.loading('Deleting');
|
||||
const handleRemove = async (selectedRows: API.RuleListItem[]) => {
|
||||
const hide = message.loading('正在删除');
|
||||
if (!selectedRows) return true;
|
||||
try {
|
||||
await removeRule({
|
||||
|
|
@ -95,8 +94,8 @@ const TableList: React.FC = () => {
|
|||
const [showDetail, setShowDetail] = useState<boolean>(false);
|
||||
|
||||
const actionRef = useRef<ActionType>();
|
||||
const [currentRow, setCurrentRow] = useState<TableListItem>();
|
||||
const [selectedRowsState, setSelectedRows] = useState<TableListItem[]>([]);
|
||||
const [currentRow, setCurrentRow] = useState<API.RuleListItem>();
|
||||
const [selectedRowsState, setSelectedRows] = useState<API.RuleListItem[]>([]);
|
||||
|
||||
/**
|
||||
* @en-US International configuration
|
||||
|
|
@ -104,7 +103,7 @@ const TableList: React.FC = () => {
|
|||
* */
|
||||
const intl = useIntl();
|
||||
|
||||
const columns: ProColumns<TableListItem>[] = [
|
||||
const columns: ProColumns<API.RuleListItem>[] = [
|
||||
{
|
||||
title: (
|
||||
<FormattedMessage
|
||||
|
|
@ -240,7 +239,7 @@ const TableList: React.FC = () => {
|
|||
|
||||
return (
|
||||
<PageContainer>
|
||||
<ProTable<TableListItem>
|
||||
<ProTable<API.RuleListItem, API.PageParams>
|
||||
headerTitle={intl.formatMessage({
|
||||
id: 'pages.searchTable.title',
|
||||
defaultMessage: 'Enquiry form',
|
||||
|
|
@ -261,7 +260,7 @@ const TableList: React.FC = () => {
|
|||
<PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="New" />
|
||||
</Button>,
|
||||
]}
|
||||
request={(params, sorter, filter) => queryRule({ ...params, sorter, filter })}
|
||||
request={rule}
|
||||
columns={columns}
|
||||
rowSelection={{
|
||||
onChange: (_, selectedRows) => {
|
||||
|
|
@ -282,7 +281,7 @@ const TableList: React.FC = () => {
|
|||
id="pages.searchTable.totalServiceCalls"
|
||||
defaultMessage="Total number of service calls"
|
||||
/>{' '}
|
||||
{selectedRowsState.reduce((pre, item) => pre + item.callNo, 0)}{' '}
|
||||
{selectedRowsState.reduce((pre, item) => pre + item.callNo!, 0)}{' '}
|
||||
<FormattedMessage id="pages.searchTable.tenThousand" defaultMessage="万" />
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -317,7 +316,7 @@ const TableList: React.FC = () => {
|
|||
visible={createModalVisible}
|
||||
onVisibleChange={handleModalVisible}
|
||||
onFinish={async (value) => {
|
||||
const success = await handleAdd(value as TableListItem);
|
||||
const success = await handleAdd(value as API.RuleListItem);
|
||||
if (success) {
|
||||
handleModalVisible(false);
|
||||
if (actionRef.current) {
|
||||
|
|
@ -372,7 +371,7 @@ const TableList: React.FC = () => {
|
|||
closable={false}
|
||||
>
|
||||
{currentRow?.name && (
|
||||
<ProDescriptions<TableListItem>
|
||||
<ProDescriptions<API.RuleListItem>
|
||||
column={2}
|
||||
title={currentRow?.name}
|
||||
request={async () => ({
|
||||
|
|
@ -381,7 +380,7 @@ const TableList: React.FC = () => {
|
|||
params={{
|
||||
id: currentRow?.name,
|
||||
}}
|
||||
columns={columns as ProDescriptionsItemProps<TableListItem>[]}
|
||||
columns={columns as ProDescriptionsItemProps<API.RuleListItem>[]}
|
||||
/>
|
||||
)}
|
||||
</Drawer>
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
import request from '@/utils/request';
|
||||
import type { TableListParams, TableListItem } from './data.d';
|
||||
|
||||
export async function queryRule(params?: TableListParams) {
|
||||
return request('/api/rule', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export async function removeRule(params: { key: number[] }) {
|
||||
return request('/api/rule', {
|
||||
method: 'POST',
|
||||
data: {
|
||||
...params,
|
||||
method: 'delete',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function addRule(params: TableListItem) {
|
||||
return request('/api/rule', {
|
||||
method: 'POST',
|
||||
data: {
|
||||
...params,
|
||||
method: 'post',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateRule(params: TableListParams) {
|
||||
return request('/api/rule', {
|
||||
method: 'POST',
|
||||
data: {
|
||||
...params,
|
||||
method: 'update',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
@import '~antd/es/style/themes/default.less';
|
||||
|
||||
.main {
|
||||
width: 328px;
|
||||
margin: 0 auto;
|
||||
@media screen and (max-width: @screen-sm) {
|
||||
width: 95%;
|
||||
max-width: 328px;
|
||||
}
|
||||
|
||||
:global {
|
||||
.@{ant-prefix}-tabs-nav-list {
|
||||
margin: auto;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-left: 16px;
|
||||
color: rgba(0, 0, 0, 0.2);
|
||||
font-size: 24px;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s;
|
||||
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.other {
|
||||
margin-top: 24px;
|
||||
line-height: 22px;
|
||||
text-align: left;
|
||||
.register {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.prefixIcon {
|
||||
color: @primary-color;
|
||||
font-size: @font-size-base;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,263 +0,0 @@
|
|||
import {
|
||||
AlipayCircleOutlined,
|
||||
LockOutlined,
|
||||
MailOutlined,
|
||||
MobileOutlined,
|
||||
TaobaoCircleOutlined,
|
||||
UserOutlined,
|
||||
WeiboCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Alert, Space, message, Tabs } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import ProForm, { ProFormCaptcha, ProFormCheckbox, ProFormText } from '@ant-design/pro-form';
|
||||
import { useIntl, connect, FormattedMessage } from 'umi';
|
||||
import { getFakeCaptcha } from '@/services/login';
|
||||
import type { Dispatch } from 'umi';
|
||||
import type { StateType } from '@/models/login';
|
||||
import type { LoginParamsType } from '@/services/login';
|
||||
import type { ConnectState } from '@/models/connect';
|
||||
|
||||
import styles from './index.less';
|
||||
|
||||
export type LoginProps = {
|
||||
dispatch: Dispatch;
|
||||
userLogin: StateType;
|
||||
submitting?: boolean;
|
||||
};
|
||||
|
||||
const LoginMessage: React.FC<{
|
||||
content: string;
|
||||
}> = ({ content }) => (
|
||||
<Alert
|
||||
style={{
|
||||
marginBottom: 24,
|
||||
}}
|
||||
message={content}
|
||||
type="error"
|
||||
showIcon
|
||||
/>
|
||||
);
|
||||
|
||||
const Login: React.FC<LoginProps> = (props) => {
|
||||
const { userLogin = {}, submitting } = props;
|
||||
const { status, type: loginType } = userLogin;
|
||||
const [type, setType] = useState<string>('account');
|
||||
const intl = useIntl();
|
||||
|
||||
const handleSubmit = (values: LoginParamsType) => {
|
||||
const { dispatch } = props;
|
||||
dispatch({
|
||||
type: 'login/login',
|
||||
payload: { ...values, type },
|
||||
});
|
||||
};
|
||||
return (
|
||||
<div className={styles.main}>
|
||||
<ProForm
|
||||
initialValues={{
|
||||
autoLogin: true,
|
||||
}}
|
||||
submitter={{
|
||||
render: (_, dom) => dom.pop(),
|
||||
submitButtonProps: {
|
||||
loading: submitting,
|
||||
size: 'large',
|
||||
style: {
|
||||
width: '100%',
|
||||
},
|
||||
},
|
||||
}}
|
||||
onFinish={(values) => {
|
||||
handleSubmit(values as LoginParamsType);
|
||||
return Promise.resolve();
|
||||
}}
|
||||
>
|
||||
<Tabs activeKey={type} onChange={setType}>
|
||||
<Tabs.TabPane
|
||||
key="account"
|
||||
tab={intl.formatMessage({
|
||||
id: 'pages.login.accountLogin.tab',
|
||||
defaultMessage: 'Account password login',
|
||||
})}
|
||||
/>
|
||||
<Tabs.TabPane
|
||||
key="mobile"
|
||||
tab={intl.formatMessage({
|
||||
id: 'pages.login.phoneLogin.tab',
|
||||
defaultMessage: 'Mobile phone number login',
|
||||
})}
|
||||
/>
|
||||
</Tabs>
|
||||
|
||||
{status === 'error' && loginType === 'account' && !submitting && (
|
||||
<LoginMessage
|
||||
content={intl.formatMessage({
|
||||
id: 'pages.login.accountLogin.errorMessage',
|
||||
defaultMessage: 'Incorrect account or password(admin/ant.design)',
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{type === 'account' && (
|
||||
<>
|
||||
<ProFormText
|
||||
name="userName"
|
||||
fieldProps={{
|
||||
size: 'large',
|
||||
prefix: <UserOutlined className={styles.prefixIcon} />,
|
||||
}}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'pages.login.username.placeholder',
|
||||
defaultMessage: 'Username: admin or user',
|
||||
})}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: (
|
||||
<FormattedMessage
|
||||
id="pages.login.username.required"
|
||||
defaultMessage="Please enter user name!"
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<ProFormText.Password
|
||||
name="password"
|
||||
fieldProps={{
|
||||
size: 'large',
|
||||
prefix: <LockOutlined className={styles.prefixIcon} />,
|
||||
}}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'pages.login.password.placeholder',
|
||||
defaultMessage: 'Password: ant.design',
|
||||
})}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: (
|
||||
<FormattedMessage
|
||||
id="pages.login.password.required"
|
||||
defaultMessage="Please enter password!"
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{status === 'error' && loginType === 'mobile' && !submitting && (
|
||||
<LoginMessage content="Verification code error" />
|
||||
)}
|
||||
{type === 'mobile' && (
|
||||
<>
|
||||
<ProFormText
|
||||
fieldProps={{
|
||||
size: 'large',
|
||||
prefix: <MobileOutlined className={styles.prefixIcon} />,
|
||||
}}
|
||||
name="mobile"
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'pages.login.phoneNumber.placeholder',
|
||||
defaultMessage: 'Phone number',
|
||||
})}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: (
|
||||
<FormattedMessage
|
||||
id="pages.login.phoneNumber.required"
|
||||
defaultMessage="Please enter phone number!"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
pattern: /^1\d{10}$/,
|
||||
message: (
|
||||
<FormattedMessage
|
||||
id="pages.login.phoneNumber.invalid"
|
||||
defaultMessage="Malformed phone number!"
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<ProFormCaptcha
|
||||
fieldProps={{
|
||||
size: 'large',
|
||||
prefix: <MailOutlined className={styles.prefixIcon} />,
|
||||
}}
|
||||
captchaProps={{
|
||||
size: 'large',
|
||||
}}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'pages.login.captcha.placeholder',
|
||||
defaultMessage: 'Please enter verification code',
|
||||
})}
|
||||
captchaTextRender={(timing, count) => {
|
||||
if (timing) {
|
||||
return `${count} ${intl.formatMessage({
|
||||
id: 'pages.getCaptchaSecondText',
|
||||
defaultMessage: 'Get verification code',
|
||||
})}`;
|
||||
}
|
||||
return intl.formatMessage({
|
||||
id: 'pages.login.phoneLogin.getVerificationCode',
|
||||
defaultMessage: 'Get verification code',
|
||||
});
|
||||
}}
|
||||
name="captcha"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: (
|
||||
<FormattedMessage
|
||||
id="pages.login.captcha.required"
|
||||
defaultMessage="Please enter verification code!"
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
onGetCaptcha={async (mobile) => {
|
||||
const result = await getFakeCaptcha(mobile);
|
||||
if (result === false) {
|
||||
return;
|
||||
}
|
||||
message.success(
|
||||
'Get the verification code successfully! The verification code is: 1234',
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
marginBottom: 24,
|
||||
}}
|
||||
>
|
||||
<ProFormCheckbox noStyle name="autoLogin">
|
||||
<FormattedMessage id="pages.login.rememberMe" defaultMessage="Auto login" />
|
||||
</ProFormCheckbox>
|
||||
<a
|
||||
style={{
|
||||
float: 'right',
|
||||
}}
|
||||
>
|
||||
<FormattedMessage id="pages.login.forgotPassword" defaultMessage="Forget password" />
|
||||
</a>
|
||||
</div>
|
||||
</ProForm>
|
||||
<Space className={styles.other}>
|
||||
<FormattedMessage id="pages.login.loginWith" defaultMessage="Other login methods" />
|
||||
<AlipayCircleOutlined className={styles.icon} />
|
||||
<TaobaoCircleOutlined className={styles.icon} />
|
||||
<WeiboCircleOutlined className={styles.icon} />
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(({ login, loading }: ConnectState) => ({
|
||||
userLogin: login,
|
||||
submitting: loading.effects['login/login'],
|
||||
}))(Login);
|
||||
|
|
@ -36,7 +36,6 @@
|
|||
padding: 0;
|
||||
}
|
||||
#root {
|
||||
background-image: url('<%= context.config.publicPath +"home_bg.png"%>');
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% auto;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,3 +69,46 @@
|
|||
color: @text-color-secondary;
|
||||
font-size: @font-size-base;
|
||||
}
|
||||
|
||||
.main {
|
||||
width: 328px;
|
||||
margin: 0 auto;
|
||||
@media screen and (max-width: @screen-sm) {
|
||||
width: 95%;
|
||||
max-width: 328px;
|
||||
}
|
||||
|
||||
:global {
|
||||
.@{ant-prefix}-tabs-nav-list {
|
||||
margin: auto;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-left: 16px;
|
||||
color: rgba(0, 0, 0, 0.2);
|
||||
font-size: 24px;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s;
|
||||
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.other {
|
||||
margin-top: 24px;
|
||||
line-height: 22px;
|
||||
text-align: left;
|
||||
.register {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.prefixIcon {
|
||||
color: @primary-color;
|
||||
font-size: @font-size-base;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,318 @@
|
|||
import {
|
||||
AlipayCircleOutlined,
|
||||
LockOutlined,
|
||||
MobileOutlined,
|
||||
TaobaoCircleOutlined,
|
||||
UserOutlined,
|
||||
WeiboCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Alert, Space, message, Tabs } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import ProForm, { ProFormCaptcha, ProFormCheckbox, ProFormText } from '@ant-design/pro-form';
|
||||
import { useIntl, Link, history, FormattedMessage, SelectLang, useModel } from 'umi';
|
||||
import Footer from '@/components/Footer';
|
||||
import { login } from '@/services/ant-design-pro/api';
|
||||
import { getFakeCaptcha } from '@/services/ant-design-pro/login';
|
||||
|
||||
import styles from './index.less';
|
||||
|
||||
const LoginMessage: React.FC<{
|
||||
content: string;
|
||||
}> = ({ content }) => (
|
||||
<Alert
|
||||
style={{
|
||||
marginBottom: 24,
|
||||
}}
|
||||
message={content}
|
||||
type="error"
|
||||
showIcon
|
||||
/>
|
||||
);
|
||||
|
||||
/** 此方法会跳转到 redirect 参数所在的位置 */
|
||||
const goto = () => {
|
||||
if (!history) return;
|
||||
setTimeout(() => {
|
||||
const { query } = history.location;
|
||||
const { redirect } = query as { redirect: string };
|
||||
history.push(redirect || '/');
|
||||
}, 10);
|
||||
};
|
||||
|
||||
const Login: React.FC = () => {
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [userLoginState, setUserLoginState] = useState<API.LoginResult>({});
|
||||
const [type, setType] = useState<string>('account');
|
||||
const { initialState, setInitialState } = useModel('@@initialState');
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
const fetchUserInfo = async () => {
|
||||
const userInfo = await initialState?.fetchUserInfo?.();
|
||||
if (userInfo) {
|
||||
setInitialState({
|
||||
...initialState,
|
||||
currentUser: userInfo,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (values: API.LoginParams) => {
|
||||
setSubmitting(true);
|
||||
try {
|
||||
// 登录
|
||||
const msg = await login({ ...values, type });
|
||||
if (msg.status === 'ok') {
|
||||
const defaultloginSuccessMessage = intl.formatMessage({
|
||||
id: 'pages.login.success',
|
||||
defaultMessage: '登录成功!',
|
||||
});
|
||||
message.success(defaultloginSuccessMessage);
|
||||
await fetchUserInfo();
|
||||
goto();
|
||||
return;
|
||||
}
|
||||
// 如果失败去设置用户错误信息
|
||||
setUserLoginState(msg);
|
||||
} catch (error) {
|
||||
const defaultloginFailureMessage = intl.formatMessage({
|
||||
id: 'pages.login.failure',
|
||||
defaultMessage: '登录失败,请重试!',
|
||||
});
|
||||
|
||||
message.error(defaultloginFailureMessage);
|
||||
}
|
||||
setSubmitting(false);
|
||||
};
|
||||
const { status, type: loginType } = userLoginState;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.lang} data-lang>
|
||||
{SelectLang && <SelectLang />}
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.top}>
|
||||
<div className={styles.header}>
|
||||
<Link to="/">
|
||||
<img alt="logo" className={styles.logo} src="/logo.svg" />
|
||||
<span className={styles.title}>Ant Design</span>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={styles.desc}>
|
||||
{intl.formatMessage({ id: 'pages.layouts.userLayout.title' })}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.main}>
|
||||
<ProForm
|
||||
initialValues={{
|
||||
autoLogin: true,
|
||||
}}
|
||||
submitter={{
|
||||
searchConfig: {
|
||||
submitText: intl.formatMessage({
|
||||
id: 'pages.login.submit',
|
||||
defaultMessage: '登录',
|
||||
}),
|
||||
},
|
||||
render: (_, dom) => dom.pop(),
|
||||
submitButtonProps: {
|
||||
loading: submitting,
|
||||
size: 'large',
|
||||
style: {
|
||||
width: '100%',
|
||||
},
|
||||
},
|
||||
}}
|
||||
onFinish={async (values) => {
|
||||
handleSubmit(values as API.LoginParams);
|
||||
}}
|
||||
>
|
||||
<Tabs activeKey={type} onChange={setType}>
|
||||
<Tabs.TabPane
|
||||
key="account"
|
||||
tab={intl.formatMessage({
|
||||
id: 'pages.login.accountLogin.tab',
|
||||
defaultMessage: '账户密码登录',
|
||||
})}
|
||||
/>
|
||||
<Tabs.TabPane
|
||||
key="mobile"
|
||||
tab={intl.formatMessage({
|
||||
id: 'pages.login.phoneLogin.tab',
|
||||
defaultMessage: '手机号登录',
|
||||
})}
|
||||
/>
|
||||
</Tabs>
|
||||
|
||||
{status === 'error' && loginType === 'account' && (
|
||||
<LoginMessage
|
||||
content={intl.formatMessage({
|
||||
id: 'pages.login.accountLogin.errorMessage',
|
||||
defaultMessage: '账户或密码错误(admin/ant.design)',
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{type === 'account' && (
|
||||
<>
|
||||
<ProFormText
|
||||
name="username"
|
||||
fieldProps={{
|
||||
size: 'large',
|
||||
prefix: <UserOutlined className={styles.prefixIcon} />,
|
||||
}}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'pages.login.username.placeholder',
|
||||
defaultMessage: '用户名: admin or user',
|
||||
})}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: (
|
||||
<FormattedMessage
|
||||
id="pages.login.username.required"
|
||||
defaultMessage="请输入用户名!"
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<ProFormText.Password
|
||||
name="password"
|
||||
fieldProps={{
|
||||
size: 'large',
|
||||
prefix: <LockOutlined className={styles.prefixIcon} />,
|
||||
}}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'pages.login.password.placeholder',
|
||||
defaultMessage: '密码: ant.design',
|
||||
})}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: (
|
||||
<FormattedMessage
|
||||
id="pages.login.password.required"
|
||||
defaultMessage="请输入密码!"
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{status === 'error' && loginType === 'mobile' && <LoginMessage content="验证码错误" />}
|
||||
{type === 'mobile' && (
|
||||
<>
|
||||
<ProFormText
|
||||
fieldProps={{
|
||||
size: 'large',
|
||||
prefix: <MobileOutlined className={styles.prefixIcon} />,
|
||||
}}
|
||||
name="mobile"
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'pages.login.phoneNumber.placeholder',
|
||||
defaultMessage: '手机号',
|
||||
})}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: (
|
||||
<FormattedMessage
|
||||
id="pages.login.phoneNumber.required"
|
||||
defaultMessage="请输入手机号!"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
pattern: /^1\d{10}$/,
|
||||
message: (
|
||||
<FormattedMessage
|
||||
id="pages.login.phoneNumber.invalid"
|
||||
defaultMessage="手机号格式错误!"
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<ProFormCaptcha
|
||||
fieldProps={{
|
||||
size: 'large',
|
||||
prefix: <LockOutlined className={styles.prefixIcon} />,
|
||||
}}
|
||||
captchaProps={{
|
||||
size: 'large',
|
||||
}}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'pages.login.captcha.placeholder',
|
||||
defaultMessage: '请输入验证码',
|
||||
})}
|
||||
captchaTextRender={(timing, count) => {
|
||||
if (timing) {
|
||||
return `${count} ${intl.formatMessage({
|
||||
id: 'pages.getCaptchaSecondText',
|
||||
defaultMessage: '获取验证码',
|
||||
})}`;
|
||||
}
|
||||
return intl.formatMessage({
|
||||
id: 'pages.login.phoneLogin.getVerificationCode',
|
||||
defaultMessage: '获取验证码',
|
||||
});
|
||||
}}
|
||||
name="captcha"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: (
|
||||
<FormattedMessage
|
||||
id="pages.login.captcha.required"
|
||||
defaultMessage="请输入验证码!"
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
onGetCaptcha={async (phone) => {
|
||||
const result = await getFakeCaptcha({
|
||||
phone,
|
||||
});
|
||||
if (result === false) {
|
||||
return;
|
||||
}
|
||||
message.success('获取验证码成功!验证码为:1234');
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
marginBottom: 24,
|
||||
}}
|
||||
>
|
||||
<ProFormCheckbox noStyle name="autoLogin">
|
||||
<FormattedMessage id="pages.login.rememberMe" defaultMessage="自动登录" />
|
||||
</ProFormCheckbox>
|
||||
<a
|
||||
style={{
|
||||
float: 'right',
|
||||
}}
|
||||
>
|
||||
<FormattedMessage id="pages.login.forgotPassword" defaultMessage="忘记密码" />
|
||||
</a>
|
||||
</div>
|
||||
</ProForm>
|
||||
<Space className={styles.other}>
|
||||
<FormattedMessage id="pages.login.loginWith" defaultMessage="其他登录方式" />
|
||||
<AlipayCircleOutlined className={styles.icon} />
|
||||
<TaobaoCircleOutlined className={styles.icon} />
|
||||
<WeiboCircleOutlined className={styles.icon} />
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
/* globals workbox */
|
||||
workbox.core.setCacheNameDetails({
|
||||
prefix: 'antd-pro',
|
||||
suffix: 'v1',
|
||||
suffix: 'v5',
|
||||
});
|
||||
// Control all opened tabs ASAP
|
||||
workbox.clientsClaim();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
// @ts-ignore
|
||||
/* eslint-disable */
|
||||
import { request } from 'umi';
|
||||
|
||||
/** 获取当前的用户 GET /api/currentUser */
|
||||
export async function currentUser(options?: { [key: string]: any }) {
|
||||
return request<API.CurrentUser>('/api/currentUser', {
|
||||
method: 'GET',
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 退出登录接口 POST /api/login/outLogin */
|
||||
export async function outLogin(options?: { [key: string]: any }) {
|
||||
return request<Record<string, any>>('/api/login/outLogin', {
|
||||
method: 'POST',
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 登录接口 POST /api/login/account */
|
||||
export async function login(body: API.LoginParams, options?: { [key: string]: any }) {
|
||||
return request<API.LoginResult>('/api/login/account', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: body,
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 GET /api/notices */
|
||||
export async function getNotices(options?: { [key: string]: any }) {
|
||||
return request<API.NoticeIconList>('/api/notices', {
|
||||
method: 'GET',
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取规则列表 GET /api/rule */
|
||||
export async function rule(
|
||||
params: {
|
||||
// query
|
||||
/** 当前的页码 */
|
||||
current?: number;
|
||||
/** 页面的容量 */
|
||||
pageSize?: number;
|
||||
},
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
return request<API.RuleList>('/api/rule', {
|
||||
method: 'GET',
|
||||
params: {
|
||||
...params,
|
||||
},
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 新建规则 PUT /api/rule */
|
||||
export async function updateRule(options?: { [key: string]: any }) {
|
||||
return request<API.RuleListItem>('/api/rule', {
|
||||
method: 'PUT',
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 新建规则 POST /api/rule */
|
||||
export async function addRule(options?: { [key: string]: any }) {
|
||||
return request<API.RuleListItem>('/api/rule', {
|
||||
method: 'POST',
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 删除规则 DELETE /api/rule */
|
||||
export async function removeRule(options?: { [key: string]: any }) {
|
||||
return request<Record<string, any>>('/api/rule', {
|
||||
method: 'DELETE',
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// @ts-ignore
|
||||
/* eslint-disable */
|
||||
// API 更新时间:
|
||||
// API 唯一标识:
|
||||
import * as api from './api';
|
||||
import * as login from './login';
|
||||
export default {
|
||||
api,
|
||||
login,
|
||||
};
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// @ts-ignore
|
||||
/* eslint-disable */
|
||||
import { request } from 'umi';
|
||||
|
||||
/** 发送验证码 POST /api/login/captcha */
|
||||
export async function getFakeCaptcha(
|
||||
params: {
|
||||
// query
|
||||
/** 手机号 */
|
||||
phone?: string;
|
||||
},
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
return request<API.FakeCaptcha>('/api/login/captcha', {
|
||||
method: 'POST',
|
||||
params: {
|
||||
...params,
|
||||
},
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
// @ts-ignore
|
||||
/* eslint-disable */
|
||||
|
||||
declare namespace API {
|
||||
type CurrentUser = {
|
||||
name?: string;
|
||||
avatar?: string;
|
||||
userid?: string;
|
||||
email?: string;
|
||||
signature?: string;
|
||||
title?: string;
|
||||
group?: string;
|
||||
tags?: { key?: string; label?: string }[];
|
||||
notifyCount?: number;
|
||||
unreadCount?: number;
|
||||
country?: string;
|
||||
access?: string;
|
||||
geographic?: {
|
||||
province?: { label?: string; key?: string };
|
||||
city?: { label?: string; key?: string };
|
||||
};
|
||||
address?: string;
|
||||
phone?: string;
|
||||
};
|
||||
|
||||
type LoginResult = {
|
||||
status?: string;
|
||||
type?: string;
|
||||
currentAuthority?: string;
|
||||
};
|
||||
|
||||
type PageParams = {
|
||||
current?: number;
|
||||
pageSize?: number;
|
||||
};
|
||||
|
||||
type RuleListItem = {
|
||||
key?: number;
|
||||
disabled?: boolean;
|
||||
href?: string;
|
||||
avatar?: string;
|
||||
name?: string;
|
||||
owner?: string;
|
||||
desc?: string;
|
||||
callNo?: number;
|
||||
status?: number;
|
||||
updatedAt?: string;
|
||||
createdAt?: string;
|
||||
progress?: number;
|
||||
};
|
||||
|
||||
type RuleList = {
|
||||
data?: RuleListItem[];
|
||||
/** 列表的内容总数 */
|
||||
total?: number;
|
||||
success?: boolean;
|
||||
};
|
||||
|
||||
type FakeCaptcha = {
|
||||
code?: number;
|
||||
status?: string;
|
||||
};
|
||||
|
||||
type LoginParams = {
|
||||
username?: string;
|
||||
password?: string;
|
||||
autoLogin?: boolean;
|
||||
type?: string;
|
||||
};
|
||||
|
||||
type ErrorResponse = {
|
||||
/** 业务约定的错误码 */
|
||||
errorCode: string;
|
||||
/** 业务上的错误信息 */
|
||||
errorMessage?: string;
|
||||
/** 业务上的请求是否成功 */
|
||||
success?: boolean;
|
||||
};
|
||||
|
||||
type NoticeIconList = {
|
||||
data?: NoticeIconItem[];
|
||||
/** 列表的内容总数 */
|
||||
total?: number;
|
||||
success?: boolean;
|
||||
};
|
||||
|
||||
type NoticeIconItemType = 'notification' | 'message' | 'event';
|
||||
|
||||
type NoticeIconItem = {
|
||||
id?: string;
|
||||
extra?: string;
|
||||
key?: string;
|
||||
read?: boolean;
|
||||
avatar?: string;
|
||||
title?: string;
|
||||
status?: string;
|
||||
datetime?: string;
|
||||
description?: string;
|
||||
type?: NoticeIconItemType;
|
||||
};
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
import request from '@/utils/request';
|
||||
|
||||
export type LoginParamsType = {
|
||||
userName: string;
|
||||
password: string;
|
||||
mobile: string;
|
||||
captcha: string;
|
||||
};
|
||||
|
||||
export async function fakeAccountLogin(params: LoginParamsType) {
|
||||
return request('/api/login/account', {
|
||||
method: 'POST',
|
||||
data: params,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getFakeCaptcha(mobile: string) {
|
||||
return request(`/api/login/captcha?mobile=${mobile}`);
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// @ts-ignore
|
||||
/* eslint-disable */
|
||||
// API 更新时间:
|
||||
// API 唯一标识:
|
||||
import * as pet from './pet';
|
||||
import * as store from './store';
|
||||
import * as user from './user';
|
||||
export default {
|
||||
pet,
|
||||
store,
|
||||
user,
|
||||
};
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
// @ts-ignore
|
||||
/* eslint-disable */
|
||||
import { request } from 'umi';
|
||||
|
||||
/** Update an existing pet PUT /pet */
|
||||
export async function updatePet(body: API.Pet, options?: { [key: string]: any }) {
|
||||
return request<any>('/pet', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: body,
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** Add a new pet to the store POST /pet */
|
||||
export async function addPet(body: API.Pet, options?: { [key: string]: any }) {
|
||||
return request<any>('/pet', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: body,
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** Finds Pets by status Multiple status values can be provided with comma separated strings GET /pet/findByStatus */
|
||||
export async function findPetsByStatus(
|
||||
params: {
|
||||
// query
|
||||
/** Status values that need to be considered for filter */
|
||||
status: 'available' | 'pending' | 'sold'[];
|
||||
},
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
return request<API.Pet[]>('/pet/findByStatus', {
|
||||
method: 'GET',
|
||||
params: {
|
||||
...params,
|
||||
},
|
||||
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** Finds Pets by tags Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. GET /pet/findByTags */
|
||||
export async function findPetsByTags(
|
||||
params: {
|
||||
// query
|
||||
/** Tags to filter by */
|
||||
tags: string[];
|
||||
},
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
return request<API.Pet[]>('/pet/findByTags', {
|
||||
method: 'GET',
|
||||
params: {
|
||||
...params,
|
||||
},
|
||||
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** Find pet by ID Returns a single pet GET /pet/${param0} */
|
||||
export async function getPetById(
|
||||
params: {
|
||||
// path
|
||||
/** ID of pet to return */
|
||||
petId: number;
|
||||
},
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const { petId: param0 } = params;
|
||||
return request<API.Pet>(`/pet/${param0}`, {
|
||||
method: 'GET',
|
||||
params: { ...params },
|
||||
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** Updates a pet in the store with form data POST /pet/${param0} */
|
||||
export async function updatePetWithForm(
|
||||
params: {
|
||||
// path
|
||||
/** ID of pet that needs to be updated */
|
||||
petId: number;
|
||||
},
|
||||
body: { name?: string; status?: string },
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const { petId: param0 } = params;
|
||||
const formData = new FormData();
|
||||
|
||||
Object.keys(body).forEach((ele) => {
|
||||
const item = (body as any)[ele];
|
||||
|
||||
if (item !== undefined && item !== null) {
|
||||
formData.append(ele, typeof item === 'object' ? JSON.stringify(item) : item);
|
||||
}
|
||||
});
|
||||
|
||||
return request<any>(`/pet/${param0}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
params: { ...params },
|
||||
data: formData,
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** Deletes a pet DELETE /pet/${param0} */
|
||||
export async function deletePet(
|
||||
params: {
|
||||
// header
|
||||
api_key?: string;
|
||||
// path
|
||||
/** Pet id to delete */
|
||||
petId: number;
|
||||
},
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const { petId: param0 } = params;
|
||||
return request<any>(`/pet/${param0}`, {
|
||||
method: 'DELETE',
|
||||
params: { ...params },
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** uploads an image POST /pet/${param0}/uploadImage */
|
||||
export async function uploadFile(
|
||||
params: {
|
||||
// path
|
||||
/** ID of pet to update */
|
||||
petId: number;
|
||||
},
|
||||
body: { additionalMetadata?: string; file?: string },
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const { petId: param0 } = params;
|
||||
const formData = new FormData();
|
||||
|
||||
Object.keys(body).forEach((ele) => {
|
||||
const item = (body as any)[ele];
|
||||
|
||||
if (item !== undefined && item !== null) {
|
||||
formData.append(ele, typeof item === 'object' ? JSON.stringify(item) : item);
|
||||
}
|
||||
});
|
||||
|
||||
return request<API.ApiResponse>(`/pet/${param0}/uploadImage`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
params: { ...params },
|
||||
data: formData,
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
// @ts-ignore
|
||||
/* eslint-disable */
|
||||
import { request } from 'umi';
|
||||
|
||||
/** Returns pet inventories by status Returns a map of status codes to quantities GET /store/inventory */
|
||||
export async function getInventory(options?: { [key: string]: any }) {
|
||||
return request<Record<string, any>>('/store/inventory', {
|
||||
method: 'GET',
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** Place an order for a pet POST /store/order */
|
||||
export async function placeOrder(body: API.Order, options?: { [key: string]: any }) {
|
||||
return request<API.Order>('/store/order', {
|
||||
method: 'POST',
|
||||
data: body,
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** Find purchase order by ID For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions GET /store/order/${param0} */
|
||||
export async function getOrderById(
|
||||
params: {
|
||||
// path
|
||||
/** ID of pet that needs to be fetched */
|
||||
orderId: number;
|
||||
},
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const { orderId: param0 } = params;
|
||||
return request<API.Order>(`/store/order/${param0}`, {
|
||||
method: 'GET',
|
||||
params: { ...params },
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** Delete purchase order by ID For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors DELETE /store/order/${param0} */
|
||||
export async function deleteOrder(
|
||||
params: {
|
||||
// path
|
||||
/** ID of the order that needs to be deleted */
|
||||
orderId: number;
|
||||
},
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const { orderId: param0 } = params;
|
||||
return request<any>(`/store/order/${param0}`, {
|
||||
method: 'DELETE',
|
||||
params: { ...params },
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
// @ts-ignore
|
||||
/* eslint-disable */
|
||||
|
||||
declare namespace API {
|
||||
type Order = {
|
||||
id?: number;
|
||||
petId?: number;
|
||||
quantity?: number;
|
||||
shipDate?: string;
|
||||
/** Order Status */
|
||||
status?: 'placed' | 'approved' | 'delivered';
|
||||
complete?: boolean;
|
||||
};
|
||||
|
||||
type Category = {
|
||||
id?: number;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
type User = {
|
||||
id?: number;
|
||||
username?: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
email?: string;
|
||||
password?: string;
|
||||
phone?: string;
|
||||
/** User Status */
|
||||
userStatus?: number;
|
||||
};
|
||||
|
||||
type Tag = {
|
||||
id?: number;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
type Pet = {
|
||||
id?: number;
|
||||
category?: Category;
|
||||
name: string;
|
||||
photoUrls: string[];
|
||||
tags?: Tag[];
|
||||
/** pet status in the store */
|
||||
status?: 'available' | 'pending' | 'sold';
|
||||
};
|
||||
|
||||
type ApiResponse = {
|
||||
code?: number;
|
||||
type?: string;
|
||||
message?: string;
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
// @ts-ignore
|
||||
/* eslint-disable */
|
||||
import { request } from 'umi';
|
||||
|
||||
/** Create user This can only be done by the logged in user. POST /user */
|
||||
export async function createUser(body: API.User, options?: { [key: string]: any }) {
|
||||
return request<any>('/user', {
|
||||
method: 'POST',
|
||||
data: body,
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** Creates list of users with given input array POST /user/createWithArray */
|
||||
export async function createUsersWithArrayInput(
|
||||
body: API.User[],
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
return request<any>('/user/createWithArray', {
|
||||
method: 'POST',
|
||||
data: body,
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** Creates list of users with given input array POST /user/createWithList */
|
||||
export async function createUsersWithListInput(body: API.User[], options?: { [key: string]: any }) {
|
||||
return request<any>('/user/createWithList', {
|
||||
method: 'POST',
|
||||
data: body,
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** Logs user into the system GET /user/login */
|
||||
export async function loginUser(
|
||||
params: {
|
||||
// query
|
||||
/** The user name for login */
|
||||
username: string;
|
||||
/** The password for login in clear text */
|
||||
password: string;
|
||||
},
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
return request<string>('/user/login', {
|
||||
method: 'GET',
|
||||
params: {
|
||||
...params,
|
||||
},
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** Logs out current logged in user session GET /user/logout */
|
||||
export async function logoutUser(options?: { [key: string]: any }) {
|
||||
return request<any>('/user/logout', {
|
||||
method: 'GET',
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** Get user by user name GET /user/${param0} */
|
||||
export async function getUserByName(
|
||||
params: {
|
||||
// path
|
||||
/** The name that needs to be fetched. Use user1 for testing. */
|
||||
username: string;
|
||||
},
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const { username: param0 } = params;
|
||||
return request<API.User>(`/user/${param0}`, {
|
||||
method: 'GET',
|
||||
params: { ...params },
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** Updated user This can only be done by the logged in user. PUT /user/${param0} */
|
||||
export async function updateUser(
|
||||
params: {
|
||||
// path
|
||||
/** name that need to be updated */
|
||||
username: string;
|
||||
},
|
||||
body: API.User,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const { username: param0 } = params;
|
||||
return request<any>(`/user/${param0}`, {
|
||||
method: 'PUT',
|
||||
params: { ...params },
|
||||
data: body,
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** Delete user This can only be done by the logged in user. DELETE /user/${param0} */
|
||||
export async function deleteUser(
|
||||
params: {
|
||||
// path
|
||||
/** The name that needs to be deleted */
|
||||
username: string;
|
||||
},
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const { username: param0 } = params;
|
||||
return request<any>(`/user/${param0}`, {
|
||||
method: 'DELETE',
|
||||
params: { ...params },
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
import request from '@/utils/request';
|
||||
|
||||
export async function query(): Promise<any> {
|
||||
return request('/api/users');
|
||||
}
|
||||
|
||||
export async function queryCurrent(): Promise<any> {
|
||||
return request('/api/currentUser');
|
||||
}
|
||||
|
||||
export async function queryNotices(): Promise<any> {
|
||||
return request('/api/notices');
|
||||
}
|
||||
|
|
@ -17,27 +17,6 @@ declare module 'mockjs';
|
|||
declare module 'react-fittext';
|
||||
declare module 'bizcharts-plugin-slider';
|
||||
|
||||
// google analytics interface
|
||||
type GAFieldsObject = {
|
||||
eventCategory: string;
|
||||
eventAction: string;
|
||||
eventLabel?: string;
|
||||
eventValue?: number;
|
||||
nonInteraction?: boolean;
|
||||
};
|
||||
|
||||
interface Window {
|
||||
ga: (
|
||||
command: 'send',
|
||||
hitType: 'event' | 'pageview',
|
||||
fieldsObject: GAFieldsObject | string,
|
||||
) => void;
|
||||
reloadAuthorized: () => void;
|
||||
routerBase: string;
|
||||
}
|
||||
|
||||
declare let ga: () => void;
|
||||
|
||||
// preview.pro.ant.design only do not use in your production ;
|
||||
// preview.pro.ant.design Dedicated environment variable, please do not use it in your project.
|
||||
declare let ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: 'site' | undefined;
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
import RenderAuthorize from '@/components/Authorized';
|
||||
import { getAuthority } from './authority';
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable import/no-mutable-exports */
|
||||
let Authorized = RenderAuthorize(getAuthority());
|
||||
|
||||
// Reload the rights component
|
||||
const reloadAuthorized = (): void => {
|
||||
Authorized = RenderAuthorize(getAuthority());
|
||||
};
|
||||
|
||||
/** Hard code block need it。 */
|
||||
window.reloadAuthorized = reloadAuthorized;
|
||||
|
||||
export { reloadAuthorized };
|
||||
export default Authorized;
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
import { reloadAuthorized } from './Authorized';
|
||||
|
||||
// use localStorage to store the authority info, which might be sent from server in actual project.
|
||||
export function getAuthority(str?: string): string | string[] {
|
||||
const authorityString =
|
||||
typeof str === 'undefined' && localStorage ? localStorage.getItem('antd-pro-authority') : str;
|
||||
// authorityString could be admin, "admin", ["admin"]
|
||||
let authority;
|
||||
try {
|
||||
if (authorityString) {
|
||||
authority = JSON.parse(authorityString);
|
||||
}
|
||||
} catch (e) {
|
||||
authority = authorityString;
|
||||
}
|
||||
if (typeof authority === 'string') {
|
||||
return [authority];
|
||||
}
|
||||
// preview.pro.ant.design only do not use in your production.
|
||||
// preview.pro.ant.design Dedicated environment variable, please do not use it in your project.
|
||||
if (!authority && ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site') {
|
||||
return ['admin'];
|
||||
}
|
||||
return authority;
|
||||
}
|
||||
|
||||
export function setAuthority(authority: string | string[]): void {
|
||||
const proAuthority = typeof authority === 'string' ? [authority] : authority;
|
||||
localStorage.setItem('antd-pro-authority', JSON.stringify(proAuthority));
|
||||
// auto reload
|
||||
reloadAuthorized();
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
/** Request 网络请求工具 更详细的 api 文档: https://github.com/umijs/umi-request */
|
||||
import { extend } from 'umi-request';
|
||||
import { notification } from 'antd';
|
||||
|
||||
const codeMessage: Record<number, string> = {
|
||||
200: '服务器成功返回请求的数据。',
|
||||
201: '新建或修改数据成功。',
|
||||
202: '一个请求已经进入后台排队(异步任务)。',
|
||||
204: '删除数据成功。',
|
||||
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
|
||||
401: '用户没有权限(令牌、用户名、密码错误)。',
|
||||
403: '用户得到授权,但是访问是被禁止的。',
|
||||
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
|
||||
406: '请求的格式不可得。',
|
||||
410: '请求的资源被永久删除,且不会再得到的。',
|
||||
422: '当创建一个对象时,发生一个验证错误。',
|
||||
500: '服务器发生错误,请检查服务器。',
|
||||
502: '网关错误。',
|
||||
503: '服务不可用,服务器暂时过载或维护。',
|
||||
504: '网关超时。',
|
||||
};
|
||||
|
||||
/**
|
||||
* @zh-CN 异常处理程序
|
||||
* @en-US Exception handler
|
||||
*/
|
||||
const errorHandler = (error: { response: Response }): Response => {
|
||||
const { response } = error;
|
||||
if (response && response.status) {
|
||||
const errorText = codeMessage[response.status] || response.statusText;
|
||||
const { status, url } = response;
|
||||
|
||||
notification.error({
|
||||
message: `Request error ${status}: ${url}`,
|
||||
description: errorText,
|
||||
});
|
||||
} else if (!response) {
|
||||
notification.error({
|
||||
description: 'Your network is abnormal and cannot connect to the server',
|
||||
message: 'Network anomaly',
|
||||
});
|
||||
}
|
||||
return response;
|
||||
};
|
||||
|
||||
/**
|
||||
* @en-US Configure the default parameters for request
|
||||
* @zh-CN 配置request请求时的默认参数
|
||||
*/
|
||||
const request = extend({
|
||||
errorHandler, // default error handling
|
||||
credentials: 'include', // Does the default request bring cookies
|
||||
});
|
||||
|
||||
export default request;
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
// mixins for clearfix
|
||||
// ------------------------
|
||||
.clearfix() {
|
||||
zoom: 1;
|
||||
&::before,
|
||||
&::after {
|
||||
display: table;
|
||||
content: ' ';
|
||||
}
|
||||
&::after {
|
||||
clear: both;
|
||||
height: 0;
|
||||
font-size: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
import { isUrl } from './utils';
|
||||
|
||||
describe('isUrl tests', (): void => {
|
||||
it('should return false for invalid and corner case inputs', (): void => {
|
||||
expect(isUrl([] as any)).toBeFalsy();
|
||||
expect(isUrl({} as any)).toBeFalsy();
|
||||
expect(isUrl(false as any)).toBeFalsy();
|
||||
expect(isUrl(true as any)).toBeFalsy();
|
||||
expect(isUrl(NaN as any)).toBeFalsy();
|
||||
expect(isUrl(null as any)).toBeFalsy();
|
||||
expect(isUrl(undefined as any)).toBeFalsy();
|
||||
expect(isUrl('')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return false for invalid URLs', (): void => {
|
||||
expect(isUrl('foo')).toBeFalsy();
|
||||
expect(isUrl('bar')).toBeFalsy();
|
||||
expect(isUrl('bar/test')).toBeFalsy();
|
||||
expect(isUrl('http:/example.com/')).toBeFalsy();
|
||||
expect(isUrl('ttp://example.com/')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return true for valid URLs', (): void => {
|
||||
expect(isUrl('http://example.com/')).toBeTruthy();
|
||||
expect(isUrl('https://example.com/')).toBeTruthy();
|
||||
expect(isUrl('http://example.com/test/123')).toBeTruthy();
|
||||
expect(isUrl('https://example.com/test/123')).toBeTruthy();
|
||||
expect(isUrl('http://example.com/test/123?foo=bar')).toBeTruthy();
|
||||
expect(isUrl('https://example.com/test/123?foo=bar')).toBeTruthy();
|
||||
expect(isUrl('http://www.example.com/')).toBeTruthy();
|
||||
expect(isUrl('https://www.example.com/')).toBeTruthy();
|
||||
expect(isUrl('http://www.example.com/test/123')).toBeTruthy();
|
||||
expect(isUrl('https://www.example.com/test/123')).toBeTruthy();
|
||||
expect(isUrl('http://www.example.com/test/123?foo=bar')).toBeTruthy();
|
||||
expect(isUrl('https://www.example.com/test/123?foo=bar')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
import { parse } from 'querystring';
|
||||
|
||||
/* eslint no-useless-escape:0 import/prefer-default-export:0 */
|
||||
const reg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/;
|
||||
|
||||
export const isUrl = (path: string): boolean => reg.test(path);
|
||||
|
||||
export const isAntDesignPro = (): boolean => {
|
||||
if (ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site') {
|
||||
return true;
|
||||
}
|
||||
return window.location.hostname === 'preview.pro.ant.design';
|
||||
};
|
||||
|
||||
// For the official demo site, it is used to turn off features that are not needed in the real development environment
|
||||
export const isAntDesignProOrDev = (): boolean => {
|
||||
const { NODE_ENV } = process.env;
|
||||
if (NODE_ENV === 'development') {
|
||||
return true;
|
||||
}
|
||||
return isAntDesignPro();
|
||||
};
|
||||
|
||||
export const getPageQuery = () => parse(window.location.href.split('?')[1]);
|
||||
|
|
@ -13,7 +13,7 @@ env.PROGRESS = 'none';
|
|||
// flag to prevent multiple test
|
||||
let once = false;
|
||||
|
||||
const startServer = spawn(/^win/.test(process.platform) ? 'npm.cmd' : 'npm', ['start'], {
|
||||
const startServer = spawn(/^win/.test(process.platform) ? 'npm.cmd' : 'npm', ['run', 'serve'], {
|
||||
env,
|
||||
});
|
||||
|
||||
|
|
@ -30,10 +30,7 @@ console.log('Starting development server for e2e tests...');
|
|||
startServer.stdout.on('data', (data) => {
|
||||
console.log(data.toString());
|
||||
// hack code , wait umi
|
||||
if (
|
||||
(!once && data.toString().indexOf('Compiled successfully') >= 0) ||
|
||||
data.toString().indexOf('Theme generated successfully') >= 0
|
||||
) {
|
||||
if (!once && data.toString().indexOf('Serving your umi project!') >= 0) {
|
||||
// eslint-disable-next-line
|
||||
once = true;
|
||||
console.log('Development server is started, ready to run tests.');
|
||||
|
|
@ -45,6 +42,7 @@ startServer.stdout.on('data', (data) => {
|
|||
},
|
||||
);
|
||||
testCmd.on('exit', (code) => {
|
||||
console.log(code);
|
||||
startServer.kill();
|
||||
process.exit(code);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
// do some test init
|
||||
|
||||
const localStorageMock = {
|
||||
getItem: jest.fn(),
|
||||
setItem: jest.fn(),
|
||||
removeItem: jest.fn(),
|
||||
clear: jest.fn(),
|
||||
};
|
||||
|
||||
global.localStorage = localStorageMock;
|
||||
Loading…
Reference in New Issue