[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() 方法

### 使用 Vue3TypeScriptVite 构建流程图编辑器 #### 初始化项目结构 为了创建基于 Vue3TypeScriptVite 的流程图编辑器,首先需要初始化一个新的前端开发环境。这可以通过安装最新版 Node.js 来实现[^3]。 ```bash node -v ``` 确认已正确安装后,可以利用 `create-vue` 脚手架工具快速搭建起一个支持 TypeScriptVue 3 应用程序: ```bash npm init vue@latest my-flowchart-editor --template ts cd my-flowchart-editor npm install ``` 此命令会自动设置好所需的依赖项并准备好基本的应用模板。 #### 添加必要的库和支持 对于构建复杂的交互界面如流程图编辑器来说,推荐引入专门用于图形绘制的第三方类库,例如 D3.js 或者 GoJS 等。这些库能够极大地简化图表渲染逻辑,并提供丰富的可视化组件来增强用户体验。 通过 NPM 安装额外的支持包: ```bash npm install d3 gojs ``` #### 修改入口文件配置 接下来调整项目的入口文件以适应新的需求。打开 `main.ts` 文件并将默认导出修改如下所示: ```typescript import { createApp } from &#39;vue&#39; import App from &#39;./App.vue&#39; // 导入样式表和其他资源... import &#39;@gojs samples/goSamples.css&#39;; const app = createApp(App) app.mount(&#39;#app&#39;) ``` 此处假设使用了 GoJS 作为绘图引擎之一;如果选择了其他替代方案,则需相应更改导入路径。 #### 开发核心功能模块 针对具体业务场景设计相应的视图组件和服务端接口调用方法。考虑到这是一个简单的示例应用,可以在 `src/components/FlowChartEditor.vue` 中定义基础布局与事件处理函数: ```html <template> <div class="flow-chart-container"> <!-- 这里放置具体的DOM元素 --> </div> </template> <script lang="ts"> export default defineComponent({ name: "FlowChartEditor", setup() { // 组件内部状态管理 return {}; } }); </script> <style scoped> /* 自定义CSS样式 */ .flow-chart-container {} </style> ``` 以上代码片段展示了如何在一个名为 FlowChartEditor 的自定义组件内封装特定于流程图的操作逻辑[^1]。 #### 启动本地服务器测试效果 完成上述步骤之后就可以运行下面这条指令启动内置的服务来进行实时预览了: ```bash npm run dev ``` 此时应该能够在浏览器中访问到正在开发中的应用程序实例,并对其进行进一步优化和完善直至满足预期目标为止。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值