第一章:项目起步

01-项目介绍

目标:了解项目背景,了解项目功能。
项目背景:

  • 它对标 CSDN 博客园 等竞品,致力成为全球知名的IT技术交流平台,它包含 技术文章,问答内容,视频解答 的专业IT资讯平台,它提供原创,优质,完整内容的专业IT社区。它是 极客园 IT资讯社区。

项目功能:

  • 极客园-个人端PC 是PC桌面web个人自媒体管理端
  • 主要功能有:
    • 登录
    • 首页
    • 内容管理(筛选,删除)
    • 发布文章(包含编辑文章)

项目物料:http://geek.itheima.net
总结: 我们知道项目的大致功能即可。

02-使用技术

目标:了解会使用到的技术
开发依赖大致如下:

  • 基础环境:nodejss12+ vscode vuecli4.x
  • 配套工具:eslint babel less
  • 使用技术:vue2.6.12 element-ui vue-router

项目中的解决方案:

  • 使用vue-cli创建vue单页应用解决方案
  • 使用vue-router实现前端路由解决方案
  • 使用element-ui快速搭建PC界面解决方案

总结: 我们大概知道用了那些东西即可。

03-创建项目

目标:知道如何使用vue-cli创建项目
大致步骤:

  • 在某个目录打开命令行工具输入创建项目的命令
  • 安装项目需求选择具体的工具,然后等待创建吧
  • 最后进入创建好的项目,启动项目即可

具体如下:

  • 执行创建命令

    1. vue create geek-client-pc
  • 选中自定义创建

image.png

  • 选择Vue版本,依赖Babel降级ES6语法,使用css预处理器,使用代码风格校验。

image.png

  • 选择vue2.0版本

image.png

  • 是否使用历史模式API,输入 n

image.png

  • 选择less这种css预处理器

image.png

  • 选择 通用语法风格配置

image.png

  • 语法风格校验的时机,保存代码校验,提交代码校验且自动修复。

image.png

  • 选择使用不同的配置文件对于所依赖工具

image.png

  • 是否记录此次操作记录,输入 n

image.png

  • 最后等待安装即可,安装完毕进入项目目录,执行 npm run serve 即可启动项目。

image.png
总结: 我们可以使用vuecli根据自己项目需求创建合适的项目。

04-调整目录

目标:根据项目功能调整下目录结构
大致步骤:

  • 配置文件解释说明
  • 调整src下目录结构

落地内容:

  • 根目录和配置文件。都是自动生成的,了解作用即可

    1. ├─node_modules
    2. ├─public
    3. ├─src
    4. ├─.browserslistrc # 适配浏览器列表
    5. ├─.editorconfig # 提供给编辑器的配置
    6. ├─.eslintrc.js # eslint代码风格配置
    7. ├─.gitignore # git忽略文件配置
    8. ├─.babel.config.js # babelES降级配置
    9. ├─package-lock.json # 包下载版本说明文件
    10. ├─package.json # 项目包说明文件
    11. ├─README.md # 说明MD文件
  • src 目录结构如下,仅供参考 (分模块的思维才重要)

    1. ├─assets # 项目资源
    2. ├─images # 图片
    3. └─styles # less代码
    4. ├─components # 全局组件,通用组件
    5. ├─router # 路由
    6. ├─utils # 工具
    7. └─views # 路由组件(页面)
    8. ├─home # 首页
    9. ├─login # 登录
    10. ├─article # 内容管理
    11. └─publish # 发布文章

    总结: 做好开发前的准备,调整下项目结构。

    第二章:项目架构

    01-使用element-ui

    目标:引入element-ui组件库,能够在项目中使用它。
    在pc管理系统开发过程中,需要创建静态页面,如果自己去编写,成本比较高。使用第三方的UI库,来快速构建页面会是最好选择。我们使用的是基于vue最流行的UI组件库 element-ui 饿了么UI(opens new window),它提供了网页开发中常用的功能,而且基于vue封装成了组件,我们只需参考官方提供的使用文档来使用即可。
    使用步骤:

  • 安装 npm i element-ui

  • 导入

    1. // 完整引入,main.js写入以下代码
    2. import ElementUI from 'element-ui'
    3. import 'element-ui/lib/theme-chalk/index.css'
    4. Vue.use(ElementUI)
    • 完整引入(建议学习阶段使用,简单粗暴)
    • 按需引入(建议开发阶段使用,优化体积)
  • 使用 App.vue

    1. <div id="app">
    2. <el-button type="success">成功按钮</el-button>
    3. </div>

    小结一下:

  • 饿了么UI是什么?

    • 是一个基于vue的UI组件库
  • 如果使用饿了么UI?
    • 安装,完整导入,全局使用。

      02-使用vue-router

      目标:在项目中使用vue-router实现前端路由
      使用步骤:
  1. 安装:npm i vue-router
  2. 以前是在main.js完成的,但是路由相关代码会比较多,现在是在src/router/index.js 进行引入和配置。

    1. import VueRouter from 'vue-router'
  3. 注册:

    1. import Vue from 'vue'
    2. Vue.use(VueRouter)
  4. 初始化:

    1. const router = new VueRouter({
    2. routes: [] // 路由规则
    3. })
  5. 导出路由实例:

    1. export default router
  6. 导入路由实例:main.js

    1. // @是路径别名,代表 src
    2. import router from '@/router'
  7. 挂载:main.js ```javascript new Vue({ render: h => h(App),

  • router }).$mount(‘#app’) ``` 总结:安装使用vue-router,提取路由模块。

    03-约定路由

    目的:约定好前端路由规则,什么路径渲染什么组件。
    设计思路:
    image.png
    具体规则:
路由路径 路由对应组件(页面) 路由等级
/login 登录 一级路由
/ 首页 一级路由
├─ / 概览页面 二级路由
├─ /article 文章管理 二级路由
├─ /publish 发布文章 二级路由

总结: 登录——>首页,内容完整切换使用一级路由,概览,文章,发布,再Layout基础之上切换嵌套路由,二级路由切换。

第三章:登录模块

01-登录-路由与组件

目的:准备好登录组件,配置好路由。
组件:src/views/login.vue

  1. <template>
  2. <div class="container">Login</div>
  3. </template>
  4. <script>
  5. export default {
  6. name: 'Login'
  7. }
  8. </script>
  9. <style scoped lang="less"></style>

路由:src/router/index.js

  1. import Login from '@/views/login'
  1. const router = new VueRouter({
  2. routes: [
  3. + { path: '/login', component: Login }
  4. ]
  5. })

出口:src/App.vue

  1. <template>
  2. <div id="app">
  3. <router-view></router-view>
  4. </div>
  5. </template>

02-登录-基础布局

目的:完成登录页面基础布局
结构:src/views/login/index.vue

  1. <div class="container">
  2. <el-card>
  3. <img class="logo" src="../../assets/logo.png" alt="">
  4. <!-- 表单 -->
  5. </el-card>
  6. </div>

样式:
src/views/login/index.vue

  1. .container {
  2. width: 100%;
  3. height: 100%;
  4. position: absolute;
  5. left: 0;
  6. top: 0;
  7. background: url(../../assets/login.png);
  8. .el-card {
  9. width: 440px;
  10. height: 380px;
  11. position: absolute;
  12. left: 50%;
  13. top: 50%;
  14. transform: translate(-50%,-50%);
  15. box-shadow: 0 0 50px rgba(0,0,0,0.1);
  16. .logo {
  17. width: 200px;
  18. display: block;
  19. margin: 0 auto 20px;
  20. }
  21. }
  22. }

