DeepSeek 助力 Vue 开发:打造丝滑的右键菜单(RightClickMenu)

前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕


目录


📚📗📕📘📖🕮💡📝🗂️✍️🛠️💻🚀🎉🏗️🌐🖼️🔗📊👉🔖⚠️🌟🔐⬇️·正文开始⬇️·🎥😊🎓📩😺🌈🤝🤖📜📋🔍✅🧰❓📄📢📈 🙋0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣🔟🆗*️⃣#️⃣

DeepSeek 助力 Vue 开发:打造丝滑的右键菜单(RightClickMenu)

📚前言

博时基金在人工智能应用方面一直保持前沿探索。2024 年初,公司发现了 DeepSeek 模型在自动编写代码和逻辑推理方面的潜力,率先在自有的昇腾服务器上部署了 DeepSeek-V1 模型,作为公司智能开发工具的基座模型,并在 2024 年 8 月升级为 DeepSeek-V2 模型。2025 年,随着 DeepSeek-R1 模型的发布,博时基金迅速完成内部部署,并开始探索它在投资研究、咨询服务和软件开发等方面的应用。R1 模型在推理能力上表现优异,可以帮助提升工作效率,支持业务创新,同时,它对算力的需求也有所降低,为进一步推广应用创造了条件 。

📚页面效果

📚指令输入

已经创建好了一个基于Vue3的组合式API的项目(Composition API),并能正常运行起来,请帮我用 Vue3的组合式API(Composition API) 生成一个 右键菜单(RightClickMenu) 的功能组件,所有代码都保存在components/RightClickMenu 下的文件夹中。功能组件的script标签中只有setup属性,使用普通 JavaScript 实现,不使用TypeScript。

功能要有,如下属性:

以下是制作 Vue3 右键菜单组件的方案:

属性 (Props)

  1. 菜单项配置
  • items: Array<MenuItem> (必填)

    javascript 复制代码
     MenuItem  = {
      id: string | number,   // 唯一标识
      label: string   ,     // 显示文本
      icon?: string   ,     // 可选图标
      disabled?: boolean   ,// 禁用状态
      divider?: boolean   , // 是否为分隔线
      children?: MenuItem[] ,// 子菜单
      handler?: Function   // 自定义点击处理
    }
  1. 定位控制
  • position: { x: number, y: number } (必填,通过右键事件坐标初始化)
  • autoAdjust: boolean (自动调整位置防止超出屏幕边界)
  1. 样式控制
  • theme: 'light' | 'dark' (预设主题样式)
  • customClass: string (自定义 CSS 类)
  • zIndex: number (层级控制)
  1. 行为控制
  • closeOnClick: boolean (点击后自动关闭)
  • showIcons: boolean (全局图标显示开关)

事件 (Emits)

  1. item-click (携带被点击的 MenuItem 对象)
  2. contextmenu-open (菜单打开时触发)
  3. contextmenu-close (菜单关闭时触发)
  4. before-close (关闭前可拦截)

其他

  1. 键盘导航

    • 方向键导航
    • Enter 确认选择
    • ESC 关闭菜单
  2. 边缘处理

    • 自动翻转子菜单位置
    • 屏幕边界碰撞检测
  3. 可访问性

    • 正确的 ARIA 角色属性
    • 键盘焦点管理
  4. 样式建议

    • 使用 position: fixed 定位
    • 内置过渡动画
    • 提供 CSS 变量主题化
  5. 扩展功能

    • 通过插槽支持自定义菜单项模板
    • 支持多级嵌套子菜单
    • 全局右键事件管理
  6. 最佳实践

    • 在组件挂载时添加全局点击监听
    • 阻止浏览器默认右键菜单
    • 提供 TypeScript 类型定义

建议优先实现核心功能,再逐步扩展高级特性。可通过配置优先 + 插槽扩展的方式平衡灵活性和易用性。

你有更好的建议也可以添加,要注明。组件定义好后给出3个及以上的调用示例。

下面是现有目录

vueAndDeepseek/

├── src/ # 源代码目录

