找回密码
 立即注册
首页 业界区 业界 记一次开发

记一次开发

奄蜊 昨天 21:19
记一次全栈开发

目录


  • 项目概述
  • 技术栈选择
  • 前端开发
  • 后端开发
  • 性能优化
  • 部署流程
  • 常见问题与解决方案
  • 维护与更新
概述

企业官网是公司的数字门面,不仅展示公司形象,还承载着品牌传播、产品展示、客户沟通等多种功能,我身为一名全栈开发又是公司的董事长必须自己开发
现在让我们开始吧,程序代码全部手敲耗时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-文件上传前端开发

项目结构
  1. jishun-website/
  2. ├── public/<head>
  3.   
  4.   <link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
  5.   <link rel="preload" href="/assets/hero-image.webp" as="image">
  6.   
  7.   
  8.   <link rel="preconnect" href="https://api.example.com">
  9.   <link rel="dns-prefetch" href="https://api.example.com">
  10. </head>   # 静态资源
  11. ├── src/
  12. │   ├── assets/             # 资源文件
  13. │   ├── components/         # 组件
  14. │   │   ├── common/         # 通用组件
  15. │   │   └── business/       # 业务组件
  16. │   ├── directives/         # 自定义指令
  17. │   ├── router/             # 路由配置
  18. │   ├── services/           # API服务
  19. │   ├── stores/             # 状态管理
  20. │   ├── views/<head>
  21.   
  22.   <link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
  23.   <link rel="preload" href="/assets/hero-image.webp" as="image">
  24.   
  25.   
  26.   <link rel="preconnect" href="https://api.example.com">
  27.   <link rel="dns-prefetch" href="https://api.example.com">
  28. </head># 页面视图
  29. │   ├── App.vue             # 根组件
  30. │   └── main.ts             # 入口文件
  31. ├── .env<head>
  32.   
  33.   <link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
  34.   <link rel="preload" href="/assets/hero-image.webp" as="image">
  35.   
  36.   
  37.   <link rel="preconnect" href="https://api.example.com">
  38.   <link rel="dns-prefetch" href="https://api.example.com">
  39. </head>      # 环境变量
  40. ├── index.html<head>
  41.   
  42.   <link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
  43.   <link rel="preload" href="/assets/hero-image.webp" as="image">
  44.   
  45.   
  46.   <link rel="preconnect" href="https://api.example.com">
  47.   <link rel="dns-prefetch" href="https://api.example.com">
  48. </head># HTML模板
  49. ├── package.json            # 项目依赖
  50. ├── tsconfig.json           # TypeScript配置
  51. └── vite.config.ts          # Vite配置
复制代码
关键实现

响应式设计

使用媒体查询和弹性布局实现全设备适配:
  1. // 断点变量
  2. $breakpoints: (
  3.   'sm': 576px,
  4.   'md': 768px,
  5.   'lg': 992px,
  6.   'xl': 1200px,
  7.   'xxl': 1400px
  8. );
  9. // 响应式混合宏
  10. @mixin respond-to($breakpoint) {
  11.   $value: map-get($breakpoints, $breakpoint);
  12.   
  13.   @if $value {
  14.     @media (min-width: $value) {
  15.       @content;
  16.     }
  17.   } @else {
  18.     @error "Unknown breakpoint: #{$breakpoint}";
  19.   }
  20. }
  21. .container {
  22.   width: 100%;
  23.   padding: 0 15px;
  24.   
  25.   @include respond-to('md') {
  26.     max-width: 720px;
  27.     margin: 0 auto;
  28.   }
  29.   
  30.   @include respond-to('lg') {
  31.     max-width: 960px;
  32.   }
  33.   
  34.   @include respond-to('xl') {
  35.     max-width: 1140px;
  36.   }
  37. }
复制代码
组件化开发

基于Vue 3组合式API实现高复用性组件:
  1. // src/components/common/ImageCarousel.vue
  2. <template>
  3.   
  4.    
  5.       <transition-group name="fade">
  6.         
  7.           <img :src="image" alt="Carousel image" />
  8.         
  9.       </transition-group>
  10.    
  11.    
  12.     <button  @click="prev">
  13.       ❮
  14.     </button>
  15.    
  16.     <button  @click="next">
  17.       ❯
  18.     </button>
  19.    
  20.    
  21.       <button
  22.         v-for="(_, index) in images"
  23.         :key="index"
  24.         
  25.         :
  26.         @click="currentIndex = index"
  27.       ></button>
  28.    
  29.   
  30. </template>
复制代码
状态管理

我使用Pinia进行状态管理:
  1. // src/stores/news.ts
  2. import { defineStore } from 'pinia';
  3. import { ref, computed } from 'vue';
  4. import type { NewsItem } from '@/types';
  5. import { fetchNewsList, fetchNewsDetail } from '@/services/api';
  6. export const useNewsStore = defineStore('news', () => {
  7.   const newsList = ref<NewsItem[]>([]);
  8.   const currentNews = ref<NewsItem | null>(null);
  9.   const loading = ref(false);
  10.   const error = ref<string | null>(null);
  11.   
  12.   const latestNews = computed(() => {
  13.     return [...newsList.value].sort((a, b) =>
  14.       new Date(b.publishDate).getTime() - new Date(a.publishDate).getTime()
  15.     ).slice(0, 5);
  16.   });
  17.   
  18.   async function getNewsList() {
  19.     loading.value = true;
  20.     error.value = null;
  21.    
  22.     try {
  23.       newsList.value = await fetchNewsList();
  24.     } catch (err) {
  25.       error.value = err instanceof Error ? err.message : '获取新闻列表失败';
  26.     } finally {
  27.       loading.value = false;
  28.     }
  29.   }
  30.   
  31.   async function getNewsDetail(id: string) {
  32.     loading.value = true;
  33.     error.value = null;
  34.    
  35.     try {
  36.       currentNews.value = await fetchNewsDetail(id);
  37.     } catch (err) {
  38.       error.value = err instanceof Error ? err.message : '获取新闻详情失败';
  39.     } finally {
  40.       loading.value = false;
  41.     }
  42.   }
  43.   
  44.   return {
  45.     newsList,
  46.     currentNews,
  47.     loading,
  48.     error,
  49.     latestNews,
  50.     getNewsList,
  51.     getNewsDetail
  52.   };
  53. });
复制代码
后端开发

项目结构
  1. jishun-website-backend/
  2. ├── src/
  3. │   ├── config/             # 配置文件
  4. │   ├── controllers/        # 控制器
  5. │   ├── middlewares/        # 中间件
  6. │   ├── models/             # 数据模型
  7. │   ├── routes/             # 路由定义
  8. │   ├── services/           # 业务逻辑
  9. │   ├── utils/<head>
  10.   
  11.   <link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
  12.   <link rel="preload" href="/assets/hero-image.webp" as="image">
  13.   
  14.   
  15.   <link rel="preconnect" href="https://api.example.com">
  16.   <link rel="dns-prefetch" href="https://api.example.com">
  17. </head># 工具函数
  18. │   ├── app.ts<head>
  19.   
  20.   <link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
  21.   <link rel="preload" href="/assets/hero-image.webp" as="image">
  22.   
  23.   
  24.   <link rel="preconnect" href="https://api.example.com">
  25.   <link rel="dns-prefetch" href="https://api.example.com">
  26. </head># 应用配置
  27. │   └── index.ts            # 入口文件
  28. ├── uploads/<head>
  29.   
  30.   <link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
  31.   <link rel="preload" href="/assets/hero-image.webp" as="image">
  32.   
  33.   
  34.   <link rel="preconnect" href="https://api.example.com">
  35.   <link rel="dns-prefetch" href="https://api.example.com">
  36. </head>  # 上传文件目录
  37. ├── .env<head>
  38.   
  39.   <link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
  40.   <link rel="preload" href="/assets/hero-image.webp" as="image">
  41.   
  42.   
  43.   <link rel="preconnect" href="https://api.example.com">
  44.   <link rel="dns-prefetch" href="https://api.example.com">
  45. </head>      # 环境变量
  46. ├── package.json            # 项目依赖
  47. └── tsconfig.json           # TypeScript配置
复制代码
关键实现

数据模型设计

使用Mongoose定义数据模型:
  1. // src/models/News.ts
  2. import mongoose, { Schema, Document } from 'mongoose';
  3. export interface INews extends Document {
  4.   title: string;
  5.   content: string;
  6.   summary: string;
  7.   coverImage: string;
  8.   publishDate: Date;
  9.   author: string;
  10.   tags: string[];
  11.   isPublished: boolean;
  12.   viewCount: number;
  13.   createdAt: Date;
  14.   updatedAt: Date;
  15. }
  16. const NewsSchema: Schema = new Schema({
  17.   title: { type: String, required: true, trim: true },
  18.   content: { type: String, required: true },
  19.   summary: { type: String, required: true, trim: true },
  20.   coverImage: { type: String, required: true },
  21.   publishDate: { type: Date, default: Date.now },
  22.   author: { type: String, required: true },
  23.   tags: [{ type: String, trim: true }],
  24.   isPublished: { type: Boolean, default: false },
  25.   viewCount: { type: Number, default: 0 }
  26. }, {
  27.   timestamps: true
  28. });
  29. // 添加全文搜索索引
  30. NewsSchema.index({
  31.   title: 'text',
  32.   content: 'text',
  33.   summary: 'text',
  34.   tags: 'text'
  35. });
  36. export default mongoose.model<INews>('News', NewsSchema);
