v0.10.5 首页 H2 产品线入口 ×6:ImageBoxWidget 重构为 Elementor 忠实 DOM + slim 白名单补齐 + H2 scoped CSS(post-18 对齐,桌面/移动/hover 实测一致)
This commit is contained in:
@@ -0,0 +1,863 @@
|
||||
/**
|
||||
* DusunIoT 重建版 PostgreSQL 数据模型(Drizzle ORM)
|
||||
*
|
||||
* 按业务域组织:content / products / commerce / inquiries / ops。
|
||||
* 每张表保留 legacy_id 以便对照原 WordPress 数据并支持增量同步。
|
||||
*/
|
||||
import {
|
||||
pgTable,
|
||||
serial,
|
||||
varchar,
|
||||
text,
|
||||
integer,
|
||||
bigint,
|
||||
boolean,
|
||||
timestamp,
|
||||
numeric,
|
||||
jsonb,
|
||||
index,
|
||||
uniqueIndex,
|
||||
primaryKey,
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
/* ============================= 内容域 ============================= */
|
||||
|
||||
/** # 数据库表
|
||||
* 内容主表(页面 page / 文章 post / 落地页 landing),默认语言(英文)的正本。
|
||||
* Elementor 原始 JSON 存于 meta.elementor,供 1:1 还原渲染器使用。
|
||||
*/
|
||||
export const contents = pgTable(
|
||||
"contents",
|
||||
{
|
||||
/** # 数据库字段 内容 ID */
|
||||
id: serial("id").primaryKey(),
|
||||
/** # 数据库字段 内容类型:page | post | landing */
|
||||
type: varchar("type", { length: 32 }).notNull(),
|
||||
/** # 数据库字段 URL 别名(默认语言) */
|
||||
slug: varchar("slug", { length: 255 }).notNull(),
|
||||
/** # 数据库字段 标题 */
|
||||
title: text("title").notNull(),
|
||||
/** # 数据库字段 摘要 */
|
||||
excerpt: text("excerpt").default("").notNull(),
|
||||
/** # 数据库字段 正文 HTML */
|
||||
html: text("html").default("").notNull(),
|
||||
/** # 数据库字段 状态:draft | review | published */
|
||||
status: varchar("status", { length: 16 }).default("published").notNull(),
|
||||
/** # 数据库字段 作者 */
|
||||
authorId: integer("author_id"),
|
||||
/** # 数据库字段 特色图片 */
|
||||
featuredMediaId: integer("featured_media_id"),
|
||||
/** # 数据库字段 页面模板名(home / product / blog / blank …) */
|
||||
template: varchar("template", { length: 64 }).default("default").notNull(),
|
||||
/** # 数据库字段 菜单排序 */
|
||||
menuOrder: integer("menu_order").default(0).notNull(),
|
||||
/** # 数据库字段 浏览次数 */
|
||||
views: integer("views").default(0).notNull(),
|
||||
/** # 数据库字段 是否置顶 */
|
||||
sticky: boolean("sticky").default(false).notNull(),
|
||||
/** # 数据库字段 SEO 标题 */
|
||||
seoTitle: text("seo_title"),
|
||||
/** # 数据库字段 SEO 描述 */
|
||||
seoDescription: text("seo_description"),
|
||||
/** # 数据库字段 扩展数据(elementor JSON 等) */
|
||||
meta: jsonb("meta").default({}).notNull(),
|
||||
/** # 数据库字段 原 WordPress post ID */
|
||||
legacyId: integer("legacy_id"),
|
||||
/** # 数据库字段 发布时间 */
|
||||
publishedAt: timestamp("published_at", { withTimezone: true }),
|
||||
/** # 数据库字段 创建时间 */
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
/** # 数据库字段 更新时间 */
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
},
|
||||
(t) => [
|
||||
uniqueIndex("contents_slug_type_idx").on(t.slug, t.type),
|
||||
index("contents_type_status_idx").on(t.type, t.status),
|
||||
index("contents_legacy_idx").on(t.legacyId),
|
||||
],
|
||||
);
|
||||
|
||||
/** # 数据库表
|
||||
* 内容多语言翻译版本(每种语言独立 slug/标题/正文/SEO;缺翻译时不生成伪语言页面)。
|
||||
*/
|
||||
export const contentTranslations = pgTable(
|
||||
"content_translations",
|
||||
{
|
||||
/** # 数据库字段 翻译 ID */
|
||||
id: serial("id").primaryKey(),
|
||||
/** # 数据库字段 内容 ID */
|
||||
contentId: integer("content_id").notNull(),
|
||||
/** # 数据库字段 语言代码(BCP47,如 fr / de) */
|
||||
lang: varchar("lang", { length: 16 }).notNull(),
|
||||
/** # 数据库字段 该语言的 URL 别名 */
|
||||
slug: varchar("slug", { length: 255 }).notNull(),
|
||||
/** # 数据库字段 标题 */
|
||||
title: text("title").notNull(),
|
||||
/** # 数据库字段 摘要 */
|
||||
excerpt: text("excerpt").default("").notNull(),
|
||||
/** # 数据库字段 正文 HTML */
|
||||
html: text("html").default("").notNull(),
|
||||
/** # 数据库字段 状态:draft | review | published */
|
||||
status: varchar("status", { length: 16 }).default("draft").notNull(),
|
||||
/** # 数据库字段 SEO 标题 */
|
||||
seoTitle: text("seo_title"),
|
||||
/** # 数据库字段 SEO 描述 */
|
||||
seoDescription: text("seo_description"),
|
||||
/** # 数据库字段 翻译时间 */
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
/** # 数据库字段 更新时间 */
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
},
|
||||
(t) => [
|
||||
uniqueIndex("content_translations_content_lang_idx").on(t.contentId, t.lang),
|
||||
index("content_translations_lang_status_idx").on(t.lang, t.status),
|
||||
],
|
||||
);
|
||||
|
||||
/** # 数据库表
|
||||
* 内容修订快照(发布前留档,支持回滚)。
|
||||
*/
|
||||
export const contentRevisions = pgTable("content_revisions", {
|
||||
/** # 数据库字段 修订 ID */
|
||||
id: serial("id").primaryKey(),
|
||||
/** # 数据库字段 内容 ID */
|
||||
contentId: integer("content_id").notNull(),
|
||||
/** # 数据库字段 语言(缺省为默认语言) */
|
||||
lang: varchar("lang", { length: 16 }).default("en").notNull(),
|
||||
/** # 数据库字段 标题快照 */
|
||||
title: text("title").notNull(),
|
||||
/** # 数据库字段 正文 HTML 快照 */
|
||||
html: text("html").notNull(),
|
||||
/** # 数据库字段 修订人 */
|
||||
authorId: integer("author_id"),
|
||||
/** # 数据库字段 修订时间 */
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
});
|
||||
|
||||
/** # 数据库表
|
||||
* 分类/标签(taxonomy:category / post_tag / product_cat / product_tag / 属性 pa_*)。
|
||||
*/
|
||||
export const categories = pgTable(
|
||||
"categories",
|
||||
{
|
||||
/** # 数据库字段 分类 ID */
|
||||
id: serial("id").primaryKey(),
|
||||
/** # 数据库字段 taxonomy 名称 */
|
||||
taxonomy: varchar("taxonomy", { length: 64 }).notNull(),
|
||||
/** # 数据库字段 名称 */
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
/** # 数据库字段 URL 别名 */
|
||||
slug: varchar("slug", { length: 255 }).notNull(),
|
||||
/** # 数据库字段 父级分类 */
|
||||
parentId: integer("parent_id"),
|
||||
/** # 数据库字段 描述 */
|
||||
description: text("description").default("").notNull(),
|
||||
/** # 数据库字段 分类图片 */
|
||||
imageMediaId: integer("image_media_id"),
|
||||
/** # 数据库字段 排序 */
|
||||
sort: integer("sort").default(0).notNull(),
|
||||
/** # 数据库字段 原 WordPress term_id */
|
||||
legacyId: integer("legacy_id"),
|
||||
},
|
||||
(t) => [
|
||||
uniqueIndex("categories_taxonomy_slug_idx").on(t.taxonomy, t.slug),
|
||||
index("categories_parent_idx").on(t.parentId),
|
||||
],
|
||||
);
|
||||
|
||||
/** # 数据库表
|
||||
* 内容与分类/标签的关联。
|
||||
*/
|
||||
export const contentTerms = pgTable(
|
||||
"content_terms",
|
||||
{
|
||||
/** # 数据库字段 内容 ID */
|
||||
contentId: integer("content_id").notNull(),
|
||||
/** # 数据库字段 分类 ID */
|
||||
categoryId: integer("category_id").notNull(),
|
||||
},
|
||||
(t) => [
|
||||
primaryKey({ columns: [t.contentId, t.categoryId] }),
|
||||
index("content_terms_category_idx").on(t.categoryId),
|
||||
],
|
||||
);
|
||||
|
||||
/** # 数据库表
|
||||
* 导航菜单定义(header / footer / account 等位置)。
|
||||
*/
|
||||
export const menus = pgTable("menus", {
|
||||
/** # 数据库字段 菜单 ID */
|
||||
id: serial("id").primaryKey(),
|
||||
/** # 数据库字段 菜单名称 */
|
||||
name: varchar("name", { length: 128 }).notNull(),
|
||||
/** # 数据库字段 位置别名(menu-1 / menu-2 / max_mega_menu_1 …) */
|
||||
location: varchar("location", { length: 64 }).notNull(),
|
||||
/** # 数据库字段 原菜单 term_id */
|
||||
legacyId: integer("legacy_id"),
|
||||
});
|
||||
|
||||
/** # 数据库表
|
||||
* 菜单项(支持多级树)。
|
||||
*/
|
||||
export const menuItems = pgTable(
|
||||
"menu_items",
|
||||
{
|
||||
/** # 数据库字段 菜单项 ID */
|
||||
id: serial("id").primaryKey(),
|
||||
/** # 数据库字段 所属菜单 */
|
||||
menuId: integer("menu_id").notNull(),
|
||||
/** # 数据库字段 父菜单项 */
|
||||
parentId: integer("parent_id"),
|
||||
/** # 数据库字段 显示文字 */
|
||||
title: varchar("title", { length: 255 }).notNull(),
|
||||
/** # 数据库字段 链接地址 */
|
||||
url: text("url").notNull(),
|
||||
/** # 数据库字段 打开方式:_self | _blank */
|
||||
target: varchar("target", { length: 16 }).default("_self").notNull(),
|
||||
/** # 数据库字段 排序 */
|
||||
sort: integer("sort").default(0).notNull(),
|
||||
/** # 数据库字段 原 menu_item post_id */
|
||||
legacyId: integer("legacy_id"),
|
||||
},
|
||||
(t) => [index("menu_items_menu_idx").on(t.menuId, t.sort)],
|
||||
);
|
||||
|
||||
/* ============================= 产品域 ============================= */
|
||||
|
||||
/** # 数据库表
|
||||
* 产品主表(WooCommerce 迁移)。
|
||||
*/
|
||||
export const products = pgTable(
|
||||
"products",
|
||||
{
|
||||
/** # 数据库字段 产品 ID */
|
||||
id: serial("id").primaryKey(),
|
||||
/** # 数据库字段 SKU */
|
||||
sku: varchar("sku", { length: 128 }),
|
||||
/** # 数据库字段 URL 别名 */
|
||||
slug: varchar("slug", { length: 255 }).notNull(),
|
||||
/** # 数据库字段 名称 */
|
||||
title: text("title").notNull(),
|
||||
/** # 数据库字段 短描述 */
|
||||
shortDescription: text("short_description").default("").notNull(),
|
||||
/** # 数据库字段 详细描述 HTML */
|
||||
description: text("description").default("").notNull(),
|
||||
/** # 数据库字段 现价 */
|
||||
price: numeric("price", { precision: 12, scale: 2 }),
|
||||
/** # 数据库字段 原价 */
|
||||
regularPrice: numeric("regular_price", { precision: 12, scale: 2 }),
|
||||
/** # 数据库字段 库存数量 */
|
||||
stock: integer("stock"),
|
||||
/** # 数据库字段 库存状态:instock | outofstock | onbackorder */
|
||||
stockStatus: varchar("stock_status", { length: 32 }).default("instock").notNull(),
|
||||
/** # 数据库字段 特色图片 */
|
||||
featuredMediaId: integer("featured_media_id"),
|
||||
/** # 数据库字段 图廊(媒体 ID 数组) */
|
||||
gallery: jsonb("gallery").default([]).notNull(),
|
||||
/** # 数据库字段 状态:draft | published */
|
||||
status: varchar("status", { length: 16 }).default("published").notNull(),
|
||||
/** # 数据库字段 是否主推产品 */
|
||||
featured: boolean("featured").default(false).notNull(),
|
||||
/** # 数据库字段 浏览次数 */
|
||||
views: integer("views").default(0).notNull(),
|
||||
/** # 数据库字段 SEO 标题 */
|
||||
seoTitle: text("seo_title"),
|
||||
/** # 数据库字段 SEO 描述 */
|
||||
seoDescription: text("seo_description"),
|
||||
/** # 数据库字段 规格书下载地址 */
|
||||
specUrl: text("spec_url"),
|
||||
/** # 数据库字段 Elementor 原始 JSON */
|
||||
meta: jsonb("meta").default({}).notNull(),
|
||||
/** # 数据库字段 原 WordPress post ID */
|
||||
legacyId: integer("legacy_id"),
|
||||
/** # 数据库字段 创建时间 */
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
/** # 数据库字段 更新时间 */
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
},
|
||||
(t) => [
|
||||
uniqueIndex("products_slug_idx").on(t.slug),
|
||||
index("products_status_idx").on(t.status),
|
||||
index("products_sku_idx").on(t.sku),
|
||||
index("products_legacy_idx").on(t.legacyId),
|
||||
],
|
||||
);
|
||||
|
||||
/** # 数据库表
|
||||
* 产品多语言翻译。
|
||||
*/
|
||||
export const productTranslations = pgTable(
|
||||
"product_translations",
|
||||
{
|
||||
/** # 数据库字段 翻译 ID */
|
||||
id: serial("id").primaryKey(),
|
||||
/** # 数据库字段 产品 ID */
|
||||
productId: integer("product_id").notNull(),
|
||||
/** # 数据库字段 语言代码 */
|
||||
lang: varchar("lang", { length: 16 }).notNull(),
|
||||
/** # 数据库字段 该语言 slug */
|
||||
slug: varchar("slug", { length: 255 }).notNull(),
|
||||
/** # 数据库字段 名称 */
|
||||
title: text("title").notNull(),
|
||||
/** # 数据库字段 短描述 */
|
||||
shortDescription: text("short_description").default("").notNull(),
|
||||
/** # 数据库字段 详细描述 */
|
||||
description: text("description").default("").notNull(),
|
||||
/** # 数据库字段 状态:draft | review | published */
|
||||
status: varchar("status", { length: 16 }).default("draft").notNull(),
|
||||
/** # 数据库字段 SEO 标题 */
|
||||
seoTitle: text("seo_title"),
|
||||
/** # 数据库字段 SEO 描述 */
|
||||
seoDescription: text("seo_description"),
|
||||
/** # 数据库字段 更新时间 */
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
},
|
||||
(t) => [uniqueIndex("product_translations_product_lang_idx").on(t.productId, t.lang)],
|
||||
);
|
||||
|
||||
/** # 数据库表
|
||||
* 产品属性定义(RAM / Flash / Chip Platform / Downlink 等,原 WooCommerce 全局属性)。
|
||||
*/
|
||||
export const productAttributes = pgTable(
|
||||
"product_attributes",
|
||||
{
|
||||
/** # 数据库字段 属性 ID */
|
||||
id: serial("id").primaryKey(),
|
||||
/** # 数据库字段 属性名 */
|
||||
name: varchar("name", { length: 128 }).notNull(),
|
||||
/** # 数据库字段 属性 slug(attribute_id,如 ram / chip-platform) */
|
||||
slug: varchar("slug", { length: 128 }).notNull(),
|
||||
/** # 数据库字段 是否作为筛选条件 */
|
||||
filterable: boolean("filterable").default(true).notNull(),
|
||||
/** # 数据库字段 排序 */
|
||||
sort: integer("sort").default(0).notNull(),
|
||||
},
|
||||
(t) => [uniqueIndex("product_attributes_slug_idx").on(t.slug)],
|
||||
);
|
||||
|
||||
/** # 数据库表
|
||||
* 产品属性可选值。
|
||||
*/
|
||||
export const productAttributeValues = pgTable(
|
||||
"product_attribute_values",
|
||||
{
|
||||
/** # 数据库字段 值 ID */
|
||||
id: serial("id").primaryKey(),
|
||||
/** # 数据库字段 所属属性 */
|
||||
attributeId: integer("attribute_id").notNull(),
|
||||
/** # 数据库字段 显示值 */
|
||||
value: varchar("value", { length: 255 }).notNull(),
|
||||
/** # 数据库字段 值 slug */
|
||||
slug: varchar("slug", { length: 255 }).notNull(),
|
||||
/** # 数据库字段 排序 */
|
||||
sort: integer("sort").default(0).notNull(),
|
||||
},
|
||||
(t) => [
|
||||
uniqueIndex("product_attr_values_attr_slug_idx").on(t.attributeId, t.slug),
|
||||
index("product_attr_values_attr_idx").on(t.attributeId),
|
||||
],
|
||||
);
|
||||
|
||||
/** # 数据库表
|
||||
* 产品 ↔ 属性值 关联(筛选查询主路径,建立复合索引)。
|
||||
*/
|
||||
export const productAttributeMap = pgTable(
|
||||
"product_attribute_map",
|
||||
{
|
||||
/** # 数据库字段 产品 ID */
|
||||
productId: integer("product_id").notNull(),
|
||||
/** # 数据库字段 属性值 ID */
|
||||
valueId: integer("value_id").notNull(),
|
||||
},
|
||||
(t) => [
|
||||
primaryKey({ columns: [t.productId, t.valueId] }),
|
||||
index("product_attr_map_value_idx").on(t.valueId, t.productId),
|
||||
],
|
||||
);
|
||||
|
||||
/** # 数据库表
|
||||
* 产品 ↔ 产品分类 关联。
|
||||
*/
|
||||
export const productTerms = pgTable(
|
||||
"product_terms",
|
||||
{
|
||||
/** # 数据库字段 产品 ID */
|
||||
productId: integer("product_id").notNull(),
|
||||
/** # 数据库字段 分类 ID(taxonomy=product_cat) */
|
||||
categoryId: integer("category_id").notNull(),
|
||||
},
|
||||
(t) => [
|
||||
primaryKey({ columns: [t.productId, t.categoryId] }),
|
||||
index("product_terms_category_idx").on(t.categoryId),
|
||||
],
|
||||
);
|
||||
|
||||
/* ============================= 商城域 ============================= */
|
||||
|
||||
/** # 数据库表
|
||||
* 用户(后台账号 + 商城注册客户)。保留 WordPress phpass 密码哈希用于存量客户登录兼容。
|
||||
*/
|
||||
export const users = pgTable(
|
||||
"users",
|
||||
{
|
||||
/** # 数据库字段 用户 ID */
|
||||
id: serial("id").primaryKey(),
|
||||
/** # 数据库字段 邮箱(登录名) */
|
||||
email: varchar("email", { length: 255 }).notNull(),
|
||||
/** # 数据库字段 用户名 */
|
||||
username: varchar("username", { length: 128 }),
|
||||
/** # 数据库字段 显示名称 */
|
||||
displayName: varchar("display_name", { length: 255 }).default("").notNull(),
|
||||
/** # 数据库字段 密码哈希(新账号 bcrypt) */
|
||||
passwordHash: varchar("password_hash", { length: 255 }),
|
||||
/** # 数据库字段 WordPress 旧密码哈希(phpass $P$) */
|
||||
legacyPasswordHash: varchar("legacy_password_hash", { length: 255 }),
|
||||
/** # 数据库字段 角色:admin | editor | manager | customer */
|
||||
role: varchar("role", { length: 32 }).default("customer").notNull(),
|
||||
/** # 数据库字段 账单地址 */
|
||||
billing: jsonb("billing").default({}).notNull(),
|
||||
/** # 数据库字段 收货地址 */
|
||||
shipping: jsonb("shipping").default({}).notNull(),
|
||||
/** # 数据库字段 原 WP user ID */
|
||||
legacyId: integer("legacy_id"),
|
||||
/** # 数据库字段 创建时间 */
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
},
|
||||
(t) => [
|
||||
uniqueIndex("users_email_idx").on(t.email),
|
||||
index("users_legacy_idx").on(t.legacyId),
|
||||
],
|
||||
);
|
||||
|
||||
/** # 数据库表
|
||||
* 服务端会话(HttpOnly Cookie → 会话记录)。
|
||||
*/
|
||||
export const sessions = pgTable(
|
||||
"sessions",
|
||||
{
|
||||
/** # 数据库字段 会话令牌 */
|
||||
token: varchar("token", { length: 64 }).primaryKey(),
|
||||
/** # 数据库字段 用户 ID */
|
||||
userId: integer("user_id").notNull(),
|
||||
/** # 数据库字段 过期时间 */
|
||||
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
||||
/** # 数据库字段 创建时间 */
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
},
|
||||
(t) => [index("sessions_user_idx").on(t.userId)],
|
||||
);
|
||||
|
||||
/** # 数据库表
|
||||
* 购物车(登录用户或匿名 session_key 维度)。
|
||||
*/
|
||||
export const carts = pgTable("carts", {
|
||||
/** # 数据库字段 购物车 ID */
|
||||
id: serial("id").primaryKey(),
|
||||
/** # 数据库字段 用户 ID(登录用户) */
|
||||
userId: integer("user_id"),
|
||||
/** # 数据库字段 匿名会话键 */
|
||||
sessionKey: varchar("session_key", { length: 64 }),
|
||||
/** # 数据库字段 创建时间 */
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
/** # 数据库字段 更新时间 */
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
});
|
||||
|
||||
/** # 数据库表
|
||||
* 购物车明细。
|
||||
*/
|
||||
export const cartItems = pgTable(
|
||||
"cart_items",
|
||||
{
|
||||
/** # 数据库字段 明细 ID */
|
||||
id: serial("id").primaryKey(),
|
||||
/** # 数据库字段 购物车 ID */
|
||||
cartId: integer("cart_id").notNull(),
|
||||
/** # 数据库字段 产品 ID */
|
||||
productId: integer("product_id").notNull(),
|
||||
/** # 数据库字段 数量 */
|
||||
qty: integer("qty").default(1).notNull(),
|
||||
},
|
||||
(t) => [index("cart_items_cart_idx").on(t.cartId)],
|
||||
);
|
||||
|
||||
/** # 数据库表
|
||||
* 订单主表。
|
||||
*/
|
||||
export const orders = pgTable(
|
||||
"orders",
|
||||
{
|
||||
/** # 数据库字段 订单 ID */
|
||||
id: serial("id").primaryKey(),
|
||||
/** # 数据库字段 订单号(对外展示) */
|
||||
orderNumber: varchar("order_number", { length: 32 }).notNull(),
|
||||
/** # 数据库字段 客户 ID */
|
||||
userId: integer("user_id"),
|
||||
/** # 数据库字段 状态:pending | on-hold | processing | completed | cancelled | refunded */
|
||||
status: varchar("status", { length: 24 }).default("pending").notNull(),
|
||||
/** # 数据库字段 币种 */
|
||||
currency: varchar("currency", { length: 8 }).default("USD").notNull(),
|
||||
/** # 数据库字段 商品小计 */
|
||||
subtotal: numeric("subtotal", { precision: 12, scale: 2 }).default("0").notNull(),
|
||||
/** # 数据库字段 运费 */
|
||||
shippingTotal: numeric("shipping_total", { precision: 12, scale: 2 }).default("0").notNull(),
|
||||
/** # 数据库字段 税费 */
|
||||
taxTotal: numeric("tax_total", { precision: 12, scale: 2 }).default("0").notNull(),
|
||||
/** # 数据库字段 订单总额 */
|
||||
total: numeric("total", { precision: 12, scale: 2 }).default("0").notNull(),
|
||||
/** # 数据库字段 支付方式 */
|
||||
paymentMethod: varchar("payment_method", { length: 64 }),
|
||||
/** # 数据库字段 支付状态 */
|
||||
paymentStatus: varchar("payment_status", { length: 24 }).default("unpaid").notNull(),
|
||||
/** # 数据库字段 支付交易号 */
|
||||
transactionId: varchar("transaction_id", { length: 128 }),
|
||||
/** # 数据库字段 账单信息 */
|
||||
billing: jsonb("billing").default({}).notNull(),
|
||||
/** # 数据库字段 收货信息 */
|
||||
shipping: jsonb("shipping").default({}).notNull(),
|
||||
/** # 数据库字段 客户备注 */
|
||||
customerNote: text("customer_note").default("").notNull(),
|
||||
/** # 数据库字段 原 WP order ID */
|
||||
legacyId: integer("legacy_id"),
|
||||
/** # 数据库字段 下单时间 */
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
/** # 数据库字段 更新时间 */
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
},
|
||||
(t) => [
|
||||
uniqueIndex("orders_number_idx").on(t.orderNumber),
|
||||
index("orders_status_idx").on(t.status),
|
||||
index("orders_user_idx").on(t.userId),
|
||||
],
|
||||
);
|
||||
|
||||
/** # 数据库表
|
||||
* 订单明细(下单时快照商品名/价格)。
|
||||
*/
|
||||
export const orderItems = pgTable(
|
||||
"order_items",
|
||||
{
|
||||
/** # 数据库字段 明细 ID */
|
||||
id: serial("id").primaryKey(),
|
||||
/** # 数据库字段 订单 ID */
|
||||
orderId: integer("order_id").notNull(),
|
||||
/** # 数据库字段 产品 ID */
|
||||
productId: integer("product_id"),
|
||||
/** # 数据库字段 商品名快照 */
|
||||
name: text("name").notNull(),
|
||||
/** # 数据库字段 SKU 快照 */
|
||||
sku: varchar("sku", { length: 128 }),
|
||||
/** # 数据库字段 数量 */
|
||||
qty: integer("qty").default(1).notNull(),
|
||||
/** # 数据库字段 单价 */
|
||||
price: numeric("price", { precision: 12, scale: 2 }).default("0").notNull(),
|
||||
/** # 数据库字段 小计 */
|
||||
total: numeric("total", { precision: 12, scale: 2 }).default("0").notNull(),
|
||||
},
|
||||
(t) => [index("order_items_order_idx").on(t.orderId)],
|
||||
);
|
||||
|
||||
/** # 数据库表
|
||||
* 支付流水(含第三方回调原始报文,幂等键 transaction_id)。
|
||||
*/
|
||||
export const payments = pgTable(
|
||||
"payments",
|
||||
{
|
||||
/** # 数据库字段 支付 ID */
|
||||
id: serial("id").primaryKey(),
|
||||
/** # 数据库字段 订单 ID */
|
||||
orderId: integer("order_id").notNull(),
|
||||
/** # 数据库字段 支付方式 */
|
||||
method: varchar("method", { length: 64 }).notNull(),
|
||||
/** # 数据库字段 状态:succeeded | failed | refunded | pending */
|
||||
status: varchar("status", { length: 24 }).default("pending").notNull(),
|
||||
/** # 数据库字段 交易号(唯一,回调幂等) */
|
||||
transactionId: varchar("transaction_id", { length: 128 }),
|
||||
/** # 数据库字段 金额 */
|
||||
amount: numeric("amount", { precision: 12, scale: 2 }).default("0").notNull(),
|
||||
/** # 数据库字段 回调原始数据 */
|
||||
raw: jsonb("raw").default({}).notNull(),
|
||||
/** # 数据库字段 创建时间 */
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
},
|
||||
(t) => [
|
||||
index("payments_order_idx").on(t.orderId),
|
||||
uniqueIndex("payments_txn_idx").on(t.transactionId),
|
||||
],
|
||||
);
|
||||
|
||||
/* ============================= 询盘域 ============================= */
|
||||
|
||||
/** # 数据库表
|
||||
* 询盘表单定义(Contact Form 7 迁移,字段结构存 JSON)。
|
||||
*/
|
||||
export const inquiryForms = pgTable(
|
||||
"inquiry_forms",
|
||||
{
|
||||
/** # 数据库字段 表单 ID */
|
||||
id: serial("id").primaryKey(),
|
||||
/** # 数据库字段 表单名称 */
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
/** # 数据库字段 表单 key(slug) */
|
||||
formKey: varchar("form_key", { length: 128 }).notNull(),
|
||||
/** # 数据库字段 字段定义 JSON */
|
||||
fields: jsonb("fields").default([]).notNull(),
|
||||
/** # 数据库字段 收件邮箱 */
|
||||
mailTo: varchar("mail_to", { length: 255 }),
|
||||
/** # 数据库字段 是否启用 */
|
||||
enabled: boolean("enabled").default(true).notNull(),
|
||||
/** # 数据库字段 原 CF7 form_id */
|
||||
legacyId: integer("legacy_id"),
|
||||
},
|
||||
(t) => [uniqueIndex("inquiry_forms_key_idx").on(t.formKey)],
|
||||
);
|
||||
|
||||
/** # 数据库表
|
||||
* 询盘记录(CF7/Flamingo 与 Elementor Form 提交合并归一)。
|
||||
*/
|
||||
export const inquiries = pgTable(
|
||||
"inquiries",
|
||||
{
|
||||
/** # 数据库字段 询盘 ID */
|
||||
id: serial("id").primaryKey(),
|
||||
/** # 数据库字段 来源表单名 */
|
||||
formName: varchar("form_name", { length: 255 }).default("").notNull(),
|
||||
/** # 数据库字段 表单 ID */
|
||||
formId: integer("form_id"),
|
||||
/** # 数据库字段 姓名 */
|
||||
name: varchar("name", { length: 255 }).default("").notNull(),
|
||||
/** # 数据库字段 邮箱 */
|
||||
email: varchar("email", { length: 255 }).default("").notNull(),
|
||||
/** # 数据库字段 电话 */
|
||||
phone: varchar("phone", { length: 128 }).default("").notNull(),
|
||||
/** # 数据库字段 公司 */
|
||||
company: varchar("company", { length: 255 }).default("").notNull(),
|
||||
/** # 数据库字段 国家/地区 */
|
||||
country: varchar("country", { length: 128 }).default("").notNull(),
|
||||
/** # 数据库字段 询盘内容 */
|
||||
message: text("message").default("").notNull(),
|
||||
/** # 数据库字段 感兴趣的产品 */
|
||||
productInterest: varchar("product_interest", { length: 255 }).default("").notNull(),
|
||||
/** # 数据库字段 完整原始字段(含 annualdemand、utm 等) */
|
||||
fields: jsonb("fields").default({}).notNull(),
|
||||
/** # 数据库字段 状态:new | read | replied | spam | archived */
|
||||
status: varchar("status", { length: 16 }).default("new").notNull(),
|
||||
/** # 数据库字段 来源渠道(channel / subject,可能很长用 text) */
|
||||
channel: text("channel").default("").notNull(),
|
||||
/** # 数据库字段 来源页面 */
|
||||
sourcePage: text("source_page").default("").notNull(),
|
||||
/** # 数据库字段 来源 IP */
|
||||
ip: varchar("ip", { length: 64 }).default("").notNull(),
|
||||
/** # 数据库字段 UTM 参数 */
|
||||
utm: jsonb("utm").default({}).notNull(),
|
||||
/** # 数据库字段 原 Flamingo/Elementor 记录 ID */
|
||||
legacyId: integer("legacy_id"),
|
||||
/** # 数据库字段 提交时间 */
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
},
|
||||
(t) => [
|
||||
index("inquiries_status_idx").on(t.status, t.createdAt),
|
||||
index("inquiries_email_idx").on(t.email),
|
||||
index("inquiries_created_idx").on(t.createdAt),
|
||||
],
|
||||
);
|
||||
|
||||
/* ============================= 运营域 ============================= */
|
||||
|
||||
/** # 数据库表
|
||||
* 媒体库(原 wp-content/uploads 附件)。
|
||||
*/
|
||||
export const media = pgTable(
|
||||
"media",
|
||||
{
|
||||
/** # 数据库字段 媒体 ID */
|
||||
id: serial("id").primaryKey(),
|
||||
/** # 数据库字段 相对路径(uploads 下) */
|
||||
path: varchar("path", { length: 512 }).notNull(),
|
||||
/** # 数据库字段 访问 URL */
|
||||
url: text("url").notNull(),
|
||||
/** # 数据库字段 MIME 类型 */
|
||||
mimeType: varchar("mime_type", { length: 128 }).default("").notNull(),
|
||||
/** # 数据库字段 宽 */
|
||||
width: integer("width"),
|
||||
/** # 数据库字段 高 */
|
||||
height: integer("height"),
|
||||
/** # 数据库字段 文件大小 */
|
||||
size: bigint("size", { mode: "number" }),
|
||||
/** # 数据库字段 替代文本 */
|
||||
altText: text("alt_text").default("").notNull(),
|
||||
/** # 数据库字段 标题 */
|
||||
title: text("title").default("").notNull(),
|
||||
/** # 数据库字段 引用次数 */
|
||||
refCount: integer("ref_count").default(0).notNull(),
|
||||
/** # 数据库字段 原 WP attachment ID */
|
||||
legacyId: integer("legacy_id"),
|
||||
/** # 数据库字段 上传时间 */
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
},
|
||||
(t) => [
|
||||
uniqueIndex("media_path_idx").on(t.path),
|
||||
index("media_legacy_idx").on(t.legacyId),
|
||||
],
|
||||
);
|
||||
|
||||
/** # 数据库表
|
||||
* 301/302 重定向(Redirection 插件迁移)。
|
||||
*/
|
||||
export const redirects = pgTable(
|
||||
"redirects",
|
||||
{
|
||||
/** # 数据库字段 重定向 ID */
|
||||
id: serial("id").primaryKey(),
|
||||
/** # 数据库字段 源路径(支持正则) */
|
||||
source: text("source").notNull(),
|
||||
/** # 数据库字段 目标地址 */
|
||||
target: text("target").notNull(),
|
||||
/** # 数据库字段 状态码 */
|
||||
code: integer("code").default(301).notNull(),
|
||||
/** # 数据库字段 是否正则匹配 */
|
||||
isRegex: boolean("is_regex").default(false).notNull(),
|
||||
/** # 数据库字段 命中次数 */
|
||||
hits: integer("hits").default(0).notNull(),
|
||||
/** # 数据库字段 是否启用 */
|
||||
enabled: boolean("enabled").default(true).notNull(),
|
||||
/** # 数据库字段 原 redirection item id */
|
||||
legacyId: integer("legacy_id"),
|
||||
},
|
||||
(t) => [index("redirects_source_idx").on(t.source)],
|
||||
);
|
||||
|
||||
/** # 数据库表
|
||||
* 筛选配置(Filter Everything 迁移,前台产品筛选器)。
|
||||
*/
|
||||
export const filters = pgTable(
|
||||
"filters",
|
||||
{
|
||||
/** # 数据库字段 筛选 ID */
|
||||
id: serial("id").primaryKey(),
|
||||
/** # 数据库字段 筛选名称 */
|
||||
name: varchar("name", { length: 128 }).notNull(),
|
||||
/** # 数据库字段 URL 参数名(如 chip-platform) */
|
||||
slug: varchar("slug", { length: 128 }).notNull(),
|
||||
/** # 数据库字段 类型:attribute | category | price */
|
||||
type: varchar("type", { length: 24 }).default("attribute").notNull(),
|
||||
/** # 数据库字段 关联属性 ID(type=attribute 时) */
|
||||
attributeId: integer("attribute_id"),
|
||||
/** # 数据库字段 关联 taxonomy */
|
||||
taxonomy: varchar("taxonomy", { length: 64 }),
|
||||
/** # 数据库字段 显示位置 */
|
||||
position: integer("position").default(0).notNull(),
|
||||
/** # 数据库字段 是否启用 */
|
||||
enabled: boolean("enabled").default(true).notNull(),
|
||||
/** # 数据库字段 原 filter-field post_id */
|
||||
legacyId: integer("legacy_id"),
|
||||
},
|
||||
(t) => [uniqueIndex("filters_slug_idx").on(t.slug)],
|
||||
);
|
||||
|
||||
/** # 数据库表
|
||||
* 语言配置(GTranslate 迁移:启用的语言与默认语言)。
|
||||
*/
|
||||
export const languages = pgTable(
|
||||
"languages",
|
||||
{
|
||||
/** # 数据库字段 语言代码(BCP47) */
|
||||
code: varchar("code", { length: 16 }).primaryKey(),
|
||||
/** # 数据库字段 显示名 */
|
||||
name: varchar("name", { length: 64 }).notNull(),
|
||||
/** # 数据库字段 是否默认语言 */
|
||||
isDefault: boolean("is_default").default(false).notNull(),
|
||||
/** # 数据库字段 是否启用(可发布真实页面) */
|
||||
enabled: boolean("enabled").default(false).notNull(),
|
||||
/** # 数据库字段 是否机器翻译(GTranslate 存量) */
|
||||
machine: boolean("machine").default(false).notNull(),
|
||||
/** # 数据库字段 排序 */
|
||||
sort: integer("sort").default(0).notNull(),
|
||||
},
|
||||
);
|
||||
|
||||
/** # 数据库表
|
||||
* 持久化任务队列(独立于 Web 请求的后台任务,带重试)。
|
||||
*/
|
||||
export const jobs = pgTable(
|
||||
"jobs",
|
||||
{
|
||||
/** # 数据库字段 任务 ID */
|
||||
id: serial("id").primaryKey(),
|
||||
/** # 数据库字段 任务类型(email.send / media.reindex …) */
|
||||
type: varchar("type", { length: 64 }).notNull(),
|
||||
/** # 数据库字段 任务参数 */
|
||||
payload: jsonb("payload").default({}).notNull(),
|
||||
/** # 数据库字段 状态:pending | running | done | failed */
|
||||
status: varchar("status", { length: 16 }).default("pending").notNull(),
|
||||
/** # 数据库字段 已重试次数 */
|
||||
attempts: integer("attempts").default(0).notNull(),
|
||||
/** # 数据库字段 最大重试次数 */
|
||||
maxAttempts: integer("max_attempts").default(3).notNull(),
|
||||
/** # 数据库字段 计划执行时间 */
|
||||
runAt: timestamp("run_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
/** # 数据库字段 最近错误 */
|
||||
lastError: text("last_error"),
|
||||
/** # 数据库字段 创建时间 */
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
/** # 数据库字段 更新时间 */
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
},
|
||||
(t) => [index("jobs_status_runat_idx").on(t.status, t.runAt)],
|
||||
);
|
||||
|
||||
/** # 数据库表
|
||||
* 审计日志(后台敏感操作留痕)。
|
||||
*/
|
||||
export const auditLogs = pgTable(
|
||||
"audit_logs",
|
||||
{
|
||||
/** # 数据库字段 日志 ID */
|
||||
id: serial("id").primaryKey(),
|
||||
/** # 数据库字段 操作人 */
|
||||
actorId: integer("actor_id"),
|
||||
/** # 数据库字段 动作(如 product.update) */
|
||||
action: varchar("action", { length: 128 }).notNull(),
|
||||
/** # 数据库字段 实体类型 */
|
||||
entity: varchar("entity", { length: 64 }).notNull(),
|
||||
/** # 数据库字段 实体 ID */
|
||||
entityId: varchar("entity_id", { length: 64 }),
|
||||
/** # 数据库字段 附加数据 */
|
||||
meta: jsonb("meta").default({}).notNull(),
|
||||
/** # 数据库字段 操作 IP */
|
||||
ip: varchar("ip", { length: 64 }).default("").notNull(),
|
||||
/** # 数据库字段 操作时间 */
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
},
|
||||
(t) => [index("audit_logs_time_idx").on(t.createdAt)],
|
||||
);
|
||||
|
||||
/** # 数据库表
|
||||
* 站点设置(键值 JSON:导航、邮件、支付、缓存开关等)。
|
||||
*/
|
||||
export const settings = pgTable("settings", {
|
||||
/** # 数据库字段 设置键 */
|
||||
key: varchar("key", { length: 128 }).primaryKey(),
|
||||
/** # 数据库字段 设置值 */
|
||||
value: jsonb("value").default({}).notNull(),
|
||||
/** # 数据库字段 更新时间 */
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
});
|
||||
|
||||
/** # 数据库表
|
||||
* 旧 ID 映射(WordPress ID → 新系统 ID,迁移校验与增量同步用)。
|
||||
*/
|
||||
export const legacyIdMappings = pgTable(
|
||||
"legacy_id_mappings",
|
||||
{
|
||||
/** # 数据库字段 映射 ID */
|
||||
id: serial("id").primaryKey(),
|
||||
/** # 数据库字段 实体类型(post/product/user/order/media …) */
|
||||
entity: varchar("entity", { length: 64 }).notNull(),
|
||||
/** # 数据库字段 原 WP ID */
|
||||
legacyId: integer("legacy_id").notNull(),
|
||||
/** # 数据库字段 新系统 ID */
|
||||
newId: integer("new_id").notNull(),
|
||||
},
|
||||
(t) => [uniqueIndex("legacy_map_entity_id_idx").on(t.entity, t.legacyId)],
|
||||
);
|
||||
Reference in New Issue
Block a user