官网开发中 0731

This commit is contained in:
eafonyang
2026-07-31 19:02:00 +08:00
parent fda4fa8627
commit eeccdb079c
36 changed files with 1944 additions and 105 deletions
+20
View File
@@ -0,0 +1,20 @@
/**
* 链接解析:区分外部 URL 与站内路由
*
* 站内路由自动加当前语言前缀(中文无前缀,英文 /en 前缀)。
*/
export function useLinkResolver() {
const localePath = useLocalePath();
function isExternal(link: string): boolean {
return /^https?:\/\//.test(link);
}
function resolveLink(link: string): string {
if (!link) return '';
if (isExternal(link)) return link;
return localePath(link);
}
return { isExternal, resolveLink };
}
+21 -8
View File
@@ -1,28 +1,41 @@
/**
* 统一请求封装(基于 Nuxt 内置 ofetch
* 响应格式约定:{ code, data, message },成功码 '0000'
*
* 与 dashboard 后端约定响应格式:{ code, msg, data }
* - code = 1:成功
* - code = 0:业务错误
* - code = 2:无数据
*/
interface ApiResponse<T = unknown> {
code: string;
data: T;
message: string;
export interface ApiResponse<T = unknown> {
code: number;
msg: string;
data: T | null;
}
export class ApiError extends Error {
code: number;
constructor(msg: string, code: number) {
super(msg);
this.code = code;
}
}
export function useRequest() {
const config = useRuntimeConfig();
const baseURL = config.public.apiBaseUrl as string;
async function request<T = unknown>(url: string, options?: Parameters<typeof $fetch>[1]): Promise<T> {
async function request<T = unknown>(url: string, options?: Parameters<typeof $fetch>[1]): Promise<T | null> {
const res = await $fetch<ApiResponse<T>>(url, {
baseURL,
...options
});
if (res.code !== '0000') {
throw new Error(res.message || 'Request failed');
if (res.code === 0) {
throw new ApiError(res.msg || '请求失败', res.code);
}
// code = 2(无数据)时 data 为 null,正常返回
return res.data;
}
+38
View File
@@ -0,0 +1,38 @@
import type { SectionBlock, SectionConfig } from '~/types/site';
/**
* 区块内容多语言解析
*
* Section 的各文本字段均有 _zh / _en 两个版本,
* 该 composable 根据当前语言返回对应的值,避免各布局组件重复判断。
*/
export function useSectionContent(section: SectionBlock) {
const { locale } = useI18n();
const isEn = computed(() => locale.value === 'en');
const overline = computed(() => (isEn.value ? section.overline_en : section.overline_zh) || '');
const title = computed(() => (isEn.value ? section.title_en : section.title_zh) || '');
const subtitle = computed(() => (isEn.value ? section.subtitle_en : section.subtitle_zh) || '');
const content = computed(() => (isEn.value ? section.content_en : section.content_zh) || '');
const ctaPrimaryText = computed(() => (isEn.value ? section.cta_primary_en : section.cta_primary_zh) || '');
const ctaSecondaryText = computed(() => (isEn.value ? section.cta_secondary_en : section.cta_secondary_zh) || '');
const mediaUrl = computed(() => (isEn.value ? section.media_url_en : section.media_url_zh) || section.media_url_zh || '');
const config = computed<SectionConfig>(() => section.config ?? {});
/** 深色主题区块 */
const isDark = computed(() => section.theme === 'dark');
return {
isEn,
overline,
title,
subtitle,
content,
ctaPrimaryText,
ctaSecondaryText,
mediaUrl,
config,
isDark
};
}
+39
View File
@@ -0,0 +1,39 @@
import type { GlobalConfig, NavConfig, SectionBlock, I18nEntry, ProductsData } from '~/types/site';
/**
* 官网前台公开接口服务层
*
* 消费 dashboard 后端的 /api/site/* 只读接口(免鉴权)。
*/
export function useSiteApi() {
const { get } = useRequest();
/** 全局配置:站点信息 + 页脚 + 社交链接 + SEO 默认值 */
function getGlobal() {
return get<GlobalConfig>('/site/global');
}
/** 导航:可见入口 + 外观 */
function getNav() {
return get<NavConfig>('/site/nav');
}
/** Section 区块列表(按页面类型,仅可见) */
function getSections(pageType: string, productId?: number) {
const params: Record<string, unknown> = { page_type: pageType };
if (productId) params.product_id = productId;
return get<{ items: SectionBlock[]; total: number }>('/site/sections', params);
}
/** i18n 词条列表 */
function getI18n() {
return get<{ items: I18nEntry[]; total: number }>('/site/i18n');
}
/** 产品:可见系列及其下可见产品 */
function getProducts() {
return get<ProductsData>('/site/products');
}
return { getGlobal, getNav, getSections, getI18n, getProducts };
}