登录  /  注册

汇总小程序开发中遇到的问题

coldplay.xixi
发布: 2021-04-06 11:24:59
转载
1963人浏览过

汇总小程序开发中遇到的问题

小程序面试题

1、bindtap和catchtap的区别是什么?

bind事件绑定不会阻止冒泡事件向上冒泡,catch事件绑定可以阻止冒泡事件向上冒泡

2、Js数组转成换字符串,强制转换成整数及转换成浮点数的函数分别是什么?

js提供了parseInt()和parseFloat()两个转换函数。前者把值转换成整数,后者把值转换成浮点数。只有对String类型调用这些方法,这两个函数才能正确运行;对其他类型返回的都是NaN(Not a Number)。

相关免费学习推荐:微信小程序开发

1、转换函数:

在 判断字符串是否是数字值前,parseInt()和parseFloat()都会仔细分析该字符串。parseInt()方法首先查看位置0处的 字符,判断它是否是个有效数字;如果不是,该方法将返回NaN,不再继续执行其他操作。但如果该字符是有效数字,该方法将查看位置1处的字符,进行同样的 测试。这一过程将持续到发现非有效数字的字符为止,此时parseInt()将把该字符之前的字符串转换成数字。

parseInt("1234blue"); //returns 1234 
parseInt("0xA"); //returns 10 
parseInt("22.5"); //returns 22 
parseInt("blue"); //returns NaN
登录后复制

2. 强制类型转换

还可使用强制类型转换(type casting)处理转换值的类型。使用强制类型转换可以访问特定的值,即使它是另一种类型的。
ECMAScript中可用的3种强制类型转换如下:
Boolean(value)——把给定的值转换成Boolean型;
Number(value)——把给定的值转换成数字(可以是整数或浮点数);
String(value)——把给定的值转换成字符串。

3. 利用js变量弱类型转换

举个小例子,一看,就会明白了。

<script> 
var str= &#39;012.345 &#39;; 
var x = str-0; 
x = x*1;
</script>
登录后复制

上例利用了js的弱类型的特点,只进行了算术运算,实现了字符串到数字的类型转换,不过这个方法还是不推荐的。

3、简单描述下微信小程序的相关文件类型

小程序:pages ——index:index.js(页面逻辑) /index.wxml (页面结构)/index.wxss (页面样式表) / index.json (页面配置)

App.js 小程序逻辑

App.json 小程序公共设置

App.wxss 小程序公共样式表

4、小程序有哪些参数传值的方法?

1、设置id的方法标识跳转后传递的参数值;

2、通过使用data - xxxx 的方法来标识要传递的值

微信小程序设置id的方法标识来传值

在要跳转的item处,设置一个id并给当前的id赋值上对应的key值,比如一部电影的id(后面带着id去下一个页面查询,详细信息)如:

后我们在js的bindtap的响应事件中获取,并传递到下一个界面中;

获取到id传的值

通过e.currentTarget.id;获取设置的id值,并通过设置全局对象的方式来传递数值,

获取全局对象 var app=getApp(); //设置全局的请求访问传递的参数 app.requestDetailid=id;

提示:其实我们也可以在,wxml中查看到我们设置的每一个item的id值

通过使用data - xxxx 的方法标识来传值

通过使用data - xxxx 的方法标识来传值,xxxx可以自定义取名 比如data-key等等都可以。

如何获取data-xxxx传递的值?

在js的bindtap的响应事件中:

通过数据解析一层层找到数据,var id=e.target.dataset.id(根据你的data-id的取名)

微信小程序如何跨页面获取值?

