feat(deploy): production deployment setup and fixes
- Debian-based api image (bookworm-slim), docker/debian mirrors, prisma binaryTargets for openssl 3.0 - nginx: admin SPA under /admin, TLS via acme.sh (ZeroSSL) + auto-renewal cron, http->https redirect - prisma: add origin_goods.delisted migration, sync missing schema (good_image/tag_font_color/good_tags), fix users.createdAt Timestamptz - api: CORS wildcard reflection, helmet CORP cross-origin, price backfill in persistProductDetail, categoryIcon ancestor fallback, mediaByColor per-color gallery in public goods detail - admin: /admin base path (vite + router) - import-data.mjs: udt_name casting, serial sequence advance fix
This commit is contained in:
+172
-172
@@ -1,173 +1,173 @@
|
||||
-- 1. 原商品表(外部来源商品)
|
||||
CREATE TABLE origin_goods (
|
||||
origin_good_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
sds_good_id TEXT NOT NULL, -- SDS商品ID,假设为外部系统标识
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE origin_goods IS '原商品表,记录外部来源商品';
|
||||
COMMENT ON COLUMN origin_goods.origin_good_id IS '原商品ID(主键)';
|
||||
COMMENT ON COLUMN origin_goods.sds_good_id IS 'SDS商品ID';
|
||||
|
||||
-- 2. 国家表
|
||||
CREATE TABLE countries (
|
||||
country_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
country_name TEXT NOT NULL UNIQUE, -- 国家名称唯一
|
||||
country_icon TEXT, -- 图标URL或路径
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE countries IS '国家表';
|
||||
COMMENT ON COLUMN countries.country_id IS '国家ID(主键)';
|
||||
COMMENT ON COLUMN countries.country_name IS '国家名称';
|
||||
COMMENT ON COLUMN countries.country_icon IS '国家图标';
|
||||
|
||||
-- 3. 品类表(支持层级)
|
||||
CREATE TABLE categories (
|
||||
category_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
parent_category_id BIGINT REFERENCES categories(category_id) ON DELETE RESTRICT,
|
||||
category_name TEXT NOT NULL,
|
||||
category_icon TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE categories IS '品类表,支持自关联层级';
|
||||
COMMENT ON COLUMN categories.category_id IS '品类ID(主键)';
|
||||
COMMENT ON COLUMN categories.parent_category_id IS '父品类ID,NULL表示顶级品类';
|
||||
COMMENT ON COLUMN categories.category_name IS '品类名称';
|
||||
COMMENT ON COLUMN categories.category_icon IS '品类图标';
|
||||
|
||||
-- 索引:外键列
|
||||
CREATE INDEX idx_categories_parent_id ON categories(parent_category_id);
|
||||
|
||||
-- 4. 标签表
|
||||
CREATE TABLE tags (
|
||||
tag_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
tag_name TEXT NOT NULL UNIQUE,
|
||||
tag_color TEXT, -- 十六进制颜色码,如 '#FF0000'
|
||||
timing TEXT, -- 时机(如促销时段)
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE tags IS '标签表';
|
||||
COMMENT ON COLUMN tags.tag_id IS '标签ID(主键)';
|
||||
COMMENT ON COLUMN tags.tag_name IS '标签名';
|
||||
COMMENT ON COLUMN tags.tag_color IS '标签颜色(前端展示用)';
|
||||
COMMENT ON COLUMN tags.timing IS '时机(例如节日促销、季节限定)';
|
||||
|
||||
-- 5. 位置表(展示位置定义)
|
||||
-- 原设计中的 Indexes 表,为避免与数据库索引关键字混淆,重命名为 positions
|
||||
CREATE TABLE positions (
|
||||
position_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
index_val INTEGER NOT NULL, -- 位置排序值或权重
|
||||
country_id BIGINT REFERENCES countries(country_id) ON DELETE CASCADE,
|
||||
category_id BIGINT REFERENCES categories(category_id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE positions IS '展示位置表,定义商品在特定国家/品类下的展示排序';
|
||||
COMMENT ON COLUMN positions.position_id IS '位置ID(主键)';
|
||||
COMMENT ON COLUMN positions.index_val IS '位置值(如排序序号、优先级权重)';
|
||||
COMMENT ON COLUMN positions.country_id IS '关联的国家ID,NULL表示不限国家';
|
||||
COMMENT ON COLUMN positions.category_id IS '关联的品类ID,NULL表示不限品类';
|
||||
|
||||
-- 索引:外键列
|
||||
CREATE INDEX idx_positions_country_id ON positions(country_id);
|
||||
CREATE INDEX idx_positions_category_id ON positions(category_id);
|
||||
-- 复合索引用于快速查询某个国家+品类下的位置列表
|
||||
CREATE INDEX idx_positions_country_category ON positions(country_id, category_id);
|
||||
|
||||
-- 6. 商品表(核心表)
|
||||
CREATE TABLE goods (
|
||||
good_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
origin_good_id BIGINT REFERENCES origin_goods(origin_good_id) ON DELETE RESTRICT,
|
||||
country_id BIGINT NOT NULL REFERENCES countries(country_id) ON DELETE RESTRICT,
|
||||
category_id BIGINT NOT NULL REFERENCES categories(category_id) ON DELETE RESTRICT,
|
||||
tag_id BIGINT REFERENCES tags(tag_id) ON DELETE SET NULL,
|
||||
position_id BIGINT REFERENCES positions(position_id) ON DELETE SET NULL,
|
||||
good_name TEXT NOT NULL,
|
||||
good_priority INTEGER NOT NULL DEFAULT 0 CHECK (good_priority >= 0), -- 优先级,数值越高越靠前
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE goods IS '商品主表';
|
||||
COMMENT ON COLUMN goods.good_id IS '商品ID(主键)';
|
||||
COMMENT ON COLUMN goods.origin_good_id IS '原商品ID(关联外部来源)';
|
||||
COMMENT ON COLUMN goods.country_id IS '国家ID(必填)';
|
||||
COMMENT ON COLUMN goods.category_id IS '品类ID(必填)';
|
||||
COMMENT ON COLUMN goods.tag_id IS '标签ID(可为空)';
|
||||
COMMENT ON COLUMN goods.position_id IS '展示位置ID(可为空)';
|
||||
COMMENT ON COLUMN goods.good_name IS '商品名称';
|
||||
COMMENT ON COLUMN goods.good_priority IS '商品优先级(全局排序,越大越靠前)';
|
||||
|
||||
-- 外键索引(必须手动创建)
|
||||
CREATE INDEX idx_goods_origin_good_id ON goods(origin_good_id);
|
||||
CREATE INDEX idx_goods_country_id ON goods(country_id);
|
||||
CREATE INDEX idx_goods_category_id ON goods(category_id);
|
||||
CREATE INDEX idx_goods_tag_id ON goods(tag_id);
|
||||
CREATE INDEX idx_goods_position_id ON goods(position_id);
|
||||
|
||||
-- 常用查询索引
|
||||
CREATE INDEX idx_goods_priority ON goods(good_priority DESC); -- 按优先级排序
|
||||
CREATE INDEX idx_goods_country_priority ON goods(country_id, good_priority DESC); -- 按国家+优先级
|
||||
CREATE INDEX idx_goods_category_country ON goods(category_id, country_id); -- 品类+国家过滤
|
||||
|
||||
-- 可选:部分索引,仅索引活跃商品(如果有软删除或状态字段)
|
||||
-- 当前设计无状态字段,暂不添加
|
||||
|
||||
-- 7. (可选)商品与标签的多对多关联表
|
||||
-- 原设计中 goods 表仅有单一 tag_id,限制了多标签场景。
|
||||
-- 若业务需要多标签,建议增加关联表,并移除 goods.tag_id 字段。
|
||||
-- 以下为扩展设计(可根据实际需求决定是否启用):
|
||||
|
||||
/*
|
||||
CREATE TABLE goods_tags (
|
||||
goods_id BIGINT NOT NULL REFERENCES goods(good_id) ON DELETE CASCADE,
|
||||
tag_id BIGINT NOT NULL REFERENCES tags(tag_id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (goods_id, tag_id)
|
||||
);
|
||||
CREATE INDEX idx_goods_tags_tag_id ON goods_tags(tag_id);
|
||||
*/
|
||||
|
||||
-- 注意:若使用 goods_tags 关联表,则应删除 goods 表中的 tag_id 列。
|
||||
-- 保留原单标签设计还是采用多标签,请根据业务需求选择。
|
||||
|
||||
-- 8. 自动更新 updated_at 的触发器函数(可选)
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = now();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- 为每个表应用自动更新 updated_at 的触发器
|
||||
CREATE TRIGGER trigger_origin_goods_updated_at
|
||||
BEFORE UPDATE ON origin_goods
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER trigger_countries_updated_at
|
||||
BEFORE UPDATE ON countries
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER trigger_categories_updated_at
|
||||
BEFORE UPDATE ON categories
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER trigger_tags_updated_at
|
||||
BEFORE UPDATE ON tags
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER trigger_positions_updated_at
|
||||
BEFORE UPDATE ON positions
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER trigger_goods_updated_at
|
||||
BEFORE UPDATE ON goods
|
||||
-- 1. 原商品表(外部来源商品)
|
||||
CREATE TABLE origin_goods (
|
||||
origin_good_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
sds_good_id TEXT NOT NULL, -- SDS商品ID,假设为外部系统标识
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE origin_goods IS '原商品表,记录外部来源商品';
|
||||
COMMENT ON COLUMN origin_goods.origin_good_id IS '原商品ID(主键)';
|
||||
COMMENT ON COLUMN origin_goods.sds_good_id IS 'SDS商品ID';
|
||||
|
||||
-- 2. 国家表
|
||||
CREATE TABLE countries (
|
||||
country_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
country_name TEXT NOT NULL UNIQUE, -- 国家名称唯一
|
||||
country_icon TEXT, -- 图标URL或路径
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE countries IS '国家表';
|
||||
COMMENT ON COLUMN countries.country_id IS '国家ID(主键)';
|
||||
COMMENT ON COLUMN countries.country_name IS '国家名称';
|
||||
COMMENT ON COLUMN countries.country_icon IS '国家图标';
|
||||
|
||||
-- 3. 品类表(支持层级)
|
||||
CREATE TABLE categories (
|
||||
category_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
parent_category_id BIGINT REFERENCES categories(category_id) ON DELETE RESTRICT,
|
||||
category_name TEXT NOT NULL,
|
||||
category_icon TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE categories IS '品类表,支持自关联层级';
|
||||
COMMENT ON COLUMN categories.category_id IS '品类ID(主键)';
|
||||
COMMENT ON COLUMN categories.parent_category_id IS '父品类ID,NULL表示顶级品类';
|
||||
COMMENT ON COLUMN categories.category_name IS '品类名称';
|
||||
COMMENT ON COLUMN categories.category_icon IS '品类图标';
|
||||
|
||||
-- 索引:外键列
|
||||
CREATE INDEX idx_categories_parent_id ON categories(parent_category_id);
|
||||
|
||||
-- 4. 标签表
|
||||
CREATE TABLE tags (
|
||||
tag_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
tag_name TEXT NOT NULL UNIQUE,
|
||||
tag_color TEXT, -- 十六进制颜色码,如 '#FF0000'
|
||||
timing TEXT, -- 时机(如促销时段)
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE tags IS '标签表';
|
||||
COMMENT ON COLUMN tags.tag_id IS '标签ID(主键)';
|
||||
COMMENT ON COLUMN tags.tag_name IS '标签名';
|
||||
COMMENT ON COLUMN tags.tag_color IS '标签颜色(前端展示用)';
|
||||
COMMENT ON COLUMN tags.timing IS '时机(例如节日促销、季节限定)';
|
||||
|
||||
-- 5. 位置表(展示位置定义)
|
||||
-- 原设计中的 Indexes 表,为避免与数据库索引关键字混淆,重命名为 positions
|
||||
CREATE TABLE positions (
|
||||
position_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
index_val INTEGER NOT NULL, -- 位置排序值或权重
|
||||
country_id BIGINT REFERENCES countries(country_id) ON DELETE CASCADE,
|
||||
category_id BIGINT REFERENCES categories(category_id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE positions IS '展示位置表,定义商品在特定国家/品类下的展示排序';
|
||||
COMMENT ON COLUMN positions.position_id IS '位置ID(主键)';
|
||||
COMMENT ON COLUMN positions.index_val IS '位置值(如排序序号、优先级权重)';
|
||||
COMMENT ON COLUMN positions.country_id IS '关联的国家ID,NULL表示不限国家';
|
||||
COMMENT ON COLUMN positions.category_id IS '关联的品类ID,NULL表示不限品类';
|
||||
|
||||
-- 索引:外键列
|
||||
CREATE INDEX idx_positions_country_id ON positions(country_id);
|
||||
CREATE INDEX idx_positions_category_id ON positions(category_id);
|
||||
-- 复合索引用于快速查询某个国家+品类下的位置列表
|
||||
CREATE INDEX idx_positions_country_category ON positions(country_id, category_id);
|
||||
|
||||
-- 6. 商品表(核心表)
|
||||
CREATE TABLE goods (
|
||||
good_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
origin_good_id BIGINT REFERENCES origin_goods(origin_good_id) ON DELETE RESTRICT,
|
||||
country_id BIGINT NOT NULL REFERENCES countries(country_id) ON DELETE RESTRICT,
|
||||
category_id BIGINT NOT NULL REFERENCES categories(category_id) ON DELETE RESTRICT,
|
||||
tag_id BIGINT REFERENCES tags(tag_id) ON DELETE SET NULL,
|
||||
position_id BIGINT REFERENCES positions(position_id) ON DELETE SET NULL,
|
||||
good_name TEXT NOT NULL,
|
||||
good_priority INTEGER NOT NULL DEFAULT 0 CHECK (good_priority >= 0), -- 优先级,数值越高越靠前
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE goods IS '商品主表';
|
||||
COMMENT ON COLUMN goods.good_id IS '商品ID(主键)';
|
||||
COMMENT ON COLUMN goods.origin_good_id IS '原商品ID(关联外部来源)';
|
||||
COMMENT ON COLUMN goods.country_id IS '国家ID(必填)';
|
||||
COMMENT ON COLUMN goods.category_id IS '品类ID(必填)';
|
||||
COMMENT ON COLUMN goods.tag_id IS '标签ID(可为空)';
|
||||
COMMENT ON COLUMN goods.position_id IS '展示位置ID(可为空)';
|
||||
COMMENT ON COLUMN goods.good_name IS '商品名称';
|
||||
COMMENT ON COLUMN goods.good_priority IS '商品优先级(全局排序,越大越靠前)';
|
||||
|
||||
-- 外键索引(必须手动创建)
|
||||
CREATE INDEX idx_goods_origin_good_id ON goods(origin_good_id);
|
||||
CREATE INDEX idx_goods_country_id ON goods(country_id);
|
||||
CREATE INDEX idx_goods_category_id ON goods(category_id);
|
||||
CREATE INDEX idx_goods_tag_id ON goods(tag_id);
|
||||
CREATE INDEX idx_goods_position_id ON goods(position_id);
|
||||
|
||||
-- 常用查询索引
|
||||
CREATE INDEX idx_goods_priority ON goods(good_priority DESC); -- 按优先级排序
|
||||
CREATE INDEX idx_goods_country_priority ON goods(country_id, good_priority DESC); -- 按国家+优先级
|
||||
CREATE INDEX idx_goods_category_country ON goods(category_id, country_id); -- 品类+国家过滤
|
||||
|
||||
-- 可选:部分索引,仅索引活跃商品(如果有软删除或状态字段)
|
||||
-- 当前设计无状态字段,暂不添加
|
||||
|
||||
-- 7. (可选)商品与标签的多对多关联表
|
||||
-- 原设计中 goods 表仅有单一 tag_id,限制了多标签场景。
|
||||
-- 若业务需要多标签,建议增加关联表,并移除 goods.tag_id 字段。
|
||||
-- 以下为扩展设计(可根据实际需求决定是否启用):
|
||||
|
||||
/*
|
||||
CREATE TABLE goods_tags (
|
||||
goods_id BIGINT NOT NULL REFERENCES goods(good_id) ON DELETE CASCADE,
|
||||
tag_id BIGINT NOT NULL REFERENCES tags(tag_id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (goods_id, tag_id)
|
||||
);
|
||||
CREATE INDEX idx_goods_tags_tag_id ON goods_tags(tag_id);
|
||||
*/
|
||||
|
||||
-- 注意:若使用 goods_tags 关联表,则应删除 goods 表中的 tag_id 列。
|
||||
-- 保留原单标签设计还是采用多标签,请根据业务需求选择。
|
||||
|
||||
-- 8. 自动更新 updated_at 的触发器函数(可选)
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = now();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- 为每个表应用自动更新 updated_at 的触发器
|
||||
CREATE TRIGGER trigger_origin_goods_updated_at
|
||||
BEFORE UPDATE ON origin_goods
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER trigger_countries_updated_at
|
||||
BEFORE UPDATE ON countries
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER trigger_categories_updated_at
|
||||
BEFORE UPDATE ON categories
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER trigger_tags_updated_at
|
||||
BEFORE UPDATE ON tags
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER trigger_positions_updated_at
|
||||
BEFORE UPDATE ON positions
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER trigger_goods_updated_at
|
||||
BEFORE UPDATE ON goods
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
@@ -1,12 +1,12 @@
|
||||
官网的首页我已经做好了, 位于 inkreach-official-website, 仅仅做了首页, 现在我需要做"产品中心"这个二级页面
|
||||
|
||||
具体的后台数据设计我已放在 docs/dev/database-table-design.md, 请参考
|
||||
|
||||
具体的数据库账号密码放在 .env, 请参考
|
||||
|
||||
我最终想要的效果是:
|
||||
1. 一个前端二级页: 前端的"产品中心"实现成: docs/dev/product-center-1.png \ docs/dev/product-center-2.png \ docs/dev/product-center-3.png, 这几张UI设计稿
|
||||
2. 一个前端后台: 通过设置产品的各种配置调控官网产品中心页面的展示效果
|
||||
3. 一个后端服务: 为前两者服务
|
||||
|
||||
官网的首页我已经做好了, 位于 inkreach-official-website, 仅仅做了首页, 现在我需要做"产品中心"这个二级页面
|
||||
|
||||
具体的后台数据设计我已放在 docs/dev/database-table-design.md, 请参考
|
||||
|
||||
具体的数据库账号密码放在 .env, 请参考
|
||||
|
||||
我最终想要的效果是:
|
||||
1. 一个前端二级页: 前端的"产品中心"实现成: docs/dev/product-center-1.png \ docs/dev/product-center-2.png \ docs/dev/product-center-3.png, 这几张UI设计稿
|
||||
2. 一个前端后台: 通过设置产品的各种配置调控官网产品中心页面的展示效果
|
||||
3. 一个后端服务: 为前两者服务
|
||||
|
||||
SDS 获取的产品列表(pod-api.ts), 其原始数据是一个树形结构, 通过后台二次配置, 再展现到前端首页
|
||||
@@ -1,409 +1,409 @@
|
||||
# Teable 权限矩阵 UI 交互说明
|
||||
|
||||
> 页面:`https://teable.inkreach.cc/base/bseVtXyL4aojvUDwFhn/authority-matrix`
|
||||
> 梳理日期:2026-07-29
|
||||
> 梳理方式:使用管理员账号在真实页面进行黑盒交互验证。未创建或保存测试角色,页面最终恢复为初始的“权限矩阵关闭”状态。
|
||||
|
||||
## 1. 页面定位与整体结构
|
||||
|
||||
权限矩阵用于为当前数据库配置基于角色的访问控制。页面主内容从上到下分为:
|
||||
|
||||
1. 标题区:`权限矩阵`、总开关、顶部 `添加角色`。
|
||||
2. 管理员区:管理员规则说明、`查看`、`添加用户`、管理员用户列表。
|
||||
3. 自定义角色区:说明文案、`默认角色` 下拉框、角色列表或空状态。
|
||||
4. 角色编辑器:添加或编辑角色时替换权限矩阵主内容区,不以模态框呈现。
|
||||
|
||||
页面仍保留数据库左侧导航和右侧 AI 助手,因此权限矩阵主内容区不是全屏页面。
|
||||
|
||||
### 1.1 页面外壳布局
|
||||
|
||||
本次桌面端实测视口为 `929 × 869 px`,页面采用横向三段式应用外壳:
|
||||
|
||||
```text
|
||||
┌──────────────────────┬──────────────────────────────────────────────┬────┐
|
||||
│ 数据库左侧导航 │ 权限矩阵主工作区 │ AI │
|
||||
│ 约 288 px │ 自适应填充剩余宽度 │栏 │
|
||||
│ │ │约 │
|
||||
│ 空间名称、搜索 │ 权限矩阵主页 / 角色编辑器 │40px│
|
||||
│ 权限矩阵、邀请、更多 │ │ │
|
||||
│ 创建、表格树 │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ 当前用户、通知 │ │ │
|
||||
└──────────────────────┴──────────────────────────────────────────────┴────┘
|
||||
```
|
||||
|
||||
- 左侧数据库导航为固定宽度栏,与权限矩阵业务内容之间使用竖向分隔线。
|
||||
- 主工作区占据中间剩余空间,是权限矩阵主页和角色编辑器共用的内容容器。
|
||||
- 右侧 AI 助手在当前宽度下折叠为窄图标栏;此前更宽状态下可展开成完整对话面板。
|
||||
- 页面外壳占满视口高度。左侧用户信息和通知入口固定在导航底部,不随中间内容滚动。
|
||||
- 权限矩阵的浮层、下拉框和禁用确认框覆盖在主工作区上方,不改变三段式外壳宽度。
|
||||
|
||||
### 1.2 权限矩阵主页布局
|
||||
|
||||
主页采用单列内容流,主内容在工作区内保留约 `32 px` 左右内边距:
|
||||
|
||||
```text
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ 权限矩阵 [总开关] [+ 添加角色] │
|
||||
│ │
|
||||
│ 管理员 │
|
||||
│ 说明文字 查看 │
|
||||
│ [添加用户] [管理员标签……] │
|
||||
│ │
|
||||
│ 自定义角色 默认角色 [无权限 ▾] │
|
||||
│ 说明文字 │
|
||||
│ │
|
||||
│ 空状态插图 │
|
||||
│ 尚未设置角色 │
|
||||
│ 辅助说明 │
|
||||
│ [+ 添加角色] │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- 顶部标题和总开关位于同一行左侧,主操作 `添加角色` 右对齐。
|
||||
- `管理员` 和 `自定义角色` 是纵向排列的两个业务区块,不使用独立卡片包裹,主要依靠标题、留白和对齐形成层级。
|
||||
- 管理员说明、`查看`、添加用户按钮和用户标签组成紧凑信息组。
|
||||
- 自定义角色区标题与说明靠左,`默认角色` 标签和下拉框在同一行靠右,形成左右对照布局。
|
||||
- 无角色时,空状态在剩余内容区域水平居中;插图、标题、说明和按钮纵向居中排列。
|
||||
- 空状态会占用大量垂直留白,使页面重点落在“创建第一个角色”操作上。
|
||||
|
||||
### 1.3 角色编辑器整体布局
|
||||
|
||||
点击 `添加角色` 后,左侧数据库导航和右侧 AI 栏保持不变,中间主页替换为角色编辑器:
|
||||
|
||||
```text
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ [返回] [角色名称输入框] │
|
||||
│ 角色规则说明备注 │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ 左侧表格导航 │ 右侧权限详情 │ │
|
||||
│ │ 搜索表格 │ 表格权限 │ │
|
||||
│ │ │ 视图权限 │ │
|
||||
│ │ 表格 / 分组树 │ 记录权限 │ │
|
||||
│ │ 权限摘要 │ 字段权限矩阵 │ │
|
||||
│ │ │ 导入 / 导出权限 │ │
|
||||
│ ├──────────────────────────┴───────────────────────────────┤ │
|
||||
│ │ [取消] [保存] [保存并返回] │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- 编辑器顶部是角色元信息区:返回图标和角色名称输入框横向排列,规则说明位于下一行。
|
||||
- 权限编辑主体使用带边框和圆角的双栏容器。
|
||||
- 在本次视口下,双栏容器宽约 `536 px`:左栏约 `228 px`,右栏约 `308 px`。尺寸为截图估算值,不代表实现中的固定像素约束。
|
||||
- 左栏为表格导航,顶部固定搜索框,下方是可展开的表格树和当前权限摘要。
|
||||
- 右栏为所选表格的权限详情。选择 `无权限` 时内容较短;选择 `可编辑` 后纵向展开多个权限分区。
|
||||
- 左右栏之间使用竖向分隔线;各权限分区之间使用横向分隔线,形成清晰的二维结构。
|
||||
- 底部操作栏横跨双栏,按钮整体右对齐,`保存并返回` 使用深色主按钮,`取消` 和 `保存` 使用次级按钮。
|
||||
|
||||
### 1.4 编辑器滚动与固定区域
|
||||
|
||||
- 编辑器外层高度受当前视口约束,不会随着字段数量无限向下撑开页面。
|
||||
- 左侧表格树与右侧权限详情位于操作栏上方的可用高度内。
|
||||
- 右侧权限详情为主要滚动区域。字段权限矩阵和导入/导出权限位于下方,需要在详情区内纵向滚动查看。
|
||||
- 底部 `取消 / 保存 / 保存并返回` 操作栏保持可见,不随右侧权限内容滚走。
|
||||
- 左侧表格导航在滚动右侧详情时保持位置,便于随时切换表格。
|
||||
- 字段权限本身是四列矩阵。在较窄工作区中,字段名列与三个操作列必须保持清晰对应;若字段名过长,需要截断或提示策略,当前数据未触发明显横向溢出。
|
||||
|
||||
### 1.5 视觉层级与密度
|
||||
|
||||
- 页面以白色为主背景,左侧当前导航项、选中表格行使用浅灰底色强调。
|
||||
- 深色填充只用于高优先级动作,例如顶部 `添加角色`、空状态 `添加角色` 和 `保存并返回`。
|
||||
- 标题层级依次为页面标题、区块标题、权限分区标题和辅助标签;字号与字重的差异比颜色差异更明显。
|
||||
- 主页密度较低,使用大面积留白突出空状态;角色编辑器密度明显提高,以分栏、分隔线、复选框和矩阵承载配置任务。
|
||||
- 权限详情中的相关控件紧密分组,不同权限域之间通过更大的上下间距和分隔线区分。
|
||||
|
||||
### 1.6 状态变化对布局的影响
|
||||
|
||||
| 状态变化 | 布局变化 |
|
||||
| --- | --- |
|
||||
| 权限矩阵关闭 → 开启 | 管理员标签直接进入管理员区,`添加用户` 启用;页面整体结构不变 |
|
||||
| 无角色 → 有角色 | 预计空状态区域替换为角色列表;本次未创建角色,因此列表布局待验证 |
|
||||
| 主页 → 添加角色 | 中间工作区整体切换为角色编辑器,左右应用外壳不变 |
|
||||
| 表格无权限 → 可编辑 | 右栏从单个权限区扩展为长内容区,并启用内部滚动 |
|
||||
| 所有视图 → 特定视图 | 在当前视图单选项下方原位插入视图复选列表,后续区块整体下移 |
|
||||
| 分组收起 → 展开 | 左栏在分组节点下原位插入二级子表,右栏当前选择不变 |
|
||||
| 搜索表格 | 左栏树被过滤,右栏继续显示当前所选表格的权限 |
|
||||
| 禁用权限矩阵 | 中央确认框覆盖主工作区,背景内容留在原位 |
|
||||
|
||||
### 1.7 响应式范围说明
|
||||
|
||||
- 以上布局基于 `929 × 869 px` 桌面视口实测。
|
||||
- 当前宽度下右侧 AI 助手已经折叠,但左侧数据库导航仍完整保留,说明系统优先压缩辅助面板。
|
||||
- 本次未切换到平板或手机宽度,因此尚不能确认左侧导航是否抽屉化、角色编辑双栏是否改为上下堆叠、字段矩阵是否启用横向滚动。
|
||||
- 若复刻该页面,建议至少验证 `1440 px`、`1024 px`、`768 px` 和 `375 px` 四档宽度,重点检查双栏编辑器和字段权限矩阵。
|
||||
|
||||
## 2. 权限矩阵总开关
|
||||
|
||||
### 2.1 关闭状态
|
||||
|
||||
- 开关为关闭状态。
|
||||
- `添加角色` 仍可点击,允许提前定义角色规则。
|
||||
- `添加用户` 为禁用状态。
|
||||
- 管理员列表不在主区域直接展示;`查看` 仍可展开管理员信息。
|
||||
- 默认角色控件和已有角色配置仍可见。
|
||||
- 当前无自定义角色时展示空状态:
|
||||
- 插图替代文本:`No roles available`。
|
||||
- 标题:`尚未设置角色`。
|
||||
- 说明:`创建你的第一个角色,开启灵活高效的权限管理`。
|
||||
- 空状态内提供第二个 `添加角色` 入口。
|
||||
|
||||
关闭权限矩阵的产品语义由确认弹窗明确说明:协作者将能够查看所有数据。
|
||||
|
||||
### 2.2 开启
|
||||
|
||||
- 点击关闭状态的开关后立即开启,没有二次确认。
|
||||
- 开关开启后,管理员用户直接显示在管理员区。
|
||||
- `添加用户` 从禁用变为可用。
|
||||
- 其余角色配置继续保留。
|
||||
|
||||
### 2.3 关闭
|
||||
|
||||
点击开启状态的开关不会立即关闭,而是显示确认弹窗:
|
||||
|
||||
- 标题:`禁用权限矩阵`。
|
||||
- 风险提示:`协作者将能够查看所有数据,是否继续?`
|
||||
- 操作:`取消`、`确认`、右上角 `Close`。
|
||||
- `取消`、`Close` 应关闭弹窗并维持开启状态。
|
||||
- `确认` 关闭权限矩阵,随后 `添加用户` 重新变为禁用。
|
||||
|
||||
因此,总开关存在有意的不对称交互:开启即时生效,关闭需要风险确认。
|
||||
|
||||
## 3. 管理员区
|
||||
|
||||
说明文案为:`空间可管理角色的用户可以管理权限矩阵,且不受其限制。`
|
||||
|
||||
这表示管理员是权限矩阵的豁免主体;角色规则不限制拥有空间角色管理能力的用户。
|
||||
|
||||
### 3.1 查看管理员
|
||||
|
||||
- `查看` 是可展开按钮。
|
||||
- 点击后在浮层中展示管理员头像和名称。
|
||||
- 再次点击按钮或点击浮层外区域可收起。
|
||||
- 本次环境只显示用户 `2197651308`。
|
||||
|
||||
### 3.2 添加用户
|
||||
|
||||
- 权限矩阵关闭时按钮禁用。
|
||||
- 权限矩阵开启后按钮可用。
|
||||
- 点击后打开用户选择浮层,并自动展开组合框。
|
||||
- 选择源只包含可被加入管理员集合的现有用户。
|
||||
- 当前没有候选用户时显示:`没有用户可被选择`。
|
||||
- `Esc` 可关闭浮层。
|
||||
|
||||
当前环境没有候选用户,因此未验证选择用户后的确认、去重和移除流程。
|
||||
|
||||
## 4. 默认角色
|
||||
|
||||
- `默认角色` 使用单选下拉框。
|
||||
- 当前没有自定义角色时,下拉框仅提供并选中 `无权限`。
|
||||
- 合理推断:创建角色后,角色会成为默认角色的可选项;新加入且未被显式分配角色的协作者应使用该默认角色。此推断需要结合产品规则或创建角色后的实际行为再次确认。
|
||||
|
||||
## 5. 添加角色入口
|
||||
|
||||
存在两个等价入口:
|
||||
|
||||
- 页面标题右侧 `添加角色`。
|
||||
- 无角色空状态中的 `添加角色`。
|
||||
|
||||
点击后,权限矩阵主页被角色编辑器替换。编辑器初始值为:
|
||||
|
||||
- 角色名:`自定义角色`。
|
||||
- 角色说明:未填写时显示 `角色规则说明备注`。
|
||||
- 所有普通表格:`无权限`。
|
||||
- 表格分组:显示 `自动隐藏`,其子表分别配置权限。
|
||||
|
||||
## 6. 角色基本信息
|
||||
|
||||
### 6.1 返回按钮
|
||||
|
||||
编辑器左上角为仅图标返回按钮。未单独验证它与底部 `取消` 在存在未保存改动时是否完全一致。
|
||||
|
||||
### 6.2 角色名称
|
||||
|
||||
- 角色名称为单行文本框。
|
||||
- 新建角色默认填入 `自定义角色`,可直接编辑。
|
||||
- 本次未提交空名称、重复名称或超长名称,因此校验规则待确认。
|
||||
|
||||
### 6.3 规则说明备注
|
||||
|
||||
- 未聚焦时以可点击文本 `角色规则说明备注` 展示。
|
||||
- 点击后原位切换成文本输入框。
|
||||
- 输入框失焦后回到静态展示状态。
|
||||
|
||||
## 7. 表格导航与搜索
|
||||
|
||||
角色编辑器左栏是可展开的表格树,每项右侧显示权限摘要。
|
||||
|
||||
### 7.1 普通表格
|
||||
|
||||
- 普通表格直接作为一级节点,例如 `自动推单表`。
|
||||
- 点击节点会选中该表格,并在右侧显示详细权限。
|
||||
- 摘要随权限选择即时更新,例如从 `无权限` 变为 `可编辑`。
|
||||
|
||||
### 7.2 表格分组
|
||||
|
||||
- 分组节点例如 `系统配置`,初始摘要为 `自动隐藏`。
|
||||
- 点击分组只展开或收起子表,不会把分组本身设为权限配置对象。
|
||||
- 子表以二级节点出现,并各自显示 `无权限` 或 `可编辑` 摘要。
|
||||
- 本次观察到的子表包括:RIIN 快递公司确定规则、RIIN 发货工厂确定规则、工厂发货地址、物流渠道、发货仓、判定规则、定时更新订单信息、定时推送订单、定时同步订单。
|
||||
|
||||
### 7.3 搜索表格
|
||||
|
||||
- 输入框占位符为 `搜索表格`。
|
||||
- 输入内容后即时过滤表格树,无需回车。
|
||||
- 搜索会保留匹配子表及其父分组。例如输入 `RIIN` 后,只显示 `系统配置` 及两个包含 RIIN 的子表。
|
||||
- 当前选中项匹配时保持选中。
|
||||
- 清空输入应恢复完整树;本次未发现独立清除按钮。
|
||||
|
||||
## 8. 表格权限
|
||||
|
||||
每张表的顶层权限是单选项:
|
||||
|
||||
- `可编辑`
|
||||
- `无权限`
|
||||
|
||||
### 8.1 无权限
|
||||
|
||||
- 右侧只显示表格权限单选项。
|
||||
- 视图、记录、字段、导入和导出等细粒度设置全部隐藏。
|
||||
- 左侧表格摘要显示 `无权限`。
|
||||
|
||||
### 8.2 可编辑
|
||||
|
||||
- 左侧摘要立即变为 `可编辑`。
|
||||
- 右侧展开全部细粒度设置。
|
||||
- 新切换为可编辑时,默认允许绝大多数操作、所有记录、所有视图、全部字段操作以及导入导出。
|
||||
|
||||
由 UI 表现可见,`可编辑` 是详细权限编辑模式,并不等于所有子权限必须保持开启。
|
||||
|
||||
## 9. 视图权限
|
||||
|
||||
### 9.1 视图操作
|
||||
|
||||
使用独立复选框控制:
|
||||
|
||||
- 创建视图
|
||||
- 删除视图
|
||||
- 更新视图
|
||||
|
||||
复选框可单独开关。
|
||||
|
||||
### 9.2 可查看的视图
|
||||
|
||||
单选模式:
|
||||
|
||||
- `所有视图`
|
||||
- `特定视图`
|
||||
|
||||
选择 `特定视图` 后,原位展开当前表的视图清单,每个视图使用独立复选框。`自动推单表` 当前展示:
|
||||
|
||||
- 明细表
|
||||
- 待推送订单
|
||||
- 已推送订单
|
||||
- 手动推送订单
|
||||
- 表格视图
|
||||
|
||||
切换到 `特定视图` 时,现有视图默认全部选中。本次观察中,切换后 `创建视图` 同时由选中变为未选中,体现“仅允许特定现有视图”与“允许创建新视图”之间的联动约束;建议实现或复刻时将该依赖作为明确规则并补充自动化测试。
|
||||
|
||||
## 10. 记录权限
|
||||
|
||||
### 10.1 记录操作
|
||||
|
||||
使用独立复选框控制:
|
||||
|
||||
- 创建记录
|
||||
- 删除记录
|
||||
- 更新记录
|
||||
- 对记录进行评论
|
||||
- 复制记录
|
||||
|
||||
### 10.2 可查看的记录
|
||||
|
||||
单选模式:
|
||||
|
||||
- `所有记录`
|
||||
- `符合特定条件的记录`
|
||||
|
||||
本次浏览器可访问性操作未能触发第二项,因此没有安全确认条件构建器的具体 UI。该选项在界面上可见且未标记为禁用,预期选择后应展开字段、运算符和值组成的条件编辑器;该部分属于待补充验证项,不应将预期当作已验证行为。
|
||||
|
||||
## 11. 字段权限
|
||||
|
||||
字段权限以矩阵表呈现:
|
||||
|
||||
| 列 | 含义 |
|
||||
| --- | --- |
|
||||
| 字段 | 字段名称及字段类型图标 |
|
||||
| 查看记录 | 是否能在读取记录时看到该字段值 |
|
||||
| 更新记录 | 是否能修改已有记录的该字段值 |
|
||||
| 创建记录 | 新建记录时是否能为该字段赋值 |
|
||||
|
||||
交互规则:
|
||||
|
||||
- 每个权限单元格使用复选框独立控制。
|
||||
- 三个权限列的表头均有 `Select all` 复选框,用于整列全选或全不选。
|
||||
- 默认状态为三列全部选中。
|
||||
- 部分特殊字段存在不可取消的权限。例如当前表的主字段 `订单号` 的“查看记录”复选框为选中且禁用,表示主字段至少必须可见。
|
||||
- 字段数量较多时矩阵纵向滚动,底部的导入/导出权限位于字段矩阵之后。
|
||||
|
||||
字段之间、查看/更新/创建三类权限之间是否存在更多级联约束,本次未逐项修改后保存,因此应通过专门测试补充。
|
||||
|
||||
## 12. 导入与导出权限
|
||||
|
||||
位于每张可编辑表格配置的底部,使用两个独立复选框:
|
||||
|
||||
- `导入数据到表格`
|
||||
- `导出表格数据`
|
||||
|
||||
新切换为可编辑时两项默认选中。
|
||||
|
||||
## 13. 保存与退出
|
||||
|
||||
编辑器底部固定提供:
|
||||
|
||||
- `取消`:放弃本次新建或编辑并返回权限矩阵主页。本次验证中,取消后没有创建角色,临时权限修改也没有保留。
|
||||
- `保存`:预期保存当前配置并停留在编辑器中,便于继续编辑;为避免写入真实业务权限,本次未点击验证。
|
||||
- `保存并返回`:预期保存后返回权限矩阵主页;本次未点击验证。
|
||||
|
||||
本次在有未保存修改时点击 `取消` 没有出现二次确认,直接退出。实现同类交互时应注意误操作丢失配置的风险。
|
||||
|
||||
## 14. 关键状态联动
|
||||
|
||||
| 触发 | UI 结果 |
|
||||
| --- | --- |
|
||||
| 开启权限矩阵 | 立即生效;管理员直接展示;`添加用户` 可用 |
|
||||
| 关闭权限矩阵 | 先弹出风险确认;确认后 `添加用户` 禁用 |
|
||||
| 表格设为无权限 | 隐藏全部细粒度权限 |
|
||||
| 表格设为可编辑 | 展开视图、记录、字段、导入和导出权限 |
|
||||
| 视图设为特定视图 | 展开视图复选清单,并观察到创建视图自动关闭 |
|
||||
| 展开表格分组 | 显示子表,但不改变右侧当前选中表 |
|
||||
| 输入表格搜索词 | 即时过滤表格树并保留匹配项的父分组 |
|
||||
| 点击角色说明占位文本 | 原位切换为文本输入框 |
|
||||
| 取消角色编辑 | 直接放弃未保存修改并返回,无二次确认 |
|
||||
|
||||
## 15. 需要后续补充验证的边界
|
||||
|
||||
以下交互因当前环境没有候选用户、没有现有自定义角色,或会改变真实业务权限而未执行到提交阶段:
|
||||
|
||||
1. 角色名称的必填、重复、长度和特殊字符校验。
|
||||
2. `保存` 与 `保存并返回` 的确切导航、成功提示和失败反馈。
|
||||
3. 已创建角色卡片的编辑、复制、删除、排序和用户分配菜单。
|
||||
4. 默认角色变更的即时保存方式与影响范围。
|
||||
5. 为角色添加或移除用户,以及同一用户的多角色冲突规则。
|
||||
6. `符合特定条件的记录` 的条件构建器和多条件组合逻辑。
|
||||
7. 字段权限之间的级联约束,以及全选在“部分选中”状态下的视觉表现。
|
||||
8. 权限矩阵开启后,对不同角色用户实际访问表格、视图、记录和字段的最终效果。
|
||||
9. 窄屏、触屏、键盘导航及屏幕阅读器体验。
|
||||
|
||||
## 16. 实现或复刻时的验收重点
|
||||
|
||||
- 权限矩阵关闭必须明确提示“所有协作者可查看全部数据”的风险。
|
||||
- 管理员必须始终豁免角色限制,且界面需清楚说明。
|
||||
- 表格权限必须是细粒度权限的总门控:无权限时不应保留可操作的隐藏子权限。
|
||||
- 表格树的分组展开、表格选择和搜索过滤必须是彼此独立的状态。
|
||||
- 视图、记录和字段权限都应支持局部选择,并正确保存半选状态。
|
||||
- 主字段等系统强制权限应呈现禁用态,避免用户误以为可以取消。
|
||||
- 取消未保存修改目前没有确认;若配置成本较高,建议补充脏状态提示。
|
||||
- 所有保存类操作都应具备加载态、防重复提交、成功反馈和失败回滚。
|
||||
# Teable 权限矩阵 UI 交互说明
|
||||
|
||||
> 页面:`https://teable.inkreach.cc/base/bseVtXyL4aojvUDwFhn/authority-matrix`
|
||||
> 梳理日期:2026-07-29
|
||||
> 梳理方式:使用管理员账号在真实页面进行黑盒交互验证。未创建或保存测试角色,页面最终恢复为初始的“权限矩阵关闭”状态。
|
||||
|
||||
## 1. 页面定位与整体结构
|
||||
|
||||
权限矩阵用于为当前数据库配置基于角色的访问控制。页面主内容从上到下分为:
|
||||
|
||||
1. 标题区:`权限矩阵`、总开关、顶部 `添加角色`。
|
||||
2. 管理员区:管理员规则说明、`查看`、`添加用户`、管理员用户列表。
|
||||
3. 自定义角色区:说明文案、`默认角色` 下拉框、角色列表或空状态。
|
||||
4. 角色编辑器:添加或编辑角色时替换权限矩阵主内容区,不以模态框呈现。
|
||||
|
||||
页面仍保留数据库左侧导航和右侧 AI 助手,因此权限矩阵主内容区不是全屏页面。
|
||||
|
||||
### 1.1 页面外壳布局
|
||||
|
||||
本次桌面端实测视口为 `929 × 869 px`,页面采用横向三段式应用外壳:
|
||||
|
||||
```text
|
||||
┌──────────────────────┬──────────────────────────────────────────────┬────┐
|
||||
│ 数据库左侧导航 │ 权限矩阵主工作区 │ AI │
|
||||
│ 约 288 px │ 自适应填充剩余宽度 │栏 │
|
||||
│ │ │约 │
|
||||
│ 空间名称、搜索 │ 权限矩阵主页 / 角色编辑器 │40px│
|
||||
│ 权限矩阵、邀请、更多 │ │ │
|
||||
│ 创建、表格树 │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ 当前用户、通知 │ │ │
|
||||
└──────────────────────┴──────────────────────────────────────────────┴────┘
|
||||
```
|
||||
|
||||
- 左侧数据库导航为固定宽度栏,与权限矩阵业务内容之间使用竖向分隔线。
|
||||
- 主工作区占据中间剩余空间,是权限矩阵主页和角色编辑器共用的内容容器。
|
||||
- 右侧 AI 助手在当前宽度下折叠为窄图标栏;此前更宽状态下可展开成完整对话面板。
|
||||
- 页面外壳占满视口高度。左侧用户信息和通知入口固定在导航底部,不随中间内容滚动。
|
||||
- 权限矩阵的浮层、下拉框和禁用确认框覆盖在主工作区上方,不改变三段式外壳宽度。
|
||||
|
||||
### 1.2 权限矩阵主页布局
|
||||
|
||||
主页采用单列内容流,主内容在工作区内保留约 `32 px` 左右内边距:
|
||||
|
||||
```text
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ 权限矩阵 [总开关] [+ 添加角色] │
|
||||
│ │
|
||||
│ 管理员 │
|
||||
│ 说明文字 查看 │
|
||||
│ [添加用户] [管理员标签……] │
|
||||
│ │
|
||||
│ 自定义角色 默认角色 [无权限 ▾] │
|
||||
│ 说明文字 │
|
||||
│ │
|
||||
│ 空状态插图 │
|
||||
│ 尚未设置角色 │
|
||||
│ 辅助说明 │
|
||||
│ [+ 添加角色] │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- 顶部标题和总开关位于同一行左侧,主操作 `添加角色` 右对齐。
|
||||
- `管理员` 和 `自定义角色` 是纵向排列的两个业务区块,不使用独立卡片包裹,主要依靠标题、留白和对齐形成层级。
|
||||
- 管理员说明、`查看`、添加用户按钮和用户标签组成紧凑信息组。
|
||||
- 自定义角色区标题与说明靠左,`默认角色` 标签和下拉框在同一行靠右,形成左右对照布局。
|
||||
- 无角色时,空状态在剩余内容区域水平居中;插图、标题、说明和按钮纵向居中排列。
|
||||
- 空状态会占用大量垂直留白,使页面重点落在“创建第一个角色”操作上。
|
||||
|
||||
### 1.3 角色编辑器整体布局
|
||||
|
||||
点击 `添加角色` 后,左侧数据库导航和右侧 AI 栏保持不变,中间主页替换为角色编辑器:
|
||||
|
||||
```text
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ [返回] [角色名称输入框] │
|
||||
│ 角色规则说明备注 │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ 左侧表格导航 │ 右侧权限详情 │ │
|
||||
│ │ 搜索表格 │ 表格权限 │ │
|
||||
│ │ │ 视图权限 │ │
|
||||
│ │ 表格 / 分组树 │ 记录权限 │ │
|
||||
│ │ 权限摘要 │ 字段权限矩阵 │ │
|
||||
│ │ │ 导入 / 导出权限 │ │
|
||||
│ ├──────────────────────────┴───────────────────────────────┤ │
|
||||
│ │ [取消] [保存] [保存并返回] │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- 编辑器顶部是角色元信息区:返回图标和角色名称输入框横向排列,规则说明位于下一行。
|
||||
- 权限编辑主体使用带边框和圆角的双栏容器。
|
||||
- 在本次视口下,双栏容器宽约 `536 px`:左栏约 `228 px`,右栏约 `308 px`。尺寸为截图估算值,不代表实现中的固定像素约束。
|
||||
- 左栏为表格导航,顶部固定搜索框,下方是可展开的表格树和当前权限摘要。
|
||||
- 右栏为所选表格的权限详情。选择 `无权限` 时内容较短;选择 `可编辑` 后纵向展开多个权限分区。
|
||||
- 左右栏之间使用竖向分隔线;各权限分区之间使用横向分隔线,形成清晰的二维结构。
|
||||
- 底部操作栏横跨双栏,按钮整体右对齐,`保存并返回` 使用深色主按钮,`取消` 和 `保存` 使用次级按钮。
|
||||
|
||||
### 1.4 编辑器滚动与固定区域
|
||||
|
||||
- 编辑器外层高度受当前视口约束,不会随着字段数量无限向下撑开页面。
|
||||
- 左侧表格树与右侧权限详情位于操作栏上方的可用高度内。
|
||||
- 右侧权限详情为主要滚动区域。字段权限矩阵和导入/导出权限位于下方,需要在详情区内纵向滚动查看。
|
||||
- 底部 `取消 / 保存 / 保存并返回` 操作栏保持可见,不随右侧权限内容滚走。
|
||||
- 左侧表格导航在滚动右侧详情时保持位置,便于随时切换表格。
|
||||
- 字段权限本身是四列矩阵。在较窄工作区中,字段名列与三个操作列必须保持清晰对应;若字段名过长,需要截断或提示策略,当前数据未触发明显横向溢出。
|
||||
|
||||
### 1.5 视觉层级与密度
|
||||
|
||||
- 页面以白色为主背景,左侧当前导航项、选中表格行使用浅灰底色强调。
|
||||
- 深色填充只用于高优先级动作,例如顶部 `添加角色`、空状态 `添加角色` 和 `保存并返回`。
|
||||
- 标题层级依次为页面标题、区块标题、权限分区标题和辅助标签;字号与字重的差异比颜色差异更明显。
|
||||
- 主页密度较低,使用大面积留白突出空状态;角色编辑器密度明显提高,以分栏、分隔线、复选框和矩阵承载配置任务。
|
||||
- 权限详情中的相关控件紧密分组,不同权限域之间通过更大的上下间距和分隔线区分。
|
||||
|
||||
### 1.6 状态变化对布局的影响
|
||||
|
||||
| 状态变化 | 布局变化 |
|
||||
| --- | --- |
|
||||
| 权限矩阵关闭 → 开启 | 管理员标签直接进入管理员区,`添加用户` 启用;页面整体结构不变 |
|
||||
| 无角色 → 有角色 | 预计空状态区域替换为角色列表;本次未创建角色,因此列表布局待验证 |
|
||||
| 主页 → 添加角色 | 中间工作区整体切换为角色编辑器,左右应用外壳不变 |
|
||||
| 表格无权限 → 可编辑 | 右栏从单个权限区扩展为长内容区,并启用内部滚动 |
|
||||
| 所有视图 → 特定视图 | 在当前视图单选项下方原位插入视图复选列表,后续区块整体下移 |
|
||||
| 分组收起 → 展开 | 左栏在分组节点下原位插入二级子表,右栏当前选择不变 |
|
||||
| 搜索表格 | 左栏树被过滤,右栏继续显示当前所选表格的权限 |
|
||||
| 禁用权限矩阵 | 中央确认框覆盖主工作区,背景内容留在原位 |
|
||||
|
||||
### 1.7 响应式范围说明
|
||||
|
||||
- 以上布局基于 `929 × 869 px` 桌面视口实测。
|
||||
- 当前宽度下右侧 AI 助手已经折叠,但左侧数据库导航仍完整保留,说明系统优先压缩辅助面板。
|
||||
- 本次未切换到平板或手机宽度,因此尚不能确认左侧导航是否抽屉化、角色编辑双栏是否改为上下堆叠、字段矩阵是否启用横向滚动。
|
||||
- 若复刻该页面,建议至少验证 `1440 px`、`1024 px`、`768 px` 和 `375 px` 四档宽度,重点检查双栏编辑器和字段权限矩阵。
|
||||
|
||||
## 2. 权限矩阵总开关
|
||||
|
||||
### 2.1 关闭状态
|
||||
|
||||
- 开关为关闭状态。
|
||||
- `添加角色` 仍可点击,允许提前定义角色规则。
|
||||
- `添加用户` 为禁用状态。
|
||||
- 管理员列表不在主区域直接展示;`查看` 仍可展开管理员信息。
|
||||
- 默认角色控件和已有角色配置仍可见。
|
||||
- 当前无自定义角色时展示空状态:
|
||||
- 插图替代文本:`No roles available`。
|
||||
- 标题:`尚未设置角色`。
|
||||
- 说明:`创建你的第一个角色,开启灵活高效的权限管理`。
|
||||
- 空状态内提供第二个 `添加角色` 入口。
|
||||
|
||||
关闭权限矩阵的产品语义由确认弹窗明确说明:协作者将能够查看所有数据。
|
||||
|
||||
### 2.2 开启
|
||||
|
||||
- 点击关闭状态的开关后立即开启,没有二次确认。
|
||||
- 开关开启后,管理员用户直接显示在管理员区。
|
||||
- `添加用户` 从禁用变为可用。
|
||||
- 其余角色配置继续保留。
|
||||
|
||||
### 2.3 关闭
|
||||
|
||||
点击开启状态的开关不会立即关闭,而是显示确认弹窗:
|
||||
|
||||
- 标题:`禁用权限矩阵`。
|
||||
- 风险提示:`协作者将能够查看所有数据,是否继续?`
|
||||
- 操作:`取消`、`确认`、右上角 `Close`。
|
||||
- `取消`、`Close` 应关闭弹窗并维持开启状态。
|
||||
- `确认` 关闭权限矩阵,随后 `添加用户` 重新变为禁用。
|
||||
|
||||
因此,总开关存在有意的不对称交互:开启即时生效,关闭需要风险确认。
|
||||
|
||||
## 3. 管理员区
|
||||
|
||||
说明文案为:`空间可管理角色的用户可以管理权限矩阵,且不受其限制。`
|
||||
|
||||
这表示管理员是权限矩阵的豁免主体;角色规则不限制拥有空间角色管理能力的用户。
|
||||
|
||||
### 3.1 查看管理员
|
||||
|
||||
- `查看` 是可展开按钮。
|
||||
- 点击后在浮层中展示管理员头像和名称。
|
||||
- 再次点击按钮或点击浮层外区域可收起。
|
||||
- 本次环境只显示用户 `2197651308`。
|
||||
|
||||
### 3.2 添加用户
|
||||
|
||||
- 权限矩阵关闭时按钮禁用。
|
||||
- 权限矩阵开启后按钮可用。
|
||||
- 点击后打开用户选择浮层,并自动展开组合框。
|
||||
- 选择源只包含可被加入管理员集合的现有用户。
|
||||
- 当前没有候选用户时显示:`没有用户可被选择`。
|
||||
- `Esc` 可关闭浮层。
|
||||
|
||||
当前环境没有候选用户,因此未验证选择用户后的确认、去重和移除流程。
|
||||
|
||||
## 4. 默认角色
|
||||
|
||||
- `默认角色` 使用单选下拉框。
|
||||
- 当前没有自定义角色时,下拉框仅提供并选中 `无权限`。
|
||||
- 合理推断:创建角色后,角色会成为默认角色的可选项;新加入且未被显式分配角色的协作者应使用该默认角色。此推断需要结合产品规则或创建角色后的实际行为再次确认。
|
||||
|
||||
## 5. 添加角色入口
|
||||
|
||||
存在两个等价入口:
|
||||
|
||||
- 页面标题右侧 `添加角色`。
|
||||
- 无角色空状态中的 `添加角色`。
|
||||
|
||||
点击后,权限矩阵主页被角色编辑器替换。编辑器初始值为:
|
||||
|
||||
- 角色名:`自定义角色`。
|
||||
- 角色说明:未填写时显示 `角色规则说明备注`。
|
||||
- 所有普通表格:`无权限`。
|
||||
- 表格分组:显示 `自动隐藏`,其子表分别配置权限。
|
||||
|
||||
## 6. 角色基本信息
|
||||
|
||||
### 6.1 返回按钮
|
||||
|
||||
编辑器左上角为仅图标返回按钮。未单独验证它与底部 `取消` 在存在未保存改动时是否完全一致。
|
||||
|
||||
### 6.2 角色名称
|
||||
|
||||
- 角色名称为单行文本框。
|
||||
- 新建角色默认填入 `自定义角色`,可直接编辑。
|
||||
- 本次未提交空名称、重复名称或超长名称,因此校验规则待确认。
|
||||
|
||||
### 6.3 规则说明备注
|
||||
|
||||
- 未聚焦时以可点击文本 `角色规则说明备注` 展示。
|
||||
- 点击后原位切换成文本输入框。
|
||||
- 输入框失焦后回到静态展示状态。
|
||||
|
||||
## 7. 表格导航与搜索
|
||||
|
||||
角色编辑器左栏是可展开的表格树,每项右侧显示权限摘要。
|
||||
|
||||
### 7.1 普通表格
|
||||
|
||||
- 普通表格直接作为一级节点,例如 `自动推单表`。
|
||||
- 点击节点会选中该表格,并在右侧显示详细权限。
|
||||
- 摘要随权限选择即时更新,例如从 `无权限` 变为 `可编辑`。
|
||||
|
||||
### 7.2 表格分组
|
||||
|
||||
- 分组节点例如 `系统配置`,初始摘要为 `自动隐藏`。
|
||||
- 点击分组只展开或收起子表,不会把分组本身设为权限配置对象。
|
||||
- 子表以二级节点出现,并各自显示 `无权限` 或 `可编辑` 摘要。
|
||||
- 本次观察到的子表包括:RIIN 快递公司确定规则、RIIN 发货工厂确定规则、工厂发货地址、物流渠道、发货仓、判定规则、定时更新订单信息、定时推送订单、定时同步订单。
|
||||
|
||||
### 7.3 搜索表格
|
||||
|
||||
- 输入框占位符为 `搜索表格`。
|
||||
- 输入内容后即时过滤表格树,无需回车。
|
||||
- 搜索会保留匹配子表及其父分组。例如输入 `RIIN` 后,只显示 `系统配置` 及两个包含 RIIN 的子表。
|
||||
- 当前选中项匹配时保持选中。
|
||||
- 清空输入应恢复完整树;本次未发现独立清除按钮。
|
||||
|
||||
## 8. 表格权限
|
||||
|
||||
每张表的顶层权限是单选项:
|
||||
|
||||
- `可编辑`
|
||||
- `无权限`
|
||||
|
||||
### 8.1 无权限
|
||||
|
||||
- 右侧只显示表格权限单选项。
|
||||
- 视图、记录、字段、导入和导出等细粒度设置全部隐藏。
|
||||
- 左侧表格摘要显示 `无权限`。
|
||||
|
||||
### 8.2 可编辑
|
||||
|
||||
- 左侧摘要立即变为 `可编辑`。
|
||||
- 右侧展开全部细粒度设置。
|
||||
- 新切换为可编辑时,默认允许绝大多数操作、所有记录、所有视图、全部字段操作以及导入导出。
|
||||
|
||||
由 UI 表现可见,`可编辑` 是详细权限编辑模式,并不等于所有子权限必须保持开启。
|
||||
|
||||
## 9. 视图权限
|
||||
|
||||
### 9.1 视图操作
|
||||
|
||||
使用独立复选框控制:
|
||||
|
||||
- 创建视图
|
||||
- 删除视图
|
||||
- 更新视图
|
||||
|
||||
复选框可单独开关。
|
||||
|
||||
### 9.2 可查看的视图
|
||||
|
||||
单选模式:
|
||||
|
||||
- `所有视图`
|
||||
- `特定视图`
|
||||
|
||||
选择 `特定视图` 后,原位展开当前表的视图清单,每个视图使用独立复选框。`自动推单表` 当前展示:
|
||||
|
||||
- 明细表
|
||||
- 待推送订单
|
||||
- 已推送订单
|
||||
- 手动推送订单
|
||||
- 表格视图
|
||||
|
||||
切换到 `特定视图` 时,现有视图默认全部选中。本次观察中,切换后 `创建视图` 同时由选中变为未选中,体现“仅允许特定现有视图”与“允许创建新视图”之间的联动约束;建议实现或复刻时将该依赖作为明确规则并补充自动化测试。
|
||||
|
||||
## 10. 记录权限
|
||||
|
||||
### 10.1 记录操作
|
||||
|
||||
使用独立复选框控制:
|
||||
|
||||
- 创建记录
|
||||
- 删除记录
|
||||
- 更新记录
|
||||
- 对记录进行评论
|
||||
- 复制记录
|
||||
|
||||
### 10.2 可查看的记录
|
||||
|
||||
单选模式:
|
||||
|
||||
- `所有记录`
|
||||
- `符合特定条件的记录`
|
||||
|
||||
本次浏览器可访问性操作未能触发第二项,因此没有安全确认条件构建器的具体 UI。该选项在界面上可见且未标记为禁用,预期选择后应展开字段、运算符和值组成的条件编辑器;该部分属于待补充验证项,不应将预期当作已验证行为。
|
||||
|
||||
## 11. 字段权限
|
||||
|
||||
字段权限以矩阵表呈现:
|
||||
|
||||
| 列 | 含义 |
|
||||
| --- | --- |
|
||||
| 字段 | 字段名称及字段类型图标 |
|
||||
| 查看记录 | 是否能在读取记录时看到该字段值 |
|
||||
| 更新记录 | 是否能修改已有记录的该字段值 |
|
||||
| 创建记录 | 新建记录时是否能为该字段赋值 |
|
||||
|
||||
交互规则:
|
||||
|
||||
- 每个权限单元格使用复选框独立控制。
|
||||
- 三个权限列的表头均有 `Select all` 复选框,用于整列全选或全不选。
|
||||
- 默认状态为三列全部选中。
|
||||
- 部分特殊字段存在不可取消的权限。例如当前表的主字段 `订单号` 的“查看记录”复选框为选中且禁用,表示主字段至少必须可见。
|
||||
- 字段数量较多时矩阵纵向滚动,底部的导入/导出权限位于字段矩阵之后。
|
||||
|
||||
字段之间、查看/更新/创建三类权限之间是否存在更多级联约束,本次未逐项修改后保存,因此应通过专门测试补充。
|
||||
|
||||
## 12. 导入与导出权限
|
||||
|
||||
位于每张可编辑表格配置的底部,使用两个独立复选框:
|
||||
|
||||
- `导入数据到表格`
|
||||
- `导出表格数据`
|
||||
|
||||
新切换为可编辑时两项默认选中。
|
||||
|
||||
## 13. 保存与退出
|
||||
|
||||
编辑器底部固定提供:
|
||||
|
||||
- `取消`:放弃本次新建或编辑并返回权限矩阵主页。本次验证中,取消后没有创建角色,临时权限修改也没有保留。
|
||||
- `保存`:预期保存当前配置并停留在编辑器中,便于继续编辑;为避免写入真实业务权限,本次未点击验证。
|
||||
- `保存并返回`:预期保存后返回权限矩阵主页;本次未点击验证。
|
||||
|
||||
本次在有未保存修改时点击 `取消` 没有出现二次确认,直接退出。实现同类交互时应注意误操作丢失配置的风险。
|
||||
|
||||
## 14. 关键状态联动
|
||||
|
||||
| 触发 | UI 结果 |
|
||||
| --- | --- |
|
||||
| 开启权限矩阵 | 立即生效;管理员直接展示;`添加用户` 可用 |
|
||||
| 关闭权限矩阵 | 先弹出风险确认;确认后 `添加用户` 禁用 |
|
||||
| 表格设为无权限 | 隐藏全部细粒度权限 |
|
||||
| 表格设为可编辑 | 展开视图、记录、字段、导入和导出权限 |
|
||||
| 视图设为特定视图 | 展开视图复选清单,并观察到创建视图自动关闭 |
|
||||
| 展开表格分组 | 显示子表,但不改变右侧当前选中表 |
|
||||
| 输入表格搜索词 | 即时过滤表格树并保留匹配项的父分组 |
|
||||
| 点击角色说明占位文本 | 原位切换为文本输入框 |
|
||||
| 取消角色编辑 | 直接放弃未保存修改并返回,无二次确认 |
|
||||
|
||||
## 15. 需要后续补充验证的边界
|
||||
|
||||
以下交互因当前环境没有候选用户、没有现有自定义角色,或会改变真实业务权限而未执行到提交阶段:
|
||||
|
||||
1. 角色名称的必填、重复、长度和特殊字符校验。
|
||||
2. `保存` 与 `保存并返回` 的确切导航、成功提示和失败反馈。
|
||||
3. 已创建角色卡片的编辑、复制、删除、排序和用户分配菜单。
|
||||
4. 默认角色变更的即时保存方式与影响范围。
|
||||
5. 为角色添加或移除用户,以及同一用户的多角色冲突规则。
|
||||
6. `符合特定条件的记录` 的条件构建器和多条件组合逻辑。
|
||||
7. 字段权限之间的级联约束,以及全选在“部分选中”状态下的视觉表现。
|
||||
8. 权限矩阵开启后,对不同角色用户实际访问表格、视图、记录和字段的最终效果。
|
||||
9. 窄屏、触屏、键盘导航及屏幕阅读器体验。
|
||||
|
||||
## 16. 实现或复刻时的验收重点
|
||||
|
||||
- 权限矩阵关闭必须明确提示“所有协作者可查看全部数据”的风险。
|
||||
- 管理员必须始终豁免角色限制,且界面需清楚说明。
|
||||
- 表格权限必须是细粒度权限的总门控:无权限时不应保留可操作的隐藏子权限。
|
||||
- 表格树的分组展开、表格选择和搜索过滤必须是彼此独立的状态。
|
||||
- 视图、记录和字段权限都应支持局部选择,并正确保存半选状态。
|
||||
- 主字段等系统强制权限应呈现禁用态,避免用户误以为可以取消。
|
||||
- 取消未保存修改目前没有确认;若配置成本较高,建议补充脏状态提示。
|
||||
- 所有保存类操作都应具备加载态、防重复提交、成功反馈和失败回滚。
|
||||
|
||||
+567
-567
File diff suppressed because it is too large
Load Diff
+43
-43
@@ -1,43 +1,43 @@
|
||||
# 官网首页
|
||||
|
||||
## 关键链接与国家货盘
|
||||
|
||||
- 首页“登录”“立即开始”“免费试用”“立即免费使用”等 CTA 统一通过 `window.location.href` 在当前页面跳转到 `https://inkpod.vip/user/login`。
|
||||
- “全球POD货盘”国家列表读取与产品中心相同的后台国家接口,完整展示所有已配置且有关联商品的国家及图标。
|
||||
- 桌面端国家列表按 6 列 × 2 行均匀排列;点击国家只切换下方对应货盘,不发生页面跳转。
|
||||
- 国家下方的品类卡链接到 `/product-center?countryId={后台国家ID}&categoryId={后台品类ID}`,产品中心打开后自动应用国家和品类筛选。
|
||||
|
||||
官网首页位于 `apps/website/app/pages/index.vue`,访问路径为 `/`。页面基于 Figma 节点 `897:2047` 还原,桌面设计基准为 `1440 x 6933px`。
|
||||
|
||||
## 页面结构
|
||||
|
||||
首页依次由导航、首屏、平台数据、四步流程、全球 POD 货盘、渠道能力、平台优势、公司简介、用户案例、行动区和页脚组成。各区块保持独立 Vue 组件,首页仅负责组合。
|
||||
|
||||
桌面端主要区块边界为:`80 / 980 / 1580 / 2515 / 3522 / 4276 / 4706 / 5306 / 6234 / 6572px`。流程区默认展示“选择产品”,渠道能力默认展示“店铺集成”;用户主动操作后仍可切换。
|
||||
|
||||
## 素材
|
||||
|
||||
首页图片统一存放于 `apps/website/public/`。Figma 用户案例图使用 `case-*.png` 命名,不依赖临时远程链接或随机占位图。
|
||||
|
||||
`/主视觉.png` 保持为宽高 `800 × 400` 的普通 `<img>`,渲染宽度不超过原图 800px,不作为背景图,也不会随超宽屏幕继续放大。
|
||||
|
||||
## 自动轮播
|
||||
|
||||
- 四步流程和“用强大功能,开启盈利快车道”功能区均每 2 秒自动切换,用户仍可点击或悬停立即切换。四步流程当前项的 2px 黄线会在 2 秒内从 0% 填满至 100%,填满后切换下一张;步骤标题与功能轮播项保持一致,桌面为 36px 常规字重。
|
||||
- “用强大功能,开启盈利快车道”左侧标题桌面端为 30px,四个功能项在 550px 高度内等分排列,与右侧轮播图上下对齐。
|
||||
- 用户案例在桌面和移动端均无缝横向滚动,完整循环放慢至 30 秒;悬停或聚焦时暂停。
|
||||
- `prefers-reduced-motion: reduce` 环境下关闭持续滚动动画。
|
||||
- 导航中的“选品推荐”和“解决方案”入口暂时隐藏,相关实现代码保留以便后续恢复。
|
||||
- 桌面导航使用 18px 字号,移动导航保持原有触控尺寸。
|
||||
- “全球 POD 货盘”使用固定旗帜列对齐国家名称,“更多产品”操作固定在货盘区底部。
|
||||
- 首屏“链通全球”使用主题橙色渐变,0 库存、0 关税、1 件起订以竖线分隔;平台数据卡的统计数值使用主题橙色。“全球 POD 货盘”的“更多产品”为无按钮容器的主题色文本链接。
|
||||
- 官网全局使用随项目提供的 PingFang SC Thin、Regular、Medium、Semibold 字体,首屏副标题使用 Thin 字重。首屏渐变从“通”字开始,“通”字仅保留少量渐变过渡;主标题与副标题共用左对齐容器。平台数据卡会抵消插图上移造成的底部占位,减少卡片留白。
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
pnpm --filter @inkreach/website test --run
|
||||
pnpm --filter @inkreach/website build
|
||||
```
|
||||
|
||||
浏览器以 `1440 x 1024` 视口检查首页总高、区块边界、图片加载和横向溢出。
|
||||
# 官网首页
|
||||
|
||||
## 关键链接与国家货盘
|
||||
|
||||
- 首页“登录”“立即开始”“免费试用”“立即免费使用”等 CTA 统一通过 `window.location.href` 在当前页面跳转到 `https://inkpod.vip/user/login`。
|
||||
- “全球POD货盘”国家列表读取与产品中心相同的后台国家接口,完整展示所有已配置且有关联商品的国家及图标。
|
||||
- 桌面端国家列表按 6 列 × 2 行均匀排列;点击国家只切换下方对应货盘,不发生页面跳转。
|
||||
- 国家下方的品类卡链接到 `/product-center?countryId={后台国家ID}&categoryId={后台品类ID}`,产品中心打开后自动应用国家和品类筛选。
|
||||
|
||||
官网首页位于 `apps/website/app/pages/index.vue`,访问路径为 `/`。页面基于 Figma 节点 `897:2047` 还原,桌面设计基准为 `1440 x 6933px`。
|
||||
|
||||
## 页面结构
|
||||
|
||||
首页依次由导航、首屏、平台数据、四步流程、全球 POD 货盘、渠道能力、平台优势、公司简介、用户案例、行动区和页脚组成。各区块保持独立 Vue 组件,首页仅负责组合。
|
||||
|
||||
桌面端主要区块边界为:`80 / 980 / 1580 / 2515 / 3522 / 4276 / 4706 / 5306 / 6234 / 6572px`。流程区默认展示“选择产品”,渠道能力默认展示“店铺集成”;用户主动操作后仍可切换。
|
||||
|
||||
## 素材
|
||||
|
||||
首页图片统一存放于 `apps/website/public/`。Figma 用户案例图使用 `case-*.png` 命名,不依赖临时远程链接或随机占位图。
|
||||
|
||||
`/主视觉.png` 保持为宽高 `800 × 400` 的普通 `<img>`,渲染宽度不超过原图 800px,不作为背景图,也不会随超宽屏幕继续放大。
|
||||
|
||||
## 自动轮播
|
||||
|
||||
- 四步流程和“用强大功能,开启盈利快车道”功能区均每 2 秒自动切换,用户仍可点击或悬停立即切换。四步流程当前项的 2px 黄线会在 2 秒内从 0% 填满至 100%,填满后切换下一张;步骤标题与功能轮播项保持一致,桌面为 36px 常规字重。
|
||||
- “用强大功能,开启盈利快车道”左侧标题桌面端为 30px,四个功能项在 550px 高度内等分排列,与右侧轮播图上下对齐。
|
||||
- 用户案例在桌面和移动端均无缝横向滚动,完整循环放慢至 30 秒;悬停或聚焦时暂停。
|
||||
- `prefers-reduced-motion: reduce` 环境下关闭持续滚动动画。
|
||||
- 导航中的“选品推荐”和“解决方案”入口暂时隐藏,相关实现代码保留以便后续恢复。
|
||||
- 桌面导航使用 18px 字号,移动导航保持原有触控尺寸。
|
||||
- “全球 POD 货盘”使用固定旗帜列对齐国家名称,“更多产品”操作固定在货盘区底部。
|
||||
- 首屏“链通全球”使用主题橙色渐变,0 库存、0 关税、1 件起订以竖线分隔;平台数据卡的统计数值使用主题橙色。“全球 POD 货盘”的“更多产品”为无按钮容器的主题色文本链接。
|
||||
- 官网全局使用随项目提供的 PingFang SC Thin、Regular、Medium、Semibold 字体,首屏副标题使用 Thin 字重。首屏渐变从“通”字开始,“通”字仅保留少量渐变过渡;主标题与副标题共用左对齐容器。平台数据卡会抵消插图上移造成的底部占位,减少卡片留白。
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
pnpm --filter @inkreach/website test --run
|
||||
pnpm --filter @inkreach/website build
|
||||
```
|
||||
|
||||
浏览器以 `1440 x 1024` 视口检查首页总高、区块边界、图片加载和横向溢出。
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
# 产品中心
|
||||
|
||||
官网产品中心位于 `apps/website/app/pages/product-center.vue`,访问路径为 `/product-center`。
|
||||
|
||||
## 默认展示
|
||||
|
||||
页面首次加载时展示全部商品和全部国家,不默认选择分类、物流或工艺标签。每页默认请求 20 条商品,用户操作筛选项后再按真实业务 ID 查询。
|
||||
|
||||
## 筛选与分页
|
||||
|
||||
- 左侧分类树:选择父分类或子分类。
|
||||
- 国家:点击国家胶囊筛选,点击“全部”取消国家筛选。
|
||||
- 物流与工艺:按标签组展示下拉选项,每个分组内暂为单选,不同分组可各选择一项。
|
||||
- 筛选菜单在选择选项或点击组件外部后自动关闭;已选 Tag 使用独立标签行。仅选择工艺时标签从最左侧开始;物流与工艺同时选择时按物流、工艺顺序与上方筛选项对应排列。
|
||||
- 搜索:输入关键词后按回车或点击“搜索”。
|
||||
- 商品卡:整卡链接到 `https://inkpod.vip/portal/detail/{商品ID}`,在新窗口打开。
|
||||
- 公共商品接口的 `id` 来自 `originGood.sdsGoodId`,即 InkPOD 商品 ID;`goods.good_id` 仅作为本站数据库内部主键使用。
|
||||
- URL 国家筛选:支持 `/product-center?countryId={后台国家ID}`,仅接受后台已配置的国家 ID。
|
||||
- 无结果:展示设计稿空状态,可通过“查看全部商品”或“清空筛选”恢复完整列表。
|
||||
- 加载中:展示两行共八个商品骨架卡,筛选栏保持可操作。
|
||||
- 分页:支持页码、前后翻页、每页数量和页码跳转。
|
||||
|
||||
所有查询通过 `useProductCenter.ts` 调用 `/api/backend/*` 代理路由,不在页面中硬编码业务 ID。
|
||||
|
||||
## 后台配置联动
|
||||
|
||||
- 品类树、国家、Tag 与 Tag Group 均来自 NestJS `/public/*` 接口。
|
||||
- 国家旗帜读取 `countryIcon`,品类图标读取 `categoryIcon`;前端不提供外部旗帜 CDN 或固定名称兜底。
|
||||
- `/uploads/*` 与 `/assets/*` 等后台相对资源地址由 `useProductCenter.ts` 根据 `runtimeConfig.public.backendUrl` 转为完整 URL。
|
||||
- 物流和工艺筛选分别由后台名称包含“物流”和“工艺”的 Tag Group 生成;“印刷位置”等其他分组仍可用于商品卡片标签,但不会自动成为顶部筛选项。
|
||||
- Figma 基准图标位于 `apps/api/public/product-center/`,通过后端 `/assets/product-center/*` 提供。
|
||||
- 更新现有数据库图标配置:`pnpm --filter @inkreach/api configure:product-center-icons`。脚本按中文名称幂等更新,不创建缺失的国家或品类。
|
||||
|
||||
## 响应式
|
||||
|
||||
桌面采用 240px 分类栏,页面左右保留 `24–56px` 自适应留白,分类栏可滚动但隐藏可见 scrollbar。商品网格以 240px 为最小卡片宽度自动增加或减少列数:1440px 基准宽度保持四列,1920px 可显示六列。国家筛选区按内容自然增高,不保留固定空白。
|
||||
|
||||
左侧品类栏不显示最左侧边线,一级品类之间保留 10px 间距;国家筛选区及物流、工艺、搜索工具区的底部分隔线从内容区向右延伸至页面边缘。
|
||||
|
||||
宽度低于 1000px 后分类栏变为抽屉;移动端商品网格降为两列或单列,国家筛选仅在自身区域横向滚动,不会撑宽页面。
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
pnpm --filter @inkreach/website test --run
|
||||
pnpm --filter @inkreach/website build
|
||||
pnpm --filter @inkreach/website dev
|
||||
pnpm --filter @inkreach/api test -- --runInBand
|
||||
pnpm --filter @inkreach/api build
|
||||
```
|
||||
# 产品中心
|
||||
|
||||
官网产品中心位于 `apps/website/app/pages/product-center.vue`,访问路径为 `/product-center`。
|
||||
|
||||
## 默认展示
|
||||
|
||||
页面首次加载时展示全部商品和全部国家,不默认选择分类、物流或工艺标签。每页默认请求 20 条商品,用户操作筛选项后再按真实业务 ID 查询。
|
||||
|
||||
## 筛选与分页
|
||||
|
||||
- 左侧分类树:选择父分类或子分类。
|
||||
- 国家:点击国家胶囊筛选,点击“全部”取消国家筛选。
|
||||
- 物流与工艺:按标签组展示下拉选项,每个分组内暂为单选,不同分组可各选择一项。
|
||||
- 筛选菜单在选择选项或点击组件外部后自动关闭;已选 Tag 使用独立标签行。仅选择工艺时标签从最左侧开始;物流与工艺同时选择时按物流、工艺顺序与上方筛选项对应排列。
|
||||
- 搜索:输入关键词后按回车或点击“搜索”。
|
||||
- 商品卡:整卡链接到 `https://inkpod.vip/portal/detail/{商品ID}`,在新窗口打开。
|
||||
- 公共商品接口的 `id` 来自 `originGood.sdsGoodId`,即 InkPOD 商品 ID;`goods.good_id` 仅作为本站数据库内部主键使用。
|
||||
- URL 国家筛选:支持 `/product-center?countryId={后台国家ID}`,仅接受后台已配置的国家 ID。
|
||||
- 无结果:展示设计稿空状态,可通过“查看全部商品”或“清空筛选”恢复完整列表。
|
||||
- 加载中:展示两行共八个商品骨架卡,筛选栏保持可操作。
|
||||
- 分页:支持页码、前后翻页、每页数量和页码跳转。
|
||||
|
||||
所有查询通过 `useProductCenter.ts` 调用 `/api/backend/*` 代理路由,不在页面中硬编码业务 ID。
|
||||
|
||||
## 后台配置联动
|
||||
|
||||
- 品类树、国家、Tag 与 Tag Group 均来自 NestJS `/public/*` 接口。
|
||||
- 国家旗帜读取 `countryIcon`,品类图标读取 `categoryIcon`;前端不提供外部旗帜 CDN 或固定名称兜底。
|
||||
- `/uploads/*` 与 `/assets/*` 等后台相对资源地址由 `useProductCenter.ts` 根据 `runtimeConfig.public.backendUrl` 转为完整 URL。
|
||||
- 物流和工艺筛选分别由后台名称包含“物流”和“工艺”的 Tag Group 生成;“印刷位置”等其他分组仍可用于商品卡片标签,但不会自动成为顶部筛选项。
|
||||
- Figma 基准图标位于 `apps/api/public/product-center/`,通过后端 `/assets/product-center/*` 提供。
|
||||
- 更新现有数据库图标配置:`pnpm --filter @inkreach/api configure:product-center-icons`。脚本按中文名称幂等更新,不创建缺失的国家或品类。
|
||||
|
||||
## 响应式
|
||||
|
||||
桌面采用 240px 分类栏,页面左右保留 `24–56px` 自适应留白,分类栏可滚动但隐藏可见 scrollbar。商品网格以 240px 为最小卡片宽度自动增加或减少列数:1440px 基准宽度保持四列,1920px 可显示六列。国家筛选区按内容自然增高,不保留固定空白。
|
||||
|
||||
左侧品类栏不显示最左侧边线,一级品类之间保留 10px 间距;国家筛选区及物流、工艺、搜索工具区的底部分隔线从内容区向右延伸至页面边缘。
|
||||
|
||||
宽度低于 1000px 后分类栏变为抽屉;移动端商品网格降为两列或单列,国家筛选仅在自身区域横向滚动,不会撑宽页面。
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
pnpm --filter @inkreach/website test --run
|
||||
pnpm --filter @inkreach/website build
|
||||
pnpm --filter @inkreach/website dev
|
||||
pnpm --filter @inkreach/api test -- --runInBand
|
||||
pnpm --filter @inkreach/api build
|
||||
```
|
||||
|
||||
+289
-289
@@ -1,289 +1,289 @@
|
||||
# InkReach Product Center — Monorepo Structure
|
||||
|
||||
> pnpm workspaces + Turborepo 统一管理三个子项目与共享包。所有子项目位于 `apps/`,共享代码位于 `packages/`。
|
||||
|
||||
## 根目录
|
||||
|
||||
```
|
||||
inkreach-official/
|
||||
├── apps/
|
||||
│ ├── api/ # NestJS 后端 (package: @inkreach/api)
|
||||
│ ├── admin/ # Vue 3 后台 (package: @inkreach/admin)
|
||||
│ └── website/ # Nuxt 4 官网 (package: @inkreach/website)
|
||||
├── packages/
|
||||
│ ├── tsconfig/ # 共享 TypeScript 配置 (base / api / vue)
|
||||
│ └── shared-types/ # 共享类型定义(PaginatedResult, BackendEnvelope, 实体接口)
|
||||
├── .agents/ # AI Agent 共享技能
|
||||
├── docs/ # 跨子项目文档
|
||||
│ ├── dev/ # 设计文档(数据库表设计、PRD、原型图)
|
||||
│ ├── references/
|
||||
│ │ └── structs.md # 本文件
|
||||
│ └── superpowers/specs/ # superpowers 规格说明
|
||||
├── plans/ # 跨子项目计划
|
||||
├── package.json # 根 workspace 配置(scripts + devDependencies)
|
||||
├── pnpm-workspace.yaml # pnpm workspace 定义(apps/*, packages/*)
|
||||
├── turbo.json # Turborepo 任务流水线
|
||||
├── .prettierrc # 统一代码格式化
|
||||
├── .gitignore # 全局忽略规则
|
||||
├── .env # 根级环境变量(DATABASE_URL 等)
|
||||
├── AGENTS.md # 根级 AI Agent 开发规范
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## 端口与子项目
|
||||
|
||||
| 子项目 | 包名 | 端口 | 启动命令 | 说明 |
|
||||
|--------|------|------|----------|------|
|
||||
| `apps/api` | `@inkreach/api` | 3001 | `pnpm --filter @inkreach/api start:dev` | NestJS + Prisma + PostgreSQL 后端 API;Swagger 文档 `/api/docs` |
|
||||
| `apps/admin` | `@inkreach/admin` | 5173 | `pnpm --filter @inkreach/admin dev` | Vue 3 + Element Plus 后台管理;通过 Vite proxy `/api → :3001` |
|
||||
| `apps/website` | `@inkreach/website` | 3000 | `pnpm --filter @inkreach/website dev` | Nuxt 4 官网;通过 Nitro `server/api/backend/*` 代理 `:3001` |
|
||||
|
||||
启动顺序:先启动 `apps/api`,再启动另两个。
|
||||
|
||||
## 子项目 1:apps/api(后端,@inkreach/api)
|
||||
|
||||
```
|
||||
apps/api/
|
||||
├── prisma/
|
||||
│ ├── schema.prisma # 数据模型(OriginGood/Country/Category/Tag/Position/Good/User/SyncLog)
|
||||
│ └── migrations/ # Prisma migrate 历史
|
||||
├── src/
|
||||
│ ├── main.ts # 入口:CORS、ValidationPipe、Swagger、BigInt JSON 序列化
|
||||
│ ├── app.module.ts # 根模块,聚合所有业务模块
|
||||
│ ├── prisma/ # PrismaService 封装
|
||||
│ ├── auth/ # JWT 认证:register / login / JwtStrategy / JwtAuthGuard
|
||||
│ ├── countries/ # 国家 CRUD(受 JWT 保护)
|
||||
│ ├── categories/ # 品类 CRUD(受 JWT 保护,自引用树)
|
||||
│ ├── tags/ # 标签 CRUD(受 JWT 保护)
|
||||
│ ├── tag-groups/ # 标签分组 CRUD(受 JWT 保护,含批量排序)
|
||||
│ ├── positions/ # 坑位 CRUD(受 JWT 保护)
|
||||
│ ├── origin-goods/ # SDS 原始商品快照(只读分页)
|
||||
│ ├── goods/ # 商品 CRUD + 批量优先级 + 批量创建
|
||||
│ ├── sync/ # SDS 同步:分类 / 商品 / 同步日志
|
||||
│ ├── public/ # 公开 API:分类树 / 国家 / 商品分页 / 商品详情
|
||||
│ └── common/ # 全局装饰器 / 过滤器 / 拦截器
|
||||
│ ├── decorators/current-user.decorator.ts
|
||||
│ ├── filters/http-exception.filter.ts
|
||||
│ └── interceptors/transform.interceptor.ts
|
||||
├── test/ # e2e 测试
|
||||
├── dist/ # 构建产物
|
||||
├── nest-cli.json
|
||||
├── tsconfig.json / tsconfig.build.json
|
||||
├── jest.config.js
|
||||
└── package.json
|
||||
```
|
||||
|
||||
### 数据模型(Prisma)
|
||||
|
||||
| 模型 | 说明 |
|
||||
|------|------|
|
||||
| `OriginGood` | SDS 原始商品缓存,关联 `sds_good_id`(唯一) |
|
||||
| `Country` | 国家,关联 goods / positions |
|
||||
| `Category` | 自引用树形品类,可选 `sds_category_id` |
|
||||
| `Tag` | 标签,含 `tagColor`、`tagFontColor`、`tagGroupId`、`sortOrder`、`timing` |
|
||||
| `TagGroup` | 标签分组(含 `groupName`、`groupColor`、`groupIcon`、`sortOrder`),删除分组时组内 tag 的 `tagGroupId` 通过 `onDelete: SetNull` 自动置空 |
|
||||
| `Position` | 坑位:`(country, category)` 维度,关联多个 goods |
|
||||
| `Good` | 商品:`originGood × country × category × tag? × position?`,含 `goodPriority` |
|
||||
| `User` | 后台用户(bcrypt 哈希) |
|
||||
| `SyncLog` | 同步任务日志,含 `SyncType`(CATEGORIES / PRODUCTS)和 `SyncStatus` |
|
||||
|
||||
### 关键设计
|
||||
|
||||
- **所有主键为 `BigInt`**,路由解析后 `BigInt(id)` 处理,序列化时通过 `BigInt.prototype.toJSON` 转为字符串。
|
||||
- **全局 `TransformInterceptor`**:把响应包装为 `{ data: T, success: true }`,前端读取 `response.data`。
|
||||
- **全局 `ValidationPipe`**:`whitelist + transform + forbidNonWhitelisted`。
|
||||
- **全局 `HttpExceptionFilter`**:统一错误响应形态。
|
||||
- **CORS 白名单**:`http://localhost:5173`(admin)和 `http://localhost:3000`(website)。
|
||||
- **JWT**:所有 `/goods /categories /countries /tags /positions /origin-goods /sync/*` 路由受 `JwtAuthGuard` 保护;`/public/*` 与 `/auth/*` 公开。
|
||||
|
||||
### API 路由
|
||||
|
||||
| 前缀 | 说明 | 鉴权 |
|
||||
|------|------|------|
|
||||
| `/auth/register` `POST` | 注册后台用户 | 公开 |
|
||||
| `/auth/login` `POST` | 登录获取 JWT | 公开 |
|
||||
| `/public/categories` `GET` | 公开品类树(仅含已挂商品的品类) | 公开 |
|
||||
| `/public/countries` `GET` | 公开国家列表(仅含已挂商品的国家) | 公开 |
|
||||
| `/public/tags` `GET` | 公开标签列表(带 `group` 字段,按 group 排序) | 公开 |
|
||||
| `/public/tag-groups` `GET` | 公开标签分组列表 | 公开 |
|
||||
| `/public/goods` `GET` | 分页商品(支持 `countryId/categoryId/tagIds(逗号分隔)/keyword/page/pageSize`,`tagIds` 为 AND 关系) | 公开 |
|
||||
| `/public/goods/:id` `GET` | 商品详情 | 公开 |
|
||||
| `/categories` `/tags` `/tag-groups` `/countries` `/positions` | 后台 CRUD | JWT |
|
||||
| `/tags/sort` `PATCH` | 批量更新 tag 排序和分组归属 | JWT |
|
||||
| `/tag-groups/sort` `PATCH` | 批量更新分组排序 | JWT |
|
||||
| `/origin-goods` `GET` | SDS 原始商品快照分页 | JWT |
|
||||
| `/goods` | 后台商品 CRUD + `POST /goods/batch` + `PATCH /goods/batch-priority` | JWT |
|
||||
| `/sync/categories` `POST` | 手动触发分类同步 | JWT |
|
||||
| `/sync/products` `POST` | 手动触发商品同步 | JWT |
|
||||
| `/sync/status` `GET` | 最近同步日志(`?limit=20`) | JWT |
|
||||
| `/api/docs` | Swagger UI | 公开 |
|
||||
|
||||
## 子项目 2:apps/admin(后台,@inkreach/admin)
|
||||
|
||||
```
|
||||
apps/admin/
|
||||
├── public/ # 静态资源
|
||||
├── src/
|
||||
│ ├── main.ts # 入口:Pinia + Vue Router + ElementPlus
|
||||
│ ├── App.vue
|
||||
│ ├── style.css # 全局样式(含品牌色变量)
|
||||
│ ├── api/ # 按业务模块拆分的 API 客户端
|
||||
│ │ ├── request.ts # axios 实例 + JWT 拦截 + 全局错误处理
|
||||
│ │ ├── auth.ts # /auth/login, /auth/me, /auth/logout
|
||||
│ │ ├── goods.ts # 商品 CRUD + 批量
|
||||
│ │ ├── categories.ts # 品类 CRUD
|
||||
│ │ ├── countries.ts # 国家 CRUD
|
||||
│ │ ├── tags.ts # 标签 CRUD(含批量排序)
|
||||
│ │ ├── tag-groups.ts # 标签分组 CRUD
|
||||
│ │ ├── positions.ts # 坑位 CRUD
|
||||
│ │ ├── origin-goods.ts # 原始商品快照
|
||||
│ │ └── sync.ts # 同步触发 + 日志
|
||||
│ ├── layouts/DefaultLayout.vue # 侧边栏 + 顶部条 + 用户菜单
|
||||
│ ├── router/index.ts # 路由 + 登录守卫
|
||||
│ ├── stores/
|
||||
│ │ ├── auth.ts # 登录态 + token + user(持久化到 localStorage)
|
||||
│ │ └── app.ts # 侧边栏折叠
|
||||
│ ├── types/index.ts # 共享类型
|
||||
│ ├── views/
|
||||
│ │ ├── login/LoginView.vue # 登录
|
||||
│ │ ├── goods/GoodsView.vue
|
||||
│ │ ├── categories/CategoriesView.vue
|
||||
│ │ ├── countries/CountriesView.vue
|
||||
│ │ ├── tags/TagsView.vue
|
||||
│ │ ├── positions/PositionsView.vue
|
||||
│ │ └── sync/SyncView.vue
|
||||
│ ├── auto-imports.d.ts # 自动生成的自动导入类型
|
||||
│ └── components.d.ts # 自动生成的组件类型
|
||||
├── index.html
|
||||
├── vite.config.ts # Vite + ElementPlus 自动导入 + /api → :3001 代理
|
||||
├── tsconfig.json / tsconfig.app.json / tsconfig.node.json
|
||||
└── package.json
|
||||
```
|
||||
|
||||
### 路由
|
||||
|
||||
| 路径 | 视图 | 鉴权 |
|
||||
|------|------|------|
|
||||
| `/login` | LoginView | 公开 |
|
||||
| `/goods` | GoodsView(默认页) | JWT |
|
||||
| `/categories` | CategoriesView | JWT |
|
||||
| `/countries` | CountriesView | JWT |
|
||||
| `/tags` | TagsView | JWT |
|
||||
| `/positions` | PositionsView | JWT |
|
||||
| `/sync` | SyncView | JWT |
|
||||
| `/:pathMatch(.*)*` | 重定向到 `/goods` | — |
|
||||
|
||||
### 关键设计
|
||||
|
||||
- **Vite 代理**:`/api/*` 代理到 `http://localhost:3001`,rewrite 去掉 `/api` 前缀。
|
||||
- **axios 拦截器**:请求注入 `Authorization: Bearer <token>`;响应直接返回 `response.data`;401 自动登出跳转。
|
||||
- **ElementPlus 自动导入**:通过 `unplugin-auto-import` + `unplugin-vue-components` + `ElementPlusResolver`。
|
||||
- **路由守卫**:未登录访问受保护路由跳 `/login`;已登录访问 `/login` 跳 `/`。
|
||||
- **Pinia 持久化**:`auth` store 主动读写 `localStorage`(`token` + `user`)。
|
||||
|
||||
## 子项目 3:apps/website(官网,@inkreach/website)
|
||||
|
||||
```
|
||||
apps/website/
|
||||
├── app/
|
||||
│ ├── app.vue # 根容器:<NuxtPage />
|
||||
│ ├── pages/
|
||||
│ │ ├── index.vue # 首页
|
||||
│ │ └── product-center.vue # 产品中心(侧边栏 + 国家/筛选 + 网格 + 分页)
|
||||
│ ├── assets/css/tailwind.css # Tailwind v4 主题(@theme 定义颜色与动画)
|
||||
│ ├── composables/
|
||||
│ │ ├── useNavData.ts # 导航数据(选品推荐/解决方案)
|
||||
│ │ ├── usePodProducts.ts # 首页 POD 产品(SDS)
|
||||
│ │ └── useProductCenter.ts # 产品中心数据(调用 /api/backend/*)
|
||||
│ └── components/
|
||||
│ ├── AppHeader.vue / AppFooter.vue
|
||||
│ ├── nav/ # 导航下拉面板
|
||||
│ │ ├── NavMegaMenu.vue
|
||||
│ │ └── NavColumnMenu.vue
|
||||
│ ├── product/ # 产品中心专用组件
|
||||
│ │ ├── ProductSidebar.vue # 树形品类侧边栏
|
||||
│ │ ├── ProductCountryFilter.vue # 国家 pill 筛选
|
||||
│ │ ├── ProductFilterBar.vue # 搜索输入 + 按钮
|
||||
│ │ ├── ProductCard.vue # 商品卡片
|
||||
│ │ ├── ProductCardSkeleton.vue # 骨架占位
|
||||
│ │ ├── ProductGrid.vue # 网格容器
|
||||
│ │ └── ProductPagination.vue # 分页器
|
||||
│ ├── HeroBanner.vue
|
||||
│ ├── TrustSection.vue
|
||||
│ ├── StepProcess.vue
|
||||
│ ├── PodProducts.vue
|
||||
│ ├── FeatureCards.vue
|
||||
│ ├── WhyInkReach.vue
|
||||
│ ├── CompanyProfile.vue
|
||||
│ ├── CustomerCases.vue
|
||||
│ └── CtaBanner.vue
|
||||
├── server/ # Nitro 后端
|
||||
│ ├── api/
|
||||
│ │ ├── pod/ # 原有 SDS POD API 代理
|
||||
│ │ └── backend/ # NestJS 后端代理(同源 + Nitro 缓存 60s)
|
||||
│ │ ├── categories.get.ts # → GET :3001/public/categories
|
||||
│ │ ├── countries.get.ts # → GET :3001/public/countries
|
||||
│ │ ├── tags.get.ts # → GET :3001/public/tags
|
||||
│ │ ├── tag-groups.get.ts # → GET :3001/public/tag-groups
|
||||
│ │ ├── goods.get.ts # → GET :3001/public/goods
|
||||
│ │ └── goods/[id].get.ts # → GET :3001/public/goods/:id
|
||||
│ └── utils/pod-api.ts
|
||||
├── public/ # 静态资源
|
||||
├── plans/feature/ # 历史功能计划
|
||||
├── docs/
|
||||
│ ├── references/structs.md # 子项目级结构文档
|
||||
│ └── superpowers/specs/
|
||||
├── nuxt.config.ts # runtimeConfig.public.backendUrl
|
||||
├── .env # NUXT_PUBLIC_BACKEND_URL=http://localhost:3001
|
||||
├── AGENTS.md # 子项目 AI Agent 规范
|
||||
├── README.md
|
||||
└── package.json
|
||||
```
|
||||
|
||||
> 子项目内部的页面、组件、composable、代理路由、组件响应式断点等详细信息见 `apps/website/docs/references/structs.md`。
|
||||
|
||||
## 数据库
|
||||
- `apps/api/public/product-center/`:产品中心 Figma 国家旗帜与一级品类图标,由 `/assets/product-center/*` 对外提供。
|
||||
- `apps/api/prisma/configure-product-center-icons.ts`:按名称幂等写入 `countryIcon/categoryIcon`,不创建业务记录。
|
||||
|
||||
## 产品中心设计还原(2026-07-16)
|
||||
|
||||
- `apps/website/app/components/product/ProductCenterHeader.vue`:Figma 产品中心专用 80px 导航栏。
|
||||
- `apps/website/app/components/product/ProductSidebar.vue`:240px 分类树及移动端抽屉内容。
|
||||
- `apps/website/app/components/product/ProductCountryFilter.vue`:国家胶囊筛选。
|
||||
- `apps/website/app/components/product/ProductTagFilter.vue`:物流与工艺分组筛选。
|
||||
- `apps/website/app/components/product/ProductCard.vue`:270 x 382 桌面商品卡片。
|
||||
- `apps/website/app/components/product/ProductPagination.vue`:总数、页码、每页数量和跳转。
|
||||
- `apps/website/test/useProductCenter.test.ts`:默认筛选名称映射测试。
|
||||
- `docs/references/product-center.md`:产品中心使用与验证说明。
|
||||
- `skills/inkreach-official-website/SKILL.md`:Agent 使用说明。
|
||||
|
||||
## 官网首页设计还原(2026-07-16)
|
||||
|
||||
- `apps/website/app/pages/index.vue`:官网首页组合入口。
|
||||
- `apps/website/app/components/HeroBanner.vue` 至 `AppFooter.vue`:首页分区组件。
|
||||
- `apps/website/public/case-*.png`:Figma 用户案例商品图。
|
||||
- `docs/references/homepage.md`:首页结构、尺寸基准和验证说明。
|
||||
- `apps/website/test/HomepageNavigationCarousel.test.ts`:首页当前页 CTA、自动轮播、主视觉图片和临时导航隐藏的行为测试。
|
||||
|
||||
## 数据库
|
||||
|
||||
- `apps/api/src/public/public.service.ts`:官网公共商品序列化边界;响应中的商品 `id` 使用 `originGood.sdsGoodId`,不暴露本地 `goods.good_id`。
|
||||
|
||||
- PostgreSQL 14+,Prisma 5.x
|
||||
- 连接配置在根 `.env` 的 `DATABASE_URL` 中
|
||||
- 所有 `TIMESTAMPTZ` 列:`@db.Timestamptz(6)`
|
||||
- 所有主键:`BigInt @default(autoincrement())`
|
||||
- 表名与列名通过 `@map` / `@@map` 映射为 `snake_case`
|
||||
- 迁移位于 `apps/api/prisma/migrations/`
|
||||
|
||||
## 环境变量总览
|
||||
|
||||
| 变量 | 位置 | 用途 | 默认 |
|
||||
|------|------|------|------|
|
||||
| `DATABASE_URL` | 根 `.env` | PostgreSQL 连接串 | — |
|
||||
| `JWT_SECRET` | `apps/api` | JWT 签名密钥 | — |
|
||||
| `PORT` | `apps/api` | 后端端口 | `3001` |
|
||||
| `SDS_API_*` | `apps/api` | 同步上游 SDS 接口凭据 | — |
|
||||
| `VITE_API_BASE` | `apps/admin` | axios baseURL | `/api` |
|
||||
| `NUXT_PUBLIC_BACKEND_URL` | `apps/website` | NestJS 后端地址 | `http://localhost:3001` |
|
||||
# InkReach Product Center — Monorepo Structure
|
||||
|
||||
> pnpm workspaces + Turborepo 统一管理三个子项目与共享包。所有子项目位于 `apps/`,共享代码位于 `packages/`。
|
||||
|
||||
## 根目录
|
||||
|
||||
```
|
||||
inkreach-official/
|
||||
├── apps/
|
||||
│ ├── api/ # NestJS 后端 (package: @inkreach/api)
|
||||
│ ├── admin/ # Vue 3 后台 (package: @inkreach/admin)
|
||||
│ └── website/ # Nuxt 4 官网 (package: @inkreach/website)
|
||||
├── packages/
|
||||
│ ├── tsconfig/ # 共享 TypeScript 配置 (base / api / vue)
|
||||
│ └── shared-types/ # 共享类型定义(PaginatedResult, BackendEnvelope, 实体接口)
|
||||
├── .agents/ # AI Agent 共享技能
|
||||
├── docs/ # 跨子项目文档
|
||||
│ ├── dev/ # 设计文档(数据库表设计、PRD、原型图)
|
||||
│ ├── references/
|
||||
│ │ └── structs.md # 本文件
|
||||
│ └── superpowers/specs/ # superpowers 规格说明
|
||||
├── plans/ # 跨子项目计划
|
||||
├── package.json # 根 workspace 配置(scripts + devDependencies)
|
||||
├── pnpm-workspace.yaml # pnpm workspace 定义(apps/*, packages/*)
|
||||
├── turbo.json # Turborepo 任务流水线
|
||||
├── .prettierrc # 统一代码格式化
|
||||
├── .gitignore # 全局忽略规则
|
||||
├── .env # 根级环境变量(DATABASE_URL 等)
|
||||
├── AGENTS.md # 根级 AI Agent 开发规范
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## 端口与子项目
|
||||
|
||||
| 子项目 | 包名 | 端口 | 启动命令 | 说明 |
|
||||
|--------|------|------|----------|------|
|
||||
| `apps/api` | `@inkreach/api` | 3001 | `pnpm --filter @inkreach/api start:dev` | NestJS + Prisma + PostgreSQL 后端 API;Swagger 文档 `/api/docs` |
|
||||
| `apps/admin` | `@inkreach/admin` | 5173 | `pnpm --filter @inkreach/admin dev` | Vue 3 + Element Plus 后台管理;通过 Vite proxy `/api → :3001` |
|
||||
| `apps/website` | `@inkreach/website` | 3000 | `pnpm --filter @inkreach/website dev` | Nuxt 4 官网;通过 Nitro `server/api/backend/*` 代理 `:3001` |
|
||||
|
||||
启动顺序:先启动 `apps/api`,再启动另两个。
|
||||
|
||||
## 子项目 1:apps/api(后端,@inkreach/api)
|
||||
|
||||
```
|
||||
apps/api/
|
||||
├── prisma/
|
||||
│ ├── schema.prisma # 数据模型(OriginGood/Country/Category/Tag/Position/Good/User/SyncLog)
|
||||
│ └── migrations/ # Prisma migrate 历史
|
||||
├── src/
|
||||
│ ├── main.ts # 入口:CORS、ValidationPipe、Swagger、BigInt JSON 序列化
|
||||
│ ├── app.module.ts # 根模块,聚合所有业务模块
|
||||
│ ├── prisma/ # PrismaService 封装
|
||||
│ ├── auth/ # JWT 认证:register / login / JwtStrategy / JwtAuthGuard
|
||||
│ ├── countries/ # 国家 CRUD(受 JWT 保护)
|
||||
│ ├── categories/ # 品类 CRUD(受 JWT 保护,自引用树)
|
||||
│ ├── tags/ # 标签 CRUD(受 JWT 保护)
|
||||
│ ├── tag-groups/ # 标签分组 CRUD(受 JWT 保护,含批量排序)
|
||||
│ ├── positions/ # 坑位 CRUD(受 JWT 保护)
|
||||
│ ├── origin-goods/ # SDS 原始商品快照(只读分页)
|
||||
│ ├── goods/ # 商品 CRUD + 批量优先级 + 批量创建
|
||||
│ ├── sync/ # SDS 同步:分类 / 商品 / 同步日志
|
||||
│ ├── public/ # 公开 API:分类树 / 国家 / 商品分页 / 商品详情
|
||||
│ └── common/ # 全局装饰器 / 过滤器 / 拦截器
|
||||
│ ├── decorators/current-user.decorator.ts
|
||||
│ ├── filters/http-exception.filter.ts
|
||||
│ └── interceptors/transform.interceptor.ts
|
||||
├── test/ # e2e 测试
|
||||
├── dist/ # 构建产物
|
||||
├── nest-cli.json
|
||||
├── tsconfig.json / tsconfig.build.json
|
||||
├── jest.config.js
|
||||
└── package.json
|
||||
```
|
||||
|
||||
### 数据模型(Prisma)
|
||||
|
||||
| 模型 | 说明 |
|
||||
|------|------|
|
||||
| `OriginGood` | SDS 原始商品缓存,关联 `sds_good_id`(唯一) |
|
||||
| `Country` | 国家,关联 goods / positions |
|
||||
| `Category` | 自引用树形品类,可选 `sds_category_id` |
|
||||
| `Tag` | 标签,含 `tagColor`、`tagFontColor`、`tagGroupId`、`sortOrder`、`timing` |
|
||||
| `TagGroup` | 标签分组(含 `groupName`、`groupColor`、`groupIcon`、`sortOrder`),删除分组时组内 tag 的 `tagGroupId` 通过 `onDelete: SetNull` 自动置空 |
|
||||
| `Position` | 坑位:`(country, category)` 维度,关联多个 goods |
|
||||
| `Good` | 商品:`originGood × country × category × tag? × position?`,含 `goodPriority` |
|
||||
| `User` | 后台用户(bcrypt 哈希) |
|
||||
| `SyncLog` | 同步任务日志,含 `SyncType`(CATEGORIES / PRODUCTS)和 `SyncStatus` |
|
||||
|
||||
### 关键设计
|
||||
|
||||
- **所有主键为 `BigInt`**,路由解析后 `BigInt(id)` 处理,序列化时通过 `BigInt.prototype.toJSON` 转为字符串。
|
||||
- **全局 `TransformInterceptor`**:把响应包装为 `{ data: T, success: true }`,前端读取 `response.data`。
|
||||
- **全局 `ValidationPipe`**:`whitelist + transform + forbidNonWhitelisted`。
|
||||
- **全局 `HttpExceptionFilter`**:统一错误响应形态。
|
||||
- **CORS 白名单**:`http://localhost:5173`(admin)和 `http://localhost:3000`(website)。
|
||||
- **JWT**:所有 `/goods /categories /countries /tags /positions /origin-goods /sync/*` 路由受 `JwtAuthGuard` 保护;`/public/*` 与 `/auth/*` 公开。
|
||||
|
||||
### API 路由
|
||||
|
||||
| 前缀 | 说明 | 鉴权 |
|
||||
|------|------|------|
|
||||
| `/auth/register` `POST` | 注册后台用户 | 公开 |
|
||||
| `/auth/login` `POST` | 登录获取 JWT | 公开 |
|
||||
| `/public/categories` `GET` | 公开品类树(仅含已挂商品的品类) | 公开 |
|
||||
| `/public/countries` `GET` | 公开国家列表(仅含已挂商品的国家) | 公开 |
|
||||
| `/public/tags` `GET` | 公开标签列表(带 `group` 字段,按 group 排序) | 公开 |
|
||||
| `/public/tag-groups` `GET` | 公开标签分组列表 | 公开 |
|
||||
| `/public/goods` `GET` | 分页商品(支持 `countryId/categoryId/tagIds(逗号分隔)/keyword/page/pageSize`,`tagIds` 为 AND 关系) | 公开 |
|
||||
| `/public/goods/:id` `GET` | 商品详情 | 公开 |
|
||||
| `/categories` `/tags` `/tag-groups` `/countries` `/positions` | 后台 CRUD | JWT |
|
||||
| `/tags/sort` `PATCH` | 批量更新 tag 排序和分组归属 | JWT |
|
||||
| `/tag-groups/sort` `PATCH` | 批量更新分组排序 | JWT |
|
||||
| `/origin-goods` `GET` | SDS 原始商品快照分页 | JWT |
|
||||
| `/goods` | 后台商品 CRUD + `POST /goods/batch` + `PATCH /goods/batch-priority` | JWT |
|
||||
| `/sync/categories` `POST` | 手动触发分类同步 | JWT |
|
||||
| `/sync/products` `POST` | 手动触发商品同步 | JWT |
|
||||
| `/sync/status` `GET` | 最近同步日志(`?limit=20`) | JWT |
|
||||
| `/api/docs` | Swagger UI | 公开 |
|
||||
|
||||
## 子项目 2:apps/admin(后台,@inkreach/admin)
|
||||
|
||||
```
|
||||
apps/admin/
|
||||
├── public/ # 静态资源
|
||||
├── src/
|
||||
│ ├── main.ts # 入口:Pinia + Vue Router + ElementPlus
|
||||
│ ├── App.vue
|
||||
│ ├── style.css # 全局样式(含品牌色变量)
|
||||
│ ├── api/ # 按业务模块拆分的 API 客户端
|
||||
│ │ ├── request.ts # axios 实例 + JWT 拦截 + 全局错误处理
|
||||
│ │ ├── auth.ts # /auth/login, /auth/me, /auth/logout
|
||||
│ │ ├── goods.ts # 商品 CRUD + 批量
|
||||
│ │ ├── categories.ts # 品类 CRUD
|
||||
│ │ ├── countries.ts # 国家 CRUD
|
||||
│ │ ├── tags.ts # 标签 CRUD(含批量排序)
|
||||
│ │ ├── tag-groups.ts # 标签分组 CRUD
|
||||
│ │ ├── positions.ts # 坑位 CRUD
|
||||
│ │ ├── origin-goods.ts # 原始商品快照
|
||||
│ │ └── sync.ts # 同步触发 + 日志
|
||||
│ ├── layouts/DefaultLayout.vue # 侧边栏 + 顶部条 + 用户菜单
|
||||
│ ├── router/index.ts # 路由 + 登录守卫
|
||||
│ ├── stores/
|
||||
│ │ ├── auth.ts # 登录态 + token + user(持久化到 localStorage)
|
||||
│ │ └── app.ts # 侧边栏折叠
|
||||
│ ├── types/index.ts # 共享类型
|
||||
│ ├── views/
|
||||
│ │ ├── login/LoginView.vue # 登录
|
||||
│ │ ├── goods/GoodsView.vue
|
||||
│ │ ├── categories/CategoriesView.vue
|
||||
│ │ ├── countries/CountriesView.vue
|
||||
│ │ ├── tags/TagsView.vue
|
||||
│ │ ├── positions/PositionsView.vue
|
||||
│ │ └── sync/SyncView.vue
|
||||
│ ├── auto-imports.d.ts # 自动生成的自动导入类型
|
||||
│ └── components.d.ts # 自动生成的组件类型
|
||||
├── index.html
|
||||
├── vite.config.ts # Vite + ElementPlus 自动导入 + /api → :3001 代理
|
||||
├── tsconfig.json / tsconfig.app.json / tsconfig.node.json
|
||||
└── package.json
|
||||
```
|
||||
|
||||
### 路由
|
||||
|
||||
| 路径 | 视图 | 鉴权 |
|
||||
|------|------|------|
|
||||
| `/login` | LoginView | 公开 |
|
||||
| `/goods` | GoodsView(默认页) | JWT |
|
||||
| `/categories` | CategoriesView | JWT |
|
||||
| `/countries` | CountriesView | JWT |
|
||||
| `/tags` | TagsView | JWT |
|
||||
| `/positions` | PositionsView | JWT |
|
||||
| `/sync` | SyncView | JWT |
|
||||
| `/:pathMatch(.*)*` | 重定向到 `/goods` | — |
|
||||
|
||||
### 关键设计
|
||||
|
||||
- **Vite 代理**:`/api/*` 代理到 `http://localhost:3001`,rewrite 去掉 `/api` 前缀。
|
||||
- **axios 拦截器**:请求注入 `Authorization: Bearer <token>`;响应直接返回 `response.data`;401 自动登出跳转。
|
||||
- **ElementPlus 自动导入**:通过 `unplugin-auto-import` + `unplugin-vue-components` + `ElementPlusResolver`。
|
||||
- **路由守卫**:未登录访问受保护路由跳 `/login`;已登录访问 `/login` 跳 `/`。
|
||||
- **Pinia 持久化**:`auth` store 主动读写 `localStorage`(`token` + `user`)。
|
||||
|
||||
## 子项目 3:apps/website(官网,@inkreach/website)
|
||||
|
||||
```
|
||||
apps/website/
|
||||
├── app/
|
||||
│ ├── app.vue # 根容器:<NuxtPage />
|
||||
│ ├── pages/
|
||||
│ │ ├── index.vue # 首页
|
||||
│ │ └── product-center.vue # 产品中心(侧边栏 + 国家/筛选 + 网格 + 分页)
|
||||
│ ├── assets/css/tailwind.css # Tailwind v4 主题(@theme 定义颜色与动画)
|
||||
│ ├── composables/
|
||||
│ │ ├── useNavData.ts # 导航数据(选品推荐/解决方案)
|
||||
│ │ ├── usePodProducts.ts # 首页 POD 产品(SDS)
|
||||
│ │ └── useProductCenter.ts # 产品中心数据(调用 /api/backend/*)
|
||||
│ └── components/
|
||||
│ ├── AppHeader.vue / AppFooter.vue
|
||||
│ ├── nav/ # 导航下拉面板
|
||||
│ │ ├── NavMegaMenu.vue
|
||||
│ │ └── NavColumnMenu.vue
|
||||
│ ├── product/ # 产品中心专用组件
|
||||
│ │ ├── ProductSidebar.vue # 树形品类侧边栏
|
||||
│ │ ├── ProductCountryFilter.vue # 国家 pill 筛选
|
||||
│ │ ├── ProductFilterBar.vue # 搜索输入 + 按钮
|
||||
│ │ ├── ProductCard.vue # 商品卡片
|
||||
│ │ ├── ProductCardSkeleton.vue # 骨架占位
|
||||
│ │ ├── ProductGrid.vue # 网格容器
|
||||
│ │ └── ProductPagination.vue # 分页器
|
||||
│ ├── HeroBanner.vue
|
||||
│ ├── TrustSection.vue
|
||||
│ ├── StepProcess.vue
|
||||
│ ├── PodProducts.vue
|
||||
│ ├── FeatureCards.vue
|
||||
│ ├── WhyInkReach.vue
|
||||
│ ├── CompanyProfile.vue
|
||||
│ ├── CustomerCases.vue
|
||||
│ └── CtaBanner.vue
|
||||
├── server/ # Nitro 后端
|
||||
│ ├── api/
|
||||
│ │ ├── pod/ # 原有 SDS POD API 代理
|
||||
│ │ └── backend/ # NestJS 后端代理(同源 + Nitro 缓存 60s)
|
||||
│ │ ├── categories.get.ts # → GET :3001/public/categories
|
||||
│ │ ├── countries.get.ts # → GET :3001/public/countries
|
||||
│ │ ├── tags.get.ts # → GET :3001/public/tags
|
||||
│ │ ├── tag-groups.get.ts # → GET :3001/public/tag-groups
|
||||
│ │ ├── goods.get.ts # → GET :3001/public/goods
|
||||
│ │ └── goods/[id].get.ts # → GET :3001/public/goods/:id
|
||||
│ └── utils/pod-api.ts
|
||||
├── public/ # 静态资源
|
||||
├── plans/feature/ # 历史功能计划
|
||||
├── docs/
|
||||
│ ├── references/structs.md # 子项目级结构文档
|
||||
│ └── superpowers/specs/
|
||||
├── nuxt.config.ts # runtimeConfig.public.backendUrl
|
||||
├── .env # NUXT_PUBLIC_BACKEND_URL=http://localhost:3001
|
||||
├── AGENTS.md # 子项目 AI Agent 规范
|
||||
├── README.md
|
||||
└── package.json
|
||||
```
|
||||
|
||||
> 子项目内部的页面、组件、composable、代理路由、组件响应式断点等详细信息见 `apps/website/docs/references/structs.md`。
|
||||
|
||||
## 数据库
|
||||
- `apps/api/public/product-center/`:产品中心 Figma 国家旗帜与一级品类图标,由 `/assets/product-center/*` 对外提供。
|
||||
- `apps/api/prisma/configure-product-center-icons.ts`:按名称幂等写入 `countryIcon/categoryIcon`,不创建业务记录。
|
||||
|
||||
## 产品中心设计还原(2026-07-16)
|
||||
|
||||
- `apps/website/app/components/product/ProductCenterHeader.vue`:Figma 产品中心专用 80px 导航栏。
|
||||
- `apps/website/app/components/product/ProductSidebar.vue`:240px 分类树及移动端抽屉内容。
|
||||
- `apps/website/app/components/product/ProductCountryFilter.vue`:国家胶囊筛选。
|
||||
- `apps/website/app/components/product/ProductTagFilter.vue`:物流与工艺分组筛选。
|
||||
- `apps/website/app/components/product/ProductCard.vue`:270 x 382 桌面商品卡片。
|
||||
- `apps/website/app/components/product/ProductPagination.vue`:总数、页码、每页数量和跳转。
|
||||
- `apps/website/test/useProductCenter.test.ts`:默认筛选名称映射测试。
|
||||
- `docs/references/product-center.md`:产品中心使用与验证说明。
|
||||
- `skills/inkreach-official-website/SKILL.md`:Agent 使用说明。
|
||||
|
||||
## 官网首页设计还原(2026-07-16)
|
||||
|
||||
- `apps/website/app/pages/index.vue`:官网首页组合入口。
|
||||
- `apps/website/app/components/HeroBanner.vue` 至 `AppFooter.vue`:首页分区组件。
|
||||
- `apps/website/public/case-*.png`:Figma 用户案例商品图。
|
||||
- `docs/references/homepage.md`:首页结构、尺寸基准和验证说明。
|
||||
- `apps/website/test/HomepageNavigationCarousel.test.ts`:首页当前页 CTA、自动轮播、主视觉图片和临时导航隐藏的行为测试。
|
||||
|
||||
## 数据库
|
||||
|
||||
- `apps/api/src/public/public.service.ts`:官网公共商品序列化边界;响应中的商品 `id` 使用 `originGood.sdsGoodId`,不暴露本地 `goods.good_id`。
|
||||
|
||||
- PostgreSQL 14+,Prisma 5.x
|
||||
- 连接配置在根 `.env` 的 `DATABASE_URL` 中
|
||||
- 所有 `TIMESTAMPTZ` 列:`@db.Timestamptz(6)`
|
||||
- 所有主键:`BigInt @default(autoincrement())`
|
||||
- 表名与列名通过 `@map` / `@@map` 映射为 `snake_case`
|
||||
- 迁移位于 `apps/api/prisma/migrations/`
|
||||
|
||||
## 环境变量总览
|
||||
|
||||
| 变量 | 位置 | 用途 | 默认 |
|
||||
|------|------|------|------|
|
||||
| `DATABASE_URL` | 根 `.env` | PostgreSQL 连接串 | — |
|
||||
| `JWT_SECRET` | `apps/api` | JWT 签名密钥 | — |
|
||||
| `PORT` | `apps/api` | 后端端口 | `3001` |
|
||||
| `SDS_API_*` | `apps/api` | 同步上游 SDS 接口凭据 | — |
|
||||
| `VITE_API_BASE` | `apps/admin` | axios baseURL | `/api` |
|
||||
| `NUXT_PUBLIC_BACKEND_URL` | `apps/website` | NestJS 后端地址 | `http://localhost:3001` |
|
||||
|
||||
@@ -1,361 +1,361 @@
|
||||
# 产品中心 (Product Center) 设计规格
|
||||
|
||||
## 概述
|
||||
|
||||
为 InkReach 官网新增"产品中心"二级页面,包含三个子系统:
|
||||
1. **NestJS 后端服务** — Prisma + PostgreSQL,RESTful API,SDS 定时同步
|
||||
2. **Vue 3 Admin 后台** — Element Plus,商品/品类/国家/标签/位置管理
|
||||
3. **Nuxt 4 官网页面** — 产品中心二级页,带侧边栏分类、国家筛选、搜索、分页
|
||||
|
||||
## 架构
|
||||
|
||||
- NestJS 监听 `:3001`,提供 RESTful API
|
||||
- Admin 直连 NestJS API(开发环境 Vite proxy 解决跨域)
|
||||
- Website 通过 Nitro server route 代理 NestJS API
|
||||
|
||||
## 实施顺序
|
||||
|
||||
后端服务 → Admin 后台 → 官网产品中心页
|
||||
|
||||
---
|
||||
|
||||
## 1. NestJS 后端服务
|
||||
|
||||
### 技术栈
|
||||
|
||||
| 技术 | 版本 | 用途 |
|
||||
|------|------|------|
|
||||
| NestJS | 10+ | 后端框架 |
|
||||
| Prisma | 6+ | ORM |
|
||||
| PostgreSQL | 16+ | 数据库 |
|
||||
| @nestjs/schedule | - | 定时同步 SDS |
|
||||
| passport + jwt | - | JWT 认证 |
|
||||
| class-validator | - | DTO 验证 |
|
||||
|
||||
### 项目结构
|
||||
|
||||
```
|
||||
inkreach-official-nestjs/
|
||||
├── prisma/
|
||||
│ ├── schema.prisma
|
||||
│ └── migrations/
|
||||
├── src/
|
||||
│ ├── main.ts
|
||||
│ ├── app.module.ts
|
||||
│ ├── auth/
|
||||
│ │ ├── auth.module.ts
|
||||
│ │ ├── auth.controller.ts
|
||||
│ │ ├── auth.service.ts
|
||||
│ │ ├── jwt.strategy.ts
|
||||
│ │ └── dto/
|
||||
│ ├── goods/
|
||||
│ │ ├── goods.module.ts
|
||||
│ │ ├── goods.controller.ts
|
||||
│ │ ├── goods.service.ts
|
||||
│ │ └── dto/
|
||||
│ ├── categories/
|
||||
│ ├── countries/
|
||||
│ ├── tags/
|
||||
│ ├── positions/
|
||||
│ ├── origin-goods/
|
||||
│ └── sync/
|
||||
│ ├── sync.module.ts
|
||||
│ ├── sync.service.ts
|
||||
│ └── sync.controller.ts
|
||||
├── test/
|
||||
├── .env
|
||||
├── nest-cli.json
|
||||
├── tsconfig.json
|
||||
└── package.json
|
||||
```
|
||||
|
||||
### Prisma Schema
|
||||
|
||||
将 `docs/dev/database-table-design.md` 的 DDL 转换为 Prisma schema,6 个核心模型:`OriginGoods`, `Country`, `Category`, `Tag`, `Position`, `Good`。
|
||||
|
||||
关系映射:
|
||||
- `Good` → `OriginGoods` (多对一, RESTRICT)
|
||||
- `Good` → `Country` (多对一, RESTRICT)
|
||||
- `Good` → `Category` (多对一, RESTRICT)
|
||||
- `Good` → `Tag` (多对一, SET NULL)
|
||||
- `Good` → `Position` (多对一, SET NULL)
|
||||
- `Category` 自关联 (parent_category_id)
|
||||
- `Position` → `Country` (多对一, CASCADE)
|
||||
- `Position` → `Category` (多对一, CASCADE)
|
||||
|
||||
### API 设计
|
||||
|
||||
#### 认证
|
||||
|
||||
| 方法 | 路径 | 描述 |
|
||||
|------|------|------|
|
||||
| POST | `/auth/login` | 管理员登录,返回 JWT |
|
||||
| POST | `/auth/register` | 注册(仅初始化用) |
|
||||
|
||||
#### 商品管理 (Admin, 需认证)
|
||||
|
||||
| 方法 | 路径 | 描述 |
|
||||
|------|------|------|
|
||||
| GET | `/goods` | 分页查询商品(支持 country/category/tag/position/keyword 筛选) |
|
||||
| GET | `/goods/:id` | 商品详情 |
|
||||
| POST | `/goods` | 创建商品 |
|
||||
| PATCH | `/goods/:id` | 更新商品 |
|
||||
| DELETE | `/goods/:id` | 删除商品 |
|
||||
| PATCH | `/goods/batch-priority` | 批量更新优先级 |
|
||||
| POST | `/goods/batch` | 批量创建商品(从 origin_goods 选取) |
|
||||
|
||||
#### 品类管理 (Admin)
|
||||
|
||||
| 方法 | 路径 | 描述 |
|
||||
|------|------|------|
|
||||
| GET | `/categories` | 获取品类树 |
|
||||
| GET | `/categories/:id` | 品类详情 |
|
||||
| POST | `/categories` | 创建品类 |
|
||||
| PATCH | `/categories/:id` | 更新品类 |
|
||||
| DELETE | `/categories/:id` | 删除品类 |
|
||||
|
||||
#### 国家管理 (Admin)
|
||||
|
||||
| 方法 | 路径 | 描述 |
|
||||
|------|------|------|
|
||||
| GET | `/countries` | 国家列表 |
|
||||
| POST | `/countries` | 创建国家 |
|
||||
| PATCH | `/countries/:id` | 更新国家 |
|
||||
| DELETE | `/countries/:id` | 删除国家 |
|
||||
|
||||
#### 标签管理 (Admin)
|
||||
|
||||
| 方法 | 路径 | 描述 |
|
||||
|------|------|------|
|
||||
| GET | `/tags` | 标签列表 |
|
||||
| POST | `/tags` | 创建标签 |
|
||||
| PATCH | `/tags/:id` | 更新标签 |
|
||||
| DELETE | `/tags/:id` | 删除标签 |
|
||||
|
||||
#### 位置管理 (Admin)
|
||||
|
||||
| 方法 | 路径 | 描述 |
|
||||
|------|------|------|
|
||||
| GET | `/positions` | 位置列表 |
|
||||
| POST | `/positions` | 创建位置 |
|
||||
| PATCH | `/positions/:id` | 更新位置 |
|
||||
| DELETE | `/positions/:id` | 删除位置 |
|
||||
|
||||
#### 原商品管理 (Admin)
|
||||
|
||||
| 方法 | 路径 | 描述 |
|
||||
|------|------|------|
|
||||
| GET | `/origin-goods` | 原商品列表(来自 SDS 同步) |
|
||||
|
||||
#### 同步 (Admin)
|
||||
|
||||
| 方法 | 路径 | 描述 |
|
||||
|------|------|------|
|
||||
| POST | `/sync/categories` | 手动触发品类同步 |
|
||||
| POST | `/sync/products` | 手动触发产品同步 |
|
||||
| GET | `/sync/status` | 获取最近同步状态 |
|
||||
|
||||
#### 公开 API (Website, 无需认证)
|
||||
|
||||
| 方法 | 路径 | 描述 |
|
||||
|------|------|------|
|
||||
| GET | `/public/categories` | 品类树(仅含已配置商品的品类) |
|
||||
| GET | `/public/countries` | 国家列表(仅含已配置商品的国家) |
|
||||
| GET | `/public/goods` | 分页查询商品(支持 country/category/tag/keyword 筛选) |
|
||||
| GET | `/public/goods/:id` | 商品详情 |
|
||||
|
||||
### SDS 同步逻辑
|
||||
|
||||
1. **品类同步**: 调用 `https://mapi.sdspod.com/category/tree/3`,将返回的树形结构扁平化写入 `categories` 表
|
||||
2. **产品同步**: 按 SDS 分类遍历产品,写入 `origin_goods` 表
|
||||
3. **定时策略**: 每小时自动同步一次 (`@Cron('0 * * * *')`)
|
||||
4. **增量策略**: 对比现有记录,存在则跳过,不存在则插入
|
||||
|
||||
### 商品查询逻辑 (公开 API)
|
||||
|
||||
排序: `good_priority DESC → position.index_val ASC → created_at DESC`
|
||||
|
||||
筛选参数:
|
||||
- `countryId` — 按国家筛选
|
||||
- `categoryId` — 按品类筛选(含子品类)
|
||||
- `tagId` — 按标签筛选
|
||||
- `keyword` — 按商品名模糊搜索
|
||||
- `page` / `pageSize` — 分页
|
||||
|
||||
---
|
||||
|
||||
## 2. Admin 后台
|
||||
|
||||
### 技术栈
|
||||
|
||||
| 技术 | 版本 | 用途 |
|
||||
|------|------|------|
|
||||
| Vue | 3.5+ | UI 框架 |
|
||||
| Vite | 6+ | 构建工具 |
|
||||
| Element Plus | 2.9+ | UI 组件库 |
|
||||
| Vue Router | 4+ | 路由 |
|
||||
| Pinia | 3+ | 状态管理 |
|
||||
| Axios | 1.7+ | HTTP 客户端 |
|
||||
| TypeScript | 5+ | 类型安全 |
|
||||
|
||||
### 项目结构
|
||||
|
||||
```
|
||||
inkreach-official-admin/
|
||||
├── src/
|
||||
│ ├── main.ts
|
||||
│ ├── App.vue
|
||||
│ ├── router/index.ts
|
||||
│ ├── stores/
|
||||
│ │ ├── auth.ts
|
||||
│ │ └── app.ts
|
||||
│ ├── api/
|
||||
│ │ ├── request.ts
|
||||
│ │ ├── auth.ts
|
||||
│ │ ├── goods.ts
|
||||
│ │ ├── categories.ts
|
||||
│ │ ├── countries.ts
|
||||
│ │ ├── tags.ts
|
||||
│ │ ├── positions.ts
|
||||
│ │ ├── origin-goods.ts
|
||||
│ │ └── sync.ts
|
||||
│ ├── layouts/
|
||||
│ │ └── DefaultLayout.vue
|
||||
│ ├── views/
|
||||
│ │ ├── login/LoginView.vue
|
||||
│ │ ├── goods/GoodsView.vue
|
||||
│ │ ├── categories/CategoriesView.vue
|
||||
│ │ ├── countries/CountriesView.vue
|
||||
│ │ ├── tags/TagsView.vue
|
||||
│ │ ├── positions/PositionsView.vue
|
||||
│ │ └── sync/SyncView.vue
|
||||
│ ├── components/
|
||||
│ └── types/index.ts
|
||||
├── .env
|
||||
├── vite.config.ts
|
||||
├── tsconfig.json
|
||||
└── package.json
|
||||
```
|
||||
|
||||
### 核心页面功能
|
||||
|
||||
**商品管理 (GoodsView)**:
|
||||
- 表格展示:商品名、国家、品类、标签、位置、优先级
|
||||
- 筛选:按国家/品类/标签筛选
|
||||
- 操作:新增、编辑、删除、调整优先级
|
||||
- 批量添加:从 origin_goods 选择商品,批量配置国家/品类/标签/优先级
|
||||
|
||||
**品类管理 (CategoriesView)**:
|
||||
- 树形表格展示层级关系
|
||||
- 新增/编辑/删除品类
|
||||
|
||||
**同步管理 (SyncView)**:
|
||||
- 显示最近同步时间、同步状态
|
||||
- 手动触发品类同步/产品同步按钮
|
||||
|
||||
---
|
||||
|
||||
## 3. 官网产品中心页
|
||||
|
||||
### 页面结构
|
||||
|
||||
左右两栏布局:左侧固定宽度侧边栏(品类导航),右侧主内容区(国家筛选 + 筛选栏 + 商品网格 + 分页)。
|
||||
|
||||
### 组件拆分
|
||||
|
||||
```
|
||||
app/pages/product-center.vue # 页面入口
|
||||
app/components/product/
|
||||
├── ProductSidebar.vue # 侧边栏品类导航
|
||||
├── ProductCountryFilter.vue # 国家标签栏
|
||||
├── ProductFilterBar.vue # 筛选下拉 + 已选标签 + 搜索
|
||||
├── ProductGrid.vue # 商品卡片网格
|
||||
├── ProductCard.vue # 单个商品卡片
|
||||
├── ProductCardSkeleton.vue # 骨架屏卡片
|
||||
└── ProductPagination.vue # 分页控件
|
||||
app/composables/
|
||||
└── useProductCenter.ts # 产品中心数据逻辑
|
||||
```
|
||||
|
||||
### 商品卡片设计
|
||||
|
||||
- 商品图片 (1:1 方形)
|
||||
- 商品名 (单行截断)
|
||||
- 标签行 (pill 样式,颜色体系:绿色=包邮,橙色=工艺,灰色=其他)
|
||||
- 价格 (粗体,"¥XX.XX起")
|
||||
|
||||
### 侧边栏品类导航
|
||||
|
||||
- 顶级品类带图标 + 折叠箭头
|
||||
- 展开子品类列表,左侧橙色竖条标识当前选中
|
||||
- 选中子品类背景 `bg-inkreach-homepage-3` + 橙色文字
|
||||
|
||||
### 国家标签栏
|
||||
|
||||
- Pill 形状按钮,每个带国旗图标
|
||||
- "全部" 为默认选中,橙色边框 + 橙色文字
|
||||
- 未选中为灰色边框
|
||||
|
||||
### 搜索
|
||||
|
||||
- 分体式搜索框:左侧输入框 + 右侧橙色"搜索"按钮
|
||||
- 支持按商品名搜索
|
||||
|
||||
### 分页
|
||||
|
||||
- 左侧:"总计 N 个产品"
|
||||
- 中间:页码按钮(方形)
|
||||
- 右侧:每页条数下拉 + 跳转输入框
|
||||
|
||||
### 骨架屏
|
||||
|
||||
数据加载时显示骨架屏:
|
||||
- 卡片区域显示 ProductCardSkeleton (4x3 网格)
|
||||
- 分页隐藏
|
||||
- 侧边栏和筛选区正常显示
|
||||
|
||||
### Nitro API 代理
|
||||
|
||||
```
|
||||
server/api/backend/categories.get.ts -> GET :3001/public/categories
|
||||
server/api/backend/countries.get.ts -> GET :3001/public/countries
|
||||
server/api/backend/goods.get.ts -> GET :3001/public/goods
|
||||
server/api/backend/goods/[id].get.ts -> GET :3001/public/goods/:id
|
||||
```
|
||||
|
||||
### 数据流
|
||||
|
||||
1. 页面加载 → `/api/backend/categories` → 渲染侧边栏
|
||||
2. 页面加载 → `/api/backend/countries` → 渲染国家标签栏
|
||||
3. 筛选/分页变化 → `/api/backend/goods?countryId=&categoryId=&keyword=&page=&pageSize=` → 渲染商品网格
|
||||
|
||||
### 关联改动
|
||||
|
||||
- 首页 `PodProducts.vue` 的"更多产品"按钮改为 `<NuxtLink to="/product-center">`
|
||||
- `AppHeader.vue` 的"产品中心"链接改为 `<NuxtLink to="/product-center">`
|
||||
|
||||
---
|
||||
|
||||
## 跨项目配置
|
||||
|
||||
### 环境变量
|
||||
|
||||
**inkreach-official-nestjs/.env**:
|
||||
- DATABASE_URL=postgresql://postgres:yoyoki219765.@localhost:5432/inkreach-official
|
||||
- JWT_SECRET=(generate-secure-secret)
|
||||
- SDS_API_BASE=https://mapi.sdspod.com
|
||||
- PORT=3001
|
||||
|
||||
**inkreach-official-admin/.env**:
|
||||
- VITE_API_BASE=http://localhost:3001
|
||||
|
||||
**inkreach-official-website**:
|
||||
- NUXT_PUBLIC_BACKEND_URL=http://localhost:3001
|
||||
|
||||
### 端口分配
|
||||
|
||||
| 项目 | 端口 |
|
||||
|------|------|
|
||||
| NestJS Backend | 3001 |
|
||||
| Admin (Vite dev) | 5173 |
|
||||
| Website (Nuxt dev) | 3000 |
|
||||
# 产品中心 (Product Center) 设计规格
|
||||
|
||||
## 概述
|
||||
|
||||
为 InkReach 官网新增"产品中心"二级页面,包含三个子系统:
|
||||
1. **NestJS 后端服务** — Prisma + PostgreSQL,RESTful API,SDS 定时同步
|
||||
2. **Vue 3 Admin 后台** — Element Plus,商品/品类/国家/标签/位置管理
|
||||
3. **Nuxt 4 官网页面** — 产品中心二级页,带侧边栏分类、国家筛选、搜索、分页
|
||||
|
||||
## 架构
|
||||
|
||||
- NestJS 监听 `:3001`,提供 RESTful API
|
||||
- Admin 直连 NestJS API(开发环境 Vite proxy 解决跨域)
|
||||
- Website 通过 Nitro server route 代理 NestJS API
|
||||
|
||||
## 实施顺序
|
||||
|
||||
后端服务 → Admin 后台 → 官网产品中心页
|
||||
|
||||
---
|
||||
|
||||
## 1. NestJS 后端服务
|
||||
|
||||
### 技术栈
|
||||
|
||||
| 技术 | 版本 | 用途 |
|
||||
|------|------|------|
|
||||
| NestJS | 10+ | 后端框架 |
|
||||
| Prisma | 6+ | ORM |
|
||||
| PostgreSQL | 16+ | 数据库 |
|
||||
| @nestjs/schedule | - | 定时同步 SDS |
|
||||
| passport + jwt | - | JWT 认证 |
|
||||
| class-validator | - | DTO 验证 |
|
||||
|
||||
### 项目结构
|
||||
|
||||
```
|
||||
inkreach-official-nestjs/
|
||||
├── prisma/
|
||||
│ ├── schema.prisma
|
||||
│ └── migrations/
|
||||
├── src/
|
||||
│ ├── main.ts
|
||||
│ ├── app.module.ts
|
||||
│ ├── auth/
|
||||
│ │ ├── auth.module.ts
|
||||
│ │ ├── auth.controller.ts
|
||||
│ │ ├── auth.service.ts
|
||||
│ │ ├── jwt.strategy.ts
|
||||
│ │ └── dto/
|
||||
│ ├── goods/
|
||||
│ │ ├── goods.module.ts
|
||||
│ │ ├── goods.controller.ts
|
||||
│ │ ├── goods.service.ts
|
||||
│ │ └── dto/
|
||||
│ ├── categories/
|
||||
│ ├── countries/
|
||||
│ ├── tags/
|
||||
│ ├── positions/
|
||||
│ ├── origin-goods/
|
||||
│ └── sync/
|
||||
│ ├── sync.module.ts
|
||||
│ ├── sync.service.ts
|
||||
│ └── sync.controller.ts
|
||||
├── test/
|
||||
├── .env
|
||||
├── nest-cli.json
|
||||
├── tsconfig.json
|
||||
└── package.json
|
||||
```
|
||||
|
||||
### Prisma Schema
|
||||
|
||||
将 `docs/dev/database-table-design.md` 的 DDL 转换为 Prisma schema,6 个核心模型:`OriginGoods`, `Country`, `Category`, `Tag`, `Position`, `Good`。
|
||||
|
||||
关系映射:
|
||||
- `Good` → `OriginGoods` (多对一, RESTRICT)
|
||||
- `Good` → `Country` (多对一, RESTRICT)
|
||||
- `Good` → `Category` (多对一, RESTRICT)
|
||||
- `Good` → `Tag` (多对一, SET NULL)
|
||||
- `Good` → `Position` (多对一, SET NULL)
|
||||
- `Category` 自关联 (parent_category_id)
|
||||
- `Position` → `Country` (多对一, CASCADE)
|
||||
- `Position` → `Category` (多对一, CASCADE)
|
||||
|
||||
### API 设计
|
||||
|
||||
#### 认证
|
||||
|
||||
| 方法 | 路径 | 描述 |
|
||||
|------|------|------|
|
||||
| POST | `/auth/login` | 管理员登录,返回 JWT |
|
||||
| POST | `/auth/register` | 注册(仅初始化用) |
|
||||
|
||||
#### 商品管理 (Admin, 需认证)
|
||||
|
||||
| 方法 | 路径 | 描述 |
|
||||
|------|------|------|
|
||||
| GET | `/goods` | 分页查询商品(支持 country/category/tag/position/keyword 筛选) |
|
||||
| GET | `/goods/:id` | 商品详情 |
|
||||
| POST | `/goods` | 创建商品 |
|
||||
| PATCH | `/goods/:id` | 更新商品 |
|
||||
| DELETE | `/goods/:id` | 删除商品 |
|
||||
| PATCH | `/goods/batch-priority` | 批量更新优先级 |
|
||||
| POST | `/goods/batch` | 批量创建商品(从 origin_goods 选取) |
|
||||
|
||||
#### 品类管理 (Admin)
|
||||
|
||||
| 方法 | 路径 | 描述 |
|
||||
|------|------|------|
|
||||
| GET | `/categories` | 获取品类树 |
|
||||
| GET | `/categories/:id` | 品类详情 |
|
||||
| POST | `/categories` | 创建品类 |
|
||||
| PATCH | `/categories/:id` | 更新品类 |
|
||||
| DELETE | `/categories/:id` | 删除品类 |
|
||||
|
||||
#### 国家管理 (Admin)
|
||||
|
||||
| 方法 | 路径 | 描述 |
|
||||
|------|------|------|
|
||||
| GET | `/countries` | 国家列表 |
|
||||
| POST | `/countries` | 创建国家 |
|
||||
| PATCH | `/countries/:id` | 更新国家 |
|
||||
| DELETE | `/countries/:id` | 删除国家 |
|
||||
|
||||
#### 标签管理 (Admin)
|
||||
|
||||
| 方法 | 路径 | 描述 |
|
||||
|------|------|------|
|
||||
| GET | `/tags` | 标签列表 |
|
||||
| POST | `/tags` | 创建标签 |
|
||||
| PATCH | `/tags/:id` | 更新标签 |
|
||||
| DELETE | `/tags/:id` | 删除标签 |
|
||||
|
||||
#### 位置管理 (Admin)
|
||||
|
||||
| 方法 | 路径 | 描述 |
|
||||
|------|------|------|
|
||||
| GET | `/positions` | 位置列表 |
|
||||
| POST | `/positions` | 创建位置 |
|
||||
| PATCH | `/positions/:id` | 更新位置 |
|
||||
| DELETE | `/positions/:id` | 删除位置 |
|
||||
|
||||
#### 原商品管理 (Admin)
|
||||
|
||||
| 方法 | 路径 | 描述 |
|
||||
|------|------|------|
|
||||
| GET | `/origin-goods` | 原商品列表(来自 SDS 同步) |
|
||||
|
||||
#### 同步 (Admin)
|
||||
|
||||
| 方法 | 路径 | 描述 |
|
||||
|------|------|------|
|
||||
| POST | `/sync/categories` | 手动触发品类同步 |
|
||||
| POST | `/sync/products` | 手动触发产品同步 |
|
||||
| GET | `/sync/status` | 获取最近同步状态 |
|
||||
|
||||
#### 公开 API (Website, 无需认证)
|
||||
|
||||
| 方法 | 路径 | 描述 |
|
||||
|------|------|------|
|
||||
| GET | `/public/categories` | 品类树(仅含已配置商品的品类) |
|
||||
| GET | `/public/countries` | 国家列表(仅含已配置商品的国家) |
|
||||
| GET | `/public/goods` | 分页查询商品(支持 country/category/tag/keyword 筛选) |
|
||||
| GET | `/public/goods/:id` | 商品详情 |
|
||||
|
||||
### SDS 同步逻辑
|
||||
|
||||
1. **品类同步**: 调用 `https://mapi.sdspod.com/category/tree/3`,将返回的树形结构扁平化写入 `categories` 表
|
||||
2. **产品同步**: 按 SDS 分类遍历产品,写入 `origin_goods` 表
|
||||
3. **定时策略**: 每小时自动同步一次 (`@Cron('0 * * * *')`)
|
||||
4. **增量策略**: 对比现有记录,存在则跳过,不存在则插入
|
||||
|
||||
### 商品查询逻辑 (公开 API)
|
||||
|
||||
排序: `good_priority DESC → position.index_val ASC → created_at DESC`
|
||||
|
||||
筛选参数:
|
||||
- `countryId` — 按国家筛选
|
||||
- `categoryId` — 按品类筛选(含子品类)
|
||||
- `tagId` — 按标签筛选
|
||||
- `keyword` — 按商品名模糊搜索
|
||||
- `page` / `pageSize` — 分页
|
||||
|
||||
---
|
||||
|
||||
## 2. Admin 后台
|
||||
|
||||
### 技术栈
|
||||
|
||||
| 技术 | 版本 | 用途 |
|
||||
|------|------|------|
|
||||
| Vue | 3.5+ | UI 框架 |
|
||||
| Vite | 6+ | 构建工具 |
|
||||
| Element Plus | 2.9+ | UI 组件库 |
|
||||
| Vue Router | 4+ | 路由 |
|
||||
| Pinia | 3+ | 状态管理 |
|
||||
| Axios | 1.7+ | HTTP 客户端 |
|
||||
| TypeScript | 5+ | 类型安全 |
|
||||
|
||||
### 项目结构
|
||||
|
||||
```
|
||||
inkreach-official-admin/
|
||||
├── src/
|
||||
│ ├── main.ts
|
||||
│ ├── App.vue
|
||||
│ ├── router/index.ts
|
||||
│ ├── stores/
|
||||
│ │ ├── auth.ts
|
||||
│ │ └── app.ts
|
||||
│ ├── api/
|
||||
│ │ ├── request.ts
|
||||
│ │ ├── auth.ts
|
||||
│ │ ├── goods.ts
|
||||
│ │ ├── categories.ts
|
||||
│ │ ├── countries.ts
|
||||
│ │ ├── tags.ts
|
||||
│ │ ├── positions.ts
|
||||
│ │ ├── origin-goods.ts
|
||||
│ │ └── sync.ts
|
||||
│ ├── layouts/
|
||||
│ │ └── DefaultLayout.vue
|
||||
│ ├── views/
|
||||
│ │ ├── login/LoginView.vue
|
||||
│ │ ├── goods/GoodsView.vue
|
||||
│ │ ├── categories/CategoriesView.vue
|
||||
│ │ ├── countries/CountriesView.vue
|
||||
│ │ ├── tags/TagsView.vue
|
||||
│ │ ├── positions/PositionsView.vue
|
||||
│ │ └── sync/SyncView.vue
|
||||
│ ├── components/
|
||||
│ └── types/index.ts
|
||||
├── .env
|
||||
├── vite.config.ts
|
||||
├── tsconfig.json
|
||||
└── package.json
|
||||
```
|
||||
|
||||
### 核心页面功能
|
||||
|
||||
**商品管理 (GoodsView)**:
|
||||
- 表格展示:商品名、国家、品类、标签、位置、优先级
|
||||
- 筛选:按国家/品类/标签筛选
|
||||
- 操作:新增、编辑、删除、调整优先级
|
||||
- 批量添加:从 origin_goods 选择商品,批量配置国家/品类/标签/优先级
|
||||
|
||||
**品类管理 (CategoriesView)**:
|
||||
- 树形表格展示层级关系
|
||||
- 新增/编辑/删除品类
|
||||
|
||||
**同步管理 (SyncView)**:
|
||||
- 显示最近同步时间、同步状态
|
||||
- 手动触发品类同步/产品同步按钮
|
||||
|
||||
---
|
||||
|
||||
## 3. 官网产品中心页
|
||||
|
||||
### 页面结构
|
||||
|
||||
左右两栏布局:左侧固定宽度侧边栏(品类导航),右侧主内容区(国家筛选 + 筛选栏 + 商品网格 + 分页)。
|
||||
|
||||
### 组件拆分
|
||||
|
||||
```
|
||||
app/pages/product-center.vue # 页面入口
|
||||
app/components/product/
|
||||
├── ProductSidebar.vue # 侧边栏品类导航
|
||||
├── ProductCountryFilter.vue # 国家标签栏
|
||||
├── ProductFilterBar.vue # 筛选下拉 + 已选标签 + 搜索
|
||||
├── ProductGrid.vue # 商品卡片网格
|
||||
├── ProductCard.vue # 单个商品卡片
|
||||
├── ProductCardSkeleton.vue # 骨架屏卡片
|
||||
└── ProductPagination.vue # 分页控件
|
||||
app/composables/
|
||||
└── useProductCenter.ts # 产品中心数据逻辑
|
||||
```
|
||||
|
||||
### 商品卡片设计
|
||||
|
||||
- 商品图片 (1:1 方形)
|
||||
- 商品名 (单行截断)
|
||||
- 标签行 (pill 样式,颜色体系:绿色=包邮,橙色=工艺,灰色=其他)
|
||||
- 价格 (粗体,"¥XX.XX起")
|
||||
|
||||
### 侧边栏品类导航
|
||||
|
||||
- 顶级品类带图标 + 折叠箭头
|
||||
- 展开子品类列表,左侧橙色竖条标识当前选中
|
||||
- 选中子品类背景 `bg-inkreach-homepage-3` + 橙色文字
|
||||
|
||||
### 国家标签栏
|
||||
|
||||
- Pill 形状按钮,每个带国旗图标
|
||||
- "全部" 为默认选中,橙色边框 + 橙色文字
|
||||
- 未选中为灰色边框
|
||||
|
||||
### 搜索
|
||||
|
||||
- 分体式搜索框:左侧输入框 + 右侧橙色"搜索"按钮
|
||||
- 支持按商品名搜索
|
||||
|
||||
### 分页
|
||||
|
||||
- 左侧:"总计 N 个产品"
|
||||
- 中间:页码按钮(方形)
|
||||
- 右侧:每页条数下拉 + 跳转输入框
|
||||
|
||||
### 骨架屏
|
||||
|
||||
数据加载时显示骨架屏:
|
||||
- 卡片区域显示 ProductCardSkeleton (4x3 网格)
|
||||
- 分页隐藏
|
||||
- 侧边栏和筛选区正常显示
|
||||
|
||||
### Nitro API 代理
|
||||
|
||||
```
|
||||
server/api/backend/categories.get.ts -> GET :3001/public/categories
|
||||
server/api/backend/countries.get.ts -> GET :3001/public/countries
|
||||
server/api/backend/goods.get.ts -> GET :3001/public/goods
|
||||
server/api/backend/goods/[id].get.ts -> GET :3001/public/goods/:id
|
||||
```
|
||||
|
||||
### 数据流
|
||||
|
||||
1. 页面加载 → `/api/backend/categories` → 渲染侧边栏
|
||||
2. 页面加载 → `/api/backend/countries` → 渲染国家标签栏
|
||||
3. 筛选/分页变化 → `/api/backend/goods?countryId=&categoryId=&keyword=&page=&pageSize=` → 渲染商品网格
|
||||
|
||||
### 关联改动
|
||||
|
||||
- 首页 `PodProducts.vue` 的"更多产品"按钮改为 `<NuxtLink to="/product-center">`
|
||||
- `AppHeader.vue` 的"产品中心"链接改为 `<NuxtLink to="/product-center">`
|
||||
|
||||
---
|
||||
|
||||
## 跨项目配置
|
||||
|
||||
### 环境变量
|
||||
|
||||
**inkreach-official-nestjs/.env**:
|
||||
- DATABASE_URL=postgresql://postgres:yoyoki219765.@localhost:5432/inkreach-official
|
||||
- JWT_SECRET=(generate-secure-secret)
|
||||
- SDS_API_BASE=https://mapi.sdspod.com
|
||||
- PORT=3001
|
||||
|
||||
**inkreach-official-admin/.env**:
|
||||
- VITE_API_BASE=http://localhost:3001
|
||||
|
||||
**inkreach-official-website**:
|
||||
- NUXT_PUBLIC_BACKEND_URL=http://localhost:3001
|
||||
|
||||
### 端口分配
|
||||
|
||||
| 项目 | 端口 |
|
||||
|------|------|
|
||||
| NestJS Backend | 3001 |
|
||||
| Admin (Vite dev) | 5173 |
|
||||
| Website (Nuxt dev) | 3000 |
|
||||
|
||||
@@ -1,92 +1,92 @@
|
||||
# Goods Management Redesign
|
||||
|
||||
## Date: 2026-06-21
|
||||
|
||||
## Problem
|
||||
|
||||
1. **Origin Good name not displayed**: GoodsView doesn't show the origin product name/image/price
|
||||
2. **Single tag per good**: Current schema only allows one tag per good via `goods.tag_id` FK
|
||||
3. **Easy to miss unconfigured origin products**: Flat paginated table with no visibility into which origin products have been configured vs not
|
||||
|
||||
## Solution
|
||||
|
||||
### 1. Multi-Tag Support (GoodTag Junction Table)
|
||||
|
||||
**Schema change:**
|
||||
- New `good_tags` junction table: `(good_id, tag_id)` composite PK
|
||||
- Migrate existing `goods.tag_id` data into `good_tags`
|
||||
- Set `goods.tag_id` to nullable (will be dropped in a future migration)
|
||||
|
||||
**Backend:**
|
||||
- `CreateGoodDto`: `tagIds: number[]` (replaces `tagId`)
|
||||
- `UpdateGoodDto`: `tagIds?: number[]` (replaces `tagId`)
|
||||
- `BatchCreateGoodDto`: `tagIds?: number[]` (replaces `tagId`)
|
||||
- `QueryGoodDto`: `tagId` filter queries `tags.some` instead of direct FK
|
||||
- `GoodsService.create/update`: manage `good_tags` records in transaction
|
||||
- `GOOD_INCLUDE`: include `tags: { include: { tag: true } }`
|
||||
|
||||
### 2. Origin Goods Tree API
|
||||
|
||||
New endpoint: `GET /origin-goods/tree`
|
||||
|
||||
Returns origin goods grouped by SDS category, with configuration status:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"sdsCategoryId": "4185",
|
||||
"categoryName": "(包邮)180g纯棉T恤-单面印花",
|
||||
"originGoods": [
|
||||
{
|
||||
"id": "123",
|
||||
"goodName": "产品A",
|
||||
"goodImage": "https://...",
|
||||
"goodPrice": "12.50",
|
||||
"sdsGoodId": "p-123",
|
||||
"configuredCount": 2,
|
||||
"configuredCountries": ["美国", "英国"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Origin goods without `sdsCategoryId` are grouped under "未分类".
|
||||
|
||||
### 3. Admin UI: Dual Tree Layout
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ [搜索原产品] [210/439 已配置] │
|
||||
├───────────────────┬─────────────────────────────────────┤
|
||||
│ 首页分类树 │ 原产品分类树 │
|
||||
│ │ │
|
||||
│ ▼ 美国 │ ▼ (包邮)180g纯棉T恤 │
|
||||
│ ▼ T恤 │ ✓ 产品A [美国,英国] │
|
||||
│ ▼ 卫衣 │ ✗ 产品B [未配置] │
|
||||
│ ▼ 英国 │ ▼ 未分类 │
|
||||
│ ▼ 韩国 │ ✗ 产品C [未配置] │
|
||||
├───────────────────┴─────────────────────────────────────┤
|
||||
│ 详情/操作区域 │
|
||||
│ - 选左侧分类: 该分类下所有 goods 表格 │
|
||||
│ - 选右侧原产品: 该原产品的配置详情 + 快捷配置按钮 │
|
||||
│ - 多标签选择 (el-select multiple) │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Key interactions:**
|
||||
- Left tree click: filter goods by selected category
|
||||
- Right tree click: show origin good detail + all its goods configs
|
||||
- Right tree badges: ✓ configured / ✗ unconfigured
|
||||
- Quick configure: select country+category+tags, click origin goods to batch create
|
||||
- Statistics bar: configured/total count
|
||||
|
||||
### 4. Data Migration
|
||||
|
||||
```sql
|
||||
-- Step 1: Create good_tags table
|
||||
-- Step 2: Copy existing tag_id data
|
||||
INSERT INTO good_tags (good_id, tag_id, created_at)
|
||||
SELECT good_id, tag_id, NOW() FROM goods WHERE tag_id IS NOT NULL;
|
||||
-- Step 3: Good.tag_id becomes nullable (already nullable)
|
||||
```
|
||||
# Goods Management Redesign
|
||||
|
||||
## Date: 2026-06-21
|
||||
|
||||
## Problem
|
||||
|
||||
1. **Origin Good name not displayed**: GoodsView doesn't show the origin product name/image/price
|
||||
2. **Single tag per good**: Current schema only allows one tag per good via `goods.tag_id` FK
|
||||
3. **Easy to miss unconfigured origin products**: Flat paginated table with no visibility into which origin products have been configured vs not
|
||||
|
||||
## Solution
|
||||
|
||||
### 1. Multi-Tag Support (GoodTag Junction Table)
|
||||
|
||||
**Schema change:**
|
||||
- New `good_tags` junction table: `(good_id, tag_id)` composite PK
|
||||
- Migrate existing `goods.tag_id` data into `good_tags`
|
||||
- Set `goods.tag_id` to nullable (will be dropped in a future migration)
|
||||
|
||||
**Backend:**
|
||||
- `CreateGoodDto`: `tagIds: number[]` (replaces `tagId`)
|
||||
- `UpdateGoodDto`: `tagIds?: number[]` (replaces `tagId`)
|
||||
- `BatchCreateGoodDto`: `tagIds?: number[]` (replaces `tagId`)
|
||||
- `QueryGoodDto`: `tagId` filter queries `tags.some` instead of direct FK
|
||||
- `GoodsService.create/update`: manage `good_tags` records in transaction
|
||||
- `GOOD_INCLUDE`: include `tags: { include: { tag: true } }`
|
||||
|
||||
### 2. Origin Goods Tree API
|
||||
|
||||
New endpoint: `GET /origin-goods/tree`
|
||||
|
||||
Returns origin goods grouped by SDS category, with configuration status:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"sdsCategoryId": "4185",
|
||||
"categoryName": "(包邮)180g纯棉T恤-单面印花",
|
||||
"originGoods": [
|
||||
{
|
||||
"id": "123",
|
||||
"goodName": "产品A",
|
||||
"goodImage": "https://...",
|
||||
"goodPrice": "12.50",
|
||||
"sdsGoodId": "p-123",
|
||||
"configuredCount": 2,
|
||||
"configuredCountries": ["美国", "英国"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Origin goods without `sdsCategoryId` are grouped under "未分类".
|
||||
|
||||
### 3. Admin UI: Dual Tree Layout
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ [搜索原产品] [210/439 已配置] │
|
||||
├───────────────────┬─────────────────────────────────────┤
|
||||
│ 首页分类树 │ 原产品分类树 │
|
||||
│ │ │
|
||||
│ ▼ 美国 │ ▼ (包邮)180g纯棉T恤 │
|
||||
│ ▼ T恤 │ ✓ 产品A [美国,英国] │
|
||||
│ ▼ 卫衣 │ ✗ 产品B [未配置] │
|
||||
│ ▼ 英国 │ ▼ 未分类 │
|
||||
│ ▼ 韩国 │ ✗ 产品C [未配置] │
|
||||
├───────────────────┴─────────────────────────────────────┤
|
||||
│ 详情/操作区域 │
|
||||
│ - 选左侧分类: 该分类下所有 goods 表格 │
|
||||
│ - 选右侧原产品: 该原产品的配置详情 + 快捷配置按钮 │
|
||||
│ - 多标签选择 (el-select multiple) │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Key interactions:**
|
||||
- Left tree click: filter goods by selected category
|
||||
- Right tree click: show origin good detail + all its goods configs
|
||||
- Right tree badges: ✓ configured / ✗ unconfigured
|
||||
- Quick configure: select country+category+tags, click origin goods to batch create
|
||||
- Statistics bar: configured/total count
|
||||
|
||||
### 4. Data Migration
|
||||
|
||||
```sql
|
||||
-- Step 1: Create good_tags table
|
||||
-- Step 2: Copy existing tag_id data
|
||||
INSERT INTO good_tags (good_id, tag_id, created_at)
|
||||
SELECT good_id, tag_id, NOW() FROM goods WHERE tag_id IS NOT NULL;
|
||||
-- Step 3: Good.tag_id becomes nullable (already nullable)
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user