前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Egg.js试水 - 文章增删改查【前后端分离】

Egg.js试水 - 文章增删改查【前后端分离】

作者头像
Jimmy_is_jimmy
发布于 2020-10-15 08:59:31
发布于 2020-10-15 08:59:31
3.4K00
代码可运行
举报
文章被收录于专栏:call_me_Rcall_me_R
运行总次数:0
代码可运行

上一篇文章讲的是后端渲染的项目 - Egg.js 试水 - 天气预报。但是没有引入数据库。这次的试水项目是文章的增删改查,将数据库引进,并且实现前后端分离。

项目的github地址是egg-demo/article-project

下面直接进入正题~

项目结构

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
article-project
├── client
├── service
└── README.md
复制代码

因为是前后端分离的项目,那么我们就以文件夹client存放客户端,以文件夹service存放服务端。README.md是项目说明文件。

客户端初始化

为了快速演示,我们使用vue-cli脚手架帮我们生成项目,并引入了vue-ant-design

项目初始化

推荐使用yarn进行包管理。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ npm install -g @vue/cli
# 或者
$ yarn global add @vue/cli
复制代码

然后新建一个项目。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ vue create client
复制代码

接着我们进入项目并启动。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ cd client
$ npm run serve
# 或者
$ yarn run serve
复制代码

此时,我们访问浏览器地址http://localhost:8080/,就会看到欢迎页面。

最后我们引入ant-design-vue

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ npm install ant-design-vue
# 或
$ yarn add ant-design-vue
复制代码

在这里,我们全局引入ant-design-vue的组件。实际开发中,按需引入比较友好,特别是只是使用了该UI框架部分功能组件的时候。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// src/main.js

import Vue from 'vue'
import App from './App.vue'
import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/antd.css'

Vue.use(Antd)
Vue.config.productionTip = false;

new Vue({
  render: h => h(App),
}).$mount('#app');
复制代码

当然,在此项目中,还牵涉到几种npm包,之后只写yarn或者npm命令行操作。

路由设置

路由的跳转需要vue-router的协助。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 路由
$ yarn add vue-router

# 进度条
$ yarn add nprogress
复制代码

这里只用到登录页,首页,文章列表页面和文章的新增/编辑页面。所以我的路由配置如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// src/router/index.js

import Vue from 'vue'
import Router from 'vue-router'
import Index from '@/views/index'
import { UserLayout, BlankLayout } from '@/components/layouts'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style

const whiteList = ['login'] // no redirect whitelist

import { getStore } from "@/utils/storage"

Vue.use(Router)

const router = new Router({
  routes: [
    {
        path: '/',
        name: 'index',
        redirect: '/dashboard/workplace',
        component: Index,
        children: [
          {
            path: 'dashboard/workplace',
            name: 'dashboard',
            component: () => import('@/views/dashboard')
          },
          {
            path: 'article/list',
            name: 'article_list',
            component: () => import('@/views/article/list')
          },
          {
            path: 'article/info',
            name: 'article_info',
            component: () => import('@/views/article/info')
          }
        ]
    },
    {
      path: '/user',
      component: UserLayout,
      redirect: '/user/login',
      // hidden: true,
      children: [
        {
          path: 'login',
          name: 'login',
          component: () => import(/* webpackChunkName: "user" */ '@/views/user/login')
        }
      ]
    },
    {
      path: '/exception',
      component: BlankLayout,
      redirect: '/exception/404',
      children: [
        {
          path: '404',
          name: '404',
          component: () => import(/* webpackChunkName: "user" */ '@/views/exception/404')
        }
      ]
    },
    {
      path: '*',
      component: () => import(/* webpackChunkName: "user" */ '@/views/exception/404')
    }
  ],
  // base: process.env.BASE_URL,
  scrollBehavior: () => ({ y: 0 }),
})

router.beforeEach((to, from, next) => {
  NProgress.start() // start progress bar
  if(getStore('token', false)) { // 有token
    if(to.name === 'index' || to.path === '/index' || to.path === '/') {
      next({ path: '/dashboard/workplace'})
      NProgress.done()
      return false
    }
    next()
  } else {
    if(to.path !== '/user/login') {
      (new Vue()).$notification['error']({
        message: '验证失效,请重新登录!'
      })
    }
    if(whiteList.includes(to.name)) {
      // 在免登录白名单,直接进入
      next()
    } else {
      next({
        path: '/user/login',
        query: {
          redirect: to.fullPath
        }
      })
      NProgress.done()
    }
  }
  next()
})

