# XFormGroup 表单组容器

提供按「表单组」维度组织和管理多个表单（或插槽内容）的容器组件，支持基于配置的动态初始化、分组滚动联动、高度解耦的显示/隐藏控制以及跨表单数据联动。


## 何时使用

- 需要将一个复杂业务流程拆分为多个逻辑表单组（例如：基础信息、表信息、补气信息等），并通过统一入口进行初始化和提交时。
- 需要统一管理多个 XForm / XAddNativeForm 表单的校验和提交，获取「所有表单组」的结果时。
- 需要部分表单组按条件动态展示/隐藏，并在隐藏时自动清理对应数据，避免脏数据干扰后续逻辑时。
- 需要在表单组之间做联动（A 表单组修改后，刷新 B 表单组的显隐逻辑或默认值）时。


## 引用方式

```javascript
import XFormGroup from '@vue2-client/base-client/components/common/XFormGroup/XFormGroup.vue'

export default {
  components: {
    XFormGroup
  }
}
```


## 基本用法

### 配置驱动初始化

```html
<template>
  <a-card>
    <a-button @click="submitForm">提交</a-button>
    <XFormGroup
      ref="xFormGroupDemo"
      :showDot="true"
      @meter-base-change="handleMeterBaseChange"
    />
  </a-card>
</template>

<script>
import XFormGroup from '@vue2-client/base-client/components/common/XFormGroup/XFormGroup.vue'
import { getConfigByNameAsync } from '@vue2-client/services/api/common'

export default {
  name: 'Demo',
  components: { XFormGroup },
  data() {
    return {
      visible: true
    }
  },
  created() {
    // 从后端拉取表单组配置，常见于低代码 / 配置化场景
    getConfigByNameAsync('changeMeterFormGroup', 'af-revenue').then(res => {
      this.$refs.xFormGroupDemo.init({
        // 后端返回的表单组配置，需包含 groups 等字段
        ...res,
        serviceName: 'af-revenue',
        // 控制表单组显示/隐藏的开关对象
        formShow: { showfillgasInfo: false },
        // 扩展数据，在表单内的逻辑函数中可通过 extData 访问
        extData: {
          a: 1,
          v: 2
        }
      })
    })
  },
  methods: {
    submitForm() {
      this.$refs.xFormGroupDemo.onSubmit().then(res => {
        console.log('所有表单组的结果', res)
      })
    },
    async handleMeterBaseChange(attr, value) {
      if (!(value || value === 0)) return

      // 更新指定表单组的表单值
      this.$refs.xFormGroupDemo.updateGroupValues('fillgasInfo', {
        f_remanent_gas: value + 11,
        f_stair_use: 12
      })

      // 根据需要，动态展示某个表单组
      // this.$refs.xFormGroupDemo.xFormShow('x-form-show', 'fillgasInfo', { f_remanent_gas: 10 })
    }
  }
}
</script>
```


### 插槽表单组示例

配置中某个分组可以声明为 `formGroupType: 'slot'`，由业务方自行提供插槽内容，但仍由 XFormGroup 统一管理滚动、显隐和数据占位：

```html
<template>
  <XFormGroup ref="formGroup" />

  <!-- 在配置中声明 slotName: 'extraInfo'，formGroupType: 'slot' -->
  <template #extraInfo="slotProps">
    <!-- slotProps.data 为所有表单组的当前数据 -->
    <a-card :title="'扩展信息：' + (slotProps.data.baseInfo?.userName || '')">
      <!-- 这里可以是任意自定义内容 / 表单 -->
      <span>这里是自定义插槽表单组 extraInfo</span>
    </a-card>
  </template>
</template>
```


## 对外 API

### Props

| 参数名 | 说明 | 类型 | 默认值 |
|-------|------|------|--------|
| showDot | 标题左侧是否显示小圆点，常用于必填或高亮提示 | `Boolean` | `false` |


### 暴露方法（通过 `ref` 调用）

> 以下方法均通过 `this.$refs.xxx` 调用。

- **init(options)**

  初始化表单组，是使用 XFormGroup 的入口方法。

  **参数结构（重点字段）**：

  ```javascript
  this.$refs.formGroup.init({
    groups,            // Array<FormGroup>，表单组配置数组（必填）
    modifyModelData,   // Object，修改模式的初始数据（按组名分组）
    serviceName,       // String，服务名称，默认 process.env.VUE_APP_SYSTEM_NAME
    env,               // 'dev' | 'prod' | 'test'，运行环境，默认 'prod'
    showLeftTab,       // Boolean，是否显示左侧分组导航
    businessType,      // String，业务类型，例如 '新增' | '修改'
    formShow,          // Object，各表单组的显隐控制，如 { showgroupA: true }
    extData            // Object，扩展上下文数据，在表单内部逻辑中可使用
  })
  ```

  **FormGroup 配置说明（核心字段）**：

  ```typescript
  type FormGroup = {
    groupName: string;                // 表单组名称（用于区分组、数据 key）
    describe: string;                 // 表单组展示标题
    formGroupType: 'slot' | 'form';   // 表单组类型：插槽 or 配置表单
    slotName?: string;                // 插槽名称（formGroupType === 'slot' 时必填）
    formJson?: Array<any>;            // XAddNativeForm 表单配置（formGroupType === 'form' 时必填）
    xAddFormLayout?: any;             // 表单布局配置
    // ... 其他会透传给 XAddNativeForm 的配置
  }
  ```