依据上面的方式设置要传递的值,页面跳转后,我们就需要在下一个页面拿到传递的数据(这个数据在传递前,就已经被设置成全局变量

在跳转后的js页面,接收传递过来的数据detail.js

同样通过全局额方式取值出来,(即和app.js中取某个变量的值是一样的)

var movieid=getApp().MovieDetailid;
console.log(movieid);

5、简述下wx.navigateTo(), wx.redirectTo(), wx.switchTab(), wx.navigateBack(), wx.reLaunch()的区别?

微信小程序 跳转页面

小程序页面有2种跳转,可以在wxml页面或者js中:

1,在wxml页面中:

<navigator url="../index/index">跳转到新页面</navigator>
<navigator url="../index/index" open-type="redirect">在当前页打开</navigator>
<navigator url="../index/index" open-type="switchTab">切换到首页Tab</navigator>
登录后复制

2,在js页面中:

【注意】此处注意两个关键词 “应用内的页面” 和 “tabBar页面”。 app.json文件中tabBar中注册过的tab页,即为“tabBar页面”,非tabBar中注册占用的页面即为“应用内的页面” 。 如下图:home页面为“应用内的页面”,index和logs页面则为 “tabBar页面”。

3,如果上述跳转遇到跳转失败或无效的问题,请访问下面链接:

wx.navigateTo/wx.redirectTo 无效

6、如果需要用户授权,用户选择拒绝授权,此时应该如何处理?

在微信小程序开发时,当我们调用API wx.getUserInfo(OBJECT) 时,需要用户授权。但如果用户拒绝授权,我们如何兼容用户拒绝授权状态,拥有更好的用户体验呢?

先看看这个接口的官方文档:

wx.getUserInfo(OBJECT)

获取用户信息,需要先调用 wx.login 接口。

OBJECT参数说明:

参数名

类型

必填

说明

withCredentials

Boolean

是否带上登录态信息

success

Function

接口调用成功的回调函数

fail

Function

接口调用失败的回调函数

complete

Function

接口调用结束的回调函数(调用成功、失败都会执行)

1. tip: wx.getUserInfo 接口需要用户授权,请兼容用户拒绝授权的场景。

我们就是要在用户点击拒绝的时候,弹出提示框,提示用户以提升用户体验。像下面这样的。

用具体代码实现就是,将弹窗写在 wx.getUserInfo 的fail回调函数中,像下面这样:

wx.getUserInfo({
success: function (resuser) {
console.log(success)
},
fail: function () {// 调用微信弹窗接口
wx.showModal({
title: &#39;警告&#39;,
content: &#39;您点击了拒绝授权,将无法正常使用******的功能体验。请10分钟后再次点击授权,或者删除小程序重新进入。&#39;,
success: function (res) {
if (res.confirm) {
console.log(&#39;用户点击确定&#39;)
}
}
})
}
})
登录后复制

这样用户就获得了提示信息,但此时,用户还是停留在页面的,如果某些展示信息,还是给要给用户展示的,只是在进行某些操作的时候要对授权进行验证的话,那就得继续修改我们的代码,保存用户的登录态,在其他地方做验证使用。

第一种思路:

保存登录态这里是这样的,将用户的登录信息传给后台,后台保存用户信息,同时用 open_id 在后台换取一个SessionId 用换取的这个SessionId 存在缓存,做为登录态验证。

wx.getUserInfo({
success: function (resuser) {
let userInfo = resuser.userInfo
that.healthApi.login(code, userInfo).then(logindata => {   // 这里将微信的请求封装成Promiese 风格
if (logindata.code === 0) {
var sessionId = logindata.data// 调用微信wechat.setStorage将换回来的 SessionId 存在本地缓存
that.wechat.setStorage(&#39;sessionId&#39;, sessionId).then(() => {
that.globalData.userInfo = userInfo
typeof cb == "function" && cb(that.globalData.userInfo)
})
}
})
},
fail: function () {
wx.showModal({
title: &#39;警告&#39;,
content: &#39;您点击了拒绝授权,将无法正常使用*****的功能体验。请10分钟后再次点击授权,或者删除小程序重新进入。&#39;,
success: function (res) {
if (res.confirm) {
console.log(&#39;用户点击确定&#39;)
}
}
})
}
})
登录后复制

这样我们将登录态保存在了 SessionId 中,在每次登录的时候我们只需要再调用一个 检查 SessionId的接口就行,检查不通过再调微信登录接口。此处不做延伸了。

第二种思路:

在3.29微信小程序更新的版本中,加入了这样一条属性

withCredentials 字段基础库版本 1.1.0 开始支持,低版本需做兼容处理

这个字段的意思就是调用 wx.getUserInfo(OBJECT) 是否带上 登录态 的信息。

官方文档是这样解释的:

withCredentials 字段基础库版本 1.1.0 开始支持,低版本需做兼容处理

注:当 withCredentials 为 true 时,要求此前有调用过 wx.login 且登录态尚未过期,此时返回的数据会包含 encryptedData, iv 等敏感信息;当 withCredentials 为 false 时,不要求有登录态,返回的数据不包含 encryptedData, iv 等敏感信息。

success返回参数说明:

参数

类型

说明

userInfo

OBJECT

用户信息对象,不包含 openid 等敏感信息

rawData

String

不包括敏感信息的原始数据字符串,用于计算签名。

signature

String

使用 sha1( rawData + sessionkey ) 得到字符串,用于校验用户信息。

encryptedData

String

包括敏感数据在内的完整用户信息的加密数据,详细见加密数据解密算法

iv

String

加密算法的初始向量,详细见加密数据解密算法

注:需要兼容微信低版本,向后兼容

那么利用这个接口,我们可以直接拿到 登录状态,在其他需要验证登录的地方进行提示,而在不需要授权的地方还可以让用户浏览小程序。

回到前面的问题,在用户点击拒绝授权后,在某些操作时需要验证用户是否授权过,弹出交互信息,那么就利用上面的 SessionId或者 withCredentials 登录态进行下面的操作:

applyIn: function applyIn() {
if (wx.getStorageSync(&#39;sessionId&#39;)) {  // 根据储存的sessionId 进行验证
wx.navigateTo({
url: &#39;familyDoctorApply/familyDoctorApply?Oid=&#39; + this.data.params.Oid + &#39;&title=&#39; + this.data.params.title + &#39;&serviceCity=&#39; + this.data.array[this.data.index].name + &#39;&productPrice=&#39; + this.data.product.productPrice
});
} else {
wx.showModal({
title: &#39;警告&#39;,
content: &#39;您点击了拒绝授权,无法使用此功能。&#39;,
success: function (res) {
if (res.confirm) {
console.log(&#39;用户点击确定&#39;)
}
}
})
}
登录后复制

效果像这样:

这样一个简单完整的登录及授权,登录态保存等前端微信小程序解决方案就完成了,还可以继续扩展到登录有效期,退出登录,用户权限等跟多扩展的地方。

7、你平时封装可以复用的方法吗?你会把可以复用的方法写在哪个文件里?

其实可以模拟一些框架的,比如bootsrap,写个demo出来,抽出css和js,js最好抽象成对象(构造函数)或者是带参数的方法,然后你只要声明对像,或者参数指定某个class或id,就可以了

写在html文件里有什么优点吗?
独立出来会有什么问题吗?尤其是载入页面的时候,应该会多发很多http请求吧,会不会造成加载变慢?

8、分析下小程序的优劣势?

小程序是在微信生态发展过程中新出现的一种应用形态,小程序的小,从某种程度上已经说明了它的体量不会很大,但是可以实现一些功能相对简单、交互相对简单的服务需求,同时解决了App长期以来多平台适配、多应用市场分发、开发成本居高不下等诸多方面的问题。所以小程序【密件】依靠微信平台和自身“阅后即焚”的功能,获得众多年轻人的好评

优势:

1)容易上手,只要之前有HTML+CSS+JS基础知识,写小程序基本上没有大问题;当然如果了解ES6+CSS3则完全可以编写出即精简又动感的小程序;

2)基本上不需要考虑兼容性问题,只要微信可以正常运行的机器,就可以运行小程序;

3)基本组件库已经比较齐全:Toast,Loading框,Picker,定位及地图,Image,Input,Checkbox,Text,TextArea,ScrollView等常用的组件都有,而且使用也挺简单、方便;

4)发布、审核高效,基本上上午发布审核,下午就审核通过,升级简单,而且支持灰度发布;

5 ) 微信官方提供使用人数、频率等数据统计,小程序js脚本执行错误日志;