router.afterEach(route => {
  NProgress.done()
})

export default router
复制代码
接口请求设置

接口请求使用了axios,我们来集成下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# axios
$ yarn add axios
复制代码

我们即将要代理的后端服务的地址是127.0.0.1:7001,所以我们的配置如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// vue.config.js

...
  devServer: {
    host: '0.0.0.0',
    port: '9008',
    https: false,
    hotOnly: false,
    proxy: { // 配置跨域
      '/api': {
        //要访问的跨域的api的域名
        target: 'http://127.0.0.1:7001/',
        ws: true,
        changOrigin: true
      },
    },
  },
...
复制代码

我们封装下请求?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// src/utils/request.js

import Vue from 'vue'
import axios from 'axios'
import store from '@/store'
import notification from 'ant-design-vue/es/notification'
import { ACCESS_TOKEN } from '@/store/mutation-types'
import { notice } from './notice';

const err = (error) => {
  if (error.response) {}
  return Promise.reject(error)
}

function loginTimeOut () {
  notification.error({ message: '登录信息失效', description: '请重新登录' })
  store.dispatch('user/logout').then(() => {
    setTimeout(() => {
      window.location.reload()
    }, 1500)
  })
}

// 创建 auth axios 实例
const auth = axios.create({
  headers: {
    'Content-Type': 'application/json;charset=UTF-8',
    'X-Requested-With': 'XMLHttpRequest'
  },
  baseURL: '/', // api base_url
  timeout: 10000 // 请求超时时间 10秒钟
})

// request interceptor
auth.interceptors.request.use(config => {
  const token = Vue.ls.get(ACCESS_TOKEN)
  if (token) {
    config.headers[ 'Authorization' ] = 'JWT '+ token // 让每个请求携带自定义 token 请根据实际情况自行修改
  }
  return config
}, err)

// response interceptor
auth.interceptors.response.use(
  response => {
    if (response.code === 10140) {
      loginTimeOut()
    } else {
      return response.data
    }
  }, 
  error => { // 错误处理
    console.log(error.response, 'come here')
    if(error.response && error.response.status === 403) {
      notice({
          title: '未授权,你没有访问权限,请联系管理员!',
      }, 'notice', 'error', 5)
      return
    }
    notice({
        title: (error.response && error.response.data && error.response.data.msg) || (error.response && `${error.response.status} - ${error.response.statusText}`),
    }, 'notice', 'error', 5)
  }
)

export {
  auth
}

复制代码
样式预处理器

当然,为了更好的管理你的页面样式,建议还是添加一种CSS预处理器。这里我选择了less预处理器。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# less 和 less-loader
$ yarn add less --dev
$ yarn add less-loader --dev
复制代码

仅仅是安装还不行,我们来配置下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// vue.config.js

...
  css: {
    loaderOptions: {
      less: {
        modifyVars: {
          blue: '#3a82f8',
          'text-color': '#333'
        },
        javascriptEnabled: true
      }
    }
  },
...
复制代码
布局文章页面

文章列表页的骨架:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!--src/views/article/list.vue-->

<template>
  <div class="article-list">
    <a-table
      style="border: none;" 
      bordered
      :loading="loading"
      :rowKey="row => row.id"
      :columns="columns" 
      :data-source="data"
      :pagination="pagination" 
      @change="change"/> 
  </div>
</template>
复制代码

文章编辑/新增页的骨架:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!--src/views/article/info.vue-->

