uni-app アプレット Laravel+jwt 権限認証シリーズ
uni-app 開発チュートリアル 列では、一連の権限認証方法を紹介します。
# 推奨: ##ユニアプリ開発チュートリアル
uni-app
laravel 5.7
jwt-auth 1.0.0
設計テーブル構造
- フロントエンドリクエストクラス
- 権限認証に関連する js パッケージには非対応リフレッシュ トークンが含まれています
- #laravel 認証ミドルウェアには非対応リフレッシュ トークンが含まれています ##ログインするための携帯電話番号を取得します
- 痛みのないリフレッシュ access_token のアイデア
- ミニ プログラムのログイン ステータスを確認する方法
- テーブル構造の設計
一般的な設計テーブルと違いはありませんが、マルチプラットフォーム ミニ プログラムの場合は、account_id を介してフェデレーテッド テーブルを関連付けます。 CREATE TABLE `users` (
`u_id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '账号id',
`u_username` varchar(15) NOT NULL DEFAULT '' COMMENT '手机号隐藏 ',
`u_nickname` varchar(15) NOT NULL COMMENT '分配用户名',
`u_headimg` varchar(200) DEFAULT NULL COMMENT '头像',
`u_province` varchar(50) DEFAULT NULL,
`u_city` varchar(50) DEFAULT NULL,
`u_platform` varchar(30) NOT NULL COMMENT '平台:小程序wx,bd等',
`u_mobile` char(11) NOT NULL COMMENT '手机号必须授权',
`u_openid` varchar(100) DEFAULT NULL COMMENT 'openid',
`u_regtime` timestamp NULL DEFAULT NULL COMMENT '注册时间',
`u_login_time` timestamp NULL DEFAULT NULL COMMENT '最后登陆时间',
`u_status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '0禁用1正常',
`account_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '平台联合id',
PRIMARY KEY (`u_id`),
KEY `platform` (`u_platform`,`u_mobile`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;
ログイン後にコピー
CREATE TABLE `users` ( `u_id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '账号id', `u_username` varchar(15) NOT NULL DEFAULT '' COMMENT '手机号隐藏 ', `u_nickname` varchar(15) NOT NULL COMMENT '分配用户名', `u_headimg` varchar(200) DEFAULT NULL COMMENT '头像', `u_province` varchar(50) DEFAULT NULL, `u_city` varchar(50) DEFAULT NULL, `u_platform` varchar(30) NOT NULL COMMENT '平台:小程序wx,bd等', `u_mobile` char(11) NOT NULL COMMENT '手机号必须授权', `u_openid` varchar(100) DEFAULT NULL COMMENT 'openid', `u_regtime` timestamp NULL DEFAULT NULL COMMENT '注册时间', `u_login_time` timestamp NULL DEFAULT NULL COMMENT '最后登陆时间', `u_status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '0禁用1正常', `account_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '平台联合id', PRIMARY KEY (`u_id`), KEY `platform` (`u_platform`,`u_mobile`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;
比較的優れたリクエスト クラス2. フロントエンド リクエスト クラス
luch-requestは、構成とインターセプターの動的な変更をサポートします。 、uni-app プラグイン マーケットで見つけることができます。
request.js を変更する必要はありません。カスタム ロジックは、index.js にあります。 index.js
import Request from './request';import jwt from '@/utils/auth/jwt.js'; // jwt 管理 见下文const http = new Request();const baseUrl = 'http://xxx'; // api 地址var platform = ''; // 登陆时需知道来自哪个平台的小程序用户// #ifdef MP-BAIDUplatform = 'MP-BAIDU';// #endif/* 设置全局配置 */http.setConfig((config) => { config.baseUrl = baseUrl; //设置 api 地址 config.header = { ...config.header } return config})/* 请求之前拦截器 */http.interceptor.request((config, cancel) => { if (!platform) {cancel('缺少平台参数');} config.header = { ...config.header, platform:platform } if (config.custom.auth) { // 需要权限认证的路由 需携带自定义参数 {custom: {auth: true}} config.header.Authorization = jwt.getAccessToken(); } return config})http.interceptor.response(async (response) => { /* 请求之后拦截器 */ console.log(response); // 如果是需要权限认证的路由 if(response.config.custom.auth){ if(response.data.code == 4011){ // 刷新 token jwt.setAccessToken(response.data.data.access_token); // 携带新 token 重新请求 let repeatRes = await http.request(response.config); if ( repeatRes ) { response = repeatRes; } } } return response}, (response) => { // 请求错误做点什么 if(response.statusCode == 401){ getApp().globalData.isLogin = false; uni.showToast({icon:'none',duration:2000,title: "请登录"}) }else if(response.statusCode == 403){ uni.showToast({ title: "您没有权限进行此项操作,请联系客服。", icon: "none" }); } return response})export { http}
グローバルマウント
import Vue from 'vue'import App from './App'import { http } from '@/utils/luch/index.js' //这里Vue.prototype.$http = http Vue.config.productionTip = falseApp.mpType = 'app'const app = new Vue({ ...App})app.$mount()
3. 権限認証に関連するJSのカプセル化
authorize.js
スペースの都合により、完全なコードは掲載されず、他のコードは使用されません。たとえば、uni.checkSession() は、アプレットのログイン状態を引き継ぐために jwt が使用されるため、このメソッドは現在使用されていません。
// #ifndef H5const loginCode = provider => { return new Promise((resolve, reject) => { uni.login({ provider: provider, success: function(loginRes) { if (loginRes && loginRes.code) { resolve(loginRes.code) } else { reject("获取code失败") } }, fail:function(){ reject("获取code失败")} }); })}// #endifexport { loginCode //登录获取code}
jwt.js
access_token の管理に特化しており、コードはあまり多くなく、ユーザー情報の管理も組み込まれています。 const tokenKey = 'accessToken';//键值const userKey = 'user'; // 用户信息// tokenconst getAccessToken = function(){
let token='';
try {token = 'Bearer '+ uni.getStorageSync(tokenKey);} catch (e) {}
return token;}const setAccessToken = (access_token) => {
try {uni.setStorageSync(tokenKey, access_token);return true;} catch (e) {return false;}}const clearAccessToken = function(){
try {uni.removeStorageSync(tokenKey);} catch (e) {}}// userinfoconst setUser = (user)=>{
try {uni.setStorageSync(userKey, user);return true;} catch (e) {return false;}}const getUser = function(){
try {return uni.getStorageSync(userKey)} catch (e) {return false;}}const clearUser = function(){
try {uni.removeStorageSync(userKey)} catch (e) {}}export default {
getAccessToken,setAccessToken,clearAccessToken,getUser,setUser,clearUser}
auth.js
ログインのみを処理します。他のファイルではなく別のファイルに置く理由は、どこでも使用されるためです
import {loginCode} from '@/utils/auth/authorize.js';import jwt from '@/utils/auth/jwt.js';import {http} from '@/utils/luch/index.js';const login=function(detail){ return new Promise((resolve, reject) => { loginCode().then(code=>{ detail.code = code; return http.post('/v1/auth/login',detail); }) .then(res=>{ jwt.setAccessToken(res.data.data.access_token); jwt.setUser(res.data.data.user); getApp().globalData.isLogin = true; resolve(res.data.data.user); }) .catch(err=>{ reject('登陆失败') }) })}export default {login}
4.laravel 認証ミドルウェア
ここで jwt-auth について少し説明します。 1. トークンの有効期限が切れてトークンが更新されると、元のトークンは「ブラックリスト」に登録され、無効になります。実際、jwt-auth はブラックリストを保存するファイルも維持しており、無効なトークンは更新時間制限に達した場合にのみクリアされます。たとえば、有効期限を 10 分、更新制限を 1 か月とすると、この期間中は大量のブラックリストが生成され、パフォーマンスに影響を与えるため、可能な限り調整してください。時間は 60 分、更新制限は 2 週間、または有効期限は 1 週間、更新制限は 1 か月であれば問題ありません。 2. 痛みのない更新ソリューションに関しては、トークンの有効期限が切れると、私が使用しているフロントエンドは更新を完了するために 2 つの要求を行いますが、ユーザーはそれを認識しません。インターネット上には自動更新とログインを直接要求するソリューションがありますが、使用しませんでした。理由はわかりません。それ以外はわかりません。ただし、さまざまな jwt 例外をコンパイルしたので、学生は必要に応じてカスタマイズできます。 TokenExpiredException 有効期限が切れた場合、TokenInvalidException はトークンを解析できない場合、UnauthorizedHttpException はトークンを保持しない場合、JWTException トークンが無効であるか更新制限に達する場合、または JWT 内部エラーが発生する場合。 <?phpnamespace App\Http\Middleware;use App\Library\Y;use Closure;use Exception;use Tymon\JWTAuth\Exceptions\JWTException;use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;use Tymon\JWTAuth\Exceptions\TokenExpiredException;class ApiAuth extends BaseMiddleware{
public function handle($request, Closure $next, $guard = 'api')
{
// 在排除名单中 比如登录
if($request->is(...$this->except)){
return $next($request);
}
try {
$this->checkForToken($request);// 是否携带令牌
if ( $this->auth->parseToken()->authenticate() ) {
return $next($request); //验证通过
}
}catch(Exception $e){
// 如果token 过期
if ($e instanceof TokenExpiredException) {
try{
// 尝试刷新 如果成功 返给前端 关于前端如何处理的 看前边 index.js
$token = $this->auth->refresh();
return Y::json(4011, $e->getMessage(),['access_token'=>$token]);
}catch(JWTException $e){
// 达到刷新时间上限
return Y::json(401, $e->getMessage());
}
}else{
// 其他各种 直接返回 401 状态码 不再细分
return Y::json(401, $e->getMessage());
}
}
}
protected $except = [
'v1/auth/login',
];}
ログイン後にコピー
<?phpnamespace App\Http\Middleware;use App\Library\Y;use Closure;use Exception;use Tymon\JWTAuth\Exceptions\JWTException;use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;use Tymon\JWTAuth\Exceptions\TokenExpiredException;class ApiAuth extends BaseMiddleware{ public function handle($request, Closure $next, $guard = 'api') { // 在排除名单中 比如登录 if($request->is(...$this->except)){ return $next($request); } try { $this->checkForToken($request);// 是否携带令牌 if ( $this->auth->parseToken()->authenticate() ) { return $next($request); //验证通过 } }catch(Exception $e){ // 如果token 过期 if ($e instanceof TokenExpiredException) { try{ // 尝试刷新 如果成功 返给前端 关于前端如何处理的 看前边 index.js $token = $this->auth->refresh(); return Y::json(4011, $e->getMessage(),['access_token'=>$token]); }catch(JWTException $e){ // 达到刷新时间上限 return Y::json(401, $e->getMessage()); } }else{ // 其他各种 直接返回 401 状态码 不再细分 return Y::json(401, $e->getMessage()); } } } protected $except = [ 'v1/auth/login', ];}
作者は、この種の更新を維持するのは非常に難しいと考えています。ワンタイム トークンを直接使用する方が良いでしょう。有効期限が切れたら再度ログインする方が良いでしょう。ミニ プログラムまたは Web サイトが強力なセキュリティを必要とするかどうかによって異なります。一般に、高度なセキュリティは必要ありません。https リクエストにはワンタイム トークンを使用することをお勧めします。ここでのミドルウェアは auth()->check()、true のみを必要としますはログイン状態を意味し、false はログインしていないことを意味します。
5. ログインするための携帯電話番号を取得します
<template> <view> <button>获取手机号</button> <button>获取用户数据</button> <button>清除用户数据</button> </view></template><script> import auth from '@/utils/auth/auth.js'; import jwt from '@/utils/auth/jwt.js'; var _self; export default{ data() {return {}}, onLoad(option) {}, onShow(){}, methods: { decryptPhoneNumber: function(e){ // console.log(e.detail); if( e.detail.errMsg == "getPhoneNumber:ok" ){ //成功 auth.login(e.detail); } }, me: function(){ this.$http.get('/v1/auth/me',{custom: {auth: true}}).then(res=>{ console.log(res,'success') }).catch(err=>{ console.log(err,'error60') }) }, clear: function(){ jwt.clearAccessToken(); jwt.clearUser(); uni.showToast({ icon: 'success', title: '清除成功', duration:2000, }); } }, components: {} }</script><style></style>ログイン後にコピー
バックエンド// 登陆
public function login(Request $request)
{
$platform = $request->header('platform');
if(!$platform || !in_array($platform,User::$platforms)){
return Y::json(1001, '不支持的平台类型');
}
$post = $request->only(['encryptedData', 'iv', 'code']);
$validator = Validator::make($post, [
'encryptedData' => 'required',
'iv' => 'required',
'code' => 'required'
]);
if ($validator->fails()) {return Y::json(1002,'非法请求');}
switch ($platform) {
case 'MP-BAIDU':
$decryption = (new BdDataDecrypt())->decrypt($post['encryptedData'],$post['iv'],$post['code']);
break;
default:
$decryption = false;
break;
}
// var_dump($decryption);
if($decryption !== false){
$user = User::where('u_platform',$platform)->where('u_mobile',$decryption['mobile'])->first();
if($user){
$user->u_login_time = date('Y-m-d H:i:s',time());
$user->save();
}else{
$user = User::create([
'u_username'=> substr_replace($decryption['mobile'],'******',3,6),
'u_nickname'=> User::crateNickName(),
'u_platform'=> $platform,
'u_mobile' => $decryption['mobile'],
'u_openid' => $decryption['openid'],
'u_regtime' => date('Y-m-d H:i:s',time())
]);
}
$token = auth()->login($user);
return Y::json(
array_merge(
$this->respondWithToken($token),
['user'=>['nickName'=>$user->u_nickname]]
)
);
}
return Y::json(1003,'登录失败');
}
// 返回 token
protected function respondWithToken($token)
{
return ['access_token' => $token];
}
ログイン後にコピー
// 登陆 public function login(Request $request) { $platform = $request->header('platform'); if(!$platform || !in_array($platform,User::$platforms)){ return Y::json(1001, '不支持的平台类型'); } $post = $request->only(['encryptedData', 'iv', 'code']); $validator = Validator::make($post, [ 'encryptedData' => 'required', 'iv' => 'required', 'code' => 'required' ]); if ($validator->fails()) {return Y::json(1002,'非法请求');} switch ($platform) { case 'MP-BAIDU': $decryption = (new BdDataDecrypt())->decrypt($post['encryptedData'],$post['iv'],$post['code']); break; default: $decryption = false; break; } // var_dump($decryption); if($decryption !== false){ $user = User::where('u_platform',$platform)->where('u_mobile',$decryption['mobile'])->first(); if($user){ $user->u_login_time = date('Y-m-d H:i:s',time()); $user->save(); }else{ $user = User::create([ 'u_username'=> substr_replace($decryption['mobile'],'******',3,6), 'u_nickname'=> User::crateNickName(), 'u_platform'=> $platform, 'u_mobile' => $decryption['mobile'], 'u_openid' => $decryption['openid'], 'u_regtime' => date('Y-m-d H:i:s',time()) ]); } $token = auth()->login($user); return Y::json( array_merge( $this->respondWithToken($token), ['user'=>['nickName'=>$user->u_nickname]] ) ); } return Y::json(1003,'登录失败'); } // 返回 token protected function respondWithToken($token) { return ['access_token' => $token]; }
モバイル電話番号の復号化
<?phpnamespace App\Library;use App\Library\Y;class BdDataDecrypt{ private $_appid; private $_app_key; private $_secret; private $_session_key; public function __construct() { $this->_appid = env('BD_APPID'); $this->_app_key = env('BAIDU_KEY'); $this->_secret = env('BD_SECRET'); } public function decrypt($encryptedData, $iv, $code){ $res = $this->getSessionKey($code); if($res === false){return false;} $data['openid'] = $res['openid']; $res = $this->handle($encryptedData,$iv,$this->_app_key,$res['session_key']); if($res === false){return false;} $res = json_decode($res,true); $data['mobile'] = $res['mobile']; return $data; } public function getSessionKey($code) { $params['code'] = $code; $params['client_id'] = $this->_app_key; $params['sk'] = $this->_secret; $res = Y::curl("https://spapi.baidu.com/oauth/jscode2sessionkey",$params,0,1); // var_dump($res); /** * 错误返回 * array(3) { ["errno"]=> int(1104) ["error"]=> string(33) "invalid code , expired or revoked" ["error_description"]=> string(33) "invalid code , expired or revoked" } 成功返回: array(2) { ["openid"]=> string(26) "z45QjEfvkUJFwYlVcpjwST5G8w" ["session_key"]=> string(32) "51b9297ababbcf43c1a099256bf82d75" } */ if( isset($res['error']) ){ return false; } return $res; } /** * 官方 demo * return string(24) "{"mobile":"18288881111"}" or false */ private function handle($ciphertext, $iv, $app_key, $session_key) { $session_key = base64_decode($session_key); $iv = base64_decode($iv); $ciphertext = base64_decode($ciphertext); $plaintext = false; if (function_exists("openssl_decrypt")) { $plaintext = openssl_decrypt($ciphertext, "AES-192-CBC", $session_key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv); } else { $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, null, MCRYPT_MODE_CBC, null); mcrypt_generic_init($td, $session_key, $iv); $plaintext = mdecrypt_generic($td, $ciphertext); mcrypt_generic_deinit($td); mcrypt_module_close($td); } if ($plaintext == false) { return false; } // trim pkcs#7 padding $pad = ord(substr($plaintext, -1)); $pad = ($pad 32) ? 0 : $pad; $plaintext = substr($plaintext, 0, strlen($plaintext) - $pad); $plaintext = substr($plaintext, 16); $unpack = unpack("Nlen/", substr($plaintext, 0, 4)); $content = substr($plaintext, 4, $unpack['len']); $app_key_decode = substr($plaintext, $unpack['len'] + 4); return $app_key == $app_key_decode ? $content : false; }}
6. access_token アイデアの簡単な更新
まず第一に、私が使用する方法は、バックエンドがトークンが持っていると判断した後です。新しいトークンの場合、フロントエンドはバックエンド応答の合意されたコードを応答インターセプターでキャプチャし、新しいトークンを保存して、2 番目のリクエストを行います。 . 最終的には、それは通常の要求として認識されます。別の考え方としては、バックエンドが正常に更新しようとした後、現在のユーザーに対して自動的にログインし、ヘッダーに新しいトークンが返されるというもので、フロントエンドはストレージのみを担当します。
7. 小程序如何判断登陆状态
其实思路也很简单,非前后端分离怎么做的,前后端分离就怎么做,原理一样。非前后端分离,在每次请求时都会读取 session ,那么前后端分离,更好一些,有些公开请求不走中间件,也就无需判断登陆态,只有在需要权限认证的页面,在页面初始化时发出一次请求走中间件,以此判断登陆状态。
定义全局登陆检查函数
import jwt from '@/utils/auth/jwt.js';Vue.prototype.checkLogin = function(){
var TOKEN = jwt.getAccessToken();
return new Promise((resolve, reject) => {
if(TOKEN){
http.get('/v1/auth/check',{custom: {auth: true}}).then(res=>{
// 通过中间件 一定是登陆态
resolve(true);
}).catch(err=>{
resolve(false);
console.log(err) // 这里是401 403 后端500错误或者网络不好
})
}else{
resolve(false) //没有token 一定是未登陆
}
})}
ログイン後にコピー
笔者最终放弃上面的这种检查登录的方式,直接检验storage中有user和token即视为登录状态。以被动的验证代替主动去验证,就是说用户执行一个请求,返回401,那么就改变登录状态。以后再补充。
前端
<script>
export default {
data() {
return {
isLogin:null
}
},
onLoad() {
this.checkLogin().then(loginStatus=>{
this.isLogin = loginStatus;
});
},
methods: {
},
components: {}
}</script>
ログイン後にコピー
定义全局登陆检查函数
import jwt from '@/utils/auth/jwt.js';Vue.prototype.checkLogin = function(){ var TOKEN = jwt.getAccessToken(); return new Promise((resolve, reject) => { if(TOKEN){ http.get('/v1/auth/check',{custom: {auth: true}}).then(res=>{ // 通过中间件 一定是登陆态 resolve(true); }).catch(err=>{ resolve(false); console.log(err) // 这里是401 403 后端500错误或者网络不好 }) }else{ resolve(false) //没有token 一定是未登陆 } })}
笔者最终放弃上面的这种检查登录的方式,直接检验storage中有user和token即视为登录状态。以被动的验证代替主动去验证,就是说用户执行一个请求,返回401,那么就改变登录状态。以后再补充。
<script> export default { data() { return { isLogin:null } }, onLoad() { this.checkLogin().then(loginStatus=>{ this.isLogin = loginStatus; }); }, methods: { }, components: {} }</script>
以上がuni-app アプレット Laravel+jwt 権限認証シリーズの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック

モバイル インターネット技術とスマートフォンの普及により、WeChat は人々の生活に欠かせないアプリケーションになりました。 WeChat ミニ プログラムを使用すると、アプリケーションをダウンロードしてインストールすることなく、ミニ プログラムを直接使用して、いくつかの簡単なニーズを解決できます。この記事では、Python を使用して WeChat アプレットを開発する方法を紹介します。 1. 準備 Python を使用して WeChat アプレットを開発する前に、関連する Python ライブラリをインストールする必要があります。ここでは、wxpy と itchat の 2 つのライブラリを使用することをお勧めします。 wxpy は WeChat マシンです

ミニプログラムはreactを利用することができます 使い方: 1. 「react-reconciler」に基づいてレンダラーを実装し、DSLを生成します; 2. DSLを解析してレンダリングするためのミニプログラムコンポーネントを作成します; 3. npmをインストールし、開発者ビルドを実行しますツール内の npm; 4. パッケージを独自のページに導入し、API を使用して開発を完了します。

VSCode でユニアプリを開発するにはどうすればよいですか?次の記事では、VSCode でのユニアプリ開発に関するチュートリアルを紹介します。これは、おそらく最も詳細なチュートリアルです。ぜひ見に来てください!

WeChat ミニ プログラムでのカードめくり効果の実装 WeChat ミニ プログラムでは、カードめくり効果の実装は、ユーザー エクスペリエンスとインターフェイス インタラクションの魅力を向上させることができる一般的なアニメーション効果です。以下では、WeChat アプレットでカードめくりの特殊効果を実装する方法と、関連するコード例を詳しく紹介します。まず、ミニ プログラムのページ レイアウト ファイルに 2 つのカード要素を定義する必要があります。1 つは前面のコンテンツを表示するため、もう 1 つは背面のコンテンツを表示するためです。具体的なサンプル コードは次のとおりです: <!--index.wxml- ->&l

10月31日の当サイトのニュースによると、今年5月27日、アント・グループは「漢字拾いプロジェクト」の立ち上げを発表し、最近新たな進展を迎えた:アリペイが「漢字拾い-珍しい文字」ミニプログラムを開始協会からコレクションを収集する レア文字は、レア文字ライブラリを補完し、アリペイでのレア文字入力方法の改善に役立つように、レア文字に異なる入力エクスペリエンスを提供します。現在、ユーザーは「漢字ピックアップ」「珍文字」などのキーワードで検索することで「珍文字」アプレットに入ることができる。ミニプログラムでは、ユーザーがシステムで認識・入力されなかった珍しい文字の画像を送信し、確認後、Alipay のエンジニアがフォントライブラリに追加エントリを作成します。当サイトでは、発音が不明瞭な珍しい単語を対象とした最新の単語分割入力方法をミニプログラムで体験できることに注目しました。ユーザー解体

uniapp を使用してシンプルなマップ ナビゲーションを開発するにはどうすればよいですか?この記事では簡単な地図の作り方を紹介しますので、ぜひ参考にしてください。

uniapp がミニ プログラムと H5 の間で迅速な変換を実現するには、具体的なコード例が必要ですが、近年、モバイル インターネットの発展とスマートフォンの普及に伴い、ミニ プログラムと H5 は不可欠なアプリケーション形式となっています。クロスプラットフォーム開発フレームワークとして、uniapp は一連のコードに基づいて小規模プログラムと H5 間の変換を迅速に実現し、開発効率を大幅に向上させます。この記事では、uniapp がミニ プログラムと H5 の間で迅速な変換を実現する方法と、具体的なコード例を紹介します。 1. uniapp uniaの紹介

uniapp を使用してスネーク ゲームを開発するにはどうすればよいですか?次の記事では、Uniapp に Snake ゲームを実装する手順を段階的に説明します。お役に立てば幸いです。