6)开发文档比较完善,开发社区比较活跃;

7)最近刚开放的牛x功能,新增webview组件,可以展示网页啦,这个比较爽;

8)支持插件式开发,一些基本功能可以开发成插件,供多个小程序调用;

劣势:

1)后台调试麻烦,因为API接口必须https请求,且公网地址,也就是说后台代码必须发布到远程服务器上;当然我们可以修改host进行dns映射把远程服务器转到本地,或者开启tomcat远程调试;不管怎么说终归调试比较麻烦。

2)前台测试有诸多坑,最头疼莫过于模拟器与真机显示不一致(之前碰到一个案例,后续单独讲解)

3)真机测试,个别功能安卓和苹果表现迥异,我们的小程序里有很多页面有定位功能,模拟器和iphone定位瞬间完成,然而安卓手机就蛋疼了,老显示“定位中...”要很久才能定位好。后来没办法只能优化,减少定位次数。

4)native组件,展示很不好,比如textarea,不能在滚动页面出现,而且至于顶层,经常其它组件会被它遮挡,点击其它组件时,就进入textarea输入框;画布组件也是如此;

5)页面跳转深度不能超过5个页面,这个比较麻烦,有些复杂的页面跳转没法实现,不过太复杂的话也有悖小程序简单易用的原则啦;