App.vue

  1. * {
  2. margin: 0;
  3. padding: 0;
  4. }

总结:el-card是饿了么UI卡片组件,组件根元素有class 类 .el-card

03-登录-绘制表单

目的:使用el-form组件绘制登录表单
大致步骤:

  • 先去阅读 el-form 组件的使用文档
  • 了解表单元素组件类型
  • 绘制表单

落的内容:src/views/login.vue

  • el-form用法 ```javascript
  1. el-form 是表单容器组件
  2. el-form-item 是表单项组件,label可以设置表单元素文字说明,可以放置表单元素组件。 ```
  • el的表单元素组件

    1. el-input el-checkbox el-select el-radio 等等
  • 绘制表单

    1. <el-form>
    2. <el-form-item>
    3. <el-input placeholder="请输入手机号"></el-input>
    4. </el-form-item>
    5. <el-form-item>
    6. <el-input placeholder="请输入手机号"></el-input>
    7. </el-form-item>
    8. <el-form-item>
    9. <el-checkbox :value="true">我已阅读并同意「用户协议」和「隐私条款」</el-checkbox>
    10. </el-form-item>
    11. <el-form-item>
    12. <el-button type="primary">登 录</el-button>
    13. </el-form-item>
    14. </el-form>
    1. .el-form {
    2. padding: 0 20px;
    3. .el-button {
    4. width: 100%;
    5. }
    6. }

    总结: 知道绘制表单用的是el-form,el-form-item和其他的表单元素组件。

    04-登录-基本的表单校验

    目的:完成表单的基本校验
    大致步骤:

  • 先阅读element-ui的表单组件文档,找到校验功能总结方法

  • 明确下校验需求
  • 实践下校验功能

落的内容:src/views/login.vue

  • 校验方法 ```javascript
  1. el-form 需要加上一个属性 model 通过这个属性绑定表单数据对象
  2. el-form 需要加上一个属性 rules 通过这个属性绑定校验规则对象(定义校验逻辑)
  3. el-from-item 需要加上一个属性 prop 通过这个属性来指定当前表单项使用的校验规则
  4. 校验规则的名称需要和绑定表单数据中的字段名一致
  5. 具体的校验规则可参考官方示例 ```
  • 校验需求

    1. 手机号:必填,1开头,3-9之间,9个数字
    2. 验证码:必填,6个字符
  • 实践代码

    1. data () {
    2. return {
    3. // 数据
    4. form: {
    5. mobile: '',
    6. code: ''
    7. },
    8. // 规则
    9. rules: {
    10. mobile: [
    11. { required: true, message: '请输入手机号', trigger: 'blur' }
    12. ],
    13. code: [
    14. { required: true, message: '请输入验证码', trigger: 'blur' },
    15. { len: 6, message: '验证码6个字符', trigger: 'blur' }
    16. ]
    17. }
    18. }
    19. }

    ```javascript

  • 我已阅读并同意「用户协议」和「隐私条款」 登 录 ``` 总结: el-form上绑定model指定表单数据对象,绑定rules指定表单校验对象,el-form-item的prop属性指定当前表单项使用校验规则中的 xxx 规则去校验表单对象中的 xxx 数据。

    05-登录-自定义表单校验

    目的:完成自己定义校验规则
    大致步骤:
  • 先阅读element-ui的表单组件文档,找到自定校验规律
  • 实践自定义校验

落地内容:src/views/login.vue

  • 自定义校验规律 ```javascript
  1. 在 校验规则 对象中, validator 是用来指定自定义校验函数
  2. 这个 自定义校验函数 必须先声明,在data的return之前声明即可
  3. 自定义校验函数三个参数:rule value callback 3.1 rule 当前字段对应的校验对象 不会使用 3.2 value 当前字符段对应的值 我们需要校验这个值 3.3 callback 失败—->callback(new Error(‘校验失败的提示’)) 成功——>callback() ```
  • 自定义校验实践 ```javascript data () {
  • const checkMobile = (rule, value, callback) => {
  • if (/^1[3-9]\d{9}$/.test(value)) {
  • callback()
  • } else {
  • callback(new Error(‘手机号格式不对’))
  • }
  • } return { // 数据 form: { mobile: ‘’, code: ‘’ }, // 规则 rules: { mobile: [
  • { required: true, message: ‘请输入手机号’, trigger: ‘blur’ }, { validator: checkMobile, trigger: ‘blur’ } ], code: [ { required: true, message: ‘请输入验证码’, trigger: ‘blur’ }, { len: 6, message: ‘验证码6个字符’, trigger: ‘blur’ } ] } } } ``` 总结: 使用validator属性可以指定一个校验函数,再校验函数中来校验value数据,调用callback代表校验完成,传入错误对象代表失败,不传代表成功。

    06-登录-整体表单校验

    目的:对整个表单进行校验
    大致步骤:
  • 先阅读element-ui的表单组件文档,找到整体表单校验方式
  • 实践整体表单校验

落地内容:src/views/login.vue

  • 整体表单校验方式 ```javascript
  1. 通过调组件实例的函数 validate 进行整体校验
  2. validate(function(valid){ // valid 为 true 校验成功 })
  3. 通过ref绑定el-form,然后this.$refs获取组件实例,再去调用validate函数 ```
  • 实践整体表单校验,点击登录按钮校验

    1. <el-form ref="form" :model="form" :rules="rules">
    1. <el-button @click="login()" type="primary">登 录</el-button>
    1. methods: {
    2. login () {
    3. this.$refs.form.validate(valid => {
    4. console.log(valid)
    5. })
    6. }
    7. }

    总结: 使用ref=”xxx”绑定el-form组件,使用this.$refs.xxx.validate()校验整体表单。

    07-登录-挂载axios

    目的:创建一个axios实现挂载再Vue原型上,将来再任何vue组件下可通过this访问,方便开发。
    大致步骤:

  • 安装axios

  • 创建axios
  • 挂载axios

落地代码:

  • 安装axios

    1. npm i axios
  • 创建axios src/utils/http.js ```javascript import axios from ‘axios’

const instance = axios.create({ baseURL: ‘http://geek.itheima.net/‘, timeout: 5000 })

export default instance

  1. - 挂载axios src/main.js
  2. ```javascript
  3. import http from '@/utils/http'
  4. Vue.prototype.$http = http

总结: 创建一个axios实例通过prototype挂载到Vue上,所有vue实例可以访问。

08-登录-进行登录

目的:进行登录实现
大致步骤:

  • 发起登录请求
  • 成功:保存token在本地,跳转首页
  • 失败:提示错误消息

落地代码:

  1. login () {
  2. this.$refs.form.validate(async valid => {
  3. if (valid) {
  4. try {
  5. // 请求
  6. const res = await this.$http.post('/v1_0/authorizations', this.form)
  7. // 成功
  8. localStorage.setItem('geek-client-pc-store', res.data.data.token)
  9. this.$router.push('/')
  10. } catch (e) {
  11. // 失败
  12. this.$message.error(e.response.data.message || '登录失败')
  13. }
  14. }
  15. })
  16. }

try catch 的用法

  1. try{
  2. // 写可能出现异常的代码片段
  3. }catch(e) {
  4. // 如果出现异常执行catch函数
  5. // e 就是错误对象
  6. }

总结: 发起登录请求,成功后存储token后跳转到首页。

09-登录-操作token工具

目标:封装一个操作本地token的工具模块auth.js
大致步骤:

  • 约定好存储数据的key定义成常量
  • 提供一个获取token函数
  • 提供一个设置token函数
  • 提供一个删除token函数

