Commit 92e72673 by tanghuan

init

1 parent 0b19628d
Showing 88 changed files with 2113 additions and 0 deletions
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
/coverage/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "05db9689081f091050f01aed79f04dce0c750154"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 05db9689081f091050f01aed79f04dce0c750154
base_revision: 05db9689081f091050f01aed79f04dce0c750154
- platform: android
create_revision: 05db9689081f091050f01aed79f04dce0c750154
base_revision: 05db9689081f091050f01aed79f04dce0c750154
- platform: ios
create_revision: 05db9689081f091050f01aed79f04dce0c750154
base_revision: 05db9689081f091050f01aed79f04dce0c750154
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks
import java.io.FileInputStream
import java.util.Properties
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
// 加载 key.properties 文件
val keystorePropertiesFile = rootProject.file("key.properties")
val keystoreProperties = Properties()
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}
android {
namespace = "cn.banxe.bxe"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "cn.banxe.bxe"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
// 添加签名配置
signingConfigs {
named("debug") {
if (keystorePropertiesFile.exists()) {
keyAlias = keystoreProperties.getProperty("keyAlias")
keyPassword = keystoreProperties.getProperty("keyPassword")
storeFile = file(keystoreProperties.getProperty("storeFile"))
storePassword = keystoreProperties.getProperty("storePassword")
}
}
create("release") {
if (keystorePropertiesFile.exists()) {
keyAlias = keystoreProperties.getProperty("keyAlias")
keyPassword = keystoreProperties.getProperty("keyPassword")
storeFile = file(keystoreProperties.getProperty("storeFile"))
storePassword = keystoreProperties.getProperty("storePassword")
}
}
}
buildTypes {
debug {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
release {
signingConfig = signingConfigs.getByName("release")
}
}
}
flutter {
source = "../.."
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:label="班小二"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:networkSecurityConfig="@xml/network_security_config">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>
package cn.banxe.bxe;
import io.flutter.embedding.android.FlutterActivity;
public class MainActivity extends FlutterActivity {
}
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<!-- 手动增加的安全配置,明文传输权限,允许http访问 -->
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="false">127.0.0.1</domain>
<domain includeSubdomains="false">localhost</domain>
</domain-config>
</network-security-config>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory =
rootProject.layout.buildDirectory
.dir("../../build")
.get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
#distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
distributionUrl=https\://mirrors.aliyun.com/macports/distfiles/gradle/gradle-8.12-all.zip
pluginManagement {
val flutterSdkPath =
run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.9.1" apply false
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
}
include(":app")
No preview for this file type
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebView Communication</title>
</head>
<body>
<h2>接口测试</h2>
<div id="resp"></div>
<button onclick="clearResp()">清除响应数据</button>
<button onclick="getDeviceInfoSync()">获取当前设备信息</button>
<button onclick="setStorageSync()">设置本地缓存</button>
<button onclick="getStorageSync()">获取本地缓存</button>
<button onclick="chooseImage()">选择图片</button>
<a href="/test/test2.html">跳转测试2</a>
<br>
<img src="" alt="test" id=testImg>
<script src="/test/test.js"></script>
</body>
</html>
\ No newline at end of file
// 显示来自Flutter的警告
function showAlert(message) {
alert(message);
}
// 清空响应数据
function clearResp() {
document.getElementById('resp').innerHTML = '';
}
// 接收Flutter响应数据
function xeJsBridgeCallback(message) {
let jsonData = JSON.parse(message);
document.getElementById('resp').innerHTML = '<p><strong>响应:</strong> ' + jsonData.data + '</p>';
// 显示图片测试
document.getElementById('testImg').src=jsonData.data;
}
// 测试获取设备信息
function getDeviceInfoSync() {
let message='{ "timestamp": 1, "unique": "123", "cmd": "getDeviceInfoSync", "params": {} }';
xeJsBridge.postMessage(message);
}
function setStorageSync() {
let message='{ "timestamp": 1, "unique": "123", "cmd": "setStorageSync", "params": {"key":"test1","value":"hello world!Hey!"} }';
xeJsBridge.postMessage(message);
}
function getStorageSync() {
let message='{ "timestamp": 1, "unique": "123", "cmd": "getStorageSync", "params": {"key":"test1"} }';
xeJsBridge.postMessage(message);
}
function chooseImage() {
let message='{ "timestamp": 1, "unique": "123", "cmd": "chooseImage", "params": {} }';
xeJsBridge.postMessage(message);
}
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebView Communication</title>
</head>
<body>
<h2>接口测试2</h2>
<div id="resp"></div>
<button onclick="getStorageSync()">获取本地缓存</button>
<a href="/test/test.html">跳转测试1</a>
<br>
<img src="" alt="test" id=testImg>
</body>
<script>
function xeJsBridgeCallback(message) {
let jsonData = JSON.parse(message);
document.getElementById('resp').innerHTML = '<p><strong>响应:</strong> ' + jsonData.data + '</p>';
}
function getStorageSync() {
let message='{ "timestamp": 1, "unique": "123", "cmd": "getStorageSync", "params": {"key":"test1"} }';
xeJsBridge.postMessage(message);
}
</script>
</html>
\ No newline at end of file
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>13.0</string>
</dict>
</plist>
#include "Generated.xcconfig"
#include "Generated.xcconfig"
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Appframe</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>appframe</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>
#import "GeneratedPluginRegistrant.h"
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'config/routes.dart';
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) => Platform.isIOS
? CupertinoApp.router(
routerConfig: router,
title: '班小二',
theme: const CupertinoThemeData(primaryColor: CupertinoColors.systemBlue),
)
: MaterialApp.router(
routerConfig: router,
title: '班小二',
theme: ThemeData(primarySwatch: Colors.blue),
);
}
import 'dart:convert';
import 'dart:io';
import 'package:appframe/data/models/message/h5_message.dart';
import 'package:appframe/services/dispatcher.dart';
import 'package:appframe/services/local_server_service.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebState extends Equatable {
final bool loaded;
final String title;
final bool needAuth;
final String? sessionCode;
final String? userCode;
final String? classCode;
final int? userType;
final String? stuId;
const WebState(
this.loaded,
this.title,
this.needAuth,
this.sessionCode,
this.userCode,
this.classCode,
this.userType,
this.stuId,
);
WebState copyWith({
bool? loaded,
String? title,
bool? needAuth,
String? sessionCode,
String? userCode,
String? classCode,
int? userType,
String? stuId,
}) {
return WebState(
loaded ?? this.loaded,
title ?? this.title,
needAuth ?? this.needAuth,
sessionCode ?? this.sessionCode,
userCode ?? this.userCode,
classCode ?? this.classCode,
userType ?? this.userType,
stuId ?? this.stuId,
);
}
@override
List<Object?> get props => [loaded, title, needAuth];
}
class WebCubit extends Cubit<WebState> {
late final MessageDispatcher _dispatcher;
late final WebViewController _controller;
late final HttpServer _server;
WebViewController get controller => _controller;
WebCubit(super.initialState) {
// 消息处理器
_dispatcher = MessageDispatcher();
// WebView控制器
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onPageFinished: (String url) {
print("onPageFinished-------------------------$url");
finishLoading();
},
onUrlChange: (UrlChange url) {
print('url===${url.url}');
},
),
)
..addJavaScriptChannel("xeJsBridge", onMessageReceived: _onMessageReceived);
// 启动本地服务器,并加载HTML
_startLocalServerAndLoadHtml();
}
_startLocalServerAndLoadHtml() async {
// 启动本地服务器
_server = await LocalServerService().startLocalServer();
final String serverUrl;
if (state.sessionCode == null || state.sessionCode == '') {
serverUrl = 'http://127.0.0.1:${_server.port}/test/test.html';
} else {
serverUrl =
'http://127.0.0.1:${_server.port}/index.html#/h5/login/pages/applogin?sessionCode=${state.sessionCode}&userCode=${state.userCode}&classCode=${state.classCode}&userType=${state.userType}&stuId=${state.stuId}';
}
print("serverUrl===$serverUrl");
_controller.loadRequest(Uri.parse(serverUrl));
}
_onMessageReceived(JavaScriptMessage message) async {
try {
final Map<String, dynamic> data = json.decode(message.message);
H5Message h5Message = H5Message.fromJson(data);
_dispatcher.dispatch(h5Message, (response) {
_sendResponse(response);
});
} catch (e) {
print('消息解析错误: $e');
}
}
// 向H5发送响应
void _sendResponse(Map<String, dynamic> response) {
String jsonString = jsonEncode(response);
String escapedJson = jsonString.replaceAll('"', '\\"');
final String script = 'xeJsBridgeCallback("$escapedJson");';
_controller.runJavaScript(script);
}
void finishLoading() {
emit(state.copyWith(loaded: true, title: '业务首页'));
}
// 测试
void setTitle(String title) {
emit(state.copyWith(title: title));
}
// 测试
void resetLoading() {
emit(state.copyWith(loaded: false, title: '界面加载中...'));
}
//测试
void goAuth() {
emit(state.copyWith(needAuth: true));
}
@override
Future<void> close() async {
_server.close();
return super.close();
}
}
import 'package:appframe/config/locator.dart';
import 'package:appframe/data/repositories/wechat_auth_repository.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluwx/fluwx.dart';
class WechatAuthState extends Equatable {
final String? sessionCode;
final String? userCode;
final String? classCode;
final int? userType;
final String? stuId;
final String? result;
const WechatAuthState(this.sessionCode, this.userCode, this.classCode, this.userType, this.stuId, this.result);
WechatAuthState copyWith({
String? sessionCode,
String? userCode,
String? classCode,
int? userType,
String? stuId,
String? result,
}) {
return WechatAuthState(
sessionCode ?? this.sessionCode,
userCode ?? this.userCode,
classCode ?? this.classCode,
userType ?? this.userType,
stuId ?? this.stuId,
result ?? this.result,
);
}
@override
List<Object?> get props => [sessionCode, userCode, classCode, userType, stuId, result];
}
class WechatAuthCubit extends Cubit<WechatAuthState> {
late final Fluwx _fluwx;
late final WechatAuthRepository _wechatAuthRepository;
WechatAuthCubit(super.initialState) {
_fluwx = Fluwx();
_register();
_subscribe();
_wechatAuthRepository = getIt<WechatAuthRepository>();
}
Future<void> _register() async {
await _fluwx.registerApi(appId: "wx8c32ea248f0c7765", universalLink: "https://univerallink.banxe.cn/link/");
}
void _subscribe() {
_fluwx.addSubscriber((response) async {
if (response is WeChatAuthResponse) {
var result = 'state :${response.state} \n code:${response.code}';
// emit(state.copyWith(result: result));
dynamic data = await _wechatAuthRepository.codeToSk(response.code!);
print("===============================================");
print(data.toString());
var dd = data['data'];
var role = dd['roles'][0];
print(dd['sessionCode']);
print(dd['userCode']);
print(role['classCode']);
print(role['userType']);
print(role['stuId']);
emit(
state.copyWith(
sessionCode: dd['sessionCode'],
userCode: dd['userCode'],
classCode: role['classCode'],
userType: role['userType'],
stuId: role['stuId'],
),
);
}
});
}
void auth() async {
var result = await _fluwx.authBy(
which: NormalAuth(scope: 'snsapi_userinfo', state: 'wechat_sdk_test'),
);
print("++++++++++++++++++++++++++++++++++++++++++++++++++++++");
print(result);
}
}
import 'package:appframe/data/repositories/message/choose_image_handler.dart';
import 'package:appframe/data/repositories/message/device_info_handler.dart';
import 'package:appframe/services/dispatcher.dart';
import 'package:appframe/data/repositories/message/storage_handler.dart';
import 'package:appframe/data/repositories/wechat_auth_repository.dart';
import 'package:appframe/services/api_service.dart';
import 'package:get_it/get_it.dart';
import 'package:shared_preferences/shared_preferences.dart';
final getIt = GetIt.instance;
Future<void> setupLocator() async {
getIt.registerSingleton<SharedPreferences>(await SharedPreferences.getInstance());
// 按指令名称注册 message handler
// 视情况,对大概率会执行的指令直接加载,对概率较低的指令使用懒加载
// 使用懒加载,用户实际用到了才会进行加载
getIt.registerLazySingleton<MessageHandler>(() => DeviceInfoHandler(), instanceName: 'getDeviceInfoSync');
getIt.registerLazySingleton<MessageHandler>(() => GetStorageSyncHandler(), instanceName: 'getStorageSync');
getIt.registerLazySingleton<MessageHandler>(() => SetStorageSyncHandler(), instanceName: 'setStorageSync');
getIt.registerLazySingleton<MessageHandler>(() => ClearStorageSyncHandler(), instanceName: 'clearStorageSync');
getIt.registerLazySingleton<MessageHandler>(() => ChooseImageHandler(), instanceName: 'chooseImage');
// service
getIt.registerLazySingleton<ApiService>(() => ApiService(baseUrl: 'https://dev.banxiaoer.net'));
// repository
getIt.registerLazySingleton<WechatAuthRepository>(() => WechatAuthRepository());
}
import 'package:appframe/ui/pages/web_page.dart';
import 'package:appframe/ui/pages/wechat_auth_page.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
final GoRouter router = GoRouter(
initialLocation: '/web',
routes: <RouteBase>[
GoRoute(
path: '/web',
builder: (BuildContext context, GoRouterState state) {
return const WebPage();
},
),
GoRoute(
path: '/wechatAuth',
builder: (BuildContext context, GoRouterState state) {
return const WechatAuthPage();
},
),
],
);
import 'package:json_annotation/json_annotation.dart';
part 'h5_message.g.dart';
@JsonSerializable()
class H5Message {
int timestamp;
String unique;
String cmd;
Map<String, dynamic> params;
H5Message(this.timestamp, this.unique, this.cmd, this.params);
factory H5Message.fromJson(Map<String, dynamic> json) =>
_$H5MessageFromJson(json);
Map<String, dynamic> toJson() => _$H5MessageToJson(this);
}
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'h5_message.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
H5Message _$H5MessageFromJson(Map<String, dynamic> json) => H5Message(
(json['timestamp'] as num).toInt(),
json['unique'] as String,
json['cmd'] as String,
json['params'] as Map<String, dynamic>,
);
Map<String, dynamic> _$H5MessageToJson(H5Message instance) => <String, dynamic>{
'timestamp': instance.timestamp,
'unique': instance.unique,
'cmd': instance.cmd,
'params': instance.params,
};
import 'package:json_annotation/json_annotation.dart';
part 'h5_resp.g.dart';
@JsonSerializable()
class H5Resp {
String unique;
String cmd;
// Map<String, dynamic> data;
dynamic data;
String errMsg;
H5Resp(this.unique, this.cmd, this.data, this.errMsg);
factory H5Resp.fromJson(Map<String, dynamic> json) => _$H5RespFromJson(json);
Map<String, dynamic> toJson() => _$H5RespToJson(this);
}
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'h5_resp.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
H5Resp _$H5RespFromJson(Map<String, dynamic> json) => H5Resp(
json['unique'] as String,
json['cmd'] as String,
json['data'] as Map<String, dynamic>,
json['errMsg'] as String,
);
Map<String, dynamic> _$H5RespToJson(H5Resp instance) => <String, dynamic>{
'unique': instance.unique,
'cmd': instance.cmd,
'data': instance.data,
'errMsg': instance.errMsg,
};
import 'dart:io';
import 'package:appframe/services/dispatcher.dart';
import 'package:image_picker/image_picker.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
class ChooseImageHandler implements MessageHandler {
@override
Future<dynamic> handleMessage(Map<String, dynamic> params) async {
try {
// 初始化图片选择器
final ImagePicker picker = ImagePicker();
// 选择图片
final XFile? pickedFile = await picker.pickImage(
source: ImageSource.gallery, // 可以改为 ImageSource.camera 用于拍照
);
if (pickedFile != null) {
// 获取临时目录
final Directory tempDir = await getTemporaryDirectory();
// 生成唯一文件名
final String fileName = path.basename(pickedFile.path);
final String uniqueFileName = '${DateTime.now().millisecondsSinceEpoch}_$fileName';
// 创建目标文件路径
final String tempFilePath = path.join(tempDir.path, uniqueFileName);
// 复制文件到临时目录
final File copiedFile = await File(pickedFile.path).copy(tempFilePath);
// 返回临时文件路径
// 前面加上特殊路径,用于后续处理
return "/temp${copiedFile.path}";
}
return null; // 用户取消选择
} catch (e) {
print(e);
return null;
}
}
Future<List<Map<String, dynamic>>?> _selectOne(String sourceType) async {
final ImagePicker picker = ImagePicker();
final XFile? pickedFile = await picker.pickImage(
source: sourceType == 'album' ? ImageSource.gallery : ImageSource.camera,
);
if (pickedFile != null) {
// 获取临时目录
final Directory tempDir = await getTemporaryDirectory();
// 生成唯一文件名
final String fileName = path.basename(pickedFile.path);
final String uniqueFileName = '${DateTime.now().millisecondsSinceEpoch}_$fileName';
// 创建目标文件路径
final String tempFilePath = path.join(tempDir.path, uniqueFileName);
// 复制文件到临时目录
final File copiedFile = await File(pickedFile.path).copy(tempFilePath);
// 返回一个元素的数组
return [
{
"tempFilePath": "/temp${copiedFile.path}",
"size": copiedFile.lengthSync(),
"fileType": copiedFile.path.split('/').last.split('.').last,
},
];
} else {
return null;
}
}
/*Future<List<Map<String, dynamic>>?> _selectMulti(int count, String sourceType) async {
final ImagePicker picker = ImagePicker();
List<XFile> fileList = await picker.pickMultiImage();
if (fileList.isNotEmpty) {
}
}*/
}
import 'dart:io';
import 'package:appframe/services/dispatcher.dart';
import 'package:device_info_plus/device_info_plus.dart';
class DeviceInfoHandler implements MessageHandler {
@override
Future<Map<String, dynamic>> handleMessage(Map<String, dynamic> params) async {
var deviceInfoPlugin = DeviceInfoPlugin();
// 测试效果,先随便响应一些数据
if (Platform.isAndroid) {
AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo;
return {
"brand": androidInfo.brand,
"model": androidInfo.model,
"system": androidInfo.version.release,
"platform": "Android",
"memorySize": (androidInfo.physicalRamSize ~/ (1024 * 1024)).toString(),
};
} else if (Platform.isIOS) {
IosDeviceInfo iosInfo = await deviceInfoPlugin.iosInfo;
return {
"brand": "Apple",
"model": iosInfo.model,
"system": iosInfo.systemVersion,
"platform": "iOS",
"memorySize": (iosInfo.physicalRamSize ~/ (1024 * 1024)).toString(),
};
} else {
return {};
}
}
}
import 'package:appframe/config/locator.dart';
import 'package:appframe/services/dispatcher.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SetStorageSyncHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(Map<String, dynamic> params) async {
final key = params['key'];
final value = params['value'];
if (key is! String || value is! String) {
throw Exception('参数错误');
}
bool result = await getIt.get<SharedPreferences>().setString(key, value);
return result;
}
}
class GetStorageSyncHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(Map<String, dynamic> params) async {
final key = params['key'];
if (key is! String) {
throw Exception('参数错误');
}
String? value = getIt.get<SharedPreferences>().getString(key);
return value ?? "";
}
}
class ClearStorageSyncHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(Map<String, dynamic> params) async {
return await getIt.get<SharedPreferences>().clear();
}
}
import 'package:appframe/config/locator.dart';
import 'package:appframe/services/api_service.dart';
import 'package:dio/dio.dart';
class WechatAuthRepository {
late final ApiService _apiService;
WechatAuthRepository() {
_apiService = getIt<ApiService>();
}
Future<dynamic> codeToSk(String code) async {
Response resp = await _apiService.post('/login/applet/wkbxe/codeToSkByApp?version=1.0.0', {
"appCode": "bxeapp",
"params": {"code": code},
});
print('登录结果: $resp');
return resp.data;
}
}
import 'package:appframe/config/locator.dart';
import 'package:flutter/material.dart';
import 'app.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await setupLocator();
runApp(const App());
}
import 'package:dio/dio.dart';
// // 使用通用的 ApiService
// final apiService = ApiService(baseUrl: 'https://bxe.cn');
//
// // 发送POST请求
// final response = await apiService.post('/users', {
// 'name': 'xiaoming',
// 'email': 'xiaoming@example.com'
// });
class ApiService {
late Dio _dio;
ApiService({
String? baseUrl,
Map<String, dynamic>? defaultHeaders,
int connectTimeout = 30000,
int receiveTimeout = 30000,
}) {
_dio = Dio();
// 基础配置
_dio.options = BaseOptions(
baseUrl: baseUrl ?? '',
connectTimeout: Duration(milliseconds: connectTimeout),
receiveTimeout: Duration(milliseconds: receiveTimeout),
headers: {'Content-Type': 'application/json', 'Accept': 'application/json', ...?defaultHeaders},
);
}
/// 发送POST请求
Future<Response<T>> post<T>(
String endpoint,
dynamic data, {
Map<String, dynamic>? queryParameters,
Map<String, dynamic>? headers,
Options? options,
}) async {
return await _dio.post<T>(
endpoint,
data: data,
queryParameters: queryParameters,
options: options?.copyWith(headers: {...?headers}),
);
}
/// 发送GET请求
Future<Response<T>> get<T>(
String endpoint, {
Map<String, dynamic>? queryParameters,
Map<String, dynamic>? headers,
Options? options,
}) async {
return await _dio.get<T>(
endpoint,
queryParameters: queryParameters,
options: options?.copyWith(headers: {...?headers}),
);
}
/// 添加拦截器
void addInterceptor(Interceptor interceptor) {
_dio.interceptors.add(interceptor);
}
}
import 'package:appframe/config/locator.dart';
import 'package:appframe/data/models/message/h5_message.dart';
import 'package:appframe/data/models/message/h5_resp.dart';
// 消息处理器抽象类
abstract class MessageHandler {
// Future<Map<String, dynamic>> handleMessage(Map<String, dynamic> params);
Future<dynamic> handleMessage(Map<String, dynamic> params);
}
// 消息分发器
class MessageDispatcher {
final Map<String, MessageHandler> _handlers = {};
// 注册
void registerHandler(String command, MessageHandler handler) {
_handlers[command] = handler;
}
// 取消注册,暂时不会用到
void unregisterHandler(String command) {
_handlers.remove(command);
}
// 分发处理
Future<void> dispatch(H5Message h5Message, Function callback) async {
var handler = _handlers[h5Message.cmd];
if (handler == null) {
try {
handler = getIt.get<MessageHandler>(instanceName: h5Message.cmd);
registerHandler(h5Message.cmd, handler);
} catch (e) {
// 处理 getIt 找不到实例的情况
H5Resp h5Resp = H5Resp(h5Message.unique, h5Message.cmd, {}, '没有对应Cmd');
callback(h5Resp.toJson());
return;
}
}
try {
final result = await handler.handleMessage(h5Message.params);
H5Resp h5Resp = H5Resp(h5Message.unique, h5Message.cmd, result, '');
callback(h5Resp.toJson());
} catch (e) {
// 执行异常
H5Resp h5Resp = H5Resp(h5Message.unique, h5Message.cmd, {}, e.toString());
callback(h5Resp.toJson());
}
}
}
import 'dart:io';
import 'package:archive/archive.dart';
import 'package:flutter/services.dart';
class LocalServerService {
// 启动本地HTTP服务器
Future<HttpServer> startLocalServer() async {
HttpServer server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
print('本地服务器启动在端口: ${server.port}');
server.listen((HttpRequest request) async {
final String requestPath = request.uri.path == '/' ? '/index.html' : request.uri.path;
try {
if (requestPath.startsWith('/temp/')) {
// 临时目录文件的请求
await _serveTempFile(request, requestPath);
} else if (requestPath.startsWith('/test/')) {
// assets文件服务逻辑
await _serveAssetFile(request, requestPath);
} else {
// asset/dist.zip 文件服务逻辑
await _serveZipFileContent(request, requestPath);
}
} catch (e) {
print('处理请求时出错: $e');
request.response
..statusCode = HttpStatus.internalServerError
..write('Internal server error')
..close();
}
});
return server;
}
// 临时目录的文件
Future<void> _serveTempFile(HttpRequest request, String requestPath) async {
try {
// 临时文件已经设备路径
// 构建文件路径(移除 /temp 前缀)
final String filePath = requestPath.substring('/temp/'.length);
// 检查文件是否存在
final File file = File(filePath);
if (await file.exists()) {
// 读取文件内容
final List<int> bytes = await file.readAsBytes();
request.response
..headers.contentType = ContentType.parse(_getContentType(filePath))
..add(bytes)
..close();
} else {
request.response
..statusCode = HttpStatus.notFound
..write('File not found: $filePath')
..close();
}
} catch (e) {
print('读取临时文件时出错: $e');
request.response
..statusCode = HttpStatus.notFound
..write('File not found')
..close();
}
}
Future<void> _serveZipFileContent(HttpRequest request, String requestPath) async {
String zipAssetPath = 'assets/dist.zip';
try {
// 使用 rootBundle.load 加载资源文件
final ByteData data = await rootBundle.load(zipAssetPath);
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('/')) {
requestPath = requestPath.substring(1);
}
if (zipFileName == requestPath) {
targetFile = file;
break;
}
}
if (targetFile == null) {
request.response
..statusCode = HttpStatus.notFound
..write('File not found in zip: $requestPath')
..close();
return;
}
// 返回文件内容
request.response
..headers.contentType = ContentType.parse(_getContentType(requestPath))
..add(targetFile.content as List<int>)
..close();
} catch (e) {
print('读取zip文件时出错: $e');
request.response
..statusCode = HttpStatus.internalServerError
..write('Error reading zip file')
..close();
}
}
// 访问assets目录下的文件
Future<void> _serveAssetFile(HttpRequest request, String requestPath) async {
// 构建文件路径(移除 /test 前缀)
final String path = requestPath.substring('/test'.length);
final String filePath = 'assets$path';
try {
final ByteData data = await rootBundle.load(filePath);
final List<int> bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
request.response
..headers.contentType = ContentType.parse(_getContentType(filePath))
..add(bytes)
..close();
} catch (e) {
request.response
..statusCode = HttpStatus.notFound
..write('File not found: $filePath')
..close();
}
}
String _getContentType(String filePath) {
if (filePath.endsWith('.html')) return 'text/html';
if (filePath.endsWith('.js')) return 'application/javascript';
if (filePath.endsWith('.css')) return 'text/css';
if (filePath.endsWith('.png')) return 'image/png';
if (filePath.endsWith('.jpg') || filePath.endsWith('.jpeg')) return 'image/jpeg';
return 'application/octet-stream';
}
}
import 'package:appframe/bloc/web_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:webview_flutter/webview_flutter.dart';
class 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>?;
print("接收到的参数: $extraData");
var sessionCode = extraData?['sessionCode'];
var userCode = extraData?['userCode'];
var classCode = extraData?['classCode'];
var userType = extraData?['userType'];
var stuId = extraData?['stuId'];
print("sessionCode:$sessionCode");
return BlocProvider(
create: (context) =>
WebCubit(WebState(false, '界面加载中...', false, sessionCode, userCode, classCode, userType, stuId)),
child: BlocConsumer<WebCubit, WebState>(
builder: (context, state) {
return Scaffold(
appBar: AppBar(title: Text(state.title)),
body: state.loaded
? WebViewWidget(controller: context.read<WebCubit>().controller)
: const Center(child: CircularProgressIndicator()),
// 用于测试一下点击跳转路由
floatingActionButton: FloatingActionButton(
onPressed: () {
context.read<WebCubit>().goAuth();
},
child: const Icon(Icons.add),
),
);
},
listener: (context, state) {
print("web page listener -------------------------");
// 跳转到微信授权页面
if (state.needAuth) {
print("跳转到微信授权页面");
context.go("/wechatAuth");
}
},
),
);
}
}
import 'package:appframe/bloc/wechat_auth_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
class WechatAuthPage extends StatelessWidget {
const WechatAuthPage({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => WechatAuthCubit(WechatAuthState(null, null, null, null, null, 'no msg!')),
child: BlocConsumer<WechatAuthCubit, WechatAuthState>(
builder: (context, state) {
return Scaffold(
appBar: AppBar(title: Text('微信授权')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(state.result ?? 'no msg!'),
ElevatedButton(
onPressed: () {
context.read<WechatAuthCubit>().auth();
},
child: const Text('授权'),
),
],
),
),
);
},
listener: (context, state) {
print("wechat auth page listener-------------------------");
print(state.sessionCode);
print(state.userCode);
print(state.classCode);
print(state.userType);
print(state.stuId);
print(state.result);
print('带参数跳转webview');
context.go(
'/web',
extra: {
"sessionCode": state.sessionCode,
"userCode": state.userCode,
"classCode": state.classCode,
"userType": state.userType,
"stuId": state.stuId,
},
);
},
),
);
}
}
This diff is collapsed. Click to expand it.
name: appframe
description: "app frame project."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1
environment:
sdk: ^3.9.0
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
fluwx: ^5.7.2
go_router: ^16.2.1
flutter_bloc: ^9.1.1
webview_flutter: ^4.13.0
permission_handler: ^12.0.1
path: ^1.9.1
path_provider: ^2.1.5
image_picker: ^1.2.0
equatable: ^2.0.7
dio: ^5.9.0
archive: ^4.0.7
get_it: ^8.2.0
json_annotation: ^4.9.0
shared_preferences: ^2.5.3
flutter_sound: ^9.28.0
device_info_plus: ^11.5.0
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.7.0
json_serializable: ^6.11.0
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^5.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
assets:
- assets/
- assets/dist.zip
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/to/asset-from-package
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/to/font-from-package
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:appframe/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// // Build our app and trigger a frame.
// await tester.pumpWidget(const MyApp());
//
// // Verify that our counter starts at 0.
// expect(find.text('0'), findsOneWidget);
// expect(find.text('1'), findsNothing);
//
// // Tap the '+' icon and trigger a frame.
// await tester.tap(find.byIcon(Icons.add));
// await tester.pump();
//
// // Verify that our counter has incremented.
// expect(find.text('0'), findsNothing);
// expect(find.text('1'), findsOneWidget);
});
}
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!