# 文件输入框

## 使用方法

`FileInput` 用来选择一个或一组文件。值是 `File`；打开 `isMultiple` 后是 `File[]`；没有选择时是 `null`。

```vue
<script setup lang="ts">
import { FileInput } from '@astryx-vue/core/FileInput'
import { shallowRef } from 'vue'

const resume = shallowRef<File | File[] | null>(null)
</script>

<template>
  <FileInput v-model="resume" label="简历" accept=".pdf,.docx" />
</template>
```

`mode="input"` 是紧凑的输入框。`mode="dropzone"` 是虚线区域，也可以把文件拖进去。不符合 `accept`、`maxSize` 或 `maxFiles` 的文件不会进入值，字段会显示错误。占位符、清除按钮和错误文案跟随 `provideAstryxLocale`。

### 结构

- **标签**: 标识字段的文字。即使视觉上隐藏，为了无障碍也始终渲染。
- **说明**: 标签和投放区之间的辅助文字，说明接受的格式或大小限制。
- **投放区**: 点击即可选择文件的区域。投放区模式下也接受拖进来的文件。
- **上传图标**: 投放区里暗示上传的托盘图标。
- **占位**: 还没选文件时的提示文字。
- **文件名**: 显示已选文件的名字。
- **清除按钮**: 去掉已选文件，并把焦点交回输入的关闭按钮。
- **转圈**: 异步上传时出现的加载指示。
- **状态消息**: 带错误、警告或成功和一条消息的校验反馈。

## 最佳实践

| 指引 | 实践 |
| --- | --- |
| 推荐 | 用 `label` 说明这个字段要什么，用 `description` 写清接受的类型。 |
| 推荐 | 用字节数设置 `maxSize`，超大的文件在上传前就被拒绝。 |
| 避免 | 一份必填文档却用拖放区。人们这时更期待一个普通输入框。 |

## 示例

### 基本用法

简历字段，接受不超过 5 MB 的 PDF 或 Word 文档。

```vue
<script setup lang="ts">
import { FileInput } from '@astryx-vue/core/FileInput'
import { shallowRef } from 'vue'

const value = shallowRef<File | File[] | null>(null)
</script>

<template>
  <div style="width: 350px">
    <FileInput
      v-model="value"
      label="Resume"
      accept=".pdf,.docx"
      description="PDF or Word document, up to 5 MB"
      :max-size="5 * 1024 * 1024"
    />
  </div>
</template>
```

## FileInput 属性

| 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `v-model` | `File \| File[] \| null` | `null` | 选中的文件。打开 `isMultiple` 时为数组。 |
| `label` | `string` | — | 必填。无障碍名称。 |
| `accept` | `string` | — | HTML accept 列表，例如 `.pdf,.docx` 或 `image/*`。 |
| `isMultiple` | `boolean` | `false` | 是否允许多个文件。 |
| `maxSize` | `number` | — | 单个文件的最大字节数。 |
| `maxFiles` | `number` | — | 多选时的文件数量上限。 |
| `mode` | `'input' \| 'dropzone'` | `'input'` | 紧凑输入框，或虚线拖放区。 |
| `placeholder` | `string` | `选择文件` | 未选择时的文字，跟随语言。 |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | 高度，分别是 28、32、36px。 |
| `status` | `FieldStatusInput` | — | 错误、警告或成功。未传时，被拒绝的文件会自己产生错误。 |
| `statusVariant` | `'attached' \| 'detached' \| 'tooltip'` | `'attached'` | 状态消息的位置。 |
| `isDisabled` | `boolean` | `false` | 禁止打开选择器，也不接受拖放。 |
| `disabledMessage` | `string` | — | 禁用原因。和 `isDisabled` 一起用时，悬停或键盘聚焦会显示这段提示，触发按钮仍可聚焦（带 `aria-disabled`），但打不开文件选择框。 |
| `labelTooltip` | `string` | — | 标签末尾信息图标里的说明。 |
| `isRequired` | `boolean` | `false` | 标记为必填。 |
| `isLoading` | `boolean` | `false` | 显示加载指示，用于上传过程。 |
| `description` | `string` | — | 标签下方的说明。 |
| `width` | `number \| string` | — | 整个字段的宽度。数字按像素处理。 |
| `changeAction` | `(files) => void \| Promise<void>` | — | 选中和清除之后调用。失败时发出 `actionError`，等待期间显示加载指示。 |
| `clearLabel` | `string` | — | 清除按钮的读屏名称，默认带字段名。提示只说「清除」。 |
| `containerClass` | `string \| object \| array` | — | 加到字段外壳的 class。在 InputGroup 和横向标签的 FormLayout 里没有外壳，此时不生效，开发环境会警告。 |
| `containerStyle` | `StyleValue` | — | 加到字段外壳的行内样式。 |
| `controlClass` | `string \| object \| array` | — | 加到文件框本体的 class。直接写在组件上的 class 也落在本体，不会警告。 |
| `controlStyle` | `StyleValue` | — | 加到文件框本体的行内样式。直接写在组件上的 style 也落在本体。 |

## 事件

| 名称 | 参数 | 说明 |
| --- | --- | --- |
| `update:modelValue` | `File \| File[] \| null` | 选择变化时触发。 |
| `change` | `File \| File[] \| null` | 携带同一个值。发生在 `changeAction` 之前。 |
| `actionError` | `unknown, File \| File[] \| null` | `changeAction` 抛错时触发。 |
