Files
chuanggao-website/design/前端项目架构.md
2026-05-12 16:53:18 +08:00

24 KiB
Raw Blame History

创高家具官网 - 前端项目架构设计

一、技术栈选型

层级 技术 版本 用途
框架 Vue 3 ^3.4.0 渐进式前端框架
构建 Vite ^5.0.0 快速构建工具
路由 Vue Router ^4.2.0 单页应用路由
状态 Pinia ^2.1.0 状态管理
国际化 Vue I18n ^9.0.0 中英文切换
UI组件 Element Plus ^2.5.0 基础组件库
动画 GSAP ^3.12.0 高级动画效果
图标 Element Plus Icons - 图标库
CSS SCSS - 预处理器

二、项目目录结构

client/
├── index.html                    # 入口HTML
├── package.json                  # 依赖配置
├── vite.config.ts                # Vite配置
├── tsconfig.json                 # TypeScript配置
├── .env                          # 环境变量(开发)
├── .env.production               # 环境变量(生产)
│
├── public/                       # 静态资源(不处理)
│   ├── favicon.ico
│   └── logo.png
│
├── src/
│   ├── main.ts                   # 应用入口
│   ├── App.vue                   # 根组件
│   │
│   ├── api/                      # API接口层
│   │   ├── request.ts            # axios封装
│   │   ├── portal/               # 前台接口
│   │   │   ├── company.ts
│   │   │   ├── home.ts
│   │   │   ├── product.ts
│   │   │   ├── case.ts
│   │   │   ├── news.ts
│   │   │   └── contact.ts
│   │   └── admin/                # 后台接口
│   │       ├── auth.ts
│   │       ├── upload.ts
│   │       ├── product.ts
│   │       ├── case.ts
│   │       ├── news.ts
│   │       ├── pageBlock.ts
│   │       └── media.ts
│   │
│   ├── assets/                   # 资源文件
│   │   ├── styles/               # 全局样式
│   │   │   ├── variables.scss    # SCSS变量
│   │   │   ├── mixins.scss       # SCSS混入
│   │   │   ├── global.scss       # 全局样式
│   │   │   └── element-plus.scss # Element Plus主题覆盖
│   │   ├── images/               # 静态图片
│   │   └── fonts/                # 字体文件
│   │
│   ├── components/               # 公共组件
│   │   ├── common/               # 通用组件
│   │   │   ├── LangSwitch.vue    # 语言切换
│   │   │   ├── PageLoading.vue   # 页面加载
│   │   │   ├── ImageLazy.vue     # 懒加载图片
│   │   │   └── Breadcrumb.vue    # 面包屑
│   │   ├── layout/               # 布局组件
│   │   │   ├── AppHeader.vue     # 顶部导航
│   │   │   ├── AppFooter.vue     # 底部导航
│   │   │   ├── MobileMenu.vue    # 移动端菜单
│   │   │   └── BackToTop.vue     # 返回顶部
│   │   ├── home/                 # 首页专用组件
│   │   │   ├── HeroBanner.vue    # 首屏轮播
│   │   │   ├── ServiceSection.vue # 服务板块
│   │   │   ├── ProductSection.vue # 产品板块
│   │   │   ├── CaseSection.vue   # 案例板块
│   │   │   └── StrengthSection.vue # 实力板块
│   │   └── product/              # 产品页组件
│   │       ├── ProductCard.vue
│   │       ├── ProductFilter.vue
│   │       └── ProductGallery.vue
│   │
│   ├── composables/              # 组合式函数
│   │   ├── useLocale.ts          # 语言相关
│   │   ├── useScroll.ts          # 滚动监听
│   │   ├── useDevice.ts          # 设备检测
│   │   └── useSEO.ts             # SEO设置
│   │
│   ├── directives/               # 自定义指令
│   │   ├── lazyLoad.ts           # 图片懒加载
│   │   └── parallax.ts           # 视差滚动
│   │
│   ├── locales/                  # 国际化文件
│   │   ├── index.ts              # 配置入口
│   │   ├── zh-CN.ts              # 中文
│   │   └── en-US.ts              # 英文
│   │
│   ├── router/                   # 路由配置
│   │   ├── index.ts              # 路由入口
│   │   ├── routes.ts             # 路由定义
│   │   ├── portal.ts             # 前台路由
│   │   └── admin.ts              # 后台路由
│   │
│   ├── stores/                   # Pinia状态管理
│   │   ├── index.ts
│   │   ├── app.ts                # 应用状态
│   │   ├── user.ts               # 用户状态
│   │   └── setting.ts            # 设置状态
│   │
│   ├── types/                    # TypeScript类型
│   │   ├── api.d.ts              # API响应类型
│   │   ├── model.d.ts            # 数据模型类型
│   │   └── global.d.ts           # 全局类型
│   │
│   ├── utils/                    # 工具函数
│   │   ├── storage.ts            # 本地存储封装
│   │   ├── format.ts             # 格式化
│   │   ├── validate.ts           # 验证
│   │   └── constants.ts          # 常量
│   │
│   └── views/                    # 页面视图
│       ├── portal/               # 前台页面
│       │   ├── HomeView.vue
│       │   ├── ProductView.vue
│       │   ├── ProductDetailView.vue
│       │   ├── CaseView.vue
│       │   ├── CaseDetailView.vue
│       │   ├── AboutView.vue
│       │   ├── NewsView.vue
│       │   ├── NewsDetailView.vue
│       │   └── ContactView.vue
│       └── admin/                # 后台管理页面
│           ├── LoginView.vue
│           ├── DashboardView.vue
│           ├── Layout.vue
│           ├── company/
│           │   └── InfoManage.vue
│           ├── content/
│           │   ├── CarouselManage.vue
│           │   ├── PageBlockManage.vue
│           │   └── MediaLibrary.vue
│           ├── product/
│           │   ├── CategoryManage.vue
│           │   └── ProductManage.vue
│           ├── case/
│           │   ├── CaseCategoryManage.vue
│           │   └── CaseManage.vue
│           ├── news/
│           │   ├── NewsCategoryManage.vue
│           │   └── NewsManage.vue
│           ├── message/
│           │   └── MessageManage.vue
│           └── system/
│               ├── UserManage.vue
│               └── LogManage.vue

三、核心配置文件

3.1 vite.config.ts

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
    },
  },
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `
          @use "@/assets/styles/variables.scss" as *;
          @use "@/assets/styles/mixins.scss" as *;
        `,
      },
    },
  },
  server: {
    port: 3000,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
      },
    },
  },
  build: {
    outDir: 'dist',
    assetsDir: 'assets',
    rollupOptions: {
      output: {
        chunkFileNames: 'js/[name]-[hash].js',
        entryFileNames: 'js/[name]-[hash].js',
        assetFileNames: (assetInfo) => {
          const info = assetInfo.name.split('.')
          const ext = info[info.length - 1]
          if (/\.(png|jpe?g|gif|svg|webp|ico)$/.test(assetInfo.name)) {
            return 'images/[name]-[hash][extname]'
          }
          if (/\.(woff2?|eot|ttf|otf)$/.test(assetInfo.name)) {
            return 'fonts/[name]-[hash][extname]'
          }
          return '[ext]/[name]-[hash][extname]'
        },
      },
    },
  },
})

3.2 .env.production

# 站点代码
VITE_SITE_CODE=cg

# API地址
VITE_API_BASE_URL=/api

# 多语言默认
VITE_DEFAULT_LANG=zh-CN

# MinIO配置
VITE_MINIO_ENDPOINT=http://localhost:9000
VITE_MINIO_BUCKET=chuanggao-images
VITE_MINIO_PUBLIC_URL=http://localhost:9000/chuanggao-images

3.3 tsconfig.json

{
  "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "lib": ["ESNext", "DOM", "DOM.Iterable"],
    "skipLibCheck": true,
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
  "references": [{ "path": "./tsconfig.node.json" }]
}

四、国际化配置

4.1 locales/index.ts

import { createI18n } from 'vue-i18n'
import zhCN from './zh-CN'
import enUS from './en-US'

const messages = {
  'zh-CN': zhCN,
  'en-US': enUS,
}

const i18n = createI18n({
  legacy: false,
  locale: localStorage.getItem('lang') || import.meta.env.VITE_DEFAULT_LANG || 'zh-CN',
  fallbackLocale: 'zh-CN',
  messages,
})

export default i18n

4.2 locales/zh-CN.ts节选

export default {
  // 通用
  common: {
    home: '首页',
    products: '产品中心',
    cases: '案例展示',
    about: '关于创高',
    contact: '联系我们',
    franchise: '招商加盟',
    more: '了解更多',
    viewAll: '查看全部',
    submit: '提交',
    cancel: '取消',
    confirm: '确认',
    loading: '加载中...',
    noData: '暂无数据',
    search: '搜索',
    hotline: '服务热线',
  },
  
  // 首页
  home: {
    hero: {
      title: '全屋定制 匠心之作',
      subtitle: '专注高端家具定制,为品质生活而生',
      btnContact: '立即咨询',
      btnProducts: '查看产品',
    },
    services: {
      title: '核心业务',
      subtitle: '三大业务板块,全方位满足您的需求',
      custom: {
        title: '全屋定制',
        desc: '从客厅到卧室,从厨房到书房,一站式全屋定制解决方案',
      },
      furniture: {
        title: '成品家具',
        desc: '精选全球优质原材料,打造舒适耐用的高端家具',
      },
      project: {
        title: '工程项目',
        desc: '酒店、办公、精装房等专业工程家具配套服务',
      },
    },
    products: {
      title: '热门产品',
      subtitle: '精选系列,品质之选',
    },
    cases: {
      title: '经典案例',
      subtitle: '实景呈现,见证品质',
    },
    strength: {
      title: '品牌实力',
      subtitle: '二十年匠心积淀,铸就行业标杆',
      years: '年行业经验',
      factory: '㎡生产基地',
      clients: '家服务客户',
      patents: '项国家专利',
    },
  },
  
  // 产品中心
  product: {
    title: '产品中心',
    filter: {
      all: '全部',
      category: '分类',
      style: '风格',
      scene: '场景',
    },
    params: '产品参数',
    scene: '场景展示',
    inquiry: '立即咨询',
    similar: '相关产品',
  },
  
  // 案例展示
  case: {
    title: '案例展示',
    filter: '场景筛选',
    area: '项目面积',
    location: '项目地点',
    date: '完工时间',
    products: '使用产品',
  },
  
  // 关于创高
  about: {
    title: '关于创高',
    story: '品牌故事',
    factory: '工厂实力',
    honor: '荣誉资质',
    news: '新闻资讯',
  },
  
  // 联系我们
  contact: {
    title: '联系我们',
    form: {
      name: '您的姓名',
      phone: '联系电话',
      email: '电子邮箱',
      company: '公司名称',
      type: '咨询类型',
      message: '留言内容',
      placeholder: '请输入您的咨询内容...',
    },
    types: {
      join: '加盟咨询',
      cooperate: '工程合作',
      feedback: '客户反馈',
      other: '其他',
    },
    success: '提交成功,我们会尽快联系您!',
  },
  
  // 招商加盟
  franchise: {
    title: '招商加盟',
    subtitle: '携手创高,共创未来',
    policy: '加盟政策',
    process: '加盟流程',
    support: '六大支持',
    contact: '立即咨询',
  },
  
  // 页脚
  footer: {
    navigation: '快速导航',
    products: '产品中心',
    service: '客户服务',
    contact: '联系方式',
    follow: '关注我们',
    copyright: '© {year} 创高家具 版权所有',
    icp: '粤ICP备xxxxxxxx号',
  },
}

4.3 locales/en-US.ts结构同上英文翻译


五、路由设计

5.1 router/portal.ts前台路由

import type { RouteRecordRaw } from 'vue-router'

const portalRoutes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'Home',
    component: () => import('@/views/portal/HomeView.vue'),
    meta: {
      title: '首页',
      titleEn: 'Home',
    },
  },
  {
    path: '/products',
    name: 'Products',
    component: () => import('@/views/portal/ProductView.vue'),
    meta: {
      title: '产品中心',
      titleEn: 'Products',
    },
  },
  {
    path: '/products/:id',
    name: 'ProductDetail',
    component: () => import('@/views/portal/ProductDetailView.vue'),
    meta: {
      title: '产品详情',
      titleEn: 'Product Detail',
    },
  },
  {
    path: '/cases',
    name: 'Cases',
    component: () => import('@/views/portal/CaseView.vue'),
    meta: {
      title: '案例展示',
      titleEn: 'Cases',
    },
  },
  {
    path: '/cases/:id',
    name: 'CaseDetail',
    component: () => import('@/views/portal/CaseDetailView.vue'),
    meta: {
      title: '案例详情',
      titleEn: 'Case Detail',
    },
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('@/views/portal/AboutView.vue'),
    meta: {
      title: '关于创高',
      titleEn: 'About Us',
    },
  },
  {
    path: '/news',
    name: 'News',
    component: () => import('@/views/portal/NewsView.vue'),
    meta: {
      title: '新闻资讯',
      titleEn: 'News',
    },
  },
  {
    path: '/news/:id',
    name: 'NewsDetail',
    component: () => import('@/views/portal/NewsDetailView.vue'),
    meta: {
      title: '新闻详情',
      titleEn: 'News Detail',
    },
  },
  {
    path: '/contact',
    name: 'Contact',
    component: () => import('@/views/portal/ContactView.vue'),
    meta: {
      title: '联系我们',
      titleEn: 'Contact Us',
    },
  },
]

export default portalRoutes

5.2 router/admin.ts后台路由

import type { RouteRecordRaw } from 'vue-router'

const adminRoutes: RouteRecordRaw[] = [
  {
    path: '/admin/login',
    name: 'AdminLogin',
    component: () => import('@/views/admin/LoginView.vue'),
    meta: { public: true },
  },
  {
    path: '/admin',
    name: 'AdminLayout',
    component: () => import('@/views/admin/Layout.vue'),
    redirect: '/admin/dashboard',
    children: [
      {
        path: 'dashboard',
        name: 'AdminDashboard',
        component: () => import('@/views/admin/DashboardView.vue'),
      },
      {
        path: 'company/info',
        name: 'CompanyInfo',
        component: () => import('@/views/admin/company/InfoManage.vue'),
      },
      {
        path: 'content/carousel',
        name: 'CarouselManage',
        component: () => import('@/views/admin/content/CarouselManage.vue'),
      },
      {
        path: 'content/blocks',
        name: 'PageBlockManage',
        component: () => import('@/views/admin/content/PageBlockManage.vue'),
      },
      {
        path: 'content/media',
        name: 'MediaLibrary',
        component: () => import('@/views/admin/content/MediaLibrary.vue'),
      },
      {
        path: 'product/category',
        name: 'ProductCategory',
        component: () => import('@/views/admin/product/CategoryManage.vue'),
      },
      {
        path: 'product/list',
        name: 'ProductList',
        component: () => import('@/views/admin/product/ProductManage.vue'),
      },
      {
        path: 'case/category',
        name: 'CaseCategory',
        component: () => import('@/views/admin/case/CaseCategoryManage.vue'),
      },
      {
        path: 'case/list',
        name: 'CaseList',
        component: () => import('@/views/admin/case/CaseManage.vue'),
      },
      {
        path: 'news/category',
        name: 'NewsCategory',
        component: () => import('@/views/admin/news/NewsCategoryManage.vue'),
      },
      {
        path: 'news/list',
        name: 'NewsList',
        component: () => import('@/views/admin/news/NewsManage.vue'),
      },
      {
        path: 'message/list',
        name: 'MessageList',
        component: () => import('@/views/admin/message/MessageManage.vue'),
      },
      {
        path: 'system/users',
        name: 'UserManage',
        component: () => import('@/views/admin/system/UserManage.vue'),
      },
    ],
  },
]

export default adminRoutes

六、核心组件设计

6.1 AppHeader.vue顶部导航

<template>
  <header :class="['app-header', { 'is-scrolled': isScrolled, 'is-mobile': isMobile }]">
    <div class="header-container">
      <!-- Logo -->
      <router-link to="/" class="logo">
        <img :src="companyInfo.logo" :alt="companyInfo.brandName">
        <span class="brand-name">{{ companyInfo.brandName }}</span>
      </router-link>
      
      <!-- 桌面导航 -->
      <nav v-if="!isMobile" class="main-nav">
        <router-link
          v-for="item in navItems"
          :key="item.path"
          :to="item.path"
          :class="['nav-item', { active: isActive(item.path) }]"
        >
          {{ $t(item.labelKey) }}
        </router-link>
      </nav>
      
      <!-- 右侧工具 -->
      <div class="header-tools">
        <!-- 语言切换 -->
        <LangSwitch />
        
        <!-- 热线 -->
        <a v-if="!isMobile" :href="`tel:${companyInfo.contactPhone}`" class="hotline">
          <el-icon><Phone /></el-icon>
          <span>{{ companyInfo.contactPhone }}</span>
        </a>
        
        <!-- 移动端菜单按钮 -->
        <button v-if="isMobile" class="menu-toggle" @click="toggleMobileMenu">
          <el-icon><Menu /></el-icon>
        </button>
      </div>
    </div>
    
    <!-- 移动端菜单 -->
    <MobileMenu v-model="mobileMenuVisible" :nav-items="navItems" />
  </header>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue'
import { useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useScroll } from '@/composables/useScroll'
import { useDevice } from '@/composables/useDevice'
import LangSwitch from '@/components/common/LangSwitch.vue'
import MobileMenu from '@/components/layout/MobileMenu.vue'

const { t } = useI18n()
const route = useRoute()
const { isScrolled } = useScroll()
const { isMobile } = useDevice()

const mobileMenuVisible = ref(false)

const navItems = [
  { path: '/', labelKey: 'common.home' },
  { path: '/products', labelKey: 'common.products' },
  { path: '/cases', labelKey: 'common.cases' },
  { path: '/about', labelKey: 'common.about' },
  { path: '/contact', labelKey: 'common.contact' },
]

const isActive = (path: string) => {
  if (path === '/') {
    return route.path === '/'
  }
  return route.path.startsWith(path)
}

const toggleMobileMenu = () => {
  mobileMenuVisible.value = !mobileMenuVisible.value
}
</script>

<style scoped lang="scss">
.app-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1000;
  height: 80px;
  background: transparent;
  transition: all 0.3s ease;
  
  &.is-scrolled {
    background: rgba(255, 255, 255, 0.95);
    backdrop-filter: blur(10px);
    box-shadow: 0 2px 20px rgba(0, 0, 0, 0.05);
    height: 64px;
  }
  
  // 深色导航样式(首页首屏)
  &:not(.is-scrolled) {
    .nav-item {
      color: #fff;
    }
    .brand-name {
      color: #fff;
    }
  }
}

