仿京东 项目笔记2(注册登录)

这里写目录标题

  • 1. 注册页面
    • 1.1 注册/登录页面——接口请求
    • 1.2 Vue开发中Element UI的样式穿透
      • 1.2.1 ::v-deep的使用
      • 1.2.2 elementUI Dialog内容区域显示滚动条
    • 1.3 注册页面——步骤条和表单联动 steps+form
    • 1.4 注册页面——滑动拼图验证
    • 1.5 注册页面——element-ui组件Popover 弹出框 条件控制显示和隐藏 滑块验证码
    • 1.6 注册页面——获取手机验证码,进行手机号校验、验证码CD60秒
    • 1.7 注册页面——element-ui组件el-autocomplete带输入建议 自动补全后缀(邮箱地址)
    • 1.8 注册页面——用户、密码强弱校验+密码自定义规则校验、提交验证
  • 2. 登录页面
    • 2.1 登录页面——接口
    • 2.2 登录——token
    • 2.3 登录页面——退出登录
    • 2.4 登录页面——全局导航守卫
    • 2.5 路由独享守卫
    • 2.6 组件导航守卫
    • 2.7 全局封装API
  • 3. 二维码生成
  • 4. 二级路由拆分(个人中心)
  • 5. 图片懒加载
  • 6. 路由懒加载
  • 7. 项目上线
    • 7.1 打包
    • 7.2 购买云服务器

1. 注册页面

1.1 注册/登录页面——接口请求

api/index.js

/*--------- 用户注册登录 ---------*/

//获取验证码
export const reqGetCode = (phone) => requests({url: `/user/passport/sendCode/${phone}`, method: 'get'})

//用户注册
export const reqUserRegister = (data) => requests({url: '/user/passport/register', method: 'post', data: data})

store/user.js

import { reqGetCode, reqUserRegister } from "@/api"

//home模块的Vuex模块
const state = {
    //state中数据默认初始值别瞎写,根据接口的返回值进行初始化
    code: '',
}
const mutations = {
    GET_CODE(state, code) {
        state.code = code
    },
}
const actions = {
    //获取验证码
    async getCode({commit}, phone){
        //获取验证码的这个接口,把验证码返回,正常情况下,后台把验证码发到用户手机上
        let result  = await reqGetCode(phone)
        // console.log("result",result) 
        if(result.code == 200){
            commit('GET_CODE',result.data)
            return 'ok'
        } else {
            return Promise.reject(new Error("fail"))
        }
    },
    //用户注册
    async userRegister({commit}, userFrom) {
        let result = await reqUserRegister(userFrom) 
        if(result.code == 200) {
            return 'ok'
        } else {
            return Promise.reject(new Error(result.message))
        }
    },
}

export default{
    state,
    getters,
    mutations,
    actions
}

1.2 Vue开发中Element UI的样式穿透

1.2.1 ::v-deep的使用

参考::v-deep的使用

在 vue 项目的开发过程,使用了 ElementUI 组件且样式 style 使用了 scoped 属性,当想要修改组件样式,发现直接修改不了,需去掉 scoped 属性或者使用深度选择器才能修改成功。去掉scoped的话又会影响全局样式,针对这种情况,可以使用深度作用选择器(即样式穿透)

1、当项目中使用的 css 原生样式 ,需要使用 >>> 深度选择器来修改 外用第三方组件的样式

<style lang="css" scoped>
    .el-button >>> span{
        color: '#f00'
    }
</style>

2、当项目中使用的 css 扩展语言是 less, 需要使用 /deep/ 或者 ::v-deep 深度选择器来修改 外用第三方组件的样式

<style lang="less" scoped>
    /deep/.el-button{
         span{
                color: '#f00'
         }
    }

    .el-button::v-deep{
         span{
                color: '#f00'
         }
    }
</style>


3、当项目中使用的 css 扩展语言是 node-sass, 需要使用 /deep/ 或者 ::v-deep 深度选择器来修改 外用第三方组件的样式

<style lang="scss" scoped>
    .el-button::v-deep{
         span{
                color: '#f00'
         }
    }

    /deep/.el-button{
         span{
                color: '#f00'
         }
    }
</style>

4、当项目中使用的 css 扩展语言是 dart-sass, 需要使用 ::v-deep 深度选择器来修改 外用第三方组件的样式,dart-sass不支持 /deep/ 和 >>> 的写法

<style lang="scss" scoped>
    .el-button::v-deep{
         span{
                color: '#f00'
         }
    }
</style>

注意:
① 操作符 >>> 可能会因为无法编译而报错,可以使用 /deep/
② vue3.0 中使用 /deep/ 会报错,更推荐使用 ::v-deep
③ 对于使用了 css 预处理器(scss 、sass、 less)时,深度选择器 ::v-deep 比较通用

1.2.2 elementUI Dialog内容区域显示滚动条

所以在项目,我要使对话框的内容区域显示滚动条,同时去掉对话框原有的外部滚动条,使用样式穿透,代码如下:

<el-dialog title="Dialog" class="roll-dialog"> </el-dialog>
.rolling-dialog {
    overflow: hidden;
    ::v-deep .el-dialog .el-dialog__body {
      overflow-y: scroll;
      height: 400px;
    }
}

实际开发中还有对话框内容区域高度自适应的要求,可以阅读这篇 Element UI 弹窗(Dialog)改成自适应高度,仅body内容部分滚动

效果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aIKYH0Br-1693146642040)(C:\Users\crj\AppData\Roaming\Typora\typora-user-images\image-20230715185749623.png)]

1.3 注册页面——步骤条和表单联动 steps+form

element的步骤条整合表单(steps+form)

场景

image-20230718153429816image-20230718153502570

在vue开发中,注册页面填写的信息过多,如果全部一起呈现,效果不是很好,这时候就可以用到步骤条来分步注册,这里用到了ElementUI里的steps组件和form

页面代码如下:

<template>

//第一步:定义出4个步骤
<el-steps :active="active" finish-status="success" align-center :space="200" class="steps">
  <el-step title="验证手机号"></el-step>
  <el-step title="填写帐号信息"></el-step>
  <el-step title="注册成功" status="success"></el-step>
</el-steps>

//第二步:定义form表单
<el-form
  ref="registerForm"
  :model="registerForm"
  :rules="rules"
  class="form-body">
    
//第三步:定义3个盒子对象active =>0 到 2
<div v-show="active == 0">
	//第四步:放置表单项
	//...
	<el-form-item class="form-item" prop="phoneNum">
	    <el-input clearable placeholder="建议使用常用手机号" v-model="registerForm.phoneNum">
	</el-form-item>
	
</div>
<div v-show="active == 1"></div>
<div v-show="active == 2"></div>

</el-form>

//第五步:设置上一步和下一步的按钮
<el-button v-if="active < 3" style="margin-top: 12px" @click="next">下一步</el-button>
<el-button v-if="active > 1" style="margin-top: 12px" @click="pre">上一步</el-button>
   
 
</template>

