Skip to content

Commit 0f210df

Browse files
committed
feat: perf sonarqube
1 parent 4eaf2dc commit 0f210df

31 files changed

Lines changed: 1227 additions & 1039 deletions

File tree

.scannerwork/report-task.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ projectKey=pro-react-admin
22
serverUrl=http://localhost:9000
33
serverVersion=25.2.0.102705
44
dashboardUrl=http://localhost:9000/dashboard?id=pro-react-admin
5-
ceTaskId=8c530982-2933-4350-80bf-097fa898a0a1
6-
ceTaskUrl=http://localhost:9000/api/ce/task?id=8c530982-2933-4350-80bf-097fa898a0a1
5+
ceTaskId=4907c80c-0a94-4652-948b-7cc4af87fae9
6+
ceTaskUrl=http://localhost:9000/api/ce/task?id=4907c80c-0a94-4652-948b-7cc4af87fae9

sonar-project.properties

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,35 @@ sonar.javascript.lcov.reportPaths=coverage/lcov.info
2222
# 覆盖率排除(不想算覆盖率的文件)
2323
sonar.coverage.exclusions=src/index.tsx,src/reportWebVitals.ts,src/setupTests.ts,src/react-app-env.d.ts,**/*.config.js,**/*.config.ts
2424

25+
# ==================== 规则配置 ====================
26+
# 禁用成本过高或不适用的规则
27+
# javascript:S4822 - 不强制要求在try中使用await(UI组件中同步message常见)
28+
# javascript:S134 - 不限制嵌套深度(菜单递归等场景需要深度嵌套)
29+
# jsx-a11y/no-static-element-interactions - 允许非原生元素交互(预加载等场景)
30+
# typescript:S3800 - 允许函数返回不同类型(路由查找等场景需要)
31+
sonar.issue.ignore.multicriteria=e1,e2,e3,e4,propTypesRule
32+
sonar.issue.ignore.multicriteria.e1.ruleKey=javascript:S4822
33+
sonar.issue.ignore.multicriteria.e1.resourceKey=**/*.jsx
34+
sonar.issue.ignore.multicriteria.e2.ruleKey=javascript:S134
35+
sonar.issue.ignore.multicriteria.e2.resourceKey=**/*.jsx
36+
sonar.issue.ignore.multicriteria.e3.ruleKey=jsx-a11y/no-static-element-interactions
37+
sonar.issue.ignore.multicriteria.e3.resourceKey=**/*.jsx
38+
sonar.issue.ignore.multicriteria.e4.ruleKey=typescript:S3800
39+
sonar.issue.ignore.multicriteria.e4.resourceKey=**/*.jsx
40+
2541
# ==================== SonarQube 服务地址 ====================
2642
sonar.host.url=http://localhost:9000
2743

28-
# Token(安全起见,建议不写死在这里,命令行用 -Dsonar.token=xxx 覆盖)
29-
sonar.token=sqa_200141afde0b4a7d4d81b8b6387b980a2f4a3a50
44+
# Token(安全起见:不要写死在仓库里)
45+
# 建议通过命令行或环境变量注入,例如:
46+
# Windows PowerShell: $env:SONAR_TOKEN='xxx'; sonar-scanner.bat -Dsonar.token=$env:SONAR_TOKEN
47+
# Bash: SONAR_TOKEN=xxx sonar-scanner -Dsonar.token=$SONAR_TOKEN
3048

3149
# ==================== 其他推荐配置 ====================
3250
sonar.sourceEncoding=UTF-8
3351
sonar.qualitygate.wait=true # 扫描完后卡住等质量门结果(CI 里很有用)
3452

3553
# ==================== 规则配置 ====================
3654
# 禁用 React components should validate prop types 规则
37-
sonar.issue.ignore.multicriteria=propTypesRule
3855
sonar.issue.ignore.multicriteria.propTypesRule.ruleKey=javascript:S4327
39-
sonar.issue.ignore.multicriteria.propTypesRule.resourceKey=**/*.jsx,**/*.js
56+
sonar.issue.ignore.multicriteria.propTypesRule.resourceKey=**/*.js,**/*.jsx,**/*.ts,**/*.tsx

