@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,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<tb-order-form>` —— 下单表单(一屏一个动作:**提交订单**)。
|
|
3
|
+
*
|
|
4
|
+
* ## 契约
|
|
5
|
+
*
|
|
6
|
+
* | 入参(`data-*`) | 说明 |
|
|
7
|
+
* |---|---|
|
|
8
|
+
* | `data-lines` | JSON 数组:`[{ goods_id?, sku_id, quantity, price_per_unit, title? }]`(`title` 只用于显示) |
|
|
9
|
+
* | `data-buyer` | 买家 tct1 地址(预填;可编辑) |
|
|
10
|
+
* | `data-seller` | 卖家 tct1 地址(只读显示) |
|
|
11
|
+
* | `data-order-ref` | 平台侧订单引用(可选;不填由宿主/守护进程生成) |
|
|
12
|
+
* | `data-session-ref` | 会话引用(可选) |
|
|
13
|
+
* | `data-timestamp-ms` | 固定毫秒时间戳(可选;**重试请传同一个值** → 意愿 hash 不变 → 幂等) |
|
|
14
|
+
* | `data-state` / `data-error` | 同其它组件 |
|
|
15
|
+
*
|
|
16
|
+
* | 出参(`CustomEvent`) | `detail` |
|
|
17
|
+
* |---|---|
|
|
18
|
+
* | `tb:order-submit` | `{ buyer, seller, order_ref, timestamp_ms, session_ref, lines, total_micro_tct, contact, intent }` |
|
|
19
|
+
* | `tb:order-error` | `{ code, message }` —— 表单不合法或意愿构造失败(此时**不发** `tb:order-submit`) |
|
|
20
|
+
*
|
|
21
|
+
* `intent = { intent, canonical, hash, memo } | null`:由**注入的** `intentBuilder` 产出
|
|
22
|
+
* (`el.intentBuilder = (input) => buildOrderIntentHash(input.lines, input)`,来自 `@trustbase/shop`)。
|
|
23
|
+
* 为什么是注入:本包**零运行时依赖**(不 import `@trustbase/shop`),而意愿口径是唯一真源,
|
|
24
|
+
* 只能有一个实现 —— 与其在 UI 里抄一遍,不如让调用方把真实现塞进来。
|
|
25
|
+
*
|
|
26
|
+
* ⚠️ 组件**不签名、不持私钥**:提交只产出"意愿 + memo",签名由调用方交给商户守护进程。
|
|
27
|
+
*/
|
|
28
|
+
import { TbElement } from '../base.js';
|
|
29
|
+
/** 表单行(`title` 仅供显示,不进任何契约) */
|
|
30
|
+
export interface OrderLineView {
|
|
31
|
+
goods_id?: string;
|
|
32
|
+
sku_id: string;
|
|
33
|
+
quantity: string | number;
|
|
34
|
+
price_per_unit: string | number;
|
|
35
|
+
title?: string;
|
|
36
|
+
}
|
|
37
|
+
/** 收货信息(只交给商户服务;链上只记 `shipping_address_hash`,由守护进程加密后上链) */
|
|
38
|
+
export interface ContactView {
|
|
39
|
+
name: string;
|
|
40
|
+
phone: string;
|
|
41
|
+
address: string;
|
|
42
|
+
}
|
|
43
|
+
/** 提交事件的 detail */
|
|
44
|
+
export interface OrderSubmitDetail {
|
|
45
|
+
buyer: string;
|
|
46
|
+
seller: string;
|
|
47
|
+
order_ref: string;
|
|
48
|
+
timestamp_ms: number;
|
|
49
|
+
session_ref: string;
|
|
50
|
+
lines: Array<{
|
|
51
|
+
goods_id: string;
|
|
52
|
+
sku_id: string;
|
|
53
|
+
quantity: string;
|
|
54
|
+
price_per_unit: string;
|
|
55
|
+
}>;
|
|
56
|
+
total_micro_tct: string;
|
|
57
|
+
contact: ContactView;
|
|
58
|
+
intent: {
|
|
59
|
+
intent: unknown;
|
|
60
|
+
canonical: string;
|
|
61
|
+
hash: string;
|
|
62
|
+
memo: string;
|
|
63
|
+
} | null;
|
|
64
|
+
}
|
|
65
|
+
/** 注入的意愿构造器(返回值原样透传给出参;抛错则转成 `tb:order-error`) */
|
|
66
|
+
export type IntentBuilder = (input: OrderSubmitDetail) => {
|
|
67
|
+
intent: unknown;
|
|
68
|
+
canonical: string;
|
|
69
|
+
hash: string;
|
|
70
|
+
memo: string;
|
|
71
|
+
};
|
|
72
|
+
export declare class TbOrderForm extends TbElement {
|
|
73
|
+
static get observedAttributes(): string[];
|
|
74
|
+
protected readonly tag = "tb-order-form";
|
|
75
|
+
/** 意愿构造器(宿主注入;不注入则 `intent` 为 null,宿主自己算) */
|
|
76
|
+
intentBuilder: IntentBuilder | null;
|
|
77
|
+
protected styles(): string;
|
|
78
|
+
private lines;
|
|
79
|
+
private totalMicroTct;
|
|
80
|
+
protected template(): string;
|
|
81
|
+
protected bind(): void;
|
|
82
|
+
/** 读表单 → 校验 → 造意愿 → 抛事件(错误走 inline 提示 + `tb:order-error`,不重渲染以免丢掉用户输入) */
|
|
83
|
+
private submit;
|
|
84
|
+
private fail;
|
|
85
|
+
private showError;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=order-form.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"order-form.d.ts","sourceRoot":"","sources":["../../../src/components/order-form.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAA+B,SAAS,EAA0B,MAAM,YAAY,CAAC;AAI5F,+BAA+B;AAC/B,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,4DAA4D;AAC5D,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,mBAAmB;AACnB,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7F,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,WAAW,CAAC;IACrB,MAAM,EAAE;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CACnF;AAED,kDAAkD;AAClD,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,iBAAiB,KAAK;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAgB7H,qBAAa,WAAY,SAAQ,SAAS;IACxC,MAAM,KAAK,kBAAkB,IAAI,MAAM,EAAE,CAExC;IAED,SAAS,CAAC,QAAQ,CAAC,GAAG,mBAAmB;IAEzC,6CAA6C;IAC7C,aAAa,EAAE,aAAa,GAAG,IAAI,CAAQ;IAE3C,SAAS,CAAC,MAAM,IAAI,MAAM;IAqC1B,OAAO,CAAC,KAAK;IAKb,OAAO,CAAC,aAAa;IAIrB,SAAS,CAAC,QAAQ,IAAI,MAAM;cAwDT,IAAI,IAAI,IAAI;IAM/B,0EAA0E;IAC1E,OAAO,CAAC,MAAM;IAuDd,OAAO,CAAC,IAAI;IAKZ,OAAO,CAAC,SAAS;CAIlB"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<tb-order-form>` —— 下单表单(一屏一个动作:**提交订单**)。
|
|
3
|
+
*
|
|
4
|
+
* ## 契约
|
|
5
|
+
*
|
|
6
|
+
* | 入参(`data-*`) | 说明 |
|
|
7
|
+
* |---|---|
|
|
8
|
+
* | `data-lines` | JSON 数组:`[{ goods_id?, sku_id, quantity, price_per_unit, title? }]`(`title` 只用于显示) |
|
|
9
|
+
* | `data-buyer` | 买家 tct1 地址(预填;可编辑) |
|
|
10
|
+
* | `data-seller` | 卖家 tct1 地址(只读显示) |
|
|
11
|
+
* | `data-order-ref` | 平台侧订单引用(可选;不填由宿主/守护进程生成) |
|
|
12
|
+
* | `data-session-ref` | 会话引用(可选) |
|
|
13
|
+
* | `data-timestamp-ms` | 固定毫秒时间戳(可选;**重试请传同一个值** → 意愿 hash 不变 → 幂等) |
|
|
14
|
+
* | `data-state` / `data-error` | 同其它组件 |
|
|
15
|
+
*
|
|
16
|
+
* | 出参(`CustomEvent`) | `detail` |
|
|
17
|
+
* |---|---|
|
|
18
|
+
* | `tb:order-submit` | `{ buyer, seller, order_ref, timestamp_ms, session_ref, lines, total_micro_tct, contact, intent }` |
|
|
19
|
+
* | `tb:order-error` | `{ code, message }` —— 表单不合法或意愿构造失败(此时**不发** `tb:order-submit`) |
|
|
20
|
+
*
|
|
21
|
+
* `intent = { intent, canonical, hash, memo } | null`:由**注入的** `intentBuilder` 产出
|
|
22
|
+
* (`el.intentBuilder = (input) => buildOrderIntentHash(input.lines, input)`,来自 `@trustbase/shop`)。
|
|
23
|
+
* 为什么是注入:本包**零运行时依赖**(不 import `@trustbase/shop`),而意愿口径是唯一真源,
|
|
24
|
+
* 只能有一个实现 —— 与其在 UI 里抄一遍,不如让调用方把真实现塞进来。
|
|
25
|
+
*
|
|
26
|
+
* ⚠️ 组件**不签名、不持私钥**:提交只产出"意愿 + memo",签名由调用方交给商户守护进程。
|
|
27
|
+
*/
|
|
28
|
+
import { SHOP_UI_EVENTS, STATE_STYLE, TbElement, escapeHtml } from '../base.js';
|
|
29
|
+
import { AMOUNT_STYLE } from '../tokens.js';
|
|
30
|
+
import { formatAmountParts, multiplyAmounts, sumAmounts } from '../format.js';
|
|
31
|
+
/** 字段级校验(够用就好:更深的口径校验在 `@trustbase/shop`,它的报错会被转成 order-error) */
|
|
32
|
+
function missingFields(input) {
|
|
33
|
+
if (input.lines.length === 0)
|
|
34
|
+
return '还没有选择商品';
|
|
35
|
+
if (input.buyer.trim() === '')
|
|
36
|
+
return '请填写买家 tct1 地址';
|
|
37
|
+
if (input.contact.name.trim() === '')
|
|
38
|
+
return '请填写收货人';
|
|
39
|
+
if (input.contact.phone.trim() === '')
|
|
40
|
+
return '请填写联系电话';
|
|
41
|
+
if (input.contact.address.trim() === '')
|
|
42
|
+
return '请填写收货地址';
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
export class TbOrderForm extends TbElement {
|
|
46
|
+
static get observedAttributes() {
|
|
47
|
+
return ['data-lines', 'data-buyer', 'data-seller', 'data-order-ref', 'data-session-ref', 'data-state', 'data-error'];
|
|
48
|
+
}
|
|
49
|
+
tag = 'tb-order-form';
|
|
50
|
+
/** 意愿构造器(宿主注入;不注入则 `intent` 为 null,宿主自己算) */
|
|
51
|
+
intentBuilder = null;
|
|
52
|
+
styles() {
|
|
53
|
+
return [
|
|
54
|
+
STATE_STYLE,
|
|
55
|
+
'.wrap{padding:var(--tb-space-md);}',
|
|
56
|
+
'.section{margin-bottom:var(--tb-space-lg);}',
|
|
57
|
+
'.section-title{font-size:var(--tb-font-size-secondary);color:var(--tb-color-text-secondary);',
|
|
58
|
+
'margin-bottom:var(--tb-space-xs);}',
|
|
59
|
+
'.rows{border-top:var(--tb-border-width) solid var(--tb-color-divider);}',
|
|
60
|
+
'.row{display:flex;align-items:baseline;gap:var(--tb-space-md);padding:var(--tb-space-sm) 0;',
|
|
61
|
+
'border-bottom:var(--tb-border-width) solid var(--tb-color-divider);}',
|
|
62
|
+
'.row-title{flex:1 1 auto;min-width:0;font-size:var(--tb-font-size-body);}',
|
|
63
|
+
'.row-title span{display:block;font-size:var(--tb-font-size-caption);color:var(--tb-color-text-tertiary);}',
|
|
64
|
+
`.row-amount{${AMOUNT_STYLE}font-size:var(--tb-font-size-secondary);text-align:right;white-space:nowrap;}`,
|
|
65
|
+
'.total{display:flex;justify-content:flex-end;align-items:baseline;gap:var(--tb-space-xs);',
|
|
66
|
+
'padding-top:var(--tb-space-sm);}',
|
|
67
|
+
'.total-label{font-size:var(--tb-font-size-secondary);color:var(--tb-color-text-secondary);}',
|
|
68
|
+
`.total .amount{${AMOUNT_STYLE}font-size:var(--tb-font-size-heading);font-weight:var(--tb-font-weight-semibold);}`,
|
|
69
|
+
'.total .unit{font-size:var(--tb-font-size-secondary);color:var(--tb-color-text-secondary);}',
|
|
70
|
+
'.field{display:block;margin-bottom:var(--tb-space-sm);}',
|
|
71
|
+
'.label{display:block;font-size:var(--tb-font-size-caption);color:var(--tb-color-text-secondary);',
|
|
72
|
+
'margin-bottom:var(--tb-space-xxs);}',
|
|
73
|
+
'.input{width:100%;padding:var(--tb-space-sm);border:var(--tb-border-width) solid var(--tb-color-border);',
|
|
74
|
+
'border-radius:var(--tb-radius-control);background:var(--tb-color-background);',
|
|
75
|
+
'font-size:var(--tb-font-size-body);}',
|
|
76
|
+
'.input:focus{outline:none;border-color:var(--tb-color-accent);}',
|
|
77
|
+
'.input[readonly]{background:var(--tb-color-surface);color:var(--tb-color-text-secondary);}',
|
|
78
|
+
'.primary{width:100%;padding:var(--tb-space-md);border:none;border-radius:var(--tb-radius-control);',
|
|
79
|
+
'background:var(--tb-color-accent);color:var(--tb-color-on-accent);font-size:var(--tb-font-size-body);',
|
|
80
|
+
'font-weight:var(--tb-font-weight-semibold);}',
|
|
81
|
+
'.primary[disabled]{background:var(--tb-color-surface);color:var(--tb-color-text-tertiary);cursor:not-allowed;}',
|
|
82
|
+
'.note{margin-top:var(--tb-space-xs);font-size:var(--tb-font-size-caption);color:var(--tb-color-text-tertiary);',
|
|
83
|
+
'text-align:center;}',
|
|
84
|
+
'.error-slot{margin-top:var(--tb-space-xs);min-height:var(--tb-space-md);',
|
|
85
|
+
'font-size:var(--tb-font-size-caption);color:var(--tb-color-danger);text-align:center;}',
|
|
86
|
+
].join('');
|
|
87
|
+
}
|
|
88
|
+
lines() {
|
|
89
|
+
const { value } = this.jsonAttr('lines', []);
|
|
90
|
+
return Array.isArray(value) ? value.filter((line) => line && typeof line.sku_id === 'string') : [];
|
|
91
|
+
}
|
|
92
|
+
totalMicroTct(lines) {
|
|
93
|
+
return sumAmounts(lines.map((line) => multiplyAmounts(String(line.price_per_unit), String(line.quantity))));
|
|
94
|
+
}
|
|
95
|
+
template() {
|
|
96
|
+
const { error } = this.jsonAttr('lines', []);
|
|
97
|
+
const lines = this.lines();
|
|
98
|
+
const state = this.uiState(lines.length > 0, error);
|
|
99
|
+
if (state !== 'ready')
|
|
100
|
+
return this.renderState(state, error);
|
|
101
|
+
const total = this.totalMicroTct(lines);
|
|
102
|
+
const totalParts = formatAmountParts(total);
|
|
103
|
+
return [
|
|
104
|
+
'<div class="wrap">',
|
|
105
|
+
'<div class="section" data-block="lines">',
|
|
106
|
+
`<div class="section-title">订单内容(${escapeHtml(String(lines.length))} 项)</div>`,
|
|
107
|
+
'<div class="rows">',
|
|
108
|
+
lines
|
|
109
|
+
.map((line) => {
|
|
110
|
+
const parts = formatAmountParts(multiplyAmounts(String(line.price_per_unit), String(line.quantity)));
|
|
111
|
+
const title = line.title ?? line.sku_id;
|
|
112
|
+
return [
|
|
113
|
+
'<div class="row">',
|
|
114
|
+
`<div class="row-title">${escapeHtml(title)}<span>${escapeHtml(line.sku_id)} × ${escapeHtml(String(line.quantity))}</span></div>`,
|
|
115
|
+
`<div class="row-amount">${escapeHtml(parts.value)} ${escapeHtml(parts.unit)}</div>`,
|
|
116
|
+
'</div>',
|
|
117
|
+
].join('');
|
|
118
|
+
})
|
|
119
|
+
.join(''),
|
|
120
|
+
'</div>',
|
|
121
|
+
`<div class="total"><span class="total-label">合计</span><span class="amount">${escapeHtml(totalParts.value)}</span><span class="unit">${escapeHtml(totalParts.unit)}</span></div>`,
|
|
122
|
+
'</div>',
|
|
123
|
+
'<div class="section" data-block="buyer">',
|
|
124
|
+
'<div class="section-title">买家身份</div>',
|
|
125
|
+
'<label class="field">',
|
|
126
|
+
'<span class="label">买家 tct1 地址(链上不收 buyer 私钥,只认地址)</span>',
|
|
127
|
+
`<input class="input" data-field="buyer" type="text" inputmode="latin" autocomplete="off" spellcheck="false" value="${escapeHtml(this.attr('buyer') ?? '')}" placeholder="tct1..." />`,
|
|
128
|
+
'</label>',
|
|
129
|
+
'<label class="field">',
|
|
130
|
+
'<span class="label">卖家(只读)</span>',
|
|
131
|
+
`<input class="input" data-field="seller" type="text" readonly value="${escapeHtml(this.attr('seller') ?? '')}" />`,
|
|
132
|
+
'</label>',
|
|
133
|
+
'</div>',
|
|
134
|
+
'<div class="section" data-block="contact">',
|
|
135
|
+
'<div class="section-title">收货信息</div>',
|
|
136
|
+
'<label class="field"><span class="label">收货人</span>',
|
|
137
|
+
'<input class="input" data-field="name" type="text" autocomplete="name" /></label>',
|
|
138
|
+
'<label class="field"><span class="label">联系电话</span>',
|
|
139
|
+
'<input class="input" data-field="phone" type="tel" inputmode="tel" autocomplete="tel" /></label>',
|
|
140
|
+
'<label class="field"><span class="label">收货地址</span>',
|
|
141
|
+
'<input class="input" data-field="address" type="text" autocomplete="street-address" /></label>',
|
|
142
|
+
'<div class="note">收货信息交给商户服务;链上只记加密后的地址哈希</div>',
|
|
143
|
+
'</div>',
|
|
144
|
+
'<button class="primary" type="button" data-action="submit">提交订单</button>',
|
|
145
|
+
'<div class="error-slot" data-error-slot role="alert"></div>',
|
|
146
|
+
'<div class="note">提交后由商户服务签名上链(本页面不接触私钥)</div>',
|
|
147
|
+
'</div>',
|
|
148
|
+
].join('');
|
|
149
|
+
}
|
|
150
|
+
bind() {
|
|
151
|
+
this.$('[data-action="submit"]')?.addEventListener('click', () => {
|
|
152
|
+
this.submit();
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/** 读表单 → 校验 → 造意愿 → 抛事件(错误走 inline 提示 + `tb:order-error`,不重渲染以免丢掉用户输入) */
|
|
156
|
+
submit() {
|
|
157
|
+
const lines = this.lines();
|
|
158
|
+
const inputByName = (name) => {
|
|
159
|
+
const el = this.$(`[data-field="${name}"]`);
|
|
160
|
+
return el === null ? '' : el.value;
|
|
161
|
+
};
|
|
162
|
+
// 时间戳:宿主固定(重试幂等)优先,否则取当前毫秒 —— 与链侧 normalizeTimestampMs 的"缺省取当前时间"同口径
|
|
163
|
+
const pinned = Number(this.attr('timestamp-ms'));
|
|
164
|
+
const draft = {
|
|
165
|
+
buyer: inputByName('buyer').trim(),
|
|
166
|
+
seller: (this.attr('seller') ?? '').trim(),
|
|
167
|
+
order_ref: (this.attr('order-ref') ?? '').trim(),
|
|
168
|
+
timestamp_ms: Number.isFinite(pinned) && pinned > 0 ? Math.trunc(pinned) : Date.now(),
|
|
169
|
+
session_ref: this.attr('session-ref') ?? '',
|
|
170
|
+
lines: lines.map((line) => ({
|
|
171
|
+
goods_id: line.goods_id ?? line.sku_id,
|
|
172
|
+
sku_id: line.sku_id,
|
|
173
|
+
quantity: String(line.quantity),
|
|
174
|
+
price_per_unit: String(line.price_per_unit),
|
|
175
|
+
})),
|
|
176
|
+
total_micro_tct: this.totalMicroTct(lines),
|
|
177
|
+
contact: {
|
|
178
|
+
name: inputByName('name').trim(),
|
|
179
|
+
phone: inputByName('phone').trim(),
|
|
180
|
+
address: inputByName('address').trim(),
|
|
181
|
+
},
|
|
182
|
+
intent: null,
|
|
183
|
+
};
|
|
184
|
+
const missing = missingFields({ buyer: draft.buyer, contact: draft.contact, lines });
|
|
185
|
+
if (missing !== null) {
|
|
186
|
+
this.fail('invalid_form', missing);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (draft.order_ref === '') {
|
|
190
|
+
// 订单引用由平台侧给(卖家代建单:链上订单号由守护进程生成),UI 不自己编 —— 编了就无法与后端对账
|
|
191
|
+
this.fail('invalid_form', '缺少订单引用(data-order-ref)');
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (this.intentBuilder !== null) {
|
|
195
|
+
try {
|
|
196
|
+
draft.intent = this.intentBuilder(draft);
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
const code = error.code ?? 'intent_failed';
|
|
200
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
201
|
+
this.fail(code, message);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
this.showError('');
|
|
206
|
+
this.emit(SHOP_UI_EVENTS.ORDER_SUBMIT, draft);
|
|
207
|
+
}
|
|
208
|
+
fail(code, message) {
|
|
209
|
+
this.showError(message);
|
|
210
|
+
this.emit(SHOP_UI_EVENTS.ORDER_ERROR, { code, message });
|
|
211
|
+
}
|
|
212
|
+
showError(message) {
|
|
213
|
+
const slot = this.$('[data-error-slot]');
|
|
214
|
+
if (slot !== null)
|
|
215
|
+
slot.textContent = message;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
//# 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,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 视图层最小格式化 —— **零依赖、纯函数**,口径与 `@trustbase/shop` 的模型层一致
|
|
3
|
+
* (`shop` 不能是本包的运行时依赖:UI 组件要零依赖;两边口径由交叉验证测试锁着,
|
|
4
|
+
* 见 `test/format.test.ts` 里直接 import `../../shop/src/index` 比对的断言)。
|
|
5
|
+
*
|
|
6
|
+
* 只放"界面上要显示什么",不放任何业务判断(业务判断在 `@trustbase/shop`)。
|
|
7
|
+
*/
|
|
8
|
+
/** 金额的两段式(数字 + 单位分开渲染:数字走等宽、单位走次要色,见规范 §2.5) */
|
|
9
|
+
export interface AmountParts {
|
|
10
|
+
/** 例:`12.30` */
|
|
11
|
+
value: string;
|
|
12
|
+
/** 例:`TCT` */
|
|
13
|
+
unit: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* microTCT 整数字符串 → `{ value, unit }`(最少 2 位小数、最多 6 位、去掉多余的 0)。
|
|
17
|
+
* `"12300000"` → `{ value: "12.30", unit: "TCT" }`。
|
|
18
|
+
*/
|
|
19
|
+
export declare function formatAmountParts(microTct: string, unit?: string): AmountParts;
|
|
20
|
+
/** microTCT → `"12.30 TCT"`(金额永远带单位) */
|
|
21
|
+
export declare function formatAmount(microTct: string, unit?: string): string;
|
|
22
|
+
/** 单价 × 数量的合计(整数运算,无浮点;只看金额,不做任何校验) */
|
|
23
|
+
export declare function multiplyAmounts(pricePerUnit: string, quantity: string | number): string;
|
|
24
|
+
/** 多行金额合计 */
|
|
25
|
+
export declare function sumAmounts(amounts: readonly string[]): string;
|
|
26
|
+
/** 地址缩写(中间省略;完整地址由 `title` 属性承载,界面不塞一屏字符) */
|
|
27
|
+
export declare function shortAddress(address: string, head?: number, tail?: number): string;
|
|
28
|
+
/** 订单状态 → 界面文案(状态值口径见 `@trustbase/shop` 的 `ORDER_STATUS`,测试比对过) */
|
|
29
|
+
export declare const ORDER_STATUS_LABELS: Readonly<Record<string, string>>;
|
|
30
|
+
export declare function orderStatusLabel(status: string): string;
|
|
31
|
+
/** 状态文案(三种状态都要有,且说人话 —— 设计规范 §2.4) */
|
|
32
|
+
export declare const STATE_TEXT: Readonly<{
|
|
33
|
+
loading: "正在加载…";
|
|
34
|
+
error: "服务暂时不可用,请刷新重试";
|
|
35
|
+
empty: "这里还没有内容";
|
|
36
|
+
}>;
|
|
37
|
+
/** 商品描述行:规格数 + 库存(能用一个列表说清就不上卡片 —— 规范 §2.8) */
|
|
38
|
+
export declare function goodsMetaLine(skuCount: number, stock: number): string;
|
|
39
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/format.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,gDAAgD;AAChD,MAAM,WAAW,WAAW;IAC1B,gBAAgB;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,SAAQ,GAAG,WAAW,CAS7E;AAED,wCAAwC;AACxC,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,SAAQ,GAAG,MAAM,CAGnE;AAED,uCAAuC;AACvC,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAIvF;AAED,aAAa;AACb,wBAAgB,UAAU,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAI7D;AAED,6CAA6C;AAC7C,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,SAAK,EAAE,IAAI,SAAI,GAAG,MAAM,CAIzE;AAED,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAO/D,CAAC;AAEH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,sCAAsC;AACtC,eAAO,MAAM,UAAU;;;;EAIrB,CAAC;AAEH,+CAA+C;AAC/C,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGrE"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 视图层最小格式化 —— **零依赖、纯函数**,口径与 `@trustbase/shop` 的模型层一致
|
|
3
|
+
* (`shop` 不能是本包的运行时依赖:UI 组件要零依赖;两边口径由交叉验证测试锁着,
|
|
4
|
+
* 见 `test/format.test.ts` 里直接 import `../../shop/src/index` 比对的断言)。
|
|
5
|
+
*
|
|
6
|
+
* 只放"界面上要显示什么",不放任何业务判断(业务判断在 `@trustbase/shop`)。
|
|
7
|
+
*/
|
|
8
|
+
/** 1 TCT = 1_000_000 microTCT / utct(链上 `TCT_DECIMALS = 6`) */
|
|
9
|
+
const MICRO_PER_TCT = BigInt(1000000);
|
|
10
|
+
const DECIMAL_INTEGER_RE = /^(0|[1-9][0-9]*)$/;
|
|
11
|
+
/**
|
|
12
|
+
* microTCT 整数字符串 → `{ value, unit }`(最少 2 位小数、最多 6 位、去掉多余的 0)。
|
|
13
|
+
* `"12300000"` → `{ value: "12.30", unit: "TCT" }`。
|
|
14
|
+
*/
|
|
15
|
+
export function formatAmountParts(microTct, unit = 'TCT') {
|
|
16
|
+
const text = String(microTct ?? '').trim();
|
|
17
|
+
if (!DECIMAL_INTEGER_RE.test(text))
|
|
18
|
+
return { value: '—', unit };
|
|
19
|
+
const micro = BigInt(text);
|
|
20
|
+
const whole = micro / MICRO_PER_TCT;
|
|
21
|
+
const frac = micro % MICRO_PER_TCT;
|
|
22
|
+
if (frac === BigInt(0))
|
|
23
|
+
return { value: `${whole.toString()}.00`, unit };
|
|
24
|
+
const digits = frac.toString().padStart(6, '0').replace(/0+$/, '');
|
|
25
|
+
return { value: `${whole.toString()}.${digits.padEnd(2, '0')}`, unit };
|
|
26
|
+
}
|
|
27
|
+
/** microTCT → `"12.30 TCT"`(金额永远带单位) */
|
|
28
|
+
export function formatAmount(microTct, unit = 'TCT') {
|
|
29
|
+
const parts = formatAmountParts(microTct, unit);
|
|
30
|
+
return `${parts.value} ${parts.unit}`;
|
|
31
|
+
}
|
|
32
|
+
/** 单价 × 数量的合计(整数运算,无浮点;只看金额,不做任何校验) */
|
|
33
|
+
export function multiplyAmounts(pricePerUnit, quantity) {
|
|
34
|
+
const price = DECIMAL_INTEGER_RE.test(String(pricePerUnit)) ? BigInt(String(pricePerUnit)) : BigInt(0);
|
|
35
|
+
const qty = DECIMAL_INTEGER_RE.test(String(quantity)) ? BigInt(String(quantity)) : BigInt(0);
|
|
36
|
+
return (price * qty).toString();
|
|
37
|
+
}
|
|
38
|
+
/** 多行金额合计 */
|
|
39
|
+
export function sumAmounts(amounts) {
|
|
40
|
+
return amounts
|
|
41
|
+
.reduce((sum, amount) => sum + (DECIMAL_INTEGER_RE.test(amount) ? BigInt(amount) : BigInt(0)), BigInt(0))
|
|
42
|
+
.toString();
|
|
43
|
+
}
|
|
44
|
+
/** 地址缩写(中间省略;完整地址由 `title` 属性承载,界面不塞一屏字符) */
|
|
45
|
+
export function shortAddress(address, head = 10, tail = 6) {
|
|
46
|
+
const text = String(address ?? '');
|
|
47
|
+
if (text.length <= head + tail + 1)
|
|
48
|
+
return text;
|
|
49
|
+
return `${text.slice(0, head)}…${text.slice(-tail)}`;
|
|
50
|
+
}
|
|
51
|
+
/** 订单状态 → 界面文案(状态值口径见 `@trustbase/shop` 的 `ORDER_STATUS`,测试比对过) */
|
|
52
|
+
export const ORDER_STATUS_LABELS = Object.freeze({
|
|
53
|
+
PENDING: '待支付',
|
|
54
|
+
PAID: '已支付',
|
|
55
|
+
SHIPPED: '已发货',
|
|
56
|
+
CONFIRMED: '已确认收货',
|
|
57
|
+
CANCELLED: '已取消',
|
|
58
|
+
DISPUTED: '申诉中',
|
|
59
|
+
});
|
|
60
|
+
export function orderStatusLabel(status) {
|
|
61
|
+
return ORDER_STATUS_LABELS[status] ?? String(status ?? '');
|
|
62
|
+
}
|
|
63
|
+
/** 状态文案(三种状态都要有,且说人话 —— 设计规范 §2.4) */
|
|
64
|
+
export const STATE_TEXT = Object.freeze({
|
|
65
|
+
loading: '正在加载…',
|
|
66
|
+
error: '服务暂时不可用,请刷新重试',
|
|
67
|
+
empty: '这里还没有内容',
|
|
68
|
+
});
|
|
69
|
+
/** 商品描述行:规格数 + 库存(能用一个列表说清就不上卡片 —— 规范 §2.8) */
|
|
70
|
+
export function goodsMetaLine(skuCount, stock) {
|
|
71
|
+
const specText = skuCount > 1 ? `${skuCount} 个规格` : '单一规格';
|
|
72
|
+
return stock > 0 ? `${specText} · 剩 ${stock} 件` : `${specText} · 已售罄`;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/format.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@trustbase/shop-ui` —— TrustBase 店铺界面组件(**框架无关 Web Components**)。
|
|
3
|
+
*
|
|
4
|
+
* ## 四个组件(名字与契约都是冻结的)
|
|
5
|
+
*
|
|
6
|
+
* | 元素 | 干什么 | 关键出参事件 |
|
|
7
|
+
* |---|---|---|
|
|
8
|
+
* | `<tb-goods-list>` | 商品列表 | `tb:goods-select` |
|
|
9
|
+
* | `<tb-goods-detail>` | 详情 + 规格选择 | `tb:sku-change` / `tb:goods-buy` |
|
|
10
|
+
* | `<tb-order-form>` | 下单表单(产出**意愿 + memo**) | `tb:order-submit` / `tb:order-error` |
|
|
11
|
+
* | `<tb-account-panel>` | 我的账户:地址/余额/订单占位 | `tb:account-action` |
|
|
12
|
+
*
|
|
13
|
+
* ## 三条硬纪律(都有测试守着)
|
|
14
|
+
*
|
|
15
|
+
* 1. **零运行时依赖**:自己写 Custom Elements,不引任何第三方 UI 框架/库;
|
|
16
|
+
* 连 `@trustbase/shop` 都不 import(意愿口径由宿主**注入** `intentBuilder`,见 `tb-order-form`)。
|
|
17
|
+
* 2. **样式只能引用 tokens**:`src/tokens.ts` 是唯一允许出现视觉字面量的文件,
|
|
18
|
+
* 组件样式里出现 `#4f8ef7` / `13px` / `200ms` 这类字面量 → `test/styles.test.ts` 失败。
|
|
19
|
+
* 3. **不持私钥、不签名**:组件只渲染 + 抛事件;下单只产出"意愿 + memo",
|
|
20
|
+
* 签名由调用方交给商户守护进程(`test/isomorphic.test.ts` 扫源码禁止签名相关标识符)。
|
|
21
|
+
*
|
|
22
|
+
* ## 用法
|
|
23
|
+
*
|
|
24
|
+
* ```html
|
|
25
|
+
* <script type="module">
|
|
26
|
+
* import { registerShopUi, TOKENS_CSS } from '../dist/esm/index.js';
|
|
27
|
+
* registerShopUi();
|
|
28
|
+
* // 想在页面(light DOM)也拿到 tokens(例如自己的壳层样式):
|
|
29
|
+
* document.head.append(Object.assign(document.createElement('style'), { textContent: TOKENS_CSS }));
|
|
30
|
+
* </script>
|
|
31
|
+
* <tb-goods-list data-goods='[{"goods_id":"g1","title":"茶","skus":[...]}]'></tb-goods-list>
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* 移动优先:默认 375 宽可用(`packages/shop-ui/demo/index.html` 就是按 375 设计的演示页)。
|
|
35
|
+
*/
|
|
36
|
+
export { AMOUNT_STYLE, HOST_STYLE, TOKENS, TOKENS_CSS, border, color, font, fontSize, fontWeight, gray, lineHeight, motion, radius, shadow, space, tokensCss, } from './tokens.js';
|
|
37
|
+
export { SHOP_UI_EVENTS, STATE_STYLE, TbElement, escapeHtml, stateBlock, type JsonAttr, type UIState, } from './base.js';
|
|
38
|
+
export { ORDER_STATUS_LABELS, STATE_TEXT, formatAmount, formatAmountParts, goodsMetaLine, multiplyAmounts, orderStatusLabel, shortAddress, sumAmounts, type AmountParts, } from './format.js';
|
|
39
|
+
export { TbGoodsList, type GoodsView } from './components/goods-list.js';
|
|
40
|
+
export { TbGoodsDetail, type GoodsDetailView } from './components/goods-detail.js';
|
|
41
|
+
export { TbOrderForm, type ContactView, type IntentBuilder, type OrderLineView, type OrderSubmitDetail, } from './components/order-form.js';
|
|
42
|
+
export { TbAccountPanel, type AccountOrderView, type AccountView } from './components/account-panel.js';
|
|
43
|
+
export { SHOP_UI_ELEMENTS, registerShopUi, shopUiElementNames, type ShopUiTag } from './register.js';
|
|
44
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EACL,YAAY,EACZ,UAAU,EACV,MAAM,EACN,UAAU,EACV,MAAM,EACN,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,UAAU,EACV,IAAI,EACJ,UAAU,EACV,MAAM,EACN,MAAM,EACN,MAAM,EACN,KAAK,EACL,SAAS,GACV,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,cAAc,EACd,WAAW,EACX,SAAS,EACT,UAAU,EACV,UAAU,EACV,KAAK,QAAQ,EACb,KAAK,OAAO,GACb,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,mBAAmB,EACnB,UAAU,EACV,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,KAAK,WAAW,GACjB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,WAAW,EAAE,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,KAAK,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACnF,OAAO,EACL,WAAW,EACX,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,iBAAiB,GACvB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,KAAK,gBAAgB,EAAE,KAAK,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAExG,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,kBAAkB,EAAE,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@trustbase/shop-ui` —— TrustBase 店铺界面组件(**框架无关 Web Components**)。
|
|
3
|
+
*
|
|
4
|
+
* ## 四个组件(名字与契约都是冻结的)
|
|
5
|
+
*
|
|
6
|
+
* | 元素 | 干什么 | 关键出参事件 |
|
|
7
|
+
* |---|---|---|
|
|
8
|
+
* | `<tb-goods-list>` | 商品列表 | `tb:goods-select` |
|
|
9
|
+
* | `<tb-goods-detail>` | 详情 + 规格选择 | `tb:sku-change` / `tb:goods-buy` |
|
|
10
|
+
* | `<tb-order-form>` | 下单表单(产出**意愿 + memo**) | `tb:order-submit` / `tb:order-error` |
|
|
11
|
+
* | `<tb-account-panel>` | 我的账户:地址/余额/订单占位 | `tb:account-action` |
|
|
12
|
+
*
|
|
13
|
+
* ## 三条硬纪律(都有测试守着)
|
|
14
|
+
*
|
|
15
|
+
* 1. **零运行时依赖**:自己写 Custom Elements,不引任何第三方 UI 框架/库;
|
|
16
|
+
* 连 `@trustbase/shop` 都不 import(意愿口径由宿主**注入** `intentBuilder`,见 `tb-order-form`)。
|
|
17
|
+
* 2. **样式只能引用 tokens**:`src/tokens.ts` 是唯一允许出现视觉字面量的文件,
|
|
18
|
+
* 组件样式里出现 `#4f8ef7` / `13px` / `200ms` 这类字面量 → `test/styles.test.ts` 失败。
|
|
19
|
+
* 3. **不持私钥、不签名**:组件只渲染 + 抛事件;下单只产出"意愿 + memo",
|
|
20
|
+
* 签名由调用方交给商户守护进程(`test/isomorphic.test.ts` 扫源码禁止签名相关标识符)。
|
|
21
|
+
*
|
|
22
|
+
* ## 用法
|
|
23
|
+
*
|
|
24
|
+
* ```html
|
|
25
|
+
* <script type="module">
|
|
26
|
+
* import { registerShopUi, TOKENS_CSS } from '../dist/esm/index.js';
|
|
27
|
+
* registerShopUi();
|
|
28
|
+
* // 想在页面(light DOM)也拿到 tokens(例如自己的壳层样式):
|
|
29
|
+
* document.head.append(Object.assign(document.createElement('style'), { textContent: TOKENS_CSS }));
|
|
30
|
+
* </script>
|
|
31
|
+
* <tb-goods-list data-goods='[{"goods_id":"g1","title":"茶","skus":[...]}]'></tb-goods-list>
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* 移动优先:默认 375 宽可用(`packages/shop-ui/demo/index.html` 就是按 375 设计的演示页)。
|
|
35
|
+
*/
|
|
36
|
+
export { AMOUNT_STYLE, HOST_STYLE, TOKENS, TOKENS_CSS, border, color, font, fontSize, fontWeight, gray, lineHeight, motion, radius, shadow, space, tokensCss, } from './tokens.js';
|
|
37
|
+
export { SHOP_UI_EVENTS, STATE_STYLE, TbElement, escapeHtml, stateBlock, } from './base.js';
|
|
38
|
+
export { ORDER_STATUS_LABELS, STATE_TEXT, formatAmount, formatAmountParts, goodsMetaLine, multiplyAmounts, orderStatusLabel, shortAddress, sumAmounts, } from './format.js';
|
|
39
|
+
export { TbGoodsList } from './components/goods-list.js';
|
|
40
|
+
export { TbGoodsDetail } from './components/goods-detail.js';
|
|
41
|
+
export { TbOrderForm, } from './components/order-form.js';
|
|
42
|
+
export { TbAccountPanel } from './components/account-panel.js';
|
|
43
|
+
export { SHOP_UI_ELEMENTS, registerShopUi, shopUiElementNames } from './register.js';
|
|
44
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EACL,YAAY,EACZ,UAAU,EACV,MAAM,EACN,UAAU,EACV,MAAM,EACN,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,UAAU,EACV,IAAI,EACJ,UAAU,EACV,MAAM,EACN,MAAM,EACN,MAAM,EACN,KAAK,EACL,SAAS,GACV,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,cAAc,EACd,WAAW,EACX,SAAS,EACT,UAAU,EACV,UAAU,GAGX,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,mBAAmB,EACnB,UAAU,EACV,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,UAAU,GAEX,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,WAAW,EAAkB,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAAE,aAAa,EAAwB,MAAM,8BAA8B,CAAC;AACnF,OAAO,EACL,WAAW,GAKZ,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,cAAc,EAA2C,MAAM,+BAA+B,CAAC;AAExG,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,kBAAkB,EAAkB,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 注册四个自定义元素。
|
|
3
|
+
*
|
|
4
|
+
* 单独一个入口(`@trustbase/shop-ui/register`)的原因:**注册是有副作用的一步**
|
|
5
|
+
* (`customElements.define`),而组件类本身可以安全地被 import(SSR / 测试 / 只取类型)。
|
|
6
|
+
* 宿主想控制注册时机(例如等 tokens 主题就绪、或只想注册用到的那几个)时,
|
|
7
|
+
* 引 `index` 而不调 `registerShopUi()` 即可。
|
|
8
|
+
*
|
|
9
|
+
* `registerShopUi()` 是**幂等**的:重复调用不会抛 `already defined`。
|
|
10
|
+
*/
|
|
11
|
+
import { TbAccountPanel } from './components/account-panel.js';
|
|
12
|
+
import { TbGoodsDetail } from './components/goods-detail.js';
|
|
13
|
+
import { TbGoodsList } from './components/goods-list.js';
|
|
14
|
+
import { TbOrderForm } from './components/order-form.js';
|
|
15
|
+
import { TbElement } from './base.js';
|
|
16
|
+
/** 元素名 → 组件类(元素名是**冻结的契约**:改名字等于破坏宿主的 HTML) */
|
|
17
|
+
export declare const SHOP_UI_ELEMENTS: Readonly<{
|
|
18
|
+
'tb-goods-list': typeof TbGoodsList;
|
|
19
|
+
'tb-goods-detail': typeof TbGoodsDetail;
|
|
20
|
+
'tb-order-form': typeof TbOrderForm;
|
|
21
|
+
'tb-account-panel': typeof TbAccountPanel;
|
|
22
|
+
}>;
|
|
23
|
+
export type ShopUiTag = keyof typeof SHOP_UI_ELEMENTS;
|
|
24
|
+
/**
|
|
25
|
+
* 注册全部自定义元素(浏览器环境)。
|
|
26
|
+
* 在 Node/SSR 里调用是**安全空操作**(没有 `customElements` 就直接返回),
|
|
27
|
+
* 这样宿主可以无脑在最外层调一次。
|
|
28
|
+
*/
|
|
29
|
+
export declare function registerShopUi(): void;
|
|
30
|
+
/** 已注册的元素名清单(宿主做能力探测/文档用) */
|
|
31
|
+
export declare function shopUiElementNames(): ShopUiTag[];
|
|
32
|
+
export { TbElement };
|
|
33
|
+
//# sourceMappingURL=register.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../src/register.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,+CAA+C;AAC/C,eAAO,MAAM,gBAAgB;;;;;EAK3B,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,gBAAgB,CAAC;AAEtD;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,IAAI,CASrC;AAED,6BAA6B;AAC7B,wBAAgB,kBAAkB,IAAI,SAAS,EAAE,CAEhD;AAED,OAAO,EAAE,SAAS,EAAE,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 注册四个自定义元素。
|
|
3
|
+
*
|
|
4
|
+
* 单独一个入口(`@trustbase/shop-ui/register`)的原因:**注册是有副作用的一步**
|
|
5
|
+
* (`customElements.define`),而组件类本身可以安全地被 import(SSR / 测试 / 只取类型)。
|
|
6
|
+
* 宿主想控制注册时机(例如等 tokens 主题就绪、或只想注册用到的那几个)时,
|
|
7
|
+
* 引 `index` 而不调 `registerShopUi()` 即可。
|
|
8
|
+
*
|
|
9
|
+
* `registerShopUi()` 是**幂等**的:重复调用不会抛 `already defined`。
|
|
10
|
+
*/
|
|
11
|
+
import { TbAccountPanel } from './components/account-panel.js';
|
|
12
|
+
import { TbGoodsDetail } from './components/goods-detail.js';
|
|
13
|
+
import { TbGoodsList } from './components/goods-list.js';
|
|
14
|
+
import { TbOrderForm } from './components/order-form.js';
|
|
15
|
+
import { TbElement } from './base.js';
|
|
16
|
+
/** 元素名 → 组件类(元素名是**冻结的契约**:改名字等于破坏宿主的 HTML) */
|
|
17
|
+
export const SHOP_UI_ELEMENTS = Object.freeze({
|
|
18
|
+
'tb-goods-list': TbGoodsList,
|
|
19
|
+
'tb-goods-detail': TbGoodsDetail,
|
|
20
|
+
'tb-order-form': TbOrderForm,
|
|
21
|
+
'tb-account-panel': TbAccountPanel,
|
|
22
|
+
});
|
|
23
|
+
/**
|
|
24
|
+
* 注册全部自定义元素(浏览器环境)。
|
|
25
|
+
* 在 Node/SSR 里调用是**安全空操作**(没有 `customElements` 就直接返回),
|
|
26
|
+
* 这样宿主可以无脑在最外层调一次。
|
|
27
|
+
*/
|
|
28
|
+
export function registerShopUi() {
|
|
29
|
+
const registry = globalThis
|
|
30
|
+
.customElements;
|
|
31
|
+
if (registry === undefined)
|
|
32
|
+
return;
|
|
33
|
+
for (const [tag, ctor] of Object.entries(SHOP_UI_ELEMENTS)) {
|
|
34
|
+
if (registry.get(tag) === undefined) {
|
|
35
|
+
registry.define(tag, ctor);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** 已注册的元素名清单(宿主做能力探测/文档用) */
|
|
40
|
+
export function shopUiElementNames() {
|
|
41
|
+
return Object.keys(SHOP_UI_ELEMENTS);
|
|
42
|
+
}
|
|
43
|
+
export { TbElement };
|
|
44
|
+
//# sourceMappingURL=register.js.map
|