fix url error

This commit is contained in:
Tsuki 2025-08-18 00:03:16 +08:00
parent 23905d33fd
commit 9cb46de3e1
16 changed files with 189 additions and 96 deletions

View File

@ -1,36 +0,0 @@
import Link from "next/link";
import { Home, Search } from "lucide-react";
export default function NotFound() {
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 flex items-center justify-center">
<div className="text-center">
<div className="text-6xl font-bold text-gray-300 dark:text-gray-700 mb-4">404</div>
<h1 className="text-2xl font-semibold text-gray-900 dark:text-white mb-4">
</h1>
<p className="text-gray-600 dark:text-gray-400 mb-8 max-w-md">
访
</p>
<div className="flex gap-4 justify-center">
<Link
href="/"
className="flex items-center gap-2 px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
<Home className="w-4 h-4" />
</Link>
<Link
href="/example"
className="flex items-center gap-2 px-6 py-3 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
>
<Search className="w-4 h-4" />
</Link>
</div>
</div>
</div>
);
}

View File

@ -1,39 +0,0 @@
import { fetchPage } from "@/lib/fetchers";
import { RenderBlock } from "@/components/registry";
import { notFound } from "next/navigation";
export const revalidate = 60; // ISR: 60秒后重新验证
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const page = await fetchPage(slug);
if (!page) {
return notFound();
}
return (
<main className="min-h-screen bg-gray-50 dark:bg-gray-900">
<div className="max-w-6xl mx-auto px-4 py-8">
{/* 页面标题 */}
<header className="mb-8">
<h1 className="text-3xl md:text-4xl font-bold text-gray-900 dark:text-white">
{page.title}
</h1>
{page.description && (
<p className="mt-2 text-lg text-gray-600 dark:text-gray-400">
{page.description}
</p>
)}
</header>
{/* 渲染所有块 */}
<div className="space-y-8">
{page.blocks.map((block) => (
<RenderBlock key={(block as any).id} block={block} />
))}
</div>
</div>
</main>
);
}

View File

@ -32,8 +32,8 @@ const schema = z.object({
status: z.enum(["draft", "published", "archived"]),
metaTitle: z.string().optional(),
metaDescription: z.string().optional(),
isFeatured: z.boolean().default(false),
isActive: z.boolean().default(true),
isFeatured: z.boolean(),
isActive: z.boolean(),
})
export default function CreateBlogForm() {

View File

@ -30,7 +30,7 @@ const schema = z.object({
color: z.string().optional(),
icon: z.string().optional(),
parentId: z.string().optional(),
isActive: z.boolean().default(true),
isActive: z.boolean(),
})
export default function CreateCategoryForm() {

View File

@ -447,14 +447,14 @@ export function RolePermissionManagement() {
const roles = rolesData?.roles?.items || []
const permissions = permissionsData?.permissions?.items || []
const activeRoles = roles.filter(r => r.isActive)
const currentRole = selectedRole ? roles.find(r => r.id === selectedRole) : null
const activeRoles = roles.filter((r: any) => r.isActive)
const currentRole = selectedRole ? roles.find((r: any) => r.id === selectedRole) : null
return (
<div className="space-y-6">
{/* 角色概览卡片 */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{activeRoles.map((role) => (
{activeRoles.map((role: any) => (
<Card
key={role.id}
className={`cursor-pointer transition-colors ${
@ -530,7 +530,7 @@ export function RolePermissionManagement() {
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
{currentRole.permissions.map((permission) => (
{currentRole.permissions.map((permission: any) => (
<Card key={permission.id} className="p-3">
<div className="space-y-2">
<div className="flex items-center gap-2">

View File

@ -27,7 +27,7 @@ const schema = z.object({
slug: z.string().min(1, "别名不能为空"),
description: z.string().optional(),
color: z.string().optional(),
isActive: z.boolean().default(true),
isActive: z.boolean(),
})
export default function CreateTagForm() {

View File

@ -39,6 +39,7 @@ export async function POST(request: NextRequest) {
} catch (error) {
console.log(process.env.GRAPHQL_BACKEND_URL)
console.error('Login error:', error);
return NextResponse.json(

View File

@ -12,7 +12,7 @@ const GET_CONFIGS = gql`
`
export async function GET(request: NextRequest) {
const client = new GraphQLClient(process.env.GRAPHQL_URL || 'http://localhost:3050/graphql');
const client = new GraphQLClient(process.env.GRAPHQL_BACKEND_URL || 'http://localhost:3050/graphql');
try {
const data: any = await client.request(GET_CONFIGS);
return NextResponse.json(data.siteConfigs);

View File

@ -164,7 +164,7 @@ export const Timeline: React.FC<Props> = React.memo(({
},
onDateChange: async (date: Date) => {
const datestr = formatInTimeZone(date, 'UTC', 'yyyyMMddHHmmss')
const url_base = process.env.GRAPHQL_BACKEND_URL || 'http://localhost:3050'
const url_base = process.env.GRAPHQL_BACKEND_URL?.replace('/graphql', '') || 'http://localhost:3050'
const response = await fetch(`${url_base}/api/v1/data/nearest?datetime=${datestr}&area=cn`)
setTimelineTime(date)

View File

@ -58,7 +58,7 @@ export function MapComponent({
useEffect(() => {
if (!isMapReady || !currentDatetime) return;
const utc_time_str = formatInTimeZone(currentDatetime, 'UTC', 'yyyyMMddHHmmss')
const new_url_prefix = process.env.GRAPHQL_BACKEND_URL || 'http://localhost:3050'
const new_url_prefix = process.env.GRAPHQL_BACKEND_URL?.replace('/graphql', '') || 'http://localhost:3050'
const new_url = `${new_url_prefix}/api/v1/data?datetime=${utc_time_str}&area=cn`
fetchRadarTile(new_url)
}, [currentDatetime, isMapReady])

View File

@ -248,7 +248,7 @@ export function EnhancedSimpleEditor({ content, onChange }: EnhancedSimpleEditor
// Only update if content actually changed to avoid infinite loops
if (JSON.stringify(currentContent) !== JSON.stringify(newContent)) {
editor.commands.setContent(newContent, false)
editor.commands.setContent(newContent, { emitUpdate: false })
}
}
}, [editor, content])

View File

@ -8,11 +8,12 @@ import { createClient } from 'graphql-ws';
const TOKEN_KEY = 'auth_token';
const httpLink = createHttpLink({
uri: "http://127.0.0.1:3050/graphql",
uri: process.env.GRAPHQL_BACKEND_URL || 'http://localhost:3050/graphql',
});
const wsLink = new GraphQLWsLink(createClient({
url: "ws://127.0.0.1:3050/ws",
// url: "ws://127.0.0.1:3050/ws",
url: process.env.GRAPHQL_BACKEND_URL?.replace('/graphql', '/ws')?.replace('http://', 'ws://') || 'ws://localhost:3050/ws',
}));
const authLink = setContext((_, { headers }) => {

View File

@ -63,8 +63,32 @@ export async function fetchPage(slug: string, jwt?: string): Promise<PageData |
}
export async function getSiteConfigs() {
const baseUrl = process.env.GRAPHQL_BACKEND_URL || 'http://localhost:3000';
const siteConfigs = await fetch(`${baseUrl}/api/site`);
try {
const baseUrl = process.env.NEXTAUTH_URL || 'http://localhost:3000';
const siteConfigs = await fetch(`${baseUrl}/api/site`, {
headers: {
'Content-Type': 'application/json',
},
// Add timeout to prevent hanging during build
signal: AbortSignal.timeout(5000)
});
if (!siteConfigs.ok) {
console.warn(`Failed to fetch site configs: ${siteConfigs.status} ${siteConfigs.statusText}`);
return getDefaultSiteConfigs();
}
const data = await siteConfigs.json();
return data;
} catch (error) {
console.warn('Failed to fetch site configs, using defaults:', error);
return getDefaultSiteConfigs();
}
}
function getDefaultSiteConfigs() {
return [
{ key: 'site.name', value: 'MosaicMap' },
{ key: 'site.description', value: 'Interactive map visualization with custom WebGL timeline' }
];
}

View File

@ -3,7 +3,7 @@ import { setContext } from '@apollo/client/link/context';
// 创建HTTP链接
const httpLink = createHttpLink({
uri: process.env.NEXT_PUBLIC_GRAPHQL_URL || 'http://localhost:4000/graphql',
uri: process.env.NEXT_PUBLIC_GRAPHQL_BACKEND_URL || 'http://localhost:4000/graphql',
});
// 认证链接

View File

@ -62,3 +62,145 @@ export interface UpdateConfig {
category?: string;
is_editable?: boolean;
}
// Config interface for component use
export interface Config {
app: {
name: string;
version: string;
debug: boolean;
timezone: string;
};
database: {
max_connections: number;
connection_timeout: number;
};
kafka: {
max_retries: number;
retry_delay: number;
};
security: {
session_timeout: number;
max_login_attempts: number;
};
logging: {
level: string;
max_files: number;
};
cache: {
ttl: number;
max_size: number;
};
site: {
name: string;
locale_default: string;
brand: {
logo_url: string;
primary_color: string;
dark_mode_default: boolean;
};
};
notice: {
banner: {
enabled: boolean;
text: {
'zh-CN': string;
'en': string;
};
};
};
maintenance: {
window: {
enabled: boolean;
start_time: string;
end_time: string;
message: {
'zh-CN': string;
'en': string;
};
};
};
ops: {
features: {
registration_enabled: boolean;
invite_code_required: boolean;
email_verification: boolean;
};
limits: {
max_users: number;
max_invite_codes_per_user: number;
session_timeout_hours: number;
};
};
}
// Default configuration
export const defaultConfig: Config = {
app: {
name: "MosaicMap",
version: "1.0.0",
debug: false,
timezone: "Asia/Shanghai"
},
database: {
max_connections: 100,
connection_timeout: 30
},
kafka: {
max_retries: 3,
retry_delay: 1000
},
security: {
session_timeout: 3600,
max_login_attempts: 5
},
logging: {
level: "info",
max_files: 10
},
cache: {
ttl: 3600,
max_size: 1000
},
site: {
name: "MosaicMap",
locale_default: "zh-CN",
brand: {
logo_url: "",
primary_color: "#3b82f6",
dark_mode_default: false
}
},
notice: {
banner: {
enabled: false,
text: {
'zh-CN': '',
'en': ''
}
}
},
maintenance: {
window: {
enabled: false,
start_time: '',
end_time: '',
message: {
'zh-CN': '',
'en': ''
}
}
},
ops: {
features: {
registration_enabled: true,
invite_code_required: false,
email_verification: false
},
limits: {
max_users: 10000,
max_invite_codes_per_user: 10,
session_timeout_hours: 24
}
}
};

View File

@ -7,7 +7,7 @@ export interface User {
resource: string
action: string
}[]
// role?: string
role?: string
}
export interface AuthState {