Koa & Mongoose & Vue实现前后端分离--10更新用户信息

米花儿团儿

上节回顾

  • JWT相关

工作内容

  • 更新用户数据

准备工作

业务逻辑

页面调用用户信息

//更新文件:client/src/views/personal-panel/index.vue
...
<script>
import http from '@/utils/http'

export default {
  data () {
    return {
      loginer: {}
    }
  },
  async created () {
    const { id } = this.$store.state.loginer
    const res = await http.get(`/users/${id}`)
    this.loginer = res.data
  }
}
</script>

在登录的时候,已经将登录用户信息存储到vuex中,通过用户ID查询用户详细信息。

服务端查询用户服务

// 更新文件: server/router/users.js
...
const { list, get, register, login, update } = require('../control/users');
...
  {
    path: '/:id',
    method: 'GET',
    handle: get
  },
...
// 更新文件:server/control/users.js
async function get (ctx) {
  const { id } = ctx.params;
  try {
    const user = await userModel.findOne({
      _id: id
    }).select('+telephone +email'); //新增查询除默认外的字段
    if (user) {
      ctx.body = {
        code: '200',
        data: user,
        msg: '成功'
      }
      return;
    }
    throw('请核对参数')
  } catch (err) {
    ctx.body = {
      code: '403',
      data: null,
      msg: err.message
    }
  }
}
...
module.exports = {
...
  get,
...
}
  • 其中,userModel.findOne({_id: id})默认查出的是Schema没有设置select: false的字段,如果需要其它字段的话,需要userModel.findOne({_id: id}).select('+telephone +email');新增字段
  • 通过ctx.params获取匹配路由:id参数。

前端展示布局

// 更新文件:client/src/views/personal-panel/index.vue
<template>
  <div class="panel-container">
    <div class="panel-avatar">
      <el-upload
        class="avatar-uploader"
        v-bind="uploadForm">
        <img v-if="imageUrl" :src="imageUrl" class="avatar">
        <i v-else class="el-icon-user"></i>
      </el-upload>
    </div>
    <div class="panel-main">
      <h3 class="panel-title">{{loginer.alias || loginer.account}}</h3>
      <ul class="panel-content">
        <li class="panel-item">
          <label class="panel-item-lanel">帐号:</label>
          <span class="panel-item-value">{{loginer.account}}</span>
        </li>
        <li class="panel-item">
          <label class="panel-item-lanel">邮箱:</label>
          <span class="panel-item-value">{{loginer.email}}</span>
        </li>
        <li class="panel-item">
          <label class="panel-item-lanel">电话:</label>
          <span class="panel-item-value">{{loginer.telephone}}</span>
        </li>
      </ul>
      <div class="panel-controls">
        <el-button class="btn-edit" type="default" @click="openFormDialog">编辑</el-button>
      </div>
    </div>
  </div>
</template>
<script>
import http from '@/utils/http'

export default {
  methods: {
    openFormDialog () {

    }
  },
  data () {
    return {
      uploadForm: {
        action: ''
      },
      imageUrl: '',
      loginer: {}
    }
  },
  async created () {
    const { id } = this.$store.state.loginer
    const res = await http.get(`/users/${id}`)
    this.loginer = res.data
  }
}
</script>
<style lang="scss" scoped>
@import '~@/stylesheets/layout.scss';
@import './index.scss';
</style>
// 新建文件:client/src/views/personal-panel/index.scss
.panel-container {
  @include flex($item: flex-start);

  .panel-avatar {
    width: 160px;
  }
  .panel-main {
    flex: 1;
    color: #525975;
    line-height: 2;
    padding: 0 40px;
    font-size: 14px;

    .panel-title {
      color: #2e3242;
      font-size: 24px;
    }
  }
  .panel-controls {
    margin-top: 20px;
  }
}
.avatar-uploader{
  /deep/ {
    .el-upload {
      border: 1px dashed #d9d9d9;
      border-radius: 100px;
      cursor: pointer;
      position: relative;
      overflow: hidden;
    }

    .el-upload:hover {
      border-color: #409EFF;
    }

    .el-icon-user {
      font-size: 70px;
      color: #8c939d;
      width: 160px;
      height: 160px;
      line-height: 160px;
      text-align: center;
    }

    .avatar {
      width: 160px;
      height: 160px;
      display: block;
    }
  }
}

展示效果
image.png

前端编辑用户信息

