@lucifer.chao.du/home-schema-components 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 ADDED
@@ -0,0 +1,9 @@
1
+ # @packages/home-schema-components
2
+
3
+ Shared home schema package for:
4
+
5
+ - `@packages/home-schema-components/schema`
6
+ - `@packages/home-schema-components/web-preview`
7
+
8
+ Current release surface excludes the member runtime entry. The `member` source
9
+ directory remains repo-internal and is not part of the published npm contract.
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@lucifer.chao.du/home-schema-components",
3
+ "version": "0.1.0",
4
+ "description": "Shared home schema components for member and web preview consumers.",
5
+ "license": "UNLICENSED",
6
+ "type": "module",
7
+ "publishConfig": {
8
+ "access": "public",
9
+ "registry": "https://registry.npmjs.org/"
10
+ },
11
+ "files": [
12
+ "src/schema",
13
+ "src/web-preview",
14
+ "README.md"
15
+ ],
16
+ "peerDependencies": {
17
+ "vue": "^3.5.0"
18
+ },
19
+ "exports": {
20
+ "./schema": {
21
+ "types": "./src/schema/index.d.ts",
22
+ "import": "./src/schema/index.js"
23
+ },
24
+ "./web-preview": {
25
+ "types": "./src/web-preview/index.ts",
26
+ "import": "./src/web-preview/index.ts"
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,183 @@
1
+ export type HomeSchemaComponent =
2
+ | 'activities'
3
+ | 'communities'
4
+ | 'services'
5
+ | 'tags'
6
+ | 'tools';
7
+
8
+ export type JumpPath = {
9
+ audienceRule: any;
10
+ displayTitle: string;
11
+ jumpType: string;
12
+ placementId?: string;
13
+ resourceId?: string;
14
+ resourceType?: string;
15
+ sortOrder: number;
16
+ target?: string;
17
+ targetParams?: Record<string, string>;
18
+ };
19
+
20
+ export type ToolItem = {
21
+ icon: string;
22
+ path: JumpPath;
23
+ title: string;
24
+ };
25
+
26
+ export type CommunityItem = {
27
+ path: JumpPath;
28
+ title: string;
29
+ };
30
+
31
+ export type TagItem = {
32
+ icon: string;
33
+ path: JumpPath;
34
+ title: string;
35
+ };
36
+
37
+ export type ActivityItem = {
38
+ image: string;
39
+ path: JumpPath;
40
+ };
41
+
42
+ export type DiscountItem = {
43
+ discounted: number;
44
+ price: number;
45
+ project: string;
46
+ type: string;
47
+ };
48
+
49
+ export type ServiceItem = {
50
+ category: string;
51
+ count: string;
52
+ discount: DiscountItem[];
53
+ distance: string;
54
+ image: string;
55
+ path: JumpPath;
56
+ range: string;
57
+ rate: string;
58
+ title: string;
59
+ type: string;
60
+ };
61
+
62
+ export type ToolsMode = {
63
+ displayMultipleItems: number;
64
+ displayRowCount: number;
65
+ };
66
+
67
+ export type TagsMode = {
68
+ displayRowCount: number;
69
+ showIcon: boolean;
70
+ };
71
+
72
+ export type CommunitiesMode = {
73
+ autoplay: boolean;
74
+ displayMultipleItems: number;
75
+ duration: number;
76
+ interval: number;
77
+ showIcon: boolean;
78
+ };
79
+
80
+ export type ActivitiesMode = {
81
+ autoplay: boolean;
82
+ duration: number;
83
+ interval: number;
84
+ };
85
+
86
+ export type ServicesMode = Record<string, any>;
87
+
88
+ export type NormalizedComponentPropsMap = {
89
+ activities: {
90
+ description?: string;
91
+ image: string;
92
+ mode: ActivitiesMode;
93
+ options: ActivityItem[];
94
+ title?: string;
95
+ };
96
+ communities: {
97
+ description?: string;
98
+ image: string;
99
+ mode: CommunitiesMode;
100
+ options: CommunityItem[];
101
+ title?: string;
102
+ };
103
+ services: {
104
+ description?: string;
105
+ image: string;
106
+ mode: ServicesMode;
107
+ options: ServiceItem[];
108
+ title?: string;
109
+ };
110
+ tags: {
111
+ description?: string;
112
+ image: string;
113
+ mode: TagsMode;
114
+ options: TagItem[];
115
+ title?: string;
116
+ };
117
+ tools: {
118
+ description?: string;
119
+ image: string;
120
+ mode: ToolsMode;
121
+ options: ToolItem[];
122
+ title?: string;
123
+ };
124
+ };
125
+
126
+ export type NormalizedComponentProps<T extends HomeSchemaComponent> =
127
+ NormalizedComponentPropsMap[T];
128
+
129
+ export declare const HOME_SCHEMA_COMPONENTS: HomeSchemaComponent[];
130
+ export declare function isPlainRecord(
131
+ value: unknown,
132
+ ): value is Record<string, any>;
133
+ export declare function normalizeString(
134
+ value: unknown,
135
+ fallback?: string,
136
+ ): string;
137
+ export declare function normalizeOptionalString(
138
+ value: unknown,
139
+ ): string | undefined;
140
+ export declare function normalizeBoolean(
141
+ value: unknown,
142
+ fallback?: boolean,
143
+ ): boolean;
144
+ export declare function normalizeNumber(
145
+ value: unknown,
146
+ fallback?: number,
147
+ limits?: { max?: number; min?: number },
148
+ ): number;
149
+ export declare function createJumpPath(): JumpPath;
150
+ export declare function normalizeJumpPath(value: unknown): JumpPath;
151
+ export declare function createToolItem(): ToolItem;
152
+ export declare function createCommunityItem(): CommunityItem;
153
+ export declare function createTagItem(): TagItem;
154
+ export declare function createActivityItem(): ActivityItem;
155
+ export declare function createDiscountItem(): DiscountItem;
156
+ export declare function createServiceItem(): ServiceItem;
157
+ export declare function normalizeToolItem(value: unknown): ToolItem;
158
+ export declare function normalizeCommunityItem(value: unknown): CommunityItem;
159
+ export declare function normalizeTagItem(value: unknown): TagItem;
160
+ export declare function normalizeActivityItem(value: unknown): ActivityItem;
161
+ export declare function normalizeDiscountItem(value: unknown): DiscountItem;
162
+ export declare function normalizeServiceItem(value: unknown): ServiceItem;
163
+ export declare function normalizeOptions<T extends HomeSchemaComponent>(
164
+ componentType: T,
165
+ options: unknown,
166
+ ): NormalizedComponentPropsMap[T]['options'];
167
+ export declare function normalizeToolsMode(mode: unknown): ToolsMode;
168
+ export declare function normalizeTagsMode(mode: unknown): TagsMode;
169
+ export declare function normalizeCommunitiesMode(mode: unknown): CommunitiesMode;
170
+ export declare function normalizeActivitiesMode(mode: unknown): ActivitiesMode;
171
+ export declare function normalizeServicesMode(mode: unknown): ServicesMode;
172
+ export declare function normalizeMode<T extends HomeSchemaComponent>(
173
+ componentType: T,
174
+ mode: unknown,
175
+ ): NormalizedComponentPropsMap[T]['mode'];
176
+ export declare function normalizeComponentProps<T extends HomeSchemaComponent>(
177
+ componentType: T,
178
+ componentProps: unknown,
179
+ ): NormalizedComponentPropsMap[T];
180
+ export declare function getComponentOptionLabel(
181
+ componentType: HomeSchemaComponent,
182
+ ): string;
183
+ export declare function getTextInitial(text: unknown, fallback?: string): string;
@@ -0,0 +1,413 @@
1
+ export const HOME_SCHEMA_COMPONENTS = [
2
+ 'tools',
3
+ 'communities',
4
+ 'tags',
5
+ 'activities',
6
+ 'services',
7
+ ];
8
+
9
+ const DEFAULT_LIMITS = {
10
+ activitiesDuration: { max: 3000, min: 100 },
11
+ communitiesDisplayMultipleItems: { max: 20, min: 1 },
12
+ interval: { max: 60000, min: 500 },
13
+ tagsDisplayRowCount: { max: 10, min: 1 },
14
+ toolsDisplayMultipleItems: { max: 50, min: 1 },
15
+ toolsDisplayRowCount: { max: 5, min: 3 },
16
+ };
17
+
18
+ export function isPlainRecord(value) {
19
+ return !!value && typeof value === 'object' && !Array.isArray(value);
20
+ }
21
+
22
+ export function normalizeString(value, fallback = '') {
23
+ if (value === undefined || value === null) {
24
+ return fallback;
25
+ }
26
+ return String(value);
27
+ }
28
+
29
+ export function normalizeOptionalString(value) {
30
+ if (value === undefined || value === null || value === '') {
31
+ return undefined;
32
+ }
33
+ const normalized = String(value).trim();
34
+ return normalized || undefined;
35
+ }
36
+
37
+ export function normalizeBoolean(value, fallback = false) {
38
+ if (typeof value === 'boolean') {
39
+ return value;
40
+ }
41
+ if (typeof value === 'number') {
42
+ return value !== 0;
43
+ }
44
+ if (typeof value === 'string') {
45
+ const normalized = value.trim().toLowerCase();
46
+ if (['1', 'on', 'true', 'yes'].includes(normalized)) {
47
+ return true;
48
+ }
49
+ if (['0', 'off', 'false', 'no'].includes(normalized)) {
50
+ return false;
51
+ }
52
+ }
53
+ return fallback;
54
+ }
55
+
56
+ export function normalizeNumber(value, fallback = 0, limits = undefined) {
57
+ const parsed = Number(value);
58
+ if (!Number.isFinite(parsed)) {
59
+ return fallback;
60
+ }
61
+
62
+ let normalized = Math.round(parsed);
63
+ if (Number.isFinite(limits?.min)) {
64
+ normalized = Math.max(normalized, Number(limits.min));
65
+ }
66
+ if (Number.isFinite(limits?.max)) {
67
+ normalized = Math.min(normalized, Number(limits.max));
68
+ }
69
+ return normalized;
70
+ }
71
+
72
+ function cloneValue(value) {
73
+ if (Array.isArray(value)) {
74
+ return value.map((item) => cloneValue(item));
75
+ }
76
+ if (isPlainRecord(value)) {
77
+ const payload = {};
78
+ Object.entries(value).forEach(([key, itemValue]) => {
79
+ payload[key] = cloneValue(itemValue);
80
+ });
81
+ return payload;
82
+ }
83
+ return value;
84
+ }
85
+
86
+ function normalizeKeyValueObject(value) {
87
+ if (value === undefined || value === null || value === '') {
88
+ return undefined;
89
+ }
90
+
91
+ if (typeof value === 'string') {
92
+ try {
93
+ return normalizeKeyValueObject(JSON.parse(value));
94
+ } catch {
95
+ return undefined;
96
+ }
97
+ }
98
+
99
+ if (!isPlainRecord(value)) {
100
+ return undefined;
101
+ }
102
+
103
+ const payload = {};
104
+ Object.entries(value).forEach(([key, itemValue]) => {
105
+ const normalizedKey = String(key || '').trim();
106
+ if (!normalizedKey) {
107
+ return;
108
+ }
109
+ payload[normalizedKey] =
110
+ itemValue === undefined || itemValue === null ? '' : String(itemValue);
111
+ });
112
+ return Object.keys(payload).length > 0 ? payload : undefined;
113
+ }
114
+
115
+ export function createJumpPath() {
116
+ return {
117
+ audienceRule: null,
118
+ displayTitle: '',
119
+ jumpType: 'NONE',
120
+ placementId: undefined,
121
+ resourceId: undefined,
122
+ resourceType: undefined,
123
+ sortOrder: 0,
124
+ target: undefined,
125
+ targetParams: undefined,
126
+ };
127
+ }
128
+
129
+ export function normalizeJumpPath(value) {
130
+ const rawPath = isPlainRecord(value) ? value : {};
131
+ const legacyTarget =
132
+ typeof value === 'string' ? normalizeOptionalString(value) : undefined;
133
+ const path = {
134
+ ...createJumpPath(),
135
+ ...cloneValue(rawPath),
136
+ displayTitle: normalizeString(rawPath.displayTitle),
137
+ jumpType:
138
+ normalizeString(rawPath.jumpType, legacyTarget ? 'H5_URL' : 'NONE') ||
139
+ 'NONE',
140
+ placementId: normalizeOptionalString(rawPath.placementId),
141
+ resourceId: normalizeOptionalString(rawPath.resourceId),
142
+ resourceType: normalizeOptionalString(rawPath.resourceType),
143
+ sortOrder: normalizeNumber(rawPath.sortOrder, 0),
144
+ target: normalizeOptionalString(rawPath.target) ?? legacyTarget,
145
+ targetParams: normalizeKeyValueObject(rawPath.targetParams),
146
+ };
147
+
148
+ if (path.jumpType === 'NONE') {
149
+ path.target = undefined;
150
+ path.targetParams = undefined;
151
+ }
152
+
153
+ return path;
154
+ }
155
+
156
+ export function createToolItem() {
157
+ return {
158
+ icon: '',
159
+ path: createJumpPath(),
160
+ title: '',
161
+ };
162
+ }
163
+
164
+ export function createCommunityItem() {
165
+ return {
166
+ path: createJumpPath(),
167
+ title: '',
168
+ };
169
+ }
170
+
171
+ export function createTagItem() {
172
+ return {
173
+ icon: '',
174
+ path: createJumpPath(),
175
+ title: '',
176
+ };
177
+ }
178
+
179
+ export function createActivityItem() {
180
+ return {
181
+ image: '',
182
+ path: createJumpPath(),
183
+ };
184
+ }
185
+
186
+ export function createDiscountItem() {
187
+ return {
188
+ discounted: 0,
189
+ price: 0,
190
+ project: '',
191
+ type: 'discount',
192
+ };
193
+ }
194
+
195
+ export function createServiceItem() {
196
+ return {
197
+ category: '',
198
+ count: '',
199
+ discount: [createDiscountItem()],
200
+ distance: '',
201
+ image: '',
202
+ path: createJumpPath(),
203
+ range: '',
204
+ rate: '',
205
+ title: '',
206
+ type: 'home',
207
+ };
208
+ }
209
+
210
+ export function normalizeToolItem(value) {
211
+ const source = isPlainRecord(value) ? value : {};
212
+ return {
213
+ ...createToolItem(),
214
+ ...cloneValue(source),
215
+ icon: normalizeString(source.icon),
216
+ path: normalizeJumpPath(source.path),
217
+ title: normalizeString(source.title),
218
+ };
219
+ }
220
+
221
+ export function normalizeCommunityItem(value) {
222
+ const source = isPlainRecord(value) ? value : {};
223
+ return {
224
+ ...createCommunityItem(),
225
+ ...cloneValue(source),
226
+ path: normalizeJumpPath(source.path),
227
+ title: normalizeString(source.title),
228
+ };
229
+ }
230
+
231
+ export function normalizeTagItem(value) {
232
+ const source = isPlainRecord(value) ? value : {};
233
+ return {
234
+ ...createTagItem(),
235
+ ...cloneValue(source),
236
+ icon: normalizeString(source.icon),
237
+ path: normalizeJumpPath(source.path),
238
+ title: normalizeString(source.title),
239
+ };
240
+ }
241
+
242
+ export function normalizeActivityItem(value) {
243
+ const source = isPlainRecord(value) ? value : {};
244
+ return {
245
+ ...createActivityItem(),
246
+ ...cloneValue(source),
247
+ image: normalizeString(source.image),
248
+ path: normalizeJumpPath(source.path),
249
+ };
250
+ }
251
+
252
+ export function normalizeDiscountItem(value) {
253
+ const source = isPlainRecord(value) ? value : {};
254
+ return {
255
+ ...createDiscountItem(),
256
+ ...cloneValue(source),
257
+ discounted: normalizeNumber(source.discounted, 0),
258
+ price: normalizeNumber(source.price, 0),
259
+ project: normalizeString(source.project),
260
+ type: normalizeString(source.type, 'discount') || 'discount',
261
+ };
262
+ }
263
+
264
+ export function normalizeServiceItem(value) {
265
+ const source = isPlainRecord(value) ? value : {};
266
+ const discount = Array.isArray(source.discount)
267
+ ? source.discount.map((item) => normalizeDiscountItem(item))
268
+ : [];
269
+
270
+ return {
271
+ ...createServiceItem(),
272
+ ...cloneValue(source),
273
+ category: normalizeString(source.category),
274
+ count: normalizeString(source.count),
275
+ discount,
276
+ distance: normalizeString(source.distance),
277
+ image: normalizeString(source.image),
278
+ path: normalizeJumpPath(source.path),
279
+ range: normalizeString(source.range),
280
+ rate: normalizeString(source.rate),
281
+ title: normalizeString(source.title),
282
+ type: normalizeString(source.type, 'home') || 'home',
283
+ };
284
+ }
285
+
286
+ export function normalizeOptions(componentType, options) {
287
+ const source = Array.isArray(options) ? options : [];
288
+
289
+ switch (componentType) {
290
+ case 'activities':
291
+ return source.map((item) => normalizeActivityItem(item));
292
+ case 'communities':
293
+ return source.map((item) => normalizeCommunityItem(item));
294
+ case 'services':
295
+ return source.map((item) => normalizeServiceItem(item));
296
+ case 'tags':
297
+ return source.map((item) => normalizeTagItem(item));
298
+ case 'tools':
299
+ default:
300
+ return source.map((item) => normalizeToolItem(item));
301
+ }
302
+ }
303
+
304
+ export function normalizeToolsMode(mode) {
305
+ return {
306
+ displayMultipleItems: normalizeNumber(
307
+ mode?.displayMultipleItems,
308
+ 10,
309
+ DEFAULT_LIMITS.toolsDisplayMultipleItems,
310
+ ),
311
+ displayRowCount: normalizeNumber(
312
+ mode?.displayRowCount,
313
+ 5,
314
+ DEFAULT_LIMITS.toolsDisplayRowCount,
315
+ ),
316
+ };
317
+ }
318
+
319
+ export function normalizeTagsMode(mode) {
320
+ return {
321
+ displayRowCount: normalizeNumber(
322
+ mode?.displayRowCount,
323
+ 4,
324
+ DEFAULT_LIMITS.tagsDisplayRowCount,
325
+ ),
326
+ showIcon: normalizeBoolean(mode?.showIcon, true),
327
+ };
328
+ }
329
+
330
+ export function normalizeCommunitiesMode(mode) {
331
+ return {
332
+ autoplay: normalizeBoolean(mode?.autoplay, true),
333
+ displayMultipleItems: normalizeNumber(
334
+ mode?.displayMultipleItems,
335
+ 2,
336
+ DEFAULT_LIMITS.communitiesDisplayMultipleItems,
337
+ ),
338
+ duration: normalizeNumber(
339
+ mode?.duration,
340
+ 500,
341
+ DEFAULT_LIMITS.activitiesDuration,
342
+ ),
343
+ interval: normalizeNumber(mode?.interval, 5000, DEFAULT_LIMITS.interval),
344
+ showIcon: normalizeBoolean(mode?.showIcon, true),
345
+ };
346
+ }
347
+
348
+ export function normalizeActivitiesMode(mode) {
349
+ return {
350
+ autoplay: normalizeBoolean(mode?.autoplay, true),
351
+ duration: normalizeNumber(
352
+ mode?.duration,
353
+ 500,
354
+ DEFAULT_LIMITS.activitiesDuration,
355
+ ),
356
+ interval: normalizeNumber(mode?.interval, 5000, DEFAULT_LIMITS.interval),
357
+ };
358
+ }
359
+
360
+ export function normalizeServicesMode(mode) {
361
+ return isPlainRecord(mode) ? cloneValue(mode) : {};
362
+ }
363
+
364
+ export function normalizeMode(componentType, mode) {
365
+ switch (componentType) {
366
+ case 'activities':
367
+ return normalizeActivitiesMode(mode);
368
+ case 'communities':
369
+ return normalizeCommunitiesMode(mode);
370
+ case 'services':
371
+ return normalizeServicesMode(mode);
372
+ case 'tags':
373
+ return normalizeTagsMode(mode);
374
+ case 'tools':
375
+ default:
376
+ return normalizeToolsMode(mode);
377
+ }
378
+ }
379
+
380
+ export function normalizeComponentProps(componentType, componentProps) {
381
+ const source = isPlainRecord(componentProps) ? cloneValue(componentProps) : {};
382
+ return {
383
+ ...source,
384
+ description: normalizeOptionalString(source.description),
385
+ image: normalizeOptionalString(source.image) ?? '',
386
+ mode: normalizeMode(componentType, source.mode),
387
+ options: normalizeOptions(componentType, source.options),
388
+ title: normalizeOptionalString(source.title),
389
+ };
390
+ }
391
+
392
+ export function getComponentOptionLabel(componentType) {
393
+ switch (componentType) {
394
+ case 'activities':
395
+ return '横幅';
396
+ case 'communities':
397
+ return '播报';
398
+ case 'services':
399
+ return '服务';
400
+ case 'tags':
401
+ return '标签';
402
+ case 'tools':
403
+ default:
404
+ return '工具';
405
+ }
406
+ }
407
+
408
+ export function getTextInitial(text, fallback = 'L') {
409
+ return String(text || fallback)
410
+ .trim()
411
+ .slice(0, 1)
412
+ .toUpperCase();
413
+ }