Vue实战笔记01 - 简易网站后台的搭建之分类功能的实现

6 篇文章 0 订阅
5 篇文章 0 订阅

简易网站后台的搭建 — 分类功能的实现

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

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

1.项目目录

完整的网站项目中,一般会拥有前台网站,后台管理网站,以及服务器端处理程序。

前台网站:提供给用户访问的网站。

后台管理网站:对网站数据进行可视化管理的网站。

服务端处理程序:处理网站请求。

所以我们在项目下,建立三个子目录为

  • /web

    使用vue搭建的前台网站。

  • /admin

    使用vue搭建的后台网站

  • /server

    提供接口处理。

根据开发的简易程度,进行开发顺序的确认。首先网站的前台项目是项目中最复杂的部分,因为一开始没有数据难以呈现效果,而且涉及到大量细节问题,所以要到最后开发。

我们在开发网站时需要拥有数据,那么我们就需要从后台管理系统,以及后端接口处理,进行着手开发。

2.快速搭建后台基础界面

项目中,使用了Element的UI库,没接触过UI库,其实也不影响使用,因为UI库是为了简化开发的,所以基本上可以拿来就用。而且后端是面向管理员的,所以对界面的细节要求不高。

Element安装有两种方法:

  • 使用vue add element

  • 使用npm install element-ui

    需要手动在main.js中引入Element。

    // 引入element-ui
    import ElementUI from 'element-ui'
    // 引入样式库
    import 'element-ui/lib/theme-chalk/index.css'
    // 挂载Element
    Vue.use(ElementUI)
    

    当然,ElementUI还提供了局部组件引用的功能,详细见Element 按需引入

我们还需要用到vue-router,进行组件路由。

我们直接使用vue add router,进行默认的配置。项目中会生成views目录,并且拥有三个组件,此时,我们只需要简单的配置。

  • 配置router.js

    import Vue from 'vue'
    import Router from 'vue-router'
    import Main from './views/Main.vue'
    
    Vue.use(Router)
    
    export default new Router({
      routes: [
        {
          path: '/',
          name: 'main',
          component: Main,
        }
        
      ]
    })
    
    
  • 配置App.vue

    <template>
      <div id="app">
        <!-- 应用路由 -->
        <router-view/>
      </div>
    </template>
    
    <style>
    body{
      margin: 0;
      padding: 0;
    }
    </style>
    
    
  • 主页面Main.vue

    简单的配置并应用Element的布局容器章节中的示例:实例

    其中需要修改的是,将组件的高度,设置为100vh,撑开容器的高度。

此时,就已经完成了最简单的后台界面了。接下来就是按照需求,进行对应的修改。

3.分类功能的实现

项目网站中,分类是最基础的功能,比如网站的新闻,活动。这些都是分类。当然分类中,还会有许多子分类。我们要实现一个分类的管理,那么我们要对分类进行增删改查操作,我们先从新增开始。

新增分类

