Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/developer-guide/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@
"label": "主题开发",
"collapsed": true
},
{
"type": "dir",
"name": "shop",
"label": "商城开发",
"collapsed": true
},
{
"type": "dir",
"name": "restful-api",
Expand Down
2 changes: 1 addition & 1 deletion docs/developer-guide/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: 开发者指南
description: Halo 开发者指南入口,涵盖核心系统、插件与主题开发、RESTful API、应用市场发布,以及 FormKit 表单和模型元数据扩展。
description: Halo 开发者指南入口,涵盖核心系统、插件与主题开发、商城开发、RESTful API、应用市场发布,以及 FormKit 表单和模型元数据扩展。
overview: true
---
13 changes: 13 additions & 0 deletions docs/developer-guide/shop/_meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[
{
"type": "file",
"name": "index",
"label": "内容导航"
},
{
"type": "dir",
"name": "theme",
"label": "主题开发",
"collapsed": false
}
]
11 changes: 11 additions & 0 deletions docs/developer-guide/shop/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
title: 商城开发
description: Halo 商城版开发指南,涵盖商城主题适配、模板与数据、前台交互。
overview: true
---

:::note 仅限 Halo 商城版
本板块面向 [Halo 商城版](../../guide/prepare.md#发行版本)。主题开发文档以 **Halo 商城版 2.27** 为基线;使用其他版本时,请同时核对模板分支和对应版本的 API。
:::

商城主题沿用 Halo 的主题机制,可以复用内置商城模板,也可以自行实现商品展示和交易界面。从[主题开发概述](./theme/overview.md)开始,选择适合的开发方式。
13 changes: 13 additions & 0 deletions docs/developer-guide/shop/theme/_meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[
"overview",
"quick-start",
"template-reuse",
"pages-and-variables",
"finder-apis",
"components",
"styling",
"source-development",
"storefront-api",
"testing",
"api-changelog"
]
36 changes: 36 additions & 0 deletions docs/developer-guide/shop/theme/api-changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
title: 版本差异
description: Halo 商城主题开发相关的 Finder 返回结构、商品永久链接、订阅和游客交易流程版本说明。
---

本页记录与商城主题适配直接相关的变化。本系列代码示例以 **Halo 商城版 2.27** 为基线;完整接口模型见 [2.27 商城版 API](https://api.halo.run/?version=2.27-paid)。

## Halo 商城版 2.27

- 新增基于 handle 的商品详情入口 `/shop/products/{handle}`,商品响应提供 `permalink`。旧数字 ID 入口 `/shop/product/{id}` 在商品有 handle 时重定向到永久链接。
- `productFinder` 新增 `getByHandle(handle)`。
- 新增订阅商品和 `subscriptionFinder`。`/shop/subscription/{handle}` 复用 `shop/product.html`,内置 `shop-buy-box` 根据商品类型展示订阅计划。
- 前台交易流程支持启用游客结算后的会话购物车、结算、订单访问和访问邮件找回。主题应使用匹配版本的组件与接口,处理游客邮箱、访问凭据和订单锁定状态。
- 订单详情由 `shop/order.html` 渲染,需处理 `locked`、`order`、`fulfillments`、`canPay`、`canCancel` 等变量。

从旧版模板升级时,重点复查商品链接、购买组件、结算请求地址、订单权限恢复和支付状态处理。复制到主题中的模板不会随 Halo 升级自动更新。

## Halo 商城版 2.23.2

`productFinder.list(params)` 的返回值从 2.23.0、2.23.1 中的 `Page<ProductResponse>` 改为 `ListResult<ProductResponse>`:

- 商品集合由 `content` 改为 `items`。
- 返回页码由 `number`(从 0 开始)改为 `page`(从 1 开始)。
- 总条数由 `totalElements` 改为 `total`。

`productCategoryFinder.list(params)` 仍返回 `Page`,不要对分类列表应用同样的字段替换。

## Halo 商城版 2.23

提供 `productFinder.list(params)` 和 `productCategoryFinder.list(params)`,用于在主题中分页查询商品与分类。

## 选择对应源码

从 [shop-templates 分支列表](https://github.com/lxware-dev/shop-templates/branches)选择与目标商城版本对应的 release 分支。`main` 为开发分支,可能包含暂未发布的功能。

主题采用源码二次开发时,模板、UI、客户端类型和后端需要作为一组验证。通用 Halo 主题能力的变化另见[主题 API 变更记录](../../theme/api-changelog.md)。
85 changes: 85 additions & 0 deletions docs/developer-guide/shop/theme/components.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
title: 使用商城组件
description: 在主题中挂载 Halo 商城购买、购物车、结算和支付 Web Components,正确传递上下文与 CSRF 参数。
---

本页以 Halo 商城版 2.27 的内置组件为基线。组件使用 Svelte 的 [Custom Elements](https://svelte.dev/docs/svelte/custom-elements) 机制,可以直接嵌入 Thymeleaf 模板。

## 加载组件资源

复用内置组件时,在页面中引入一次:

```html
<th:block th:replace="~{shop/modules/style}" />
<th:block th:replace="~{shop/modules/script}" />
```

这会加载 Halo 提供的 `/shop-dist/style.css` 和 `/shop-dist/main.iife.js`,内置引用附带 `site.version` 用于区分版本。

如果使用了[源码二次开发](./source-development.md)构建的组件,应改用主题自己的资源入口,避免重复注册相同标签。

## 组件与参数

下表使用 HTML 属性名。模板中的 `th:csrfToken` 最终对应 `csrftoken`,不要改写成 `csrf-token`。

| 标签 | 属性 | 用途 |
| -------------------- | ----------------------------------------- | ---------------------------- |
| `shop-buy-box` | `id`、`csrftoken` | 商品购买区域;`id` 为商品 ID |
| `shop-page-cart` | `csrftoken` | 当前顾客或游客会话的购物车 |
| `shop-page-checkout` | `contextid`、`csrftoken`、`authenticated` | 结算页 |
| `shop-page-payments` | `ordercode`、`csrftoken`、`authenticated` | 支付页 |

`authenticated` 是字符串属性,组件以字符串 `'true'` 判断登录状态。应传入服务端变量,不要仅写一个没有值的 `authenticated` 属性。

### 商品购买区域

```html
<shop-buy-box
th:id="${product.id}"
th:csrfToken="${_csrf.token}"
></shop-buy-box>
```

组件查询商品详情和 SKU 后处理规格选择、价格、数量、库存与购买入口。外链商品展示外部跳转;询价商品使用店铺的询价地址或联系方式。

2.27 中,订阅商品同样使用 `shop-buy-box`。组件内部查询订阅计划并展示档位、周期、试用和当前订阅状态;`SubscriptionBuyBox` 是内部组件,不是一个可直接挂载的独立 HTML 标签。

### 购物车

```html
<shop-page-cart th:csrfToken="${_csrf.token}"></shop-page-cart>
```

组件通过 `/shop/cart/items` 获取当前会话购物车。游客使用需启用游客结算;不要在模板中自行拼装其他客户的购物车数据。

### 结算页

```html
<shop-page-checkout
th:contextId="${context.id}"
th:csrfToken="${_csrf.token}"
th:authenticated="${authenticated}"
></shop-page-checkout>
```

该标签放在 `/shop/checkout/{contextId}` 对应的页面中。组件读取结算上下文,处理收货地址、备注、折扣和优惠券等交互。游客流程不使用登录用户的地址簿或客户优惠券。

Halo 商城版 2.27 尚未实现配送方式选择与运费计算,填写收货地址不代表支持这两项能力。

### 支付页

```html
<shop-page-payments
th:orderCode="${orderCode}"
th:csrfToken="${_csrf.token}"
th:authenticated="${authenticated}"
></shop-page-payments>
```

组件查询订单、选择支付方式、创建支付会话并检查支付状态。游客访问凭据失效时,提供邮箱找回访问链接的入口。

## 样式与语言

内置组件关闭 Shadow DOM,因此主题 CSS 可以影响其内部元素。优先通过 `--shop-*` CSS 变量调整外观,具体见[定制样式](./styling.md)。

内置 UI 支持 `en`、`es`、`zh-CN`、`zh-TW`。语言优先取 `language` Cookie,再取浏览器语言,未匹配时回退到英文。服务端模板文案与组件文案分别维护;只修改 `site.language` 或 `<html lang>` 并不会替换全部 UI 文案。定制组件文案需要修改对应源码语言文件并重新构建。
154 changes: 154 additions & 0 deletions docs/developer-guide/shop/theme/finder-apis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
---
title: Finder API
description: 在 Thymeleaf 中查询商城商品、分类和订阅计划,了解查询参数、返回模型与分页差异。
---

商城 Finder 用于在首页、侧边栏或自定义页面中读取商品数据,不需要浏览器额外发送请求。以下示例以 Halo 商城版 2.27 为基线。

## productFinder.list(params)

商品 Finder 自 Halo 商城版 2.23 起提供,**2.23.2 起返回 `ListResult`**;2.23.0 和 2.23.1 返回 `Page`,字段差异见[版本差异](./api-changelog.md#halo-商城版-2232)。2.27 支持下表中的参数。

### 参数

`params` 是一个 Map,支持:

| 参数 | 类型 | 默认值与说明 |
| ---------------------- | ---------- | ------------------------------------------ |
| `page` | 整数 | 默认 1,从 1 开始 |
| `size` | 整数 | 默认 10,有效范围 1–999;超出范围回退到 10 |
| `sort` | 字符串列表 | 如 `{'id,desc'}`,未设置时不指定排序 |
| `keyword` | 字符串 | 商品标题关键词 |
| `categoryId` | 长整数 | 分类 ID,正数 |
| `minPrice`、`maxPrice` | 数值 | 价格筛选边界 |

Finder 的每页数量限制与 `/shop/products` HTTP 路由的最大 100 条不同,不要混用。

### 返回值

返回 `ListResult<ProductResponse>`,只包含已发布商品。用 `items` 遍历,`page` 为从 1 开始的页码,`total` 为总条数。

```html
<section
th:with="products = ${productFinder.list({page: 1, size: 6, sort: {'id,desc'}})}"
>
<article th:each="product : ${products.items}">
<a th:href="@{${product.permalink}}">
<img
th:if="${!#strings.isEmpty(product.coverImageUrl)}"
th:src="${product.coverImageUrl}"
th:alt="${product.title}"
loading="lazy"
/>
<span th:text="${product.title}"></span>
</a>
</article>
<p th:if="${products.items.isEmpty()}">暂无商品</p>
</section>
```

列表返回用于商品卡片的摘要数据。商品字段说明见[商品详情](./pages-and-variables.md#商品详情)。

## productFinder.getByHandle(handle)

**自 Halo 商城版 2.27 起可用。**

按字符串 `handle` 查询单个已发布商品,返回包含正文、图库和 SKU 的 `ProductResponse`。商品不存在、未发布或 handle 为空时无结果,模板应处理空值。

```html
<th:block th:with="product = ${productFinder.getByHandle('example-product')}">
<article th:if="${product != null}">
<h2 th:text="${product.title}"></h2>
<a th:href="@{${product.permalink}}">查看商品</a>
</article>
</th:block>
```

## productCategoryFinder.list(params)

**自 Halo 商城版 2.23 起可用。** 2.27 中仍返回 `Page<ProductCategoryResponse>`。

### 参数

支持 `page`、`size`、`sort`,默认值与商品 Finder 相同;不支持商品关键词、价格或 `categoryId` 筛选。

### 返回值

分类分页与商品分页使用不同字段:

| 含义 | 商品 `ListResult` | 分类 `Page` |
| ---------- | ----------------- | ------------------- |
| 当前页数据 | `items` | `content` |
| 当前页码 | `page`,从 1 开始 | `number`,从 0 开始 |
| 每页数量 | `size` | `size` |
| 总条数 | `total` | `totalElements` |
| 总页数 | `totalPages` | `totalPages` |

两者的**输入参数 `page` 都从 1 开始**。分类 Finder 返回平铺分页,不等同于商品列表页的 `categoryTree`。

```html
<nav
th:with="categories = ${productCategoryFinder.list({page: 1, size: 20, sort: {'name,asc'}})}"
>
<a
th:each="category : ${categories.content}"
th:href="@{/shop/products(categoryId=${category.id})}"
th:text="${category.name}"
></a>
</nav>
```

## subscriptionFinder.subscription(handle, customerId)

**自 Halo 商城版 2.27 起可用。**

查询已发布订阅商品的渲染数据;普通商品、未发布商品或不存在的 handle 无结果。

- `handle`:订阅商品的 handle。
- `customerId`:商城客户的数字 ID,传 `null` 时仅查询公开计划信息。

`customerId` 不是 Halo 用户名,也不会自动取当前登录用户。主题展示公开订阅计划时传 `null` 即可;展示当前用户的订阅时,优先使用购买组件或登录态订阅商品接口,不要从查询参数读取客户 ID 传给 Finder。

### 返回值

| 字段 | 含义 |
| --------------------- | ---------------------------------------------------- |
| `product` | 商品详情 |
| `tiers` | 有已发布计划的档位列表 |
| `currentSubscription` | 指定客户的当前订阅;未传客户 ID 时为空 |
| `subscriptions` | 指定客户在该商品下的订阅列表;未传客户 ID 时为空列表 |
| `singleSubscription` | 该商品是否采用单订阅策略 |

`tiers` 中每项包含 `id`、`name`、`handle`、`sortOrder`、`entitlements` 和 `plans`。计划包含 `id`、`variantId`、`name`、`price`、`billingMode`、`billingPeriod`、`periodCount`、试用与数量限制等信息。完整模型可查阅 [2.27 商城版 API](https://api.halo.run/?version=2.27-paid) 中的 `SubscriptionPlanResponse`。

```html
<th:block
th:with="subscription = ${subscriptionFinder.subscription('membership', null)}"
>
<section th:if="${subscription != null}">
<h2 th:text="${subscription.product.title}"></h2>
<article th:each="tier : ${subscription.tiers}">
<h3 th:text="${tier.name}"></h3>
<ul>
<li th:each="plan : ${tier.plans}">
<span th:text="${plan.name}"></span>
<span th:text="|¥${#numbers.formatDecimal(plan.price, 1, 2)}|"></span>
</li>
</ul>
</article>
<a th:href="@{${subscription.product.permalink}}">选择订阅计划</a>
</section>
</th:block>
```

上例只展示公开计划价格。试用资格、续费、升级补差价和数量限制应交给商城交易流程处理。

## subscriptionFinder.list(params)

**自 Halo 商城版 2.27 起可用。** 支持 `page`、`size`,默认分别为 1 和 10,`size` 有效范围为 1–999。返回 `ListResult<SubscriptionProductRenderModel>`,每项结构与上节相同,且不包含客户订阅数据。

:::warning 2.27 的分页限制
此方法先获取一页已发布商品,再筛选其中的订阅商品。返回数量可能少于 `size`,`total` 为该页筛选后的数量,不能用来计算全站订阅商品总数或可靠的连续分页。如果需要展示确定的订阅商品入口,优先逐个按 handle 查询。
:::

它不支持商品 Finder 的关键词、价格、分类与排序参数。
5 changes: 5 additions & 0 deletions docs/developer-guide/shop/theme/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: 商城主题开发
description: Halo 商城主题开发文档索引,包含模板适配、页面变量、Finder API、Web Components、样式和交易流程。
overview: true
---
42 changes: 42 additions & 0 deletions docs/developer-guide/shop/theme/overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
title: 开发概述
description: 了解 Halo 商城主题的渲染方式、模板回退机制、开发路径与版本兼容要求。
---

商城主题是普通 Halo 主题的一部分。你可以在现有主题的 `templates/shop/` 中添加商城页面,也可以开发以商城为主的完整主题。主题负责商品展示和顾客交互,商品、订单、支付与履约由 Halo 商城版提供。

本系列以 **Halo 商城版 2.27** 为基线。首次开发主题时,请先了解[主题目录结构](../../theme/structure.md)、[主题配置](../../theme/config.md)和 [Thymeleaf](../../theme/thymeleaf.md)。

## 页面如何渲染

Halo 商城版内置了 [shop-templates](https://github.com/lxware-dev/shop-templates) 提供的商城模板。访问商城时,优先使用当前主题中的同名模板;主题未提供的模板继续使用内置版本。

内置模板采用两种方式组合渲染:

- **Thymeleaf**:在服务器端输出商品列表、商品详情正文和订单详情,便于展示内容和搜索引擎读取公开商品页面。
- **Web Components**:在浏览器中处理购买规格、购物车、结算和支付等交互。内置组件由 Svelte 编译,使用这些组件的主题不需要采用 Svelte。

商品详情同时包含服务端商品内容和浏览器端购买组件。订单详情是 Thymeleaf 页面,不需要寻找一个 `shop-page-order` 组件。

## 选择开发方式

| 需求 | 方式 | 需要维护的内容 |
| ---------------------------------- | ---------------------------------------------------------------- | ------------------------------- |
| 只调整颜色、圆角或内容宽度 | [定制样式](./styling.md) | CSS 变量和少量样式覆盖 |
| 加入主题导航、页脚,统一页面布局 | [复用模板](./template-reuse.md) | 页面外壳和被覆盖的模块 |
| 自定义商品列表或详情,保留交易交互 | [页面与变量](./pages-and-variables.md) + [组件](./components.md) | 自定义 HTML、数据展示及组件挂载 |
| 大幅调整交易页面结构与交互 | [源码二次开发](./source-development.md) | UI 源码、构建产物及 API 兼容性 |
| 使用其他前端框架实现商城 | [前台接口与交易流程](./storefront-api.md) | 完整交易流程与错误处理 |

通常可以先复用内置模板,再逐页替换确有定制需求的部分。未覆盖的模块能够继续使用 Halo 升级带来的更新;复制进主题的模板和 UI 源码则需要自行同步。

## 版本与参考资料

`shop-templates` 的 `main` 是开发分支,可能包含 Halo 商城版暂未发布的功能。开发前从[分支列表](https://github.com/lxware-dev/shop-templates/branches)选择与目标 Halo 商城版对应的 release 分支,不要将最新版源码与旧版后端直接混用。

- [基础示例主题](https://github.com/lxware-dev/theme-shop-starter):复用内置模板。
- [二次开发示例主题](https://github.com/lxware-dev/theme-shop-advanced-starter):在主题中集成商城 UI 源码。
- [Halo 商城版 2.27 RESTful API](https://api.halo.run/?version=2.27-paid):查询请求字段、响应模型与接口分组。
- [版本差异](./api-changelog.md):查看本系列涉及的重要模板和 API 变化。

接下来按照[快速开始](./quick-start.md)完成第一个页面。
Loading
Loading