<think>我们正在使用Ant Design Vue 1.x封装一个上传组件,要求支持父组件控制fileList。这样父组件可以动态设置初始文件列表,并在上传过程中更新fileList(例如,在编辑时回显已上传的文件,或者在上传完成后添加新文件)。
关键点:
1. 在封装的组件中,使用`value`(或`fileList`)prop来接收父组件传递的文件列表。
2. 在组件内部,使用`a-upload`组件的`fileList`属性绑定这个prop,这样父组件就可以控制文件列表。
3. 由于`a-upload`组件内部会修改`fileList`(例如上传状态变化),我们需要在封装组件中监听变化并通知父组件(通过`input`事件或`update:value`事件,以实现双向绑定)。
4. 同时,我们也要支持自定义上传逻辑(customRequest)和上传前的校验(beforeUpload)。
注意:Ant Design Vue 1.x中,`a-upload`组件的`fileList`是受控的,所以我们需要通过事件来更新父组件的数据。
步骤:
1. 定义props,包括`value`(用于v-model绑定,类型为数组)和其他需要传递给a-upload的props(如action, multiple等)。
2. 在模板中,将a-upload的fileList绑定到value(或我们定义的prop)。
3. 监听a-upload的change事件,在change事件中,我们更新文件列表并触发input事件(或update:value事件)以便父组件更新。
4. 同时,我们也要支持beforeUpload和customRequest(如果需要自定义上传)。
另外,根据引用[2]中的示例,在编辑时,我们可以通过给fileList赋值来回显文件(如已经上传的文件)[^2]。
封装代码示例:
```vue
<template>
<a-upload
:action="action"
:fileList="value" // 绑定父组件传递的fileList
:multiple="multiple"
:beforeUpload="beforeUpload"
:customRequest="customRequest || defaultRequest" // 支持自定义上传
@change="handleChange"
:remove="handleRemove"
:listType="listType"
:accept="accept"
>
<!-- 根据listType显示不同的上传按钮 -->
<div v-if="value.length < maxCount">
<a-icon type="plus" v-if="listType === 'picture-card'" />
<a-button v-else>
<a-icon type="upload" /> 选择文件
</a-button>
</div>
</a-upload>
</template>
<script>
export default {
name: 'ControlledUpload',
props: {
value: {
type: Array,
default: () => []
},
action: {
type: String,
required: true
},
multiple: {
type: Boolean,
default: false
},
maxCount: { // 最多上传数量
type: Number,
default: 1
},
listType: {
type: String,
default: 'text' // 可选: text, picture, picture-card
},
accept: String,
beforeUpload: Function, // 上传前的校验函数
customRequest: Function // 自定义上传函数
},
methods: {
// 处理文件变化
handleChange(info) {
let fileList = [...info.fileList];
// 限制最大上传数量
if (this.maxCount) {
fileList = fileList.slice(-this.maxCount);
}
// 在这里可以做一些状态处理(如上传中、完成、失败等)
// 但注意:fileList已经是响应式的,我们只需要将新的fileList通知给父组件
this.$emit('input', fileList); // 使用v-model时,触发input事件
// 或者使用 .sync 修饰符时,触发 update:value 事件
// this.$emit('update:value', fileList);
},
// 处理文件移除
handleRemove(file) {
// 移除文件时,直接由handleChange触发即可,因为handleChange会拿到新的fileList
// 如果需要在移除时做一些额外操作,可以在这里做,然后触发一个remove事件
this.$emit('remove', file);
},
// 默认的上传请求(如果父组件没有传递customRequest,则使用这个默认的)
defaultRequest({ file, onProgress, onSuccess, onError }) {
// 这里可以写默认的上传逻辑,比如使用FormData上传
const formData = new FormData();
formData.append('file', file);
// 使用axios或fetch发送请求
// 注意:这里需要根据实际情况修改
axios.post(this.action, formData, {
onUploadProgress: ({ total, loaded }) => {
onProgress({ percent: Math.round(loaded / total * 100).toFixed(2) }, file);
}
}).then(response => {
onSuccess(response, file);
}).catch(error => {
onError(error, file);
});
}
}
};
</script>
```
注意:
1. 我们使用了`value`作为prop,并监听内部变化后触发`input`事件,这样父组件可以使用`v-model`绑定。
2. 如果父组件需要.sync修饰符,可以触发`update:value`事件。
3. 我们支持了最大数量限制(maxCount),在handleChange中截取文件列表。
4. 支持自定义上传请求(customRequest),如果没有提供,则使用默认的defaultRequest(这里使用了axios,需要确保项目中有axios)。
5. 移除文件时,我们通过handleChange事件已经更新了fileList,所以不需要额外处理,但提供了一个remove事件供父组件监听。
父组件使用示例:
```vue
<template>
<div>
<controlled-upload v-model="fileList" action="/api/upload" :maxCount="5" />
</div>
</template>
<script>
import ControlledUpload from './ControlledUpload.vue';
export default {
components: { ControlledUpload },
data() {
return {
fileList: [] // 初始为空,也可以从后台获取数据后赋值(用于编辑回显)
};
}
};
</script>
```
编辑回显示例(如引用[2]所示):
在编辑时,我们可以将后台返回的文件信息构造成fileList需要的格式,然后赋值给fileList[^2]。
例如:
```javascript
// 假设后台返回的图片地址是:http://example.com/image.png
this.fileList = [{
uid: '-1', // 注意uid最好唯一,如果是已存在的文件,可以使用文件id
name: 'image.png',
status: 'done', // 状态为done表示已上传
url: 'http://example.com/image.png',
}];
```
注意事项:
- 文件状态(status)包括:'uploading', 'done', 'error', 'removed'
- 在编辑时,我们通常将已上传的文件状态设置为'done',这样会显示为已上传状态。
- 在上传过程中,组件会自动更新文件状态,父组件只需要绑定fileList即可。
这样,我们就封装了一个支持父组件控制fileList的上传组件。</think># Ant Design Vue 1.x 封装支持父组件控制fileList的上传组件
在 Ant Design Vue 1.x 中封装支持父组件控制 `fileList` 的上传组件,可以更好地实现父子组件通信,方便在编辑模式下回显已上传文件。下面我将提供一个完整的解决方案,包含组件封装和示例使用场景。
## 封装思路
1. 使用 `v-model` 实现双向绑定,让父组件可以控制 `fileList`
2. 支持编辑模式下回显已上传文件(参考引用[2]的回显方案)
3. 提供自定义上传逻辑的能力
4. 支持文件类型和大小验证
5. 实现上传状态反馈和错误处理
## 封装后的上传组件代码
```vue
<template>
<div class="controlled-upload">
<a-upload
:action="action"
:fileList="value"
:multiple="multiple"
:listType="listType"
:accept="accept"
:beforeUpload="handleBeforeUpload"
:customRequest="customRequest"
@change="handleChange"
@preview="handlePreview"
:remove="handleRemove"
>
<div v-if="value.length < maxCount">
<a-button v-if="listType !== 'picture-card'">
<a-icon type="upload" /> {{ buttonText }}
</a-button>
<div v-else>
<a-icon type="plus" />
<div class="ant-upload-text">{{ buttonText }}</div>
</div>
</div>
</a-upload>
<!-- 预览模态框 -->
<a-modal :visible="previewVisible" :footer="null" @cancel="previewVisible = false">
<img alt="预览" :src="previewImage" style="width: 100%" />
</a-modal>
</div>
</template>
<script>
export default {
name: 'ControlledUpload',
model: {
prop: 'value',
event: 'change'
},
props: {
// 使用v-model双向绑定fileList
value: {
type: Array,
default: () => []
},
// 上传地址
action: {
type: String,
required: true
},
// 是否多选
multiple: {
type: Boolean,
default: false
},
// 上传类型:text/picture/picture-card
listType: {
type: String,
default: 'text'
},
// 接受的文件类型
accept: {
type: String,
default: 'image/*'
},
// 最大上传数量
maxCount: {
type: Number,
default: 1
},
// 上传按钮文本
buttonText: {
type: String,
default: '点击上传'
},
// 文件大小限制(MB)
maxSize: {
type: Number,
default: 10
},
// 允许的文件类型
allowedTypes: {
type: Array,
default: () => ['image/jpeg', 'image/png', 'image/gif']
},
// 自定义上传方法(可选)
customRequest: {
type: Function,
default: null
}
},
data() {
return {
previewVisible: false,
previewImage: '',
};
},
methods: {
// 上传前验证
handleBeforeUpload(file) {
// 文件类型验证
const isAllowedType = this.allowedTypes.includes(file.type);
if (!isAllowedType) {
this.$message.error(`仅支持上传 ${this.allowedTypes.join(', ')} 格式的文件`);
return false;
}
// 文件大小验证
const isLtMaxSize = file.size / 1024 / 1024 < this.maxSize;
if (!isLtMaxSize) {
this.$message.error(`文件大小不能超过 ${this.maxSize}MB`);
return false;
}
// 触发父组件的beforeUpload钩子
if (this.$listeners.beforeUpload) {
return this.$listeners.beforeUpload(file);
}
return true;
},
// 处理文件变化
handleChange(info) {
let fileList = [...info.fileList];
// 限制最大数量
if (this.maxCount) {
fileList = fileList.slice(-this.maxCount);
}
// 更新状态
fileList = fileList.map(file => {
if (file.response) {
// 上传成功时,设置URL
file.url = file.response.url;
}
return file;
});
// 触发v-model更新
this.$emit('change', fileList);
// 上传状态处理
if (info.file.status === 'done') {
this.$message.success(`${info.file.name} 上传成功`);
this.$emit('success', info.file.response);
} else if (info.file.status === 'error') {
this.$message.error(`${info.file.name} 上传失败`);
this.$emit('error', info.file.error);
}
},
// 处理文件移除
handleRemove(file) {
// 触发父组件的remove事件
this.$emit('remove', file);
return true;
},
// 处理预览
handlePreview(file) {
this.previewImage = file.url || file.thumbUrl;
this.previewVisible = true;
},
// 默认上传方法(当未提供customRequest时使用)
defaultRequest({ file, onProgress, onSuccess, onError }) {
const formData = new FormData();
formData.append('file', file);
// 使用axios或fetch实现上传
const config = {
onUploadProgress: ({ total, loaded }) => {
onProgress({ percent: Math.round(loaded / total * 100) }, file);
}
};
// 这里使用fetch作为示例,实际项目中可以使用axios
fetch(this.action, {
method: 'POST',
body: formData,
...config
})
.then(response => response.json())
.then(data => {
onSuccess(data, file);
})
.catch(error => {
onError(error, file);
});
}
}
};
</script>
<style scoped>
.controlled-upload {
margin: 20px 0;
}
/* 图片卡片样式调整 */
:deep(.ant-upload.ant-upload-select-picture-card) {
margin-bottom: 0;
}
</style>
```
## 在父组件中使用示例
```vue
<template>
<div class="upload-container">
<h2>封装上传组件示例</h2>
<div class="upload-section">
<h3>单张图片上传(编辑模式回显)</h3>
<controlled-upload
v-model="avatarFileList"
action="/api/upload"
listType="picture-card"
:maxCount="1"
buttonText="上传头像"
@success="handleUploadSuccess"
@remove="handleRemoveAvatar"
/>
</div>
<div class="upload-section">
<h3>多文件上传</h3>
<controlled-upload
v-model="documentsFileList"
action="/api/upload"
:multiple="true"
:maxCount="3"
accept=".pdf,.doc,.docx"
buttonText="上传文档"
:allowedTypes="['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document']"
@beforeUpload="customBeforeUpload"
/>
<p class="upload-hint">最多可上传3个PDF或Word文档</p>
</div>
<div class="upload-section">
<h3>自定义上传逻辑</h3>
<controlled-upload
v-model="customFileList"
:customRequest="customUploadHandler"
buttonText="自定义上传"
:maxSize="50"
/>
</div>
<div class="actions">
<a-button type="primary" @click="submitAll">提交所有文件</a-button>
<a-button @click="resetAll">重置</a-button>
</div>
</div>
</template>
<script>
import ControlledUpload from './components/ControlledUpload.vue';
export default {
components: {
ControlledUpload
},
data() {
return {
// 头像上传(模拟编辑模式下回显)
avatarFileList: [
{
uid: '-1',
name: 'avatar.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
}
],
// 文档上传
documentsFileList: [],
// 自定义上传
customFileList: []
};
},
methods: {
// 处理上传成功
handleUploadSuccess(response) {
console.log('上传成功:', response);
this.$message.success('文件上传成功');
},
// 处理移除头像
handleRemoveAvatar(file) {
console.log('移除文件:', file);
},
// 自定义上传前验证
customBeforeUpload(file) {
console.log('自定义验证:', file);
return true;
},
// 自定义上传处理
customUploadHandler({ file, onProgress, onSuccess, onError }) {
console.log('自定义上传处理:', file);
// 模拟上传进度
let progress = 0;
const interval = setInterval(() => {
progress += 10;
onProgress({ percent: progress }, file);
if (progress >= 100) {
clearInterval(interval);
// 模拟上传成功响应
onSuccess({
url: `https://example.com/files/${file.name}`,
id: Date.now()
}, file);
}
}, 300);
},
// 提交所有文件
submitAll() {
console.log('提交所有文件:');
console.log('头像:', this.avatarFileList);
console.log('文档:', this.documentsFileList);
console.log('自定义:', this.customFileList);
this.$message.info('已提交所有文件信息');
},
// 重置所有上传
resetAll() {
this.avatarFileList = [];
this.documentsFileList = [];
this.customFileList = [];
this.$message.success('已重置所有上传');
}
}
};
</script>
<style scoped>
.upload-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.upload-section {
margin-bottom: 30px;
padding: 20px;
border: 1px solid #e8e8e8;
border-radius: 4px;
background-color: #fafafa;
}
.upload-hint {
color: #999;
font-size: 12px;
margin-top: 8px;
}
.actions {
margin-top: 20px;
text-align: center;
}
.actions button {
margin: 0 10px;
}
h2 {
text-align: center;
color: #1890ff;
margin-bottom: 30px;
}
h3 {
color: #333;
margin-bottom: 15px;
}
</style>
```
## 关键特性说明
1. **v-model双向绑定**:
- 使用 `model` 选项配置 `v-model` 的双向绑定
- 父组件通过 `v-model` 控制 `fileList`,子组件通过 `$emit('change')` 更新
2. **编辑模式回显**:
- 父组件可以设置初始 `fileList`(如引用[2]所示)
- 文件状态设置为 `done` 表示已上传完成
3. **灵活的验证机制**:
- 内置文件类型和大小验证
- 支持通过 `beforeUpload` 事件自定义验证逻辑
4. **自定义上传处理**:
- 通过 `customRequest` prop 支持自定义上传实现
- 提供默认上传方法作为备用
5. **预览功能**:
- 内置图片预览模态框
- 支持点击预览上传的文件
6. **响应式设计**:
- 支持三种列表类型:text/picture/picture-card
- 自适应布局
## 应用场景
1. **用户头像上传**:使用 `picture-card` 类型,限制最大数量为1
2. **多文件文档上传**:使用 `text` 类型,支持PDF/Word等文档格式
3. **自定义上传逻辑**:集成第三方存储服务(如阿里云OSS、七牛云等)
4. **编辑模式**:从API获取已上传文件并回显
这个封装组件解决了Ant Design Vue 1.x中父组件难以控制上传状态的问题,提供了更灵活的文件管理能力,同时保持了原组件良好的用户体验。