343 changed files with 25861 additions and 2891 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: '👑 [需求]' |
|||
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: '🧐[问题]' |
|||
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,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,31 +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: yarn |
|||
|
|||
- name: plugins |
|||
run: yarn add umi-plugin-pro |
|||
|
|||
- name: fetch-blocks |
|||
run: yarn run pro fetch-blocks |
|||
|
|||
- name: site |
|||
run: npm run build |
|||
|
|||
- name: Deploy |
|||
uses: peaceiris/actions-gh-pages@v3 |
|||
with: |
|||
github_token: ${{ secrets.GITHUB_TOKEN }} |
|||
publish_dir: ./dist |
|||
force_orphan: true |
|||
@ -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 |
|||
@ -1 +0,0 @@ |
|||
_ |
|||
@ -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,3 +0,0 @@ |
|||
module.exports = { |
|||
extends: [require.resolve('@umijs/fabric/dist/stylelint')], |
|||
}; |
|||
@ -1,8 +0,0 @@ |
|||
{ |
|||
"recommendations": [ |
|||
"esbenp.prettier-vscode", |
|||
"dbaeumer.vscode-eslint", |
|||
"stylelint.vscode-stylelint", |
|||
"wangzy.sneak-mark" |
|||
] |
|||
} |
|||
@ -1,5 +0,0 @@ |
|||
{ |
|||
"editor.formatOnSave": true, |
|||
"prettier.requireConfig": true, |
|||
"editor.defaultFormatter": "esbenp.prettier-vscode" |
|||
} |
|||
@ -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:e2e |
|||
|
|||
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.npmmirror.com |
|||
|
|||
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:e2e |
|||
|
|||
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;"] |
|||
@ -1,15 +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: {}, |
|||
}, |
|||
}); |
|||
@ -1,21 +1,28 @@ |
|||
import { Settings as LayoutSettings } from '@ant-design/pro-layout'; |
|||
import { ProLayoutProps } from '@ant-design/pro-components'; |
|||
|
|||
const Settings: LayoutSettings & { |
|||
/** |
|||
* @name |
|||
*/ |
|||
const Settings: ProLayoutProps & { |
|||
pwa?: boolean; |
|||
logo?: string; |
|||
} = { |
|||
navTheme: 'light', |
|||
// 拂晓蓝
|
|||
primaryColor: '#1890ff', |
|||
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 default Settings; |
|||
|
|||
@ -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', |
|||
verbose: false, |
|||
extraSetupFiles: ['./tests/setupTests.js'], |
|||
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(); |
|||
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', |
|||
}, |
|||
}; |
|||
@ -1,22 +0,0 @@ |
|||
// playwright.config.ts
|
|||
import type { PlaywrightTestConfig } from '@playwright/test'; |
|||
import { devices } from '@playwright/test'; |
|||
|
|||
const config: PlaywrightTestConfig = { |
|||
forbidOnly: !!process.env.CI, |
|||
retries: process.env.CI ? 2 : 0, |
|||
use: { |
|||
trace: 'on-first-retry', |
|||
}, |
|||
projects: [ |
|||
{ |
|||
name: 'chromium', |
|||
use: { ...devices['Desktop Chrome'] }, |
|||
}, |
|||
{ |
|||
name: 'firefox', |
|||
use: { ...devices['Desktop Firefox'] }, |
|||
}, |
|||
], |
|||
}; |
|||
export default config; |
|||
File diff suppressed because it is too large
@ -1,16 +0,0 @@ |
|||
@import (reference) '~antd/es/style/themes/index'; |
|||
|
|||
.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,23 @@ |
|||
import type { DropDownProps } from 'antd/es/dropdown'; |
|||
import { Dropdown } from 'antd'; |
|||
import type { DropDownProps } from 'antd/es/dropdown'; |
|||
import React from 'react'; |
|||
import { useEmotionCss } from '@ant-design/use-emotion-css'; |
|||
import classNames from 'classnames'; |
|||
import styles from './index.less'; |
|||
|
|||
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 className = useEmotionCss(({ token }) => { |
|||
return { |
|||
[`@media screen and (max-width: ${token.screenXS})`]: { |
|||
width: '100%', |
|||
}, |
|||
}; |
|||
}); |
|||
return <Dropdown overlayClassName={classNames(className, cls)} {...restProps} />; |
|||
}; |
|||
|
|||
export default HeaderDropdown; |
|||
|
|||
@ -1,25 +0,0 @@ |
|||
@import (reference) '~antd/es/style/themes/index'; |
|||
|
|||
.headerSearch { |
|||
display: inline-flex; |
|||
align-items: center; |
|||
.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 { |
|||
box-shadow: none !important; |
|||
} |
|||
|
|||
&.show { |
|||
width: 210px; |
|||
margin-left: 8px; |
|||
} |
|||
} |
|||
} |
|||
@ -1,102 +0,0 @@ |
|||
import { SearchOutlined } from '@ant-design/icons'; |
|||
import type { InputRef } from 'antd'; |
|||
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']; |
|||
defaultVisible?: boolean; |
|||
visible?: boolean; |
|||
defaultValue?: string; |
|||
value?: string; |
|||
}; |
|||
|
|||
const HeaderSearch: React.FC<HeaderSearchProps> = (props) => { |
|||
const { |
|||
className, |
|||
defaultValue, |
|||
onVisibleChange, |
|||
placeholder, |
|||
visible, |
|||
defaultVisible, |
|||
...restProps |
|||
} = props; |
|||
|
|||
const inputRef = useRef<InputRef | null>(null); |
|||
|
|||
const [value, setValue] = useMergedState<string | undefined>(defaultValue, { |
|||
value: props.value, |
|||
onChange: props.onChange, |
|||
}); |
|||
|
|||
const [searchMode, setSearchMode] = useMergedState(defaultVisible ?? false, { |
|||
value: props.visible, |
|||
onChange: onVisibleChange, |
|||
}); |
|||
|
|||
const inputClass = classNames(styles.input, { |
|||
[styles.show]: searchMode, |
|||
}); |
|||
return ( |
|||
<div |
|||
className={classNames(className, styles.headerSearch)} |
|||
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} |
|||
options={restProps.options} |
|||
onChange={(completeValue) => setValue(completeValue)} |
|||
> |
|||
<Input |
|||
size="small" |
|||
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,126 +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 NoticeIconProps = { |
|||
count?: number; |
|||
bell?: React.ReactNode; |
|||
className?: string; |
|||
loading?: boolean; |
|||
onClear?: (tabName: string, tabKey: string) => void; |
|||
onItemClick?: (item: API.NoticeIconItem, tabProps: NoticeIconTabProps) => void; |
|||
onViewMore?: (tabProps: NoticeIconTabProps, e: MouseEvent) => void; |
|||
onTabChange?: (tabTile: string) => void; |
|||
style?: React.CSSProperties; |
|||
onPopupVisibleChange?: (visible: boolean) => void; |
|||
popupVisible?: boolean; |
|||
clearText?: string; |
|||
viewMoreText?: string; |
|||
clearClose?: boolean; |
|||
emptyImage?: string; |
|||
children?: React.ReactElement<NoticeIconTabProps>[]; |
|||
}; |
|||
|
|||
const NoticeIcon: React.FC<NoticeIconProps> & { |
|||
Tab: typeof NoticeList; |
|||
} = (props) => { |
|||
const getNotificationBox = (): React.ReactNode => { |
|||
const { |
|||
children, |
|||
loading, |
|||
onClear, |
|||
onTabChange, |
|||
onItemClick, |
|||
onViewMore, |
|||
clearText, |
|||
viewMoreText, |
|||
} = props; |
|||
if (!children) { |
|||
return null; |
|||
} |
|||
const panes: React.ReactNode[] = []; |
|||
React.Children.forEach(children, (child: React.ReactElement<NoticeIconTabProps>): void => { |
|||
if (!child) { |
|||
return; |
|||
} |
|||
const { list, title, count, tabKey, showClear, showViewMore } = child.props; |
|||
const len = list && list.length ? list.length : 0; |
|||
const msgCount = count || count === 0 ? count : len; |
|||
const tabTitle: string = msgCount > 0 ? `${title} (${msgCount})` : title; |
|||
panes.push( |
|||
<TabPane tab={tabTitle} key={tabKey}> |
|||
<NoticeList |
|||
clearText={clearText} |
|||
viewMoreText={viewMoreText} |
|||
list={list} |
|||
tabKey={tabKey} |
|||
onClear={(): void => onClear && onClear(title, tabKey)} |
|||
onClick={(item): void => onItemClick && onItemClick(item, child.props)} |
|||
onViewMore={(event): void => onViewMore && onViewMore(child.props, event)} |
|||
showClear={showClear} |
|||
showViewMore={showViewMore} |
|||
title={title} |
|||
/> |
|||
</TabPane>, |
|||
); |
|||
}); |
|||
return ( |
|||
<> |
|||
<Spin spinning={loading} delay={300}> |
|||
<Tabs className={styles.tabs} onChange={onTabChange}> |
|||
{panes} |
|||
</Tabs> |
|||
</Spin> |
|||
</> |
|||
); |
|||
}; |
|||
|
|||
const { className, count, bell } = props; |
|||
|
|||
const [visible, setVisible] = useMergedState<boolean>(false, { |
|||
value: props.popupVisible, |
|||
onChange: props.onPopupVisibleChange, |
|||
}); |
|||
const noticeButtonClass = classNames(className, styles.noticeButton); |
|||
const notificationBox = getNotificationBox(); |
|||
const NoticeBellIcon = bell || <BellOutlined className={styles.icon} />; |
|||
const trigger = ( |
|||
<span className={classNames(noticeButtonClass, { opened: visible })}> |
|||
<Badge count={count} style={{ boxShadow: 'none' }} className={styles.badge}> |
|||
{NoticeBellIcon} |
|||
</Badge> |
|||
</span> |
|||
); |
|||
if (!notificationBox) { |
|||
return trigger; |
|||
} |
|||
|
|||
return ( |
|||
<HeaderDropdown |
|||
placement="bottomRight" |
|||
overlay={notificationBox} |
|||
overlayClassName={styles.popover} |
|||
trigger={['click']} |
|||
visible={visible} |
|||
onVisibleChange={setVisible} |
|||
> |
|||
{trigger} |
|||
</HeaderDropdown> |
|||
); |
|||
}; |
|||
|
|||
NoticeIcon.defaultProps = { |
|||
emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg', |
|||
}; |
|||
|
|||
NoticeIcon.Tab = NoticeList; |
|||
|
|||
export default NoticeIcon; |
|||
@ -1,103 +0,0 @@ |
|||
@import (reference) '~antd/es/style/themes/index'; |
|||
|
|||
.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,113 +0,0 @@ |
|||
import { Avatar, List } from 'antd'; |
|||
|
|||
import React from 'react'; |
|||
import classNames from 'classnames'; |
|||
import styles from './NoticeList.less'; |
|||
|
|||
export type NoticeIconTabProps = { |
|||
count?: number; |
|||
showClear?: boolean; |
|||
showViewMore?: boolean; |
|||
style?: React.CSSProperties; |
|||
title: string; |
|||
tabKey: API.NoticeIconItemType; |
|||
onClick?: (item: API.NoticeIconItem) => void; |
|||
onClear?: () => void; |
|||
emptyText?: string; |
|||
clearText?: string; |
|||
viewMoreText?: string; |
|||
list: API.NoticeIconItem[]; |
|||
onViewMore?: (e: any) => void; |
|||
}; |
|||
const NoticeList: React.FC<NoticeIconTabProps> = ({ |
|||
list = [], |
|||
onClick, |
|||
onClear, |
|||
title, |
|||
onViewMore, |
|||
emptyText, |
|||
showClear = true, |
|||
clearText, |
|||
viewMoreText, |
|||
showViewMore = false, |
|||
}) => { |
|||
if (!list || list.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<API.NoticeIconItem> |
|||
className={styles.list} |
|||
dataSource={list} |
|||
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 (reference) '~antd/es/style/themes/index'; |
|||
|
|||
.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-nav { |
|||
margin-bottom: 0; |
|||
} |
|||
} |
|||
} |
|||
@ -1,153 +0,0 @@ |
|||
import { useEffect, useState } from 'react'; |
|||
import { Tag, message } from 'antd'; |
|||
import { groupBy } from 'lodash'; |
|||
import moment from 'moment'; |
|||
import { useModel, useRequest } from 'umi'; |
|||
import { getNotices } from '@/services/ant-design-pro/api'; |
|||
|
|||
import NoticeIcon from './NoticeIcon'; |
|||
import styles from './index.less'; |
|||
|
|||
export type GlobalHeaderRightProps = { |
|||
fetchingNotices?: boolean; |
|||
onNoticeVisibleChange?: (visible: boolean) => void; |
|||
onNoticeClear?: (tabName?: string) => void; |
|||
}; |
|||
|
|||
const getNoticeData = (notices: API.NoticeIconItem[]): Record<string, API.NoticeIconItem[]> => { |
|||
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> |
|||
) as any; |
|||
} |
|||
|
|||
return newNotice; |
|||
}); |
|||
return groupBy(newNotices, 'type'); |
|||
}; |
|||
|
|||
const getUnreadData = (noticeData: Record<string, API.NoticeIconItem[]>) => { |
|||
const unreadMsg: Record<string, number> = {}; |
|||
Object.keys(noticeData).forEach((key) => { |
|||
const value = noticeData[key]; |
|||
|
|||
if (!unreadMsg[key]) { |
|||
unreadMsg[key] = 0; |
|||
} |
|||
|
|||
if (Array.isArray(value)) { |
|||
unreadMsg[key] = value.filter((item) => !item.read).length; |
|||
} |
|||
}); |
|||
return unreadMsg; |
|||
}; |
|||
|
|||
const NoticeIconView: React.FC = () => { |
|||
const { initialState } = useModel('@@initialState'); |
|||
const { currentUser } = initialState || {}; |
|||
const [notices, setNotices] = useState<API.NoticeIconItem[]>([]); |
|||
const { data } = useRequest(getNotices); |
|||
|
|||
useEffect(() => { |
|||
setNotices(data || []); |
|||
}, [data]); |
|||
|
|||
const noticeData = getNoticeData(notices); |
|||
const unreadMsg = getUnreadData(noticeData || {}); |
|||
|
|||
const changeReadState = (id: string) => { |
|||
setNotices( |
|||
notices.map((item) => { |
|||
const notice = { ...item }; |
|||
if (notice.id === id) { |
|||
notice.read = true; |
|||
} |
|||
return notice; |
|||
}), |
|||
); |
|||
}; |
|||
|
|||
const clearReadState = (title: string, key: string) => { |
|||
setNotices( |
|||
notices.map((item) => { |
|||
const notice = { ...item }; |
|||
if (notice.type === key) { |
|||
notice.read = true; |
|||
} |
|||
return notice; |
|||
}), |
|||
); |
|||
message.success(`${'清空了'} ${title}`); |
|||
}; |
|||
|
|||
return ( |
|||
<NoticeIcon |
|||
className={styles.action} |
|||
count={currentUser && currentUser.unreadCount} |
|||
onItemClick={(item) => { |
|||
changeReadState(item.id!); |
|||
}} |
|||
onClear={(title: string, key: string) => clearReadState(title, key)} |
|||
loading={false} |
|||
clearText="清空" |
|||
viewMoreText="查看更多" |
|||
onViewMore={() => message.info('Click on view more')} |
|||
clearClose |
|||
> |
|||
<NoticeIcon.Tab |
|||
tabKey="notification" |
|||
count={unreadMsg.notification} |
|||
list={noticeData.notification} |
|||
title="通知" |
|||
emptyText="你已查看所有通知" |
|||
showViewMore |
|||
/> |
|||
<NoticeIcon.Tab |
|||
tabKey="message" |
|||
count={unreadMsg.message} |
|||
list={noticeData.message} |
|||
title="消息" |
|||
emptyText="您已读完所有消息" |
|||
showViewMore |
|||
/> |
|||
<NoticeIcon.Tab |
|||
tabKey="event" |
|||
title="待办" |
|||
emptyText="你已完成所有待办" |
|||
count={unreadMsg.event} |
|||
list={noticeData.event} |
|||
showViewMore |
|||
/> |
|||
</NoticeIcon> |
|||
); |
|||
}; |
|||
|
|||
export default NoticeIconView; |
|||
@ -1,84 +0,0 @@ |
|||
@import (reference) '~antd/es/style/themes/index'; |
|||
|
|||
@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: 48px; |
|||
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-right: 8px; |
|||
color: @primary-color; |
|||
vertical-align: top; |
|||
background: rgba(255, 255, 255, 0.85); |
|||
} |
|||
} |
|||
} |
|||
|
|||
.dark { |
|||
.action { |
|||
&:hover { |
|||
background: #252a3d; |
|||
} |
|||
&:global(.opened) { |
|||
background: #252a3d; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@media only screen and (max-width: @screen-md) { |
|||
:global(.ant-divider-vertical) { |
|||
vertical-align: unset; |
|||
} |
|||
.name { |
|||
display: none; |
|||
} |
|||
.right { |
|||
position: absolute; |
|||
top: 0; |
|||
right: 12px; |
|||
.account { |
|||
.avatar { |
|||
margin-right: 0; |
|||
} |
|||
} |
|||
.search { |
|||
display: none; |
|||
} |
|||
} |
|||
} |
|||
@ -1,272 +0,0 @@ |
|||
--- |
|||
title: 业务组件 |
|||
sidemenu: false |
|||
--- |
|||
|
|||
> 此功能由[dumi](https://d.umijs.org/zh-CN/guide/advanced#umi-%E9%A1%B9%E7%9B%AE%E9%9B%86%E6%88%90%E6%A8%A1%E5%BC%8F)提供,dumi 是一个 📖 为组件开发场景而生的文档工具,用过的都说好。 |
|||
|
|||
# 业务组件 |
|||
|
|||
这里列举了 Pro 中所有用到的组件,这些组件不适合作为组件库,但是在业务中却真实需要。所以我们准备了这个文档,来指导大家是否需要使用这个组件。 |
|||
|
|||
## Footer 页脚组件 |
|||
|
|||
这个组件自带了一些 Pro 的配置,你一般都需要改掉它的信息。 |
|||
|
|||
```tsx |
|||
/** |
|||
* background: '#f0f2f5' |
|||
*/ |
|||
import React from 'react'; |
|||
import Footer from '@/components/Footer'; |
|||
|
|||
export default () => <Footer />; |
|||
``` |
|||
|
|||
## HeaderDropdown 头部下拉列表 |
|||
|
|||
HeaderDropdown 是 antd Dropdown 的封装,但是增加了移动端的特殊处理,用法也是相同的。 |
|||
|
|||
```tsx |
|||
/** |
|||
* background: '#f0f2f5' |
|||
*/ |
|||
import { Button, Menu } from 'antd'; |
|||
import React from 'react'; |
|||
import HeaderDropdown from '@/components/HeaderDropdown'; |
|||
|
|||
export default () => { |
|||
const menuHeaderDropdown = ( |
|||
<Menu selectedKeys={[]}> |
|||
<Menu.Item key="center">个人中心</Menu.Item> |
|||
<Menu.Item key="settings">个人设置</Menu.Item> |
|||
<Menu.Divider /> |
|||
<Menu.Item key="logout">退出登录</Menu.Item> |
|||
</Menu> |
|||
); |
|||
return ( |
|||
<HeaderDropdown overlay={menuHeaderDropdown}> |
|||
<Button>hover 展示菜单</Button> |
|||
</HeaderDropdown> |
|||
); |
|||
}; |
|||
``` |
|||
|
|||
## HeaderSearch 头部搜索框 |
|||
|
|||
一个带补全数据的输入框,支持收起和展开 Input |
|||
|
|||
```tsx |
|||
/** |
|||
* background: '#f0f2f5' |
|||
*/ |
|||
import { Button, Menu } from 'antd'; |
|||
import React from 'react'; |
|||
import HeaderSearch from '@/components/HeaderSearch'; |
|||
|
|||
export default () => { |
|||
return ( |
|||
<HeaderSearch |
|||
placeholder="站内搜索" |
|||
defaultValue="umi ui" |
|||
options={[ |
|||
{ label: 'Ant Design Pro', value: 'Ant Design Pro' }, |
|||
{ |
|||
label: 'Ant Design', |
|||
value: 'Ant Design', |
|||
}, |
|||
{ |
|||
label: 'Pro Table', |
|||
value: 'Pro Table', |
|||
}, |
|||
{ |
|||
label: 'Pro Layout', |
|||
value: 'Pro Layout', |
|||
}, |
|||
]} |
|||
onSearch={(value) => { |
|||
console.log('input', value); |
|||
}} |
|||
/> |
|||
); |
|||
}; |
|||
``` |
|||
|
|||
### API |
|||
|
|||
| 参数 | 说明 | 类型 | 默认值 | |
|||
| --------------- | ---------------------------------- | ---------------------------- | ------ | |
|||
| value | 输入框的值 | `string` | - | |
|||
| onChange | 值修改后触发 | `(value?: string) => void` | - | |
|||
| onSearch | 查询后触发 | `(value?: string) => void` | - | |
|||
| options | 选项菜单的的列表 | `{label,value}[]` | - | |
|||
| defaultVisible | 输入框默认是否显示,只有第一次生效 | `boolean` | - | |
|||
| visible | 输入框是否显示 | `boolean` | - | |
|||
| onVisibleChange | 输入框显示隐藏的回调函数 | `(visible: boolean) => void` | - | |
|||
|
|||
## NoticeIcon 通知工具 |
|||
|
|||
通知工具提供一个展示多种通知信息的界面。 |
|||
|
|||
```tsx |
|||
/** |
|||
* background: '#f0f2f5' |
|||
*/ |
|||
import { message } from 'antd'; |
|||
import React from 'react'; |
|||
import NoticeIcon from '@/components/NoticeIcon/NoticeIcon'; |
|||
|
|||
export default () => { |
|||
const list = [ |
|||
{ |
|||
id: '000000001', |
|||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png', |
|||
title: '你收到了 14 份新周报', |
|||
datetime: '2017-08-09', |
|||
type: 'notification', |
|||
}, |
|||
{ |
|||
id: '000000002', |
|||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png', |
|||
title: '你推荐的 曲妮妮 已通过第三轮面试', |
|||
datetime: '2017-08-08', |
|||
type: 'notification', |
|||
}, |
|||
]; |
|||
return ( |
|||
<NoticeIcon |
|||
count={10} |
|||
onItemClick={(item) => { |
|||
message.info(`${item.title} 被点击了`); |
|||
}} |
|||
onClear={(title: string, key: string) => message.info('点击了清空更多')} |
|||
loading={false} |
|||
clearText="清空" |
|||
viewMoreText="查看更多" |
|||
onViewMore={() => message.info('点击了查看更多')} |
|||
clearClose |
|||
> |
|||
<NoticeIcon.Tab |
|||
tabKey="notification" |
|||
count={2} |
|||
list={list} |
|||
title="通知" |
|||
emptyText="你已查看所有通知" |
|||
showViewMore |
|||
/> |
|||
<NoticeIcon.Tab |
|||
tabKey="message" |
|||
count={2} |
|||
list={list} |
|||
title="消息" |
|||
emptyText="您已读完所有消息" |
|||
showViewMore |
|||
/> |
|||
<NoticeIcon.Tab |
|||
tabKey="event" |
|||
title="待办" |
|||
emptyText="你已完成所有待办" |
|||
count={2} |
|||
list={list} |
|||
showViewMore |
|||
/> |
|||
</NoticeIcon> |
|||
); |
|||
}; |
|||
``` |
|||
|
|||
### NoticeIcon API |
|||
|
|||
| 参数 | 说明 | 类型 | 默认值 | |
|||
| --- | --- | --- | --- | |
|||
| count | 有多少未读通知 | `number` | - | |
|||
| bell | 铃铛的图表 | `ReactNode` | - | |
|||
| onClear | 点击清空数据按钮 | `(tabName: string, tabKey: string) => void` | - | |
|||
| onItemClick | 未读消息列被点击 | `(item: API.NoticeIconData, tabProps: NoticeIconTabProps) => void` | - | |
|||
| onViewMore | 查看更多的按钮点击 | `(tabProps: NoticeIconTabProps, e: MouseEvent) => void` | - | |
|||
| onTabChange | 通知 Tab 的切换 | `(tabTile: string) => void;` | - | |
|||
| popupVisible | 通知显示是否展示 | `boolean` | - | |
|||
| onPopupVisibleChange | 通知信息显示隐藏的回调函数 | `(visible: boolean) => void` | - | |
|||
| clearText | 清空按钮的文字 | `string` | - | |
|||
| viewMoreText | 查看更多的按钮文字 | `string` | - | |
|||
| clearClose | 展示清空按钮 | `boolean` | - | |
|||
| emptyImage | 列表为空时的兜底展示 | `ReactNode` | - | |
|||
|
|||
### NoticeIcon.Tab API |
|||
|
|||
| 参数 | 说明 | 类型 | 默认值 | |
|||
| ------------ | ------------------ | ------------------------------------ | ------ | |
|||
| count | 有多少未读通知 | `number` | - | |
|||
| title | 通知 Tab 的标题 | `ReactNode` | - | |
|||
| showClear | 展示清除按钮 | `boolean` | `true` | |
|||
| showViewMore | 展示加载更 | `boolean` | `true` | |
|||
| tabKey | Tab 的唯一 key | `string` | - | |
|||
| onClick | 子项的单击事件 | `(item: API.NoticeIconData) => void` | - | |
|||
| onClear | 清楚按钮的点击 | `()=>void` | - | |
|||
| emptyText | 为空的时候测试 | `()=>void` | - | |
|||
| viewMoreText | 查看更多的按钮文字 | `string` | - | |
|||
| onViewMore | 查看更多的按钮点击 | `( e: MouseEvent) => void` | - | |
|||
| list | 通知信息的列表 | `API.NoticeIconData` | - | |
|||
|
|||
### NoticeIconData |
|||
|
|||
```tsx | pure |
|||
export interface NoticeIconData { |
|||
id: string; |
|||
key: string; |
|||
avatar: string; |
|||
title: string; |
|||
datetime: string; |
|||
type: string; |
|||
read?: boolean; |
|||
description: string; |
|||
clickClose?: boolean; |
|||
extra: any; |
|||
status: string; |
|||
} |
|||
``` |
|||
|
|||
## RightContent |
|||
|
|||
RightContent 是以上几个组件的组合,同时新增了 plugins 的 `SelectLang` 插件。 |
|||
|
|||
```tsx | pure |
|||
<Space> |
|||
<HeaderSearch |
|||
placeholder="站内搜索" |
|||
defaultValue="umi ui" |
|||
options={[ |
|||
{ label: <a href="https://umijs.org/zh/guide/umi-ui.html">umi ui</a>, value: 'umi ui' }, |
|||
{ |
|||
label: <a href="next.ant.design">Ant Design</a>, |
|||
value: 'Ant Design', |
|||
}, |
|||
{ |
|||
label: <a href="https://protable.ant.design/">Pro Table</a>, |
|||
value: 'Pro Table', |
|||
}, |
|||
{ |
|||
label: <a href="https://prolayout.ant.design/">Pro Layout</a>, |
|||
value: 'Pro Layout', |
|||
}, |
|||
]} |
|||
/> |
|||
<Tooltip title="使用文档"> |
|||
<span |
|||
className={styles.action} |
|||
onClick={() => { |
|||
window.location.href = 'https://pro.ant.design/docs/getting-started'; |
|||
}} |
|||
> |
|||
<QuestionCircleOutlined /> |
|||
</span> |
|||
</Tooltip> |
|||
<Avatar /> |
|||
{REACT_APP_ENV && ( |
|||
<span> |
|||
<Tag color={ENVTagColor[REACT_APP_ENV]}>{REACT_APP_ENV}</Tag> |
|||
</span> |
|||
)} |
|||
<SelectLang className={styles.action} /> |
|||
</Space> |
|||
``` |
|||
@ -1,45 +0,0 @@ |
|||
import type { Page } from '@playwright/test'; |
|||
import { test, expect } from '@playwright/test'; |
|||
const { uniq } = require('lodash'); |
|||
const RouterConfig = require('../../config/routes').default; |
|||
|
|||
const BASE_URL = `http://localhost:${process.env.PORT || 8001}`; |
|||
|
|||
function formatter(routes: any, parentPath = ''): string[] { |
|||
const fixedParentPath = parentPath.replace(/\/{1,}/g, '/'); |
|||
let result: string[] = []; |
|||
routes.forEach((item: { path: string; routes: string }) => { |
|||
if (item.path && !item.path.startsWith('/')) { |
|||
result.push(`${fixedParentPath}/${item.path}`.replace(/\/{1,}/g, '/')); |
|||
} |
|||
if (item.path && item.path.startsWith('/')) { |
|||
result.push(`${item.path}`.replace(/\/{1,}/g, '/')); |
|||
} |
|||
if (item.routes) { |
|||
result = result.concat( |
|||
formatter(item.routes, item.path ? `${fixedParentPath}/${item.path}` : parentPath), |
|||
); |
|||
} |
|||
}); |
|||
return uniq(result.filter((item) => !!item)); |
|||
} |
|||
|
|||
const testPage = (path: string, page: Page) => async () => { |
|||
await page.evaluate(() => { |
|||
localStorage.setItem('antd-pro-authority', '["admin"]'); |
|||
}); |
|||
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) => { |
|||
test(`test route page ${route}`, async ({ page }) => { |
|||
await testPage(route, page); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,42 @@ |
|||
import { createStyles } from 'antd-style'; |
|||
|
|||
const useStyles = createStyles(({ token }) => { |
|||
return { |
|||
colorWeak: { |
|||
filter: 'invert(80%)', |
|||
}, |
|||
'ant-layout': { |
|||
minHeight: '100vh', |
|||
}, |
|||
'ant-pro-sider.ant-layout-sider.ant-pro-sider-fixed': { |
|||
left: 'unset', |
|||
}, |
|||
canvas: { |
|||
display: 'block', |
|||
}, |
|||
body: { |
|||
textRendering: 'optimizeLegibility', |
|||
WebkitFontSmoothing: 'antialiased', |
|||
MozOsxFontSmoothing: 'grayscale', |
|||
}, |
|||
'ul,ol': { |
|||
listStyle: 'none', |
|||
}, |
|||
'@media(max-width: 768px)': { |
|||
'ant-table': { |
|||
width: '100%', |
|||
overflowX: 'auto', |
|||
'&-thead > tr, &-tbody > tr': { |
|||
'> th, > td': { |
|||
whiteSpace: 'pre', |
|||
'> span': { |
|||
display: 'block', |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
}; |
|||
}); |
|||
|
|||
export default useStyles; |
|||
@ -0,0 +1,65 @@ |
|||
export default { |
|||
'pages.layouts.userLayout.title': 'Ant Design 是西湖區最具影響力的 Web 設計規範', |
|||
'pages.login.accountLogin.tab': '賬戶密碼登錄', |
|||
'pages.login.accountLogin.errorMessage': '錯誤的用戶名和密碼(admin/ant.design)', |
|||
'pages.login.failure': '登錄失敗,請重試!', |
|||
'pages.login.success': '登錄成功!', |
|||
'pages.login.username.placeholder': '用戶名: admin or user', |
|||
'pages.login.username.required': '用戶名是必填項!', |
|||
'pages.login.password.placeholder': '密碼: ant.design', |
|||
'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.admin.subPage.title': '這個頁面只有 admin 權限才能查看', |
|||
'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': '萬', |
|||
'pages.searchTable.batchDeletion': '批量刪除', |
|||
'pages.searchTable.batchApproval': '批量審批', |
|||
}; |
|||
File diff suppressed because it is too large
@ -0,0 +1,96 @@ |
|||
import { render, fireEvent, act } from '@testing-library/react'; |
|||
import React from 'react'; |
|||
import { TestBrowser } from '@@/testBrowser'; |
|||
|
|||
// @ts-ignore
|
|||
import { startMock } from '@@/requestRecordMock'; |
|||
|
|||
const waitTime = (time: number = 100) => { |
|||
return new Promise((resolve) => { |
|||
setTimeout(() => { |
|||
resolve(true); |
|||
}, time); |
|||
}); |
|||
}; |
|||
|
|||
let server: { |
|||
close: () => void; |
|||
}; |
|||
|
|||
describe('Login Page', () => { |
|||
beforeAll(async () => { |
|||
server = await startMock({ |
|||
port: 8000, |
|||
scene: 'login', |
|||
}); |
|||
}); |
|||
|
|||
afterAll(() => { |
|||
server?.close(); |
|||
}); |
|||
|
|||
it('should show login form', async () => { |
|||
const historyRef = React.createRef<any>(); |
|||
const rootContainer = render( |
|||
<TestBrowser |
|||
historyRef={historyRef} |
|||
location={{ |
|||
pathname: '/user/login', |
|||
}} |
|||
/>, |
|||
); |
|||
|
|||
await rootContainer.findAllByText('Ant Design'); |
|||
|
|||
act(() => { |
|||
historyRef.current?.push('/user/login'); |
|||
}); |
|||
|
|||
expect(rootContainer.baseElement?.querySelector('.ant-pro-form-login-desc')?.textContent).toBe( |
|||
'Ant Design is the most influential web design specification in Xihu district', |
|||
); |
|||
|
|||
expect(rootContainer.asFragment()).toMatchSnapshot(); |
|||
|
|||
rootContainer.unmount(); |
|||
}); |
|||
|
|||
it('should login success', async () => { |
|||
const historyRef = React.createRef<any>(); |
|||
const rootContainer = render( |
|||
<TestBrowser |
|||
historyRef={historyRef} |
|||
location={{ |
|||
pathname: '/user/login', |
|||
}} |
|||
/>, |
|||
); |
|||
|
|||
await rootContainer.findAllByText('Ant Design'); |
|||
|
|||
const userNameInput = await rootContainer.findByPlaceholderText('Username: admin or user'); |
|||
|
|||
act(() => { |
|||
fireEvent.change(userNameInput, { target: { value: 'admin' } }); |
|||
}); |
|||
|
|||
const passwordInput = await rootContainer.findByPlaceholderText('Password: ant.design'); |
|||
|
|||
act(() => { |
|||
fireEvent.change(passwordInput, { target: { value: 'ant.design' } }); |
|||
}); |
|||
|
|||
await (await rootContainer.findByText('Login')).click(); |
|||
|
|||
// 等待接口返回结果
|
|||
await waitTime(5000); |
|||
|
|||
await rootContainer.findAllByText('Ant Design Pro'); |
|||
|
|||
expect(rootContainer.asFragment()).toMatchSnapshot(); |
|||
|
|||
await waitTime(2000); |
|||
|
|||
rootContainer.unmount(); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,27 @@ |
|||
import { createStyles } from 'antd-style'; |
|||
|
|||
const useStyles = createStyles(({ token }) => { |
|||
return { |
|||
registerResult: { |
|||
width: '800px', |
|||
minHeight: '400px', |
|||
margin: 'auto', |
|||
padding: '80px', |
|||
background: 'none', |
|||
':global': { |
|||
anticon: { |
|||
fontSize: '64px', |
|||
}, |
|||
}, |
|||
title: { marginTop: '32px', fontSize: '20px', lineHeight: '28px' }, |
|||
actions: { |
|||
marginTop: '40px', |
|||
'a + a': { |
|||
marginLeft: '8px', |
|||
}, |
|||
}, |
|||
}, |
|||
}; |
|||
}); |
|||
|
|||
export default useStyles; |
|||
@ -0,0 +1,41 @@ |
|||
import { createStyles } from 'antd-style'; |
|||
|
|||
const useStyles = createStyles(({ token }) => { |
|||
return { |
|||
main: { |
|||
width: '368px', |
|||
margin: '0 auto', |
|||
h3: { marginBottom: '20px', fontSize: '16px' }, |
|||
password: { |
|||
marginBottom: '24px', |
|||
':global': { |
|||
'ant-form-item-explain': { display: 'none' }, |
|||
}, |
|||
}, |
|||
getCaptcha: { display: 'block', width: '100%' }, |
|||
submit: { width: '50%' }, |
|||
login: { float: 'right', lineHeight: token.btnHeightLg }, |
|||
}, |
|||
'success,.warning,.error': { |
|||
transition: 'color 0.3s', |
|||
}, |
|||
success: { |
|||
color: token.successColor, |
|||
}, |
|||
warning: { |
|||
color: token.warningColor, |
|||
}, |
|||
error: { |
|||
color: token.errorColor, |
|||
}, |
|||
'progress-pass > .progress': { |
|||
':global': { |
|||
'ant-progress-bg': { |
|||
backgroundColor: token.warningColor, |
|||
}, |
|||
}, |
|||
}, |
|||
}; |
|||
}); |
|||
|
|||
export default useStyles; |
|||
@ -1,8 +0,0 @@ |
|||
@import (reference) '~antd/es/style/themes/index'; |
|||
|
|||
.pre { |
|||
margin: 12px 0; |
|||
padding: 12px 20px; |
|||
background: @input-bg; |
|||
box-shadow: @card-shadow; |
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
import { createStyles } from 'antd-style'; |
|||
|
|||
const useStyles = createStyles(({ token }) => { |
|||
return { |
|||
avatarHolder: { |
|||
marginBottom: '24px', |
|||
textAlign: 'center', |
|||
'& > img': { width: '104px', height: '104px', marginBottom: '20px' }, |
|||
name: { |
|||
marginBottom: '4px', |
|||
color: token.headingColor, |
|||
fontWeight: '500', |
|||
fontSize: '20px', |
|||
lineHeight: '28px', |
|||
}, |
|||
}, |
|||
detail: { |
|||
p: { |
|||
position: 'relative', |
|||
marginBottom: '8px', |
|||
paddingLeft: '26px', |
|||
'&:last-child': { |
|||
marginBottom: '0', |
|||
}, |
|||
}, |
|||
i: { |
|||
position: 'absolute', |
|||
top: '4px', |
|||
left: '0', |
|||
width: '14px', |
|||
height: '14px', |
|||
}, |
|||
}, |
|||
'tagsTitle,.teamTitle': { |
|||
marginBottom: '12px', |
|||
color: token.headingColor, |
|||
fontWeight: '500', |
|||
}, |
|||
tags: { |
|||
':global': { |
|||
'ant-tag': { |
|||
marginBottom: '8px', |
|||
}, |
|||
}, |
|||
}, |
|||
team: { |
|||
':global': { |
|||
'ant-avatar': { |
|||
marginRight: '12px', |
|||
}, |
|||
}, |
|||
a: { |
|||
display: 'block', |
|||
marginBottom: '24px', |
|||
overflow: 'hidden', |
|||
color: token.textColor, |
|||
whiteSpace: 'nowrap', |
|||
textOverflow: 'ellipsis', |
|||
wordBreak: 'break-all', |
|||
transition: 'color 0.3s', |
|||
'&:hover': { |
|||
color: token.colorPrimary, |
|||
}, |
|||
}, |
|||
}, |
|||
tabsCard: { |
|||
':global': { |
|||
'ant-card-head': { |
|||
padding: '0 16px', |
|||
}, |
|||
}, |
|||
}, |
|||
}; |
|||
}); |
|||
|
|||
export default useStyles; |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue