目录

1. HTML、CSS 问题记录

1.1 页面缩放后,滚动条虽然有效,但页面底部会有部分内容被遮挡

1.2 video 标签的 autoplay 属性不起作用,视频无法自动播放

1.3 使用 a 标签在 Android / IOS 上发送短信不兼容

1.4 FIXME、TODO、XXX 的含义

2. TypeScript 补充学习

2.1 通过已有数据类型接口,快速扩展其他数据类型接口

2.2 使用索引访问类型

3. 使用 Vue3 封装验证码组件,防止用户频繁操作

3.1 封装拼图页面 verify-slide.vue

3.2 封装拼图容器 verify-home.vue

3.3 使用验证码组件

4. 微前端相关配置文件介绍

4.1 基座应用里的文件介绍

4.1.1 public/global/config/api-config.js

4.1.2 public/global/config/config-micro-app.js

4.1.3 public/global/config/config-webpack.js

4.1.4 vue.config.js

4.2 解决访问不到服务器上的 api-config.js 文件

5. 如何在开发者工具中,筛选给地图发送的消息?

6. 使用 userAgent 判断设备类型


1. HTML、CSS 问题记录

1.1 页面缩放后,滚动条虽然有效,但页面底部会有部分内容被遮挡

看一下高度是否是 固定数值,把 固定数值 改为 根据页面动态计算高度

.skin-big-screen iframe {height: calc(100vh - 240px) !important;
}

1.2 video 标签的 autoplay 属性不起作用,视频无法自动播放

浏览器会拦截自动播放声音的视频,加上 muted 属性即可解决

<video id="video" muted autoplay='true' loop src="./video/v1.mp4"></video>

1.3 使用 a 标签在 Android / IOS 上发送短信不兼容

Android —— <a href="sms:15328656551?body=2955"></a>

IOS —— <a href="sms:15328656551&body=2955"></a>

兼容 Android / IOS —— <a href="sms:15328656551;?&body=2955"></a>

href="'sms:' + smsInfo.target + ';?&body=' + smsInfo.code"

1.4 FIXME、TODO、XXX 的含义

// TODO: 将实现的功能 - 想到哪写到哪,还没完全实现

// FIXME: 如何修复该部分代码问题 - 运行可能存在问题,需要解决 bug

// XXX: 如何改进优化该部分代码 - 功能没问题,但是代码写的不太好,可以优化

2. TypeScript 补充学习

2.1 通过已有数据类型接口,快速扩展其他数据类型接口

  • Pick —— 选择其中的属性
  • Omit —— 排除其中的属性
  • Partial —— 让全部属性变成可选
  • Required —— 让全部属性变成必选

举个栗子~~~

// 以下方数据类型接口为例,进行扩展
interface Test {name: string;sex: boolean;height: number;
}// Pick —— 选择其中的属性type PickTest = Pick<Test, 'sex'>;
const a: PickTest = { sex: true };// Omit —— 排除其中的属性type OmitTest = Omit<Test, 'sex'>;
const b: OmitTest = { name: 'Lyrelion', height: 188 };// Partial —— 让全部属性变成可选type PartialTest = Partial<Test>Required —— 让全部属性变成必选type RequiredTest = Required<Test>

2.2 使用索引访问类型

type Person = {name: string,age: number,hobby: [] as string[],
}type TestOne = Person["name"] // 可以设置一个
type TestMore = Person["name" | "age"] // 可以设置多个const aaa: TestMore = 'abc' // ok
const bbb: TestMore = 123 // ok
const ccc: TestMore = [1,2,3] // error

3. 使用 Vue3 封装验证码组件,防止用户频繁操作

3.1 封装拼图页面 verify-slide.vue

