Kaynağa Gözat

[新增]个人信息设置

WuXinyu 7 yıl önce
ebeveyn
işleme
3b1afd877a

+ 10 - 0
mock/modules/user/index.js

@@ -22,4 +22,14 @@ router.route("/:userId").post((req, res) => {
   });
 });
 
+router.route("/:userId/password").post((req, res) => {
+  //修改用户密码
+  res.send({
+    data: userSerializer(),
+    abilities: {
+      update: true
+    }
+  });
+});
+
 module.exports = router;

+ 2 - 0
src/assets/scss/_variables.scss

@@ -77,6 +77,8 @@ $variable-columns: true !default;
 
 $primary: $turquoise !default;
 
+$primary: #7060ff;
+
 $info: $cyan !default;
 $success: $green !default;
 $warning: $yellow !default;

+ 1 - 1
src/assets/scss/app.scss

@@ -31,7 +31,7 @@ $default-margin-between:1.5rem;
 }
 
 
-.menu{
+.menu.dark-menu{
 
   .menu-label {
     color: rgba(255, 255, 255, 0.79);

+ 14 - 4
src/components/UserDropdown/index.vue

@@ -9,10 +9,12 @@
       以 <b>{{ name }}</b> 的身份登录
     </b-dropdown-item>
     <hr class="dropdown-divider" />
-    <b-dropdown-item value="settings">
-      <b-icon icon="settings"></b-icon>
-      个人设置
-    </b-dropdown-item>
+    <router-link :to="`${prefix}/profile`">
+      <b-dropdown-item value="settings">
+        <b-icon icon="settings"></b-icon>
+        个人设置
+      </b-dropdown-item>
+    </router-link>
     <b-dropdown-item value="logout">
       <b-icon icon="logout"></b-icon>
       登出
@@ -32,6 +34,14 @@ export default {
     name() {
       const { nickname, username } = this.profile;
       return nickname ? nickname : username;
+    },
+    role() {
+      const { role } = this.profile;
+      return role;
+    },
+    prefix() {
+      const { role = "" } = this;
+      return `/${role.toLowerCase()}/setting`;
     }
   },
   async mounted() {

+ 65 - 0
src/layouts/SettingLayout.vue

@@ -0,0 +1,65 @@
+<template>
+  <div class="section">
+    <div class="container">
+      <div class="columns is-desktop">
+        <div class="column is-3">
+          <aside class="menu">
+            <ul class="menu-list">
+              <li>
+                <router-link active-class="is-active" :to="`${prefix}/profile`">
+                  个人设置
+                </router-link>
+              </li>
+              <li>
+                <router-link
+                  active-class="is-active"
+                  :to="`${prefix}/password`"
+                >
+                  密码设置
+                </router-link>
+              </li>
+            </ul>
+          </aside>
+        </div>
+        <div class="column is-9">
+          <div class="router-part"><router-view /></div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { mapState } from "vuex";
+import { FETCH_PROFILE } from "@/store/type/actions.type";
+
+export default {
+  computed: {
+    ...mapState({
+      profile: state => state.user.profile
+    }),
+    role() {
+      const { role } = this.profile;
+      return role;
+    },
+    prefix() {
+      const { role = "" } = this;
+      return `/${role.toLowerCase()}/setting`;
+    }
+  },
+  async mounted() {
+    await this.$store.dispatch(FETCH_PROFILE, this.profile.id);
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+@import "../assets/scss/app";
+.router-part {
+  background: white;
+  padding: $default-margin-between;
+  border-radius: $default-radius;
+  box-shadow: $default-shadow;
+  border: $default-border;
+}
+</style>

+ 1 - 1
src/layouts/courseLayouts/StudentMenu.vue

@@ -1,5 +1,5 @@
 <template>
-  <aside class="menu">
+  <aside class="menu dark-menu">
     <p class="menu-label">总览</p>
     <ul class="menu-list">
       <li>

+ 1 - 1
src/layouts/courseLayouts/TeacherMenu.vue

@@ -1,5 +1,5 @@
 <template>
-  <aside class="menu">
+  <aside class="menu dark-menu">
     <p class="menu-label">总览</p>
     <ul class="menu-list">
       <li>

+ 28 - 0
src/router/routes.js

@@ -37,6 +37,20 @@ export default [
         name: TEACHER,
         path: "",
         component: () => import("@/views/teacher/Home")
+      },
+      {
+        path: "setting",
+        component: () => import("@/layouts/SettingLayout"),
+        children: [
+          {
+            path: "profile",
+            component: () => import("@/views/setting/Profile")
+          },
+          {
+            path: "password",
+            component: () => import("@/views/setting/Password")
+          }
+        ]
       }
     ]
   },
@@ -93,6 +107,20 @@ export default [
         name: STUDENT,
         path: "",
         component: () => import("@/views/student/Home")
+      },
+      {
+        path: "setting",
+        component: () => import("@/layouts/SettingLayout"),
+        children: [
+          {
+            path: "profile",
+            component: () => import("@/views/setting/Profile")
+          },
+          {
+            path: "password",
+            component: () => import("@/views/setting/Password")
+          }
+        ]
       }
     ]
   },

+ 120 - 0
src/views/setting/Password.vue

@@ -0,0 +1,120 @@
+<template>
+  <div>
+    <b-notification type="is-danger" :active.sync="isServerError">
+      {{ errorMassage }}
+    </b-notification>
+
+    <b-field
+      label="旧密码"
+      :type="$v.oldPassword.$error ? 'is-danger' : ''"
+      :message="$v.oldPassword.$error ? '请输入旧密码' : ''"
+    >
+      <b-input
+        type="password"
+        v-model="$v.oldPassword.$model"
+        placeholder="在这里输入旧密码"
+        password-reveal
+      >
+      </b-input>
+    </b-field>
+    <b-field
+      label="新密码"
+      :type="$v.password.$error ? 'is-danger' : ''"
+      :message="
+        $v.password.$error
+          ? [
+              !$v.password.complex
+                ? '密码至少为8位,且要包含字母和数字'
+                : undefined,
+              !$v.password.required ? '请输入密码' : undefined
+            ]
+          : ''
+      "
+    >
+      <b-input
+        v-model="$v.password.$model"
+        type="password"
+        placeholder="在此输入新密码"
+        password-reveal
+      ></b-input>
+    </b-field>
+
+    <b-field
+      label="再次输入密码"
+      :type="$v.passwordAgain.$error ? 'is-danger' : ''"
+      :message="$v.passwordAgain.$error ? '密码需一致' : ''"
+    >
+      <b-input
+        type="password"
+        v-model="$v.passwordAgain.$model"
+        placeholder="在这里重复密码"
+        password-reveal
+      >
+      </b-input>
+    </b-field>
+    <b-field>
+      <button
+        :class="{ 'is-loading': submitting }"
+        class="button is-primary"
+        @click="registerAction"
+      >
+        更 新
+      </button>
+    </b-field>
+  </div>
+</template>
+
+<script>
+import { required, sameAs } from "vuelidate/lib/validators";
+import { postUserPassword } from "@/api/user";
+
+export default {
+  data() {
+    return {
+      oldPassword: "",
+      password: "",
+      passwordAgain: "",
+      submitting: false,
+      errorMassage: null,
+      isServerError: false
+    };
+  },
+  validations: {
+    oldPassword: {
+      required
+    },
+    password: {
+      required,
+      complex: password =>
+        /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/.test(password)
+    },
+    passwordAgain: {
+      sameAsPassword: sameAs("password")
+    }
+  },
+  methods: {
+    async registerAction() {
+      this.$v.$touch();
+      if (!this.$v.$invalid) {
+        this.submitting = true;
+        const { password = "", oldPassword = "" } = this;
+        const { id, username, nickname } = this.$store.state.user.profile;
+        try {
+          await postUserPassword(id, { oldPassword, newPassword: password });
+          this.$toast.open({
+            message: `用户「${
+              nickname !== null && nickname.length > 0 ? nickname : username
+            }」密码修改成功`,
+            type: "is-success"
+          });
+        } catch (e) {
+          this.errorMassage = e.message;
+          this.isServerError = true;
+        } finally {
+          this.submitting = false;
+        }
+      }
+    }
+  }
+};
+</script>

+ 88 - 0
src/views/setting/Profile.vue

@@ -0,0 +1,88 @@
+<template>
+  <div>
+    <b-notification type="is-danger" :active.sync="isServerError">
+      {{ errorMassage }}
+    </b-notification>
+    <b-field
+      label="昵称"
+      :type="$v.nickname.$error ? 'is-danger' : ''"
+      :message="$v.nickname.$error ? '昵称须由长度为4至16的字母或数字组成' : ''"
+    >
+      <b-input
+        v-model="$v.nickname.$model"
+        placeholder="在此输入昵称"
+      ></b-input>
+    </b-field>
+    <b-field label="介绍一下你自己">
+      <b-input maxlength="100" v-model="bio" type="textarea"></b-input>
+    </b-field>
+    <b-field>
+      <button
+        :class="{ 'is-loading': submitting }"
+        class="button is-primary"
+        @click="updateProfileAction"
+      >
+        更 新
+      </button>
+    </b-field>
+  </div>
+</template>
+
+<script>
+import { alphaNum, minLength, maxLength } from "vuelidate/lib/validators";
+import { postUserProfile } from "@/api/user";
+import { FETCH_PROFILE } from "@/store/type/actions.type";
+
+export default {
+  data() {
+    return {
+      nickname: "",
+      bio: "",
+      submitting: false,
+      errorMassage: null,
+      isServerError: false
+    };
+  },
+  validations: {
+    nickname: {
+      alphaNum,
+      minLength: minLength(4),
+      maxLength: maxLength(16)
+    }
+  },
+  mounted() {
+    this.getProfileFromStore();
+  },
+  methods: {
+    async updateProfileAction() {
+      this.$v.$touch();
+      if (!this.$v.$invalid) {
+        this.submitting = true;
+        const { id, username } = this.$store.state.user.profile;
+        const { bio = "", nickname = "" } = this;
+        try {
+          await postUserProfile(id, { bio, nickname });
+          await this.$store.dispatch(FETCH_PROFILE, id);
+          this.$toast.open({
+            message: `用户「${
+              nickname.length === 0 ? username : nickname
+            }」修改成功`,
+            type: "is-success"
+          });
+          this.getProfileFromStore();
+        } catch (e) {
+          this.errorMassage = e.message;
+          this.isServerError = true;
+        } finally {
+          this.submitting = false;
+        }
+      }
+    },
+    getProfileFromStore() {
+      const { nickname = "", bio = "" } = this.$store.state.user.profile;
+      this.nickname = nickname;
+      this.bio = bio;
+    }
+  }
+};
+</script>