diff --git a/.gitignore b/.gitignore
index fa8763cc..0b824a2b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,8 @@ __pycache__/
backend/.pytest_cache/
# Local secrets and generated artifacts
+.env.deploytmp
+storage/
backend/.env
backend/storage/
backend/tmp_migration_check.db
diff --git a/frontend/src/app/admin/page.tsx b/frontend/src/app/admin/page.tsx
index 2b4282f7..72d7f732 100644
--- a/frontend/src/app/admin/page.tsx
+++ b/frontend/src/app/admin/page.tsx
@@ -1,4 +1,10 @@
+"use client";
+
import Link from "next/link";
+import { useEffect } from "react";
+import { useRouter } from "next/navigation";
+import { Result } from "antd";
+import { useAuthStore } from "@/stores/auth.store";
const adminSections = [
{
@@ -64,6 +70,25 @@ const adminSections = [
];
export default function AdminPage() {
+ const router = useRouter();
+ const currentUser = useAuthStore((state) => state.currentUser);
+
+ useEffect(() => {
+ if (currentUser && currentUser.role !== "admin") {
+ router.replace("/");
+ }
+ }, [currentUser, router]);
+
+ if (currentUser && currentUser.role !== "admin") {
+ return (
+
+
+
+ );
+ }
+
return (
diff --git a/frontend/src/app/contracts/[id]/page.tsx b/frontend/src/app/contracts/[id]/page.tsx
index d6f842e9..96f9852c 100644
--- a/frontend/src/app/contracts/[id]/page.tsx
+++ b/frontend/src/app/contracts/[id]/page.tsx
@@ -357,10 +357,17 @@ export default function GeneratedContractDetailPage({ params }: { params: Promis
return;
}
const previewResponse = await getGeneratedContractPreview(id);
- const lifecycleTaskResponse = await listContractLifecycleTasks(id);
+ let lifecycleTaskResponse: Awaited> | null = null;
+ try {
+ lifecycleTaskResponse = await listContractLifecycleTasks(id);
+ } catch (error: unknown) {
+ if (!(isRequestError(error) && error.status === 404)) {
+ throw error;
+ }
+ }
setDetail(detailResponse.data);
setPreview(previewResponse.code === 0 ? previewResponse.data : null);
- setLifecycleTasks(lifecycleTaskResponse.code === 0 ? lifecycleTaskResponse.data.items : []);
+ setLifecycleTasks(lifecycleTaskResponse?.code === 0 ? lifecycleTaskResponse.data.items : []);
} catch (error: unknown) {
if (isRequestError(error) && error.status === 404) {
setNotFound(true);
diff --git a/frontend/src/app/contracts/page.tsx b/frontend/src/app/contracts/page.tsx
index 6a500d8a..23f97827 100644
--- a/frontend/src/app/contracts/page.tsx
+++ b/frontend/src/app/contracts/page.tsx
@@ -297,6 +297,10 @@ export default function ContractsPage() {
const [lifecycleModalOpen, setLifecycleModalOpen] = useState(false);
const [lifecycleRecord, setLifecycleRecord] = useState(null);
const [lifecycleUpdating, setLifecycleUpdating] = useState(false);
+ const canDownloadContracts = currentUser?.role === "admin";
+ const canOpenReviewDetail = (autoReview?: AutoReviewSummary | null) => Boolean(autoReview?.detail_url);
+ const canOpenReviewChat = (autoReview?: AutoReviewSummary | null) => Boolean(autoReview?.chat_url);
+ const canDownloadReview = (autoReview?: AutoReviewSummary | null) => Boolean(autoReview?.report_download_url);
const clearRestoreQuery = useCallback(() => {
if (!searchParams.get("restore")) {
@@ -599,9 +603,9 @@ export default function ContractsPage() {
return;
}
- const target = autoReview.detail_url ?? (autoReview.review_id ? `/reviews/${autoReview.review_id}` : null);
+ const target = autoReview.detail_url;
if (!target) {
- messageApi.warning("自动审查详情尚未生成");
+ messageApi.warning("自动审查详情尚未生成或当前记录未绑定可访问详情");
return;
}
@@ -813,7 +817,7 @@ export default function ContractsPage() {
{autoReview.current_stage_label ?? autoReview.next_action ?? "-"}
-