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

960 lines
24 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 创高家具官网 - 前端项目架构设计
## 一、技术栈选型
| 层级 | 技术 | 版本 | 用途 |
|------|------|------|------|
| 框架 | 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
```typescript
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
```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
```typescript
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节选
```typescript
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前台路由
```typescript
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后台路由
```typescript
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顶部导航
```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变量
```scss
: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 加速
---
## 十、开发命令
```bash
# 安装依赖
npm install
# 开发环境
npm run dev
# 生产构建
npm run build
# 预览生产构建
npm run preview
# 类型检查
npm run type-check
# 代码检查
npm run lint
```