扩展字段组件

1. 介绍

背景说明

在现有项目中,我们通过扩展字段类型的方式支持自定义字段功能。这种机制虽然灵活,但存在一定的局限性:

某些场景下,我们并不需要新增一种字段类型,而是希望改变现有字段的展示形式,以适应不同的业务需求或用户体验优化。

以 URL 字段为例,目前它通常以纯文本方式展示。但我们可能需要以下多种展示方式:

  • 作为可点击的链接;
  • 显示链接预览(如网页标题或缩略图);
  • 仅展示域名部分;
  • 自定义格式化输出等。

这类需求并不需要新建字段类型,而应视为字段展示层的扩展能力。

解决方案

通过 CollectionFieldInterfaceManager 提供的扩展机制,我们可以为现有字段类型添加多种展示组件选项,让用户能够根据具体需求选择合适的展示方式。

2. 核心概念

字段接口组件选项

1interface CollectionFieldInterfaceComponentOption {
2  label: string;           // 展示给用户的标签名称
3  value: string;           // 组件的唯一标识符
4  useVisible?: () => boolean;  // 控制该选项是否可见
5  useProps?: () => any;    // 返回传递给组件的属性
6}

管理器接口

1class CollectionFieldInterfaceManager {
2  // 为指定字段类型添加展示组件选项
3  addFieldInterfaceComponentOption(
4    interfaceName: string, 
5    componentOption: CollectionFieldInterfaceComponentOption
6  ): void;
7}

3. 基础用法

1// 定义
2interface CollectionFieldInterfaceComponentOption {
3  label: string;
4  value: string;
5  useVisible?: () => boolean;
6  useProps?: () => any;
7}
8
9class CollectionFieldInterfaceManager {
10  addFieldInterfaceComponentOption(
11    interfaceName: string, 
12    componentOption:CollectionFieldInterfaceComponentOption
13  ): void
14}
15
16// 用法
17class MyPlugin extends Plugin {
18  async load() {
19    this.app.dataSourceManager.
20    collectionFieldInterfaceManager.
21    addFieldInterfaceComponentOption('url', {
22      label: 'Preview',
23      value: 'Input.Preview',
24    });
25  }
26}

4. 使用场景

4.1 URL 字段多展示形式

1class UrlDisplayPlugin extends Plugin {
2  async load() {
3    const manager = this.app.dataSourceManager.collectionFieldInterfaceManager;
4    
5    // 1. 可点击链接展示
6    manager.addFieldInterfaceComponentOption('url', {
7      label: '可点击链接',
8      value: 'UrlField.ClickableLink',
9      useProps: () => ({
10        target: '_blank',
11        rel: 'noopener noreferrer'
12      })
13    });
14    
15    // 2. 链接预览展示
16    manager.addFieldInterfaceComponentOption('url', {
17      label: '链接预览',
18      value: 'UrlField.Preview',
19      useProps: () => ({
20        showTitle: true,
21        showThumbnail: true,
22        maxWidth: 300
23      })
24    });
25    
26    // 3. 域名展示
27    manager.addFieldInterfaceComponentOption('url', {
28      label: '仅显示域名',
29      value: 'UrlField.DomainOnly',
30      useProps: () => ({
31        showProtocol: false,
32        showPath: false
33      })
34    });
35    
36    // 4. 格式化展示
37    manager.addFieldInterfaceComponentOption('url', {
38      label: '格式化显示',
39      value: 'UrlField.Formatted',
40      useProps: () => ({
41        format: 'short',
42        maxLength: 50
43      })
44    });
45  }
46}

4.2 数字字段多展示形式

1class NumberDisplayPlugin extends Plugin {
2  async load() {
3    const manager = this.app.dataSourceManager.collectionFieldInterfaceManager;
4    
5    // 1. 货币格式
6    manager.addFieldInterfaceComponentOption('number', {
7      label: '货币格式',
8      value: 'NumberField.Currency',
9      useProps: () => ({
10        currency: 'CNY',
11        precision: 2
12      })
13    });
14    
15    // 2. 百分比格式
16    manager.addFieldInterfaceComponentOption('number', {
17      label: '百分比',
18      value: 'NumberField.Percentage',
19      useProps: () => ({
20        precision: 1,
21        suffix: '%'
22      })
23    });
24    
25    // 3. 科学计数法
26    manager.addFieldInterfaceComponentOption('number', {
27      label: '科学计数法',
28      value: 'NumberField.Scientific',
29      useProps: () => ({
30        notation: 'scientific',
31        precision: 3
32      })
33    });
34  }
35}

4.3 日期字段多展示形式

1class DateDisplayPlugin extends Plugin {
2  async load() {
3    const manager = this.app.dataSourceManager.collectionFieldInterfaceManager;
4    
5    // 1. 相对时间
6    manager.addFieldInterfaceComponentOption('datetime', {
7      label: '相对时间',
8      value: 'DateField.Relative',
9      useProps: () => ({
10        format: 'relative',
11        updateInterval: 60000 // 每分钟更新
12      })
13    });
14    
15    // 2. 自定义格式
16    manager.addFieldInterfaceComponentOption('datetime', {
17      label: '自定义格式',
18      value: 'DateField.Custom',
19      useProps: () => ({
20        format: 'YYYY年MM月DD日 HH:mm:ss'
21      })
22    });
23    
24    // 3. 时间范围
25    manager.addFieldInterfaceComponentOption('datetime', {
26      label: '时间范围',
27      value: 'DateField.Range',
28      useProps: () => ({
29        showRange: true,
30        rangeFormat: 'YYYY-MM-DD'
31      })
32    });
33  }
34}

5. 条件显示

5.1 基于字段值控制显示

1manager.addFieldInterfaceComponentOption('url', {
2  label: '高级预览',
3  value: 'UrlField.AdvancedPreview',
4  useVisible: () => {
5    // 只在特定条件下显示此选项
6    const currentUser = this.app.getCurrentUser();
7    return currentUser.hasPermission('advanced_preview');
8  },
9  useProps: () => ({
10    showMetadata: true,
11    showSecurityInfo: true
12  })
13});

5.2 基于环境控制显示

1manager.addFieldInterfaceComponentOption('url', {
2  label: '开发模式预览',
3  value: 'UrlField.DevPreview',
4  useVisible: () => {
5    return process.env.NODE_ENV === 'development';
6  },
7  useProps: () => ({
8    showDebugInfo: true,
9    showPerformance: true
10  })
11});

6. 完整示例

6.1 插件定义

1import { Plugin } from '@tachybase/client';
2
3export class FieldDisplayExtensionsPlugin extends Plugin {
4  async load() {
5    this.registerUrlFieldExtensions();
6    this.registerNumberFieldExtensions();
7    this.registerDateFieldExtensions();
8  }
9  
10  private registerUrlFieldExtensions() {
11    const manager = this.app.dataSourceManager.collectionFieldInterfaceManager;
12    
13    // 基础链接展示
14    manager.addFieldInterfaceComponentOption('url', {
15      label: '基础链接',
16      value: 'UrlField.Basic',
17      useProps: () => ({
18        target: '_blank',
19        className: 'url-link'
20      })
21    });
22    
23    // 智能预览
24    manager.addFieldInterfaceComponentOption('url', {
25      label: '智能预览',
26      value: 'UrlField.SmartPreview',
27      useProps: () => ({
28        fetchMetadata: true,
29        showFavicon: true,
30        showDescription: true,
31        maxDescriptionLength: 100
32      })
33    });
34    
35    // 安全链接
36    manager.addFieldInterfaceComponentOption('url', {
37      label: '安全链接',
38      value: 'UrlField.Secure',
39      useProps: () => ({
40        showSecurityBadge: true,
41        validateSSL: true,
42        showDomainInfo: true
43      })
44    });
45  }
46  
47  private registerNumberFieldExtensions() {
48    const manager = this.app.dataSourceManager.collectionFieldInterfaceManager;
49    
50    // 千分位分隔
51    manager.addFieldInterfaceComponentOption('number', {
52      label: '千分位分隔',
53      value: 'NumberField.Thousands',
54      useProps: () => ({
55        separator: ',',
56        precision: 0
57      })
58    });
59    
60    // 文件大小
61    manager.addFieldInterfaceComponentOption('number', {
62      label: '文件大小',
63      value: 'NumberField.FileSize',
64      useProps: () => ({
65        format: 'fileSize',
66        precision: 2
67      })
68    });
69  }
70  
71  private registerDateFieldExtensions() {
72    const manager = this.app.dataSourceManager.collectionFieldInterfaceManager;
73    
74    // 倒计时
75    manager.addFieldInterfaceComponentOption('datetime', {
76      label: '倒计时',
77      value: 'DateField.Countdown',
78      useProps: () => ({
79        format: 'countdown',
80        showDays: true,
81        showHours: true,
82        showMinutes: true
83      })
84    });
85  }
86}

6.2 使用方式

1// 在应用中注册插件
2import { FieldDisplayExtensionsPlugin } from './plugins/FieldDisplayExtensionsPlugin';
3
4const app = new Application({
5  plugins: [
6    FieldDisplayExtensionsPlugin,
7    // 其他插件...
8  ]
9});

7. 最佳实践

7.1 命名规范

  • 组件值使用 FieldType.ComponentName 格式
  • 标签使用简洁明了的中文描述
  • 保持命名的一致性和可读性

7.2 性能优化

  • useProps 中避免复杂的计算
  • 合理使用 useVisible 控制选项显示
  • 缓存计算结果避免重复计算

7.3 用户体验

  • 提供合理的默认选项
  • 根据用户权限和环境动态调整选项
  • 保持选项的简洁性,避免过度复杂

7.4 扩展性

  • 设计可复用的组件选项
  • 支持配置化的属性传递
  • 考虑未来可能的扩展需求

8. 注意事项

  1. 组件注册:确保相应的展示组件已经正确注册到系统中
  2. 属性兼容性:传递给组件的属性需要与组件接口兼容
  3. 性能影响:过多的展示选项可能影响用户选择体验
  4. 维护成本:需要维护多个展示组件的实现和测试

9. 总结

通过字段展示扩展机制,我们可以在不改变字段类型的情况下,为现有字段提供丰富的展示形式。这种设计既保持了系统的灵活性,又避免了不必要的复杂性,是提升用户体验的有效方式。