Skip to content
Toggle navigation
Toggle navigation
This project
Loading...
Sign in
ethan
/
appframe
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Network
Compare
Branches
Tags
Commit 128403df
authored
2025-10-15 10:02:26 +0800
by
tanghuan
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
dev
1 parent
59970d1c
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
622 additions
and
508 deletions
android/app/src/main/AndroidManifest.xml
android/app/src/main/res/xml/network_security_config.xml
assets/login.html
assets/test.html
assets/test.js
assets/test3.html
lib/bloc/web_cubit.dart
lib/bloc/wechat_auth_cubit.dart
lib/config/locator.dart
lib/config/server.dart
lib/data/repositories/message/choose_video_handler.dart
lib/data/repositories/message/go_login_handler.dart
lib/data/repositories/message/location_handler.dart
lib/data/repositories/message/recorder_handler.dart
lib/data/repositories/message/upgrade_handler.dart
lib/services/dispatcher.dart
lib/services/local_server_service.dart
lib/ui/pages/web_page.dart
android/app/src/main/AndroidManifest.xml
View file @
128403d
...
@@ -15,6 +15,9 @@
...
@@ -15,6 +15,9 @@
<uses-permission
android:name=
"android.permission.ACCESS_BACKGROUND_LOCATION"
/>
<uses-permission
android:name=
"android.permission.ACCESS_BACKGROUND_LOCATION"
/>
<uses-permission
android:name=
"android.permission.FOREGROUND_SERVICE_LOCATION"
/>
<uses-permission
android:name=
"android.permission.FOREGROUND_SERVICE_LOCATION"
/>
<uses-permission
android:name=
"android.permission.READ_MEDIA_IMAGES"
/>
<uses-permission
android:name=
"android.permission.READ_MEDIA_VIDEO"
/>
<application
<application
android:label=
"班小二"
android:label=
"班小二"
...
...
android/app/src/main/res/xml/network_security_config.xml
View file @
128403d
...
@@ -5,9 +5,11 @@
...
@@ -5,9 +5,11 @@
<domain
includeSubdomains=
"false"
>
127.0.0.1
</domain>
<domain
includeSubdomains=
"false"
>
127.0.0.1
</domain>
<domain
includeSubdomains=
"false"
>
localhost
</domain>
<domain
includeSubdomains=
"false"
>
localhost
</domain>
<domain
includeSubdomains=
"false"
>
192.168.2.59
</domain>
<domain
includeSubdomains=
"false"
>
192.168.2.59
</domain>
<domain
includeSubdomains=
"false"
>
192.168.2.215
</domain>
<domain
includeSubdomains=
"false"
>
192.168.2.177
</domain>
<domain
includeSubdomains=
"false"
>
192.168.2.177
</domain>
<domain
includeSubdomains=
"false"
>
192.168.1.136
</domain>
<domain
includeSubdomains=
"false"
>
192.168.1.136
</domain>
<domain
includeSubdomains=
"false"
>
localdev.banxiaoer.net
</domain>
<domain
includeSubdomains=
"false"
>
localdev.banxiaoer.net
</domain>
<domain
includeSubdomains=
"false"
>
appdev-th.banxiaoer.net
</domain>
<domain
includeSubdomains=
"false"
>
appdev-th.banxiaoer.net
</domain>
<domain
includeSubdomains=
"false"
>
appdev-xj.banxiaoer.net
</domain>
</domain-config>
</domain-config>
</network-security-config>
</network-security-config>
assets/login.html
0 → 100644
View file @
128403d
<!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
\ No newline at end of file
assets/test.html
View file @
128403d
...
@@ -9,6 +9,15 @@
...
@@ -9,6 +9,15 @@
<div
id=
"resp"
></div>
<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>
<button
onclick=
"clearResp()"
>
清除响应数据
</button>
<br>
<br>
...
@@ -26,7 +35,7 @@
...
@@ -26,7 +35,7 @@
<br>
<br>
<br>
<br>
<button
onclick=
"chooseImage(1)"
>
选择单个图片
</button>
<button
onclick=
"chooseImage(1)"
>
选择单个图片
</button>
<button
onclick=
"chooseMultipleImage()"
>
选择多个图片
</button>
<button
onclick=
"chooseMultipleImage(
1
)"
>
选择多个图片
</button>
<br>
<br>
<br>
<br>
...
@@ -55,6 +64,8 @@
...
@@ -55,6 +64,8 @@
<script
src=
"/test/test.js"
></script>
<script
src=
"/test/test.js"
></script>
...
...
assets/test.js
View file @
128403d
...
@@ -11,6 +11,12 @@ function clearResp() {
...
@@ -11,6 +11,12 @@ function clearResp() {
// 接收Flutter响应数据
// 接收Flutter响应数据
function
xeJsBridgeCallback
(
message
)
{
function
xeJsBridgeCallback
(
message
)
{
document
.
getElementById
(
'resp'
).
innerHTML
=
'<p><strong>响应:</strong> '
+
message
+
'</p>'
;
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;
}*/
}
}
// 测试获取设备信息
// 测试获取设备信息
...
@@ -52,11 +58,11 @@ function chooseImage(sourceType) {
...
@@ -52,11 +58,11 @@ function chooseImage(sourceType) {
}
}
function
chooseMultipleImage
()
{
function
chooseMultipleImage
(
sourceType
)
{
let
params
=
{
let
params
=
{
"timestamp"
:
1
,
"unique"
:
"123"
,
"cmd"
:
"chooseImage"
,
"params"
:
{
"timestamp"
:
1
,
"unique"
:
"123"
,
"cmd"
:
"chooseImage"
,
"params"
:
{
"sourceType"
:
"album
"
,
"sourceType"
:
sourceType
==
1
?
"album"
:
"camera
"
,
"count"
:
9
,
"count"
:
9
,
"sizeType"
:
[
"original"
,
"compressed"
],
"sizeType"
:
[
"original"
,
"compressed"
],
}
}
...
...
assets/test3.html
View file @
128403d
...
@@ -36,7 +36,7 @@
...
@@ -36,7 +36,7 @@
cmd
:
'chooseFile'
,
cmd
:
'chooseFile'
,
params
:
{
params
:
{
count
:
2
,
count
:
2
,
fileTypes
:
[
'docx'
,
'xlsx'
,
'pdf'
,
'mp4'
,
'csv'
]
fileTypes
:
[
'docx'
,
'xlsx'
,
'pdf'
,
'mp4'
,
'csv'
,
'png'
,
'm4a'
]
}
}
};
};
xeJsBridge
.
postMessage
(
JSON
.
stringify
(
params
));
xeJsBridge
.
postMessage
(
JSON
.
stringify
(
params
));
...
...
lib/bloc/web_cubit.dart
View file @
128403d
...
@@ -16,6 +16,7 @@ import 'package:fluwx/fluwx.dart';
...
@@ -16,6 +16,7 @@ import 'package:fluwx/fluwx.dart';
import
'package:path/path.dart'
as
path
;
import
'package:path/path.dart'
as
path
;
import
'package:path_provider/path_provider.dart'
;
import
'package:path_provider/path_provider.dart'
;
import
'package:permission_handler/permission_handler.dart'
;
import
'package:permission_handler/permission_handler.dart'
;
import
'package:shared_preferences/shared_preferences.dart'
;
import
'package:uuid/uuid.dart'
;
import
'package:uuid/uuid.dart'
;
import
'package:webview_flutter/webview_flutter.dart'
;
import
'package:webview_flutter/webview_flutter.dart'
;
import
'package:wechat_assets_picker/wechat_assets_picker.dart'
;
import
'package:wechat_assets_picker/wechat_assets_picker.dart'
;
...
@@ -55,6 +56,10 @@ class WebState extends Equatable {
...
@@ -55,6 +56,10 @@ class WebState extends Equatable {
final
bool
chooseImageCmdFlag
;
final
bool
chooseImageCmdFlag
;
final
String
chooseImageCmdMessage
;
final
String
chooseImageCmdMessage
;
/// chooseVideoCmd
final
bool
chooseVideoCmdFlag
;
final
String
chooseVideoCmdMessage
;
const
WebState
({
const
WebState
({
this
.
loaded
=
false
,
this
.
loaded
=
false
,
this
.
title
=
'界面加载中...'
,
this
.
title
=
'界面加载中...'
,
...
@@ -77,6 +82,8 @@ class WebState extends Equatable {
...
@@ -77,6 +82,8 @@ class WebState extends Equatable {
this
.
windowInfoCmdMessage
=
''
,
this
.
windowInfoCmdMessage
=
''
,
this
.
chooseImageCmdFlag
=
false
,
this
.
chooseImageCmdFlag
=
false
,
this
.
chooseImageCmdMessage
=
''
,
this
.
chooseImageCmdMessage
=
''
,
this
.
chooseVideoCmdFlag
=
false
,
this
.
chooseVideoCmdMessage
=
''
,
});
});
WebState
copyWith
({
WebState
copyWith
({
...
@@ -101,6 +108,8 @@ class WebState extends Equatable {
...
@@ -101,6 +108,8 @@ class WebState extends Equatable {
String
?
windowInfoCmdMessage
,
String
?
windowInfoCmdMessage
,
bool
?
chooseImageCmdFlag
,
bool
?
chooseImageCmdFlag
,
String
?
chooseImageCmdMessage
,
String
?
chooseImageCmdMessage
,
bool
?
chooseVideoCmdFlag
,
String
?
chooseVideoCmdMessage
,
})
{
})
{
return
WebState
(
return
WebState
(
loaded:
loaded
??
this
.
loaded
,
loaded:
loaded
??
this
.
loaded
,
...
@@ -124,6 +133,8 @@ class WebState extends Equatable {
...
@@ -124,6 +133,8 @@ class WebState extends Equatable {
windowInfoCmdMessage:
windowInfoCmdMessage
??
this
.
windowInfoCmdMessage
,
windowInfoCmdMessage:
windowInfoCmdMessage
??
this
.
windowInfoCmdMessage
,
chooseImageCmdFlag:
chooseImageCmdFlag
??
this
.
chooseImageCmdFlag
,
chooseImageCmdFlag:
chooseImageCmdFlag
??
this
.
chooseImageCmdFlag
,
chooseImageCmdMessage:
chooseImageCmdMessage
??
this
.
chooseImageCmdMessage
,
chooseImageCmdMessage:
chooseImageCmdMessage
??
this
.
chooseImageCmdMessage
,
chooseVideoCmdFlag:
chooseVideoCmdFlag
??
this
.
chooseVideoCmdFlag
,
chooseVideoCmdMessage:
chooseVideoCmdMessage
??
this
.
chooseVideoCmdMessage
,
);
);
}
}
...
@@ -150,6 +161,8 @@ class WebState extends Equatable {
...
@@ -150,6 +161,8 @@ class WebState extends Equatable {
windowInfoCmdMessage
,
windowInfoCmdMessage
,
chooseImageCmdFlag
,
chooseImageCmdFlag
,
chooseImageCmdMessage
,
chooseImageCmdMessage
,
chooseVideoCmdFlag
,
chooseVideoCmdMessage
,
];
];
}
}
...
@@ -211,13 +224,11 @@ class WebCubit extends Cubit<WebState> {
...
@@ -211,13 +224,11 @@ class WebCubit extends Cubit<WebState> {
final
String
serverUrl
;
final
String
serverUrl
;
if
(
state
.
sessionCode
==
null
||
state
.
sessionCode
==
''
)
{
if
(
state
.
sessionCode
==
null
||
state
.
sessionCode
==
''
)
{
// serverUrl = 'http://${state.ip}:${_server.port}/test/test
.html';
serverUrl
=
'http://127.0.0.1:
${_server.port}
/test/login
.html'
;
serverUrl
=
'http://127.0.0.1:
${_server.port}
/test/test.html'
;
//
serverUrl = 'http://127.0.0.1:${_server.port}/test/test.html';
}
else
{
}
else
{
serverUrl
=
serverUrl
=
'http://
${state.ip}
:
${_server.port}
/index.html#/h5/login/pages/applogin?sessionCode=
${state.sessionCode}
&userCode=
${state.userCode}
&classCode=
${state.classCode}
&userType=
${state.userType}
&stuId=
${state.stuId}
'
;
'http://
${state.ip}
:
${_server.port}
/index.html#/h5/login/pages/applogin?sessionCode=
${state.sessionCode}
&userCode=
${state.userCode}
&classCode=
${state.classCode}
&userType=
${state.userType}
&stuId=
${state.stuId}
'
;
// serverUrl =
// 'http://localdev.banxiaoer.net/index.html#/h5/login/pages/applogin?sessionCode=${state.sessionCode}&userCode=${state.userCode}&classCode=${state.classCode}&userType=${state.userType}&stuId=${state.stuId}';
}
}
_controller
.
loadRequest
(
Uri
.
parse
(
serverUrl
));
_controller
.
loadRequest
(
Uri
.
parse
(
serverUrl
));
...
@@ -257,7 +268,8 @@ class WebCubit extends Cubit<WebState> {
...
@@ -257,7 +268,8 @@ class WebCubit extends Cubit<WebState> {
//测试
//测试
void
goAuth
()
{
void
goAuth
()
{
String
serverUrl
=
'http://
${state.ip}
:
${_server.port}
/index.html'
;
// String serverUrl = 'http://${state.ip}:${_server.port}/index.html';
String
serverUrl
=
'http://127.0.0.1:
${_server.port}
/index.html'
;
// String serverUrl = 'http://localdev.banxiaoer.net';
// String serverUrl = 'http://localdev.banxiaoer.net';
_controller
.
loadRequest
(
Uri
.
parse
(
serverUrl
));
_controller
.
loadRequest
(
Uri
.
parse
(
serverUrl
));
}
}
...
@@ -303,6 +315,10 @@ class WebCubit extends Cubit<WebState> {
...
@@ -303,6 +315,10 @@ class WebCubit extends Cubit<WebState> {
_controller
.
reload
();
_controller
.
reload
();
}
}
Future
<
void
>
clearStorage
()
async
{
await
getIt
.
get
<
SharedPreferences
>().
clear
();
}
void
setChooseImageCmdFlag
(
bool
chooseImageCmdFlag
,
String
chooseImageCmdMessage
)
{
void
setChooseImageCmdFlag
(
bool
chooseImageCmdFlag
,
String
chooseImageCmdMessage
)
{
emit
(
state
.
copyWith
(
chooseImageCmdFlag:
chooseImageCmdFlag
,
chooseImageCmdMessage:
chooseImageCmdMessage
));
emit
(
state
.
copyWith
(
chooseImageCmdFlag:
chooseImageCmdFlag
,
chooseImageCmdMessage:
chooseImageCmdMessage
));
}
}
...
@@ -333,22 +349,27 @@ class WebCubit extends Cubit<WebState> {
...
@@ -333,22 +349,27 @@ class WebCubit extends Cubit<WebState> {
// 相册
// 相册
if
(
sourceType
==
'album'
)
{
if
(
sourceType
==
'album'
)
{
_chooseFromAlbum
(
context
,
count
,
h5Message
.
unique
,
h5Message
.
cmd
);
_choose
Image
FromAlbum
(
context
,
count
,
h5Message
.
unique
,
h5Message
.
cmd
);
}
}
// 拍照
// 拍照
else
{
else
{
_chooseFromCamera
(
context
,
h5Message
.
unique
,
h5Message
.
cmd
);
_choose
Image
FromCamera
(
context
,
h5Message
.
unique
,
h5Message
.
cmd
);
}
}
}
}
void
_chooseFromAlbum
(
BuildContext
context
,
int
count
,
String
unique
,
String
cmd
)
async
{
void
_choose
Image
FromAlbum
(
BuildContext
context
,
int
count
,
String
unique
,
String
cmd
)
async
{
final
List
<
AssetEntity
>?
result
=
await
AssetPicker
.
pickAssets
(
final
List
<
AssetEntity
>?
result
=
await
AssetPicker
.
pickAssets
(
context
,
context
,
pickerConfig:
AssetPickerConfig
(
maxAssets:
count
,
requestType:
RequestType
.
image
),
pickerConfig:
AssetPickerConfig
(
maxAssets:
count
,
requestType:
RequestType
.
image
,
gridThumbnailSize:
const
ThumbnailSize
.
square
(
80
),
previewThumbnailSize:
const
ThumbnailSize
.
square
(
150
),
),
);
);
if
(
result
==
null
||
result
.
isEmpty
)
{
if
(
result
==
null
||
result
.
isEmpty
)
{
var
resp
=
{
'unique'
:
unique
,
'cmd'
:
cmd
,
'data'
:
null
,
'errMsg'
:
'
未选择图片
'
};
var
resp
=
{
'unique'
:
unique
,
'cmd'
:
cmd
,
'data'
:
null
,
'errMsg'
:
'
cancel
'
};
_sendResponse
(
resp
);
_sendResponse
(
resp
);
return
;
return
;
}
}
...
@@ -360,15 +381,20 @@ class WebCubit extends Cubit<WebState> {
...
@@ -360,15 +381,20 @@ class WebCubit extends Cubit<WebState> {
for
(
var
asset
in
result
)
{
for
(
var
asset
in
result
)
{
resultList
.
add
(
await
_handleSingleImage
(
asset
,
tempDir
));
resultList
.
add
(
await
_handleSingleImage
(
asset
,
tempDir
));
}
}
var
resp
=
{
'unique'
:
unique
,
'cmd'
:
cmd
,
'data'
:
resultList
,
'errMsg'
:
''
};
var
resp
=
{
'unique'
:
unique
,
'cmd'
:
cmd
,
'data'
:
{
'tempFiles'
:
resultList
},
'errMsg'
:
''
,
};
_sendResponse
(
resp
);
_sendResponse
(
resp
);
}
}
void
_chooseFromCamera
(
BuildContext
context
,
String
unique
,
String
cmd
)
async
{
void
_choose
Image
FromCamera
(
BuildContext
context
,
String
unique
,
String
cmd
)
async
{
AssetEntity
?
asset
=
await
CameraPicker
.
pickFromCamera
(
context
,
pickerConfig:
const
CameraPickerConfig
());
AssetEntity
?
asset
=
await
CameraPicker
.
pickFromCamera
(
context
,
pickerConfig:
const
CameraPickerConfig
());
if
(
asset
==
null
)
{
if
(
asset
==
null
)
{
var
resp
=
{
'unique'
:
unique
,
'cmd'
:
cmd
,
'data'
:
null
,
'errMsg'
:
'
未选择图片
'
};
var
resp
=
{
'unique'
:
unique
,
'cmd'
:
cmd
,
'data'
:
null
,
'errMsg'
:
'
cancel
'
};
_sendResponse
(
resp
);
_sendResponse
(
resp
);
return
;
return
;
}
}
...
@@ -378,7 +404,9 @@ class WebCubit extends Cubit<WebState> {
...
@@ -378,7 +404,9 @@ class WebCubit extends Cubit<WebState> {
var
resp
=
{
var
resp
=
{
'unique'
:
unique
,
'unique'
:
unique
,
'cmd'
:
cmd
,
'cmd'
:
cmd
,
'data'
:
[
result
],
'data'
:
{
'tempFiles'
:
[
result
],
},
'errMsg'
:
''
,
'errMsg'
:
''
,
};
};
_sendResponse
(
resp
);
_sendResponse
(
resp
);
...
@@ -394,11 +422,132 @@ class WebCubit extends Cubit<WebState> {
...
@@ -394,11 +422,132 @@ class WebCubit extends Cubit<WebState> {
).
writeAsBytes
(
data
!);
).
writeAsBytes
(
data
!);
return
{
return
{
"tempFilePath"
:
file
!.
path
,
"tempFilePath"
:
'http://127.0.0.1:
${_server.port}
/temp
${file!.path}
'
,
"size"
:
file
.
lengthSync
(),
"width"
:
asset
.
width
,
"height"
:
asset
.
height
,
"thumbTempFilePath"
:
'http://127.0.0.1:
${_server.port}
/temp
${thumbnailFile.path}
'
,
"fileType"
:
file
.
path
.
split
(
'/'
).
last
.
split
(
'.'
).
last
,
};
}
void
setChooseVideoCmdFlag
(
bool
chooseVideoCmdFlag
,
String
chooseVideoCmdMessage
)
{
emit
(
state
.
copyWith
(
chooseVideoCmdFlag:
chooseVideoCmdFlag
,
chooseVideoCmdMessage:
chooseVideoCmdMessage
));
}
void
chooseVideo
(
BuildContext
context
)
async
{
final
Map
<
String
,
dynamic
>
data
=
json
.
decode
(
state
.
chooseVideoCmdMessage
);
H5Message
h5Message
=
H5Message
.
fromJson
(
data
);
setChooseVideoCmdFlag
(
false
,
''
);
final
params
=
h5Message
.
params
;
if
(
params
is
!
Map
<
String
,
dynamic
>)
{
throw
Exception
(
'参数错误'
);
}
var
sourceType
=
params
[
'sourceType'
]
as
String
;
if
(
sourceType
!=
'album'
&&
sourceType
!=
'camera'
)
{
sourceType
=
'album'
;
}
// 暂时忽略 sizeType 参数
int
count
=
1
;
if
(
params
.
containsKey
(
'count'
))
{
count
=
params
[
'count'
]
as
int
;
if
(
count
<
1
||
count
>
9
)
{
count
=
9
;
}
}
int
maxDuration
=
60
;
if
(
params
.
containsKey
(
'maxDuration'
))
{
maxDuration
=
params
[
'maxDuration'
]
as
int
;
if
(
maxDuration
<
1
||
maxDuration
>
600
)
{
maxDuration
=
60
;
}
}
// 相册选择
if
(
sourceType
==
'album'
)
{
_chooseVideoFromAlbum
(
context
,
count
,
h5Message
.
unique
,
h5Message
.
cmd
);
}
// 拍摄
else
{
_chooseVideoFromCamera
(
context
,
maxDuration
,
h5Message
.
unique
,
h5Message
.
cmd
);
}
}
void
_chooseVideoFromAlbum
(
BuildContext
context
,
int
count
,
String
unique
,
String
cmd
)
async
{
final
List
<
AssetEntity
>?
result
=
await
AssetPicker
.
pickAssets
(
context
,
pickerConfig:
AssetPickerConfig
(
maxAssets:
count
,
requestType:
RequestType
.
video
),
);
if
(
result
==
null
||
result
.
isEmpty
)
{
var
resp
=
{
'unique'
:
unique
,
'cmd'
:
cmd
,
'data'
:
null
,
'errMsg'
:
'cancel'
};
_sendResponse
(
resp
);
return
;
}
// 获取临时目录
final
Directory
tempDir
=
await
getTemporaryDirectory
();
final
List
<
Map
<
String
,
dynamic
>>
resultList
=
[];
for
(
var
asset
in
result
)
{
resultList
.
add
(
await
_handleSingleVideo
(
asset
,
tempDir
));
}
var
resp
=
{
'unique'
:
unique
,
'cmd'
:
cmd
,
'data'
:
{
'tempFiles'
:
resultList
},
'errMsg'
:
''
,
};
_sendResponse
(
resp
);
}
void
_chooseVideoFromCamera
(
BuildContext
context
,
int
maxDuration
,
String
unique
,
String
cmd
)
async
{
AssetEntity
?
asset
=
await
CameraPicker
.
pickFromCamera
(
context
,
pickerConfig:
CameraPickerConfig
(
enableRecording:
true
,
onlyEnableRecording:
true
,
maximumRecordingDuration:
Duration
(
seconds:
maxDuration
),
),
);
if
(
asset
==
null
)
{
var
resp
=
{
'unique'
:
unique
,
'cmd'
:
cmd
,
'data'
:
null
,
'errMsg'
:
'cancel'
};
_sendResponse
(
resp
);
return
;
}
final
Directory
tempDir
=
await
getTemporaryDirectory
();
final
Map
<
String
,
dynamic
>
result
=
await
_handleSingleVideo
(
asset
,
tempDir
);
var
resp
=
{
'unique'
:
unique
,
'cmd'
:
cmd
,
'data'
:
{
'tempFiles'
:
[
result
],
},
'errMsg'
:
''
,
};
_sendResponse
(
resp
);
}
Future
<
Map
<
String
,
dynamic
>>
_handleSingleVideo
(
AssetEntity
asset
,
Directory
tempDir
)
async
{
final
file
=
await
asset
.
file
;
// 获取缩略图
final
data
=
await
asset
.
thumbnailData
;
final
thumbnailFile
=
await
File
(
'
${tempDir.path}
/
${DateTime.now().millisecondsSinceEpoch}
.png'
,
).
writeAsBytes
(
data
!);
return
{
"tempFilePath"
:
'http://127.0.0.1:
${_server.port}
/temp
${file!.path}
'
,
"size"
:
file
.
lengthSync
(),
"size"
:
file
.
lengthSync
(),
"width"
:
asset
.
width
,
"width"
:
asset
.
width
,
"height"
:
asset
.
height
,
"height"
:
asset
.
height
,
"thumbTempFilePath"
:
'/temp
${thumbnailFile.path}
'
,
"thumbTempFilePath"
:
'
http://127.0.0.1:
${_server.port}
/temp
${thumbnailFile.path}
'
,
"fileType"
:
file
.
path
.
split
(
'/'
).
last
.
split
(
'.'
).
last
,
"fileType"
:
file
.
path
.
split
(
'/'
).
last
.
split
(
'.'
).
last
,
};
};
}
}
...
@@ -475,7 +624,7 @@ class WebCubit extends Cubit<WebState> {
...
@@ -475,7 +624,7 @@ class WebCubit extends Cubit<WebState> {
// 请求麦克风权限
// 请求麦克风权限
var
status
=
await
Permission
.
microphone
.
request
();
var
status
=
await
Permission
.
microphone
.
request
();
if
(
status
!=
PermissionStatus
.
granted
)
{
if
(
status
!=
PermissionStatus
.
granted
)
{
throw
RecordingPermissionException
(
'
麦克风权限未授权!
'
);
throw
RecordingPermissionException
(
'
no auth
'
);
}
}
if
(
state
.
recordState
!=
0
)
{
if
(
state
.
recordState
!=
0
)
{
...
...
lib/bloc/wechat_auth_cubit.dart
View file @
128403d
...
@@ -5,10 +5,12 @@ import 'package:equatable/equatable.dart';
...
@@ -5,10 +5,12 @@ import 'package:equatable/equatable.dart';
import
'package:flutter/material.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter_bloc/flutter_bloc.dart'
;
import
'package:flutter_bloc/flutter_bloc.dart'
;
import
'package:fluwx/fluwx.dart'
;
import
'package:fluwx/fluwx.dart'
;
import
'package:shared_preferences/shared_preferences.dart'
;
class
WechatAuthState
extends
Equatable
{
class
WechatAuthState
extends
Equatable
{
final
String
ip
;
final
String
ip
;
final
String
?
result
;
final
String
?
result
;
final
String
?
sessionCode
;
final
String
?
sessionCode
;
final
String
?
userCode
;
final
String
?
userCode
;
final
String
?
classCode
;
final
String
?
classCode
;
...
@@ -61,8 +63,8 @@ class WechatAuthCubit extends Cubit<WechatAuthState> {
...
@@ -61,8 +63,8 @@ class WechatAuthCubit extends Cubit<WechatAuthState> {
_fluwx
.
addSubscriber
(
_responseListener
);
_fluwx
.
addSubscriber
(
_responseListener
);
// _textEditingController = TextEditingController()..text = '127.0.0.1';
// _textEditingController = TextEditingController()..text = '127.0.0.1';
_textEditingController
=
TextEditingController
()..
text
=
'appdev-th.banxiaoer.net'
;
//
_textEditingController = TextEditingController()..text = 'appdev-th.banxiaoer.net';
//
_textEditingController = TextEditingController()..text = 'appdev-xj.banxiaoer.net';
_textEditingController
=
TextEditingController
()..
text
=
'appdev-xj.banxiaoer.net'
;
// _textEditingController = TextEditingController()..text = '192.168.1.136';
// _textEditingController = TextEditingController()..text = '192.168.1.136';
_wechatAuthRepository
=
getIt
<
WechatAuthRepository
>();
_wechatAuthRepository
=
getIt
<
WechatAuthRepository
>();
...
@@ -75,15 +77,31 @@ class WechatAuthCubit extends Cubit<WechatAuthState> {
...
@@ -75,15 +77,31 @@ class WechatAuthCubit extends Cubit<WechatAuthState> {
var
data
=
resultData
[
'data'
];
var
data
=
resultData
[
'data'
];
var
role
=
data
[
'roles'
][
0
];
var
role
=
data
[
'roles'
][
0
];
final
sessionCode
=
data
[
'sessionCode'
];
final
userCode
=
data
[
'userCode'
];
final
classCode
=
role
[
'classCode'
];
final
userType
=
role
[
'userType'
];
final
stuId
=
role
[
'stuId'
];
var
sharedPreferences
=
getIt
.
get
<
SharedPreferences
>();
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_ip', 'appdev-th.banxiaoer.net');
sharedPreferences
.
setString
(
'auth_ip'
,
'appdev-xj.banxiaoer.net'
);
// sharedPreferences.setString('auth_ip', '192.168.1.136');
router
.
go
(
router
.
go
(
'/web'
,
'/web'
,
extra:
{
extra:
{
'ip'
:
state
.
ip
,
'ip'
:
state
.
ip
,
'sessionCode'
:
data
[
'sessionCode'
]
,
'sessionCode'
:
sessionCode
,
'userCode'
:
data
[
'userCode'
]
,
'userCode'
:
userCode
,
'classCode'
:
role
[
'classCode'
]
,
'classCode'
:
classCode
,
'userType'
:
role
[
'userType'
]
,
'userType'
:
userType
,
'stuId'
:
role
[
'stuId'
]
,
'stuId'
:
stuId
,
},
},
);
);
}
}
...
...
lib/config/locator.dart
View file @
128403d
...
@@ -8,6 +8,7 @@ import 'package:appframe/data/repositories/message/clipboard_data_handler.dart';
...
@@ -8,6 +8,7 @@ import 'package:appframe/data/repositories/message/clipboard_data_handler.dart';
import
'package:appframe/data/repositories/message/compress_handler.dart'
;
import
'package:appframe/data/repositories/message/compress_handler.dart'
;
import
'package:appframe/data/repositories/message/device_info_handler.dart'
;
import
'package:appframe/data/repositories/message/device_info_handler.dart'
;
import
'package:appframe/data/repositories/message/download_file_handler.dart'
;
import
'package:appframe/data/repositories/message/download_file_handler.dart'
;
import
'package:appframe/data/repositories/message/go_login_handler.dart'
;
import
'package:appframe/data/repositories/message/image_info_handler.dart'
;
import
'package:appframe/data/repositories/message/image_info_handler.dart'
;
import
'package:appframe/data/repositories/message/location_handler.dart'
;
import
'package:appframe/data/repositories/message/location_handler.dart'
;
import
'package:appframe/data/repositories/message/network_type_handler.dart'
;
import
'package:appframe/data/repositories/message/network_type_handler.dart'
;
...
@@ -154,6 +155,9 @@ Future<void> setupLocator() async {
...
@@ -154,6 +155,9 @@ Future<void> setupLocator() async {
/// 设置标题和返回按钮
/// 设置标题和返回按钮
getIt
.
registerLazySingleton
<
MessageHandler
>(()
=>
SetTitleHandler
(),
instanceName:
'setTitle'
);
getIt
.
registerLazySingleton
<
MessageHandler
>(()
=>
SetTitleHandler
(),
instanceName:
'setTitle'
);
/// 登录
getIt
.
registerLazySingleton
<
MessageHandler
>(()
=>
GoLoginHandler
(),
instanceName:
'goLogin'
);
/// service
/// service
getIt
.
registerLazySingleton
<
ApiService
>(()
=>
ApiService
(
baseUrl:
'https://dev.banxiaoer.net'
));
getIt
.
registerLazySingleton
<
ApiService
>(()
=>
ApiService
(
baseUrl:
'https://dev.banxiaoer.net'
));
...
...
lib/config/server.dart
0 → 100644
View file @
128403d
final
int
serverPort
=
35982
;
lib/data/repositories/message/choose_video_handler.dart
View file @
128403d
import
'dart:io'
;
import
'package:appframe/bloc/web_cubit.dart'
;
import
'package:appframe/services/dispatcher.dart'
;
import
'package:appframe/services/dispatcher.dart'
;
import
'package:appframe/utils/thumbnail_util.dart'
;
import
'package:image_picker/image_picker.dart'
;
import
'package:image_size_getter/file_input.dart'
;
import
'package:image_size_getter/image_size_getter.dart'
;
import
'package:path_provider/path_provider.dart'
;
class
ChooseVideoHandler
extends
MessageHandler
{
class
ChooseVideoHandler
extends
MessageHandler
{
late
WebCubit
?
_webCubit
;
late
String
?
_message
;
@override
void
setCubit
(
WebCubit
cubit
)
{
this
.
_webCubit
=
cubit
;
}
void
_unfollowCubit
()
{
this
.
_webCubit
=
null
;
}
@override
void
setMessage
(
String
message
)
{
this
.
_message
=
message
;
}
@override
@override
Future
<
dynamic
>
handleMessage
(
params
)
async
{
Future
<
dynamic
>
handleMessage
(
params
)
async
{
if
(
params
is
!
Map
<
String
,
dynamic
>)
{
if
(
params
is
!
Map
<
String
,
dynamic
>)
{
...
@@ -17,14 +28,6 @@ class ChooseVideoHandler extends MessageHandler {
...
@@ -17,14 +28,6 @@ class ChooseVideoHandler extends MessageHandler {
if
(
sourceType
!=
'album'
&&
sourceType
!=
'camera'
)
{
if
(
sourceType
!=
'album'
&&
sourceType
!=
'camera'
)
{
throw
Exception
(
'参数错误'
);
throw
Exception
(
'参数错误'
);
}
}
// 暂时忽略对此参数的处理
List
<
dynamic
>?
sizeType
;
if
(
params
.
containsKey
(
'sizeType'
))
{
sizeType
=
params
[
'sizeType'
]
as
List
<
dynamic
>;
if
(
sizeType
.
isEmpty
||
sizeType
.
length
>
2
)
{
throw
Exception
(
'参数错误'
);
}
}
int
count
=
1
;
int
count
=
1
;
if
(
params
.
containsKey
(
'count'
))
{
if
(
params
.
containsKey
(
'count'
))
{
...
@@ -34,104 +37,143 @@ class ChooseVideoHandler extends MessageHandler {
...
@@ -34,104 +37,143 @@ class ChooseVideoHandler extends MessageHandler {
}
}
}
}
int
number
=
60
;
int
maxDuration
=
60
;
if
(
params
.
containsKey
(
'
number
'
))
{
if
(
params
.
containsKey
(
'
maxDuration
'
))
{
number
=
params
[
'number
'
]
as
int
;
maxDuration
=
params
[
'maxDuration
'
]
as
int
;
if
(
number
<
1
||
number
>
6
0
)
{
if
(
maxDuration
<
1
||
maxDuration
>
60
0
)
{
throw
Exception
(
'参数错误'
);
throw
Exception
(
'参数错误'
);
}
}
}
}
// 从相册选择
_webCubit
!.
setChooseVideoCmdFlag
(
true
,
_message
!);
if
(
sourceType
==
'album'
)
{
if
(
count
==
1
)
{
return
await
_selectSingleVideo
();
}
else
{
return
await
_selectMultiVideo
(
count
);
}
}
// 拍摄
else
{
return
await
_cameraSingle
();
}
}
Future
<
List
<
dynamic
>>
_selectSingleVideo
()
async
{
final
ImagePicker
picker
=
ImagePicker
();
final
XFile
?
pickedVideo
=
await
picker
.
pickVideo
(
source
:
ImageSource
.
gallery
);
// 用户取消选择,返回空数组
if
(
pickedVideo
==
null
)
{
return
[];
}
// 获取临时目录
final
Directory
tempDir
=
await
getTemporaryDirectory
();
return
[
await
_handleOne
(
pickedVideo
,
tempDir
)];
}
Future
<
List
<
dynamic
>>
_selectMultiVideo
(
int
limit
)
async
{
final
ImagePicker
picker
=
ImagePicker
();
final
List
<
XFile
>
pickedVideoList
=
await
picker
.
pickMultiVideo
(
limit:
limit
);
// 用户取消选择,返回空数组
if
(
pickedVideoList
.
isEmpty
)
{
return
[];
}
// 限制最多limit张
if
(
pickedVideoList
.
length
>
limit
)
{
pickedVideoList
.
removeRange
(
limit
,
pickedVideoList
.
length
);
}
// 获取临时目录
final
Directory
tempDir
=
await
getTemporaryDirectory
();
final
List
<
Map
<
String
,
dynamic
>>
result
=
[];
for
(
final
XFile
?
file
in
pickedVideoList
)
{
if
(
file
!=
null
)
{
result
.
add
(
await
_handleOne
(
file
,
tempDir
));
}
}
return
result
;
}
///
/// 拍照
///
Future
<
List
<
Map
<
String
,
dynamic
>>?>
_cameraSingle
()
async
{
final
ImagePicker
picker
=
ImagePicker
();
final
XFile
?
pickedFile
=
await
picker
.
pickVideo
(
source
:
ImageSource
.
camera
);
// 用户取消选择,返回空数组
if
(
pickedFile
==
null
)
{
return
[];
}
// 获取临时目录
final
Directory
tempDir
=
await
getTemporaryDirectory
();
return
[
await
_handleOne
(
pickedFile
,
tempDir
)];
}
Future
<
Map
<
String
,
dynamic
>>
_handleOne
(
XFile
pickedFile
,
Directory
tempDir
)
async
{
var
sourceFile
=
File
(
pickedFile
.
path
);
final
thumbnailPath
=
await
ThumbnailUtil
.
genVideoThumbnail
(
pickedFile
.
path
,
tempDir
);
// 暂时这样进行接口测试
// 根据视频预览图,获取视频的宽度和高度
final
sizeResult
=
ImageSizeGetter
.
getSizeResult
(
FileInput
(
File
(
thumbnailPath
!)));
// 返回一个元素的数组
return
{
"tempFilePath"
:
"/temp
${sourceFile.path}
"
,
"size"
:
sourceFile
.
lengthSync
(),
"width"
:
sizeResult
.
size
.
width
,
"height"
:
sizeResult
.
size
.
height
,
"thumbTempFilePath"
:
'/temp
$thumbnailPath
'
,
"fileType"
:
sourceFile
.
path
.
split
(
'/'
).
last
.
split
(
'.'
).
last
,
};
}
}
}
}
// class ChooseVideoHandler extends MessageHandler {
// @override
// Future<dynamic> handleMessage(params) async {
// if (params is! Map<String, dynamic>) {
// throw Exception('参数错误');
// }
// var sourceType = params['sourceType'] as String;
// if (sourceType != 'album' && sourceType != 'camera') {
// throw Exception('参数错误');
// }
// // 暂时忽略对此参数的处理
// List<dynamic>? sizeType;
// if (params.containsKey('sizeType')) {
// sizeType = params['sizeType'] as List<dynamic>;
// if (sizeType.isEmpty || sizeType.length > 2) {
// throw Exception('参数错误');
// }
// }
//
// int count = 1;
// if (params.containsKey('count')) {
// count = params['count'] as int;
// if (count < 1 || count > 9) {
// throw Exception('参数错误');
// }
// }
//
// int number = 60;
// if (params.containsKey('number')) {
// number = params['number'] as int;
// if (number < 1 || number > 60) {
// throw Exception('参数错误');
// }
// }
//
// // 从相册选择
// if (sourceType == 'album') {
// if (count == 1) {
// return await _selectSingleVideo();
// } else {
// return await _selectMultiVideo(count);
// }
// }
// // 拍摄
// else {
// return await _cameraSingle();
// }
// }
//
// Future<List<dynamic>> _selectSingleVideo() async {
// final ImagePicker picker = ImagePicker();
// final XFile? pickedVideo = await picker.pickVideo(source: ImageSource.gallery);
//
// // 用户取消选择,返回空数组
// if (pickedVideo == null) {
// return [];
// }
//
// // 获取临时目录
// final Directory tempDir = await getTemporaryDirectory();
//
// return [await _handleOne(pickedVideo, tempDir)];
// }
//
// Future<List<dynamic>> _selectMultiVideo(int limit) async {
// final ImagePicker picker = ImagePicker();
// final List<XFile> pickedVideoList = await picker.pickMultiVideo(limit: limit);
//
// // 用户取消选择,返回空数组
// if (pickedVideoList.isEmpty) {
// return [];
// }
//
// // 限制最多limit张
// if (pickedVideoList.length > limit) {
// pickedVideoList.removeRange(limit, pickedVideoList.length);
// }
//
// // 获取临时目录
// final Directory tempDir = await getTemporaryDirectory();
//
// final List<Map<String, dynamic>> result = [];
// for (final XFile? file in pickedVideoList) {
// if (file != null) {
// result.add(await _handleOne(file, tempDir));
// }
// }
// return result;
// }
//
// ///
// /// 拍照
// ///
// Future<List<Map<String, dynamic>>?> _cameraSingle() async {
// final ImagePicker picker = ImagePicker();
//
// final XFile? pickedFile = await picker.pickVideo(source: ImageSource.camera);
//
// // 用户取消选择,返回空数组
// if (pickedFile == null) {
// return [];
// }
//
// // 获取临时目录
// final Directory tempDir = await getTemporaryDirectory();
//
// return [await _handleOne(pickedFile, tempDir)];
// }
//
// Future<Map<String, dynamic>> _handleOne(XFile pickedFile, Directory tempDir) async {
// var sourceFile = File(pickedFile.path);
//
// final thumbnailPath = await ThumbnailUtil.genVideoThumbnail(pickedFile.path, tempDir);
// // 暂时这样进行接口测试
// // 根据视频预览图,获取视频的宽度和高度
// final sizeResult = ImageSizeGetter.getSizeResult(FileInput(File(thumbnailPath!)));
//
// // 返回一个元素的数组
// return {
// "tempFilePath": "/temp${sourceFile.path}",
// "size": sourceFile.lengthSync(),
// "width": sizeResult.size.width,
// "height": sizeResult.size.height,
// "thumbTempFilePath": '/temp$thumbnailPath',
// "fileType": sourceFile.path.split('/').last.split('.').last,
// };
// }
// }
lib/data/repositories/message/go_login_handler.dart
0 → 100644
View file @
128403d
import
'package:appframe/bloc/web_cubit.dart'
;
import
'package:appframe/services/dispatcher.dart'
;
class
GoLoginHandler
extends
MessageHandler
{
late
WebCubit
?
_webCubit
;
@override
void
setCubit
(
WebCubit
cubit
)
{
this
.
_webCubit
=
cubit
;
}
void
_unfollowCubit
()
{
this
.
_webCubit
=
null
;
}
@override
Future
<
dynamic
>
handleMessage
(
params
)
async
{
_webCubit
!.
goWechatAuth
();
return
;
}
}
lib/data/repositories/message/location_handler.dart
View file @
128403d
...
@@ -23,12 +23,12 @@ class LocationHandler extends MessageHandler {
...
@@ -23,12 +23,12 @@ class LocationHandler extends MessageHandler {
if
(
permission
==
LocationPermission
.
denied
)
{
if
(
permission
==
LocationPermission
.
denied
)
{
permission
=
await
Geolocator
.
requestPermission
();
permission
=
await
Geolocator
.
requestPermission
();
if
(
permission
==
LocationPermission
.
denied
)
{
if
(
permission
==
LocationPermission
.
denied
)
{
throw
'
定位权限被拒绝
'
;
throw
'
no auth
'
;
}
}
}
}
if
(
permission
==
LocationPermission
.
deniedForever
)
{
if
(
permission
==
LocationPermission
.
deniedForever
)
{
throw
'
定位权限被永久拒绝
'
;
throw
'
no auth
'
;
}
}
final
pos
=
await
geolocator
.
getCurrentPosition
();
final
pos
=
await
geolocator
.
getCurrentPosition
();
...
...
lib/data/repositories/message/recorder_handler.dart
View file @
128403d
...
@@ -13,7 +13,7 @@ Future<bool> initLocalRecorder() async {
...
@@ -13,7 +13,7 @@ Future<bool> initLocalRecorder() async {
// 请求麦克风权限
// 请求麦克风权限
var
status
=
await
Permission
.
microphone
.
request
();
var
status
=
await
Permission
.
microphone
.
request
();
if
(
status
!=
PermissionStatus
.
granted
)
{
if
(
status
!=
PermissionStatus
.
granted
)
{
throw
RecordingPermissionException
(
'
麦克风权限未授权!
'
);
throw
RecordingPermissionException
(
'
no auth
'
);
}
}
if
(!(!
isLocalRecording
&&
!
isLocalPauseRecording
&&
localRecordedFilePath
==
null
))
{
if
(!(!
isLocalRecording
&&
!
isLocalPauseRecording
&&
localRecordedFilePath
==
null
))
{
...
...
lib/data/repositories/message/upgrade_handler.dart
0 → 100644
View file @
128403d
import
'dart:io'
;
import
'package:appframe/services/dispatcher.dart'
;
import
'package:archive/archive.dart'
;
import
'package:dio/dio.dart'
;
import
'package:path_provider/path_provider.dart'
;
class
UpgradeHandler
extends
MessageHandler
{
@override
Future
<
dynamic
>
handleMessage
(
params
)
async
{
if
(
params
is
!
Map
<
String
,
dynamic
>)
{
throw
Exception
(
'参数错误'
);
}
final
url
=
params
[
'url'
]
as
String
;
final
version
=
params
[
'version'
]
as
String
;
// 1 下载
var
direct
=
await
getExternalStorageDirectory
();
var
saveFilePath
=
'
${direct?.path}
/dist_
$version
.zip'
;
var
dio
=
Dio
();
final
resp
=
await
dio
.
download
(
url
,
saveFilePath
,
onReceiveProgress:
(
received
,
total
)
{
if
(
total
!=
-
1
)
{
print
((
received
/
total
*
100
).
toStringAsFixed
(
0
)
+
"%"
);
}
},
);
dio
.
close
();
if
(
resp
.
statusCode
!=
200
)
{
throw
Exception
(
'文件下载失败'
);
}
// 2 解压
var
outputDirectory
=
'
${direct?.path}
/http_dist_assets'
;
await
_extract
(
saveFilePath
,
outputDirectory
);
}
Future
<
void
>
_extract
(
String
zipFilePath
,
String
outputDirectory
)
async
{
var
file
=
File
(
zipFilePath
);
var
bytes
=
await
file
.
readAsBytes
();
// 解码 ZIP 文件
final
archive
=
ZipDecoder
().
decodeBytes
(
bytes
);
// 提取文件到指定目录
for
(
final
file
in
archive
)
{
final
filename
=
file
.
name
;
if
(
file
.
isFile
)
{
final
data
=
file
.
content
as
List
<
int
>;
final
outputFile
=
File
(
'
$outputDirectory
/
$filename
'
);
outputFile
.
createSync
(
recursive:
true
);
outputFile
.
writeAsBytesSync
(
data
);
}
else
{
// 创建目录
Directory
(
'
$outputDirectory
/
$filename
'
).
createSync
(
recursive:
true
);
}
}
}
}
lib/services/dispatcher.dart
View file @
128403d
...
@@ -52,6 +52,8 @@ class MessageDispatcher {
...
@@ -52,6 +52,8 @@ class MessageDispatcher {
h5Message
.
cmd
==
"getOrientation"
||
h5Message
.
cmd
==
"getOrientation"
||
h5Message
.
cmd
==
"getWindowInfo"
||
h5Message
.
cmd
==
"getWindowInfo"
||
h5Message
.
cmd
==
"chooseImage"
||
h5Message
.
cmd
==
"chooseImage"
||
h5Message
.
cmd
==
"chooseVideo"
||
h5Message
.
cmd
==
"goLogin"
||
h5Message
.
cmd
.
startsWith
(
"audio"
)
||
h5Message
.
cmd
.
startsWith
(
"audio"
)
||
h5Message
.
cmd
.
startsWith
(
"setTitle"
))
{
h5Message
.
cmd
.
startsWith
(
"setTitle"
))
{
handler
.
setCubit
(
webCubit
!);
handler
.
setCubit
(
webCubit
!);
...
...
lib/services/local_server_service.dart
View file @
128403d
import
'dart:io'
;
import
'dart:io'
;
import
'package:archive/archive.dart'
;
import
'package:archive/archive.dart'
;
import
'package:dio/dio.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/services.dart'
;
import
'package:path_provider/path_provider.dart'
;
class
LocalServerService
{
class
LocalServerService
{
String
?
_httpDirectory
;
// 启动本地HTTP服务器
// 启动本地HTTP服务器
Future
<
HttpServer
>
startLocalServer
()
async
{
Future
<
HttpServer
>
startLocalServer
()
async
{
// 测试情况下, 每次启动服务,先解压dist文件
_extractDist
();
HttpServer
server
=
await
HttpServer
.
bind
(
InternetAddress
.
loopbackIPv4
,
35982
);
HttpServer
server
=
await
HttpServer
.
bind
(
InternetAddress
.
loopbackIPv4
,
35982
);
// HttpServer server = await HttpServer.bind(InternetAddress.loopbackIPv4, 8080);
print
(
'本地服务器启动在端口:
${server.port}
'
);
print
(
'本地服务器启动在端口:
${server.port}
'
);
server
.
listen
((
HttpRequest
request
)
async
{
server
.
listen
((
HttpRequest
request
)
async
{
...
@@ -15,14 +21,14 @@ class LocalServerService {
...
@@ -15,14 +21,14 @@ class LocalServerService {
try
{
try
{
if
(
requestPath
.
startsWith
(
'/temp/'
))
{
if
(
requestPath
.
startsWith
(
'/temp/'
))
{
//
临时目录文件的请求
//
目录文件服务逻辑
await
_serveTempFile
(
request
,
requestPath
);
await
_serveTempFile
(
request
,
requestPath
);
}
else
if
(
requestPath
.
startsWith
(
'/test/'
))
{
}
else
if
(
requestPath
.
startsWith
(
'/test/'
))
{
// assets文件服务逻辑
//
内部
assets文件服务逻辑
await
_serveAssetFile
(
request
,
requestPath
);
await
_serveAssetFile
(
request
,
requestPath
);
}
else
{
}
else
{
//
asset/dist.zip
文件服务逻辑
//
内部集成H5
文件服务逻辑
await
_serve
ZipFileContent
(
request
,
requestPath
);
await
_serve
HttpFile
(
request
,
requestPath
);
}
}
}
catch
(
e
)
{
}
catch
(
e
)
{
print
(
'处理请求时出错:
$e
'
);
print
(
'处理请求时出错:
$e
'
);
...
@@ -36,10 +42,10 @@ class LocalServerService {
...
@@ -36,10 +42,10 @@ class LocalServerService {
return
server
;
return
server
;
}
}
//
临时目录
的文件
//
目录下
的文件
Future
<
void
>
_serveTempFile
(
HttpRequest
request
,
String
requestPath
)
async
{
Future
<
void
>
_serveTempFile
(
HttpRequest
request
,
String
requestPath
)
async
{
try
{
try
{
// 临时文件
已经设备
路径
// 临时文件路径
// 构建文件路径(移除 /temp 前缀)
// 构建文件路径(移除 /temp 前缀)
final
String
filePath
=
requestPath
.
substring
(
'/temp/'
.
length
);
final
String
filePath
=
requestPath
.
substring
(
'/temp/'
.
length
);
...
@@ -68,52 +74,32 @@ class LocalServerService {
...
@@ -68,52 +74,32 @@ class LocalServerService {
}
}
}
}
Future
<
void
>
_serveZipFileContent
(
HttpRequest
request
,
String
requestPath
)
async
{
Future
<
void
>
_serveHttpFile
(
HttpRequest
request
,
String
requestPath
)
async
{
String
zipAssetPath
=
'assets/dist.zip'
;
try
{
try
{
// 使用 rootBundle.load 加载资源文件
var
httpDirectory
=
await
getHttpDirectory
();
final
ByteData
data
=
await
rootBundle
.
load
(
zipAssetPath
);
final
String
filePath
=
'
$httpDirectory$requestPath
'
;
final
List
<
int
>
bytes
=
data
.
buffer
.
asUint8List
(
data
.
offsetInBytes
,
data
.
lengthInBytes
);
// 读取并解压zip文件内容
final
Archive
archive
=
ZipDecoder
().
decodeBytes
(
bytes
);
// 查找请求的内部文件
ArchiveFile
?
targetFile
;
for
(
final
file
in
archive
)
{
// 标准化路径分隔符(统一使用 '/')
String
zipFileName
=
file
.
name
.
replaceAll
(
'
\\
'
,
'/'
);
// 移除开头的 '/'(如果存在)
// 检查文件是否存在
if
(
requestPath
.
startsWith
(
'/'
))
{
final
File
file
=
File
(
filePath
);
requestPath
=
requestPath
.
substring
(
1
);
if
(
await
file
.
exists
())
{
}
// 读取文件内容
final
List
<
int
>
bytes
=
await
file
.
readAsBytes
();
if
(
zipFileName
==
requestPath
)
{
targetFile
=
file
;
break
;
}
}
if
(
targetFile
==
null
)
{
request
.
response
..
headers
.
contentType
=
ContentType
.
parse
(
_getContentType
(
filePath
))
..
add
(
bytes
)
..
close
();
}
else
{
request
.
response
request
.
response
..
statusCode
=
HttpStatus
.
notFound
..
statusCode
=
HttpStatus
.
notFound
..
write
(
'File not found
in zip:
$request
Path
'
)
..
write
(
'File not found
:
$file
Path
'
)
..
close
();
..
close
();
return
;
}
}
// 返回文件内容
request
.
response
..
headers
.
contentType
=
ContentType
.
parse
(
_getContentType
(
requestPath
))
..
add
(
targetFile
.
content
as
List
<
int
>)
..
close
();
}
catch
(
e
)
{
}
catch
(
e
)
{
print
(
'读取
zip
文件时出错:
$e
'
);
print
(
'读取
临时
文件时出错:
$e
'
);
request
.
response
request
.
response
..
statusCode
=
HttpStatus
.
internalServerError
..
statusCode
=
HttpStatus
.
notFound
..
write
(
'
Error reading zip file
'
)
..
write
(
'
File not found
'
)
..
close
();
..
close
();
}
}
}
}
...
@@ -148,4 +134,68 @@ class LocalServerService {
...
@@ -148,4 +134,68 @@ class LocalServerService {
if
(
filePath
.
endsWith
(
'.jpg'
)
||
filePath
.
endsWith
(
'.jpeg'
))
return
'image/jpeg'
;
if
(
filePath
.
endsWith
(
'.jpg'
)
||
filePath
.
endsWith
(
'.jpeg'
))
return
'image/jpeg'
;
return
'application/octet-stream'
;
return
'application/octet-stream'
;
}
}
Future
<
String
>
getHttpDirectory
()
async
{
if
(
_httpDirectory
==
null
)
{
await
_initHttpDirectory
();
}
return
_httpDirectory
!;
}
Future
<
void
>
_initHttpDirectory
()
async
{
var
direct
=
await
getExternalStorageDirectory
();
_httpDirectory
=
'
${direct?.path}
/http_dist_assets'
;
// var direct = await getApplicationSupportDirectory();
// _httpDirectory = '${direct.path}/http_dist_assets';
}
Future
<
void
>
_extractDist
()
async
{
var
outputDirectory
=
await
getHttpDirectory
();
// 判断目录存在则不需要再解压
if
(
Directory
(
outputDirectory
).
existsSync
())
{
return
;
}
var
zipFilePath
=
"assets/dist.zip"
;
final
ByteData
data
=
await
rootBundle
.
load
(
zipFilePath
);
final
bytes
=
data
.
buffer
.
asUint8List
();
// 解码 ZIP 文件
final
archive
=
ZipDecoder
().
decodeBytes
(
bytes
);
// 提取文件到指定目录
for
(
final
file
in
archive
)
{
final
filename
=
file
.
name
;
if
(
file
.
isFile
)
{
final
data
=
file
.
content
as
List
<
int
>;
final
outputFile
=
File
(
'
$outputDirectory
/
$filename
'
);
outputFile
.
createSync
(
recursive:
true
);
outputFile
.
writeAsBytesSync
(
data
);
}
else
{
// 创建目录
Directory
(
'
$outputDirectory
/
$filename
'
).
createSync
(
recursive:
true
);
}
}
}
Future
<
void
>
_clearDist
()
async
{
var
outputDirectory
=
await
getHttpDirectory
();
Directory
(
outputDirectory
).
deleteSync
(
recursive:
true
);
}
void
_downloadDist
()
async
{
var
httpDirectory
=
await
getHttpDirectory
();
var
distUrl
=
"https://github.com/xinxin-wu/flutter_web_dist/releases/download/v1.0.0/dist.zip"
;
// Dio进行下载
var
dio
=
Dio
();
dio
.
download
(
distUrl
,
'
$httpDirectory
/dist.zip'
,
onReceiveProgress:
(
received
,
total
)
{
if
(
total
!=
-
1
)
{
print
((
received
/
total
*
100
).
toStringAsFixed
(
0
)
+
"%"
);
}
}).
then
((
_
)
{
_extractDist
();
});
dio
.
close
();
}
}
}
lib/ui/pages/web_page.dart
View file @
128403d
import
'package:appframe/bloc/web_cubit.dart'
;
import
'package:appframe/bloc/web_cubit.dart'
;
import
'package:appframe/config/locator.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter_bloc/flutter_bloc.dart'
;
import
'package:flutter_bloc/flutter_bloc.dart'
;
import
'package:go_router/go_router.dart'
;
import
'package:go_router/go_router.dart'
;
import
'package:shared_preferences/shared_preferences.dart'
;
import
'package:webview_flutter/webview_flutter.dart'
;
import
'package:webview_flutter/webview_flutter.dart'
;
class
WebPage
extends
StatelessWidget
{
class
WebPage
extends
StatelessWidget
{
...
@@ -18,6 +20,16 @@ class WebPage extends StatelessWidget {
...
@@ -18,6 +20,16 @@ class WebPage extends StatelessWidget {
var
userType
=
extraData
?[
'userType'
];
var
userType
=
extraData
?[
'userType'
];
var
stuId
=
extraData
?[
'stuId'
];
var
stuId
=
extraData
?[
'stuId'
];
if
(
sessionCode
==
null
||
sessionCode
==
''
)
{
var
sharedPreferences
=
getIt
.
get
<
SharedPreferences
>();
sessionCode
=
sharedPreferences
.
getString
(
'auth_sessionCode'
);
userCode
=
sharedPreferences
.
getString
(
'auth_userCode'
);
classCode
=
sharedPreferences
.
getString
(
'auth_classCode'
);
userType
=
sharedPreferences
.
getInt
(
'auth_userType'
);
stuId
=
sharedPreferences
.
getString
(
'auth_stuId'
);
ip
=
sharedPreferences
.
getString
(
'auth_ip'
);
}
return
BlocProvider
(
return
BlocProvider
(
create:
(
context
)
=>
WebCubit
(
create:
(
context
)
=>
WebCubit
(
WebState
(
WebState
(
...
@@ -59,19 +71,47 @@ class WebPage extends StatelessWidget {
...
@@ -59,19 +71,47 @@ class WebPage extends StatelessWidget {
context:
context
,
context:
context
,
builder:
(
BuildContext
context
)
{
builder:
(
BuildContext
context
)
{
return
SizedBox
(
return
SizedBox
(
height:
2
00
,
height:
3
00
,
child:
Column
(
child:
Column
(
children:
[
children:
[
ListTile
(
ListTile
(
title:
Text
(
'选项1'
),
leading:
const
Icon
(
Icons
.
chat_outlined
),
title:
const
Text
(
'微信授权'
),
onTap:
()
{
Navigator
.
pop
(
context
);
ctx
.
read
<
WebCubit
>().
goWechatAuth
();
},
),
ListTile
(
leading:
const
Icon
(
Icons
.
accessibility_new
),
title:
const
Text
(
'身份认证'
),
onTap:
()
{
Navigator
.
pop
(
context
);
ctx
.
read
<
WebCubit
>().
goAuth
();
},
),
ListTile
(
leading:
const
Icon
(
Icons
.
app_blocking_sharp
),
title:
const
Text
(
'打开小程序'
),
onTap:
()
{
Navigator
.
pop
(
context
);
ctx
.
read
<
WebCubit
>().
goMiniProgram
();
},
),
ListTile
(
leading:
const
Icon
(
Icons
.
refresh
),
title:
const
Text
(
'刷新'
),
onTap:
()
{
onTap:
()
{
Navigator
.
pop
(
context
);
Navigator
.
pop
(
context
);
ctx
.
read
<
WebCubit
>().
refresh
();
},
},
),
),
ListTile
(
ListTile
(
title:
Text
(
'选项2'
),
leading:
const
Icon
(
Icons
.
cleaning_services
),
title:
const
Text
(
'清理缓存'
),
onTap:
()
{
onTap:
()
{
Navigator
.
pop
(
context
);
Navigator
.
pop
(
context
);
ctx
.
read
<
WebCubit
>().
clearStorage
();
},
},
),
),
],
],
...
@@ -91,43 +131,6 @@ class WebPage extends StatelessWidget {
...
@@ -91,43 +131,6 @@ class WebPage extends StatelessWidget {
child:
WebViewWidget
(
controller:
ctx
.
read
<
WebCubit
>().
controller
),
child:
WebViewWidget
(
controller:
ctx
.
read
<
WebCubit
>().
controller
),
)
)
:
const
Center
(
child:
CircularProgressIndicator
()),
:
const
Center
(
child:
CircularProgressIndicator
()),
// 用于测试一下点击跳转路由
floatingActionButton:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
end
,
children:
[
FloatingActionButton
(
heroTag:
"btn1"
,
onPressed:
()
{
ctx
.
read
<
WebCubit
>().
goWechatAuth
();
},
child:
const
Icon
(
Icons
.
chat_outlined
),
),
const
SizedBox
(
height:
16
),
FloatingActionButton
(
heroTag:
"btn2"
,
onPressed:
()
{
ctx
.
read
<
WebCubit
>().
goAuth
();
},
child:
const
Icon
(
Icons
.
accessibility_new
),
),
const
SizedBox
(
height:
16
),
FloatingActionButton
(
heroTag:
"btn3"
,
onPressed:
()
{
ctx
.
read
<
WebCubit
>().
goMiniProgram
();
},
child:
const
Icon
(
Icons
.
app_blocking_sharp
),
),
const
SizedBox
(
height:
16
),
FloatingActionButton
(
heroTag:
"btn4"
,
onPressed:
()
async
{
ctx
.
read
<
WebCubit
>().
refresh
();
},
child:
const
Icon
(
Icons
.
refresh
),
),
],
),
),
),
);
);
},
},
...
@@ -138,292 +141,11 @@ class WebPage extends StatelessWidget {
...
@@ -138,292 +141,11 @@ class WebPage extends StatelessWidget {
context
.
read
<
WebCubit
>().
getWindowInfo
(
context
);
context
.
read
<
WebCubit
>().
getWindowInfo
(
context
);
}
else
if
(
state
.
chooseImageCmdFlag
)
{
}
else
if
(
state
.
chooseImageCmdFlag
)
{
context
.
read
<
WebCubit
>().
chooseImage
(
context
);
context
.
read
<
WebCubit
>().
chooseImage
(
context
);
}
else
if
(
state
.
chooseVideoCmdFlag
)
{
context
.
read
<
WebCubit
>().
chooseVideo
(
context
);
}
}
},
},
),
),
);
);
}
}
}
}
// class _WebPageState extends State<WebPage> with WidgetsBindingObserver {
// @override
// void initState() {
// super.initState();
// WidgetsBinding.instance.addObserver(this);
// }
//
// @override
// void dispose() {
// WidgetsBinding.instance.removeObserver(this);
// super.dispose();
// }
//
// Orientation? _lastOrientation;
//
// /// 用于监听处理屏幕方向变化
// @override
// Future<void> didChangeMetrics() async {
// print('didChangeMetrics------------------------------->');
// final mediaQuery = MediaQuery.of(context);
// // 方向
// final orientation = mediaQuery.orientation;
// if (orientation != _lastOrientation) {
// _lastOrientation = orientation;
// await getIt.get<SharedPreferences>().setString(
// 'orientation',
// orientation == Orientation.portrait ? "portrait" : "landscape",
// );
//
// final viewPadding = mediaQuery.viewPadding;
// final size = mediaQuery.size;
// final safeArea = mediaQuery.padding;
// final devicePixelRatio = mediaQuery.devicePixelRatio;
//
// // 计算安全区域坐标
// final safeAreaLeft = safeArea.left;
// final safeAreaRight = size.width - safeArea.right;
// final safeAreaTop = safeArea.top;
// final safeAreaBottom = size.height - safeArea.bottom;
// final safeAreaWidth = size.width - safeArea.horizontal;
// final safeAreaHeight = size.height - safeArea.vertical;
//
// final windowInfo = {
// 'pixelRatio': devicePixelRatio,
// 'screenWidth': size.width * devicePixelRatio,
// 'screenHeight': size.height * devicePixelRatio,
// 'windowWidth': size.width,
// 'windowHeight': size.height,
// 'statusBarHeight': viewPadding.top,
// 'screenTop': 0, // Flutter中通常不使用此值,设为0
// 'safeArea': {
// 'left': safeAreaLeft,
// 'right': safeAreaRight,
// 'top': safeAreaTop,
// 'bottom': safeAreaBottom,
// 'width': safeAreaWidth,
// 'height': safeAreaHeight,
// },
// };
// await getIt.get<SharedPreferences>().setString('windowInfo', jsonEncode(windowInfo));
// }
// }
//
// @override
// Widget build(BuildContext buildContext) {
// final Map<String, dynamic>? extraData = GoRouterState.of(buildContext).extra as Map<String, dynamic>?;
//
// var ip = extraData?['ip'] ?? '127.0.0.1';
// var sessionCode = extraData?['sessionCode'];
// var userCode = extraData?['userCode'];
// var classCode = extraData?['classCode'];
// var userType = extraData?['userType'];
// var stuId = extraData?['stuId'];
//
// return BlocProvider(
// create: (context) => WebCubit(
// WebState(
// ip: ip,
// sessionCode: sessionCode,
// userCode: userCode,
// classCode: classCode,
// userType: userType,
// stuId: stuId,
// ),
// ),
// child: BlocConsumer<WebCubit, WebState>(
// builder: (ctx, state) {
// return PopScope(
// canPop: false,
// onPopInvokedWithResult: (didPop, result) {
// ctx.read<WebCubit>().handleBack();
// },
// child: Scaffold(
// appBar: AppBar(
// title: Text(state.title),
// centerTitle: true,
// automaticallyImplyLeading: false,
// leading: state.beBack
// ? IconButton(
// icon: const Icon(Icons.arrow_back_ios),
// onPressed: () {
// ctx.read<WebCubit>().handleBack();
// },
// )
// : null,
// actions: [
// Builder(
// builder: (BuildContext context) {
// return IconButton(
// icon: const Icon(Icons.more_vert),
// onPressed: () {
// showModalBottomSheet(
// context: context,
// builder: (BuildContext context) {
// return SizedBox(
// height: 200,
// child: Column(
// children: [
// ListTile(
// title: Text('选项1'),
// onTap: () {
// Navigator.pop(context);
// },
// ),
// ListTile(
// title: Text('选项2'),
// onTap: () {
// Navigator.pop(context);
// },
// ),
// ],
// ),
// );
// },
// );
// },
// );
// },
// ),
// ],
// ),
// body: state.loaded
// ? SizedBox(
// height: MediaQuery.of(ctx).size.height - 120, // 减去100像素留空
// child: WebViewWidget(controller: ctx.read<WebCubit>().controller),
// )
// : const Center(child: CircularProgressIndicator()),
// // 用于测试一下点击跳转路由
// floatingActionButton: Column(
// mainAxisAlignment: MainAxisAlignment.end,
// children: [
// FloatingActionButton(
// heroTag: "btn1",
// onPressed: () {
// ctx.read<WebCubit>().goWechatAuth();
// },
// child: const Icon(Icons.chat_outlined),
// ),
// const SizedBox(height: 16),
// FloatingActionButton(
// heroTag: "btn2",
// onPressed: () {
// ctx.read<WebCubit>().goAuth();
// },
// child: const Icon(Icons.accessibility_new),
// ),
// const SizedBox(height: 16),
// FloatingActionButton(
// heroTag: "btn3",
// onPressed: () {
// ctx.read<WebCubit>().goMiniProgram();
// },
// child: const Icon(Icons.app_blocking_sharp),
// ),
// const SizedBox(height: 16),
// FloatingActionButton(
// heroTag: "btn4",
// onPressed: () async {
// ctx.read<WebCubit>().refresh();
// },
// child: const Icon(Icons.refresh),
// ),
// ],
// ),
// ),
// );
// },
// listener: (context, state) {
// if (state.orientationCmdFlag) {
// context.read<WebCubit>().getOrientation(context);
// } else if (state.windowInfoCmdFlag) {
// context.read<WebCubit>().getWindowInfo(context);
// }
// },
// ),
// );
// }
// }
// class WebPage extends StatelessWidget {
// const WebPage({super.key});
//
// @override
// Widget build(BuildContext context) {
// final Map<String, dynamic>? extraData = GoRouterState.of(context).extra as Map<String, dynamic>?;
//
// var ip = extraData?['ip'] ?? '127.0.0.1';
// var sessionCode = extraData?['sessionCode'];
// var userCode = extraData?['userCode'];
// var classCode = extraData?['classCode'];
// var userType = extraData?['userType'];
// var stuId = extraData?['stuId'];
//
// return BlocProvider(
// create: (context) => WebCubit(
// WebState(
// ip: ip,
// sessionCode: sessionCode,
// userCode: userCode,
// classCode: classCode,
// userType: userType,
// stuId: stuId,
// ),
// ),
// child: BlocConsumer<WebCubit, WebState>(
// builder: (context, state) {
// return PopScope(
// canPop: false,
// onPopInvokedWithResult: (didPop, result) {
// context.read<WebCubit>().handleBack();
// },
// child: Scaffold(
// appBar: AppBar(title: Text(state.title)),
// body: state.loaded
// ? SizedBox(
// height: MediaQuery.of(context).size.height - 120, // 减去100像素留空
// child: WebViewWidget(controller: context.read<WebCubit>().controller),
// )
// : const Center(child: CircularProgressIndicator()),
// // 用于测试一下点击跳转路由
// floatingActionButton: Column(
// mainAxisAlignment: MainAxisAlignment.end,
// children: [
// FloatingActionButton(
// heroTag: "btn1",
// onPressed: () {
// context.read<WebCubit>().goWechatAuth();
// },
// child: const Icon(Icons.chat_outlined),
// ),
// const SizedBox(height: 16),
// FloatingActionButton(
// heroTag: "btn2",
// onPressed: () {
// context.read<WebCubit>().goAuth();
// },
// child: const Icon(Icons.accessibility_new),
// ),
// const SizedBox(height: 16),
// FloatingActionButton(
// heroTag: "btn3",
// onPressed: () {
// context.read<WebCubit>().goMiniProgram();
// },
// child: const Icon(Icons.app_blocking_sharp),
// ),
// ],
// ),
// ),
// );
// },
// listener: (context, state) {
// if(!state.recorderIsInit) {
//
// }
// },
// ),
// );
// }
//
//
// }
Write
Preview
Styling with
Markdown
is supported
Attach a file
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to post a comment