@baishuyun/ai-design 2.1.4 → 2.1.5

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @baishuyun/ai-design
2
2
 
3
+ ## 2.1.5
4
+
5
+ ### Patch Changes
6
+
7
+ - Markdown 分块时丢掉空白 token,并把正文放进同一个 prose 容器,避免空标签和段落间距被清掉。
8
+
3
9
  ## 2.1.4
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@baishuyun/ai-design",
3
- "version": "2.1.4",
3
+ "version": "2.1.5",
4
4
  "description": "AI 功能 UI 组件库 - 所有 AI 功能的 UI 表现唯一真相来源",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -5,7 +5,7 @@ import remarkBreaks from 'remark-breaks';
5
5
 
6
6
  function parseMarkdownIntoBlocks(markdown: string): string[] {
7
7
  const tokens = marked.lexer(markdown);
8
- return tokens.map((token) => token.raw);
8
+ return tokens.flatMap((token) => ((token.raw ?? '').trim() ? [token.raw] : []));
9
9
  }
10
10
 
11
11
  const MemoizedMarkdownBlock = memo(({ content, breaks }: { content: string; breaks?: boolean }) => {
@@ -26,11 +26,13 @@ const Prose = memo(function Prose({
26
26
  }: MarkdownProps & { style?: CSSProperties }) {
27
27
  const blocks = useMemo(() => parseMarkdownIntoBlocks(content), [content]);
28
28
 
29
- return blocks.map((block, index) => (
30
- <article className="prose" key={`${id}-block_${index}`} style={style}>
31
- <MemoizedMarkdownBlock breaks={breaks} content={block} />
29
+ return (
30
+ <article className="prose" style={style}>
31
+ {blocks.map((block, index) => (
32
+ <MemoizedMarkdownBlock breaks={breaks} content={block} key={`${id}-block_${index}`} />
33
+ ))}
32
34
  </article>
33
- ));
35
+ );
34
36
  });
35
37
 
36
38
  export const MemoizedMarkdown = Object.assign(
@@ -74,6 +74,146 @@ export const LongContent: Story = {
74
74
  },
75
75
  };
76
76
 
77
+ export const LongDocument: Story = {
78
+ parameters: {
79
+ layout: 'padded',
80
+ },
81
+ args: {
82
+ id: 'long-document',
83
+ content: `
84
+ # 供应商档案填写说明
85
+
86
+ 这份说明覆盖从建档到校验的完整流程。阅读时按章节往下走即可:先确认主表字段,再补产品子表,最后用示例代码核对提交结构。
87
+
88
+ 供应商档案用于登记合作方的基础身份、联系人和可供产品。主表只保留一份当前有效记录,历史变更不在这张表里维护。产品明细通过子表单追加,一行对应一个可采购规格。
89
+
90
+ 下面按实际填写顺序说明每个区块。空行只用来分隔段落,不会单独占一块正文。
91
+
92
+ ## 建档前要准备的材料
93
+
94
+ 开始填写之前,先向采购或法务确认下面几项。缺任何一项都不要先保存草稿,否则后续校验会在提交时集中报错,返工成本更高。
95
+
96
+ - **营业执照**:需要统一社会信用代码,和系统里的供应商 ID 一一对应
97
+ - **联系人**:至少一名在职对接人,电话和邮箱都要能接通
98
+ - **结算账户**:开户行、账号、户名三者一致,户名必须与执照名称相同
99
+ - **可供产品**:每个规格单独一行,单价使用含税金额
100
+
101
+ 如果是续签供应商,不要新建档案。打开已有记录,只改发生变化的字段。新建会生成新的供应商 ID,历史订单无法自动关联。
102
+
103
+ ### 推荐顺序
104
+
105
+ 1. 填写供应商 ID、名称和统一社会信用代码
106
+ 2. 补联系人、电话和邮箱
107
+ 3. 在产品子表单里逐行录入规格、型号和单价
108
+ 4. 保存前用页面底部的校验结果看一遍必填项
109
+
110
+ 编号类字段一旦保存就不能改。名称、联系人和产品行可以在后续编辑里调整。
111
+
112
+ ## 字段怎么写
113
+
114
+ 主表字段区分必填和选填。必填项留空时,提交按钮保持禁用,光标会停在第一个空字段上。
115
+
116
+ 供应商 ID 使用大写前缀加六位数字,例如 \`SUP-000128\`。不要手写空格,也不要混入中文标点。名称写执照上的全称,简称只放在备注里。
117
+
118
+ 联系电话接受手机号或带区号的座机。座机把区号和号码用一个短横线连接,例如 \`021-12345678\`。多个号码不要写在同一个字段里,额外联系人放到备注。
119
+
120
+ ### 产品子表单
121
+
122
+ 每一行是一个可下单规格。列和主表字段不要混在同一行描述里。
123
+
124
+ - 产品名称写对外销售名,不要写内部物料简称
125
+ - 型号与包装规格分开,型号放型号列,规格写在备注
126
+ - 单价保留两位小数,货币默认人民币,不要在数字后面再写「元」
127
+ - 停产规格不要删行,把状态改成停用,避免历史单据失去引用
128
+
129
+ 子表单至少保留一行。如果当前还没有可售产品,先填一行占位,名称写「待补充」,状态选草稿,等采购确认后再改成启用。
130
+
131
+ ## 提交结构示例
132
+
133
+ 保存时前端会把主表和子表打成一份 JSON。下面是一条通过校验的最小示例,字段名和页面上的控件名一致。
134
+
135
+ \`\`\`json
136
+ {
137
+ "supplierId": "SUP-000128",
138
+ "supplierName": "杭州青禾材料有限公司",
139
+ "contact": "林夏",
140
+ "phone": "13800001111",
141
+ "products": [
142
+ { "name": "冷轧钢板", "model": "SPCC-1.0", "price": 6.8, "status": "enabled" },
143
+ { "name": "镀锌卷", "model": "DX51D-0.8", "price": 7.4, "status": "draft" }
144
+ ]
145
+ }
146
+ \`\`\`
147
+
148
+ 产品数组里的每一项对应子表单的一行。\`status\` 只接受 \`enabled\`、\`draft\` 和 \`disabled\` 三个值,其它字符串会被校验器拒绝。
149
+
150
+ 如果要在脚本里生成同样的结构,可以按下面的函数组装。注意单价先转成数字再写入,不要把输入框里的字符串直接放进 \`price\`。
151
+
152
+ \`\`\`ts
153
+ type Product = {
154
+ name: string;
155
+ model: string;
156
+ price: number;
157
+ status: 'enabled' | 'draft' | 'disabled';
158
+ };
159
+
160
+ type SupplierDraft = {
161
+ supplierId: string;
162
+ supplierName: string;
163
+ contact: string;
164
+ phone: string;
165
+ products: Product[];
166
+ };
167
+
168
+ function buildDraft(form: SupplierDraft) {
169
+ return {
170
+ ...form,
171
+ products: form.products.map((row) => ({
172
+ ...row,
173
+ price: Number(row.price.toFixed(2)),
174
+ })),
175
+ };
176
+ }
177
+ \`\`\`
178
+
179
+ \`buildDraft\` 不会补默认值。调用前要保证必填字段已经由页面校验过,否则这里只是原样拷贝,后端仍会返回字段级错误。
180
+
181
+ ## 常见校验失败
182
+
183
+ 提交失败时,接口按字段返回原因。页面会把原因显示在对应控件下方,不会再弹一整页错误。
184
+
185
+ - 供应商 ID 重复:换一个未占用的编号,或改去编辑已有档案
186
+ - 电话格式不对:去掉空格和中文括号,座机补上区号
187
+ - 单价不是数字:不要带货币符号,千分位逗号也要去掉
188
+ - 产品行为空:至少保留一行,草稿状态可以后补型号
189
+
190
+ 下面这段响应就是单价字段失败时的典型返回。\`path\` 用点号加下标定位到子表单的具体行。
191
+
192
+ \`\`\`json
193
+ {
194
+ "ok": false,
195
+ "errors": [
196
+ { "path": "products.1.price", "message": "单价必须是大于 0 的数字" }
197
+ ]
198
+ }
199
+ \`\`\`
200
+
201
+ 改完该行后重新提交即可,其它已通过的字段不需要重填。如果同一行连续失败两次,先看输入框里有没有不可见空格,再看是否把型号误填进了单价。
202
+
203
+ ## 保存之后
204
+
205
+ 档案保存成功会回到列表,并高亮刚写入的那一行。此时供应商 ID 已锁定。需要更正名称或联系人时,从列表进入编辑,不要复制一份再新建。
206
+
207
+ 产品行的增删只影响新单据。已经引用该规格的历史订单仍指向原来的行 ID,所以停用优于删除。删除仅在该行从未被任何单据引用时才会成功。
208
+ `.trim(),
209
+ },
210
+ render: (args) => (
211
+ <div style={{ width: 720, maxWidth: '100%' }}>
212
+ <MemoizedMarkdown.Prose {...args} />
213
+ </div>
214
+ ),
215
+ };
216
+
77
217
  export const JSONCodeBlock: Story = {
78
218
  args: {
79
219
  id: 'json-block',