Electron + Vue 现代化“新品展示“和“快捷下单“菜单

项目结构

复制代码
src/
├── main/
│   └── index.js                 # Electron 主进程
├── preload/
│   └── index.js                 # 预加载脚本
├── renderer/
│   ├── App.vue                  # 根组件
│   ├── main.js                  # Vue 入口
│   ├── styles/
│   │   └── main.scss             # 全局样式
│   ├── components/
│   │   ├── TitleBar.vue         # 自定义标题栏
│   │   ├── Sidebar.vue          # 侧边栏菜单
│   │   ├── ProductShowcase.vue  # 新品展示
│   │   └── QuickOrder.vue       # 快捷下单
│   └── views/
│       └── Home.vue              # 首页
├── assets/
│   └── icons/                   # 图标资源
└── package.json

1. 主进程 (main/index.js)

复制代码
const { app, BrowserWindow, ipcMain, Menu } = require('electron')
const path = require('path')

let mainWindow

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 1400,
    height: 900,
    minWidth: 1200,
    minHeight: 700,
    frame: false,
    backgroundColor: '#0a0a0a',
    webPreferences: {
      preload: path.join(__dirname, '../preload/index.js'),
      contextIsolation: true,
      nodeIntegration: false
    }
  })

  // 开发环境加载 localhost,生产环境加载文件
  if (process.env.NODE_ENV === 'development') {
    mainWindow.loadURL('http://localhost:5173')
  } else {
    mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'))
  }

  // 创建现代化应用菜单
  createAppMenu()
}

// 创建应用菜单
function createAppMenu() {
  const template = [
    {
      label: '🏠 首页',
      key: 'home',
      click: () => {
        mainWindow.webContents.send('navigate', 'home')
      }
    },
    {
      label: '🆕 新品展示',
      submenu: [
        { 
          label: '🌸 春季新品', 
          key: 'new-spring',
          click: () => mainWindow.webContents.send('show-new-products', 'spring')
        },
        { 
          label: '☀️ 夏季特惠', 
          key: 'new-summer',
          click: () => mainWindow.webContents.send('show-new-products', 'summer')
        },
        { 
          label: '🍂 秋季限定', 
          key: 'new-autumn',
          click: () => mainWindow.webContents.send('show-new-products', 'autumn')
        },
        { type: 'separator' },
        { 
          label: '📦 全部新品', 
          key: 'new-all',
          accelerator: 'CmdOrCtrl+N',
          click: () => mainWindow.webContents.send('show-new-products', 'all')
        }
      ]
    },
    {
      label: '⚡ 快捷下单',
      submenu: [
        { 
          label: '🛒 快速购买', 
          key: 'quick-buy',
          accelerator: 'CmdOrCtrl+Q',
          click: () => mainWindow.webContents.send('quick-order', 'buy')
        },
        { 
          label: '📋 批量下单', 
          key: 'batch-order',
          click: () => mainWindow.webContents.send('quick-order', 'batch')
        },
        { 
          label: '📱 扫码下单', 
          key: 'scan-order',
          click: () => mainWindow.webContents.send('quick-order', 'scan')
        },
        { type: 'separator' },
        { 
          label: '🎫 优惠券', 
          key: 'coupon',
          click: () => mainWindow.webContents.send('quick-order', 'coupon')
        }
      ]
    },
    {
      label: '📊 订单管理',
      submenu: [
        { label: '📄 待付款', click: () => {} },
        { label: '📦 待发货', click: () => {} },
        { label: '✅ 已完成', click: () => {} }
      ]
    },
    {
      label: '⚙️ 设置',
      submenu: [
        { label: '🎨 主题设置', click: () => {} },
        { label: '🔔 通知设置', click: () => {} },
        { type: 'separator' },
        { label: '🚪 退出', role: 'quit' }
      ]
    }
  ]

  const menu = Menu.buildFromTemplate(template)
  Menu.setApplicationMenu(menu)
}

// 窗口控制
ipcMain.on('minimize-window', () => {
  mainWindow.minimize()
})