对应的属性和方法

data() {
    return {
    	 //默认第一步
		 active: 0,
	}
},
methods: {
    // 步骤条下一步的方法
    next() {
      if (this.active++ > 2) this.active = 0
    },
     // 步骤条上一步的方法
    pre() {
      if (this.active-- < 0) this.active = 0
    },
    
 }

1.4 注册页面——滑动拼图验证

Vue实现滑块拼图验证,这里使用了vue-monoplasty-slide-verify插件

参考 vue实现登录滑动拼图验证的两种方法,纯前端组件验证以及前后端同时验证

1.5 注册页面——element-ui组件Popover 弹出框 条件控制显示和隐藏 滑块验证码

场景:输入手机号码,手机格式正确,点击按钮验证才可显示弹出框

image-20230714164134589image-20230714164118737

手动控制el-popver弹窗的显示与隐藏,给el-popver层绑定一个v-model,值为true或是false,这是官网上给的Attributes。

<el-popover v-model="showPopover">

在这里插入图片描述

而且显示根本不用控制,el-popover有一个trigger属性,trigger可以为click/focus/hover/manual,默认值是click,所以单击就能触发,主要是弹窗的隐藏问题。

我们使用manul来控制显示

<el-popover trigger="manual">

实际代码:

<el-popover placement="top" width="320" trigger="manual" v-model="showSliderVerify">
  <div class="popper-title">
    <span>完成拼图验证</span>
    <i class="el-icon-close close-icon" @click="showSliderVerify=false"></i>
  </div>
  <slide-verify :l="42" :r="10" :w="310":h="155" 
                slider-text="向右滑动" 
                @success="onSuccess" 
                @fail="onFail" 
                @refresh="onRefresh">
  </slide-verify>
  <div style="margin-top: 15px">{{ msg }}</div>
  <!--点击控制弹窗的显示-->
  <el-button slot="reference" style="width: 400px" @click="openSliderVerify">
      点击按钮进行验证
  </el-button>
</el-popover>
<script>
  export default {
    data() {
      return {
        showSliderVerify:false,//v-model默认值是false, click激活变成true
        msg: ""
      }
    },
    methods: {
        //滑块按钮点击
        openSliderVerify() {
          //只有手机号码通过才能显示滑块验证码
          this.$refs.registerForm.validateField("phoneNum", async (valid) => {
            if (!valid) {
              //手机号码格式正确,才可以显示滑块验证码
              this.showSliderVerify = true;
            } else {
              return false;
            }
          });
        },
        //滑块验证通过
        onSuccess(times) {
          this.msg = `success, 耗时${(times / 1000).toFixed(1)}s`;
          this.showSliderVerify = false;
          this.showCode = true;
          //验证码一通过,就自动获取验证码
          this.getCode();
        },
        //滑块验证失败
        onFail() {
          this.msg = "验证不通过";
        },
        //滑块验证刷新
        onRefresh() {
          this.msg = "";
          console.log("点击了刷新小图标");
        },
        //滑块验证刷新完成
        onFulfilled() {
          this.msg = "重新验证";
        },
    }
  }
</script>

代码中我还设置了样式,但是样式在当前vue文件下,怎么样调整都不动。

后来看来这个blog

原来是要在App.vue下写css样式,

<style lang="less">
.popper-title {
  margin-bottom: 10px;
  display: flex;
  justify-content: space-between;
  font-size: 16px;
  .close-icon {
    cursor: pointer;
    font-size: 20px;
    color: #4f4f4f;
  }
}
</style>

原因可以看下面这张图,你会发现 app 和 el-popover 是平级,又因为我们每个组件的style标签都写有 scoped 属性,所以在组件里写样式不起效

img

1.6 注册页面——获取手机验证码,进行手机号校验、验证码CD60秒

参考blog

场景

手机输入不能为空且必须为正确格式
在这里插入图片描述

点击下一步,如果未完成验证,则不可以进行下一步注册步骤

完成滑块拼图验证后,立即自动发送验证码,60s有效期,
在这里插入图片描述

由于注册页面用到了前面的提到的steps,所以手机及验证码的输入框是在 <div v-show="active == 0"></div>内,且只有手机号码输入正确,才可进行滑块拼图验证,滑块拼图验证通过之后,立即发送验证码

页面代码如下

<el-steps :active="active" finish-status="success" align-center :space="200" class="steps" >
  <el-step title="验证手机号"></el-step>
  <el-step title="填写帐号信息"></el-step>
  <el-step title="注册成功"></el-step>
</el-steps>
<el-form ref="registerForm" :model="registerForm" :rules="rules" class="form-body">
   <!-- 步骤条 active==0 -->
   <div v-show="active == 0">
    <el-form-item class="form-item" prop="phoneNum">
      <el-input clearable placeholder="建议使用常用手机号" v-model="registerForm.phoneNum">
      <el-select
          v-model="registerForm.select"
          placeholder="中国+86"
          slot="prepend"
          style="width: 120px"
        >
          <el-option label="中国+86" value="中国+86"></el-option>
          <el-option label="+40" value="+40"></el-option>
          <el-option label="+111" value="+111"></el-option>
        </el-select>
      </el-input>
    </el-form-item>
    <el-form-item class="form-item" v-show="!showCode">
      <el-popover placement="top" width="320" trigger="manual" v-model="showSliderVerify">
        <div class="popper-title">
          <span>完成拼图验证</span>
          <i class="el-icon-close close-icon" @click="showSliderVerify = false"></i>
        </div>
        <slide-verify :l="42" :r="10" :w="310" :h="155" :imgs="bgimgs"
          slider-text="向右滑动"
          @success="onSuccess"
          @fail="onFail"
          @refresh="onRefresh"
        ></slide-verify>
        <div style="margin-top: 15px">{{ msg }}</div>
        <el-button slot="reference" class="wd400" @click="openSliderVerify">
            点击按钮进行验证
        </el-button>
      </el-popover>
      <div v-show="validateCode" class="el-form-item__error">请完成验证</div>
    </el-form-item>
    <el-form-item prop="code" class="form-item" v-show="showCode">
      <el-input clearable v-model="registerForm.code" placeholder="请输入验证码">
        <template slot="prepend">手机验证码</template>
        <el-button slot="append" :disabled="codeCd" size="samll" @click="getCode">
          <span v-if="codeCd">{{ long }}后重新获取</span>
          <span v-else>获取验证码</span>
        </el-button>
      </el-input>
    </el-form-item>
    <el-form-item class="form-item">
      <el-button class="wd400" @click="next">下一步</el-button>
    </el-form-item>
  </div>
</el-form>

对应的逻辑代码如下

