committed by
GitHub
43 changed files with 976 additions and 1232 deletions
@ -0,0 +1,27 @@ |
|||
name: React Doctor |
|||
|
|||
on: |
|||
pull_request: |
|||
types: [opened, synchronize, reopened, ready_for_review] |
|||
push: |
|||
branches: [master] |
|||
|
|||
permissions: |
|||
contents: read |
|||
pull-requests: write |
|||
issues: write |
|||
statuses: write |
|||
|
|||
concurrency: |
|||
group: react-doctor-${{ github.event.pull_request.number || github.ref }} |
|||
cancel-in-progress: true |
|||
|
|||
jobs: |
|||
react-doctor: |
|||
runs-on: ubuntu-latest |
|||
steps: |
|||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 |
|||
with: |
|||
fetch-depth: 0 |
|||
persist-credentials: false |
|||
- uses: millionco/react-doctor@0b4f4f4bd248a154e64eb508a48347f71154b3f3 # v2 |
|||
@ -0,0 +1,46 @@ |
|||
{ |
|||
"ignore": { |
|||
"files": [ |
|||
"config/**", |
|||
"mock/**", |
|||
"public/**", |
|||
"scripts/**", |
|||
"types/**", |
|||
".worktrees/**", |
|||
"cloudflare-worker/**", |
|||
"src/.umi*/**", |
|||
"src/services/**", |
|||
"src/service-worker.js", |
|||
"**/_mock.ts", |
|||
"**/*.md", |
|||
"**/*.less", |
|||
"**/*.css" |
|||
], |
|||
"overrides": [ |
|||
{ |
|||
"files": [ |
|||
"src/global.tsx", |
|||
"src/global.style.ts", |
|||
"src/loading.tsx", |
|||
"src/locales/**", |
|||
"src/pages/**", |
|||
"tests/__mocks__/**" |
|||
], |
|||
"rules": ["deslop/unused-file"] |
|||
}, |
|||
{ |
|||
"files": ["package.json"], |
|||
"rules": ["deslop/unused-dev-dependency"] |
|||
}, |
|||
{ |
|||
"files": [ |
|||
"src/pages/form/advanced-form/index.tsx", |
|||
"src/pages/table-list/index.tsx" |
|||
], |
|||
"rules": ["react-doctor/no-giant-component"] |
|||
} |
|||
] |
|||
}, |
|||
"blocking": "error", |
|||
"adoptExistingLintConfig": true |
|||
} |
|||
File diff suppressed because it is too large
@ -1,21 +0,0 @@ |
|||
{ |
|||
"ignore": { |
|||
"files": [ |
|||
"config/**", |
|||
"mock/**", |
|||
"public/**", |
|||
"scripts/**", |
|||
"types/**", |
|||
"cloudflare-worker/**", |
|||
"src/.umi*/**", |
|||
"src/services/**", |
|||
"src/service-worker.js", |
|||
"**/_mock.ts", |
|||
"**/*.md", |
|||
"**/*.less", |
|||
"**/*.css" |
|||
] |
|||
}, |
|||
"failOn": "error", |
|||
"adoptExistingLintConfig": true |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
import { CheckOutlined, GlobalOutlined } from '@ant-design/icons'; |
|||
import { getAllLocales, getLocale, setLocale } from '@umijs/max'; |
|||
import type { MenuProps } from 'antd'; |
|||
import { Button } from 'antd'; |
|||
import { useMemo } from 'react'; |
|||
import HeaderDropdown from '../HeaderDropdown'; |
|||
import useHeaderActionStyles from './style'; |
|||
|
|||
const localeLabelMap: Record<string, { emoji: string; label: string }> = { |
|||
'zh-CN': { emoji: '🇨🇳', label: '简体中文' }, |
|||
'zh-TW': { emoji: '🇭🇰', label: '繁體中文' }, |
|||
'en-US': { emoji: '🇺🇸', label: 'English' }, |
|||
'ja-JP': { emoji: '🇯🇵', label: '日本語' }, |
|||
'pt-BR': { emoji: '🇧🇷', label: 'Português' }, |
|||
'id-ID': { emoji: '🇮🇩', label: 'Bahasa Indonesia' }, |
|||
'fa-IR': { emoji: '🇮🇷', label: 'فارسی' }, |
|||
'bn-BD': { emoji: '🇧🇩', label: 'বাংলা' }, |
|||
}; |
|||
|
|||
const onLangClick: MenuProps['onClick'] = ({ key }) => { |
|||
if (key.startsWith('lang-')) { |
|||
setLocale(key.replace('lang-', ''), false); |
|||
} |
|||
}; |
|||
|
|||
export const LangDropdown: React.FC = () => { |
|||
const { styles } = useHeaderActionStyles(); |
|||
const allLocales = useMemo(() => getAllLocales(), []); |
|||
const currentLocale = getLocale(); |
|||
const supportLocales = allLocales.filter((l) => l in localeLabelMap); |
|||
|
|||
if (supportLocales.length <= 1) { |
|||
return null; |
|||
} |
|||
|
|||
const langItems: MenuProps['items'] = supportLocales.map((locale) => ({ |
|||
key: `lang-${locale}`, |
|||
icon: |
|||
locale === currentLocale ? ( |
|||
<CheckOutlined style={{ color: '#52c41a' }} /> |
|||
) : ( |
|||
<span style={{ display: 'inline-block', width: 14 }} /> |
|||
), |
|||
label: `${localeLabelMap[locale]?.emoji ?? ''} ${localeLabelMap[locale]?.label ?? locale}`, |
|||
})); |
|||
|
|||
return ( |
|||
<HeaderDropdown |
|||
placement="bottomRight" |
|||
arrow |
|||
menu={{ |
|||
selectedKeys: [`lang-${currentLocale}`], |
|||
onClick: onLangClick, |
|||
items: langItems, |
|||
style: { minWidth: 180 }, |
|||
}} |
|||
> |
|||
<Button type="text" className={styles.action} aria-label="语言切换"> |
|||
<GlobalOutlined /> |
|||
</Button> |
|||
</HeaderDropdown> |
|||
); |
|||
}; |
|||
@ -0,0 +1,36 @@ |
|||
import { ForkOutlined } from '@ant-design/icons'; |
|||
import type { MenuProps } from 'antd'; |
|||
import { Button } from 'antd'; |
|||
import HeaderDropdown from '../HeaderDropdown'; |
|||
import useHeaderActionStyles from './style'; |
|||
|
|||
const versionItems: MenuProps['items'] = [ |
|||
{ key: 'https://v5.pro.ant.design', label: 'v5' }, |
|||
{ key: 'https://v4.pro.ant.design', label: 'v4' }, |
|||
{ key: 'https://v2.pro.ant.design', label: 'v2' }, |
|||
{ key: 'https://v1.pro.ant.design', label: 'v1' }, |
|||
]; |
|||
|
|||
const onVersionClick: MenuProps['onClick'] = ({ key }) => { |
|||
window.open(key, '_blank', 'noopener,noreferrer'); |
|||
}; |
|||
|
|||
export const VersionDropdown: React.FC = () => { |
|||
const { styles } = useHeaderActionStyles(); |
|||
return ( |
|||
<HeaderDropdown |
|||
placement="bottomRight" |
|||
arrow |
|||
menu={{ |
|||
selectedKeys: [], |
|||
onClick: onVersionClick, |
|||
items: versionItems, |
|||
style: { minWidth: 100 }, |
|||
}} |
|||
> |
|||
<Button type="text" className={styles.action} aria-label="历史版本"> |
|||
<ForkOutlined /> |
|||
</Button> |
|||
</HeaderDropdown> |
|||
); |
|||
}; |
|||
@ -0,0 +1,16 @@ |
|||
import { createStyles } from 'antd-style'; |
|||
|
|||
const useHeaderActionStyles = createStyles(({ token, css }) => ({ |
|||
action: css` |
|||
display: inline-flex !important; |
|||
align-items: center !important; |
|||
justify-content: center !important; |
|||
height: 36px !important; |
|||
min-width: 36px; |
|||
padding-inline: 8px !important; |
|||
padding-block: 0 !important; |
|||
border-radius: ${token.borderRadius}px !important; |
|||
`,
|
|||
})); |
|||
|
|||
export default useHeaderActionStyles; |
|||
@ -1,11 +0,0 @@ |
|||
import { formatYuan } from '@/utils/format'; |
|||
import ChartCard from './ChartCard'; |
|||
import Field from './Field'; |
|||
|
|||
const Charts = { |
|||
yuan: formatYuan, |
|||
ChartCard, |
|||
Field, |
|||
}; |
|||
|
|||
export { ChartCard, Charts as default, Field, formatYuan as yuan }; |
|||
@ -1,17 +1,7 @@ |
|||
import { useEffect, useRef } from 'react'; |
|||
import { yuan } from '../components/Charts'; |
|||
import { formatYuan } from '@/utils/format'; |
|||
|
|||
/** 减少使用 dangerouslySetInnerHTML */ |
|||
const Yuan: React.FC<{ children: string | number }> = ({ children }) => { |
|||
const spanRef = useRef<HTMLSpanElement>(null); |
|||
|
|||
useEffect(() => { |
|||
if (spanRef.current) { |
|||
spanRef.current.innerHTML = yuan(children); |
|||
} |
|||
}, [children]); |
|||
|
|||
return <span ref={spanRef} />; |
|||
return <span>{formatYuan(children)}</span>; |
|||
}; |
|||
|
|||
export default Yuan; |
|||
|
|||
Loading…
Reference in new issue