src/app-hooks/usePermission.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ export const usePermission = () => {
2323
try {
2424
setLoading(true)
2525
const userPermissions = await permissionService.getPermissions()
26-
setPermissions(userPermissions.permissions)
27-
setRoles(userPermissions.roles.map((role) => role.code))
28-
setRoutes(userPermissions.routes)
26+
setPermissions(Array.isArray(userPermissions.permissions) ? userPermissions.permissions : [])
27+
setRoles(Array.isArray(userPermissions.roles) ? userPermissions.roles.map((role) => role.code) : [])
28+
setRoutes(Array.isArray(userPermissions.routes) ? userPermissions.routes : [])
2929
} catch (error) {
3030
console.error('加载权限失败:', error)
3131
} finally {

src/app-hooks/useSafeNavigate/index.js

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,43 +3,56 @@ import { useNavigate } from 'react-router-dom'
33
import { message } from 'antd'
44
import { permissionService } from '@src/service/permissionService'
55

6+
const PUBLIC_PATHS = new Set(['/', '/signin', '/signup'])
7+
8+
const isPublicPath = (path) => PUBLIC_PATHS.has(path)
9+
10+
const safeShowDenied = () => {
11+
Promise.resolve()
12+
.then(() => message.error('您没有权限访问该页面'))
13+
.catch((e) => {
14+
console.warn('显示权限提示失败:', e)
15+
})
16+
}
17+
618
export default function useSafeNavigate() {
719
const navigate = useNavigate()
820
const lastDeniedRef = useRef(null)
921

22+
const navigateTo = (path, replace) => {
23+
if (replace) navigate(path, { replace: true })
24+
else navigate(path)
25+
}
26+
27+
const denyOnce = (path) => {
28+
if (lastDeniedRef.current === path) return
29+
lastDeniedRef.current = path
30+
safeShowDenied()
31+
}
32+
1033
const redirectTo = async (path, options = {}) => {
1134
if (!path) return
35+
1236
// allow common public pages
13-
if (path === '/' || path === '/signin' || path === '/signup') {
14-
if (options.replace) navigate(path, { replace: true })
15-
else navigate(path)
37+
if (isPublicPath(path)) {
38+
navigateTo(path, !!options.replace)
1639
return
1740
}
1841

42+
let ok = false
1943
try {
20-
const ok = await permissionService.canAccessRoute(path, false)
21-
if (!ok) {
22-
if (lastDeniedRef.current !== path) {
23-
lastDeniedRef.current = path
24-
try {
25-
message.error('您没有权限访问该页面')
26-
} catch (e) {
27-
// ignore
28-
}
29-
}
30-
return
31-
}
32-
if (options.replace) navigate(path, { replace: true })
33-
else navigate(path)
44+
ok = await permissionService.canAccessRoute(path, false)
3445
} catch (err) {
3546
console.error('safe navigate error', err)
36-
if (lastDeniedRef.current !== path) {
37-
lastDeniedRef.current = path
38-
try {
39-
message.error('您没有权限访问该页面')
40-
} catch (e) {}
41-
}
47+
ok = false
48+
}
49+
50+
if (!ok) {
51+
denyOnce(path)
52+
return
4253
}
54+
55+
navigateTo(path, !!options.replace)
4356
}
4457

4558
const goBack = () => {

src/components/ErrorBoundary.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ type State = {
1818
}
1919

2020
export default class ErrorBoundary extends React.Component<Props, State> {
21-
static defaultProps = {
21+
static readonly defaultProps = {
2222
showDetails: true,
2323
}
2424
state: State = { hasError: false }

src/components/auth/AuthCallback.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,10 @@ export const AuthCallback: React.FC = () => {
4040
// 获取用户可访问的第一个路由
4141
try {
4242
const routes = await permissionService.getAccessibleRoutes(true)
43-
if (routes && routes.length > 0) {
43+
const safeRoutes = Array.isArray(routes) ? routes : []
44+
if (safeRoutes.length > 0) {
4445
// 跳转到第一个有权限的路由(优先首页)
45-
const targetRoute = routes.includes('/') ? '/' : routes[0]
46+
const targetRoute = safeRoutes.includes('/') ? '/' : safeRoutes[0]
4647
redirectTo(targetRoute)
4748
} else {
4849
// 如果没有权限,跳转到403

src/components/stateless/OneTimePasscode/index.module.less

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,19 @@
1717
width: 3.25rem; /* base size */
1818
height: 3.25rem;
1919
border-radius: 0.75rem;
20-
border: 1px solid rgba(15, 23, 32, 0.06);
21-
background: linear-gradient(180deg, rgba(255, 255, 255, 0.85), rgba(250, 250, 252, 0.85));
20+
border: 1px solid rgb(15 23 32 / 6%);
21+
background: linear-gradient(180deg, rgb(255 255 255 / 85%), rgb(250 250 252 / 85%));
2222
color: var(--otp-fg);
2323
font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, monospace;
2424
font-size: 1.125rem;
2525
font-weight: 600;
2626
text-align: center;
27-
box-shadow: 0 8px 20px rgba(2, 6, 23, 0.06);
27+
box-shadow: 0 8px 20px rgb(2 6 23 / 6%);
2828
transition:
2929
box-shadow 180ms cubic-bezier(0.2, 0.9, 0.2, 1),
3030
transform 160ms ease,
3131
border-color 120ms ease;
3232
appearance: none;
33-
-webkit-appearance: none;
3433
outline: none;
3534
caret-color: var(--otp-primary);
3635
}
@@ -42,8 +41,8 @@
4241
height: 3.5rem;
4342
border-radius: 0.9rem;
4443
font-size: 1.125rem;
45-
box-shadow: 0 10px 28px rgba(2, 6, 23, 0.08);
46-
background: linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(245, 247, 250, 0.92));
44+
box-shadow: 0 10px 28px rgb(2 6 23 / 8%);
45+
background: linear-gradient(180deg, rgb(255 255 255 / 92%), rgb(245 247 250 / 92%));
4746
}
4847

4948
/* explicit variant classes to ensure CSS module exports these keys */
@@ -56,7 +55,7 @@
5655
height: 2.25rem;
5756
border-radius: 0.5rem;
5857
font-size: 0.95rem;
59-
box-shadow: 0 4px 12px rgba(2, 6, 23, 0.04);
58+
box-shadow: 0 4px 12px rgb(2 6 23 / 4%);
6059
}
6160

6261
.compact {
@@ -67,9 +66,9 @@
6766
width: 3rem;
6867
height: 3rem;
6968
border-radius: 0.35rem;
70-
border: 1px solid rgba(15, 23, 32, 0.12);
69+
border: 1px solid rgb(15 23 32 / 12%);
7170
background: linear-gradient(180deg, #fff, #f7fafc);
72-
box-shadow: inset 0 -2px 6px rgba(0, 0, 0, 0.02);
71+
box-shadow: inset 0 -2px 6px rgb(0 0 0 / 2%);
7372
}
7473

7574
.classic {
@@ -80,8 +79,8 @@
8079
transform: translateY(-2px) scale(1.02);
8180
border-color: var(--otp-primary);
8281
box-shadow:
83-
0 10px 30px rgba(2, 6, 23, 0.08),
84-
0 0 0 8px rgba(var(--otp-primary-rgb), 0.12);
82+
0 10px 30px rgb(2 6 23 / 8%),
83+
0 0 0 8px rgb(var(--otp-primary-rgb), 0.12);
8584
}
8685

8786
.input[disabled] {
@@ -90,7 +89,7 @@
9089
}
9190

9291
/* smaller on mobile */
93-
@media (max-width: 480px) {
92+
@media (width <= 480px) {
9493
.input {
9594
width: 2.1rem;
9695
height: 2.1rem;

src/components/stateless/PointerMove/index.jsx

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,45 @@ const PointerMove = () => {
66
const ref = useRef(null)
77
const [coordinates, setCoordinates] = useState({ x: 158, y: 18 })
88

9+
const rafIdRef = useRef(0)
10+
const latestPointerRef = useRef({ x: 0, y: 0 })
11+
912
useEffect(() => {
1013
// check if DOM element referenced by ref has been mounted
1114
if (ref.current) {
12-
const handlePointerMove = ({ clientX, clientY }) => {
15+
const flush = () => {
16+
rafIdRef.current = 0
1317
const element = ref.current
1418
if (!element) return
15-
// calculate x and y coordinates
19+
const { x: clientX, y: clientY } = latestPointerRef.current
20+
// calculate x and y coordinates (keep original semantics)
1621
const x = clientX + (element.offsetLeft + element.offsetWidth)
1722
const y = clientY + (element.offsetTop + element.offsetHeight)
18-
// update state
1923
setCoordinates({ x, y })
2024
}
2125

26+
const handlePointerMove = ({ clientX, clientY }) => {
27+
latestPointerRef.current = { x: clientX, y: clientY }
28+
if (rafIdRef.current) return
29+
rafIdRef.current = requestAnimationFrame(flush)
30+
}
31+
2232
window.addEventListener('pointermove', handlePointerMove)
23-
return () => window.removeEventListener('pointermove', handlePointerMove)
33+
return () => {
34+
window.removeEventListener('pointermove', handlePointerMove)
35+
if (rafIdRef.current) {
36+
cancelAnimationFrame(rafIdRef.current)
37+
rafIdRef.current = 0
38+
}
39+
}
2440
}
2541
}, [])
2642

2743
return (
2844
<motion.div
2945
ref={ref}
3046
className={styles.star}
31-
animate={{ x: coordinates.x, y: coordinates.y, opacity: 1 }}
47+
animate={{ x: coordinates?.x ?? 0, y: coordinates?.y ?? 0, opacity: 1 }}
3248
transition={{
3349
type: 'spring',
3450
}}

0 commit comments

Comments
 (0)