Koa & Mongoose & Vue實現先後端分離--12聯表查詢

上節回顧

  • 圖片上傳 & 存儲 & 訪問

工做內容

  • 審批的增刪改查
  • 更新、刪除數據時,校驗當前用戶是否是數據所屬人
  • 經過ref查詢另外一表中的數據

準備工做

  • npm i -S moment //分別切換到/server/client目錄下安裝
  • 把服務端/users的路由和處理邏輯拷一份,改更名
  • 這裏主要是羅列代碼

業務邏輯

服務端代碼

  • 路由處理邏輯
// 新建文件:server/control/approves.js
const moment =  require('moment');
const approveModel =  require('../model/approve');
const userModel =  require('../model/user');

async  function  list(ctx) {
    // 【一般作法,由多的一方過濾單一的一方】
    // 文檔:http://www.mongoosejs.net/docs/populate.html#refs-to-children
    try {
        const approves = await approveModel.find({
            author: ctx.state.auth.id
        });
        ctx.body = {
            code:  '200',
            data: approves,
            msg:  '查詢成功'
        };
    } catch (err) {
        ctx.body = {
            code:  '403',
            data: {
                error: err
            },
            msg:  '查詢失敗'
        }
    }
}

async  function  get(ctx){
    const { id } = ctx.params;
    try {
        const approve = await approveModel.findOne({
            _id: id
        });
        if(approve) {
            ctx.body = {
                code:  '200',
                data: approve,
                msg:  '成功'
            }
        } else {

            ctx.body = {
                code:  '403',
                data: null,
                msg:  '找不到數據建立人'
            }
        }
    } catch (err) {
        ctx.body = {
            code:  '404',
            data: {
                _id: id
            },
            msg:  '獲取失敗,請覈對數據id'
        }
    }
}

async  function  create(ctx) {
    const { id: loginerId } = ctx.state.auth;
    const payload = ctx.request.body;
    const curtime =  moment().format('x');
    try {
        const newApprove = await  new approveModel({
            ...payload,
            status: false,
            author: loginerId,
            modifier: loginerId,
            createtime: curtime,
            latesttime: curtime,
        }).save();
        ctx.body = {
            code:  '200',
            data: newApprove,
            msg:  '新建成功'
        }
    } catch (err) {
        ctx.body = {
            code:  '403',
            data: null,
            msg:  '新建失敗'
        }
    }
}

async  function  update(ctx){
    const payload = ctx.request.body;
    const { id } = ctx.params;
    const curtime =  moment().format('x');
    try {
        await approveModel.updateOne(
            {
                _id: id
            },
            {
                ...payload,
                latesttime: curtime
            }
        ).exec();
        ctx.body= {
            code:  '200',
            data: {
                _id: id
            },
            msg:  '更新成功'
        }
    } catch (err) {
        ctx.body = {
            code:  '404',
            data: payload,
            msg:  '更新失敗,請覈對數據id'
        }
    }
}

async  function  drop(ctx){
    const { id } = ctx.params;
    await approveModel.findOneAndRemove({
        _id: id
    })
    ctx.body = {
        code:  '200',
        data: null,
        msg:  '刪除成功'
    }
} 

module.exports = {
    list,
    get,
    create,
    update,
    drop
}
  • 路由攔截css

    • 在進行敏感操做,如更新、刪除,先判斷當前用戶是不是數據所屬人checkLoginer
// 更新文件:server/router/approves.js
const Router =  require('@koa/router');
const approveModel =  require('../model/approve');
const controls =  require('../control/approves');
const routerUtils =  require('../utils/router');
const {
    list,
    get,
    create,
    update,
    drop
} = controls;
const router =  new Router({
    prefix:  '/approves'
});

async  function  checkLoginer  (ctx, next) {
    const { id } = ctx.params;
    const approve = await approveModel.findOne({
        _id: id
    });
    if (approve.author != ctx.state.auth.id) {
        ctx.body = {
            code:  '403',
            data: null,
            msg:  '當前用戶不是建立者'
        }
    } else {
        await  next()
    }
}
const routes = [
    {
        path:  '/',
        method:  'GET',
        handle: list
    },
    {
        path:  '/',
        method:  'POST',
        handle: create
    },
    {
        path:  '/:id',
        method:  'GET',
        handle: get
    },
    {
        path:  '/:id',
        method:  'PATCH',
        payload: {},
        handle: update,
        middlewares: [
            checkLoginer
        ]
    },
    {
        path:  '/:id',
        method:  'DELETE',
        handle: drop,
        middlewares: [
            checkLoginer
        ]
    }
]
routerUtils.register.call(router, routes);
module.exports = router;
  • 更新路由配置文件
// 更新文件:server/router/index.js
const userRouter =  require('./users');
const assetsRouter =  require('./assets');
const approvesRouter =  require('./approves');

