跳到主要内容

编码题✅

实现一个文件夹树组件,支持拖拽排序

答案
<template>
  <div class="app">
    <h1>Vue 文件夹树组件(支持拖拽排序)</h1>
    
    <div class="demo-container">
      <div class="instructions">
        <h3>使用说明:</h3>
        <ul>
          <li>🖱️ 点击文件夹图标展开/收起</li>
          <li>🎯 拖拽文件/文件夹进行排序</li>
          <li>📁 可以将文件拖入文件夹中</li>
          <li>⚠️ 不能将文件夹拖入自己的子目录</li>
        </ul>
      </div>
      
      <div class="tree-container">
        <FileTree />
      </div>
    </div>
  </div>
</template>

<script setup>
import FileTree from './FileTree.vue'
</script>

<style scoped>
.app {
  padding: 20px;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  max-width: 800px;
  margin: 0 auto;
}

.demo-container {
  background: #f8f9fa;
  border-radius: 12px;
  padding: 20px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.instructions {
  margin-bottom: 20px;
  padding: 16px;
  background: #e3f2fd;
  border-radius: 8px;
  border-left: 4px solid #2196f3;
}

.instructions h3 {
  margin: 0 0 12px 0;
  color: #1976d2;
  font-size: 16px;
}

.instructions ul {
  margin: 0;
  padding-left: 20px;
  color: #424242;
}

.instructions li {
  margin: 8px 0;
  font-size: 14px;
}

.tree-container {
  background: white;
  border-radius: 8px;
  padding: 16px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  min-height: 400px;
}
</style>

答案解析

该题考察如下知识点:

  1. 递归组件:使用组件自身递归渲染树形结构
  2. 拖拽 API:使用 HTML5 拖拽 API(dragstartdragoverdrop 等事件)
  3. 数据结构操作:对嵌套树形数据进行增删改查操作
  4. 事件传递:子组件向父组件传递事件进行数据更新
  5. CSS 样式控制:拖拽时的视觉反馈

核心实现思路

  • 使用递归组件渲染文件夹树形结构
  • 实现拖拽源和拖放目标的事件处理
  • 通过事件向上传递,在根组件中统一处理数据更新
  • 提供拖拽时的视觉反馈效果
  • 处理拖拽排序的边界情况(如不能拖拽到自身子节点)

实现一个 ErrorBoundary 组件,捕获子组件的错误并可自定义错误组件

答案
<script setup lang="ts">
  import { ref } from "vue";
  import VueErrorBoundary from "./VueErrorBoundary.vue";

  // 用于模拟渲染异常的组件
  const shouldThrow = ref(false);

  const triggerError = () => {
    shouldThrow.value = true;
  };
</script>

<template>
  <h2>VueErrorBoundary 使用示例</h2>
  <button @click="triggerError">点击触发渲染异常</button>
  <h2>默认异常组件</h2>
  <VueErrorBoundary>
    <DemoComponent v-if="!shouldThrow" />
    <ErrorComponent v-else />
  </VueErrorBoundary>
  <h2>自定义异常组件</h2>
  <VueErrorBoundary>
    <DemoComponent v-if="!shouldThrow" />
    <ErrorComponent v-else />
    <template #error="errorCtx">
      <div class="v-default-fallback">
        <h2>发生错误</h2>
        <p><strong>错误信息:</strong> {{ errorCtx }}</p>
      </div>
    </template>
  </VueErrorBoundary>
</template>

<script lang="ts">
  // 模拟正常组件
  const DemoComponent = {
    template: `<div>这是一个正常渲染的组件。</div>`,
  };

  // 模拟抛出异常的组件
  const ErrorComponent = {
    setup() {
      throw new Error("模拟渲染异常!");
    },
    template: `<div>你看不到我</div>`,
  };
</script>

答案解析

该题考察如下知识点

  1. 是否知道 Vue 组件错误处理的 hook onErrorCaptured, 参考问题 组件生命周期
  2. 是否知道作用域插槽来抛出 Vue 内部错误给自定义组件消费, 参考问题 讲解一下插槽的使用

实现下拉菜单,支持点击区域外关闭下拉组件?

答案
<template>
  <div class="app">
    <h1>Vue 点击外部关闭下拉菜单示例</h1>
    
    <div class="demo-container">
      <h3>基础下拉菜单</h3>
      <ClickOutside />
    </div>
    
    <div class="demo-container">
      <h3>多个下拉菜单</h3>
      <div class="flex-container">
        <ClickOutside title="菜单 1" />
        <ClickOutside title="菜单 2" />
        <ClickOutside title="菜单 3" />
      </div>
    </div>
  </div>
</template>

<script setup>
import ClickOutside from './ClickOutside.vue'
</script>

<style scoped>
.app {
  padding: 20px;
  font-family: Arial, sans-serif;
}

.demo-container {
  margin: 30px 0;
  padding: 20px;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  background-color: #f9f9f9;
}

.demo-container h3 {
  margin-top: 0;
  color: #333;
}

.flex-container {
  display: flex;
  gap: 20px;
  flex-wrap: wrap;
}
</style>

答案解析

该题考察如下知识点:

  1. 自定义指令:通过 v-click-outside 指令实现点击外部区域的检测
  2. 事件处理:使用 addEventListenerremoveEventListener 管理全局事件
  3. DOM 节点操作:使用 contains 方法判断点击目标是否在组件内部
  4. 组件生命周期:在指令的 mountedunmounted 钩子中添加和移除事件监听器

核心实现思路

  • 在组件挂载时添加全局 click 事件监听器
  • 在事件处理函数中判断点击目标是否在组件内部
  • 如果点击在外部,则执行关闭操作
  • 在组件卸载时移除事件监听器以防止内存泄漏

实现一个简单的 i18n (国际化 (Internationalization) 的缩写) 插件

实现下面的这样的一个插件 <h1>{{ $translate('greetings.hello') }}</h1>

答案

核心实现思路

以下是一个简单的 Vue 3 的国际化插件实现:

  1. 创建一个名为i18nPlugin.js的文件:
const i18nPlugin = {
install (app, options) {
const translations = options.translations
app.config.globalProperties.$translate = (key) => {
const parts = key.split('.')
let value = translations[parts[0]]
for (let i = 1; i < parts.length && value; i++) {
value = value[parts[i]]
}
return value || key
}
}
}

export default i18nPlugin
  1. 在你的 Vue 3 项目中使用这个插件:

假设你有以下的语言翻译对象:

// en.js
const enTranslations = {
greetings: {
hello: 'Hello!'
}
}

// export default enTranslations

// zh.js
const zhTranslations = {
greetings: {
hello: '你好!'
}
}

// export default zhTranslations

在项目的入口文件(通常是main.jsmain.ts)中:

import { createApp } from 'vue'
import App from './App.vue'
import enTranslations from './locales/en'
import i18nPlugin from './i18nPlugin'

const app = createApp(App)

app.use(i18nPlugin, { translations: enTranslations })

app.mount('#app')

这样,在你的组件中就可以使用{{ $translate('greetings.hello') }}来获取翻译后的文本,并且可以通过修改传入插件的翻译对象来切换不同的语言。

答案解析

该题考察如下知识点:

  1. Vue 插件机制:理解 Vue 插件的 install 方法和如何注册全局属性
  2. 对象深度访问:通过点分隔符访问嵌套对象的属性值
  3. 全局属性注册:使用 app.config.globalProperties 注册全局方法
  4. 字符串处理:解析点分隔的键名并遍历对象结构

核心技术要点

  • 插件通过 app.use() 方式注册
  • 使用 globalProperties 在所有组件中提供 $translate 方法
  • 通过递归方式访问嵌套的翻译对象
  • 当找不到翻译时返回原始键名作为降级处理