记一次全栈开发
目录
- 项目概述
- 技术栈选择
- 前端开发
- 后端开发
- 性能优化
- 部署流程
- 常见问题与解决方案
- 维护与更新
概述
企业官网是公司的数字门面,不仅展示公司形象,还承载着品牌传播、产品展示、客户沟通等多种功能,我身为一名全栈开发又是公司的董事长必须自己开发
现在让我们开始吧,程序代码全部手敲耗时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(' ')[1];
- 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);
复制代码- // 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.sh
复制代码 2. 安装必要软件
通过宝塔面板安装:
- 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 build
复制代码 4. 后端部署
- # 克隆代码库
- 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
复制代码 本文档详细记录了企业网站的开发与部署流程,从技术选型到最终上线。通过合理的架构设计、性能优化和部署配置,我成功构建了一个高性能、易维护的企业官网系统,希望能给各位开发者帮助吧
那么下面请欣赏我的报错
感谢浏览,能否给我一个赞和评论呢让我们共同进步
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |