@trustbaseai/shop-ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +191 -0
- package/dist/base.d.ts +79 -0
- package/dist/base.d.ts.map +1 -0
- package/dist/base.js +149 -0
- package/dist/base.js.map +1 -0
- package/dist/components/account-panel.d.ts +44 -0
- package/dist/components/account-panel.d.ts.map +1 -0
- package/dist/components/account-panel.js +144 -0
- package/dist/components/account-panel.js.map +1 -0
- package/dist/components/goods-detail.d.ts +58 -0
- package/dist/components/goods-detail.d.ts.map +1 -0
- package/dist/components/goods-detail.js +232 -0
- package/dist/components/goods-detail.js.map +1 -0
- package/dist/components/goods-list.d.ts +43 -0
- package/dist/components/goods-list.d.ts.map +1 -0
- package/dist/components/goods-list.js +104 -0
- package/dist/components/goods-list.js.map +1 -0
- package/dist/components/order-form.d.ts +87 -0
- package/dist/components/order-form.d.ts.map +1 -0
- package/dist/components/order-form.js +222 -0
- package/dist/components/order-form.js.map +1 -0
- package/dist/esm/base.d.ts +79 -0
- package/dist/esm/base.d.ts.map +1 -0
- package/dist/esm/base.js +143 -0
- package/dist/esm/base.js.map +1 -0
- package/dist/esm/components/account-panel.d.ts +44 -0
- package/dist/esm/components/account-panel.d.ts.map +1 -0
- package/dist/esm/components/account-panel.js +140 -0
- package/dist/esm/components/account-panel.js.map +1 -0
- package/dist/esm/components/goods-detail.d.ts +58 -0
- package/dist/esm/components/goods-detail.d.ts.map +1 -0
- package/dist/esm/components/goods-detail.js +228 -0
- package/dist/esm/components/goods-detail.js.map +1 -0
- package/dist/esm/components/goods-list.d.ts +43 -0
- package/dist/esm/components/goods-list.d.ts.map +1 -0
- package/dist/esm/components/goods-list.js +100 -0
- package/dist/esm/components/goods-list.js.map +1 -0
- package/dist/esm/components/order-form.d.ts +87 -0
- package/dist/esm/components/order-form.d.ts.map +1 -0
- package/dist/esm/components/order-form.js +218 -0
- package/dist/esm/components/order-form.js.map +1 -0
- package/dist/esm/format.d.ts +39 -0
- package/dist/esm/format.d.ts.map +1 -0
- package/dist/esm/format.js +74 -0
- package/dist/esm/format.js.map +1 -0
- package/dist/esm/index.d.ts +44 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +44 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/register.d.ts +33 -0
- package/dist/esm/register.d.ts.map +1 -0
- package/dist/esm/register.js +44 -0
- package/dist/esm/register.js.map +1 -0
- package/dist/esm/tokens.d.ts +124 -0
- package/dist/esm/tokens.d.ts.map +1 -0
- package/dist/esm/tokens.js +184 -0
- package/dist/esm/tokens.js.map +1 -0
- package/dist/format.d.ts +39 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +84 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +84 -0
- package/dist/index.js.map +1 -0
- package/dist/register.d.ts +33 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +49 -0
- package/dist/register.js.map +1 -0
- package/dist/tokens.d.ts +124 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +188 -0
- package/dist/tokens.js.map +1 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# @trustbaseai/shop-ui
|
|
2
|
+
|
|
3
|
+
TrustBase 店铺界面组件 —— **框架无关的 Web Components**,四个最小元素:
|
|
4
|
+
商品列表 / 商品详情(规格选择)/ 下单表单 / 我的账户。
|
|
5
|
+
|
|
6
|
+
- **零运行时依赖**:自己写 Custom Elements,不引任何第三方 UI 框架/库(连 `@trustbase/shop` 都不 import)。
|
|
7
|
+
- **样式只引用 design tokens**(`src/tokens.ts` 是唯一允许出现视觉字面量的文件)。
|
|
8
|
+
- **不持私钥、不签名**:组件只渲染 + 抛事件;下单只产出"意愿 + memo"。
|
|
9
|
+
- 移动优先:默认 375 宽可用(`demo/index.html` 就是按 375 做的)。
|
|
10
|
+
- 状态:**已实现并测试通过(122 个用例)**;未验证/未做的清单见 §7。
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 1. 组件契约(元素名/属性/事件都是冻结的)
|
|
15
|
+
|
|
16
|
+
### `<tb-goods-list>` — 商品列表
|
|
17
|
+
|
|
18
|
+
| 入参 `data-*` | 说明 |
|
|
19
|
+
|---|---|
|
|
20
|
+
| `data-goods` | JSON 数组:`[{ goods_id, seller?, title, summary?, image?, status?, skus: [{ sku_id, title, price_per_unit, stock, spec? }] }]` |
|
|
21
|
+
| `data-state` | `loading` / `error` / `empty` / `ready`(可选,不写按数据推断) |
|
|
22
|
+
| `data-error` | 错误文案(`data-state="error"` 时显示) |
|
|
23
|
+
|
|
24
|
+
| 出参事件 | `detail` |
|
|
25
|
+
|---|---|
|
|
26
|
+
| `tb:goods-select` | `{ goods }` —— 被点的商品原样(宿主据此打开详情) |
|
|
27
|
+
|
|
28
|
+
### `<tb-goods-detail>` — 详情 + 规格选择
|
|
29
|
+
|
|
30
|
+
| 入参 `data-*` | 说明 |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `data-goods` | JSON 对象(单个商品,结构同上) |
|
|
33
|
+
| `data-sku-id` | 预选规格(可选;缺省 = 有货且最便宜的) |
|
|
34
|
+
| `data-quantity` | 初始数量(可选,默认 1,上限 = 库存) |
|
|
35
|
+
| `data-state` / `data-error` | 同上 |
|
|
36
|
+
|
|
37
|
+
| 出参事件 | `detail` |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `tb:sku-change` | `{ goods_id, sku_id, sku, quantity }` |
|
|
40
|
+
| `tb:goods-buy` | `{ goods_id, seller, sku_id, sku, quantity, price_per_unit, amount_micro_tct }` —— **下单意愿的输入** |
|
|
41
|
+
|
|
42
|
+
单规格商品不渲染规格选择器(规范 §3.5「默认即最优」);缺货/下架时主按钮禁用。
|
|
43
|
+
|
|
44
|
+
### `<tb-order-form>` — 下单表单
|
|
45
|
+
|
|
46
|
+
| 入参 `data-*` | 说明 |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `data-lines` | JSON 数组:`[{ goods_id?, sku_id, quantity, price_per_unit, title? }]`(`title` 只用于显示) |
|
|
49
|
+
| `data-buyer` | 买家 tct1 地址(预填可改) |
|
|
50
|
+
| `data-seller` | 卖家 tct1 地址(只读显示) |
|
|
51
|
+
| `data-order-ref` | 平台侧订单引用(**必填**:订单号由平台侧给,UI 不自己编) |
|
|
52
|
+
| `data-session-ref` | 会话引用(可选) |
|
|
53
|
+
| `data-timestamp-ms` | 固定毫秒时间戳(可选;**重试请传同一个值** → 意愿 hash 不变 → 幂等) |
|
|
54
|
+
| `data-state` / `data-error` | 同上 |
|
|
55
|
+
|
|
56
|
+
| 出参事件 | `detail` |
|
|
57
|
+
|---|---|
|
|
58
|
+
| `tb:order-submit` | `{ buyer, seller, order_ref, timestamp_ms, session_ref, lines: [{ goods_id, sku_id, quantity, price_per_unit }], total_micro_tct, contact: { name, phone, address }, intent }` |
|
|
59
|
+
| `tb:order-error` | `{ code, message }` —— 表单不合法或意愿构造失败(此时**不发** `tb:order-submit`) |
|
|
60
|
+
|
|
61
|
+
**`intent` 由宿主注入的 `intentBuilder` 产出**(属性,不是属性值):
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
import { buildOrderIntentHash } from '@trustbase/shop';
|
|
65
|
+
const form = document.querySelector('tb-order-form');
|
|
66
|
+
form.intentBuilder = (draft) =>
|
|
67
|
+
buildOrderIntentHash(
|
|
68
|
+
draft.lines.map((l) => ({ sku_id: l.sku_id, quantity: l.quantity, price_per_unit: l.price_per_unit })),
|
|
69
|
+
{ buyer: draft.buyer, seller: draft.seller, order_ref: draft.order_ref, timestamp_ms: draft.timestamp_ms, session_ref: draft.session_ref }
|
|
70
|
+
);
|
|
71
|
+
// → detail.intent = { intent, canonical, hash, memo };memo 形如 "tb-auth:<64 位小写 hex>"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
为什么是注入而不是 import:**意愿口径只能有一个实现**(`@trustbase/shop`),
|
|
75
|
+
而本包零依赖 —— 与其在 UI 里抄第二份、再指望两份不漂移,不如让调用方把真实现塞进来。
|
|
76
|
+
`test/format.test.ts` 与 `test/order-form.test.ts` 就是拿 `@trustbase/shop` 的真实现做的交叉验证。
|
|
77
|
+
|
|
78
|
+
### `<tb-account-panel>` — 我的账户
|
|
79
|
+
|
|
80
|
+
| 入参 `data-*` | 说明 |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `data-account` | JSON:`{ address, balance_micro_tct?, balance_unit?, pending_orders? }` |
|
|
83
|
+
| `data-orders` | JSON 数组(**占位**):`[{ order_id, status, title?, total_micro_tct?, created_at? }]` |
|
|
84
|
+
| `data-state` / `data-error` | 同上 |
|
|
85
|
+
|
|
86
|
+
| 出参事件 | `detail` |
|
|
87
|
+
|---|---|
|
|
88
|
+
| `tb:account-action` | `{ action, address? }`,`action ∈ topup \| addresses \| orders \| copy-address` |
|
|
89
|
+
|
|
90
|
+
## 2. 注册与使用
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
import { registerShopUi, TOKENS_CSS } from '@trustbaseai/shop-ui';
|
|
94
|
+
registerShopUi(); // 幂等;Node/SSR 里是安全空操作
|
|
95
|
+
document.head.append(Object.assign(document.createElement('style'), { textContent: TOKENS_CSS })); // 可选:壳层也用同一套变量
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
```html
|
|
99
|
+
<tb-goods-list data-goods='[{"goods_id":"g1","title":"茶","skus":[{"sku_id":"s1","title":"250g","price_per_unit":"19900000","stock":5}]}]'></tb-goods-list>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
事件是 `bubbles: true` + `composed: true`,所以在 `document` 上挂监听也能收到(有测试)。
|
|
103
|
+
|
|
104
|
+
## 3. Design tokens(`src/tokens.ts`,唯一来源)
|
|
105
|
+
|
|
106
|
+
《TrustBase-设计风格规范-2026-09-22》§1 的数字逐条落成代码 + 断言:
|
|
107
|
+
|
|
108
|
+
| 类别 | 取值 |
|
|
109
|
+
|---|---|
|
|
110
|
+
| 灰阶(**恰好 5 级**) | `#1D1D1F / #6E6E73 / #8E8E93 / #C7C7CC / #E5E5EA` |
|
|
111
|
+
| 主色 / 强调色 | 文字 `#1D1D1F`、底 `#FFFFFF`、分区底 `#F5F5F7`;强调色 `#C4633C`(只用于主操作与选中态) |
|
|
112
|
+
| 语义色(只用于状态) | 成功 `#1D8A5B` / 警告 `#B26A00` / 错误 `#C0392B`(**规范未指定色值,v0 取值写在这里**) |
|
|
113
|
+
| 字号(**≤5 级**) | 34 / 22 / 17 / 15 / 13 |
|
|
114
|
+
| 字重 · 行高 | 400 · 600;正文 1.5、标题 1.2 |
|
|
115
|
+
| 圆角(**3 种**) | 16 / 10 / 6 |
|
|
116
|
+
| 间距(**7 档**) | 4 / 8 / 12 / 16 / 24 / 32 / 48 |
|
|
117
|
+
| 描边或阴影(二选一) | 组件统一用**描边**(`--tb-border-width: 1px`,规范未给宽度);`--tb-shadow-card` 留给将来的浮层 |
|
|
118
|
+
| 动效 | `120ms` / `200ms`,`ease-out`,只用于状态变化 |
|
|
119
|
+
|
|
120
|
+
tokens 由 `tokensCss(':host')` 注入每个组件的 shadow root —— **组件自带主题**,不依赖宿主先引全局 CSS。
|
|
121
|
+
|
|
122
|
+
## 4. 证据(两条"防退化"的机器检查)
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
npm test # 122 个用例,8 个套件
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
- **无魔法值**(`test/styles.test.ts`):扫描四个组件真渲染出的 `<style>`(去掉 tokens 注入块),
|
|
129
|
+
禁止 `#hex` / `rgb()` / `hsl()` / `13px` / `1.5rem` / `200ms` / 内联 `style="`;
|
|
130
|
+
并断言**用到的每个 `var(--tb-*)` 都已在 tokens 里定义**(写错变量名浏览器不报错,只有这条能发现);
|
|
131
|
+
另断言"描边与阴影不在同一条规则里叠加"。`src/` 其余文件也扫(`tokens.ts` 是唯一例外)。
|
|
132
|
+
- **tokens 与规范一致**(`test/tokens.test.ts`):逐条断言上表的数字,并做一条通用兜底 ——
|
|
133
|
+
**所有 token 里出现的 px 取值必须落在允许集合内**(防"顺手加个 20px 圆角")。
|
|
134
|
+
|
|
135
|
+
## 5. 演示页(375 宽,纯静态,引用构建产物)
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
npm run build && npm run serve:demo # http://127.0.0.1:4173/demo/
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`demo/index.html` + `demo/app.js` 演示完整流程:商品列表 → 详情(选规格/改数量)→ 下单表单 →
|
|
142
|
+
**意愿回执**(展示要交给商户守护进程的 payload 与 `tb-auth:<hash>` memo)→ 我的账户(地址/余额/订单占位)。
|
|
143
|
+
|
|
144
|
+
- 演示页直接 import **ESM 构建产物**(`../dist/esm/index.js`),不需要打包器;
|
|
145
|
+
唯一的裸依赖 `@noble/hashes`(shop 的 sha256)用浏览器原生 **import map** 映射(见 `index.html` 注释)。
|
|
146
|
+
- 截图(Edge + puppeteer-core,375×812@2x)在 `demo/shots/`。
|
|
147
|
+
|
|
148
|
+
## 6. 公开 API
|
|
149
|
+
|
|
150
|
+
| 出口 | 内容 |
|
|
151
|
+
|---|---|
|
|
152
|
+
| 组件 | `TbGoodsList` `TbGoodsDetail` `TbOrderForm` `TbAccountPanel` `TbElement` |
|
|
153
|
+
| 注册 | `registerShopUi()` `SHOP_UI_ELEMENTS` `shopUiElementNames()` |
|
|
154
|
+
| tokens | `TOKENS` `TOKENS_CSS` `tokensCss()` `HOST_STYLE` `AMOUNT_STYLE` + 分组常量(`gray` `color` `fontSize` …) |
|
|
155
|
+
| 事件名 | `SHOP_UI_EVENTS`(`GOODS_SELECT` `SKU_CHANGE` `GOODS_BUY` `ORDER_SUBMIT` `ORDER_ERROR` `ACCOUNT_ACTION`) |
|
|
156
|
+
| 视图格式化 | `formatAmount` `formatAmountParts` `multiplyAmounts` `sumAmounts` `shortAddress` `orderStatusLabel` `goodsMetaLine` |
|
|
157
|
+
| 类型 | `GoodsView` `GoodsDetailView` `OrderLineView` `OrderSubmitDetail` `IntentBuilder` `AccountView` `AccountOrderView` `UIState` |
|
|
158
|
+
|
|
159
|
+
## 7. 未验证 / 未做(诚实清单)
|
|
160
|
+
|
|
161
|
+
- **未验证**:手机真机上的表现(只有 Edge 375 宽截图为证);PWA 离线/安装(属 `@trustbase/pwa-kit`)。
|
|
162
|
+
- **未验证**:组件与真实索引器数据的对接(现在只吃过演示数据与测试夹具,映射属 WP5)。
|
|
163
|
+
- **未做**:购物车界面(`@trustbase/shop` 有购物车模型,但本期 UI 只有"立即购买"一条路 ——
|
|
164
|
+
一屏一个主操作);商品图上传/裁剪;库存实时刷新;分页/无限滚动;搜索。
|
|
165
|
+
- **未做**:收货地址簿(`tb-account-panel` 的"收货地址"只抛事件);
|
|
166
|
+
订单详情页;评价与售后入口;优惠券、多仓。
|
|
167
|
+
- **未做**:a11y 全面审计(已做基础项:`role`/`tabindex`/键盘 Enter·空格/`aria-pressed`/`aria-label`,未过读屏实测)。
|
|
168
|
+
- **未做**:i18n(文案现在直接是中文)。
|
|
169
|
+
- **未做**:`shipping_address_hash` 的加密与提交 —— **由商户守护进程做**(界面只把明文交给它)。
|
|
170
|
+
|
|
171
|
+
## 8. 开发
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
npm run build # CJS → dist/ ;ESM → dist/esm/(浏览器可直接 import)
|
|
175
|
+
npm test # jest + jsdom(122 用例)
|
|
176
|
+
npm run typecheck # tsc --noEmit
|
|
177
|
+
npm run serve:demo # 演示页(先 build)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
- `src/` 的相对导入写 **ESM 形态**(带 `.js` 后缀)→ `dist/esm/` 可被浏览器直接加载;
|
|
181
|
+
jest 侧用 `moduleNameMapper` 摘后缀。
|
|
182
|
+
- 测试用 jsdom;`test/setup.cjs` 补 `TextEncoder`/`TextDecoder`(jsdom 没有、浏览器有),
|
|
183
|
+
这样"组件 + `@trustbase/shop` 真意愿实现"能在同一个测试里跑通。
|
|
184
|
+
- `package.json` 的 `sideEffects` 只留 `register`(`customElements.define` 是副作用入口),其余模块可 tree-shake。
|
|
185
|
+
|
|
186
|
+
## 9. 约束(会被测试拦下的写法)
|
|
187
|
+
|
|
188
|
+
- 组件样式出现字面量 → `test/styles.test.ts` 失败;
|
|
189
|
+
- `src/` 里 import 任何外部包(含 `@trustbase/*`)→ `test/isomorphic.test.ts` 失败;
|
|
190
|
+
- 源码出现 `privateKey` / `mnemonic` / `signTransaction` / `keystore` 等标识符 → 同款测试失败;
|
|
191
|
+
- 新增第 6 种圆角/字号/间距 → `test/tokens.test.ts` 失败。
|
package/dist/base.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 组件基类 —— 自己写 Custom Elements(**不引任何第三方 UI 框架/库**)。
|
|
3
|
+
*
|
|
4
|
+
* 三个约定,四个组件都照这个来:
|
|
5
|
+
*
|
|
6
|
+
* 1. **shadow DOM 自带主题**:每个组件把 `tokens.ts` 的 CSS 变量注入自己的 shadow root
|
|
7
|
+
* (`:host{--tb-*}`),所以组件不依赖宿主页面先引一份全局样式;变量按 CSS 继承规则往下传。
|
|
8
|
+
* 2. **入参只走 `data-*` 属性,出参只走 `CustomEvent`**:属性是外部世界的输入(宿主/索引器给的数据),
|
|
9
|
+
* 事件是组件对外的唯一输出(`bubbles: true` + `composed: true`,能穿过 shadow 边界被宿主接住)。
|
|
10
|
+
* 3. **组件不持私钥、不签名、不做业务判断**:它只渲染 + 抛事件;下单只产出"意愿 + memo",
|
|
11
|
+
* 签名由调用方交给商户守护进程。
|
|
12
|
+
*
|
|
13
|
+
* 渲染策略是"整体重渲染"(`innerHTML` 重写),因为组件的输入就是属性与少量内部状态;
|
|
14
|
+
* 手写 diff 在这个规模上只会带来 bug。**注意**:重渲染会丢输入框里的临时内容,
|
|
15
|
+
* 所以表单组件在提交时才读 input 的值,且在 `data-state` 之外不触发重渲染。
|
|
16
|
+
*/
|
|
17
|
+
/** 组件对外事件名(一处定义,避免各处手写字符串写歪) */
|
|
18
|
+
export declare const SHOP_UI_EVENTS: Readonly<{
|
|
19
|
+
/** `tb-goods-list`:点了某个商品 */
|
|
20
|
+
readonly GOODS_SELECT: "tb:goods-select";
|
|
21
|
+
/** `tb-goods-detail`:切换规格/数量 */
|
|
22
|
+
readonly SKU_CHANGE: "tb:sku-change";
|
|
23
|
+
/** `tb-goods-detail`:点"立即购买"(下方单意愿的输入) */
|
|
24
|
+
readonly GOODS_BUY: "tb:goods-buy";
|
|
25
|
+
/** `tb-order-form`:点"提交订单"且表单合法(含意愿 + memo,若有注入 builder) */
|
|
26
|
+
readonly ORDER_SUBMIT: "tb:order-submit";
|
|
27
|
+
/** `tb-order-form`:表单不合法或意愿构造失败(**不会**同时发 ORDER_SUBMIT) */
|
|
28
|
+
readonly ORDER_ERROR: "tb:order-error";
|
|
29
|
+
/** `tb-account-panel`:点了账户面板上的某个入口 */
|
|
30
|
+
readonly ACCOUNT_ACTION: "tb:account-action";
|
|
31
|
+
}>;
|
|
32
|
+
/** 组件的可视状态(三种状态都要有:加载/错误/空 —— 设计规范 §2.4) */
|
|
33
|
+
export type UIState = 'loading' | 'error' | 'empty' | 'ready';
|
|
34
|
+
/** HTML 转义:组件渲染的是**外部数据**(商品标题、订单备注),不转义就是 XSS 入口 */
|
|
35
|
+
export declare function escapeHtml(value: unknown): string;
|
|
36
|
+
/** JSON 属性解析结果:解析失败也是一种状态(走 error 态而不是白屏) */
|
|
37
|
+
export interface JsonAttr<T> {
|
|
38
|
+
value: T;
|
|
39
|
+
error: string | null;
|
|
40
|
+
}
|
|
41
|
+
export declare abstract class TbElement extends HTMLElement {
|
|
42
|
+
protected readonly root: ShadowRoot;
|
|
43
|
+
constructor();
|
|
44
|
+
/** 组件名(子类覆盖,用于报错与断言) */
|
|
45
|
+
protected abstract readonly tag: string;
|
|
46
|
+
/** 组件自己的样式(**只允许 `var(--tb-*)`**,字面量会被 `test/styles.test.ts` 拦下) */
|
|
47
|
+
protected abstract styles(): string;
|
|
48
|
+
/** 结构(纯字符串;数据用 `escapeHtml` 过一遍) */
|
|
49
|
+
protected abstract template(): string;
|
|
50
|
+
/** 渲染后绑定事件(每次都重新绑:整体重渲染会丢监听器) */
|
|
51
|
+
protected bind(): void;
|
|
52
|
+
connectedCallback(): void;
|
|
53
|
+
attributeChangedCallback(_name: string, oldValue: string | null, newValue: string | null): void;
|
|
54
|
+
/** 渲染 = 样式 + 结构 + 绑定(一次性) */
|
|
55
|
+
protected render(): void;
|
|
56
|
+
/** `data-*` 属性读取(组件的唯一入参通道) */
|
|
57
|
+
protected attr(name: string): string | null;
|
|
58
|
+
/** `data-*` 属性 → JSON(解析失败返回 error,不抛) */
|
|
59
|
+
protected jsonAttr<T>(name: string, fallback: T): JsonAttr<T>;
|
|
60
|
+
/** 组件状态:`data-state` 显式指定优先,否则按错误/数据推断 */
|
|
61
|
+
protected uiState(hasData: boolean, parseError?: string | null): UIState;
|
|
62
|
+
/** 错误文案:`data-error` 优先,其次解析错误,最后兜底一句人话(不吐技术报错 —— 规范 §2.4) */
|
|
63
|
+
protected errorText(parseError?: string | null): string;
|
|
64
|
+
/**
|
|
65
|
+
* 三态的渲染入口:**只有 error 态用 `data-error`/解析错误当文案**,
|
|
66
|
+
* 空态与加载态用人话默认值(否则空列表会显示"服务暂时不可用")。
|
|
67
|
+
*/
|
|
68
|
+
protected renderState(state: UIState, parseError?: string | null): string;
|
|
69
|
+
/** 对外抛事件(bubbles + composed:能穿过 shadow 边界) */
|
|
70
|
+
protected emit<T>(type: string, detail: T): void;
|
|
71
|
+
/** shadow 内查询(单/多) */
|
|
72
|
+
protected $(selector: string): HTMLElement | null;
|
|
73
|
+
protected $$(selector: string): HTMLElement[];
|
|
74
|
+
}
|
|
75
|
+
/** 加载 / 错误 / 空 三态的结构(各组件复用;`ready` 返回空串) */
|
|
76
|
+
export declare function stateBlock(state: UIState, message?: string): string;
|
|
77
|
+
/** 三态样式(各组件在 `styles()` 里拼进去;只引用 tokens) */
|
|
78
|
+
export declare const STATE_STYLE: string;
|
|
79
|
+
//# sourceMappingURL=base.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../src/base.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,gCAAgC;AAChC,eAAO,MAAM,cAAc;IACzB,6BAA6B;;IAE7B,gCAAgC;;IAEhC,0CAA0C;;IAE1C,4DAA4D;;IAE5D,2DAA2D;;IAE3D,sCAAsC;;EAE7B,CAAC;AAEZ,4CAA4C;AAC5C,MAAM,MAAM,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAE9D,qDAAqD;AACrD,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAKjD;AAED,6CAA6C;AAC7C,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,KAAK,EAAE,CAAC,CAAC;IACT,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,8BAAsB,SAAU,SAAQ,WAAW;IACjD,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;;IAOpC,wBAAwB;IACxB,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAExC,oEAAoE;IACpE,SAAS,CAAC,QAAQ,CAAC,MAAM,IAAI,MAAM;IAEnC,oCAAoC;IACpC,SAAS,CAAC,QAAQ,CAAC,QAAQ,IAAI,MAAM;IAErC,iCAAiC;IACjC,SAAS,CAAC,IAAI,IAAI,IAAI;IAItB,iBAAiB,IAAI,IAAI;IAIzB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAI/F,6BAA6B;IAC7B,SAAS,CAAC,MAAM,IAAI,IAAI;IAUxB,+BAA+B;IAC/B,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAI3C,0CAA0C;IAC1C,SAAS,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;IAc7D,0CAA0C;IAC1C,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO;IASxE,8DAA8D;IAC9D,SAAS,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM;IAOvD;;;OAGG;IACH,SAAS,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM;IAMzE,8CAA8C;IAC9C,SAAS,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,IAAI;IAIhD,sBAAsB;IACtB,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAIjD,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,EAAE;CAG9C;AAED,4CAA4C;AAC5C,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAKnE;AACD,4CAA4C;AAC5C,eAAO,MAAM,WAAW,QAId,CAAC"}
|
package/dist/base.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 组件基类 —— 自己写 Custom Elements(**不引任何第三方 UI 框架/库**)。
|
|
4
|
+
*
|
|
5
|
+
* 三个约定,四个组件都照这个来:
|
|
6
|
+
*
|
|
7
|
+
* 1. **shadow DOM 自带主题**:每个组件把 `tokens.ts` 的 CSS 变量注入自己的 shadow root
|
|
8
|
+
* (`:host{--tb-*}`),所以组件不依赖宿主页面先引一份全局样式;变量按 CSS 继承规则往下传。
|
|
9
|
+
* 2. **入参只走 `data-*` 属性,出参只走 `CustomEvent`**:属性是外部世界的输入(宿主/索引器给的数据),
|
|
10
|
+
* 事件是组件对外的唯一输出(`bubbles: true` + `composed: true`,能穿过 shadow 边界被宿主接住)。
|
|
11
|
+
* 3. **组件不持私钥、不签名、不做业务判断**:它只渲染 + 抛事件;下单只产出"意愿 + memo",
|
|
12
|
+
* 签名由调用方交给商户守护进程。
|
|
13
|
+
*
|
|
14
|
+
* 渲染策略是"整体重渲染"(`innerHTML` 重写),因为组件的输入就是属性与少量内部状态;
|
|
15
|
+
* 手写 diff 在这个规模上只会带来 bug。**注意**:重渲染会丢输入框里的临时内容,
|
|
16
|
+
* 所以表单组件在提交时才读 input 的值,且在 `data-state` 之外不触发重渲染。
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.STATE_STYLE = exports.TbElement = exports.SHOP_UI_EVENTS = void 0;
|
|
20
|
+
exports.escapeHtml = escapeHtml;
|
|
21
|
+
exports.stateBlock = stateBlock;
|
|
22
|
+
const tokens_js_1 = require("./tokens.js");
|
|
23
|
+
/** 组件对外事件名(一处定义,避免各处手写字符串写歪) */
|
|
24
|
+
exports.SHOP_UI_EVENTS = Object.freeze({
|
|
25
|
+
/** `tb-goods-list`:点了某个商品 */
|
|
26
|
+
GOODS_SELECT: 'tb:goods-select',
|
|
27
|
+
/** `tb-goods-detail`:切换规格/数量 */
|
|
28
|
+
SKU_CHANGE: 'tb:sku-change',
|
|
29
|
+
/** `tb-goods-detail`:点"立即购买"(下方单意愿的输入) */
|
|
30
|
+
GOODS_BUY: 'tb:goods-buy',
|
|
31
|
+
/** `tb-order-form`:点"提交订单"且表单合法(含意愿 + memo,若有注入 builder) */
|
|
32
|
+
ORDER_SUBMIT: 'tb:order-submit',
|
|
33
|
+
/** `tb-order-form`:表单不合法或意愿构造失败(**不会**同时发 ORDER_SUBMIT) */
|
|
34
|
+
ORDER_ERROR: 'tb:order-error',
|
|
35
|
+
/** `tb-account-panel`:点了账户面板上的某个入口 */
|
|
36
|
+
ACCOUNT_ACTION: 'tb:account-action',
|
|
37
|
+
});
|
|
38
|
+
/** HTML 转义:组件渲染的是**外部数据**(商品标题、订单备注),不转义就是 XSS 入口 */
|
|
39
|
+
function escapeHtml(value) {
|
|
40
|
+
return String(value ?? '').replace(/[&<>"']/g, (ch) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[ch] ?? ch);
|
|
41
|
+
}
|
|
42
|
+
class TbElement extends HTMLElement {
|
|
43
|
+
root;
|
|
44
|
+
constructor() {
|
|
45
|
+
super();
|
|
46
|
+
this.root = this.attachShadow({ mode: 'open' });
|
|
47
|
+
}
|
|
48
|
+
/** 渲染后绑定事件(每次都重新绑:整体重渲染会丢监听器) */
|
|
49
|
+
bind() {
|
|
50
|
+
/* 子类按需覆盖 */
|
|
51
|
+
}
|
|
52
|
+
connectedCallback() {
|
|
53
|
+
this.render();
|
|
54
|
+
}
|
|
55
|
+
attributeChangedCallback(_name, oldValue, newValue) {
|
|
56
|
+
if (oldValue !== newValue && this.isConnected)
|
|
57
|
+
this.render();
|
|
58
|
+
}
|
|
59
|
+
/** 渲染 = 样式 + 结构 + 绑定(一次性) */
|
|
60
|
+
render() {
|
|
61
|
+
try {
|
|
62
|
+
this.root.innerHTML = `<style>${tokens_js_1.HOST_STYLE}${this.styles()}</style>${this.template()}`;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
66
|
+
this.root.innerHTML = `<style>${tokens_js_1.HOST_STYLE}${this.styles()}</style>${stateBlock('error', message)}`;
|
|
67
|
+
}
|
|
68
|
+
this.bind();
|
|
69
|
+
}
|
|
70
|
+
/** `data-*` 属性读取(组件的唯一入参通道) */
|
|
71
|
+
attr(name) {
|
|
72
|
+
return this.getAttribute(`data-${name}`);
|
|
73
|
+
}
|
|
74
|
+
/** `data-*` 属性 → JSON(解析失败返回 error,不抛) */
|
|
75
|
+
jsonAttr(name, fallback) {
|
|
76
|
+
const raw = this.attr(name);
|
|
77
|
+
if (raw === null || raw === '')
|
|
78
|
+
return { value: fallback, error: null };
|
|
79
|
+
try {
|
|
80
|
+
const parsed = JSON.parse(raw);
|
|
81
|
+
if (parsed === null || typeof parsed !== 'object') {
|
|
82
|
+
return { value: fallback, error: `data-${name} 必须是 JSON 对象或数组` };
|
|
83
|
+
}
|
|
84
|
+
return { value: parsed, error: null };
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return { value: fallback, error: `data-${name} 不是合法 JSON` };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/** 组件状态:`data-state` 显式指定优先,否则按错误/数据推断 */
|
|
91
|
+
uiState(hasData, parseError) {
|
|
92
|
+
const declared = this.attr('state');
|
|
93
|
+
if (declared === 'loading' || declared === 'error' || declared === 'empty' || declared === 'ready') {
|
|
94
|
+
return declared;
|
|
95
|
+
}
|
|
96
|
+
if (parseError)
|
|
97
|
+
return 'error';
|
|
98
|
+
return hasData ? 'ready' : 'empty';
|
|
99
|
+
}
|
|
100
|
+
/** 错误文案:`data-error` 优先,其次解析错误,最后兜底一句人话(不吐技术报错 —— 规范 §2.4) */
|
|
101
|
+
errorText(parseError) {
|
|
102
|
+
const declared = this.attr('error');
|
|
103
|
+
if (declared !== null && declared !== '')
|
|
104
|
+
return declared;
|
|
105
|
+
if (parseError)
|
|
106
|
+
return parseError;
|
|
107
|
+
return '服务暂时不可用,请刷新重试';
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 三态的渲染入口:**只有 error 态用 `data-error`/解析错误当文案**,
|
|
111
|
+
* 空态与加载态用人话默认值(否则空列表会显示"服务暂时不可用")。
|
|
112
|
+
*/
|
|
113
|
+
renderState(state, parseError) {
|
|
114
|
+
if (state === 'ready')
|
|
115
|
+
return '';
|
|
116
|
+
if (state === 'error')
|
|
117
|
+
return stateBlock('error', this.errorText(parseError));
|
|
118
|
+
return stateBlock(state);
|
|
119
|
+
}
|
|
120
|
+
/** 对外抛事件(bubbles + composed:能穿过 shadow 边界) */
|
|
121
|
+
emit(type, detail) {
|
|
122
|
+
this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));
|
|
123
|
+
}
|
|
124
|
+
/** shadow 内查询(单/多) */
|
|
125
|
+
$(selector) {
|
|
126
|
+
return this.root.querySelector(selector);
|
|
127
|
+
}
|
|
128
|
+
$$(selector) {
|
|
129
|
+
return Array.from(this.root.querySelectorAll(selector));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
exports.TbElement = TbElement;
|
|
133
|
+
/** 加载 / 错误 / 空 三态的结构(各组件复用;`ready` 返回空串) */
|
|
134
|
+
function stateBlock(state, message) {
|
|
135
|
+
if (state === 'ready')
|
|
136
|
+
return '';
|
|
137
|
+
if (state === 'loading')
|
|
138
|
+
return `<div class="state" data-state-block="loading">正在加载…</div>`;
|
|
139
|
+
if (state === 'empty')
|
|
140
|
+
return `<div class="state" data-state-block="empty">${escapeHtml(message ?? '这里还没有内容')}</div>`;
|
|
141
|
+
return `<div class="state state-error" data-state-block="error">${escapeHtml(message ?? '服务暂时不可用,请刷新重试')}</div>`;
|
|
142
|
+
}
|
|
143
|
+
/** 三态样式(各组件在 `styles()` 里拼进去;只引用 tokens) */
|
|
144
|
+
exports.STATE_STYLE = [
|
|
145
|
+
'.state{padding:var(--tb-space-lg) var(--tb-space-md);color:var(--tb-color-text-secondary);',
|
|
146
|
+
'font-size:var(--tb-font-size-secondary);text-align:center;}',
|
|
147
|
+
'.state-error{color:var(--tb-color-danger);}',
|
|
148
|
+
].join('');
|
|
149
|
+
//# sourceMappingURL=base.js.map
|
package/dist/base.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.js","sourceRoot":"","sources":["../src/base.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<tb-account-panel>` —— 我的账户:地址 / 余额 / 入口 / 订单列表(**占位**)。
|
|
3
|
+
*
|
|
4
|
+
* ## 契约
|
|
5
|
+
*
|
|
6
|
+
* | 入参(`data-*`) | 说明 |
|
|
7
|
+
* |---|---|
|
|
8
|
+
* | `data-account` | JSON:`{ address, balance_micro_tct?, balance_unit?, pending_orders? }` |
|
|
9
|
+
* | `data-orders` | JSON 数组:`[{ order_id, status, title?, total_micro_tct?, created_at? }]` —— 订单列表占位 |
|
|
10
|
+
* | `data-state` / `data-error` | 同其它组件 |
|
|
11
|
+
*
|
|
12
|
+
* | 出参(`CustomEvent`) | `detail` |
|
|
13
|
+
* |---|---|
|
|
14
|
+
* | `tb:account-action` | `{ action, address? }`,`action ∈ topup \| addresses \| orders \| copy-address` |
|
|
15
|
+
*
|
|
16
|
+
* 面板**只显示与转发**:余额从哪来(链上/索引器)、怎么充值、地址怎么改,全由宿主决定。
|
|
17
|
+
* 订单列表是**占位**——真实数据要等 `@trustbase/index-client`(WP5)接上索引器。
|
|
18
|
+
*/
|
|
19
|
+
import { TbElement } from '../base.js';
|
|
20
|
+
/** 账户视图 */
|
|
21
|
+
export interface AccountView {
|
|
22
|
+
address: string;
|
|
23
|
+
balance_micro_tct?: string;
|
|
24
|
+
balance_unit?: string;
|
|
25
|
+
pending_orders?: number;
|
|
26
|
+
}
|
|
27
|
+
/** 订单行视图(占位列表用) */
|
|
28
|
+
export interface AccountOrderView {
|
|
29
|
+
order_id: string;
|
|
30
|
+
status: string;
|
|
31
|
+
title?: string;
|
|
32
|
+
total_micro_tct?: string;
|
|
33
|
+
created_at?: string;
|
|
34
|
+
}
|
|
35
|
+
export declare class TbAccountPanel extends TbElement {
|
|
36
|
+
static get observedAttributes(): string[];
|
|
37
|
+
protected readonly tag = "tb-account-panel";
|
|
38
|
+
protected styles(): string;
|
|
39
|
+
private orders;
|
|
40
|
+
protected template(): string;
|
|
41
|
+
private orderRow;
|
|
42
|
+
protected bind(): void;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=account-panel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account-panel.d.ts","sourceRoot":"","sources":["../../src/components/account-panel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAA+B,SAAS,EAA0B,MAAM,YAAY,CAAC;AAI5F,WAAW;AACX,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,mBAAmB;AACnB,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,cAAe,SAAQ,SAAS;IAC3C,MAAM,KAAK,kBAAkB,IAAI,MAAM,EAAE,CAExC;IAED,SAAS,CAAC,QAAQ,CAAC,GAAG,sBAAsB;IAE5C,SAAS,CAAC,MAAM,IAAI,MAAM;IAgC1B,OAAO,CAAC,MAAM;IAKd,SAAS,CAAC,QAAQ,IAAI,MAAM;IA4C5B,OAAO,CAAC,QAAQ;cAcG,IAAI,IAAI,IAAI;CAmBhC"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `<tb-account-panel>` —— 我的账户:地址 / 余额 / 入口 / 订单列表(**占位**)。
|
|
4
|
+
*
|
|
5
|
+
* ## 契约
|
|
6
|
+
*
|
|
7
|
+
* | 入参(`data-*`) | 说明 |
|
|
8
|
+
* |---|---|
|
|
9
|
+
* | `data-account` | JSON:`{ address, balance_micro_tct?, balance_unit?, pending_orders? }` |
|
|
10
|
+
* | `data-orders` | JSON 数组:`[{ order_id, status, title?, total_micro_tct?, created_at? }]` —— 订单列表占位 |
|
|
11
|
+
* | `data-state` / `data-error` | 同其它组件 |
|
|
12
|
+
*
|
|
13
|
+
* | 出参(`CustomEvent`) | `detail` |
|
|
14
|
+
* |---|---|
|
|
15
|
+
* | `tb:account-action` | `{ action, address? }`,`action ∈ topup \| addresses \| orders \| copy-address` |
|
|
16
|
+
*
|
|
17
|
+
* 面板**只显示与转发**:余额从哪来(链上/索引器)、怎么充值、地址怎么改,全由宿主决定。
|
|
18
|
+
* 订单列表是**占位**——真实数据要等 `@trustbase/index-client`(WP5)接上索引器。
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.TbAccountPanel = void 0;
|
|
22
|
+
const base_js_1 = require("../base.js");
|
|
23
|
+
const tokens_js_1 = require("../tokens.js");
|
|
24
|
+
const format_js_1 = require("../format.js");
|
|
25
|
+
class TbAccountPanel extends base_js_1.TbElement {
|
|
26
|
+
static get observedAttributes() {
|
|
27
|
+
return ['data-account', 'data-orders', 'data-state', 'data-error'];
|
|
28
|
+
}
|
|
29
|
+
tag = 'tb-account-panel';
|
|
30
|
+
styles() {
|
|
31
|
+
return [
|
|
32
|
+
base_js_1.STATE_STYLE,
|
|
33
|
+
'.wrap{padding:var(--tb-space-md);}',
|
|
34
|
+
'.section{margin-bottom:var(--tb-space-lg);}',
|
|
35
|
+
'.section-title{font-size:var(--tb-font-size-secondary);color:var(--tb-color-text-secondary);',
|
|
36
|
+
'margin-bottom:var(--tb-space-xs);}',
|
|
37
|
+
'.address{display:flex;align-items:center;gap:var(--tb-space-xs);}',
|
|
38
|
+
`.address-value{${tokens_js_1.AMOUNT_STYLE}flex:1 1 auto;min-width:0;font-size:var(--tb-font-size-secondary);`,
|
|
39
|
+
'overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}',
|
|
40
|
+
'.ghost{padding:var(--tb-space-xs) var(--tb-space-sm);border:var(--tb-border-width) solid var(--tb-color-border);',
|
|
41
|
+
'border-radius:var(--tb-radius-control);background:var(--tb-color-background);',
|
|
42
|
+
'font-size:var(--tb-font-size-caption);color:var(--tb-color-text-secondary);}',
|
|
43
|
+
'.balance{display:flex;align-items:baseline;gap:var(--tb-space-xxs);}',
|
|
44
|
+
`.balance .amount{${tokens_js_1.AMOUNT_STYLE}font-size:var(--tb-font-size-title);font-weight:var(--tb-font-weight-semibold);}`,
|
|
45
|
+
'.balance .unit{font-size:var(--tb-font-size-secondary);color:var(--tb-color-text-secondary);}',
|
|
46
|
+
'.actions{display:flex;gap:var(--tb-space-xs);}',
|
|
47
|
+
'.action{flex:1 1 0;padding:var(--tb-space-sm);border:var(--tb-border-width) solid var(--tb-color-border);',
|
|
48
|
+
'border-radius:var(--tb-radius-control);background:var(--tb-color-background);',
|
|
49
|
+
'font-size:var(--tb-font-size-secondary);}',
|
|
50
|
+
'.action:hover{border-color:var(--tb-color-accent);color:var(--tb-color-accent);}',
|
|
51
|
+
'.rows{border-top:var(--tb-border-width) solid var(--tb-color-divider);}',
|
|
52
|
+
'.row{display:flex;align-items:baseline;gap:var(--tb-space-md);padding:var(--tb-space-sm) 0;',
|
|
53
|
+
'border-bottom:var(--tb-border-width) solid var(--tb-color-divider);}',
|
|
54
|
+
'.row-main{flex:1 1 auto;min-width:0;}',
|
|
55
|
+
'.row-title{font-size:var(--tb-font-size-body);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}',
|
|
56
|
+
'.row-meta{font-size:var(--tb-font-size-caption);color:var(--tb-color-text-tertiary);}',
|
|
57
|
+
'.row-status{font-size:var(--tb-font-size-caption);color:var(--tb-color-text-secondary);white-space:nowrap;}',
|
|
58
|
+
`.row-amount{${tokens_js_1.AMOUNT_STYLE}font-size:var(--tb-font-size-secondary);white-space:nowrap;}`,
|
|
59
|
+
].join('');
|
|
60
|
+
}
|
|
61
|
+
orders() {
|
|
62
|
+
const { value } = this.jsonAttr('orders', []);
|
|
63
|
+
return Array.isArray(value) ? value.filter((row) => row && typeof row.order_id === 'string') : [];
|
|
64
|
+
}
|
|
65
|
+
template() {
|
|
66
|
+
const { value: account, error } = this.jsonAttr('account', null);
|
|
67
|
+
const declared = this.attr('state');
|
|
68
|
+
if (declared === 'loading' || declared === 'error')
|
|
69
|
+
return (0, base_js_1.stateBlock)(declared, this.errorText(error));
|
|
70
|
+
if (account === null || typeof account.address !== 'string') {
|
|
71
|
+
if (error !== null)
|
|
72
|
+
return (0, base_js_1.stateBlock)('error', this.errorText(error));
|
|
73
|
+
return (0, base_js_1.stateBlock)('empty', '还没有账户信息');
|
|
74
|
+
}
|
|
75
|
+
const orders = this.orders();
|
|
76
|
+
const balance = (0, format_js_1.formatAmountParts)(account.balance_micro_tct ?? '0', account.balance_unit ?? 'TCT');
|
|
77
|
+
const pending = Number(account.pending_orders ?? 0);
|
|
78
|
+
return [
|
|
79
|
+
'<div class="wrap">',
|
|
80
|
+
'<div class="section" data-block="address">',
|
|
81
|
+
'<div class="section-title">我的地址</div>',
|
|
82
|
+
'<div class="address">',
|
|
83
|
+
`<span class="address-value" data-address title="${(0, base_js_1.escapeHtml)(account.address)}">${(0, base_js_1.escapeHtml)((0, format_js_1.shortAddress)(account.address))}</span>`,
|
|
84
|
+
`<button class="ghost" type="button" data-action="copy-address">复制</button>`,
|
|
85
|
+
'</div>',
|
|
86
|
+
'</div>',
|
|
87
|
+
'<div class="section" data-block="balance">',
|
|
88
|
+
'<div class="section-title">余额</div>',
|
|
89
|
+
`<div class="balance" data-balance="${(0, base_js_1.escapeHtml)(account.balance_micro_tct ?? '0')}">`,
|
|
90
|
+
`<span class="amount">${(0, base_js_1.escapeHtml)(balance.value)}</span><span class="unit">${(0, base_js_1.escapeHtml)(balance.unit)}</span>`,
|
|
91
|
+
'</div>',
|
|
92
|
+
'</div>',
|
|
93
|
+
'<div class="section" data-block="actions">',
|
|
94
|
+
'<div class="actions">',
|
|
95
|
+
'<button class="action" type="button" data-action="topup">充值</button>',
|
|
96
|
+
'<button class="action" type="button" data-action="addresses">收货地址</button>',
|
|
97
|
+
`<button class="action" type="button" data-action="orders">我的订单${pending > 0 ? `(${(0, base_js_1.escapeHtml)(String(pending))})` : ''}</button>`,
|
|
98
|
+
'</div>',
|
|
99
|
+
'</div>',
|
|
100
|
+
'<div class="section" data-block="orders">',
|
|
101
|
+
`<div class="section-title">最近订单${orders.length > 0 ? `(${(0, base_js_1.escapeHtml)(String(orders.length))})` : ''}</div>`,
|
|
102
|
+
orders.length === 0
|
|
103
|
+
? '<div class="state" data-state-block="empty">还没有订单</div>'
|
|
104
|
+
: `<div class="rows">${orders.map((row) => this.orderRow(row)).join('')}</div>`,
|
|
105
|
+
'</div>',
|
|
106
|
+
'</div>',
|
|
107
|
+
].join('');
|
|
108
|
+
}
|
|
109
|
+
orderRow(row) {
|
|
110
|
+
const parts = (0, format_js_1.formatAmountParts)(row.total_micro_tct ?? '0');
|
|
111
|
+
return [
|
|
112
|
+
'<div class="row" data-order-id="', (0, base_js_1.escapeHtml)(row.order_id), '">',
|
|
113
|
+
'<div class="row-main">',
|
|
114
|
+
`<div class="row-title">${(0, base_js_1.escapeHtml)(row.title ?? row.order_id)}</div>`,
|
|
115
|
+
`<div class="row-meta">${(0, base_js_1.escapeHtml)(row.order_id)}${row.created_at ? ` · ${(0, base_js_1.escapeHtml)(row.created_at)}` : ''}</div>`,
|
|
116
|
+
'</div>',
|
|
117
|
+
`<div class="row-status">${(0, base_js_1.escapeHtml)((0, format_js_1.orderStatusLabel)(row.status))}</div>`,
|
|
118
|
+
`<div class="row-amount">${(0, base_js_1.escapeHtml)(parts.value)} ${(0, base_js_1.escapeHtml)(parts.unit)}</div>`,
|
|
119
|
+
'</div>',
|
|
120
|
+
].join('');
|
|
121
|
+
}
|
|
122
|
+
bind() {
|
|
123
|
+
const { value: account } = this.jsonAttr('account', null);
|
|
124
|
+
const address = account?.address ?? '';
|
|
125
|
+
for (const button of this.$$('[data-action]')) {
|
|
126
|
+
button.addEventListener('click', () => {
|
|
127
|
+
const action = button.getAttribute('data-action') ?? '';
|
|
128
|
+
if (action === 'copy-address') {
|
|
129
|
+
// 复制尽力而为:环境没有 clipboard(Node/旧浏览器/非 https)也不报错 —— 真复制由宿主决定
|
|
130
|
+
const clipboard = globalThis
|
|
131
|
+
.navigator?.clipboard;
|
|
132
|
+
const pending = clipboard?.writeText?.(address);
|
|
133
|
+
if (pending !== undefined)
|
|
134
|
+
void pending.catch(() => undefined);
|
|
135
|
+
this.emit(base_js_1.SHOP_UI_EVENTS.ACCOUNT_ACTION, { action, address });
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
this.emit(base_js_1.SHOP_UI_EVENTS.ACCOUNT_ACTION, { action });
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
exports.TbAccountPanel = TbAccountPanel;
|
|
144
|
+
//# sourceMappingURL=account-panel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account-panel.js","sourceRoot":"","sources":["../../src/components/account-panel.ts"],"names":[],"mappings":"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"}
|