Mimingguang ee078b61bc feat: 后台管理系统完整重构 + 前后台数据联通
- 后台全面引入 Naive UI 组件库,统一 UI 规范
- 前台 usePublicApi 切换到 API 调用(GET /api/content/[key])
- 前后台数据源统一(site_content 表)
- 产品线统一管理(tour/camp/course 三套编辑器)
- 产品数据归一化(itinerary/faq/pricing 格式统一)
- 表单提交 API 改写入 submissions 表
- 新增 submissions 标记已读 API
- site-content PUT 支持 upsert
- 修复前台 bug(stories 路由冲突、占位符警告、selector breadcrumb)
- 消除 PageHero 类型警告(useSEO reactive 修复)
- 25 个前台页面全部 HTTP 200,展示效果不变

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 17:48:02 +08:00

110 行
5.5 KiB
JavaScript

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
// ── 管理员 ──────────────────────────────────────────
export const adminUsers = sqliteTable('admin_users', {
id: integer('id').primaryKey({ autoIncrement: true }),
username: text('username').notNull().unique(),
// 存储 hash 后的密码
passwordHash: text('password_hash').notNull(),
createdAt: text('created_at').notNull().$defaultFn(() => new Date().toISOString()),
})
// ── 新闻/公告 ────────────────────────────────────────
export const articles = sqliteTable('articles', {
id: integer('id').primaryKey({ autoIncrement: true }),
slug: text('slug').notNull().unique(),
title: text('title').notNull(),
summary: text('summary'),
content: text('content'), // HTML 或 Markdown
category: text('category'), // 产品 / 公告 / 活动 / 攻略 / 亲子
type: text('type').notNull(), // news / blog
coverImage: text('cover_image'),
author: text('author'),
tags: text('tags'), // JSON 数组字符串
seoTitle: text('seo_title'),
seoDesc: text('seo_description'),
seoKeywords: text('seo_keywords'),
published: integer('published', { mode: 'boolean' }).default(true),
sortOrder: integer('sort_order').default(0),
createdAt: text('created_at').notNull().$defaultFn(() => new Date().toISOString()),
updatedAt: text('updated_at').notNull().$defaultFn(() => new Date().toISOString()),
})
// ── 客户评价 ─────────────────────────────────────────
export const reviews = sqliteTable('reviews', {
id: integer('id').primaryKey({ autoIncrement: true }),
author: text('author').notNull(),
rating: integer('rating').default(5),
title: text('title'),
content: text('content'),
date: text('date'),
verified: integer('verified', { mode: 'boolean' }).default(false),
published: integer('published', { mode: 'boolean' }).default(true),
sortOrder: integer('sort_order').default(0),
createdAt: text('created_at').notNull().$defaultFn(() => new Date().toISOString()),
})
// ── FAQ ──────────────────────────────────────────────
export const faqs = sqliteTable('faqs', {
id: integer('id').primaryKey({ autoIncrement: true }),
question: text('question').notNull(),
answer: text('answer').notNull(),
sortOrder: integer('sort_order').default(0),
published: integer('published', { mode: 'boolean' }).default(true),
})
// ── 相册/旅拍 ────────────────────────────────────────
export const galleryItems = sqliteTable('gallery_items', {
id: integer('id').primaryKey({ autoIncrement: true }),
title: text('title').notNull(),
category: text('category'), // 草原 / 森林 / 湖泊 / 冬季 ...
location: text('location'),
description: text('description'),
image: text('image').notNull(), // 图片路径
orientation: text('orientation'), // portrait / landscape
published: integer('published', { mode: 'boolean' }).default(true),
sortOrder: integer('sort_order').default(0),
})
// ── 客户故事 ─────────────────────────────────────────
export const stories = sqliteTable('stories', {
id: integer('id').primaryKey({ autoIncrement: true }),
slug: text('slug').notNull().unique(),
title: text('title').notNull(),
subtitle: text('subtitle'),
familyType: text('family_type'),
childrenAge: text('children_age'),
fromCity: text('from_city'),
tripProduct: text('trip_product'),
travelDate: text('travel_date'),
coverImage: text('cover_image'),
// sections 存为 JSON 字符串: [{ heading, content }]
sections: text('sections'),
keyMoments: text('key_moments'), // JSON 数组
seoTitle: text('seo_title'),
seoDesc: text('seo_description'),
published: integer('published', { mode: 'boolean' }).default(true),
sortOrder: integer('sort_order').default(0),
createdAt: text('created_at').notNull().$defaultFn(() => new Date().toISOString()),
})
// ── 表单提交记录 ─────────────────────────────────────
export const submissions = sqliteTable('submissions', {
id: integer('id').primaryKey({ autoIncrement: true }),
type: text('type').notNull(), // contact / customize
data: text('data').notNull(), // JSON 字符串,存完整表单数据
ip: text('ip'),
read: integer('read', { mode: 'boolean' }).default(false),
createdAt: text('created_at').notNull().$defaultFn(() => new Date().toISOString()),
})
// ── 站点配置catch-all,存复杂 JSON 内容)───────────
// 用于 products / destinations / courses / navigation 等复杂嵌套数据
export const siteContent = sqliteTable('site_content', {
id: integer('id').primaryKey({ autoIncrement: true }),
key: text('key').notNull().unique(), // 如 'products', 'navigation', 'brand'
label: text('label').notNull(), // 显示名称,如 '产品管理', '导航配置'
data: text('data').notNull(), // JSON 字符串
updatedAt: text('updated_at').notNull().$defaultFn(() => new Date().toISOString()),
})