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.rhkb.cn/news/192516.html

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

相关文章

理工ubuntu20.04电脑配置记录

8188gu无线网卡配置 首先下载github上的文件&#xff0c;进入文件夹 安装make命令 1. 查看usb无线网卡 sudo lsusb|grep 8188 2. 环境准备 sudo apt-get install git make build-essential git dkms linux-headers-$(uname -r) 3. 编译安装 git clone https://github.com…

Linux_磁盘管理_df命令

1、df命令是用来干什么的 df的全称是disk free&#xff0c;意为“磁盘空间”。 使用df命令可以查看系统中磁盘的占用情况&#xff0c;有哪些文件系统&#xff0c;在什么位置&#xff08;挂载点&#xff09;&#xff0c;总空间&#xff0c;已使用空间&#xff0c;剩余空间等。…

SpringBoot写接口小记 以及 几个层的功能总结(自用 勿喷)

目录 Entity层&#xff1a;实体层 数据库在项目中的类 Mapper层&#xff1a; 持久层 主要与数据库进行交互 Service层&#xff1a;业务层 控制业务 Controller层&#xff1a;控制层 控制业务逻辑 Entity层&#xff1a;实体层 数据库在项目中的类 Entity层是实体层&#xff…

医院手术麻醉信息系统(麻醉知情同意书)源码

手术室麻醉信息管理系统是定位于手术室和麻醉科的科室级临床信息管理系统&#xff0c;主要用于与手术麻醉相关的各项数据的记录、管理和应用&#xff0c;实现医疗信息的共享及再利用&#xff0c;提高科室的整体信息化水平。 该系统将手术室内的各种设备&#xff08;如呼吸机、麻…

#[量化投资-学习笔记018]Python+TDengine从零开始搭建量化分析平台-正态分布与收益率

正态分布(Normal Distribution)又叫高斯分布、常态分布。通常用来描述随机变量的概率分布。 自然界的数据分布通常是符合正态分布规律的&#xff0c;比如说人的身高、体重。但是非自然界数据就不一定了。尤其是经过人为加工过的数据。 金融领域大量使用正态分布来计算收益率和…

如何进行iOS技术博客的备案?

如何进行iOS技术博客的备案&#xff1f; 标题&#xff1a;iOS技术博客备案流程及要求解析 摘要&#xff1a; 在本篇问答中&#xff0c;我们将为iOS技术博主介绍如何进行备案。如果你的iOS应用只包含简单的页面&#xff0c;并通过蓝牙进行数据采集和传输&#xff0c;那么你可能…

Android实验:Activity界面基础

目录 前言实验目的实验内容实验要求代码实现mainActivityResultActivityactivity_mainactivity_result 结果展示 前言 我们都知道&#xff0c;activity是Android中最重要的组件之一&#xff0c;关于activity的具体内容在这里就不多赘述&#xff0c;主打的就是一个主次分明&…

源码级JVS低代码功能新增:动态配置、逻辑多级循环嵌套等等

低代码更新功能 新增: 1.下拉组件选项新增动态配置&#xff1b; 选项的内容可以根据特定的条件或数据源进行动态变化的功能&#xff0c;通过动态配置&#xff0c;用户可以灵活地设置下拉组件的选项内容&#xff0c;例如从数据库或其他数据源中获取选项数据&#xff0c;或者根…

3.3 Linux 文件管理

1、查看系统信息 tty 命令 描述&#xff1a;查看当前系统在哪个终端语法&#xff1a;tty Linux默认情况下提供6个虚拟终端来让用户登录&#xff0c;系统将F1~F6定义为tty1~tty6。 ctrlalt(F1~F6) &#xff1a;从图形界面切换到命令行界面的第 n 个虚拟终端&#xff08;F1 是…

DAY53 1143.最长公共子序列 + 1035.不相交的线 + 53. 最大子序和

1143.最长公共子序列 题目要求&#xff1a;给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长公共子序列的长度。 一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串在不改变字符的相对顺序的情况下删除某些字符&#xff08;也可以不删…

MHA的那些事儿