6)小程序升级问题,官方文档说会自动更新,实际情况往往是要先把原来的小程序删除掉,重新搜索添加,才能加载最新版本;

7)页面渲染稳定性有待提高,已经好几次出现部分用户的页面显示异常,整个页面被放大了好几倍,先删除原来小程序再添加回来,如此重复好几次,才能显示正常;

8)js引用只能使用绝对路径,很蛋疼;基于安全性及MINA框架实现原理,小程序中对js使用做了很多限制,不能使用:new Function,eval,Generator,不能操作cookie,不能操作DOM;

9)开发工具bug比较多且效率比较低,三天两头升级,解决老问题的同时又出现问题;文件查找、资源定位、代码编辑较eclipse有一定差距。经常出现把a.js当做b.js来修改

9、设置值到页面暂存区(即data)里面的方法有几种?分别是什么?有什么区别?

1. 使用QueryString变量
QueryString是一种非常简单的传值方式,他可以将传送的值显示在浏览器的地址栏中。如果是传递一个或多个安全性要求不高或是结构简单的数值时,可以使用这个方法。但是对于传递数组或对象的话,就不能用这个方法了。下面是一个例子:
a.aspx的C#代码

private void Button1_Click(object sender, System.EventArgs e) 
{ 
 string s_url; 
 s_url = "b.aspx?name=" + Label1.Text; 
 Response.Redirect(s_url); 
} 
b.aspx中C#代码 
private void Page_Load(object sender, EventArgs e) 
{ 
 Label2.Text = Request.QueryString["name"]; 
}
登录后复制

2. 使用Application 对象变量
Application对象的作用范围是整个全局,也就是说对所有用户都有效。其常用的方法用Lock和UnLock。
a.aspx的C#代码

private void Button1_Click(object sender, System.EventArgs e) 
{ 
 Application["name"] = Label1.Text; 
 Server.Transfer("b.aspx"); 
} 
b.aspx中C#代码 
private void Page_Load(object sender, EventArgs e) 
{ 
 string name; 
 Application.Lock(); 
 name = Application["name"].ToString(); 
 Application.UnLock(); 
}
登录后复制

3. 使用Session变量
想必这个肯定是大家使用中最常见的用法了,其操作与Application类似,作用于用户个人,所以,过量的存储会导致服务器内存资源的耗尽。
a.aspx的C#代码

private void Button1_Click(object sender, System.EventArgs e) 
{ 
 Session["name"] = Label.Text; 
} 
b.aspx中C#代码 
private void Page_Load(object sender, EventArgs e) 
{ 
 string name; 
 name = Session["name"].ToString(); 
}
登录后复制

4. 使用Cookie对象变量
这个也是大家常使用的方法,与Session一样,其是什对每一个用户而言的,但是有个本质的区别,即Cookie是存放在客户端的,而session是存放在服务器端的。而且Cookie的使用要配合ASP.NET内置对象Request来使用。
a.aspx的C#代码

private void Button1_Click(object sender, System.EventArgs e) 
{ 
 HttpCookie cookie_name = new HttpCookie("name"); 
 cookie_name.Value = Label1.Text; 
 Reponse.AppendCookie(cookie_name); 
 Server.Transfer("b.aspx"); 
} 
b.aspx中C#代码 
private void Page_Load(object sender, EventArgs e) 
{ 
 string name; 
 name = Request.Cookie["name"].Value.ToString(); 
}
登录后复制

