阅读视图

发现新文章,点击刷新页面。

Vue + ElementPlus 实现权限管理系统(十一): 实现个人中心页面

在后台管理系统中,一般都会有个人中心页面用于修改查看我们的账户信息、修改密码等。本篇文章将使用vueelementplus来实现我们后台权限管理系统的个人中心页面。最终效果如下 image.png

可以查看我们当前的账户信息,更换头像以及修改基本信息及密码。接下来我们就来实现这些功能。

接口引入

这个页面需要四个接口,分别是获取当前用户信息、头像上传、修改密码、修改基本信息接口。访问本地地址http://localhost:3000/fs_admin/api可以看到对应的丝袜哥接口文档

image.png

src/api/user/index.ts导出这三个接口(上传接口使用element提供的upload组件)。

//获取用户信息
export const getProfile = () => {
  return request({
    url: "/user/profile",
    method: "get",
    loading: false,
  });
};

//修改密码
export const updatePassword = (data: {
  oldPassword: string,
  newPassword: string,
}) => {
  return request({
    url: "/user/updatePassword",
    method: "put",
    data,
  });
};

//修改个人信息
export const updateUserInfo = (data: Form) => {
  return request({
    url: "/user/updateUserInfo",
    method: "put",
    data,
  });
};

页面布局

页面布局使用elementplusel-col将页面分为左右两部分。左边是使用el-card组件实现个人信息展示区域。右边是修改个人信息区域,同时使用el-tabs实现修改密码和修改基本信息两个选项卡。我们src/views下新建profile/index.vue来实现这个页面。

<template>
  <el-row class="fs_profile" :gutter="20">
    <el-col :span="8">
      <el-card>
        <template #header>
          <div class="card-header">
            <span>我的信息</span>
          </div>
        </template>
        <el-upload
          class="avatar-uploader"
          :action="uploadParams.uploadUrl"
          :headers="uploadParams.headers"
          :on-success="handleAvatarSuccess"
          :show-file-list="false"
        >
          <img
            v-if="proFileData.avatar"
            :src="proFileData.avatar"
            class="avatar"
          />
          <el-icon v-else class="avatar-uploader-icon">
            <Plus />
          </el-icon>
        </el-upload>
        <ul>
          <li class="profile">
            <div class="profile_label">
              <User class="profile_label_icon" />
              <div class="profile_label_text">用户名</div>
            </div>
            <div>{{ proFileData.username }}</div>
          </li>
          <li class="profile">
            <div class="profile_label">
              <Avatar class="profile_label_icon" />
              <div class="profile_label_text">昵称</div>
            </div>
            <div>{{ proFileData.nickname }}</div>
          </li>
          <li class="profile">
            <div class="profile_label">
              <Iphone class="profile_label_icon" />
              <div class="profile_label_text">手机号</div>
            </div>
            <div>{{ proFileData.telephone }}</div>
          </li>
          <li class="profile">
            <div class="profile_label">
              <Message class="profile_label_icon" />
              <div class="profile_label_text">邮箱</div>
            </div>
            <div>{{ proFileData.email }}</div>
          </li>
          <li class="profile">
            <div class="profile_label">
              <Calendar class="profile_label_icon" />
              <div class="profile_label_text">创建时间</div>
            </div>
            <div>{{ proFileData.create_time }}</div>
          </li>
        </ul>
      </el-card>
    </el-col>
    <el-col :span="16">
      <el-card>
        <template v-slot:header>
          <div>
            <span>个人资料</span>
          </div>
        </template>
        <el-tabs v-model="activeName">
          <el-tab-pane label="基本资料" name="userinfo">
            <userInfo :user="proFileData" @submit="updateUser" />
          </el-tab-pane>
          <el-tab-pane label="修改密码" name="resetPwd">
            <resetPassWord @submit="resetPwd" />
          </el-tab-pane>
        </el-tabs>
      </el-card>
    </el-col>
  </el-row>
</template>

其中userInforesetPassWord是两个子组件,分别用来实现修改基本信息和修改密码的功能,后面再讲。

同时这次样式我们使用 scss 来写(用 tailwind 太麻烦)。

.fs_profile {
  min-width: 1000px;

  .avatar-uploader {
    text-align: center;
    margin: 30px 0;

    .el-upload {
      border: 1px dashed var(--el-border-color);
      border-radius: 50%;
      cursor: pointer;
      position: relative;
      overflow: hidden;
      transition: var(--el-transition-duration-fast);
      margin: 0 auto;
    }

    .avatar {
      width: 150px;
      height: 150px;
      object-fit: cover;
    }
  }

  .avatar-uploader .el-upload:hover {
    border-color: var(--el-color-primary);
  }

  .el-icon.avatar-uploader-icon {
    font-size: 28px;
    color: #8c939d;
    width: 178px;
    height: 178px;

    text-align: center;
  }

  .profile {
    display: flex;
    justify-content: space-between;
    padding: 15px 5px;
    border-top: 1px solid lightgray;

    .profile_label {
      display: flex;
      color: gray;
      align-items: center;

      .profile_label_icon {
        width: 20px;
        height: 20px;
        margin-right: 4px;
      }

      .profile_label_text {
        flex: none;
      }
    }
  }
}

信息查询

我们先看信息查询功能,实现还是比较简单的,调用接口获取用户信息,然后将数据绑定到页面上即可

import { getProfile } from "@/api/user/index";
import type { ResetForm, UserInfo } from "./types";
const proFileData = ref < Partial < UserInfo >> {};
const getProFileData = async () => {
  const { data } = await getProfile();
  proFileData.value = data;
};

其中用到了UserInfo类型,这些类型我们放在types目录下