什么是MHA&#xff1f; masterhight availability&#xff1a;基于主库的高可用环境下&#xff0c;主从复制和故障切换 主从的架构 MHA至少要一主两从 出现的目的&#xff1a;解决MySQL的单点故障问题。一旦主库崩溃&#xff0c;MHA可以在0-30s内自动完成故障切换 MHA使用的…

redis-5.0.8主从集群搭建、不重启修改配置文件

一、环境准备 192.168.5.100 redis-01 192.168.5.101 redis-02 192.168.5.102 redis-03 关闭防火墙、能够通网 二、安装redis [rootlocalhost ~]# wget http://download.redis.io/releases/redis-5.0.8.tar.gz [rootlocalhost ~]# tar xf redis-5.0.8.tar.gz -C /usr/loca…

爆款元服务!教你如何设计高使用率卡片

元服务的概念相信大家已经在 HDC 2023 上有了很详细的了解&#xff0c;更轻便的开发方式&#xff0c;让开发者跃跃欲试。目前也已经有很多开发者开发出了一些爆款元服务&#xff0c;那么如何让你的元服务拥有更高的传播范围、更高的用户使用率和更多的用户触点呢&#xff1f;设…

CPS-8910

PCI Express&#xff0c;有线开关设备 CPS-8910专为在PXI平台或软件无线电设备上实现大型多输入多输出(MIMO)扩展配置和系统控制而设计。 CPS-8910提供了2个PCI Express上行端口和8个下行端口来实现无缝系统扩展。 下行端口可以连接软件无线电可重配置设备等外部设备&#xff0…

「Verilog学习笔记」优先编码器Ⅰ

专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点&#xff0c;刷题网站用的是牛客网 分析 分析编码器的功能表&#xff1a; 当使能El1时&#xff0c;编码器工作&#xff1a;而当E10时&#xff0c;禁止编码器工作&#xff0c;此时不论8个输入端为何种状态&…

LLM(四)| Chinese-LLaMA-Alpaca:包含中文 LLaMA 模型和经过指令微调的 Alpaca 大型模型

论文题目&#xff1a;《EFFICIENT AND EFFECTIVE TEXT ENCODING FOR CHINESE LL AMA AND ALPACA》 ​论文地址&#xff1a;https://arxiv.org/pdf/2304.08177v1.pdf Github地址&#xff1a;https://github.com/ymcui/Chinese-LLaMA-Alpaca 一、项目介绍 通过在原有的LLaMA词…

10-18 请求与相应1

前后台联调 前台通过一个表单, action写的servlet绑定的url,提交表单,请求我们servlet的doGet()/ doPost()方法 问题: 1.后台怎么获取前端的提交,请求的数据?底层:TCP通信,socket的得到输入流,读取数据 2.后台处理请求之后,怎么把结果给到前端?底层:TCP通信,socket的得到输入…

交换机堆叠 配置(H3C)

堆叠用来干什么&#xff1f; 一台交换机网口有限&#xff0c;无法满足网络需求&#xff1b; 无法达到网络要求&#xff0c;为了扩展核心设备的转发要求&#xff0c;不改变原来网络&#xff0c; 可以使用新交换机和原来交换机组成IRF。 配合聚合可以达到备用作用&#xff0c;防…

Programming Abstractions in C阅读笔记:p196

《Programming Abstractions in C》学习第63天&#xff0c;p196总结。涉及到编程之外的知识&#xff0c;依然是读起来很费劲&#xff0c;需要了解作者在书中提到的人物(Edouard Lucas)、地点(Benares)、神话传说(Brahma)等等。虽然深知自己做不到对人文知识&#xff0c;历史知识…

2023最新electron 进程间通讯的几种方法

数据传递&#xff08;旧&#xff09; 渲染进程发数据到主进程 // 按钮事件 const handleWebRootPathClick () > {ipcRenderer.send(open_dir) }// main.ts中接收 ipcMain.on(open_dir, () > {console.log(recv ok) }) 主进程发数据到渲染进程 // main.ts中发送数据 …