│ ├── assets/ # 静态资源

│ │ ├── base.css

│ │ ├── main.css

│ │ └── logo.svg

│ ├── components/ # 组件目录

│ │ ├── HelloWorld.vue

│ │ ├── TheWelcome.vue

│ │ ├── WelcomeItem.vue

│ │ ├── Progress/

│ │ │ └── Progress.vue

│ │ ├── Accordion/

│ │ ├── BackToTop/

│ │ ├── Card/

│ │ ├── InfiniteScroll/

│ │ ├── Notification/

│ │ ├── Timeline/

│ │ ├── Switch/

│ │ ├── Tabs/

│ │ ├── Sidebar/

│ │ ├── Breadcrumbs/

│ │ ├── MasonryLayout/

│ │ ├── Rating/

│ │ ├── ColorPicker/

│ │ ├── RightClickMenu/

│ │ ├── DatePicker/

│ │ └── icons/

│ ├── router/ # 路由配置

│ │ └── index.js

│ ├── stores/ # Pinia 状态管理

│ │ └── counter.js

│ ├── views/ # 页面组件

│ │ ├── ProgressView.vue

│ │ ├── TabsView.vue

│ │ ├── SwitchView.vue

│ │ ├── TimelineView.vue

│ │ ├── NotificationView.vue

│ │ ├── CardView.vue

│ │ ├── InfiniteScrollView.vue

│ │ ├── BackToTopView.vue

│ │ ├── AccordionView.vue

│ │ ├── SidebarView.vue

│ │ ├── BreadcrumbsView.vue

│ │ ├── MasonryLayoutView.vue

│ │ ├── RatingView.vue

│ │ ├── ColorPickerView.vue

│ │ ├── RightClickMenuView.vue

│ │ ├── DatePickerView.vue

│ │ └── AboutView.vue

│ ├── App.vue # 根组件

│ └── main.js # 应用入口

├── public/ # 公共资源目录

├── index.html # HTML 模板

├── package.json # 项目配置

├── vite.config.js # Vite 配置

└── node_modules/ # 依赖包目录

📘组件代码 src\components\RightClickMenu\RightClickMenu.vue

html 复制代码
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';

const props = defineProps({
  items: {
    type: Array,
    required: true,
    default: () => []
  },
  theme: {
    type: String,
    default: 'light'
  },
  customClass: {
    type: String,
    default: ''
  },
  zIndex: {
    type: Number,
    default: 1000
  },
  closeOnClick: {
    type: Boolean,
    default: true
  },
  showIcons: {
    type: Boolean,
    default: true
  }
});

const emit = defineEmits(['item-click', 'contextmenu-open']);

const visible = ref(false);
const position = ref({ x: 0, y: 0 });
const menuRef = ref(null);

// 显示菜单
const show = (event) => {
  event.preventDefault();
  position.value = {
    x: event.clientX,
    y: event.clientY
  };
  visible.value = true;
  emit('contextmenu-open');
  
  // 添加全局点击事件监听
  document.addEventListener('click', handleClickOutside);
};

// 隐藏菜单
const hide = () => {
  visible.value = false;
  document.removeEventListener('click', handleClickOutside);
};

// 处理菜单项点击
const handleItemClick = (item) => {
  if (item.disabled) return;
  
  emit('item-click', item);
  if (item.handler) {
    item.handler(item);
  }
  
  if (props.closeOnClick) {
    hide();
  }
};

// 处理点击外部
const handleClickOutside = (event) => {
  if (menuRef.value && !menuRef.value.contains(event.target)) {
    hide();
  }
};

// 组件卸载时清理
onUnmounted(() => {
  document.removeEventListener('click', handleClickOutside);
});

// 暴露方法给父组件
defineExpose({
  show,
  hide
});
</script>

<template>
  <div v-show="visible"
       ref="menuRef"
       :class="[
         'right-click-menu',
         `theme-${theme}`,
         customClass
       ]"
       :style="{
         left: `${position.x}px`,
         top: `${position.y}px`,
         zIndex
       }">
    <ul class="menu-list">
      <template v-for="item in items" :key="item.id || item.label">
        <!-- 分隔线 -->
        <li v-if="item.divider" class="menu-divider"></li>
        <!-- 菜单项 -->
        <li v-else
            :class="[
              'menu-item',
              { 'disabled': item.disabled }
            ]"
            @click="handleItemClick(item)">
          <span v-if="showIcons && item.icon" :class="['item-icon', item.icon]"></span>
          <span class="item-label">{{ item.label }}</span>
          <!-- 子菜单指示器 -->
          <span v-if="item.children" class="submenu-arrow">▶</span>
        </li>
      </template>
    </ul>
  </div>
</template>

<style>
.right-click-menu {
  position: fixed;
  background: #ffffff;
  border: 1px solid #e4e7ed;
  border-radius: 4px;
  box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
  padding: 5px 0;
  min-width: 150px;
}

.menu-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.menu-item {
  padding: 8px 16px;
  cursor: pointer;
  display: flex;
  align-items: center;
  white-space: nowrap;
}

.menu-item:hover {
  background-color: #f5f7fa;
}

.menu-item.disabled {
  color: #c0c4cc;
  cursor: not-allowed;
}

.menu-divider {
  height: 1px;
  margin: 5px 0;
  background-color: #e4e7ed;
}

.item-icon {
  margin-right: 8px;
  width: 16px;
  text-align: center;
}

.item-label {
  flex: 1;
}

.submenu-arrow {
  margin-left: 8px;
  font-size: 12px;
}

/* 深色主题 */
.theme-dark {
  background: #303133;
  border-color: #606266;
}

.theme-dark .menu-item {
  color: #ffffff;
}

.theme-dark .menu-item:hover {
  background-color: #409EFF;
}

.theme-dark .menu-divider {
  background-color: #606266;
}

.theme-dark .menu-item.disabled {
  color: #909399;
}
</style>

📘定义组件 src\views\RightClickMenuView.vue

html 复制代码
<script setup>
import { ref } from 'vue';
import RightClickMenu from '../components/RightClickMenu/RightClickMenu.vue';

const menuRef1 = ref(null);
const menuRef2 = ref(null);
const menuRef3 = ref(null);

// 基础菜单项配置
const basicMenuItems = [
  {
    id: 'copy',
    label: '复制',
    icon: 'icon-copy',
    handler: () => console.log('复制')
  },
  {
    id: 'paste',
    label: '粘贴',
    icon: 'icon-paste'
  },
  { divider: true },
  {
    id: 'delete',
    label: '删除',
    icon: 'icon-delete'
  }
];

// 带子菜单的配置
const nestedMenuItems = [
  {
    id: 'file',
    label: '文件',
    children: [
      {
        id: 'new',
        label: '新建',
        icon: 'icon-new'
      },
      {
        id: 'open',
        label: '打开',
        icon: 'icon-open'
      }
    ]
  },
  {
    id: 'edit',
    label: '编辑',
    children: [
      {
        id: 'copy',
        label: '复制',
        icon: 'icon-copy'
      },
      {
        id: 'paste',
        label: '粘贴',
        icon: 'icon-paste',
        disabled: true
      }
    ]
  }
];

// 自定义菜单项
const customMenuItems = [
  {
    id: 'share',
    label: '分享到',
    children: [
      {
        id: 'wechat',
        label: '微信',
        icon: 'icon-wechat'
      },
      {
        id: 'weibo',
        label: '微博',
        icon: 'icon-weibo'
      }
    ]
  },
  { divider: true },
  {
    id: 'delete',
    label: '删除',
    icon: 'icon-delete',
    disabled: true
  }
];

// 事件处理函数
const handleContextMenu1 = (event) => {
  event.preventDefault();
  menuRef1.value?.show(event);
};

const handleContextMenu2 = (event) => {
  event.preventDefault();
  menuRef2.value?.show(event);
};

const handleContextMenu3 = (event) => {
  event.preventDefault();
  menuRef3.value?.show(event);
};