export type UserInfo = {
  username: string,
  nickname: string,
  telephone: string,
  email: string,
  avatar: string,
  create_time: string,
};

export type ResetForm = {
  oldPassword: string,
  newPassword: string,
  confirmPassword?: string,
};

这样我们的查询功能就实现了。

基本信息修改

前面我们提到了userInforesetPassWord两个子组件,分别用来实现修改基本信息和修改密码的功能。我们先来看基本信息userInfo组件。

<template>
  <el-form ref="userRef" :model="user" label-width="80px">
    <el-form-item label="用户昵称" prop="nickName">
      <el-input v-model="user.nickname" maxlength="30" />
    </el-form-item>
    <el-form-item label="手机号码" prop="telephone">
      <el-input v-model="user.telephone" maxlength="11" />
    </el-form-item>
    <el-form-item label="邮箱" prop="email">
      <el-input v-model="user.email" maxlength="50" />
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="submit">保存资料</el-button>
    </el-form-item>
  </el-form>
</template>

<script lang="ts" setup>
  import { toRefs } from "vue";
  import type { UserInfo } from "../types";

  const userProps = defineProps<{ user: Partial<UserInfo> }>();
  //结构解出user且是响应式的
  const { user } = toRefs(userProps);
  type Emits = {
    (e: "submit", user: Partial<UserInfo>): void;
  };
  const emits = defineEmits<Emits>();
  const submit = () => {
    emits("submit", userProps.user);
  };
</script>

然后在父组件中使用它,同时传入user参数以及接收submit修改事件

<userInfo :user="proFileData" @submit="updateUser" />

最后在updateUser函数中实现修改基本信息的功能即可

const updateUser = async (data: Partial<UserInfo>) => {
  await updateUserInfo(data);
  ElMessage.success("修改成功");
  getProFileData();
};

修改密码

修改密码需要输入旧密码、新密码、确认密码。我们先来看子组件resetPassWord

<template>
  <el-form ref="pwdRef" :model="user" :rules="rules" label-width="80px">
    <el-form-item label="旧密码" prop="oldPassword">
      <el-input
        v-model="user.oldPassword"
        placeholder="请输入旧密码"
        type="password"
        show-password
      />
    </el-form-item>
    <el-form-item label="新密码" prop="newPassword">
      <el-input
        v-model="user.newPassword"
        placeholder="请输入新密码"
        type="password"
        show-password
      />
    </el-form-item>
    <el-form-item label="确认密码" prop="confirmPassword">
      <el-input
        v-model="user.confirmPassword"
        placeholder="请确认密码"
        type="password"
        show-password
      />
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="submit(pwdRef)">确认修改</el-button>
    </el-form-item>
  </el-form>
</template>

<script setup lang="ts">
  import { FormInstance } from "element-plus";
  import { ref } from "vue";
  import { ResetForm } from "../types";
  const pwdRef = ref<FormInstance>();
  const user = ref<ResetForm>({
    oldPassword: "",
    newPassword: "",
    confirmPassword: "",
  });

  type Emits = {
    (e: "submit", user: ResetForm): void;
  };
  const emits = defineEmits<Emits>();
  const equalToPassword = (
    rule: any,
    value: string,
    callback: (arg0?: Error) => void
  ) => {
    if (user.value.newPassword !== value) {
      callback(new Error("两次输入的密码不一致"));
    } else {
      callback();
    }
  };
  const rules = ref({
    oldPassword: [
      { required: true, message: "旧密码不能为空", trigger: "blur" },
    ],
    newPassword: [
      { required: true, message: "新密码不能为空", trigger: "blur" },
      { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" },
    ],
    confirmPassword: [
      { required: true, message: "确认密码不能为空", trigger: "blur" },
      { required: true, validator: equalToPassword, trigger: "blur" },
    ],
  });

  function submit(pwdRef: FormInstance | undefined) {
    if (!pwdRef) return;
    pwdRef.validate((valid) => {
      if (valid) {
        emits("submit", user.value);
      }
    });
  }
</script>

这里使用了element-plus的表单验证功能,验证规则在rules中,定义了新旧密码不能为空及两次输入的密码一不一致等情况。比如如果不满足页面就会有所提示。

image.png

验证通过将会给父组件发送一个submit事件同时传入修改后的用户信息。父组件拿到这些信息调用修改密码接口即可。

const resetPwd = async (data: ResetForm) => {
  await updatePassword(data);
  ElMessage.success("修改成功");
  getProFileData();
};

头像上传

头像上传使用了element-plusupload组件,在页面是我们是这么使用的

<el-upload
  class="avatar-uploader"
  :action="uploadParams.uploadUrl"
  :headers="uploadParams.headers"
  :on-success="handleAvatarSuccess"
  :show-file-list="false"
></el-upload>

其中action是上传地址,headers是上传接口的请求头,这里我们需要和其它接口一样加上token不然是禁止请求的。handleAvatarSuccess是上传成功后的回调函数,可以接收到成功后的返回信息,show-file-list是是否显示文件列表。

我们看一下实现逻辑代码

import { Storage } from "@/utils/storage";

// 上传相关参数
const uploadParams = {
  uploadUrl: import.meta.env.VITE_APP_API + "/user/uploadAvatar",
  headers: { authorization: "Bearer " + Storage.get("token") },
};

//上传成功回调

const handleAvatarSuccess = (res: any) => {
  if (res.code == 200) {
    ElMessage.success("头像设置成功");
    getProFileData();
  } else {
    ElMessage.error(res.describe);
  }
};

就是这么简单。

到这里就实现了个人中心页面。下一篇文章我们将介绍如何使用 NestJS 来实现这些接口。

源码地址

❌