# PropertyEditor 属性编辑器组件

一个动态属性编辑器组件，根据元数据配置渲染表单字段。支持多种字段类型、嵌套对象、自动组件选择和国际化。非常适合管理面板、表单构建器和配置界面。

## 安装

```bash
npm install @ticatec/uniface-element
```

## 导入

```typescript
import PropertyEditor from "@ticatec/uniface-element/PropertyEditor";
import { MetaEditorType, type PropertyField } from "@ticatec/uniface-element/PropertyEditor";
```

## 基本用法

```svelte
<script>
  import PropertyEditor, { MetaEditorType } from "@ticatec/uniface-element/PropertyEditor";
  
  let userData = {
    name: "张伟",
    age: 30,
    email: "zhangwei@example.com",
    isActive: true
  };
  
  const userFields = [
    {
      name: "name",
      label: "姓名",
      type: MetaEditorType.Text,
      attrs: {
        placeholder: "请输入姓名",
        maxLength: 50
      }
    },
    {
      name: "age",
      label: "年龄",
      type: MetaEditorType.Number,
      attrs: {
        min: 0,
        max: 120
      }
    },
    {
      name: "email",
      label: "电子邮件",
      type: MetaEditorType.Text,
      attrs: {
        placeholder: "请输入电子邮件",
        type: "email"
      }
    },
    {
      name: "isActive",
      label: "激活状态",
      type: MetaEditorType.Boolean
    }
  ];
</script>

<PropertyEditor fields={userFields} bind:data={userData} />
```

## 属性

| 属性 | 类型 | 默认值 | 描述 |
|------|------|---------|-------------|
| `fields` | `Array<PropertyField>` | 必需 | 字段定义数组 |
| `data` | `any` | 必需 | 要编辑的数据对象 |

## 类型定义

### PropertyField
```typescript
interface PropertyField {
  name: string;           // 字段名称（支持点符号表示嵌套）
  label: string;          // 字段显示标签
  type: MetaEditorType;   // 编辑器类型
  attrs?: any;            // 编辑器组件的附加属性
}
```

### MetaEditorType
```typescript
enum MetaEditorType {
  Text = 'Text',                    // 文本输入
  CommonText = 'CommonText',        // 带建议的文本输入
  Number = 'Number',                // 数字输入
  Date = 'Date',                    // 日期选择器
  DateTime = 'DateTime',            // 日期时间选择器
  Options = 'Options',              // 下拉选择
  Boolean = 'Boolean'               // 布尔值（真/假）
}
```

## 字段类型

### 文本字段

```svelte
<script>
  import PropertyEditor, { MetaEditorType } from "@ticatec/uniface-element/PropertyEditor";
  
  let textData = {
    title: "",
    description: "",
    keywords: ""
  };
  
  const textFields = [
    {
      name: "title",
      label: "标题",
      type: MetaEditorType.Text,
      attrs: {
        placeholder: "请输入标题",
        maxLength: 100
      }
    },
    {
      name: "description",
      label: "描述",
      type: MetaEditorType.Text,
      attrs: {
        placeholder: "请输入描述",
        rows: 3
      }
    },
    {
      name: "keywords",
      label: "关键词",
      type: MetaEditorType.CommonText,
      attrs: {
        words: ["javascript", "svelte", "typescript", "前端"],
        placeholder: "请输入关键词"
      }
    }
  ];
</script>

<div class="text-editor-demo">
  <h3>文本字段示例</h3>
  <PropertyEditor fields={textFields} bind:data={textData} />
  
  <div class="output">
    <h4>当前值：</h4>
    <pre>{JSON.stringify(textData, null, 2)}</pre>
  </div>
</div>

<style>
  .text-editor-demo {
    max-width: 600px;
    margin: 20px auto;
    padding: 20px;
    border: 1px solid #ddd;
    border-radius: 8px;
  }
  
  .output {
    margin-top: 20px;
    padding: 16px;
    background: #f8f9fa;
    border-radius: 4px;
  }
  
  .output pre {
    margin: 8px 0 0 0;
    font-size: 12px;
    overflow-x: auto;
  }
</style>
```

### 数字字段

```svelte
<script>
  import PropertyEditor, { MetaEditorType } from "@ticatec/uniface-element/PropertyEditor";
  
  let productData = {
    price: 99.99,
    quantity: 10,
    weight: 0,
    rating: 5
  };
  
  const numberFields = [
    {
      name: "price",
      label: "价格（¥）",
      type: MetaEditorType.Number,
      attrs: {
        min: 0,
        precision: 2,
        step: 0.01
      }
    },
    {
      name: "quantity",
      label: "数量",
      type: MetaEditorType.Number,
      attrs: {
        min: 0,
        precision: 0,
        step: 1
      }
    },
    {
      name: "weight",
      label: "重量（kg）",
      type: MetaEditorType.Number,
      attrs: {
        min: 0,
        precision: 3,
        step: 0.001
      }
    },
    {
      name: "rating",
      label: "评分",
      type: MetaEditorType.Number,
      attrs: {
        min: 1,
        max: 5,
        precision: 0
      }
    }
  ];
</script>

<div class="number-editor-demo">
  <h3>数字字段示例</h3>
  <PropertyEditor fields={numberFields} bind:data={productData} />
  
  <div class="summary">
    <h4>产品摘要</h4>
    <p>价格：¥{productData.price}</p>
    <p>数量：{productData.quantity} 件</p>
    <p>重量：{productData.weight} kg</p>
    <p>评分：{productData.rating}/5 星</p>
  </div>
</div>

<style>
  .number-editor-demo {
    max-width: 600px;
    margin: 20px auto;
    padding: 20px;
    border: 1px solid #ddd;
    border-radius: 8px;
  }
  
  .summary {
    margin-top: 20px;
    padding: 16px;
    background: #f8f9fa;
    border-radius: 4px;
  }
  
  .summary h4 {
    margin: 0 0 12px 0;
  }
  
  .summary p {
    margin: 4px 0;
    font-weight: 500;
  }
</style>
```

### 日期和日期时间字段

```svelte
<script>
  import PropertyEditor, { MetaEditorType } from "@ticatec/uniface-element/PropertyEditor";
  
  let eventData = {
    startDate: null,
    endDate: null,
    registrationDeadline: null,
    createdAt: new Date().toISOString()
  };
  
  const dateFields = [
    {
      name: "startDate",
      label: "开始日期",
      type: MetaEditorType.Date,
      attrs: {
        placeholder: "选择开始日期"
      }
    },
    {
      name: "endDate",
      label: "结束日期",
      type: MetaEditorType.Date,
      attrs: {
        placeholder: "选择结束日期"
      }
    },
    {
      name: "registrationDeadline",
      label: "注册截止日期",
      type: MetaEditorType.DateTime,
      attrs: {
        placeholder: "选择截止日期"
      }
    },
    {
      name: "createdAt",
      label: "创建时间",
      type: MetaEditorType.DateTime,
      attrs: {
        readonly: true
      }
    }
  ];
</script>

<div class="date-editor-demo">
  <h3>日期字段示例</h3>
  <PropertyEditor fields={dateFields} bind:data={eventData} />
  
  <div class="event-info">
    <h4>活动信息</h4>
    {#if eventData.startDate && eventData.endDate}
      <p>活动时间：{eventData.startDate} 至 {eventData.endDate}</p>
    {/if}
    {#if eventData.registrationDeadline}
      <p>注册截止：{eventData.registrationDeadline}</p>
    {/if}
    <p>创建于：{new Date(eventData.createdAt).toLocaleDateString()}</p>
  </div>
</div>

<style>
  .date-editor-demo {
    max-width: 600px;
    margin: 20px auto;
    padding: 20px;
    border: 1px solid #ddd;
    border-radius: 8px;
  }
  
  .event-info {
    margin-top: 20px;
    padding: 16px;
    background: #f8f9fa;
    border-radius: 4px;
  }
  
  .event-info h4 {
    margin: 0 0 12px 0;
  }
  
  .event-info p {
    margin: 4px 0;
  }
</style>
```

### 选项和布尔字段

```svelte
<script>
  import PropertyEditor, { MetaEditorType } from "@ticatec/uniface-element/PropertyEditor";
  
  let settingsData = {
    theme: "light",
    language: "zh",
    notifications: true,
    autoSave: false,
    privacy: "public"
  };
  
  const settingsFields = [
    {
      name: "theme",
      label: "主题",
      type: MetaEditorType.Options,
      attrs: {
        options: [
          { code: "light", text: "浅色主题" },
          { code: "dark", text: "深色主题" },
          { code: "auto", text: "自动（系统）" }
        ]
      }
    },
    {
      name: "language",
      label: "语言",
      type: MetaEditorType.Options,
      attrs: {
        options: [
          { code: "en", text: "English" },
          { code: "zh", text: "中文" },
          { code: "es", text: "Español" },
          { code: "fr", text: "Français" }
        ]
      }
    },
    {
      name: "notifications",
      label: "电子邮件通知",
      type: MetaEditorType.Boolean
    },
    {
      name: "autoSave",
      label: "自动保存",
      type: MetaEditorType.Boolean
    },
    {
      name: "privacy",
      label: "个人资料可见性",
      type: MetaEditorType.Options,
      attrs: {
        options: [
          { code: "public", text: "公开" },
          { code: "friends", text: "仅限好友" },
          { code: "private", text: "私有" }
        ]
      }
    }
  ];
</script>

<div class="settings-editor-demo">
  <h3>选项和布尔字段</h3>
  <PropertyEditor fields={settingsFields} bind:data={settingsData} />
  
  <div class="settings-preview">
    <h4>当前设置</h4>
    <ul>
      <li>主题：{settingsData.theme}</li>
      <li>语言：{settingsData.language}</li>
      <li>电子邮件通知：{settingsData.notifications ? '启用' : '禁用'}</li>
      <li>自动保存：{settingsData.autoSave ? '启用' : '禁用'}</li>
      <li>个人资料：{settingsData.privacy}</li>
    </ul>
  </div>
</div>

<style>
  .settings-editor-demo {
    max-width: 600px;
    margin: 20px auto;
    padding: 20px;
    border: 1px solid #ddd;
    border-radius: 8px;
  }
  
  .settings-preview {
    margin-top: 20px;
    padding: 16px;
    background: #f8f9fa;
    border-radius: 4px;
  }
  
  .settings-preview h4 {
    margin: 0 0 12px 0;
  }
  
  .settings-preview ul {
    margin: 0;
    padding-left: 20px;
  }
  
  .settings-preview li {
    margin: 6px 0;
  }
</style>
```

### 嵌套对象支持

```svelte
<script>
  import PropertyEditor, { MetaEditorType } from "@ticatec/uniface-element/PropertyEditor";
  
  let profileData = {
    personal: {
      firstName: "张",
      lastName: "伟",
      birthDate: null
    },
    contact: {
      email: "zhangwei@example.com",
      phone: "",
      address: {
        street: "",
        city: "",
        country: "CN"
      }
    },
    preferences: {
      newsletter: true,
      theme: "light"
    }
  };
  
  const nestedFields = [
    {
      name: "personal.firstName",
      label: "名",
      type: MetaEditorType.Text,
      attrs: { placeholder: "请输入名" }
    },
    {
      name: "personal.lastName",
      label: "姓",
      type: MetaEditorType.Text,
      attrs: { placeholder: "请输入姓" }
    },
    {
      name: "personal.birthDate",
      label: "出生日期",
      type: MetaEditorType.Date
    },
    {
      name: "contact.email",
      label: "电子邮件",
      type: MetaEditorType.Text,
      attrs: { type: "email", placeholder: "请输入电子邮件" }
    },
    {
      name: "contact.phone",
      label: "电话",
      type: MetaEditorType.Text,
      attrs: { placeholder: "请输入电话号码" }
    },
    {
      name: "contact.address.street",
      label: "街道地址",
      type: MetaEditorType.Text,
      attrs: { placeholder: "请输入街道地址" }
    },
    {
      name: "contact.address.city",
      label: "城市",
      type: MetaEditorType.Text,
      attrs: { placeholder: "请输入城市" }
    },
    {
      name: "contact.address.country",
      label: "国家",
      type: MetaEditorType.Options,
      attrs: {
        options: [
          { code: "CN", text: "中国" },
          { code: "US", text: "美国" },
          { code: "UK", text: "英国" },
          { code: "DE", text: "德国" },
          { code: "FR", text: "法国" }
        ]
      }
    },
    {
      name: "preferences.newsletter",
      label: "订阅时事通讯",
      type: MetaEditorType.Boolean
    },
    {
      name: "preferences.theme",
      label: "首选主题",
      type: MetaEditorType.Options,
      attrs: {
        options: [
          { code: "light", text: "浅色" },
          { code: "dark", text: "深色" }
        ]
      }
    }
  ];
</script>

<div class="nested-editor-demo">
  <h3>嵌套对象编辑器</h3>
  <PropertyEditor fields={nestedFields} bind:data={profileData} />
  
  <div class="profile-summary">
    <h4>个人资料摘要</h4>
    <div class="section">
      <h5>个人信息</h5>
      <p>姓名：{profileData.personal.firstName} {profileData.personal.lastName}</p>
      {#if profileData.personal.birthDate}
        <p>出生日期：{profileData.personal.birthDate}</p>
      {/if}
    </div>
    
    <div class="section">
      <h5>联系信息</h5>
      <p>电子邮件：{profileData.contact.email}</p>
      {#if profileData.contact.phone}
        <p>电话：{profileData.contact.phone}</p>
      {/if}
      <p>地址：{profileData.contact.address.street}, {profileData.contact.address.city}, {profileData.contact.address.country}</p>
    </div>
    
    <div class="section">
      <h5>偏好设置</h5>
      <p>时事通讯：{profileData.preferences.newsletter ? '已订阅' : '未订阅'}</p>
      <p>主题：{profileData.preferences.theme}</p>
    </div>
  </div>
</div>

<style>
  .nested-editor-demo {
    max-width: 700px;
    margin: 20px auto;
    padding: 20px;
    border: 1px solid #ddd;
    border-radius: 8px;
  }
  
  .profile-summary {
    margin-top: 20px;
    padding: 16px;
    background: #f8f9fa;
    border-radius: 4px;
  }
  
  .profile-summary h4 {
    margin: 0 0 16px 0;
  }
  
  .section {
    margin-bottom: 16px;
    padding-bottom: 12px;
    border-bottom: 1px solid #dee2e6;
  }
  
  .section:last-child {
    border-bottom: none;
    margin-bottom: 0;
  }
  
  .section h5 {
    margin: 0 0 8px 0;
    color: #495057;
    font-size: 14px;
    font-weight: 600;
    text-transform: uppercase;
  }
  
  .section p {
    margin: 4px 0;
    font-size: 14px;
  }
</style>
```

### 动态表单构建器

```svelte
<script>
  import PropertyEditor, { MetaEditorType } from "@ticatec/uniface-element/PropertyEditor";
  
  let formData = {};
  let formFields = [
    {
      name: "name",
      label: "姓名",
      type: MetaEditorType.Text,
      attrs: { placeholder: "请输入您的姓名" }
    }
  ];
  
  const availableFieldTypes = [
    { type: MetaEditorType.Text, label: "文本输入" },
    { type: MetaEditorType.Number, label: "数字输入" },
    { type: MetaEditorType.Date, label: "日期选择器" },
    { type: MetaEditorType.Boolean, label: "布尔值（是/否）" },
    { type: MetaEditorType.Options, label: "下拉列表" }
  ];
  
  function addField() {
    const fieldName = prompt("请输入字段名称：");
    if (!fieldName) return;
    
    const fieldLabel = prompt("请输入字段标签：");
    if (!fieldLabel) return;
    
    const fieldType = prompt(`请输入字段类型 (${availableFieldTypes.map(t => t.type).join(', ')})：`);
    const selectedType = availableFieldTypes.find(t => t.type === fieldType);
    
    if (!selectedType) {
      alert("无效的字段类型");
      return;
    }
    
    const newField = {
      name: fieldName,
      label: fieldLabel,
      type: selectedType.type,
      attrs: {}
    };
    
    // 为选项类型添加默认选项
    if (selectedType.type === MetaEditorType.Options) {
      newField.attrs.options = [
        { code: "option1", text: "选项 1" },
        { code: "option2", text: "选项 2" }
      ];
    }
    
    formFields = [...formFields, newField];
  }
  
  function removeField(index) {
    formFields = formFields.filter((_, i) => i !== index);
  }
  
  function exportFormData() {
    const exportData = {
      fields: formFields,
      data: formData
    };
    
    const blob = new Blob([JSON.stringify(exportData, null, 2)], {
      type: 'application/json'
    });
    
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'form-data.json';
    a.click();
    URL.revokeObjectURL(url);
  }
</script>

<div class="form-builder-demo">
  <div class="builder-header">
    <h3>动态表单构建器</h3>
    <div class="builder-actions">
      <button on:click={addField}>添加字段</button>
      <button on:click={exportFormData}>导出数据</button>
    </div>
  </div>
  
  <div class="builder-content">
    <div class="field-list">
      <h4>表单字段</h4>
      {#each formFields as field, index}
        <div class="field-item">
          <span>{field.label} ({field.type})</span>
          <button on:click={() => removeField(index)}>移除</button>
        </div>
      {/each}
    </div>
    
    <div class="form-preview">
      <h4>表单预览</h4>
      <PropertyEditor fields={formFields} bind:data={formData} />
    </div>
  </div>
  
  <div class="form-data">
    <h4>表单数据</h4>
    <pre>{JSON.stringify(formData, null, 2)}</pre>
  </div>
</div>

<style>
  .form-builder-demo {
    max-width: 900px;
    margin: 20px auto;
    padding: 20px;
    border: 1px solid #ddd;
    border-radius: 8px;
  }
  
  .builder-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
    padding-bottom: 12px;
    border-bottom: 1px solid #eee;
  }
  
  .builder-header h3 {
    margin: 0;
  }
  
  .builder-actions {
    display: flex;
    gap: 8px;
  }
  
  .builder-actions button {
    padding: 8px 16px;
    border: 1px solid #007bff;
    background: white;
    color: #007bff;
    border-radius: 4px;
    cursor: pointer;
  }
  
  .builder-actions button:hover {
    background: #007bff;
    color: white;
  }
  
  .builder-content {
    display: grid;
    grid-template-columns: 1fr 2fr;
    gap: 20px;
    margin-bottom: 20px;
  }
  
  .field-list h4,
  .form-preview h4 {
    margin: 0 0 12px 0;
    font-size: 16px;
  }
  
  .field-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 8px 12px;
    background: #f8f9fa;
    border-radius: 4px;
    margin-bottom: 8px;
  }
  
  .field-item button {
    padding: 4px 8px;
    background: #dc3545;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 12px;
  }
  
  .form-preview {
    padding: 16px;
    background: #f8f9fa;
    border-radius: 4px;
  }
  
  .form-data {
    padding: 16px;
    background: #f8f9fa;
    border-radius: 4px;
  }
  
  .form-data h4 {
    margin: 0 0 12px 0;
  }
  
  .form-data pre {
    margin: 0;
    font-size: 12px;
    overflow-x: auto;
    max-height: 200px;
    overflow-y: auto;
  }
</style>
```