落地代码:

  • 定义工具 src/utils/auth.js

    1. // 操作认证信息token的工具函数
    2. const KEY = 'geek-client-pc-store'
    3. export default {
    4. // 获取token
    5. getToken () {
    6. return localStorage.getItem(KEY)
    7. },
    8. // 设置token
    9. setToken (token) {
    10. localStorage.setItem(KEY, token)
    11. },
    12. // 删除token
    13. delToken () {
    14. localStorage.removeItem(KEY)
    15. }
    16. }
  • 使用工具 src/views/login.vue

    1. import auth from '@/utils/auth'

    ```javascript

    1. // 请求
    2. const res = await this.$http.post('/v1_0/authorizations', this.form)
    3. // 成功
    4. // localStorage.setItem('geek-client-pc-store', res.data.data.token)
  • auth.setToken(res.data.data.token) this.$router.push(‘/‘) ```

    10-登录-访问控制

    目的:实现除去登录页面,其他页面访问都需要判断是否存储token,没有拦截到登录
    大致步骤:
  • 弄清楚访问控制的逻辑
  • 使用路由导航守卫实现

落地代码:

  • 弄清楚访问控制的逻辑

image.png

  • 使用路由导航守卫实现 src/router/index.js

    1. import auth from '@/utils/auth'
    1. router.beforeEach((to, from, next) => {
    2. // 获取token
    3. const token = auth.getToken()
    4. // 不是访问登录,有没有token,跳转登录页面
    5. if (to.path !== '/login' && !token) return next('/login')
    6. // 其他情况放行
    7. next()
    8. })

    总结:使用router的breforeEach进行访问权限控制。

    第四章:概览页面

    00-代码片段

    目的:使用vue快捷方式创建vue模版组件
    步骤:

  • ctrl + shift + p 打开功能搜索框

  • 输入 代码片段 搜索功能 ,找到如下功能点击它

image.png

  • 点击新建代码片段

image.png

  • 将下面的代码复制到文件

    1. {
    2. "vue-component": {
    3. "scope": "vue",
    4. "prefix": "vue",
    5. "body": [
    6. "<template>",
    7. " <div class=\"container\"></div>",
    8. "</template>",
    9. "<script>",
    10. "export default {",
    11. " name: ''",
    12. "}",
    13. "</script>",
    14. "<style scoped lang=\"less\"></style>",
    15. ""
    16. ],
    17. "description": "生成一个vue单文件组件"
    18. }
    19. }

    image.png

  • 最后再vue文件输入 vue 提示代码生成方式

    01-概览-路由与布局组件

    组件:src/views/Layout.vue

    1. <template>
    2. <div class="container">Layout</div>
    3. </template>
    4. <script>
    5. export default {
    6. name: 'Layout'
    7. }
    8. </script>
    9. <style scoped lang="less"></style>

    路由:src/router/index.js

    1. import Layout from '@/views/Layout'

    ```javascript const router = new VueRouter({ routes: [ { path: ‘/login’, component: Login },

  • {path: ‘/‘,component: Layout} ] }) ```

    02-概览-布局组件架子

    目标:使用element-ui布局容器准备Layout组件的页面架子
    大致步骤:
  • 找到element-ui文档中的布局容器组件,找到对应的结构代码。
  • 在Layout.vue组件中使用代码,添加结构和样式

落地代码:src/views/Layout.vue

  1. <template>
  2. <el-container class="container">
  3. <el-aside width="200px">
  4. <div class="logo"></div>
  5. </el-aside>
  6. <el-container>
  7. <el-header>
  8. <span style="margin-right:20px">用户名</span>
  9. <el-link icon="el-icon-unlock" :underline="false">退出</el-link>
  10. </el-header>
  11. <el-main>
  12. <router-view></router-view>
  13. </el-main>
  14. </el-container>
  15. </el-container>
  16. </template>
  17. <script>
  18. export default {
  19. name: 'Layout'
  20. }
  21. </script>
  22. <style scoped lang="less">
  23. .container {
  24. width: 100%;
  25. height: 100%;
  26. position: absolute;
  27. left: 0;
  28. top: 0;
  29. .el-aside {
  30. background: #023;
  31. .logo {
  32. width: 200px;
  33. height: 60px;
  34. background:#023 url(../assets/logo.png) no-repeat center / 160px auto;
  35. }
  36. }
  37. .el-header {
  38. border-bottom: 1px solid #ddd;
  39. display: flex;
  40. justify-content: flex-end;
  41. align-items: center;
  42. }
  43. }
  44. </style>

03-概览-布局组件菜单

目的:在左侧栏加上el-menu组件搭建菜单
大致步骤:

  • 找到element-ui文档中的导航菜单组件,找到对应的结构代码。
  • 在Layout.vue组件中使用代码,改造成我们需要的菜单。

落的代码:

  • 分析示例代码 ```javascript
  1. el-menu 是导航菜单容器
  2. el-menu-item 是菜单项
  3. 如果有子级菜单使用 el-submenu 包裹 el-menu-item 组件,如果有分组 el-menu-item 外包裹 el-menu-item-group 组件 ```
  • 使用导航菜单

    1. <el-menu
    2. background-color="#023"
    3. style="border-right:none"
    4. text-color="#fff"
    5. default-active="/"
    6. >
    7. <el-menu-item index="/">
    8. <i class="el-icon-s-home"></i>
    9. <span slot="title">数据概览</span>
    10. </el-menu-item>
    11. <el-menu-item index="/article">
    12. <i class="el-icon-document"></i>
    13. <span slot="title">内容管理</span>
    14. </el-menu-item>
    15. <el-menu-item index="/publish">
    16. <i class="el-icon-s-promotion"></i>
    17. <span slot="title">发布文章</span>
    18. </el-menu-item>
    19. </el-menu>

    属性:

  • el-menu —-> default-active 当前激活那个菜单项,值为el-menu-item的index的值。

  • el-menu-item —-> index 菜单项标识

总结: 使用element-ui组件,先分析,再使用。诀窍:试一试。

04-概览-路由与概览组件

目的:完成概览组件,完成二级路由配置,以及出口的配置。
组件:src/home/index.vue

  1. <template>
  2. <div class="home"></div>
  3. </template>
  4. <script>
  5. export default {
  6. name: 'Home'
  7. }
  8. </script>
  9. <style scoped lang="less">
  10. .home {
  11. width: 100%;
  12. height: 100%;
  13. background:#f5f5f5 url(../../assets/chart.png) no-repeat center / contain;
  14. }
  15. </style>

路由:src/router/index.js

  1. import Home from '@/views/home'
  1. const router = new VueRouter({
  2. routes: [
  3. { path: '/login', component: Login },
  4. {
  5. path: '/',
  6. component: Layout,
  7. + children: [
  8. + { path: '/', component: Home }
  9. + ]
  10. }
  11. ]
  12. })

出口:src/views/Layout.vue

  1. <el-main>
  2. + <router-view></router-view>
  3. </el-main>

05-概览-获取个人信息

目的:Layout.vue组件中需要登录用户的名称,需要调用接口获取个人信息。
大致步骤:

  • data中声明 用户数据 对象
  • 组件初始化获取个人信息,设置用户信息
  • 模版中使用 name 没有就使用 mobile

落地代码:src/views/Layout.vue

  1. data () {
  2. return {
  3. user: {}
  4. }
  5. },
  6. async created () {
  7. const res = await this.$http.get('v1_0/user/profile')
  8. this.user = res.data.data
  9. },
  1. <el-header>
  2. + <span style="margin-right:20px">{{user.name||user.mobile}}</span>
  3. <el-link icon="el-icon-unlock" :underline="false">退出</el-link>
  4. </el-header>

