diff --git a/.husky/pre-push b/.husky/pre-push
index e703b2b..1983b94 100644
--- a/.husky/pre-push
+++ b/.husky/pre-push
@@ -1,6 +1,9 @@
# pre-push: 严格检查,阻塞有问题的代码推送到远程
-# 类型检查
+# 类型检查(全项目,因为类型错误可能跨文件传播)
npx tsc --noEmit
-# ESLint 严格检查(不允许 warning)
-npx eslint . --max-warnings=0
+# ESLint 严格检查(仅检查本次推送的变更文件,不阻塞不相关的旧代码)
+CHANGED_FILES=$(git diff --name-only --diff-filter=d @{upstream}...HEAD -- '*.ts' '*.tsx' '*.js' '*.jsx' '*.mjs')
+if [ -n "$CHANGED_FILES" ]; then
+ echo "$CHANGED_FILES" | xargs npx eslint --max-warnings=0
+fi
diff --git a/src/pages/QrCode/__tests__/index.test.tsx b/src/pages/QrCode/__tests__/index.test.tsx
index eca7ae1..dc415bb 100644
--- a/src/pages/QrCode/__tests__/index.test.tsx
+++ b/src/pages/QrCode/__tests__/index.test.tsx
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
-import { fireEvent, render, screen } from '@testing-library/react';
+import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import QrCodePage from '../index';
vi.mock('lucide-react', async (importOriginal) => {
@@ -102,7 +102,7 @@ describe('QrCodePage', () => {
expect(generateButton).not.toBeDisabled();
});
- it('点击生成按钮应该切换到预览态', () => {
+ it('点击生成按钮应该切换到预览态', async () => {
render();
// 输入文本
@@ -114,14 +114,16 @@ describe('QrCodePage', () => {
fireEvent.click(generateButton);
// 验证切换到预览态
- expect(screen.getByText('原始文本')).toBeInTheDocument();
- expect(screen.getByText('编辑')).toBeInTheDocument();
- expect(screen.queryByText('生成二维码')).not.toBeInTheDocument();
- // 预览态应该显示二维码
- expect(screen.getByTestId('qr-code-preview')).toBeInTheDocument();
+ await waitFor(() => {
+ expect(screen.getByText('原始文本')).toBeInTheDocument();
+ expect(screen.getByText('编辑')).toBeInTheDocument();
+ expect(screen.queryByText('生成二维码')).not.toBeInTheDocument();
+ // 预览态应该显示二维码
+ expect(screen.getByTestId('qr-code-preview')).toBeInTheDocument();
+ });
});
- it('预览态应该显示截断的文本', () => {
+ it('预览态应该显示截断的文本', async () => {
render();
// 输入长文本
@@ -135,10 +137,12 @@ describe('QrCodePage', () => {
// 验证显示截断的文本(80字符 + "...")
const truncatedText = longText.slice(0, 80) + '...';
- expect(screen.getByText(truncatedText)).toBeInTheDocument();
+ await waitFor(() => {
+ expect(screen.getByText(truncatedText)).toBeInTheDocument();
+ });
});
- it('预览态应该显示完整的短文本', () => {
+ it('预览态应该显示完整的短文本', async () => {
render();
// 输入短文本
@@ -150,10 +154,12 @@ describe('QrCodePage', () => {
fireEvent.click(screen.getByText('生成二维码'));
// 验证显示完整文本
- expect(screen.getByText(shortText)).toBeInTheDocument();
+ await waitFor(() => {
+ expect(screen.getByText(shortText)).toBeInTheDocument();
+ });
});
- it('点击编辑按钮应该返回输入态', () => {
+ it('点击编辑按钮应该返回输入态', async () => {
render();
// 输入文本并生成
@@ -161,6 +167,11 @@ describe('QrCodePage', () => {
fireEvent.change(textarea, { target: { value: 'https://example.com' } });
fireEvent.click(screen.getByText('生成二维码'));
+ // 等待生成完成
+ await waitFor(() => {
+ expect(screen.getByText('编辑')).toBeInTheDocument();
+ });
+
// 点击编辑按钮
fireEvent.click(screen.getByText('编辑'));
@@ -172,7 +183,7 @@ describe('QrCodePage', () => {
expect(screen.queryByTestId('qr-code-preview')).not.toBeInTheDocument();
});
- it('返回编辑态应该保留上次输入的内容', () => {
+ it('返回编辑态应该保留上次输入的内容', async () => {
render();
// 输入文本并生成
@@ -180,6 +191,11 @@ describe('QrCodePage', () => {
fireEvent.change(textarea, { target: { value: 'https://example.com' } });
fireEvent.click(screen.getByText('生成二维码'));
+ // 等待生成完成
+ await waitFor(() => {
+ expect(screen.getByText('编辑')).toBeInTheDocument();
+ });
+
// 点击编辑按钮
fireEvent.click(screen.getByText('编辑'));
diff --git a/src/pages/QrCode/hooks/useQrCode.ts b/src/pages/QrCode/hooks/useQrCode.ts
index a88b019..8ec8eff 100644
--- a/src/pages/QrCode/hooks/useQrCode.ts
+++ b/src/pages/QrCode/hooks/useQrCode.ts
@@ -165,27 +165,31 @@ export function useQrCode(): QrCodeContextValue {
return;
}
+ // 先设置 loading 状态,让 React 渲染加载动画
setGeneratorState((prev) => ({ ...prev, generating: true, inputError: '' }));
- const qrCodeDataUrl = generateQrCodeDataUrl(text);
+ // 延迟到下一帧生成,确保 loading 状态先被渲染显示
+ setTimeout(() => {
+ const qrCodeDataUrl = generateQrCodeDataUrl(text);
+
+ if (!qrCodeDataUrl) {
+ setGeneratorState((prev) => ({
+ ...prev,
+ generating: false,
+ inputError: t('qrCode:generateError'),
+ }));
+ toast.error(t('qrCode:generateError'));
+ return;
+ }
- if (!qrCodeDataUrl) {
setGeneratorState((prev) => ({
...prev,
+ step: 'preview',
+ savedText: text,
+ qrCodeDataUrl,
generating: false,
- inputError: t('qrCode:generateError'),
}));
- toast.error(t('qrCode:generateError'));
- return;
- }
-
- setGeneratorState((prev) => ({
- ...prev,
- step: 'preview',
- savedText: text,
- qrCodeDataUrl,
- generating: false,
- }));
+ }, 0);
}, [generatorState.textToEncode, t]);
/** 返回编辑态,保留上次输入内容 */
diff --git a/src/utils/contextMenu.ts b/src/utils/contextMenu.ts
index f394619..eac11b9 100644
--- a/src/utils/contextMenu.ts
+++ b/src/utils/contextMenu.ts
@@ -103,8 +103,8 @@ export function parseContextMenuClick(
): ParseResult {
const featureKey = getMenuPageType(menuItemId);
- // 处理图片 URL(右键点击图片时)
- if (info.srcUrl) {
+ // 处理图片 URL(仅 qrCode-image 菜单项,右键点击图片时)
+ if (menuItemId === 'qrCode-image' && info.srcUrl) {
return {
success: true,
data: { featureKey, payload: info.srcUrl },