现在我们的后台网站,只有一个主页面,并且显示的内容还是模板中的内容。我们先初始化后台的基础页面,保留一些我们需要展示的内容。

  • 后台管理页面的前端修改

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zvxm29HX-1573006263218)(F:\Github\myrepositories\learning-notes\全栈之巅学习笔记\lesson1\assets\image-20191104143432627.png)]

    我们需要这样的列表,那么我们把其他清空了,只保留一项菜单,新增选项分类列表新建分类

    1. 由于我们需要点击选项时,进行路由。使用element提供的router选项,就可以点击跳转到index属性所指向的组件了,可配置选项点击这里查看。

    2. <el-main>中,现在是写死的表格,我们需要点击选项切换,所以我们要抽离成两个组件(CategoryList.vue 与 CategoryEdit.vue),并配置子路由。

      CategoryList展示分类列表、CategoryEdit用于分类的新建与编辑。

      我们预设路由是/categories/list/categories/create

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Wb3jzmv-1573006263220)(F:\Github\myrepositories\learning-notes\全栈之巅学习笔记\lesson1\assets\image-20191104145518009.png)]

      Main.vuetempalte部分

      <template>
        <div id="main">
          <el-container style="height: 100vh; border: 1px solid #eee;">
            <el-aside width="200px" style="background-color: rgb(238, 241, 246)">
              <el-menu router :default-openeds="['1', '3']">
                <el-submenu index="1">
                  <template slot="title"><i class="el-icon-message"></i>内容管理</template>
                  <el-menu-item-group>
                    <template slot="title">分类</template>
                      <!-- 跳转到对应路由 -->
                    <el-menu-item index="/categories/list">分类列表</el-menu-item>
                    <el-menu-item index="/categories/create">新建分类</el-menu-item>
                  </el-menu-item-group>
                </el-submenu>
              </el-menu>
            </el-aside>
      
            <el-container>
              <el-header style="text-align: right; font-size: 12px">
                <el-dropdown>
                  <i class="el-icon-setting" style="margin-right: 15px"></i>
                  <el-dropdown-menu slot="dropdown">
                    <el-dropdown-item>查看</el-dropdown-item>
                    <el-dropdown-item>新增</el-dropdown-item>
                    <el-dropdown-item>删除</el-dropdown-item>
                  </el-dropdown-menu>
                </el-dropdown>
                <span>王小虎</span>
              </el-header>
      
              <el-main>
                  <!-- 路由到子路由 -->
                  <router-view></router-view>
              </el-main>
            </el-container>
          </el-container>
        </div>
      </template>
      

      CategoryList.vue 新建分类页面

      <template>
        <div class="about">
          <h1>新建分类</h1>
          <el-form label-width="120px" @submit.native.parent="save">
            <el-form-item label="名称">
              <el-input v-model="model.name"></el-input>
            </el-form-item>
            <el-form-item>
              <el-button type="primary" native-type="submit">保存</el-button>
            </el-form-item>
          </el-form>
        </div>
      </template>
      export default {
        data () {
          return {
            model: {}
          }
        },
        methods: {
          save () {
            // 保存新建的分类
          }
        }
      }
      

      router.js路由配置

      import Vue from 'vue'
      import Router from 'vue-router'
      import Main from './views/Main.vue'
      import CategoryEdit from './views/CategoryEdit.vue'
      
      Vue.use(Router)
      
      export default new Router({
        routes: [
          {
            path: '/',
            name: 'main',
            component: Main,
            children: [
              { path: '/categories/create', component: CategoryEdit },
            ]
          }
          
        ]
      })
      
  • 新建分类后端接口的实现

    后端接口的实现其实非常简单,此时我们进入server目录,进行后端接口的实现。

    此项目中我们使用了express、mongoose以及跨域处理插件cors

    npm install express mongoose cors
    

    开启监听端口服务index.js

    const express = require('express')
    
    const app = express()
    
    // 解决跨域问题
    app.use(require('cors')())
    
    app.listen(3000, () => {
      console.log('http://localhost:3000')
    })
    

    此时我们就可以run起来,看见提示语句。

    为了让层次更分明,我们需要将接口写入admin/api,方便管理。

    const express = require('express')
    const app = express()
    app.use(require('cors')())
    app.use(express.json())
    
    // 引入admin接口,传入express对象
    require('./routes/admin')(app)
    
    app.listen(3000, () => {
      console.log('http://localhost:3000')
    })
    

    然后在admin/api新建一个index.js`的接口处理文件,管理admin中相关的接口。

    module.exports = app => {
      const express = require('express')  
      const router = express.Router()
      // 接口
      router.post('/categories', async(req, res) => {
        res.send(‘請求成功’)
      })
    
      // 输出router对象,挂载在全局express上
      app.use('/admin/api', router)
    }
    

    此时我们可以配合前端界面,进行接口的测试。

    前端安装axios插件,并单独新建一个http.js进行请求方法的管理。

    import axios from 'axios'
    
    const http = axios.create({
      baseURL: 'http://localhost:3000/admin/api'
    })
    
    export default http
    

    main.js中引用该配置文件,并挂载到Vue对象上

    // 引入axios
    import http from './http'
    Vue.prototype.$http = http
    

    修改CategoryEdit.vue中的save方法,发起请求,测试是否成功

    async save () {
        // 保存新建的分类
        res = await this.$http.post('categories', this.model)
        // 输出`请求成功`
    	console.log(res.data)
    }
    

    此时我们的接口已经正常运作,我们需要配置我们的数据库,用作数据的存储。

    当然数据库,我们也是希望用单独的文件来管理,防止index.js中内容过多,不容易后期的管理。

    新建plugins文件夹,并创建db.js

    module.exports = app => {
      // 连接mongoDB
      const mongoose = require('mongoose')
      // herohoner,可以自定义
      mongoose.connect('mongodb://127.0.0.1:27017/herohoner', {
        useNewUrlParser: true
      })
    }
    

    然后再index.js入口文件中,引用配置

    // 引入db
    require('./plugins/db')(app)
    

    接入数据库的操作就完成,接下来就是将数据写入数据库中,mongoDB中,我们要写入数据,必需要对数据进行规范,写Schema约束,然后使用Schema创建model模型,最后再将数据写入model模型中。

    每一个表都有自己的Schema,所以我们可以单独管理各个Schema创建的model模型。

    新建文件夹models用于存放不同的模型,在下面创建我们要使用的model管理文件Category.js

    const mongoose = require('mongoose')
    // 创建约束
    const schema = new mongoose.Schema({
      name: { type: String }
    })
    // 输出具备约束的model模型
    module.exports = mongoose.model('Category', schema)
    

    万事俱备,只欠东风,我们的数据就可以写入数据库了。回到/routes/admin/api/index.js

    写入接口

      // 引入model模型
      const Category = require('../../models/Category')
      /**
       * 新建分类
       */
      router.post('/categories', async(req, res) => {
        // 创建该模型,并填入post中传递过来的数据。
        const model = await Category.create(req.body)
        // 将创建后的模型数据返回到前端
        res.send(model)
      })
    
    

    前端接受到后端的响应,进行对应的处理

    async save () {
        let res
        if (this.id) {
            // 编辑
            res = await this.$http.put(`categories/${this.id}`, this.model)
        } else {
            // 新建
            res = await this.$http.post('categories', this.model)
        }
        this.$router.push('/categories/list')
        this.$message({
            type: 'success',
            message: '保存成功'
        })
    },
    

查询分类列表

不要害怕,完成新增分类完成后,基本上已经完成了大部分的后端接口功能了,接下来操作都几乎差不多。

CategoryList.vue

我们照样从官网,搬一些基础的模板过来,查询时,我们只需要在页面初始化阶段,发送一个GET请求,请求查询接口就可以完成查询了。

<template>
  <div>
    <h1>分类列表</h1>
    <el-table :data="items">
      <el-table-column prop="_id" lable="ID" width="220"></el-table-column>
      <el-table-column prop="name" lable="分类名称"></el-table-column>
      <el-table-column
        fixed="right"
        label="操作"
        width="100">
        <template slot-scope="scope">
          <el-button type="text" size="small">编辑</el-button>
          <el-button type="text" size="small">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

<script>
export default {
  data () {
    return {
      items: []
    }
  },
  methods: {
    async fetch() {
      // 请求查询接口
      const res = await this.$http.get('categories')
      this.items = res.data
    }
  },
  created () {
    this.fetch()
  }
}
</script>

后端接口的修改非常简单,只需要在admin/api/index.js中新增接口处理

/**
* 获取分类列表
*/
router.get('/categories', async(req, res) => {
    // 这里限制了10条,后期可以使用limit做分页处理
    const items = await Category.find().limit(10)
    res.send(items)
})

查询也完成了,是不是十分简单,只需要熟悉一下mongoose中的数据操作方法就可以了。

编辑分类

编辑分类也完全是一样的套路,修改前端,请求接口,新增接口处理,编辑数据库中对应的数据。

当然这次不一样的是,我们需要知道我们在编辑哪个数据,这时候我们要使用到唯一标识_id

在前端请求编辑接口时,在url上添加查询出来的_id就可以了。非常简单。

其中scope.row代表了表格中,当前行的数据。

<el-button
   type="text"
   size="small"
   @click="$router.push(`/categories/edit/${scope.row._id}`)">
    编辑
</el-button>

当然编辑页我们也需要,但是编辑页和新建页可以复用,所以我们可以修改成如下

<template>
  <div class="about">
    <h1>{{id? '编辑': '新建'}}分类</h1>
    <el-form label-width="120px" @submit.native.parent="save">
      <el-form-item label="名称">
        <el-input v-model="model.name"></el-input>
      </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 () {
    return {
      model: {}
    }
  },
  methods: {
    async save () {
      let res
      if (this.id) {
        // 编辑
        res = await this.$http.put(`categories/${this.id}`, this.model)
      } else {
        // 新建
        res = await this.$http.post('categories', this.model)
      }
      this.$router.push('/categories/list')
      this.$message({
        type: 'success',
        message: '保存成功'
      })
    },
    async fetch () {
      // 获取详情
      const res = await this.$http.get(`categories/${this.id}`)
      this.model = res.data
    }
  },
  created () {
    // 当编辑时拥有id才会触发fetch初始化model数据。
    this.id && this.fetch()
  }
}
</script>

还需要新增一个路由。

router.js

import Vue from 'vue'
import Router from 'vue-router'
import Main from './views/Main.vue'
import CategoryEdit from './views/CategoryEdit.vue'
import CategoryList from './views/CategoryList.vue'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'main',
      component: Main,
      children: [
        { path: '/categories/create', component: CategoryEdit },
        // 技巧:注入url参数进入该组件,
        // 组件使用路由参数需要使用this.$router.params可实现同样效果
        { path: '/categories/edit/:id', component: CategoryEdit, props: true },
        { path: '/categories/list', component: CategoryList }
      ]
    }
    
  ]
})

前端大功告成,到后端新增一个获取详情接口以及更新分类的接口就完事了。

   /**
   * 编辑分类
   */
  router.put('/categories/:id', async(req, res) => {
    const model = await Category.findByIdAndUpdate(req.params.id, req.body)
    res.send(model)
  })
  /**
  * 获取分类详情
  */
  router.get('/categories/:id', async(req, res) => {
    const model = await Category.findById(req.params.id)
    res.send(model)
  })

删除分类

同样的套路,不过我们要保证体验,需要提供一个提示确认窗口

categoryList.vue中添加

<el-button type="text" size="small" @click="remove(scope.row)">删除</el-button>
async remove (row) {
    this.$confirm(`是否确定删除分类${row.name}?`, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
    }).then(async () => {
        const res = await this.$http.delete(`categories/${row._id}`)
        this.fetch();
        this.$message({
            type: 'success',
            message: '删除成功!'
        });
    })
}

后端接口新增删除接口

  /**
   * 删除分类
   */
  router.delete('/categories/:id', async(req, res) => {
    await Category.findByIdAndDelete(req.params.id, req.body)
    res.send({
      success: true
    })
  })

增加上级分类

分类通常会有一定的关联,所以我们增加在每一个分类中添加上级分类的字段,通过该字段可以查询到上级分类的相关数据。

前端List页和Edit页面增加上级分类的信息。

CategoryList.vue在table中新增一列,展示获取到分类的parent选项对应的name名称。

<el-table-column prop="parent.name" label="上级分类"></el-table-column>

CategoryEdit.vue增加一个下拉选项框,选择已有的分类作为父级分类。

<el-form-item label="上级分类">
    // 默认选择已有的parent
    <el-select v-model="model.parent" clearable placeholder="上级分类">
    // 分类列表
    <el-option
      v-for="item in parents"
      :key="item._id"
      :label="item.name"
      :value="item._id"
    ></el-option>
    </el-select>
</el-form-item>

我们还需要获取到所有列表的信息。

async fetchParents () {
    const res = await this.$http.get(`categories`)
    this.parents = res.data
}

这样前端部分就完成了。

后端相对简单,只需要在模型中增加一个字段,并用moongoose.SchemaTypes.Object,并用ref属性关联模型。然后在查询时,使用populate('字段名称')方法查询出关联模型的数据,就可以了。

Category.js

const mongoose = require('mongoose')

const schema = new mongoose.Schema({
  name: { type: String },
  // type为mongoose的唯一id标识mongoose.SchemaTypes.ObjectId
  // ref为需要关联的model
  parent: { type: mongoose.SchemaTypes.ObjectId, ref: 'Category'}
})

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

index.js

/**
 * 获取分类列表
*/
router.get('/categories', async(req, res) => {
  // populate关联字段
  // populate指定的字段会继续找到指定的字段它的model对象
  const items = await Category.find().populate('parent').limit(10)
  res.send(items)
})

修复侧边栏状态不同步bug

眼尖的你,可能已经发现了,此时,新建后,跳转回列表页,发现侧边栏的高亮,竟然还是新建列表。一阵苦恼怎么做。查看一下ElementUI的文档最下面选项配置,或者百度一下,都可以轻松解决这个问题。

解决方法:使用default-active,每次更新组件页面时,动态改变其值。说白了,就是让它值跟随你的url$router.path来变化。

<el-menu router :default-openeds="['1']" :default-active="$route.path">
    <el-submenu index="1">
        <template slot="title"><i class="el-icon-message"></i>内容管理</template>
        <el-menu-item-group >
            <template slot="title">分类</template>
            <el-menu-item index="/categories/list">分类列表</el-menu-item>
            <el-menu-item index="/categories/create">新建分类</el-menu-item>
        </el-menu-item-group>
    </el-submenu>
</el-menu>

到此你就完成了一个拥有简单分类功能的后台了,接下来我们把接口做成通用的增删改查接口。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值