x-next 1.13.0 → 1.15.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/AI_CODING.md +6 -5
- package/AI_USAGE/catalog.md +108 -105
- package/AI_USAGE/components/alert-api.md +48 -0
- package/AI_USAGE/components/alert.md +222 -3
- package/AI_USAGE/components/bar-chart-api.md +177 -0
- package/AI_USAGE/components/bar-chart.md +297 -3
- package/AI_USAGE/components/boxicons.md +147 -0
- package/AI_USAGE/components/button-api.md +144 -0
- package/AI_USAGE/components/button.md +447 -3
- package/AI_USAGE/components/charts.md +81 -0
- package/AI_USAGE/components/checkbox-api.md +145 -0
- package/AI_USAGE/components/checkbox.md +365 -3
- package/AI_USAGE/components/color-picker-api.md +52 -0
- package/AI_USAGE/components/color-picker.md +211 -3
- package/AI_USAGE/components/config-provider-api.md +38 -0
- package/AI_USAGE/components/config-provider.md +250 -3
- package/AI_USAGE/components/date-picker-api.md +1114 -0
- package/AI_USAGE/components/date-picker.md +378 -3
- package/AI_USAGE/components/dialog-api.md +142 -0
- package/AI_USAGE/components/dialog.md +284 -3
- package/AI_USAGE/components/draggable-api.md +146 -0
- package/AI_USAGE/components/draggable.md +259 -0
- package/AI_USAGE/components/drawer-api.md +78 -0
- package/AI_USAGE/components/drawer.md +256 -0
- package/AI_USAGE/components/dropdown-api.md +529 -0
- package/AI_USAGE/components/dropdown.md +392 -3
- package/AI_USAGE/components/empty-api.md +36 -0
- package/AI_USAGE/components/empty.md +227 -3
- package/AI_USAGE/components/finicon.md +88 -0
- package/AI_USAGE/components/form-api.md +524 -0
- package/AI_USAGE/components/form.md +454 -0
- package/AI_USAGE/components/grid-api.md +527 -0
- package/AI_USAGE/components/grid.md +224 -0
- package/AI_USAGE/components/heatmap-api.md +122 -0
- package/AI_USAGE/components/heatmap.md +220 -0
- package/AI_USAGE/components/icon-api.md +4707 -0
- package/AI_USAGE/components/icon.md +257 -0
- package/AI_USAGE/components/image-api.md +440 -0
- package/AI_USAGE/components/image.md +465 -0
- package/AI_USAGE/components/input-api.md +184 -0
- package/AI_USAGE/components/input-number-api.md +66 -0
- package/AI_USAGE/components/input-number.md +294 -0
- package/AI_USAGE/components/input-tag-api.md +114 -0
- package/AI_USAGE/components/input-tag.md +482 -0
- package/AI_USAGE/components/input.md +293 -3
- package/AI_USAGE/components/layout-api.md +147 -0
- package/AI_USAGE/components/layout.md +230 -0
- package/AI_USAGE/components/line-chart-api.md +197 -0
- package/AI_USAGE/components/line-chart.md +306 -3
- package/AI_USAGE/components/link-api.md +42 -0
- package/AI_USAGE/components/link.md +197 -0
- package/AI_USAGE/components/menu-api.md +516 -0
- package/AI_USAGE/components/menu.md +268 -0
- package/AI_USAGE/components/message-api.md +215 -0
- package/AI_USAGE/components/message-box-api.md +108 -0
- package/AI_USAGE/components/message-box.md +268 -3
- package/AI_USAGE/components/message.md +231 -3
- package/AI_USAGE/components/network-api.md +91 -0
- package/AI_USAGE/components/network.md +378 -0
- package/AI_USAGE/components/notification-api.md +236 -0
- package/AI_USAGE/components/notification.md +275 -3
- package/AI_USAGE/components/pagination-api.md +62 -0
- package/AI_USAGE/components/pagination.md +380 -3
- package/AI_USAGE/components/pie-chart-api.md +109 -0
- package/AI_USAGE/components/pie-chart.md +245 -3
- package/AI_USAGE/components/popconfirm-api.md +75 -0
- package/AI_USAGE/components/popconfirm.md +231 -3
- package/AI_USAGE/components/popover-api.md +75 -0
- package/AI_USAGE/components/popover.md +208 -3
- package/AI_USAGE/components/popup-api.md +75 -0
- package/AI_USAGE/components/popup.md +52 -0
- package/AI_USAGE/components/progress-api.md +44 -0
- package/AI_USAGE/components/progress.md +248 -3
- package/AI_USAGE/components/radio-api.md +165 -0
- package/AI_USAGE/components/radio.md +259 -3
- package/AI_USAGE/components/rate-api.md +54 -0
- package/AI_USAGE/components/rate.md +233 -3
- package/AI_USAGE/components/resize-box-api.md +56 -0
- package/AI_USAGE/components/resize-box.md +231 -0
- package/AI_USAGE/components/scrollbar-api.md +77 -0
- package/AI_USAGE/components/scrollbar.md +218 -0
- package/AI_USAGE/components/select-api.md +758 -0
- package/AI_USAGE/components/select.md +640 -3
- package/AI_USAGE/components/skeleton-api.md +85 -0
- package/AI_USAGE/components/skeleton.md +150 -0
- package/AI_USAGE/components/slider-api.md +51 -0
- package/AI_USAGE/components/slider.md +233 -3
- package/AI_USAGE/components/space-api.md +38 -0
- package/AI_USAGE/components/space.md +145 -0
- package/AI_USAGE/components/spin-api.md +43 -0
- package/AI_USAGE/components/spin.md +205 -0
- package/AI_USAGE/components/steps-api.md +111 -0
- package/AI_USAGE/components/steps.md +201 -3
- package/AI_USAGE/components/switch-api.md +58 -0
- package/AI_USAGE/components/switch.md +290 -3
- package/AI_USAGE/components/table-api.md +2919 -0
- package/AI_USAGE/components/table.md +1457 -0
- package/AI_USAGE/components/tabs-api.md +135 -0
- package/AI_USAGE/components/tabs.md +308 -3
- package/AI_USAGE/components/tag-api.md +124 -0
- package/AI_USAGE/components/tag.md +292 -0
- package/AI_USAGE/components/textarea-api.md +55 -0
- package/AI_USAGE/components/textarea.md +326 -3
- package/AI_USAGE/components/time-picker-api.md +137 -0
- package/AI_USAGE/components/time-picker.md +271 -3
- package/AI_USAGE/components/time-range-selector-api.md +44 -0
- package/AI_USAGE/components/time-range-selector.md +355 -0
- package/AI_USAGE/components/timeline-api.md +93 -0
- package/AI_USAGE/components/timeline.md +312 -0
- package/AI_USAGE/components/tooltip-api.md +77 -0
- package/AI_USAGE/components/tooltip.md +182 -0
- package/AI_USAGE/components/tree-select-api.md +498 -0
- package/AI_USAGE/components/tree-select.md +744 -0
- package/AI_USAGE/components/trend-chart-api.md +119 -0
- package/AI_USAGE/components/trend-chart.md +335 -0
- package/AI_USAGE/components/trigger-api.md +124 -0
- package/AI_USAGE/components/trigger.md +185 -0
- package/AI_USAGE/components/upload-api.md +200 -0
- package/AI_USAGE/components/upload.md +711 -3
- package/AI_USAGE/components/watermark-api.md +87 -0
- package/AI_USAGE/components/watermark.md +200 -0
- package/AI_USAGE/core.md +1 -1
- package/AI_USAGE/index.md +36 -7
- package/AI_USAGE/recipes/form-on-demand.md +1 -1
- package/AI_USAGE.md +34972 -5
- package/CHANGELOG.md +39 -0
- package/dist/_props/css.d.ts +2 -1
- package/dist/_utils/inherit-component-type.d.ts +11 -0
- package/dist/components/bar-chart/BarChart.d.ts +1 -1
- package/dist/components/bar-chart/index.d.ts +3 -3
- package/dist/components/date-picker/index.d.ts +420 -5
- package/dist/components/form-input/index.d.ts +617 -2
- package/dist/components/index.d.ts +1 -0
- package/dist/components/layout/index.d.ts +689 -1
- package/dist/components/line-chart/LineChart.d.ts +1 -1
- package/dist/components/line-chart/index.d.ts +3 -3
- package/dist/components/line-chart/interface.d.ts +2 -2
- package/dist/components/menu/index.d.ts +786 -1
- package/dist/components/tree-select/TreeNode.vue.d.ts +478 -0
- package/dist/components/tree-select/TreeSelect.d.ts +1549 -0
- package/dist/components/tree-select/TreeSelectDropdown.vue.d.ts +569 -0
- package/dist/components/tree-select/hooks/use-tree-select.d.ts +92 -0
- package/dist/components/tree-select/index.d.ts +3321 -0
- package/dist/components/tree-select/interface.d.ts +203 -0
- package/dist/components/tree-select/utils.d.ts +64 -0
- package/dist/es/_styles/palette-aliases.js +3 -0
- package/dist/es/_utils/inherit-component-type.js +6 -0
- package/dist/es/components/_components/feedback-icon/index.vue2.js +10 -10
- package/dist/es/components/button/Button.js +9 -9
- package/dist/es/components/date-picker/index.js +18 -17
- package/dist/es/components/drawer/src/Drawer.vue.js +6 -6
- package/dist/es/components/form-input/index.js +12 -11
- package/dist/es/components/form-switch/Switch.vue2.js +18 -18
- package/dist/es/components/image/Image.vue2.js +13 -13
- package/dist/es/components/image/ImagePreview.vue2.js +33 -33
- package/dist/es/components/index.js +55 -52
- package/dist/es/components/layout/index.js +10 -9
- package/dist/es/components/link/Link.vue2.js +12 -12
- package/dist/es/components/menu/hooks/use-level.js +5 -5
- package/dist/es/components/menu/index.js +13 -10
- package/dist/es/components/message/message.vue2.js +13 -13
- package/dist/es/components/spin/Spin.js +28 -28
- package/dist/es/components/tag/Tag.vue2.js +22 -22
- package/dist/es/components/timeline/src/Timeline.js +7 -7
- package/dist/es/components/tree-select/TreeNode.vue.js +91 -0
- package/dist/es/components/tree-select/TreeNode.vue2.js +162 -0
- package/dist/es/components/tree-select/TreeSelect.js +865 -0
- package/dist/es/components/tree-select/TreeSelectDropdown.vue.js +57 -0
- package/dist/es/components/tree-select/TreeSelectDropdown.vue2.js +79 -0
- package/dist/es/components/tree-select/hooks/use-tree-select.js +376 -0
- package/dist/es/components/tree-select/index.js +13 -0
- package/dist/es/components/tree-select/utils.js +181 -0
- package/dist/es/icons/animation/animation-loading.js +2 -2
- package/dist/es/index.js +71 -68
- package/dist/es/style.css +1 -1
- package/dist/index.cjs +3 -3
- package/dist/index.umd.js +3 -3
- package/dist/style/base.css +1 -1
- package/dist/style/dependencies.json +10 -0
- package/dist/style/drawer.css +1 -1
- package/dist/style/table.css +1 -1
- package/dist/style/tree-select.css +1 -0
- package/dist/style/tree-select.js +8 -0
- package/dist/style.css +1 -1
- package/dist/types.d.ts +2 -0
- package/package.json +11 -5
- package/volar.d.ts +2 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
<!-- Generated by pnpm generate:ai-usage. Edit script/ai-usage/content.md and script/ai-usage/. -->
|
|
2
2
|
# Upload AI 使用
|
|
3
3
|
|
|
4
|
-
适用版本:x-next@1.
|
|
4
|
+
适用版本:x-next@1.15.0;来源指纹:`47ea153587c8d0b3bf318977b10bde9f8dd4acff90c5818541ba6513252ad362`。
|
|
5
5
|
|
|
6
|
-
先读 [通用接入](../core.md)。公开导出:`Upload
|
|
6
|
+
先读 [通用接入](../core.md)。公开导出:`Upload`;主要模型:v-model:file-list。完整 Props、Events、Slots、实例方法与服务签名见 [本地 API](./upload-api.md),包含本组件族全部子组件和别名。
|
|
7
7
|
|
|
8
8
|
按需样式:`x-next/style/upload`(包含 base.css、image.css、progress.css、trigger.css、tooltip.css、upload.css)。代码块未单独引样式时在应用入口加载全量样式,或按实际所有组件加载 JS 入口;不要仅因复制一个组件页就漏掉其中的 Button 等组合依赖。
|
|
9
9
|
|
|
10
|
+
## 已核验行为与示例
|
|
11
|
+
|
|
10
12
|
[组件文档与 live demo](https://x-next.yjmf.com/components/upload)。`action` 是上传地址,也可用 `customRequest` 接管请求;两者都没提供时组件不会发起上传。`name` 是 FormData 字段名,默认 `'file'`;`method` 默认 `'post'`,`withCredentials` 默认 `false`。
|
|
11
13
|
|
|
12
14
|
**接入已有上传方法的推荐写法**:`customRequest` 返回 Promise 时组件自动接管状态,通常 resolve 成功(结果写入 `file.response`,可用 `responseUrlKey` 提取 url,字符串支持 `data.url` 嵌套路径)、reject 失败并出现重试入口,无需手动调用 `onSuccess` / `onError`。axios、flyio、fetch 或任何返回 Promise 的方法都适用。fetch `Response` 为非 2xx 时,即使 Promise resolve 也按失败处理,`error.status` 为状态码;成功响应仅对 JSON/text 内容类型解析,二进制等其他类型保留 Response 本体,不自动转为 Blob。需要进度与取消时,把 option 的 `signal`(取消 / 移除 / 卸载时触发)与 `onProgress`(0 ~ 1)透传给底层请求。返回 `{ abort }` 并手动回调的旧写法仍然支持。
|
|
@@ -57,4 +59,710 @@ function beforeUpload(file: File) {
|
|
|
57
59
|
</template>
|
|
58
60
|
```
|
|
59
61
|
|
|
60
|
-
|
|
62
|
+
## 场景用法
|
|
63
|
+
|
|
64
|
+
以下示例从本地 `src/docs/components/upload.md` 显式导入到独立 AI 源,补齐局部注册、状态和样式后进行严格模板检查。上方已核验行为优先;原始 prop 默认值、包装透传和示例业务适配分别查 [完整 API](./upload-api.md) 与场景说明。类型通过不代表所有散文和交互已验证。
|
|
65
|
+
|
|
66
|
+
### 用途与选型
|
|
67
|
+
|
|
68
|
+
用于把本地文件选择或拖拽到页面并上传到服务端,支持文件列表管理、进度、失败重试和图片预览。
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
### 何时使用
|
|
72
|
+
|
|
73
|
+
- 需要在表单、详情页或配置面板中让用户提交附件、图片、报表等文件。
|
|
74
|
+
- 需要展示上传进度、限制文件数量与类型,或对失败文件提供重试入口。
|
|
75
|
+
- 需要拖拽、粘贴、文件夹选择等更轻量的收集方式。
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
### 接入已有上传方法
|
|
79
|
+
|
|
80
|
+
`customRequest` 返回 Promise 时,组件自动接管状态:业务只需调用项目里已经封装好的上传方法,**不用手动回调 `onSuccess` / `onError`**。resolve 即成功,结果落在 `file.response`;reject 即失败,列表出现重试入口。axios、flyio、fetch 或任何返回 Promise 的方法都可以直接接入。
|
|
81
|
+
|
|
82
|
+
接入 `fetch` 时直接返回 `Response` 即可,组件会读取响应体并判断 HTTP 状态:
|
|
83
|
+
|
|
84
|
+
接入封装好的 axios 实例时返回它的 Promise;`axios` 成功的响应体(`response.data`)会成为 `file.response`:
|
|
85
|
+
|
|
86
|
+
> 也可以直接用内置请求:`<x-upload action="/api/upload" name="file" />`。`name` 是 FormData 的字段名。
|
|
87
|
+
|
|
88
|
+
```vue
|
|
89
|
+
<script setup lang="ts">
|
|
90
|
+
import 'x-next/style.css';
|
|
91
|
+
import { Upload } from 'x-next';
|
|
92
|
+
import { ref } from 'vue';
|
|
93
|
+
|
|
94
|
+
import type { UploadFile } from 'x-next';
|
|
95
|
+
|
|
96
|
+
const fileList = ref<UploadFile[]>([]);
|
|
97
|
+
|
|
98
|
+
// 项目里已封装好的上传方法:返回 Promise 即可,组件自动显示进度、成功与失败
|
|
99
|
+
const uploadApi = ({ file, orderId }: { file: File; orderId: string }) =>
|
|
100
|
+
new Promise<{ code: number; data: { url: string } }>((resolve) => {
|
|
101
|
+
// 实际项目里替换为 axios / flyio / fetch 调用
|
|
102
|
+
window.setTimeout(() => {
|
|
103
|
+
resolve({ code: 0, data: { url: `/files/${orderId}/${encodeURIComponent(file.name)}` } });
|
|
104
|
+
}, 600);
|
|
105
|
+
});
|
|
106
|
+
</script>
|
|
107
|
+
<template>
|
|
108
|
+
<Upload
|
|
109
|
+
v-model:file-list="fileList"
|
|
110
|
+
:custom-request="({ file }) => uploadApi({ file, orderId: 'SO-20261003' })"
|
|
111
|
+
response-url-key="data.url"
|
|
112
|
+
/>
|
|
113
|
+
</template>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 响应提取与失败处理
|
|
117
|
+
|
|
118
|
+
字符串 `responseUrlKey` 支持 `data.url` 这样的嵌套路径,也可以传函数自行从 `response` 取值。方法 reject 后文件进入错误态,列表内点击重试即可再次调用同一个方法;`error.message` 会作为失败图标的提示,图片卡则会直接显示该文案。
|
|
119
|
+
|
|
120
|
+
```vue
|
|
121
|
+
<script setup lang="ts">
|
|
122
|
+
import 'x-next/style.css';
|
|
123
|
+
import { Upload } from 'x-next';
|
|
124
|
+
import { ref } from 'vue';
|
|
125
|
+
|
|
126
|
+
import type { UploadFile } from 'x-next';
|
|
127
|
+
|
|
128
|
+
const logs = ref<string[]>([]);
|
|
129
|
+
const pushLog = (message: string) => {
|
|
130
|
+
logs.value = [message, ...logs.value].slice(0, 4);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// 服务端失败时直接 reject;组件的 error.message 会显示该文案
|
|
134
|
+
const uploadApi = ({ file }: { file: File }) =>
|
|
135
|
+
new Promise<{ data: { url: string } }>((_resolve, reject) => {
|
|
136
|
+
window.setTimeout(() => reject(new Error('服务端拒绝了该文件')), 600);
|
|
137
|
+
});
|
|
138
|
+
</script>
|
|
139
|
+
<template>
|
|
140
|
+
<Upload
|
|
141
|
+
response-url-key="data.url"
|
|
142
|
+
:custom-request="uploadApi"
|
|
143
|
+
@error="(file) => pushLog(`${file.name} 上传失败,已提供重试入口`)"
|
|
144
|
+
@success="(file) => pushLog(`${file.name} 上传成功`)"
|
|
145
|
+
/>
|
|
146
|
+
<ul>
|
|
147
|
+
<li v-for="(message, index) in logs" :key="index">{{ message }}</li>
|
|
148
|
+
</ul>
|
|
149
|
+
</template>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 进度与取消
|
|
153
|
+
|
|
154
|
+
业务方法需要展示进度时,把 `signal` 与 `onProgress` 透传给底层请求:`signal` 在取消、移除或组件卸载时触发,可用于中止 fetch / axios;`onProgress` 传 `0 ~ 1` 的百分比,不调用则进度只有 0 与 1 两个状态。
|
|
155
|
+
|
|
156
|
+
```vue
|
|
157
|
+
<script setup lang="ts">
|
|
158
|
+
import 'x-next/style.css';
|
|
159
|
+
import { Upload } from 'x-next';
|
|
160
|
+
import { ref } from 'vue';
|
|
161
|
+
|
|
162
|
+
import type { UploadFile, UploadRequestOption } from 'x-next';
|
|
163
|
+
|
|
164
|
+
const fileList = ref<UploadFile[]>([]);
|
|
165
|
+
|
|
166
|
+
const uploadApi = (options: UploadRequestOption) =>
|
|
167
|
+
new Promise<{ data: { url: string } }>((resolve, reject) => {
|
|
168
|
+
let percent = 0;
|
|
169
|
+
const timer = window.setInterval(() => {
|
|
170
|
+
percent += 10;
|
|
171
|
+
if (percent < 100) {
|
|
172
|
+
// onProgress 传 0 ~ 1
|
|
173
|
+
options.onProgress(percent / 100);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
window.clearInterval(timer);
|
|
177
|
+
resolve({ data: { url: `/files/${encodeURIComponent(options.file.name)}` } });
|
|
178
|
+
}, 200);
|
|
179
|
+
// signal 在取消 / 移除 / 卸载时触发;用 fetch 时可直接传 signal
|
|
180
|
+
options.signal?.addEventListener('abort', () => {
|
|
181
|
+
window.clearInterval(timer);
|
|
182
|
+
reject(new DOMException('Aborted', 'AbortError'));
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
</script>
|
|
186
|
+
<template>
|
|
187
|
+
<Upload
|
|
188
|
+
v-model:file-list="fileList"
|
|
189
|
+
:custom-request="uploadApi"
|
|
190
|
+
response-url-key="data.url"
|
|
191
|
+
/>
|
|
192
|
+
</template>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### 多选与数量限制
|
|
196
|
+
|
|
197
|
+
`multiple` 开启多选;`limit` 限制列表数量,超出的文件不会进入列表,而是触发 `exceed`。
|
|
198
|
+
|
|
199
|
+
```vue
|
|
200
|
+
<script setup lang="ts">
|
|
201
|
+
import 'x-next/style.css';
|
|
202
|
+
import { Upload } from 'x-next';
|
|
203
|
+
import { ref } from 'vue';
|
|
204
|
+
|
|
205
|
+
import type { UploadFile, UploadRequestOption } from 'x-next';
|
|
206
|
+
|
|
207
|
+
const mockRequest = (options: UploadRequestOption) => {
|
|
208
|
+
let percent = 0;
|
|
209
|
+
const timer = window.setInterval(() => {
|
|
210
|
+
percent = Math.min(1, percent + 0.2);
|
|
211
|
+
options.onProgress(percent);
|
|
212
|
+
if (percent === 1) {
|
|
213
|
+
window.clearInterval(timer);
|
|
214
|
+
options.onSuccess({ url: `/files/${encodeURIComponent(options.file.name)}` });
|
|
215
|
+
}
|
|
216
|
+
}, 240);
|
|
217
|
+
return { abort: () => window.clearInterval(timer) };
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const logs = ref<string[]>([]);
|
|
221
|
+
const pushLog = (message: string) => {
|
|
222
|
+
logs.value = [message, ...logs.value].slice(0, 5);
|
|
223
|
+
};
|
|
224
|
+
</script>
|
|
225
|
+
<template>
|
|
226
|
+
<Upload
|
|
227
|
+
multiple
|
|
228
|
+
:limit="2"
|
|
229
|
+
tip="最多 2 个文件"
|
|
230
|
+
response-url-key="url"
|
|
231
|
+
:custom-request="mockRequest"
|
|
232
|
+
@exceed="(info) => pushLog(`超出限制:${info.files.map((file) => file.name).join('、')}`)"
|
|
233
|
+
/>
|
|
234
|
+
<ul
|
|
235
|
+
><li v-for="(message, index) in logs" :key="index">{{ message }}</li></ul
|
|
236
|
+
>
|
|
237
|
+
</template>
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### 受控文件列表
|
|
241
|
+
|
|
242
|
+
传入 `fileList` 后由外部驱动列表;配合 `v-model:file-list` 可以拿到每一次变更后的完整列表。
|
|
243
|
+
|
|
244
|
+
```vue
|
|
245
|
+
<script setup lang="ts">
|
|
246
|
+
import 'x-next/style.css';
|
|
247
|
+
import { Upload, Button } from 'x-next';
|
|
248
|
+
import { ref } from 'vue';
|
|
249
|
+
|
|
250
|
+
const fileList = ref<UploadFile[]>([
|
|
251
|
+
{
|
|
252
|
+
uid: 'seed-1',
|
|
253
|
+
name: '季度报表.xlsx',
|
|
254
|
+
status: 'done',
|
|
255
|
+
percent: 1,
|
|
256
|
+
url: 'https://example.com/files/report.xlsx',
|
|
257
|
+
},
|
|
258
|
+
]);
|
|
259
|
+
|
|
260
|
+
import type { UploadFile, UploadRequestOption } from 'x-next';
|
|
261
|
+
|
|
262
|
+
const mockRequest = (options: UploadRequestOption) => {
|
|
263
|
+
let percent = 0;
|
|
264
|
+
const timer = window.setInterval(() => {
|
|
265
|
+
percent = Math.min(1, percent + 0.2);
|
|
266
|
+
options.onProgress(percent);
|
|
267
|
+
if (percent === 1) {
|
|
268
|
+
window.clearInterval(timer);
|
|
269
|
+
options.onSuccess({ url: `/files/${encodeURIComponent(options.file.name)}` });
|
|
270
|
+
}
|
|
271
|
+
}, 240);
|
|
272
|
+
return { abort: () => window.clearInterval(timer) };
|
|
273
|
+
};
|
|
274
|
+
</script>
|
|
275
|
+
<template>
|
|
276
|
+
<Upload v-model:file-list="fileList" response-url-key="url" :custom-request="mockRequest" />
|
|
277
|
+
<Button size="small" @click="fileList = []">清空列表</Button>
|
|
278
|
+
</template>
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### 拖拽上传
|
|
282
|
+
|
|
283
|
+
打开 `draggable` 后整块区域可点击、可拖入;批量收集需同时开启 `multiple`,`tip` 用于说明限制。默认插槽会替换整块区域内容。
|
|
284
|
+
|
|
285
|
+
`directory` 开启后,选择文件夹与拖入文件夹都会递归收集其中的文件,并保留相对路径。
|
|
286
|
+
|
|
287
|
+
```vue
|
|
288
|
+
<script setup lang="ts">
|
|
289
|
+
import 'x-next/style.css';
|
|
290
|
+
import { Upload } from 'x-next';
|
|
291
|
+
import { ref } from 'vue';
|
|
292
|
+
|
|
293
|
+
import type { UploadFile, UploadRequestOption } from 'x-next';
|
|
294
|
+
|
|
295
|
+
const mockRequest = (options: UploadRequestOption) => {
|
|
296
|
+
let percent = 0;
|
|
297
|
+
const timer = window.setInterval(() => {
|
|
298
|
+
percent = Math.min(1, percent + 0.2);
|
|
299
|
+
options.onProgress(percent);
|
|
300
|
+
if (percent === 1) {
|
|
301
|
+
window.clearInterval(timer);
|
|
302
|
+
options.onSuccess({ url: `/files/${encodeURIComponent(options.file.name)}` });
|
|
303
|
+
}
|
|
304
|
+
}, 240);
|
|
305
|
+
return { abort: () => window.clearInterval(timer) };
|
|
306
|
+
};
|
|
307
|
+
</script>
|
|
308
|
+
<template>
|
|
309
|
+
<Upload
|
|
310
|
+
draggable
|
|
311
|
+
multiple
|
|
312
|
+
response-url-key="url"
|
|
313
|
+
:custom-request="mockRequest"
|
|
314
|
+
tip="支持批量选择,单个文件不超过 20MB"
|
|
315
|
+
>
|
|
316
|
+
<div class="upload-demo-drag">
|
|
317
|
+
<strong>拖拽文件到此处</strong>
|
|
318
|
+
<span>或点击选择,支持任意类型</span>
|
|
319
|
+
</div>
|
|
320
|
+
</Upload>
|
|
321
|
+
</template>
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### 图片列表
|
|
325
|
+
|
|
326
|
+
`listType="picture"` 在行内展示缩略图;图片文件在加入列表时自动生成缩略图,也可以直接传 `url` / `thumbUrl`。
|
|
327
|
+
|
|
328
|
+
```vue
|
|
329
|
+
<script setup lang="ts">
|
|
330
|
+
import 'x-next/style.css';
|
|
331
|
+
import { Upload } from 'x-next';
|
|
332
|
+
import { ref } from 'vue';
|
|
333
|
+
|
|
334
|
+
const fileList = ref<UploadFile[]>([
|
|
335
|
+
{
|
|
336
|
+
uid: 'pic-1',
|
|
337
|
+
name: '封面图.png',
|
|
338
|
+
status: 'done',
|
|
339
|
+
percent: 1,
|
|
340
|
+
type: 'image/png',
|
|
341
|
+
url: 'https://example.com/cover.png',
|
|
342
|
+
},
|
|
343
|
+
]);
|
|
344
|
+
|
|
345
|
+
import type { UploadFile, UploadRequestOption } from 'x-next';
|
|
346
|
+
|
|
347
|
+
const mockRequest = (options: UploadRequestOption) => {
|
|
348
|
+
let percent = 0;
|
|
349
|
+
const timer = window.setInterval(() => {
|
|
350
|
+
percent = Math.min(1, percent + 0.2);
|
|
351
|
+
options.onProgress(percent);
|
|
352
|
+
if (percent === 1) {
|
|
353
|
+
window.clearInterval(timer);
|
|
354
|
+
options.onSuccess({ url: `/files/${encodeURIComponent(options.file.name)}` });
|
|
355
|
+
}
|
|
356
|
+
}, 240);
|
|
357
|
+
return { abort: () => window.clearInterval(timer) };
|
|
358
|
+
};
|
|
359
|
+
</script>
|
|
360
|
+
<template>
|
|
361
|
+
<Upload
|
|
362
|
+
response-url-key="url"
|
|
363
|
+
:custom-request="mockRequest"
|
|
364
|
+
v-model:file-list="fileList"
|
|
365
|
+
list-type="picture"
|
|
366
|
+
accept="image/*"
|
|
367
|
+
image-loading="lazy"
|
|
368
|
+
multiple
|
|
369
|
+
>
|
|
370
|
+
<template #image="{ file }">
|
|
371
|
+
<img :src="file.thumbUrl || file.url" :alt="file.name" loading="lazy" />
|
|
372
|
+
</template>
|
|
373
|
+
</Upload>
|
|
374
|
+
</template>
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### 图片卡与预览
|
|
378
|
+
|
|
379
|
+
`listType="picture-card"` 以瓦片展示,适合头像、封面、商品图等场景。打开 `imagePreview` 后点击卡片的预览按钮会用 `ImagePreview` 打开大图,并支持左右切换。
|
|
380
|
+
|
|
381
|
+
```vue
|
|
382
|
+
<script setup lang="ts">
|
|
383
|
+
import 'x-next/style.css';
|
|
384
|
+
import { Upload } from 'x-next';
|
|
385
|
+
import { ref } from 'vue';
|
|
386
|
+
|
|
387
|
+
import type { UploadFile, UploadRequestOption } from 'x-next';
|
|
388
|
+
|
|
389
|
+
const fileList = ref<UploadFile[]>([
|
|
390
|
+
{
|
|
391
|
+
uid: 'card-1',
|
|
392
|
+
name: '主视觉.png',
|
|
393
|
+
status: 'done',
|
|
394
|
+
percent: 1,
|
|
395
|
+
type: 'image/png',
|
|
396
|
+
url: 'https://example.com/cover.png',
|
|
397
|
+
},
|
|
398
|
+
]);
|
|
399
|
+
const mockRequest = (options: UploadRequestOption) => {
|
|
400
|
+
const timer = window.setTimeout(
|
|
401
|
+
() => options.onSuccess({ url: `/files/${encodeURIComponent(options.file.name)}` }),
|
|
402
|
+
600,
|
|
403
|
+
);
|
|
404
|
+
return { abort: () => window.clearTimeout(timer) };
|
|
405
|
+
};
|
|
406
|
+
</script>
|
|
407
|
+
<template>
|
|
408
|
+
<Upload
|
|
409
|
+
v-model:file-list="fileList"
|
|
410
|
+
list-type="picture-card"
|
|
411
|
+
accept="image/*"
|
|
412
|
+
image-preview
|
|
413
|
+
:custom-request="mockRequest"
|
|
414
|
+
response-url-key="url"
|
|
415
|
+
:limit="4"
|
|
416
|
+
tip="最多 4 张"
|
|
417
|
+
/>
|
|
418
|
+
</template>
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### 手动上传
|
|
422
|
+
|
|
423
|
+
`auto-upload` 关闭后,文件先以 `init` 状态入列,由业务调用 `submit()` 决定何时上传;`abort()` 取消单个或全部上传,`retry()` 重新上传失败文件。
|
|
424
|
+
|
|
425
|
+
```vue
|
|
426
|
+
<script setup lang="ts">
|
|
427
|
+
import 'x-next/style.css';
|
|
428
|
+
import { Upload, Button } from 'x-next';
|
|
429
|
+
import { ref } from 'vue';
|
|
430
|
+
|
|
431
|
+
const uploadRef = ref();
|
|
432
|
+
const fileList = ref<UploadFile[]>([]);
|
|
433
|
+
|
|
434
|
+
import type { UploadFile, UploadRequestOption } from 'x-next';
|
|
435
|
+
|
|
436
|
+
const mockRequest = (options: UploadRequestOption) => {
|
|
437
|
+
let percent = 0;
|
|
438
|
+
const timer = window.setInterval(() => {
|
|
439
|
+
percent = Math.min(1, percent + 0.2);
|
|
440
|
+
options.onProgress(percent);
|
|
441
|
+
if (percent === 1) {
|
|
442
|
+
window.clearInterval(timer);
|
|
443
|
+
options.onSuccess({ url: `/files/${encodeURIComponent(options.file.name)}` });
|
|
444
|
+
}
|
|
445
|
+
}, 240);
|
|
446
|
+
return { abort: () => window.clearInterval(timer) };
|
|
447
|
+
};
|
|
448
|
+
</script>
|
|
449
|
+
<template>
|
|
450
|
+
<Upload
|
|
451
|
+
response-url-key="url"
|
|
452
|
+
:custom-request="mockRequest"
|
|
453
|
+
ref="uploadRef"
|
|
454
|
+
v-model:file-list="fileList"
|
|
455
|
+
:auto-upload="false"
|
|
456
|
+
multiple
|
|
457
|
+
/>
|
|
458
|
+
<Button type="primary" @click="uploadRef?.submit()">开始上传</Button>
|
|
459
|
+
<Button @click="uploadRef?.abort()">全部取消</Button>
|
|
460
|
+
</template>
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
### 上传校验与失败重试
|
|
464
|
+
|
|
465
|
+
`beforeUpload` 在文件入列前执行:返回 `false` 拦截,返回新的 `File` 可替换内容,返回 Promise 支持异步校验。上传失败的文件会进入错误态,列表内提供重试入口。
|
|
466
|
+
|
|
467
|
+
```vue
|
|
468
|
+
<script setup lang="ts">
|
|
469
|
+
import 'x-next/style.css';
|
|
470
|
+
import { Upload } from 'x-next';
|
|
471
|
+
import { ref } from 'vue';
|
|
472
|
+
|
|
473
|
+
const fileList = ref<UploadFile[]>([]);
|
|
474
|
+
|
|
475
|
+
const beforeUpload = (file: File) => {
|
|
476
|
+
if (file.size > 1024 * 200) {
|
|
477
|
+
// 返回 false 表示拦截,文件不会进入列表
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
return true;
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
import type { UploadFile, UploadRequestOption } from 'x-next';
|
|
484
|
+
|
|
485
|
+
const mockRequest = (options: UploadRequestOption) => {
|
|
486
|
+
let percent = 0;
|
|
487
|
+
const timer = window.setInterval(() => {
|
|
488
|
+
percent = Math.min(1, percent + 0.2);
|
|
489
|
+
options.onProgress(percent);
|
|
490
|
+
if (percent === 1) {
|
|
491
|
+
window.clearInterval(timer);
|
|
492
|
+
options.onError(new Error('示例:服务端拒绝了该文件'));
|
|
493
|
+
}
|
|
494
|
+
}, 240);
|
|
495
|
+
return { abort: () => window.clearInterval(timer) };
|
|
496
|
+
};
|
|
497
|
+
</script>
|
|
498
|
+
<template>
|
|
499
|
+
<Upload
|
|
500
|
+
v-model:file-list="fileList"
|
|
501
|
+
multiple
|
|
502
|
+
accept=".png,.jpg,.jpeg,.pdf"
|
|
503
|
+
:before-upload="beforeUpload"
|
|
504
|
+
response-url-key="url"
|
|
505
|
+
:custom-request="mockRequest"
|
|
506
|
+
/>
|
|
507
|
+
</template>
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### 自定义入口与列表项
|
|
511
|
+
|
|
512
|
+
`upload-button` 替换入口内容,组件保留文件 input、点击、键盘和拖拽处理,`file-name` 定制文件名,`extra-button` 在操作区追加自定义操作,`upload-item` 可以整体替换某一行的渲染。
|
|
513
|
+
|
|
514
|
+
```vue
|
|
515
|
+
<script setup lang="ts">
|
|
516
|
+
import 'x-next/style.css';
|
|
517
|
+
import { Upload, Tag, Link } from 'x-next';
|
|
518
|
+
import { ref } from 'vue';
|
|
519
|
+
|
|
520
|
+
const fileList = ref<UploadFile[]>([
|
|
521
|
+
{ uid: 'slot-1', name: '合同扫描件.pdf', status: 'done', percent: 1 },
|
|
522
|
+
]);
|
|
523
|
+
const detail = ref('');
|
|
524
|
+
|
|
525
|
+
import type { UploadFile, UploadRequestOption } from 'x-next';
|
|
526
|
+
|
|
527
|
+
const mockRequest = (options: UploadRequestOption) => {
|
|
528
|
+
let percent = 0;
|
|
529
|
+
const timer = window.setInterval(() => {
|
|
530
|
+
percent = Math.min(1, percent + 0.2);
|
|
531
|
+
options.onProgress(percent);
|
|
532
|
+
if (percent === 1) {
|
|
533
|
+
window.clearInterval(timer);
|
|
534
|
+
options.onSuccess({ url: `/files/${encodeURIComponent(options.file.name)}` });
|
|
535
|
+
}
|
|
536
|
+
}, 240);
|
|
537
|
+
return { abort: () => window.clearInterval(timer) };
|
|
538
|
+
};
|
|
539
|
+
</script>
|
|
540
|
+
<template>
|
|
541
|
+
<Upload response-url-key="url" :custom-request="mockRequest" v-model:file-list="fileList">
|
|
542
|
+
<template #upload-button>
|
|
543
|
+
<Tag color="blue">点击补充附件</Tag>
|
|
544
|
+
</template>
|
|
545
|
+
<template #file-name="{ file }">
|
|
546
|
+
<span>{{ file.name }}(自定义文件名)</span>
|
|
547
|
+
</template>
|
|
548
|
+
<template #file-icon>
|
|
549
|
+
<Tag size="small">附件</Tag>
|
|
550
|
+
</template>
|
|
551
|
+
<template #extra-button="{ file }">
|
|
552
|
+
<Link @click="detail = `查看 ${file.name} 的业务详情`">详情</Link>
|
|
553
|
+
</template>
|
|
554
|
+
</Upload>
|
|
555
|
+
<span>{{ detail }}</span>
|
|
556
|
+
</template>
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### 禁用与粘贴
|
|
560
|
+
|
|
561
|
+
`disabled` 会禁用选择、开始、重试、取消与移除;历史附件仍可预览和下载;`paste` 打开后,聚焦组件即可粘贴剪贴板中的文件(例如截图)。
|
|
562
|
+
|
|
563
|
+
```vue
|
|
564
|
+
<script setup lang="ts">
|
|
565
|
+
import 'x-next/style.css';
|
|
566
|
+
import { Upload } from 'x-next';
|
|
567
|
+
import { ref } from 'vue';
|
|
568
|
+
|
|
569
|
+
const fileList = ref<UploadFile[]>([]);
|
|
570
|
+
const log = ref('');
|
|
571
|
+
|
|
572
|
+
import type { UploadFile, UploadRequestOption } from 'x-next';
|
|
573
|
+
|
|
574
|
+
const mockRequest = (options: UploadRequestOption) => {
|
|
575
|
+
let percent = 0;
|
|
576
|
+
const timer = window.setInterval(() => {
|
|
577
|
+
percent = Math.min(1, percent + 0.2);
|
|
578
|
+
options.onProgress(percent);
|
|
579
|
+
if (percent === 1) {
|
|
580
|
+
window.clearInterval(timer);
|
|
581
|
+
options.onSuccess({ url: `/files/${encodeURIComponent(options.file.name)}` });
|
|
582
|
+
}
|
|
583
|
+
}, 240);
|
|
584
|
+
return { abort: () => window.clearInterval(timer) };
|
|
585
|
+
};
|
|
586
|
+
</script>
|
|
587
|
+
<template>
|
|
588
|
+
<Upload
|
|
589
|
+
response-url-key="url"
|
|
590
|
+
:custom-request="mockRequest"
|
|
591
|
+
v-model:file-list="fileList"
|
|
592
|
+
paste
|
|
593
|
+
multiple
|
|
594
|
+
@change="(info) => (log = info.file.name)"
|
|
595
|
+
/>
|
|
596
|
+
<Upload response-url-key="url" :custom-request="mockRequest" disabled />
|
|
597
|
+
</template>
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### 文件夹选择
|
|
601
|
+
|
|
602
|
+
`directory` 会收集整个目录,自动接收多个文件,不需要额外打开 `multiple`。选择后可以从原始文件的 `webkitRelativePath` 读取路径;拖入目录按批次递归读取子目录。
|
|
603
|
+
|
|
604
|
+
```vue
|
|
605
|
+
<script setup lang="ts">
|
|
606
|
+
import 'x-next/style.css';
|
|
607
|
+
import { Upload } from 'x-next';
|
|
608
|
+
import { ref } from 'vue';
|
|
609
|
+
import type { UploadFile, UploadChangeInfo } from 'x-next';
|
|
610
|
+
const files = ref<UploadFile[]>([]);
|
|
611
|
+
const paths = ref<string[]>([]);
|
|
612
|
+
const recordPath = ({ file }: UploadChangeInfo) => {
|
|
613
|
+
paths.value = [file.file?.webkitRelativePath || file.name, ...paths.value].slice(0, 6);
|
|
614
|
+
};
|
|
615
|
+
</script>
|
|
616
|
+
<template>
|
|
617
|
+
<Upload
|
|
618
|
+
v-model:file-list="files"
|
|
619
|
+
directory
|
|
620
|
+
draggable
|
|
621
|
+
:auto-upload="false"
|
|
622
|
+
@change="recordPath"
|
|
623
|
+
/>
|
|
624
|
+
<ul
|
|
625
|
+
><li v-for="(path, index) in paths" :key="index">{{ path }}</li></ul
|
|
626
|
+
>
|
|
627
|
+
</template>
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
### 替换文件与自定义列表
|
|
631
|
+
|
|
632
|
+
`updateFile(uid, file)` 保留 uid,取消旧请求,清理旧响应和地址,并回到 `init`;点击逐项“开始上传”或调用 `submit()` 发送新内容。自定义 `upload-item` 时操作需由业务调用组件方法。
|
|
633
|
+
|
|
634
|
+
```vue
|
|
635
|
+
<script setup lang="ts">
|
|
636
|
+
import 'x-next/style.css';
|
|
637
|
+
import { Upload, Button } from 'x-next';
|
|
638
|
+
import { ref } from 'vue';
|
|
639
|
+
import type { UploadFile, UploadRequestOption } from 'x-next';
|
|
640
|
+
const uploadRef = ref();
|
|
641
|
+
const files = ref<UploadFile[]>([]);
|
|
642
|
+
const replace = () => {
|
|
643
|
+
if (files.value[0])
|
|
644
|
+
uploadRef.value?.updateFile(
|
|
645
|
+
files.value[0].uid,
|
|
646
|
+
new File(['修订'], '修订稿.txt', { type: 'text/plain' }),
|
|
647
|
+
);
|
|
648
|
+
};
|
|
649
|
+
const request = (option: UploadRequestOption) => {
|
|
650
|
+
const timer = window.setTimeout(() => option.onSuccess({ url: '/files/revised.txt' }), 600);
|
|
651
|
+
return { abort: () => window.clearTimeout(timer) };
|
|
652
|
+
};
|
|
653
|
+
</script>
|
|
654
|
+
<template>
|
|
655
|
+
<Upload
|
|
656
|
+
ref="uploadRef"
|
|
657
|
+
v-model:file-list="files"
|
|
658
|
+
:auto-upload="false"
|
|
659
|
+
:custom-request="request"
|
|
660
|
+
response-url-key="url"
|
|
661
|
+
>
|
|
662
|
+
<template #upload-item="{ file, index }">
|
|
663
|
+
<span>{{ index + 1 }}. {{ file.name }}({{ file.status }})</span>
|
|
664
|
+
<Button :disabled="file.status === 'uploading'" @click="uploadRef?.submit(file)"
|
|
665
|
+
>开始上传</Button
|
|
666
|
+
>
|
|
667
|
+
<Button :disabled="file.status !== 'uploading'" @click="uploadRef?.abort(file)"
|
|
668
|
+
>取消</Button
|
|
669
|
+
>
|
|
670
|
+
</template>
|
|
671
|
+
</Upload>
|
|
672
|
+
<Button :disabled="!files.length" @click="replace">替换第一个文件</Button>
|
|
673
|
+
</template>
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
### Form 联动
|
|
677
|
+
|
|
678
|
+
上传列表绑定 Form 模型字段后,加入、移除和上传状态变化会触发 change 校验。入口关联标签、必填状态和帮助/错误说明;Form 禁用态同时禁用上传操作。
|
|
679
|
+
|
|
680
|
+
```vue
|
|
681
|
+
<script setup lang="ts">
|
|
682
|
+
import 'x-next/style.css';
|
|
683
|
+
import { Form, FormItem, Upload, Button } from 'x-next';
|
|
684
|
+
import { ref } from 'vue';
|
|
685
|
+
import type { UploadFile } from 'x-next';
|
|
686
|
+
const formRef = ref();
|
|
687
|
+
const model = ref({ attachments: [] as UploadFile[] });
|
|
688
|
+
const result = ref('');
|
|
689
|
+
const validate = async () => {
|
|
690
|
+
result.value = (await formRef.value?.validate()) ? '请补充附件' : '校验通过';
|
|
691
|
+
};
|
|
692
|
+
</script>
|
|
693
|
+
<template>
|
|
694
|
+
<Form ref="formRef" :model="model">
|
|
695
|
+
<FormItem
|
|
696
|
+
field="attachments"
|
|
697
|
+
label="附件"
|
|
698
|
+
required
|
|
699
|
+
help="至少选择一个文件"
|
|
700
|
+
:rules="[{ required: true, type: 'array', minLength: 1, message: '请上传附件' }]"
|
|
701
|
+
>
|
|
702
|
+
<Upload v-model:file-list="model.attachments" :auto-upload="false" action="/api/upload" />
|
|
703
|
+
</FormItem>
|
|
704
|
+
</Form>
|
|
705
|
+
<Button @click="validate">校验附件</Button>
|
|
706
|
+
<span>{{ result }}</span>
|
|
707
|
+
</template>
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
### UploadFile
|
|
711
|
+
|
|
712
|
+
| 字段 | 说明 | 类型 |
|
|
713
|
+
| ---------- | ------------------------------------ | -------------------------------------------- |
|
|
714
|
+
| `uid` | 文件唯一标识,外部未提供时自动生成 | `string` |
|
|
715
|
+
| `name` | 文件名 | `string` |
|
|
716
|
+
| `size` | 文件大小(字节) | `number` |
|
|
717
|
+
| `type` | MIME 类型 | `string` |
|
|
718
|
+
| `status` | 状态:待上传 / 上传中 / 完成 / 失败 | `'init' \| 'uploading' \| 'done' \| 'error'` |
|
|
719
|
+
| `percent` | 上传进度,0 ~ 1 | `number` |
|
|
720
|
+
| `url` | 文件地址 | `string` |
|
|
721
|
+
| `thumbUrl` | 缩略图地址,图片文件自动生成本地预览 | `string` |
|
|
722
|
+
| `response` | 上传接口响应体 | `any` |
|
|
723
|
+
| `error` | 失败信息 | `unknown` |
|
|
724
|
+
| `file` | 原始 `File` 对象 | `File` |
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
### UploadRequestOption
|
|
728
|
+
|
|
729
|
+
`customRequest` 收到的参数,三种收尾方式二选一,不要混用:
|
|
730
|
+
|
|
731
|
+
- **返回 Promise**(推荐):resolve 即成功,reject 即失败,无需手动回调;`option` 里的 `signal` / `onProgress` 按需透传给底层请求。
|
|
732
|
+
- **返回 `{ abort }` 或 undefined**:完全手动控制,自行调用 `onProgress` / `onSuccess` / `onError`;`abort` 会在取消、移除、卸载时被调用。
|
|
733
|
+
|
|
734
|
+
| 字段 | 说明 | 类型 |
|
|
735
|
+
| --- | --- | --- |
|
|
736
|
+
| `action` | 解析后的上传地址;未配置 `action` 时为空字符串 | `string` |
|
|
737
|
+
| `method` | 请求方法 | `'post' \| 'put' \| 'patch'` |
|
|
738
|
+
| `name` | FormData 文件字段名 | `string` |
|
|
739
|
+
| `file` | 原始 `File` 对象 | `File` |
|
|
740
|
+
| `fileItem` | 列表项,可读取 `uid` 与业务字段 | `UploadFile` |
|
|
741
|
+
| `data` | 解析后的附加表单字段 | `Record<string, unknown> \| undefined` |
|
|
742
|
+
| `headers` | 请求头 | `Record<string, string> \| undefined` |
|
|
743
|
+
| `withCredentials` | 是否携带 Cookie | `boolean` |
|
|
744
|
+
| `timeout` | 超时毫秒数 | `number` |
|
|
745
|
+
| `signal` | 每次尝试的中止信号;取消 / 移除 / 卸载时触发,可直接透传给 `fetch`、axios | `AbortSignal \| undefined` |
|
|
746
|
+
| `onProgress` | 进度回调,`0 ~ 1` | `(percent: number, event?: ProgressEvent) => void` |
|
|
747
|
+
| `onSuccess` | 手动成功回调 | `(response?: unknown) => void` |
|
|
748
|
+
| `onError` | 手动失败回调 | `(error?: unknown) => void` |
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
### 已知限制
|
|
752
|
+
|
|
753
|
+
- 内置 XHR 只处理单次请求,分片上传、断点续传需要自行实现 `customRequest`。
|
|
754
|
+
- Promise 模式下组件的取消、移除与卸载会触发 `option.signal`;业务未使用 `signal` 时取消仅失效回调,网络请求不会被真正中止,但列表会回到待上传态。
|
|
755
|
+
- 返回 fetch `Response` 时,非 2xx 会按失败处理,`error.status` 为 HTTP 状态码;JSON / text 响应体会解析后写入 `file.response`。
|
|
756
|
+
- 在异步 `customRequest` 中既手动调用回调、又返回 Promise 时,以手动回调结果为准。
|
|
757
|
+
- `accept` 是前端筛选,服务端仍需二次校验类型与大小。
|
|
758
|
+
- `reject` 的 `reason` 为 `limit` 时,文件在 accept 之后被数量上限拦截;类型不符则为 `accept`。
|
|
759
|
+
- 目录读取失败会触发 `reject`,`reason="read"`;此时 `files` 是浏览器在拖拽事件中提供的原始文件,可能为空。
|
|
760
|
+
- 图片缩略图通过 `URL.createObjectURL` 生成,组件移除或卸载时会回收;外部传入的 `blob:` 地址由业务自行释放。
|
|
761
|
+
- 组件挂载时若同时传入 `fileList` 与 `defaultFileList`,以 `fileList` 为准。
|
|
762
|
+
- 自定义入口保留 Enter / Space 与点击选择;`image` 在图片列表和图片卡生效。`imageLoading="lazy"` 直接使用浏览器原生懒加载。
|
|
763
|
+
- `capture="user"` / `"environment"` 仅在支持相机捕获的移动端有意义,需要配合 `accept="image/*"` 在真实设备上验证。
|
|
764
|
+
- 原生选择窗口由浏览器管理;取消选择不会改变列表。`abort()` 取消后组件会忽略本次尝试的后续回调;要真正停止网络请求,返回 `{ abort() {} }` 或使用 `option.signal`。
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
返回 [AI 索引](../index.md)。先查本页与 [本地 API](./upload-api.md);需要更深类型时查 [公开组件导航](../catalog.md),不默认上网查找本包用法。
|