记一次开发
记一次全栈开发目录
[*]项目概述
[*]技术栈选择
[*]前端开发
[*]后端开发
[*]性能优化
[*]部署流程
[*]常见问题与解决方案
[*]维护与更新
概述
企业官网是公司的数字门面,不仅展示公司形象,还承载着品牌传播、产品展示、客户沟通等多种功能,我身为一名全栈开发又是公司的董事长必须自己开发
现在让我们开始吧,程序代码全部手敲耗时20小时,希望大家可以给各位开发者带来帮助
核心功能需求
[*]公司简介与品牌展示
[*]产品与服务介绍
[*]成功案例展示
[*]新闻动态发布
[*]团队介绍
[*]联系方式
[*]后台内容管理系统
技术栈选择
前端技术栈
技术版本用途Vue.js3.x前端框架Vite4.x构建工具Vue Router4.x路由管理Pinia2.x状态管理Element Plus2.xUI组件库TypeScript4.x类型系统SCSS-CSS预处理器后端技术栈
技术版本用途Node.js16.x运行环境Express4.xWeb框架MongoDB4.4数据库Mongoose6.xODM工具JWT-身份认证Multer-文件上传前端开发
项目结构
jishun-website/
├── public/<head>
<link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/hero-image.webp" as="image">
<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://api.example.com">
</head> # 静态资源
├── src/
│ ├── assets/ # 资源文件
│ ├── components/ # 组件
│ │ ├── common/ # 通用组件
│ │ └── business/ # 业务组件
│ ├── directives/ # 自定义指令
│ ├── router/ # 路由配置
│ ├── services/ # API服务
│ ├── stores/ # 状态管理
│ ├── views/<head>
<link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/hero-image.webp" as="image">
<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://api.example.com">
</head># 页面视图
│ ├── App.vue # 根组件
│ └── main.ts # 入口文件
├── .env<head>
<link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/hero-image.webp" as="image">
<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://api.example.com">
</head> # 环境变量
├── index.html<head>
<link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/hero-image.webp" as="image">
<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://api.example.com">
</head># HTML模板
├── package.json # 项目依赖
├── tsconfig.json # TypeScript配置
└── vite.config.ts # Vite配置关键实现
响应式设计
使用媒体查询和弹性布局实现全设备适配:
// 断点变量
$breakpoints: (
'sm': 576px,
'md': 768px,
'lg': 992px,
'xl': 1200px,
'xxl': 1400px
);
// 响应式混合宏
@mixin respond-to($breakpoint) {
$value: map-get($breakpoints, $breakpoint);
@if $value {
@media (min-width: $value) {
@content;
}
} @else {
@error "Unknown breakpoint: #{$breakpoint}";
}
}
.container {
width: 100%;
padding: 0 15px;
@include respond-to('md') {
max-width: 720px;
margin: 0 auto;
}
@include respond-to('lg') {
max-width: 960px;
}
@include respond-to('xl') {
max-width: 1140px;
}
}组件化开发
基于Vue 3组合式API实现高复用性组件:
// src/components/common/ImageCarousel.vue
<template>
<transition-group name="fade">
<img :src="image" alt="Carousel image" />
</transition-group>
<button@click="prev">
❮
</button>
<button@click="next">
❯
</button>
<button
v-for="(_, index) in images"
:key="index"
:
@click="currentIndex = index"
></button>
</template>状态管理
我使用Pinia进行状态管理:
// src/stores/news.ts
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import type { NewsItem } from '@/types';
import { fetchNewsList, fetchNewsDetail } from '@/services/api';
export const useNewsStore = defineStore('news', () => {
const newsList = ref<NewsItem[]>([]);
const currentNews = ref<NewsItem | null>(null);
const loading = ref(false);
const error = ref<string | null>(null);
const latestNews = computed(() => {
return [...newsList.value].sort((a, b) =>
new Date(b.publishDate).getTime() - new Date(a.publishDate).getTime()
).slice(0, 5);
});
async function getNewsList() {
loading.value = true;
error.value = null;
try {
newsList.value = await fetchNewsList();
} catch (err) {
error.value = err instanceof Error ? err.message : '获取新闻列表失败';
} finally {
loading.value = false;
}
}
async function getNewsDetail(id: string) {
loading.value = true;
error.value = null;
try {
currentNews.value = await fetchNewsDetail(id);
} catch (err) {
error.value = err instanceof Error ? err.message : '获取新闻详情失败';
} finally {
loading.value = false;
}
}
return {
newsList,
currentNews,
loading,
error,
latestNews,
getNewsList,
getNewsDetail
};
});后端开发
项目结构
jishun-website-backend/
├── src/
│ ├── config/ # 配置文件
│ ├── controllers/ # 控制器
│ ├── middlewares/ # 中间件
│ ├── models/ # 数据模型
│ ├── routes/ # 路由定义
│ ├── services/ # 业务逻辑
│ ├── utils/<head>
<link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/hero-image.webp" as="image">
<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://api.example.com">
</head># 工具函数
│ ├── app.ts<head>
<link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/hero-image.webp" as="image">
<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://api.example.com">
</head># 应用配置
│ └── index.ts # 入口文件
├── uploads/<head>
<link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/hero-image.webp" as="image">
<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://api.example.com">
</head># 上传文件目录
├── .env<head>
<link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/hero-image.webp" as="image">
<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://api.example.com">
</head> # 环境变量
├── package.json # 项目依赖
└── tsconfig.json # TypeScript配置关键实现
数据模型设计
使用Mongoose定义数据模型:
// src/models/News.ts
import mongoose, { Schema, Document } from 'mongoose';
export interface INews extends Document {
title: string;
content: string;
summary: string;
coverImage: string;
publishDate: Date;
author: string;
tags: string[];
isPublished: boolean;
viewCount: number;
createdAt: Date;
updatedAt: Date;
}
const NewsSchema: Schema = new Schema({
title: { type: String, required: true, trim: true },
content: { type: String, required: true },
summary: { type: String, required: true, trim: true },
coverImage: { type: String, required: true },
publishDate: { type: Date, default: Date.now },
author: { type: String, required: true },
tags: [{ type: String, trim: true }],
isPublished: { type: Boolean, default: false },
viewCount: { type: Number, default: 0 }
}, {
timestamps: true
});
// 添加全文搜索索引
NewsSchema.index({
title: 'text',
content: 'text',
summary: 'text',
tags: 'text'
});
export default mongoose.model<INews>('News', NewsSchema);API路由设计
RESTful API设计:
// src/routes/news.ts
import express from 'express';
import {
getAllNews,
getNewsById,
createNews,
updateNews,
deleteNews,
searchNews
} from '../controllers/newsController';
import { authenticate, authorize } from '../middlewares/auth';
import { validateNewsInput } from '../middlewares/validation';
const router = express.Router();
// 公开路由
router.get('/', getAllNews);
router.get('/search', searchNews);
router.get('/:id', getNewsById);
// 需要认证的路由
router.post('/', authenticate, authorize(['admin', 'editor']), validateNewsInput, createNews);
router.put('/:id', authenticate, authorize(['admin', 'editor']), validateNewsInput, updateNews);
router.delete('/:id', authenticate, authorize(['admin']), deleteNews);
export default router;身份验证中间件
JWT认证实现:
// src/middlewares/auth.ts
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import User from '../models/User';
interface DecodedToken {
id: string;
role: string;
}
declare global {
namespace Express {
interface Request {
user?: any;
}
}
}
export const authenticate = async (req: Request, res: Response, next: NextFunction) => {
try {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ message: '未提供认证令牌' });
}
const token = authHeader.split(' ');
const decoded = jwt.verify(token, process.env.JWT_SECRET as string) as DecodedToken;
const user = await User.findById(decoded.id).select('-password');
if (!user) {
return res.status(401).json({ message: '用户不存在' });
}
req.user = user;
next();
} catch (error) {
return res.status(401).json({ message: '无效的认证令牌' });
}
};
export const authorize = (roles: string[]) => {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ message: '未认证的用户' });
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({ message: '没有权限执行此操作' });
}
next();
};
};性能优化
前端性能优化
[*]代码分割与懒加载
// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
const routes = [
{
path: '/',
component: () => import('../views/Home.vue')
},
{
path: '/about',
component: () => import('../views/About.vue')
},
{
path: '/news',
component: () => import('../views/News.vue')
},
{
path: '/news/:id',
component: () => import('../views/NewsDetail.vue')
},
];
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition;
} else {
return { top: 0 };
}
}
});
export default router;
[*]图片优化
// src/directives/lazyload.ts
import { DirectiveBinding } from 'vue';
export default {
mounted(el: HTMLImageElement, binding: DirectiveBinding) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
el.src = binding.value;
observer.unobserve(el);
}
});
});
observer.observe(el);
}
};
// 使用方式
// <img v-lazy="'/path/to/image.jpg'" alt="Lazy loaded image">
[*]资源预加载
<head>
<link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/hero-image.webp" as="image">
<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://api.example.com">
</head>后端性能优化
[*]数据库查询优化
// 添加适当的索引
NewsSchema.index({ publishDate: -1 });
NewsSchema.index({ tags: 1 });
// 使用投影只返回需要的字段
const newsList = await News.find({ isPublished: true })
.select('title summary coverImage publishDate author')
.sort({ publishDate: -1 })
.limit(10);
[*]API响应缓存
// src/middlewares/cache.ts
import { Request, Response, NextFunction } from 'express';
import NodeCache from 'node-cache';
const cache = new NodeCache({ stdTTL: 60 }); // 默认缓存60秒
export const cacheMiddleware = (duration: number = 60) => {
return (req: Request, res: Response, next: NextFunction) => {
// 只缓存GET请求
if (req.method !== 'GET') {
return next();
}
const key = `__express__${req.originalUrl || req.url}`;
const cachedBody = cache.get(key);
if (cachedBody) {
res.send(cachedBody);
return;
}
const originalSend = res.send;
res.send = function(body): Response {
cache.set(key, body, duration);
return originalSend.call(this, body);
};
next();
};
};
// 使用方式
// app.use('/api/news', cacheMiddleware(300), newsRoutes);部署流程
宝塔面板部署
1. 安装宝塔面板
# CentOS系统
yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh
# Ubuntu/Debian系统
wget -O install.sh http://download.bt.cn/install/install-ubuntu_6.0.sh && sudo bash install.sh2. 安装必要软件
通过宝塔面板安装:
[*]Nginx 1.20
[*]Node.js 16.x
[*]MongoDB 4.4
[*]PM2管理器
3. 前端部署
# 克隆代码库
git clone https://github.com/your-repo/jishun-website.git /www/wwwroot/jishunkeji.cn/jishun-website
# 安装依赖并构建
cd /www/wwwroot/jishunkeji.cn/jishun-website
npm install
npm run build4. 后端部署
# 克隆代码库
git clone https://github.com/your-repo/jishun-website-backend.git /www/wwwroot/jishunkeji.cn/jishun-website-backend
# 安装依赖并构建
cd /www/wwwroot/jishunkeji.cn/jishun-website-backend
npm install
npm run build
# 创建PM2配置文件
cat > ecosystem.config.js << 'EOL'
module.exports = {
apps: [{
name: "jishun-backend",
script: "./dist/index.js",
instances: 2,
exec_mode: "cluster",
env: {
NODE_ENV: "production",
PORT: 5001
},
max_memory_restart: "300M"
}]
}
EOL
# 启动服务
pm2 start ecosystem.config.js本文档详细记录了企业网站的开发与部署流程,从技术选型到最终上线。通过合理的架构设计、性能优化和部署配置,我成功构建了一个高性能、易维护的企业官网系统,希望能给各位开发者帮助吧
那么下面请欣赏我的报错
感谢浏览,能否给我一个赞和评论呢让我们共同进步
来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除
页:
[1]