使用uView UI+UniApp开发微信小程序--判断用户是否登录并跳转
<p>在《使用uView UI+UniApp开发微信小程序》的随笔中,介绍了基于uView UI+UniApp开发微信小程序的一些基础知识和准备工作,其中也大概介绍了一下基本的登录过程,本篇随笔详细介绍一下微信小程序的相关登录处理以及登录后设置用户身份信息,并跳转到相应页面的处理过程。</p><h3>1、令牌判断和登录方式</h3>
<p> 在之前介绍过,在业务系统中,我们需要根据登录用户的身份获取对应的数据,如果用户没有登录,这些信息是无法获到的,那么我们可以在app.vue中判断用户是否登录,然后跳转到对应的页面,如下所示。</p>
<p><img src="https://img2020.cnblogs.com/blog/8867/202109/8867-20210903095816289-1849088460.png" alt="" width="285" height="425" loading="lazy"></p>
<p>也就是系统启动的进入的时候,我们需要对系统用户的身份做一次判断,判断token是否存在,并且是否有效(因为token是有时效的)。</p>
<p>我们先来介绍下如何如何判断token是否存在的处理过程,由于token本身在登陆的时候,设置了存储,因此只需要通过uView的token获取操作即可读取出来,并进行判断即可。</p>
<p>我们只需要简单判断this.vuex_token 是否存在值即可,因为this.vuex_token是由于uView在加载Mixin的时候,已经自动映射了存储的键值,因此我们可以通过this.vuex_token访问到对应的值。</p>
<p>如果是简单的判断,我们在app.vue的如下代码即可处理</p>
<div class="cnblogs_code">
<pre><script><span style="color: rgba(0, 0, 0, 1)">
export </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)"> {
globalData: {
username: </span><span style="color: rgba(128, 0, 0, 1)">''</span><span style="color: rgba(0, 0, 0, 1)">
},
onLaunch() {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">如果用户没有登录或令牌失效,跳转到登录界面
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> console.log(this.vuex_token)</span>
<span style="color: rgba(0, 0, 255, 1)">if</span>(!<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.vuex_token) {
</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.$u.route({
url: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">pages/template/login/password</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
});
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
uni.switchTab({
url: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">/pages/example/myinfo</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
});
}
},
}
</span></script></pre>
</div>
<p>不过我们需要通过判断它的时限有效性,那么通过判断失效日期进行处理,如下代码所示。</p>
<div class="cnblogs_code">
<pre><script><span style="color: rgba(0, 0, 0, 1)">
export </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)"> {
onLaunch() {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">如果用户没有登录或令牌失效,跳转到登录界面
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> console.log(this.vuex_token)</span>
console.log(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.$u.http.config.baseUrl)
</span><span style="color: rgba(0, 0, 255, 1)">var</span> authed = <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.checkToken()
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(!<span style="color: rgba(0, 0, 0, 1)">authed) {
</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.$u.route({
url: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">/pages/task/login/index</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
});
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.$u.route({
type:</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">tab</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
url: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">pages/task/login/myinfo</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
})
}
},
methods: {
checkToken() {
</span><strong><span style="font-size: 15px"><span style="color: rgba(0, 0, 255, 1)">if</span>(<span style="color: rgba(0, 0, 255, 1)">this</span>.vuex_token && <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.vuex_user) {
</span><span style="color: rgba(0, 0, 255, 1)">var</span> expired = <span style="color: rgba(0, 0, 255, 1)">new</span> Date(<span style="color: rgba(0, 0, 255, 1)">this</span>.vuex_user.expired) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">token过期时间</span>
<span style="color: rgba(0, 0, 255, 1)">const</span> now =<span style="color: rgba(0, 0, 0, 1)"> Date.now()
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(expired - now > <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">
}
}
</span></span></strong><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">
}
}
}
</span></script></pre>
</div>
<p>其中expired是我们获取到token的时间,并加上token的失效时间的。</p>
<p>我们这里使用了一个checkToken的函数,用来判断是否正常登录并且有效的,如果令牌无效,那么跳转到登陆界面,否则直接跳到个人信息页面下。</p>
<p>我们先来看看登录界面,我们这里提供了几种登录方式,账号密码登录、短信验证码登陆、微信授权登录几种方式。</p>
<p><img src="https://img2020.cnblogs.com/blog/8867/202109/8867-20210923101941561-1475193238.png" alt="" width="294" height="439" loading="lazy"></p>
<p><img src="https://img2020.cnblogs.com/blog/8867/202109/8867-20210923102026891-52525017.png" alt="" width="293" height="353" loading="lazy"></p>
<p><img src="https://img2020.cnblogs.com/blog/8867/202109/8867-20210923102103712-371025353.png" alt="" width="289" height="399" loading="lazy"></p>
<p>这几种不同的登录方式,都是在验证成功后,需要获取用户的身份信息,并设置到Storage存储中去,逻辑上有相同之处。</p>
<p> </p>
<h3>2、登录的处理逻辑</h3>
<p>用户登录的时候,需要输入用户名,密码,构建相关的参数后,进行登录处理,处理过程代码如下所示。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">submit() {
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.$refs.uForm.validate(valid =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (valid) {
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.$u.api.User.login(<span style="color: rgba(0, 0, 255, 1)">this</span>.model).then(data =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 登陆成功跳转到Tab页面 </span>
<span style="color: rgba(0, 0, 0, 1)"> uni.switchTab({
url: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">/pages/task/login/myinfo</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
});
});
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
console.log(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">验证失败</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
}
});
},</span></pre>
</div>
<p>其中 this.$u.api.User 是用户API接口的统一调用方式,其中http.api.js的代码如下所示。</p>
<div class="cnblogs_code">
<pre>import User <span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">../api/user.js</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
import Task </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">../api/task.js</span><span style="color: rgba(128, 0, 0, 1)">'</span>
<span style="color: rgba(0, 0, 255, 1)">const</span> install = (Vue, vm) =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 将各个定义的接口名称,统一放进对象挂载到vm.$u.api(因为vm就是this,也即this.$u.api)下</span>
vm.$u.api = { <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 将 vm 对象传递到模块中</span>
<span style="color: rgba(0, 0, 0, 1)"> User: User(vm),
Task: Task(vm)
}
}
export </span><span style="color: rgba(0, 0, 255, 1)">default</span> { install }</pre>
</div>
<p>其中api/user.js里面定义了访问远程WebAPI的操作,同时也是我们封装一些处理逻辑的操作函数,我们可以通过ES6的Promise进行封装一个简单的登录函数。</p>
<p><img src="https://img2020.cnblogs.com/blog/8867/202109/8867-20210923110313657-1807350398.png" alt="" loading="lazy"></p>
<p>由于我们这里登录过程,除了用户名密码外,还需要appid、时间戳以及签名参数等信息组合,因此构建参数比较多一点。</p>
<p><img src="https://img2020.cnblogs.com/blog/8867/202109/8867-20210923103159988-480906510.png" alt="" loading="lazy"></p>
<p>登录成功后,我们就调用resolve的执行即可,如果失败,调用reject的处理。</p>
<p><img src="https://img2020.cnblogs.com/blog/8867/202109/8867-20210923103501026-1397352631.png" alt="" loading="lazy"></p>
<p> 这样我们就可以直接通过Promise的操作处理登录成功后的操作了,如下代码所示。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">this</span>.$u.api.User.login(<span style="color: rgba(0, 0, 255, 1)">this</span>.model).then(data =><span style="color: rgba(0, 0, 0, 1)"> {
uni.switchTab({
url: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">/pages/task/login/myinfo</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
});
});</span></pre>
</div>
<p>而其中setUserToken函数,主要是便于重用的目的抽取出来,因为设置令牌和用户信息,是其他两个登录方式(短信登陆、微信登陆)所通用的操作。</p>
<p><img src="https://img2020.cnblogs.com/blog/8867/202109/8867-20210923103848185-1301316741.png" alt="" loading="lazy"></p>
<p> 短信验证码的登录方式也是类似,需要后端配合判断短信的有效性即可,前端先调用后端的发送短信操作,如下代码所示</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">发送短信验证码</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> <span style="color: rgba(0, 0, 255, 1)">params</span> = { PhoneNumber: <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.tel }
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.$u.api.User.<strong><span style="font-size: 15px">SendPhoneLoginSmsCode</span></strong>(<span style="color: rgba(0, 0, 255, 1)">params</span>).then(res =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">(res.success) {
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.$u.toast(`验证码已发送至手机 ${<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.tel},请注意查收!`)
</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.$u.route({
url: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">pages/task/login/code</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 0, 255, 1)">params</span><span style="color: rgba(0, 0, 0, 1)">: {
mobile:</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.tel
}
});
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.$u.toast(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">发送出现错误:</span><span style="color: rgba(128, 0, 0, 1)">'</span> +<span style="color: rgba(0, 0, 0, 1)"> res.errorMessage)
}
})</span></pre>
</div>
<p>而前端调用的函数也就是在api/user.js中简单封装一下对API的调用即可。</p>
<div class="cnblogs_code">
<pre>SendPhoneLoginSmsCode(data) { <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 发送登录动态码</span>
<span style="color: rgba(0, 0, 255, 1)">return</span> vm.$u.post('/api/framework/User/SendPhoneLoginSmsCode'<span style="color: rgba(0, 0, 0, 1)">, data)
},</span></pre>
</div>
<p>发送的后端代码如下所示,主要就是放在缓存中一段时间供验证即可。</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">/ <summary></span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">/ 发送登录动态码</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">/ </summary></span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">/ <returns></returns></span>
<span style="color: rgba(0, 0, 0, 1)">
public CommonResult SendPhoneLoginSmsCode(PhoneCaptchaModel model)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">获取随机6位数字动态验证码</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> code = RandomChinese.GetRandomNumber(6<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">使用自定义模板处理短信发送</span>
string message =<span style="color: rgba(0, 0, 0, 1)"> string.Format(ConfigData.MySmsCodeTemplate, code);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> smsSender = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> MySmsSender();
</span><span style="color: rgba(0, 0, 255, 1)">var</span> result =<span style="color: rgba(0, 0, 0, 1)"> smsSender.Send(model.PhoneNumber, message);
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (result.Success)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> cacheKey = model.PhoneNumber;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">以手机号码作为键存储验证码缓存</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> cacheItem = <span style="color: rgba(0, 0, 255, 1)">new</span> SmsLoginCodeCacheItem { Code = code, PhoneNumber =<span style="color: rgba(0, 0, 0, 1)"> model.PhoneNumber };
</span><span style="color: rgba(0, 0, 255, 1)">var</span> cache = CacheManagerHelper.GetCacheItem(cacheKey, () =><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> cacheItem;
}, TimeSpan.FromMinutes(ConfigData.SmsCodeExpiredMinutes));</span>
<span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> result;
}</span></pre>
</div>
<p>验证的时候,只需要判断缓存里面是否存在记录和对应验证码是否匹配即可,如果顺利通过,那么构建用户的token信息返回给前端就是。</p>
<p><img src="https://img2020.cnblogs.com/blog/8867/202109/8867-20210923104742476-1111189260.png" alt="" loading="lazy"></p>
<p> 如果前端发送验证码成功,那么登陆界面跳转到等待输入验证码的界面,如下所示。</p>
<p><img src="https://img2020.cnblogs.com/blog/8867/202109/8867-20210923104906098-365298176.png" alt="" width="284" height="368" loading="lazy"></p>
<p> 输入正确的验证码即可顺利登陆,否则过期则要求重新发送短信验证码。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">submit() {
</span><span style="color: rgba(0, 0, 255, 1)">var</span> params = { mobile: <span style="color: rgba(0, 0, 255, 1)">this</span>.mobile, smscode: <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.smscode };
console.log(params);
</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.$u.api.User.dynamiclogin(params)
.then(res </span>=><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.$u.toast('验证成功'<span style="color: rgba(0, 0, 0, 1)">);
uni.switchTab({
url: </span>'/pages/task/login/myinfo'<span style="color: rgba(0, 0, 0, 1)">
});
})
.</span><span style="color: rgba(0, 0, 255, 1)">catch</span>(error =><span style="color: rgba(0, 0, 0, 1)"> {
console.log(</span>'验证失败' +<span style="color: rgba(0, 0, 0, 1)"> error);
</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.$u.toast(error);
});
},</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/8867/202109/8867-20210923105055309-194951807.png" alt="" width="291" height="508" loading="lazy"></p>
<p>如果顺利登陆,则跳转到我的页面里面去,展示一些常用的信息汇总,以及常见处理操作。</p>
<p><img src="https://img2020.cnblogs.com/blog/8867/202109/8867-20210923105228099-49035726.png" alt="" width="315" height="584" loading="lazy"></p>
<p> </p>
<p> 这里面涉及一个退出登录的操作,主要就是注销当前用户的身份,只要清空身份信息,并跳转到登录首页即可。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">logout() {
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.$u.vuex('vuex_token', <span style="color: rgba(0, 0, 255, 1)">null</span>) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 重置</span>
<span style="color: rgba(0, 0, 255, 1)">this</span>.$u.vuex('vuex_user', <span style="color: rgba(0, 0, 255, 1)">null</span>) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 重置</span>
<span style="color: rgba(0, 0, 255, 1)">this</span>.$u.toast('退出成功,请重新登录!'<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.second = 0<span style="color: rgba(0, 0, 0, 1)">
setTimeout(()</span>=><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.$u.route({ url: '/pages/task/login/index'<span style="color: rgba(0, 0, 0, 1)"> })
}, </span>1500<span style="color: rgba(0, 0, 0, 1)">)
},</span></pre>
</div>
<p>鉴于篇幅原因,先介绍到这里,关于微信授权登陆及绑定的操作过程,后续再介绍。</p>
</div>
<div id="MySignature" role="contentinfo">
<div style="border-right-color: #cccccc; border-right-width: 1px; border-right-style: solid; padding-right: 5px; border-top-color: #cccccc; border-top-width: 1px; border-top-style: solid; padding-left: 4px; font-size: 13px; padding-bottom: 4px; border-left-color: #cccccc; border-left-width: 1px; border-left-style: solid; width: 98%; padding-top: 4px; border-bottom-color: #cccccc; border-bottom-width: 1px; border-bottom-style: solid; background-color: #eeeeee;">
<img src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align="top" alt>
<span style="color: #000000"><span class="Apple-tab-span" style="white-space: pre"></span>
专注于代码生成工具、.Net/Python 框架架构及软件开发,以及各种Vue.js的前端技术应用。著有Winform开发框架/混合式开发框架、微信开发框架、Bootstrap开发框架、ABP开发框架、SqlSugar开发框架、Python开发框架等框架产品。
<br> 转载请注明出处:撰写人:伍华聪 http://www.iqidi.com <br> </span></div><br><br>
来源:https://www.cnblogs.com/wuhuacong/p/15323217.html
頁:
[1]