Compare commits
19 Commits
9ac7462523
...
99ab6695a9
| Author | SHA1 | Date |
|---|---|---|
|
|
99ab6695a9 | |
|
|
172e30b2a3 | |
|
|
ad9e833aba | |
|
|
229965a3c9 | |
|
|
51d33a1137 | |
|
|
784cad7f3d | |
|
|
f6af40701c | |
|
|
0f139adcdd | |
|
|
8e8bf0d53c | |
|
|
5c9ec8a98b | |
|
|
dc2acc7284 | |
|
|
625579e510 | |
|
|
6474edc27e | |
|
|
67be45e447 | |
|
|
120cccf235 | |
|
|
b6a1e84658 | |
|
|
bda8bb5570 | |
|
|
87b3157e14 | |
|
|
cef3227375 |
|
|
@ -1,20 +1,102 @@
|
||||||
class JavaScriptString {
|
class JavaScriptString {
|
||||||
/// 点击监听
|
/// 点击监听
|
||||||
static String clickEventkJSString =
|
static String clickEventJSString =
|
||||||
'''document.addEventListener('click', function(event) {
|
'''document.addEventListener('click', function(event) {
|
||||||
var x = event.clientX;
|
var x = event.clientX;
|
||||||
var y = event.clientY;
|
var y = event.clientY;
|
||||||
|
|
||||||
console.log('点击坐标:x=' + x + ', y=' + y);
|
console.log('点击坐标:x=' + x + ', y=' + y);
|
||||||
window.flutter_inappwebview.callHandler('Click', x, y);
|
window.flutter_inappwebview.callHandler('click', x, y);
|
||||||
|
|
||||||
});''';
|
});''';
|
||||||
|
|
||||||
|
/// 触摸监听
|
||||||
|
static String touchendEventJSString =
|
||||||
|
'''document.addEventListener('touchend', function(event) {
|
||||||
|
var x = event.changedTouches[0].clientX;
|
||||||
|
var y = event.changedTouches[0].clientY;
|
||||||
|
|
||||||
|
// 获取目标元素
|
||||||
|
var target = event.target;
|
||||||
|
|
||||||
|
// 获取目标元素的class和id
|
||||||
|
var targetClass = target.className;
|
||||||
|
var targetId = target.id;
|
||||||
|
|
||||||
|
console.log('Class: ' + targetClass);
|
||||||
|
console.log('Id: ' + targetId);
|
||||||
|
|
||||||
|
console.log('触摸坐标:x=' + x + ', y=' + y);
|
||||||
|
window.flutter_inappwebview.callHandler('touchend', x, y);
|
||||||
|
|
||||||
|
});''';
|
||||||
|
|
||||||
|
/// 退出登录
|
||||||
|
static String loginOutJsString = '''
|
||||||
|
\$.ajax({
|
||||||
|
type: "GET",
|
||||||
|
url: "/api/logout.do",
|
||||||
|
success: function(t) {
|
||||||
|
"index.html" != location.pathname ? window.location.href = "index.html" : location.reload()
|
||||||
|
},
|
||||||
|
error: function(t) {
|
||||||
|
var e = \$.parseJSON(t.responseText + "");
|
||||||
|
alert(e.msg, 2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
''';
|
||||||
|
|
||||||
/// 模拟点击
|
/// 模拟点击
|
||||||
static String clickJSString(int x, int y) {
|
static String clickJSString(int x, int y) {
|
||||||
return 'document.elementFromPoint($x, $y).click();';
|
return 'document.elementFromPoint($x, $y).click();';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 模拟触摸
|
||||||
|
static String touchendJsString(int x, int y) {
|
||||||
|
return '''
|
||||||
|
|
||||||
|
''';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 模拟触摸
|
||||||
|
static String getClassTouchendJsString(int x, int y) {
|
||||||
|
return '''
|
||||||
|
try{
|
||||||
|
// 获取鼠标点击位置的坐标
|
||||||
|
var x = $x;
|
||||||
|
var y = $y;
|
||||||
|
|
||||||
|
// 创建一个touchstart事件
|
||||||
|
var touchstartEvent = new TouchEvent('touchstart', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
view: window,
|
||||||
|
changedTouches: [new Touch({ identifier: Date.now(), target: document.body, clientX: x, clientY: y })],
|
||||||
|
targetTouches: [new Touch({ identifier: Date.now(), target: document.body, clientX: x, clientY: y })]
|
||||||
|
});
|
||||||
|
|
||||||
|
// touchstartEvent
|
||||||
|
document.body.dispatchEvent(touchstartEvent);
|
||||||
|
|
||||||
|
// 创建一个touchend事件
|
||||||
|
var touchendEvent = new TouchEvent('touchend', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
view: window,
|
||||||
|
changedTouches: [new Touch({ identifier: Date.now(), target: document.body, clientX: x, clientY: y })],
|
||||||
|
targetTouches: [new Touch({ identifier: Date.now(), target: document.body, clientX: x, clientY: y })]
|
||||||
|
});
|
||||||
|
|
||||||
|
// 触发touchend事件
|
||||||
|
document.body.dispatchEvent(touchendEvent);
|
||||||
|
|
||||||
|
} catch (t) {
|
||||||
|
console.log('模拟触摸错误 -- ' + t);
|
||||||
|
}
|
||||||
|
|
||||||
|
''';
|
||||||
|
}
|
||||||
|
|
||||||
/// 输入
|
/// 输入
|
||||||
static String inputJsString(int value) {
|
static String inputJsString(int value) {
|
||||||
return '''
|
return '''
|
||||||
|
|
@ -24,7 +106,7 @@ var inputEvent = new Event('input', {
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
var inputElement = document.querySelector(".bet-money");
|
var inputElement = document.querySelector(".input");
|
||||||
|
|
||||||
inputElement.value = "$value";
|
inputElement.value = "$value";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
|
||||||
|
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||||
|
import 'package:common/utils/platform_utils.dart';
|
||||||
|
import 'package:common/utils/toast_utils.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:web_synchronization_tool/login/login_socket_utils.dart';
|
||||||
|
import 'package:web_synchronization_tool/login/widget/account_number_login_widget.dart';
|
||||||
|
import 'package:web_synchronization_tool/windows/windows_main_page.dart';
|
||||||
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
|
import '../windows/socket_tool.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class LoginPage extends StatefulWidget {
|
||||||
|
const LoginPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LoginPage> createState() => _LoginPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoginPageState extends State<LoginPage> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
initWindow();
|
||||||
|
|
||||||
|
LoginSocketUtils.getInstance().connect();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
initWindow(){
|
||||||
|
//允许调整窗口大小
|
||||||
|
windowManager.setResizable(false);
|
||||||
|
|
||||||
|
const double width = 800;
|
||||||
|
const double height = 600;
|
||||||
|
//设置最小大小
|
||||||
|
const windowSize = Size(width, height);
|
||||||
|
windowManager.setSize(windowSize);
|
||||||
|
appWindow.minSize = windowSize;
|
||||||
|
windowManager.center();
|
||||||
|
windowManager.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xff272b38),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.all( 140 ),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: PlatformUtils.isPhoneWeb(context) ? MediaQuery.of(context).size.width - 40 : 400,
|
||||||
|
child: AccountNumberLoginWidget(
|
||||||
|
loginTap: Login,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 登录
|
||||||
|
Login(String account, String password) {
|
||||||
|
|
||||||
|
ToastUtils.showLoading();
|
||||||
|
|
||||||
|
/// 发送登录消息
|
||||||
|
LoginSocketUtils.getInstance().login(account, password,(data){
|
||||||
|
ToastUtils.dismissLoading();
|
||||||
|
|
||||||
|
if(data['err'] == 1){
|
||||||
|
ToastUtils.showToast('登录失败!请检查账号和密码');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 登录服务心跳
|
||||||
|
LoginSocketUtils.getInstance().heartbeat();
|
||||||
|
/// 同步socket
|
||||||
|
SocketUtils.getInstance().connect();
|
||||||
|
SocketUtils.getInstance().heartbeat();
|
||||||
|
|
||||||
|
Navigator.pushAndRemoveUntil(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (_) => const WindowsPage()),
|
||||||
|
(route) => false);
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,168 @@
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:ffi';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:common/utils/toast_utils.dart';
|
||||||
|
import 'package:web_synchronization_tool/windows/little_extension.dart';
|
||||||
|
import 'package:web_synchronization_tool/windows/socket_tool.dart';
|
||||||
|
|
||||||
|
import '../windows/code.dart';
|
||||||
|
|
||||||
|
class LoginSocketUtils extends LoginSocket {
|
||||||
|
|
||||||
|
// 私有构造函数
|
||||||
|
LoginSocketUtils._();
|
||||||
|
// 私有静态变量,保存类的唯一实例
|
||||||
|
static LoginSocketUtils? _instance;
|
||||||
|
|
||||||
|
// 公开的静态方法,返回类的唯一实例
|
||||||
|
static LoginSocketUtils getInstance() {
|
||||||
|
_instance ??= LoginSocketUtils._();
|
||||||
|
return _instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
typedef loginBlockFun = Function(Map);
|
||||||
|
|
||||||
|
|
||||||
|
class LoginSocket{
|
||||||
|
|
||||||
|
String url = '110.42.251.214';
|
||||||
|
static int port = 37785;
|
||||||
|
|
||||||
|
String uuid = '';
|
||||||
|
|
||||||
|
Socket? socket;
|
||||||
|
|
||||||
|
int heartTime = 0;
|
||||||
|
|
||||||
|
|
||||||
|
connect() async {
|
||||||
|
socket?.close();
|
||||||
|
socket = await Socket.connect(url, port, timeout: const Duration(seconds: 30));
|
||||||
|
heartTime = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
|
||||||
|
socket?.listen((event) {
|
||||||
|
|
||||||
|
Map data = dataCute(event);
|
||||||
|
|
||||||
|
// 登录结果
|
||||||
|
if (data['op'] == 2){
|
||||||
|
loginBlock(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 服务器心跳
|
||||||
|
if (data['op'] == 4){
|
||||||
|
try{
|
||||||
|
|
||||||
|
// 更新上次心跳时间
|
||||||
|
heartTime = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
|
||||||
|
// 心跳回报时间是否超时
|
||||||
|
int time = data['time'];
|
||||||
|
time = DateTime.now().millisecondsSinceEpoch - time;
|
||||||
|
if(time> (3 * 60 * 1000) ){ // 时间戳 > 3分钟
|
||||||
|
socketError();
|
||||||
|
}
|
||||||
|
}catch(e){
|
||||||
|
socketError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
},onDone: (){
|
||||||
|
},onError: (error){
|
||||||
|
// 连接断开
|
||||||
|
socketError();
|
||||||
|
socket?.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
socketError(){
|
||||||
|
/// 任何错误都显示这个
|
||||||
|
ToastUtils.showLoading(msg: '重连中');
|
||||||
|
/// 关闭同步 socket
|
||||||
|
SocketUtils.getInstance().socket?.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
late loginBlockFun loginBlock;
|
||||||
|
|
||||||
|
// 登录
|
||||||
|
login(String account,String passwoord,loginBlockFun blockFun){
|
||||||
|
|
||||||
|
loginBlock = blockFun;
|
||||||
|
|
||||||
|
Map map = {
|
||||||
|
'op':1,
|
||||||
|
'account':account,
|
||||||
|
'password':passwoord,
|
||||||
|
'uuid':uuid
|
||||||
|
};
|
||||||
|
|
||||||
|
final dd = dataMake(map);
|
||||||
|
socket?.add(dd);
|
||||||
|
socket?.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 心跳
|
||||||
|
heartbeat(){
|
||||||
|
|
||||||
|
Timer(const Duration(seconds: 20), () async {
|
||||||
|
|
||||||
|
final time = DateTime.now().millisecondsSinceEpoch - heartTime;
|
||||||
|
if(time > (3 * 60 * 1000) ){ // 时间戳 > 3分钟
|
||||||
|
socketError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
heartbeat();
|
||||||
|
|
||||||
|
Map map = {
|
||||||
|
"op": 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
socket?.add(dataMake(map));
|
||||||
|
socket?.flush();
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// 发送数据的处理
|
||||||
|
List<int> dataMake(Map map){
|
||||||
|
|
||||||
|
|
||||||
|
// 将Map对象转换为JSON字符串
|
||||||
|
String json = jsonEncode(map);
|
||||||
|
// 加密
|
||||||
|
String ps = makecode(json, skey);
|
||||||
|
|
||||||
|
List<int> bytes = ps.codeUnits; // 将字符串转换为字节数组
|
||||||
|
|
||||||
|
return bytes.toLittle();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 接收数据处理
|
||||||
|
Map dataCute(Uint8List data){
|
||||||
|
|
||||||
|
if (data.length <= 4) return {};
|
||||||
|
|
||||||
|
Uint8List pData = data.sublist(4,data.length);
|
||||||
|
|
||||||
|
String str = cutecode(pData);
|
||||||
|
|
||||||
|
Map map = json.decode(str);
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
|
||||||
|
import 'package:common/utils/toast_utils.dart';
|
||||||
|
import 'login_text_field_widget.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:common/utils/widget_utils.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class LoginController{
|
||||||
|
|
||||||
|
set name(String newText) {
|
||||||
|
if (nameChang != null){
|
||||||
|
nameChang!(newText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Function(String name)? nameChang;
|
||||||
|
|
||||||
|
set password(String newText) {
|
||||||
|
if (passwordChang != null){
|
||||||
|
passwordChang!(newText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Function(String password)? passwordChang;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class AccountNumberLoginWidget extends StatefulWidget {
|
||||||
|
const AccountNumberLoginWidget({Key? key, required this.loginTap, this.controller }) : super(key: key);
|
||||||
|
|
||||||
|
final LoginController? controller;
|
||||||
|
final Function(String,String) loginTap;
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AccountNumberLoginWidget> createState() =>
|
||||||
|
_AccountNumberLoginWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AccountNumberLoginWidgetState extends State<AccountNumberLoginWidget> {
|
||||||
|
TextEditingController phoneController = TextEditingController();
|
||||||
|
TextEditingController passwordController = TextEditingController();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
_login() {
|
||||||
|
|
||||||
|
if (phoneController.text.isEmpty || passwordController.text.isEmpty){
|
||||||
|
ToastUtils.showToast('请输入账号和密码');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
widget.loginTap(phoneController.text,passwordController.text);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
widget.controller?.nameChang = (name){
|
||||||
|
phoneController.text = name;
|
||||||
|
};
|
||||||
|
widget.controller?.passwordChang = (password){
|
||||||
|
passwordController.text = password;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
LoginPhoneTextField(controller: phoneController),
|
||||||
|
WidgetUtils.spacer(height: 15),
|
||||||
|
LoginPasswordTextField(controller: passwordController,onSubmitted: (_){
|
||||||
|
_login();
|
||||||
|
},),
|
||||||
|
WidgetUtils.spacer(height: 15),
|
||||||
|
TextButton(
|
||||||
|
onPressed: _login,
|
||||||
|
style: TextButton.styleFrom(padding: EdgeInsets.zero),
|
||||||
|
child: Container(
|
||||||
|
height: 40,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.blue,
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(5))),
|
||||||
|
child: const Text(
|
||||||
|
'登录',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
class LoginPhoneTextField extends StatelessWidget {
|
||||||
|
const LoginPhoneTextField(
|
||||||
|
{Key? key, required this.controller, this.helperText, this.rightWidget})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
final TextEditingController controller;
|
||||||
|
final String? helperText;
|
||||||
|
final Widget? rightWidget;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Widget textField = SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: TextFieldBase(
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
leftWidget: const Padding(
|
||||||
|
padding: EdgeInsets.only(left: 10),
|
||||||
|
child: Icon(
|
||||||
|
Icons.perm_contact_calendar_sharp,
|
||||||
|
color: Colors.grey,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
rightWidget: rightWidget,
|
||||||
|
hintText: '请输入账号',
|
||||||
|
style: const TextStyle(color: Colors.white),
|
||||||
|
controller: controller,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return helperText == null
|
||||||
|
? textField
|
||||||
|
: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
textField,
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 5),
|
||||||
|
child: Text(
|
||||||
|
helperText!,
|
||||||
|
style: const TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoginPasswordTextField extends StatefulWidget {
|
||||||
|
const LoginPasswordTextField(
|
||||||
|
{Key? key,
|
||||||
|
required this.controller,
|
||||||
|
this.hintText = '请输入密码',
|
||||||
|
this.onSubmitted})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
final TextEditingController controller;
|
||||||
|
final String hintText;
|
||||||
|
final ValueChanged<String>? onSubmitted;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LoginPasswordTextField> createState() => _LoginPasswordTextFieldState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoginPasswordTextFieldState extends State<LoginPasswordTextField> {
|
||||||
|
bool hideCancel = true;
|
||||||
|
bool obsureText = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Widget cancel = Offstage(
|
||||||
|
offstage: hideCancel,
|
||||||
|
child: TextButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
overlayColor: MaterialStateProperty.all(Colors.transparent),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
obsureText = !obsureText;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Icon(
|
||||||
|
Icons.remove_red_eye_rounded,
|
||||||
|
color: Colors.grey,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
widget.controller.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
hideCancel = !widget.controller.text.isNotEmpty;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: TextFieldBase(
|
||||||
|
hintText: widget.hintText,
|
||||||
|
controller: widget.controller,
|
||||||
|
obsureText: obsureText,
|
||||||
|
onSubmitted: widget.onSubmitted,
|
||||||
|
leftWidget: const Padding(
|
||||||
|
padding: EdgeInsets.only(left: 10),
|
||||||
|
child: Icon(
|
||||||
|
Icons.lock_sharp,
|
||||||
|
color: Colors.grey,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
style: const TextStyle(color: Colors.white),
|
||||||
|
rightWidget: cancel,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TextFieldBase extends StatefulWidget {
|
||||||
|
const TextFieldBase(
|
||||||
|
{Key? key,
|
||||||
|
this.leftWidget,
|
||||||
|
this.rightWidget,
|
||||||
|
this.hintText = '',
|
||||||
|
required this.controller,
|
||||||
|
this.obsureText = false,
|
||||||
|
this.keyboardType,
|
||||||
|
this.textAlign = TextAlign.start,
|
||||||
|
this.onChanged,
|
||||||
|
this.onSubmitted,
|
||||||
|
this.inputFormatters,
|
||||||
|
this.focusNode,
|
||||||
|
this.decoration,
|
||||||
|
this.style,
|
||||||
|
this.noFocusSubmitted = false})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
final TextEditingController controller;
|
||||||
|
final FocusNode? focusNode;
|
||||||
|
final Widget? leftWidget;
|
||||||
|
final Widget? rightWidget;
|
||||||
|
final InputDecoration? decoration;
|
||||||
|
final TextStyle? style;
|
||||||
|
final TextInputType? keyboardType;
|
||||||
|
final String hintText;
|
||||||
|
/// 暗文
|
||||||
|
final bool obsureText;
|
||||||
|
final TextAlign textAlign;
|
||||||
|
final List<TextInputFormatter>? inputFormatters;
|
||||||
|
/// 失去焦点是否回调
|
||||||
|
final bool noFocusSubmitted;
|
||||||
|
final ValueChanged<String>? onChanged;
|
||||||
|
final ValueChanged<String>? onSubmitted;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<TextFieldBase> createState() => _TextFieldBaseState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TextFieldBaseState extends State<TextFieldBase> {
|
||||||
|
late FocusNode focusNode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
focusNode = widget.focusNode ?? FocusNode();
|
||||||
|
|
||||||
|
if (widget.onSubmitted != null && widget.noFocusSubmitted){
|
||||||
|
focusNode.addListener(() {
|
||||||
|
if (focusNode.hasFocus == false){
|
||||||
|
widget.onSubmitted!(widget.controller.text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final decoration = widget.decoration ??
|
||||||
|
InputDecoration(
|
||||||
|
prefixIcon: widget.leftWidget,
|
||||||
|
prefixIconConstraints: const BoxConstraints(minWidth: 4),
|
||||||
|
suffixIcon: widget.rightWidget,
|
||||||
|
hintText: widget.hintText,
|
||||||
|
hintStyle: const TextStyle(color: Colors.grey),
|
||||||
|
border: MaterialStateOutlineInputBorder.resolveWith((states) =>
|
||||||
|
const OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(color: Colors.grey))),
|
||||||
|
contentPadding:
|
||||||
|
const EdgeInsets.only(left: 0, top: 0, bottom: 0, right: 15),
|
||||||
|
);
|
||||||
|
|
||||||
|
return TextField(
|
||||||
|
focusNode: focusNode,
|
||||||
|
controller: widget.controller,
|
||||||
|
inputFormatters: widget.inputFormatters,
|
||||||
|
style: widget.style ?? const TextStyle(fontSize: 14),
|
||||||
|
keyboardType: widget.keyboardType,
|
||||||
|
obscureText: widget.obsureText,
|
||||||
|
textAlign: widget.textAlign,
|
||||||
|
cursorColor: Colors.blue,
|
||||||
|
onChanged: widget.onChanged,
|
||||||
|
onSubmitted: widget.onSubmitted,
|
||||||
|
decoration: decoration,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,53 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:web_synchronization_tool/main_page.dart';
|
|
||||||
|
|
||||||
void main() {
|
import 'package:common/utils/toast_utils.dart';
|
||||||
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:web_synchronization_tool/login/login_page.dart';
|
||||||
|
import 'package:web_synchronization_tool/login/login_socket_utils.dart';
|
||||||
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
await windowManager.ensureInitialized();
|
||||||
|
|
||||||
|
WindowOptions windowOptions = const WindowOptions(
|
||||||
|
center: true, backgroundColor: Colors.white,// fullScreen: true,//, title: '赢佳'
|
||||||
|
// skipTaskbar: true, //跳过任务栏,任务栏无显示
|
||||||
|
// titleBarStyle: TitleBarStyle.hidden, //隐藏顶部标题栏
|
||||||
|
);
|
||||||
|
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||||
|
await windowManager.show();
|
||||||
|
await windowManager.focus();
|
||||||
|
});
|
||||||
|
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatefulWidget {
|
||||||
const MyApp({super.key});
|
const MyApp({super.key});
|
||||||
|
|
||||||
// This widget is the root of your application.
|
@override
|
||||||
|
State<MyApp> createState() => _MyAppState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MyAppState extends State<MyApp> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
info();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
info() async {
|
||||||
|
final deviceInfo = await DeviceInfoPlugin().deviceInfo;
|
||||||
|
String uuid = deviceInfo.data['deviceId'];
|
||||||
|
uuid = uuid.replaceAll('{', '');
|
||||||
|
uuid = uuid.replaceAll('}', '');
|
||||||
|
LoginSocketUtils.getInstance().uuid = uuid;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
|
|
@ -17,7 +56,10 @@ class MyApp extends StatelessWidget {
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
),
|
),
|
||||||
home: const MainPage(),
|
// home: const MainPage(),
|
||||||
|
home: const LoginPage(),
|
||||||
|
navigatorObservers: [FlutterSmartDialog.observer],
|
||||||
|
builder: FlutterSmartDialog.init(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,116 +1,145 @@
|
||||||
import 'package:flutter/material.dart';
|
//
|
||||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
// import 'package:flutter/material.dart';
|
||||||
import 'package:web_synchronization_tool/web_widget.dart';
|
// import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||||
|
// import 'package:web_synchronization_tool/web_widget.dart';
|
||||||
import 'JavaScriptString.dart';
|
//
|
||||||
|
// import 'JavaScriptString.dart';
|
||||||
class MainPage extends StatefulWidget {
|
//
|
||||||
const MainPage({super.key});
|
// class MainPage extends StatefulWidget {
|
||||||
|
// const MainPage({super.key});
|
||||||
@override
|
//
|
||||||
State<MainPage> createState() => _MainPageState();
|
// @override
|
||||||
}
|
// State<MainPage> createState() => _MainPageState();
|
||||||
|
// }
|
||||||
class _MainPageState extends State<MainPage> {
|
//
|
||||||
|
// class _MainPageState extends State<MainPage> {
|
||||||
late InAppWebViewController mainController;
|
//
|
||||||
late InAppWebViewController controller;
|
// late InAppWebViewController mainController;
|
||||||
|
// late InAppWebViewController controller;
|
||||||
@override
|
//
|
||||||
void initState() {
|
// bool asyncState = false;
|
||||||
super.initState();
|
//
|
||||||
|
// @override
|
||||||
|
// void initState() {
|
||||||
}
|
// super.initState();
|
||||||
|
//
|
||||||
/// 注入点击监听
|
// }
|
||||||
addClickEventJS(){
|
//
|
||||||
|
// @override
|
||||||
final clickJsUS = UserScript(groupName: 'click',source: JavaScriptString.clickEventkJSString, injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START);
|
// void dispose() {
|
||||||
|
// super.dispose();
|
||||||
mainController.addUserScript(userScript: clickJsUS);
|
//
|
||||||
|
// WebStorageManager.instance().deleteAllData();
|
||||||
mainController.addJavaScriptHandler(handlerName: 'Click', callback: (args){
|
// }
|
||||||
controller.evaluateJavascript(source: JavaScriptString.clickJSString(args.first, args.last) );
|
//
|
||||||
});
|
// /// 注入触摸监听
|
||||||
|
// addTouchendEventJS(){
|
||||||
}
|
//
|
||||||
|
// final clickJsUS = UserScript(groupName: 'touchend',source: JavaScriptString.touchendEventJSString, injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START);
|
||||||
@override
|
// mainController.addUserScript(userScript: clickJsUS);
|
||||||
Widget build(BuildContext context) {
|
//
|
||||||
return Scaffold(
|
// mainController.addJavaScriptHandler(handlerName: 'touchend', callback: (args){
|
||||||
body: Column(
|
// if (asyncState){
|
||||||
children: [
|
// int x = double.parse(args.first.toString()).toInt();
|
||||||
Container(
|
// int y = double.parse(args.last.toString()).toInt();
|
||||||
height: 50,
|
// controller.evaluateJavascript(source: JavaScriptString.clickJSString(x, y) );
|
||||||
color: Colors.white,
|
// // controller.evaluateJavascript(source: JavaScriptString.touchendJsString(x, y) );
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 50),
|
// // controller.evaluateJavascript(source: JavaScriptString.getClassTouchendJsString(x, y) );
|
||||||
child: Row(
|
// }
|
||||||
children: [
|
// });
|
||||||
TextButton(
|
//
|
||||||
onPressed: () {
|
// }
|
||||||
controller.evaluateJavascript(source: JavaScriptString.clickJSString(600, 280) );
|
//
|
||||||
},
|
// @override
|
||||||
child: const Text('模拟点击测试')),
|
// Widget build(BuildContext context) {
|
||||||
TextButton(
|
// return Scaffold(
|
||||||
onPressed: () {
|
// body: Column(
|
||||||
mainController.evaluateJavascript(source: JavaScriptString.inputJsString(45) );
|
// crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
controller.evaluateJavascript(source: JavaScriptString.inputJsString(45) );
|
// children: [
|
||||||
},
|
// Container(
|
||||||
child: const Text('模拟输入测试')),
|
// height: 50,
|
||||||
const SizedBox(
|
// color: Colors.white,
|
||||||
width: 50,
|
// padding: const EdgeInsets.symmetric(horizontal: 50),
|
||||||
child: TextField(
|
// child: Row(
|
||||||
keyboardType: TextInputType.number,
|
// children: [
|
||||||
decoration: InputDecoration(prefixText: '网页数量'),
|
// TextButton(
|
||||||
),
|
// onPressed: () {
|
||||||
)
|
// controller.evaluateJavascript(source: JavaScriptString.clickJSString(50, 100) );
|
||||||
],
|
// },
|
||||||
),
|
// child: const Text('模拟点击测试')),
|
||||||
),
|
// TextButton(
|
||||||
Expanded(child: pageViewWidget()),
|
// onPressed: () {
|
||||||
],
|
// mainController.evaluateJavascript(source: JavaScriptString.inputJsString(45) );
|
||||||
),
|
// controller.evaluateJavascript(source: JavaScriptString.inputJsString(45) );
|
||||||
);
|
// },
|
||||||
}
|
// child: const Text('模拟输入测试')),
|
||||||
|
// Row(
|
||||||
Widget pageViewWidget() {
|
// children: [
|
||||||
return PageView(
|
// const Text('同步'),
|
||||||
children: <Widget>[
|
// Switch(value: asyncState, onChanged: (value){
|
||||||
KeepAlivePage(
|
// setState(() {
|
||||||
child: WebWidget(controlerCallBack: (_mainController) {
|
// asyncState = value;
|
||||||
mainController = _mainController;
|
// });
|
||||||
|
// })
|
||||||
addClickEventJS();
|
// ],
|
||||||
}),
|
// )
|
||||||
),
|
// ],
|
||||||
KeepAlivePage(
|
// ),
|
||||||
child: WebWidget(controlerCallBack: (_controller) {
|
// ),
|
||||||
controller = _controller;
|
// Expanded(child: pageViewWidget()),
|
||||||
}),
|
// ],
|
||||||
)
|
// ),
|
||||||
],
|
// );
|
||||||
);
|
// }
|
||||||
}
|
//
|
||||||
|
// Widget pageViewWidget() {
|
||||||
}
|
// return Row(
|
||||||
|
// children: <Widget>[
|
||||||
class KeepAlivePage extends StatefulWidget {
|
// Expanded(
|
||||||
final Widget child;
|
// child: Container(
|
||||||
|
// color: Colors.yellow,
|
||||||
KeepAlivePage({super.key , required this.child});
|
// child: KeepAlivePage(
|
||||||
|
// child: WebWidget(controlerCallBack: (_mainController) {
|
||||||
@override
|
// mainController = _mainController;
|
||||||
_KeepAlivePageState createState() => _KeepAlivePageState();
|
//
|
||||||
}
|
// addTouchendEventJS();
|
||||||
|
//
|
||||||
class _KeepAlivePageState extends State<KeepAlivePage> with AutomaticKeepAliveClientMixin {
|
// }),
|
||||||
@override
|
// ),
|
||||||
bool get wantKeepAlive => true;
|
// ),
|
||||||
|
// ),
|
||||||
@override
|
// Expanded(
|
||||||
Widget build(BuildContext context) {
|
// child: Container(
|
||||||
super.build(context);
|
// color: Colors.blue,
|
||||||
return widget.child;
|
// child: KeepAlivePage(
|
||||||
}
|
// child: WebWidget(controlerCallBack: (_controller) {
|
||||||
}
|
// controller = _controller;
|
||||||
|
// }),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
// ],
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// class KeepAlivePage extends StatefulWidget {
|
||||||
|
// final Widget child;
|
||||||
|
//
|
||||||
|
// KeepAlivePage({super.key , required this.child});
|
||||||
|
//
|
||||||
|
// @override
|
||||||
|
// _KeepAlivePageState createState() => _KeepAlivePageState();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// class _KeepAlivePageState extends State<KeepAlivePage> with AutomaticKeepAliveClientMixin {
|
||||||
|
// @override
|
||||||
|
// bool get wantKeepAlive => true;
|
||||||
|
//
|
||||||
|
// @override
|
||||||
|
// Widget build(BuildContext context) {
|
||||||
|
// super.build(context);
|
||||||
|
// return widget.child;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,42 @@
|
||||||
import 'package:flutter/material.dart';
|
// import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
// import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||||
import 'package:web_synchronization_tool/JavaScriptString.dart';
|
// import 'package:web_synchronization_tool/JavaScriptString.dart';
|
||||||
|
//
|
||||||
class WebWidget extends StatefulWidget {
|
// class WebWidget extends StatefulWidget {
|
||||||
WebWidget({super.key, required this.controlerCallBack});
|
// WebWidget({super.key, required this.controlerCallBack});
|
||||||
|
//
|
||||||
final Function(InAppWebViewController) controlerCallBack;
|
// final Function(InAppWebViewController) controlerCallBack;
|
||||||
|
//
|
||||||
@override
|
// @override
|
||||||
State<WebWidget> createState() => _WebWidgetState();
|
// State<WebWidget> createState() => _WebWidgetState();
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
class _WebWidgetState extends State<WebWidget> {
|
// class _WebWidgetState extends State<WebWidget> {
|
||||||
@override
|
// @override
|
||||||
void initState() {
|
// void initState() {
|
||||||
super.initState();
|
// super.initState();
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@override
|
// @override
|
||||||
Widget build(BuildContext context) {
|
// Widget build(BuildContext context) {
|
||||||
return InAppWebView(
|
// return InAppWebView(
|
||||||
initialUrlRequest:
|
// initialUrlRequest:
|
||||||
URLRequest(url: WebUri('http://www.df6831.com/game/')),
|
// URLRequest(url: WebUri('http://www.df6831.com/mobile')),//
|
||||||
initialSettings: InAppWebViewSettings(initialScale: 200,loadWithOverviewMode: false,useWideViewPort: false),
|
// initialSettings: InAppWebViewSettings(
|
||||||
onWebViewCreated: (_controller) {
|
// // initialScale: 180,
|
||||||
widget.controlerCallBack(_controller);
|
// loadWithOverviewMode: false,
|
||||||
},
|
// useWideViewPort: false,
|
||||||
);
|
// // preferredContentMode: UserPreferredContentMode.MOBILE,
|
||||||
}
|
// // cacheEnabled: false, //启用缓存
|
||||||
}
|
// // clearSessionCache: true,//清除会话缓存
|
||||||
|
// // databaseEnabled:false, // 启用数据库
|
||||||
|
// // domStorageEnabled: false,//启用 dom 存储
|
||||||
|
// incognito: true, //隐身模式
|
||||||
|
// sharedCookiesEnabled: false, // 共享Cookie
|
||||||
|
// ),
|
||||||
|
// onWebViewCreated: (_controller) {
|
||||||
|
// widget.controlerCallBack(_controller);
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
// 定义密钥
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
List skey = [8, 1, 2, 5, 4];
|
||||||
|
|
||||||
|
// 单个字符异或运算
|
||||||
|
String makecodeChar(String c, int key, int key2) {
|
||||||
|
int charCode = c.codeUnitAt(0);
|
||||||
|
charCode = (((charCode + key) ^ key2) ^ key) + 1;
|
||||||
|
return String.fromCharCode(charCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单个字符解密
|
||||||
|
String cutcodeChar(String c, int key, int key2) {
|
||||||
|
int charCode = c.codeUnitAt(0);
|
||||||
|
int decryptedCharCode = (((charCode - 1) ^ key) ^ key2) - key;
|
||||||
|
return String.fromCharCode(decryptedCharCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加密
|
||||||
|
String makecode(String pstr, List pkey) {
|
||||||
|
String pp = pstr;
|
||||||
|
for (int i = 0; i < pp.length; i++) {
|
||||||
|
pp = pp.replaceRange(i, i + 1, makecodeChar(pp[i], pkey[i % 5], pkey[(i + 18) % 5]));
|
||||||
|
}
|
||||||
|
return pp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解密
|
||||||
|
String cutecode(List<int> pstr) {
|
||||||
|
int len = pstr.length;
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
pstr[i] = cutcodeChar(String.fromCharCode(pstr[i])
|
||||||
|
, skey[i % 5], skey[(i + 18) % 5]).codeUnitAt(0);
|
||||||
|
}
|
||||||
|
return String.fromCharCodes(pstr);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
/// 添加小端序头
|
||||||
|
extension MapLittle on Map {
|
||||||
|
|
||||||
|
/// 在转成 Uint8List 的数据前面添加 小端序
|
||||||
|
Uint8List toLittle({dynamic value = 0, int length = 4}){
|
||||||
|
|
||||||
|
/// map 转 json
|
||||||
|
String mapJson = jsonEncode(this);
|
||||||
|
/// json 转 Uint8List
|
||||||
|
final mapData = utf8.encode(mapJson);
|
||||||
|
|
||||||
|
/// 添加小端序
|
||||||
|
final data = mapData.toLittle(value: value,length: length);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension ListIntLittle on List<int> {
|
||||||
|
|
||||||
|
List<int> toLittle({int? value, int length = 4}){
|
||||||
|
|
||||||
|
List<int> ret = [];
|
||||||
|
/// 初始化 定义数据长度
|
||||||
|
final little = ByteData(4);
|
||||||
|
/// 设置小端序数据
|
||||||
|
little.setInt32(0, value ?? this.length, Endian.little);
|
||||||
|
|
||||||
|
for (int i = 0; i < little.lengthInBytes; i++) {
|
||||||
|
ret.add(little.getUint8(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.addAll(this);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
extension Uint8ListLittle on Uint8List {
|
||||||
|
|
||||||
|
/// 在转成 Uint8List 的数据前面添加 小端序
|
||||||
|
Uint8List toLittle({dynamic value = 0, int length = 4}){
|
||||||
|
|
||||||
|
var mapData = this;
|
||||||
|
|
||||||
|
/// 初始化 定义数据长度
|
||||||
|
final little = ByteData(length);
|
||||||
|
/// 设置小端序数据
|
||||||
|
little.setInt32(0, value,Endian.little);
|
||||||
|
/// 小端序 转 Uint8List
|
||||||
|
var littleData = little.buffer.asUint8List();
|
||||||
|
|
||||||
|
/// 初始化 Uint8List
|
||||||
|
Uint8List data = Uint8List(littleData.length + mapData.length );
|
||||||
|
|
||||||
|
/// 组装 map Uint8List 和 小端序 Uint8List
|
||||||
|
data.setRange(0, littleData.length, littleData);
|
||||||
|
data.setRange(littleData.length, mapData.length + littleData.length, mapData);
|
||||||
|
|
||||||
|
/// 返回组合数据
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint8List转int 从前4位转小端序
|
||||||
|
int? fromLittle({int start = 0,int length = 4}) {
|
||||||
|
if (this.length < length){
|
||||||
|
print('转小端序的数据长度不足');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final littleData = sublist(start,start + length);
|
||||||
|
// 创建ByteData
|
||||||
|
ByteData byteData = littleData.buffer.asByteData();
|
||||||
|
|
||||||
|
// 读取小端序的32位整数
|
||||||
|
int little = byteData.getInt32(0, Endian.little);
|
||||||
|
|
||||||
|
return little;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
class NumberTool {
|
||||||
|
|
||||||
|
/// 生成随机数
|
||||||
|
/// @allNum: 随机数的和
|
||||||
|
/// @num: 随机数的数量
|
||||||
|
List<int> randomNum(int allNum, {int num = 10}) {
|
||||||
|
Random random = Random();
|
||||||
|
|
||||||
|
// 基础数字 最小值
|
||||||
|
int base = allNum ~/ 1.5 ~/ num;
|
||||||
|
List<int> numbers = List.generate(num, (index) => base);
|
||||||
|
|
||||||
|
|
||||||
|
for (int i = 0; i < num - 1; i++) {
|
||||||
|
int randomNumber = random.nextInt(base); // 生成0到base之间的随机整数
|
||||||
|
numbers[i] += randomNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sum = numbers.reduce((a, b) => a + b) ;
|
||||||
|
int lastNumber = allNum - sum;
|
||||||
|
|
||||||
|
// 将最后一个数 /2 再/数量 取整数
|
||||||
|
base = (lastNumber / 2) ~/ (num);
|
||||||
|
for (int i = 0; i < num - 1; i++) {
|
||||||
|
numbers[i] += base;
|
||||||
|
}
|
||||||
|
|
||||||
|
sum = numbers.reduce((a, b) => a + b);
|
||||||
|
lastNumber = allNum - sum;
|
||||||
|
|
||||||
|
// 将最后一个数 切一半 随机加到数组中
|
||||||
|
base = lastNumber ~/ 2;
|
||||||
|
numbers[ random.nextInt(num - 1)] = base;
|
||||||
|
sum = numbers.reduce((a, b) => a + b);
|
||||||
|
lastNumber = allNum - sum;
|
||||||
|
|
||||||
|
numbers.add(lastNumber);
|
||||||
|
|
||||||
|
// 打乱顺序
|
||||||
|
numbers.shuffle();
|
||||||
|
|
||||||
|
return numbers;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
enum TransitionType {
|
||||||
|
///左侧弹出
|
||||||
|
inFromLeft,
|
||||||
|
///右侧弹出
|
||||||
|
inFromRight,
|
||||||
|
///顶部弹出
|
||||||
|
inFromTop,
|
||||||
|
///底部弹出
|
||||||
|
inFromBottom,
|
||||||
|
///缩放
|
||||||
|
scale,
|
||||||
|
///渐变
|
||||||
|
fade,
|
||||||
|
///旋转缩放
|
||||||
|
rotation,
|
||||||
|
///放大
|
||||||
|
size,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//弹出动画
|
||||||
|
// barrierDismissible:点击背景是否消失
|
||||||
|
// child,builder:子 widget
|
||||||
|
// useRootNavigator: 是否推入传入 context的 导航器
|
||||||
|
// routeSettings: 弹窗路由设置
|
||||||
|
Future<T?> showAnimationDialog<T>({
|
||||||
|
required BuildContext context,
|
||||||
|
bool barrierDismissible = true,
|
||||||
|
Widget? child,
|
||||||
|
WidgetBuilder? builder,
|
||||||
|
bool useRootNavigator = true,
|
||||||
|
RouteSettings? routeSettings,
|
||||||
|
TransitionType? transitionType,
|
||||||
|
}) {
|
||||||
|
assert(child == null || builder == null);
|
||||||
|
assert(debugCheckHasMaterialLocalizations(context));
|
||||||
|
|
||||||
|
final ThemeData theme = Theme.of(context);
|
||||||
|
return showGeneralDialog(
|
||||||
|
context: context,
|
||||||
|
pageBuilder: (BuildContext buildContext, Animation<double> animation, Animation<double> secondaryAnimation) {
|
||||||
|
final Widget pageChild = child ?? Builder(builder: builder!);
|
||||||
|
return SafeArea(
|
||||||
|
child: Builder(builder: (BuildContext context) {
|
||||||
|
return Theme(data: theme, child: pageChild);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
barrierDismissible: barrierDismissible, ///点击背景是否消失
|
||||||
|
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
||||||
|
barrierColor: Colors.black38, ///背景色
|
||||||
|
transitionDuration: const Duration(milliseconds: 200),///动画时间
|
||||||
|
transitionBuilder: (context, animation1, animation2, child) { ///动画效果
|
||||||
|
return _buildDialogTransitions(context, animation1, animation2, child, transitionType?? TransitionType.inFromBottom);
|
||||||
|
},
|
||||||
|
useRootNavigator: useRootNavigator,
|
||||||
|
routeSettings: routeSettings,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///返回动画效果
|
||||||
|
Widget _buildDialogTransitions(
|
||||||
|
BuildContext context, Animation<double> animaton1, Animation<double> secondaryAnimation, Widget child, TransitionType type) {
|
||||||
|
|
||||||
|
/// 渐变效果
|
||||||
|
if (type == TransitionType.fade) {
|
||||||
|
return FadeTransition(
|
||||||
|
// 从0开始到1
|
||||||
|
opacity: Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(
|
||||||
|
// 传入设置的动画
|
||||||
|
parent: animaton1,
|
||||||
|
// 设置效果,快进漫出 这里有很多内置的效果
|
||||||
|
curve: Curves.fastOutSlowIn,
|
||||||
|
)),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
|
||||||
|
///缩放
|
||||||
|
} else if (type == TransitionType.scale) {
|
||||||
|
return ScaleTransition(
|
||||||
|
scale: Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(parent: animaton1, curve: Curves.easeOutBack)),//Curves.easeOutBack
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// 旋转加缩放动画效果
|
||||||
|
} else if (type == TransitionType.rotation) {
|
||||||
|
|
||||||
|
return RotationTransition(
|
||||||
|
turns: Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(
|
||||||
|
parent: animaton1,
|
||||||
|
curve: Curves.fastOutSlowIn,
|
||||||
|
)),
|
||||||
|
child: ScaleTransition(
|
||||||
|
scale: Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(parent: animaton1, curve: Curves.fastOutSlowIn)),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
/// 左滑出动画效果
|
||||||
|
} else if (type == TransitionType.inFromLeft) {
|
||||||
|
|
||||||
|
return SlideTransition(
|
||||||
|
position: Tween<Offset>(begin: Offset(-1.0, 0.0), end: Offset(0.0, 0.0))
|
||||||
|
.animate(CurvedAnimation(parent: animaton1, curve: Curves.fastOutSlowIn)),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// 右滑出动画效果
|
||||||
|
} else if (type == TransitionType.inFromRight) {
|
||||||
|
return SlideTransition(
|
||||||
|
position: Tween<Offset>(begin: Offset(1.0, 0.0), end: Offset(0.0, 0.0))
|
||||||
|
.animate(CurvedAnimation(parent: animaton1, curve: Curves.fastOutSlowIn)),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// 顶部滑出动画效果
|
||||||
|
} else if (type == TransitionType.inFromTop) {
|
||||||
|
return SlideTransition(
|
||||||
|
position: Tween<Offset>(begin: Offset(0.0, -1.0), end: Offset(0.0, 0.0))
|
||||||
|
.animate(CurvedAnimation(parent: animaton1, curve: Curves.fastOutSlowIn)),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// 底部滑出动画效果
|
||||||
|
} else if (type == TransitionType.inFromBottom) {
|
||||||
|
return SlideTransition(
|
||||||
|
position: Tween<Offset>(begin: Offset(0.0, 1.0), end: Offset(0.0, 0.0))
|
||||||
|
.animate(CurvedAnimation(parent: animaton1, curve: Curves.fastOutSlowIn)),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// 放大出显动画效果
|
||||||
|
} else if (type == TransitionType.size) {
|
||||||
|
return ScaleTransition(
|
||||||
|
child: child,
|
||||||
|
scale: Tween<double>(begin: 0.0, end: 1.0).animate(CurvedAnimation(parent: animaton1, curve: Curves.fastOutSlowIn)),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:web_synchronization_tool/windows/synchronization_web_tool.dart';
|
||||||
|
|
||||||
|
import 'little_extension.dart';
|
||||||
|
import 'code.dart';
|
||||||
|
import 'number_tool.dart';
|
||||||
|
|
||||||
|
class SocketUtils extends SyncSocket{
|
||||||
|
// 私有构造函数
|
||||||
|
SocketUtils._();
|
||||||
|
// 私有静态变量,保存类的唯一实例
|
||||||
|
static SocketUtils? _instance;
|
||||||
|
|
||||||
|
// 公开的静态方法,返回类的唯一实例
|
||||||
|
static SocketUtils getInstance() {
|
||||||
|
_instance ??= SocketUtils._();
|
||||||
|
return _instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class SyncSocket {
|
||||||
|
|
||||||
|
String url = '127.0.0.1';
|
||||||
|
static int port = 46182;
|
||||||
|
|
||||||
|
RawDatagramSocket? socket;
|
||||||
|
|
||||||
|
// IC 0主机 1 从机
|
||||||
|
int ic = 1;
|
||||||
|
|
||||||
|
/// 从机ip
|
||||||
|
List<String> childrenIp = [];
|
||||||
|
|
||||||
|
connect() async {
|
||||||
|
socket = await RawDatagramSocket.bind(InternetAddress.anyIPv4, 0);
|
||||||
|
|
||||||
|
// 发送身份包
|
||||||
|
Map data = {
|
||||||
|
"op": 0,
|
||||||
|
"IC": ic
|
||||||
|
};
|
||||||
|
socket?.send(dataMake(data), InternetAddress(url), port);
|
||||||
|
|
||||||
|
// 监听来自网络的数据包
|
||||||
|
socket?.listen((RawSocketEvent e) {
|
||||||
|
Datagram? d = socket?.receive();
|
||||||
|
if (d == null) return;
|
||||||
|
|
||||||
|
Map data = dataCute(d.data);
|
||||||
|
|
||||||
|
// 收到消息
|
||||||
|
if (data['op'] == 2){
|
||||||
|
|
||||||
|
print(data);
|
||||||
|
|
||||||
|
//点击
|
||||||
|
if (data['click'] != null){
|
||||||
|
Map click = data['click'];
|
||||||
|
int x = click['x'] as int;
|
||||||
|
int y = click['y'] as int;
|
||||||
|
|
||||||
|
SynchronizationWebTool.getInstance().clickSynchronization(x, y);
|
||||||
|
}
|
||||||
|
//输入
|
||||||
|
if (data['input'] != null){
|
||||||
|
int input = data['input'];
|
||||||
|
List<int> nums = NumberTool().randomNum(input);
|
||||||
|
SynchronizationWebTool.getInstance().input(nums);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 心跳
|
||||||
|
if (data['op'] == 11){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 心跳
|
||||||
|
heartbeat(){
|
||||||
|
Timer timer = Timer(const Duration(seconds: 10), () async {
|
||||||
|
|
||||||
|
if (socket == null){
|
||||||
|
// 重连
|
||||||
|
await connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map data = {
|
||||||
|
"op": 1,
|
||||||
|
"IC": ic
|
||||||
|
};
|
||||||
|
socket?.send(dataMake(data), InternetAddress(url), port);
|
||||||
|
|
||||||
|
heartbeat();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 发送数据的处理
|
||||||
|
List<int> dataMake(Map map){
|
||||||
|
// 将Map对象转换为JSON字符串
|
||||||
|
String json = jsonEncode(map);
|
||||||
|
// 加密
|
||||||
|
String ps = makecode(json, skey);
|
||||||
|
// 转成无符号整数数组
|
||||||
|
Uint8List uinData = Uint8List.fromList(ps.codeUnits);
|
||||||
|
// 在前面添加 4位 表示长度的小端序
|
||||||
|
uinData = uinData.toLittle(value: uinData.length);
|
||||||
|
return uinData.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 接收数据处理
|
||||||
|
Map dataCute(Uint8List data){
|
||||||
|
|
||||||
|
if (data.length <= 4) return {};
|
||||||
|
|
||||||
|
Uint8List pData = data.sublist(4,data.length);
|
||||||
|
|
||||||
|
String str = cutecode(pData);
|
||||||
|
|
||||||
|
Map map = json.decode(str);
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
|
||||||
|
import 'dart:ffi';
|
||||||
|
|
||||||
|
import 'package:web_synchronization_tool/windows/socket_tool.dart';
|
||||||
|
import 'package:web_synchronization_tool/windows/windowsJs.dart';
|
||||||
|
import 'package:webview_windows/webview_windows.dart';
|
||||||
|
|
||||||
|
class SynchronizationWebTool{
|
||||||
|
|
||||||
|
// 私有构造函数
|
||||||
|
SynchronizationWebTool._();
|
||||||
|
// 私有静态变量,保存类的唯一实例
|
||||||
|
static SynchronizationWebTool? _instance;
|
||||||
|
|
||||||
|
// 公开的静态方法,返回类的唯一实例
|
||||||
|
static SynchronizationWebTool getInstance() {
|
||||||
|
_instance ??= SynchronizationWebTool._();
|
||||||
|
return _instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 受控
|
||||||
|
List<WebviewController> _childControllers = [];
|
||||||
|
|
||||||
|
/// 设置控制器时 注入js
|
||||||
|
setChildController(List<WebviewController> childControllers){
|
||||||
|
|
||||||
|
// for (var controller in childControllers) {
|
||||||
|
// controller.addScriptToExecuteOnDocumentCreated(WindowsJs.clickEventJs);
|
||||||
|
// // controller.addScriptToExecuteOnDocumentCreated(WindowsJs.onloadZoom(90));
|
||||||
|
// }
|
||||||
|
|
||||||
|
_childControllers = childControllers;
|
||||||
|
|
||||||
|
// 滚动监听
|
||||||
|
// mainController.addScriptToExecuteOnDocumentCreated(WindowsJs.scrollEventJs);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
List<WebviewController> get childController{
|
||||||
|
return _childControllers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 点击同步
|
||||||
|
clickSynchronization(int x,int y){
|
||||||
|
for (var controller in childController) {
|
||||||
|
controller.executeScript(WindowsJs.clickJs(x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// 滚动同步
|
||||||
|
scrollSynchronization(int y){
|
||||||
|
for (var controller in childController) {
|
||||||
|
controller.executeScript(WindowsJs.scrollTo(y) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 输入
|
||||||
|
input(List<int> values){
|
||||||
|
for (int i =0;i<childController.length;i++){
|
||||||
|
WebviewController controller = childController[i];
|
||||||
|
controller.executeScript(WindowsJs.inputJs(values[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,280 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:web_synchronization_tool/windows/socket_tool.dart';
|
||||||
|
import 'package:web_synchronization_tool/windows/synchronization_web_tool.dart';
|
||||||
|
import 'package:webview_windows/webview_windows.dart';
|
||||||
|
|
||||||
|
class WebGridController {
|
||||||
|
Function(WebviewController controller)? addWebControllerBlack;
|
||||||
|
Function? addWebBlack;
|
||||||
|
Function? addAllWebBlack;
|
||||||
|
|
||||||
|
addMainController(WebviewController controller) {
|
||||||
|
if (addWebControllerBlack != null) {
|
||||||
|
addWebControllerBlack!(controller);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addWeb() {
|
||||||
|
if (addWebBlack != null) {
|
||||||
|
addWebBlack!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addAllWeb() {
|
||||||
|
if (addAllWebBlack != null) {
|
||||||
|
addAllWebBlack!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebGridWidget extends StatefulWidget {
|
||||||
|
const WebGridWidget({super.key, required this.controller});
|
||||||
|
|
||||||
|
final WebGridController controller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<WebGridWidget> createState() => _WebGridWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WebGridWidgetState extends State<WebGridWidget> {
|
||||||
|
List<WebviewController> controllers = [];
|
||||||
|
|
||||||
|
bool initDone = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
widget.controller.addWebBlack = () {
|
||||||
|
addController();
|
||||||
|
};
|
||||||
|
|
||||||
|
widget.controller.addAllWebBlack = () {
|
||||||
|
addAllController();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提娜佳一个网页
|
||||||
|
addController() async {
|
||||||
|
if (controllers.length >= 10) return;
|
||||||
|
|
||||||
|
var controller = WebviewController();
|
||||||
|
await controller.initialize();
|
||||||
|
controller.loadUrl('https://www.df6831.com/');
|
||||||
|
|
||||||
|
controllers.add(controller);
|
||||||
|
SynchronizationWebTool.getInstance().setChildController(controllers);
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将网页加满到10个
|
||||||
|
addAllController() async {
|
||||||
|
final num = 10 - controllers.length;
|
||||||
|
if (num < 1) return;
|
||||||
|
|
||||||
|
for (int i = 0; i < num; i++) {
|
||||||
|
var controller = WebviewController();
|
||||||
|
await controller.initialize();
|
||||||
|
controller.loadUrl('https://www.df6831.com/');
|
||||||
|
controllers.add(controller);
|
||||||
|
}
|
||||||
|
SynchronizationWebTool.getInstance().setChildController(controllers);
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
GridView.builder(
|
||||||
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 5, // 两列
|
||||||
|
crossAxisSpacing: 8.0, // 水平间距
|
||||||
|
mainAxisSpacing: 8.0, // 垂直间距
|
||||||
|
childAspectRatio: 0.9),
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Webview(controllers[index]),
|
||||||
|
TextButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
overlayColor: MaterialStateProperty.resolveWith(
|
||||||
|
(states) => Colors.transparent)),
|
||||||
|
onPressed: () {
|
||||||
|
var controller = controllers[index];
|
||||||
|
// controller.executeScript(WindowsJs.zoom(100));
|
||||||
|
|
||||||
|
// showAnimationDialog(
|
||||||
|
// context: context,
|
||||||
|
// // barrierDismissible: false,
|
||||||
|
// child: ShowWebWidget(controller: controller,));
|
||||||
|
|
||||||
|
// Navigator.push(context, MaterialPageRoute(builder: (context)=> ShowWebWidget(controller: controller,) ));
|
||||||
|
|
||||||
|
Navigator.of(context).push(PageRouteBuilder(
|
||||||
|
opaque: false, // 设置路由本身透明
|
||||||
|
pageBuilder: (BuildContext context, _, __) =>
|
||||||
|
ShowWebWidget(
|
||||||
|
controller: controller,
|
||||||
|
main: index == 0,
|
||||||
|
allLoadUrl: (String url) {
|
||||||
|
controllers.forEach((controller) {
|
||||||
|
controller.loadUrl(url);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
));
|
||||||
|
},
|
||||||
|
child: Container(),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
alignment: Alignment.topRight,
|
||||||
|
child: TextButton(
|
||||||
|
style: TextButton.styleFrom(padding: const EdgeInsets.all(0)),
|
||||||
|
onPressed: () {
|
||||||
|
controllers.removeAt(index);
|
||||||
|
SynchronizationWebTool.getInstance()
|
||||||
|
.setChildController(controllers);
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
color: Colors.blue[100],
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: const Icon(Icons.clear)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: controllers.length, // 生成20个瀑布流瓦片
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShowWebWidget extends StatefulWidget {
|
||||||
|
const ShowWebWidget(
|
||||||
|
{super.key,
|
||||||
|
required this.controller,
|
||||||
|
this.main = false,
|
||||||
|
required this.allLoadUrl});
|
||||||
|
|
||||||
|
final WebviewController controller;
|
||||||
|
|
||||||
|
final Function(String url) allLoadUrl;
|
||||||
|
|
||||||
|
final bool main;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ShowWebWidget> createState() => _ShowWebWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ShowWebWidgetState extends State<ShowWebWidget> {
|
||||||
|
/// 网址
|
||||||
|
TextEditingController urlController = TextEditingController();
|
||||||
|
|
||||||
|
/// 服务器ip
|
||||||
|
TextEditingController ipController = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
|
||||||
|
// widget.controller.clearCache();
|
||||||
|
// widget.controller.clearCookies();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
appBar: AppBar(
|
||||||
|
title: getTitleWidget(),
|
||||||
|
),
|
||||||
|
body: Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 1400,
|
||||||
|
height: 900,
|
||||||
|
child: Webview(widget.controller),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget getTitleWidget() {
|
||||||
|
if (widget.main == false) return input();
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
// TextButton(
|
||||||
|
// onPressed: () {
|
||||||
|
// widget.controller.openDevTools();
|
||||||
|
// },
|
||||||
|
// child: const Text('开发者')),
|
||||||
|
input(),
|
||||||
|
ipSet()
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 跳转网址
|
||||||
|
input() {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 400,
|
||||||
|
child: TextField(
|
||||||
|
style: const TextStyle(fontSize: 14),
|
||||||
|
controller: urlController,
|
||||||
|
)),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
String url = urlController.text;
|
||||||
|
// if (!url.startsWith("https://") ) {
|
||||||
|
// url = "https://$url";
|
||||||
|
// }
|
||||||
|
widget.controller.loadUrl(url);
|
||||||
|
},
|
||||||
|
child: const Text(
|
||||||
|
'跳转',
|
||||||
|
style: TextStyle(fontSize: 14),
|
||||||
|
)),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
String url = urlController.text;
|
||||||
|
|
||||||
|
widget.allLoadUrl(url);
|
||||||
|
},
|
||||||
|
child: const Text(
|
||||||
|
'全部跳转',
|
||||||
|
style: TextStyle(fontSize: 14),
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ipSet() {
|
||||||
|
ipController.text = SocketUtils.getInstance().url;
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 100,
|
||||||
|
child: TextField(
|
||||||
|
style: const TextStyle(fontSize: 14),
|
||||||
|
controller: ipController,
|
||||||
|
)),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
SocketUtils.getInstance().url = ipController.text;
|
||||||
|
SocketUtils.getInstance().connect();
|
||||||
|
},
|
||||||
|
child: const Text(
|
||||||
|
'保存服务器ip',
|
||||||
|
style: TextStyle(fontSize: 14),
|
||||||
|
))
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,134 @@
|
||||||
|
class WindowsJs {
|
||||||
|
/// 点击监听
|
||||||
|
static String clickEventJs = '''
|
||||||
|
document.addEventListener("click", function (event) {
|
||||||
|
var x = event.clientX;
|
||||||
|
var y = event.clientY;
|
||||||
|
|
||||||
|
var click = { "x": x, "y": y };
|
||||||
|
var value = { click: click };
|
||||||
|
|
||||||
|
// 发送消息
|
||||||
|
window.chrome.webview.postMessage(value);
|
||||||
|
|
||||||
|
// window.chrome.webview.postMessage({"text":event.target.textContent,"end":event.target.textContent === "进入游戏"});
|
||||||
|
|
||||||
|
// 检查点击的元素是否有类名为 'btn'
|
||||||
|
if (event.target.textContent === "进入游戏") {
|
||||||
|
// 获取按钮上显示的文本内容
|
||||||
|
window.chrome.webview.postMessage({ "成功获取到btn": 1 });
|
||||||
|
// window.chrome.webview.postMessage({ "btn": event.target.textContent });
|
||||||
|
|
||||||
|
// 阻止默认的链接跳转行为
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
var oldUrl = window.location.href;
|
||||||
|
var classUrl = event.target.href;
|
||||||
|
|
||||||
|
var str = oldUrl;
|
||||||
|
var startIndex = str.indexOf('www.');
|
||||||
|
var endIndex = str.indexOf('/', startIndex);
|
||||||
|
var url = str.substring(startIndex, endIndex);
|
||||||
|
var newUrl = "https://" + url + "/game/";
|
||||||
|
|
||||||
|
// window.chrome.webview.postMessage({"元素地址": classUrl ,"旧地址":window.location.href ,"截取":url,"新地址": newUrl});
|
||||||
|
window.location.href = newUrl;
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
''';
|
||||||
|
|
||||||
|
/// 滚动监听
|
||||||
|
static String scrollEventJs = '''
|
||||||
|
// window.onscroll = function() {
|
||||||
|
// // 获取垂直滚动位置
|
||||||
|
// var scrollTop = window.scrollY;
|
||||||
|
// window.chrome.webview.postMessage({"scroll": 1 ,"y": scrollTop });
|
||||||
|
// };
|
||||||
|
// 轮询检查元素是否存在
|
||||||
|
// const checkExist = setInterval(function() {
|
||||||
|
// const contentWrap = document.querySelector('content');
|
||||||
|
// const clList = document.querySelector('cl-list');
|
||||||
|
// console.log('轮询');
|
||||||
|
// if (contentWrap && clList) {
|
||||||
|
// clearInterval(checkExist); // 停止轮询
|
||||||
|
// console.log('停止轮询');
|
||||||
|
// contentWrap.addEventListener('scroll', function() {
|
||||||
|
// console.log('contentWrap position:', element.scrollTop);
|
||||||
|
// });
|
||||||
|
// clList.addEventListener('scroll', function() {
|
||||||
|
// console.log('clList position:', element.scrollTop);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// }, 500); // 每500毫秒检查一次
|
||||||
|
|
||||||
|
''';
|
||||||
|
|
||||||
|
/// 滚动到
|
||||||
|
static String scrollTo(int value){
|
||||||
|
return '''
|
||||||
|
window.scrollTo({
|
||||||
|
top: $value,
|
||||||
|
behavior: 'smooth' // 可选,设置滚动行为为平滑滚动
|
||||||
|
});
|
||||||
|
''';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 模拟点击
|
||||||
|
static String clickJs(int x, int y) {
|
||||||
|
return 'document.elementFromPoint($x, $y).click();';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 输入
|
||||||
|
static String inputJs(int value) {
|
||||||
|
return '''
|
||||||
|
console.log('输入赋值');
|
||||||
|
var inputEvent = new Event('input', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
});
|
||||||
|
var inputElement = document.querySelector(".bet-money");
|
||||||
|
inputElement.value = "$value";
|
||||||
|
inputElement.dispatchEvent(inputEvent);
|
||||||
|
|
||||||
|
// var inputElements = document.querySelectorAll(".bet-money");
|
||||||
|
// inputElements.forEach(function(element) {
|
||||||
|
// var inputEvent = new Event('input', {
|
||||||
|
// bubbles: true,
|
||||||
|
// cancelable: true,
|
||||||
|
// });
|
||||||
|
// element.value = "$value";
|
||||||
|
// element.dispatchEvent(inputEvent);
|
||||||
|
// });
|
||||||
|
// const contentWrap = document.querySelector('content');
|
||||||
|
// const clList = document.querySelector('cl-list');
|
||||||
|
// console.log(contentWrap, clList);
|
||||||
|
''';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// 监听加载,在加载完成后缩放
|
||||||
|
static String onloadZoom(int zoom){
|
||||||
|
assert(zoom >= 1 && zoom <= 100, 'zoom 1 到 100');
|
||||||
|
return '''
|
||||||
|
window.onload = function () {
|
||||||
|
var currentUrl = window.location.href;
|
||||||
|
if (currentUrl.endsWith('game/')) {
|
||||||
|
${WindowsJs.zoom(zoom)}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
''';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// 修改缩放
|
||||||
|
static String zoom(int zoom){
|
||||||
|
assert(zoom >= 1 && zoom <= 100, 'zoom 1 到 100');
|
||||||
|
return '''
|
||||||
|
document.body.style.zoom = "$zoom%";
|
||||||
|
''';
|
||||||
|
}
|
||||||
|
|
||||||
|
static String message = 'window.chrome.webview.postMessage({x:3333});';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:web_synchronization_tool/windows/socket_tool.dart';
|
||||||
|
import 'package:web_synchronization_tool/windows/web_grid_view.dart';
|
||||||
|
import 'package:webview_windows/webview_windows.dart';
|
||||||
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
|
class WindowsPage extends StatefulWidget {
|
||||||
|
const WindowsPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<WindowsPage> createState() => _WindowsPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WindowsPageState extends State<WindowsPage> {
|
||||||
|
Widget? windowsWebWidget;
|
||||||
|
WebviewController mainController = WebviewController();
|
||||||
|
final gridController = WebGridController();
|
||||||
|
|
||||||
|
bool initDone = false;
|
||||||
|
|
||||||
|
String clickId = '';
|
||||||
|
String zoomId = '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
|
||||||
|
initWindow();
|
||||||
|
controllerInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
initWindow(){
|
||||||
|
//允许调整窗口大小
|
||||||
|
windowManager.setResizable(false);
|
||||||
|
|
||||||
|
const double width = 1920;
|
||||||
|
const double height = 1000;
|
||||||
|
//设置最小大小
|
||||||
|
const windowSize = Size(width, height);
|
||||||
|
windowManager.setSize(windowSize);
|
||||||
|
appWindow.minSize = windowSize;
|
||||||
|
windowManager.center();
|
||||||
|
windowManager.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Future controllerInit() async {
|
||||||
|
await mainController.initialize();
|
||||||
|
|
||||||
|
mainController.loadUrl('http://www.df6831.com/');
|
||||||
|
|
||||||
|
gridController.addMainController(mainController);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
initDone = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: WebGridWidget(
|
||||||
|
controller: gridController,
|
||||||
|
),
|
||||||
|
floatingActionButton: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
FloatingActionButton(
|
||||||
|
onPressed: (){
|
||||||
|
gridController.addWeb();
|
||||||
|
},
|
||||||
|
heroTag: 1,
|
||||||
|
tooltip: '添加网页',
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 20,)
|
||||||
|
,
|
||||||
|
FloatingActionButton(
|
||||||
|
onPressed: (){
|
||||||
|
gridController.addAllWeb();
|
||||||
|
},
|
||||||
|
heroTag: 2,
|
||||||
|
tooltip: '添加10个网页',
|
||||||
|
child: const Row(
|
||||||
|
children: [Icon(Icons.add),Text('10')],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:webview_windows/webview_windows.dart';
|
||||||
|
|
||||||
|
class WindowsWebWidget extends StatefulWidget {
|
||||||
|
WindowsWebWidget({super.key, required this.controller});
|
||||||
|
final WebviewController controller;
|
||||||
|
@override
|
||||||
|
State<WindowsWebWidget> createState() => _WindowsWebWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WindowsWebWidgetState extends State<WindowsWebWidget> {
|
||||||
|
|
||||||
|
Webview? webview;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return inWindowsWebView();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget inWindowsWebView(){
|
||||||
|
webview ??= Webview(widget.controller);
|
||||||
|
return webview!;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
10
pubspec.yaml
10
pubspec.yaml
|
|
@ -11,9 +11,13 @@ dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
cupertino_icons: ^1.0.2
|
common:
|
||||||
flutter_inappwebview: ^6.0.0
|
path: E:\Code\FlutterProjectCode\common
|
||||||
webview_dart: ^1.0.1
|
webview_windows:
|
||||||
|
path: ../flutter-webview-windows-main
|
||||||
|
bitsdojo_window: ^0.1.6
|
||||||
|
window_manager: ^0.3.7
|
||||||
|
device_info_plus: ^9.1.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue