@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
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `<tb-order-form>` —— 下单表单(一屏一个动作:**提交订单**)。
|
|
4
|
+
*
|
|
5
|
+
* ## 契约
|
|
6
|
+
*
|
|
7
|
+
* | 入参(`data-*`) | 说明 |
|
|
8
|
+
* |---|---|
|
|
9
|
+
* | `data-lines` | JSON 数组:`[{ goods_id?, sku_id, quantity, price_per_unit, title? }]`(`title` 只用于显示) |
|
|
10
|
+
* | `data-buyer` | 买家 tct1 地址(预填;可编辑) |
|
|
11
|
+
* | `data-seller` | 卖家 tct1 地址(只读显示) |
|
|
12
|
+
* | `data-order-ref` | 平台侧订单引用(可选;不填由宿主/守护进程生成) |
|
|
13
|
+
* | `data-session-ref` | 会话引用(可选) |
|
|
14
|
+
* | `data-timestamp-ms` | 固定毫秒时间戳(可选;**重试请传同一个值** → 意愿 hash 不变 → 幂等) |
|
|
15
|
+
* | `data-state` / `data-error` | 同其它组件 |
|
|
16
|
+
*
|
|
17
|
+
* | 出参(`CustomEvent`) | `detail` |
|
|
18
|
+
* |---|---|
|
|
19
|
+
* | `tb:order-submit` | `{ buyer, seller, order_ref, timestamp_ms, session_ref, lines, total_micro_tct, contact, intent }` |
|
|
20
|
+
* | `tb:order-error` | `{ code, message }` —— 表单不合法或意愿构造失败(此时**不发** `tb:order-submit`) |
|
|
21
|
+
*
|
|
22
|
+
* `intent = { intent, canonical, hash, memo } | null`:由**注入的** `intentBuilder` 产出
|
|
23
|
+
* (`el.intentBuilder = (input) => buildOrderIntentHash(input.lines, input)`,来自 `@trustbase/shop`)。
|
|
24
|
+
* 为什么是注入:本包**零运行时依赖**(不 import `@trustbase/shop`),而意愿口径是唯一真源,
|
|
25
|
+
* 只能有一个实现 —— 与其在 UI 里抄一遍,不如让调用方把真实现塞进来。
|
|
26
|
+
*
|
|
27
|
+
* ⚠️ 组件**不签名、不持私钥**:提交只产出"意愿 + memo",签名由调用方交给商户守护进程。
|
|
28
|
+
*/
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.TbOrderForm = void 0;
|
|
31
|
+
const base_js_1 = require("../base.js");
|
|
32
|
+
const tokens_js_1 = require("../tokens.js");
|
|
33
|
+
const format_js_1 = require("../format.js");
|
|
34
|
+
/** 字段级校验(够用就好:更深的口径校验在 `@trustbase/shop`,它的报错会被转成 order-error) */
|
|
35
|
+
function missingFields(input) {
|
|
36
|
+
if (input.lines.length === 0)
|
|
37
|
+
return '还没有选择商品';
|
|
38
|
+
if (input.buyer.trim() === '')
|
|
39
|
+
return '请填写买家 tct1 地址';
|
|
40
|
+
if (input.contact.name.trim() === '')
|
|
41
|
+
return '请填写收货人';
|
|
42
|
+
if (input.contact.phone.trim() === '')
|
|
43
|
+
return '请填写联系电话';
|
|
44
|
+
if (input.contact.address.trim() === '')
|
|
45
|
+
return '请填写收货地址';
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
class TbOrderForm extends base_js_1.TbElement {
|
|
49
|
+
static get observedAttributes() {
|
|
50
|
+
return ['data-lines', 'data-buyer', 'data-seller', 'data-order-ref', 'data-session-ref', 'data-state', 'data-error'];
|
|
51
|
+
}
|
|
52
|
+
tag = 'tb-order-form';
|
|
53
|
+
/** 意愿构造器(宿主注入;不注入则 `intent` 为 null,宿主自己算) */
|
|
54
|
+
intentBuilder = null;
|
|
55
|
+
styles() {
|
|
56
|
+
return [
|
|
57
|
+
base_js_1.STATE_STYLE,
|
|
58
|
+
'.wrap{padding:var(--tb-space-md);}',
|
|
59
|
+
'.section{margin-bottom:var(--tb-space-lg);}',
|
|
60
|
+
'.section-title{font-size:var(--tb-font-size-secondary);color:var(--tb-color-text-secondary);',
|
|
61
|
+
'margin-bottom:var(--tb-space-xs);}',
|
|
62
|
+
'.rows{border-top:var(--tb-border-width) solid var(--tb-color-divider);}',
|
|
63
|
+
'.row{display:flex;align-items:baseline;gap:var(--tb-space-md);padding:var(--tb-space-sm) 0;',
|
|
64
|
+
'border-bottom:var(--tb-border-width) solid var(--tb-color-divider);}',
|
|
65
|
+
'.row-title{flex:1 1 auto;min-width:0;font-size:var(--tb-font-size-body);}',
|
|
66
|
+
'.row-title span{display:block;font-size:var(--tb-font-size-caption);color:var(--tb-color-text-tertiary);}',
|
|
67
|
+
`.row-amount{${tokens_js_1.AMOUNT_STYLE}font-size:var(--tb-font-size-secondary);text-align:right;white-space:nowrap;}`,
|
|
68
|
+
'.total{display:flex;justify-content:flex-end;align-items:baseline;gap:var(--tb-space-xs);',
|
|
69
|
+
'padding-top:var(--tb-space-sm);}',
|
|
70
|
+
'.total-label{font-size:var(--tb-font-size-secondary);color:var(--tb-color-text-secondary);}',
|
|
71
|
+
`.total .amount{${tokens_js_1.AMOUNT_STYLE}font-size:var(--tb-font-size-heading);font-weight:var(--tb-font-weight-semibold);}`,
|
|
72
|
+
'.total .unit{font-size:var(--tb-font-size-secondary);color:var(--tb-color-text-secondary);}',
|
|
73
|
+
'.field{display:block;margin-bottom:var(--tb-space-sm);}',
|
|
74
|
+
'.label{display:block;font-size:var(--tb-font-size-caption);color:var(--tb-color-text-secondary);',
|
|
75
|
+
'margin-bottom:var(--tb-space-xxs);}',
|
|
76
|
+
'.input{width:100%;padding:var(--tb-space-sm);border:var(--tb-border-width) solid var(--tb-color-border);',
|
|
77
|
+
'border-radius:var(--tb-radius-control);background:var(--tb-color-background);',
|
|
78
|
+
'font-size:var(--tb-font-size-body);}',
|
|
79
|
+
'.input:focus{outline:none;border-color:var(--tb-color-accent);}',
|
|
80
|
+
'.input[readonly]{background:var(--tb-color-surface);color:var(--tb-color-text-secondary);}',
|
|
81
|
+
'.primary{width:100%;padding:var(--tb-space-md);border:none;border-radius:var(--tb-radius-control);',
|
|
82
|
+
'background:var(--tb-color-accent);color:var(--tb-color-on-accent);font-size:var(--tb-font-size-body);',
|
|
83
|
+
'font-weight:var(--tb-font-weight-semibold);}',
|
|
84
|
+
'.primary[disabled]{background:var(--tb-color-surface);color:var(--tb-color-text-tertiary);cursor:not-allowed;}',
|
|
85
|
+
'.note{margin-top:var(--tb-space-xs);font-size:var(--tb-font-size-caption);color:var(--tb-color-text-tertiary);',
|
|
86
|
+
'text-align:center;}',
|
|
87
|
+
'.error-slot{margin-top:var(--tb-space-xs);min-height:var(--tb-space-md);',
|
|
88
|
+
'font-size:var(--tb-font-size-caption);color:var(--tb-color-danger);text-align:center;}',
|
|
89
|
+
].join('');
|
|
90
|
+
}
|
|
91
|
+
lines() {
|
|
92
|
+
const { value } = this.jsonAttr('lines', []);
|
|
93
|
+
return Array.isArray(value) ? value.filter((line) => line && typeof line.sku_id === 'string') : [];
|
|
94
|
+
}
|
|
95
|
+
totalMicroTct(lines) {
|
|
96
|
+
return (0, format_js_1.sumAmounts)(lines.map((line) => (0, format_js_1.multiplyAmounts)(String(line.price_per_unit), String(line.quantity))));
|
|
97
|
+
}
|
|
98
|
+
template() {
|
|
99
|
+
const { error } = this.jsonAttr('lines', []);
|
|
100
|
+
const lines = this.lines();
|
|
101
|
+
const state = this.uiState(lines.length > 0, error);
|
|
102
|
+
if (state !== 'ready')
|
|
103
|
+
return this.renderState(state, error);
|
|
104
|
+
const total = this.totalMicroTct(lines);
|
|
105
|
+
const totalParts = (0, format_js_1.formatAmountParts)(total);
|
|
106
|
+
return [
|
|
107
|
+
'<div class="wrap">',
|
|
108
|
+
'<div class="section" data-block="lines">',
|
|
109
|
+
`<div class="section-title">订单内容(${(0, base_js_1.escapeHtml)(String(lines.length))} 项)</div>`,
|
|
110
|
+
'<div class="rows">',
|
|
111
|
+
lines
|
|
112
|
+
.map((line) => {
|
|
113
|
+
const parts = (0, format_js_1.formatAmountParts)((0, format_js_1.multiplyAmounts)(String(line.price_per_unit), String(line.quantity)));
|
|
114
|
+
const title = line.title ?? line.sku_id;
|
|
115
|
+
return [
|
|
116
|
+
'<div class="row">',
|
|
117
|
+
`<div class="row-title">${(0, base_js_1.escapeHtml)(title)}<span>${(0, base_js_1.escapeHtml)(line.sku_id)} × ${(0, base_js_1.escapeHtml)(String(line.quantity))}</span></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
|
+
.join(''),
|
|
123
|
+
'</div>',
|
|
124
|
+
`<div class="total"><span class="total-label">合计</span><span class="amount">${(0, base_js_1.escapeHtml)(totalParts.value)}</span><span class="unit">${(0, base_js_1.escapeHtml)(totalParts.unit)}</span></div>`,
|
|
125
|
+
'</div>',
|
|
126
|
+
'<div class="section" data-block="buyer">',
|
|
127
|
+
'<div class="section-title">买家身份</div>',
|
|
128
|
+
'<label class="field">',
|
|
129
|
+
'<span class="label">买家 tct1 地址(链上不收 buyer 私钥,只认地址)</span>',
|
|
130
|
+
`<input class="input" data-field="buyer" type="text" inputmode="latin" autocomplete="off" spellcheck="false" value="${(0, base_js_1.escapeHtml)(this.attr('buyer') ?? '')}" placeholder="tct1..." />`,
|
|
131
|
+
'</label>',
|
|
132
|
+
'<label class="field">',
|
|
133
|
+
'<span class="label">卖家(只读)</span>',
|
|
134
|
+
`<input class="input" data-field="seller" type="text" readonly value="${(0, base_js_1.escapeHtml)(this.attr('seller') ?? '')}" />`,
|
|
135
|
+
'</label>',
|
|
136
|
+
'</div>',
|
|
137
|
+
'<div class="section" data-block="contact">',
|
|
138
|
+
'<div class="section-title">收货信息</div>',
|
|
139
|
+
'<label class="field"><span class="label">收货人</span>',
|
|
140
|
+
'<input class="input" data-field="name" type="text" autocomplete="name" /></label>',
|
|
141
|
+
'<label class="field"><span class="label">联系电话</span>',
|
|
142
|
+
'<input class="input" data-field="phone" type="tel" inputmode="tel" autocomplete="tel" /></label>',
|
|
143
|
+
'<label class="field"><span class="label">收货地址</span>',
|
|
144
|
+
'<input class="input" data-field="address" type="text" autocomplete="street-address" /></label>',
|
|
145
|
+
'<div class="note">收货信息交给商户服务;链上只记加密后的地址哈希</div>',
|
|
146
|
+
'</div>',
|
|
147
|
+
'<button class="primary" type="button" data-action="submit">提交订单</button>',
|
|
148
|
+
'<div class="error-slot" data-error-slot role="alert"></div>',
|
|
149
|
+
'<div class="note">提交后由商户服务签名上链(本页面不接触私钥)</div>',
|
|
150
|
+
'</div>',
|
|
151
|
+
].join('');
|
|
152
|
+
}
|
|
153
|
+
bind() {
|
|
154
|
+
this.$('[data-action="submit"]')?.addEventListener('click', () => {
|
|
155
|
+
this.submit();
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
/** 读表单 → 校验 → 造意愿 → 抛事件(错误走 inline 提示 + `tb:order-error`,不重渲染以免丢掉用户输入) */
|
|
159
|
+
submit() {
|
|
160
|
+
const lines = this.lines();
|
|
161
|
+
const inputByName = (name) => {
|
|
162
|
+
const el = this.$(`[data-field="${name}"]`);
|
|
163
|
+
return el === null ? '' : el.value;
|
|
164
|
+
};
|
|
165
|
+
// 时间戳:宿主固定(重试幂等)优先,否则取当前毫秒 —— 与链侧 normalizeTimestampMs 的"缺省取当前时间"同口径
|
|
166
|
+
const pinned = Number(this.attr('timestamp-ms'));
|
|
167
|
+
const draft = {
|
|
168
|
+
buyer: inputByName('buyer').trim(),
|
|
169
|
+
seller: (this.attr('seller') ?? '').trim(),
|
|
170
|
+
order_ref: (this.attr('order-ref') ?? '').trim(),
|
|
171
|
+
timestamp_ms: Number.isFinite(pinned) && pinned > 0 ? Math.trunc(pinned) : Date.now(),
|
|
172
|
+
session_ref: this.attr('session-ref') ?? '',
|
|
173
|
+
lines: lines.map((line) => ({
|
|
174
|
+
goods_id: line.goods_id ?? line.sku_id,
|
|
175
|
+
sku_id: line.sku_id,
|
|
176
|
+
quantity: String(line.quantity),
|
|
177
|
+
price_per_unit: String(line.price_per_unit),
|
|
178
|
+
})),
|
|
179
|
+
total_micro_tct: this.totalMicroTct(lines),
|
|
180
|
+
contact: {
|
|
181
|
+
name: inputByName('name').trim(),
|
|
182
|
+
phone: inputByName('phone').trim(),
|
|
183
|
+
address: inputByName('address').trim(),
|
|
184
|
+
},
|
|
185
|
+
intent: null,
|
|
186
|
+
};
|
|
187
|
+
const missing = missingFields({ buyer: draft.buyer, contact: draft.contact, lines });
|
|
188
|
+
if (missing !== null) {
|
|
189
|
+
this.fail('invalid_form', missing);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (draft.order_ref === '') {
|
|
193
|
+
// 订单引用由平台侧给(卖家代建单:链上订单号由守护进程生成),UI 不自己编 —— 编了就无法与后端对账
|
|
194
|
+
this.fail('invalid_form', '缺少订单引用(data-order-ref)');
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (this.intentBuilder !== null) {
|
|
198
|
+
try {
|
|
199
|
+
draft.intent = this.intentBuilder(draft);
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
const code = error.code ?? 'intent_failed';
|
|
203
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
204
|
+
this.fail(code, message);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
this.showError('');
|
|
209
|
+
this.emit(base_js_1.SHOP_UI_EVENTS.ORDER_SUBMIT, draft);
|
|
210
|
+
}
|
|
211
|
+
fail(code, message) {
|
|
212
|
+
this.showError(message);
|
|
213
|
+
this.emit(base_js_1.SHOP_UI_EVENTS.ORDER_ERROR, { code, message });
|
|
214
|
+
}
|
|
215
|
+
showError(message) {
|
|
216
|
+
const slot = this.$('[data-error-slot]');
|
|
217
|
+
if (slot !== null)
|
|
218
|
+
slot.textContent = message;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
exports.TbOrderForm = TbOrderForm;
|
|
222
|
+
//# sourceMappingURL=order-form.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"order-form.js","sourceRoot":"","sources":["../../src/components/order-form.ts"],"names":[],"mappings":"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"}
|
|
@@ -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/esm/base.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
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
|
+
import { HOST_STYLE } from './tokens.js';
|
|
18
|
+
/** 组件对外事件名(一处定义,避免各处手写字符串写歪) */
|
|
19
|
+
export const SHOP_UI_EVENTS = Object.freeze({
|
|
20
|
+
/** `tb-goods-list`:点了某个商品 */
|
|
21
|
+
GOODS_SELECT: 'tb:goods-select',
|
|
22
|
+
/** `tb-goods-detail`:切换规格/数量 */
|
|
23
|
+
SKU_CHANGE: 'tb:sku-change',
|
|
24
|
+
/** `tb-goods-detail`:点"立即购买"(下方单意愿的输入) */
|
|
25
|
+
GOODS_BUY: 'tb:goods-buy',
|
|
26
|
+
/** `tb-order-form`:点"提交订单"且表单合法(含意愿 + memo,若有注入 builder) */
|
|
27
|
+
ORDER_SUBMIT: 'tb:order-submit',
|
|
28
|
+
/** `tb-order-form`:表单不合法或意愿构造失败(**不会**同时发 ORDER_SUBMIT) */
|
|
29
|
+
ORDER_ERROR: 'tb:order-error',
|
|
30
|
+
/** `tb-account-panel`:点了账户面板上的某个入口 */
|
|
31
|
+
ACCOUNT_ACTION: 'tb:account-action',
|
|
32
|
+
});
|
|
33
|
+
/** HTML 转义:组件渲染的是**外部数据**(商品标题、订单备注),不转义就是 XSS 入口 */
|
|
34
|
+
export function escapeHtml(value) {
|
|
35
|
+
return String(value ?? '').replace(/[&<>"']/g, (ch) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[ch] ?? ch);
|
|
36
|
+
}
|
|
37
|
+
export class TbElement extends HTMLElement {
|
|
38
|
+
root;
|
|
39
|
+
constructor() {
|
|
40
|
+
super();
|
|
41
|
+
this.root = this.attachShadow({ mode: 'open' });
|
|
42
|
+
}
|
|
43
|
+
/** 渲染后绑定事件(每次都重新绑:整体重渲染会丢监听器) */
|
|
44
|
+
bind() {
|
|
45
|
+
/* 子类按需覆盖 */
|
|
46
|
+
}
|
|
47
|
+
connectedCallback() {
|
|
48
|
+
this.render();
|
|
49
|
+
}
|
|
50
|
+
attributeChangedCallback(_name, oldValue, newValue) {
|
|
51
|
+
if (oldValue !== newValue && this.isConnected)
|
|
52
|
+
this.render();
|
|
53
|
+
}
|
|
54
|
+
/** 渲染 = 样式 + 结构 + 绑定(一次性) */
|
|
55
|
+
render() {
|
|
56
|
+
try {
|
|
57
|
+
this.root.innerHTML = `<style>${HOST_STYLE}${this.styles()}</style>${this.template()}`;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
61
|
+
this.root.innerHTML = `<style>${HOST_STYLE}${this.styles()}</style>${stateBlock('error', message)}`;
|
|
62
|
+
}
|
|
63
|
+
this.bind();
|
|
64
|
+
}
|
|
65
|
+
/** `data-*` 属性读取(组件的唯一入参通道) */
|
|
66
|
+
attr(name) {
|
|
67
|
+
return this.getAttribute(`data-${name}`);
|
|
68
|
+
}
|
|
69
|
+
/** `data-*` 属性 → JSON(解析失败返回 error,不抛) */
|
|
70
|
+
jsonAttr(name, fallback) {
|
|
71
|
+
const raw = this.attr(name);
|
|
72
|
+
if (raw === null || raw === '')
|
|
73
|
+
return { value: fallback, error: null };
|
|
74
|
+
try {
|
|
75
|
+
const parsed = JSON.parse(raw);
|
|
76
|
+
if (parsed === null || typeof parsed !== 'object') {
|
|
77
|
+
return { value: fallback, error: `data-${name} 必须是 JSON 对象或数组` };
|
|
78
|
+
}
|
|
79
|
+
return { value: parsed, error: null };
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return { value: fallback, error: `data-${name} 不是合法 JSON` };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** 组件状态:`data-state` 显式指定优先,否则按错误/数据推断 */
|
|
86
|
+
uiState(hasData, parseError) {
|
|
87
|
+
const declared = this.attr('state');
|
|
88
|
+
if (declared === 'loading' || declared === 'error' || declared === 'empty' || declared === 'ready') {
|
|
89
|
+
return declared;
|
|
90
|
+
}
|
|
91
|
+
if (parseError)
|
|
92
|
+
return 'error';
|
|
93
|
+
return hasData ? 'ready' : 'empty';
|
|
94
|
+
}
|
|
95
|
+
/** 错误文案:`data-error` 优先,其次解析错误,最后兜底一句人话(不吐技术报错 —— 规范 §2.4) */
|
|
96
|
+
errorText(parseError) {
|
|
97
|
+
const declared = this.attr('error');
|
|
98
|
+
if (declared !== null && declared !== '')
|
|
99
|
+
return declared;
|
|
100
|
+
if (parseError)
|
|
101
|
+
return parseError;
|
|
102
|
+
return '服务暂时不可用,请刷新重试';
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* 三态的渲染入口:**只有 error 态用 `data-error`/解析错误当文案**,
|
|
106
|
+
* 空态与加载态用人话默认值(否则空列表会显示"服务暂时不可用")。
|
|
107
|
+
*/
|
|
108
|
+
renderState(state, parseError) {
|
|
109
|
+
if (state === 'ready')
|
|
110
|
+
return '';
|
|
111
|
+
if (state === 'error')
|
|
112
|
+
return stateBlock('error', this.errorText(parseError));
|
|
113
|
+
return stateBlock(state);
|
|
114
|
+
}
|
|
115
|
+
/** 对外抛事件(bubbles + composed:能穿过 shadow 边界) */
|
|
116
|
+
emit(type, detail) {
|
|
117
|
+
this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));
|
|
118
|
+
}
|
|
119
|
+
/** shadow 内查询(单/多) */
|
|
120
|
+
$(selector) {
|
|
121
|
+
return this.root.querySelector(selector);
|
|
122
|
+
}
|
|
123
|
+
$$(selector) {
|
|
124
|
+
return Array.from(this.root.querySelectorAll(selector));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** 加载 / 错误 / 空 三态的结构(各组件复用;`ready` 返回空串) */
|
|
128
|
+
export function stateBlock(state, message) {
|
|
129
|
+
if (state === 'ready')
|
|
130
|
+
return '';
|
|
131
|
+
if (state === 'loading')
|
|
132
|
+
return `<div class="state" data-state-block="loading">正在加载…</div>`;
|
|
133
|
+
if (state === 'empty')
|
|
134
|
+
return `<div class="state" data-state-block="empty">${escapeHtml(message ?? '这里还没有内容')}</div>`;
|
|
135
|
+
return `<div class="state state-error" data-state-block="error">${escapeHtml(message ?? '服务暂时不可用,请刷新重试')}</div>`;
|
|
136
|
+
}
|
|
137
|
+
/** 三态样式(各组件在 `styles()` 里拼进去;只引用 tokens) */
|
|
138
|
+
export const STATE_STYLE = [
|
|
139
|
+
'.state{padding:var(--tb-space-lg) var(--tb-space-md);color:var(--tb-color-text-secondary);',
|
|
140
|
+
'font-size:var(--tb-font-size-secondary);text-align:center;}',
|
|
141
|
+
'.state-error{color:var(--tb-color-danger);}',
|
|
142
|
+
].join('');
|
|
143
|
+
//# sourceMappingURL=base.js.map
|
|
@@ -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"}
|