首页
学习
关于
友链
Search
1
小程序 蓝牙连接(出现的问题和一些解决方法)
313 阅读
2
颜色空间:RGB、HSV和HSL
138 阅读
3
小程序_连接蓝牙设备根据平台去打开定位权限
133 阅读
4
小程序 加快安卓手机向蓝牙设备发送大数据
92 阅读
5
侧边菜单切换的显示与隐藏,图标的 + 与 -
90 阅读
全部
感想
旅行
生活
学习
登录
Search
标签搜索
css
javascript
jquery
html
小程序
github
图床
假期
发布订阅
typecho
第一次
未来
快乐与忧伤
努力
奋斗
PicGo
倒计时
元旦
svg
vue
逸曦穆泽
累计撰写
35
篇文章
累计收到
2
条评论
首页
栏目
全部
感想
旅行
生活
学习
页面
学习
关于
友链
搜索到
25
篇与
学习
的结果
2022-12-17
小程序_图片转canvas,上传和rgb888转rgb565发送到设备
序: 小程序需要将图片上传到蓝牙设备,那么图片要转为二进制数据,转为二进制的图片数据不能过大,就要裁剪图片,因为要尽量减少图片的二进制数据,要将图片的rgb888转为rgb565,也就是24位缩小为16位数据;例如:100像素*100像素的图片,就有1万个点,一个点包含rgba四个数,只需要rgb这三个数,rgb三个数各包含8位(也就是二进制,为rgb888),rgb888为三个字节,rgb565为两个字节,数据量传输就少了三分一,但也有几十K的数据了,分240个字节每包发送完也要十秒左右;如果要上传,上传到后端的路径要是临时路径才可以,不能是本地路径,所以要使用 canvas 转一下得到 tempFilePath 的临时路径,一、图片渲染为 canvas 得到临时路径,并上传// 选择本地图片渲染成 canvas locationImg(e) { let imgUrl = e.currentTarget.dataset.img; let that = this; gbImgComStr = ""; wx.createSelectorQuery().select('#canvasImg').fields({ node: true, size: true }).exec((res) => { const canvas = res[0].node; const ctx = canvas.getContext('2d'); var dpr = wx.getSystemInfoSync().pixelRatio // 设备像素比 canvas.width = res[0].width * dpr canvas.height = res[0].height * dpr ctx.scale(dpr, dpr) const img = canvas.createImage(); img.onload = () => { ctx.drawImage(img, 0, 0, 200, 200); wx.canvasToTempFilePath({ x: 0, y: 0, width: 200, height: 200, destWidth: 200, destHeight: 200, canvas: canvas, fileType: "jpg", quality: .8, success(res) { that.initImgData(res.tempFilePath); // 使用服务器 } }) } img.src = imgUrl; }) // console.log(imgUrl) }, // 图片上传到服务器 initImgData(imgUrl) { let that = this; let gbImgPath = ""; wx.uploadFile({ url: apiUrl+'/imgUpload', //你的后端上传路径 filePath: imgUrl, name: "imgPath", // 键值key header: { "content-type": "multipart/form-data"}, success: function (res) { if (res.statusCode == 200) { gbImgPath = JSON.parse(res.data).imgPath; // 存放到服务器上的图片路径 } }, fail: function (err) { wx.showToast({ title: "上传失败", icon: "none", duration: 2000 }) }, }) // 图片回显 或 gbImgPath that.setData({ imgPath: imgUrl }) },后端接口(PHP)://对应的文件夹 public function dirFile($name){ $dirName = "store/".$name."/".date("Ym"); if(!file_exists($dirName)){ mkdir(app()->getRootPath().'public/'.$dirName,0777,true); } return $dirName; } // 图片处理 function imgUpload(){ if(request()->isPost()){ $dirName = $this -> dirFile('image'); $file = $_FILES['imgPath']; // 获取小程序传来的图片 if(is_uploaded_file($file['tmp_name'])) { $type = $file["type"];//文件类型 如:image/png $ext = strrchr($type, '/');//切割字符串 //类型判断 if (in_array($ext, ['/jpeg', '/jpg', '/png', '/svg', '/gif','ico'])) { $filePrefix1 = strstr($file["name"], '.', true);// 文件名前缀 无.png $filestr = date("Ymdhis").$filePrefix1; // 避免同一时间调用该方法时产生同一个文件名 $fileExt1 = strstr($file["name"], '.'); // 获取 .png $fileName = $dirName . "/" . md5($filestr) . $fileExt1; //拼接文件路径 //判断文件是否存在 // if (file_exists($fileName)) { // return json(["statusCode" => 200,"msg"=>"exist","imgPath" => $fileName]); // } else { //保存文件 ($_FILES["文件名"]["tmp_name"],newPath),(旧路径,新路径) move_uploaded_file($file["tmp_name"], $fileName); return json(["statusCode" => 200,"msg"=>"success","imgPath" => $fileName]); // } } return json(["statusCode" => 402,"msg"=>"format error"]); } return json(["statusCode"=>401,"msg"=>"file error"]); } return json(["statusCode"=>400,"msg"=>"request error"]); }二、在小程序上将图片数据rgb888转为rgb565var gbImgComStr = ""; // 图片:16进制补零 function imgHexFill(hexStr) { let len = hexStr.length; var map = { 1: "000", 2: "00", 3: "0" } if (map[len]) { hexStr = map[len] + hexStr; } return hexStr; } // 上为 Page 外,下为 Page 内 // 图片画成 canvas 获取数据 initImgData565: async function(imgUrl) { return new Promise((resolve, reject) => { let that = this; imgLimitNum = 0; wx.createSelectorQuery().select('#canvasImg').fields({ node: true, size: true }).exec((res) => { const canvas = res[0].node; const ctx = canvas.getContext('2d'); // var dpr = wx.getSystemInfoSync().pixelRatio // 设备像素比 var dpr = 1; canvas.width = res[0].width * dpr canvas.height = res[0].height * dpr // ctx.scale(dpr, dpr) let sideLen = 100; ctx.fillRect(0, 0, sideLen, sideLen); const img = canvas.createImage(); img.onload = () => { ctx.drawImage(img, 0, 0, sideLen, sideLen); var imageData = ctx.getImageData(0, 0, sideLen, sideLen).data; // console.log(imageData.length) var rgbStr = ""; var rgb = 0; var rgb565 = 0; var w = 0; var h = 0; // 逐列式 (逐行式则是Z扫描) for (var w = 0; w < sideLen; w++) { for (var h = 0; h < sideLen; h++) { var offset = (h*sideLen + w)*4; var red = imageData[offset]; var green = imageData[offset + 1]; var blue = imageData[offset + 2]; rgb = 0xff000000 | (red << 16) | (green << 8) | blue; rgb565 = ((rgb & 0xf80000) >> 8) | ((rgb & 0xfc00) >> 5) | ((rgb & 0xf8) >> 3) rgbStr += imgHexFill(rgb565.toString(16)); } } gbImgComStr = rgbStr.toUpperCase(); imgUrl != "" ? resolve() : reject(); } img.src = imgUrl; }) }) },由于数据过大,这些数据还要分包发送到蓝牙设备,首先,要发第一包数据告诉设备要发图片的指令了,可以加入长度,这个要和设备那边沟通,分包发送还不能全部直接发,不然蓝牙设备很容易造成丢包的,小程序发完了,设备那边可能才获取十分之一呢,要分20、30包发完后再发一个确定指令,如果返回正确的指令则继续分20、30包发下去再发一个确定指令,然后重复发完为止;部分代码:// 图片_下个数据包指令 imgNextCmd: async function(){ let that = this; let sData = that.data; let comStr = "", roleData = "", command = ""; let rlt = ""; let packageNum = 30; // 连续发送包的个数 var rowSum = ((imgPackageSum - imgIndex) >= packageNum) ? (imgIndex + packageNum) : imgPackageSum; for (let row = imgIndex; row < rowSum; row++) { comStr = gbImgComStr.substr((row)*(imgCharLen-10),(imgCharLen-10)); roleData = "AC" + decToHexLenFill(row+1) + decToHex(comStr.length/2) + comStr; let res = new Uint8Array(roleData.match(/[\da-f]{2}/gi).map(function (h) { return parseInt(h, 16) })); command = roleData + arrSumCheck(res); imgIndex += 1; // await sleep(50); rlt = await that.sendCom(command, sData.deviceId, sData.serviceId, sData.characteristicId); if (rlt == "success") { // 包确认 if(row == (rowSum-1)){ // console.log(">>> sure",rowSum)// 成功 let sureCom = "BCxxxx"+decToHexLenFill(row+1); let cs = new Uint8Array(sureCom.match(/[\da-f]{2}/gi).map(function (h) { return parseInt(h, 16) })); let sureCommand = sureCom + arrSumCheck(cs); that.writeBLECharacteristicValue(sureCommand); } } } if(rowSum == imgPackageSum && imgLimitNum == 0){ imgLimitNum = 1; let endCom = "xxx"; // 传输完毕 that.writeBLECharacteristicValue(endCom); } },彩蛋: 一般一个包只能发20个字节,你觉得一张150*150的图片数据发完需要多久?要两分钟左右吧,这个还没考虑中途断连等情况呢!你觉得你是用户,你会接受吗?所以,可以先获取一下mtu是很有必要的,一般Android的是23(包含头部3个字节信息),iOS的是512,有部分Android手机是没有mtu值的,只能落空默认值了,Android可以协商mtu值 wx.setBLEMTU ,你可以一个包发256、300个字节,看你需求,过大会产生一些问题,并且每包发送也可以设置一下延迟多少毫秒,也可以避免传输过程中断连问题。
2022年12月17日
44 阅读
0 评论
0 点赞
2022-11-13
小程序 加快安卓手机向蓝牙设备发送大数据
序: 小程序要将图片数据传到蓝牙设备上,几十上百K的图片数据相对于蓝牙设备来说是很大的,正常20个字节发送完到设备要一两分钟,如果与安卓协商mtu长度后,则可以达到十几二十多秒左右,安卓低机型不支持。和安卓协商mtuvar imgCharLen = 240; // 图片有效数据长度,iOS:240(120个字节),Android:40(20个字节) var platform = true; // true_ios,false_android onLoad(){ const res = wx.getSystemInfoSync(); platform = res.platform == "ios" ? true : false; } onShow() { // 连上设备 if(app.globalData.connected){ // 和安卓协商mtu if(!platform){ wx.setBLEMTU({ deviceId: app.globalData.deviceId, mtu: 240, success:(res)=>{ if(res.errCode == 0){ imgCharLen = 240; // mtu:240 }else{ imgCharLen = 40; } } }) } } }app.globalData.connected 这个是全局变量来着,也就是设备连接之后才协商的当然,也可以根据你的实际需要来设置,ios的mtu默认是512,Android的mtu默认是23.
2022年11月13日
92 阅读
0 评论
0 点赞
2022-10-15
小程序_连接蓝牙设备根据平台去打开定位权限
全局定义和使用:全局获取一次,适用单个或多个页面去使用,避免多次频繁去获取接口信息。app.jsonLaunch() { const res = wx.getSystemInfoSync(); this.globalData.platform = res.platform != "ios" ? true : false; }, globalData: { platform: false, // 默认 ios,其他平台需要权限 }xxx.js 使用const app = getApp(); var platform = false; // 平台:iOS_false,其他_true Page({ onLoad: function (options) { let that = this; platform = app.globalData.platform if(platform){ that.getLocation(); } } })在单独页面使用:单个页面获取与使用,在单页可以单次或多次使用,方便直接。const app = getApp(); var platform = false; // 平台:iOS_false,其他_true Page({ onLoad: function (options) { let that = this; const res = wx.getSystemInfoSync() platform = res.platform != "ios" ? true : false; if(platform){ that.getLocation(); } }, /** 获取定位信息 */ getLocation: function () { // 获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限 wx.getSetting({ success: function (res) { if(res.authSetting['scope.userLocation'] != undefined && res.authSetting['scope.userLocation'] != true){ wx.showModal({ title: '是否授权当前位置', content: '需要获取你的地理位置,请确认授权,否则无法获蓝牙', success: function(mres){ if(mres.confirm){ wx.openSetting({ success (authData) { if(authData.authSetting['scope.userLocation'] == true){ wx.showToast({ title: '授权成功', icon: "success", duration: 1000 }) }else{ wx.showToast({ title: '授权失败', icon: "error", duration: 1000 }) } } }) }else if(mres.cancel){ wx.showToast({ title: '授权失败', icon: "error", duration: 1000 }) } } }) }else{ // 当前位置 wgs84 返回 gps 坐标,gcj02 返回可用于 wx.openLocation 的坐标 wx.getLocation({ type: 'wgs84', success: function (res) { app.hintLog('打开地理位置','') }, }) } }, }) }, })可能有很多人有疑惑,为什么Android手机需要打开定位权限,其实不是的,Android手机需要打开定位权限的只是部分手机而已,并不是全部,如果可以,你可以根据部分手机去过滤掉也是可行的。
2022年10月15日
133 阅读
0 评论
0 点赞
2022-09-18
JS基本特效 -- 常见40个常用的js页面效果图
1、将彻底屏蔽鼠标右键(oncontextmenu="window.event.returnValue=false")<table border oncontextmenu=return(false)><td>no</table> // 可用于Table2、取消选取、防止复制<body onselectstart="return false"> 3、不准粘贴onpaste="return false"4、防止复制oncopy="return false;" oncut="return false;" 5、IE地址栏前换成自己的图标 <link rel="Shortcut Icon" href="favicon.ico"> 6、可以在收藏夹中显示出你的图标<link rel="Bookmark" href="favicon.ico">7、关闭输入法<input style="ime-mode:disabled">8、永远都会带着框架<script language="JavaScript"><!--if (window == top)top.location.href = "frames.htm"; //frames.htm为框架网页 --> </script>9、防止被人frame<script language="javascript" > <!-- if (top.location != self.location)top.location=self.location;--> </script>10、网页将不能被另存为<noscript><iframe src="/blog/*.html>";</iframe></noscript>11、查看网页源代码<input type=button value="查看网页源代码" onclick="window.location='view-source:' + 'http://www.williamlong.info'">12、删除时确认<a href="javascript:if(confirm('确实要删除吗?'))console.log('删除')">删除</a>13、取得控件的绝对位置//Javascript <script language="Javascript"> function getIE(e){ var t=e.offsetTop; var l=e.offsetLeft; while(e=e.offsetParent) alert("top="+t+"/n left="+l); } </script> <script language="VBScript"> function getIE() dim t,l,a,b set a=document.all.img1 t=document.all.img1.offsetTop l=document.all.img1.offsetLeft while a.tagName<>"BODY" set a = a.offsetParent t=t+a.offsetTop l=l+a.offsetLeft wend msgbox "top="&t&chr(13)&"left="&l,64,"得到控件的位置" end function </script>14、光标是停在文本框文字的最后<script language="javascript"> function lightFocus(){ var e = event.srcElement; var r =e.createTextRange(); r.moveStart("character",e.value.length); r.collapse(true); r.select(); } </script> <input name="test" value="333" onfocus="lightFocus()" />15、判断上一页的来源 javascript: document.referrer16、最小化、最大化、关闭窗口 <object id="min" classid="clsid:ADB880A6-D8FF-11CF-9377-00AA003B7A11"> <param name="Command" value="Minimize"> </object> <object id="max" classid="clsid:ADB880A6-D8FF-11CF-9377-00AA003B7A11"> <param name="Command" value="Maximize"> </object> <object id="close" classid="clsid:adb880a6-d8ff-11cf-9377-00aa003b7a11"> <param name="Command" value="Close"> </object> <input type="button" value="最小化" onclick="min.tabMin()"> <input type="button" value="最大化" onclick="max.tabMax()"> <input type="button" value="关闭" onclick="close.tabClose()">17、屏蔽功能键Shift,Alt,Ctrl<script> function look(){ if(event.shiftKey){} alert("禁止按Shift键!"); //可以换成ALT CTRL } document.onkeydown=look; } </script>18、网页不会被缓存 <META HTTP-EQUIV="pragma" CONTENT="no-cache"> <META HTTP-EQUIV="Cache-Control" CONTENT="no-cache, must-revalidate"> <META HTTP-EQUIV="expires" CONTENT="Wed, 26 Feb 1997 08:21:57 GMT"> 或者<META HTTP-EQUIV="expires" CONTENT="0">19、怎样让表单没有凹凸感?<input type=text style="border:1 solid #000000">或 <input type=text style="border-left:none; border-right:none; border-top:none; border-bottom:1 solid #000000"></textarea>20、<div><span>&<layer>的区别? <div>(division)用来定义大段的页面元素,会产生转行 <span>用来定义同一行内的元素,跟<div>的唯一区别是不产生转行 <layer>是ns的标记,ie不支持,相当于<div>21、让弹出窗口总是在最上面: <body onblur="this.focus();">22、不要滚动条? // 让竖条没有: <body style="overflow:scroll;overflow-y:hidden"> </body> // 让横条没有: <body style="overflow:scroll;overflow-x:hidden"> </body> // 两个都去掉?更简单了 <body scroll="no"></body>23、怎样去掉图片链接点击后,图片周围的虚线?<a href="#" onFocus="this.blur()"><img src="/xxx/logo.jpg" border=0></a>24、电子邮件处理提交表单 <form name="form1" method="post" action="mailto:****@***.com" enctype="text/plain"> <input type=submit> </form>25、在打开的子窗口刷新父窗口的代码里如何写? window.opener.location.reload()26、如何设定打开页面的大小 <body onload="top.resizeTo(300,200);"> 打开页面的位置<body onload="top.moveBy(300,200);">27、在页面中如何加入不是满铺的背景图片,拉动页面时背景图不动<style> body{background-image:url(/blog/logo.gif); background-repeat:no-repeat; background-position:center;background-attachment: fixed} </style>28、检查一段字符串是否全由数字组成<script language="Javascript"> function checkNum(str){return str.match(//D/)==null} alert(checkNum("1232142141")) alert(checkNum("123214214a1")) </script>29、获得一个窗口的大小document.body.clientWidth; document.body.clientHeight30、怎么判断是否是字符 if (/[^/x00-/xff]/g.test(s)){ alert("含有汉字"); } else { alert("全是字符"); }31、TEXTAREA自适应文字行数的多少 <textarea rows=1 name=s1 cols=27 onpropertychange="this.style.posHeight=this.scrollHeight"></textarea>32、日期减去天数等于第二个日期<script language=Javascript> function test(d,td){ //可以加上错误处理 var a = new Date(d); a = a.valueOf(); a = a - td * 24 * 60 * 60 * 1000; b = new Date(a); alert(b.getFullYear() + "年" + (b.getMonth() + 1) + "月" + b.getDate() + "日") } test("12/23/2002",2); </script>33、选择了哪一个Radio<html> <script language="vbscript"> function checkme() // 点击切换 } </script> <body> <input name="radio1" type="radio" value="style" checked>Style <input name="radio1" type="radio" value="barcode">Barcode <input type="button" value="check" onclick="checkme()"> </body> </html>34、脚本永不出错<script language="JavaScript"> function killErrors() { return true; } window.onerror = killErrors; </script>35、ENTER键可以让光标移到下一个输入框 <input onkeydown="if(event.keyCode==13)event.keyCode=9">36、各种样式的光标 auto :标准光标 default :标准箭头 hand :手形光标 wait :等待光标 text :I形光标 vertical-text :水平I形光标 no-drop :不可拖动光标 not-allowed :无效光标 help :?帮助光标 all-scroll :三角方向标 move :移动标 crosshair :十字标 e-resize n-resize nw-resize w-resize s-resize se-resize sw-resize37、页面进入和退出的特效 进入页面<meta http-equiv="Page-Enter" content="revealTrans(duration=x, transition=y)"> 推出页面<meta http-equiv="Page-Exit" content="revealTrans(duration=x, transition=y)"> 这个是页面被载入和调出时的一些特效。duration表示特效的持续时间,以秒为单位。transition表示使用哪种特效,取值为1-23: 0 矩形缩小 1 矩形扩大 2 圆形缩小 3 圆形扩大 4 下到上刷新 5 上到下刷新 6 左到右刷新 7 右到左刷新 8 竖百叶窗 9 横百叶窗 10 错位横百叶窗 11 错位竖百叶窗 12 点扩散 13 左右到中间刷新 14 中间到左右刷新 15 中间到上下 16 上下到中间 17 右下到左上 18 右上到左下 19 左上到右下 20 左下到右上 21 横条 22 竖条 23 以上22种随机选择一种38、在规定时间内跳转<meta http-equiv=V="REFRESH" content="5;URL=http://www.williamlong.info">39、网页是否被检索 <meta name="ROBOTS" content="属性值"> 其中属性值有以下一些: 属性值为"all": 文件将被检索,且页上链接可被查询; 属性值为"none": 文件不被检索,而且不查询页上的链接; 属性值为"index": 文件将被检索; 属性值为"follow": 查询页上的链接; 属性值为"noindex": 文件不检索,但可被查询链接; 属性值为"nofollow": 文件不被检索,但可查询页上的链接。 最大化窗口?<script language="JavaScript"> self.moveTo(0,0) self.resizeTo(screen.availWidth,screen.availHeight) </script>页面自动刷新(说明)当你做网页时,是不是有的时候想让你的网页自动不停刷新,或者过一段时间自动跳转到另外一个你自己设定的页面?其实实现这个效果非常地简单,而且这个效果甚至不能称之为特效。你只要把如下代码加入你的网页中就可以了。1,页面自动刷新把如下代码加入<head>区域中<meta http-equiv="refresh" content="20">,其中20指每隔20秒刷新一次页面.<meta http-equiv="refresh" content="20">2,页面自动跳转把如下代码加入<head>区域中<meta http-equiv="refresh" content="20;url=https://www.baidu.com">,其中20是指隔20秒后跳转到 https://www.baidu.com页面。<meta http-equiv="refresh" content="20;url=https://www.baidu.com">3、页面自动关闭5000是指5秒后弹出窗口自动关闭<body onLoad="setTimeout(window.close, 5000)">
2022年09月18日
29 阅读
0 评论
0 点赞
2022-07-25
php 网站的多语言设置(IP地址区分国内国外)
序:本来,以为做一个语言切换的按钮就可以一直安枕无忧了,突然上头说要来一个区分国内与国外的,在国内访问的显示中文,在国外访问的显示英文。好吧!找一下资料看看。tp5,需求是按钮切换的,选择参考3;方法一:IP查询网在线API申请,有1000次免费的;代码示例,有各大主流的语言写法 代码实现( 参考3有完整的)// 多语言 将 think_val 自定义设置为 customLang function lang(){ $lang = input('lang'); if(!$lang){ // 默认:think_var if(Cookie::has("customLang")){ $lang = Cookie::get("customLang"); }else{ // $lang = "zh-cn"; $ip = request() -> ip(0,true); $datatype = 'json'; $url = 'https://api.ip138.com/ip/?ip='.$ip.'&datatype='.$datatype; $header = array('token:你的token'); $obj = $this-> getRequest($url,$header); $objson = json_decode($obj,true); $isAddress = $objson['data'][0]; if($isAddress == "中国"){ $lang = "zh-cn"; }else{ $lang = "en-us"; } } } $lang = Lang::range($lang);//设定当前语言 Lang::load(THINK_PATH.'lang'.DS.$lang.EXT,$lang);//加载当前语言包 Cookie::set('customLang',$lang); return $lang; }方法二:maxmind GeoIP :先使用邮箱注册一个账号,并登录账号,创建一个密钥,名称就像一个备注,它会生成一个ID和密钥,记得保存好,它只显示一次;1、登录后就可以下载一个数据包了,数据包是每周二更新的:2、获取代码支持:查看1)、安装:composer require geoip2/geoip2:~2.02)、下载数据库,需要注册邮箱账号登录:https://www.maxmind.com/en/accounts/722009/geoip/downloads3)、代码实现:use GeoIp2\Database\Reader;function lang(){ $lang = input('lang'); if(!$lang){ if(Cookie::has("lenze_lang")){ $lang = Cookie::get("lenze_lang"); }else{ $ip = request() -> ip(0,true); // maxmind 获取判断 $reader = new Reader('./public/static/GeoLite2-Country.mmdb'); // 解压的数据包 $record = $reader->country($ip); $isAddress = $record->country->isoCode; if($isAddress == "cn" || $isAddress == "CN"){ $lang = "zh-cn"; }else{ $lang = "en-us"; } } } $lang = Lang::range($lang);//设定当前语言 Lang::load(THINK_PATH.'lang'.DS.$lang.EXT,$lang);//加载当前语言包 Cookie::set('lenze_lang',$lang); return json(['lang'=>$lang]); }方法三:可以使用高德地图的IP定位,不过,高德需要搜索的IP地址(仅支持国内),刚好,搜索得到的就判断为国内,搜索数据为空的为国外。IP定位-API文档-开发指南-Web服务 API | 高德地图API这个有开发文档了,就不用我多唠叨和做无用功了,嘎嘎!参考:1、最新多种方式, 判断客户端IP是国内还是国外?_解忧杂货铺Q的博客-CSDN博客_判断ip是国内还是国外2、tp5多语言切换_红卡的博客-CSDN博客_tp5多语言3、tp5 实现多语言切换的一个小bug_逸曦穆泽的博客-CSDN博客_tp 多语言
2022年07月25日
63 阅读
0 评论
0 点赞
1
2
...
5