5. 使用Server.Transfer方法
这个才可以说是面象对象开发所使用的方法,其使用Server.Transfer方法把流程从当前页面引导到另一个页面中,新的页面使用前一个页面的应答流,所以这个方法是完全面象对象的,简洁有效。
a.aspx的C#代码

public string Name 
{ 
 get{ return Label1.Text;} 
} 
private void Button1_Click(object sender, System.EventArgs e) 
{ 
 Server.Transfer("b.aspx"); 
} 
b.aspx中C#代码 
private void Page_Load(object sender, EventArgs e) 
{ 
 a newWeb; //实例a窗体 
 newWeb = (source)Context.Handler; 
 string name; 
 name = newWeb.Name; 
}
登录后复制

微信小程序--data的赋值与取值

通过小程序官方文档可知:

Page() 函数用来注册一个页面。接受一个 object 参数,其指定页面的初始数据、生命周期函数、事件处理函数等。其中的参数data用来设置初始数据,WXML 中的动态数据均来自对应 Page 的 data。

所以如果页面需要显示动态数据必须要把数据更新到data中对应的变量中。

页面js文件中这么写:

Page({
  data: {
    message: &#39;Hello MINA!&#39;
  }
})
· wxml中这么写:
<view> {{ message }} </view>
登录后复制

如果该数据在操作过程中发生变化,需要将新数据重新绑定到该变量中,写法如下:

function setData(){
    var that = this;
    that.setData({
      message: &#39;新消息&#39;
    })
}
登录后复制

如果想在js文件中使用data中的数据写法如下:

function getData(){
    var that = this;
    console.log(that.data.message)
}
登录后复制

10、如何检测用户的微信版本是否支持某项功能?

第一期开放的接口,不是不能使用,而是无需检测,全部都是支持的。
只有后面最新开放的一些接口,才需要检测是否支持。
目前开放的所有接口:
onMenuShareTimeline
onMenuShareAppMessage
onMenuShareQQ
onMenuShareWeibo
onMenuShareQZone
startRecord
stopRecord
onVoiceRecordEnd
playVoice
pauseVoice
stopVoice
onVoicePlayEnd
uploadVoice
downloadVoice
chooseImage
previewImage
uploadImage
downloadImage
translateVoice
getNetworkType
openLocation
getLocation
hideOptionMenu
showOptionMenu
hideMenuItems
showMenuItems
hideAllNonBaseMenuItem
showAllNonBaseMenuItem
closeWindow
scanQRCode
chooseWXPay
openProductSpecificView
addCard
chooseCard
openCard

11、如何分包加载?分包加载的优势在哪?

分包加载的介绍
大部分小程序都会由某几个功能组成,通常这几个功能之间是独立的,但会依赖一些公共的逻辑,并且这些功能通常会对应某几个独立的页面。那么小程序代码的打包,大可不必一定要打成一个,可以按照功能的划分,拆分成几个分包,当需要用到某个功能时,才加载这个功能对应的分包。
对于用户来说,小程序加载流程变成了:
1.首次启动时,先下载小程序主包,显示主包内的页面;
2.如果用户进入了某个分包的页面,再下载这个对应分包,下载完毕后,显示分包的页面。
采用分包加载,对开发者而言,能使小程序有更大的代码体积,承载更多的功能与服务;而对用户而言,可以更快地打开小程序,同时在不影响启动速度前提下使用更多功能。
分包的划分
在配置前首先需要开发者规划下各个分包需要容纳的内容,我们建议开发者按照功能划分的的原则,将同一个功能下的页面和逻辑放置于同一个目录下,对于一些跨功能之间公共逻辑,将其放置于主包下,这样可以确保在分包引用这部分功能时,这部分的逻辑一定存在。
在分包划分时,应该注意以下事项:
1.避免分包与分包之间引用上的耦合。因为分包的加载是由用户操作触发的,并不能确保某分包加载时,另外一个分包就一定存在,这个时候可能会导致 JS 逻辑异常的情况,例如报「"xxx.js" is not defined」这样的错误;
2.一些公共用到的自定义组件,需要放在主包内。
分包的配置
当理清了分包的划分后,就可以进行分包的配置了,这一步并不复杂。

假设支持分包的小程序目录结构如下:

开发者通过在 app.json subPackages 字段声明项目分包结构:

