Write the Code. Change the World.

8月 04

上一篇,把基本的构建做成了。不过,页面不够完善。现在结合接口,来做一波。尽管如此,这篇没有使用的 vuex 状态管理,也是不完美的。

welcome.vue

<template>
  <div class="user">
    <div v-if="user">
      <div class="info">Hello, 第 {{ user.id }} 号用户。你创建于 {{ user.created_at}}</div>
      <el-button type="primary" @click="logout" size="small">退出登录</el-button>
    </div>
    <div v-else>
      <span>你还没有登录,请先登录</span>
      <el-link type="primary" :underline="false" href="./#/admin/user/login">开始登录</el-link>
    </div>

    <div>
        <el-button type="primary" @click="test" size="small">测试有权限接口</el-button>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      user: null,
    };
  },
  mounted() {
    this.checkUser();
  },
  methods: {
    checkUser() {
      let user = localStorage.getItem("user");
      if (user) {
        this.user = JSON.parse(user);
      } else {
        this.user = null;
      }
    },
    logout() {
      axios
        .get("logout", null)
        .then((res) => {
          if (res && res.data) {
            localStorage.clear();
            this.checkUser();
          }
        })
        .catch((error) => {});
    },
    test() {
      axios
        .get("currentUser", null)
        .then((res) => {
          this.$message({ message: "拥有权限,测试成功", type: "success" });
        })
        .catch((error) => {
          this.$message('你还没有登录,无权操作');
        });
    },
  },
};
</script>

<style lang="scss" scoped>
.user {
  display: flex;
  flex-direction: column;
  justify-content: center;
  box-sizing: border-box;
  padding: 20px;

  .info {
    margin-bottom: 10px;
  }

  div {
      margin-top: 20px;
  }
}
</style>

login.vue

<template>
  <div class="login-panel">
    <el-card class="box-card" shadow="always">
      <div slot="header" class="clearfix">
        <span>用户登录</span>
      </div>
      <el-row>
        <el-form
          label-position="right"
          label-width="80px"
          :model="formData"
          ref="form"
          :rules="formRule"
        >
          <el-form-item label="账号" prop="account">
            <el-input
              @change="accountChange"
              v-model="formData.account"
              autocomplete="on"
              name="account"
              placeholder="手机号码或邮箱"
            ></el-input>
          </el-form-item>

          <el-form-item label="验证码" prop="captchas" v-if="showCaptcha">
            <el-input
              autocomplete="on"
              v-model="captchas.form.captcha_code"
              name="captcha_code"
              placeholder="请输入验证码"
            ></el-input>
            <div @click="getCaptchas" class="captchas-btn">
              <el-image style="width: 100px; height: 36px" :src="captchas.src" fit="contain"></el-image>
            </div>
          </el-form-item>

          <el-form-item label="密码" prop="password">
            <el-input
              type="password"
              v-model="formData.password"
              autocomplete="on"
              name="password"
              @keyup.enter.native="login"
            ></el-input>
          </el-form-item>

          <el-form-item>
            <el-button type="primary" @click="login" size="samll">登录</el-button>
            <div class="other-way">
              <span>还没有账号?</span>
              <el-link type="primary" :underline="false" href="./#/admin/user/register">免费注册</el-link>
            </div>
          </el-form-item>
        </el-form>
      </el-row>
    </el-card>
  </div>
</template>

<script>
export default {
  data() {
    return {
      showCaptcha: false,
      type: "phone",
      formData: {
        account: "",
        password: "",
      },
      captchas: {
        requested: false,
        src: "",
        form: {
          captcha_key: "",
          captcha_code: "",
        },
      },
      formRule: {
        username: [
          { required: true, message: "请输入用户名", trigger: "blur" },
        ],
        password: [{ required: true, message: "请输入密码", trigger: "blur" }],
      },
    };
  },
  mounted() {
    this.testJump();
    this.test();
  },
  methods: {
    testJump() {
      let user = localStorage.getItem("user");
      if (user) {
        this.$router.push("/");
      }
    },
    accountChange() {
      this.type = "";
      if (
        /^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199)\d{8}$/.test(
          this.formData.account
        )
      ) {
        this.type = "phone";
      }

      if (
        /^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/.test(
          this.formData.account
        )
      ) {
        this.type = "email";
      }

      if (this.type) {
        this.getCaptchas();
      }
    },
    getCaptchas() {
      let data = { type: this.type };
      if (this.type == "phone") {
        if (!this.formData.account) {
          this.$message.error("手机号码不能为空");
          return;
        }

        if (
          !/^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199)\d{8}$/.test(
            this.formData.account
          )
        ) {
          this.$message.error("手机号码格式错误");
          return;
        }
        data.phone = this.formData.account;
      } else {
        if (!this.formData.account) {
          this.$message.error("邮箱不能为空");
          return;
        }

        if (
          !/^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/.test(
            this.formData.account
          )
        ) {
          this.$message.error("邮箱格式错误");
          return;
        }
        data.email = this.formData.account;
      }

      axios
        .post("captchas", data)
        .then((res) => {
          if (res && res.data) {
            this.captchas.requested = true;
            this.captchas.src = res.data.captcha_image_content;
            this.captchas.form.captcha_key = res.data.captcha_key;
          }
        })
        .catch((error) => {});
    },
    login() {
      if (!this.formData.account) {
        this.$message.error("账号不能为空");
        return;
      }

      if (!this.formData.password) {
        this.$message.error("密码不能为空");
        return;
      }

      if (!this.type) {
        this.$message.error("类型不能为空");
        return;
      }

      let data = { used: "login", type: this.type, ...this.formData };

      axios
        .get("/sanctum/csrf-cookie")
        .then((response) => {
          axios.post("login", data).then((res) => {
            if (res && res.data) {
              localStorage.setItem("user", JSON.stringify(res.data));
            }
            this.testJump();
            this.test();
          });
        })
        .catch((error) => {});
    },
    test() {
      return;
      axios
        .get("currentUser", null)
        .then((res) => {
          console.log("res", res);
        })
        .catch((error) => {
          console.log(error.response);
        });
    },
  },
};
</script>

<style lang="scss" scoped>
.login-panel {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100vh;
  background-color: rgba(10, 20, 100, 0.2);

  .box-card {
    width: 640px;

    .el-menu {
      margin-bottom: 20px;
    }

    .captchas-btn {
      position: absolute;
      right: 2px;
      top: 2px;
      cursor: pointer;
    }

    .oneline-input {
      display: block;

      .el-form-item__content {
        display: flex !important;
        width: 100%;

        .el-input {
          display: flex;
          flex: 1;
        }
      }
    }

    .mail-btn {
      position: absolute;
      right: 4px;
      top: 4px;
    }

    .other-way {
      display: inline-block;
      margin-left: 16px;

      span {
        margin-right: 4px;
      }
    }
  }
}
</style>

register.vue

<template>
  <div class="login-panel">
    <el-card class="box-card" shadow="always">
      <el-row>
        <el-form
          label-position="right"
          label-width="80px"
          :model="formData"
          ref="form"
          :rules="formRule"
        >
          <el-form-item>
            <el-menu
              :default-active="activeIndex"
              class="el-menu"
              mode="horizontal"
              @select="menuSelect"
            >
              <el-menu-item index="1">手机号注册</el-menu-item>
              <el-menu-item index="2">邮箱注册</el-menu-item>
            </el-menu>
          </el-form-item>

          <el-form-item v-if="type == 'phone'" label="手机号码" prop="account">
            <el-input
              @change="phoneChange"
              v-model="formData.account"
              autocomplete="on"
              name="account"
              placeholder="请输入手机号码"
            ></el-input>
          </el-form-item>

          <el-form-item v-if="type == 'email'" label="邮箱" prop="account">
            <el-input
              @change="emailChange"
              v-model="formData.account"
              autocomplete="on"
              name="account"
              placeholder="请输入邮箱"
            ></el-input>
          </el-form-item>

          <el-form-item label="验证码" prop="captchas" v-if="showCaptcha">
            <el-input
              autocomplete="on"
              v-model="captchas.form.captcha_code"
              name="captcha_code"
              placeholder="请输入验证码"
            ></el-input>
            <div @click="getCaptchas" class="captchas-btn">
              <el-image style="width: 100px; height: 36px" :src="captchas.src" fit="contain"></el-image>
            </div>
          </el-form-item>

          <el-form-item v-if="type == 'phone' && showCaptcha" label="短信验证" prop="mail">
            <el-input
              v-model="mail.form.verification_code"
              autocomplete="on"
              name="mail"
              maxlength="6"
            ></el-input>
            <el-button
              @click="verificationMail"
              class="mail-btn"
              type="primary"
              size="small"
              :disabled="!captchas.requested || mail.mailWating"
            >{{mail.mailStr}}</el-button>
          </el-form-item>

          <el-form-item label="密码" prop="password">
            <el-input
              type="password"
              v-model="formData.password"
              autocomplete="on"
              name="password"
              @keyup.enter.native="register"
            ></el-input>
          </el-form-item>

          <el-form-item>
            <el-button type="primary" @click="register" size="samll">注册</el-button>
            <div class="other-way">
              <span>已有账号</span>
              <el-link type="primary" :underline="false" href="./#/admin/user/login">开始登录</el-link>
            </div>
          </el-form-item>
        </el-form>
      </el-row>
    </el-card>
  </div>
</template>