// 更新文件:client/src/views/personal-panel/index.vue
<template>
  <div class="panel-container">
    <div class="panel-avatar">
      <el-upload
        class="avatar-uploader"
        v-bind="uploadForm">
        <img v-if="imageUrl" :src="imageUrl" class="avatar">
        <i v-else class="el-icon-user"></i>
      </el-upload>
    </div>
    <div class="panel-main">
      <h3 class="panel-title">{{loginer.alias || loginer.account}}</h3>
      <ul class="panel-content">
        <li class="panel-item">
          <label class="panel-item-lanel">帐号:</label>
          <span class="panel-item-value">{{loginer.account}}</span>
        </li>
        <li class="panel-item">
          <label class="panel-item-lanel">邮箱:</label>
          <span class="panel-item-value">{{loginer.email}}</span>
        </li>
        <li class="panel-item">
          <label class="panel-item-lanel">电话:</label>
          <span class="panel-item-value">{{loginer.telephone}}</span>
        </li>
      </ul>
      <div class="panel-controls">
        <el-button class="btn-edit" type="default" @click="openFormDialog">编辑</el-button>
      </div>
    </div>
    <el-dialog :title="dialogForm.title" :visible.sync="dialogForm.visible">
      <el-form :ref="dialogForm.formRef" :model="dialogForm.form" :rules="dialogForm.rules">
        <el-form-item label="帐号" prop="account">
          <el-input v-model="dialogForm.form.account"></el-input>
        </el-form-item>
        <el-form-item label="昵称" prop="alias">
          <el-input v-model="dialogForm.form.alias"></el-input>
        </el-form-item>
        <el-form-item label="邮箱" prop="email">
          <el-input type="email" v-model="dialogForm.form.email"></el-input>
        </el-form-item>
        <el-form-item label="电话" prop="telephone">
          <el-input type="telephone" v-model="dialogForm.form.telephone"></el-input>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="dialogForm.visible = false">取 消</el-button>
        <el-button type="primary" @click="submitForm">确 定</el-button>
      </div>
    </el-dialog>
  </div>
</template>
<script>
import http from '@/utils/http'

export default {
  methods: {
    openFormDialog () {
      this.dialogForm.visible = true
    },
    async submitForm () {
      try {
        const valid = await this.$refs[this.dialogForm.formRef].validate()
        if (valid) {
          const res = await http.patch(
            `/users/${this.userId}`,
            {
              ...this.dialogForm.form
            }
          )
          if (res.code === '200') {
            const { account, alias } = res.data
            this.$store.commit(
              'putLoginer',
              {
                account,
                alias
              }
            ) // 更新vuex状态
            this.loginer = Object.assign(
              {},
              this.loginer,
              {
                ...res.data
              }
            ) // 更新展示的默认值
            this.$set(this.dialogForm, 'form', Object.assign(
              {},
              this.dialogForm.form,
              {
                ...res.data
              }
            )) // 更新弹窗默认值
            this.dialogForm.visible = false
          } else {
            this.$message({
              type: 'error',
              message: res.msg
            })
          }
        }
      } catch (err) {
        console.error(err)
      }
    }
  },
  data () {
    return {
      uploadForm: {
        action: ''
      },
      imageUrl: '',
      loginer: {},
      dialogForm: {
        visible: false,
        title: '编辑',
        formRef: 'dialogForm',
        form: {
          account: '',
          alias: '',
          email: '',
          telephone: ''
        },
        rules: {
          account: [
            { required: true, message: '请输入帐号', trigger: 'blur' },
            { min: 5, message: '长度至少5个字符', trigger: 'blur' }
          ],
          alias: [
            { required: true, message: '请输入昵称', trigger: 'blur' },
            { min: 1, max: 15, message: '长度不能超过十五个字符', trigger: 'blur'}
          ],
          email: [
            { type: 'email', required: true, message: '请输入邮箱', trigger: 'blur' }
          ],
          telephone: [
            { required: true, message: '请输入手机号码', trigger: 'blur' },
            {
              validator: (rule, value, cb) => {
                if (/^1[0-9]{10}$/.test(value)) {
                  cb()
                } else {
                  cb(new Error('手机号码格式错误'))
                }
              },
              trigger: 'blur'
            }
          ]
        }
      }
    }
  },
  computed: {
    userId () {
      return this.$store.state.loginer.id
    }
  },
  async created () {
    // 调用完初始化接口时,需要赋展示初始值
    const res = await http.get(`/users/${this.userId}`)
    if (res.code === '200') {
      this.loginer = res.data
      this.dialogForm.form = {...res.data}
      this.imageUrl = res.data.avatar
    } else {
      this.$message({
        type: 'error',
        message: '获取用户信息失败'
      })
    }
  }
}
</script>
  • get方法调用/users/:id接口后,需要给用户设初始值。
  • patch方法调用/users/:id接口,更新用户信息后,要更新vuex存储的状态,更新展示的用户信息,更新弹窗初始值。

展示效果:
image.png

服务端更新用户服务

// 更新文件:server/control/users.js
...
async function update (ctx) {
  const { id } = ctx.params;
  const payload = ctx.request.body;
  try {
    await userModel.updateOne(
      {
        _id: id
      },
      {
        ...payload
      }
    );
    ctx.body = {
      code: '200',
      data: {
        id,
        ...payload
      },
      msg: '更新成功'
    }
  } catch (err) {
    ctx.body = {
      code: '403',
      data: null,
      msg: '请核查参数'
    }
  }
}
...

测试结果:
update.gif

参考文档

Model操作

阅读 1.1k

1.2k 声望
68 粉丝
0 条评论
1.2k 声望
68 粉丝
文章目录
宣传栏