image.png
由于我们请求的时候没有带上token请求后台,导致后台认为我们没有登录,响应401告诉你token失效或未传。
总结: 调用需要登录状态的后台接口是需要携带token的,下一节我们实现下携带token

06-概览-请求携带token

目的:在请求前获取本地存储的token设置在请求头
大致步骤:

  • 分析示例代码
  • 完成token携带

落地代码:src/utils/http.js

  • 分析-请求拦截器

    1. // 添加请求拦截器
    2. axios.interceptors.request.use(function (config) {
    3. // 在发送请求之前做些什么
    4. return config;
    5. }, function (error) {
    6. // 对请求错误做些什么
    7. return Promise.reject(error);
    8. });
  • 使用

    1. import auth from '@/utils/auth'
    1. instance.interceptors.request.use(config => {
    2. const token = auth.getToken()
    3. if (token) config.headers.Authorization = `Bearer ${token}`
    4. return config
    5. }, err => Promise.reject(err))

    总结: 使用axios请求拦截器可以在请求前给请求头带上token

    07-概览-拦截token失效

    目的:在token失效的时候拦截跳转到登录页面
    大致步骤:

  • token在服务端是会去判断有效时间的,极客园的是2小时失效

  • 我们不能等两小时再来操作,可以在浏览器把token改成一个错误的,其实就是默认token失效。
  • 响应拦截器来根据401判断,分析示例代码
  • 完成token失效拦截

落地代码:src/utils/http.js

  • 分析-响应拦截器

    1. // 添加响应拦截器
    2. axios.interceptors.response.use(function (response) {
    3. // 对响应数据做点什么
    4. return response;
    5. }, function (error) {
    6. // 对响应错误做点什么
    7. return Promise.reject(error);
    8. });
  • 使用

    1. import router from '@/router'
    1. instance.interceptors.response.use(res => res, err => {
    2. // 失效token
    3. if (err.response && err.response.status === 401) {
    4. auth.delToken()
    5. router.push('/login')
    6. }
    7. return Promise.reject(err)
    8. })

    总结: 使用axios响应拦截器可以根据401响应状态码拦截token失效

    08-概览-退出功能

    目的:失效退出登录
    大致步骤:

  • 本地删除token就是退出登录

  • 绑定按钮事件,事件中删除token跳转登录页面即可。

落地代码:src/views/Layout.vue

  1. <el-link @click="logout()" icon="el-icon-unlock" :underline="false">退出</el-link>
  1. import auth from '@/utils/auth.js'
  1. methods: {
  2. logout () {
  3. auth.delToken()
  4. this.$router.push('/login')
  5. }
  6. }

第五章:内容管理

01-内容管理-路由与组件

组件:src/views/article/index.vue

  1. <template>
  2. <div class="article">Article</div>
  3. </template>
  4. <script>
  5. export default {
  6. name: 'Article'
  7. }
  8. </script>
  9. <style scoped lang="less"></style>

路由:src/router/index.js

  1. import Article from '@/views/article'
  1. children: [
  2. { path: '/', component: Home },
  3. + { path: '/article', component: Article }
  4. ]

02-内容管理-开启菜单路由

目的:点击菜单进行路由跳转,刷新页面需要激活当前菜单。
大致步骤:

  • 开启导航菜单路由功能
  • 刷新页面后保存激活状态

落地代码:src/views/Layout.vue

  • 开启导航菜单路由功能 ```javascript
    1. <el-menu
    2. background-color="#023"
    3. style="border-right:none"
    4. text-color="#fff"
    5. default-active="/"
  • router > 加上router属性后,el-menu-item的index属性值就是跳转的路径javascript <el-menu background-color=”#023” style=”border-right:none” text-color=”#fff”
  • :default-active=”$route.path” router > ``` $route.path 获取当前路由路径,设置给default-active属性,激活对应的el-menu-item菜单项。

    03-内容管理-筛选区域

    目的:完成筛选区域布局
    大致步骤:
  • 准备基本结构
  • 添加-状态-单选框
  • 添加-频道-下拉框
  • 添加-时间-日期控件

落地代码:src/views/article/index.vue
1)基本结构

  1. <div class='article-page'>
  2. <!-- 筛选区域 -->
  3. <el-card>
  4. <div slot="header">
  5. <el-breadcrumb separator-class="el-icon-arrow-right">
  6. <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
  7. <el-breadcrumb-item>内容管理</el-breadcrumb-item>
  8. </el-breadcrumb>
  9. </div>
  10. <!-- 表单 -->
  11. <el-form label-width="80px">
  12. <el-form-item label="状态:"></el-form-item>
  13. <el-form-item label="频道:"></el-form-item>
  14. <el-form-item label="日期:"></el-form-item>
  15. <el-form-item label="">
  16. <el-button type="primary">筛选</el-button>
  17. </el-form-item>
  18. </el-form>
  19. </el-card>
  20. <!-- 结果区域 -->
  21. </div>

2)状态-单选框

  1. <el-form-item label="状态:">
  2. <el-radio-group v-model="reqParams.status">
  3. <el-radio :label="null">全部</el-radio>
  4. <el-radio :label="0">草稿</el-radio>
  5. <el-radio :label="1">待审核</el-radio>
  6. <el-radio :label="2">审核通过</el-radio>
  7. <el-radio :label="3">审核失败</el-radio>
  8. <el-radio :label="4">已删除</el-radio>
  9. </el-radio-group>
  10. </el-form-item>
  1. data () {
  2. return {
  3. // 提交给后台的参数
  4. reqParams: {
  5. // 默认值为null,使用axios提交的时候null值是不会传递的。
  6. status: null
  7. }
  8. }
  9. }

3)频道-下拉框

  1. <el-form-item label="频道:">
  2. <el-select v-model="reqParams.channel_id" placeholder="请选择">
  3. <el-option
  4. v-for="item in channelOptions"
  5. :key="item.value"
  6. :label="item.label"
  7. :value="item.value">
  8. </el-option>
  9. </el-select>
  10. </el-form-item>
  1. data () {
  2. return {
  3. // 提交给后台的参数
  4. reqParams: {
  5. // 默认值为null,使用axios提交的时候null值是不会传递的。
  6. status: null,
  7. + channel_id: null
  8. },
  9. // 频道选项
  10. + channelOptions: [{ value: 1, label: '前端' }, { value: 2, label: 'java' }]
  11. }
  12. }

4)时间-日期控件

  1. <el-form-item label="日期:">
  2. <el-date-picker
  3. v-model="dateArr"
  4. type="daterange"
  5. range-separator="至"
  6. start-placeholder="开始日期"
  7. end-placeholder="结束日期">
  8. </el-date-picker>
  9. </el-form-item>
  1. data () {
  2. return {
  3. // 提交给后台的参数
  4. reqParams: {
  5. // 默认值为null,使用axios提交的时候null值是不会传递的。
  6. status: null,
  7. channel_id: null,
  8. + begin_pubdate: null,
  9. + end_pubdate: null
  10. },
  11. // 频道选项
  12. channelOptions: [{ value: 1, label: '前端' }, { value: 2, label: 'java' }],
  13. // 日期范围 [起始时间,结束时间]
  14. + dateArr: []
  15. }
  16. }

04-内容管理-结果区域

目的:完成结果区域布局
大致步骤:

  • 基本布局
  • 分析表格组件
  • 绘制表格组件
  • 使用分页组件

