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 的需求或建议 |
about: 对 Ant Design Pro 的需求或建议 |
||||
title: '👑 [需求]' |
title: '👑 [需求 | Feature]' |
||||
labels: '👑Feature Request' |
labels: '👑 Feature Request' |
||||
assignees: '' |
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 使用的疑问或需要帮助 |
about: 对 Ant Design Pro 使用的疑问或需要帮助 |
||||
title: '🧐[问题]' |
title: '🧐[问题 | question]' |
||||
labels: '🧐question' |
labels: '🧐 question' |
||||
assignees: '' |
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 = { |
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; |
pwa?: boolean; |
||||
logo?: string; |
logo?: string; |
||||
} = { |
} = { |
||||
navTheme: 'light', |
navTheme: 'light', |
||||
// 拂晓蓝
|
// 拂晓蓝
|
||||
primaryColor: '#1890ff', |
colorPrimary: '#1890ff', |
||||
layout: 'mix', |
layout: 'mix', |
||||
contentWidth: 'Fluid', |
contentWidth: 'Fluid', |
||||
fixedHeader: false, |
fixedHeader: false, |
||||
fixSiderbar: true, |
fixSiderbar: true, |
||||
colorWeak: false, |
colorWeak: false, |
||||
title: 'Ant Design Pro', |
title: 'Ant Design Pro', |
||||
pwa: false, |
pwa: true, |
||||
logo: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg', |
logo: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg', |
||||
iconfontUrl: '', |
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; |
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 { Dropdown } from 'antd'; |
||||
|
import type { DropDownProps } from 'antd/es/dropdown'; |
||||
import React from 'react'; |
import React from 'react'; |
||||
|
import { useEmotionCss } from '@ant-design/use-emotion-css'; |
||||
import classNames from 'classnames'; |
import classNames from 'classnames'; |
||||
import styles from './index.less'; |
|
||||
|
|
||||
export type HeaderDropdownProps = { |
export type HeaderDropdownProps = { |
||||
overlayClassName?: string; |
overlayClassName?: string; |
||||
overlay: React.ReactNode | (() => React.ReactNode) | any; |
|
||||
placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight' | 'bottomCenter'; |
placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight' | 'bottomCenter'; |
||||
} & Omit<DropDownProps, 'overlay'>; |
} & Omit<DropDownProps, 'overlay'>; |
||||
|
|
||||
const HeaderDropdown: React.FC<HeaderDropdownProps> = ({ overlayClassName: cls, ...restProps }) => ( |
const HeaderDropdown: React.FC<HeaderDropdownProps> = ({ overlayClassName: cls, ...restProps }) => { |
||||
<Dropdown overlayClassName={classNames(styles.container, 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; |
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,68 +1,31 @@ |
|||||
import { Space } from 'antd'; |
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons'; |
import { QuestionCircleOutlined } from '@ant-design/icons'; |
||||
|
import { SelectLang as UmiSelectLang } from '@umijs/max'; |
||||
import React from 'react'; |
import React from 'react'; |
||||
import { useModel, SelectLang } from 'umi'; |
|
||||
import Avatar from './AvatarDropdown'; |
|
||||
import HeaderSearch from '../HeaderSearch'; |
|
||||
import styles from './index.less'; |
|
||||
import NoticeIconView from '../NoticeIcon'; |
|
||||
|
|
||||
export type SiderTheme = 'light' | 'dark'; |
export type SiderTheme = 'light' | 'dark'; |
||||
|
|
||||
const GlobalHeaderRight: React.FC = () => { |
export const SelectLang = () => { |
||||
const { initialState } = useModel('@@initialState'); |
|
||||
|
|
||||
if (!initialState || !initialState.settings) { |
|
||||
return null; |
|
||||
} |
|
||||
|
|
||||
const { navTheme, layout } = initialState.settings; |
|
||||
let className = styles.right; |
|
||||
|
|
||||
if ((navTheme === 'dark' && layout === 'top') || layout === 'mix') { |
|
||||
className = `${styles.right} ${styles.dark}`; |
|
||||
} |
|
||||
|
|
||||
return ( |
return ( |
||||
<Space className={className}> |
<UmiSelectLang |
||||
<HeaderSearch |
style={{ |
||||
className={`${styles.action} ${styles.search}`} |
padding: 4, |
||||
placeholder="站内搜索" |
}} |
||||
defaultValue="umi ui" |
/> |
||||
options={[ |
|
||||
{ |
|
||||
label: <a href="https://umijs.org/zh/guide/umi-ui.html">umi ui</a>, |
|
||||
value: 'umi ui', |
|
||||
}, |
|
||||
{ |
|
||||
label: <a href="next.ant.design">Ant Design</a>, |
|
||||
value: 'Ant Design', |
|
||||
}, |
|
||||
{ |
|
||||
label: <a href="https://protable.ant.design/">Pro Table</a>, |
|
||||
value: 'Pro Table', |
|
||||
}, |
|
||||
{ |
|
||||
label: <a href="https://prolayout.ant.design/">Pro Layout</a>, |
|
||||
value: 'Pro Layout', |
|
||||
}, |
|
||||
]} // onSearch={value => {
|
|
||||
// console.log('input', value);
|
|
||||
// }}
|
|
||||
/> |
|
||||
<span |
|
||||
className={styles.action} |
|
||||
onClick={() => { |
|
||||
window.open('https://pro.ant.design/docs/getting-started'); |
|
||||
}} |
|
||||
> |
|
||||
<QuestionCircleOutlined /> |
|
||||
</span> |
|
||||
<NoticeIconView /> |
|
||||
<Avatar menu /> |
|
||||
<SelectLang className={styles.action} /> |
|
||||
</Space> |
|
||||
); |
); |
||||
}; |
}; |
||||
|
|
||||
export default GlobalHeaderRight; |
export const Question = () => { |
||||
|
return ( |
||||
|
<div |
||||
|
style={{ |
||||
|
display: 'flex', |
||||
|
height: 26, |
||||
|
}} |
||||
|
onClick={() => { |
||||
|
window.open('https://pro.ant.design/docs/getting-started'); |
||||
|
}} |
||||
|
> |
||||
|
<QuestionCircleOutlined /> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|||||
@ -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