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