Skip to content

Commit 75545ae

Browse files
authored
Merge pull request #22 from osoulmate/main
fix
2 parents 6c65ddf + 3793393 commit 75545ae

10 files changed

Lines changed: 333 additions & 129 deletions

File tree

src/actions/document.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ export async function fetchDocumentPreviewByDocIdAction(docId: string) {
233233
documentId: doc?.id,
234234
url: doc?.url,
235235
mimeType: doc?.mimeType,
236-
name: doc?.originalName,
236+
name: doc?.name,
237237
};
238238
} catch (error) {
239239
logger.error('获取预览信息失败:', error);

src/actions/project.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,14 @@ export interface CreateProjectData {
3333
};
3434
}
3535

36-
export async function fetchUserProjectsAction() {
36+
export async function fetchUserProjectsAction(page: number = 1, pageSize: number = 10) {
3737
noStore();
3838
const session = await auth();
39-
if (!session?.user?.id) return [];
39+
// 这里如果没登录,返回空结构而不是空数组,保持类型一致
40+
if (!session?.user?.id) return { data: [], total: 0 };
4041

41-
return findProjectsByUserIdDB(session.user.id);
42+
const result = await findProjectsByUserIdDB(session.user.id, page, pageSize);
43+
return result || { data: [], total: 0 };
4244
}
4345

4446
export async function createNewProjectAction(data: CreateProjectData) {

src/app/(app)/dashboard/projects/[id]/init/page.tsx

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export default function ProjectInitPage() {
4949
const [terms, setTerms] = useState<Array<{ term: string; count: number; score?: number }>>([]);
5050
const [previewHtml, setPreviewHtml] = useState<string>('');
5151
// 横向步骤视图无需折叠面板
52-
52+
const [isNavigatingToIDE, setIsNavigatingToIDE] = useState(false);
5353
const overall = Math.round((segPct + termPct) / 2);
5454

5555
useEffect(() => {
@@ -656,7 +656,18 @@ export default function ProjectInitPage() {
656656
>
657657
{t('gotoDict')}
658658
</Button>
659-
<Button onClick={() => router.push(`/ide/${projectId}`)}>
659+
<Button
660+
disabled={isNavigatingToIDE} // 跳转过程中禁用按钮
661+
onClick={() => {
662+
setIsNavigatingToIDE(true); // 开启遮罩
663+
// 使用 setTimeout 稍微延迟跳转,确保 React 先渲染出遮罩(可选,通常直接 push 也可以)
664+
requestAnimationFrame(() => {
665+
router.push(`/ide/${projectId}`);
666+
});
667+
}}
668+
>
669+
{/* 可选:在按钮内部也显示一个小圈圈 */}
670+
{isNavigatingToIDE && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
660671
{t('gotoIDE')}
661672
</Button>
662673
</>
@@ -801,6 +812,21 @@ export default function ProjectInitPage() {
801812
</div>
802813
</div>
803814
)}
815+
{isNavigatingToIDE && (
816+
<div className="fixed inset-0 z-[60] flex flex-col items-center justify-center gap-4 bg-black/50 backdrop-blur-sm transition-all duration-300">
817+
<div className="flex flex-col items-center justify-center gap-3 rounded-xl bg-white p-8 shadow-2xl dark:bg-gray-900">
818+
<Loader2 className="h-10 w-10 animate-spin text-primary" />
819+
<div className="text-center">
820+
<p className="text-base font-semibold text-gray-900 dark:text-gray-100">
821+
{t('preparingIDE') || '正在准备编辑器环境...'}
822+
</p>
823+
<p className="mt-1 text-xs text-muted-foreground">
824+
请稍候,正在加载项目资源
825+
</p>
826+
</div>
827+
</div>
828+
</div>
829+
)}
804830
</div>
805831
);
806832
}
Lines changed: 90 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,56 @@
11
'use client';
2-
import { FolderIcon } from 'lucide-react';
3-
import { CreateProjectDialog } from '../components/create-project-dialog';
4-
import ProjectList from '../components/project-list';
5-
import { ScrollArea } from '@/components/ui/scroll-area';
62
import { fetchUserProjectsAction } from '@/actions/project';
3+
import { Button } from '@/components/ui/button';
74
import type { Project } from '@prisma/client';
8-
import { useEffect, useState } from 'react';
5+
import { ChevronLeft, ChevronRight, FolderIcon } from 'lucide-react';
96
import { useTranslations } from 'next-intl';
7+
import { useEffect, useState } from 'react';
8+
import { CreateProjectDialog } from '../components/create-project-dialog';
9+
import ProjectList from '../components/project-list';
10+
11+
const PAGE_SIZE = 10;
1012

1113
const ProjectListPage = () => {
14+
// 状态定义
1215
const [projects, setProjects] = useState<Project[]>([]);
16+
const [totalCount, setTotalCount] = useState(0); // 新增:总条数
17+
const [currentPage, setCurrentPage] = useState(1);
18+
1319
const t = useTranslations('Projects');
1420

21+
// 计算总页数
22+
const totalPages = Math.ceil(totalCount / PAGE_SIZE) || 1;
23+
24+
// 加载数据的核心函数
25+
const loadProjects = async () => {
26+
// 传入 currentPage 和 PAGE_SIZE
27+
const { data, total } = await fetchUserProjectsAction(currentPage, PAGE_SIZE);
28+
setProjects(data as any);
29+
setTotalCount(total);
30+
};
31+
1532
useEffect(() => {
1633
let mounted = true;
17-
const loadProjects = async () => {
18-
const data = await fetchUserProjectsAction();
19-
if (mounted) setProjects(data as any);
20-
};
34+
35+
// 初始加载
2136
loadProjects();
22-
const timer = setInterval(loadProjects, 5000); // 5s 轮询状态
37+
38+
// 轮询 (注意:轮询应该请求当前所在的页码)
39+
const timer = setInterval(() => {
40+
if (mounted) loadProjects();
41+
}, 5000);
42+
2343
return () => {
2444
mounted = false;
2545
clearInterval(timer);
2646
};
27-
}, []);
47+
// 依赖项加入 currentPage,当页码改变时,自动触发重新加载
48+
}, [currentPage]);
49+
50+
// 翻页处理
51+
const handlePrevPage = () => setCurrentPage(p => Math.max(1, p - 1));
52+
const handleNextPage = () => setCurrentPage(p => Math.min(totalPages, p + 1));
53+
2854
return (
2955
<>
3056
<div className="ml-2 flex items-center justify-between">
@@ -33,37 +59,71 @@ const ProjectListPage = () => {
3359
</h2>
3460
<div className="text-light flex items-center gap-2 text-xs text-muted-foreground">
3561
<FolderIcon size="16" />
36-
<p className="text-light flex items-center">{projects.length}/10</p>
62+
{/* 显示当前页范围 / 总数 */}
63+
<p className="text-light flex items-center">
64+
{totalCount > 0
65+
? `${(currentPage - 1) * PAGE_SIZE + 1} - ${Math.min(currentPage * PAGE_SIZE, totalCount)} of ${totalCount}`
66+
: '0 of 0'
67+
}
68+
</p>
3769
<div className="w-26">
3870
<CreateProjectDialog
39-
onCreated={p => {
40-
setProjects(prev => {
41-
const map = new Map<string, Project>();
42-
for (const item of prev as any)
43-
map.set((item as any).id, item as any);
44-
map.set((p as any).id, p as any);
45-
const arr = Array.from(map.values());
46-
// 按创建时间倒序,若无 date 则保持现有顺序
47-
arr.sort(
48-
(a: any, b: any) =>
49-
(new Date(b.date).getTime() || 0) -
50-
(new Date(a.date).getTime() || 0)
51-
);
52-
return arr as any;
53-
});
71+
onCreated={() => {
72+
// 创建成功后:
73+
// 1. 如果不在第一页,跳转回第一页查看最新项目
74+
// 2. 重新加载数据
75+
if (currentPage !== 1) {
76+
setCurrentPage(1);
77+
} else {
78+
loadProjects();
79+
}
5480
}}
5581
/>
5682
</div>
5783
</div>
5884
</div>
59-
<div className="flex flex-col">
85+
86+
<div className="flex flex-col gap-4">
87+
{/* 直接传递后端返回的数据,不需要前端切片 */}
6088
<ProjectList
6189
projects={projects}
62-
onDeleted={id => setProjects(prev => prev.filter(p => p.id !== id))}
90+
onDeleted={async (id) => {
91+
// 乐观更新:先在界面移除,提升体验
92+
setProjects(prev => prev.filter(p => p.id !== id));
93+
setTotalCount(prev => Math.max(0, prev - 1));
94+
95+
// 可选:删除后重新 fetch 以确保分页数据正确(例如从第二页补位数据上来)
96+
//await loadProjects();
97+
}}
6398
/>
99+
100+
{/* 分页条 */}
101+
{totalCount > 0 && (
102+
<div className="flex items-center justify-end gap-2 px-2 py-4">
103+
<div className="text-sm text-muted-foreground mr-2">
104+
Page {currentPage} of {totalPages}
105+
</div>
106+
<Button
107+
variant="outline"
108+
size="icon"
109+
onClick={handlePrevPage}
110+
disabled={currentPage === 1}
111+
>
112+
<ChevronLeft className="h-4 w-4" />
113+
</Button>
114+
<Button
115+
variant="outline"
116+
size="icon"
117+
onClick={handleNextPage}
118+
disabled={currentPage === totalPages}
119+
>
120+
<ChevronRight className="h-4 w-4" />
121+
</Button>
122+
</div>
123+
)}
64124
</div>
65125
</>
66126
);
67127
};
68128

69-
export default ProjectListPage;
129+
export default ProjectListPage;

src/app/(app)/ide/[id]/components/menu/action-section.tsx

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,11 @@ export function ActionSection() {
8989
| 'translate_batch'
9090
| 'evaluate_single'
9191
| 'evaluate_batch'
92-
| 'post_edit'
92+
| 'post_edit_single'
93+
| 'post_edit_batch'
94+
| 'signoff_single'
95+
| 'signoff_batch'
96+
| 'complete_batch'
9397
>('idle');
9498
const { activeDocumentItem, setActiveDocumentItem } = useActiveDocumentItem();
9599
const { settings } = useUserSettings();
@@ -163,11 +167,20 @@ export function ActionSection() {
163167
toast.error('没有激活的文档项,无法进行预翻译');
164168
return;
165169
}
166-
170+
let currentItemStatus = activeDocumentItem?.status;
171+
// 从 explorerTabs 中查找最新的状态
172+
const tabs = explorerTabs?.documentTabs ?? [];
173+
for (const tab of tabs) {
174+
const item = (tab.items ?? []).find((it: any) => it.id === id);
175+
if (item) {
176+
currentItemStatus = item.status;
177+
break;
178+
}
179+
}
167180
// 检查当前状态是否允许质检(应该在 NOT_STARTED 状态)
168-
if (activeDocumentItem?.status !== 'NOT_STARTED') {
181+
if (currentItemStatus !== 'NOT_STARTED') {
169182
toast.error(
170-
`当前分段状态为 ${activeDocumentItem?.status || '未知'},无法进行预翻译`
183+
`当前分段状态为 ${currentItemStatus || '未知'},无法进行预翻译。仅在未开始阶段允许预翻译`
171184
);
172185
return;
173186
}
@@ -424,11 +437,20 @@ export function ActionSection() {
424437
toast.error('没有激活的文档项,无法进行质检');
425438
return;
426439
}
427-
440+
let currentItemStatus = activeDocumentItem?.status;
441+
// 从 explorerTabs 中查找最新的状态
442+
const tabs = explorerTabs?.documentTabs ?? [];
443+
for (const tab of tabs) {
444+
const item = (tab.items ?? []).find((it: any) => it.id === id);
445+
if (item) {
446+
currentItemStatus = item.status;
447+
break;
448+
}
449+
}
428450
// 检查当前状态是否允许质检(应该在 MT_REVIEW 状态)
429-
if (activeDocumentItem?.status !== 'MT_REVIEW') {
451+
if (currentItemStatus !== 'MT_REVIEW') {
430452
toast.error(
431-
`当前分段状态为 ${activeDocumentItem?.status || '未知'},无法进行质检。仅在预翻译复核阶段允许质检`
453+
`当前分段状态为 ${currentItemStatus || '未知'},无法进行质检。仅在预翻译复核阶段允许质检`
432454
);
433455
return;
434456
}
@@ -717,7 +739,7 @@ export function ActionSection() {
717739
setBatchProgress(0);
718740
setBatchOpen(true);
719741
setIsRunning(true);
720-
setCurrentOperation('post_edit');
742+
setCurrentOperation('signoff_batch');
721743

722744
let done = 0;
723745
for (const it of itemsToSignoff) {
@@ -921,7 +943,7 @@ export function ActionSection() {
921943

922944
// 3) 标记译后→签发→完成(当前页签)- 只处理需要推进的分段
923945
setProgressTitle('批量完成中');
924-
setCurrentOperation('post_edit');
946+
setCurrentOperation('complete_batch');
925947
let done = 0;
926948

927949
for (const it of itemsToProcess) {
@@ -1231,7 +1253,9 @@ export function ActionSection() {
12311253
progressPercent={batchProgress}
12321254
/>
12331255
<PostEditMenu
1234-
isTranslating={isRunning && currentOperation === 'post_edit'}
1256+
isTranslating={isRunning &&
1257+
(currentOperation === 'post_edit_single' ||
1258+
currentOperation === 'post_edit_batch')}
12351259
// 修复:只允许 QA_REVIEW 状态的分段进入译后编辑
12361260
canEnter={(explorerTabs?.documentTabs ?? [])
12371261
.flatMap(t => t.items ?? [])
@@ -1262,12 +1286,12 @@ export function ActionSection() {
12621286
}
12631287
if (currentItemStatus !== 'QA_REVIEW') {
12641288
toast.error(
1265-
`当前分段状态为 ${currentItemStatus || '未知'},无法进入译后编辑。需要质检复核通过状态`
1289+
`当前分段状态为 ${currentItemStatus || '未知'},无法进入译后编辑。仅质检复核通过状态可以进行译后编辑`
12661290
);
12671291
return;
12681292
}
12691293

1270-
setCurrentOperation('post_edit');
1294+
setCurrentOperation('post_edit_single');
12711295
setIsRunning(true);
12721296

12731297
try {
@@ -1319,7 +1343,7 @@ export function ActionSection() {
13191343
}
13201344

13211345
setIsRunning(true);
1322-
setCurrentOperation('post_edit');
1346+
setCurrentOperation('post_edit_batch');
13231347
setProgressTitle('批量译后编辑中');
13241348
setBatchProgress(0);
13251349
setBatchOpen(true);
@@ -1385,8 +1409,25 @@ export function ActionSection() {
13851409
try {
13861410
const id = (activeDocumentItem as any)?.id;
13871411
if (!id) return;
1412+
// 检查当前分段状态是否为 POST_EDIT_REVIEW
1413+
let currentItemStatus = activeDocumentItem?.status;
1414+
// 从 explorerTabs 中查找最新的状态
1415+
const tabs = explorerTabs?.documentTabs ?? [];
1416+
for (const tab of tabs) {
1417+
const item = (tab.items ?? []).find((it: any) => it.id === id);
1418+
if (item) {
1419+
currentItemStatus = item.status;
1420+
break;
1421+
}
1422+
}
1423+
if (currentItemStatus !== 'POST_EDIT_REVIEW') {
1424+
toast.error(
1425+
`当前分段状态为 ${currentItemStatus || '未知'},无法签发。仅译后编辑复核状态可以进行签发`
1426+
);
1427+
return;
1428+
}
13881429
setIsRunning(true);
1389-
setCurrentOperation('post_edit');
1430+
setCurrentOperation('signoff_single');
13901431
try {
13911432
await updateDocItemStatusAction(id, 'SIGN_OFF');
13921433
// 只记录 SIGN_OFF 事件,COMPLETED 事件应该在后续流程中记录
@@ -1429,7 +1470,7 @@ export function ActionSection() {
14291470
setBatchProgress(0);
14301471
setBatchOpen(true);
14311472
setIsRunning(true);
1432-
setCurrentOperation('post_edit');
1473+
setCurrentOperation('signoff_batch');
14331474

14341475
let done = 0;
14351476
const total = items.length;

0 commit comments

Comments
 (0)