Vue实战笔记03 - 简易网站后台的搭建之后台信息管理功能的实现

后台信息管理功能的实现

本文章是学习B站全栈之巅系列课程的学习笔记,利用笔记来吸收升华学到的知识。

学习感受是,如果有一定的nodejs+mongoDB+vue的基础,加做过一两个相关的练手项目,再学习这个课程,你会发现原来搭建一个完整有基础功能的网站,是如此的简单,会有很大的收获。
参考源码:https://github.com/morningClock/herohoner
视频学习地址:点击这里


物品管理


新增前端界面

一样的套路,新增Edit页面,List页面,然后去路由新增该页面路由,Main.vue组件新增路由选项。详细解释可以看注释。

修改ItemList.vue

物品列表

修改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'))

英雄管理


新增英雄管理相关页面

以物品管理页面为基础模板,新增页面。

1.新增HeroList.vue以及HeroEdit作为创建英雄,编辑英雄,新增英雄页面。

并修改相关文本为英雄相关文本。

2.在路由中添加英雄相关的三个页面

// ...
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 },
      ]
    }
    
  ]
})

3.将hero相关页面的items全部替换成heroes,以使用通用接口,请求到英雄的hero`接口。

做到这一步,你可以发送请求,发现数据正常了,服务端返回500,并提示找不到对应的模型。

4.新增数据库Model模型

Item的模型为范例,新增模型,并输出ModelHero

const mongoose = require('mongoose')

const schema = new mongoose.Schema({
  name: { type: String },
  icon: { type: String }
})

module.exports = mongoose.model('Hero', schema)

5.完善数据模型

完成到上一步,我们可以对英雄进行 新增,编辑,查询,删除操作了,但是英雄数据中,只有一个图标和一个名称,显然我们英雄数据中,并不只是这些,所以我们要新增相关的数据。

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)

6.完善前端

根据数据类型,在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].iconitem.icon,如果我们要把icon绑定在model.skills[index].icon中,我们就要传递对应的index,但是默认Element中的on-success中不会携带额外的参数,只接受三个参数。

解决方法:就如上述代码,使用:来绑定动态数据,在vue字符串解析中可以使用遍历出来的item,直接在结构上写处理方法1:

// 此处使用$set,新增属性添加响应式属性,触发视图更新。
res => $set(item, 'icon', res.url)

HeroEdit.vue完整代码








文章管理


新增文章相关页面

新增文章页面的套路与其他一样,其中我们所需要的文章页面与category差不多,所以直接复制一份。

套路

1.新增复制的categoriesEditcategoriesList页面。

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来获取。


    
    

完成的界面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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中使用


    
    

解决对齐混乱

ElementUIVue-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)

到这里我们完成了后端基础的功能管理界面了,接下来就是登陆系统的设计与实现

你可能感兴趣的:(实战项目,Vue)