const handleItemClick = (item) => {
  console.log('菜单项被点击:', item);
};

const handleContextMenuOpen = () => {
  console.log('右键菜单已打开');
};
</script>

<template>
  <div class="right-click-menu-demo">
    <h2>右键菜单示例</h2>
    
    <!-- 示例1:基础用法 -->
    <div class="demo-box"
         @contextmenu="handleContextMenu1">
      <h3>基础用法</h3>
      <p>在此区域右键点击查看基础菜单</p>
    </div>
    
    <!-- 示例2:多级菜单 + 深色主题 -->
    <div class="demo-box dark"
         @contextmenu="handleContextMenu2">
      <h3>多级菜单 + 深色主题</h3>
      <p>在此区域右键点击查看多级菜单</p>
    </div>
    
    <!-- 示例3:自定义样式 -->
    <div class="demo-box custom"
         @contextmenu="handleContextMenu3">
      <h3>自定义样式</h3>
      <p>在此区域右键点击查看自定义样式菜单</p>
    </div>

    <!-- 菜单组件 -->
    <RightClickMenu
      ref="menuRef1"
      :items="basicMenuItems"
      @item-click="handleItemClick"
    />
    
    <RightClickMenu
      ref="menuRef2"
      :items="nestedMenuItems"
      theme="dark"
      :show-icons="true"
      @item-click="handleItemClick"
      @contextmenu-open="handleContextMenuOpen"
    />
    
    <RightClickMenu
      ref="menuRef3"
      :items="customMenuItems"
      custom-class="custom-menu"
      :z-index="2000"
      :close-on-click="false"
      @item-click="handleItemClick"
    />
  </div>
</template>

<style scoped>
.right-click-menu-demo {
  padding: 20px;
}

.demo-box {
  margin: 20px;
  padding: 40px;
  border: 1px solid #ddd;
  border-radius: 8px;
  background: #f5f7fa;
  cursor: context-menu;
}

.demo-box h3 {
  margin: 0 0 15px 0;
  color: #333;
}

.demo-box p {
  margin: 0;
  color: #666;
}

/* 深色主题示例 */
.demo-box.dark {
  background: #303133;
  border-color: #606266;
}

.demo-box.dark h3,
.demo-box.dark p {
  color: #fff;
}

/* 自定义样式示例 */
.demo-box.custom {
  background: linear-gradient(135deg, #42d392 25%, #647eff);
  border: none;
}

.demo-box.custom h3,
.demo-box.custom p {
  color: #fff;
}

/* 自定义菜单样式 */
:deep(.custom-menu) {
  background: rgba(255, 255, 255, 0.95);
  backdrop-filter: blur(10px);
  border: none;
  box-shadow: 0 4px 20px rgba(0,0,0,0.15);
}

:deep(.custom-menu .menu-item:hover) {
  background-color: #42d392;
  color: white;
}
</style>

📚代码测试

正常

📚测试代码正常跑通,附其他基本代码

  • 添加路由
  • 页面展示入口

📘编写路由 src\router\index.js

javascript 复制代码
import { createRouter, createWebHistory } from 'vue-router'
import RightClickMenuView from '../views/RightClickMenuView.vue'


const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'progress',
      component:  () => import('../views/ProgressView.vue'),
    },
    {
      path: '/tabs',
      name: 'tabs',
      // route level code-splitting
      // this generates a separate chunk (About.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      // 标签页(Tabs)
      component: () => import('../views/TabsView.vue'),
    },
    {
      path: '/accordion',
      name: 'accordion',
      // 折叠面板(Accordion)
      component: () => import('../views/AccordionView.vue'),
    },
    {
      path: '/timeline',
      name: 'timeline',
      // 时间线(Timeline)
      component: () => import('../views/TimelineView.vue'),
    },
    {
      path: '/backToTop',
      name: 'backToTop',
      component: () => import('../views/BackToTopView.vue')
    },
    {
      path: '/notification',
      name: 'notification',
      component: () => import('../views/NotificationView.vue')
    },
    {
      path: '/card',
      name: 'card',
      component: () => import('../views/CardView.vue')
    },
    {
      path: '/infiniteScroll',
      name: 'infiniteScroll',
      component: () => import('../views/InfiniteScrollView.vue')
    },
    {
      path: '/switch',
      name: 'switch',
      component: () => import('../views/SwitchView.vue')
    },
    {
      path: '/sidebar',
      name: 'sidebar',
      component: () => import('../views/SidebarView.vue')
    },
    {
      path: '/breadcrumbs',
      name: 'breadcrumbs',
      component: () => import('../views/BreadcrumbsView.vue')
    },
    {
      path: '/masonryLayout',
      name: 'masonryLayout',
      component: () => import('../views/MasonryLayoutView.vue')
    },
    {
      path: '/rating',
      name: 'rating',
      component: () => import('../views/RatingView.vue')
    },
    {
      path: '/datePicker',
      name: 'datePicker',
      component: () => import('../views/DatePickerView.vue')
    },
    {
      path: '/colorPicker',
      name: 'colorPicker',
      component: () => import('../views/ColorPickerView.vue')
    },
    {
      path: '/rightClickMenu',
      name: 'rightClickMenu',
      component: RightClickMenuView
    }
  ],
})

export default router

📘编写展示入口 src\App.vue

html 复制代码
<script setup>
import { RouterLink, RouterView } from 'vue-router'
import HelloWorld from './components/HelloWorld.vue'
</script>

<template>
  <header>
    <img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />

    <div class="wrapper">
      <HelloWorld msg="You did it!" />
      <nav>
        <RouterLink to="/">Progress</RouterLink>
        <RouterLink to="/tabs">Tabs</RouterLink>
        <RouterLink to="/accordion">Accordion</RouterLink>
        <RouterLink to="/timeline">Timeline</RouterLink>
        <RouterLink to="/backToTop">BackToTop</RouterLink>
        <RouterLink to="/notification">Notification</RouterLink>
        <RouterLink to="/card">Card</RouterLink>
        <RouterLink to="/infiniteScroll">InfiniteScroll</RouterLink>
        <RouterLink to="/switch">Switch</RouterLink>
        <RouterLink to="/sidebar">Sidebar</RouterLink>
        <RouterLink to="/breadcrumbs">Breadcrumbs</RouterLink>
        <RouterLink to="/masonryLayout">MasonryLayout</RouterLink>
        <RouterLink to="/rating">Rating</RouterLink>
        <RouterLink to="/datePicker">DatePicker</RouterLink>
        <RouterLink to="/colorPicker">ColorPicker</RouterLink>
        <RouterLink to="/rightClickMenu">RightClickMenu</RouterLink>
      </nav>
    </div>
  </header>

  <RouterView />
</template>

<style scoped>
header {
  line-height: 1.5;
  max-height: 100vh;
}

.logo {
  display: block;
  margin: 0 auto 2rem;
}

nav {
  width: 100%;
  font-size: 12px;
  text-align: center;
  margin-top: 2rem;
}

nav a.router-link-exact-active {
  color: var(--color-text);
}

nav a.router-link-exact-active:hover {
  background-color: transparent;
}

nav a {
  display: inline-block;
  padding: 0 1rem;
  border-left: 1px solid var(--color-border);
}

nav a:first-of-type {
  border: 0;
}

@media (min-width: 1024px) {
  header {
    display: flex;
    place-items: center;
    padding-right: calc(var(--section-gap) / 2);
  }

  .logo {
    margin: 0 2rem 0 0;
  }

  header .wrapper {
    display: flex;
    place-items: flex-start;
    flex-wrap: wrap;
  }

  nav {
    text-align: left;
    margin-left: -1rem;
    font-size: 1rem;

    padding: 1rem 0;
    margin-top: 1rem;
  }
}
</style>

📚页面效果

📚相关文章

