本文章是学习B站全栈之巅系列课程的学习笔记,利用笔记来吸收升华学到的知识。
学习感受是,如果有一定的
nodejs+mongoDB+vue
的基础,加做过一两个相关的练手项目,再学习这个课程,你会发现原来搭建一个完整有基础功能的网站,是如此的简单,会有很大的收获。
参考源码:https://github.com/morningClock/herohoner
视频学习地址:点击这里
一样的套路,新增Edit
页面,List
页面,然后去路由新增该页面路由,Main.vue
组件新增路由选项。详细解释可以看注释。
ItemList.vue
物品列表
// 绑定当前行的icon属性
编辑
删除
ItemEdit.vue
{{id? '编辑': '新建'}}物品
// Element官方的上传模板
// 存储到服务端模型的icon属性。
保存
// ...
import ItemEdit from './views/ItemEdit.vue'
import ItemList from './views/ItemList.vue'
// ...
export default new Router({
routes: [
{
path: '/',
name: 'main',
component: Main,
children: [
// ...
{ path: '/items/create', component: ItemEdit },
{ path: '/items/edit/:id', component: ItemEdit, props: true },
{ path: '/items/list', component: ItemList },
]
}
]
})
上传图片接口,我们需要另外定义一个统一接口,如admin/api/upload
,将图片上传到服务端后存储到文件夹中,并开放一个地址,提供给前端访问。
这里使用multer
插件进行上传处理。只需要简单的配置,就可以将文件存储到本地,并将文件信息存储到一个属性中,可以返回到前端使用。
/**
* 上传图片处理
*/
const multer = require('multer')
const upload = multer({ dest: __dirname + '/../../uploads/' })
app.post('/admin/api/upload', upload.single('file'), (req,res,next) => {
// 返回的file属性中新增一个开放访问的url
const file = req.file
file.url = `http://localhost:3000/uploads/${file.filename}`
res.send(file)
})
当然我们需要在入口文件中开放文件访问权限。
// 开放静态资源
app.use('/uploads',express.static(__dirname+'/uploads'))
以物品管理页面为基础模板,新增页面。
HeroList.vue
以及HeroEdit
作为创建英雄,编辑英雄,新增英雄页面。并修改相关文本为英雄相关文本。
// ...
import HeroEdit from './views/HeroEdit.vue'
import HeroList from './views/HeroList.vue'
// ...
export default new Router({
routes: [
{
path: '/',
name: 'main',
component: Main,
children: [
// ...
{ path: '/heroes/create', component: HeroEdit },
{ path: '/heroes/edit/:id', component: HeroEdit, props: true },
{ path: '/heroes/list', component: HeroList },
]
}
]
})
全部替换成
heroes,以使用通用接口,请求到英雄的
hero`接口。做到这一步,你可以发送请求,发现数据正常了,服务端返回500,并提示找不到对应的模型。
以Item
的模型为范例,新增模型,并输出Model
为Hero
。
const mongoose = require('mongoose')
const schema = new mongoose.Schema({
name: { type: String },
icon: { type: String }
})
module.exports = mongoose.model('Hero', schema)
完成到上一步,我们可以对英雄进行 新增,编辑,查询,删除操作了,但是英雄数据中,只有一个图标和一个名称,显然我们英雄数据中,并不只是这些,所以我们要新增相关的数据。
const mongoose = require('mongoose')
const schema = new mongoose.Schema({
// 名称
name: { type: String },
// 头像--->不要忘了前端要把icon修改为avatar
avatar: { type: String },
// 称号
title: { type: String },
// 英雄分类(多个)-->数组可以多选
categories: [{ type: mongoose.SchemaTypes.ObjectId, ref:'Category' }],
// 评分:操作难度.技能伤害.攻击力分数.生存能力.
scores: {
difficulty: { type: Number },
skills: { type: Number },
attack: { type: Number },
survive: { type: Number }
},
// 技能(多个):每个技能的图标,名字,相关描述与应用技巧
skills: [{
icon: { type: String },
name: { type: String },
description: { type: String },
tips: { type: String }
}],
// 顺风出装(多个)
items1: [{ type: mongoose.SchemaTypes.ObjectId, ref:'Item' }],
// 逆风出装(多个)
items2: [{ type: mongoose.SchemaTypes.ObjectId, ref:'Item' }],
usageTips: { type: String },
battleTips: { type: String },
teamTips: { type: String },
// 搭档(多个)
partners: [{
hero: { type: mongoose.SchemaTypes.ObjectId, ref:'Hero' },
description: { type: String }
}]
})
module.exports = mongoose.model('Hero', schema)
根据数据类型,在HeroEdit
页面添加相关的数据项的操作.
直接使用Element添加一个表单项。
类似于之前写过的新增上级分类。
// 动态绑定获取的英雄分类
// multiple为多选选项
当然我们要循环出所有英雄分类,需要一个categories
数组,数组内容是查询数据库中英雄分类的内容,
export default {
// ...
data () {
return {
// ...
categories: [],
}
},
methods: {
// ...
async fetchCategories () {
// 获取分类列表
const res = await this.$http.get(`rest/categories`)
this.categories = res.data
},
}
}
分数是在scores中的子属性
// 星星组件
如果我们直接读取,scores的difficulty
,会报错,找不到difficulty
属性。原因是在初始化初次渲染结构阶段,model数据为data定义的默认值。它不包含score属性,更别说difficulty了,所以它会报错。
解决方法也很简单,判断下是否拥有该属性再绑定到model上,或者直接在data初始化这个属性。
data () {
return {
categories: [],
model: {
name: '',
avatar: '',
scores:{
difficulty: 0,
skills: 0,
attack: 0,
survive: 0,
}
}
}
},
和新增英雄分类一样,定义获取items
的所有物品,然后,将items渲染成下拉选项。
async fetchItems () {
// 获取分类列表
let res = await this.$http.get(`rest/items`)
this.items1 = res.data
res = await this.$http.get(`rest/items`)
this.items2 = res.data
},
vue
因为技能与英雄属性都是要在同一个页面新增绑定的,我们可以使用tab页来整理分类,使添加属性时内容不会太过拥挤混乱。
我们通过ElementTag与ElementCard来改造这个页面。
将原本的所有el-form-item
选项迁移入以下结构。
然后我们就可以开始写英雄技能栏的内容了。
因为技能可以有多个,我们可以用按钮,控制增加技能,每次点击增加一个技能卡片。
使用model.skills.push({})
增加卡片
使用model.skills.splice(index,1)
删除卡片
// 我们需要一个按钮用户增加技能
// 每次点击,为model.skills增加一个对象
添加技能
// 使用for循环遍历出model.skills的内容
// 这里是使用flex行进行布局,在中等屏幕中一行占2个卡片
// 循环出skills中的item与下标
// 然后使用item来绑定动态渲染的数据
技能{{index+1}}
删除技能
data () {
return {
categories: [],
model: {
name: '',
avatar: '',
scores:{
difficulty: 0,
skills: 0,
attack: 0,
survive: 0,
},
// +++增加++++
// 解决初始化界面时,操作undefined数据。
skills: []
},
items1: [],
items2: []
}
},
其中最重要的是on-success
中的处理,原本我们使用的是afterUpload()
,处理头像数据,将数据绑定在model.avatar
中,但是此处要关联的数据是model.skills[index].icon
即item.icon
,如果我们要把icon绑定在model.skills[index].icon
中,我们就要传递对应的index,但是默认Element中的on-success中不会携带额外的参数,只接受三个参数。
解决方法:就如上述代码,使用:
来绑定动态数据,在vue
字符串解析中可以使用遍历出来的item
,直接在结构上写处理方法1:
// 此处使用$set,新增属性添加响应式属性,触发视图更新。
res => $set(item, 'icon', res.url)
HeroEdit.vue
完整代码
{{id? '编辑': '新建'}}英雄
添加技能
技能{{index+1}}
删除技能
保存
新增文章页面的套路与其他一样,其中我们所需要的文章页面与category
差不多,所以直接复制一份。
1.新增复制的categoriesEdit
与categoriesList
页面。
2.更换接口与数据绑定的数据名称。
3.router新增页面路由。
4.main文件中增加路由入口。
5.后端新增Article Model
。
如果要在前端查询出对应分类的名称,就要在后端Article模型中,建立数据的关联。建立categories的schema,关联到Category模型中,然后查询接口新增特殊处理.
const mongoose = require('mongoose')
const schema = new mongoose.Schema({
title: { type: String },
categories: [{ type: mongoose.SchemaTypes.ObjectId, ref: 'Category'}],
content: { type: String }
})
module.exports = mongoose.model('Article', schema)
/**
* 获取分类列表
*/
router.get('/', async(req, res) => {
const queryOptions = {}
if(req.Model.modelName === 'Category') {
queryOptions.populate = 'parent'
}
// ++++++++++++++++++++新增++++++++++++++++++++++
// 通过categoies查询出对应的数据
else if(req.Model.modelName === 'Article') {
queryOptions.populate = 'categories'
}
const items = await req.Model.find().setOptions(queryOptions).limit(10)
res.send(items)
})
后端数据正常后,回到前端修改。
ArticleEdit.vue
修改
增加data的定义 categories:[]
,用于存储查询到的所有分类。
// 获取分类列表,存储到categories[]中
async fetchCategories () {
const res = await this.$http.get(`rest/categories`)
this.categories = res.data
}
视图层修改
ArticleList.vue
修改
我们需要获取到model.categoies
列表,遍历循环打印其中的内容。
其中要注意的是,在element的组件中,要自定义内容,必需使用slot插槽插入自定义内容。
数据可以根据slot-scope
定义的属性中,例如通过scope.row
来获取。
{{item.name}}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z4IO51je-1573700791806)(F:\Github\myrepositories\learning-notes\全栈之巅学习笔记\lesson4 管理页面\assets\image-20191113164330665.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6pCOE0RZ-1573700791824)(F:\Github\myrepositories\learning-notes\全栈之巅学习笔记\lesson4 管理页面\assets\image-20191113164350061.png)]
安装插件vue-quill-editor
富文本插件。
// 富文本编辑器
// 此处用全局安装,因为考虑到可能其他地方也会用到
import VueQuillEditor from 'vue-quill-editor'
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
Vue.use(VueQuillEditor)
在Vue
中使用
ElementUI
与Vue-quill-editor
会有样式冲突使得quill-editor的下拉选项对不齐,我们只要自己给quill-editor再写一个样式就可以了。
原因:
在Element中设置了div.el-form-item
的样式.el-form-item__content
,其中的line-height:40;
影响了quill的布局,quill自身并没有设置line-height
导致继承了element的行高,导致布局混乱了。
解决:
设置quill自身的行高,就可以解决了
.ql-editor {
line-height: normal;
}
在vue-quill-editor
中没有对自定义图片上传进行封装,我们只能使用官方文档中介绍的自定义方法进行重写上传图片,
思路:
1.根据文档配置自定义上传方法。
2.点击图片上传,触发一个隐藏的上传按钮。
3.上传成功,获取富文本光标,并在文本处插入一个图片的链接,最后将光标移到最后。
实现方法参考
lesdom - 富文本编辑自定义图片上传
官方文档 - toolbar
// 工具栏配置 https://quilljs.com/docs/modules/toolbar/
const toolbarOptions = [
['bold', 'italic', 'underline', 'strike'], // toggled buttons
['blockquote', 'code-block'],
[{'header': 1}, {'header': 2}], // custom button values
[{'list': 'ordered'}, {'list': 'bullet'}],
[{'script': 'sub'}, {'script': 'super'}], // superscript/subscript
[{'indent': '-1'}, {'indent': '+1'}], // outdent/indent
[{'direction': 'rtl'}], // text direction
[{'size': ['small', false, 'large', 'huge']}], // custom dropdown
[{'header': [1, 2, 3, 4, 5, 6, false]}],
[{'color': []}, {'background': []}], // dropdown with defaults from theme
[{'font': []}],
[{'align': []}],
['link', 'image', 'video'],
['clean'] // remove formatting button
]
export default {
props: {
id: {}
},
data () {
return {
model: {
content:''
},
categories: [],
editorOption:{
modules:{
toolbar: {
container: toolbarOptions,
handlers: {
// handlers object will be merged with default handlers object
'image': function(value) {
if (value) {
// 选择上传文件按钮
document.querySelector('#quillUploader .el-upload__input').click()
} else {
this.quill.format('image', false);
}
}
}
}
}
}
}
},
method:{
uploadQuillImage(res) {
console.log('上传成功')
// 获取富文本组件实例
let quill = this.$refs.quillEditor.quill
// 如果上传成功
if (res) {
// 获取光标所在位置
let length = quill.getSelection().index;
// 插入图片,res为服务器返回的图片链接地址
quill.insertEmbed(length, 'image', res.url)
// 调整光标到最后
quill.setSelection(length + 1)
} else {
// 提示信息,需引入Message
this.$message.error('图片插入失败')
}
}
}
}
一样的套路,无所畏惧,除了数据模型需要设计好外,其他问题,利用前面的知识可以完成。
Ad的数据模型
const mongoose = require('mongoose')
const schema = new mongoose.Schema({
name: { type: String },
// type为mongoose的唯一id标识mongoose.SchemaTypes.ObjectId
// ref为需要关联的model
items: [{
title: { type: String },
image: { type: String },
link: { type: String }
}]
})
module.exports = mongoose.model('Ad', schema)