## 功能

- **多种字段类型**：支持文本、数字、日期、日期时间、选项、布尔和常用文本类型
- **嵌套对象支持**：使用点符号编辑深层对象属性
- **动态组件选择**：根据字段类型自动选择合适的组件
- **灵活属性**：向底层编辑器组件传递自定义属性
- **双向数据绑定**：与数据对象实时同步
- **国际化**：内置对标签的 i18n 支持
- **可扩展**：易于添加新字段类型和组件

## 字段类型详情

### 文本字段
- **类型**：`MetaEditorType.Text`
- **组件**：TextEditor
- **属性**：placeholder, maxLength, type 等

### 常用文本字段
- **类型**：`MetaEditorType.CommonText`
- **组件**：PromptsTextEditor
- **属性**：words（建议词数组）, placeholder

### 数字字段
- **类型**：`MetaEditorType.Number`
- **组件**：NumberEditor
- **属性**：min, max, precision, step

### 日期字段
- **类型**：`MetaEditorType.Date`
- **组件**：DatePicker
- **属性**：format, placeholder, readonly

### 日期时间字段
- **类型**：`MetaEditorType.DateTime`
- **组件**：DateTimePicker
- **属性**：format, placeholder, readonly

### 选项字段
- **类型**：`MetaEditorType.Options`
- **组件**：OptionsSelector
- **属性**：options（包含 code 和 text 的对象数组）

### 布尔字段
- **类型**：`MetaEditorType.Boolean`
- **组件**：OptionsSelector（提供 true/false 选项）
- **属性**：自动配置布尔选项

## 样式

PropertyEditor 组件采用类似表格的布局：

```css
/* 属性编辑器容器 */
.uniface-property-editor {
  /* 自定义样式 */
}

/* 表头样式 */
.table-header {
  display: grid;
  grid-template-columns: 1fr 2fr;
  /* 表头样式 */
}

/* 内容区域 */
.table-content {
  /* 内容样式 */
}

/* 单个属性行 */
.property_label,
.property_control {
  /* 属性行样式 */
}
```

## 最佳实践

1. **字段定义**：将字段定义保存在单独的常量中以便复用
2. **验证**：在父组件中实现验证逻辑
3. **嵌套对象**：对嵌套属性使用一致的点符号
4. **类型安全**：使用 TypeScript 进行更好的类型检查
5. **性能**：对大型字段数组考虑使用记忆化
6. **可访问性**：确保适当的标签和 ARIA 属性

## 浏览器支持

- 支持完整事件处理的现代浏览器
- 兼容 Svelte 5+
- 支持 Grid 布局
- 完整的 TypeScript 支持

## 相关组件

- `TextEditor` - 文本输入组件
- `NumberEditor` - 数字输入组件
- `DatePicker` - 日期选择组件
- `OptionsSelect` - 下拉选择组件