当前位置: 首页 > news >正文

仿京东 项目笔记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-formref="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激活变成truemsg: ""}},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-selectv-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,//滑块拼图验证码msgmsg: "",//滑块验证码背景图bgimgs: [],//步骤条的activeactive: 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-autocompleteclearablev-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.suffixlet 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-inputclearablev-model="registerForm.password"show-passwordplaceholder="请输入包含英文字母大小写、数字和特殊符号的 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-inputclearablev-model="registerForm.repassword"show-passwordplaceholder="请再次输入密码"><template slot="prepend">确认密码</template></el-input></el-form-item><el-form-item class="form-item" prop="email"><el-autocompleteclearablev-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-inputclearablev-model="registerForm.emailCode"placeholder="请输入邮箱验证码"><template slot="prepend">邮箱验证码</template><el-buttonslot="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 = 0if(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 = 0for(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 = 0for(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 = 0for(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+rewardif(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存入vuexcommit("USER_LOGIN", result.data.token)//持久化存储tokenlocalStorage.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} = thisphone && 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.tokenlet 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.pathif(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 = truethis.$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 = falsenext()}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-lazyloadimport 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

相关文章:

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

这里写目录标题 1. 注册页面1.1 注册/登录页面——接口请求1.2 Vue开发中Element UI的样式穿透1.2.1 ::v-deep的使用1.2.2 elementUI Dialog内容区域显示滚动条 1.3 注册页面——步骤条和表单联动 stepsform1.4 注册页面——滑动拼图验证1.5 注册页面——element-ui组件Popover…...

Spark与Flink的区别

分析&回答 &#xff08;1&#xff09;设计理念 1、Spark的技术理念是使用微批来模拟流的计算,基于Micro-batch,数据流以时间为单位被切分为一个个批次,通过分布式数据集RDD进行批量处理,是一种伪实时。 2、Flink是基于事件驱动的&#xff0c;是面向流的处理框架, Flink基于…...

未来智造:珠三角引领人工智能产业集群

原创 | 文 BFT机器人 产业集群是指产业或产业群体在地理位置上集聚的现象&#xff0c;产业集群的研究对拉动区域经济发展&#xff0c;提高区域产业竞争力具有重要意义。 从我国人工智能产业集群形成及区域布局来看&#xff0c;我国人工智能产业发展主要集聚在京津冀、长三角、…...

【Unity db】sqlite

背景 最近使用unity&#xff0c;需要用到sqlite&#xff0c;记录下使用过程 需要的动态库 Mono.Data.Sqlite.dll&#xff0c;这个文件下载参考下面链接 SqliteConnection的Close和Open 连接的概念&#xff1a; 在数据库编程中&#xff0c;连接是一个重要的概念&#xff0c…...

Linux 指令心法(四)`touch` 创建一个新的空文件

文章目录 命令的概述和用途命令的用法命令行选项和参数的详细说明命令的示例命令的注意事项或提示 命令的概述和用途 touch 是一个用于在 Linux 和 Unix 系统中创建空文件或更改现有文件的访问和修改时间的命令。如果指定的文件不存在&#xff0c;touch会创建一个新的空文件&a…...

分类算法系列②:KNN算法

目录 KNN算法 1、简介 2、原理分析 数学原理 相关公式及其过程分析 距离度量 k值选择 分类决策规则 3、API 4、⭐案例实践 4.1、分析 4.2、代码 5、K-近邻算法总结 &#x1f343;作者介绍&#xff1a;准大三网络工程专业在读&#xff0c;努力学习Java&#xff0c;涉…...

12. 微积分 - 梯度积分

