Browse Source

完成登录页、注册页、注册结果页

drafish 5 years ago
parent
commit
eb70029e20
7 changed files with 755 additions and 12 deletions
  1. 9 1
      babel.config.js
  2. 6 0
      src/main.js
  3. 6 0
      src/router.js
  4. 17 5
      src/store/modules/login.js
  5. 329 3
      src/views/User/Login.vue
  6. 336 3
      src/views/User/Register.vue
  7. 52 0
      src/views/User/RegisterResult.vue

+ 9 - 1
babel.config.js

@@ -1,5 +1,13 @@
 module.exports = {
-  presets: ["@vue/app", "@vue/babel-preset-jsx"],
+  presets: [
+    "@vue/app",
+    [
+      "@vue/babel-preset-jsx",
+      {
+        injectH: false
+      }
+    ]
+  ],
   plugins: [
     [
       "import",

+ 6 - 0
src/main.js

@@ -31,6 +31,9 @@ import {
   Checkbox,
   Col,
   Row,
+  Popover,
+  Progress,
+  Alert,
   message
 } from "ant-design-vue";
 import Authorized from "./components/Authorized";
@@ -65,6 +68,9 @@ Vue.use(Tag);
 Vue.use(Checkbox);
 Vue.use(Col);
 Vue.use(Row);
+Vue.use(Popover);
+Vue.use(Progress);
+Vue.use(Alert);
 Vue.prototype.$message = message;
 
 const i18n = new VueI18n({

+ 6 - 0
src/router.js

@@ -33,6 +33,12 @@ const router = new Router({
           name: "register",
           component: () =>
             import(/* webpackChunkName: "user" */ "./views/User/Register")
+        },
+        {
+          path: "/user/register-result",
+          name: "register.result",
+          component: () =>
+            import(/* webpackChunkName: "user" */ "./views/User/RegisterResult")
         }
       ]
     },

+ 17 - 5
src/store/modules/login.js

@@ -9,6 +9,7 @@ const state = {
 
 const actions = {
   async login({ commit }, payload) {
+    console.log(payload);
     const response = (await request({
       url: "/api/login/account",
       method: "POST",
@@ -50,15 +51,26 @@ const actions = {
       });
     }
   },
-  async register({ commit }, { payload }) {
-    const response = await request({
+  async register({ commit }, payload) {
+    console.log(payload);
+    const response = (await request({
       url: "/api/register",
       method: "POST",
       data: payload
-    });
-    commit("changeLoginStatus", response);
+    })).data;
+    console.log(response);
+    if (response.status === "ok") {
+      commit("changeLoginStatus", response);
+      router.push({
+        name: "register.result",
+        params: {
+          account: payload.mail
+        }
+      });
+    }
   },
-  async getSmsCaptcha({ payload }) {
+  // eslint-disable-next-line no-unused-vars
+  async getCaptcha({ commit }, payload) {
     console.log(payload);
     await request({
       url: `/api/captcha?mobile=${payload.mobile}`,

+ 329 - 3
src/views/User/Login.vue

@@ -1,9 +1,335 @@
 <template>
-  <div>登录页</div>
+  <div class="main">
+    <div class="login">
+      <a-form :form="form" @submit="handleSubmit">
+        <a-tabs
+          :animated="false"
+          class="tabs"
+          :activeKey="type"
+          @change="onSwitch"
+        >
+          <a-tab-pane key="account" tab="账户密码登录">
+            <VNodes :vnodes="renderMessage('account')" />
+            <a-form-item>
+              <a-input
+                id="userName"
+                type="UserName"
+                size="large"
+                placeholder="用户名:admin or user"
+                v-decorator="[
+                  'userName',
+                  {
+                    rules: [
+                      {
+                        required: true,
+                        message: '请输入用户名!'
+                      }
+                    ]
+                  }
+                ]"
+              >
+                <a-icon slot="prefix" type="user" class="prefixIcon" />
+              </a-input>
+            </a-form-item>
+            <a-form-item>
+              <a-input
+                id="password"
+                type="Password"
+                size="large"
+                placeholder="密码:ant.design"
+                v-decorator="[
+                  'password',
+                  {
+                    rules: [
+                      {
+                        required: true,
+                        message: '请输入密码!'
+                      }
+                    ]
+                  }
+                ]"
+                :onPressEnter="onPressEnter"
+              >
+                <a-icon slot="prefix" type="lock" class="prefixIcon" />
+              </a-input>
+            </a-form-item>
+          </a-tab-pane>
+          <a-tab-pane key="mobile" tab="手机号登录">
+            <VNodes :vnodes="renderMessage('mobile')" />
+            <a-form-item>
+              <a-input
+                size="large"
+                type="Mobile"
+                placeholder="手机号"
+                v-decorator="[
+                  'mobile',
+                  {
+                    rules: [
+                      {
+                        required: true,
+                        message: '请输入手机号!'
+                      },
+                      {
+                        pattern: /^1\d{10}$/,
+                        message: '手机号格式错误!'
+                      }
+                    ]
+                  }
+                ]"
+              >
+                <a-icon slot="prefix" type="mobile" class="prefixIcon" />
+              </a-input>
+            </a-form-item>
+            <a-form-item>
+              <a-row :gutter="8">
+                <a-col :span="16">
+                  <a-input
+                    size="large"
+                    type="Captcha"
+                    placeholder="验证码"
+                    v-decorator="[
+                      'captcha',
+                      {
+                        rules: [
+                          {
+                            required: true,
+                            message: '请输入验证码!'
+                          }
+                        ]
+                      }
+                    ]"
+                  >
+                    <a-icon slot="prefix" type="mail" class="prefixIcon" />
+                  </a-input>
+                </a-col>
+                <a-col :span="8">
+                  <a-button
+                    class="getCaptcha"
+                    size="large"
+                    @click="onGetCaptcha"
+                    :disabled="!!count"
+                    >{{ count ? `${count} 秒` : "获取验证码" }}</a-button
+                  >
+                </a-col>
+              </a-row>
+            </a-form-item>
+          </a-tab-pane>
+        </a-tabs>
+        <div>
+          <a-checkbox :checked="autoLogin" @change="changeAutoLogin">
+            自动登录
+          </a-checkbox>
+          <a style="float: right" href="">
+            忘记密码
+          </a>
+        </div>
+        <a-form-item>
+          <a-button
+            size="large"
+            class="submit"
+            type="primary"
+            htmlType="submit"
+            :loading="submitting"
+            >登录</a-button
+          >
+        </a-form-item>
+        <div class="other">
+          其他登录方式
+          <a-icon type="alipay-circle" class="icon" theme="outlined" />
+          <a-icon type="taobao-circle" class="icon" theme="outlined" />
+          <a-icon type="weibo-circle" class="icon" theme="outlined" />
+          <router-link class="register" to="/user/register">
+            注册账户
+          </router-link>
+        </div>
+      </a-form>
+    </div>
+  </div>
 </template>
 
 <script>
-export default {};
+import { mapActions, mapState } from "vuex";
+import { Modal } from "ant-design-vue";
+export default {
+  components: {
+    VNodes: {
+      functional: true,
+      render: (h, ctx) => ctx.props.vnodes
+    }
+  },
+  data() {
+    return {
+      form: this.$form.createForm(this),
+      type: "account",
+      autoLogin: true,
+      submitting: false,
+      count: 0,
+      active: {
+        account: ["userName", "password"],
+        mobile: ["mobile", "captcha"]
+      }
+    };
+  },
+  beforeDestroy() {
+    clearInterval(this.interval);
+  },
+  computed: {
+    ...mapState("login", {
+      status: state => state.status
+    })
+  },
+  methods: {
+    ...mapActions("login", ["login", "getCaptcha"]),
+    handleSubmit(e) {
+      e.preventDefault();
+      const { type, form } = this;
+      const activeFileds = this.active[type];
+      form.validateFields(activeFileds, { force: true }, (err, values) => {
+        if (!err) {
+          this.submitting = true;
+          this.login({
+            ...values,
+            type
+          }).then(() => {
+            this.submitting = false;
+          });
+        }
+      });
+    },
+    onSwitch(key) {
+      this.type = key;
+    },
+    changeAutoLogin(e) {
+      this.autoLogin = e.target.checked;
+    },
+    runGetCaptchaCountDown() {
+      this.count = 59;
+      this.interval = setInterval(() => {
+        this.count -= 1;
+        if (this.count === 0) {
+          clearInterval(this.interval);
+        }
+      }, 1000);
+    },
+    onGetCaptcha() {
+      const { form } = this;
+      form.validateFields(["mobile"], { force: true }, (err, values) => {
+        if (!err) {
+          this.getCaptcha({ mobile: values.mobile });
+          this.runGetCaptchaCountDown();
+          Modal.info({
+            title:
+              "此项目为演示项目,并不会真的给您发送验证码。请切换到账户密码登录界面按提示登录。"
+          });
+        }
+      });
+    },
+    onPressEnter(e) {
+      e.preventDefault();
+      this.form.validateFields(this.handleSubmit);
+    },
+    renderMessage(type) {
+      if (this.status === "error" && this.type === type && !this.submitting) {
+        return (
+          <a-alert
+            style="margin-bottom: 24px"
+            message={
+              type === "account"
+                ? "账户或密码错误(admin/ant.design)"
+                : "验证码错误"
+            }
+            type="error"
+            showIcon
+          />
+        );
+      } else {
+        return null;
+      }
+    }
+  }
+};
 </script>
 
-<style></style>
+<style scoped lang="less">
+@import "~ant-design-vue/lib/style/themes/default.less";
+
+.main {
+  width: 388px;
+  margin: 0 auto;
+  @media screen and (max-width: @screen-sm) {
+    width: 95%;
+  }
+
+  .icon {
+    margin-left: 16px;
+    color: rgba(0, 0, 0, 0.2);
+    font-size: 24px;
+    vertical-align: middle;
+    cursor: pointer;
+    transition: color 0.3s;
+
+    &:hover {
+      color: @primary-color;
+    }
+  }
+
+  .other {
+    margin-top: 24px;
+    line-height: 22px;
+    text-align: left;
+
+    .register {
+      float: right;
+    }
+  }
+  .login {
+    /deep/ .ant-tabs .ant-tabs-bar {
+      margin-bottom: 24px;
+      text-align: center;
+      border-bottom: 0;
+    }
+
+    /deep/ .ant-form-item {
+      margin: 0 2px 24px;
+    }
+
+    .getCaptcha {
+      display: block;
+      width: 100%;
+    }
+
+    .icon {
+      margin-left: 16px;
+      color: rgba(0, 0, 0, 0.2);
+      font-size: 24px;
+      vertical-align: middle;
+      cursor: pointer;
+      transition: color 0.3s;
+
+      &:hover {
+        color: @primary-color;
+      }
+    }
+
+    .other {
+      margin-top: 24px;
+      line-height: 22px;
+      text-align: left;
+
+      .register {
+        float: right;
+      }
+    }
+
+    .prefixIcon {
+      color: @disabled-color;
+      font-size: @font-size-base;
+    }
+
+    .submit {
+      width: 100%;
+      margin-top: 24px;
+    }
+  }
+}
+</style>

+ 336 - 3
src/views/User/Register.vue

@@ -1,9 +1,342 @@
 <template>
-  <div>注册页</div>
+  <div class="main">
+    <h3>注册</h3>
+    <a-form :form="form" @submit="handleSubmit">
+      <a-form-item>
+        <a-input
+          size="large"
+          placeholder="邮箱"
+          v-decorator="[
+            'mail',
+            {
+              rules: [
+                {
+                  required: true,
+                  message: '请输入邮箱地址!'
+                },
+                {
+                  type: 'email',
+                  message: '邮箱地址格式错误!'
+                }
+              ]
+            }
+          ]"
+        />
+      </a-form-item>
+      <a-form-item :help="help">
+        <a-popover
+          :overlayStyle="{ width: '240px' }"
+          placement="right"
+          :visible="visible"
+        >
+          <template slot="content">
+            <div style="padding: 4px 0">
+              <VNodes :vnodes="passwordStatusMap()" />
+              <VNodes :vnodes="renderPasswordProgress()" />
+              <div style="margin-top: 10">
+                请至少输入 6 个字符。请不要使用容易被猜到的密码。
+              </div>
+            </div>
+          </template>
+          <a-input
+            type="password"
+            size="large"
+            placeholder="至少6位密码,区分大小写"
+            v-decorator="[
+              'password',
+              {
+                rules: [{ validator: this.checkPassword }]
+              }
+            ]"
+          />
+        </a-popover>
+      </a-form-item>
+      <a-form-item>
+        <a-input
+          type="password"
+          size="large"
+          placeholder="确认密码"
+          v-decorator="[
+            'confirm',
+            {
+              rules: [
+                {
+                  required: true,
+                  message: '请确认密码!'
+                },
+                {
+                  validator: this.checkConfirm
+                }
+              ]
+            }
+          ]"
+        />
+      </a-form-item>
+      <a-form-item>
+        <a-input-group compact>
+          <a-select
+            size="large"
+            :value="prefix"
+            style="width: 20%"
+            @change="changePrefix"
+          >
+            <a-select-option value="86">+86</a-select-option>
+            <a-select-option value="87">+87</a-select-option>
+          </a-select>
+          <a-input
+            size="large"
+            style="width: 80%"
+            placeholder="手机号"
+            v-decorator="[
+              'mobile',
+              {
+                rules: [
+                  {
+                    required: true,
+                    message: '请输入手机号!'
+                  },
+                  {
+                    pattern: /^\d{11}$/,
+                    message: '手机号格式错误!'
+                  }
+                ]
+              }
+            ]"
+          />
+        </a-input-group>
+      </a-form-item>
+      <a-form-item>
+        <a-row :gutter="8">
+          <a-col :span="16">
+            <a-input
+              size="large"
+              type="Captcha"
+              placeholder="验证码"
+              v-decorator="[
+                'captcha',
+                {
+                  rules: [
+                    {
+                      required: true,
+                      message: '请输入验证码!'
+                    }
+                  ]
+                }
+              ]"
+            />
+          </a-col>
+          <a-col :span="8">
+            <a-button
+              class="getCaptcha"
+              size="large"
+              @click="onGetCaptcha"
+              :disabled="!!count"
+              >{{ count ? `${count} 秒` : "获取验证码" }}</a-button
+            >
+          </a-col>
+        </a-row>
+      </a-form-item>
+      <a-form-item>
+        <a-button
+          size="large"
+          class="submit"
+          type="primary"
+          htmlType="submit"
+          :loading="submitting"
+          >注册</a-button
+        >
+        <router-link class="login" to="/user/login">
+          使用已有账户登录
+        </router-link>
+      </a-form-item>
+    </a-form>
+  </div>
 </template>
 
 <script>
-export default {};
+import { mapActions } from "vuex";
+
+const passwordProgressMap = {
+  ok: "success",
+  pass: "normal",
+  poor: "exception"
+};
+
+export default {
+  components: {
+    VNodes: {
+      functional: true,
+      render: (h, ctx) => ctx.props.vnodes
+    }
+  },
+  data() {
+    return {
+      form: this.$form.createForm(this),
+      count: 0,
+      confirmDirty: false,
+      visible: false,
+      help: "",
+      prefix: "86",
+      submitting: false
+    };
+  },
+  beforeDestroy() {
+    clearInterval(this.interval);
+  },
+  methods: {
+    ...mapActions("login", ["register"]),
+    handleSubmit(e) {
+      e.preventDefault();
+      const { form } = this;
+      form.validateFields({ force: true }, (err, values) => {
+        if (!err) {
+          this.submitting = true;
+          const { prefix } = this;
+          this.register({
+            ...values,
+            prefix
+          }).then(() => {
+            this.submitting = false;
+          });
+        }
+      });
+    },
+    runGetCaptchaCountDown() {
+      this.count = 59;
+      this.interval = setInterval(() => {
+        this.count -= 1;
+        if (this.count === 0) {
+          clearInterval(this.interval);
+        }
+      }, 1000);
+    },
+    onGetCaptcha() {
+      this.runGetCaptchaCountDown();
+    },
+    changePrefix(value) {
+      this.prefix = value;
+    },
+    getPasswordStatus() {
+      const { form } = this;
+      const value = form.getFieldValue("password");
+      if (value && value.length > 9) {
+        return "ok";
+      }
+      if (value && value.length > 5) {
+        return "pass";
+      }
+      return "poor";
+    },
+    passwordStatusMap() {
+      const passwordStatusMap = {
+        ok: <div class="success">强度:强</div>,
+        pass: <div class="warning">强度:中</div>,
+        poor: <div class="error">强度:太短</div>
+      };
+      return passwordStatusMap[this.getPasswordStatus()];
+    },
+    checkPassword(rule, value, callback) {
+      const { visible, confirmDirty } = this;
+      if (!value) {
+        (this.help = "请输入密码!"),
+          (this.visible = !!value),
+          callback("error");
+      } else {
+        this.help = "";
+        if (!visible) {
+          this.visible = !!value;
+        }
+        if (value.length < 6) {
+          callback("error");
+        } else {
+          const { form } = this;
+          if (value && confirmDirty) {
+            form.validateFields(["confirm"], { force: true });
+          }
+          callback();
+        }
+      }
+    },
+    renderPasswordProgress() {
+      const { form } = this;
+      const value = form.getFieldValue("password");
+      const passwordStatus = this.getPasswordStatus();
+      return value && value.length ? (
+        <div class={`progress-${passwordStatus}`}>
+          <a-progress
+            status={passwordProgressMap[passwordStatus]}
+            class="progress"
+            strokeWidth={6}
+            percent={value.length * 10 > 100 ? 100 : value.length * 10}
+            showInfo={false}
+          />
+        </div>
+      ) : null;
+    },
+    checkConfirm(rule, value, callback) {
+      const { form } = this;
+      if (value && value !== form.getFieldValue("password")) {
+        callback("两次输入的密码不匹配!");
+      } else {
+        callback();
+      }
+    }
+  }
+};
 </script>
 
-<style></style>
+<style scoped lang="less">
+@import "~ant-design-vue/lib/style/themes/default.less";
+
+.main {
+  width: 388px;
+  margin: 0 auto;
+
+  /deep/ .ant-form-item {
+    margin-bottom: 24px;
+  }
+
+  h3 {
+    margin-bottom: 20px;
+    font-size: 16px;
+  }
+
+  .getCaptcha {
+    display: block;
+    width: 100%;
+  }
+
+  .submit {
+    width: 50%;
+  }
+
+  .login {
+    float: right;
+    line-height: @btn-height-lg;
+  }
+}
+
+.success,
+.warning,
+.error {
+  transition: color 0.3s;
+}
+
+.success {
+  color: @success-color;
+}
+
+.warning {
+  color: @warning-color;
+}
+
+.error {
+  color: @error-color;
+}
+
+.progress-pass > .progress {
+  /deep/ .ant-progress-bg {
+    background-color: @warning-color;
+  }
+}
+</style>

+ 52 - 0
src/views/User/RegisterResult.vue

@@ -0,0 +1,52 @@
+<template>
+  <div class="registerResult">
+    <div>你的账户:{{ account }} 注册成功</div>
+    <div>
+      激活邮件已发送到你的邮箱中,邮件有效期为24小时。请及时登录邮箱,点击邮件中的链接激活帐户。
+    </div>
+    <div class="actions">
+      <a href="">
+        <a-button size="large" type="primary">
+          查看邮箱
+        </a-button>
+      </a>
+      <router-link to="/">
+        <a-button size="large">
+          返回首页
+        </a-button>
+      </router-link>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      account: ""
+    };
+  },
+  created() {
+    this.account = this.$route.params.account;
+  }
+};
+</script>
+
+<style scoped lang="less">
+.registerResult {
+  /deep/ .anticon {
+    font-size: 64px;
+  }
+  .title {
+    margin-top: 32px;
+    font-size: 20px;
+    line-height: 28px;
+  }
+  .actions {
+    margin-top: 40px;
+    a + a {
+      margin-left: 8px;
+    }
+  }
+}
+</style>