<!--* @Description: 验证码 - 滑动滑块验证* @Author: lyrelion* @Date: 2022-08-16 10:34:26* @LastEditors: lyrelion* @LastEditTime: 2022-10-31 20:43:13
--><template><div class="p-relative"><!-- 图片部分容器 --><div v-if="isSlideVerify" :style="{ height: parseInt(setSize.imgHeight) + vSpace + 'px' }"><!-- 大图片容器 - 相对定位 --><div class="verify-img-panel" :style="{ width: setSize.imgWidth, height: setSize.imgHeight }"><!-- 背景图片(缺失拼图的图片) --><img:src="'data:image/png;base64,' + backImgBase"class="back-img"/><!-- 刷新验证码图片的按钮 --><!-- <div v-show="showRefresh" class="verify-refresh" @click="refresh"><i class="iconfont icon-refresh"></i></div> --><!-- 提示信息(验证成功、验证失败,动态展示) --><transition name="tips"><span v-if="tipWords" class="verify-tips" :class="passFlag ? 'suc-bg' : 'err-bg'">{{ tipWords }}</span></transition></div></div><!-- 滑块部分容器 --><divclass="verify-bar-area":style="{width: setSize.imgWidth,height: barSize.height,}"><!-- 用户操作提示信息 - 向右滑动完成验证 --><!-- <span class="verify-msg" v-text="text"></span> --><!-- 可以拖动的滑块容器 --><divclass="verify-left-bar":style="{width: leftBarWidth !== undefined ? leftBarWidth : barSize.height,height: barSize.height,transaction: transitionWidth,}"><!-- 被拖拽的滑块(实际移动的滑块) --><divclass="verify-move-block":style="{width: blockSize.width,height: blockSize.height,left: moveBlockLeft,transition: transitionLeft,}"@touchstart="start"@mousedown="start"><!-- <img src="@/assets/images/login/verify-block.png" alt="" /> --><!-- <i :class="['verify-icon iconfont', iconClass]" :style="{ color: iconColor, 'margin-right': '40px' }">aaaa</i> --><!-- 跟着滑块移动的拼图容器 --><divv-if="isSlideVerify"class="verify-sub-block":style="{width: Math.floor((parseInt(setSize.imgWidth) * 47) / 310) + 'px',height: setSize.imgHeight,top: '-' + (parseInt(setSize.imgHeight) + vSpace) + 'px','background-size': setSize.imgWidth + ' ' + setSize.imgHeight,}"><!-- 跟着滑块移动的拼图图片 --><img:src="'data:image/png;base64,' + blockBackImgBase"class="sub-block"/></div></div></div></div></div>
</template><script lang="ts">
import {reactive,toRefs,defineComponent,computed,onMounted,watch,nextTick,getCurrentInstance,
} from 'vue';
import codeCase from '@/utils/codeCase';
import { resetSize } from '@/utils/common';
import { reqGet, reqCheck } from '@/services/common/verify';export default defineComponent({name: 'VerifySlide',props: {// 验证码类型(滑块拼图、点击文字)captchaType: {type: String,default: 'blockPuzzle',},// 验证码类型是否为滑块拼图isSlideVerify: {type: Boolean,default: true,},// 验证码的显示方式,弹出式 - pop,固定 - fixed,默认为 mode: 'pop'mode: {type: String,default: 'pop',},// 验证码图片和移动条容器的间隔,默认单位是px。如:间隔为5px,默认:vSpace:5vSpace: {type: Number,default: 5,},// 滑动条内的提示,不设置默认是:'向右滑动完成验证'explain: {type: String,default: '向右滑动完成验证',},// 图片的大小对象, 有默认值 { width: '310px', height: '155px' }, 可省略imgSize: {type: Object,default() {return {width: '310px',height: '155px',};},},// 下方滑块的大小对象, 有默认值 { width: '310px', height: '50px' }, 可省略barSize: {type: Object,default() {return {width: '310px',height: '40px',};},},blockSize: {type: Object,default() {return {width: '50px',height: '50px',};},},},emits: ['init-failed', 'success', 'error'],setup(props: any, { emit }) {const { proxy } = getCurrentInstance() as any;// 响应式变量const state = reactive({// 后端返回的aes加密秘钥secretKey: '',// 是否通过的标识passFlag: false,// 验证码背景图片backImgBase: '',// 验证滑块的背景图片blockBackImgBase: '',// 后端返回的唯一token值backToken: '',// 移动开始的时间startMoveTime: 0,// 移动结束的时间endMovetime: 0,// 提示词的背景颜色tipsBackColor: '',// 提示词tipWords: '',// 滑动条内的提示text: '',setSize: {imgHeight: '0px',imgWidth: '0px',barHeight: '0px',barWidth: '0px',},top: 0,left: 0,moveBlockLeft: '0px',leftBarWidth: '0px',// 移动中样式iconColor: '',iconClass: 'icon-right',// 鼠标状态status: false,// 是否验证完成isEnd: false,showRefresh: true,transitionLeft: '',transitionWidth: '',startLeft: 0,});const barArea = computed(() => proxy.$el.querySelector('.verify-bar-area'));/*** 请求背景图片和验证图片*/const getPictrue = () => {const data = {captchaType: props.captchaType,};reqGet(data).then((res: any) => {console.log('请求背景图片和验证图片 ===', res);if (res.data.repCode === '0000') {state.backImgBase = res.data.repData.originalImageBase64;state.blockBackImgBase = res.data.repData.jigsawImageBase64;state.backToken = res.data.repData.token;state.secretKey = res.data.repData.secretKey;} else {state.tipWords = res.data.repMsg;emit('init-failed', res);}});};/*** 刷新*/const refresh = () => {state.showRefresh = true;state.transitionLeft = 'left .3s';state.moveBlockLeft = '0px';state.leftBarWidth = '0px';state.transitionWidth = 'width .3s';state.iconColor = '#000';state.iconClass = 'icon-right';state.isEnd = false;// 请求背景图片和验证图片getPictrue();setTimeout(() => {state.transitionWidth = '';state.transitionLeft = '';state.text = props.explain;}, 300);};/*** 鼠标按下*/const start = (e: any) => {// eslint-disable-next-line no-param-reassigne = e || window.event;let x: any;if (!e.touches) {// 兼容PC端x = e.clientX;} else {// 兼容移动端x = e.touches[0].pageX;}console.log('barArea ===', barArea);state.startLeft = Math.floor(x - barArea.value.getBoundingClientRect().left);// 开始滑动的时间state.startMoveTime = +new Date();if (!state.isEnd) {state.text = '';state.iconColor = '#fff';e.stopPropagation();state.status = true;}};/*** 鼠标移动*/const move = (e: any) => {// eslint-disable-next-line no-param-reassigne = e || window.event;let x: any;if (state.status && !state.isEnd) {if (!e.touches) {// 兼容PC端x = e.clientX;} else {// 兼容移动端x = e.touches[0].pageX;}// eslint-disable-next-line camelcaseconst bar_area_left = barArea.value.getBoundingClientRect().left;// 小方块相对于父元素的left值// eslint-disable-next-line camelcaselet move_block_left = x - bar_area_left;// 计算 props 中的 blockSize 滑块尺寸const blockSizeWidthNum = parseInt(props.blockSize.width, 10); // 50// eslint-disable-next-line camelcaseif (move_block_left >= barArea.value.offsetWidth - blockSizeWidthNum / 2 - 2) {// eslint-disable-next-line camelcasemove_block_left = barArea.value.offsetWidth - blockSizeWidthNum / 2 - 2;}// eslint-disable-next-line camelcaseif (move_block_left <= 0) {// eslint-disable-next-line camelcasemove_block_left = blockSizeWidthNum / 2;}// 拖动后小方块的left值// eslint-disable-next-line camelcasestate.moveBlockLeft = move_block_left - state.startLeft + 'px';// eslint-disable-next-line camelcasestate.leftBarWidth = move_block_left - state.startLeft + 'px';}};/*** 鼠标松开*/const end = () => {state.endMovetime = +new Date();// 判断是否重合if (state.status && !state.isEnd) {let moveLeftDistance: any;moveLeftDistance = parseInt((state.moveBlockLeft || '').replace('px', ''), 10);moveLeftDistance = (moveLeftDistance * 310) / parseInt(state.setSize.imgWidth, 10);const data = {captchaType: props.captchaType,pointJson: state.secretKey? codeCase.verifyAesEncrypt(JSON.stringify({ x: moveLeftDistance, y: 5.0 }), state.secretKey): JSON.stringify({ x: moveLeftDistance, y: 5.0 }),token: state.backToken,};reqCheck(data).then((res: any) => {console.log('校验验证码图片 ===', res.data);if (res.data.repCode === '0000') {state.iconColor = '#fff';state.iconClass = 'icon-check';state.showRefresh = false;state.isEnd = true;if (props.mode === 'pop') {setTimeout(() => {proxy.$parent.clickShow = false;refresh();}, 1500);}state.passFlag = true;state.tipWords = `${((state.endMovetime - state.startMoveTime) / 1000).toFixed(2)}s验证成功`;const captchaVerification = state.secretKey? codeCase.verifyAesEncrypt(state.backToken + '---' + JSON.stringify({ x: moveLeftDistance, y: 5.0 }),state.secretKey,): state.backToken + '---' + JSON.stringify({ x: moveLeftDistance, y: 5.0 });setTimeout(() => {state.tipWords = '';proxy.$parent.closeBox();// proxy.$parent.$emit('success', { captchaVerification });emit('success', { captchaVerification });}, 1000);} else {state.iconColor = '#fff';state.iconClass = 'icon-close';state.passFlag = false;setTimeout(() => {refresh();}, 1000);// proxy.$parent.$emit('error', proxy);emit('error', proxy);state.tipWords = '验证失败';setTimeout(() => {state.tipWords = '';}, 1000);}});state.status = false;}};/*** 页面初始化*/const init = () => {state.text = props.explain;// 请求背景图片和验证图片getPictrue();// 重新设置相关组件尺寸nextTick(() => {const { imgHeight, imgWidth, barHeight, barWidth } = resetSize(proxy);state.setSize.imgHeight = imgHeight;state.setSize.imgWidth = imgWidth;state.setSize.barHeight = barHeight;state.setSize.barWidth = barWidth;// console.log('proxy templete 里第一行不能加注释,否则会导致获取不到 proxy.$el ===', proxy);proxy.$parent.$emit('ready', proxy);});// 移除已有监听器window.removeEventListener('touchmove', (e) => {move(e);});window.removeEventListener('mousemove', (e) => {move(e);});window.removeEventListener('touchend', () => {end();});window.removeEventListener('mouseup', () => {end();});// 添加新的监听器window.addEventListener('touchmove', (e) => {move(e);});window.addEventListener('mousemove', (e) => {move(e);});window.addEventListener('touchend', () => {end();});window.addEventListener('mouseup', () => {end();});};onMounted(() => {// 页面初始化init();// 对象开始选中时触发(此处含义为禁止选中、拖拽)proxy.$el.onselectstart = () => false;});watch(() => props.isSlideVerify,() => {// 页面初始化init();},);return {...toRefs(state),barArea,refresh,start,};},
});
</script><style lang="scss" scoped>
/* 验证码父容器,相对定位标志 */
.p-relative {position: relative !important;
}/* 背景图片(缺失拼图的图片) */
.back-img {display: block;width: 100%;height: 100%;
}/* 跟着滑块移动的拼图图片 */
.sub-block {display: block;width: 100%;height: 100%;-webkit-user-drag: none;
}/* 提示信息(验证成功、验证失败,动态展示) */
.verify-tips {position: absolute;bottom: 0;left: 0;box-sizing: border-box;width: 100%;height: 30px;padding: 0 0 0 10px;color: #FFF;font-size: 14px;line-height: 30px;
}.suc-bg {background-color: rgba(92, 184, 92, 0.5);filter: progid:dximagetransform.microsoft.gradient(startcolorstr=#7f5CB85C, endcolorstr=#7f5CB85C);
}.err-bg {background-color: rgba(217, 83, 79, 0.5);filter: progid:dximagetransform.microsoft.gradient(startcolorstr=#7fD9534F, endcolorstr=#7fD9534F);
}.tips-enter,
.tips-leave-to {bottom: -30px;
}.tips-enter-active,
.tips-leave-active {transition: bottom 0.5s;
}/* 常规验证码 */
.verify-code {margin-bottom: 5px;border: 1px solid #DDD;font-size: 20px;text-align: center;cursor: pointer;
}.cerify-code-panel {overflow: hidden;height: 100%;
}.verify-code-area {float: left;
}.verify-input-area {float: left;width: 60%;padding-right: 10px;
}.verify-change-area {float: left;line-height: 30px;
}.varify-input-code {display: inline-block;width: 100%;height: 25px;
}.verify-change-code {color: #337AB7;cursor: pointer;
}.verify-btn {width: 200px;height: 30px;margin-top: 10px;border: 0;background-color: #337AB7;color: #FFF;
}/* 滑块部分容器 */
.verify-bar-area {position: relative;border: 0;border-radius: 10px;text-align: center;&::before {content: ' ';position: absolute;width: 100%;height: 16px;left: 0;top: 50%;background: #D8D8D8;border-radius: 10px;transform: translate(0, -50%);}
}/* 被拖拽的滑块(实际移动的滑块) */
.verify-bar-area .verify-move-block {position: absolute;top: 0;left: 0;// background: #00A870;background: var(--theme-color);border-radius: 50px;cursor: pointer;&::before {content: ' ';position: absolute;left: 50%;top: 50%;width: 26px;height: 20px;background: url(../../../assets/images/login/verify-block-center.png) no-repeat left center/ cover;transform: translate(-50%, -50%);}
}/* 可以拖动的滑块容器 */
.verify-bar-area .verify-left-bar {position: absolute;// top: -1px;// left: -1px;border: 0;cursor: pointer;&::before {content: ' ';position: absolute;width: 100%;height: 16px;left: 0;top: 50%;background: #D8D8D8;border-top-left-radius: 10px;border-bottom-left-radius: 10px;transform: translate(0, -50%);}
}/* 大图片容器 - 相对定位 */
.verify-img-panel {position: relative;box-sizing: content-box;margin: 0;border-radius: 4px;
}/* 刷新验证码图片的按钮 */
.verify-img-panel .verify-refresh {position: absolute;top: 0;right: 0;z-index: 2;width: 25px;height: 25px;padding: 5px;text-align: center;cursor: pointer;
}.verify-img-panel .icon-refresh {color: #FFF;font-size: 14px;
}.verify-img-panel .verify-gap {position: relative;z-index: 2;border: 1px solid #FFF;background-color: #FFF;
}/* 跟着滑块移动的拼图容器 */
.verify-bar-area .verify-move-block .verify-sub-block {position: absolute;z-index: 3;text-align: center;
}.verify-bar-area .verify-move-block .verify-icon {font-size: 14px;
}/* 用户操作提示信息 - 向右滑动完成验证 */
.verify-msg {font-size: 14px;
}.verify-bar-area .verify-msg {z-index: 3;
}/* 字体图标 - 这里我隐藏了,也没有引入字体图标 */
.iconfont {font-style: normal;font-size: 16px;font-family: 'iconfont' !important;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;
}.icon-check:before,
.icon-close:before,
.icon-right:before,
.icon-refresh:before {content: ' ';position: absolute;top: 0;right: 0;bottom: 0;left: 0;z-index: 9999;display: block;width: 16px;height: 16px;margin: auto;background-size: contain;
}
</style>

3.2 封装拼图容器 verify-home.vue

<!--* @Description: 验证码校验弹框* @Author: lyrelion* @Date: 2022-08-16 11:35:55* @LastEditors: lyrelion* @LastEditTime: 2022-11-01 14:14:47
--><template><div v-show="showBox" :class="{ 'mask' : mode === modeEnum.pop }"><!-- 验证码容器 --><div:class="{ 'verifybox': mode === modeEnum.pop }":style="{ 'max-width': `${parseInt(imgSize.width)}px` }"><!-- 弹框顶部标题 及 关闭按钮 --><div v-if="mode === modeEnum.pop" class="verifybox-top"><img alt="安全验证" draggable="true" src="@/assets/images/login/verify-check.png" />安全验证<span class="verifybox-close" @click="closeBox"> ✕ </span></div><!-- 验证码组件容器 --><div class="verifybox-bottom" :style="{ padding: mode === modeEnum.pop ? '15px 0 0' : '0' }"><!-- 验证码组件 --><component:is="componentName"v-if="componentName"ref="verifyComponentDOM":captcha-type="captchaType":is-slide-verify="captchaType === captchaTypeEnum.blockPuzzle":mode="mode":v-space="vSpace":explain="explain":img-size="imgSize":block-size="blockSize":bar-size="barSize"@init-failed="handleInitFailed"@success="handleSuccess"@error="handleError"></component></div></div></div>
</template><script lang="ts">
import {reactive,toRefs,defineComponent,computed,ref,watchEffect,
} from 'vue';
// 滑块拼图验证码
import VerifySlide from '@/components/verify/verify-type/verify-slide.vue';// 验证码类型(滑块拼图、点击文字)
export enum captchaTypeEnum {blockPuzzle = 'blockPuzzle', // 滑块拼图clickWord = 'clickWord', // 点击文字
}// 验证码的显示方式,弹出式 - pop,固定 - fixed,默认为 mode: 'pop'
export enum modeEnum {pop = 'pop', // 弹出式fixed = 'fixed', // 固定
}export default defineComponent({name: 'VerifyHome',components: {VerifySlide, // 滑块拼图验证码},props: {// 验证码类型(滑块拼图、点击文字)captchaType: {type: String,default: captchaTypeEnum.blockPuzzle,},// 验证码的显示方式,弹出式 - pop,固定 - fixed,默认为 mode: 'pop'mode: {type: String,default: modeEnum.pop,},// 验证码图片和移动条容器的间隔,默认单位是px。如:间隔为5px,默认:vSpace:5vSpace: {type: Number,default: 16,},// 滑动条内的提示,不设置默认是:'向右滑动完成验证'explain: {type: String,default: '向右滑动完成验证',},// 图片的大小对象, 有默认值 { width: '310px', height: '155px' }, 可省略imgSize: {type: Object,default: () => ({width: '600px',height: '320px',}),},// 下方滑块的大小对象, 有默认值 { width: '310px', height: '50px' }, 可省略barSize: {type: Object,default: () => ({width: '310px',height: '34px',}),},blockSize: {type: Object,default: () => ({width: '64px',height: '34px',}),},},emits: ['init-failed', 'success', 'error'],setup(props, { emit }) {// 响应式变量const state = reactive({});// 控制弹窗的显隐(不要随便改这个变量名,因为在子组件里,有直接修改到它)const clickShow = ref(false);// 验证码类型对应的组件名称const componentName = ref('');// 验证码组件DOMconst verifyComponentDOM = ref<HTMLFormElement | null>(null);/*** 刷新验证码*/const refreshVerify = () => {// console.log('验证码组件 DOM 实例 ===', verifyComponentDOM.value);if (verifyComponentDOM.value && verifyComponentDOM.value.refresh) {verifyComponentDOM.value.refresh();}};/*** 展示弹框 - 组件内部调用*/const showBox = computed(() => {if (props.mode === modeEnum.pop) {return clickShow.value;}return true;});/*** 展示弹框 - 组件外部调用*/const showDialogOutSide = () => {if (props.mode === modeEnum.pop) {clickShow.value = true;}};/*** 关闭弹窗*/const closeBox = () => {clickShow.value = false;// 刷新验证码refreshVerify();};/*** 验证码初始化失败*/const handleInitFailed = (res: any) => {// console.log('验证码初始化失败 ===', res);emit('init-failed', res);};/*** 验证码校验成功*/const handleSuccess = (res: any) => {// console.log('验证码校验成功 ===', res);emit('success', res);};/*** 验证码校验失败*/const handleError = (res: any) => {// console.log('验证码校验失败 ===', res);emit('error', res);};/*** 根据验证码类型,确定验证码组件*/watchEffect(() => {// 验证码类型if (props.captchaType === captchaTypeEnum.blockPuzzle) {componentName.value = 'VerifySlide';} else {componentName.value = 'VerifyPoints';}});/** onMounted(() => {*   proxy.$on('success', (data: any) => {*     handleSuccess(data);*   });*   proxy.$on('error', (data: any) => {*     handleError(data);*   });* });*//** onUnmounted(() => {*   proxy.$off('success');*   proxy.$off('error');* });*/return {...toRefs(state),modeEnum,componentName,captchaTypeEnum,verifyComponentDOM,showBox,closeBox,showDialogOutSide,handleInitFailed,handleSuccess,handleError,};},
});
</script><style lang="scss" scoped>
/* 验证码容器 */
.verifybox {position: relative;top: 50%;left: 50%;padding: 8px 16px 24px;background-color: #FFF;// border: 1px solid red;border-radius: 4px;transform: translate(-50%, -50%);/* 弹框顶部标题 及 关闭按钮 */&-top {position: relative;display: flex;justify-content: flex-start;align-items: center;height: 48px;line-height: 16px;padding: 0;border-bottom: 1px solid #D8D8D8;font-size: 16px;color: rgba(0, 0, 0, 0.9);img {width: 16px;height: 16px;margin-right: 4px;}}/* 关闭按钮 */&-close {position: absolute;top: 50%;right: 0;font-size: 16px;font-weight: 700;color: rgba(0, 0, 0, 0.6);cursor: pointer;transform: translate(-50%, -50%);}/* 验证码组件容器 */&-bottom {box-sizing: border-box;padding: 0;}
}/* 遮罩 */
.mask {position: fixed;top: 0;left: 0;z-index: 10001;width: 100%;height: 100vh;background: rgba(0, 0, 0, 0.6);transition: all 0.5s;
}
</style>

3.3 使用验证码组件

场景:

  • 首次进入系统,点击表格项,无需弹出验证码
  • 30s 内再次点击表格项,需要弹出验证码
  • 30s 内刷新页面,点击表格项,仍需弹出验证码;30s 外则不需要

实现思路:

  • 点击列表后,将列表项信息存到页面中
  • 如果验证码初始化失败,则不进行跳转,无法查看列表项,并提示用户
  • 从本地存储 localStorage 中获取上次点击列表项的时间戳
  • 如果没有,则证明是第一次点击列表项,直接执行打开表单详情操作即可,并存储当前点击列表项的时间
  • 如果有时间戳,则要与现在的时间进行对比;如果超过 30s,则执行打开表单详情操作即可,并存储当前点击列表项的时间;如果没超过 30s,则调用验证码 DOM 实例的方法,展示验证码
  • 用户验证码校验成功,才能进入表单查看页面,并存储当前点击列表项的时间;用户校验失败,则无法进入表单查看页面,不用存储此次点击时间
  <verify-homeref="verifyDOM"@success="handleSuccess"@error="handleError"@init-failed="handleInitFailed"></verify-home>// 验证码组件import VerifyHome from '@/components/verify/verify-home.vue';// 验证码组件 DOMconst verifyDOM = ref<HTMLFormElement | null>(null);const state = reactive({// 是否初始化成功verifyInitSuccess: true,})/*** 处理表格查看事件*/const handleView = (row: ListFillingListItemInf) => {// 切换当前编辑/查看的项目idstate.enterId = row.enterId;state.enterRowInfo = row;if (!state.verifyInitSuccess) {ElMessage.error('验证码初始化失败,无法查看企业信息');return;}// 从 localstorage 中获取上一次查看企业的时间const vsTime = localStorage.getItem('verifySapceTime');// 如果没有时间,证明为第一次查看企业信息,则直接展示即可if (!vsTime) {// 展示列表详情页state.viewVisible = true;// 新建时间戳localStorage.setItem('verifySapceTime', Number(new Date()).toString());console.log('没有时间间隔');// 如果有时间,则弹出验证码} else {// 如果时间间隔超过 30s,则直接展示表单,并且重置时间间隔// eslint-disable-next-line no-lonely-ifif (Number(new Date()) - Number(vsTime) > 1000 * 30) {console.log('时间间隔超过 30s', Number(new Date()) - Number(vsTime));// 展示列表详情页state.viewVisible = true;// 新建时间戳localStorage.setItem('verifySapceTime', Number(new Date()).toString());// 如果时间间隔小于 30s,则展示验证码,防止频繁操作} else {console.log('时间间隔不到 30s', Number(new Date()) - Number(vsTime));// console.log('verifyDOM ===', verifyDOM.value);if (verifyDOM.value) {// 将验证码弹框,挂载到页面中(verifyDOM.value as any)!.showDialogOutSide();}}}};/*** 验证码初始化失败*/const handleInitFailed = (res: any) => {ElMessage.error(`请联系维护人员!${res.data.repMsg}`);console.log('验证码初始化失败 ===', res);// 标识验证码初始化失败state.verifyInitSuccess = false;};/*** 验证码校验成功*/const handleSuccess = async (res: any) => {console.log('验证码校验成功 ===', res);// 如果 验证码接口 没有返回二次校验参数if (!res.captchaVerification) {ElMessage.error(`验证码获取失败,请联系管理员 res.captchaVerification ${res.captchaVerification}`);return;}// 展示列表详情页state.viewVisible = true;// 新建时间戳localStorage.setItem('verifySapceTime', Number(new Date()).toString());};/*** 验证码校验失败*/const handleError = (res: any) => {console.log('验证码校验失败 ===', res);ElMessage.error('验证码校验失败,请重试');};

4. 微前端相关配置文件介绍

4.1 基座应用里的文件介绍

4.1.1 public/global/config/api-config.js

此步骤打包前、后修改都可以

api-config.js 中存放的是:项目中所有微应用需要的各种接口的公共部分(例如 ip+port)和 加载微应用 没有任何关系

api-config.js 中的内容即使在打包后,再进行修改也是生效的,所以在打包前、打包后修改都行

api-config.js 目前没有分开发环境或部署环境,所以部署完项目访问系统的时候,这个文件里写的是什么,读取的就是什么

api-config.js 是通过基座中 public > global > config > config-webpack.js 加载到每个微应用中的

4.1.2 public/global/config/config-micro-app.js

此步骤打包前、后修改都可以

config-micro-app.js 中存放的是:微应用的信息,和 加载微应用 有关系,所以在不同环境部署的时候,这个文件内容必须修改

config-micro-app.js 中的内容即使在打包后,再进行修改也是生效的,所以在打包前、打包后修改都行

config-micro-app.js 只有在基座中有,微应用里都没有这个文件

4.1.3 public/global/config/config-webpack.js

此步骤 打包前修改CDN链接 和 打包后修改CDN链接 的方式不一样

config-webpack.js 中存放的是:项目中所有应用的公共依赖(所谓的公共依赖,其实最后访问的都是基座应用中 public/global/libs 下的资源)

config-webpack.js 在 所有应用(基座应用、微应用)打包前、打包时 都使用到了,唯独 打包后 没有用

config-webpack.js 中的内容,最后都会通过 webpack 插件,动态添加到 public/index.html 中去,所以修改CDN链接有种方式:

  • 打包前,就把对应的资源路径修改好,再进行打包【推荐】
  • 打包后,直接修改 dist/public/index.html 中的资源的路径,而不是改 config-webpack.js 中的路径

4.1.4 vue.config.js

此步骤 打包前修改CDN链接 和 打包后修改CDN链接 的方式不一样

基座中的 vue.config.js 一般不需要修改,他用于存放 webpack 打包配置 和 vue 项目基本配置

vue.config.js 的内容会在 打包前、打包时 使用到了,打包后 没有用

下图中的 ${name} 对应的是 package.json 中的 name ,也对应部署环境的上下文访问地址(例如:http://localhost:9160/idp-base-app/,package.json 中的 name 和 应用的上下文访问地址 对应的都是 idp-base-app)

如果需要修改应用上下文的话,有两种方式:

  • 打包前修改 package.json 中的 name,然后重新打包【推荐】
  • 打包好的 dist 文件夹里全局搜索并替换(因为有一些压缩文件中的也需要修改)

4.2 解决访问不到服务器上的 api-config.js 文件

有以下几种可能:

  • 服务器跨域
  • 访问了服务器上的 api-config.js ,但是服务器上的加密了,没解密成功
  • 解密失败还有可能是使用了 es6语法中的 const,目前仅支持 var

解决方案:

  • 修改微应用中的 webOnline,换成本地基座文件 http://localhost:8080...
  • 同时,还要修改基座中的 config-webpack.js ,把里面的 api-config.js 改成本地地址

5. 如何在开发者工具中,筛选给地图发送的消息?

  1. 打开开发者工具,选择 Network,在 Filter 中筛选 eio
  2. 随便点开一个 eio:
  3. 操作页面,使地图发生变化
  4. 在 Message 下搜索 给地图发送事件的类型(type):
  5. 点击数据,即可获得消息相关信息:

6. 使用 userAgent 判断设备类型

      // 设备 0-PC,1-移动端let dev = '0';// eslint-disable-next-line max-lenif ((navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i))) {dev = '1';} else {dev = '0';}

区域环评项目(Vue3 PC)实现验证码等功能 问题记录相关推荐

  1. 信息系统项目的应急预案方案_从环评到验收、排污许可证、应急预案,都应在项目什么阶段开展? 先后顺序是什么?...

    点击上方蓝字关注我们 依据<建设项目竣工环境保护验收暂行办法>第六条及第十四条规定,需要对建设项目配套建设的环境保护设施进行调试的,建设单位应当确保调试期间污染物排放符合国家和地方有关污染 ...

  2. 信息系统项目的应急预案方案_【学习】环评、验收、排污许可证、应急预案,都应在项目什么阶段开展?...

    导读 记住这个完整流程:环评编制-取得环评批复(或完成登记表备案)-申请排污许可证-编制环境风险应急预案-试运行-开展竣工环保验收-完成自主验收-正式投产 关于环评.验收.排污许可.应急预案有关规定 ...

  3. 完成各种项目生态环评工作丨全流程基于最新导则下的生态环境影响评价技术方法及图件制作与案例

    目录 专题一  生态环境影响评价框架及流程 专题二 基于遥感解译的土地利用现状图的编制 专题三 生物多样性测定及R语言分析 专题四 植被类型及植被覆盖度图的编制 专题五 生物量与净初级生产力测定:实测 ...

  4. 【环保】超图环保赋能“三线一单”数据应用 智能研判助力战略环评落地

    2017年年底,生态环境部印发了<"生态保护红线.环境质量底线.资源利用上线和环境准入负面清单"编制技术指南(试行)>(以下简称<指南>).<指南&g ...

  5. 微型计算机用什么评价判断,环评中常用评价等级的判定

    环评中常用评价等级的判定 环评就是分析项目建成投产后可能对环境产生的影响,并提出污染防治对策和措施.那么环评中常用评价等级的判定有哪些呢? 一.生态影响评价工作等级划分表 影响区域生态敏感性 工程占地 ...

  6. python项目二:多种验证码及二维码输出

    python项目二:多种验证码及二维码输出 ` import os import qrcode import random import time import tkinter from pystri ...

  7. 环评师c语言题目,C语言考试——编程题_文库吧

    C语言考试--编程题_文库吧 2020-09-27 08:21:23 [导读]该数本身,比如153=13+53+33,故153是水仙花数).要求将判断某数是否素数的功.能编成一个独立的函数,然后在ma ...

  8. 环评制图丨最新导则下的生态系统、土地利用、植被覆盖、适宜生境分布图等制图

    根据最新生态环境影响评价导则,结合生态环评内容庞杂.综合性强的特点,以既包括陆域.又包括水域的项目为主要案例,对生态环评的具体流程及所需内容进行系统阐述.利用Rstudio.Fragstats等软件分 ...

  9. 东海高新技术产业开发区规划环评第一次公示

    东海高新技术产业开发区坐落于东海县牛山镇,是以硅材料产业为特色战略产业,以农副产品精深加工为主导产业,以先进制造,新医药,新型建材等产业为培育型产业的高新区,园区规划面积22.93平方公里. 根据&l ...

最新文章

  1. IDC简报:3月国外最佳共享虚拟主机提供商Top5
  2. 合肥工业大学宣城校区大学生创新创业训练项目申报书:“基于Spark平台的人工智能知识的知识图谱构建”...
  3. 【C语言项目】贪吃蛇游戏(上)
  4. .f' '或者.F' '或者string.format(args)
  5. java 接口和抽象类的区别6_JAVA基础篇-接口和抽象类的区别
  6. bxslider 纵向滑动 vertical image thumbnail slider
  7. Python编程基础06:认识程序控制结构
  8. 爱普生690k打印针测试软件_办公室打印机什么牌子好 办公室打印机怎么选购【详解】...
  9. 后台备份20080917
  10. 配置LVS + Keepalived高可用负载均衡集群之图文教程
  11. 在线预览CAD 在线预览office 在线预览3D模型
  12. python高维数据_高维数据怎样可视化?
  13. 微信app用户及市场调研
  14. pack_padded_sequence torch说明
  15. C/C++软件工程师常见面试题(updating)
  16. mfc vs2010 C++ 连接mysql等数据库
  17. 『阶段总结』研一学习生活总结
  18. OCA全贴合材料工艺解析
  19. 通俗易懂的告诉你大数据、O2O、互联网思维、红海、蓝海是什么!
  20. 大连30年来共产生30位世界冠军 获得奥运金牌4枚

热门文章

  1. 苹果Mac OS系统终端命令大全介绍
  2. 计算机领域当前的主流技术及其社会需求调查报告
  3. 一个数据库SQL查询的数次轮回
  4. leantween.color
  5. Python + Selenium 自动化测试《人生重开模拟器》
  6. HT81293 内置自适应动态升压20W单声道D类功放IC解决方案
  7. Vue开发实战一:FormData参数传递
  8. 开源蓝牙协议栈-Zephyr_polling简介
  9. 程序员颈椎病康复指南(最新)
  10. 处理Win10权限问题,以解决从C盘写入文件或数据时的拒绝访问问题若干