module.exports = [
    userRouter.routes(),
    userRouter.allowedMethods(),
    approvesRouter.routes(),
    approvesRouter.allowedMethods(),
    assetsRouter.routes(),
    assetsRouter.allowedMethods()
];
  • 測試新建:[動態太大,傳不上來]
  • 測試查詢

list.gif

  • 測試更新:

update.gif

  • 測試刪除:

delete.gif

前端代碼

// 更新文件:client/src/views/approve-panel/index.vue
<template\>

<div  class\="approve-module"\>

<div  class\="btns-wrap"\>

<el-button  type\="primary" @click\="handleApprove('increase')"\>新建</el-button\>

</div\>

<el-table

:data\="table.tableBody"

border

@selection-change\="handleSelectionChange"

style\="width: 100%"\>

<template  v-for\="header  in  table.tableHeader"\>

<el-table-column

v-if\="header.key === 'selection'"

:key\="header.key"

v-bind\="Object.assign({}, header.options, header.layout)"

\>

</el-table-column\>

<el-table-column

v-else

:key\="header.key"

:label\="header.metas.label"

v-bind\="Object.assign({}, header.options, header.layout)"\>

<template  slot-scope\="scope"\>

<span  v-if\="header.metas.type === 'text'"\>{{scope.row\[header.key\]}}</span\>

<el-button-group  v-else-if\="header.metas.type === 'button'"\>

<template  v-for\="btn  in  header.metas.value"\>

<el-button :class\="\`btn-${btn.key}\`" @click\="handleApprove(btn.key, scope.row)"  v-if\="btn.attributes.visible"  size\="small"  type\="text" :key\="btn.key"  v-bind\="btn.attributes"\>{{btn.label}}</el-button\>

</template\>

</el-button-group\>

<span  v-else\>{{header.metas.formatter(scope.row\[header.key\])}}</span\>

</template\>

</el-table-column\>

</template\>

</el-table\>

<el-dialog :title\="dialogForm.title" :visible.sync\="dialogForm.visible"\>

<el-form :inline\="false" :ref\="dialogForm.formRef" :model\="dialogForm.form" :rules\="dialogForm.rules"\>

<el-form-item  label\="名稱"  prop\="name"\>

<el-input  v-model\="dialogForm.form.name"\></el-input\>

</el-form-item\>

<el-form-item  label\="分類"  prop\="category"\>

<el-select  v-model\="dialogForm.form.category"  placeholder\="請選擇分類"\>

<el-option  label\="年假"  value\="1"\></el-option\>

<el-option  label\="病假"  value\="2"\></el-option\>

</el-select\>

</el-form-item\>

<el-form-item  label\="描述"  prop\="description"\>

<el-input  type\="textarea"  v-model\="dialogForm.form.description"\></el-input\>

</el-form-item\>

</el-form\>

<div  slot\="footer"  class\="dialog-footer"\>

<el-button @click\="cancelOperate"\>取 消</el-button\>

<el-button  type\="primary" @click\="submitForm"\>確 定</el-button\>

</div\>

</el-dialog\>

</div\>

</template\>

  

<script\>

import  moment  from  'moment'

import  http  from  '@/utils/http'

  

