24 KiB
24 KiB
创高家具官网 - 前端项目架构设计
一、技术栈选型
| 层级 | 技术 | 版本 | 用途 |
|---|---|---|---|
| 框架 | 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.scss(CSS变量)
: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