Commit ca90354c by Administrator

Merge branch 'feature-2511-opt' into develop

base v1.0
2 parents e1a297a1 739105e0
Showing 133 changed files with 1781 additions and 579 deletions
......@@ -11,6 +11,14 @@ flutter run -d 00008030-001C75810E42402E --release
flutter run -d 00008140-001068C93AB8801C --release
gao 00008130-0010788A2E01001C
yongosng 00008110-000A284C2178801E
chenqian 00008101-000C19483450001E
flutter build ios --build-number=2511061
flutter build ipa --export-method ad-hoc
flutter build ipa --export-method ad-hoc --build-name=1.0.0 --build-number=1
......@@ -84,3 +92,11 @@ git clone https://gitee.com/mirrors/DKImagePickerController.git --branch 4.3.9
# 在 ios/Podfile 中添加:
pod 'DKImagePickerController', :path => '~/Downloads/DKImagePickerController'
在项目根目录,使用 flutter run 命令启动你的App到真机。这是最标准的方式。
或者,先通过Xcode运行App,然后在另一个终端窗口使用 flutter attach 连接上它(确保已解决之前的本地网络权限问题)。
在 flutter run 成功运行的终端界面,不要按 Ctrl+C 中断。
在代码编辑器里,随便修改一个 Text('Hello') 的字符串。
保存文件后,立刻回到终端,按下键盘的 r 键,然后按回车。
\ No newline at end of file
......@@ -101,7 +101,7 @@ dependencies {
// OPPO
//implementation("com.tencent.timpush:oppo:8.7.7201")
// vivo
implementation("com.tencent.timpush:vivo:8.7.7201")
implementation("com.tencent.timpush:vivo:8.8.7357")
// Honor
//implementation("com.tencent.timpush:honor:8.7.7201")
// Meizu
......
<?xml version="1.0" encoding="utf-8"?>
<!-- 手动增加的安全配置,明文传输权限,允许http访问 -->
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
<!--
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="false">127.0.0.1</domain>
<domain includeSubdomains="false">localhost</domain>
......@@ -11,5 +17,7 @@
<domain includeSubdomains="false">localdev.banxiaoer.net</domain>
<domain includeSubdomains="false">appdev-th.banxiaoer.net</domain>
<domain includeSubdomains="false">appdev-xj.banxiaoer.net</domain>
<domain includeSubdomains="false">img.jyeoo.net</domain>
</domain-config>
-->
</network-security-config>
#################################################
# 脚本代码中不能含有中文,否则默认PowerShell环境可能会报错
#################################################
# 1. 读取脚本参数
#-------------------------------------------
param(
# app打包环境:dev, prod
[Parameter(Mandatory = $true)]
[ValidateSet("dev", "pro")]
[string]$appenv,
# App版本,例如:1.0.2512181
[Parameter(Mandatory = $true)]
[string]$version,
# 下载地址
[string]$url = "https://bxe.obs.cn-north-4.myhuaweicloud.com/fronts/material/xehybrid/assets/basepkg/base-$appenv.zip",
# 下载保存文件的路径
[string]$downloadPath = "C:\Users\tang-\StudioProjects\appframe\assets\base-$appenv.zip",
# App路径
[string]$appPath = "C:\Users\tang-\StudioProjects\appframe"
)
# 2. 验证目录存在
#-------------------------------------------
if (-not (Test-Path $appPath))
{
Write-Error "appPath not exists: $appPath"
exit 1
}
# 确保下载目录存在
$downloadDir = Split-Path $downloadPath -Parent
if (-not (Test-Path $downloadDir))
{
Write-Error "the path for save file does not exists: $appPath"
exit 1
}
# 3. 下载文件(添加错误处理)
#-------------------------------------------
try
{
Write-Host "downloading from : $url" -ForegroundColor Green
Write-Host "save to : $downloadPath" -ForegroundColor Green
Invoke-WebRequest -Uri $url -OutFile $downloadPath -ErrorAction Stop
if (Test-Path $downloadPath)
{
Write-Host "download success!" -ForegroundColor Green
}
else
{
Write-Error "download failed!"
exit 1
}
}
catch
{
Write-Error "downloading failed: $_"
exit 1
}
# 4. 执行flutter构建
#-------------------------------------------
try
{
Set-Location -Path $appPath -ErrorAction Stop
Write-Host "flutter building ..." -ForegroundColor Green
Write-Host "appenv: $appenv, version: $version" -ForegroundColor Cyan
flutter build apk `
--release `
--split-per-abi `
--target-platform android-arm64 `
--dart-define=env=$appenv `
--dart-define=version=$version
if ($LASTEXITCODE -eq 0)
{
Write-Host "build success!" -ForegroundColor Green
}
else
{
Write-Error "build failed!"
exit $LASTEXITCODE
}
}
catch
{
Write-Error "building failed: $_"
exit 1
}
# 5. 复制生成新命名的APK文件
# "build\app\outputs\flutter-apk\app-arm64-v8a-release.apk"
#-------------------------------------------
try
{
$apkOutputDir = Join-Path $appPath "build\app\outputs\flutter-apk"
$sourceApk = Join-Path $apkOutputDir "app-arm64-v8a-release.apk"
$newApkName = "bxeapp-$appenv-$version.apk"
$destApkPath = Join-Path $apkOutputDir $newApkName
if (Test-Path $sourceApk)
{
Copy-Item -Path $sourceApk -Destination $destApkPath -Force
Write-Host "Copied and renamed: $newApkName" -ForegroundColor Yellow
}
else
{
Write-Warning "Source APK not found: $sourceApk"
}
}
catch
{
Write-Error "Failed to copy and rename APK: $_"
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebView Communication</title>
</head>
<body>
<h2>登录跳转</h2>
</body>
<script>
window.onload = function () {
let message = '{ "timestamp": 1, "unique": "1", "cmd": "goLogin", "params": {} }';
xeJsBridge.postMessage(message);
}
</script>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebView Communication</title>
</head>
<body>
<h2>接口测试</h2>
<div id="resp"></div>
<input type="file" />
<audio controls>
<source src="https://files-obs.banxiaoer.com/d2/pridel/user/20250918/bxe/220387141757177856/f/comm/bxe_homework/audio/fmp3t1758184802425n5zldhtvw.mp3">
</audio>
<img id="testImg" src="" alt="">
<button onclick="clearResp()">清除响应数据</button>
<br>
<br>
<button onclick="getDeviceInfoSync()">获取当前设备信息</button>
<br>
<br>
<button onclick="setStorageSync()">设置本地缓存</button>
<button onclick="getStorageSync()">获取本地缓存</button>
<button onclick="removeStorageSync()">删除本地缓存</button>
<button onclick="clearStorageSync()">清空本地缓存</button>
<br>
<br>
<button onclick="chooseImage(1)">选择单个图片</button>
<button onclick="chooseMultipleImage(1)">选择多个图片</button>
<br>
<br>
<button onclick="scanCode()">扫码测试</button>
<button onclick="camera()">录像测试</button>
<br>
<br>
<button onclick="openWeapp()">打开小程序</button>
<br>
<br>
<a href="/test/test2.html">跳转测试2</a>
<br>
<br>
<a href="/test/test3.html">跳转测试3</a>
<br>
<br>
<a href="/test/test4.html">跳转测试4</a>
<br>
<br>
<a href="/index.html">iOS跳转</a>
<br>
<br>
<script src="/test/test.js"></script>
</body>
</html>
\ No newline at end of file
// 显示来自Flutter的警告
function showAlert(message) {
alert(message);
}
// 清空响应数据
function clearResp() {
document.getElementById('resp').innerHTML = '';
}
// 接收Flutter响应数据
function xeJsBridgeCallback(message) {
document.getElementById('resp').innerHTML = '<p><strong>响应:</strong> ' + message + '</p>';
/*let jsonObj=JSON.parse(message);
if(jsonObj.cmd=='chooseImage'){
document.getElementById('testImg').src='/temp'+jsonObj.data[0].tempFilePath;
}*/
}
// 测试获取设备信息
function getDeviceInfoSync() {
let message = '{ "timestamp": 1, "unique": "123", "cmd": "getDeviceInfoSync", "params": {} }';
xeJsBridge.postMessage(message);
}
function setStorageSync() {
let message = '{ "timestamp": 1, "unique": "123", "cmd": "setStorageSync", "params": {"key":"test1","value":"hello world!Hey!"} }';
xeJsBridge.postMessage(message);
}
function getStorageSync() {
let message = '{ "timestamp": 1, "unique": "123", "cmd": "getStorageSync", "params": "test1" }';
xeJsBridge.postMessage(message);
}
function removeStorageSync() {
let message = '{ "timestamp": 1, "unique": "123", "cmd": "removeStorageSync", "params": "test1" }';
xeJsBridge.postMessage(message);
}
function clearStorageSync() {
let message = '{ "timestamp": 1, "unique": "123", "cmd": "clearStorageSync", "params": {} }';
xeJsBridge.postMessage(message);
}
function chooseImage(sourceType) {
let params = {
"timestamp": 1, "unique": "123", "cmd": "chooseImage", "params": {
"sourceType": sourceType == 1 ? "album" : "camera",
"count": 1,
"sizeType": ["original", "compressed"],
}
};
xeJsBridge.postMessage(JSON.stringify(params));
}
function chooseMultipleImage(sourceType) {
let params = {
"timestamp": 1, "unique": "123", "cmd": "chooseImage", "params": {
"sourceType": sourceType == 1 ? "album" : "camera",
"count": 9,
"sizeType": ["original", "compressed"],
}
};
xeJsBridge.postMessage(JSON.stringify(params));
}
function scanCode() {
let params = {
"timestamp": 1, "unique": "123", "cmd": "scanCode", "params": {}
};
xeJsBridge.postMessage(JSON.stringify(params));
}
function openWeapp(){
let params = {
"timestamp": 1, "unique": "123", "cmd": "openWeapp", "params": {
appid:'gh_9a8d84445828',
path:'/pages/index/index?classCode=needswitch',
envVersion:'trial'
}
};
xeJsBridge.postMessage(JSON.stringify(params));
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebView Communication</title>
</head>
<body>
<h2>接口测试2</h2>
<div id="resp"></div>
<input type="text" id="text" value="">
<button onclick="startRecord()">开始录音</button>
<button onclick="pauseRecord()">暂停录音</button>
<button onclick="resumeRecord()">唤醒录音</button>
<button onclick="stopRecord()">停止录音</button>
<br>
<br>
<input type="hidden" id="url" value="">
<button onclick="startPlay()">开始播放</button>
<button onclick="pausePlay()">暂停播放</button>
<button onclick="resumePlay()">唤醒播放</button>
<button onclick="stopPlay()">停止播放</button>
<button onclick="openlink()">打开新链接</button>
<br>
<br>
<a href="/test/test.html">跳转测试1</a>
</body>
<script>
function xeJsBridgeCallback(message) {
document.getElementById('resp').innerHTML = '<p><strong>响应:</strong> ' + message + '</p>';
// 设置id为url的input的值
let jsonData = JSON.parse(message);
if(jsonData.cmd=="audioRecorderStop"){
document.getElementById('url').value = jsonData.data.tempFilePath;
}
}
function startRecord() {
let message = '{ "timestamp": 1, "unique": "123", "cmd": "audioRecorderStart", "params": {"duration":3} }';
xeJsBridge.postMessage(message);
}
function pauseRecord() {
let message = '{ "timestamp": 1, "unique": "123", "cmd": "audioRecorderPause", "params": {} }';
xeJsBridge.postMessage(message);
}
function resumeRecord() {
let message = '{ "timestamp": 1, "unique": "123", "cmd": "audioRecorderResume", "params": {} }';
xeJsBridge.postMessage(message);
}
function stopRecord() {
let message = '{ "timestamp": 1, "unique": "123", "cmd": "audioRecorderStop", "params": {} }';
xeJsBridge.postMessage(message);
}
function startPlay() {
let url = document.getElementById('url').value;
//let url = 'https://files-cos.banxiaoer.net/txbb/res/mp3/d2-d560e952-4029-11eb-928a-acde48001122/wd-db0b7502-4029-11eb-928a-acde48001122_h.mp3';
let message = '{ "timestamp": 1, "unique": "123", "cmd": "audioPlay", "params": {"url":"' + url + '"} }';
xeJsBridge.postMessage(message);
}
function pausePlay() {
let message = '{ "timestamp": 1, "unique": "123", "cmd": "audioPause", "params": {} }';
xeJsBridge.postMessage(message);
}
function resumePlay() {
let message = '{ "timestamp": 1, "unique": "123", "cmd": "audioResume", "params": {} }';
xeJsBridge.postMessage(message);
}
function stopPlay() {
let message = '{ "timestamp": 1, "unique": "123", "cmd": "audioStop", "params": {} }';
xeJsBridge.postMessage(message);
}
function openlink() {
let url = 'https://xw.qq.com';
let message = '{ "timestamp": 1, "unique": "123", "cmd": "openlink", "params": {"url":"' + url + '"} }';
xeJsBridge.postMessage(message);
}
</script>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebView Communication</title>
</head>
<body>
<h2>接口测试3</h2>
<div id="resp"></div>
<button onclick="selectFile()">选择文件</button>
<button onclick="saveImg()">保存图片视频</button>
<br>
<br>
<button onclick="setClipboard()">设置剪贴板</button>
<button onclick="getClipboard()">获取剪贴板</button>
<br>
<br>
<a href="/test/test.html">跳转测试1</a>
</body>
<script>
function xeJsBridgeCallback(message) {
document.getElementById('resp').innerHTML = '<p><strong>响应:</strong> ' + message + '</p>';
}
function selectFile() {
let params = {
timestamp: 1,
unique: '123',
cmd: 'chooseFile',
params: {
count: 2,
fileTypes: ['docx', 'xlsx', 'pdf', 'mp4','csv', 'png', 'm4a']
}
};
xeJsBridge.postMessage(JSON.stringify(params));
}
function saveImg() {
let params = {
timestamp: 1,
unique: '123',
cmd: 'saveToAlbum',
params: {
// filePath: 'http://www.people.com.cn/NMediaFile/2025/0910/MAIN175747975880516PN6WLU10.jpg'
// filePath: '/data/user/0/cn.banxe.bxe/cache/1757576655307_1000019720.jpg'
filePath: '/data/user/0/cn.banxe.bxe/cache/1757577249083_VID_20250906_111114.mp4'
}
};
xeJsBridge.postMessage(JSON.stringify(params));
}
function setClipboard() {
let message = '{ "timestamp": 1, "unique": "123", "cmd": "setClipboardData", "params": "此为测试剪贴板的内容" }';
xeJsBridge.postMessage(message);
}
function getClipboard() {
let message = '{ "timestamp": 1, "unique": "123", "cmd": "getClipboardData", "params": {} }';
xeJsBridge.postMessage(message);
}
</script>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebView Communication</title>
</head>
<body>
<h2>接口测试4</h2>
<div id="resp"></div>
<button onclick="clearResp()">清除响应数据</button>
<br>
<br>
<button onclick="compressImage()">测试图片压缩</button>
<br>
<br>
<button onclick="upload()">测试文件上传</button>
<br>
<br>
<a href="/test/test.html">跳转测试1</a>
</body>
<script>
// 清空响应数据
function clearResp() {
document.getElementById('resp').innerHTML = '';
}
function xeJsBridgeCallback(message) {
document.getElementById('resp').innerHTML = '<p><strong>响应:</strong> ' + message + '</p>';
}
function compressImage() {
let params = {
timestamp: 1,
unique: '123',
cmd: 'compressImage',
params: {
url: '/data/user/0/cn.banxe.bxe/cache/1757649757566_1000019639.jpg',
quality: 50,
compressedWidth: 800,
compressedHeight: 600,
}
};
xeJsBridge.postMessage(JSON.stringify(params));
}
function upload() {
let params = {
timestamp: 1,
unique: '123',
cmd: 'uploadFile',
params: {
tempFilePath: '/data/user/0/cn.banxe.bxe/cache/1758248263516_test.csv',
}
};
xeJsBridge.postMessage(JSON.stringify(params));
}
</script>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebView Communication</title>
</head>
<body>
<h2>前端测试</h2>
<form>
<input type="text" id="cliIp" value="">
<input type="submit">
</form>
<br>
<br>
<a href="/test/test.html">跳转测试1</a>
</body>
<script>
window.aaa = function (a, b, c) {
console.log(a, b, c);
}
function test(term, ) {
}
</script>
</html>
\ No newline at end of file
# 编译发布前需要执行的操作
if [ -z "$1" ]; then
echo '需要指定环境变量'
fi
env=$1
_rebuild_=$2
_main_ver='1.0.'
_ver=`date +%y%m%d%H%M`
base_root='/Users/ethanlam/works/gitlab/flutter_pros/appframe.git'
cd $base_root
git pull
if [ ! -z "$env" ]; then
# 首先下载同步最新的数据包
rm -f assets/dist.zip
cd assets
if [ "$env" == 'dev' ]; then
rm -f $base_root/assets/base-dev.zip*
wget2 https://bxe.obs.cn-north-4.myhuaweicloud.com/fronts/material/xehybrid/assets/basepkg/base-dev.zip
cp -f base-dev.zip $base_root/assets/dist.zip
echo 'wget dev is done '
fi
if [ "$env" == 'pro' ]; then
rm -f $base_root/assets/base-pro.zip*
wget2 https://bxe.obs.cn-north-4.myhuaweicloud.com/fronts/material/xehybrid/assets/basepkg/base-pro.zip
cp -f base-pro.zip $base_root/assets/dist.zip
echo 'wget pro is done '
fi
fi
cd $base_root
if [ ! -z "$_rebuild_" ]; then
flutter clean
flutter pub get
cd ios
rm -rf Pods
rm -rf Podfile.lock
rm -rf .symlinks
rm -rf Flutter/Flutter.framework
rm -rf Flutter/App.framework
pod repo update
pod install
fi
#16
#flutter run --dart-define=env=$env --dart-define=version=$_main_ver$_ver -d 00008140-001068C93AB8801C
#11
flutter run --dart-define=env=$env --dart-define=version=$_main_ver$_ver -d 00008030-001C75810E42402E
import Flutter
import UIKit
import Flutter
@main
@objc class AppDelegate: FlutterAppDelegate {
private var edgePanRecognizer: UIScreenEdgePanGestureRecognizer?
private var methodChannel: FlutterMethodChannel?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// 获取 root FlutterViewController 实例
guard let controller = window?.rootViewController as? FlutterViewController else {
fatalError("rootViewController is not a FlutterViewController")
}
// 注册插件
GeneratedPluginRegistrant.register(with: self)
// 创建 MethodChannel,用于与 Flutter 通信
methodChannel = FlutterMethodChannel(
name: "ios_edge_swipe",
binaryMessenger: controller.binaryMessenger
)
methodChannel?.setMethodCallHandler { [weak self] (call, result) in
if call.method == "initSwipeListener" {
self?.setupEdgeSwipeGesture(controller: controller)
result(nil)
}
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
// 添加边缘滑动手势识别器
private func setupEdgeSwipeGesture(controller: UIViewController) {
if edgePanRecognizer != nil {
return // 防止重复添加
}
edgePanRecognizer = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleEdgeSwipe(_:)))
edgePanRecognizer?.edges = [.left] // 监听左边缘
edgePanRecognizer?.delegate = self
controller.view.addGestureRecognizer(edgePanRecognizer!)
print("✅ 左边缘滑动手势监听已添加")
}
// 触发时回调
@objc private func handleEdgeSwipe(_ gesture: UIScreenEdgePanGestureRecognizer) {
if gesture.state == .recognized {
methodChannel?.invokeMethod("onEdgeSwipe", arguments: nil)
print("👈 左边缘滑动检测到!已通知 Flutter")
}
}
}
// 可选:防止冲突的手势识别设置
extension AppDelegate: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// 如果需要同时识别,比如与 WebView 的滚动手势共存
return true
}
}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>班小二App</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>cn.banxe.appframe</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>banxiaoer</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>weixin</string>
<key>CFBundleURLSchemes</key>
<array>
<string>wx8c32ea248f0c7765</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>weixin</string>
<string>wechat</string>
<string>weixinULAPI</string>
<string>weixinURLParamsAPI</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSAllowsArbitraryLoadsInWebContent</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>dev.banxiaoer.net</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
<key>NSCameraUsageDescription</key>
<string>需要访问相机</string>
<key>NSMicrophoneUsageDescription</key>
<string>需要麦克风权限用于录音</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>需要访问相册选择图片或视频</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>io.flutter.embedded_views_preview</key>
<true/>
<key>NSLocalNetworkUsageDescription</key>
<string>此应用需要访问本地网络以发现和连接智能设备</string>
<key>NSBonjourServices</key>
<array>
<string>_dartvm._tcp</string>
<string>_dartobservatory._tcp</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
</dict>
</plist>
......@@ -40,6 +40,7 @@
<key>LSApplicationQueriesSchemes</key>
<array>
<string>weixin</string>
<string>wechat</string>
<string>weixinULAPI</string>
<string>weixinURLParamsAPI</string>
</array>
......@@ -93,7 +94,14 @@
<string>此应用需要访问本地网络以发现和连接智能设备</string>
<key>NSBonjourServices</key>
<array>
<string>_dartvm._tcp</string>
<string>_dartobservatory._tcp</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>班小二App</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>cn.banxe.appframe</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>banxiaoer</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>weixin</string>
<key>CFBundleURLSchemes</key>
<array>
<string>wx8c32ea248f0c7765</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>weixin</string>
<string>wechat</string>
<string>weixinULAPI</string>
<string>weixinURLParamsAPI</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSAllowsArbitraryLoadsInWebContent</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>dev.banxiaoer.net</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
<key>NSCameraUsageDescription</key>
<string>需要访问相机</string>
<key>NSMicrophoneUsageDescription</key>
<string>需要麦克风权限用于录音</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>需要访问相册选择图片或视频</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>io.flutter.embedded_views_preview</key>
<true/>
<key>NSLocalNetworkUsageDescription</key>
<string>此应用需要访问本地网络以发现和连接智能设备</string>
<key>NSBonjourServices</key>
<array>
<string>_dartvm._tcp</string>
<!-- 对于更新的Flutter版本,有时还需要添加以下行 -->
<string>_dartobservatory._tcp</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>班小二App</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>cn.banxe.appframe</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>banxiaoer</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>weixin</string>
<key>CFBundleURLSchemes</key>
<array>
<string>wx8c32ea248f0c7765</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>weixin</string>
<string>wechat</string>
<string>weixinULAPI</string>
<string>weixinURLParamsAPI</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSAllowsArbitraryLoadsInWebContent</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>dev.banxiaoer.net</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
<key>NSCameraUsageDescription</key>
<string>需要访问相机</string>
<key>NSMicrophoneUsageDescription</key>
<string>需要麦克风权限用于录音</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>需要访问相册选择图片或视频</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>io.flutter.embedded_views_preview</key>
<true/>
<key>NSLocalNetworkUsageDescription</key>
<string>此应用需要访问本地网络以发现和连接智能设备</string>
<key>NSBonjourServices</key>
<array>
<string>_dartvm._tcp</string>
<!-- 对于更新的Flutter版本,有时还需要添加以下行 -->
<string>_dartobservatory._tcp</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
</dict>
</plist>
{"guid":"dc4b70c03e8043e50e38f2068887b1d4","name":"Pods","path":"/Users/ethanlam/works/gitlab/flutter_pros/appframe.git/ios/Pods/Pods.xcodeproj/project.xcworkspace","projects":["PROJECT@v11_mod=a5693d2b86d2bd745700c225bc3c49cb_hash=bfdfe7dc352907fc980b868725387e98plugins=1OJSG6M1FOV3XYQCBH7Z29RZ0FPR9XDE1"]}
\ No newline at end of file
{"guid":"dc4b70c03e8043e50e38f2068887b1d4","name":"Pods","path":"/Users/ethanlam/works/gitlab/flutter_pros/appframe.git/ios/Pods/Pods.xcodeproj/project.xcworkspace","projects":["PROJECT@v11_mod=14ee682db2378d556925c69eae52a50e_hash=bfdfe7dc352907fc980b868725387e98plugins=1OJSG6M1FOV3XYQCBH7Z29RZ0FPR9XDE1"]}
\ No newline at end of file
{"guid":"dc4b70c03e8043e50e38f2068887b1d4","name":"Pods","path":"/Users/ethanlam/works/gitlab/flutter_pros/appframe.git/ios/Pods/Pods.xcodeproj/project.xcworkspace","projects":["PROJECT@v11_mod=1ac66dbb2b848fb24d56375428d6412e_hash=bfdfe7dc352907fc980b868725387e98plugins=1OJSG6M1FOV3XYQCBH7Z29RZ0FPR9XDE1"]}
\ No newline at end of file
# 编译发布前需要执行的操作
if [ -z "$1" ]; then
echo '需要指定环境变量'
fi
env=$1
base_root='/Users/ethanlam/works/gitlab/flutter_pros/appframe.git'
cd $base_root
if [ ! -z "$env" ]; then
# 首先下载同步最新的数据包
rm -f assets/dist.zip
cd assets
if [ "$env" == 'dev' ]; then
rm -f $base_root/assets/base-dev.zip*
wget2 https://bxe.obs.cn-north-4.myhuaweicloud.com/fronts/material/xehybrid/assets/basepkg/base-dev.zip
cp -f base-dev.zip $base_root/assets/dist.zip
echo 'wget dev is done '
fi
if [ "$env" == 'pro' ]; then
rm -f $base_root/assets/base-pro.zip*
wget2 https://bxe.obs.cn-north-4.myhuaweicloud.com/fronts/material/xehybrid/assets/basepkg/base-pro.zip
cp -f base-pro.zip $base_root/assets/dist.zip
echo 'wget pro is done '
fi
fi
echo 'done '
......@@ -3,7 +3,6 @@ import 'dart:io';
import 'package:appframe/l10n/gen/app_localizations.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; // 添加这行
import 'config/routes.dart';
......@@ -30,20 +29,10 @@ class App extends StatelessWidget {
// ],
)
: MaterialApp.router(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
routerConfig: router,
title: '班小二',
theme: ThemeData(primarySwatch: Colors.blue),
// // === 为 iOS 添加本地化配置 ===
// localizationsDelegates: const [
// GlobalMaterialLocalizations.delegate, // 为Material组件提供本地化
// GlobalCupertinoLocalizations.delegate, // 为Cupertino组件提供本地化
// GlobalWidgetsLocalizations.delegate, // 定义文本方向等
// ],
// supportedLocales: const [
// Locale('zh', 'CN'), // 中文(中国)
// Locale('en', 'US'), // 英语(美国)
// ],
supportedLocales: AppLocalizations.supportedLocales,
localizationsDelegates: AppLocalizations.localizationsDelegates,
);
}
......@@ -2,22 +2,37 @@ import 'package:appframe/config/routes.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:webview_flutter/webview_flutter.dart';
class LinkState extends Equatable {
final bool loaded;
final String url;
final String title;
const LinkState({this.loaded = false, this.url = ''});
const LinkState({
this.loaded = false,
this.url = '',
this.title = '',
});
LinkState copyWith({bool? loaded, String? url}) {
return LinkState(loaded: loaded ?? this.loaded, url: url ?? this.url);
LinkState copyWith({
bool? loaded,
String? url,
String? title,
}) {
return LinkState(
loaded: loaded ?? this.loaded,
url: url ?? this.url,
title: title ?? this.title,
);
}
@override
// TODO: implement props
List<Object?> get props => [loaded, url];
List<Object?> get props => [
loaded,
url,
title,
];
}
class LinkCubit extends Cubit<LinkState> {
......@@ -32,16 +47,24 @@ class LinkCubit extends Cubit<LinkState> {
NavigationDelegate(
onUrlChange: (UrlChange url) {},
onPageStarted: (String url) {},
onPageFinished: (String url) {
onPageFinished: (String url) async {
_controller.runJavaScript(
'document.querySelector("meta[name=viewport]").setAttribute("content", "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no")',
);
// 如果 state.title 为空,则读取网页的 title
if (state.title.isEmpty) {
final pageTitle = await _controller.runJavaScriptReturningResult('document.title') as String?;
// 移除可能存在的引号
final cleanTitle = pageTitle?.replaceAll('"', '');
emit(state.copyWith(title: cleanTitle ?? ''));
}
_finishLoading();
},
),
)
..loadRequest(Uri.parse(state.url));
..loadRequest(Uri.parse(state.url));
}
void _finishLoading() {
......
......@@ -3,6 +3,7 @@ import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart';
import 'package:appframe/data/repositories/wechat_auth_repository.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluwx/fluwx.dart';
import 'package:shared_preferences/shared_preferences.dart';
......@@ -11,24 +12,23 @@ class LoginMainState extends Equatable {
final bool agreed;
final bool showAgreed;
final bool loading;
final bool showSnackBar;
final String snackBarMsg;
const LoginMainState({
this.agreed = false,
this.showAgreed = false,
this.loading = false,
this.showSnackBar = false,
this.snackBarMsg = '',
});
@override
List<Object?> get props => [
agreed,
showAgreed,
loading,
];
LoginMainState copyWith({
bool? agreed,
bool? showAgreed,
bool? loading,
bool? showSnackBar,
String? snackBarMsg,
}) {
return LoginMainState(
agreed: agreed ?? this.agreed,
......@@ -36,16 +36,26 @@ class LoginMainState extends Equatable {
loading: loading ?? this.loading,
);
}
@override
List<Object?> get props => [
agreed,
showAgreed,
loading,
showSnackBar,
snackBarMsg,
];
}
class LoginMainCubit extends Cubit<LoginMainState> {
late final Fluwx _fluwx;
late final FluwxCancelable _fluwxCancelable;
late final WechatAuthRepository _wechatAuthRepository;
LoginMainCubit(super.initialState) {
_fluwx = getIt.get<Fluwx>();
_fluwx.addSubscriber(_responseListener);
_wechatAuthRepository = getIt<WechatAuthRepository>();
_fluwxCancelable = _fluwx.addSubscriber(_responseListener);
_wechatAuthRepository = getIt.get<WechatAuthRepository>();
}
void toggleAgreed(bool value) {
......@@ -67,12 +77,14 @@ class LoginMainCubit extends Cubit<LoginMainState> {
return;
}
var result = await _fluwx.authBy(
var authResult = await _fluwx.authBy(
which: NormalAuth(scope: 'snsapi_userinfo', state: 'wechat_sdk_test'),
);
if (!result) {
throw Exception('微信授权处理失败');
if (!authResult) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '微信授权处理失败'));
emit(state.copyWith(showSnackBar: false));
return;
}
// 控制显示加载框
......@@ -83,6 +95,10 @@ class LoginMainCubit extends Cubit<LoginMainState> {
router.go('/loginPhone');
}
void goLoginQr() {
router.go('/loginQr');
}
void _responseListener(WeChatResponse response) async {
if (response is WeChatAuthResponse) {
if (response.code == null || response.code == '') {
......@@ -90,23 +106,86 @@ class LoginMainCubit extends Cubit<LoginMainState> {
return;
}
dynamic resultData = await _wechatAuthRepository.codeToSk(response.code!);
var resultData = await _wechatAuthRepository.codeToSk(response.code!) as Map<String, dynamic>?;
// 请求接口异常
if (resultData == null) {
emit(state.copyWith(loading: false, showSnackBar: true, snackBarMsg: '登录请求处理失败'));
emit(state.copyWith(showSnackBar: false));
return;
}
// 状态码错误
if (resultData['resultCode'] != '001') {
emit(state.copyWith(loading: false, showSnackBar: true, snackBarMsg: '登录请求状态失败'));
emit(state.copyWith(showSnackBar: false));
return;
}
var data = resultData['data'];
var role = data['roles'][0];
var data = resultData['data'] as Map<String, dynamic>;
var roles = data['roles'];
// 过滤出家长角色的数据
roles.removeWhere((element) => element['userType'] != 2);
final sessionCode = data['sessionCode'];
final userCode = data['userCode'];
final classCode = role['classCode'];
final userType = role['userType'];
final stuId = role['stuId'];
var sessionCode = data['sessionCode'];
var userCode = data['userCode'];
var classCode = '';
var userType = 0;
var stuId = '';
var sharedPreferences = getIt.get<SharedPreferences>();
if (roles.isNotEmpty) {
var role = roles[0];
classCode = role['classCode'];
userType = role['userType'];
stuId = role['stuId'];
// 将角色中的班级数据处理后,进行缓存
List<String> classIdList = [];
for (var role in roles) {
classIdList.add(role['classCode'] as String);
}
debugPrint('classCodeIds:-------------- $classIdList');
sharedPreferences.setStringList(Constant.classIdSetKey, classIdList);
} else {
sharedPreferences.setStringList(Constant.classIdSetKey, []);
}
var preUserCode = sharedPreferences.getString('pre_userCode') ?? '';
if (userCode != preUserCode) {
// 新用户登录
sharedPreferences.setString('pre_userCode', userCode);
sharedPreferences.setString('pre_classCode', classCode);
sharedPreferences.setInt('pre_userType', userType);
sharedPreferences.setString('pre_stuId', stuId);
} else {
// 前一个登录用户重新登录
var preClassCode = sharedPreferences.getString('pre_classCode') ?? '';
var preUserType = sharedPreferences.getInt('pre_userType') ?? 0;
var preStuId = sharedPreferences.getString('pre_stuId') ?? '';
if (preClassCode != '' &&
roles.any((element) =>
element['classCode'] == preClassCode &&
element['userType'] == preUserType &&
element['stuId'] == preStuId)) {
classCode = preClassCode;
userType = preUserType;
stuId = preStuId;
} else {
sharedPreferences.setString('pre_userCode', userCode);
sharedPreferences.setString('pre_classCode', classCode);
sharedPreferences.setInt('pre_userType', userType);
sharedPreferences.setString('pre_stuId', stuId);
}
}
sharedPreferences.setString('auth_sessionCode', sessionCode);
sharedPreferences.setString('auth_userCode', userCode);
sharedPreferences.setString('auth_classCode', classCode);
sharedPreferences.setInt('auth_userType', userType);
sharedPreferences.setString('auth_stuId', stuId ?? '');
sharedPreferences.setString('auth_stuId', stuId);
router.go(
'/web',
......@@ -123,7 +202,7 @@ class LoginMainCubit extends Cubit<LoginMainState> {
@override
Future<void> close() {
_fluwx.removeSubscriber(_responseListener);
_fluwxCancelable.cancel();
return super.close();
}
}
import 'dart:async';
import 'package:appframe/config/constant.dart';
import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart';
import 'package:appframe/data/repositories/phone_auth_repository.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:shared_preferences/shared_preferences.dart';
class LoginPhoneState extends Equatable {
final bool agreed;
final bool showAgreed;
final String phone;
final String password;
final bool showSnackBar;
final String snackBarMsg;
final bool allowSend;
final int seconds;
const LoginPhoneState(
{this.agreed = false,
this.showAgreed = false,
this.phone = '',
this.password = '',
this.allowSend = true,
this.seconds = 0});
const LoginPhoneState({
this.agreed = false,
this.showAgreed = false,
this.showSnackBar = false,
this.snackBarMsg = '',
this.allowSend = true,
this.seconds = 0,
});
LoginPhoneState copyWith({
bool? agreed,
bool? showAgreed,
String? phone,
String? password,
bool? showSnackBar,
String? snackBarMsg,
bool? allowSend,
int? seconds,
}) {
return LoginPhoneState(
agreed: agreed ?? this.agreed,
showAgreed: showAgreed ?? this.showAgreed,
phone: phone ?? this.phone,
password: password ?? this.password,
showSnackBar: showSnackBar ?? this.showSnackBar,
snackBarMsg: snackBarMsg ?? this.snackBarMsg,
allowSend: allowSend ?? this.allowSend,
seconds: seconds ?? this.seconds,
);
}
@override
List<Object?> get props => [phone, password, agreed, showAgreed, allowSend, seconds];
List<Object?> get props => [
agreed,
showAgreed,
showSnackBar,
snackBarMsg,
allowSend,
seconds,
];
}
class LoginPhoneCubit extends Cubit<LoginPhoneState> {
......@@ -49,6 +62,8 @@ class LoginPhoneCubit extends Cubit<LoginPhoneState> {
Timer? _timer;
int countdown = 60; // 倒计时秒数
late final PhoneAuthRepository _phoneAuthRepository;
TextEditingController get phoneController => _phoneController;
TextEditingController get codeController => _codeController;
......@@ -57,8 +72,10 @@ class LoginPhoneCubit extends Cubit<LoginPhoneState> {
_phoneController = TextEditingController();
_codeController = TextEditingController();
_phoneController.text = state.phone;
_codeController.text = state.password;
_phoneController.text = '';
_codeController.text = '';
_phoneAuthRepository = getIt.get<PhoneAuthRepository>();
}
/// 开始倒计时
......@@ -78,33 +95,174 @@ class LoginPhoneCubit extends Cubit<LoginPhoneState> {
}
/// 发送验证码
void sendVerificationCode() {
Future<void> sendVerificationCode() async {
if (state.allowSend) {
// 实际发送验证码的逻辑
startCountdown(); // 开始倒计时
// 验证手机号码
String phone = _phoneController.text;
if (!RegExp(r'^1[3-9][0-9]{9}$').hasMatch(phone)) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '请输入正确的手机号码'));
emit(state.copyWith(showSnackBar: false));
return;
}
// 发送验证码
var result = await _phoneAuthRepository.verifyCode(phone, 0);
if (result['code'] != 0) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: result['error']));
emit(state.copyWith(showSnackBar: false));
return;
}
emit(state.copyWith(allowSend: false, seconds: 60));
// 开始倒计时
startCountdown();
}
}
Future<void> phoneAuth() async {
String phone = _phoneController.text;
String verifyCode = _codeController.text;
if (!RegExp(r'^1[3-9][0-9]{9}$').hasMatch(phone)) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '请输入正确的手机号码'));
emit(state.copyWith(showSnackBar: false));
return;
}
if (!RegExp(r'^\d{4}$').hasMatch(verifyCode)) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '请输入正确的验证码'));
emit(state.copyWith(showSnackBar: false));
return;
}
if (!state.agreed) {
emit(state.copyWith(showAgreed: true));
return;
}
var resultData = await _phoneAuthRepository.login(phone, verifyCode);
if (resultData == null) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '登录请求失败'));
emit(state.copyWith(showSnackBar: false));
return;
}
if (resultData['code'] != 0) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: resultData['error']));
emit(state.copyWith(showSnackBar: false));
return;
}
var data = resultData['data'] as Map<String, dynamic>;
var roles = data['roles'];
// 过滤出家长角色的数据
roles.removeWhere((element) => element['userType'] != 2);
var sessionCode = data['sessionCode'];
var userCode = data['userCode'];
var classCode = '';
var userType = 0;
var stuId = '';
var sharedPreferences = getIt.get<SharedPreferences>();
if (roles.isNotEmpty) {
var role = roles[0];
classCode = role['classCode'];
userType = role['userType'];
stuId = role['stuId'];
// 将角色中的班级数据处理后,进行缓存
List<String> classIdList = [];
for (var role in roles) {
classIdList.add(role['classCode'] as String);
}
debugPrint('classCodeIds:-------------- $classIdList');
sharedPreferences.setStringList(Constant.classIdSetKey, classIdList);
} else {
sharedPreferences.setStringList(Constant.classIdSetKey, []);
}
var preUserCode = sharedPreferences.getString('pre_userCode') ?? '';
if (userCode != preUserCode) {
// 新用户登录
sharedPreferences.setString('pre_userCode', userCode);
sharedPreferences.setString('pre_classCode', classCode);
sharedPreferences.setInt('pre_userType', userType);
sharedPreferences.setString('pre_stuId', stuId);
} else {
// 前一个登录用户重新登录
var preClassCode = sharedPreferences.getString('pre_classCode') ?? '';
var preUserType = sharedPreferences.getInt('pre_userType') ?? 0;
var preStuId = sharedPreferences.getString('pre_stuId') ?? '';
if (preClassCode != '' &&
roles.any((element) =>
element['classCode'] == preClassCode &&
element['userType'] == preUserType &&
element['stuId'] == preStuId)) {
classCode = preClassCode;
userType = preUserType;
stuId = preStuId;
} else {
sharedPreferences.setString('pre_userCode', userCode);
sharedPreferences.setString('pre_classCode', classCode);
sharedPreferences.setInt('pre_userType', userType);
sharedPreferences.setString('pre_stuId', stuId);
}
}
sharedPreferences.setString('auth_sessionCode', sessionCode);
sharedPreferences.setString('auth_userCode', userCode);
sharedPreferences.setString('auth_classCode', classCode);
sharedPreferences.setInt('auth_userType', userType);
sharedPreferences.setString('auth_stuId', stuId);
router.go(
'/web',
extra: {
'sessionCode': sessionCode,
'userCode': userCode,
'classCode': classCode,
'userType': userType,
'stuId': stuId,
},
);
}
void toggleAgreed(bool value) {
emit(state.copyWith(agreed: value));
}
void confirmAgreed() {
emit(state.copyWith(agreed: true, showAgreed: false));
phoneAuth();
}
void cancelAgreed() {
emit(state.copyWith(showAgreed: false));
}
void goLoginMain() {
router.go('/loginMain');
}
void goLoginQr() {
router.go('/loginQr');
}
@override
Future<void> close() async {
try {
_phoneController.dispose();
} catch (e) {
print(e);
debugPrint('Error disposing controller: $e');
}
try {
_codeController.dispose();
} catch (e) {
print(e);
debugPrint('Error disposing controller: $e');
}
await super.close();
......
import 'dart:convert';
import 'dart:typed_data';
import 'package:appframe/config/constant.dart';
import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart';
import 'package:appframe/data/repositories/wechat_auth_repository.dart';
import 'package:crypto/crypto.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluwx/fluwx.dart';
import 'package:shared_preferences/shared_preferences.dart';
class LoginQrState extends Equatable {
final int status;
final Uint8List? image;
final String tip;
final bool showSnackBar;
final String snackBarMsg;
const LoginQrState({
this.status = 0,
this.image,
this.tip = '',
this.showSnackBar = false,
this.snackBarMsg = '',
});
LoginQrState copyWith({
int? status,
Uint8List? image,
String? tip,
bool? showSnackBar,
String? snackBarMsg,
}) {
return LoginQrState(
status: status ?? this.status,
image: image ?? this.image,
tip: tip ?? this.tip,
showSnackBar: showSnackBar ?? this.showSnackBar,
snackBarMsg: snackBarMsg ?? this.snackBarMsg,
);
}
@override
List<Object?> get props => [
status,
image,
tip,
showSnackBar,
snackBarMsg,
];
}
class LoginQrCubit extends Cubit<LoginQrState> {
late final Fluwx _fluwx;
late final FluwxCancelable _fluwxCancelable;
late final WechatAuthRepository _wechatAuthRepository;
LoginQrCubit(super.initialState) {
_fluwx = getIt.get<Fluwx>();
_fluwxCancelable = _fluwx.addSubscriber(_responseListener);
_wechatAuthRepository = getIt.get<WechatAuthRepository>();
init();
}
Future<void> init() async {
// sdk_ticket
var resultData = await _wechatAuthRepository.getTicket() as Map<String, dynamic>?;
// 请求接口异常
if (resultData == null) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '生成二维码失败'));
emit(state.copyWith(showSnackBar: false));
return;
}
// 状态码错误
if (resultData['resultCode'] != '001') {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '生成二维码状态错误'));
emit(state.copyWith(showSnackBar: false));
return;
}
var sdkTicket = resultData['data'];
// 当前时间戳
var timestamp = DateTime.now().millisecondsSinceEpoch;
var signature = _sig(Constant.wxAppId, '$timestamp', sdkTicket, '$timestamp');
var authResult = await _fluwx.authBy(
which: QRCode(
appId: Constant.wxAppId,
scope: 'snsapi_userinfo',
nonceStr: '$timestamp',
timestamp: '$timestamp',
signature: signature,
),
);
if (!authResult) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '请求微信失败'));
emit(state.copyWith(showSnackBar: false));
return;
}
}
void _responseListener(WeChatResponse response) async {
if (response is WeChatAuthGotQRCodeResponse) {
int? errCode = response.errCode;
if (errCode != null && errCode == 0) {
emit(state.copyWith(
status: 1,
image: response.qrCode,
tip: '打开微信,扫描二维码登录',
));
} else {
emit(state.copyWith(tip: '错误 $errCode'));
}
} else if (response is WeChatQRCodeScannedResponse) {
int? errCode = response.errCode;
if (errCode != null && errCode == 0) {
emit(state.copyWith(
status: 2,
tip: '在微信中轻触允许即可登录',
));
} else {
emit(state.copyWith(tip: '错误 $errCode'));
}
} else if (response is WeChatAuthByQRCodeFinishedResponse) {
int? errCode = response.errCode;
if (errCode != null && errCode == 0) {
_doLogin(response.authCode!);
} else {
emit(state.copyWith(
status: 3,
tip: '你已取消此次登录',
));
}
}
}
String _sig(String appId, String nonceStr, String sdkTicket, String timestamp) {
String str = 'appid=$appId&noncestr=$nonceStr&sdk_ticket=$sdkTicket&timestamp=$timestamp';
return sha1.convert(utf8.encode(str)).toString();
}
Future<void> _doLogin(String code) async {
var resultData = await _wechatAuthRepository.codeToSk(code) as Map<String, dynamic>?;
// 请求接口异常
if (resultData == null) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '登录请求处理失败'));
emit(state.copyWith(showSnackBar: false));
return;
}
// 状态码错误
if (resultData['resultCode'] != '001') {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '登录请求状态失败'));
emit(state.copyWith(showSnackBar: false));
return;
}
var data = resultData['data'] as Map<String, dynamic>;
var roles = data['roles'];
// 过滤出家长角色的数据
roles.removeWhere((element) => element['userType'] != 2);
var sessionCode = data['sessionCode'];
var userCode = data['userCode'];
var classCode = '';
var userType = 0;
var stuId = '';
var sharedPreferences = getIt.get<SharedPreferences>();
if (roles.isNotEmpty) {
var role = roles[0];
classCode = role['classCode'];
userType = role['userType'];
stuId = role['stuId'];
// 将角色中的班级数据处理后,进行缓存
List<String> classIdList = [];
for (var role in roles) {
classIdList.add(role['classCode'] as String);
}
debugPrint('classCodeIds:-------------- $classIdList');
sharedPreferences.setStringList(Constant.classIdSetKey, classIdList);
} else {
sharedPreferences.setStringList(Constant.classIdSetKey, []);
}
var preUserCode = sharedPreferences.getString('pre_userCode') ?? '';
if (userCode != preUserCode) {
// 新用户登录
sharedPreferences.setString('pre_userCode', userCode);
sharedPreferences.setString('pre_classCode', classCode);
sharedPreferences.setInt('pre_userType', userType);
sharedPreferences.setString('pre_stuId', stuId);
} else {
// 前一个登录用户重新登录
var preClassCode = sharedPreferences.getString('pre_classCode') ?? '';
var preUserType = sharedPreferences.getInt('pre_userType') ?? 0;
var preStuId = sharedPreferences.getString('pre_stuId') ?? '';
if (preClassCode != '' &&
roles.any((element) =>
element['classCode'] == preClassCode &&
element['userType'] == preUserType &&
element['stuId'] == preStuId)) {
classCode = preClassCode;
userType = preUserType;
stuId = preStuId;
} else {
sharedPreferences.setString('pre_userCode', userCode);
sharedPreferences.setString('pre_classCode', classCode);
sharedPreferences.setInt('pre_userType', userType);
sharedPreferences.setString('pre_stuId', stuId);
}
}
sharedPreferences.setString('auth_sessionCode', sessionCode);
sharedPreferences.setString('auth_userCode', userCode);
sharedPreferences.setString('auth_classCode', classCode);
sharedPreferences.setInt('auth_userType', userType);
sharedPreferences.setString('auth_stuId', stuId);
router.go(
'/web',
extra: {
'sessionCode': sessionCode,
'userCode': userCode,
'classCode': classCode,
'userType': userType,
'stuId': stuId,
},
);
}
void goLoginMain() {
router.go('/loginMain');
}
@override
Future<void> close() {
_fluwxCancelable.cancel();
_fluwx.stopAuthByQRCode();
return super.close();
}
}
import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart';
import 'package:appframe/data/repositories/phone_auth_repository.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AccountState extends Equatable {
final bool loaded;
final String name;
final String phone;
final String nickname;
final String imgIcon;
const AccountState({
this.loaded = false,
this.name = '',
this.phone = '',
this.nickname = '',
this.imgIcon = '',
});
AccountState copyWith({
bool? loaded,
String? name,
String? phone,
String? nickname,
String? imgIcon,
}) {
return AccountState(
loaded: loaded ?? this.loaded,
name: name ?? this.name,
phone: phone ?? this.phone,
nickname: nickname ?? this.nickname,
imgIcon: imgIcon ?? this.imgIcon,
);
}
@override
List<Object?> get props => [
loaded,
name,
phone,
nickname,
imgIcon,
];
}
class AccountCubit extends Cubit<AccountState> {
late final PhoneAuthRepository _phoneAuthRepository;
AccountCubit(super.initialState) {
_phoneAuthRepository = getIt.get<PhoneAuthRepository>();
init();
}
Future<void> init() async {
var sharedPreferences = getIt.get<SharedPreferences>();
var userCode = sharedPreferences.getString('auth_userCode') ?? '';
try {
var result = await _phoneAuthRepository.bindCheck(userCode);
var code = result['code'];
var data = result['data'];
if (code != 0) {
return;
}
emit(
state.copyWith(
loaded: true,
name: data['name'],
phone: data['phone'],
nickname: data['nickname'],
imgIcon: data['imgIcon'],
),
);
} catch (e) {
print(e);
}
}
Future<void> goBind() async {
String? result = await router.push(
'/account/phone',
extra: {
'phone': state.phone,
},
);
if (result != null && result.isNotEmpty) {
emit(state.copyWith(phone: result));
}
}
}
import 'dart:async';
import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart';
import 'package:appframe/data/repositories/phone_auth_repository.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AccountPhoneState extends Equatable {
final String phone;
final bool allowBind;
final bool showSnackBar;
final String snackBarMsg;
final bool allowSend;
final int seconds;
const AccountPhoneState({
this.phone = '',
this.allowBind = false,
this.showSnackBar = false,
this.snackBarMsg = '',
this.allowSend = true,
this.seconds = 0,
});
AccountPhoneState copyWith({
String? phone,
bool? allowBind,
bool? showSnackBar,
String? snackBarMsg,
bool? allowSend,
int? seconds,
}) {
return AccountPhoneState(
phone: phone ?? this.phone,
allowBind: allowBind ?? this.allowBind,
showSnackBar: showSnackBar ?? this.showSnackBar,
snackBarMsg: snackBarMsg ?? this.snackBarMsg,
allowSend: allowSend ?? this.allowSend,
seconds: seconds ?? this.seconds,
);
}
@override
List<Object?> get props => [
phone,
allowBind,
showSnackBar,
snackBarMsg,
allowSend,
seconds,
];
}
class AccountPhoneCubit extends Cubit<AccountPhoneState> {
late TextEditingController _phoneController;
late TextEditingController _codeController;
Timer? _timer;
int countdown = 60; // 倒计时秒数
late final PhoneAuthRepository _phoneAuthRepository;
TextEditingController get phoneController => _phoneController;
TextEditingController get codeController => _codeController;
AccountPhoneCubit(super.initialState) {
_phoneController = TextEditingController();
_codeController = TextEditingController();
_phoneController.text = '';
_codeController.text = '';
_phoneAuthRepository = getIt.get<PhoneAuthRepository>();
}
void change(){
emit(state.copyWith(allowBind: true));
}
/// 开始倒计时
void startCountdown() {
countdown = 60;
emit(state.copyWith(allowSend: false, seconds: countdown));
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
countdown--;
if (countdown <= 0) {
_timer?.cancel();
emit(state.copyWith(allowSend: true, seconds: 60));
} else {
emit(state.copyWith(seconds: countdown));
}
});
}
/// 发送验证码
Future<void> sendVerificationCode() async {
if (state.allowSend) {
// 验证手机号码
String phone = _phoneController.text;
if (!RegExp(r'^1[3-9][0-9]{9}$').hasMatch(phone)) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '请输入正确的手机号码'));
emit(state.copyWith(showSnackBar: false));
return;
}
// 发送验证码
var result = await _phoneAuthRepository.verifyCode(phone, 1);
if (result['code'] != 0) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: result['error']));
emit(state.copyWith(showSnackBar: false));
return;
}
emit(state.copyWith(allowSend: false, seconds: 60));
// 开始倒计时
startCountdown();
}
}
Future<void> bind() async {
String phone = _phoneController.text;
String verifyCode = _codeController.text;
if (!RegExp(r'^1[3-9][0-9]{9}$').hasMatch(phone)) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '请输入正确的手机号码'));
emit(state.copyWith(showSnackBar: false));
return;
}
if (!RegExp(r'^\d{4}$').hasMatch(verifyCode)) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '请输入正确的验证码'));
emit(state.copyWith(showSnackBar: false));
return;
}
var sharedPreferences = getIt.get<SharedPreferences>();
var userCode = sharedPreferences.getString('auth_userCode') ?? '';
var result = await _phoneAuthRepository.bind(userCode, phone, verifyCode);
if (result['code'] != 0) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: result['error']));
emit(state.copyWith(showSnackBar: false));
return;
}
// 绑定成功,返回手机号码
router.pop(phone);
}
@override
Future<void> close() async {
try {
_phoneController.dispose();
} catch (e) {
print(e);
}
try {
_codeController.dispose();
} catch (e) {
print(e);
}
await super.close();
}
}
import 'package:appframe/config/env_config.dart';
class Constant {
/// local server 相关
/// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
/// 应用内部 http 服务
static const int localServerPort = 35982;
///
// static const String localServerHost = 'appdev-xj.banxiaoer.net';
static const String localServerHost = '127.0.0.1';
static const String localServerUrl = 'http://$localServerHost:$localServerPort';
static int localServerPort = 35982;
static const localServerPortOption = [35982, 35983, 35984];
static String localServerUrl = 'http://$localServerHost:$localServerPort';
static const String localFileUrl = 'http://127.0.0.1:$localServerPort';
static String localFileUrl = 'http://127.0.0.1:$localServerPort';
static const String localServerTemp = '/temp';
static const String localServerTempFileUrl = '$localFileUrl$localServerTemp';
static String localServerTempFileUrl = '$localFileUrl$localServerTemp';
static const String localServerTest = '/test';
static const String localServerTestFileUrl = '$localFileUrl$localServerTest';
static String localServerTestFileUrl = '$localFileUrl$localServerTest';
/// obs 相关
/// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
/// obs文件分片上传的分片大小:5M
static const int obsUploadChunkSize = 1024 * 1024 * 5;
/// 版本号
static const String appVersion = '1.0.0';
static const String h5Version = '1.0.0';
/// obs文件上传的逻辑前缀
static const String obsLogicPrefix = EnvConfig.env == 'dev' ? 'd2/pridel/user/' : 'p2/unpridel/user/';
// 定义obs存储业务上的关键业务类型,属于这种类型的业务,在存储上区分其分属于何种删除规则
static const List<String> obsPridelFileConfigs = [
'homework',
'clockin',
'clock',
'clazzclock',
'clockinQcard',
'recite',
'aloud',
'hurdle',
'tbx',
'txbb',
'dictation',
'xegd',
'kouyu'
];
/// 版本相关
/// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
/// app 版本号规则
static const String appVersion = EnvConfig.version;
/// H5的起始终最低版本号规则
static String h5Version = '0.0.0';
/// H5的版本号存储的key
static const String h5VersionKey = 'h5_version';
/// 用于显示的H5版本号存储的key
static const String h5ShowVersionKey = 'h5_show_version';
/// H5版本号配置文件地址
static const String configUrl = 'https://bxe-obs.banxiaoer.com/conf/xeapp_conf_dev.json';
static const String configUrl = EnvConfig.env == 'dev'
? 'https://bxe-obs.banxiaoer.com/fronts/material/xehybrid/assets/develop/xeapp_conf_dev.json'
// ? 'https://bxe-obs.banxiaoer.com/conf/xeapp_conf_dev.json'
// ? 'http://192.168.2.177/xeapp_conf_dev.json'
: 'https://bxe-obs.banxiaoer.com/conf/xeapp_conf_pro.json';
/// 内部 H5 dist 目录
static const String h5DistDir = 'http_dist_assets';
/// BASE URL 相关
/// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
static const String bxeBaseUrl = EnvConfig.env == 'dev' ? 'https://dev.banxiaoer.net' : 'https://bxr.banxiaoer.com';
static const String iotAppBaseUrl =
EnvConfig.env == 'dev' ? 'https://iotapp-dev.banxiaoer.com/iotapp' : 'https://iotapp.banxiaoer.com/iotapp';
/// 微信登录相关
/// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
static const String wxAppId = 'wx8c32ea248f0c7765';
static const String universalLink = 'https://dev.banxiaoer.net/path/to/wechat/';
/// IM 相关
/// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
/// IM SDK
static const int imSdkAppId = 1400310691;
static const String imClientSecure = 'kM4yqbehB3io9UiLvH6eHvM7xAhfYxoyyaO1tLoHgKltcaI7MZXkUbpFaWdeQIqe';
static const int imSdkAppId = EnvConfig.env == 'dev' ? 1400310691 : 1600117207;
static const String imClientSecure = EnvConfig.env == 'dev'
? 'kM4yqbehB3io9UiLvH6eHvM7xAhfYxoyyaO1tLoHgKltcaI7MZXkUbpFaWdeQIqe'
: 'GkMkhAnrCThYrZxApCBdFidcAC8USwVnhoqMGzqmSvmcegRCvETtDR2Te9btarnG';
/// Key
/// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
static const String classIdSetKey = 'auth_class_ids';
/// 测试阶段使用
static const bool needIM = true;
static const bool needIM = false;
static const bool needUpgrade = true;
}
class EnvConfig {
static const String env = String.fromEnvironment('env', defaultValue: 'dev');
static const String version = String.fromEnvironment('version', defaultValue: '0.0.0');
static bool isDev() {
return env == 'dev';
}
}
import 'dart:io' show Platform;
import 'package:appframe/config/constant.dart';
import 'package:appframe/data/repositories/message/app_info_handler.dart';
import 'package:appframe/data/repositories/message/audio_player_handler.dart';
import 'package:appframe/data/repositories/message/audio_recorder_handler.dart';
......@@ -19,9 +20,11 @@ import 'package:appframe/data/repositories/message/open_document_handler.dart';
import 'package:appframe/data/repositories/message/open_link_handler.dart';
import 'package:appframe/data/repositories/message/open_weapp_handler.dart';
import 'package:appframe/data/repositories/message/orientation_handler.dart';
import 'package:appframe/data/repositories/message/role_info_handler.dart';
import 'package:appframe/data/repositories/message/save_file_to_disk_handler.dart';
import 'package:appframe/data/repositories/message/save_to_album_handler.dart';
import 'package:appframe/data/repositories/message/scan_code_handler.dart';
import 'package:appframe/data/repositories/message/share_handler.dart';
import 'package:appframe/data/repositories/message/share_to_wx_handler.dart';
import 'package:appframe/data/repositories/message/storage_handler.dart';
import 'package:appframe/data/repositories/message/title_bar_handler.dart';
......@@ -30,6 +33,7 @@ import 'package:appframe/data/repositories/message/vibrate_short_handler.dart';
import 'package:appframe/data/repositories/message/video_info_handler.dart';
import 'package:appframe/data/repositories/message/wifi_info_handler.dart';
import 'package:appframe/data/repositories/message/window_info_handler.dart';
import 'package:appframe/data/repositories/phone_auth_repository.dart';
import 'package:appframe/data/repositories/wechat_auth_repository.dart';
import 'package:appframe/services/api_service.dart';
import 'package:appframe/services/dispatcher.dart';
......@@ -50,8 +54,8 @@ Future<void> setupLocator() async {
Fluwx fluwx = Fluwx();
if (Platform.isAndroid || Platform.isIOS) {
await fluwx.registerApi(
appId: "wx8c32ea248f0c7765",
universalLink: "https://dev.banxiaoer.net/path/to/wechat/",
appId: Constant.wxAppId,
universalLink: Constant.universalLink,
);
}
return fluwx;
......@@ -68,6 +72,9 @@ Future<void> setupLocator() async {
/// 打开小程序
getIt.registerLazySingleton<MessageHandler>(() => OpenWeappHandler(), instanceName: 'openWeapp');
/// 分享微信小程序卡片
getIt.registerLazySingleton<MessageHandler>(() => ShareHandler(), instanceName: 'share');
/// 分享微信会话
getIt.registerLazySingleton<MessageHandler>(() => ShareToWxHandler(), instanceName: 'sharetowx');
......@@ -154,6 +161,7 @@ Future<void> setupLocator() async {
getIt.registerLazySingleton<MessageHandler>(() => AudioPauseHandler(), instanceName: 'audioPause');
getIt.registerLazySingleton<MessageHandler>(() => AudioResumeHandler(), instanceName: 'audioResume');
getIt.registerLazySingleton<MessageHandler>(() => AudioSeekHandler(), instanceName: 'audioSeek');
getIt.registerLazySingleton<MessageHandler>(() => AudioPlayRateHandler(), instanceName: 'audioPlayRate');
getIt.registerLazySingleton<MessageHandler>(() => AudioStopHandler(), instanceName: 'audioStop');
getIt.registerLazySingleton<MessageHandler>(() => AudioClearHandler(), instanceName: 'audioClear');
......@@ -176,11 +184,14 @@ Future<void> setupLocator() async {
getIt.registerLazySingleton<MessageHandler>(() => TitleBarHandler(), instanceName: 'setTitlebar');
/// 新路由打开链接
getIt.registerLazySingleton<MessageHandler>(() => OpenLinkHandler(), instanceName: 'openlink');
getIt.registerLazySingleton<MessageHandler>(() => OpenLinkHandler(), instanceName: 'openLink');
/// 登录
getIt.registerLazySingleton<MessageHandler>(() => GoLoginHandler(), instanceName: 'goLogin');
/// 设置用户角色信息
getIt.registerLazySingleton<MessageHandler>(() => RoleInfoHandler(), instanceName: 'setRoleInfo');
/// service
///
/// local server
......@@ -188,11 +199,11 @@ Future<void> setupLocator() async {
/// apiService
getIt.registerLazySingleton<ApiService>(
() => ApiService(baseUrl: 'https://dev.banxiaoer.net'),
() => ApiService(baseUrl: Constant.bxeBaseUrl),
instanceName: "bxeApiService",
);
getIt.registerLazySingleton<ApiService>(
() => ApiService(baseUrl: 'https://iotapp-dev.banxiaoer.com/iotapp'),
() => ApiService(baseUrl: Constant.iotAppBaseUrl),
instanceName: "appApiService",
);
......@@ -207,4 +218,5 @@ Future<void> setupLocator() async {
/// repository
getIt.registerLazySingleton<WechatAuthRepository>(() => WechatAuthRepository());
getIt.registerLazySingleton<PhoneAuthRepository>(() => PhoneAuthRepository());
}
import 'dart:io';
import 'package:appframe/bloc/web_cubit.dart';
import 'package:appframe/ui/pages/adv_page.dart';
import 'package:appframe/ui/pages/im_page.dart';
import 'package:appframe/ui/pages/link_page.dart';
import 'package:appframe/ui/pages/login_main_page.dart';
import 'package:appframe/ui/pages/login_phone_page.dart';
import 'package:appframe/ui/pages/login_qr_page.dart';
import 'package:appframe/ui/pages/reload_page.dart';
import 'package:appframe/ui/pages/scan_code_page.dart';
import 'package:appframe/ui/pages/setting/account_page.dart';
import 'package:appframe/ui/pages/setting/account_phone_page.dart';
import 'package:appframe/ui/pages/web_page.dart';
import 'package:appframe/ui/widgets/ios_edge_swipe_detector.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
final GoRouter router = GoRouter(
initialLocation: '/web',
observers: Platform.isIOS ? [AppRouteObserver()] : [],
routes: <RouteBase>[
GoRoute(
path: '/web',
......@@ -42,6 +51,24 @@ final GoRouter router = GoRouter(
},
),
GoRoute(
path: '/loginQr',
builder: (BuildContext context, GoRouterState state) {
return const LoginQrPage();
},
),
GoRoute(
path: '/account',
builder: (BuildContext context, GoRouterState state) {
return const AccountPage();
},
),
GoRoute(
path: '/account/phone',
builder: (BuildContext context, GoRouterState state) {
return const AccountPhonePage();
},
),
GoRoute(
path: '/adv',
builder: (BuildContext context, GoRouterState state) {
return const AdvPage();
......@@ -53,5 +80,80 @@ final GoRouter router = GoRouter(
return const ImPage();
},
),
GoRoute(
path: '/reload',
builder: (BuildContext context, GoRouterState state) {
return const ReloadPage();
},
),
],
);
///
/// 只针对iOS使用
///
class AppRouteObserver extends NavigatorObserver {
@override
void didPush(Route route, Route? previousRoute) {
super.didPush(route, previousRoute);
if (route.settings.name == '/web') {
// push时,当前路由为 /web,代表 /web 路由被push进栈,展示web页面
// 设置手势监听回调
debugPrint("设置监听--------");
IosEdgeSwipeDetector.onEdgeSwipe(
() {
WebCubitHolder.instance?.handleBack();
},
);
} else if (previousRoute?.settings.name == '/web') {
// push时,前一个路由是 /web,代表是从web页进入此页面
// 将手势监听回调取消
debugPrint("取消监听--------");
IosEdgeSwipeDetector.dispose();
}
}
@override
void didPop(Route route, Route? previousRoute) {
super.didPop(route, previousRoute);
if (previousRoute?.settings.name == '/web') {
// Pop时, 前一个路由是/web,代表回到web页面
// 设置手势监听回调
debugPrint("设置监听--------");
IosEdgeSwipeDetector.onEdgeSwipe(
() {
WebCubitHolder.instance?.handleBack();
},
);
}
}
@override
void didRemove(Route route, Route? previousRoute) {
super.didRemove(route, previousRoute);
if (route.settings.name == '/web') {
// remove时, 当前路由为 /web, 代表 /web 路由被删除,展示的不是web页面
// 将手势监听回调取消
debugPrint("取消监听--------");
IosEdgeSwipeDetector.dispose();
}
}
}
///
/// 只针对iOS使用
///
class WebCubitHolder {
static WebCubit? instance;
static void register(WebCubit cubit) {
instance = cubit;
}
static void unregister() {
instance = null;
}
}
import 'package:appframe/bloc/web_cubit.dart';
import 'package:appframe/config/locator.dart';
import 'package:appframe/services/dispatcher.dart';
import 'package:appframe/services/player_service.dart';
import 'package:uuid/uuid.dart';
class AudioPlayHandler extends MessageHandler {
late WebCubit? _webCubit;
@override
Future<dynamic> handleMessage(dynamic params) async {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
try {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
final url = params['url'] as String;
final seek = params['seek'] as int? ?? 0;
// 暂时忽略
// final isBg = params['isBg'] as bool ?? false;
var playId = params['playId'] as String? ?? '';
if (playId.isEmpty) {
playId = Uuid().v4();
}
// 检测音量
_webCubit!.checkVolume();
final url = params['url'] as String;
final seek = params['seek'] as int? ?? 0;
// 暂时忽略
// final isBg = params['isBg'] as bool ?? false;
final playRate = (params['playRate'] as num?)?.toDouble() ?? 1.0;
var playerService = getIt.get<PlayerService>();
var result = await playerService.playAudio(url, seek, playId);
var playId = params['playId'] as String? ?? '';
if (playId.isEmpty) {
playId = Uuid().v4();
}
if (!result) {
throw Exception('播放错误');
var playerService = getIt.get<PlayerService>();
var result = await playerService.playAudio(url, seek, playId, playRate);
if (!result) {
throw Exception('播放错误');
}
return {'playId': playId};
} finally {
_unfollowCubit();
}
return {'playId': playId};
}
@override
void setCubit(WebCubit cubit) {
this._webCubit = cubit;
}
void _unfollowCubit() {
this._webCubit = null;
}
}
......@@ -39,7 +60,8 @@ class AudioPauseHandler extends MessageHandler {
class AudioResumeHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
return await getIt.get<PlayerService>().resumeAudio();
double? playRate = (params['playRate'] as num?)?.toDouble();
return await getIt.get<PlayerService>().resumeAudio(playRate);
}
}
......@@ -55,6 +77,20 @@ class AudioSeekHandler extends MessageHandler {
}
}
class AudioPlayRateHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
// var playId = params['playId'] as String? ?? '';
final playRate = (params['playRate'] as num?)?.toDouble() ?? 1.0;
return await getIt.get<PlayerService>().rateAudio(playRate);
}
}
class AudioStopHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
......
......@@ -3,7 +3,8 @@ import 'dart:io';
import 'package:appframe/config/constant.dart';
import 'package:appframe/services/dispatcher.dart';
import 'package:appframe/utils/file_type_util.dart';
import 'package:appframe/utils/thumbnail_util.dart';
import 'package:appframe/utils/image_util.dart';
import 'package:appframe/utils/video_util.dart';
import 'package:file_picker/file_picker.dart';
import 'package:image_size_getter/file_input.dart';
import 'package:image_size_getter/image_size_getter.dart';
......@@ -64,10 +65,9 @@ class ChooseFileHandler extends MessageHandler {
// 通过image_size_getter获取图片尺寸
var sizeResult = ImageSizeGetter.getSizeResult(FileInput(originalFile));
var size = sizeResult.size;
imgWidth = sizeResult.size.width;
imgHeight = sizeResult.size.height;
final tempDir = await getTemporaryDirectory();
imgThumbFilePath = await ThumbnailUtil.genTempThumbnail(originalFile, tempDir);
imgWidth = size.width;
imgHeight = size.height;
imgThumbFilePath = await ImageUtil.genTempThumbnail(originalFile.path);
}
double? videoWidth, videoHeight;
......@@ -82,7 +82,8 @@ class ChooseFileHandler extends MessageHandler {
controller.dispose();
final tempDir = await getTemporaryDirectory();
videoThumbFilePath = await ThumbnailUtil.genVideoThumbnail(originalFile.path, tempDir);
final thumbnailPath = '${tempDir.path}/video_thumb_${DateTime.now().millisecondsSinceEpoch}.jpg';
videoThumbFilePath = await VideoUtil.genVideoThumbnail(originalFile.path, thumbnailPath);
}
// 返回临时文件信息
......
......@@ -7,15 +7,22 @@ class OpenWeappHandler extends MessageHandler {
@override
Future<bool> handleMessage(params) async {
_fluwx = getIt.get<Fluwx>();
// _fluwx.addSubscriber(_responseListener);
if (!await _fluwx.isWeChatInstalled) {
throw Exception('设备上未安装微信App,不支持跳转打开微信小程序');
}
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
final appid = params['appid'] as String;
final path = params['path'] as String;
final path = params['path'] as String?;
final envVersion = params['envVersion'] as String?;
if (appid.isEmpty || path.isEmpty) {
if (appid.isEmpty) {
throw Exception('参数错误');
}
......@@ -23,17 +30,23 @@ class OpenWeappHandler extends MessageHandler {
throw Exception('参数错误');
}
_fluwx = getIt.get<Fluwx>();
// _fluwx.addSubscriber(_responseListener);
try {
return await _fluwx.open(
target: MiniProgram(
username: appid,
path: path,
miniProgramType: _getWXMiniProgramType(envVersion),
),
);
if (path != null) {
return await _fluwx.open(
target: MiniProgram(
username: appid,
path: path,
miniProgramType: _getWXMiniProgramType(envVersion),
),
);
} else {
return await _fluwx.open(
target: MiniProgram(
username: appid,
miniProgramType: _getWXMiniProgramType(envVersion),
),
);
}
} catch (e) {
print(e);
return false;
......@@ -58,7 +71,7 @@ class OpenWeappHandler extends MessageHandler {
case 'develop':
return WXMiniProgramType.test;
default:
return WXMiniProgramType.preview;
return WXMiniProgramType.release;
}
}
}
import 'package:appframe/config/locator.dart';
import 'package:appframe/services/dispatcher.dart';
import 'package:shared_preferences/shared_preferences.dart';
class RoleInfoHandler extends MessageHandler {
@override
Future<bool> handleMessage(params) async {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
final String userId = params['userId'] as String;
final String classCode = params['classCode'] as String;
final int userType = params['userType'] as int;
final String stuId = params['stuId'] as String ?? '';
var sharedPreferences = getIt.get<SharedPreferences>();
sharedPreferences.setString('auth_userCode', userId);
sharedPreferences.setString('auth_classCode', classCode);
sharedPreferences.setInt('auth_userType', userType);
sharedPreferences.setString('auth_stuId', stuId);
sharedPreferences.setString('pre_userCode', userId);
sharedPreferences.setString('pre_classCode', classCode);
sharedPreferences.setInt('pre_userType', userType);
sharedPreferences.setString('pre_stuId', stuId);
return true;
}
}
This diff is collapsed. Click to expand it.
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!