分包加载的低版本兼容问题
微信 6.6.0 版本开始支持分包加载,而对于低于这个版本的客户端,我们做了兼容处理,开发者不需要对老版本微信客户端做兼容。对于老版本的客户端,编译后台会将所有的分包打包成一个整包,老版本的客户端依然按照整包的方式进行加载。
所以在老版本的微信客户端下,是依然采取整包加载的方式加载的,建议开发者尽量控制代码包的大小。
目前小程序分包大小的限制:
整个小程序所有分包大小不超过 4M
单个分包/主包大小不能超过 2M
随着时间推移,老版本覆盖率降低,我们会考虑进一步扩大代码包的大小。

12、在你开发小程序的过程中遇到过什么坑? 你是怎么解决的?

1.我们使用app.json文件来对微信小程序进行全局配置,决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 的时候在pages中写注释的时候回报错。
例如:

{
  "pages":[
      //这是首页面
    "pages/welcome/welcome"
  ]}
登录后复制

此时就会报错


2.在json文件中没有写内容的时候也要加一对大括号{ },不然的话也会报错

3. ①在开发微信小程序的时候,我们使用app.json文件来对微信小程序进行全局配置,决定页面文件的路径,窗口表现,设置网络超时时间,设置多Tab等。
以下是一个包含了所有配置选项的简单配置,app.json :

{
  //设置页面路径
  "pages": [
    "pages/index/index",
    "pages/logs/index"
  ],
  //设置默认页面的窗口表现
  "window": {
    "navigationBarTitleText": "Demo"
  },
  //设置底部 tab 的表现
  "tabBar": {
    "list": [{
      "pagePath": "pages/index/index",
      "text": "首页"
    }, {
      "pagePath": "pages/logs/logs",
      "text": "日志"
    }]
  },
  //设置网络超时时间
  "networkTimeout": {
    "request": 10000,
    "downloadFile": 10000
  },
  //设置是否开启 debug 模式
  "debug": true
}
登录后复制

②但是在对页面json文件进行配置的时候只可以配置设置默认页面的窗口表现(即只能对window进行配置),但是在此时可以直接省略window,如果加window则没有效果,也不会报错。
以下是一个包含了window配置选项的简单配置,post.json :

注意:这是错误的写法

{
  "window":{
    "navigationBarBackgroundColor": "#ffffff",
    "navigationBarTextStyle": "black",
    "navigationBarTitleText": "微信接口功能演示",
    "backgroundColor": "#eeeeee",
    "backgroundTextStyle": "light"
  }}
登录后复制

注意:正确的写法

{
    "navigationBarBackgroundColor": "#ffffff",
    "navigationBarTextStyle": "black",
    "navigationBarTitleText": "微信接口功能演示",
    "backgroundColor": "#eeeeee",
    "backgroundTextStyle": "light"}
登录后复制

4.此前一直没有注意vertical-align: middle和height:40rpx;line-height:40rpx进行设置垂直剧中的区别,这次主要说一下vertical-align: middle
代码如下:

<view class="post-author-date">
    <image class="post-author" src="../../images/avatar/1.png">
    </image>
    <text class="post-date">Nov 15 2016</text>
</view>
 
.post-author{
    width: 60rpx;
    height: 60rpx;
    vertical-align: middle;
}
.post-date{
    margin-top: 5rpx;
    vertical-align: middle;
    /*height: 40rpx;
    line-height: 40rpx;*/
}
登录后复制

总结: 
①vertical-align: middle;把此元素放在父元素的中部 
②当一个父元素里面有多个子元素,需要把几个子元素水平对齐,并且每个子元素都垂直剧中的时候,对每一个子元素进行设置 vertical-align: middle 
③height: 40rpx; line-height: 40rpx;可以对文本进行垂直居中

相关学习推荐:小程序开发教程

以上就是汇总小程序开发中遇到的问题的详细内容,更多请关注php中文网其它相关文章!

智能AI问答
PHP中文网智能助手能迅速回答你的编程问题,提供实时的代码和解决方案,帮助你解决各种难题。不仅如此,它还能提供编程资源和学习指导,帮助你快速提升编程技能。无论你是初学者还是专业人士,AI智能助手都能成为你的可靠助手,助力你在编程领域取得更大的成就。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2024 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号