export  default  {

INITFORMDATA:  {

name:  '',

category:  '',

description:  '',

status:  false,

latesttime:  0,

createtime:  0,

author:  {},

},

methods:  {

async  init  ()  {

const  res  \=  await  http.get('/approves')

if (res.code  \===  '200') {

this.$set(this.table,  'tableBody',  res.data)

}

},

handleSelectionChange  (val)  {

console.log(val)

},

handleClick  (row)  {

console.log(row)

},

handleApprove  (type,  initData)  {

this\[\`${type}Handle\`\](initData)

},

cancelOperate  ()  {

this.dialogForm  \=  Object.assign(

{},

this.dialogForm,

{

visible:  false,

formRef:  '',

form:  this.$options.INITFORMDATA

}

)

},

increaseHandle  (initData  \=  {})  {

this.dialogForm  \=  Object.assign(

this.dialogForm,

{

visible:  true,

title:  '新建',

formRef:  'increaseForm'

}

)

},

editHandle  (initData  \=  {})  {

this.dialogForm  \=  Object.assign(

this.dialogForm,

{

visible:  true,

title:  '編輯',

formRef:  'editForm',

form:  {

...initData

}

}

)

},

dropHandle  (initData  \=  {})  {

this.$confirm('此操做將永久刪除該數據, 是否繼續?',  '提示',  {

confirmButtonText:  '肯定',

cancelButtonText:  '取消',

type:  'warning'

}).then(async  ()  \=>  {

const  res  \=  await  http.delete(\`/approves/${initData.\_id}\`)

if (res.code  \===  '200') {

this.$message({

type:  'success',

message:  '刪除成功!'

})

this.init()

}  else  {

throw(new Error('發生錯誤'))

}

}).catch((err)  \=>  {

this.$message({

type:  'info',

message:  err.message  ||  '已取消刪除'

});

});

},

async  submitForm  ()  {

try  {

const  valid  \=  this.$refs\[this.dialogForm.formRef\].validate()

if (this.dialogForm.formRef  \===  'increaseForm') {

const  res  \=  await  http.post(

'/approves',

{

...this.dialogForm.form

}

)

if (res.code  \===  '200') {

this.$message({

type:  'success',

message:  '新建成功'

})

}  else  {

this.$message({

type:  'error',

message:  res.msg

})

}

}  else  {

const  {  \_id,  name,  category,  description,  status}  \=  this.dialogForm.form

const  res  \=  await  http.patch(

\`/approves/${\_id}\`,

{

name,

category,

description,

status

}

)

if (res.code  \===  '200') {

this.$message({

type:  'success',

message:  '更新成功'

})

}  else  {

this.$message({

type:  'error',

message:  res.msg

})

}

}

this.cancelOperate()

this.init()

}  catch (err) {

console.log(err)

}

}

},

data  ()  {

return  {

table:  {

tableHeader: \[

{

key:  'selection',

metas:  {

},

options:  {

type:  'selection'

},

layout:  {

}

},

{

key:  'name',

metas:  {

label:  '名稱',

type:  'text'

},

options:  {

'show-overflow-tooltip':  true

},

layout:  {

width:  200

}

},

{

key:  'category',

metas:  {

label:  '類別',

type:  'text'

},

layout:  {

width:  60

}

},

{

key:  'description',

metas:  {

label:  '描述',

type:  'text'

},

options:  {

'show-overflow-tooltip':  true

},

layout:  {

}

},

{

key:  'author',

metas:  {

label:  '建立人',

type:  'object',

formatter  (val)  {

return  val.alias  ||  val.account

}

},

layout:  {

width:  80

}

},

{

key:  'createtime',

metas:  {

label:  '建立時間',

type:  'timestamp',

formatter  (val)  {

return  moment(val).format('YYYY-MM-DD hh:mm')

}

},

layout:  {

width:  150

}

},

{

key:  'latesttime',

metas:  {

label:  '更新時間',

type:  'timestamp',

formatter  (val)  {

return  moment(val).format('YYYY-MM-DD HH:mm')

}

},

layout:  {

width:  150

}

},

{

key:  'operate',

metas:  {

label:  '操做',

type:  'button',

value: \[

{

key:  'edit',

label:  '編輯',

attributes:  {

visible:  true,

disabled:  false

}

},

{

key:  'drop',

label:  '刪除',

attributes:  {

visible:  true,

disabled:  false

}

}

\]

},

layout:  {

fixed:  'right',

width:  100

}

}

\],

tableBody: \[\]

},

dialogForm:  {

visible:  false,

title:  '',

formRef:  '',

form:  {

name:  '',

category:  '',

description:  '',

status:  false

},

rules:  {

name: \[

{  required:  true,  message:  '請輸入名稱',  trigger:  'blur'  }

\],

category: \[

{  required:  true,  message:  '請選擇分類',  trigger:  'change'  }

\],

description: \[\]

}

}

}

},

async  created  ()  {

this.init()

}

}

</script\>

<style  lang\="scss"  scoped\>

@import  '~@/stylesheets/layout.scss';

@import  './index.scss';

</style\>
  • 這代碼符號被轉義…格式…建議複製到編輯器內,全局替換並格式化一下。
// 新建文件:client/src/views/approve-panel/index.scss
.approve-module  {
    .btns-wrap  {
        margin-bottom:  20px;
        @include  flex($content:  flex-end);
    }
    /deep/ {
        .btn-drop  {
            margin-left:  16px;
        }
    }
}

展現效果:
image.pnghtml

這裏「建立人」是具體的用戶名,上傳和存儲的時候都是用戶ID,如何經過用戶ID查找用戶具體信息:前端

// 更新文件:server/control/approves.js
...
async function list (ctx) {
  ...
  try {
    const approves = await approveModel.find({
        author: ctx.state.auth.id
    }).populate('author');
    ctx.body = {
        code:  '200',
        data: approves,
        msg:  '查詢成功'
    };
  }
  ...
}
...
async function get (ctx) {
  const { id } = ctx.params;
  try {
    const approve = await approveModel.findOne({
      _id: id
  }).populate({
    path:  'author',
    select:  '+avatar +alias +telephone +email +department +job +role +_id +__v'
  }).exec();
  ...
}
  • 使用populateserver/model/approve.js文件中的
author: {
    type:  Schema.Types.ObjectId,
    ref:  'User',
    required: true
},

對應使用,可查出用戶信息。
測試結果:[動態太大,傳不上來]
image.pngvue

發現,描述沒有顯示出來,這是由於description沒有被查出,修改server/control/approves.jslist部分代碼.populate('author').select('+description');npm

參考文檔

一對多關係app

相關文章
相關標籤/搜索