<template>
  <div class="article-info">
    <a-spin :spinning="loading">
      <a-row style="display: flex; justify-content: flex-end; margin-bottom: 20px;">
        <a-button type="primary" @click="$router.go(-1)">返回</a-button>
      </a-row>
      <a-form :form="form" v-bind="formItemLayout">
        <a-form-item
          label="标题">
          <a-input 
            placeholder="请输入标题"
            v-decorator="[
              'title',
              {rules: [{ required: true, message: '请输入标题'}]}
            ]"/>
        </a-form-item>
        <a-form-item
          label="分组">
          <a-select
            showSearch
            v-decorator="[
              'group',
              {rules: [{ required: true, message: '请选择分组'}]}
            ]"
            placeholder="请选择分组">
            <a-select-option value="分组1">分组1</a-select-option>
            <a-select-option value="分组2">分组2</a-select-option>
            <a-select-option value="分组3">分组3</a-select-option>
            <a-select-option value="分组4">分组4</a-select-option>
          </a-select>
        </a-form-item>
        <a-form-item
          label="作者">
          <a-input 
            placeholder="请输入作者"
            v-decorator="[
              'author',
              {rules: [{ required: true, message: '请输入作者'}]}
            ]"/>
        </a-form-item>
        <a-form-item
          label="内容">
          <a-textarea 
            :autosize="{ minRows: 10, maxRows: 12 }"
            placeholder="请输入文章内容"
            v-decorator="[
              'content',
              {rules: [{ required: true, message: '请输入文章内容'}]}
            ]"/>
        </a-form-item>
      </a-form>

      <a-row style="margin-top: 20px; display: flex; justify-content: space-around;">
        <a-button @click="$router.go(-1)">取消</a-button>
        <a-button type="primary" icon="upload" @click="submit">提交</a-button>
      </a-row>
    </a-spin>
  </div>
</template>
复制代码

前端的项目有了雏形,下面搭建下服务端的项目。

服务端初始化

这里直接使用eggjs框架来实现服务端。你可以考虑使用typescript方式的来初始化项目,但是我们这里直接使用javascript而不是它的超级typescript来初始化项目。

初始化项目
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ mkdir service
$ cd service
$ npm init egg --type=simple
$ npm i
复制代码

启动项目:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ npm run dev
复制代码

在浏览器中打开localhost:7001地址,我们就可以看到eggjs的欢迎页面。当然,我们这里基本上不会涉及到浏览器页面,因为我们开发的是api接口。更多的是使用postman工具进行调试。

引入数据库

这里使用的数据库是mysql,但是我们不是直接使它,而是安装封装过的mysql2egg-sequelize

在 Node.js 社区中,sequelize 是一个广泛使用的 ORM 框架,它支持 MySQLPostgreSQLSQLite 和 MSSQL 等多个数据源。它会辅助我们将定义好的 Model 对象加载到 app 和 ctx 上。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 安装mysql
$ yarn add mysql2

# 安装sequelize
$ yarn add egg-sequelize
复制代码

当然,我们需要一个数据库进行连接,那就得安装一个数据库,如果你使用的是mac os的话,你可以通过下面的方法进行安装:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
brew install mysql
brew services start mysql
复制代码

window系统的话,可以考虑下载相关的安装包执行就行了,这里不展开说了。

数据库安装好后,我们管理数据库,可以通过控制台命令行进行控制,也可以通过图形化工具进行控制。我们推荐后者,我们下载了一个Navicat Premiun的工具。

Navicat Premiun 是一款数据库管理工具。

当然还可以下载phpstudy进行辅助开发。

连接数据库

配置数据库的基本信息,前提是我们已经创建好了这个数据库。假设我们创建了一个名为article的数据库,用户是reng,密码是123456。那么,我们就可以像下面这样连接。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// config/config.default.js
...
  config.sequelize = {
    dialect: 'mysql',
    host: '127.0.0.1',
    port: 3306,
    database: 'article',
    username: 'reng',
    password: '123456',
    operatorsAliases: false
  };
...
复制代码

当然,这是通过包egg-sequelize处理的,我们也要将其引入,告诉eggjs去使用这个插件。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// config/plugin.js
...
  sequelize: {
    enable: true,
    package: 'egg-sequelize',
  },
...
复制代码
创建数据库表

你可以直接通过控制台命令行执行mysql语句创建。但是,我们直接使用迁移操作完成。

在项目中,我们希望将所有的数据库Migrations相关的内容都放在database目录下面,所以我们在根目录下新建一个.sequelizerc配置文件:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// .sequelizerc

'use strict';

const path = require('path');

module.exports = {
  config: path.join(__dirname, 'database/config.json'),
  'migrations-path': path.join(__dirname, 'database/migrations'),
  'seeders-path': path.join(__dirname, 'database/seeders'),
  'models-path': path.join(__dirname, 'app/model'),
};
复制代码