落地代码:src/views/article/index.vue
1)基本布局

  1. <!-- 结果区域 -->
  2. <el-card style="margin-top:20px">
  3. <div slot="header">根据筛选条件共查询到 46148 条结果:</div>
  4. <!-- 表格 -->
  5. <!-- 分页 -->
  6. </el-card>

2)分析表格组件
image.png
3)绘制表格组件

  • el-table 属性 data 接收表格数组数据,必填。
    1. <!-- 表格 -->
    2. <el-table :data="articles">
    3. <el-table-column label="封面"></el-table-column>
    4. <el-table-column label="标题"></el-table-column>
    5. <el-table-column label="状态"></el-table-column>
    6. <el-table-column label="发布时间"></el-table-column>
    7. <el-table-column label="阅读数"></el-table-column>
    8. <el-table-column label="评论数"></el-table-column>
    9. <el-table-column label="点赞数"></el-table-column>
    10. <el-table-column label="操作"></el-table-column>
    11. </el-table>
    ```javascript
    1. dateArr: [],
  • articles: []
    1. 4)使用分页组件
    2. ```javascript
    3. <!-- 分页 -->
    4. <el-pagination
    5. style="margin-top:20px"
    6. background
    7. layout="prev, pager, next"
    8. :total="1000">
    9. </el-pagination>

    05-内容管理-渲染频道选项

    目的:获取文章频道数据且进行渲染
    大致步骤:
  • 在mehtods中定义一个获取选项的函数 ,获取数据赋值给data中的选项
  • 在created中调用改函数即可
  • 渲染模版

落地代码:src/views/article/index.vue

  • 获取选项函数

    1. methods: {
    2. async getChannelOptions () {
    3. const res = await this.$http.get('v1_0/channels')
    4. this.channelOptions = res.data.data.channels
    5. }
    6. }
  • 调用函数

    1. created () {
    2. this.getChannelOptions()
    3. },
  • 渲染模版 ```javascript

    1. <el-select v-model="reqParams.channel_id" placeholder="请选择">
    2. <el-option
    3. v-for="item in channelOptions"
  • :key=”item.id”
  • :label=”item.name”
  • :value=”item.id”> ```

    06-内容管理-渲染简单列

    目的:获取文章列表数据,渲染 标题,发布时间,阅读数,评论数,点赞数 5列。
    大致步骤:
  • 获取文章列表数据在组件初始化后
  • 渲染 标题,发布时间 两列,它们简单些

落地代码:src/views/article/index.vue

  • 获取数据 ```javascript data () { return {
    1. // 提交给后台的参数
    2. reqParams: {
    3. // 默认值为null,使用axios提交的时候null值是不会传递的。
    4. status: null,
    5. channel_id: null,
    6. begin_pubdate: null,
    7. end_pubdate: null,
  • page: 1,
  • per_page: 10 },
    1. ```javascript
    2. methods: {
    3. async getArticles () {
    4. const res = await this.$http.get('v1_0/mp/articles', { params: this.reqParams })
    5. this.articles = res.data.data.results
    6. }
    7. }
    1. created () {
    2. this.getChannelOptions()
    3. this.getArticles()
    4. },
  • 渲染内容

    1. <!-- 表格 -->
    2. <el-table :data="articles">
    3. <el-table-column label="封面"></el-table-column>
    4. <el-table-column label="标题" prop="title" width="400px"></el-table-column>
    5. <el-table-column label="状态"></el-table-column>
    6. <el-table-column label="阅读数" width="120px" prop="read_count"></el-table-column>
    7. <el-table-column label="评论数" width="120px" prop="comment_count"></el-table-column>
    8. <el-table-column label="点赞数" width="120px" prop="like_count"></el-table-column>
    9. <el-table-column label="发布时间" prop="pubdate"></el-table-column>
    10. <el-table-column label="操作" width="120px"></el-table-column>
    11. </el-table>

    总结: 使用el-table-column的prop属性指定当前列显示什么字段的数据

    07-内容管理-渲染复杂列

    目的:渲染 封面,状态,操作 三列。
    大致步骤:

  • 参考element-ui文档,总结渲染自定义列套路

  • 渲染封面列
  • 渲染状态列
  • 渲染操作列

落地代码:src/views/article/index.vue

  1. 需要使用作用域插槽,暴露的数据 scope = { $index, row }
  2. $index 是遍历数组(articles)的索引
  3. row 是遍历数组(articles)的每一项 (行数据) ```
  • 渲染封面

    1. <el-table-column label="封面">
    2. <template slot-scope="scope">
    3. <el-image :src="scope.row.cover.images[0]" style="width:200px;height:150px">
    4. <div slot="error" class="image-slot">
    5. <img src="@/assets/error.png" alt="" width="200px" height="150px">
    6. </div>
    7. </el-image>
    8. </template>
    9. </el-table-column>
  • 渲染状态列

    1. <el-table-column label="状态">
    2. <template slot-scope="scope">
    3. <el-tag v-if="scope.row.status===0" type="info">草稿</el-tag>
    4. <el-tag v-if="scope.row.status===1">待审核</el-tag>
    5. <el-tag v-if="scope.row.status===2" type="success">审核通过</el-tag>
    6. <el-tag v-if="scope.row.status===3" type="warning">审核失败</el-tag>
    7. <el-tag v-if="scope.row.status===4" type="danger">已删除</el-tag>
    8. </template>
    9. </el-table-column>
  • 渲染操作列

    1. <el-table-column label="操作" width="120px">
    2. <template>
    3. <el-button type="primary" icon="el-icon-edit" circle plain></el-button>
    4. <el-button type="danger" icon="el-icon-delete" circle plain></el-button>
    5. </template>
    6. </el-table-column>

    image.png

    08-内容管理-实现分页功能

    目标:完成分页渲染和切换功能
    大致步骤:

  • 准备总条数数据total

  • 完成分页渲染,通过分页组件提供的属性
    • total属性,指定一共多少条
    • page-size属性,指定一页多少条
    • current-page属性,指定当前是第几页
  • 切换分页功能
    • current-change事件,触发事件得到当前改变的页码,通过页码去请求

落地代码:src/views/article/index.vue
1)total数据

  1. articles: [],
  2. total: 0
  1. async getArticles () {
  2. const res = await this.$http.get('v1_0/mp/articles', { params: this.reqParams })
  3. this.articles = res.data.data.results
  4. this.total = res.data.data.total_count
  5. },
  1. <div slot="header">根据筛选条件共查询到 {{total}} 条结果:</div>

2)完成分页渲染

  1. <!-- 分页 -->
  2. <el-pagination
  3. style="margin-top:20px"
  4. background
  5. layout="prev, pager, next"
  6. :current-page="reqParams.page"
  7. :page-size="reqParams.per_page"
  8. :total="total">
  9. </el-pagination>

3)切换分页

  1. <!-- 分页 -->
  2. <el-pagination
  3. style="margin-top:20px"
  4. background
  5. layout="prev, pager, next"
  6. @current-change="togglePage"
  7. :current-page="reqParams.page"
  8. :page-size="reqParams.per_page"
  9. :total="total">
  10. </el-pagination>
  1. togglePage (changedPage) {
  2. this.reqParams.page = changedPage
  3. this.getArticles()
  4. }

09-内容管理-实现筛选功能

目标:实现文章筛选功能
大致步骤:src/views/article/index.vue

  • 绑定筛选按钮的点击事件,指定处理函数。
  • 需要带上筛选条件进行查询,而且页码需要变成第一页,发请求渲染列表即可。
    • 页码第一页,this.reqParams.page = 1 搞定
    • 双向绑定reqParams的条件不用管,只要改动了,reqParams中已经变化。
      • 状态 status
      • 频道 channel_id
    • 两个条件 begin_pubdate end_pubdate 需要在时间范围选择之后,给他们赋值。
  • 频道选项需要清空。

落地代码:

  • 筛选函数

    1. <el-button type="primary" @click="filterArticles()">筛选</el-button>
    1. filterArticles () {
    2. this.reqParams.page = 1
    3. this.getArticles()
    4. },
  • 日期条件

    1. <el-date-picker
    2. @change="changeDate"
    3. v-model="dateArr"
    4. type="daterange"
    5. range-separator="至"
    6. start-placeholder="开始日期"
    7. end-placeholder="结束日期">
    8. </el-date-picker>
    1. changeDate (dateArr) {
    2. // 日期访问改变后给reqParams中的日期赋值
    3. // dateArr 选择日期后 [start,end]
    4. // dateArr 用户做清空 null
    5. if (dateArr) {
    6. this.reqParams.begin_pubdate = dateArr[0]
    7. this.reqParams.end_pubdate = dateArr[1]
    8. } else {
    9. this.reqParams.begin_pubdate = null
    10. this.reqParams.end_pubdate = null
    11. }
    12. },
  • 频道清空

    1. <el-select clearable v-model="reqParams.channel_id" placeholder="请选择" >
    2. <el-option
    3. v-for="item in channelOptions"
    4. :key="item.id"
    5. :label="item.name"
    6. :value="item.id">
    7. </el-option>
    8. </el-select>

    10-内容管理-实现删除功能

    目的:实现文章删除功能
    大致步骤:

  • 给删除按钮绑定点击事件,指定处理函数,把文章ID传递给函数。

  • 弹出一个确认框,标题:温馨提示,内容:此操作将永久删除该文章, 是否继续?
  • 点击确认的时候,发送删除请求,删除成功之后,更新当前列表。

落地代码:src/views/article/index.vue

  1. <template slot-scope="scope">
  2. <el-button type="primary" icon="el-icon-edit" circle plain></el-button>
  3. <el-button @click="deleteArticle(scope.row.id)" type="danger" icon="el-icon-delete" circle plain></el-button>
  4. </template>
  1. deleteArticle (id) {
  2. this.$confirm('此操作将永久删除该文章, 是否继续?', '温馨提示', {
  3. confirmButtonText: '确定',
  4. cancelButtonText: '取消',
  5. type: 'warning'
  6. }).then(async () => {
  7. // 发送删除请求
  8. await this.$http.delete(`v1_0/mp/articles/${id}`)
  9. // 删除成功
  10. this.$message.success('删除文章成功')
  11. this.getArticles()
  12. }).catch(() => {
  13. // 取消不做任何事情
  14. })
  15. }

第六章:发布文章

01-发布文章-路由与组件

组件:src/views/publish/index.vue

  1. <template>
  2. <div class="publish">Publish</div>
  3. </template>
  4. <script>
  5. export default {
  6. name: 'Publish'
  7. }
  8. </script>
  9. <style scoped lang="less"></style>

路由:src/router/index.vue

  1. import Publish from '@/views/publish'
  1. children: [
  2. { path: '/', component: Home },
  3. { path: '/article', component: Article },
  4. { path: '/publish', component: Publish }
  5. ]

02-发布文章-基础布局

目的:完成发布文章基础布局
大致步骤:

  • 卡片容器 el-card
    • 头部,面板屑
    • 内容,表单组件 el-form
      • 标题 el-input
      • 频道 封装频道组件(空)
      • 封面 el-radio 单选框 + 上传组件(空)
      • 内容 富文本组件(空)
      • 按钮

落地代码:

  1. <template>
  2. <div class='publish-page'>
  3. <el-card>
  4. <div slot="header">
  5. <el-breadcrumb separator-class="el-icon-arrow-right">
  6. <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
  7. <el-breadcrumb-item>发布文章</el-breadcrumb-item>
  8. </el-breadcrumb>
  9. </div>
  10. <!-- 表单 -->
  11. <el-form label-width="120px">
  12. <el-form-item label="标题:">
  13. <el-input v-model="articleForm.title" placeholder="请输入文章标题" style="width:400px"></el-input>
  14. </el-form-item>
  15. <el-form-item label="频道:">频道组件</el-form-item>
  16. <el-form-item label="封面:">
  17. <el-radio-group v-model="articleForm.cover.type">
  18. <el-radio :label="1">单图</el-radio>
  19. <el-radio :label="3">三图</el-radio>
  20. <el-radio :label="0">无图</el-radio>
  21. <el-radio :label="-1">自动</el-radio>
  22. </el-radio-group>
  23. <div>上传组件</div>
  24. </el-form-item>
  25. <el-form-item label="内容:">富文本</el-form-item>
  26. <el-form-item label="">
  27. <el-button type="primary">发布文章</el-button>
  28. <el-button>存入草稿</el-button>
  29. </el-form-item>
  30. </el-form>
  31. </el-card>
  32. </div>
  33. </template>
  34. <script>
  35. export default {
  36. name: 'PublishPage',
  37. data () {
  38. return {
  39. // 表单数据
  40. articleForm: {
  41. title: null,
  42. content: null,
  43. cover: {
  44. type: 1,
  45. images: []
  46. },
  47. channel_id: null
  48. }
  49. }
  50. }
  51. }
  52. </script>
  53. <style scoped lang='less'></style>

03-发布文章-频道组件-功能实现

目的:实现频道组件默认功能,能选择即可。
大致步骤:

  • 在components中新建一个组件,实现频道下拉选择功能。
  • 在main.js中注册为一个全局组件。
  • 在内容管理,发布文章,使用这个组件。

落地代码:

  • 定义组件 src/components/my-channel.vue ```javascript

  1. - 注册组件 src/main.js
  2. ```javascript
  3. // 注册全局组件
  4. import MyChannel from '@/components/my-channel'
  5. Vue.component(MyChannel.name, MyChannel)
  • 使用组件

src/views/article.vue

  1. <el-form-item label="频道:">
  2. + <my-channel></my-channel>
  3. </el-form-item>

总结: 这样只是封装了功能,数据还需要实现双向数据绑定。

04-发布文章-频道组件-双向绑定

目的:实现频道组件双向数据绑定功能,支持v-model指令。
大致步骤:

  • 回顾父子传值,以及v-model语法糖
    • 父组件传递子组件:父 :value=”count” ——> 子 props:[‘value’]
    • 子组件传递父组件:子 $emit(‘input’, 100) ——> 父 @input=”count=$event”
    • 然而 是可以简写成
      • 基于v-model语法糖原理
  • 频道ID父传子
  • 频道ID子传父
  • 使用v-model绑定频道ID

落地代码:

  1. 父传子
  • 父组件

    1. <my-channel :value="reqParams.channel_id"></my-channel>
  • 子组件 ```javascript // props接收的数据,特点:只读

  • props: [‘value’],
    1. ```javascript
    2. <el-select
    3. @change="changeChannel"
    4. v-model="channeId"
    5. :value="value"
  1. 子传父
  • 父组件

    1. <my-channel :value="reqParams.channel_id" @input="reqParams.channel_id=$event"></my-channel>
  • 子组件

    1. data () {
    2. return {
    3. // channelId: null,
    4. channelOptions: []
    5. }
    6. },
    7. created () {
    8. this.getChannelOptions()
    9. },
    10. methods: {
    11. changeChannel (changedChannelId) {
    12. // 通知父组件,频道ID已变化,你也需要改变
    13. this.$emit('input', changedChannelId)
    14. },
  1. v-model的使用
    1. <el-form-item label="频道:">
    2. <!-- 使用my-channel.vue组件 -->
    3. <!-- <my-channel :value="reqParams.channel_id" @input="reqParams.channel_id=$event"></my-channel> -->
    4. <my-channel v-model="reqParams.channel_id"></my-channel>
    5. </el-form-item>
    4)发布文章中使用组件
    1. <el-form-item label="频道:">
    2. <my-channel v-model="articleForm.channel_id"></my-channel>
    3. </el-form-item>
    1. .el-select {
    2. width: 400px;
    3. }
    axios传参:后端提供接口
  • 地址?后键值对 query 对应axios axios({params:参数对象}) axios.get(url,{params:参数对象})
  • 请求体传参 body 对应axios axios({data:参数对象}) axios.post(url, 参数对象) put patch
  • 地址/后参数 路径参数 对应axios axios({url:’进行拼接’})
  • 请求头参数 headers 对应axios axios({headers:参数对象})

    05-发布文章-封面-上传组件

    目的:使用上传组件完成上传图片位置布局和交互
    大致步骤:

  • 查看element-ui文档找到el-upload组件照片墙示例,分析代码

  • 使用el-upload组件
  • 完成根据封面图类型切换组件形态

落地代码:

  • 一张图,三张图才使用组件,上传图片的限制和type的值一致。

    1. <div v-if="articleForm.cover.type===1||articleForm.cover.type===3">
    2. <el-upload
    3. ref="upload"
    4. action="https://jsonplaceholder.typicode.com/posts/"
    5. list-type="picture-card"
    6. :limit="articleForm.cover.type"
    7. >
    8. <i class="el-icon-plus"></i>
    9. </el-upload>
    10. </div>
  • 当类型修改的时候,需要重置选择的图片

    1. <el-radio-group @change="changeCoverType()" v-model="articleForm.cover.type">
    2. <el-radio :label="1">单图</el-radio>
    3. <el-radio :label="3">三图</el-radio>
    4. <el-radio :label="0">无图</el-radio>
    5. <el-radio :label="-1">自动</el-radio>
    6. </el-radio-group>
    1. changeCoverType () {
    2. this.$refs.upload && this.$refs.upload.clearFiles()
    3. },
  • 修改样式,上传组件动画

    1. ::v-deep .el-upload-list__item {
    2. transition: none;
    3. }

    总结: 能够基本使用el-upload组件完成上传图片的布局和交互

    06-发布文章-封面-完成上传

    目的:调用后台接口完成上传和数据保存
    大致步骤:

  • 阅读接口文档得到:地址,参数,返回数据

  • 找到el-upload组件设置 地址,参数,请求头,的属性并且设置
  • 找到el-upload组件设置 上传成功函数,处理成功后的逻辑

落地代码:

  • 配置请求地址,参数,请求头

    1. <el-upload
    2. ref="upload"
    3. :action="`${$http.defaults.baseURL}v1_0/upload`"
    4. name="image"
    5. :headers="{Authorization: `Bearer ${token}`}"
    6. list-type="picture-card"
    7. :limit="articleForm.cover.type"
    8. >
    9. <i class="el-icon-plus"></i>
    10. </el-upload>
    1. // data 中数据
    2. token: auth.getToken()
    1. import auth from '@/utils/auth'
  • 配置上传成功函数,处理数据

    1. <el-upload
    2. ref="upload"
    3. :action="`${$http.defaults.baseURL}v1_0/upload`"
    4. name="image"
    5. :headers="{Authorization: `Bearer ${token}`}"
    6. list-type="picture-card"
    7. :limit="articleForm.cover.type"
    8. :on-success="uploadSuccess"
    9. >
    10. <i class="el-icon-plus"></i>
    11. </el-upload>
    1. uploadSuccess (res) {
    2. this.articleForm.cover.images.push(res.data.url)
    3. }

    切换类型清理数据

    1. changeCoverType () {
    2. this.articleForm.cover.images = []
    3. this.$refs.upload && this.$refs.upload.clearFiles()
    4. },

    07-发布文章-富文本使用

    目的:掌握在vue项目中使用富文本。
    大致步骤:

  • 找到我们需要的富文本组件

  • 安装
  • 注册
  • 使用

落地代码:

import { quillEditor } from ‘vue-quill-editor’ export default { name: ‘PostPage’, components: { quillEditor },

  1. - 使用
  2. ```javascript
  3. <el-form-item label="内容:">
  4. <quill-editor v-model="articleForm.content" :options="editorOption"/>
  5. </el-form-item>
  1. data () {
  2. return {
  3. // 表单数据
  4. articleForm: {
  5. title: null,
  6. content: null,
  7. cover: {
  8. type: 1,
  9. images: []
  10. },
  11. channel_id: null
  12. },
  13. // 富文本配置
  14. + editorOption: {}
  15. }
  16. }
  1. ::v-deep .ql-editor {
  2. min-height: 300px;
  3. }

