committed by
GitHub
181 changed files with 31146 additions and 3973 deletions
@ -1,35 +0,0 @@ |
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. |
|||
|
|||
# dependencies |
|||
**/node_modules |
|||
/src/utils/request-temp.js |
|||
|
|||
# production |
|||
/.vscode |
|||
|
|||
# misc |
|||
.DS_Store |
|||
npm-debug.log* |
|||
yarn-error.log |
|||
|
|||
/coverage |
|||
.idea |
|||
yarn.lock |
|||
package-lock.json |
|||
*bak |
|||
.vscode |
|||
|
|||
# visual studio code |
|||
.history |
|||
*.log |
|||
|
|||
functions/mock |
|||
.temp/** |
|||
|
|||
# umi |
|||
.umi |
|||
.umi-production |
|||
|
|||
# screenshot |
|||
screenshot |
|||
.firebase |
|||
@ -1,25 +1,28 @@ |
|||
--- |
|||
name: '功能需求 ✨' |
|||
name: '功能需求 | Feature Requirements ✨' |
|||
about: 对 Ant Design Pro 的需求或建议 |
|||
title: '👑 [需求]' |
|||
labels: '👑Feature Request' |
|||
title: '👑 [需求 | Feature]' |
|||
labels: '👑 Feature Request' |
|||
assignees: '' |
|||
--- |
|||
|
|||
### 🥰 需求描述 |
|||
### 🥰 需求描述 | Requirements description |
|||
|
|||
<!-- |
|||
详细地描述需求,让大家都能理解 |
|||
Describe the requirements in detail so that everyone can understand them |
|||
--> |
|||
|
|||
### 🧐 解决方案 |
|||
### 🧐 解决方案 | Solution |
|||
|
|||
<!-- |
|||
如果你有解决方案,在这里清晰地阐述 |
|||
If you have a solution, explain it clearly here |
|||
--> |
|||
|
|||
### 🚑 其他信息 |
|||
### 🚑 其他信息 | Other information |
|||
|
|||
<!-- |
|||
如截图等其他信息可以贴在这里 |
|||
Other information such as screenshots can be posted here |
|||
--> |
|||
|
|||
@ -1,25 +1,34 @@ |
|||
--- |
|||
name: '疑问或需要帮助 ❓' |
|||
name: '疑问或需要帮助 | Questions or need help ❓' |
|||
about: 对 Ant Design Pro 使用的疑问或需要帮助 |
|||
title: '🧐[问题]' |
|||
labels: '🧐question' |
|||
title: '🧐[问题 | question]' |
|||
labels: '🧐 question' |
|||
assignees: '' |
|||
--- |
|||
|
|||
### 🧐 问题描述 |
|||
### 🧐 问题描述 | Problem description |
|||
|
|||
<!-- |
|||
详细地描述问题,让大家都能理解 |
|||
Describe the problem in detail so that everyone can understand it |
|||
--> |
|||
|
|||
### 💻 示例代码 |
|||
### 💻 示例代码 | Sample code |
|||
|
|||
<!-- |
|||
如果你有解决方案,在这里清晰地阐述 |
|||
一个最小可重现的代码,让开发者可以快速的定位问题 |
|||
A minimal reproducible code that allows developers to quickly locate problems |
|||
--> |
|||
|
|||
### 🚑 其他信息 |
|||
### 🚑 其他信息 | Other information |
|||
|
|||
<!-- |
|||
如截图等其他信息可以贴在这里 |
|||
Other information such as screenshots can be posted here |
|||
--> |
|||
|
|||
OS: |
|||
|
|||
Node: |
|||
|
|||
浏览器 | browser: |
|||
|
|||
@ -0,0 +1,11 @@ |
|||
# To get started with Dependabot version updates, you'll need to specify which |
|||
# package ecosystems to update and where the package manifests are located. |
|||
# Please see the documentation for all configuration options: |
|||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file |
|||
|
|||
version: 2 |
|||
updates: |
|||
- package-ecosystem: "" # See documentation for possible values |
|||
directory: "/" # Location of package manifests |
|||
schedule: |
|||
interval: "weekly" |
|||
@ -0,0 +1,41 @@ |
|||
name: "CodeQL" |
|||
|
|||
on: |
|||
push: |
|||
branches: [ "master" ] |
|||
pull_request: |
|||
branches: [ "master" ] |
|||
schedule: |
|||
- cron: "48 12 * * 2" |
|||
|
|||
jobs: |
|||
analyze: |
|||
name: Analyze |
|||
runs-on: ubuntu-latest |
|||
permissions: |
|||
actions: read |
|||
contents: read |
|||
security-events: write |
|||
|
|||
strategy: |
|||
fail-fast: false |
|||
matrix: |
|||
language: [ javascript ] |
|||
|
|||
steps: |
|||
- name: Checkout |
|||
uses: actions/checkout@v3 |
|||
|
|||
- name: Initialize CodeQL |
|||
uses: github/codeql-action/init@v2 |
|||
with: |
|||
languages: ${{ matrix.language }} |
|||
queries: +security-and-quality |
|||
|
|||
- name: Autobuild |
|||
uses: github/codeql-action/autobuild@v2 |
|||
|
|||
- name: Perform CodeQL Analysis |
|||
uses: github/codeql-action/analyze@v2 |
|||
with: |
|||
category: "/language:${{ matrix.language }}" |
|||
@ -0,0 +1,27 @@ |
|||
name: coverage CI |
|||
|
|||
on: [push, pull_request] |
|||
|
|||
permissions: |
|||
contents: read |
|||
|
|||
jobs: |
|||
build: |
|||
runs-on: ubuntu-latest |
|||
steps: |
|||
- uses: actions/checkout@v1 |
|||
- name: Use Node.js 16.x |
|||
uses: actions/setup-node@v1 |
|||
with: |
|||
node-version: 16.x |
|||
- run: echo ${{github.ref}} |
|||
- run: curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7 |
|||
- run: pnpm config set store-dir ~/.pnpm-store |
|||
- run: pnpm install --strict-peer-dependencies=false |
|||
- run: yarn run test:coverage |
|||
env: |
|||
CI: true |
|||
PROGRESS: none |
|||
NODE_ENV: test |
|||
NODE_OPTIONS: --max_old_space_size=4096 |
|||
- run: bash <(curl -s https://codecov.io/bash) |
|||
@ -1,28 +0,0 @@ |
|||
name: Deploy CI |
|||
on: |
|||
push: |
|||
branches: |
|||
- master |
|||
|
|||
jobs: |
|||
build-and-deploy: |
|||
runs-on: ubuntu-latest |
|||
steps: |
|||
- name: checkout |
|||
uses: actions/checkout@master |
|||
|
|||
- name: install |
|||
run: npm install |
|||
|
|||
- name: plugins |
|||
run: yarn add umi-plugin-antd-theme umi-plugin-pro |
|||
|
|||
- name: site |
|||
run: npm run site |
|||
|
|||
- name: Deploy |
|||
uses: peaceiris/actions-gh-pages@v3 |
|||
with: |
|||
deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} |
|||
publish_dir: ./dist |
|||
force_orphan: true |
|||
@ -0,0 +1,14 @@ |
|||
name: Emoji Helper |
|||
|
|||
on: |
|||
release: |
|||
types: [published] |
|||
|
|||
jobs: |
|||
emoji: |
|||
runs-on: ubuntu-latest |
|||
steps: |
|||
- uses: actions-cool/emoji-helper@v1.0.0 |
|||
with: |
|||
type: 'release' |
|||
emoji: '+1, laugh, heart, hooray, rocket, eyes' |
|||
@ -0,0 +1,37 @@ |
|||
name: Issue labeled |
|||
|
|||
on: |
|||
issues: |
|||
types: [labeled] |
|||
|
|||
jobs: |
|||
reply-helper: |
|||
runs-on: ubuntu-latest |
|||
steps: |
|||
- name: help wanted |
|||
if: github.event.label.name == '❤️ help wanted' || github.event.label.name == '🤝Welcome PR' |
|||
uses: actions-cool/issues-helper@v1.11 |
|||
with: |
|||
actions: 'create-comment' |
|||
token: ${{ secrets.GITHUB_TOKEN }} |
|||
issue-number: ${{ github.event.issue.number }} |
|||
body: | |
|||
Hello @${{ github.event.issue.user.login }}. We totally like your proposal/feedback, welcome to [send us a Pull Request](https://help.github.com/en/articles/creating-a-pull-request) for it. Please provide changelog/TypeScript/documentation/test cases if needed and make sure CI passed, we will review it soon. We appreciate your effort in advance and looking forward to your contribution! |
|||
|
|||
你好 @${{ github.event.issue.user.login }},我们完全同意你的提议/反馈,欢迎直接在此仓库 [创建一个 Pull Request](https://help.github.com/en/articles/creating-a-pull-request) 来解决这个问题。请务必提供改动所需相应的 changelog、TypeScript 定义、测试用例、文档等,并确保 CI 通过,我们会尽快进行 Review,提前感谢和期待您的贡献。 |
|||
|
|||
 |
|||
|
|||
- name: Need Reproduce |
|||
if: github.event.label.name == '🤔 Need Reproduce' |
|||
uses: actions-cool/issues-helper@v1.11 |
|||
with: |
|||
actions: 'create-comment' |
|||
token: ${{ secrets.GITHUB_TOKEN }} |
|||
issue-number: ${{ github.event.issue.number }} |
|||
body: | |
|||
Hello @${{ github.event.issue.user.login }}. Please provide a online reproduction by forking this link https://codesandbox.io/ or a minimal GitHub repository. |
|||
|
|||
你好 @${{ github.event.issue.user.login }}, 我们需要你提供一个在线的重现实例以便于我们帮你排查问题。你可以通过点击 [此处](https://codesandbox.io/) 创建一个 codesandbox 或者提供一个最小化的 GitHub 仓库。 |
|||
|
|||
 |
|||
@ -0,0 +1,33 @@ |
|||
name: Node pnpm CI |
|||
|
|||
on: [push, pull_request] |
|||
|
|||
permissions: |
|||
contents: read |
|||
|
|||
jobs: |
|||
build: |
|||
runs-on: ${{ matrix.os }} |
|||
strategy: |
|||
matrix: |
|||
node_version: [16.x] |
|||
os: [ubuntu-latest, windows-latest, macOS-latest] |
|||
steps: |
|||
- uses: actions/checkout@v1 |
|||
- name: Use Node.js ${{ matrix.node_version }} |
|||
uses: actions/setup-node@v1 |
|||
with: |
|||
node-version: ${{ matrix.node_version }} |
|||
- run: echo ${{github.ref}} |
|||
- run: curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7 |
|||
- run: pnpm config set store-dir ~/.pnpm-store |
|||
- run: pnpm install --strict-peer-dependencies=false |
|||
- run: pnpm run lint |
|||
- run: pnpm run tsc |
|||
- run: pnpm run build |
|||
- run: pnpm run test |
|||
env: |
|||
CI: true |
|||
PROGRESS: none |
|||
NODE_ENV: test |
|||
NODE_OPTIONS: --max_old_space_size=4096 |
|||
@ -1,17 +0,0 @@ |
|||
on: |
|||
issue_comment: |
|||
types: [created] |
|||
name: Automatic Rebase |
|||
jobs: |
|||
rebase: |
|||
name: Rebase |
|||
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 }} |
|||
@ -1,6 +0,0 @@ |
|||
ports: |
|||
- port: 8000 |
|||
onOpen: open-preview |
|||
tasks: |
|||
- init: npm install |
|||
command: npm start |
|||
@ -0,0 +1,7 @@ |
|||
#!/bin/sh |
|||
. "$(dirname "$0")/_/husky.sh" |
|||
|
|||
# Export Git hook params |
|||
export GIT_PARAMS=$* |
|||
|
|||
npx --no-install fabric verify-commit |
|||
@ -0,0 +1,4 @@ |
|||
#!/bin/sh |
|||
. "$(dirname "$0")/_/husky.sh" |
|||
|
|||
npx --no-install lint-staged |
|||
@ -1,5 +1,21 @@ |
|||
const fabric = require('@umijs/fabric'); |
|||
|
|||
module.exports = { |
|||
...fabric.prettier, |
|||
singleQuote: true, |
|||
trailingComma: 'all', |
|||
printWidth: 100, |
|||
proseWrap: 'never', |
|||
endOfLine: 'lf', |
|||
overrides: [ |
|||
{ |
|||
files: '.prettierrc', |
|||
options: { |
|||
parser: 'json', |
|||
}, |
|||
}, |
|||
{ |
|||
files: 'document.ejs', |
|||
options: { |
|||
parser: 'html', |
|||
}, |
|||
}, |
|||
], |
|||
}; |
|||
|
|||
@ -1,5 +0,0 @@ |
|||
const fabric = require('@umijs/fabric'); |
|||
|
|||
module.exports = { |
|||
...fabric.stylelint, |
|||
}; |
|||
@ -1,14 +0,0 @@ |
|||
FROM circleci/node:latest-browsers |
|||
|
|||
WORKDIR /usr/src/app/ |
|||
USER root |
|||
COPY package.json ./ |
|||
RUN yarn |
|||
|
|||
COPY ./ ./ |
|||
|
|||
RUN npm run test:all |
|||
|
|||
RUN npm run fetch:blocks |
|||
|
|||
CMD ["npm", "run", "build"] |
|||
@ -1,12 +0,0 @@ |
|||
FROM node:latest |
|||
|
|||
WORKDIR /usr/src/app/ |
|||
|
|||
COPY package.json ./ |
|||
RUN npm install --silent --no-cache --registry=https://registry.npm.taobao.org |
|||
|
|||
COPY ./ ./ |
|||
|
|||
RUN npm run fetch:blocks |
|||
|
|||
CMD ["npm", "run", "start"] |
|||
@ -1,27 +0,0 @@ |
|||
FROM circleci/node:latest-browsers as builder |
|||
|
|||
WORKDIR /usr/src/app/ |
|||
USER root |
|||
COPY package.json ./ |
|||
RUN yarn |
|||
|
|||
COPY ./ ./ |
|||
|
|||
RUN npm run test:all |
|||
|
|||
RUN npm run fetch:blocks |
|||
|
|||
RUN npm run build |
|||
|
|||
|
|||
FROM nginx |
|||
|
|||
WORKDIR /usr/share/nginx/html/ |
|||
|
|||
COPY ./docker/nginx.conf /etc/nginx/conf.d/default.conf |
|||
|
|||
COPY --from=builder /usr/src/app/dist /usr/share/nginx/html/ |
|||
|
|||
EXPOSE 80 |
|||
|
|||
CMD ["nginx", "-g", "daemon off;"] |
|||
@ -0,0 +1,132 @@ |
|||
Idioma: 🇺🇸 | [🇨🇳](./README.zh-CN.md) | [🇷🇺](./README.ru-RU.md) | [🇹🇷](./README.tr-TR.md) | [🇯🇵](./README.ja-JP.md) | [🇫🇷](./README.fr-FR.md) | [🇵🇹](./README.pt-BR.md) | [🇸🇦](./README.ar-DZ.md) | [🇪🇸](./README.es-ES.md) |
|||
|
|||
<h1 align="center">Ant Design Pro</h1> |
|||
|
|||
<div align="center"> |
|||
|
|||
Una solución de IU listo para usar para aplicaciones empresariales como plantilla de React. |
|||
|
|||
[](https://dev.azure.com/ant-design/ant-design-pro/_build/latest?definitionId=1?branchName=master)   |
|||
|
|||
[](https://gitter.im/ant-design/pro-english?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](http://umijs.org/)  |
|||
|
|||
 |
|||
|
|||
</div> |
|||
|
|||
- Vista previa: http://preview.pro.ant.design |
|||
- Página de inicio: http://pro.ant.design |
|||
- Documentación: http://pro.ant.design/docs/getting-started |
|||
- Registro de cambios: http://pro.ant.design/docs/changelog |
|||
- Preguntas frecuentes: http://pro.ant.design/docs/faq |
|||
- Sitio espejo en China: http://ant-design-pro.gitee.io |
|||
|
|||
## ¡La versión 5.0 ya está disponible! 🎉🎉🎉 |
|||
|
|||
[Ant Design Pro 5.0.0](https://github.com/ant-design/ant-design-pro/issues/8656) |
|||
|
|||
## Reclutamiento de traductores :loudspeaker: |
|||
|
|||
Necesitamos tu ayuda: https://github.com/ant-design/ant-design-pro/issues/120 |
|||
|
|||
## Características |
|||
|
|||
- :bulb: **TypeScript**: Un lenguaje para aplicaciones JavaScript a gran escala. |
|||
- :scroll: **Bloques**: Construye páginas con plantillas de bloque. |
|||
- :gem: **Diseño elegante**: Sigue la [especificación de Ant Design](http://ant.design/). |
|||
- :triangular_ruler: **Plantillas comunes**: Plantillas típicas para aplicaciones empresariales. |
|||
- :rocket: **Desarrollo de vanguardia**: La pila de desarrollo más reciente de React/umi/dva/antd. |
|||
- :iphone: **Adaptable**: Diseñado para tamaños de pantalla variables. |
|||
- :art: **Tematización**: Tema personalizable con configuración sencilla. |
|||
- :globe_with_meridians: **Internacional**: Solución de i18n incorporada. |
|||
- :gear: **Mejores prácticas**: Flujo de trabajo sólido para mantener tu código saludable. |
|||
- :1234: **Desarrollo simulado**: Solución de desarrollo simulado fácil de usar. |
|||
- :white_check_mark: **Pruebas de interfaz de usuario**: Vuela con seguridad con pruebas de unidad y extremo a extremo. |
|||
|
|||
## Plantillas |
|||
|
|||
- **Tablero de control** |
|||
- Análisis |
|||
- Monitor |
|||
- Espacio de trabajo |
|||
- **Formulario** |
|||
- Formulario básico |
|||
- Formulario paso a paso |
|||
- Formulario avanzado |
|||
- **Lista** |
|||
- Tabla estándar |
|||
- Lista estándar |
|||
- Lista de tarjetas |
|||
- Lista de búsqueda (Proyecto/Aplicaciones/Artículo) |
|||
- **Perfil** |
|||
- Perfil simple |
|||
- Perfil avanzado |
|||
- **Cuenta** |
|||
- Centro de cuentas |
|||
- Configuración de cuentas |
|||
- **Resultado** |
|||
- Éxito |
|||
- Fallido |
|||
- **Excepción** |
|||
- 403 |
|||
- 404 |
|||
- 500 |
|||
- **Usuario** |
|||
- Iniciar sesión |
|||
- Registrarse |
|||
- Resultado del registro |
|||
|
|||
## Uso |
|||
|
|||
### Uso de bash |
|||
|
|||
Proporcionamos `pro-cli` para inicializar rápidamente la estructura del proyecto. |
|||
|
|||
```bash |
|||
# Utiliza npm |
|||
npm i @ant-design/pro-cli -g |
|||
pro create myapp |
|||
``` |
|||
Selecciona la versión de umi |
|||
|
|||
``` |
|||
🐂 ¿Usar umi@4 o umi@3 ? (Usa las teclas de flecha) |
|||
❯ umi@4 |
|||
umi@3 |
|||
|
|||
``` |
|||
> Si seleccionas la versión umi@4, los bloques completos aún no son compatibles. |
|||
|
|||
Si eliges umi@3, también puedes elegir la plantilla "pro". "Pro" es la plantilla básica, que solo proporciona el contenido básico de la operación del marco. "Complete" contiene todos los bloques, lo cual no es adecuado para el desarrollo secundario como una plantilla básica. |
|||
|
|||
```shell |
|||
? 🚀 ¿Completo o una estructura simple? (Usa las teclas de flecha) |
|||
❯ simple |
|||
complete |
|||
|
|||
``` |
|||
|
|||
Instala las dependencias: |
|||
|
|||
```shell |
|||
$ cd myapp && tyarn |
|||
// o |
|||
$ cd myapp && npm install |
|||
|
|||
``` |
|||
|
|||
## Compatibilidad con Navegadores |
|||
|
|||
Navegadores modernos. |
|||
|
|||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera | |
|||
| --- | --- | --- | --- | --- | |
|||
| Edge | Últimas 2 versiones | Últimas 2 versiones | Últimas 2 versiones | Últimas 2 versiones | |
|||
|
|||
## Contribuciones |
|||
|
|||
Cualquier tipo de contribución es bienvenida, aquí tienes algunos ejemplos de cómo puedes contribuir a este proyecto: |
|||
|
|||
- Utiliza Ant Design Pro en tu trabajo diario. |
|||
- Envía [issues](http://github.com/ant-design/ant-design-pro/issues) para reportar errores o hacer preguntas. |
|||
- Propón [pull requests](http://github.com/ant-design/ant-design-pro/pulls) para mejorar nuestro código. |
|||
@ -1,16 +0,0 @@ |
|||
// https://umijs.org/config/
|
|||
import { defineConfig } from 'umi'; |
|||
|
|||
export default defineConfig({ |
|||
plugins: [ |
|||
// https://github.com/zthxxx/react-dev-inspector
|
|||
'react-dev-inspector/plugins/umi/react-inspector', |
|||
], |
|||
// https://github.com/zthxxx/react-dev-inspector#inspector-loader-props
|
|||
inspectorConfig: { |
|||
exclude: [], |
|||
babelPlugins: [], |
|||
babelOptions: {}, |
|||
}, |
|||
webpack5: {}, |
|||
}); |
|||
@ -1,23 +1,28 @@ |
|||
import { Settings as ProSettings } from '@ant-design/pro-layout'; |
|||
import { ProLayoutProps } from '@ant-design/pro-components'; |
|||
|
|||
type DefaultSettings = Partial<ProSettings> & { |
|||
pwa: boolean; |
|||
}; |
|||
|
|||
const proSettings: DefaultSettings = { |
|||
navTheme: 'dark', |
|||
/** |
|||
* @name |
|||
*/ |
|||
const Settings: ProLayoutProps & { |
|||
pwa?: boolean; |
|||
logo?: string; |
|||
} = { |
|||
navTheme: 'light', |
|||
// 拂晓蓝
|
|||
primaryColor: '#1890ff', |
|||
layout: 'side', |
|||
colorPrimary: '#1890ff', |
|||
layout: 'mix', |
|||
contentWidth: 'Fluid', |
|||
fixedHeader: false, |
|||
fixSiderbar: true, |
|||
colorWeak: false, |
|||
title: 'Ant Design Pro', |
|||
pwa: false, |
|||
pwa: true, |
|||
logo: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg', |
|||
iconfontUrl: '', |
|||
token: { |
|||
// 参见ts声明,demo 见文档,通过token 修改样式
|
|||
//https://procomponents.ant.design/components/layout#%E9%80%9A%E8%BF%87-token-%E4%BF%AE%E6%94%B9%E6%A0%B7%E5%BC%8F
|
|||
}, |
|||
}; |
|||
|
|||
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,73 +1,63 @@ |
|||
export default [ |
|||
/** |
|||
* @name umi 的路由配置 |
|||
* @description 只支持 path,component,routes,redirect,wrappers,name,icon 的配置 |
|||
* @param path path 只支持两种占位符配置,第一种是动态参数 :id 的形式,第二种是 * 通配符,通配符只能出现路由字符串的最后。 |
|||
* @param component 配置 location 和 path 匹配后用于渲染的 React 组件路径。可以是绝对路径,也可以是相对路径,如果是相对路径,会从 src/pages 开始找起。 |
|||
* @param routes 配置子路由,通常在需要为多个路径增加 layout 组件时使用。 |
|||
* @param redirect 配置路由跳转 |
|||
* @param wrappers 配置路由组件的包装组件,通过包装组件可以为当前的路由组件组合进更多的功能。 比如,可以用于路由级别的权限校验 |
|||
* @param name 配置路由的标题,默认读取国际化文件 menu.ts 中 menu.xxxx 的值,如配置 name 为 login,则读取 menu.ts 中 menu.login 的取值作为标题 |
|||
* @param icon 配置路由的图标,取值参考 https://ant.design/components/icon-cn, 注意去除风格后缀和大小写,如想要配置图标为 <StepBackwardOutlined /> 则取值应为 stepBackward 或 StepBackward,如想要配置图标为 <UserOutlined /> 则取值应为 user 或者 User
|
|||
* @doc https://umijs.org/docs/guides/routes
|
|||
*/ |
|||
export default [ |
|||
{ |
|||
path: '/', |
|||
component: '../layouts/BlankLayout', |
|||
path: '/user', |
|||
layout: false, |
|||
routes: [ |
|||
{ |
|||
name: 'login', |
|||
path: '/user/login', |
|||
component: './User/Login', |
|||
}, |
|||
], |
|||
}, |
|||
{ |
|||
path: '/welcome', |
|||
name: 'welcome', |
|||
icon: 'smile', |
|||
component: './Welcome', |
|||
}, |
|||
{ |
|||
path: '/admin', |
|||
name: 'admin', |
|||
icon: 'crown', |
|||
access: 'canAdmin', |
|||
routes: [ |
|||
{ |
|||
path: '/user', |
|||
component: '../layouts/UserLayout', |
|||
routes: [ |
|||
{ |
|||
name: 'login', |
|||
path: '/user/login', |
|||
component: './User/login', |
|||
}, |
|||
], |
|||
path: '/admin', |
|||
redirect: '/admin/sub-page', |
|||
}, |
|||
{ |
|||
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', |
|||
}, |
|||
], |
|||
path: '/admin/sub-page', |
|||
name: 'sub-page', |
|||
component: './Admin', |
|||
}, |
|||
], |
|||
}, |
|||
{ |
|||
name: 'list.table-list', |
|||
icon: 'table', |
|||
path: '/list', |
|||
component: './TableList', |
|||
}, |
|||
{ |
|||
path: '/', |
|||
redirect: '/welcome', |
|||
}, |
|||
{ |
|||
path: '*', |
|||
layout: false, |
|||
component: './404', |
|||
}, |
|||
]; |
|||
|
|||
@ -1,14 +0,0 @@ |
|||
version: '3.5' |
|||
|
|||
services: |
|||
ant-design-pro_dev: |
|||
ports: |
|||
- 8000:8000 |
|||
build: |
|||
context: ../ |
|||
dockerfile: Dockerfile.dev |
|||
container_name: 'ant-design-pro_dev' |
|||
volumes: |
|||
- ../src:/usr/src/app/src |
|||
- ../config:/usr/src/app/config |
|||
- ../mock:/usr/src/app/mock |
|||
@ -1,21 +0,0 @@ |
|||
version: '3.5' |
|||
|
|||
services: |
|||
ant-design-pro_build: |
|||
build: ../ |
|||
container_name: 'ant-design-pro_build' |
|||
volumes: |
|||
- dist:/usr/src/app/dist |
|||
|
|||
ant-design-pro_web: |
|||
image: nginx |
|||
ports: |
|||
- 80:80 |
|||
container_name: 'ant-design-pro_web' |
|||
restart: unless-stopped |
|||
volumes: |
|||
- dist:/usr/share/nginx/html:ro |
|||
- ./nginx.conf:/etc/nginx/conf.d/default.conf |
|||
|
|||
volumes: |
|||
dist: |
|||
@ -1,21 +0,0 @@ |
|||
server { |
|||
listen 80; |
|||
# gzip config |
|||
gzip on; |
|||
gzip_min_length 1k; |
|||
gzip_comp_level 9; |
|||
gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml; |
|||
gzip_vary on; |
|||
gzip_disable "MSIE [1-6]\."; |
|||
|
|||
root /usr/share/nginx/html; |
|||
include /etc/nginx/mime.types; |
|||
location / { |
|||
try_files $uri $uri/ /index.html; |
|||
} |
|||
location /api { |
|||
proxy_pass https://proapi.azurewebsites.net; |
|||
proxy_set_header X-Forwarded-Proto $scheme; |
|||
proxy_set_header X-Real-IP $remote_addr; |
|||
} |
|||
} |
|||
@ -1,9 +0,0 @@ |
|||
module.exports = { |
|||
testURL: 'http://localhost:8000', |
|||
testEnvironment: './tests/PuppeteerEnvironment', |
|||
verbose: false, |
|||
globals: { |
|||
ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: false, |
|||
localStorage: null, |
|||
}, |
|||
}; |
|||
@ -0,0 +1,23 @@ |
|||
import { configUmiAlias, createConfig } from '@umijs/max/test'; |
|||
|
|||
export default async () => { |
|||
const config = await configUmiAlias({ |
|||
...createConfig({ |
|||
target: 'browser', |
|||
}), |
|||
}); |
|||
console.log(JSON.stringify(config)); |
|||
|
|||
return { |
|||
...config, |
|||
testEnvironmentOptions: { |
|||
...(config?.testEnvironmentOptions || {}), |
|||
url: 'http://localhost:8000', |
|||
}, |
|||
setupFiles: [...(config.setupFiles || []), './tests/setupTests.jsx'], |
|||
globals: { |
|||
...config.globals, |
|||
localStorage: null, |
|||
}, |
|||
}; |
|||
}; |
|||
@ -0,0 +1,324 @@ |
|||
module.exports = { |
|||
'GET /api/currentUser': { |
|||
data: { |
|||
name: 'Serati Ma', |
|||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/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', |
|||
geographic: { |
|||
province: { label: '浙江省', key: '330000' }, |
|||
city: { label: '杭州市', key: '330100' }, |
|||
}, |
|||
address: '西湖区工专路 77 号', |
|||
phone: '0752-268888888', |
|||
}, |
|||
}, |
|||
'GET /api/rule': { |
|||
data: [ |
|||
{ |
|||
key: 99, |
|||
disabled: false, |
|||
href: 'https://ant.design', |
|||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
|||
name: 'TradeCode 99', |
|||
owner: '曲丽丽', |
|||
desc: '这是一段描述', |
|||
callNo: 503, |
|||
status: '0', |
|||
updatedAt: '2022-12-06T05:00:57.040Z', |
|||
createdAt: '2022-12-06T05:00:57.040Z', |
|||
progress: 81, |
|||
}, |
|||
{ |
|||
key: 98, |
|||
disabled: false, |
|||
href: 'https://ant.design', |
|||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
|||
name: 'TradeCode 98', |
|||
owner: '曲丽丽', |
|||
desc: '这是一段描述', |
|||
callNo: 164, |
|||
status: '0', |
|||
updatedAt: '2022-12-06T05:00:57.040Z', |
|||
createdAt: '2022-12-06T05:00:57.040Z', |
|||
progress: 12, |
|||
}, |
|||
{ |
|||
key: 97, |
|||
disabled: false, |
|||
href: 'https://ant.design', |
|||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
|||
name: 'TradeCode 97', |
|||
owner: '曲丽丽', |
|||
desc: '这是一段描述', |
|||
callNo: 174, |
|||
status: '1', |
|||
updatedAt: '2022-12-06T05:00:57.040Z', |
|||
createdAt: '2022-12-06T05:00:57.040Z', |
|||
progress: 81, |
|||
}, |
|||
{ |
|||
key: 96, |
|||
disabled: true, |
|||
href: 'https://ant.design', |
|||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
|||
name: 'TradeCode 96', |
|||
owner: '曲丽丽', |
|||
desc: '这是一段描述', |
|||
callNo: 914, |
|||
status: '0', |
|||
updatedAt: '2022-12-06T05:00:57.040Z', |
|||
createdAt: '2022-12-06T05:00:57.040Z', |
|||
progress: 7, |
|||
}, |
|||
{ |
|||
key: 95, |
|||
disabled: false, |
|||
href: 'https://ant.design', |
|||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
|||
name: 'TradeCode 95', |
|||
owner: '曲丽丽', |
|||
desc: '这是一段描述', |
|||
callNo: 698, |
|||
status: '2', |
|||
updatedAt: '2022-12-06T05:00:57.040Z', |
|||
createdAt: '2022-12-06T05:00:57.040Z', |
|||
progress: 82, |
|||
}, |
|||
{ |
|||
key: 94, |
|||
disabled: false, |
|||
href: 'https://ant.design', |
|||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
|||
name: 'TradeCode 94', |
|||
owner: '曲丽丽', |
|||
desc: '这是一段描述', |
|||
callNo: 488, |
|||
status: '1', |
|||
updatedAt: '2022-12-06T05:00:57.040Z', |
|||
createdAt: '2022-12-06T05:00:57.040Z', |
|||
progress: 14, |
|||
}, |
|||
{ |
|||
key: 93, |
|||
disabled: false, |
|||
href: 'https://ant.design', |
|||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
|||
name: 'TradeCode 93', |
|||
owner: '曲丽丽', |
|||
desc: '这是一段描述', |
|||
callNo: 580, |
|||
status: '2', |
|||
updatedAt: '2022-12-06T05:00:57.040Z', |
|||
createdAt: '2022-12-06T05:00:57.040Z', |
|||
progress: 77, |
|||
}, |
|||
{ |
|||
key: 92, |
|||
disabled: false, |
|||
href: 'https://ant.design', |
|||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
|||
name: 'TradeCode 92', |
|||
owner: '曲丽丽', |
|||
desc: '这是一段描述', |
|||
callNo: 244, |
|||
status: '3', |
|||
updatedAt: '2022-12-06T05:00:57.040Z', |
|||
createdAt: '2022-12-06T05:00:57.040Z', |
|||
progress: 58, |
|||
}, |
|||
{ |
|||
key: 91, |
|||
disabled: false, |
|||
href: 'https://ant.design', |
|||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
|||
name: 'TradeCode 91', |
|||
owner: '曲丽丽', |
|||
desc: '这是一段描述', |
|||
callNo: 959, |
|||
status: '0', |
|||
updatedAt: '2022-12-06T05:00:57.040Z', |
|||
createdAt: '2022-12-06T05:00:57.040Z', |
|||
progress: 66, |
|||
}, |
|||
{ |
|||
key: 90, |
|||
disabled: true, |
|||
href: 'https://ant.design', |
|||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
|||
name: 'TradeCode 90', |
|||
owner: '曲丽丽', |
|||
desc: '这是一段描述', |
|||
callNo: 958, |
|||
status: '0', |
|||
updatedAt: '2022-12-06T05:00:57.040Z', |
|||
createdAt: '2022-12-06T05:00:57.040Z', |
|||
progress: 72, |
|||
}, |
|||
{ |
|||
key: 89, |
|||
disabled: false, |
|||
href: 'https://ant.design', |
|||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
|||
name: 'TradeCode 89', |
|||
owner: '曲丽丽', |
|||
desc: '这是一段描述', |
|||
callNo: 301, |
|||
status: '2', |
|||
updatedAt: '2022-12-06T05:00:57.040Z', |
|||
createdAt: '2022-12-06T05:00:57.040Z', |
|||
progress: 2, |
|||
}, |
|||
{ |
|||
key: 88, |
|||
disabled: false, |
|||
href: 'https://ant.design', |
|||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
|||
name: 'TradeCode 88', |
|||
owner: '曲丽丽', |
|||
desc: '这是一段描述', |
|||
callNo: 277, |
|||
status: '1', |
|||
updatedAt: '2022-12-06T05:00:57.040Z', |
|||
createdAt: '2022-12-06T05:00:57.040Z', |
|||
progress: 12, |
|||
}, |
|||
{ |
|||
key: 87, |
|||
disabled: false, |
|||
href: 'https://ant.design', |
|||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
|||
name: 'TradeCode 87', |
|||
owner: '曲丽丽', |
|||
desc: '这是一段描述', |
|||
callNo: 810, |
|||
status: '1', |
|||
updatedAt: '2022-12-06T05:00:57.040Z', |
|||
createdAt: '2022-12-06T05:00:57.040Z', |
|||
progress: 82, |
|||
}, |
|||
{ |
|||
key: 86, |
|||
disabled: false, |
|||
href: 'https://ant.design', |
|||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
|||
name: 'TradeCode 86', |
|||
owner: '曲丽丽', |
|||
desc: '这是一段描述', |
|||
callNo: 780, |
|||
status: '3', |
|||
updatedAt: '2022-12-06T05:00:57.040Z', |
|||
createdAt: '2022-12-06T05:00:57.040Z', |
|||
progress: 22, |
|||
}, |
|||
{ |
|||
key: 85, |
|||
disabled: false, |
|||
href: 'https://ant.design', |
|||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
|||
name: 'TradeCode 85', |
|||
owner: '曲丽丽', |
|||
desc: '这是一段描述', |
|||
callNo: 705, |
|||
status: '3', |
|||
updatedAt: '2022-12-06T05:00:57.040Z', |
|||
createdAt: '2022-12-06T05:00:57.040Z', |
|||
progress: 12, |
|||
}, |
|||
{ |
|||
key: 84, |
|||
disabled: true, |
|||
href: 'https://ant.design', |
|||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
|||
name: 'TradeCode 84', |
|||
owner: '曲丽丽', |
|||
desc: '这是一段描述', |
|||
callNo: 203, |
|||
status: '0', |
|||
updatedAt: '2022-12-06T05:00:57.040Z', |
|||
createdAt: '2022-12-06T05:00:57.040Z', |
|||
progress: 79, |
|||
}, |
|||
{ |
|||
key: 83, |
|||
disabled: false, |
|||
href: 'https://ant.design', |
|||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
|||
name: 'TradeCode 83', |
|||
owner: '曲丽丽', |
|||
desc: '这是一段描述', |
|||
callNo: 491, |
|||
status: '2', |
|||
updatedAt: '2022-12-06T05:00:57.040Z', |
|||
createdAt: '2022-12-06T05:00:57.040Z', |
|||
progress: 59, |
|||
}, |
|||
{ |
|||
key: 82, |
|||
disabled: false, |
|||
href: 'https://ant.design', |
|||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
|||
name: 'TradeCode 82', |
|||
owner: '曲丽丽', |
|||
desc: '这是一段描述', |
|||
callNo: 73, |
|||
status: '0', |
|||
updatedAt: '2022-12-06T05:00:57.040Z', |
|||
createdAt: '2022-12-06T05:00:57.040Z', |
|||
progress: 100, |
|||
}, |
|||
{ |
|||
key: 81, |
|||
disabled: false, |
|||
href: 'https://ant.design', |
|||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
|||
name: 'TradeCode 81', |
|||
owner: '曲丽丽', |
|||
desc: '这是一段描述', |
|||
callNo: 406, |
|||
status: '3', |
|||
updatedAt: '2022-12-06T05:00:57.040Z', |
|||
createdAt: '2022-12-06T05:00:57.040Z', |
|||
progress: 61, |
|||
}, |
|||
{ |
|||
key: 80, |
|||
disabled: false, |
|||
href: 'https://ant.design', |
|||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
|||
name: 'TradeCode 80', |
|||
owner: '曲丽丽', |
|||
desc: '这是一段描述', |
|||
callNo: 112, |
|||
status: '2', |
|||
updatedAt: '2022-12-06T05:00:57.040Z', |
|||
createdAt: '2022-12-06T05:00:57.040Z', |
|||
progress: 20, |
|||
}, |
|||
], |
|||
total: 100, |
|||
success: true, |
|||
pageSize: 20, |
|||
current: 1, |
|||
}, |
|||
'POST /api/login/outLogin': { data: {}, success: true }, |
|||
'POST /api/login/account': { |
|||
status: 'ok', |
|||
type: 'account', |
|||
currentAuthority: 'admin', |
|||
}, |
|||
}; |
|||
File diff suppressed because it is too large
|
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/docs/max/access#access
|
|||
* */ |
|||
export default function access(initialState: { currentUser?: API.CurrentUser } | undefined) { |
|||
const { currentUser } = initialState ?? {}; |
|||
return { |
|||
canAdmin: currentUser && currentUser.access === 'admin', |
|||
}; |
|||
} |
|||
@ -0,0 +1,136 @@ |
|||
import { Footer, Question, SelectLang, AvatarDropdown, AvatarName } from '@/components'; |
|||
import { LinkOutlined } from '@ant-design/icons'; |
|||
import type { Settings as LayoutSettings } from '@ant-design/pro-components'; |
|||
import { SettingDrawer } from '@ant-design/pro-components'; |
|||
import type { RunTimeLayoutConfig } from '@umijs/max'; |
|||
import { history, Link } from '@umijs/max'; |
|||
import defaultSettings from '../config/defaultSettings'; |
|||
import { errorConfig } from './requestErrorConfig'; |
|||
import { currentUser as queryCurrentUser } from '@/services/ant-design-pro/api'; |
|||
import React from 'react'; |
|||
const isDev = process.env.NODE_ENV === 'development'; |
|||
const loginPath = '/user/login'; |
|||
|
|||
/** |
|||
* @see https://umijs.org/zh-CN/plugins/plugin-initial-state
|
|||
* */ |
|||
export async function getInitialState(): Promise<{ |
|||
settings?: Partial<LayoutSettings>; |
|||
currentUser?: API.CurrentUser; |
|||
loading?: boolean; |
|||
fetchUserInfo?: () => Promise<API.CurrentUser | undefined>; |
|||
}> { |
|||
const fetchUserInfo = async () => { |
|||
try { |
|||
const msg = await queryCurrentUser({ |
|||
skipErrorHandler: true, |
|||
}); |
|||
return msg.data; |
|||
} catch (error) { |
|||
history.push(loginPath); |
|||
} |
|||
return undefined; |
|||
}; |
|||
// 如果不是登录页面,执行
|
|||
const { location } = history; |
|||
if (location.pathname !== loginPath) { |
|||
const currentUser = await fetchUserInfo(); |
|||
return { |
|||
fetchUserInfo, |
|||
currentUser, |
|||
settings: defaultSettings as Partial<LayoutSettings>, |
|||
}; |
|||
} |
|||
return { |
|||
fetchUserInfo, |
|||
settings: defaultSettings as Partial<LayoutSettings>, |
|||
}; |
|||
} |
|||
|
|||
// ProLayout 支持的api https://procomponents.ant.design/components/layout
|
|||
export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) => { |
|||
return { |
|||
actionsRender: () => [<Question key="doc" />, <SelectLang key="SelectLang" />], |
|||
avatarProps: { |
|||
src: initialState?.currentUser?.avatar, |
|||
title: <AvatarName />, |
|||
render: (_, avatarChildren) => { |
|||
return <AvatarDropdown>{avatarChildren}</AvatarDropdown>; |
|||
}, |
|||
}, |
|||
waterMarkProps: { |
|||
content: initialState?.currentUser?.name, |
|||
}, |
|||
footerRender: () => <Footer />, |
|||
onPageChange: () => { |
|||
const { location } = history; |
|||
// 如果没有登录,重定向到 login
|
|||
if (!initialState?.currentUser && location.pathname !== loginPath) { |
|||
history.push(loginPath); |
|||
} |
|||
}, |
|||
bgLayoutImgList: [ |
|||
{ |
|||
src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/D2LWSqNny4sAAAAAAAAAAAAAFl94AQBr', |
|||
left: 85, |
|||
bottom: 100, |
|||
height: '303px', |
|||
}, |
|||
{ |
|||
src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/C2TWRpJpiC0AAAAAAAAAAAAAFl94AQBr', |
|||
bottom: -68, |
|||
right: -45, |
|||
height: '303px', |
|||
}, |
|||
{ |
|||
src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/F6vSTbj8KpYAAAAAAAAAAAAAFl94AQBr', |
|||
bottom: 0, |
|||
left: 0, |
|||
width: '331px', |
|||
}, |
|||
], |
|||
links: isDev |
|||
? [ |
|||
<Link key="openapi" to="/umi/plugin/openapi" target="_blank"> |
|||
<LinkOutlined /> |
|||
<span>OpenAPI 文档</span> |
|||
</Link>, |
|||
] |
|||
: [], |
|||
menuHeaderRender: undefined, |
|||
// 自定义 403 页面
|
|||
// unAccessible: <div>unAccessible</div>,
|
|||
// 增加一个 loading 的状态
|
|||
childrenRender: (children) => { |
|||
// if (initialState?.loading) return <PageLoading />;
|
|||
return ( |
|||
<> |
|||
{children} |
|||
{isDev && ( |
|||
<SettingDrawer |
|||
disableUrlParams |
|||
enableDarkTheme |
|||
settings={initialState?.settings} |
|||
onSettingChange={(settings) => { |
|||
setInitialState((preInitialState) => ({ |
|||
...preInitialState, |
|||
settings, |
|||
})); |
|||
}} |
|||
/> |
|||
)} |
|||
</> |
|||
); |
|||
}, |
|||
...initialState?.settings, |
|||
}; |
|||
}; |
|||
|
|||
/** |
|||
* @name request 配置,可以配置错误处理 |
|||
* 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。 |
|||
* @doc https://umijs.org/docs/max/request#配置
|
|||
*/ |
|||
export const request = { |
|||
...errorConfig, |
|||
}; |
|||
@ -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,35 @@ |
|||
import { GithubOutlined } from '@ant-design/icons'; |
|||
import { DefaultFooter } from '@ant-design/pro-components'; |
|||
import React from 'react'; |
|||
|
|||
const Footer: React.FC = () => { |
|||
return ( |
|||
<DefaultFooter |
|||
style={{ |
|||
background: 'none', |
|||
}} |
|||
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, |
|||
}, |
|||
]} |
|||
/> |
|||
); |
|||
}; |
|||
|
|||
export default Footer; |
|||
@ -1,93 +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; |
|||
domEvent: React.MouseEvent<HTMLElement>; |
|||
}) => { |
|||
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,82 +0,0 @@ |
|||
@import '~antd/es/style/themes/default.less'; |
|||
|
|||
@pro-header-hover-bg: rgba(0, 0, 0, 0.025); |
|||
|
|||
.menu { |
|||
:global(.anticon) { |
|||
margin-right: 8px; |
|||
} |
|||
:global(.ant-dropdown-menu-item) { |
|||
min-width: 160px; |
|||
} |
|||
} |
|||
|
|||
.right { |
|||
display: flex; |
|||
float: right; |
|||
height: 48px; |
|||
margin-left: auto; |
|||
overflow: hidden; |
|||
.action { |
|||
display: flex; |
|||
align-items: center; |
|||
height: 100%; |
|||
padding: 0 12px; |
|||
cursor: pointer; |
|||
transition: all 0.3s; |
|||
> span { |
|||
vertical-align: middle; |
|||
} |
|||
&:hover { |
|||
background: @pro-header-hover-bg; |
|||
} |
|||
&:global(.opened) { |
|||
background: @pro-header-hover-bg; |
|||
} |
|||
} |
|||
.search { |
|||
padding: 0 12px; |
|||
&:hover { |
|||
background: transparent; |
|||
} |
|||
} |
|||
.account { |
|||
.avatar { |
|||
margin: ~'calc((@{layout-header-height} - 24px) / 2)' 0; |
|||
margin-right: 8px; |
|||
color: @primary-color; |
|||
vertical-align: top; |
|||
background: rgba(255, 255, 255, 0.85); |
|||
} |
|||
} |
|||
} |
|||
|
|||
.dark { |
|||
.action { |
|||
color: rgba(255, 255, 255, 0.85); |
|||
> span { |
|||
color: rgba(255, 255, 255, 0.85); |
|||
} |
|||
&: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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,16 +0,0 @@ |
|||
@import '~antd/es/style/themes/default.less'; |
|||
|
|||
.container > * { |
|||
background-color: @popover-bg; |
|||
border-radius: 4px; |
|||
box-shadow: @shadow-1-down; |
|||
} |
|||
|
|||
@media screen and (max-width: @screen-xs) { |
|||
.container { |
|||
width: 100% !important; |
|||
} |
|||
.container > * { |
|||
border-radius: 0 !important; |
|||
} |
|||
} |
|||
@ -1,17 +1,27 @@ |
|||
import type { DropDownProps } from 'antd/es/dropdown'; |
|||
import { Dropdown } from 'antd'; |
|||
import type { DropDownProps } from 'antd/es/dropdown'; |
|||
import React from 'react'; |
|||
import { createStyles } from 'antd-style'; |
|||
import classNames from 'classnames'; |
|||
import styles from './index.less'; |
|||
|
|||
const useStyles = createStyles(({ token }) => { |
|||
return { |
|||
dropdown: { |
|||
[`@media screen and (max-width: ${token.screenXS}px)`]: { |
|||
width: '100%', |
|||
}, |
|||
}, |
|||
}; |
|||
}); |
|||
|
|||
export type HeaderDropdownProps = { |
|||
overlayClassName?: string; |
|||
overlay: React.ReactNode | (() => React.ReactNode) | any; |
|||
placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight' | 'bottomCenter'; |
|||
} & Omit<DropDownProps, 'overlay'>; |
|||
|
|||
const HeaderDropdown: React.FC<HeaderDropdownProps> = ({ overlayClassName: cls, ...restProps }) => ( |
|||
<Dropdown overlayClassName={classNames(styles.container, cls)} {...restProps} /> |
|||
); |
|||
const HeaderDropdown: React.FC<HeaderDropdownProps> = ({ overlayClassName: cls, ...restProps }) => { |
|||
const { styles } = useStyles(); |
|||
return <Dropdown overlayClassName={classNames(styles.dropdown, cls)} {...restProps} />; |
|||
}; |
|||
|
|||
export default HeaderDropdown; |
|||
|
|||
@ -1,30 +0,0 @@ |
|||
@import '~antd/es/style/themes/default.less'; |
|||
|
|||
.headerSearch { |
|||
.input { |
|||
width: 0; |
|||
min-width: 0; |
|||
overflow: hidden; |
|||
background: transparent; |
|||
border-radius: 0; |
|||
transition: width 0.3s, margin-left 0.3s; |
|||
:global(.ant-select-selection) { |
|||
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; |
|||
} |
|||
} |
|||
} |
|||
@ -1,105 +0,0 @@ |
|||
import { SearchOutlined } from '@ant-design/icons'; |
|||
import { AutoComplete, Input } from 'antd'; |
|||
import useMergedState from 'rc-util/es/hooks/useMergedState'; |
|||
import type { AutoCompleteProps } from 'antd/es/auto-complete'; |
|||
import React, { useRef } from 'react'; |
|||
|
|||
import classNames from 'classnames'; |
|||
import styles from './index.less'; |
|||
|
|||
export type HeaderSearchProps = { |
|||
onSearch?: (value?: string) => void; |
|||
onChange?: (value?: string) => void; |
|||
onVisibleChange?: (b: boolean) => void; |
|||
className?: string; |
|||
placeholder?: string; |
|||
options: AutoCompleteProps['options']; |
|||
defaultOpen?: boolean; |
|||
open?: boolean; |
|||
defaultValue?: string; |
|||
value?: string; |
|||
}; |
|||
|
|||
const HeaderSearch: React.FC<HeaderSearchProps> = (props) => { |
|||
const { |
|||
className, |
|||
defaultValue, |
|||
onVisibleChange, |
|||
placeholder, |
|||
open, |
|||
defaultOpen, |
|||
...restProps |
|||
} = props; |
|||
|
|||
const inputRef = useRef<Input | null>(null); |
|||
|
|||
const [value, setValue] = useMergedState<string | undefined>(defaultValue, { |
|||
value: props.value, |
|||
onChange: props.onChange, |
|||
}); |
|||
|
|||
const [searchMode, setSearchMode] = useMergedState(defaultOpen ?? false, { |
|||
value: props.open, |
|||
onChange: onVisibleChange, |
|||
}); |
|||
|
|||
const inputClass = classNames(styles.input, { |
|||
[styles.show]: searchMode, |
|||
}); |
|||
|
|||
return ( |
|||
<div |
|||
className={classNames(className, styles.headerSearch)} |
|||
onClick={() => { |
|||
setSearchMode(true); |
|||
if (searchMode && inputRef.current) { |
|||
inputRef.current.focus(); |
|||
} |
|||
}} |
|||
onTransitionEnd={({ propertyName }) => { |
|||
if (propertyName === 'width' && !searchMode) { |
|||
if (onVisibleChange) { |
|||
onVisibleChange(searchMode); |
|||
} |
|||
} |
|||
}} |
|||
> |
|||
<SearchOutlined |
|||
key="Icon" |
|||
style={{ |
|||
cursor: 'pointer', |
|||
}} |
|||
/> |
|||
<AutoComplete |
|||
key="AutoComplete" |
|||
className={inputClass} |
|||
value={value} |
|||
style={{ |
|||
height: 28, |
|||
marginTop: -6, |
|||
}} |
|||
options={restProps.options} |
|||
onChange={setValue} |
|||
> |
|||
<Input |
|||
ref={inputRef} |
|||
defaultValue={defaultValue} |
|||
aria-label={placeholder} |
|||
placeholder={placeholder} |
|||
onKeyDown={(e) => { |
|||
if (e.key === 'Enter') { |
|||
if (restProps.onSearch) { |
|||
restProps.onSearch(value); |
|||
} |
|||
} |
|||
}} |
|||
onBlur={() => { |
|||
setSearchMode(false); |
|||
}} |
|||
/> |
|||
</AutoComplete> |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
export default HeaderSearch; |
|||
@ -1,103 +0,0 @@ |
|||
@import '~antd/es/style/themes/default.less'; |
|||
|
|||
.list { |
|||
max-height: 400px; |
|||
overflow: auto; |
|||
&::-webkit-scrollbar { |
|||
display: none; |
|||
} |
|||
.item { |
|||
padding-right: 24px; |
|||
padding-left: 24px; |
|||
overflow: hidden; |
|||
cursor: pointer; |
|||
transition: all 0.3s; |
|||
|
|||
.meta { |
|||
width: 100%; |
|||
} |
|||
|
|||
.avatar { |
|||
margin-top: 4px; |
|||
background: @component-background; |
|||
} |
|||
.iconElement { |
|||
font-size: 32px; |
|||
} |
|||
|
|||
&.read { |
|||
opacity: 0.4; |
|||
} |
|||
&:last-child { |
|||
border-bottom: 0; |
|||
} |
|||
&:hover { |
|||
background: @primary-1; |
|||
} |
|||
.title { |
|||
margin-bottom: 8px; |
|||
font-weight: normal; |
|||
} |
|||
.description { |
|||
font-size: 12px; |
|||
line-height: @line-height-base; |
|||
} |
|||
.datetime { |
|||
margin-top: 4px; |
|||
font-size: 12px; |
|||
line-height: @line-height-base; |
|||
} |
|||
.extra { |
|||
float: right; |
|||
margin-top: -1.5px; |
|||
margin-right: 0; |
|||
color: @text-color-secondary; |
|||
font-weight: normal; |
|||
} |
|||
} |
|||
.loadMore { |
|||
padding: 8px 0; |
|||
color: @primary-6; |
|||
text-align: center; |
|||
cursor: pointer; |
|||
&.loadedAll { |
|||
color: rgba(0, 0, 0, 0.25); |
|||
cursor: unset; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.notFound { |
|||
padding: 73px 0 88px; |
|||
color: @text-color-secondary; |
|||
text-align: center; |
|||
img { |
|||
display: inline-block; |
|||
height: 76px; |
|||
margin-bottom: 16px; |
|||
} |
|||
} |
|||
|
|||
.bottomBar { |
|||
height: 46px; |
|||
color: @text-color; |
|||
line-height: 46px; |
|||
text-align: center; |
|||
border-top: 1px solid @border-color-split; |
|||
border-radius: 0 0 @border-radius-base @border-radius-base; |
|||
transition: all 0.3s; |
|||
div { |
|||
display: inline-block; |
|||
width: 50%; |
|||
cursor: pointer; |
|||
transition: all 0.3s; |
|||
user-select: none; |
|||
|
|||
&:only-child { |
|||
width: 100%; |
|||
} |
|||
&:not(:only-child):last-child { |
|||
border-left: 1px solid @border-color-split; |
|||
} |
|||
} |
|||
} |
|||
@ -1,116 +0,0 @@ |
|||
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; |
|||
onClear?: () => void; |
|||
emptyText?: string; |
|||
clearText?: string; |
|||
viewMoreText?: string; |
|||
list: NoticeIconData[]; |
|||
onViewMore?: (e: any) => void; |
|||
}; |
|||
const NoticeList: React.SFC<NoticeIconTabProps> = ({ |
|||
data = [], |
|||
onClick, |
|||
onClear, |
|||
title, |
|||
onViewMore, |
|||
emptyText, |
|||
showClear = true, |
|||
clearText, |
|||
viewMoreText, |
|||
showViewMore = false, |
|||
}) => { |
|||
if (!data || data.length === 0) { |
|||
return ( |
|||
<div className={styles.notFound}> |
|||
<img |
|||
src="https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg" |
|||
alt="not found" |
|||
/> |
|||
<div>{emptyText}</div> |
|||
</div> |
|||
); |
|||
} |
|||
return ( |
|||
<div> |
|||
<List<NoticeIconData> |
|||
className={styles.list} |
|||
dataSource={data} |
|||
renderItem={(item, i) => { |
|||
const itemCls = classNames(styles.item, { |
|||
[styles.read]: item.read, |
|||
}); |
|||
// eslint-disable-next-line no-nested-ternary
|
|||
const leftIcon = item.avatar ? ( |
|||
typeof item.avatar === 'string' ? ( |
|||
<Avatar className={styles.avatar} src={item.avatar} /> |
|||
) : ( |
|||
<span className={styles.iconElement}>{item.avatar}</span> |
|||
) |
|||
) : null; |
|||
|
|||
return ( |
|||
<List.Item |
|||
className={itemCls} |
|||
key={item.key || i} |
|||
onClick={() => { |
|||
onClick?.(item); |
|||
}} |
|||
> |
|||
<List.Item.Meta |
|||
className={styles.meta} |
|||
avatar={leftIcon} |
|||
title={ |
|||
<div className={styles.title}> |
|||
{item.title} |
|||
<div className={styles.extra}>{item.extra}</div> |
|||
</div> |
|||
} |
|||
description={ |
|||
<div> |
|||
<div className={styles.description}>{item.description}</div> |
|||
<div className={styles.datetime}>{item.datetime}</div> |
|||
</div> |
|||
} |
|||
/> |
|||
</List.Item> |
|||
); |
|||
}} |
|||
/> |
|||
<div className={styles.bottomBar}> |
|||
{showClear ? ( |
|||
<div onClick={onClear}> |
|||
{clearText} {title} |
|||
</div> |
|||
) : null} |
|||
{showViewMore ? ( |
|||
<div |
|||
onClick={(e) => { |
|||
if (onViewMore) { |
|||
onViewMore(e); |
|||
} |
|||
}} |
|||
> |
|||
{viewMoreText} |
|||
</div> |
|||
) : null} |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
export default NoticeList; |
|||
@ -1,35 +0,0 @@ |
|||
@import '~antd/es/style/themes/default.less'; |
|||
|
|||
.popover { |
|||
position: relative; |
|||
width: 336px; |
|||
} |
|||
|
|||
.noticeButton { |
|||
display: inline-block; |
|||
cursor: pointer; |
|||
transition: all 0.3s; |
|||
} |
|||
.icon { |
|||
padding: 4px; |
|||
vertical-align: middle; |
|||
} |
|||
|
|||
.badge { |
|||
font-size: 16px; |
|||
} |
|||
|
|||
.tabs { |
|||
:global { |
|||
.ant-tabs-nav-list { |
|||
margin: auto; |
|||
} |
|||
|
|||
.ant-tabs-nav-scroll { |
|||
text-align: center; |
|||
} |
|||
.ant-tabs-bar { |
|||
margin-bottom: 0; |
|||
} |
|||
} |
|||
} |
|||
@ -1,142 +0,0 @@ |
|||
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 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 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 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 |
|||
{...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> |
|||
); |
|||
}; |
|||
|
|||
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; |
|||
@ -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,138 @@ |
|||
import { outLogin } from '@/services/ant-design-pro/api'; |
|||
import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons'; |
|||
import { history, useModel } from '@umijs/max'; |
|||
import { Spin } from 'antd'; |
|||
import { createStyles } from 'antd-style'; |
|||
import { stringify } from 'querystring'; |
|||
import type { MenuInfo } from 'rc-menu/lib/interface'; |
|||
import React, { useCallback } from 'react'; |
|||
import { flushSync } from 'react-dom'; |
|||
import HeaderDropdown from '../HeaderDropdown'; |
|||
|
|||
export type GlobalHeaderRightProps = { |
|||
menu?: boolean; |
|||
children?: React.ReactNode; |
|||
}; |
|||
|
|||
export const AvatarName = () => { |
|||
const { initialState } = useModel('@@initialState'); |
|||
const { currentUser } = initialState || {}; |
|||
return <span className="anticon">{currentUser?.name}</span>; |
|||
}; |
|||
|
|||
const useStyles = createStyles(({ token }) => { |
|||
return { |
|||
action: { |
|||
display: 'flex', |
|||
height: '48px', |
|||
marginLeft: 'auto', |
|||
overflow: 'hidden', |
|||
alignItems: 'center', |
|||
padding: '0 8px', |
|||
cursor: 'pointer', |
|||
borderRadius: token.borderRadius, |
|||
'&:hover': { |
|||
backgroundColor: token.colorBgTextHover, |
|||
}, |
|||
}, |
|||
}; |
|||
}); |
|||
|
|||
export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu, children }) => { |
|||
/** |
|||
* 退出登录,并且将当前的 url 保存 |
|||
*/ |
|||
const loginOut = async () => { |
|||
await outLogin(); |
|||
const { search, pathname } = window.location; |
|||
const urlParams = new URL(window.location.href).searchParams; |
|||
/** 此方法会跳转到 redirect 参数所在的位置 */ |
|||
const redirect = urlParams.get('redirect'); |
|||
// Note: There may be security issues, please note
|
|||
if (window.location.pathname !== '/user/login' && !redirect) { |
|||
history.replace({ |
|||
pathname: '/user/login', |
|||
search: stringify({ |
|||
redirect: pathname + search, |
|||
}), |
|||
}); |
|||
} |
|||
}; |
|||
const { styles } = useStyles(); |
|||
|
|||
const { initialState, setInitialState } = useModel('@@initialState'); |
|||
|
|||
const onMenuClick = useCallback( |
|||
(event: MenuInfo) => { |
|||
const { key } = event; |
|||
if (key === 'logout') { |
|||
flushSync(() => { |
|||
setInitialState((s) => ({ ...s, currentUser: undefined })); |
|||
}); |
|||
loginOut(); |
|||
return; |
|||
} |
|||
history.push(`/account/${key}`); |
|||
}, |
|||
[setInitialState], |
|||
); |
|||
|
|||
const loading = ( |
|||
<span className={styles.action}> |
|||
<Spin |
|||
size="small" |
|||
style={{ |
|||
marginLeft: 8, |
|||
marginRight: 8, |
|||
}} |
|||
/> |
|||
</span> |
|||
); |
|||
|
|||
if (!initialState) { |
|||
return loading; |
|||
} |
|||
|
|||
const { currentUser } = initialState; |
|||
|
|||
if (!currentUser || !currentUser.name) { |
|||
return loading; |
|||
} |
|||
|
|||
const menuItems = [ |
|||
...(menu |
|||
? [ |
|||
{ |
|||
key: 'center', |
|||
icon: <UserOutlined />, |
|||
label: '个人中心', |
|||
}, |
|||
{ |
|||
key: 'settings', |
|||
icon: <SettingOutlined />, |
|||
label: '个人设置', |
|||
}, |
|||
{ |
|||
type: 'divider' as const, |
|||
}, |
|||
] |
|||
: []), |
|||
{ |
|||
key: 'logout', |
|||
icon: <LogoutOutlined />, |
|||
label: '退出登录', |
|||
}, |
|||
]; |
|||
|
|||
return ( |
|||
<HeaderDropdown |
|||
menu={{ |
|||
selectedKeys: [], |
|||
onClick: onMenuClick, |
|||
items: menuItems, |
|||
}} |
|||
> |
|||
{children} |
|||
</HeaderDropdown> |
|||
); |
|||
}; |
|||
@ -0,0 +1,31 @@ |
|||
import { QuestionCircleOutlined } from '@ant-design/icons'; |
|||
import { SelectLang as UmiSelectLang } from '@umijs/max'; |
|||
import React from 'react'; |
|||
|
|||
export type SiderTheme = 'light' | 'dark'; |
|||
|
|||
export const SelectLang = () => { |
|||
return ( |
|||
<UmiSelectLang |
|||
style={{ |
|||
padding: 4, |
|||
}} |
|||
/> |
|||
); |
|||
}; |
|||
|
|||
export const Question = () => { |
|||
return ( |
|||
<div |
|||
style={{ |
|||
display: 'flex', |
|||
height: 26, |
|||
}} |
|||
onClick={() => { |
|||
window.open('https://pro.ant.design/docs/getting-started'); |
|||
}} |
|||
> |
|||
<QuestionCircleOutlined /> |
|||
</div> |
|||
); |
|||
}; |
|||
@ -0,0 +1,12 @@ |
|||
/** |
|||
* 这个文件作为组件的目录 |
|||
* 目的是统一管理对外输出的组件,方便分类 |
|||
*/ |
|||
/** |
|||
* 布局组件 |
|||
*/ |
|||
import Footer from './Footer'; |
|||
import { Question, SelectLang } from './RightContent'; |
|||
import { AvatarDropdown, AvatarName } from './RightContent/AvatarDropdown'; |
|||
|
|||
export { Footer, Question, SelectLang, AvatarDropdown, AvatarName }; |
|||
@ -1 +0,0 @@ |
|||
export default undefined; |
|||
@ -1,57 +0,0 @@ |
|||
const { uniq } = require('lodash'); |
|||
const RouterConfig = require('../../config/config').default.routes; |
|||
|
|||
const BASE_URL = `http://localhost:${process.env.PORT || 8000}`; |
|||
|
|||
function formatter(routes, parentPath = '') { |
|||
const fixedParentPath = parentPath.replace(/\/{1,}/g, '/'); |
|||
let result = []; |
|||
routes.forEach((item) => { |
|||
if (item.path) { |
|||
result.push(`${fixedParentPath}/${item.path}`.replace(/\/{1,}/g, '/')); |
|||
} |
|||
if (item.routes) { |
|||
result = result.concat( |
|||
formatter(item.routes, item.path ? `${fixedParentPath}/${item.path}` : parentPath), |
|||
); |
|||
} |
|||
}); |
|||
return uniq(result.filter((item) => !!item)); |
|||
} |
|||
|
|||
beforeEach(async () => { |
|||
await page.goto(`${BASE_URL}`); |
|||
await page.evaluate(() => { |
|||
localStorage.setItem('antd-pro-authority', '["admin"]'); |
|||
}); |
|||
}); |
|||
|
|||
describe('Ant Design Pro E2E test', () => { |
|||
const testPage = (path) => async () => { |
|||
await page.goto(`${BASE_URL}${path}`); |
|||
await page.waitForSelector('footer', { |
|||
timeout: 2000, |
|||
}); |
|||
const haveFooter = await page.evaluate( |
|||
() => document.getElementsByTagName('footer').length > 0, |
|||
); |
|||
expect(haveFooter).toBeTruthy(); |
|||
}; |
|||
|
|||
const routers = formatter(RouterConfig); |
|||
routers.forEach((route) => { |
|||
it(`test pages ${route}`, testPage(route)); |
|||
}); |
|||
|
|||
it('topmenu should have footer', async () => { |
|||
const params = '?navTheme=light&layout=topmenu'; |
|||
await page.goto(`${BASE_URL}${params}`); |
|||
await page.waitForSelector('footer', { |
|||
timeout: 2000, |
|||
}); |
|||
const haveFooter = await page.evaluate( |
|||
() => document.getElementsByTagName('footer').length > 0, |
|||
); |
|||
expect(haveFooter).toBeTruthy(); |
|||
}); |
|||
}); |
|||
@ -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,71 +0,0 @@ |
|||
@import '~antd/es/style/themes/default.less'; |
|||
|
|||
.container { |
|||
display: flex; |
|||
flex-direction: column; |
|||
height: 100vh; |
|||
overflow: auto; |
|||
background: @layout-body-background; |
|||
} |
|||
|
|||
.lang { |
|||
width: 100%; |
|||
height: 40px; |
|||
line-height: 44px; |
|||
text-align: right; |
|||
:global(.ant-dropdown-trigger) { |
|||
margin-right: 24px; |
|||
} |
|||
} |
|||
|
|||
.content { |
|||
flex: 1; |
|||
padding: 32px 0; |
|||
} |
|||
|
|||
@media (min-width: @screen-md-min) { |
|||
.container { |
|||
background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg'); |
|||
background-repeat: no-repeat; |
|||
background-position: center 110px; |
|||
background-size: 100%; |
|||
} |
|||
|
|||
.content { |
|||
padding: 32px 0 24px; |
|||
} |
|||
} |
|||
|
|||
.top { |
|||
text-align: center; |
|||
} |
|||
|
|||
.header { |
|||
height: 44px; |
|||
line-height: 44px; |
|||
a { |
|||
text-decoration: none; |
|||
} |
|||
} |
|||
|
|||
.logo { |
|||
height: 44px; |
|||
margin-right: 16px; |
|||
vertical-align: top; |
|||
} |
|||
|
|||
.title { |
|||
position: relative; |
|||
top: 2px; |
|||
color: @heading-color; |
|||
font-weight: 600; |
|||
font-size: 33px; |
|||
font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif; |
|||
} |
|||
|
|||
.desc { |
|||
margin-top: 12px; |
|||
margin-bottom: 40px; |
|||
color: @text-color-secondary; |
|||
font-size: @font-size-base; |
|||
} |
|||
@ -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); |
|||
@ -0,0 +1,25 @@ |
|||
import component from './bn-BD/component'; |
|||
import globalHeader from './bn-BD/globalHeader'; |
|||
import menu from './bn-BD/menu'; |
|||
import pages from './bn-BD/pages'; |
|||
import pwa from './bn-BD/pwa'; |
|||
import settingDrawer from './bn-BD/settingDrawer'; |
|||
import settings from './bn-BD/settings'; |
|||
|
|||
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': |
|||
'`block` ডেভেলপমেন্ট এর উপর ভিত্তি করে দ্রুত স্ট্যান্ডার্ড, পৃষ্ঠাসমূহ তৈরি করুন।', |
|||
...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': 'অনুসন্ধান উদাহরণ ১', |
|||
'component.globalHeader.search.example2': 'অনুসন্ধান উদাহরণ ২', |
|||
'component.globalHeader.search.example3': 'অনুসন্ধান উদাহরণ ৩', |
|||
'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': 'কোনি সম্পাদক', |
|||
}; |
|||
@ -0,0 +1,70 @@ |
|||
export default { |
|||
'pages.layouts.userLayout.title': |
|||
'পিঁপড়া ডিজাইন হচ্ছে সিহু জেলার সবচেয়ে প্রভাবশালী ওয়েব ডিজাইনের স্পেসিফিকেশন', |
|||
'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', |
|||
'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.link': 'স্বাগতম', |
|||
'pages.welcome.alertMessage': 'দ্রুত এবং শক্তিশালী ভারী শুল্ক উপাদান প্রকাশ করা হয়েছে।', |
|||
'pages.404.subTitle': 'দুঃখিত, আপনি যে পৃষ্ঠাটি দেখতে চান তা বিদ্যমান নেই।', |
|||
'pages.404.buttonText': 'প্রধান পাতায় ফিরে যান', |
|||
'pages.admin.subPage.title': 'এই পৃষ্ঠাটি কেবল অ্যাডমিন দ্বারা দেখা যাবে', |
|||
'pages.admin.subPage.alertMessage': |
|||
'UMI UI এখন প্রকাশিত হয়েছে, অভিজ্ঞতা শুরু করতে 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': '000', |
|||
'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': 'রিফ্রেশ', |
|||
}; |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue