资源下载地址:https://download.csdn.net/download/sheziqiong/85602497
资源下载地址:https://download.csdn.net/download/sheziqiong/85602497

1.项目简介

  • admin 后台界面
  • server 整个项目的服务端
  • web 客户端界面

1.1运行要求

  • 很痛苦之前上传到git上面管理,没有贡献值!!!因为邮箱问题很痛苦2019:6-9~6-18
  • 请先安装 nodejs 10+ ,mongodb server,nodemon,vue cli 3

1.2后台前端

  • 技术栈:Vue.js
  • Vue.cli Vue脚手架,Vue的构建工具
  • router 路由管理器,对路由进行管理
  • ElementUI 组件进行开发
  • axios 进行异步操作管理
  • vue2-editor 作富文本编辑器
  • dayjs 对时间戳进行格式化

1.3服务端

  • 技术栈:node.js,MongoDB
  • Express框架 处理数据接口
  • mongoose 建立数据模型
  • Express static中间件对静态文件托管
  • cors 作跨域访问
  • bcrypt 用于对密码进行加密处理
  • http-assert 用于错误处理
  • inflection 用于对传入路径字符串进行类名的转换
  • multer Express中间件实现前端传入文件进行保存
  • JSON Web Token 对登录状态码的生成以及验证
  • multer-aliyun-oss 阿里云对象存储OSS上传图片