ipcMain.on('maximize-window', () => {
  if (mainWindow.isMaximized()) {
    mainWindow.unmaximize()
  } else {
    mainWindow.maximize()
  }
})

ipcMain.on('close-window', () => {
  mainWindow.close()
})

app.whenReady().then(createWindow)

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow()
  }
})

2. 预加载脚本 (preload/index.js)

复制代码
const { contextBridge, ipcRenderer } = require('electron')

// 暴露安全的 API 给渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
  // 窗口控制
  minimizeWindow: () => ipcRenderer.send('minimize-window'),
  maximizeWindow: () => ipcRenderer.send('maximize-window'),
  closeWindow: () => ipcRenderer.send('close-window'),
  
  // 导航
  onNavigate: (callback) => ipcRenderer.on('navigate', (event, page) => callback(page)),
  onShowNewProducts: (callback) => ipcRenderer.on('show-new-products', (event, season) => callback(season)),
  onQuickOrder: (callback) => ipcRenderer.on('quick-order', (event, type) => callback(type))
})

3. Vue 入口 (renderer/main.js)

复制代码
import { createApp } from 'vue'
import App from './App.vue'
import './styles/main.scss'

createApp(App).mount('#app')

4. 全局样式 (renderer/styles/main.scss)

复制代码
// 变量定义
$primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
$secondary-gradient: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
$dark-bg: #0a0a0a;
$card-bg: #1a1a2e;
$text-primary: #ffffff;
$text-secondary: #888888;
$accent-color: #667eea;

// 重置样式
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html, body, #app {
  height: 100%;
  font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
  background: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 100%);
  color: $text-primary;
  overflow: hidden;
}

// 滚动条样式
::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}

::-webkit-scrollbar-track {
  background: transparent;
}

::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.1);
  border-radius: 3px;
  
  &:hover {
    background: rgba(255, 255, 255, 0.2);
  }
}

// 动画
@keyframes fadeInUp {
  from {
    opacity: 0;
    transform: translateY(30px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes pulse {
  0%, 100% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.05);
  }
}

@keyframes shimmer {
  0% {
    background-position: -200% 0;
  }
  100% {
    background-position: 200% 0;
  }
}

@keyframes float {
  0%, 100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-10px);
  }
}

.animate-fadeInUp {
  animation: fadeInUp 0.6s ease forwards;
}

.animate-pulse {
  animation: pulse 2s infinite;
}

.animate-float {
  animation: float 3s ease-in-out infinite;
}

5. 根组件 (renderer/App.vue)

复制代码
<template>
  <div class="app-container">
    <!-- 自定义标题栏 -->
    <TitleBar />
    
    <!-- 主内容区 -->
    <div class="main-wrapper">
      <!-- 侧边栏 -->
      <Sidebar 
        :active-menu="activeMenu" 
        @update:active-menu="activeMenu = $event"
      />
      
      <!-- 内容区 -->
      <main class="content-area">
        <Home 
          v-if="activeMenu === 'home'"
          @show-new-products="handleShowNewProducts"
          @quick-order="handleQuickOrder"
        />
      </main>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import TitleBar from './components/TitleBar.vue'
import Sidebar from './components/Sidebar.vue'
import Home from './views/Home.vue'

const activeMenu = ref('home')

// 处理主进程消息
const handleShowNewProducts = (season) => {
  console.log('显示新品:', season)
}

const handleQuickOrder = (type) => {
  console.log('快捷下单:', type)
}

onMounted(() => {
  // 监听主进程导航事件
  if (window.electronAPI) {
    window.electronAPI.onNavigate((page) => {
      activeMenu.value = page
    })
    
    window.electronAPI.onShowNewProducts((season) => {
      handleShowNewProducts(season)
    })
    
    window.electronAPI.onQuickOrder((type) => {
      handleQuickOrder(type)
    })
  }
})
</script>

<style lang="scss" scoped>
.app-container {
  display: flex;
  flex-direction: column;
  height: 100vh;
  background: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 100%);
}

.main-wrapper {
  display: flex;
  flex: 1;
  overflow: hidden;
}

.content-area {
  flex: 1;
  overflow-y: auto;
  padding: 24px;
}
</style>