export default {
  name: "Register",
  data() {
    //验证手机号
    const validatePhone = (rule, value, callback) => {
      if (!value) {
        callback(new Error("手机号码不能为空"));
      }
      // 使用正则表达式验证手机号码
      if (!/^1[3456789]\d{9}$/.test(value)) {
        callback(new Error("手机号码格式不正确"));
      }
      //自定义校验规则,需要调用callback()函数
      callback();
    };
    return {
      registerForm: {
        phoneNum: null,
        code: "",
      },
      rules: {
        phoneNum: [
          {
            required: true,
            validator: validatePhone,
            trigger: "blur",
          },
        ],
        code: [
          {
            required: true,
            message: "验证码不能为空!",
            trigger: "blur",
          },
        ],
      },
      //验证码秒数倒计时
      long: 60,
      //验证码是否等候
      codeCd: false,
      //滑块拼图验证码msg
      msg: "",
      //滑块验证码背景图
      bgimgs: [],
      //步骤条的active
      active: 0,
      //是否显示滑块拼图验证
      showSliderVerify: false,
      //是否显示验证码
      showCode: false,
      //验证是否完成拼图滑块
      validateCode: false
     };
  },
  methods: {
    //滑块按钮点击
    openSliderVerify() {
      //只有手机号码通过才能显示滑块验证码
      this.$refs.registerForm.validateField("phoneNum", async (valid) => {
        if (!valid) {
          //手机号码格式正确,才可以显示滑块验证码
          this.showSliderVerify = true;
        } else {
          return false;
        }
      });
    },
    //步骤条下一步
    next() {
      //当前表格是否进行验证
      const { phoneNum, code } = this.registerForm;
      const { showCode } = this;
      //验证手机号
      if (!phoneNum) {
        this.$refs.registerForm.validateField("phoneNum");
        return;
      }
      //验证滑块拼图验证码
      if (!showCode) {
        //展示提示信息
        this.validateCode = true       
        return;
      } //完成滑块验证后,验证是否填写验证码
      else if (showCode && !code) {
        this.$refs.registerForm.validateField("code");
        return;
      }
      //以上都通过才进入下一步
      if (this.active++ > 2) this.active = 0;
    },
    //获取手机验证码
    getCode() {
      this.$refs.registerForm.validateField("phoneNum", async (valid) => {
        // valid是验证手机号码是否通过
        if (!valid) {
          // 获取验证码
          try {
            //发送验证码
            this.$store.dispatch("getCode", this.registerForm.phoneNum);
            //开始计时,60秒倒计时
            this.codeCd = true;
            const timer = setInterval(() => {
              this.long--;
              if (this.long <= 0) {
                this.long = 60;
                this.codeCd = false;
                clearInterval(timer);
              }
            }, 1000);
            //假设手动输入验证码
            this.registerForm.code = this.$store.state.user.code;
          } catch (error) {
            alert(error.message);
          }
        } else {
          return false;
        }
      });
    },
    //滑块验证通过
    onSuccess(times) {
      this.msg = `success, 耗时${(times / 1000).toFixed(1)}s`;
      this.showSliderVerify = false;
      this.showCode = true;
      //验证码一通过,就自动获取验证码
      this.getCode();
    },
    //滑块验证失败
    onFail() {
      this.msg = "验证不通过";
    },
    //滑块验证刷新
    onRefresh() {
      this.msg = "";
      //console.log("点击了刷新小图标");
    },
    //滑块验证刷新完成
    onFulfilled() {
      this.msg = "重新验证";
    },
  1. 进行手机号校验关键在对单个手机号输入框进行校验,需要使用到validateField对部分表单字段进行校验,valid是校验完的提示信息,当valid为空时代表校验成功
  2. 读秒和设置禁用,在校验成功时设置一个60s计时器,读秒过程禁用按钮,用了element-ui的按钮组件,在读秒过程中给按钮增加disabled属性;读秒过程结束,解除按钮禁用

1.7 注册页面——element-ui组件el-autocomplete带输入建议 自动补全后缀(邮箱地址)

效果:实现输入数字,自动补齐邮箱后缀
在这里插入图片描述

autocomplete 是一个可带输入建议的输入框组件,fetch-suggestions 是一个返回输入建议的方法属性,如 querySearch(queryString, cb),在该方法中你可以在你的输入建议数据准备好时通过 cb(data) 返回到 autocomplete 组件中。

<el-autocomplete
    clearable
    v-model="registerForm.email"
    :fetch-suggestions="emailSuffix"
    :trigger-on-focus = 'false'
    @select="selectEmailSuffix"
    class="wd400"
    placeholder="请输入邮箱"
  >
    <template slot="prepend">邮箱验证</template>
  </el-autocomplete>
data(){
    return {
        suffix: []
    }
},
mounted() {
  this.suffix = this.loadAll()
},
methods: {
    //邮箱后缀输入建议
    emailSuffix(queryString, callback) {
      console.log(queryString);
      let suffix = this.suffix
      let results = JSON.parse(JSON.stringify(suffix))
      for(let item in results) {
        results[item].value = queryString + '' + suffix[item].value
      }
      callback(results)
    },
    //选择的哪个值
    selectEmailSuffix(item) {
      console.log(item);
    },
    loadAll() {
      return [
        {"value": "@qq.com"},
        {"value": "@126.com"},
        {"value": "@163.com"},
        {"value": "@sohu.com"},
        {"value": "@Gmail.com"},
        {"value": "@Sina.com"}
      ]
    },
}

1.8 注册页面——用户、密码强弱校验+密码自定义规则校验、提交验证

参考vue3+ts+element-plus密码强弱校验+密码自定义规则校验

博客中用的是Vue3,自己项目中用的Vue2,去掉密码规则中的"是否包含3个及以上键盘连续字符;(横向、斜向都包括)"这一项,其他要求都差不多。

修修改改,实现效果如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

页面代码

<el-form ref="registerForm" :model="registerForm" :rules="rules" class="form-body">
    <div v-show="active ==0 ">
       <!-- 验证手机号.... -->
    </div>
    <div v-show="active == 1">
        <el-form-item class="form-item" prop="username">
          <el-input clearable v-model="registerForm.username" placeholder="账户唯一识别,可用来登录">
            <template slot="prepend">账号名</template>
          </el-input>
        </el-form-item>
        <el-form-item class="form-item" prop="password">
          <el-input
            clearable
            v-model="registerForm.password"
            show-password
            placeholder="请输入包含英文字母大小写、数字和特殊符号的 8-16 位组合"
          >
            <template slot="prepend">设置密码</template>
          </el-input>
          <div class="barbox"
               v-if="registerForm.password !== '' && registerForm.password !== undefined">
            <div class="strength" :style="{ color: barColor }">{{ strength }} </div>
            <div class="bar" :style="{ background: barColor, width: width + '%' }"></div>
          </div>
        </el-form-item>
        <el-form-item class="form-item" prop="repassword">
          <el-input
            clearable
            v-model="registerForm.repassword"
            show-password
            placeholder="请再次输入密码"
          >
            <template slot="prepend">确认密码</template>
          </el-input>
        </el-form-item>
        <el-form-item class="form-item" prop="email">
          <el-autocomplete
            clearable
            v-model="registerForm.email"
            :fetch-suggestions="emailSuffix"
            :trigger-on-focus="false"
            @select="selectEmailSuffix"
            class="wd400"
            placeholder="请输入邮箱"
          >
            <template slot="prepend">邮箱验证</template>
          </el-autocomplete>
        </el-form-item>
        <el-form-item class="form-item" prop="emailCode">
          <el-input
            clearable
            v-model="registerForm.emailCode"
            placeholder="请输入邮箱验证码"
          >
            <template slot="prepend">邮箱验证码</template>
            <el-button
              slot="append"
              :disabled="emailCodeCd"
              size="samll"
              @click="getEmailCode"
            >
              <span v-if="emailCodeCd">{{ emaillong }}后重新获取</span>
              <span v-else>获取验证码</span>
            </el-button>
          </el-input>
        </el-form-item>
        <el-form-item class="form-item">
          <el-button class="wd400" @click="submitForm">立即注册</el-button>
        </el-form-item>
    </div>
    <div v-show="active == 2">
        <div class="registerOk">
          <i class="el-icon-time icon"></i>
          <h1>恭喜您 {{ this.registerForm.username }}</h1>
          <span>您已成功注册为京东用户,祝您购物愉快~</span>
          <router-link class="btn" to="/home">去购物</router-link>
        </div>
     </div>
</el-form>

css代码

.barbox {
    display: flex;
    align-items: center;
    height: 26px;
    .strength {
      font-size: 13px;
      color: #271e25;
      transition: 0.5s all ease;
      margin-right: 5px;
      flex-shrink: 0;
    }
    .bar {
      height: 5px;
      background: red;
      transition: 0.5s all ease;
      max-width: 400px;
    }
}
.registerOk {
  color: #333;
  font-size: 14px;
  display: flex;
  flex-flow: column;
  align-items: center;
  .icon {
    font-size: 40px;
    color: green;
  }
  h1 {
    font-size: 40px;
    margin: 16px 0;
  }
  span {
    margin-bottom: 16px;
  }
  .btn {
    padding: 10px 20px;
    background-color: #c81623;
    color: #fff;
  }
}

对应的逻辑代码

//引入验证方法
import { checkPasswordRule, level } from "./CheckPassword";
export default {
  name: "Register",
  data() {
    //验证手机号
    const validatePhone = (rule, value, callback) => {
      if (!value) {
        callback(new Error("手机号码不能为空"));
      }
      // 使用正则表达式验证手机号码
      if (!/^1[3456789]\d{9}$/.test(value)) {
        callback(new Error("手机号码格式不正确"));
      }
      //自定义校验规则,需要调用callback()函数
      callback();
    };
    //密码验证
    const passwordValidate = (rule, value, callback) => {
      if (!value) {
        callback(new Error("密码不能为空"));
      } else {
        let name =
          this.registerForm.username === "" ? "" : this.registerForm.username;
        const result = checkPasswordRule(value, name);
        if (result === "校验通过") {
          callback();
        } else {
          callback(new Error(result));
        }
      }
      //该部分是只验证密码是否满足reg正则,而不进行强弱校验
      // const reg = /^(?=.*[A-Za-z])(?=.*[0-9])[A-Za-z0-9]{8,16}$/g;
      // if (!reg.test(value)) {
      //   callback(new Error("请输入包含英文字母、数字的 8-16 位组合"));
      // } else {
      //   callback();
      // }
    };
    //密码与确认密码不一样,一定要写在data里,但不是return里
    const repeatValidate = (rule, value, callback) => {
      if (!value) {
        callback(new Error("请再次输入密码"));
      } else if (value !== this.registerForm.password) {
        callback(new Error("两次输入密码不一致"));
      } else {
        callback();
      }
    };
    return {
      registerForm: {
        username: "",
        phoneNum: null,
        code: "",
        email: "",
        password: "",
        repassword: "",
        emailCode: "",
      },
      rules: {
        phoneNum: [
          {
            required: true,
            validator: validatePhone,
            trigger: "blur",
          },
        ],
        code: [
          {
            required: true,
            message: "验证码不能为空!",
            trigger: "blur",
          },
        ],
        password: [
          {
            required: true,
            validator: passwordValidate,
            trigger: "blur",
          },
        ],
        repassword: [
          {
            required: true,
            validator: repeatValidate,
            trigger: "blur",
          },
        ],
        username: [
          {
            required: true,
            message: "账户名不能为空",
            trigger: "blur",
          },
          {
            min: 4,
            max: 30,
            message: "账户名长度在4至30个字符之间",
            trigger: "blur",
          },
        ],
        email: [
          {
            required: true,
            message: "请输入邮箱地址",
            trigger: "blur",
          },
          {
            type: "email",
            message: "请输入正确的邮箱地址",
            trigger: ["blur", "change"],
          },
        ],
        emailCode: [
          {
            required: true,
            message: "邮箱验证码不能为空!",
            trigger: "blur",
          },
        ],
      },
      //验证码秒数倒计时
      long: 60,
      emaillong: 300,
      //密码强度背景色
      barColor: "",
      //密码强度长度
      width: "",
      //密码强度
      strength: "",
      //验证码是否等候
      codeCd: false,
      //邮箱验证码等候
      emailCodeCd: false,
    };
  },
  methods: {
    //提交表单_用户注册
    submitForm() {
      this.$refs["registerForm"].validate(async(valid)=>{
        if(valid) {
           try {
            const { phoneNum, code, password } = this.registerForm;
              phoneNum && code && password && (await this.$store.dispatch("userRegister", {
                phone: phoneNum,
                code: code,
                password:password,
              }));
			
            this.active++;
            // this.$router.push("/login");
          } catch (error) {
            alert(error.message);
          }
        }else {
          return false
        }
      })
    },  
  },
  watch: {
    "registerForm.password"(newVal, oldVal) {
      if (newVal !== "") {
        const res = level(newVal);
        this.strength = res;
        switch (res) {
          case "非常强":
            this.barColor = "#1B8EF8";
            this.width = "100";
            break;
          case "强":
            this.barColor = "green";
            this.width = "80";
            break;
          case "一般":
            this.barColor = "orange";
            this.width = "60";
            break;
          case "弱":
            this.barColor = "#ee795c";
            this.width = "40";
            break;
          case "非常弱":
            this.barColor = "red";
            this.width = "20";
            break;
        }
      }
    },
  },

强弱校验、规则校验 CheckPassword.js:

// 数字
const REG_NUMBER = '.*\\d+.*'
//大写字母
const REG_UPPERCASE = '.*[A-Z].*'
//小写字母
const REG_LOWERCASE = '.*[a-z].*'
//特殊符号
const REG_SYMBOL = ".*[~!@#$%^&*()_+|<>,.?/:;'\\[\\]{}\"]+.*"
/**
 * 校验密码是否符合条件
 * @param password 密码
 * @param username 用户名
 */
export const checkPasswordRule = (password, username) => {
    if(password === '' ) {
        return "密码不能为空"
    }else if (password.length < 8 || password.length > 20) {
        return "密码长度应大于8小于20"
    } 
    if(username && password.indexOf(username) !== -1) {
        return "请勿包含用户名"
    }
    if(isContinuousChar(password)) {
        return "请勿包含3个及以上相同或连续的字符"
    }
    let i = 0
    if(password.match(REG_NUMBER)) i++
    if(password.match(REG_UPPERCASE)) i++
    if(password.match(REG_LOWERCASE)) i++
    if(password.match(REG_SYMBOL)) i++
    if(i<2) {
        return "数字、小写字母、大写字母、特殊字符,至少包含两种";
    }
    return "校验通过"
}

/**
 * 是否包含3个及以上相同或字典连续字符
 */
const isContinuousChar = (password) => {
    let chars = password.split('')
    let charCode = []
    for(let i=0; i<chars.length-2; i++) {
        charCode[i] = chars[i].charCodeAt(0)
    }
    for(let i=0; i<chars.length-2; i++) {
        let n1 = charCode[i]
        let n2 = charCode[i+1]
        let n3 = charCode[i+2]
        //判断重复字符
        if(n1 == n2 && n2 == n3) {
            return true
        }
        //判断连续字符: 正序+倒序
        if((n1 + 1 == n2 && n2 + 2 == n3) || (n1 - 1 == n2 && n2 - 2 == n3)) {
            return true
        }
    }
    return false
}

/**
 * 密码强度校验
 */

/**
 * 长度
 * @param str 
 */
const length = (str) => { 
    if(str.length<5){ 
        return 5;
    }else if(str.length<8){
        return 10;
    }else{
        return 25;
    }
}


/**
 * 字母
 * @param str 
 */
const letters = (str)=> {
    let count1 = 0, count2 = 0
    for(let i=0; i<str.length; i++) {
        if(str.charAt(i) >= 'a' && str.charAt(i) <= 'z'){
            count1++
        }
        if(str.charAt(i) >= 'A' && str.charAt(i) <= 'Z'){
            count2++
        }
    }
    if(count1==0 && count2==0) {
        return 0
    }
    if(count1!=0 && count2!=0) {
        return 20
    }
    return 10
}

/**
 * 数字
 * @param str 
 */
const numbers = (str)=> {
    let count = 0
    for(let i=0; i<str.length; i++) {
        if(str.charAt(i) >= '0' && str.charAt(i) <= '9'){
            count++
        }
    }
    if(count==0) {
        return 0
    }
    if(count==1) {
        return 10
    }
    return 20
}

/**
 * 符号
 * @param str 
 */
const symbols = (str)=> {
    let count = 0
    for(let i=0; i<str.length; i++) {
        if(str.charCodeAt(i)>=0x21 && str.charCodeAt(i)<=0x2F ||
        str.charCodeAt(i)>=0x3A && str.charCodeAt(i)<=0x40 ||
        str.charCodeAt(i)>=0x5B && str.charCodeAt(i)<=0x60 ||
        str.charCodeAt(i)>=0x7B && str.charCodeAt(i)<=0x7E ){
        count++;
    }
    }
    if(count==0) {
        return 0
    }
    if(count==1) {
        return 10
    }
    return 25
}

/**
 * 得分机制
 * @param str 
 */
const rewards = (str) => {
    let letter = letters(str)
    let number = numbers(str)
    let symbol = symbols(str)
    if(letter>0 && number>0 && symbol==0){    //字母和数字
        return 2;
    }
    if(letter==10 && number>0 && symbol>0){    //字母、数字和符号
        return 3;
    }
    if(letter==20 && number>0 && symbol>0){   //大小写字母、数字和符号
        return 5;
    }
    return 0;
}


/**
 * 最终评分
 * @param str 
 */
export const level = (str) => {
    let lengths=length(str);//长度
    let letter=letters(str);//字母
    let number=numbers(str);//数字
    let symbol=symbols(str);//符号
    let reward=rewards(str);//奖励
    let sum = lengths+letter+number+symbol+reward
    if(sum>=80) {
        return '非常强'
    }else if (sum>=60) {
        return "强"
    }else if(sum>=40) {
        return '一般'
    }else if(sum>=25) {
        return '弱'
    }else {
        return "非常弱"
    }
}

常用的密码校验正则和 Regex 正则表达式

包含英文字母大小写、数字和特殊符号的 8-16 位组合

/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[._~!@#$^&*])[A-Za-z0-9._~!@#$^&*]{8,16}$/g

2. 登录页面

2.1 登录页面——接口

api/index.js

/*--------- 用户登录 ---------*/

//登录
export const reqUserLogin = (data) => requests({url: '/user/passport/login', method: 'post', data: data})

//登陆后获取用户信息(需要带着用户的token向服务器要用户信息)
export const reqUserInfo = () => requests({url: '/user/passport/auth/getUserInfo',method: 'get'})

//退出登录
export const reqLogout = () => requests({url: '/user/passport/logout',method: 'get'})

store/user.js

import { reqUserLogin } from "@/api"

//home模块的Vuex模块
const state = {
    //state中数据默认初始值别瞎写,根据接口的返回值进行初始化
    token: localStorage.getItem("TOKEN"),
    userInfo: {}
}
const mutations = {
    USER_LOGIN(state, token){
        state.token = token
    },
    USER_INFO(state, data) {
        state.userInfo = data
    },
    USER_CLEAR(state) {
      //清除本地数据
      state.token = ""
      state.userInfo = {}
      localStorage.removeItem("TOKEN")
    }
}
const actions = {
    //用户登录
    async userLogin({commit}, userFrom) {
        let result = await reqUserLogin(userFrom) 
        //服务器下发token,用户唯一标识符(uuid)
        //将来经常通过带token找服务器要用户信息进行展示
        if(result.code == 200) {
            //token存入vuex
            commit("USER_LOGIN", result.data.token)
            //持久化存储token
            localStorage.setItem('TOKEN', result.data.token)
            return 'ok'
        } else {
            return Promise.reject(new Error(result.message))
        }
    },
    //获取用户信息
    async getUserInfo({commit}) {
        let result = await reqUserInfo()
        if(result.code == 200) {
            commit("USER_INFO", result.data)
            return 'ok'
        } else {
            return Promise.reject(new Error(result.message))
        }
    },
    //退出登录
    async userLogout({commit}) {
        let result = await reqLogout()
        if(result.code == 200) {
            commit("USER_CLEAR", result.data)
            return 'ok'
        } else {
            return Promise.reject(new Error(result.message))
        }
    }
}

export default{
    state,
    getters,
    mutations,
    actions
}

2.2 登录——token

登录成功的时候,后台为了区分你这个用户是谁-服务器下发token[令牌:唯一标识符]

一般登录成功服务器会下发token,前台持久化存储token,[带着token找服务器要用户信息进行展示]

Vuex不是持久化存储,如果token存储在Vuex,页面一刷新token数据就没有了,所以使用localStorage存储

登陆组件methods登陆函数userLogin

methods: {
      async userLogin() {
        try {
          const {phone, password} = this
          phone && password && await this.$store.dispatch('userLogin', {phone,password})
          //登陆成功,有query参数,就跳到query参数指定的路由,无query参数,跳到home组件
          //这个query参数是导航守卫设置的,next("/login?redirect="+toPath),toPath是原路由
          let toPath = this.$route.query.redirect || '/home'
          this.$router.push(toPath)
        } catch (error) {
           alert(error.message)
        }
        
      }
    }

登录成功后跳转到指定路由或是首页,若是跳转到首页,在首页获取用户信息,向服务器请求用户信息,需要携带token

view/home/index.vue

mounted() {
// 触发vuex的异步action调用, 从mock接口请求数据到state中
this.$store.dispatch("getFloorList")

//获取用户信息在首页展示
this.$store.dispatch("getUserInfo")
},

api/request.js 下的请求拦截器,设置携带token

//配置请求拦截器
requests.interceptors.request.use(config => {
    //config内主要是对请求头Header配置
    //比如添加token
    //1、先判断uuid_token是否为空
    if(store.state.detail.uuid_token) {
        //2、userTempId字段和后端统一
        config.headers['userTempId'] = store.state.detail.uuid_token
    }
    //需要携带token带给服务器
    if(store.state.user.token) {
        config.headers.token = store.state.user.token
    }
    //开启进度条
    nprogress.start()
    return config;
})

登录接口返回的token
在这里插入图片描述

获取用户信息接口,携带token

在这里插入图片描述

2.3 登录页面——退出登录

methods: {
    //退出登录
    async logout() {
      //1.发请求,通知服务器退出登录(清除数据,如token)
      //2. 清楚项目当中的用户数据(userInfo)
      try {
        //退出成功
        await this.$store.dispatch("userLogout")
        //回到首页
        this.$router.push('/home')
      } catch (error) {
        alert(error.message)
      }
    }
}

2.4 登录页面——全局导航守卫

存在的问题:

  1. 多个组件需要展示用户信息,需要在每一个组件的mounted中触发 获取用户信息接口函数
  2. 用户已经登录,不应该再回登陆页面
  3. 未登录,不允许跳转到购物车和订单

流程

在这里插入图片描述

为什么要判断name?

因为store中的token是通过localStorage获取的,token有存放在本地。当页面刷新时,本地token不会消失,所以store中的token也不会消失。但是,store中的其他数据(用户信息等)会清空,此时会出现用户信息不存在,但是有token,这种情况是不可以访问其他页面的,必须先去获取用户信息。由于用户信息是一个对象,所以我们通过它的一个属性name判断用户信息是否存在。
所以不仅要判断token,还要判断用户信息

router/index.js全局前置守卫代码

//设置全局导航前置守卫
router.beforeEach(async(to, from, next) =>  {
    let token = store.state.user.token
    let name = store.state.user.userInfo.name
    //1、有token代表登录,全部页面放行
    if(token){
        //1.1登陆了,不允许前往登录页
        if(to.path==='/login'){
            next('/home')
        } else{
            //1.2、因为store中的token是通过localStorage获取的,token有存放在本地
            // 当页面刷新时,token不会消失,但是store中的其他数据会清空,
            // 所以不仅要判断token,还要判断用户信息

            //1.2.1、判断仓库中是否有用户信息,有放行,没有派发actions获取信息
            if(name)
                next()
            else{
                //1.2.2、如果没有用户信息,则派发actions获取用户信息
                try{
                    await store.dispatch('getUserInfo')
                    next()
                }catch (error){
                    //1.2.3、获取用户信息失败,原因:token过期
                    //清除前后端token,跳转到登陆页面
                    await store.dispatch('logout')
                    next('/login')
                }
            }
        }
    }else{
        //2、未登录,首页或者登录页可以正常访问
        //2. 未登录,支付页面、订单页
       let toPath = to.path
       if(toPath.indexOf('/pay') !== -1 || 
          toPath.indexOf('/trade')!==-1 || 
          toPath.indexOf('/center')!==-1) 
       {
          alert("请先登录")
          //登录成功后,回到原页面,源地址存储在地址栏中
      	  next("/login?redirect="+toPath)
        } else {
          // 其他可以正常访问
          next()
        }
    }
})

2.5 路由独享守卫

全局导航守卫已经帮助我们限制未登录的用户不可以访问相关页面。但是还会有一个问题。
例如:

用户已经登陆,用户在home页直接通过地址栏访问trade结算页面,发现可以成功进入该页面,正常情况,用户只能通过在shopcart页面点击去结算按钮才可以到达trade页面。我们可以通过路由独享守卫解决该问题

路由独享的守卫:只针对一个路由的守卫,所以该守卫会定义在某个路由中。
以上面问题为例,我们可以通过路由独享的守卫解决。
在trade路由信息中加入路由独享守卫

//交易组件
    {
        name: 'Trade',
        path: '/trade',
        meta: {showFooter: true},
        component:  () => import('@/views/Trade'),
        //路由独享首位
        beforeEnter: (to, from, next) => {
            //购物车页面才可进入交易页面
            if(from.path ===  '/shopcart' ){
                next()
            }else{
                next(false)
            }
        }
    },
 //支付组件
	{
    path: '/pay',
    component: () => import('@/views/Pay'),
    meta: {showFooter: true},
    //路由独享守卫
    beforeEnter: (to, from, next) => {
      if(from.path === '/trade'){
        next()
      } else {
        next(false)
      }
    }   
  },

上面的代码已经实现了trade路由只能从shopcart路由跳转。next(false)指回到from路由。
但是,上面的代码还会有bug,就是当我们在shopcart页面通过地址栏访问trade时还是会成功。正常情况应该是只有当我们点击去结算按钮后才可以进入到trade页面。(这只是我个人观点)
解决办法:
在shopcart路由信息meta中加一个flag,初始值为false。当点击去结算按钮后,将flag置为true。在trade的独享路由守卫中判断一下flag是否为true,当flag为true时,代表是通过点击去结算按钮跳转的,所以就放行。
shopcart路由信息

 //购物车
    {
        path: "/shopcart",
        component: () => import('@/views/ShopCart'),
        meta:{showFooter: true,flag: false},
    },

shopcart组件去结算按钮触发事件

toTrade(){
    this.$route.meta.flag = true
    this.$router.push('/trade')
}

trade路由信息

{
    name: 'Trade',
    path: '/trade',
    meta: {showFooter: true},
    component:  () => import('@/views/Trade'),
    //路由独享首位
    beforeEnter: (to, from, next) => {
        //购物车页面才可进入交易页面
        if(from.path ===  '/shopcart'&& from.meta.flag === true){
            from.meta.flag = false
            next()
        }else{
            next(false)
        }
    }
},

注意,判断通过后,在跳转之前一定要将flag置为false。

2.6 组件导航守卫

支付成功页面,设置只有通过支付页面后才能访问

<script>
  export default {
    name: 'PaySuccess',
    //组件内守卫:通过路由规则,进入该组件时被调用
    //不能获取组件实例——this,因为守卫执行前,组件实例还未被创建
    beforeRouteEnter (to, from, next) {
      if(from.path == '/pay') {
        next()
      }else {
        next(false)
      }
    },
  }
</script>

2.7 全局封装API

推荐:API封装的具体步骤

若是想在组件里调用请求接口,而不通过Vuex来调用请求接口,该如何统一配置api接口,一次调用即可,而不须一个个引入

api/index.js文件是请求接口

main.js

//统一接口api
import * as api from '@/api'
new Vue({
    render: (h) => h(App),
    beforeCreate() {
    	//全局事件总线
    	Vue.prototype.$bus = this;
        Vue.prototype.$api = api;
  },
})

组件内发请求

methods: {
    //提交订单
    submitOrder() {
      console.log(this.$API.reqSubmitOrder());
    }
}

3. 二维码生成

qrcode

import QRCode from "qrcode"
//立即支付弹出框
async openMsgBox(){
    //生成二维码(地址)
    let code = await QRCode.toDataURL(this.payInfo.codeUrl)
    this.$alert(`<img src=${code} />`, '请微信支付', {
      dangerouslyUseHTMLString: true,
      center: true,
      showCancelButton: true,
      confirmButtonText: '已支付成功',
      cancelButtonText: '支付遇见问题',
      showClose: false
    })
}

4. 二级路由拆分(个人中心)

菜单栏

<dt><i>·</i> 订单中心</dt>
    <dd>
      <router-link to="/center/myorder">我的订单</router-link>
    </dd>
    <dd>
      <router-link to="/center/grouporder">团购订单</router-link>
    </dd>
</dt>

个人中心路由

//个人中心
{
    path: '/center',
    component:  () => import('@/views/Center'),
    children: [
        {
            //二级路由要么不写/,要么写全:'/center/myorder'
            path: 'myorder',
            component: () => import('@/views/Center/MyOrder')
        },
        {
            path: 'groupbuy',
            component: () => import('@/views/Center/GroupOrder'),
        },
        //默认显示
        {
            path: '',
            redirect: 'myorder'
        }
    ]
}

{ path: '', redirect: 'myorder' }表示当我们访问center路由时,center中的router-view部分默认显示myorder二级路由内容。
我们的子路由最好放在父路由文件夹下,如下所示。
在这里插入图片描述

注意:当某个路由有子级路由时,父级路由须要一个默认的路由,因此父级路由不能定义name属性

5. 图片懒加载

在网络不好时,每个图片都有一个基础的默认图片,即在请求服务器结束前 加载默认设置的图片

懒加载vue-lazyload插件官网
插件的使用直接参考官方教程,很简单。

npm i vue-lazyload

vue使用插件的步骤,main.js

import VueLazyload from "vue-lazyload

import loadingImg from '@/assets/images/loading.jpeg'
Vue.use(VueLazyload, {
  //懒加载默认的图片
  loading: loadingImg
})

使用懒加载

<img v-lazy="good.defaultImg" />

在使用中报错 如下图所示:

img

因为该 模块 版本问题, 可安装低版本的 vue-lazyload 来解决该问题:

# 先写在原有的安装
npm uninstall vue-lazyload --save

# 再安装低版本的
npm install vue-lazyload@1.3.3 --save

6. 路由懒加载

路由懒加载

当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就会更加高效。

// 将
// import Center from '@/views/Center'
// 替换成
const Center = () => import('./views/Center')
{
        path: '/center',
        component: Center,
        meta: { showFooter: true },
}

component (和 components) 配置接收一个返回 Promise 组件的函数,Vue Router 只会在第一次进入页面时才会获取这个函数,然后使用缓存数据。

7. 项目上线

7.1 打包

npm run build

会生成dist打包文件。

在这里插入图片描述

dist就是我们打包好的项目文件

在这里插入图片描述

dist文件下的js文件存放我们所有的js文件,并且经过了加密,并且还会生成对应的map文件。

**map文件作用:**因为代码是经过加密的,如果运行时报错,输出的错误信息无法准确得知时那里的代码报错。有了map就可以像未加密的代码一样,准确的输出是哪一行那一列有错。

当然map文件也可以去除(map文件大小还是比较大的)
vue.config.js配置productionSourceMap: false即可。
注意:vue.config.js配置改变,需要重启项目
map

7.2 购买云服务器

阿里云、腾讯云
记得重置密码
设置安全组 开放端口
利用xshell等工具登录服务器

nginx
1、如何通过服务器IP地址直接访问到项目?
在服务器上部署dist文件地址:/root/project/dist
2、项目数据来自于哪个服务器
通过nginx从数据服务器拿数据
配置Nginx:在etc文件下

cd etc
ls

安装nginx:yum install nginx

cd nginx

存在文件nginx.conf
编辑vim nginx.conf
解决第一个问题:

location /{
root /root/project/dist;
index index.html;
try_files $uri/ /index.html;
}

解决第二个问题:

location /api{
	proxy_pass http://39.983123.211;

启动nginx服务器:service nginx start

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/102612.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【数据库】通过实例讲清楚,Mongodb的增删查改,分组查询,聚合查询aggregate

目录 一.基础概念 二.数据库的管理 1.创建数据库 2.删除数据库 二.集合的管理 1.显示所有集合 2.创建集合 3.删除当前集合 4.向集合中插入元素 三.文档的管理 1.文档插入 2.文档的更新 3.文档的删除 4.文档查询 &#xff08;1&#xff09;查询基本语法&#xff1…

多机单目标跟踪Cross-Drone Transformer Network for Robust Single Object Tracking

1. 摘要 无人机已被广泛用于各种应用&#xff0c;如空中摄影和军事安全&#xff0c;因为与固定摄像机相比&#xff0c;无人机具有高机动性和广阔的视野。多架无人机跟踪系统可以通过收集不同视角的互补视频片段来提供丰富的目标信息&#xff0c;特别是当目标在某些视角下被遮挡…

SPSS统计作图教程:百分条图堆积条图

1、问题与数据 某研究者想看不同年龄分组人群&#xff08;Age_cat&#xff09;中不同程度的维生素D缺乏&#xff08;VD&#xff09;的百分构成比&#xff0c;部分数据如图1。研究者想以条图形式来展现&#xff0c;该如何操作呢&#xff1f; 图1 部分数据 2. 具体操作&#xf…

【小沐学Python】UML类图的箭头连线关系总结(python+graphviz)

文章目录 1、简介1.1 类图1.2 Graphviz 2、Graphviz2.1 安装2.2 命令行测试2.3 python测试 3、关系3.1 实现3.2 泛化3.3 关联3.4 依赖3.5 聚合3.6 组合 结语 1、简介 UML&#xff08;unified modeling language&#xff0c;统一建模语言&#xff09;是一种常用的面向对象设计的…

逻辑回归Logistic

回归 概念 假设现在有一些数据点&#xff0c;我们用一条直线对这些点进行拟合&#xff08;这条直线称为最佳拟合直线&#xff09;&#xff0c;这个拟合的过程就叫做回归。进而可以得到对这些点的拟合直线方程。 最后结果用sigmoid函数输出 因此&#xff0c;为了实现 Logisti…

前端基础4——jQuery

文章目录 一、基本了解1.1 导入jQuery库1.2 基本语法1.3 选择器 二、操作HTML2.1 隐藏和显示元素2.2 获取与设置内容2.3 获取、设置和删除属性2.4 添加元素2.5 删除元素2.6 设置CSS样式 三、jQuery Ajax3.1 基本语法3.2 回调函数3.3 常用HTTP方法3.4 案例一3.4.1 准备工作3.4.2…

PostgreSQL 查询语句大全

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

如何使用『Nginx』配置后端『HTTPS』协议访问

前言 本篇博客主要讲解如何使用 Nginx 部署后端应用接口 SSL 证书&#xff0c;从而实现 HTTPS 协议访问接口&#xff08;本文使用公网 IP 部署&#xff0c;读者可以自行替换为域名&#xff09; 申请证书 须知 请在您的云服务平台申请 SSL 证书&#xff0c;一般来说证书期限…

安卓逆向 - Frida反调试绕过

本文仅供学习交流&#xff0c;只提供关键思路不会给出完整代码&#xff0c;严禁用于非法用途&#xff0c;谢绝转载&#xff0c;若有侵权请联系我删除&#xff01; 本文案例 app&#xff1a;5Lqs5LicYXBwMTEuMy4y 一、引言&#xff1a; Frida是非常优秀的一款 Hook框架&#…

word导出为HTML格式教程,同时也导出图片

在写文档教程时&#xff0c;有时需要借鉴人家的专业文档内容&#xff0c;一般都是word格式文档。word直接复制里面的内容&#xff0c;帐帖到网站编辑器会有很多问题&#xff0c;需要二次清楚下格式才行&#xff0c;而且图片是没办法直接复制到编辑器内的。所以最方便的办法是将…

lv3 嵌入式开发-3 linux shell命令(文件搜索、文件处理、压缩)

目录 1 查看文件相关命令 1.1 常用命令 1.2 硬链接和软链接 2 文件搜索相关命令 2.1 查找文件命令 2.2 查找文件内容命令 2.3 其他相关命令 3 文件处理相关命令 3.1 cut 3.2 sed 过滤 3.3 awk 匹配 4 解压缩相关命令 4.1 解压缩文件的意义 4.2 解压缩相关命令 1 …

在STS里使用Gradle编译Apache POI5.0.0

1、到官方下面地址下载Gradle最新的版本 Gradle Distributions 2、解压后拷贝到D盘下D:\gradle-8.3-rc-4里 3、配置环境变量 新建系统变量 GRADLE_HOME &#xff0c;值为 路径 4、在 Path 中添加上面目录的 bin 文件路径 &#xff08;可以用 %GRADLE_HOME%\bin&#xff0c…

【python爬虫】5.爬虫实操(歌词爬取)

文章目录 前言项目&#xff1a;寻找周杰伦分析过程代码实现重新分析过程什么是NetworkNetwork怎么用什么是XHR&#xff1f;XHR怎么请求&#xff1f;json是什么&#xff1f;json数据如何解析&#xff1f;实操&#xff1a;完成代码实现 一个总结一个复习 前言 这关让我们一起来寻…

React笔记(三)类组件(1)

一、组件的概念 使用组件方式进行编程&#xff0c;可以提高开发效率&#xff0c;提高组件的复用性、提高代码的可维护性和可扩展性 React定义组件的方式有两种 类组件&#xff1a;React16.8版本之前几乎React使用都是类组件 函数组件:React16.8之后&#xff0c;函数式组件使…

Kubernetes可视化管理工具Kuboard部署使用及k8s常用命令梳理记录

温故知新 &#x1f4da;第一章 前言&#x1f4d7;背景&#x1f4d7;目的&#x1f4d7;总体方向 &#x1f4da;第二章 安装 Kubernetes 多集群管理工具 - Kuboard v3&#x1f4d7;部署方式&#x1f4d7;通过Kuboard v3 - Kubernetes安装&#xff08;在master节点执行)&#x1f4…

Blender 围绕自身的原点旋转与游标旋转

默认情况下的旋转是&#xff0c;R后旋转是物体自身的原点旋转 可以修改为围绕游标旋转&#xff0c;通过旋转R时 局部与全局坐标 全局的坐标不会变 局部的会随着物体的旋转变化 如果平稳时GZZ会在全局到局部坐标之间切换 或在局部到全局之间的切换 学习视频&#xff1a;【基础…

C++ do...while 循环

不像 for 和 while 循环&#xff0c;它们是在循环头部测试循环条件。do…while 循环是在循环的尾部检查它的条件。 do…while 循环与 while 循环类似&#xff0c;但是 do…while 循环会确保至少执行一次循环。 语法 C 中 do…while 循环的语法&#xff1a; do {statement(s…

【Unity】常见的角色移动旋转

在Unity 3D游戏引擎中&#xff0c;可以使用不同的方式对物体进行旋转。以下是几种常见的旋转方式&#xff1a; 欧拉角&#xff08;Euler Angles&#xff09;&#xff1a;欧拉角是一种常用的旋转表示方法&#xff0c;通过绕物体的 X、Y 和 Z 轴的旋转角度来描述物体的旋转。在Un…

攻防世界-Caesar

原题 解题思路 没出现什么特殊字符&#xff0c;可能是个移位密码。凯撒密码加密解密。偏移12位就行。

【AWS实验】 配置中转网关及对等连接

文章目录 实验概览目标实验环境任务 1&#xff1a;查看网络拓扑并创建基准任务 2&#xff1a;创建中转网关任务 3&#xff1a;创建中转网关挂载任务 4&#xff1a;创建中转网关路由表任务 4.1&#xff1a;创建路由表关联任务 4.2&#xff1a;创建路由传播 任务 5&#xff1a;更…