Hi,大家好。我是茶桁。 上一节课,我们讲了方向导数,并且在最后留了个小尾巴,是什么呢?就是梯度。 我们再来回看一下但是的这个式子: [ f x f y...

Large Language Models and Knowledge Graphs: Opportunities and Challenges

本文是LLM系列的文章&#xff0c;针对《Large Language Models and Knowledge Graphs: Opportunities and Challenges》的翻译。 大语言模型和知识图谱&#xff1a;机会与挑战 摘要1 引言2 社区内的共同辩论点3 机会和愿景4 关键研究主题和相关挑战5 前景 摘要 大型语言模型&…...

Python操作Excel教程(图文教程,超详细)Python xlwings模块详解,

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;小白零基础《Python入门到精通》 xlwings模块详解 1、快速入门1、打开Excel2、创建工作簿2.1、使用工作簿2.2、操作…...

Java入门

Java导入包 import 主要用于导入在使用类前准备好了Import 关键字可以多次使用&#xff0c;导入多个包Import 关键字可以多次使用&#xff0c;导入多个类如果同一个包中需要大量的类&#xff0c;那么可以使用通配符进行导入如果Import了不同包&#xff0c;相同名称的类&#x…...

深度解析BERT:从理论到Pytorch实战

本文从BERT的基本概念和架构开始&#xff0c;详细讲解了其预训练和微调机制&#xff0c;并通过Python和PyTorch代码示例展示了如何在实际应用中使用这一模型。我们探讨了BERT的核心特点&#xff0c;包括其强大的注意力机制和与其他Transformer架构的差异。 关注TechLead&#x…...

小程序数据导出文件

小程序josn数据生成excel文件 先从下载传送门将xlsx.mini.min.js拷贝下来&#xff0c;新建xlsx.js文件放入小程序项目文件夹下。 const XLSX require(./xlsx)//在需要用的页面中引入// 定义导出 Excel 报表的方法exportData() {const that thislet newData [{time:2021,val…...

hadoop1.2.1伪分布式搭建

0.使用host-only方式 将Windows上的虚拟网卡改成跟Linux上的网卡在同一网段 注意&#xff1a;一定要将widonws上的WMnet1的IP设置和你的虚拟机在同一网段&#xff0c;但是IP不能相同 1.Linux环境配置&#xff08;windows下面的防火墙也要关闭&#xff09; 1.1修改主…...

【校招VIP】前端JavaScript语言之跨域

考点介绍&#xff1a; 什么是跨域&#xff1f;浏览器从一个域名的网页去请求另一个域名的资源时&#xff0c;域名、端口、协议任一不同&#xff0c;都是跨域。跨域是前端校招的一个重要考点&#xff0c;在面试过程中经常遇到&#xff0c;需要着重掌握。本期分享的前端算法考点之…...

mysql调优小计

1.选择最合适的字段属性&#xff1a;类型、⻓度、是否允许NULL等&#xff1b;尽量把字段设为not null&#xff0c;⼀⾯查询时对⽐是否为null&#xff1b; 2.要尽量避免全表扫描&#xff0c;⾸先应考虑在 where 及 order by 涉及的列上建⽴索引。 3.应尽量避免在 where ⼦句中对…...

AI:04-基于机器学习的蘑菇分类

蘑菇是一类广泛分布的真菌,其中许多种类具有重要的食用和药用价值,但也存在着一些有毒蘑菇。因此,准确地区分可食用和有毒的蘑菇对于保障人们的食品安全和健康至关重要。本研究旨在基于机器学习技术开发一种蘑菇分类系统,以实现对蘑菇的自动分类和识别。通过构建合适的数据…...

算法——排序

排序 下面的代码会用到宏定义&#xff0c;因为再C中没有swap交换函数&#xff0c;所以对于swap的宏定义代码如下&#xff1a; #define swap(a, b) {\__typeof(a) __a a; a b; b __a;\ } 稳定排序&#xff1a; 1.插入排序&#xff1a; 插入排序会将数组&#xff0c;分位两个部…...

leetCode动态规划“不同路径II”

迷宫问题是比较经典的算法问题&#xff0c;一般可以用动态规划、回溯等方法进行解题&#xff0c;这道题目是我昨晚不同路径这道题趁热打铁继续做的&#xff0c;思路与原题差不多&#xff0c;只是有需要注意细节的地方&#xff0c;那么话不多说&#xff0c;直接上coding和解析&a…...

100天精通Python(可视化篇)——第99天:Pyecharts绘制多种炫酷K线图参数说明+代码实战

文章目录 专栏导读一、K线图介绍1. 说明2. 应用场景 二、配置说明三、K线图实战1. 普通k线图2. 添加辅助线3. k线图鼠标缩放4. 添加数据缩放滑块5. K线周期图表 书籍推荐 专栏导读 &#x1f525;&#x1f525;本文已收录于《100天精通Python从入门到就业》&#xff1a;本专栏专…...

哈希表与有序表

哈希表与有序表 Set结构 key Map结构 key-value 哈希表 哈希表的时间复杂度都是常数项级别的&#xff0c;但常数较大 增删改查的时间都是常数级别的&#xff0c;与数据量无关 当哈希表存储的值是基础数据类型&#xff08;Integer - int&#xff09;&#xff0c;哈希表中内…...

什么时候使用RPA?如何使用RPA?需要什么样的硬件支持?需要安装哪些软件?

RPA&#xff08;Robotic Process Automation&#xff09;是一种用于自动化执行重复性任务的技术&#xff0c;它可以帮助企业提高工作效率&#xff0c;降低人力成本&#xff0c;并减少人为错误。RPA适用于各种行业和场景&#xff0c;例如财务、人力资源、客户服务、IT运维等。 …...

R语言入门——line和lines的区别

目录 0 引言一、 line()二、 lines() 0 引言 首先&#xff0c;从直观上看&#xff0c;lines比line多了一个s&#xff0c;但它们还是有很大的区别的&#xff0c;下面将具体解释这个两个函数的区别。 一、 line() 从R语言的帮助文档中找到&#xff0c;line()的使用&#xff0c…...

C语言:static关键字的使用

1.static修饰局部变量 这是static关键字使用最多的情况。我们知道局部变量是在程序运行阶段在栈上创建的&#xff0c;但是static修饰的局部变量是在程序编译阶段在代码段&#xff08;静态区&#xff09;创建的。所以在static修饰的变量所在函数执行结束后该变量依然存在。 //…...

AUTOSAR知识点 之 ECUM (三):ECUM的ISOLAR-AB配置及代码解析

目录 1、概述 2、ISOLAR-AB配置 2.1、EcuMGeneral 2.2、EcuMConfiguration 2.2.1、EcuMDefaultShutdownTarget 2.2.2、EcuMDriverInitListOne...

2023年MySQL-8.0.34保姆级安装教程

重点放前面&#xff1a;演示环境为windows环境。 MySQL社区版本安装教程如下&#xff1a; 一、MySQL安装包下载二、安装配置设置三、配置环境变量 大体分为3个步骤&#xff1a;①安装包的下载&#xff1b;②安装配置设置&#xff1b;③配置环境变量 一、MySQL安装包下载 下载官…...

ElasticSearch入门

一、基本命令_cat 1、查看节点信息 http://192.168.101.132:9200/_cat/nodes2、查看健康状况 http://192.168.101.132:9200/_cat/health3、查看主节点的信息 http://192.168.101.132:9200/_cat/master4、查看所有索引 http://192.168.101.132:9200/_cat/indices二、索引一…...

RocketMQ的Broker

1 Broker角色 Broker角色分为ASYNC_MASTER (异步主机)、SYNC_MASTER (同步主机)以及SLAVE (从机)。如果对消息的可靠性要求比较严格&#xff0c;可以采用SYNC_MASTER加SLAV E的部署方式。如果对消息可靠性要求不高&#xff0c;可以采用ASYNC_MASTER加ASL AVE的部署方式。如果只…...

使用Puppeteer进行游戏数据可视化

导语 Puppeteer是一个基于Node.js的库&#xff0c;可以用来控制Chrome或Chromium浏览器&#xff0c;实现网页操作、截图、测试、爬虫等功能。本文将介绍如何使用Puppeteer进行游戏数据的爬取和可视化&#xff0c;以《英雄联盟》为例。 概述 《英雄联盟》是一款由Riot Games开…...

【Flask】from flask_sqlalchemy import SQLAlchemy报错

【可能出现的情况】 1、未安装 Flask-SQLAlchemy&#xff1a; 在使用 flask_sqlalchemy 之前&#xff0c;你需要确保已经通过 pip 安装了 Flask-SQLAlchemy。可以通过以下命令安装它&#xff1a; pip install Flask-SQLAlchemy 2、包名大小写问题&#xff1a; Python 是区分大…...

索引简单概述(SQL)

一、什么是索引&#xff1f; 索引是一种特殊的文件&#xff08;InnoDB数据表上的索引是表空间的一个组成部分&#xff09;&#xff0c;他们包含着对数据表里所有记录的引用指针。 索引是一种数据结构。数据库索引&#xff0c;是数据库管理系统中一个排序的数据结构&#xff0…...