6. 自定义标题栏 (renderer/components/TitleBar.vue)

复制代码
<template>
  <header class="title-bar">
    <div class="title-bar-left">
      <span class="logo">🛍️</span>
      <span class="app-name">电商管理系统</span>
    </div>
    
    <div class="title-bar-center">
      <div class="search-box">
        <input 
          v-model="searchQuery" 
          type="text" 
          placeholder="搜索商品..."
          @keyup.enter="handleSearch"
        />
        <button class="search-btn" @click="handleSearch">
          🔍
        </button>
      </div>
    </div>
    
    <div class="title-bar-right">
      <button class="window-btn" @click="minimizeWindow">─</button>
      <button class="window-btn" @click="maximizeWindow">□</button>
      <button class="window-btn close-btn" @click="closeWindow">✕</button>
    </div>
  </header>
</template>

<script setup>
import { ref } from 'vue'

const searchQuery = ref('')

const handleSearch = () => {
  console.log('搜索:', searchQuery.value)
}

const minimizeWindow = () => {
  window.electronAPI?.minimizeWindow()
}

const maximizeWindow = () => {
  window.electronAPI?.maximizeWindow()
}

const closeWindow = () => {
  window.electronAPI?.closeWindow()
}
</script>

<style lang="scss" scoped>
.title-bar {
  height: 56px;
  background: linear-gradient(90deg, #0f0f23 0%, #1a1a3e 100%);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 16px;
  -webkit-app-region: drag;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  backdrop-filter: blur(10px);
}

.title-bar-left {
  display: flex;
  align-items: center;
  gap: 12px;
  
  .logo {
    font-size: 24px;
    filter: drop-shadow(0 0 8px rgba(102, 126, 234, 0.5));
  }
  
  .app-name {
    font-size: 16px;
    font-weight: 600;
    background: linear-gradient(90deg, #667eea, #764ba2);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
  }
}

.title-bar-center {
  flex: 1;
  max-width: 400px;
  margin: 0 24px;
}

.search-box {
  display: flex;
  background: rgba(255, 255, 255, 0.05);
  border-radius: 24px;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.1);
  transition: all 0.3s ease;
  
  &:focus-within {
    border-color: #667eea;
    box-shadow: 0 0 20px rgba(102, 126, 234, 0.2);
  }
  
  input {
    flex: 1;
    background: transparent;
    border: none;
    padding: 10px 18px;
    color: #fff;
    font-size: 14px;
    outline: none;
    
    &::placeholder {
      color: rgba(255, 255, 255, 0.4);
    }
  }
  
  .search-btn {
    background: linear-gradient(90deg, #667eea, #764ba2);
    border: none;
    padding: 10px 18px;
    color: #fff;
    cursor: pointer;
    transition: all 0.3s ease;
    
    &:hover {
      opacity: 0.9;
    }
  }
}

.title-bar-right {
  display: flex;
  -webkit-app-region: no-drag;
  gap: 4px;
}

.window-btn {
  width: 42px;
  height: 32px;
  background: transparent;
  border: none;
  color: #666;
  font-size: 12px;
  cursor: pointer;
  transition: all 0.2s ease;
  border-radius: 4px;
  
  &:hover {
    background: rgba(255, 255, 255, 0.1);
    color: #fff;
  }
  
  &.close-btn:hover {
    background: #e81123;
    color: #fff;
  }
}
</style>

7. 侧边栏 (renderer/components/Sidebar.vue)

复制代码
<template>
  <aside class="sidebar">
    <div class="sidebar-section">
      <h3 class="section-title">
        <span>📦 商品管理</span>
      </h3>
      <ul class="menu-list">
        <li 
          v-for="item in productMenus" 
          :key="item.id"
          :class="['menu-item', { active: activeMenu === item.id }]"
          @click="$emit('update:active-menu', item.id)"
        >
          <span class="icon">{{ item.icon }}</span>
          <span class="text">{{ item.label }}</span>
        </li>
      </ul>
    </div>

    <div class="sidebar-section">
      <h3 class="section-title">
        <span>🆕 新品展示</span>
      </h3>
      <ul class="menu-list">
        <li 
          v-for="item in newProductMenus" 
          :key="item.id"
          :class="['menu-item', { active: activeMenu === item.id }]"
          @click="$emit('update:active-menu', item.id)"
        >
          <span class="icon">{{ item.icon }}</span>
          <span class="text">{{ item.label }}</span>
          <span v-if="item.badge" :class="['badge', item.badge.type]">
            {{ item.badge.text }}
          </span>
        </li>
      </ul>
    </div>

    <div class="sidebar-section">
      <h3 class="section-title">
        <span>⚡ 快捷下单</span>
      </h3>
      <ul class="menu-list">
        <li 
          v-for="item in quickOrderMenus" 
          :key="item.id"
          :class="['menu-item', { active: activeMenu === item.id }]"
          @click="$emit('update:active-menu', item.id)"
        >
          <span class="icon">{{ item.icon }}</span>
          <span class="text">{{ item.label }}</span>
          <span v-if="item.shortcut" class="shortcut">{{ item.shortcut }}</span>
        </li>
      </ul>
    </div>

    <div class="sidebar-section">
      <h3 class="section-title">
        <span>📊 数据统计</span>
      </h3>
      <ul class="menu-list">
        <li 
          v-for="item in statsMenus" 
          :key="item.id"
          :class="['menu-item', { active: activeMenu === item.id }]"
          @click="$emit('update:active-menu', item.id)"
        >
          <span class="icon">{{ item.icon }}</span>
          <span class="text">{{ item.label }}</span>
        </li>
      </ul>
    </div>
  </aside>
</template>

<script setup>
defineProps({
  activeMenu: {
    type: String,
    default: 'home'
  }
})

defineEmits(['update:active-menu'])

// 商品管理菜单
const productMenus = [
  { id: 'home', icon: '🏠', label: '首页' },
  { id: 'products', icon: '📦', label: '商品列表' },
  { id: 'categories', icon: '🏷️', label: '分类管理' }
]

// 新品展示菜单
const newProductMenus = [
  { 
    id: 'new-spring', 
    icon: '🌸', 
    label: '春季新品',
    badge: { type: 'new', text: 'NEW' }
  },
  { 
    id: 'new-summer', 
    icon: '☀️', 
    label: '夏季特惠',
    badge: { type: 'hot', text: 'HOT' }
  },
  { 
    id: 'new-autumn', 
    icon: '🍂', 
    label: '秋季限定' 
  }
]

// 快捷下单菜单
const quickOrderMenus = [
  { 
    id: 'quick-buy', 
    icon: '🛒', 
    label: '快速购买',
    shortcut: 'Ctrl+Q'
  },
  { 
    id: 'batch-order', 
    icon: '📋', 
    label: '批量下单' 
  },
  { 
    id: 'scan-order', 
    icon: '📱', 
    label: '扫码下单' 
  }
]

// 数据统计菜单
const statsMenus = [
  { id: 'sales', icon: '📈', label: '销售统计' },
  { id: 'orders', icon: '📄', label: '订单管理' }
]
</script>

<style lang="scss" scoped>
.sidebar {
  width: 260px;
  background: linear-gradient(180deg, #0f0f23 0%, #1a1a3e 100%);
  padding: 20px 0;
  border-right: 1px solid rgba(255, 255, 255, 0.06);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.sidebar-section {
  padding: 0 12px;
}

.section-title {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 1.5px;
  color: #555;
  padding: 8px 12px;
  margin-bottom: 4px;
  font-weight: 600;
}

.menu-list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.menu-item {
  display: flex;
  align-items: center;
  padding: 12px 16px;
  color: #888;
  cursor: pointer;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  position: relative;
  border-radius: 10px;
  margin: 2px 0;
  
  &:hover {
    background: rgba(102, 126, 234, 0.08);
    color: #fff;
    
    .icon {
      transform: scale(1.1);
    }
  }
  
  &.active {
    background: linear-gradient(90deg, rgba(102, 126, 234, 0.15), transparent);
    color: #667eea;
    
    &::before {
      content: '';
      position: absolute;
      left: 0;
      top: 50%;
      transform: translateY(-50%);
      width: 3px;
      height: 60%;
      background: linear-gradient(180deg, #667eea, #764ba2);
      border-radius: 0 3px 3px 0;
    }
  }
  
  .icon {
    font-size: 18px;
    margin-right: 12px;
    width: 24px;
    text-align: center;
    transition: transform 0.3s ease;
  }
  
  .text {
    flex: 1;
    font-size: 14px;
    font-weight: 500;
  }
  
  .badge {
    font-size: 10px;
    padding: 3px 8px;
    border-radius: 10px;
    text-transform: uppercase;
    font-weight: 600;
    
    &.new {
      background: linear-gradient(90deg, #00b894, #00cec9);
      color: #fff;
      box-shadow: 0 2px 8px rgba(0, 184, 148, 0.3);
    }
    
    &.hot {
      background: linear-gradient(90deg, #e17055, #d63031);
      color: #fff;
      box-shadow: 0 2px 8px rgba(214, 48, 49, 0.3);
    }
  }
  
  .shortcut {
    font-size: 11px;
    color: #555;
    background: rgba(255, 255, 255, 0.05);
    padding: 3px 8px;
    border-radius: 4px;
  }
}
</style>

8. 首页视图 (renderer/views/Home.vue)

复制代码
<template>
  <div class="home-view">
    <!-- 欢迎卡片 -->
    <div class="welcome-card animate-fadeInUp">
      <h1>🎉 欢迎使用电商管理系统</h1>
      <p>选择左侧菜单开始使用,或浏览下方推荐商品</p>
    </div>

    <!-- 新品展示区 -->
    <section class="product-section">
      <div class="section-header">
        <h2>🆕 新品展示</h2>
        <div class="header-actions">
          <span class="season-tabs">
            <button 
              v-for="tab in seasonTabs" 
              :key="tab.id"
              :class="['tab-btn', { active: currentSeason === tab.id }]"
              @click="currentSeason = tab.id"
            >
              {{ tab.icon }} {{ tab.label }}
            </button>
          </span>
          <a href="#" class="view-all" @click.prevent="viewAllNew">查看全部 →</a>
        </div>
      </div>
      
      <div class="product-grid">
        <div 
          v-for="(product, index) in filteredProducts" 
          :key="product.id"
          class="product-card"
          :style="{ animationDelay: `${index * 0.1}s` }"
          @click="viewProduct(product)"
        >
          <div class="product-image">
            <img :src="product.image" :alt="product.name" />
            <span v-if="product.badge" :class="['product-badge', product.badge.type]">
              {{ product.badge.text }}
            </span>
            <div class="product-overlay">
              <button class="quick-view-btn">👁️ 快速查看</button>
            </div>
          </div>
          <div class="product-info">
            <h3>{{ product.name }}</h3>
            <p class="product-desc">{{ product.description }}</p>
            <div class="product-footer">
              <span class="price">¥{{ product.price.toFixed(2) }}</span>
              <button class="buy-btn" @click.stop="quickBuy(product)">
                🛒 购买
              </button>
            </div>
          </div>
        </div>
      </div>
    </section>

    <!-- 快捷下单区 -->
    <section class="quick-order-section">
      <div class="section-header">
        <h2>⚡ 快捷下单</h2>
        <p>快速完成您的采购需求</p>
      </div>
      
      <div class="order-options">
        <div 
          v-for="option in orderOptions" 
          :key="option.id"
          class="order-card"
          :class="[`order-${option.id}`]"
          @click="selectOrderType(option)"
        >
          <div class="order-icon" :class="{ 'animate-float': option.animated }">
            {{ option.icon }}
          </div>
          <h3>{{ option.title }}</h3>
          <p>{{ option.description }}</p>
          <button class="order-btn" :class="option.btnClass">
            {{ option.btnText }}
          </button>
        </div>
      </div>
    </section>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

// 季节标签
const seasonTabs = [
  { id: 'all', icon: '📦', label: '全部' },
  { id: 'spring', icon: '🌸', label: '春季' },
  { id: 'summer', icon: '☀️', label: '夏季' },
  { id: 'autumn', icon: '🍂', label: '秋季' }
]

const currentSeason = ref('all')

// 产品数据
const products = ref([
  {
    id: 1,
    name: '时尚运动鞋',
    description: '舒适透气,潮流百搭',
    price: 299.00,
    image: 'https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=400&h=400&fit=crop',
    season: 'spring',
    badge: { type: 'new', text: '新品' }
  },
  {
    id: 2,
    name: '智能手表 Pro',
    description: '多功能健康监测,长续航',
    price: 599.00,
    image: 'https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=400&h=400&fit=crop',
    season: 'summer',
    badge: { type: 'hot', text: '热卖' }
  },
  {
    id: 3,
    name: '无线降噪耳机',
    description: 'Hi-Fi音质,40小时续航',
    price: 199.00,
    image: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=400&h=400&fit=crop',
    season: 'autumn',
    badge: { type: 'sale', text: '特价' }
  },
  {
    id: 4,
    name: '设计师背包',
    description: '防水材质,大容量收纳',
    price: 159.00,
    image: 'https://images.unsplash.com/photo-1553062407-98eeb64c6a62?w=400&h=400&fit=crop',
    season: 'spring',
    badge: null
  },
  {
    id: 5,
    name: '便携蓝牙音箱',
    description: '360°环绕音效,IPX7防水',
    price: 89.00,
    image: 'https://images.unsplash.com/photo-1608043152269-423dbba4e7e1?w=400&h=400&fit=crop',
    season: 'summer',
    badge: { type: 'new', text: '新品' }
  },
  {
    id: 6,
    name: '机械键盘',
    description: 'RGB背光,青轴手感',
    price: 249.00,
    image: 'https://images.unsplash.com/photo-1511467687858-23d96c32e4ae?w=400&h=400&fit=crop',
    season: 'autumn',
    badge: { type: 'hot', text: '热卖' }
  }
])

// 过滤后的产品
const filteredProducts = computed(() => {
  if (currentSeason.value === 'all') {
    return products.value
  }
  return products.value.filter(p => p.season === currentSeason.value)
})

// 下单选项
const orderOptions = [
  {
    id: 'buy',
    icon: '🛒',
    title: '快速购买',
    description: '一键下单,极速配送',
    btnText: '立即购买',
    btnClass: 'btn-primary',
    animated: true
  },
  {
    id: 'batch',
    icon: '📋',
    title: '批量下单',
    description: '多商品同时采购,享批发价',
    btnText: '批量采购',
    btnClass: 'btn-success',
    animated: false
  },
  {
    id: 'scan',
    icon: '📱',
    title: '扫码下单',
    description: '扫描二维码快速下单',
    btnText: '扫码购买',
    btnClass: 'btn-warning',
    animated: true
  }
]

// 方法
const viewAllNew = () => {
  currentSeason.value = 'all'
}

const viewProduct = (product) => {
  console.log('查看产品:', product)
}

const quickBuy = (product) => {
  console.log('快速购买:', product)
}

const selectOrderType = (option) => {
  console.log('选择下单类型:', option)
}
</script>

<style lang="scss" scoped>
.home-view {
  display: flex;
  flex-direction: column;
  gap: 32px;
}

// 欢迎卡片
.welcome-card {
  text-align: center;
  padding: 60px 40px;
  background: linear-gradient(135deg, rgba(102, 126, 234, 0.1), rgba(118, 75, 162, 0.1));
  border-radius: 24px;
  border: 1px solid rgba(255, 255, 255, 0.08);
  backdrop-filter: blur(10px);
  
  h1 {
    font-size: 36px;
    font-weight: 700;
    margin-bottom: 12px;
    background: linear-gradient(90deg, #667eea, #764ba2, #f093fb);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
  }
  
  p {
    color: #888;
    font-size: 16px;
  }
}

// 产品区
.product-section {
  .section-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 24px;
    
    h2 {
      font-size: 24px;
      font-weight: 600;
    }
    
    .header-actions {
      display: flex;
      align-items: center;
      gap: 20px;
    }
    
    .season-tabs {
      display: flex;
      gap: 8px;
      
      .tab-btn {
        padding: 8px 16px;
        background: rgba(255, 255, 255, 0.05);
        border: 1px solid rgba(255, 255, 255, 0.1);
        border-radius: 20px;
        color: #888;
        font-size: 13px;
        cursor: pointer;
        transition: all 0.3s ease;
        
        &:hover {
          background: rgba(102, 126, 234, 0.1);
          color: #fff;
        }
        
        &.active {
          background: linear-gradient(90deg, #667eea, #764ba2);
          border-color: transparent;
          color: #fff;
        }
      }
    }
    
    .view-all {
      color: #667eea;
      text-decoration: none;
      font-size: 14px;
      font-weight: 500;
      transition: all 0.3s ease;
      
      &:hover {
        color: #764ba2;
        text-decoration: underline;
      }
    }
  }
}

.product-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 24px;
}

.product-card {
  background: linear-gradient(145deg, #1a1a2e, #16213e);
  border-radius: 20px;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.06);
  transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
  cursor: pointer;
  animation: fadeInUp 0.6s ease forwards;
  opacity: 0;
  
  &:hover {
    transform: translateY(-8px);
    box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3);
    border-color: rgba(102, 126, 234, 0.3);
    
    .product-image img {
      transform: scale(1.1);
    }
    
    .product-overlay {
      opacity: 1;
    }
  }
  
  .product-image {
    position: relative;
    height: 220px;
    overflow: hidden;
    
    img {
      width: 100%;
      height: 100%;
      object-fit: cover;
      transition: transform 0.5s ease;
    }
    
    .product-badge {
      position: absolute;
      top: 12px;
      left: 12px;
      padding: 6px 14px;
      border-radius: 20px;
      font-size: 12px;
      font-weight: 600;
      color: #fff;
      z-index: 1;
      
      &.new {
        background: linear-gradient(90deg, #00b894, #00cec9);
        box-shadow: 0 4px 15px rgba(0, 184, 148, 0.4);
      }
      
      &.hot {
        background: linear-gradient(90deg, #e17055, #d63031);
        box-shadow: 0 4px 15px rgba(214, 48, 49, 0.4);
      }
      
      &.sale {
        background: linear-gradient(90deg, #fdcb6e, #e17055);
        box-shadow: 0 4px 15px rgba(225, 112, 85, 0.4);
      }
    }
    
    .product-overlay {
      position: absolute;
      inset: 0;
      background: rgba(0, 0, 0, 0.6);
      display: flex;
      align-items: center;
      justify-content: center;
      opacity: 0;
      transition: opacity 0.3s ease;
      
      .quick-view-btn {
        padding: 10px 24px;
        background: linear-gradient(90deg, #667eea, #764ba2);
        border: none;
        border-radius: 25px;
        color: #fff;
        font-size: 14px;
        font-weight: 500;
        cursor: pointer;
        transition: all 0.3s ease;
        
        &:hover {
          transform: scale(1.05);
          box-shadow: 0 5px 20px rgba(102, 126, 234, 0.5);
        }
      }
    }
  }
  
  .product-info {
    padding: 20px;
    
    h3 {
      font-size: 18px;
      font-weight: 600;
      margin-bottom: 8px;
      color: #fff;
    }
    
    .product-desc {
      font-size: 13px;
      color: #888;
      margin-bottom: 16px;
      line-height: 1.5;
    }
    
    .product-footer {
      display: flex;
      justify-content: space-between;
      align-items: center;
      
      .price {
        font-size: 24px;
        font-weight: 700;
        color: #667eea;
      }
      
      .buy-btn {
        padding: 8px 20px;
        background: linear-gradient(90deg, #667eea, #764ba2);
        border: none;
        border-radius: 8px;
        color: #fff;
        font-size: 14px;
        font-weight: 500;
        cursor: pointer;
        transition: all 0.3s ease;
        
        &:hover {
          transform: translateY(-2px);
          box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
        }
      }
    }
  }
}

// 快捷下单区
.quick-order-section {
  .section-header {
    margin-bottom: 24px;
    
    h2 {
      font-size: 24px;
      font-weight: 600;
      margin-bottom: 8px;
    }
    
    p {
      color: #888;
      font-size: 14px;
    }
  }
}

.order-options {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 24px;
}

.order-card {
  background: linear-gradient(145deg, #1a1a2e, #16213e);
  border-radius: 20px;
  padding: 32px;
  text-align: center;
  border: 1px solid rgba(255, 255, 255, 0.06);
  transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
  cursor: pointer;
  position: relative;
  overflow: hidden;
  
  &::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 3px;
    background: linear-gradient(90deg, #667eea, #764ba2);
    transform: scaleX(0);
    transition: transform 0.3s ease;
  }
  
  &:hover {
    transform: translateY(-8px);
    border-color: rgba(102, 126, 234, 0.3);
    box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3);
    
    &::before {
      transform: scaleX(1);
    }
    
    .order-icon {
      transform: scale(1.1);
    }
  }
  
  .order-icon {
    font-size: 56px;
    margin-bottom: 20px;
    display: inline-block;
    transition: transform 0.3s ease;
  }
  
  h3 {
    font-size: 20px;
    font-weight: 600;
    margin-bottom: 8px;
    color: #fff;
  }
  
  p {
    font-size: 14px;
    color: #888;
    margin-bottom: 20px;
    line-height: 1.5;
  }
  
  .order-btn {
    padding: 10px 28px;
    border: none;
    border-radius: 10px;
    color: #fff;
    font-size: 14px;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.3s ease;
    
    &.btn-primary {
      background: linear-gradient(90deg, #667eea, #764ba2);
      
      &:hover {
        box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
      }
    }
    
    &.btn-success {
      background: linear-gradient(90deg, #00b894, #00cec9);
      
      &:hover {
        box-shadow: 0 5px 20px rgba(0, 184, 148, 0.4);
      }
    }
    
    &.btn-warning {
      background: linear-gradient(90deg, #fdcb6e, #e17055);
      
      &:hover {
        box-shadow: 0 5px 20px rgba(225, 112, 85, 0.4);
      }
    }
  }
}
</style>

9. 运行项目

复制代码
# 安装依赖
npm install

# 开发模式运行
npm run dev

# 生产模式构建
npm run build

主要特性

  1. 渐变色彩​ - 使用紫色/蓝紫渐变,现代感强

  2. 动画效果​ - 卡片悬停上浮、图片放大、按钮发光

  3. 毛玻璃效果​ - 标题栏和卡片使用 backdrop-filter

  4. 徽章标签​ - NEW、HOT、SALE 等状态标识

  5. 快捷键支持​ - 显示 Ctrl+Q 等快捷键

  6. 响应式设计​ - 自适应不同屏幕尺寸

  7. 无边框窗口​ - 自定义标题栏,更美观

  8. IPC 通信​ - 主进程与渲染进程安全通信

相关推荐
重庆穿山甲1 小时前
身份证照片自动裁剪(OpenCV 四边形检测 + 透视矫正)
前端·后端
泡沫_cqy1 小时前
Java初学者文档
java·开发语言
何贤1 小时前
用 Three.js 写了一个《我的世界》,结果老外差点给我众筹做游戏?
前端·开源·three.js
前进的李工1 小时前
数据库视图:数据安全与权限管理利器
开发语言·数据库·mysql·navicat
C_心欲无痕1 小时前
使用 XLSX.js 导出 Excel 文件
开发语言·javascript·excel
踩着两条虫2 小时前
AI 驱动的 Vue3 应用开发平台 深入探究(七):双向代码转换之 Vue源码到DSL解析
前端·vue.js·ai编程
专业流量卡2 小时前
用ai去看源码
前端·react.js
踩着两条虫2 小时前
AI 驱动的 Vue3 应用开发平台 深入探究(六):双向代码转换之DSL到Vue代码生成
前端·vue.js·ai编程
Wect2 小时前
React 中的双缓存 Fiber 树机制
前端·react.js·面试