.header-container {
  max-width: 1400px;
  margin: 0 auto;
  padding: 0 24px;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.logo {
  display: flex;
  align-items: center;
  gap: 12px;
  text-decoration: none;
  
  img {
    height: 40px;
    width: auto;
  }
  
  .brand-name {
    font-size: 20px;
    font-weight: 600;
    color: var(--text-primary);
  }
}

.main-nav {
  display: flex;
  gap: 40px;
  
  .nav-item {
    font-size: 16px;
    font-weight: 500;
    color: var(--text-primary);
    text-decoration: none;
    transition: color 0.3s;
    position: relative;
    
    &::after {
      content: '';
      position: absolute;
      bottom: -4px;
      left: 0;
      width: 0;
      height: 2px;
      background: var(--primary-color);
      transition: width 0.3s;
    }
    
    &:hover,
    &.active {
      color: var(--primary-color);
      
      &::after {
        width: 100%;
      }
    }
  }
}

.header-tools {
  display: flex;
  align-items: center;
  gap: 24px;
}

.hotline {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--text-primary);
  text-decoration: none;
  font-size: 14px;
  font-weight: 500;
  
  .el-icon {
    font-size: 16px;
    color: var(--primary-color);
  }
}
</style>

七、样式系统设计

7.1 variables.scssCSS变量

:root {
  // 品牌色 - 深色底+金属质感
  --primary-color: #C9A962;        // 金色(金属质感)
  --primary-light: #E8D5A3;        // 浅金色
  --primary-dark: #9A7B3D;         // 深金色
  
  // 背景色
  --bg-dark: #1A1A1A;              // 深色背景
  --bg-dark-secondary: #242424;    // 次级深色
  --bg-light: #FFFFFF;             // 浅色背景
  --bg-gray: #F5F5F5;              // 灰色背景
  
  // 文字色
  --text-primary: #1A1A1A;         // 主文字(深色)
  --text-secondary: #666666;         // 次级文字
  --text-light: #999999;           // 辅助文字
  --text-white: #FFFFFF;           // 白色文字
  
  // 边框
  --border-color: #E8E8E8;
  --border-dark: #333333;
  
  // 阴影
  --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.08);
  --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.12);
  --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.16);
  
  // 间距
  --spacing-xs: 4px;
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 24px;
  --spacing-xl: 32px;
  --spacing-xxl: 48px;
  
  // 圆角
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 16px;
  
  // 过渡
  --transition-fast: 0.2s ease;
  --transition-normal: 0.3s ease;
  --transition-slow: 0.5s ease;
  
  // 布局
  --header-height: 80px;
  --header-height-mobile: 64px;
  --container-max-width: 1400px;
  --container-padding: 24px;
}

// 响应式断点
$breakpoints: (
  'xs': 576px,
  'sm': 768px,
  'md': 992px,
  'lg': 1200px,
  'xl': 1400px,
);

@mixin respond-to($breakpoint) {
  $value: map-get($breakpoints, $breakpoint);
  @media (max-width: $value) {
    @content;
  }
}

八、响应式断点策略

断点 宽度 布局调整
xl ≥1400px 最大容器宽度,完整导航
lg 1200-1399px 略窄容器
md 992-1199px 平板横屏,简化导航
sm 768-991px 平板竖屏,移动菜单
xs <768px 手机,单列布局

九、性能优化策略

9.1 图片处理

  • 使用 loading="lazy" 懒加载
  • 多尺寸图片pc端 / mobile端分离
  • WebP 格式优先JPEG 降级
  • 响应式图片 srcset

9.2 代码分割

  • 路由级别懒加载
  • 组件异步加载
  • vendors chunk 分离

9.3 缓存策略

  • 静态资源长期缓存1年
  • API 响应 Cache-Control
  • 图片 CDN 加速

十、开发命令

# 安装依赖
npm install

# 开发环境
npm run dev

# 生产构建
npm run build

# 预览生产构建
npm run preview

# 类型检查
npm run type-check

# 代码检查
npm run lint