初始化Migrations配置文件和目录。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
npx sequelize init:config
npx sequelize init:migrations
复制代码

更加详细内容,可见eggjs sequelize章节。

我们按照官网上的操作初始化了文章列表的数据库表articles。对应的model内容如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// app/model/article.js

'use strict';

module.exports = app => {
  const { STRING, INTEGER, DATE, NOW, TEXT } = app.Sequelize;

  const Article = app.model.define('articles', {
    id: {type: INTEGER, primaryKey: true, autoIncrement: true},//记录id
    title: {type: STRING(255)},// 标题
    group: {type: STRING(255)}, // 分组
    author: {type: STRING(255)},// 作者  
    content: {type: TEXT}, // 内容
    created_at: {type: DATE, defaultValue: NOW},// 创建时间
    updated_at: {type: DATE, defaultValue: NOW}// 更新时间
  }, {
    freezeTableName: true // 不自动将表名添加复数
  });

  return Article;
};
复制代码

API的CRUD

上面服务端的工作,已经帮我们做好编写接口的准备了。那么,下面结合数据库,我们来实现下文章增删改查的操作。

我们使用的是MVC的架构,那么我们的现有代码逻辑自然会这样流向:

app/router.js 获取文章路由到 -> app/controller/article.js中对应的方法 -> 到app/service/article.js中的方法。那么,我们就主要展示在controller层和service层做的事情吧。毕竟router层没啥好讲的。

获取文章列表

get /api/get-article-list

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// app/controller/article.js
...
  async getList() {
    const { ctx } = this
    const { page, page_size } = ctx.request.query
    let lists = await ctx.service.article.findArticle({ page, page_size })
    ctx.returnBody(200, '获取文章列表成功!', {
      count: lists && lists.count || 0,
      results: lists && lists.rows || []
    }, '00000')
  }
...
复制代码
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// app/service/article.js
...
  async findArticle(obj) {
    const { ctx } = this
    return await ctx.model.Article.findAndCountAll({
      order: [['created_at', 'ASC']],
      offset: (parseInt(obj.page) - 1) * parseInt(obj.page_size), 
      limit: parseInt(obj.page_size)
    })
  }
...
复制代码
获取文章详情

get /api/get-article

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// app/controller/article.js
...
  async getItem() {
    const { ctx } = this
    const { id } = ctx.request.query
    let articleDetail = await ctx.service.article.getArticle(id)
    if(!articleDetail) {
      ctx.returnBody(400, '不存在此条数据!', {}, '00001')
      return
    }
    ctx.returnBody(200, '获取文章成功!', articleDetail, '00000')
  }
...
复制代码
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// app/service/article.js
...
  async getArticle(id) {
    const { ctx } = this
    return await ctx.model.Article.findOne({
      where: {
        id
      }
    })
  }
...
复制代码
添加文章

post /api/post-article

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// app/controller/article.js
...
  async postItem() {
    const { ctx } = this
    const { author, title, content, group } = ctx.request.body

    // 新文章
    let newArticle = { author, title, content, group }

    let article = await ctx.service.article.addArticle(newArticle)
    
    if(!article) {
      ctx.returnBody(400, '网络错误,请稍后再试!', {}, '00001')
      return
    }
    ctx.returnBody(200, '新建文章成功!', article, '00000')
  }
...
复制代码
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// app/service/article.js
...
  async addArticle(data) {
    const { ctx } = this
    return await ctx.model.Article.create(data)
  }
...
复制代码
编辑文章

put /api/put-article

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// app/controller/article.js
...
  async putItem() {
    const { ctx } = this
    const { id } = ctx.request.query
    const { author, title, content, group } = ctx.request.body

    // 存在文章
    let editArticle = { author, title, content, group }

    let article = await ctx.service.article.editArticle(id, editArticle)
    
    if(!article) {
      ctx.returnBody(400, '网络错误,请稍后再试!', {}, '00001')
      return
    }
    ctx.returnBody(200, '编辑文章成功!', article, '00000')
  }
...
复制代码
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// app/service/article.js
...
  async editArticle(id, data) {
    const { ctx } = this
    return await ctx.model.Article.update(data, {
      where: {
        id
      }
    })
  }
...
复制代码
删除文章

delete /api/delete-article

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// app/controller/article.js
...
  async deleteItem() {
    const { ctx } = this
    const { id } =  ctx.request.query
    let articleDetail = await ctx.service.article.deleteArticle(id)
    if(!articleDetail) {
      ctx.returnBody(400, '不存在此条数据!', {}, '00001')
      return
    }
    ctx.returnBody(200, '删除文章成功!', articleDetail, '00000')
  }
...
复制代码
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// app/service/article.js
...
  async deleteArticle(id) {
    const { ctx } = this
    return await ctx.model.Article.destroy({
      where: {
        id
      }
    })
  }
...
复制代码

在完成接口的编写后,你可以通过postman 应用去验证下是否返回的数据。

前端对接接口

接下来就得切回来client文件夹进行操作了。我们在上面已经简单封装了请求方法。这里来编写文章CRUD的请求方法,我们为了方便调用,将其统一挂载在Vue实例下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// src/api/index.js

import article from './article'

const api = {
  article
}

export default api

export const ApiPlugin = {}

ApiPlugin.install = function (Vue, options) {
  Vue.prototype.api = api // 挂载api在原型上
}
复制代码
获取文章列表
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// src/api/article.js
...
  export function getList(params) {
    return auth({
      url: '/api/get-article-list',
      method: 'get',
      params
    })
  }
...
复制代码
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// src/views/article/list.vue
...
  getList() {
    let vm = this
    vm.loading = true
    vm.api.article.getList({
      page: vm.pagination.current,
      page_size: vm.pagination.pageSize
    }).then(res => {
      if(res.code === '00000'){
        vm.pagination.total = res.data && res.data.count || 0
        vm.data = res.data && res.data.results || []
      } else {
        vm.$message.warning(res.msg || '获取文章列表失败')
      }
    }).finally(() => {
      vm.loading = false
    })
  }
...
复制代码
获取文章详情
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// src/api/article.js
...
  export function getItem(params) {
    return auth({
      url: '/api/get-article',
      method: 'get',
      params
    })
  }
...
复制代码
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// src/views/article/info.vue
...
  getDetail(id) {
    let vm = this
    vm.loading = true
    vm.api.article.getItem({ id }).then(res => {
      if(res.code === '00000') {
        // 数据回填
        vm.form.setFieldsValue({
          title: res.data && res.data.title || undefined,
          author: res.data && res.data.author || undefined,
          content: res.data && res.data.content || undefined,
          group: res.data && res.data.group || undefined,
        })
      } else {
        vm.$message.warning(res.msg || '获取文章详情失败!')
      }
    }).finally(() => {
      vm.loading = false
    })
  },
...
复制代码
添加文章
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// src/api/article.js
...
  export function postItem(data) {
    return auth({
      url: '/api/post-article',
      method: 'post',
      data
    })
  }
...
复制代码
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// src/views/article/info.vue
...
  submit() {
    let vm = this
    vm.loading = true
    vm.form.validateFields((err, values) => {
      if(err){
        vm.loading = false
        return
      }
      let data = {
        title: values.title,
        group: values.group,
        author: values.author,
        content: values.content
      }
      vm.api.article.postItem(data).then(res => {
        if(res.code === '00000') {
          vm.$message.success(res.msg || '新增成功!')
          vm.$router.push({
            path: '/article/list'
          })
        } else {
          vm.$message.warning(res.msg || '新增失败!')
        }
      }).finally(() => {
        vm.loading = false
      })
    })
  },
...
复制代码
编辑文章
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// src/api/article.js
...
  export function putItem(params, data) {
    return auth({
      url: '/api/put-article',
      method: 'put',
      params,
      data
    })
  }
...
复制代码
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// src/views/article/info.vue
...
  submit() {
    let vm = this
    vm.loading = true
    vm.form.validateFields((err, values) => {
      if(err){
        vm.loading = false
        return
      }
      let data = {
        title: values.title,
        group: values.group,
        author: values.author,
        content: values.content
      }
      vm.api.article.putItem({id: vm.$route.query.id}, data).then(res => {
        if(res.code === '00000') {
          vm.$message.success(res.msg || '新增成功!')
          vm.$router.push({
            path: '/article/list'
          })
        } else {
          vm.$message.warning(res.msg || '新增失败!')
        }
      }).finally(() => {
        vm.loading = false
      })
    })
  }