复制代码
API路由设计

RESTful API设计:
  1. // src/routes/news.ts
  2. import express from 'express';
  3. import {
  4.   getAllNews,
  5.   getNewsById,
  6.   createNews,
  7.   updateNews,
  8.   deleteNews,
  9.   searchNews
  10. } from '../controllers/newsController';
  11. import { authenticate, authorize } from '../middlewares/auth';
  12. import { validateNewsInput } from '../middlewares/validation';
  13. const router = express.Router();
  14. // 公开路由
  15. router.get('/', getAllNews);
  16. router.get('/search', searchNews);
  17. router.get('/:id', getNewsById);
  18. // 需要认证的路由
  19. router.post('/', authenticate, authorize(['admin', 'editor']), validateNewsInput, createNews);
  20. router.put('/:id', authenticate, authorize(['admin', 'editor']), validateNewsInput, updateNews);
  21. router.delete('/:id', authenticate, authorize(['admin']), deleteNews);
  22. export default router;
复制代码
身份验证中间件

JWT认证实现:
  1. // src/middlewares/auth.ts
  2. import { Request, Response, NextFunction } from 'express';
  3. import jwt from 'jsonwebtoken';
  4. import User from '../models/User';
  5. interface DecodedToken {
  6.   id: string;
  7.   role: string;
  8. }
  9. declare global {
  10.   namespace Express {
  11.     interface Request {
  12.       user?: any;
  13.     }
  14.   }
  15. }
  16. export const authenticate = async (req: Request, res: Response, next: NextFunction) => {
  17.   try {
  18.     const authHeader = req.headers.authorization;
  19.    
  20.     if (!authHeader || !authHeader.startsWith('Bearer ')) {
  21.       return res.status(401).json({ message: '未提供认证令牌' });
  22.     }
  23.    
  24.     const token = authHeader.split(' ')[1];
  25.     const decoded = jwt.verify(token, process.env.JWT_SECRET as string) as DecodedToken;
  26.    
  27.     const user = await User.findById(decoded.id).select('-password');
  28.    
  29.     if (!user) {
  30.       return res.status(401).json({ message: '用户不存在' });
  31.     }
  32.    
  33.     req.user = user;
  34.     next();
  35.   } catch (error) {
  36.     return res.status(401).json({ message: '无效的认证令牌' });
  37.   }
  38. };
  39. export const authorize = (roles: string[]) => {
  40.   return (req: Request, res: Response, next: NextFunction) => {
  41.     if (!req.user) {
  42.       return res.status(401).json({ message: '未认证的用户' });
  43.     }
  44.    
  45.     if (!roles.includes(req.user.role)) {
  46.       return res.status(403).json({ message: '没有权限执行此操作' });
  47.     }
  48.    
  49.     next();
  50.   };
  51. };
复制代码
性能优化

前端性能优化


  • 代码分割与懒加载
  1. // src/router/index.ts
  2. import { createRouter, createWebHistory } from 'vue-router';
  3. const routes = [
  4.   {
  5.     path: '/',
  6.     component: () => import('../views/Home.vue')
  7.   },
  8.   {
  9.     path: '/about',
  10.     component: () => import('../views/About.vue')
  11.   },
  12.   {
  13.     path: '/news',
  14.     component: () => import('../views/News.vue')
  15.   },
  16.   {
  17.     path: '/news/:id',
  18.     component: () => import('../views/NewsDetail.vue')
  19.   },
  20. ];
  21. const router = createRouter({
  22.   history: createWebHistory(),
  23.   routes,
  24.   scrollBehavior(to, from, savedPosition) {
  25.     if (savedPosition) {
  26.       return savedPosition;
  27.     } else {
  28.       return { top: 0 };
  29.     }
  30.   }
  31. });
  32. export default router;
