|
|
|
|
import fs from "fs";
|
|
|
|
|
import path from "path";
|
|
|
|
|
import { cache } from "react";
|
|
|
|
|
import type {
|
|
|
|
|
Floor,
|
|
|
|
|
NavItem,
|
|
|
|
|
HeroData,
|
|
|
|
|
AboutData,
|
|
|
|
|
TechData,
|
|
|
|
|
SolutionsData,
|
|
|
|
|
CasesData,
|
|
|
|
|
PartnersData,
|
|
|
|
|
NewsData,
|
|
|
|
|
CareersData,
|
|
|
|
|
ContactData,
|
|
|
|
|
} from "../types";
|
|
|
|
|
|
|
|
|
|
// 内存缓存,避免重复读取文件
|
|
|
|
|
const fileCache = new Map<string, { data: unknown; mtime: number }>();
|
|
|
|
|
|
|
|
|
|
function readJson<T>(relativePath: string): T {
|
|
|
|
|
const filePath = path.join(process.cwd(), relativePath);
|
|
|
|
|
|
|
|
|
|
// 检查文件修改时间
|
|
|
|
|
let stats: fs.Stats;
|
|
|
|
|
try {
|
|
|
|
|
stats = fs.statSync(filePath);
|
|
|
|
|
} catch {
|
|
|
|
|
throw new Error(`File not found: ${relativePath}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查缓存
|
|
|
|
|
const cached = fileCache.get(filePath);
|
|
|
|
|
if (cached && cached.mtime === stats.mtimeMs) {
|
|
|
|
|
return cached.data as T;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 读取并缓存
|
|
|
|
|
const raw = fs.readFileSync(filePath, "utf-8");
|
|
|
|
|
const data = JSON.parse(raw) as T;
|
|
|
|
|
fileCache.set(filePath, { data, mtime: stats.mtimeMs });
|
|
|
|
|
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function dataPathFor(locale: string | undefined, filename: string): string {
|
|
|
|
|
const base = "data";
|
|
|
|
|
const candidates = [
|
|
|
|
|
locale ? path.join(base, locale, filename) : undefined,
|
|
|
|
|
path.join(base, filename),
|
|
|
|
|
].filter(Boolean) as string[];
|
|
|
|
|
for (const p of candidates) {
|
|
|
|
|
if (fs.existsSync(path.join(process.cwd(), p))) return p;
|
|
|
|
|
}
|
|
|
|
|
return path.join(base, filename);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 使用 React cache 包装所有数据读取函数,实现请求级别的缓存
|
|
|
|
|
export const getMainNav = cache((locale?: string) => {
|
|
|
|
|
return readJson<NavItem[]>(dataPathFor(locale, "mainnav.json"));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const getFloors = cache((locale?: string) => {
|
|
|
|
|
return readJson<Floor[]>(dataPathFor(locale, "products.json"));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const getFloorBySlug = cache((slug: string, locale?: string) => {
|
|
|
|
|
const floors = getFloors(locale);
|
|
|
|
|
// 允许 id 带前缀,如 floor-phone,对应 slug phone
|
|
|
|
|
return floors.find((f) => f.id === slug || f.id === `floor-${slug}`);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const getProductById = cache((id: string, locale?: string) => {
|
|
|
|
|
const floors = getFloors(locale);
|
|
|
|
|
for (const f of floors) {
|
|
|
|
|
const p = f.products.find((x) => x.id === id);
|
|
|
|
|
if (p) return { product: p, floor: f } as const;
|
|
|
|
|
}
|
|
|
|
|
return undefined;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const getHero = cache((locale?: string) => {
|
|
|
|
|
return readJson<HeroData>(dataPathFor(locale, "hero.json"));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const getAbout = cache((locale?: string) => {
|
|
|
|
|
return readJson<AboutData>(dataPathFor(locale, "about.json"));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const getAboutMarkdownFile = cache((locale?: string): string => {
|
|
|
|
|
const candidates = [
|
|
|
|
|
locale ? path.join("data", locale, "about.md") : undefined,
|
|
|
|
|
path.join("data", "about.md"),
|
|
|
|
|
path.join("..", "..", "关于我们.md"),
|
|
|
|
|
].filter(Boolean) as string[];
|
|
|
|
|
|
|
|
|
|
for (const relativePath of candidates) {
|
|
|
|
|
const filePath = path.join(process.cwd(), relativePath);
|
|
|
|
|
if (fs.existsSync(filePath)) {
|
|
|
|
|
try {
|
|
|
|
|
// 检查缓存
|
|
|
|
|
const stats = fs.statSync(filePath);
|
|
|
|
|
const cached = fileCache.get(filePath);
|
|
|
|
|
if (cached && cached.mtime === stats.mtimeMs && typeof cached.data === 'string') {
|
|
|
|
|
return cached.data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const content = fs.readFileSync(filePath, "utf-8");
|
|
|
|
|
fileCache.set(filePath, { data: content, mtime: stats.mtimeMs });
|
|
|
|
|
return content;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn(`Failed to load ${relativePath}`, error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.warn("About markdown not found in any candidate path.");
|
|
|
|
|
return "";
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const getAboutMarkdown = cache((locale?: string): string => {
|
|
|
|
|
return getAboutMarkdownFile(locale);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const getTech = cache((locale?: string) => {
|
|
|
|
|
return readJson<TechData>(dataPathFor(locale, "tech.json"));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const getSolutions = cache((locale?: string) => {
|
|
|
|
|
return readJson<SolutionsData>(dataPathFor(locale, "solutions.json"));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const getCases = cache((locale?: string) => {
|
|
|
|
|
return readJson<CasesData>(dataPathFor(locale, "cases.json"));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const getPartners = cache((locale?: string) => {
|
|
|
|
|
return readJson<PartnersData>(dataPathFor(locale, "partners.json"));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const getNews = cache((locale?: string) => {
|
|
|
|
|
return readJson<NewsData>(dataPathFor(locale, "news.json"));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const getCareers = cache((locale?: string) => {
|
|
|
|
|
return readJson<CareersData>(dataPathFor(locale, "careers.json"));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const getContact = cache((locale?: string) => {
|
|
|
|
|
return readJson<ContactData>(dataPathFor(locale, "contact.json"));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|