08-发布文章-基本校验

目的:完成发布文章表单的基本校验功能
大致步骤:

  • 绑定表单数据对象和校验规则对象
  • 准备校验规则对象中具体的规则,
  • 通过prop给表单项添加校验

落地代码:

  • 校验规则对象

    1. // 校验规则
    2. articleRules: {
    3. title: [
    4. { required: true, message: '请输入文章标题', trigger: 'blur' },
    5. { min: 4, max: 50, message: '文章标题4-50字符', trigger: 'blur' }
    6. ],
    7. cover: [
    8. // 需要自定义
    9. ],
    10. channel_id: [
    11. { required: true, message: '请选择文章频道', trigger: 'change' }
    12. ],
    13. content: [
    14. { required: true, message: '请输入文章内容', trigger: 'blur' }
    15. ]
    16. },
  • 使用校验规则 ```javascript

  • 单图 三图 无图 自动
    <el-upload
    1. ref="upload"
    2. action="https://jsonplaceholder.typicode.com/posts/"
    3. name="image"
    4. :headers="{Authorization: `Bearer ${token}`}"
    5. list-type="picture-card"
    6. :limit="articleForm.cover.type"
    7. :on-success="uploadSuccess"
    >
    1. <i class="el-icon-plus"></i>
  • 发布文章 存入草稿 ``` 总结: 还是按照表单校验的套路完成基本的校验功能。

    09-发布文章-特殊校验

    目的:完成特殊表单项的校验,封面图,富文本。
    大致步骤:
  • 完成 文章内容 校验
  • 完成 封面图 校验

落地代码:

  • 完成 文章内容 校验

    1. methods: {
    2. checkContent () {
    3. // 校验内容表单项
    4. this.$refs.articleForm.validateField('content')
    5. },
    1. <el-form-item label="内容:" prop="content">
    2. <quill-editor @blur="checkContent()" v-model="articleForm.content" :options="editorOption"/>
    3. </el-form-item>
    1. +<el-form ref="articleForm" :model="articleForm" :rules="articleRules" label-width="120px">
  • 完成 封面图 校验

    1. // 自定义校验规则
    2. const checkCoverFn = (rule, value, cb) => {
    3. // 自己对value进行校验
    4. if (value.type === 1) {
    5. if (!value.images[0]) {
    6. return cb(new Error('请选择一张封面图'))
    7. }
    8. }
    9. if (value.type === 3) {
    10. if (!(value.images[0] && value.images[1] && value.images[2])) {
    11. return cb(new Error('请选择三张封面图'))
    12. }
    13. }
    14. // 代码走到这,校验成功
    15. cb()
    16. }
    1. cover: [
    2. { validator: checkCoverFn, trigger: 'change' }
    3. ]

    ```javascript // 上传图片成功 uploadSuccess (res) {

    1. // res就是响应数据
    2. this.articleForm.cover.images.push(res.data.url)
  • // 触发一次校验
  • this.$refs.articleForm.validateField(‘cover’) } ```
  • 边界情况:删除的时候需要删除数据还需要进行校验,三张图上传成功切换一张图(清空了图片数据)加一次校验。 ```javascript <el-upload
    1. ref="upload"
    2. :action="$http.defaults.baseURL+'v1_0/upload'"
    3. name="image"
    4. :headers="{Authorization: `Bearer ${token}`}"
    5. :limit="articleForm.cover.type"
    6. :on-success="uploadSuccess"
  • :on-remove=”removeFile” list-type=”picture-card”>
    1. ```javascript
    2. // 删除文件
    3. removeFile (file) {
    4. // 主动:删除images数组中对应的图片
    5. // file 中保存了之前上传图片响应的信息 response.data.url 图片地址
    6. // 根据这个图片地址找到images对应的索引,通过splice(索引,1) 删除图片
    7. const index = this.articleForm.cover.images.findIndex(item => item === file.response.data.url)
    8. this.articleForm.cover.images.splice(index, 1)
    9. // 自己再次校验
    10. this.$refs.articleForm.validateField('cover')
    11. }
    ```javascript // 切换封面类型 changeCoverType () { // 重置数据 this.articleForm.cover.images = [] // 重置组件 this.$refs.upload && this.$refs.upload.clearFiles()
  • // 自己再次校验
  • this.$refs.articleForm.validateField(‘cover’) }, ``` 总结: 遇到校验规则需要自定义使用 validator 配置,遇到非element-ui表单组件自己触发校验方法。

    10-发布文章-进行提交

    目的:能够完成发布文章功能
    大致步骤:
  • 绑定 发布文章 和 存入草稿 按钮点击事件,传入不同标识区别操作
  • 再函数中先校验整体表单,通过后发送请求
  • 成功:提示,跳转列表
  • 失败:提示

落地代码:

  • 绑定事件

    1. <el-form-item label="">
    2. <el-button @click="submit(false)" type="primary">发布文章</el-button>
    3. <el-button @click="submit(true)">存入草稿</el-button>
    4. </el-form-item>
  • 提交函数

    1. submit (draft) {
    2. this.$refs.articleForm.validate(valid => {
    3. if (valid) {
    4. this.$http.post('/v1_0/mp/articles?draft=' + draft, this.articleForm).then(res => {
    5. this.$message.success('发布成功')
    6. this.$router.push('/article')
    7. }).catch(() => {
    8. this.$message.error('发布失败')
    9. })
    10. }
    11. })
    12. }

    11-修改文章-填充数据

    目的:当你是进入编辑文件页面,获取文章详情,填充表单,修改标题和按钮。
    大致步骤:

  • 文章管理页面添加跳转逻辑代码,带上文章ID跳转到文章编辑

  • 从文章管理进入文章编辑,组件初始化的时候,判断是否有ID,获取文章详情数据,填充表单
  • 切换标题,和操作按钮

落地代码:
src/views/article/index.vue

  1. <el-button @click="$router.push('/publish?id='+scope.row.id)" type="primary" icon="el-icon-edit" circle plain></el-button>

src/views/publish/index.vue

  1. created () {
  2. if (this.$route.query.id) {
  3. this.getArticle(this.$route.query.id)
  4. }
  5. },
  6. methods: {
  7. getArticle (id) {
  8. this.$http.get('/v1_0/mp/articles/' + id).then(res => {
  9. this.articleForm = res.data.data
  10. })
  11. },
  1. <el-breadcrumb-item>{{$route.query.id?'修改':'发布'}}文章</el-breadcrumb-item>
  1. <el-form-item label="" v-if="$route.query.id">
  2. <el-button type="primary">修改文章</el-button>
  3. <el-button>存入草稿</el-button>
  4. </el-form-item>
  5. <el-form-item label="" v-else>
  6. <el-button @click="submit(false)" type="primary">发布文章</el-button>
  7. <el-button @click="submit(true)">存入草稿</el-button>
  8. </el-form-item>

填充图片

  1. :file-list="fileList"
  1. computed: {
  2. fileList () {
  3. return this.articleForm.cover.images.map(item=>({name:item,url:item}))
  4. }
  5. }

12-修改文章-路由组件钩子

目的:在由编辑切换发布组件不会初始化,可以使用路由组件钩子
大致步骤:

  • 地址栏参数变化,路径不变化,对应的组件不会重新初始化。
  • 我们使用 beforeRouteUpdate 来监听,编辑和发布 之间的切换。
  • 在钩子函数中根据将要切换的地址,ID就填充表单,没有就重置表单。

落地代码:

  1. beforeRouteUpdate (to, from, next) {
  2. if (to.query.id) {
  3. this.getArticle(to.query.id)
  4. } else {
  5. this.$refs.articleForm.resetFields()
  6. }
  7. next()
  8. },

总结:

  • 何时地址变化不会初始化组件?参数变化,路径不变
  • 如何监听路由参数的变化?beforeRouteUpdate 或者 watch也行

    13-修改文章-进行修改

    目的:完成文章的修改

  • 大致步骤:

    • 绑定 发布文章 和 存入草稿 按钮点击事件,传入不同标识区别操作
    • 再函数中先校验整体表单,通过后发送请求
    • 成功:提示,跳转列表
    • 失败:提示

落地代码:

  • 绑定事件

    1. <el-form-item label="" v-if="$route.query.id">
    2. <el-button @click="update(false)" type="primary">修改文章</el-button>
    3. <el-button @click="update(true)">存入草稿</el-button>
    4. </el-form-item>
  • 进行修改

    1. update (draft) {
    2. this.$refs.articleForm.validate(valid => {
    3. if (valid) {
    4. this.$http.put('/v1_0/mp/articles/' + this.$route.query.id + '?draft=' + draft, this.articleForm).then(res => {
    5. this.$message.success('修改成功')
    6. this.$router.push('/article')
    7. }).catch(() => {
    8. this.$message.error('修改失败')
    9. })
    10. }
    11. })
    12. }

    第七章:项目收尾

    01-项目打包

    目的:能够打包项目启动托管服务,通过路由懒加载优化首屏加载速度。
    大致步骤:

  • 先做一次简单的打包

  • 然后做项目文件托管
  • 最后优化下首屏加载

落地代码:

  • 先做一次简单的打包

    1. npm run build
  • 然后做项目文件托管 npm i serve -g

安装 serve 工具,在 dist 目录执行 serve 通过 http:localhost:5000 访问

  • 最后优化下首屏加载 src/router/index.js

    1. const Login = () => import('@/views/login')
    2. const Layout = () => import('@/views/Layout')
    3. const Home = () => import('@/views/home')
    4. const Article = () => import('@/views/article')
    5. const Publish = () => import('@/views/publish')

    路由懒加载:访问 /login 路由,只去加载登录对应的资源

    02-项目总结

    总结: 项目中重点内容。

  • 使用vue-cli创建项目

  • element-ui需要会用
  • vue-router需要会用
  • axios需要会用,配置,拦截器
  • 登录模块,校验需要使用
  • 首页模块,导航菜单
  • 内容管理,筛选,列表,一套实现
  • 发布文章,频道组件封装,合并修改