[Vue3 + TS + Vite]文件选择器-组件

文件选择器组件代码

<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种类存在原生的缺陷,无法响应文件选择器关闭取消事件。

  1. 组件设计:

    • Props: 定义了三个props: buttonTextUnactive, buttonTextActive, 和 onFatherClick。这些props允许父组件自定义按钮文本和控制文件选择器的触发方式。
    • Computed Properties: buttonText 是一个计算属性,用于根据isPickerActive和传递的props来动态显示按钮文本。
    • State Management: 使用ref来管理组件状态,如isPickerActiveselectedFiles
    • Exposing Methods: 通过defineExpose暴露fatherClick方法给父组件,以便父组件能够触发文件选择器。
  2. 事件处理:

    • Emit Events: 使用emits定义了两个事件: file-selectedpicker-canceled。这些事件在文件选择后或用户取消选择时被触发。
    • Handling Clicks: 如果onFatherClicktrue,则按钮不绑定点击事件,而是等待父组件调用fatherClick方法。否则,点击事件由组件内部处理。
  3. 模板结构:

    • Conditional Rendering: 根据onFatherClick的值来条件渲染不同的按钮。
    • Button Interaction: 按钮根据isPickerActive的值禁用或启用。
  4. 生命周期钩子:

    • Setup Cleanup: 使用onMounted来添加和移除事件监听器,确保组件正确清理。
  5. 父组件使用:

    • Method 1: 使用ref获取组件实例,并通过事件监听器来响应文件选择结果。
    • Method 2: 通过ref调用fatherClick方法来触发文件选择器,并通过事件监听器来响应文件选择结果。
  6. 注意事项:

    • showOpenFilePicker API: 这个API需要在安全的上下文中使用,例如HTTPS,并且只在部分现代浏览器中可用。
    • showOpenFilePicker关闭事件: showOpenFilePicker没有原生的关闭事件,这里通过try/catch来模拟文件选择器关闭的情况。
  7. 调试与测试:

    • Console Logs: 在关键位置添加console logs来调试流程。
    • Testing Scenarios: 测试不同的场景,包括选择文件、取消选择、多次打开文件选择器等。

结论

这个组件提供了一种灵活的方式来创建文件选择器,允许父组件自定义外观和行为,并通过事件和方法与组件进行交互。它使用Vue 3的现代特性,如<script setup>语法糖和ref/computed,使得代码简洁高效。

参考链接:
Window:showOpenFilePicker() 方法

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值