php+vue3实现点选验证码

buildadmin 中的点选验证码实现
验证码类

<?phpnamespace ba;use Throwable;
use think\facade\Db;
use think\facade\Lang;
use think\facade\Config;/*** 点选文字验证码类*/
class ClickCaptcha
{/*** 验证码过期时间(s)* @var int*/private int $expire = 600;/*** 可以使用的背景图片路径* @var array*/private array $bgPaths = ['static/images/captcha/click/bgs/1.png','static/images/captcha/click/bgs/2.png','static/images/captcha/click/bgs/3.png',];/*** 可以使用的字体文件路径* @var array*/private array $fontPaths = ['static/fonts/zhttfs/SourceHanSansCN-Normal.ttf',];/*** 验证点 Icon 映射表* @var array*/private array $iconDict = ['aeroplane' => '飞机','apple'     => '苹果','banana'    => '香蕉','bell'      => '铃铛','bicycle'   => '自行车','bird'      => '小鸟','bomb'      => '炸弹','butterfly' => '蝴蝶','candy'     => '糖果','crab'      => '螃蟹','cup'       => '杯子','dolphin'   => '海豚','fire'      => '火','guitar'    => '吉他','hexagon'   => '六角形','pear'      => '梨','rocket'    => '火箭','sailboat'  => '帆船','snowflake' => '雪花','wolf head' => '狼头',];/*** 配置* @var array*/private array $config = [// 透明度'alpha' => 36,// 中文字符集'zhSet' => '们以我到他会作时要动国产的是工就年阶义发成部民可出能方进在和有大这主中为来分生对于学级地用同行面说种过命度革而多子后自社加小机也经力线本电高量长党得实家定深法表着水理化争现所起政好十战无农使前等反体合斗路图把结第里正新开论之物从当两些还天资事队点育重其思与间内去因件利相由压员气业代全组数果期导平各基或月然如应形想制心样都向变关问比展那它最及外没看治提五解系林者米群头意只明四道马认次文通但条较克又公孔领军流接席位情运器并飞原油放立题质指建区验活众很教决特此常石强极已根共直团统式转别造切九你取西持总料连任志观调么山程百报更见必真保热委手改管处己将修支识象先老光专什六型具示复安带每东增则完风回南劳轮科北打积车计给节做务被整联步类集号列温装即毫知轴研单坚据速防史拉世设达尔场织历花求传断况采精金界品判参层止边清至万确究书术状须离再目海权且青才证低越际八试规斯近注办布门铁需走议县兵固除般引齿胜细影济白格效置推空配叶率述今选养德话查差半敌始片施响收华觉备名红续均药标记难存测身紧液派准斤角降维板许破述技消底床田势端感往神便贺村构照容非亚磨族段算适讲按值美态易彪服早班麦削信排台声该击素张密害侯何树肥继右属市严径螺检左页抗苏显苦英快称坏移巴材省黑武培著河帝仅针怎植京助升王眼她抓苗副杂普谈围食源例致酸旧却充足短划剂宣环落首尺波承粉践府鱼随考刻靠够满夫失包住促枝局菌杆周护岩师举曲春元超负砂封换太模贫减阳扬江析亩木言球朝医校古呢稻宋听唯输滑站另卫字鼓刚写刘微略范供阿块某功友限项余倒卷创律雨让骨远帮初皮播优占圈伟季训控激找叫云互跟粮粒母练塞钢顶策双留误础阻故寸盾晚丝女散焊功株亲院冷彻弹错散商视艺版烈零室轻倍缺厘泵察绝富城冲喷壤简否柱李望盘磁雄似困巩益洲脱投送侧润盖挥距触星松送获兴独官混纪依未突架宽冬章偏纹吃执阀矿寨责熟稳夺硬价努翻奇甲预职评读背协损棉侵灰虽矛厚罗泥辟告箱掌氧恩爱停曾溶营终纲孟钱待尽俄缩沙退陈讨奋械载胞哪旋征槽倒握担仍呀鲜吧卡粗介钻逐弱脚怕盐末丰雾冠丙街莱贝辐肠付吉渗瑞惊顿挤秒悬姆森糖圣凹陶词迟蚕亿矩康遵牧遭幅园腔订香肉弟屋敏恢忘编印蜂急拿扩飞露核缘游振操央伍域甚迅辉异序免纸夜乡久隶念兰映沟乙吗儒汽磷艰晶埃燃欢铁补咱芽永瓦倾阵碳演威附牙芽永瓦斜灌欧献顺猪洋腐请透司括脉宜笑若尾束壮暴企菜穗楚汉愈绿拖牛份染既秋遍锻玉夏疗尖井费州访吹荣铜沿替滚客召旱悟刺脑措贯藏敢令隙炉壳硫煤迎铸粘探临薄旬善福纵择礼愿伏残雷延烟句纯渐耕跑泽慢栽鲁赤繁境潮横掉锥希池败船假亮谓托伙哲怀摆贡呈劲财仪沉炼麻祖息车穿货销齐鼠抽画饲龙库守筑房歌寒喜哥洗蚀废纳腹乎录镜脂庄擦险赞钟摇典柄辩竹谷乱虚桥奥伯赶垂途额壁网截野遗静谋弄挂课镇妄盛耐扎虑键归符庆聚绕摩忙舞遇索顾胶羊湖钉仁音迹碎伸灯避泛答勇频皇柳哈揭甘诺概宪浓岛袭谁洪谢炮浇斑讯懂灵蛋闭孩释巨徒私银伊景坦累匀霉杜乐勒隔弯绩招绍胡呼峰零柴簧午跳居尚秦稍追梁折耗碱殊岗挖氏刃剧堆赫荷胸衡勤膜篇登驻案刊秧缓凸役剪川雪链渔啦脸户洛孢勃盟买杨宗焦赛旗滤硅炭股坐蒸凝竟枪黎救冒暗洞犯筒您宋弧爆谬涂味津臂障褐陆啊健尊豆拔莫抵桑坡缝警挑冰柬嘴啥饭塑寄赵喊垫丹渡耳虎笔稀昆浪萨茶滴浅拥覆伦娘吨浸袖珠雌妈紫戏塔锤震岁貌洁剖牢锋疑霸闪埔猛诉刷忽闹乔唐漏闻沈熔氯荒茎男凡抢像浆旁玻亦忠唱蒙予纷捕锁尤乘乌智淡允叛畜俘摸锈扫毕璃宝芯爷鉴秘净蒋钙肩腾枯抛轨堂拌爸循诱祝励肯酒绳塘燥泡袋朗喂铝软渠颗惯贸综墙趋彼届墨碍启逆卸航衣孙龄岭休借',];/*** 构造方法* @param array $config 点击验证码配置* @throws Throwable*/public function __construct(array $config = []){$clickConfig  = Config::get('buildadmin.click_captcha');//这里会得配置文件中的数据合并//$clickConfig 中的配置是这样的//    'click_captcha'         => [// 模式:text=文字,icon=图标(若只有icon则适用于国际化站点)//'mode'           => ['text', 'icon'],// 长度//'length'         => 2,// 混淆点长度//'confuse_length' => 2,],$this->config = array_merge($clickConfig, $this->config, $config);// 清理过期的验证码Db::name('captcha')->where('expire_time', '<', time())->delete();}/*** 创建图形验证码* @param string $id 验证码ID,开发者自定义* @return array 返回验证码图片的base64编码和验证码文字信息*/public function creat(string $id): array{$imagePath  = Filesystem::fsFit(public_path() . $this->bgPaths[mt_rand(0, count($this->bgPaths) - 1)]);  //随机一个背景图片$fontPath   = Filesystem::fsFit(public_path() . $this->fontPaths[mt_rand(0, count($this->fontPaths) - 1)]);  //随机一个字体$randPoints = $this->randPoints($this->config['length'] + $this->config['confuse_length']);  //生成验证码的长度, 加上混肖点的长度相加$lang = Lang::getLangSet();foreach ($randPoints as $v) {$tmp['size'] = rand(15, 30);if (isset($this->iconDict[$v])) {// 图标$tmp['icon']   = true;$tmp['name']   = $v;$tmp['text']   = $lang == 'zh-cn' ? "<{$this->iconDict[$v]}>" : "<$v>";$iconInfo      = getimagesize(Filesystem::fsFit(public_path() . 'static/images/captcha/click/icons/' . $v . '.png'));$tmp['width']  = $iconInfo[0];   //$size = getimagesize($filename);  $size[0]: 图像的宽度 $size[1]: 图像的高度$tmp['height'] = $iconInfo[1];} else {// 字符串文本框宽度和长度$fontArea      = imagettfbbox($tmp['size'], 0, $fontPath, $v);$textWidth     = $fontArea[2] - $fontArea[0];  //得到文字的宽度$textHeight    = $fontArea[1] - $fontArea[7];   //得到文字的高度$tmp['icon']   = false;  //说明这个不是图片$tmp['text']   = $v;$tmp['width']  = $textWidth;        //文字的宽度$tmp['height'] = $textHeight;      //文字的高度}$textArr['text'][] = $tmp;}// 图片宽高和类型$imageInfo         = getimagesize($imagePath);$textArr['width']  = $imageInfo[0];  //$textArr 的宽度 是背景图宽度$textArr['height'] = $imageInfo[1];  //$testArr 的高度 是背景图高度// 随机生成验证点位置foreach ($textArr['text'] as &$v) {list($x, $y) = $this->randPosition($textArr['text'], $textArr['width'], $textArr['height'], $v['width'], $v['height'], $v['icon']);$v['x'] = $x;$v['y'] = $y;$text[] = $v['text'];  //这里的把生成的标记也按顺序 记录了下来}unset($v);;// 创建图片的实例$image = imagecreatefromstring(file_get_contents($imagePath));foreach ($textArr['text'] as $v) {if ($v['icon']) {$this->iconCover($image, $v);} else {//字体颜色$color = imagecolorallocatealpha($image, 239, 239, 234, 127 - intval($this->config['alpha'] * (127 / 100)));// 绘画文字imagettftext($image, $v['size'], 0, $v['x'], $v['y'], $color, $fontPath, $v['text']);}}$nowTime         = time();$textArr['text'] = array_splice($textArr['text'], 0, $this->config['length']);   //取了两个$text            = array_splice($text, 0, $this->config['length']);   //前两个的text ,用来返回给前端用的Db::name('captcha')->replace()->insert(['key'         => md5($id),'code'        => md5(implode(',', $text)),'captcha'     => json_encode($textArr, JSON_UNESCAPED_UNICODE),'create_time' => $nowTime,'expire_time' => $nowTime + $this->expire]);// 输出图片while (ob_get_level()) {ob_end_clean();}if (!ob_get_level()) ob_start();switch ($imageInfo[2]) {case 1:// GIFimagegif($image);$content = ob_get_clean();break;case 2:// JPGimagejpeg($image);$content = ob_get_clean();break;case 3:// PNGimagepng($image);$content = ob_get_clean();break;default:$content = '';break;}imagedestroy($image);return ['id'     => $id,'text'   => $text,'base64' => 'data:' . $imageInfo['mime'] . ';base64,' . base64_encode($content),'width'  => $textArr['width'],'height' => $textArr['height'],];}/*** 检查验证码* @param string $id    开发者自定义的验证码ID* @param string $info  验证信息* @param bool   $unset 验证成功是否删除验证码* @return bool* @throws Throwable*/public function check(string $id, string $info, bool $unset = true): bool{$key     = md5($id);$captcha = Db::name('captcha')->where('key', $key)->find();if ($captcha) {// 验证码过期if (time() > $captcha['expire_time']) {Db::name('captcha')->where('key', $key)->delete();return false;}$textArr = json_decode($captcha['captcha'], true);list($xy, $w, $h) = explode(';', $info);$xyArr = explode('-', $xy);//xyArr[0] 249,112    xyArr[1]47,68$xPro  = $w / $textArr['width'];// 宽度比例$yPro  = $h / $textArr['height'];// 高度比例foreach ($xyArr as $k => $v) {$xy = explode(',', $v);$x  = $xy[0];  //249$y  = $xy[1];   //112if ($x / $xPro < $textArr['text'][$k]['x'] || $x / $xPro > $textArr['text'][$k]['x'] + $textArr['text'][$k]['width']) {return false;}$phStart = $textArr['text'][$k]['icon'] ? $textArr['text'][$k]['y'] : $textArr['text'][$k]['y'] - $textArr['text'][$k]['height'];$phEnd   = $textArr['text'][$k]['icon'] ? $textArr['text'][$k]['y'] + $textArr['text'][$k]['height'] : $textArr['text'][$k]['y'];if ($y / $yPro < $phStart || $y / $yPro > $phEnd) {return false;}}if ($unset) Db::name('captcha')->where('key', $key)->delete();return true;} else {return false;}}/*** 绘制Icon*/protected function iconCover($bgImg, $iconImgData): void{$iconImage      = imagecreatefrompng(Filesystem::fsFit(public_path() . 'static/images/captcha/click/icons/' . $iconImgData['name'] . '.png'));$trueColorImage = imagecreatetruecolor($iconImgData['width'], $iconImgData['height']);imagecopy($trueColorImage, $bgImg, 0, 0, $iconImgData['x'], $iconImgData['y'], $iconImgData['width'], $iconImgData['height']);imagecopy($trueColorImage, $iconImage, 0, 0, 0, 0, $iconImgData['width'], $iconImgData['height']);imagecopymerge($bgImg, $trueColorImage, $iconImgData['x'], $iconImgData['y'], 0, 0, $iconImgData['width'], $iconImgData['height'], $this->config['alpha']);imagedestroy($iconImage);imagedestroy($trueColorImage);}/*** 随机生成验证点元素* @param int $length* @return array*/public function randPoints(int $length = 4): array{$arr = [];// 文字if (in_array('text', $this->config['mode'])) {for ($i = 0; $i < $length; $i++) {$arr[] = mb_substr($this->config['zhSet'], mt_rand(0, mb_strlen($this->config['zhSet'], 'utf-8') - 1), 1, 'utf-8');}}//这里生成了 4 个文字// 图标if (in_array('icon', $this->config['mode'])) {$icon = array_keys($this->iconDict); //得到所有的图片的 keyshuffle($icon);  //打乱key的顺序$icon = array_slice($icon, 0, $length);  //截取4个图片的key$arr  = array_merge($arr, $icon);  //把生成的 文字和图片的 数组合并}shuffle($arr); //打乱顺序return array_slice($arr, 0, $length);  //取出前4个}/*** 随机生成位置布局* @param array $textArr 点位数据* @param int   $imgW    图片宽度* @param int   $imgH    图片高度* @param int   $fontW   文字宽度* @param int   $fontH   文字高度* @param bool  $isIcon  是否是图标* @return array*/private function randPosition(array $textArr, int $imgW, int $imgH, int $fontW, int $fontH, bool $isIcon): array{$x = rand(0, $imgW - $fontW);$y = rand($fontH, $imgH - $fontH);// 碰撞验证if (!$this->checkPosition($textArr, $x, $y, $fontW, $fontH, $isIcon)) {$position = $this->randPosition($textArr, $imgW, $imgH, $fontW, $fontH, $isIcon);} else {$position = [$x, $y];}return $position;}/*** 碰撞验证* @param array $textArr 验证点数据* @param int   $x       x轴位置* @param int   $y       y轴位置* @param int   $w       验证点宽度* @param int   $h       验证点高度* @param bool  $isIcon  是否是图标* @return bool*/public function checkPosition(array $textArr, int $x, int $y, int $w, int $h, bool $isIcon): bool{$flag = true;foreach ($textArr as $v) {if (isset($v['x']) && isset($v['y'])) {$flagX     = false;$flagY     = false;$historyPw = $v['x'] + $v['width'];if (($x + $w) < $v['x'] || $x > $historyPw) {$flagX = true;}$currentPhStart = $isIcon ? $y : $y - $h;$currentPhEnd   = $isIcon ? $y + $v['height'] : $y;$historyPhStart = $v['icon'] ? $v['y'] : ($v['y'] - $v['height']);$historyPhEnd   = $v['icon'] ? ($v['y'] + $v['height']) : $v['y'];if ($currentPhEnd < $historyPhStart || $currentPhStart > $historyPhEnd) {$flagY = true;}if (!$flagX && !$flagY) {$flag = false;}}}return $flag;}
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
这里的知识点,和验证的时候,图片和文字的 x 坐标和 y 坐标的对比不一样是有关系的

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


前端代码
前端通过代码, 请求 后台的 验证码的 creat ,得到图片,并显示到前端页面

<template><div :id="uuid"><div class="ba-click-captcha" :class="props.class"><div v-if="state.loading" class="loading">{{ i18n.global.t('utils.Loading') }}</div><div v-else class="captcha-img-box"><imgclass="captcha-img"@click.prevent="onRecord($event)":src="state.captcha.base64":alt="i18n.global.t('validate.Captcha loading failed, please click refresh button')"/><spanv-for="(item, index) in state.xy":key="index"class="step"@click="onCancelRecord(index)":style="`left:${parseFloat(item.split(',')[0]) - 13}px;top:${parseFloat(item.split(',')[1]) - 13}px`">{{ index + 1 }}</span></div><div class="captcha-prompt" v-if="state.tip">{{ state.tip }}</div><div v-else class="captcha-prompt">{{ i18n.global.t('validate.Please click') }}<span v-for="(text, index) in state.captcha.text" :key="index" :class="state.xy.length > index ? 'clicaptcha-clicked' : ''">{{ text }}</span></div><div class="captcha-refresh-box"><div class="captcha-refresh-line captcha-refresh-line-l"></div><i class="fa fa-refresh captcha-refresh-btn" :title="i18n.global.t('Refresh')" @click="load"></i><div class="captcha-refresh-line captcha-refresh-line-r"></div></div></div><div class="ba-layout-shade" @click="onClose"></div></div>
</template><script setup lang="ts">
import { reactive, computed } from 'vue'
import { getCaptchaData, checkClickCaptcha } from '/@/api/common'
import { i18n } from '/@/lang'interface Props {uuid: stringcallback?: (captchaInfo: string) => voidclass?: stringunset?: booleanerror?: stringsuccess?: string
}const props = withDefaults(defineProps<Props>(), {uuid: '',callback: () => {},class: '',unset: false,error: i18n.global.t('validate.The correct area is not clicked, please try again!'),success: i18n.global.t('validate.Verification is successful!'),
})const state: {loading: booleanxy: string[]tip: stringcaptcha: {id: stringtext: stringbase64: stringwidth: numberheight: number}
} = reactive({loading: true,xy: [],tip: '',captcha: {id: '',text: '',base64: '',width: 350,height: 200,},
})const load = () => {state.loading = truegetCaptchaData(props.uuid).then((res) => {state.xy = []state.tip = ''state.loading = falsestate.captcha = res.data})
}const onRecord = (event: MouseEvent) => {if (state.xy.length < state.captcha.text.length) {state.xy.push(event.offsetX + ',' + event.offsetY)if (state.xy.length == state.captcha.text.length) {const captchaInfo = [state.xy.join('-'), (event.target as HTMLImageElement).width, (event.target as HTMLImageElement).height].join(';')checkClickCaptcha(props.uuid, captchaInfo, props.unset).then(() => {state.tip = props.successsetTimeout(() => {props.callback?.(captchaInfo)onClose()}, 1500)}).catch(() => {state.tip = props.errorsetTimeout(() => {load()}, 1500)})}}
}const onCancelRecord = (index: number) => {state.xy.splice(index, 1)
}const onClose = () => {document.getElementById(props.uuid)?.remove()
}const captchaBoxTop = computed(() => (state.captcha.height + 200) / 2 + 'px')
const captchaBoxLeft = computed(() => (state.captcha.width + 24) / 2 + 'px')load()
</script><style scoped lang="scss">
.ba-click-captcha {padding: 12px;border: 1px solid var(--el-border-color-extra-light);background-color: var(--el-color-white);position: fixed;z-index: 9999991;left: calc(50% - v-bind('captchaBoxLeft'));top: calc(50% - v-bind('captchaBoxTop'));border-radius: 10px;box-shadow: 0 0 0 1px hsla(0, 0%, 100%, 0.3) inset, 0 0.5em 1em rgba(0, 0, 0, 0.6);.loading {color: var(--el-color-info);width: 350px;text-align: center;line-height: 200px;}.captcha-img-box {position: relative;.captcha-img {width: v-bind('state.captcha.width') px;height: v-bind('state.captcha.height') px;border: none;cursor: pointer;}.step {box-sizing: border-box;position: absolute;width: 20px;height: 20px;line-height: 20px;font-size: var(--el-font-size-small);font-weight: bold;text-align: center;color: var(--el-color-white);border: 1px solid var(--el-border-color-extra-light);background-color: var(--el-color-primary);border-radius: 30px;box-shadow: 0 0 10px var(--el-color-white);user-select: none;cursor: pointer;}}.captcha-prompt {height: 40px;line-height: 40px;font-size: var(--el-font-size-base);text-align: center;color: var(--el-color-info);span {margin-left: 10px;font-size: var(--el-font-size-medium);font-weight: bold;color: var(--el-color-error);&.clicaptcha-clicked {color: var(--el-color-primary);}}}.captcha-refresh-box {position: relative;margin-top: 10px;.captcha-refresh-line {position: absolute;top: 16px;width: 140px;height: 1px;background-color: #ccc;}.captcha-refresh-line-l {left: 5px;}.captcha-refresh-line-r {right: 5px;}.captcha-refresh-btn {cursor: pointer;display: block;margin: 0 auto;width: 32px;height: 32px;font-size: 32px;color: var(--el-color-info);}}
}
</style>

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当点击次数达到两次的时候,就提交到后台去验证
前端提交的数据格式是这样的
在这里插入图片描述
id是前后端对应的, info 中 以 “-” 分隔了 两次点击的 坐标点 , 350是图片的完度,200是图片的高度

接下来后端进行验证, 我们来看看后端的验证过程

在这里插入图片描述
在这里插入图片描述

自已写一个试试, 后端的接口还是用的 buildadmin 的接口,前端我自己写了一下简易代码做了一下实验,如果开发的时候 可以使用上面的 代码, 注意上面的代码是 ts 的,稍稍改一下代码就可以了
以下是我用 vue3 js写的简易代码, 也是可以实现验证码的 , 仅供参考

<template><div class="captcha-wrapper"><div class="captcha"><img v-if="data.captchaInfo.base64" width="300" height="200"  @click.prevent="clickcaptcha($event)" class="img-captcha" :src="data.captchaInfo.base64" /><span v-for="(item,index) in data.captchaInfo.clickXY" :style="{left:item.x+'px',top:item.y+'px'}">{{index+1}}</span></div><div>请点击字符或图片:{{data.captchaInfo.text}}</div><button @click="getphoto"> 刷新验证码 </button></div></template><script setup>
import {onMounted,ref,reactive} from "vue"
import { checkClickCaptcha, getCaptchaData } from '../../api/common'let uuid = ref("");
let data = reactive({captchaInfo:{base64:"",text:"",width:"",   //后端返回的图片的宽高, 一般在显示的时候就按这个大小显示, 本例中没有使用它们,而是自定义了一个 宽高,验证时,要把本地自定义的宽高传给后端才可以height:"",id:uuid,number:0,       //当前图片被点击的次数clickposition:"",clickXY:[]}
})//生命周期onMounted(()=>{console.log(123);uuid = Math.floor(Math.random()*(10000-1+1))+1;getphoto();  //生命周期开始时调用后台接口,得到 验证码图片});//图片的点击事件let clickcaptcha = (e)=>{let xy = e.offsetX+","+e.offsetY;   //得到点击的位置,因为是两个验证码,所以要点击两次if(data.captchaInfo.number == 0){data.captchaInfo.clickposition = xy;   //如果是第一次点击 记录一下, 点击位置data.captchaInfo.clickXY = [{x:e.offsetX,y:e.offsetY}]}else {data.captchaInfo.clickposition = data.captchaInfo.clickposition + "-" + xy;  //如果是第二次点击 ,把两次点击的位置都记录下来data.captchaInfo.clickXY.push({x:e.offsetX,y:e.offsetY})}data.captchaInfo.number++;if(data.captchaInfo.number == 2){  //点击了两次checkClickCaptcha(data.captchaInfo.id, data.captchaInfo.clickposition+";"+'300;200', true).then((res) => {console.log(res);if(res.code == 1){//这里验证成功的代码,  验证成功之后, 把captchaInfo 的数据清空//然后提交表单中的数据alert("验证成功")}else if(res.code == 0){alert("验证失败")}}).catch(() => {alert("验证失败")})}}let getphoto = ()=>{getCaptchaData(uuid).then(res=>{data.captchaInfo = Object.assign(data.captchaInfo,res.data,{ number:0,       //当前图片被点击的次数clickposition:"",clickXY:[]});})}</script><style scoped lang="scss">.captcha-wrapper{width:300px;  //这里要和自定义的图片一样宽.captcha{position: relative;.img-captcha{}span{position:absolute;display:block;width:20px;height:20px;background:#f60;text-align: center;line-height: 20px;border-radius: 10px;color:#fff;}}}
</style>

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Leetcode刷题详解——太平洋大西洋水流问题

1. 题目链接&#xff1a;417. 太平洋大西洋水流问题 2. 题目描述&#xff1a; 有一个 m n 的矩形岛屿&#xff0c;与 太平洋 和 大西洋 相邻。 “太平洋” 处于大陆的左边界和上边界&#xff0c;而 “大西洋” 处于大陆的右边界和下边界。 这个岛被分割成一个由若干方形单元格…

Spring Cloud学习(八)【RabbitMQ 服务异步通讯】

文章目录 初识 MQ同步通讯异步通讯MQ 常见框架 RabbitMQ 快速入门RabbitMQ 单机部署RabbitMQ概述常见消息模型 SpringAMQPSimpleQueue 模型WorkQueue 模型发布订阅模型发布订阅-Fanout Exchange发布订阅-DirectExchange发布订阅-TopicExchange消息转换器 初识 MQ 同步通讯 同步…

解密图像处理中的利器——直方图与均衡化

直方图与均衡化是数字图像处理中常用的重要工具&#xff0c;它们能够帮助我们更好地理解和改善图像的亮度分布。本文将首先介绍直方图的基本概念以及其在图像处理中的意义&#xff0c;接着详细阐述直方图均衡化的原理和算法。同时&#xff0c;文章将探讨直方图均衡化在图像增强…

基于Vue+SpringBoot的天然气工程运维系统 开源项目

目录 一、摘要1.1 项目介绍1.2 项目详细录屏 二、功能模块2.1 系统角色分类2.2 核心功能2.2.1 流程 12.2.2 流程 22.3 各角色功能2.3.1 系统管理员功能2.3.2 用户服务部功能2.3.3 分公司&#xff08;施工单位&#xff09;功能2.3.3.1 技术员角色功能2.3.3.2 材料员角色功能 2.3…

【Redis系列】Redis上设置key,value的时候出现NOAUTH Authentication required提示如何解决?

哈喽&#xff0c;大家好&#xff0c;我是小浪。相信大家在初学一门新的知识点的时候都会遇到各种各样的问题&#xff0c;在网上找了一大堆的解决方案&#xff0c;最后还是无功而返&#xff0c;那么今天博主就记录一下在进行Redis的一些操作中遇到的问题~ 当我们好不容易安装好R…

matlab simulink PSO算法优化simulink的PID参数

1、内容简介 略 13-可以交流、咨询、答疑 PSO算法优化simulink的PID参数 2、内容说明 标准的PSO算法优化simulink的PID参数 PSO、粒子群算法、simulink参数优化 3、仿真分析 4、参考论文 略 链接&#xff1a;https://pan.baidu.com/s/1yQ1yDfk-_Qnq7tGpa23L7g 提取码&…

计算机组成原理——指令系统题库1-20

1、以下有关指令系统的说法中错误的是什么。 A、 指令系统是一台机器硬件能执行的指令全体 B、 任何程序运行前都要先转化为机器语言 C、 指令系统是计算机软件、硬件的界面 D、 指令系统和机器语言是无关的。 2、在CPU执行指令的过程中&#xff0c;指令的地址由什么给出。…

线性模型拟合非线性数据中,如何找到最优的【分箱】数

具体的数据可以回看上一条博客。我们先来始化三个空列表&#xff0c;用于存储后续计算的预测得分、交叉验证得分的平均值和交叉验证得分的方差。 pred,score,var [], [], [] 2. 再定义一个列表&#xff0c;包含了我们想要尝试的分箱数量。 binsrange [2,5,10,15,20,30] 3…

Domino 14中安装配置使用OnTime团队日历组件

大家好&#xff0c;才是真的好。 在9月底发布的Domino 14 EA3中&#xff0c;包含了OnTime团队日历组件。OnTime是一款日历协作功能应用&#xff0c;侧重于团队日历&#xff0c;功能侧重于协作&#xff0c;界面较为简洁、现代等。 Domino如今越来越多地集成进合作伙伴的解决方…

Android设计模式--原型模式

一&#xff0c;定义 原型模式就是用原型实例指定创建对象的种类&#xff0c;并通过拷贝这些原型创建新的对象 也就是说用户从一个实例中复制出一个内部属性一致的对象&#xff0c;这个被复制的对象就是原型。 原型模式多用于创建复杂的或者构造耗时的实例&#xff0c;因为这…

Python入门:一文详解Python列表(List)操作方法

文章目录 前言一、创建一个列表二、访问列表中的值三、更新列表四、删除列表元素六、Python列表截取七、Python列表操作的函数和方法关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源码合集①Python工具包②…