<script>
export default {
  data() {
    return {
      showCaptcha: false,
      activeIndex: "1",
      type: "phone",
      formData: {
        account: "",
        password: "",
      },
      mail: {
        mailStr: "获取验证码",
        mailWating: false,
        form: {
          verification_key: "",
          verification_code: "",
        },
      },
      captchas: {
        requested: false,
        src: "",
        form: {
          captcha_key: "",
          captcha_code: "",
        },
      },
      formRule: {
        username: [
          { required: true, message: "请输入用户名", trigger: "blur" },
        ],
        password: [{ required: true, message: "请输入密码", trigger: "blur" }],
      },
    };
  },
  mounted() {
    this.testJump();
    this.test();
  },
  methods: {
    testJump() {
      let user = localStorage.getItem("user");
      if (user) {
        this.$router.push("/");
      }
    },
    menuSelect(key, keyPath) {
      this.type = key == 1 ? "phone" : "email";
      this.showCaptcha = false;
      this.formData.account = "";
      this.captchas.src = "";
      this.captchas.src = "";
      this.captchas.required = false;
    },
    phoneChange() {
      if (
        /^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199)\d{8}$/.test(
          this.formData.account
        )
      ) {
        this.showCaptcha = true;
        this.getCaptchas();
      }
    },
    emailChange() {
      if (
        /^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/.test(
          this.formData.account
        )
      ) {
        this.showCaptcha = true;
        this.getCaptchas();
      }
    },
    getCaptchas() {
      let data = { type: this.type };
      if (this.type == "phone") {
        if (!this.formData.account) {
          this.$message.error("手机号码不能为空");
          return;
        }

        if (
          !/^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199)\d{8}$/.test(
            this.formData.account
          )
        ) {
          this.$message.error("手机号码格式错误");
          return;
        }
        data.phone = this.formData.account;
      } else {
        if (!this.formData.account) {
          this.$message.error("邮箱不能为空");
          return;
        }

        if (
          !/^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/.test(
            this.formData.account
          )
        ) {
          this.$message.error("邮箱格式错误");
          return;
        }
        data.email = this.formData.account;
      }

      axios
        .post("captchas", data)
        .then((res) => {
          if (res && res.data) {
            this.captchas.requested = true;
            this.captchas.src = res.data.captcha_image_content;
            this.captchas.form.captcha_key = res.data.captcha_key;
          }
        })
        .catch((error) => {});
    },
    verificationMail() {
      if (!this.formData.account) {
        this.$message.error("手机号码不能为空");
        return;
      }

      if (!this.captchas.form.captcha_code) {
        this.$message.error("验证码不能为空");
        return;
      }

      const data = {
        phone: this.formData.account,
        ...this.captchas.form,
      };

      axios
        .post("verificationCodes", data)
        .then((res) => {
          this.$message({ message: "短信已发送", type: "success" });
          this.mail.mailWating = true;
          this.mail.form.verification_key = res.data.key;
          let duration = 60;
          let timer = setInterval(() => {
            this.mail.mailStr = `${duration}秒后重新获取`;
            duration--;
          }, 1000);
        })
        .catch((error) => {
          if (error && error.response && error.response.data) {
            this.$message.error(error.response.data.message);
            this.mail.mailWating = false;
            this.mail.mailStr = "获取验证码";
          }
        });
    },
    register() {
      if (!this.formData.password) {
        this.$message.error("密码不能为空");
        return;
      }

      let data = { used: "register", type: this.type, ...this.formData };
      if (this.type == "phone") {
        if (!this.formData.account) {
          this.$message.error("手机号码不能为空");
          return;
        }

        if (
          !this.mail.form.verification_key ||
          !this.mail.form.verification_code
        ) {
          this.$message.error("验证码不能为空");
          return;
        }

        data = { ...data, ...this.mail.form };
      } else if (this.type == "email") {
        if (!this.formData.account) {
          this.$message.error("邮箱不能为空");
          return;
        }

        if (
          !this.captchas.form.captcha_key ||
          !this.captchas.form.captcha_code
        ) {
          this.$message.error("验证码不能为空");
          return;
        }

        data = { ...data, ...this.captchas.form };
      }

      axios
        .get("/sanctum/csrf-cookie")
        .then((response) => {
          axios.post("register", data).then((res) => {
            if (res && res.data) {
              localStorage.setItem("user", JSON.stringify(res.data));
            }
            this.testJump();
            this.test();
          });
        })
        .catch((error) => {});
    },
    test() {
      return;
      axios
        .get("currentUser", null)
        .then((res) => {})
        .catch((error) => {});
    },
  },
};
</script>

<style lang="scss" scoped>
.login-panel {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100vh;
  background-color: rgba(10, 20, 100, 0.2);

  .box-card {
    width: 640px;

    .el-menu {
      margin-bottom: 20px;
    }

    .captchas-btn {
      position: absolute;
      right: 2px;
      top: 2px;
      cursor: pointer;
    }

    .oneline-input {
      display: block;

      .el-form-item__content {
        display: flex !important;
        width: 100%;

        .el-input {
          display: flex;
          flex: 1;
        }
      }
    }

    .mail-btn {
      position: absolute;
      right: 4px;
      top: 4px;
    }

    .other-way {
      display: inline-block;
      margin-left: 16px;

      span {
        margin-right: 4px;
      }
    }
  }
}
</style>

后端,使用 laravel 提供接口。这里使用 Sanctum 做 api 认证,使用的是 cookie 方式。已经走通。但是非同域名下,该方式存在问题。还没解决。如果是用 token 的方式 ,就不会有这个问题。

最后

这里没有使用 vuex ,也没有使用 vue-element-admin 后边有时间会去操作一波。

发表评论

电子邮件地址不会被公开。 必填项已用*标注