- **updateGroupValues(groupName, values): boolean**

  更新指定表单组当前表单的字段值。

  - **groupName**：表单组名称（配置中的 `groupName`）。
  - **values**：要合并到当前表单数据中的对象。
  - 若表单已渲染：调用内部 `setForm` 更新表单，并同步 `allFormData[groupName]`。
  - 若表单尚未初始化：数据先缓存在 `modifyModelData[groupName]` 中，待表单渲染后自动生效。

- **getGroupValues(groupName): object**

  获取某个表单组的当前值，返回 `allFormData[groupName]`（不存在时返回空对象）。

- **xFormShow(func, name, defaultForm?): void**

  控制表单组的显示/隐藏，同时负责必要的数据初始化与清理。

  - **func**：`'x-form-show'` 显示 / `'x-form-no-show'` 隐藏。
  - **name**：
    - 普通表单组传 `groupName`。
    - 插槽表单组传 `slotName`。
  - **defaultForm**：可选，显示时用于给新初始化的表单组传入默认值。
  - 隐藏时会自动调用内部的 `clearGroupData(name)`，清理 `modifyModelData` 和 `allFormData` 中对应的数据。

- **onSubmit(): Promise<object>**

  统一触发所有表单组校验和提交。

  - 内部会依次遍历 `groups`，调用每个子表单的 `asyncSubmit`。
  - 校验通过：返回 `Promise.resolve(allFormData)`，`allFormData` 结构为 `{ [groupNameOrSlotName]: formValue }`。
  - 任一表单校验失败：
    - 自动滚动到出错表单组位置。
    - 弹出错误提示：`请检查表单填写是否正确`。
    - 返回 `Promise.reject`。

- **asyncSubmit(): Promise<object>**

  `onSubmit` 的别名，方便对齐其他 X 组件的调用习惯。


### 提供给子组件的依赖注入（provide）

XFormGroup 通过 `provide` 向后代组件（例如 XForm/XFormItem）注入以下字段：

- **inXFormGroup**：标记当前处于 XFormGroup 环境下。
- **formGroupContext**：当前 XFormGroup 实例，可在子组件中访问分组上下文能力。
- **formDataChange(groupName, formData)**：表单组内部表单数据变化时调用，用于：
  - 更新 `allFormData[groupName]`。
  - 通知其他表单组刷新显隐逻辑（调用子表单的 `refreshShowFunc`）。


### 事件

XFormGroup 自身没有定义固定的业务事件，但会把 XAddNativeForm / XFormItem 中向上传递的事件继续向上透传：

- **x-form-item-emit-func**

  - 当内部表单项通过 `emitFunc` 向上传事件时，如果既不是 `x-form-show` 也不是 `x-form-no-show`，则：
    - `XFormGroup` 会直接 `$emit(func, data, value)`，方便业务方监听具体事件名。
    - 同时统一触发 `x-form-item-emit-func`，便于做一些通用统计/记录。


## 滚动与左侧导航联动

- 当 `showLeftTab` 为 `true` 且 `groups` 数组非空时，左侧会渲染一个分组 Tab 列表（使用每个分组的 `describe` 作为标题）。
- 点击左侧 Tab 会调用 `scrollToGroup(index)`，滚动右侧表单区域到对应分组位置。
- 右侧滚动时，使用节流函数 `handleScroll` 计算当前可视区域内的分组，并反向更新 `activeTab`，实现「滚动-Tab 高亮」联动。


## 使用建议

- **优先通过配置驱动**：推荐由后端或配置中心维护 `groups` 与 `formJson`，前端通过 `init` 接收并渲染，降低耦合度。
- **合理拆分表单组**：每个表单组尽量聚焦一个业务子域，避免单组过大影响用户体验和性能。
- **善用 `formShow` 与 `xFormShow`**：
  - 首次加载时使用 `formShow` 控制基础显隐。
  - 运行时使用 `xFormShow` 根据前后端逻辑动态展示/隐藏，并让组件负责数据初始化与清理。
- **跨组联动场景优先通过 `formDataChange` 通知**：XFormGroup 已内置「某组变化 → 其他组 refreshShowFunc」的触发机制，尽量复用该能力而不是手动强行操作 DOM。