...
复制代码
删除文章
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// src/api/article.js
...
  export function deleteItem(params) {
    return auth({
      url: '/api/delete-article',
      method: 'delete',
      params
    })
  }
...
复制代码
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// src/views/article/list.vue
...
  delete(text, record, index) {
    let vm = this
    vm.$confirm({
      title: `确定删除【${record.title}`,
      content: '',
      okText: '确定',
      okType: 'danger',
      cancelText: '取消',
      onOk() {
        vm.api.article.deleteItem({ id: record.id }).then(res => {
          if(res.code === '00000') {
            vm.$message.success(res.msg || '删除成功!')
            vm.handlerSearch()
          } else {
            vm.$message.warning(res.msg || '删除失败!')
          }
        })
      },
      onCancel() {},
    })
  }
...
复制代码

效果图

egg-demo/article-project/client/前端项目中,页面包含了登录页面,欢迎页面和文章页面。

欢迎页面忽略不计

登录页

文章列表

文章编辑

后话

至此,整个项目已经完成。代码仓库为egg-demo/article-project/,感兴趣可以进行扩展学习。

下一篇文章,我们讲讲mysql

更多的内容见Jimmy Github

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
Java基本数据类型、包装类及拆装箱详解
Java的基本数据类型和对应的包装类是Java语言中处理数据的两个关键概念。基本数据类型提供了简单而高效的方式来存储数据,而包装类使得基本数据类型具有对象的特性。本文将深入探讨基本数据类型与包装类的应用场景及详细描述,并对自动拆箱和装箱的源码实现进行分析。
修己xj
2023/12/05
6500
Java基本数据类型、包装类及拆装箱详解
Java学习【深入探索包装类和泛型】
在Java的学习中,包装类和泛型是两个重要的概念,它们不仅丰富了Java的数据类型,还提高了代码的可读性和安全性。下面,我们将深入探讨这两个主题。
2的n次方
2024/10/15
870
Java学习【深入探索包装类和泛型】
【Java】什么是泛型?什么是包装类
Java是一个面向对象的编程语言,但其基本数据类型(如int、char、boolean等)并不直接支持面向对象的特性。为了弥补这一不足,Java为每种基本数据类型设计了一个对应的类,这些类统称为包装类(Wrapper Class)。包装类均位于java.lang包中。
椰椰椰耶
2024/10/15
1380
【Java】什么是泛型?什么是包装类
包装类和泛型
在Java中,由于基本;类型不是继承自Object,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了一个包装类型。
用户11070251
2024/04/11
1320
【数据结构】包装类和泛型
除了int的包装类是Integer,char的包装类是Character,其他的基本类型对应的包装类都是将首字母大写。
用户11162265
2024/08/05
980
【数据结构】包装类和泛型
《JavaSE》---21.<简单认识Java的集合框架&包装类&泛型>
其主要表现为将多个元素 element 置于一个单元中,用于对这些元素进行快速、便捷的存储 store 、检索 retrieve 、 管理 manipulate ,即平时我们俗称的增删查改 CRUD 。
用户11288958
2024/09/24
1750
《JavaSE》---21.<简单认识Java的集合框架&包装类&泛型>
数据结构-1.初始包装类与泛型
在 Java 中,由于基本类型不是继承自 Object ,为了在泛型代码中可以支持基本类型, Java 给每个基本类型都对应了一个包装类型.
用户11369350
2024/11/19
600
知识改变命运——【数据结构】包装类&简单认识泛型
在Java中,由于基本类型不是继承自Object,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了 一个包装类型。
用户11319080
2024/10/17
720
知识改变命运——【数据结构】包装类&简单认识泛型
Java 泛型深入解析:类型安全与灵活性的平衡
Java 泛型(Generics)是一个强大的语言特性,它允许在类、接口和方法中使用参数化类型,从而实现代码的重用、增强类型安全性,并提升代码的可读性。泛型的引入解决了 Java 编程中常见的类型转换问题,使得我们能够编写更加灵活且健壮的代码。然而,泛型背后的类型擦除(Type Erasure)机制和一些高级特性也给我们带来了一定的挑战。
科技新语
2024/10/11
1790
Java 泛型深入解析:类型安全与灵活性的平衡
【数据结构】包装类&泛型
小编在这里设置了一个上界comparable,可以进行comparaTo方法的调用,当然改为整型也是可以的。 
用户11288949
2024/09/24
970
【数据结构】包装类&泛型
Java中的泛型(很细)
非常好,让我们深入探讨Java中的泛型这个重要主题。我将按照之前提供的框架,为您创作一篇全面而专业的技术博客文章。
程序员朱永胜
2024/07/18
2250
Java中的泛型(很细)
Java 中文官方教程 2022 版(六)
大多数情况下,如果您使用单个字符值,您将使用基本的char类型。然而,有时您需要将 char 用作对象—例如,作为期望对象的方法参数。Java 编程语言为此提供了一个包装类,将char包装在Character对象中。Character类型的对象包含一个类型为char的单个字段。这个Character类还提供了许多有用的类(即静态)方法来操作字符。
ApacheCN_飞龙
2024/05/24
2580
Java 中文官方教程 2022 版(六)
包装类与泛型,到底区别在哪?
在 Java 中,基本数据类型是不具有对象特性的,不支持面向对象的操作。但是,在某些情况下,我们需要将基本数据类型作为对象来操作,这时就需要使用包装类。
网络技术联盟站
2023/06/04
1910
Java 泛型:理解和应用
这就是泛型的概念,是 Java 后期的重大变化之一。泛型实现了参数化类型,可以适用于多种类型。泛型为 Java 的动态类型机制提供很好的补充,但是 Java 的泛型本质上是一种高级语法糖,也存在类型擦除导致的信息丢失等多种缺点,我们可以在本篇文章中深度探讨和分析。
phoenix.xiao
2023/08/28
2800
Java 泛型:理解和应用
Java泛型深入理解「建议收藏」
在面向对象编程语言中,多态算是一种泛化机制。例如,你可以将方法的参数类型设置为基类,那么该方法就可以接受从这个基类中导出的任何类作为参数,这样的方法将会更具有通用性。此外,如果将方法参数声明为接口,将会更加灵活。
全栈程序员站长
2022/09/10
8800
Java基础(十四):包装类
冬天vs不冷
2025/01/21
950
Java基础(十四):包装类
java基础之泛型
泛型 术语 "?"通配符 通配符的扩展 自定义泛型方法 "擦除"实例 类型参数的类型推断 自定义泛型类 泛型方法和泛型类的比较 泛型和反射 通过反射获得泛型的实际类型参数 本文对泛型的基本
xiangzhihong
2018/02/01
1.1K0
java基础之泛型
如何使用 Java 泛型来避免 ClassCastException
泛型是相关语言特性的集合,它允许类或方法对各种类型的对象进行操作,同时提供编译时类型安全性检查
Java宝典
2021/01/28
2.2K0
Java 比较器 和 包装类
Java Comparable接口强行对实现它的每个类的对象进行整体排序 这种排序被称为:自然排序
Java_慈祥
2024/08/06
1710
Java 比较器 和 包装类
Java包装类,基本的装箱与拆箱
将原始类型和包装类分开以保持简单。当需要一个适合像面向对象编程的类型时就需要包装类。当希望数据类型变得简单时就使用原始类型。
JanYork_简昀
2022/03/31
5260
Java包装类,基本的装箱与拆箱
相关推荐
Java基本数据类型、包装类及拆装箱详解
更多 >
LV.1
这个人很懒,什么都没有留下~
目录
  • 项目结构
  • 客户端初始化
    • 项目初始化
    • 路由设置
    • 接口请求设置
    • 样式预处理器
    • 布局文章页面
  • 服务端初始化
    • 初始化项目
    • 引入数据库
    • 连接数据库
    • 创建数据库表
  • API的CRUD
    • 获取文章列表
    • 获取文章详情
    • 添加文章
    • 编辑文章
    • 删除文章
  • 前端对接接口
    • 获取文章列表
    • 获取文章详情
    • 添加文章
    • 编辑文章
    • 删除文章
  • 效果图
    • 登录页
    • 文章列表
    • 文章编辑
  • 后话
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档