------------ 相 关 文 章 ------------

  1. 0基础3步部署自己的DeepSeek安装步骤

  2. DeepSeek 助力 Vue 开发:打造丝滑的步骤条(Step bar)https://blog.csdn.net/qq_33650655/article/details/145560497

  3. DeepSeek 助力 Vue 开发:打造丝滑的进度条(Progress Bar)https://blog.csdn.net/qq_33650655/article/details/145577034

  4. 自己部署 DeepSeek 助力 Vue 开发:打造丝滑的标签页(Tabs)https://blog.csdn.net/qq_33650655/article/details/145587999

  5. 自己部署 DeepSeek 助力 Vue 开发:打造丝滑的折叠面板(Accordion)https://blog.csdn.net/qq_33650655/article/details/145590404

  6. 自己部署 DeepSeek 助力 Vue 开发:打造丝滑的时间线(Timeline )https://blog.csdn.net/qq_33650655/article/details/145597372

  7. DeepSeek 助力 Vue 开发:打造丝滑的返回顶部按钮(Back to Top)https://blog.csdn.net/qq_33650655/article/details/145615550

  8. DeepSeek 助力 Vue 开发:打造丝滑的通知栏(Notification Bar)https://blog.csdn.net/qq_33650655/article/details/145620055

  9. DeepSeek 助力 Vue 开发:打造丝滑的卡片(Card)https://blog.csdn.net/qq_33650655/article/details/145634564

  10. DeepSeek 助力 Vue 开发:打造丝滑的无限滚动(Infinite Scroll)https://blog.csdn.net/qq_33650655/article/details/145638452

  11. DeepSeek 助力 Vue 开发:打造丝滑的开关切换(Switch)https://blog.csdn.net/qq_33650655/article/details/145644151

  12. DeepSeek 助力 Vue 开发:打造丝滑的侧边栏(Sidebar)https://blog.csdn.net/qq_33650655/article/details/145654204

  13. DeepSeek 助力 Vue 开发:打造丝滑的面包屑导航(Breadcrumbs)https://blog.csdn.net/qq_33650655/article/details/145656895

  14. DeepSeek 助力 Vue 开发:打造丝滑的瀑布流布局(Masonry Layout)https://blog.csdn.net/qq_33650655/article/details/145663699

  15. DeepSeek 助力 Vue 开发:打造丝滑的评分组件(Rating)https://blog.csdn.net/qq_33650655/article/details/145664576

  16. DeepSeek 助力 Vue 开发:打造丝滑的日期选择器(Date Picker),未使用第三方插件 https://blog.csdn.net/qq_33650655/article/details/145673279

  17. DeepSeek 助力 Vue 开发:打造丝滑的颜色选择器(Color Picker)https://blog.csdn.net/qq_33650655/article/details/145689522

到此这篇文章就介绍到这了,更多精彩内容请关注本人以前的文章或继续浏览下面的文章,创作不易,如果能帮助到大家,希望大家多多支持宝码香车~💕,若转载本文,一定注明本文链接。


更多专栏订阅推荐:

👍 html+css+js 绚丽效果

💕 vue

✈️ Electron

⭐️ js

📝 字符串

✍️ 时间对象(Date())操作

相关推荐
Darling02zjh25 分钟前
GUI图形化演示
前端
Channing Lewis27 分钟前
如何判断一个网站后端是用什么语言写的
前端·数据库·python
互联网搬砖老肖38 分钟前
Web 架构之状态码全解
前端·架构
showmethetime1 小时前
matlab提取脑电数据的五种频域特征指标数值
前端·人工智能·matlab
码农捻旧1 小时前
解决Mongoose “Cannot overwrite model once compiled“ 错误的完整指南
javascript·数据库·mongodb·node.js·express
淡笑沐白1 小时前
探索Turn.js:打造惊艳的3D翻页效果
javascript·html5·turn.js
海上彼尚1 小时前
秒删node_modules[无废话版]
vue.js·react.js
sunxunyong2 小时前
yarn任务筛选spark任务,判断内存/CPU使用超过限制任务
javascript·ajax·spark
Ynov2 小时前
详细解释api
javascript·visual studio code
左钦杨2 小时前
IOS CSS3 right transformX 动画卡顿 回弹
前端·ios·css3