1.4前台前端

  • 技术栈:Vue.js
  • Vue.cli Vue脚手架,Vue的构建工具
  • SCSS 工具类样式对css进行开发
  • router 路由管理器,对路由进行管理
  • CategoryCard BookCard 自封装分类与图书卡片组件
  • Vue-Awesome-Swiper 组件用于首页轮播图,书籍推荐界面,分类卡片应用 Github:[https://github.com/surmon-china/vue-awesome-swiper]
  • axios 进行异步请求操作
  • ElementUI 组件应用在label,消息提示,登录卡片,表格
  • dayjs 对时间戳进行格式化
    http://www.biyezuopin.vip

2.项目预览

2.1前台

2.1.1前台-首页

2.1.2前台-读者服务

2.1.3前台-新闻公告
新闻公告列表 新闻公告详情
2.1.4前台-馆内图书
图书列表 图书详情
2.1.5前台-我的图书馆
读者登陆页 读者详情页
读者修改资料 读者修改密码
2.1.6前台-馆内搜索

2.2后台

2.2.1后台-图书分类管理
图书分类列表 图书分类添加
2.2.2后台-图书管理
图书列表 图书入库
图书新增 图书修改
2.2.3后台-读者管理
读者列表 读者添加
读者借书 读者还书
2.2.4后台-广告管理
广告列表 广告添加
修改广告
2.2.5后台-文章分类管理
文章分类列表 文章分类添加
2.2.6后台-新闻管理
文章列表 文章添加
修改文章
2.2.7后台-服务管理
服务列表 服务添加
2.2.8后台-管理员管理
管理员列表 管理员添加

3.系统实现

3.1路由配置

module.exports = app => {
  const express = require('express')
  const jwt = require('jsonwebtoken')
  const assert = require('http-assert')
  const AdminUser = require('../../models/admin/AdminUser')
  const ArticleCategory = require('../../models/admin/ArticleCategory')
  const router = express.Router({
    mergeParams: true
  })

  //管理员修改密码接口
  /* router.post('/admin/api/checkpassword', async (req, res,) => {
    const reqPassword = req.body
    const user = await req.Model.findById(req.param.id).select('+password')
    const isValid = require('bcrypt').compareSync(reqPassword , user.password)
    assert(isValid, 422 , 原始密码错误) 
  }) */

  //新增数据接口
  router.post('/', async (req, res, ) => {
    const model = await req.Model.create(req.body)
    res.send(model)
  })

  //列表数据接口
  router.get('/', async (req, res) => {
    const queryOptions = {}
    if (req.Model.modelName === 'Book') {
      queryOptions.populate = 'category'
    } else if (req.Model.modelName === 'Article') {
      queryOptions.populate = 'categories'
    }
    const items = await req.Model.find().sort({ '_id': -1 }).setOptions(queryOptions).limit(20)
    res.send(items)
  })

  //获取详情接口
  router.get('/:id', async (req, res) => {
    const queryOptions = {}
    if (req.Model.modelName === 'Reader') {
      queryOptions.populate = 'lends'
    }
    const model = await req.Model.findById(req.params.id).setOptions(queryOptions).select('+message').select('+body')
    res.send(model)
  })
  //修改接口
  router.put('/:id', async (req, res) => {
    const model = await req.Model.findByIdAndUpdate(req.params.id, req.body)
    res.send(model)
  })
  //删除接口
  router.delete('/:id', async (req, res) => {
    await req.Model.findByIdAndDelete(req.params.id, req.body)
    res.send({
      success: true
    })
  })

  //TODO:模糊查询封装
  /*  router.get('/:bookname',async(req,rep)=>{
     const bookname = req.params.bookname
     
     const data = await Model.find({''})
     res.send(data)
   }) */




  /**
   * 登录验证处理函数中间件封装
   * auth
   * @param {*} req 
   * @param {*} res 
   * @param {*} next 
   */
  const authMiddleware = require('../../middleware/auth')



  //图书资源路由
  app.use('/admin/api/book/:resource', authMiddleware(), async (req, res, next) => {
    const modelName = require('inflection').classify(req.params.resource)
    req.Model = require(`../../models/book/${modelName}`)
    next()
  }, router)

  //用户资源路由
  app.use('/admin/api/reader/:resource', authMiddleware(), async (req, res, next) => {
    const modelName = require('inflection').classify(req.params.resource)
    req.Model = require(`../../models/reader/${modelName}`)
    next()
  }, router)

  //广告资源路由
  app.use('/admin/api/ad/:resource', authMiddleware(), async (req, res, next) => {
    const modelName = require('inflection').classify(req.params.resource)
    req.Model = require(`../../models/admin/${modelName}`)
    next()
  }, router)

  //新闻公告路由
  app.use('/admin/api/article/:resource', authMiddleware(), async (req, res, next) => {
    const modelName = require('inflection').classify(req.params.resource)
    req.Model = require(`../../models/admin/${modelName}`)
    next()
  }, router)

  //服务资源路由
  app.use('/admin/api/server/:resource', authMiddleware(), async (req, res, next) => {
    const modelName = require('inflection').classify(req.params.resource)
    req.Model = require(`../../models/admin/${modelName}`)
    next()
  }, router)

  //管理员资源路由
  app.use('/admin/api/admin/:resource', authMiddleware(), async (req, res, next) => {
    const modelName = require('inflection').classify(req.params.resource)
    req.Model = require(`../../models/admin/${modelName}`)
    next()
  }, router)

  //TODO:模糊查询
  /*  app.get('/admin/api/search/:bookname',async(req,res)=>{
     const bookname = req.params.bookname
     const Model = require('../../models/book/Book')
     const data = await Model.find({name:{$regex:`{bookname}`}})
     const queryOptions = {}
     if(Model==='Book'){
       queryOptions.populate = 'category'
     }
     const items =  await Model.find({name:{$regex:`{bookname}`}}).setOptions(queryOptions).limit(10)
     res.send(items)
     
   }) */

  /**
   * 上传路由
   */
  const multer = require('multer')
  const MAO = require('multer-aliyun-oss');
  const upload = multer({
    //dest: __dirname + '/../../uploads'
    storage: MAO({
      config: {
        region: 'oss-cn-shenzhen',
        accessKeyId: 'LTAIjEGFd2BoapVD',
        accessKeySecret: 'Msit8rbBwZYMH1X6hjSt7RJMS4DRHM',
        bucket: 'library-qbenben'
      }
    })
  })
  app.post('/admin/api/upload', authMiddleware(), upload.single('file'), async (req, res) => {
    const file = req.file
    //file.url = `http://localhost:3000/uploads/${file.filename}`
    res.send(file)
  })

  /**
   * 登录路由:
   * 1 根据用户名找用户(select('+password')因为我们已经默认取不出,要手动取出)
   * 2 校验密码
   * 3 返回token
   */
  app.post('/admin/api/login', async (req, res) => {
    const {
      username,
      password
    } = req.body
    const AdminUser = require('../../models/admin/AdminUser')
    const user = await AdminUser.findOne({
      username: username
    }).select('+password')
    /*原生处理,现在用http-assert处理 
      下载:npm i http-assert
      if (!user) {
      return res.status(422).send({
        message: '账户不存在'
      })
    } */
    assert(user, 422, '账户不存在')
    const isValid = require('bcrypt').compareSync(password, user.password)
    assert(isValid, 422, '密码错误')


    /**
     * 安装token: npm i jsonwebtoken
     */
    const token = jwt.sign({
      id: user.id,
    }, app.get('secret'))
    res.send({
      token
    })
  })



  /**
   * express 错误处理函数
   */
  app.use(async (err, req, res, next) => {
    res.status(err.statusCode || 500).send({
      message: err.message
    })
  })
}

3.2全局响应捕获错误,拦截器

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

//引入scss,npm i -D sass sass-loader
import './assets/scss/style.scss'
import './assets/icons/iconfont.css'

//Vue-Awesome-Swiper组件
import router from './router'
import VueAwesomeSwiper from 'vue-awesome-swiper'
import 'swiper/dist/css/swiper.css'
Vue.use(VueAwesomeSwiper, /* { default global options } */)

//自封装分类卡片组件
import CategoryCard from './components/CategoryCard.vue'
Vue.component('qb-list',CategoryCard)
//自封装图书卡片组件
import BookCard from './components/BookCard.vue'
Vue.component('qb-book',BookCard)

//axios
import axios from 'axios'
import './plugins/element.js'
const http = axios.create({
  baseURL: process.env.VUE_APP_API_URL || '/web/api'
  //baseURL: 'http://localhost:3000/web/api'
})


//全局响应捕获错误,拦截器
http.interceptors.response.use(res => {
  return res
}, err => {
  if (err.response.data.message) {
    Vue.prototype.$message({
      type: 'error',
      message: err.response.data.message
    })
  }
  return Promise.reject(err)
})


Vue.prototype.$http = http
new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

3.3登录

<template>
  <div class="login-container">
    <el-card header="管理员登录" class="login-card">
      <div class="tip">(用户名:qb 密码:qb)</div>
      <el-form @submit.native.prevent="saveform('model')" label-width="100px" :model="model" :rules="rules" ref="model">
        <el-form-item label="用户名:" prop="username">
          <el-col :span="16">
            <el-input v-model="model.username" clearable placeholder="请输入管理账号"></el-input>
          </el-col>
        </el-form-item>
        <el-form-item label="密码:" prop="password">
          <el-col :span="16">
            <el-input type="password" v-model="model.password" clearable placeholder="请输入密码"></el-input>
          </el-col>
        </el-form-item>
        <el-form-item style="margin:35px;">
          <el-button type="primary" native-type="submit">登录</el-button>
        </el-form-item>
      </el-form>
    </el-card>
  </div>

</template>
<script>
  export default {
    data() {
      return {
        model: {},
        rules: {
          username: [{
              required: true,
              message: '请输入用户名',
              trigger: 'blur'
            },
            {
              min: 1,
              max: 16,
              message: '用户名小于16位数',
              trigger: 'blur'
            },
          ],
          password: [{
              required: true,
              message: '请输入密码',
              trigger: 'blur'
            },
            {
              min: 1,
              max: 16,
              message: '密码小于16位数',
              trigger: 'blur'
            }
          ],

        }
      }
    },
    methods: {
      saveform(model) {
        this.$refs[model].validate(valid => {
          if (valid) {
            this.login();
          } else {
            console.log('error submit!!')
            return false
          }
        })
      },
      async login() {
        const res = await this.$http.post('/login', this.model)
        //sessionStorage 声明周期在关闭
        //localStorage.clear() 清空
        localStorage.token = res.data.token
        this.$router.push('/')
        this.$message({
          type: 'success',
          message: '登陆成功'
        })
      }
    }
  }
</script>



<style>
.tip{
  margin-bottom: 5px;
  text-align: center;
  font-size: 14px;
}
  .login-card {
    width: 25rem;
    margin: 10rem auto;
  }

  .el-card__header {
    text-align: center;
  }

  .el-form-item.is-required:not(.is-no-asterisk) .el-form-item__label-wrap>.el-form-item__label:before,
  .el-form-item.is-required:not(.is-no-asterisk)>.el-form-item__label:before {
    content: ''
  }
</style>

3.4业务代码

<template>
  <div class="about">
    <h1> 修改读者密码</h1>
    <el-form label-width="120px" @submit.native.prevent="saveform('model')" :model="model" :rules="rules" ref="model">
      <el-form-item label="读者新密码:" prop="newpassword">
        <el-col :span="4">
          <el-input type="password" v-model="model.newpassword" clearable autocomplete="off"></el-input>
        </el-col>
      </el-form-item>

      <el-form-item label="重复输入密码:" prop="password">
        <el-col :span="4">
          <el-input type="password" v-model="model.password" clearable autocomplete="off"></el-input>
        </el-col>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" native-type="submit">修改</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>



<script>
  export default {
    props: {
      id: {}
    },
    data() {
      const vaildatePass = (rule, value, callback) => {
        if (value === '') {
          callback(new Error('请输入密码'));
        } else {
          if (this.model.password !== '') {
            this.$refs.model.validateField('checkpassword');
          }
          callback();
        }
      };http://www.biyezuopin.vip
      const vaildatePass2 = (rule, value, callback) => {
        if (value === '') {
          callback(new Error('请再次输入密码'));
        } else if (value !== this.model.newpassword) {
          callback(new Error('两次输入不一致'));
        } else {
          callback();
        }
      };
      return {
        model: {
          user: {}
        },
        rules: {
          newpassword: [{
            validator: vaildatePass,
            trigger: 'blur'
          }, {
            required: true,
            message: '请输入新密码',
            trigger: 'blur'
          }],
          password: [{
            validator: vaildatePass2,
            trigger: 'blur'
          }, {
            required: true,
            message: '请重复确定密码',
            trigger: 'blur'
          }]
        }
      }
    },
    methods: {
      saveform(model) {
        this.$refs[model].validate(valid => {
          if (valid) {
            this.save();
          } else {
            this.$message({
              type: 'error',
              message: '请再审核一下'
            })
            return false
          }
        })
      },

      async save() {

        let res = this.$http.put(`/reader/readers/${this.id}`, this.model)
        res = await this.$http.get('reader/readers')
        this.$router.push('/reader/list')
        this.$message({
          type: 'success',
          message: '修改成功'
        })
      },


    }

  }
</script>

4.发布和部署

  • 生产环境编译 [done]
  • 购买服务器、购买域名 [done]
  • 域名解析 [done]
  • Nginx 安装和配置 [done]
  • MongoDB数据库安装和配置 [done]
  • 更新服务器MongoDB版本号 [done]
  • Node.js安装、配置淘宝镜像 [done]
  • 拉取代码,安装pm2并启动项目 [done]
  • 配置 Nginx 的反向代理 [done]
  • 备案成功 [done]
  • 配置SSL证书 [done]
  • 使用阿里云OSS对象存储上传图片 [done]
  • CDN加速 [done]
  • gzip压缩 [done]

资源下载地址:https://download.csdn.net/download/sheziqiong/85602497
资源下载地址:https://download.csdn.net/download/sheziqiong/85602497

Logo

前往低代码交流专区

更多推荐