fix url error
This commit is contained in:
parent
23905d33fd
commit
9cb46de3e1
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -32,8 +32,8 @@ const schema = z.object({
|
|||||||
status: z.enum(["draft", "published", "archived"]),
|
status: z.enum(["draft", "published", "archived"]),
|
||||||
metaTitle: z.string().optional(),
|
metaTitle: z.string().optional(),
|
||||||
metaDescription: z.string().optional(),
|
metaDescription: z.string().optional(),
|
||||||
isFeatured: z.boolean().default(false),
|
isFeatured: z.boolean(),
|
||||||
isActive: z.boolean().default(true),
|
isActive: z.boolean(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export default function CreateBlogForm() {
|
export default function CreateBlogForm() {
|
||||||
|
|||||||
@ -30,7 +30,7 @@ const schema = z.object({
|
|||||||
color: z.string().optional(),
|
color: z.string().optional(),
|
||||||
icon: z.string().optional(),
|
icon: z.string().optional(),
|
||||||
parentId: z.string().optional(),
|
parentId: z.string().optional(),
|
||||||
isActive: z.boolean().default(true),
|
isActive: z.boolean(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export default function CreateCategoryForm() {
|
export default function CreateCategoryForm() {
|
||||||
|
|||||||
@ -447,14 +447,14 @@ export function RolePermissionManagement() {
|
|||||||
const roles = rolesData?.roles?.items || []
|
const roles = rolesData?.roles?.items || []
|
||||||
const permissions = permissionsData?.permissions?.items || []
|
const permissions = permissionsData?.permissions?.items || []
|
||||||
|
|
||||||
const activeRoles = roles.filter(r => r.isActive)
|
const activeRoles = roles.filter((r: any) => r.isActive)
|
||||||
const currentRole = selectedRole ? roles.find(r => r.id === selectedRole) : null
|
const currentRole = selectedRole ? roles.find((r: any) => r.id === selectedRole) : null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* 角色概览卡片 */}
|
{/* 角色概览卡片 */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
{activeRoles.map((role) => (
|
{activeRoles.map((role: any) => (
|
||||||
<Card
|
<Card
|
||||||
key={role.id}
|
key={role.id}
|
||||||
className={`cursor-pointer transition-colors ${
|
className={`cursor-pointer transition-colors ${
|
||||||
@ -530,7 +530,7 @@ export function RolePermissionManagement() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
<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">
|
<Card key={permission.id} className="p-3">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|||||||
@ -27,7 +27,7 @@ const schema = z.object({
|
|||||||
slug: z.string().min(1, "别名不能为空"),
|
slug: z.string().min(1, "别名不能为空"),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
color: z.string().optional(),
|
color: z.string().optional(),
|
||||||
isActive: z.boolean().default(true),
|
isActive: z.boolean(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export default function CreateTagForm() {
|
export default function CreateTagForm() {
|
||||||
|
|||||||
@ -39,6 +39,7 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(process.env.GRAPHQL_BACKEND_URL)
|
||||||
|
|
||||||
console.error('Login error:', error);
|
console.error('Login error:', error);
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
|
|||||||
@ -12,7 +12,7 @@ const GET_CONFIGS = gql`
|
|||||||
`
|
`
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
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 {
|
try {
|
||||||
const data: any = await client.request(GET_CONFIGS);
|
const data: any = await client.request(GET_CONFIGS);
|
||||||
return NextResponse.json(data.siteConfigs);
|
return NextResponse.json(data.siteConfigs);
|
||||||
|
|||||||
@ -164,7 +164,7 @@ export const Timeline: React.FC<Props> = React.memo(({
|
|||||||
},
|
},
|
||||||
onDateChange: async (date: Date) => {
|
onDateChange: async (date: Date) => {
|
||||||
const datestr = formatInTimeZone(date, 'UTC', 'yyyyMMddHHmmss')
|
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`)
|
const response = await fetch(`${url_base}/api/v1/data/nearest?datetime=${datestr}&area=cn`)
|
||||||
|
|
||||||
setTimelineTime(date)
|
setTimelineTime(date)
|
||||||
|
|||||||
@ -58,7 +58,7 @@ export function MapComponent({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isMapReady || !currentDatetime) return;
|
if (!isMapReady || !currentDatetime) return;
|
||||||
const utc_time_str = formatInTimeZone(currentDatetime, 'UTC', 'yyyyMMddHHmmss')
|
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`
|
const new_url = `${new_url_prefix}/api/v1/data?datetime=${utc_time_str}&area=cn`
|
||||||
fetchRadarTile(new_url)
|
fetchRadarTile(new_url)
|
||||||
}, [currentDatetime, isMapReady])
|
}, [currentDatetime, isMapReady])
|
||||||
|
|||||||
@ -248,7 +248,7 @@ export function EnhancedSimpleEditor({ content, onChange }: EnhancedSimpleEditor
|
|||||||
|
|
||||||
// Only update if content actually changed to avoid infinite loops
|
// Only update if content actually changed to avoid infinite loops
|
||||||
if (JSON.stringify(currentContent) !== JSON.stringify(newContent)) {
|
if (JSON.stringify(currentContent) !== JSON.stringify(newContent)) {
|
||||||
editor.commands.setContent(newContent, false)
|
editor.commands.setContent(newContent, { emitUpdate: false })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [editor, content])
|
}, [editor, content])
|
||||||
|
|||||||
@ -8,11 +8,12 @@ import { createClient } from 'graphql-ws';
|
|||||||
const TOKEN_KEY = 'auth_token';
|
const TOKEN_KEY = 'auth_token';
|
||||||
|
|
||||||
const httpLink = createHttpLink({
|
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({
|
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 }) => {
|
const authLink = setContext((_, { headers }) => {
|
||||||
|
|||||||
@ -63,8 +63,32 @@ export async function fetchPage(slug: string, jwt?: string): Promise<PageData |
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getSiteConfigs() {
|
export async function getSiteConfigs() {
|
||||||
const baseUrl = process.env.GRAPHQL_BACKEND_URL || 'http://localhost:3000';
|
try {
|
||||||
const siteConfigs = await fetch(`${baseUrl}/api/site`);
|
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();
|
const data = await siteConfigs.json();
|
||||||
return data;
|
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' }
|
||||||
|
];
|
||||||
}
|
}
|
||||||
@ -3,7 +3,7 @@ import { setContext } from '@apollo/client/link/context';
|
|||||||
|
|
||||||
// 创建HTTP链接
|
// 创建HTTP链接
|
||||||
const httpLink = createHttpLink({
|
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',
|
||||||
});
|
});
|
||||||
|
|
||||||
// 认证链接
|
// 认证链接
|
||||||
|
|||||||
142
types/config.ts
142
types/config.ts
@ -62,3 +62,145 @@ export interface UpdateConfig {
|
|||||||
category?: string;
|
category?: string;
|
||||||
is_editable?: boolean;
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -7,7 +7,7 @@ export interface User {
|
|||||||
resource: string
|
resource: string
|
||||||
action: string
|
action: string
|
||||||
}[]
|
}[]
|
||||||
// role?: string
|
role?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthState {
|
export interface AuthState {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user