文件选择器组件代码
<script setup lang="ts">
import { ref, onMounted, defineProps, defineEmits, computed, toRaw } from 'vue';
// 定义props
interface Props {
buttonTextUnactive?: string;
buttonTextActive?: string;
onFatherClick?: boolean;
}
// 定义emits
interface Emits {
(e: 'file-selected', files: File[]): void;
(e: 'picker-canceled'): void;
}
const props = defineProps<Props>();
const emits = defineEmits<Emits>();
// 状态
const isPickerActive = ref(false);
const selectedFiles = ref<File[]>([]);
// 初始化按钮文本
const buttonText = computed(() => {
let defaultButtonText = {
unactive: '选择文件',
active: '文件选择器已打开'
};
return isPickerActive.value ?
(props.buttonTextActive ? props.buttonTextActive : defaultButtonText.active) :
(props.buttonTextUnactive ? props.buttonTextUnactive : defaultButtonText.unactive);
});
// 文件选择器由父组件click事件响应
const onFatherClick = computed(() => {
return props.onFatherClick === true;
});
const fatherClick = async () => {
console.log('props', props.onFatherClick);
console.log('onFatherClick', onFatherClick.value);
await openFilePicker(); // 等待文件选择器的结果
const result = toRaw(selectedFiles.value);
return result; // 返回文件选择的结果
};
// 外部触发文件选择器
const handleClick = () => {
if (isPickerActive.value) return;
openFilePicker();
};
// 打开文件选择器
const openFilePicker = async () => {
isPickerActive.value = true;
try {
const files = await window.showOpenFilePicker();
selectedFiles.value = Array.from(files);
console.log('组件--file-selected');
emits('file-selected', selectedFiles.value);
} catch (error) {
console.log('组件--picker-canceled');
emits('picker-canceled');
} finally {
isPickerActive.value = false;
}
};
onMounted(() => {
// 添加监听器
// 清理
return () => {
};
});
defineExpose({
fatherClick,
});
</script>
<template>
<div>
<button v-if="onFatherClick" :disabled="isPickerActive">
{{ buttonText }}
</button>
<button v-else @click="handleClick" :disabled="isPickerActive">
{{ buttonText }}
</button>
<!-- <pre v-if="selectedFiles.length > 0">
{{ selectedFiles }}
</pre> -->
</div>
</template>
父组件调用代码
调用方法1-子组件触发Click事件:
<script setup lang="ts">
import { ref } from "vue";
const cc_click = ref();
const onFileSelected = (files: File[]) => {
console.log('父组件--文件已选择:', files);
};
const onPickerCanceled = () => {
console.log('父组件--文件选择被取消');
};
</script>
<template>
<file-selector
ref="cc_click"
@file-selected="onFileSelected"
@picker-canceled="onPickerCanceled"
/>
</template>
调用方法2-父组件触发Click事件:
<script setup lang="ts">
import { ref } from "vue";
const cc_click = ref();
const onFileSelected = (files: File[]) => {
console.log('父组件--文件已选择:', files);
};
const onPickerCanceled = () => {
console.log('父组件--文件选择被取消');
};
const click_select = async () => {
let value = await cc_click.value.fatherClick();
console.log('cc_click', value);
};
</script>
<template>
<file-selector
ref="cc_click"
:onFatherClick="true"
@click="click_select"
@file-selected="onFileSelected"
@picker-canceled="onPickerCanceled"
/>
</template>
经验笔记
之所以不选择使用input标签file种类,是因为input标签file种类存在原生的缺陷,无法响应文件选择器关闭取消事件。
-
组件设计:
- Props: 定义了三个props:
buttonTextUnactive
,buttonTextActive
, 和onFatherClick
。这些props允许父组件自定义按钮文本和控制文件选择器的触发方式。 - Computed Properties:
buttonText
是一个计算属性,用于根据isPickerActive
和传递的props来动态显示按钮文本。 - State Management: 使用
ref
来管理组件状态,如isPickerActive
和selectedFiles
。 - Exposing Methods: 通过
defineExpose
暴露fatherClick
方法给父组件,以便父组件能够触发文件选择器。
- Props: 定义了三个props:
-
事件处理:
- Emit Events: 使用
emits
定义了两个事件:file-selected
和picker-canceled
。这些事件在文件选择后或用户取消选择时被触发。 - Handling Clicks: 如果
onFatherClick
为true
,则按钮不绑定点击事件,而是等待父组件调用fatherClick
方法。否则,点击事件由组件内部处理。
- Emit Events: 使用
-
模板结构:
- Conditional Rendering: 根据
onFatherClick
的值来条件渲染不同的按钮。 - Button Interaction: 按钮根据
isPickerActive
的值禁用或启用。
- Conditional Rendering: 根据
-
生命周期钩子:
- Setup Cleanup: 使用
onMounted
来添加和移除事件监听器,确保组件正确清理。
- Setup Cleanup: 使用
-
父组件使用:
- Method 1: 使用
ref
获取组件实例,并通过事件监听器来响应文件选择结果。 - Method 2: 通过
ref
调用fatherClick
方法来触发文件选择器,并通过事件监听器来响应文件选择结果。
- Method 1: 使用
-
注意事项:
showOpenFilePicker
API: 这个API需要在安全的上下文中使用,例如HTTPS,并且只在部分现代浏览器中可用。showOpenFilePicker
关闭事件:showOpenFilePicker
没有原生的关闭事件,这里通过try/catch
来模拟文件选择器关闭的情况。
-
调试与测试:
- Console Logs: 在关键位置添加console logs来调试流程。
- Testing Scenarios: 测试不同的场景,包括选择文件、取消选择、多次打开文件选择器等。
结论
这个组件提供了一种灵活的方式来创建文件选择器,允许父组件自定义外观和行为,并通过事件和方法与组件进行交互。它使用Vue 3的现代特性,如<script setup>
语法糖和ref
/computed
,使得代码简洁高效。