复制代码

  • 图片优化
  1. // src/directives/lazyload.ts
  2. import { DirectiveBinding } from 'vue';
  3. export default {
  4.   mounted(el: HTMLImageElement, binding: DirectiveBinding) {
  5.     const observer = new IntersectionObserver((entries) => {
  6.       entries.forEach(entry => {
  7.         if (entry.isIntersecting) {
  8.           el.src = binding.value;
  9.           observer.unobserve(el);
  10.         }
  11.       });
  12.     });
  13.    
  14.     observer.observe(el);
  15.   }
  16. };
  17. // 使用方式
  18. // <img v-lazy="'/path/to/image.jpg'" alt="Lazy loaded image">
复制代码

  • 资源预加载
  1. <head>
  2.   
  3.   <link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
  4.   <link rel="preload" href="/assets/hero-image.webp" as="image">
  5.   
  6.   
  7.   <link rel="preconnect" href="https://api.example.com">
  8.   <link rel="dns-prefetch" href="https://api.example.com">
  9. </head>
复制代码
后端性能优化


  • 数据库查询优化
  1. // 添加适当的索引
  2. NewsSchema.index({ publishDate: -1 });
  3. NewsSchema.index({ tags: 1 });
  4. // 使用投影只返回需要的字段
  5. const newsList = await News.find({ isPublished: true })
  6.   .select('title summary coverImage publishDate author')
  7.   .sort({ publishDate: -1 })
  8.   .limit(10);
复制代码

  • API响应缓存
  1. // src/middlewares/cache.ts
  2. import { Request, Response, NextFunction } from 'express';
  3. import NodeCache from 'node-cache';
  4. const cache = new NodeCache({ stdTTL: 60 }); // 默认缓存60秒
  5. export const cacheMiddleware = (duration: number = 60) => {
  6.   return (req: Request, res: Response, next: NextFunction) => {
  7.     // 只缓存GET请求
  8.     if (req.method !== 'GET') {
  9.       return next();
  10.     }
  11.    
  12.     const key = `__express__${req.originalUrl || req.url}`;
  13.     const cachedBody = cache.get(key);
  14.    
  15.     if (cachedBody) {
  16.       res.send(cachedBody);
  17.       return;
  18.     }
  19.    
  20.     const originalSend = res.send;
  21.    
  22.     res.send = function(body): Response {
  23.       cache.set(key, body, duration);
  24.       return originalSend.call(this, body);
  25.     };
  26.    
  27.     next();
  28.   };
  29. };
  30. // 使用方式
  31. // app.use('/api/news', cacheMiddleware(300), newsRoutes);
复制代码
部署流程

宝塔面板部署

1. 安装宝塔面板
  1. # CentOS系统
  2. yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh
  3. # Ubuntu/Debian系统
  4. wget -O install.sh http://download.bt.cn/install/install-ubuntu_6.0.sh && sudo bash install.sh
复制代码
2. 安装必要软件

通过宝塔面板安装:

  • Nginx 1.20
  • Node.js 16.x
  • MongoDB 4.4
  • PM2管理器
3. 前端部署
  1. # 克隆代码库
  2. git clone https://github.com/your-repo/jishun-website.git /www/wwwroot/jishunkeji.cn/jishun-website
  3. # 安装依赖并构建
  4. cd /www/wwwroot/jishunkeji.cn/jishun-website
  5. npm install
  6. npm run build
复制代码
4. 后端部署
  1. # 克隆代码库
  2. git clone https://github.com/your-repo/jishun-website-backend.git /www/wwwroot/jishunkeji.cn/jishun-website-backend
  3. # 安装依赖并构建
  4. cd /www/wwwroot/jishunkeji.cn/jishun-website-backend
  5. npm install
  6. npm run build
  7. # 创建PM2配置文件
  8. cat > ecosystem.config.js << 'EOL'
  9. module.exports = {
  10.   apps: [{
  11.     name: "jishun-backend",
  12.     script: "./dist/index.js",
  13.     instances: 2,
  14.     exec_mode: "cluster",
  15.     env: {
  16.       NODE_ENV: "production",
  17.       PORT: 5001
  18.     },
  19.     max_memory_restart: "300M"
  20.   }]
  21. }
  22. EOL
  23. # 启动服务
  24. pm2 start ecosystem.config.js
复制代码
本文档详细记录了企业网站的开发与部署流程,从技术选型到最终上线。通过合理的架构设计、性能优化和部署配置,我成功构建了一个高性能、易维护的企业官网系统,希望能给各位开发者帮助吧
那么下面请欣赏我的报错
2.png

3.png

4.png

5.png

6.png

感谢浏览,能否给我一个赞和评论呢让我们共同进步


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册