资源下载地址: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('/', 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
})
})
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)
const multer = require('multer')
const MAO = require('multer-aliyun-oss');
const upload = multer({
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
res.send(file)
})
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')
assert(user, 422, '账户不存在')
const isValid = require('bcrypt').compareSync(password, user.password)
assert(isValid, 422, '密码错误')
const token = jwt.sign({
id: user.id,
}, app.get('secret'))
res.send({
token
})
})
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
import './assets/scss/style.scss'
import './assets/icons/iconfont.css'
import router from './router'
import VueAwesomeSwiper from 'vue-awesome-swiper'
import 'swiper/dist/css/swiper.css'
Vue.use(VueAwesomeSwiper, )
import CategoryCard from './components/CategoryCard.vue'
Vue.component('qb-list',CategoryCard)
import BookCard from './components/BookCard.vue'
Vue.component('qb-book',BookCard)
import axios from 'axios'
import './plugins/element.js'
const http = axios.create({
baseURL: process.env.VUE_APP_API_URL || '/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
所有评论(0)