首次提交
@@ -0,0 +1,49 @@
|
|||||||
|
plugins {
|
||||||
|
id 'com.android.application'
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 33
|
||||||
|
buildToolsVersion "30.0.3"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "com.example.waterapp"
|
||||||
|
minSdkVersion 26
|
||||||
|
targetSdkVersion 33
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
|
||||||
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
ndk {
|
||||||
|
abiFilters "arm64-v8a", "armeabi-v7a"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
signingConfig signingConfigs.debug
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicFeatures = []
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
jniLibs.srcDirs = ['libs']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
|
||||||
|
testImplementation 'junit:junit:4.+'
|
||||||
|
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||||
|
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.example.waterapp;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import android.support.test.InstrumentationRegistry;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class ExampleInstrumentedTest {
|
||||||
|
@Test
|
||||||
|
public void useAppContext() {
|
||||||
|
// Context of the app under test.
|
||||||
|
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||||
|
assertEquals("com.example.waterapp", appContext.getPackageName());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.waterapp">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
<application
|
||||||
|
android:name=".MyApplication"
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@drawable/arkuix"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:extractNativeLibs="true"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<activity android:exported="true" android:name=".EntryEntryAbilityActivity"
|
||||||
|
android:windowSoftInputMode="adjustResize |stateHidden"
|
||||||
|
android:configChanges="orientation|keyboard|layoutDirection|screenSize|uiMode|smallestScreenSize|density"
|
||||||
|
>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.example.waterapp;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import ohos.stage.ability.adapter.StageActivity;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example ace activity class, which will load ArkUI-X ability instance.
|
||||||
|
* StageActivity is provided by ArkUI-X
|
||||||
|
* @see <a href=
|
||||||
|
* "https://gitee.com/arkui-x/docs/blob/master/zh-cn/application-dev/tutorial/how-to-integrate-arkui-into-android.md">
|
||||||
|
* to build android library</a>
|
||||||
|
*/
|
||||||
|
public class EntryEntryAbilityActivity extends StageActivity {
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
Log.e("HiHelloWorld", "EntryEntryAbilityActivity");
|
||||||
|
|
||||||
|
setInstanceName("com.example.waterapp:entry:EntryAbility:");
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.example.waterapp;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import ohos.stage.ability.adapter.StageApplication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example ace application class, which will load ArkUI-X application instance.
|
||||||
|
* StageApplication is provided by ArkUI-X
|
||||||
|
* @see <a href=
|
||||||
|
* "https://gitee.com/arkui-x/docs/blob/master/zh-cn/application-dev/tutorial/how-to-integrate-arkui-into-android.md">
|
||||||
|
* to build android library</a>
|
||||||
|
*/
|
||||||
|
public class MyApplication extends StageApplication {
|
||||||
|
private static final String LOG_TAG = "HiHelloWorld";
|
||||||
|
|
||||||
|
private static final String RES_NAME = "res";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
Log.e(LOG_TAG, "MyApplication");
|
||||||
|
super.onCreate();
|
||||||
|
Log.e(LOG_TAG, "MyApplication onCreate");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 5.9 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">Waterapp</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.example.waterapp;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example local unit test, which will execute on the development machine (host).
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
public class ExampleUnitTest {
|
||||||
|
@Test
|
||||||
|
public void addition_isCorrect() {
|
||||||
|
assertEquals(4, 2 + 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:7.4.1'
|
||||||
|
|
||||||
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
// in the individual module build.gradle files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task clean(type: Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Project-wide Gradle settings.
|
||||||
|
# IDE (e.g. Android Studio) users:
|
||||||
|
# Gradle settings configured through the IDE *will override*
|
||||||
|
# any settings specified in this file.
|
||||||
|
# For more details on how to configure your build environment visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
|
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||||
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
|
# org.gradle.parallel=true
|
||||||
|
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||||
|
# Android operating system, and which are packaged with your app"s APK
|
||||||
|
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||||
|
android.useAndroidX=false
|
||||||
|
# Automatically convert third-party libraries to use AndroidX
|
||||||
|
android.enableJetifier=false
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#Thu Jan 07 16:36:34 CST 2021
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://repo.huaweicloud.com/gradle/gradle-8.4-bin.zip
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS="-Dfile.encoding=UTF-8"
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
(0) set -- ;;
|
||||||
|
(1) set -- "$args0" ;;
|
||||||
|
(2) set -- "$args0" "$args1" ;;
|
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=$(save "$@")
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||||
|
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Dfile.encoding=GBK"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windows variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
## This file must *NOT* be checked into Version Control Systems,
|
||||||
|
# as it contains information specific to your local configuration.
|
||||||
|
#
|
||||||
|
# Location of the SDK. This is only used by Gradle.
|
||||||
|
# For customization when using a Version Control System, please read the
|
||||||
|
# header note.
|
||||||
|
sdk.dir=C:/development/Android/sdk
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
include ':app'
|
||||||
|
rootProject.name = 'Waterapp'
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"crossplatform": true,
|
||||||
|
"modules": [
|
||||||
|
"entry"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,391 @@
|
|||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 52;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
89E41E4529F505070004F33E /* EntryEntryAbilityViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 89E41E4329F505070004F33E /* EntryEntryAbilityViewController.m */; };
|
||||||
|
89E41E4629F505CD0004F33E /* libarkui_ios.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = DF9C1B5D28B9FC3A005DCF58 /* libarkui_ios.xcframework */; };
|
||||||
|
89E41E4729F505CD0004F33E /* libarkui_ios.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DF9C1B5D28B9FC3A005DCF58 /* libarkui_ios.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
|
DFC5555629D7F36400B63EB3 /* arkui-x in Resources */ = {isa = PBXBuildFile; fileRef = DFC5555529D7F36400B63EB3 /* arkui-x */; };
|
||||||
|
F0AF1AD227D3684F008C55DD /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = F0AF1AD127D3684F008C55DD /* AppDelegate.m */; };
|
||||||
|
F0AF1ADD27D36856008C55DD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F0AF1ADC27D36856008C55DD /* Assets.xcassets */; };
|
||||||
|
F0AF1AE027D36856008C55DD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F0AF1ADE27D36856008C55DD /* LaunchScreen.storyboard */; };
|
||||||
|
F0AF1AE327D36856008C55DD /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = F0AF1AE227D36856008C55DD /* main.m */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
89E41E4829F505CD0004F33E /* Embed Frameworks */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 10;
|
||||||
|
files = (
|
||||||
|
89E41E4729F505CD0004F33E /* libarkui_ios.xcframework in Embed Frameworks */,
|
||||||
|
);
|
||||||
|
name = "Embed Frameworks";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
89E41E4329F505070004F33E /* EntryEntryAbilityViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EntryEntryAbilityViewController.m; sourceTree = "<group>"; };
|
||||||
|
89E41E4429F505070004F33E /* EntryEntryAbilityViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EntryEntryAbilityViewController.h; sourceTree = "<group>"; };
|
||||||
|
DF9C1B5D28B9FC3A005DCF58 /* libarkui_ios.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = libarkui_ios.xcframework; path = frameworks/libarkui_ios.xcframework; sourceTree = "<group>"; };
|
||||||
|
DFC5555529D7F36400B63EB3 /* arkui-x */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "arkui-x"; sourceTree = "<group>"; };
|
||||||
|
F0AF1ACD27D3684F008C55DD /* app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = app.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
F0AF1AD027D3684F008C55DD /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
|
||||||
|
F0AF1AD127D3684F008C55DD /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AppDelegate.m; sourceTree = "<group>"; };
|
||||||
|
F0AF1ADC27D36856008C55DD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
F0AF1ADF27D36856008C55DD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
|
F0AF1AE127D36856008C55DD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
F0AF1AE227D36856008C55DD /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
F0AF1ACA27D3684F008C55DD /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
89E41E4629F505CD0004F33E /* libarkui_ios.xcframework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
F0AF1AC427D3684F008C55DD = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
DFC5555529D7F36400B63EB3 /* arkui-x */,
|
||||||
|
F0AF1ACF27D3684F008C55DD /* app */,
|
||||||
|
F0AF1ACE27D3684F008C55DD /* Products */,
|
||||||
|
F0AF1AEB27D36948008C55DD /* Frameworks */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
F0AF1ACE27D3684F008C55DD /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
F0AF1ACD27D3684F008C55DD /* app.app */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
F0AF1ACF27D3684F008C55DD /* app */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
89E41E4429F505070004F33E /* EntryEntryAbilityViewController.h */,
|
||||||
|
89E41E4329F505070004F33E /* EntryEntryAbilityViewController.m */,
|
||||||
|
F0AF1AD027D3684F008C55DD /* AppDelegate.h */,
|
||||||
|
F0AF1AD127D3684F008C55DD /* AppDelegate.m */,
|
||||||
|
F0AF1ADC27D36856008C55DD /* Assets.xcassets */,
|
||||||
|
F0AF1ADE27D36856008C55DD /* LaunchScreen.storyboard */,
|
||||||
|
F0AF1AE127D36856008C55DD /* Info.plist */,
|
||||||
|
F0AF1AE227D36856008C55DD /* main.m */,
|
||||||
|
);
|
||||||
|
path = app;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
F0AF1AEB27D36948008C55DD /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
DF9C1B5D28B9FC3A005DCF58 /* libarkui_ios.xcframework */,
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
F0AF1ACC27D3684F008C55DD /* app */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = F0AF1AE627D36856008C55DD /* Build configuration list for PBXNativeTarget "app" */;
|
||||||
|
buildPhases = (
|
||||||
|
F0AF1AC927D3684F008C55DD /* Sources */,
|
||||||
|
F0AF1ACA27D3684F008C55DD /* Frameworks */,
|
||||||
|
F0AF1ACB27D3684F008C55DD /* Resources */,
|
||||||
|
89E41E4829F505CD0004F33E /* Embed Frameworks */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = app;
|
||||||
|
productName = app;
|
||||||
|
productReference = F0AF1ACD27D3684F008C55DD /* app.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
F0AF1AC527D3684F008C55DD /* Project object */ = {
|
||||||
|
isa = PBXProject;
|
||||||
|
attributes = {
|
||||||
|
LastUpgradeCheck = 1300;
|
||||||
|
TargetAttributes = {
|
||||||
|
F0AF1ACC27D3684F008C55DD = {
|
||||||
|
CreatedOnToolsVersion = 13.0;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = F0AF1AC827D3684F008C55DD /* Build configuration list for PBXProject "app" */;
|
||||||
|
compatibilityVersion = "Xcode 9.3";
|
||||||
|
developmentRegion = en;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (
|
||||||
|
en,
|
||||||
|
Base,
|
||||||
|
);
|
||||||
|
mainGroup = F0AF1AC427D3684F008C55DD;
|
||||||
|
productRefGroup = F0AF1ACE27D3684F008C55DD /* Products */;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (
|
||||||
|
F0AF1ACC27D3684F008C55DD /* app */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
F0AF1ACB27D3684F008C55DD /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
F0AF1AE027D36856008C55DD /* LaunchScreen.storyboard in Resources */,
|
||||||
|
DFC5555629D7F36400B63EB3 /* arkui-x in Resources */,
|
||||||
|
F0AF1ADD27D36856008C55DD /* Assets.xcassets in Resources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
F0AF1AC927D3684F008C55DD /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
F0AF1AD227D3684F008C55DD /* AppDelegate.m in Sources */,
|
||||||
|
89E41E4529F505070004F33E /* EntryEntryAbilityViewController.m in Sources */,
|
||||||
|
F0AF1AE327D36856008C55DD /* main.m in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXVariantGroup section */
|
||||||
|
F0AF1ADE27D36856008C55DD /* LaunchScreen.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
F0AF1ADF27D36856008C55DD /* Base */,
|
||||||
|
);
|
||||||
|
name = LaunchScreen.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
F0AF1AE427D36856008C55DD /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_TESTABILITY = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
|
"DEBUG=1",
|
||||||
|
"$(inherited)",
|
||||||
|
);
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
F0AF1AE527D36856008C55DD /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
F0AF1AE727D36856008C55DD /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ARCHS = arm64;
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = JCS8W75JJS;
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = app/Info.plist;
|
||||||
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
|
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||||
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = "com.example.waterapp";
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "no sign";
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/frameworks/libarkui_ios.xcframework/ios-arm64/libarkui_ios.framework/Headers/include";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
F0AF1AE827D36856008C55DD /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ARCHS = arm64;
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = JCS8W75JJS;
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = app/Info.plist;
|
||||||
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
|
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||||
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = "com.example.waterapp";
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "no sign";
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/frameworks/libarkui_ios.xcframework/ios-arm64/libarkui_ios.framework/Headers/include";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
F0AF1AC827D3684F008C55DD /* Build configuration list for PBXProject "app" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
F0AF1AE427D36856008C55DD /* Debug */,
|
||||||
|
F0AF1AE527D36856008C55DD /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
F0AF1AE627D36856008C55DD /* Build configuration list for PBXNativeTarget "app" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
F0AF1AE727D36856008C55DD /* Debug */,
|
||||||
|
F0AF1AE827D36856008C55DD /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
};
|
||||||
|
rootObject = F0AF1AC527D3684F008C55DD /* Project object */;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Huawei Device Co., Ltd.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
@interface AppDelegate : UIResponder <UIApplicationDelegate>
|
||||||
|
|
||||||
|
@property (nonatomic, strong) UIWindow *window;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Huawei Device Co., Ltd.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "AppDelegate.h"
|
||||||
|
#import "EntryEntryAbilityViewController.h"
|
||||||
|
#import <libarkui_ios/StageApplication.h>
|
||||||
|
|
||||||
|
#define BUNDLE_DIRECTORY @"arkui-x"
|
||||||
|
#define BUNDLE_NAME @"com.example.waterapp"
|
||||||
|
|
||||||
|
@interface AppDelegate ()
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation AppDelegate
|
||||||
|
|
||||||
|
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||||
|
[StageApplication configModuleWithBundleDirectory:BUNDLE_DIRECTORY];
|
||||||
|
[StageApplication launchApplication];
|
||||||
|
|
||||||
|
NSString *instanceName = [NSString stringWithFormat:@"%@:%@:%@",BUNDLE_NAME, @"entry", @"EntryAbility"];
|
||||||
|
EntryEntryAbilityViewController *mainView = [[EntryEntryAbilityViewController alloc] initWithInstanceName:instanceName];
|
||||||
|
[self setNavRootVC:mainView];
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options {
|
||||||
|
NSLog(@"appdelegate openUrl callback, url : %@", url.absoluteString); // eg: (com.entry.arkui://entry?OtherAbility)
|
||||||
|
|
||||||
|
NSString *bundleName = url.scheme;
|
||||||
|
NSString *moduleName = url.host;
|
||||||
|
NSString *abilityName, *params;
|
||||||
|
|
||||||
|
NSURLComponents * urlComponents = [NSURLComponents componentsWithString:url.absoluteString];
|
||||||
|
NSArray <NSURLQueryItem *> *array = urlComponents.queryItems;
|
||||||
|
for (NSURLQueryItem * item in array) {
|
||||||
|
if ([item.name isEqualToString:@"abilityName"]) {
|
||||||
|
abilityName = item.value;
|
||||||
|
} else if ([item.name isEqualToString:@"params"]) {
|
||||||
|
params = item.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[self handleOpenUrlWithBundleName:bundleName
|
||||||
|
moduleName:moduleName
|
||||||
|
abilityName:abilityName
|
||||||
|
params:params, nil];
|
||||||
|
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)handleOpenUrlWithBundleName:(NSString *)bundleName
|
||||||
|
moduleName:(NSString *)moduleName
|
||||||
|
abilityName:(NSString *)abilityName
|
||||||
|
params:(NSString *)params, ...NS_REQUIRES_NIL_TERMINATION {
|
||||||
|
|
||||||
|
id rootVC = [[UIApplication sharedApplication].delegate window].rootViewController;
|
||||||
|
BOOL hasRoot = NO;
|
||||||
|
if ([rootVC isKindOfClass:[UINavigationController class]]) {
|
||||||
|
hasRoot = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
id subStageVC = nil;
|
||||||
|
|
||||||
|
if ([moduleName isEqualToString:@"entry"] && [abilityName isEqualToString:@"EntryAbility"]) {
|
||||||
|
NSString *instanceName = [NSString stringWithFormat:@"%@:%@:%@",bundleName, moduleName, abilityName];
|
||||||
|
EntryEntryAbilityViewController *otherVC = [[EntryEntryAbilityViewController alloc] initWithInstanceName:instanceName];
|
||||||
|
subStageVC = (EntryEntryAbilityViewController *)otherVC;
|
||||||
|
} else if ([moduleName isEqualToString:@"entry"] && [abilityName isEqualToString:@"EntryAbility"]) {
|
||||||
|
NSString *instanceName = [NSString stringWithFormat:@"%@:%@:%@",bundleName, moduleName, abilityName];
|
||||||
|
EntryEntryAbilityViewController *otherVC = [[EntryEntryAbilityViewController alloc] initWithInstanceName:instanceName];
|
||||||
|
subStageVC = (EntryEntryAbilityViewController *)otherVC;
|
||||||
|
} // other ViewController
|
||||||
|
|
||||||
|
if (!subStageVC) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasRoot) {
|
||||||
|
[self setNavRootVC:subStageVC];
|
||||||
|
} else {
|
||||||
|
UINavigationController *rootNav = (UINavigationController *)self.window.rootViewController;
|
||||||
|
[rootNav pushViewController:subStageVC animated:YES];
|
||||||
|
}
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setNavRootVC:(id)viewController {
|
||||||
|
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||||
|
self.window.backgroundColor = [UIColor whiteColor];
|
||||||
|
[self.window makeKeyAndVisible];
|
||||||
|
UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:viewController];
|
||||||
|
[self setNaviAppearance:navi];
|
||||||
|
self.window.rootViewController = navi;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setNaviAppearance:(UINavigationController *)navi {
|
||||||
|
UINavigationBarAppearance *appearance = [UINavigationBarAppearance new];
|
||||||
|
[appearance configureWithOpaqueBackground];
|
||||||
|
appearance.backgroundColor = UIColor.whiteColor;
|
||||||
|
navi.navigationBar.standardAppearance = appearance;
|
||||||
|
navi.navigationBar.scrollEdgeAppearance = navi.navigationBar.standardAppearance;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "20x20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"scale" : "3x",
|
||||||
|
"size" : "20x20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "29x29"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"scale" : "3x",
|
||||||
|
"size" : "29x29"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "40x40"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"scale" : "3x",
|
||||||
|
"size" : "40x40"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "60x60"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"scale" : "3x",
|
||||||
|
"size" : "60x60"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "20x20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "20x20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "29x29"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "29x29"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "40x40"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "40x40"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "76x76"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "76x76"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "83.5x83.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ios-marketing",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||||
|
<dependencies>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
|
||||||
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--View Controller-->
|
||||||
|
<scene sceneID="EHf-IW-A2E">
|
||||||
|
<objects>
|
||||||
|
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||||
|
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||||
|
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="53" y="375"/>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
</document>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Huawei Device Co., Ltd.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef EntryEntryAbilityViewController_h
|
||||||
|
#define EntryEntryAbilityViewController_h
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
#import <libarkui_ios/StageViewController.h>
|
||||||
|
@interface EntryEntryAbilityViewController : StageViewController
|
||||||
|
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
#endif /* EntryEntryAbilityViewController_h */
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Huawei Device Co., Ltd.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "EntryEntryAbilityViewController.h"
|
||||||
|
|
||||||
|
@interface EntryEntryAbilityViewController ()
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation EntryEntryAbilityViewController
|
||||||
|
- (instancetype)initWithInstanceName:(NSString *)instanceName {
|
||||||
|
self = [super initWithInstanceName:instanceName];
|
||||||
|
if (self) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)viewDidLoad {
|
||||||
|
[super viewDidLoad];
|
||||||
|
|
||||||
|
self.edgesForExtendedLayout = UIRectEdgeNone;
|
||||||
|
self.extendedLayoutIncludesOpaqueBars = YES;
|
||||||
|
}
|
||||||
|
@end
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?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/>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 Huawei Device Co., Ltd.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
#import "AppDelegate.h"
|
||||||
|
|
||||||
|
int main(int argc, char * argv[]) {
|
||||||
|
NSString * appDelegateClassName;
|
||||||
|
@autoreleasepool {
|
||||||
|
// Setup code that might create autoreleased objects goes here.
|
||||||
|
appDelegateClassName = NSStringFromClass([AppDelegate class]);
|
||||||
|
}
|
||||||
|
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/node_modules
|
||||||
|
/oh_modules
|
||||||
|
/local.properties
|
||||||
|
/.idea
|
||||||
|
**/build
|
||||||
|
/.hvigor
|
||||||
|
.cxx
|
||||||
|
/.clangd
|
||||||
|
/.clang-format
|
||||||
|
/.clang-tidy
|
||||||
|
**/.test
|
||||||
|
/.appanalyzer
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"app": {
|
||||||
|
"bundleName": "com.example.waterapp",
|
||||||
|
"vendor": "example",
|
||||||
|
"versionCode": 1000000,
|
||||||
|
"versionName": "1.0.0",
|
||||||
|
"buildVersion": "1",
|
||||||
|
"icon": "$media:layered_image",
|
||||||
|
"label": "$string:app_name"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"string": [
|
||||||
|
{
|
||||||
|
"name": "app_name",
|
||||||
|
"value": "Waterapp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"layered-image":
|
||||||
|
{
|
||||||
|
"background" : "$media:background",
|
||||||
|
"foreground" : "$media:foreground"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"app": {
|
||||||
|
"signingConfigs": [],
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"name": "default",
|
||||||
|
"signingConfig": "default",
|
||||||
|
"targetSdkVersion": "6.1.0(23)",
|
||||||
|
"compatibleSdkVersion": "6.0.2(22)",
|
||||||
|
"runtimeOS": "HarmonyOS",
|
||||||
|
"buildOption": {
|
||||||
|
"strictMode": {
|
||||||
|
"caseSensitiveCheck": true,
|
||||||
|
"useNormalizedOHMUrl": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buildModeSet": [
|
||||||
|
{
|
||||||
|
"name": "debug",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "release"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"name": "entry",
|
||||||
|
"srcPath": "./entry",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"name": "default",
|
||||||
|
"applyToProducts": [
|
||||||
|
"default"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"**/*.ets"
|
||||||
|
],
|
||||||
|
"ignore": [
|
||||||
|
"**/src/ohosTest/**/*",
|
||||||
|
"**/src/test/**/*",
|
||||||
|
"**/src/mock/**/*",
|
||||||
|
"**/node_modules/**/*",
|
||||||
|
"**/oh_modules/**/*",
|
||||||
|
"**/build/**/*",
|
||||||
|
"**/.preview/**/*"
|
||||||
|
],
|
||||||
|
"ruleSet": [
|
||||||
|
"plugin:@performance/recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"@security/no-unsafe-aes": "error",
|
||||||
|
"@security/no-unsafe-hash": "error",
|
||||||
|
"@security/no-unsafe-mac": "warn",
|
||||||
|
"@security/no-unsafe-dh": "error",
|
||||||
|
"@security/no-unsafe-dsa": "error",
|
||||||
|
"@security/no-unsafe-ecdsa": "error",
|
||||||
|
"@security/no-unsafe-rsa-encrypt": "error",
|
||||||
|
"@security/no-unsafe-rsa-sign": "error",
|
||||||
|
"@security/no-unsafe-rsa-key": "error",
|
||||||
|
"@security/no-unsafe-dsa-key": "error",
|
||||||
|
"@security/no-unsafe-dh-key": "error",
|
||||||
|
"@security/no-unsafe-3des": "error"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/node_modules
|
||||||
|
/oh_modules
|
||||||
|
/.preview
|
||||||
|
/build
|
||||||
|
/.cxx
|
||||||
|
/.test
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"apiType": "stageMode",
|
||||||
|
"buildOption": {
|
||||||
|
"resOptions": {
|
||||||
|
"copyCodeResource": {
|
||||||
|
"enable": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"buildOptionSet": [
|
||||||
|
{
|
||||||
|
"name": "release",
|
||||||
|
"arkOptions": {
|
||||||
|
"obfuscation": {
|
||||||
|
"ruleOptions": {
|
||||||
|
"enable": false,
|
||||||
|
"files": [
|
||||||
|
"./obfuscation-rules.txt"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"name": "default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ohosTest",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently.
|
||||||
|
export { HapTasks } from '@ohos/hvigor-ohos-arkui-x-plugin';
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Define project specific obfuscation rules here.
|
||||||
|
# You can include the obfuscation configuration files in the current module's build-profile.json5.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/source-obfuscation
|
||||||
|
|
||||||
|
# Obfuscation options:
|
||||||
|
# -disable-obfuscation: disable all obfuscations
|
||||||
|
# -enable-property-obfuscation: obfuscate the property names
|
||||||
|
# -enable-toplevel-obfuscation: obfuscate the names in the global scope
|
||||||
|
# -compact: remove unnecessary blank spaces and all line feeds
|
||||||
|
# -remove-log: remove all console.* statements
|
||||||
|
# -print-namecache: print the name cache that contains the mapping from the old names to new names
|
||||||
|
# -apply-namecache: reuse the given cache file
|
||||||
|
|
||||||
|
# Keep options:
|
||||||
|
# -keep-property-name: specifies property names that you want to keep
|
||||||
|
# -keep-global-name: specifies names that you want to keep in the global scope
|
||||||
|
|
||||||
|
-enable-property-obfuscation
|
||||||
|
-enable-toplevel-obfuscation
|
||||||
|
-enable-filename-obfuscation
|
||||||
|
-enable-export-obfuscation
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "entry",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Please describe the basic information.",
|
||||||
|
"main": "",
|
||||||
|
"author": "",
|
||||||
|
"license": "",
|
||||||
|
"dependencies": {}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
import { promptAction } from '@kit.ArkUI';
|
||||||
|
import DeviceListInterface from "../model/DeviceListInterface";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加页面组件 - 科技蓝风格
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
export struct Add {
|
||||||
|
@State deviceCode: string = '';
|
||||||
|
@State deviceName: string = '';
|
||||||
|
|
||||||
|
// 确认添加设备
|
||||||
|
handleAddDevice(): void {
|
||||||
|
if (!this.deviceCode || !this.deviceName) {
|
||||||
|
promptAction.showToast({ message: '请填写完整信息' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新设备数据
|
||||||
|
const newDevice: DeviceListInterface = {
|
||||||
|
id: Date.now(),
|
||||||
|
name: this.deviceName,
|
||||||
|
code: this.deviceCode,
|
||||||
|
latitudeLongitude: '',
|
||||||
|
screenPosition: '',
|
||||||
|
status: 1 // 新添加的设备默认为正常状态
|
||||||
|
};
|
||||||
|
|
||||||
|
// 存储到AppStorage,供设备页面获取
|
||||||
|
AppStorage.setOrCreate('newDevice', newDevice);
|
||||||
|
|
||||||
|
promptAction.showToast({ message: '设备绑定成功' });
|
||||||
|
|
||||||
|
// 清空输入框
|
||||||
|
this.deviceCode = '';
|
||||||
|
this.deviceName = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
Column() {
|
||||||
|
// 顶部标题栏 - 科技蓝风格
|
||||||
|
Row() {
|
||||||
|
Image($rawfile("add/添加 加号.png"))
|
||||||
|
.width(20)
|
||||||
|
.height(20)
|
||||||
|
.fillColor('#FFFFFF')
|
||||||
|
.margin({ left: 20, right: 10 })
|
||||||
|
Text("添加设备")
|
||||||
|
.fontSize(20)
|
||||||
|
.fontWeight(600)
|
||||||
|
.fontColor("#FFFFFF")
|
||||||
|
|
||||||
|
Blank()
|
||||||
|
|
||||||
|
Text('设备绑定')
|
||||||
|
.fontSize(12)
|
||||||
|
.fontColor('rgba(255,255,255,0.5)')
|
||||||
|
}
|
||||||
|
.width("100%")
|
||||||
|
.height(60)
|
||||||
|
.padding({ left: 10, right: 20 })
|
||||||
|
.alignItems(VerticalAlign.Center)
|
||||||
|
|
||||||
|
// 表单区域
|
||||||
|
Column({ space: 16 }) {
|
||||||
|
// 设备编号输入
|
||||||
|
Column({ space: 8 }) {
|
||||||
|
Text('设备编号')
|
||||||
|
.fontSize(12)
|
||||||
|
.fontColor('rgba(255,255,255,0.6)')
|
||||||
|
.width('100%')
|
||||||
|
|
||||||
|
Row() {
|
||||||
|
Image($rawfile("add/条形码.png"))
|
||||||
|
.width(18)
|
||||||
|
.height(18)
|
||||||
|
.fillColor('rgba(255,255,255,0.5)')
|
||||||
|
.margin({ left: 16 })
|
||||||
|
TextInput({ placeholder: "请输入设备编号", text: this.deviceCode })
|
||||||
|
.onChange((value: string) => {
|
||||||
|
this.deviceCode = value;
|
||||||
|
})
|
||||||
|
.backgroundColor(Color.Transparent)
|
||||||
|
.layoutWeight(1)
|
||||||
|
.placeholderColor('rgba(255,255,255,0.4)')
|
||||||
|
.fontColor('#FFFFFF')
|
||||||
|
.caretColor('#00FF88')
|
||||||
|
}
|
||||||
|
.width("100%")
|
||||||
|
.height(50)
|
||||||
|
.backgroundColor('rgba(22,119,255,0.15)')
|
||||||
|
.borderRadius(12)
|
||||||
|
.border({ width: 1, color: 'rgba(22,119,255,0.3)' })
|
||||||
|
}
|
||||||
|
.width('88%')
|
||||||
|
.alignItems(HorizontalAlign.Start)
|
||||||
|
|
||||||
|
// 设备名称输入
|
||||||
|
Column({ space: 8 }) {
|
||||||
|
Text('设备名称')
|
||||||
|
.fontSize(12)
|
||||||
|
.fontColor('rgba(255,255,255,0.6)')
|
||||||
|
.width('100%')
|
||||||
|
|
||||||
|
Row() {
|
||||||
|
Image($rawfile("add/编写.png"))
|
||||||
|
.width(18)
|
||||||
|
.height(18)
|
||||||
|
.fillColor('rgba(255,255,255,0.5)')
|
||||||
|
.margin({ left: 16 })
|
||||||
|
TextInput({ placeholder: "请输入设备名称", text: this.deviceName })
|
||||||
|
.onChange((value: string) => {
|
||||||
|
this.deviceName = value;
|
||||||
|
})
|
||||||
|
.backgroundColor(Color.Transparent)
|
||||||
|
.layoutWeight(1)
|
||||||
|
.placeholderColor('rgba(255,255,255,0.4)')
|
||||||
|
.fontColor('#FFFFFF')
|
||||||
|
.caretColor('#00FF88')
|
||||||
|
}
|
||||||
|
.width("100%")
|
||||||
|
.height(50)
|
||||||
|
.backgroundColor('rgba(22,119,255,0.15)')
|
||||||
|
.borderRadius(12)
|
||||||
|
.border({ width: 1, color: 'rgba(22,119,255,0.3)' })
|
||||||
|
}
|
||||||
|
.width('88%')
|
||||||
|
.alignItems(HorizontalAlign.Start)
|
||||||
|
|
||||||
|
// 确认按钮
|
||||||
|
Button("确认绑定")
|
||||||
|
.width("70%")
|
||||||
|
.height(45)
|
||||||
|
.fontSize(16)
|
||||||
|
.fontColor('#FFFFFF')
|
||||||
|
.fontWeight(FontWeight.Bold)
|
||||||
|
.backgroundColor('rgba(0,255,136,0.25)')
|
||||||
|
.borderRadius(22)
|
||||||
|
.border({ width: 1, color: 'rgba(0,255,136,0.4)' })
|
||||||
|
.onClick(() => {
|
||||||
|
this.handleAddDevice();
|
||||||
|
})
|
||||||
|
|
||||||
|
// 提示信息
|
||||||
|
Text('绑定后可在设备列表中查看')
|
||||||
|
.fontSize(12)
|
||||||
|
.fontColor('rgba(255,255,255,0.4)')
|
||||||
|
.margin({ top: 12 })
|
||||||
|
}
|
||||||
|
.width("100%")
|
||||||
|
.layoutWeight(1)
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.linearGradient({
|
||||||
|
angle: 180,
|
||||||
|
colors: [['#0a1628', 0], ['#0d2847', 0.5], ['#1677FF', 1]]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,320 @@
|
|||||||
|
import { webview } from '@kit.ArkWeb';
|
||||||
|
import { http } from '@kit.NetworkKit';
|
||||||
|
import CarbonInfoInterface from '../model/CarbonInfoInterface';
|
||||||
|
import StatisticalInterface from '../model/StatisticalInterface';
|
||||||
|
import DeviceListInterface from '../model/DeviceListInterface';
|
||||||
|
import { getCarbonInfo, getCarbonList, getDeviceList } from '../utils/ApiService';
|
||||||
|
|
||||||
|
// 监测点数据接口
|
||||||
|
interface MonitorPoint {
|
||||||
|
name: string;
|
||||||
|
lat: number;
|
||||||
|
lng: number;
|
||||||
|
status: number;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 碳汇计算组件
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
export struct CarbonCalc {
|
||||||
|
// 图表控制器
|
||||||
|
controller: webview.WebviewController = new webview.WebviewController();
|
||||||
|
// 状态数据
|
||||||
|
@State carbonInfo: CarbonInfoInterface | null = null;
|
||||||
|
@State carbonTrend: StatisticalInterface[] = [];
|
||||||
|
@State monitorPoints: MonitorPoint[] = [];
|
||||||
|
@State isLoading: boolean = true;
|
||||||
|
@State isWebViewReady: boolean = false;
|
||||||
|
@State avgTemp: number = 25.0;
|
||||||
|
|
||||||
|
// 模拟数据
|
||||||
|
private mockCarbonInfo: CarbonInfoInterface = {
|
||||||
|
carbonSequestration: 125.6,
|
||||||
|
ph: 7.4,
|
||||||
|
waterTemperature: 25.2
|
||||||
|
};
|
||||||
|
|
||||||
|
private mockTrend: number[] = [98, 105, 112, 118, 122, 126];
|
||||||
|
|
||||||
|
private mockPoints: MonitorPoint[] = [
|
||||||
|
{ name: '长江监测点A', lat: 30.5, lng: 114.3, status: 1, color: '#00FF88' },
|
||||||
|
{ name: '长江监测点B', lat: 31.2, lng: 115.1, status: 0, color: '#FF6B6B' },
|
||||||
|
{ name: '长江监测点C', lat: 29.8, lng: 113.5, status: 1, color: '#00FF88' },
|
||||||
|
{ name: '长江监测点D', lat: 32.0, lng: 118.7, status: 1, color: '#00FF88' }
|
||||||
|
];
|
||||||
|
|
||||||
|
aboutToAppear(): void {
|
||||||
|
this.loadData();
|
||||||
|
setTimeout(() => {
|
||||||
|
this.isWebViewReady = true;
|
||||||
|
this.updateChart();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadData(): void {
|
||||||
|
this.carbonInfo = this.mockCarbonInfo;
|
||||||
|
this.monitorPoints = this.mockPoints;
|
||||||
|
this.avgTemp = this.mockCarbonInfo.waterTemperature;
|
||||||
|
this.isLoading = false;
|
||||||
|
|
||||||
|
// 尝试获取真实数据
|
||||||
|
getCarbonInfo().then((result) => {
|
||||||
|
if (result && result.data) {
|
||||||
|
this.carbonInfo = result.data;
|
||||||
|
this.avgTemp = result.data.waterTemperature;
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
// 使用模拟数据
|
||||||
|
});
|
||||||
|
|
||||||
|
getCarbonList().then((result) => {
|
||||||
|
if (result && result.data && result.data.length > 0) {
|
||||||
|
this.carbonTrend = result.data;
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
// 使用模拟数据
|
||||||
|
});
|
||||||
|
|
||||||
|
getDeviceList().then((result) => {
|
||||||
|
if (result && result.data && result.data.length > 0) {
|
||||||
|
this.parseDeviceData(result.data);
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
// 使用模拟数据
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parseDeviceData(devices: DeviceListInterface[]): void {
|
||||||
|
const points: MonitorPoint[] = [];
|
||||||
|
for (let i: number = 0; i < devices.length; i++) {
|
||||||
|
const device: DeviceListInterface = devices[i];
|
||||||
|
const coords: string[] = device.latitudeLongitude.split(',');
|
||||||
|
if (coords.length >= 2) {
|
||||||
|
const lat: number = parseFloat(coords[0]);
|
||||||
|
const lng: number = parseFloat(coords[1]);
|
||||||
|
points.push({
|
||||||
|
name: device.name,
|
||||||
|
lat: lat,
|
||||||
|
lng: lng,
|
||||||
|
status: device.status,
|
||||||
|
color: device.status === 1 ? '#00FF88' : '#FF6B6B'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (points.length > 0) {
|
||||||
|
this.monitorPoints = points;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateChart(): void {
|
||||||
|
if (!this.isWebViewReady) return;
|
||||||
|
try {
|
||||||
|
const jsCode: string = `updateData([${this.mockTrend.join(',')}])`;
|
||||||
|
this.controller.runJavaScript(jsCode);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('图表更新失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
Column() {
|
||||||
|
// 标题
|
||||||
|
Row() {
|
||||||
|
Text('碳汇计算')
|
||||||
|
.fontSize(18)
|
||||||
|
.fontWeight(FontWeight.Bold)
|
||||||
|
.fontColor('#FFFFFF')
|
||||||
|
Blank()
|
||||||
|
Text('水体固碳能力')
|
||||||
|
.fontSize(11)
|
||||||
|
.fontColor('rgba(255,255,255,0.5)')
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
.padding({ top: 12, bottom: 8 })
|
||||||
|
|
||||||
|
// ===== 上部分:统计卡片 =====
|
||||||
|
Row({ space: 8 }) {
|
||||||
|
// 上月平均温度
|
||||||
|
Column({ space: 4 }) {
|
||||||
|
Text('上月平均温度')
|
||||||
|
.fontSize(11)
|
||||||
|
.fontColor('rgba(255,255,255,0.6)')
|
||||||
|
Text(this.avgTemp.toFixed(1) + '°C')
|
||||||
|
.fontSize(18)
|
||||||
|
.fontWeight(600)
|
||||||
|
.fontColor('#FFE66D')
|
||||||
|
Row() {
|
||||||
|
Circle().width(4).height(4).fill('#FFE66D')
|
||||||
|
Text('正常')
|
||||||
|
.fontSize(9)
|
||||||
|
.fontColor('rgba(255,255,255,0.5)')
|
||||||
|
.margin({ left: 3 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.width('31%')
|
||||||
|
.height(70)
|
||||||
|
.padding(8)
|
||||||
|
.borderRadius(12)
|
||||||
|
.border({ width: 1, color: 'rgba(255,230,109,0.3)' })
|
||||||
|
.backgroundColor('rgba(255,230,109,0.08)')
|
||||||
|
|
||||||
|
// 上月碳汇能力
|
||||||
|
Column({ space: 4 }) {
|
||||||
|
Text('上月碳汇能力')
|
||||||
|
.fontSize(11)
|
||||||
|
.fontColor('rgba(255,255,255,0.6)')
|
||||||
|
Text((this.carbonInfo?.carbonSequestration ?? 0).toFixed(1) + 'kg')
|
||||||
|
.fontSize(18)
|
||||||
|
.fontWeight(600)
|
||||||
|
.fontColor('#00FF88')
|
||||||
|
Row() {
|
||||||
|
Circle().width(4).height(4).fill('#00FF88')
|
||||||
|
Text('良好')
|
||||||
|
.fontSize(9)
|
||||||
|
.fontColor('rgba(255,255,255,0.5)')
|
||||||
|
.margin({ left: 3 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.width('31%')
|
||||||
|
.height(70)
|
||||||
|
.padding(8)
|
||||||
|
.borderRadius(12)
|
||||||
|
.border({ width: 1, color: 'rgba(0,255,136,0.3)' })
|
||||||
|
.backgroundColor('rgba(0,255,136,0.08)')
|
||||||
|
|
||||||
|
// 上月平均pH
|
||||||
|
Column({ space: 4 }) {
|
||||||
|
Text('上月平均pH')
|
||||||
|
.fontSize(11)
|
||||||
|
.fontColor('rgba(255,255,255,0.6)')
|
||||||
|
Text((this.carbonInfo?.ph ?? 0).toFixed(1))
|
||||||
|
.fontSize(18)
|
||||||
|
.fontWeight(600)
|
||||||
|
.fontColor('#4ECDC4')
|
||||||
|
Row() {
|
||||||
|
Circle().width(4).height(4).fill('#4ECDC4')
|
||||||
|
Text('达标')
|
||||||
|
.fontSize(9)
|
||||||
|
.fontColor('rgba(255,255,255,0.5)')
|
||||||
|
.margin({ left: 3 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.width('31%')
|
||||||
|
.height(70)
|
||||||
|
.padding(8)
|
||||||
|
.borderRadius(12)
|
||||||
|
.border({ width: 1, color: 'rgba(78,205,196,0.3)' })
|
||||||
|
.backgroundColor('rgba(78,205,196,0.08)')
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
.justifyContent(FlexAlign.SpaceBetween)
|
||||||
|
|
||||||
|
// ===== 中间部分:碳汇趋势折线图 =====
|
||||||
|
Column({ space: 6 }) {
|
||||||
|
Row() {
|
||||||
|
Text('碳汇趋势')
|
||||||
|
.fontSize(13)
|
||||||
|
.fontWeight(500)
|
||||||
|
.fontColor('#FFFFFF')
|
||||||
|
Blank()
|
||||||
|
Text('近6个月')
|
||||||
|
.fontSize(10)
|
||||||
|
.fontColor('rgba(255,255,255,0.5)')
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
|
||||||
|
Web({ src: $rawfile('chart/carbon_chart.html'), controller: this.controller })
|
||||||
|
.width('100%')
|
||||||
|
.height(160)
|
||||||
|
.backgroundColor('transparent')
|
||||||
|
.javaScriptAccess(true)
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
.padding(10)
|
||||||
|
.margin({ top: 8 })
|
||||||
|
.borderRadius(12)
|
||||||
|
.border({ width: 1, color: 'rgba(22,119,255,0.25)' })
|
||||||
|
.backgroundColor('rgba(13,40,71,0.5)')
|
||||||
|
|
||||||
|
// ===== 下部分:监测点分布地图 =====
|
||||||
|
Column({ space: 8 }) {
|
||||||
|
// 地图标题
|
||||||
|
Row() {
|
||||||
|
Text('监测点分布')
|
||||||
|
.fontSize(13)
|
||||||
|
.fontWeight(500)
|
||||||
|
.fontColor('#FFFFFF')
|
||||||
|
Blank()
|
||||||
|
Row({ space: 6 }) {
|
||||||
|
Row({ space: 3 }) {
|
||||||
|
Circle().width(5).height(5).fill('#00FF88')
|
||||||
|
Text('正常')
|
||||||
|
.fontSize(9)
|
||||||
|
.fontColor('rgba(255,255,255,0.6)')
|
||||||
|
}
|
||||||
|
Row({ space: 3 }) {
|
||||||
|
Circle().width(5).height(5).fill('#FF6B6B')
|
||||||
|
Text('异常')
|
||||||
|
.fontSize(9)
|
||||||
|
.fontColor('rgba(255,255,255,0.6)')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
|
||||||
|
// 监测点地图区域(图片占位符,后期添加图片后取消注释)
|
||||||
|
Image($rawfile('ces/all_point.png'))
|
||||||
|
.width('100%')
|
||||||
|
.height(180)
|
||||||
|
.borderRadius(12)
|
||||||
|
.border({ width: 1, color: 'rgba(22,119,255,0.3)' })
|
||||||
|
|
||||||
|
// 临时占位区域(待添加图片)
|
||||||
|
// Column()
|
||||||
|
// .width('100%')
|
||||||
|
// .height(180)
|
||||||
|
// .linearGradient({
|
||||||
|
// angle: 135,
|
||||||
|
// colors: [['#1a3a5c', 0], ['#0d2847', 1]]
|
||||||
|
// })
|
||||||
|
// .borderRadius(12)
|
||||||
|
// .border({ width: 1, color: 'rgba(22,119,255,0.3)' })
|
||||||
|
|
||||||
|
// 监测点列表
|
||||||
|
Row({ space: 10 }) {
|
||||||
|
ForEach(this.monitorPoints, (point: MonitorPoint) => {
|
||||||
|
Row({ space: 4 }) {
|
||||||
|
Circle()
|
||||||
|
.width(6)
|
||||||
|
.height(6)
|
||||||
|
.fill(point.color)
|
||||||
|
Text(point.name)
|
||||||
|
.fontSize(10)
|
||||||
|
.fontColor('rgba(255,255,255,0.8)')
|
||||||
|
}
|
||||||
|
.padding({ left: 6, right: 6, top: 4, bottom: 4 })
|
||||||
|
.borderRadius(8)
|
||||||
|
.backgroundColor('rgba(22,119,255,0.1)')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
.layoutWeight(1)
|
||||||
|
.margin({ top: 8 })
|
||||||
|
.padding(10)
|
||||||
|
.borderRadius(12)
|
||||||
|
.border({ width: 1, color: 'rgba(22,119,255,0.25)' })
|
||||||
|
.backgroundColor('rgba(13,40,71,0.4)')
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.linearGradient({
|
||||||
|
angle: 180,
|
||||||
|
colors: [['#0a1628', 0], ['#0d2847', 0.5], ['#1677FF', 1]]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,457 @@
|
|||||||
|
import { webview } from '@kit.ArkWeb';
|
||||||
|
import { buffer, JSON } from '@kit.ArkTS';
|
||||||
|
import { http } from '@kit.NetworkKit';
|
||||||
|
import { BusinessError } from '@kit.BasicServicesKit';
|
||||||
|
import { getContrast } from '../utils/ApiService';
|
||||||
|
import StatisticalInterface from '../model/StatisticalInterface';
|
||||||
|
|
||||||
|
interface IndicatorConfig {
|
||||||
|
name: string;
|
||||||
|
unit: string;
|
||||||
|
color: string;
|
||||||
|
range: string;
|
||||||
|
data: number[];
|
||||||
|
paramType: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DifyStreamChunk {
|
||||||
|
event?: string;
|
||||||
|
answer?: string;
|
||||||
|
conversation_id?: string;
|
||||||
|
message?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DifyChatRequestBody {
|
||||||
|
inputs: Record<string, string>;
|
||||||
|
query: string;
|
||||||
|
response_mode: 'streaming';
|
||||||
|
conversation_id: string;
|
||||||
|
user: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DifyErrorResponse {
|
||||||
|
message: string;
|
||||||
|
code?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StatisticalItem {
|
||||||
|
month: string;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export struct ContrastAnalysis {
|
||||||
|
controller: webview.WebviewController = new webview.WebviewController();
|
||||||
|
@State currentIndicator: number = 0;
|
||||||
|
@State aiMessage: string = '';
|
||||||
|
@State isGenerating: boolean = false;
|
||||||
|
@State conversationId: string = '';
|
||||||
|
@State isWebReady: boolean = false;
|
||||||
|
@State isComponentActive: boolean = true;
|
||||||
|
|
||||||
|
private httpRequest: http.HttpRequest | null = null;
|
||||||
|
private sseBuffer: string = '';
|
||||||
|
private currentRequestIndicator: number = -1;
|
||||||
|
|
||||||
|
private readonly DIFY_BASE_URL: string = 'http://192.168.31.26';
|
||||||
|
private readonly DIFY_API_KEY: string = 'app-OeXDz5qppFhB4JMU9PQBEJai';
|
||||||
|
|
||||||
|
private indicators: IndicatorConfig[] = [
|
||||||
|
{ name: '水温', unit: '°C', color: '#FFE66D', range: '15-30', data: [], paramType: 1 },
|
||||||
|
{ name: 'COD', unit: 'mg/L', color: '#00FF88', range: '≤40', data: [], paramType: 2 },
|
||||||
|
{ name: 'pH', unit: 'pH', color: '#4ECDC4', range: '6.5-8.5', data: [], paramType: 3 },
|
||||||
|
{ name: '余氯', unit: 'mg/L', color: '#F38181', range: '0.3-0.5', data: [], paramType: 4 },
|
||||||
|
{ name: '电导率', unit: 'μS/cm', color: '#95E1D3', range: '500-1500', data: [], paramType: 5 },
|
||||||
|
{ name: '氨氮', unit: 'mg/L', color: '#FF6B6B', range: '≤1.0', data: [], paramType: 6 }
|
||||||
|
];
|
||||||
|
|
||||||
|
aboutToAppear(): void {
|
||||||
|
this.isComponentActive = true;
|
||||||
|
this.aiMessage = '点击"AI分析"按钮,将自动分析当前选中的指标数据。';
|
||||||
|
this.isWebReady = false;
|
||||||
|
this.currentRequestIndicator = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
aboutToDisappear(): void {
|
||||||
|
this.isComponentActive = false;
|
||||||
|
this.isWebReady = false;
|
||||||
|
this.cancelRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelRequest(): void {
|
||||||
|
this.isGenerating = false;
|
||||||
|
this.destroyRequest();
|
||||||
|
if (this.aiMessage && this.aiMessage !== '等待分析...') {
|
||||||
|
this.aiMessage += '\n[已取消请求]';
|
||||||
|
} else {
|
||||||
|
this.aiMessage = '已取消请求';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private destroyRequest(): void {
|
||||||
|
if (this.httpRequest) {
|
||||||
|
try {
|
||||||
|
this.httpRequest.destroy();
|
||||||
|
} catch (e) {
|
||||||
|
console.error('销毁HTTP请求失败', JSON.stringify(e));
|
||||||
|
}
|
||||||
|
this.httpRequest = null;
|
||||||
|
}
|
||||||
|
this.sseBuffer = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
private runChartScript(script: string): void {
|
||||||
|
if (!this.isComponentActive || !this.isWebReady) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.controller.runJavaScript(script);
|
||||||
|
console.info('执行图表脚本:' + script);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('图表脚本执行失败', JSON.stringify(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildChartDataFromContrast(rawList: StatisticalInterface[]): StatisticalItem[] {
|
||||||
|
return rawList.map((item: StatisticalInterface): StatisticalItem => ({
|
||||||
|
month: `${item.type}${item.date}月`,
|
||||||
|
value: Number(item.value) || 0
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadChartData(): Promise<void> {
|
||||||
|
const targetIndicator: number = this.currentIndicator;
|
||||||
|
this.currentRequestIndicator = targetIndicator;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const config: IndicatorConfig = this.indicators[targetIndicator];
|
||||||
|
console.info('请求指标 paramType:' + config.paramType);
|
||||||
|
|
||||||
|
const res = await getContrast(config.paramType);
|
||||||
|
|
||||||
|
if (this.currentIndicator !== targetIndicator || !this.isComponentActive || !this.isWebReady) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawList: StatisticalInterface[] = res.data || [];
|
||||||
|
const chartData: StatisticalItem[] = this.buildChartDataFromContrast(rawList);
|
||||||
|
config.data = chartData.map((item: StatisticalItem): number => item.value);
|
||||||
|
|
||||||
|
this.runChartScript(`initChart(${JSON.stringify(chartData)})`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('加载图表数据失败', JSON.stringify(err));
|
||||||
|
} finally {
|
||||||
|
if (this.currentRequestIndicator === targetIndicator) {
|
||||||
|
this.currentRequestIndicator = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onIndicatorChange(index: number): void {
|
||||||
|
this.currentIndicator = index;
|
||||||
|
this.loadChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
sendAiRequest(): void {
|
||||||
|
if (this.isGenerating || !this.isComponentActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const indicator: IndicatorConfig = this.indicators[this.currentIndicator];
|
||||||
|
const data: number[] = indicator.data;
|
||||||
|
|
||||||
|
if (!data.length) {
|
||||||
|
this.aiMessage = '暂无数据,无法分析';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const avg: number = data.reduce((a: number, b: number): number => a + b, 0) / data.length;
|
||||||
|
const max: number = Math.max(...data);
|
||||||
|
const min: number = Math.min(...data);
|
||||||
|
|
||||||
|
const prompt: string =
|
||||||
|
`分析${indicator.name}指标年度数据:平均值${avg.toFixed(2)}${indicator.unit},最高${max.toFixed(2)}${indicator.unit},最低${min.toFixed(2)}${indicator.unit},标准${indicator.range}。简要分析趋势并给出建议。`;
|
||||||
|
|
||||||
|
this.isGenerating = true;
|
||||||
|
this.aiMessage = '';
|
||||||
|
this.requestDifyInStream(prompt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bufferToStr(src: ArrayBuffer, encoding: buffer.BufferEncoding = 'utf-8'): string {
|
||||||
|
const buf: buffer.Buffer = buffer.from(src);
|
||||||
|
return buf.toString(encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
private requestDifyInStream(query: string): void {
|
||||||
|
if (!this.DIFY_API_KEY.trim() || !this.isComponentActive) {
|
||||||
|
this.aiMessage = 'Dify API Key 未配置';
|
||||||
|
this.isGenerating = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.destroyRequest();
|
||||||
|
this.httpRequest = http.createHttp();
|
||||||
|
this.sseBuffer = '';
|
||||||
|
|
||||||
|
const url: string = this.DIFY_BASE_URL.replace(/\/+$/, '') + '/v1/chat-messages';
|
||||||
|
|
||||||
|
const body: DifyChatRequestBody = {
|
||||||
|
inputs: {
|
||||||
|
"code": 'DEVICE0121',
|
||||||
|
"oldMonth": `${new Date().getFullYear() - 1}年${new Date().getMonth().toString().padStart(2, '0')}月`,
|
||||||
|
"newMonth": `${new Date().getFullYear()}年${new Date().getMonth().toString().padStart(2, '0')}月`
|
||||||
|
},
|
||||||
|
query: query,
|
||||||
|
user: 'qyl',
|
||||||
|
response_mode: 'streaming',
|
||||||
|
conversation_id: this.conversationId || ''
|
||||||
|
};
|
||||||
|
|
||||||
|
let isSseResponse: boolean = false;
|
||||||
|
let rawNonSse: string = '';
|
||||||
|
this.aiMessage = '连接中...';
|
||||||
|
|
||||||
|
this.httpRequest.on('headersReceive', (headers: Object): void => {
|
||||||
|
if (!this.isComponentActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const headerStr = JSON.stringify(headers).toLowerCase();
|
||||||
|
isSseResponse = headerStr.includes('text/event-stream');
|
||||||
|
} catch (e) {
|
||||||
|
console.error('解析响应头失败', JSON.stringify(e));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.httpRequest.on('dataReceive', (data: ArrayBuffer): void => {
|
||||||
|
if (!this.isGenerating || !this.isComponentActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunk: string = this.bufferToStr(data);
|
||||||
|
|
||||||
|
if (isSseResponse) {
|
||||||
|
this.sseBuffer += chunk;
|
||||||
|
const events: string[] = this.sseBuffer.split('\n\n');
|
||||||
|
this.sseBuffer = events.pop() || '';
|
||||||
|
|
||||||
|
for (const event of events) {
|
||||||
|
const lines: string[] = event.split('\n');
|
||||||
|
let payload: string = '';
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const trimLine: string = line.trim();
|
||||||
|
if (trimLine.startsWith('data:')) {
|
||||||
|
payload += trimLine.replace(/^data:\s*/, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!payload || payload === '[DONE]') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const js = JSON.parse(payload) as DifyStreamChunk;
|
||||||
|
if (js.event === 'message' || js.event === 'agent_message') {
|
||||||
|
if (this.aiMessage === '连接中...') {
|
||||||
|
this.aiMessage = '';
|
||||||
|
}
|
||||||
|
this.aiMessage += js.answer || '';
|
||||||
|
if (js.conversation_id) {
|
||||||
|
this.conversationId = js.conversation_id;
|
||||||
|
}
|
||||||
|
} else if (js.event === 'message_end') {
|
||||||
|
this.isGenerating = false;
|
||||||
|
} else if (js.event === 'error') {
|
||||||
|
this.aiMessage = 'Dify错误: ' + (js.message || '未知错误');
|
||||||
|
this.isGenerating = false;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('解析SSE数据失败', JSON.stringify(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rawNonSse += chunk;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.httpRequest.on('dataEnd', (): void => {
|
||||||
|
if (!this.isComponentActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSseResponse && rawNonSse.trim().length > 0) {
|
||||||
|
try {
|
||||||
|
const errJson = JSON.parse(rawNonSse) as DifyErrorResponse;
|
||||||
|
this.aiMessage = '错误:' + (errJson.message || rawNonSse);
|
||||||
|
} catch (e) {
|
||||||
|
this.aiMessage = rawNonSse;
|
||||||
|
}
|
||||||
|
} else if (this.aiMessage === '连接中...') {
|
||||||
|
this.aiMessage = '未收到有效数据';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isGenerating = false;
|
||||||
|
this.destroyRequest();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.httpRequest.requestInStream(url, {
|
||||||
|
method: http.RequestMethod.POST,
|
||||||
|
header: {
|
||||||
|
Authorization: 'Bearer ' + this.DIFY_API_KEY,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
extraData: JSON.stringify(body),
|
||||||
|
connectTimeout: 60000,
|
||||||
|
readTimeout: 120000
|
||||||
|
}).catch((err: BusinessError): void => {
|
||||||
|
if (!this.isComponentActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.aiMessage = '网络错误: ' + err.message;
|
||||||
|
this.isGenerating = false;
|
||||||
|
this.destroyRequest();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
Column() {
|
||||||
|
Row() {
|
||||||
|
Text('对比分析')
|
||||||
|
.fontSize(18)
|
||||||
|
.fontWeight(FontWeight.Bold)
|
||||||
|
.fontColor('#FFFFFF');
|
||||||
|
Blank();
|
||||||
|
Text('历年趋势')
|
||||||
|
.fontSize(11)
|
||||||
|
.fontColor('rgba(255,255,255,0.5)');
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
.padding({ top: 12, bottom: 8 });
|
||||||
|
|
||||||
|
Column({ space: 10 }) {
|
||||||
|
Row() {
|
||||||
|
Row({ space: 6 }) {
|
||||||
|
Text(this.indicators[this.currentIndicator].name)
|
||||||
|
.fontSize(14)
|
||||||
|
.fontWeight(FontWeight.Bold)
|
||||||
|
.fontColor(this.indicators[this.currentIndicator].color);
|
||||||
|
Text('单位: ' + this.indicators[this.currentIndicator].unit)
|
||||||
|
.fontSize(11)
|
||||||
|
.fontColor('rgba(255,255,255,0.6)');
|
||||||
|
}
|
||||||
|
Blank();
|
||||||
|
Row({ space: 4 }) {
|
||||||
|
Button() {
|
||||||
|
Text('◀')
|
||||||
|
.fontSize(8)
|
||||||
|
.fontColor('#FFFFFF');
|
||||||
|
}
|
||||||
|
.width(20)
|
||||||
|
.height(20)
|
||||||
|
.onClick(() => {
|
||||||
|
this.onIndicatorChange(
|
||||||
|
this.currentIndicator > 0 ? this.currentIndicator - 1 : this.indicators.length - 1
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Button() {
|
||||||
|
Text('▶')
|
||||||
|
.fontSize(8)
|
||||||
|
.fontColor('#FFFFFF');
|
||||||
|
}
|
||||||
|
.width(20)
|
||||||
|
.height(20)
|
||||||
|
.onClick(() => {
|
||||||
|
this.onIndicatorChange(
|
||||||
|
this.currentIndicator < this.indicators.length - 1 ? this.currentIndicator + 1 : 0
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row() {
|
||||||
|
Text('标准范围: ')
|
||||||
|
.fontSize(11)
|
||||||
|
.fontColor('rgba(255,255,255,0.5)');
|
||||||
|
Text(this.indicators[this.currentIndicator].range)
|
||||||
|
.fontSize(11)
|
||||||
|
.fontColor(this.indicators[this.currentIndicator].color);
|
||||||
|
}
|
||||||
|
.justifyContent(FlexAlign.Center);
|
||||||
|
|
||||||
|
Web({
|
||||||
|
src: $rawfile('chart/contrast_chart.html'),
|
||||||
|
controller: this.controller
|
||||||
|
})
|
||||||
|
.height(210)
|
||||||
|
.width('100%')
|
||||||
|
.javaScriptAccess(true)
|
||||||
|
.onPageBegin(() => {
|
||||||
|
this.isWebReady = false;
|
||||||
|
})
|
||||||
|
.onPageEnd(() => {
|
||||||
|
this.isWebReady = true;
|
||||||
|
this.loadChartData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
.padding(12)
|
||||||
|
.borderRadius(14)
|
||||||
|
.border({ width: 1, color: 'rgba(22,119,255,0.25)' })
|
||||||
|
.backgroundColor('rgba(13,40,71,0.55)');
|
||||||
|
|
||||||
|
Column({ space: 8 }) {
|
||||||
|
Row() {
|
||||||
|
Row({ space: 6 }) {
|
||||||
|
Text('AI智能分析')
|
||||||
|
.fontSize(13)
|
||||||
|
.fontColor('#FFFFFF');
|
||||||
|
}
|
||||||
|
Blank();
|
||||||
|
Circle()
|
||||||
|
.width(5)
|
||||||
|
.height(5)
|
||||||
|
.fill(this.isGenerating ? '#00FF88' : '#1677FF');
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(this.isGenerating ? '停止' : 'AI分析')
|
||||||
|
.width('100%')
|
||||||
|
.height(34)
|
||||||
|
.borderRadius(17)
|
||||||
|
.backgroundColor(this.isGenerating ? 'rgba(255,77,79,0.25)' : 'rgba(0,255,136,0.15)')
|
||||||
|
.fontColor('#FFFFFF')
|
||||||
|
.onClick(() => {
|
||||||
|
this.isGenerating ? this.cancelRequest() : this.sendAiRequest();
|
||||||
|
});
|
||||||
|
|
||||||
|
Scroll() {
|
||||||
|
Text(this.aiMessage || '等待分析...')
|
||||||
|
.fontSize(12)
|
||||||
|
.fontColor('rgba(255,255,255,0.9)')
|
||||||
|
.lineHeight(18)
|
||||||
|
.width('100%')
|
||||||
|
.padding(10);
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.layoutWeight(1)
|
||||||
|
.scrollBar(BarState.Auto)
|
||||||
|
.edgeEffect(EdgeEffect.Spring)
|
||||||
|
.borderRadius(10)
|
||||||
|
.backgroundColor('rgba(22,119,255,0.08)');
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
.layoutWeight(1)
|
||||||
|
.margin({ top: 8 })
|
||||||
|
.padding(12)
|
||||||
|
.borderRadius(14)
|
||||||
|
.backgroundColor('rgba(13,40,71,0.4)');
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.linearGradient({
|
||||||
|
angle: 180,
|
||||||
|
colors: [['#0a1628', 0], ['#0d2847', 0.5], ['#1677FF', 1]]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,581 @@
|
|||||||
|
import { getAlarmList, getAlarmStatistics } from '../utils/ApiService';
|
||||||
|
import AlarmItemInterface from '../model/AlarmItemInterface';
|
||||||
|
import AlarmStatisticsInterface from '../model/AlarmStatisticsInterface';
|
||||||
|
import DeviceListInterface from '../model/DeviceListInterface';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备报警组件
|
||||||
|
* 上部为地图区域,下部为设备报警列表
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
export struct DeviceAlarm {
|
||||||
|
// 报警列表数据
|
||||||
|
@State alarmList: AlarmItemInterface[] = [];
|
||||||
|
// 报警统计数据
|
||||||
|
@State alarmStatistics: AlarmStatisticsInterface | null = null;
|
||||||
|
// 加载状态
|
||||||
|
@State isLoading: boolean = true;
|
||||||
|
// 当前设备信息
|
||||||
|
private currentDevice: DeviceListInterface | null = null;
|
||||||
|
|
||||||
|
// 模拟报警数据(用于演示)
|
||||||
|
private mockAlarmList: AlarmItemInterface[] = [
|
||||||
|
{
|
||||||
|
deviceId: 1,
|
||||||
|
id: 1,
|
||||||
|
msg: 'pH值超标',
|
||||||
|
value: '9.2',
|
||||||
|
standard: '6.5-8.5',
|
||||||
|
coordinate: '30.5,114.3',
|
||||||
|
screenPosition: '',
|
||||||
|
createTime: '2026-05-01 14:32:00',
|
||||||
|
name: '长江监测点A',
|
||||||
|
code: 'DEVICE001'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deviceId: 2,
|
||||||
|
id: 2,
|
||||||
|
msg: '氨氮超标',
|
||||||
|
value: '1.8',
|
||||||
|
standard: '≤1.0',
|
||||||
|
coordinate: '31.2,115.1',
|
||||||
|
screenPosition: '',
|
||||||
|
createTime: '2026-05-01 10:15:00',
|
||||||
|
name: '长江监测点B',
|
||||||
|
code: 'DEVICE002'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deviceId: 1,
|
||||||
|
id: 3,
|
||||||
|
msg: 'COD超标',
|
||||||
|
value: '58',
|
||||||
|
standard: '≤40',
|
||||||
|
coordinate: '30.5,114.3',
|
||||||
|
screenPosition: '',
|
||||||
|
createTime: '2026-04-30 16:45:00',
|
||||||
|
name: '长江监测点A',
|
||||||
|
code: 'DEVICE001'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deviceId: 3,
|
||||||
|
id: 4,
|
||||||
|
msg: '电导率异常',
|
||||||
|
value: '1850',
|
||||||
|
standard: '500-1500',
|
||||||
|
coordinate: '29.8,113.5',
|
||||||
|
screenPosition: '',
|
||||||
|
createTime: '2026-04-30 08:20:00',
|
||||||
|
name: '长江监测点C',
|
||||||
|
code: 'DEVICE003'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deviceId: 2,
|
||||||
|
id: 5,
|
||||||
|
msg: '水温异常',
|
||||||
|
value: '35.5',
|
||||||
|
standard: '15-30',
|
||||||
|
coordinate: '31.2,115.1',
|
||||||
|
screenPosition: '',
|
||||||
|
createTime: '2026-04-29 11:30:00',
|
||||||
|
name: '长江监测点B',
|
||||||
|
code: 'DEVICE002'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// 模拟统计数据
|
||||||
|
private mockStatistics: AlarmStatisticsInterface = {
|
||||||
|
todayCount: '3',
|
||||||
|
todayTrend: '1',
|
||||||
|
todayPercentage: '15',
|
||||||
|
weekCount: '12',
|
||||||
|
weekTrend: '0',
|
||||||
|
weekPercentage: '8',
|
||||||
|
monthCount: '28',
|
||||||
|
monthTrend: '1',
|
||||||
|
monthPercentage: '12'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 科技蓝主色调
|
||||||
|
private primaryColor: string = '#1677FF';
|
||||||
|
|
||||||
|
aboutToAppear(): void {
|
||||||
|
// 从AppStorage获取当前设备信息
|
||||||
|
const device = AppStorage.get<DeviceListInterface>('point');
|
||||||
|
this.currentDevice = device ?? null;
|
||||||
|
// 加载报警数据
|
||||||
|
this.loadAlarmData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载报警数据
|
||||||
|
async loadAlarmData(): Promise<void> {
|
||||||
|
try {
|
||||||
|
// 获取报警列表
|
||||||
|
const listResult = await getAlarmList();
|
||||||
|
if (listResult && listResult.data && listResult.data.length > 0) {
|
||||||
|
this.alarmList = listResult.data;
|
||||||
|
} else {
|
||||||
|
// 如果API无数据,使用模拟数据
|
||||||
|
this.alarmList = this.mockAlarmList;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取报警统计
|
||||||
|
const statsResult = await getAlarmStatistics();
|
||||||
|
if (statsResult && statsResult.data) {
|
||||||
|
this.alarmStatistics = statsResult.data;
|
||||||
|
} else {
|
||||||
|
// 如果API无数据,使用模拟数据
|
||||||
|
this.alarmStatistics = this.mockStatistics;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载报警数据失败:', error);
|
||||||
|
// 加载失败时使用模拟数据
|
||||||
|
this.alarmList = this.mockAlarmList;
|
||||||
|
this.alarmStatistics = this.mockStatistics;
|
||||||
|
} finally {
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算水质超标数量
|
||||||
|
getWaterQualityAlarmCount(): number {
|
||||||
|
const keywords = ['pH', '氨氮', 'COD', '电导率', '水温', '余氯', '溶解氧'];
|
||||||
|
let count = 0;
|
||||||
|
this.alarmList.forEach((alarm: AlarmItemInterface) => {
|
||||||
|
if (alarm.msg && keywords.some(keyword => alarm.msg.includes(keyword))) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化时间显示
|
||||||
|
formatTime(createTime: string): string {
|
||||||
|
if (!createTime) return '未知时间';
|
||||||
|
if (createTime.includes('-')) {
|
||||||
|
const parts = createTime.split(' ');
|
||||||
|
if (parts.length >= 2) {
|
||||||
|
const datePart = parts[0].substring(5);
|
||||||
|
const timePart = parts[1].substring(0, 5);
|
||||||
|
return `${datePart} ${timePart}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return createTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取趋势图标
|
||||||
|
getTrendIcon(trend: string): string {
|
||||||
|
if (trend === '1') return '↑';
|
||||||
|
if (trend === '0') return '↓';
|
||||||
|
return '=';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取趋势颜色
|
||||||
|
getTrendColor(trend: string): string {
|
||||||
|
if (trend === '1') return '#FF4D4F';
|
||||||
|
if (trend === '0') return '#52C41A';
|
||||||
|
return '#FFFFFF';
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
Stack({ alignContent: Alignment.TopStart }) {
|
||||||
|
// 科技蓝渐变背景
|
||||||
|
Column()
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.linearGradient({
|
||||||
|
angle: 180,
|
||||||
|
colors: [['#0a1628', 0], ['#0d2847', 0.4], ['#1677FF', 1]]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 内容区域
|
||||||
|
Column({ space: 16 }) {
|
||||||
|
// 标题区域
|
||||||
|
Row() {
|
||||||
|
Text("设备报警")
|
||||||
|
.fontSize(18)
|
||||||
|
.fontWeight(600)
|
||||||
|
.fontColor('#FFFFFF')
|
||||||
|
|
||||||
|
Blank()
|
||||||
|
|
||||||
|
Row({ space: 6 }) {
|
||||||
|
Circle()
|
||||||
|
.width(8)
|
||||||
|
.height(8)
|
||||||
|
.fill(this.primaryColor)
|
||||||
|
Text(`${this.alarmList.length} 条报警`)
|
||||||
|
.fontSize(11)
|
||||||
|
.fontColor('rgba(255,255,255,0.7)')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
.padding({ top: 16, bottom: 8 })
|
||||||
|
|
||||||
|
// ===== 报警数量统计卡片 =====
|
||||||
|
Row({ space: 10 }) {
|
||||||
|
// 总报警数量
|
||||||
|
Column({ space: 6 }) {
|
||||||
|
Row({ space: 6 }) {
|
||||||
|
Image($rawfile('Equipment/设备管理.png'))
|
||||||
|
.width(16)
|
||||||
|
.height(16)
|
||||||
|
.fillColor('#FFFFFF')
|
||||||
|
Text("报警数量")
|
||||||
|
.fontSize(12)
|
||||||
|
.fontColor('rgba(255,255,255,0.8)')
|
||||||
|
}
|
||||||
|
Text(`${this.alarmList.length}`)
|
||||||
|
.fontSize(28)
|
||||||
|
.fontWeight(700)
|
||||||
|
.fontColor('#FFFFFF')
|
||||||
|
|
||||||
|
}
|
||||||
|
.width('48%')
|
||||||
|
.height(80)
|
||||||
|
.backgroundColor('rgba(22,119,255,0.3)')
|
||||||
|
.borderRadius(12)
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
.borderWidth(1)
|
||||||
|
.borderColor('rgba(255,255,255,0.2)')
|
||||||
|
|
||||||
|
// 水质超标数量
|
||||||
|
Column({ space: 6 }) {
|
||||||
|
Row({ space: 6 }) {
|
||||||
|
Column()
|
||||||
|
.width(16)
|
||||||
|
.height(16)
|
||||||
|
.borderRadius(8)
|
||||||
|
.backgroundColor('#FF4D4F')
|
||||||
|
Text("水质超标")
|
||||||
|
.fontSize(12)
|
||||||
|
.fontColor('rgba(255,255,255,0.8)')
|
||||||
|
}
|
||||||
|
Text(`${this.getWaterQualityAlarmCount()}`)
|
||||||
|
.fontSize(28)
|
||||||
|
.fontWeight(700)
|
||||||
|
.fontColor('#FFFFFF')
|
||||||
|
|
||||||
|
}
|
||||||
|
.width('48%')
|
||||||
|
.height(80)
|
||||||
|
.backgroundColor('rgba(255,77,79,0.2)')
|
||||||
|
.borderRadius(12)
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
.borderWidth(1)
|
||||||
|
.borderColor('rgba(255,77,79,0.3)')
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
|
||||||
|
// ===== 地图区域 =====
|
||||||
|
Column() {
|
||||||
|
Stack({ alignContent: Alignment.Center }) {
|
||||||
|
// 地图背景 - 科技蓝风格
|
||||||
|
Column()
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.linearGradient({
|
||||||
|
angle: 135,
|
||||||
|
colors: [['#1a3a5c', 0], ['#0d2847', 1]]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 模拟地图网格线
|
||||||
|
Column() {
|
||||||
|
ForEach([0, 1, 2, 3, 4], (row: number) => {
|
||||||
|
Row() {
|
||||||
|
ForEach([0, 1, 2, 3, 4, 5], (col: number) => {
|
||||||
|
Column()
|
||||||
|
.width('16%')
|
||||||
|
.height('20%')
|
||||||
|
.borderWidth(0.5)
|
||||||
|
.borderColor('rgba(22,119,255,0.2)')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height('20%')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
|
||||||
|
// 设备位置标记点
|
||||||
|
if (this.currentDevice) {
|
||||||
|
Column({ space: 4 }) {
|
||||||
|
Stack({ alignContent: Alignment.Center }) {
|
||||||
|
Column()
|
||||||
|
.width(40)
|
||||||
|
.height(40)
|
||||||
|
.borderRadius(20)
|
||||||
|
.backgroundColor('rgba(22,119,255,0.4)')
|
||||||
|
|
||||||
|
Column()
|
||||||
|
.width(20)
|
||||||
|
.height(20)
|
||||||
|
.borderRadius(10)
|
||||||
|
.backgroundColor(this.primaryColor)
|
||||||
|
|
||||||
|
Column()
|
||||||
|
.width(8)
|
||||||
|
.height(8)
|
||||||
|
.borderRadius(4)
|
||||||
|
.backgroundColor('#FFFFFF')
|
||||||
|
}
|
||||||
|
|
||||||
|
Column() {
|
||||||
|
Text(this.currentDevice.name || '监测点')
|
||||||
|
.fontSize(10)
|
||||||
|
.fontColor('#FFFFFF')
|
||||||
|
.fontWeight(500)
|
||||||
|
}
|
||||||
|
.backgroundColor('rgba(22,119,255,0.8)')
|
||||||
|
.borderRadius(4)
|
||||||
|
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
|
||||||
|
}
|
||||||
|
.position({ x: '45%', y: '40%' })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 坐标信息
|
||||||
|
Column() {
|
||||||
|
Row({ space: 8 }) {
|
||||||
|
Image($rawfile('Equipment/设备管理.png'))
|
||||||
|
.width(14)
|
||||||
|
.height(14)
|
||||||
|
.fillColor('#FFFFFF')
|
||||||
|
if (this.currentDevice) {
|
||||||
|
Text(`坐标: ${this.currentDevice.latitudeLongitude || '未设置'}`)
|
||||||
|
.fontSize(11)
|
||||||
|
.fontColor('rgba(255,255,255,0.7)')
|
||||||
|
} else {
|
||||||
|
Text('坐标: 未获取')
|
||||||
|
.fontSize(11)
|
||||||
|
.fontColor('rgba(255,255,255,0.7)')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.position({ x: 12, y: 12 })
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
.height('28%')
|
||||||
|
.borderRadius(12)
|
||||||
|
.clip(true)
|
||||||
|
.borderWidth(1)
|
||||||
|
.borderColor('rgba(22,119,255,0.3)')
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
|
||||||
|
// ===== 时间段统计 =====
|
||||||
|
if (this.alarmStatistics) {
|
||||||
|
Row({ space: 10 }) {
|
||||||
|
this.StatisticsCard('今日', this.alarmStatistics.todayCount,
|
||||||
|
this.alarmStatistics.todayTrend, this.alarmStatistics.todayPercentage)
|
||||||
|
this.StatisticsCard('本周', this.alarmStatistics.weekCount,
|
||||||
|
this.alarmStatistics.weekTrend, this.alarmStatistics.weekPercentage)
|
||||||
|
this.StatisticsCard('本月', this.alarmStatistics.monthCount,
|
||||||
|
this.alarmStatistics.monthTrend, this.alarmStatistics.monthPercentage)
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 报警列表 =====
|
||||||
|
Column() {
|
||||||
|
Row() {
|
||||||
|
Text("报警记录")
|
||||||
|
.fontSize(14)
|
||||||
|
.fontWeight(500)
|
||||||
|
.fontColor('#333333')
|
||||||
|
|
||||||
|
Blank()
|
||||||
|
|
||||||
|
Text(`共${this.alarmList.length}条`)
|
||||||
|
.fontSize(12)
|
||||||
|
.fontColor('#999999')
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.padding({ left: 14, right: 14, top: 12, bottom: 8 })
|
||||||
|
|
||||||
|
if (this.isLoading) {
|
||||||
|
Column() {
|
||||||
|
Text("加载中...")
|
||||||
|
.fontSize(14)
|
||||||
|
.fontColor('#999999')
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height(150)
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
} else if (this.alarmList.length === 0) {
|
||||||
|
Column() {
|
||||||
|
Image($rawfile('Equipment/设备管理.png'))
|
||||||
|
.width(40)
|
||||||
|
.height(40)
|
||||||
|
.fillColor('#CCCCCC')
|
||||||
|
Text("暂无报警记录")
|
||||||
|
.fontSize(14)
|
||||||
|
.fontColor('#999999')
|
||||||
|
.margin({ top: 8 })
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height(150)
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
} else {
|
||||||
|
Scroll() {
|
||||||
|
Column({ space: 12 }) {
|
||||||
|
ForEach(this.alarmList, (alarm: AlarmItemInterface, index: number) => {
|
||||||
|
this.AlarmCard(alarm, index)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.padding({ left: 14, right: 14, top: 8, bottom: 16 })
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.layoutWeight(1)
|
||||||
|
.scrollBar(BarState.Auto)
|
||||||
|
.edgeEffect(EdgeEffect.Spring)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
.layoutWeight(1)
|
||||||
|
.backgroundColor('#FFFFFF')
|
||||||
|
.borderRadius({ topLeft: 12, topRight: 12 })
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.justifyContent(FlexAlign.Start)
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
}
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
StatisticsCard(title: string, count: string, trend: string, percentage: string) {
|
||||||
|
Column({ space: 6 }) {
|
||||||
|
Text(title)
|
||||||
|
.fontSize(11)
|
||||||
|
.fontColor('rgba(255,255,255,0.7)')
|
||||||
|
|
||||||
|
Row({ space: 4 }) {
|
||||||
|
Text(count)
|
||||||
|
.fontSize(18)
|
||||||
|
.fontWeight(600)
|
||||||
|
.fontColor('#FFFFFF')
|
||||||
|
|
||||||
|
Text(this.getTrendIcon(trend))
|
||||||
|
.fontSize(12)
|
||||||
|
.fontColor(this.getTrendColor(trend))
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(`${percentage}%`)
|
||||||
|
.fontSize(10)
|
||||||
|
.fontColor('rgba(255,255,255,0.5)')
|
||||||
|
}
|
||||||
|
.width('31%')
|
||||||
|
.height(60)
|
||||||
|
.backgroundColor('rgba(255,255,255,0.1)')
|
||||||
|
.borderRadius(8)
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
.borderWidth(1)
|
||||||
|
.borderColor('rgba(255,255,255,0.2)')
|
||||||
|
}
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
AlarmCard(alarm: AlarmItemInterface, index: number) {
|
||||||
|
Column({ space: 10 }) {
|
||||||
|
Row() {
|
||||||
|
Row({ space: 4 }) {
|
||||||
|
Image($rawfile('Equipment/设备管理.png'))
|
||||||
|
.width(12)
|
||||||
|
.height(12)
|
||||||
|
.fillColor(this.primaryColor)
|
||||||
|
Text(alarm.code || '未知设备')
|
||||||
|
.fontSize(12)
|
||||||
|
.fontWeight(500)
|
||||||
|
.fontColor(this.primaryColor)
|
||||||
|
}
|
||||||
|
.backgroundColor('#E6F4FF')
|
||||||
|
.borderRadius(4)
|
||||||
|
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
|
||||||
|
|
||||||
|
Blank()
|
||||||
|
|
||||||
|
Row({ space: 4 }) {
|
||||||
|
Text("掉线时间:")
|
||||||
|
.fontSize(11)
|
||||||
|
.fontColor('#999999')
|
||||||
|
Text(this.formatTime(alarm.createTime))
|
||||||
|
.fontSize(11)
|
||||||
|
.fontWeight(500)
|
||||||
|
.fontColor('#666666')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
|
||||||
|
Column({ space: 6 }) {
|
||||||
|
Row({ space: 8 }) {
|
||||||
|
Text("水质情况:")
|
||||||
|
.fontSize(12)
|
||||||
|
.fontColor('#333333')
|
||||||
|
|
||||||
|
Blank()
|
||||||
|
|
||||||
|
Text(alarm.msg || '异常')
|
||||||
|
.fontSize(13)
|
||||||
|
.fontWeight(500)
|
||||||
|
.fontColor('#FF4D4F')
|
||||||
|
.maxLines(1)
|
||||||
|
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
|
||||||
|
Row({ space: 12 }) {
|
||||||
|
Column({ space: 4 }) {
|
||||||
|
Text("当前值")
|
||||||
|
.fontSize(10)
|
||||||
|
.fontColor('#999999')
|
||||||
|
Text(alarm.value || '-')
|
||||||
|
.fontSize(14)
|
||||||
|
.fontWeight(600)
|
||||||
|
.fontColor('#FF4D4F')
|
||||||
|
}
|
||||||
|
.alignItems(HorizontalAlign.Start)
|
||||||
|
|
||||||
|
Column()
|
||||||
|
.width(1)
|
||||||
|
.height(30)
|
||||||
|
.backgroundColor('#E5E5E5')
|
||||||
|
|
||||||
|
Column({ space: 4 }) {
|
||||||
|
Text("标准值")
|
||||||
|
.fontSize(10)
|
||||||
|
.fontColor('#999999')
|
||||||
|
Text(alarm.standard || '-')
|
||||||
|
.fontSize(14)
|
||||||
|
.fontWeight(600)
|
||||||
|
.fontColor('#52C41A')
|
||||||
|
}
|
||||||
|
.alignItems(HorizontalAlign.Start)
|
||||||
|
|
||||||
|
Blank()
|
||||||
|
|
||||||
|
Text(`#${index + 1}`)
|
||||||
|
.fontSize(10)
|
||||||
|
.fontColor('#CCCCCC')
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.backgroundColor('#F5F5F5')
|
||||||
|
.borderRadius(8)
|
||||||
|
.padding(12)
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.backgroundColor('#FFFFFF')
|
||||||
|
.borderRadius(12)
|
||||||
|
.padding(14)
|
||||||
|
.borderWidth(1)
|
||||||
|
.borderColor('rgba(22,119,255,0.15)')
|
||||||
|
.shadow({
|
||||||
|
radius: 4,
|
||||||
|
color: 'rgba(0,0,0,0.05)',
|
||||||
|
offsetX: 0,
|
||||||
|
offsetY: 2
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
import { router } from "@kit.ArkUI";
|
||||||
|
import DeviceListInterface from "../model/DeviceListInterface";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备页面组件 - 科技蓝风格
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
export struct Equipment {
|
||||||
|
// 设备列表数据
|
||||||
|
@State deviceList: DeviceListInterface[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '长江监测点A',
|
||||||
|
code: 'DEVICE001',
|
||||||
|
latitudeLongitude: '30.5,114.3',
|
||||||
|
screenPosition: '',
|
||||||
|
status: 1 // 正常
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: '长江监测点B',
|
||||||
|
code: 'DEVICE002',
|
||||||
|
latitudeLongitude: '31.2,115.1',
|
||||||
|
screenPosition: '',
|
||||||
|
status: 0 // 异常
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
aboutToAppear(): void {
|
||||||
|
// 从AppStorage获取新添加的设备
|
||||||
|
const newDevice = AppStorage.get<DeviceListInterface>('newDevice');
|
||||||
|
if (newDevice) {
|
||||||
|
// 添加到设备列表
|
||||||
|
this.deviceList = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '长江监测点A',
|
||||||
|
code: 'DEVICE001',
|
||||||
|
latitudeLongitude: '30.5,114.3',
|
||||||
|
screenPosition: '',
|
||||||
|
status: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: '长江监测点B',
|
||||||
|
code: 'DEVICE002',
|
||||||
|
latitudeLongitude: '31.2,115.1',
|
||||||
|
screenPosition: '',
|
||||||
|
status: 0
|
||||||
|
},
|
||||||
|
newDevice
|
||||||
|
];
|
||||||
|
// 清除临时存储
|
||||||
|
AppStorage.delete('newDevice');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
Column() {
|
||||||
|
// 顶部标题栏 - 科技蓝风格
|
||||||
|
Row() {
|
||||||
|
Image($rawfile("Equipment/设备管理.png"))
|
||||||
|
.width(20)
|
||||||
|
.height(20)
|
||||||
|
.fillColor('#FFFFFF')
|
||||||
|
.margin({ left: 20, right: 10 })
|
||||||
|
Text("设备管理")
|
||||||
|
.fontSize(20)
|
||||||
|
.fontWeight(600)
|
||||||
|
.fontColor("#FFFFFF")
|
||||||
|
|
||||||
|
Blank()
|
||||||
|
|
||||||
|
Row({ space: 6 }) {
|
||||||
|
Circle()
|
||||||
|
.width(8)
|
||||||
|
.height(8)
|
||||||
|
.fill('#00FF88')
|
||||||
|
Text(`${this.deviceList.length} 台`)
|
||||||
|
.fontSize(12)
|
||||||
|
.fontColor('rgba(255,255,255,0.7)')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.width("100%")
|
||||||
|
.height(60)
|
||||||
|
.padding({ left: 10, right: 20 })
|
||||||
|
.alignItems(VerticalAlign.Center)
|
||||||
|
|
||||||
|
// 设备列表区域
|
||||||
|
Column({ space: 12 }) {
|
||||||
|
ForEach(this.deviceList, (device: DeviceListInterface) => {
|
||||||
|
Row() {
|
||||||
|
Column() {
|
||||||
|
Row({ space: 10 }) {
|
||||||
|
Text(device.name)
|
||||||
|
.fontSize(16)
|
||||||
|
.fontWeight(FontWeight.Bold)
|
||||||
|
.fontColor('#FFFFFF')
|
||||||
|
Text(device.code)
|
||||||
|
.fontSize(12)
|
||||||
|
.fontColor('rgba(255,255,255,0.6)')
|
||||||
|
}
|
||||||
|
Row({ space: 6 }) {
|
||||||
|
Circle()
|
||||||
|
.width(6)
|
||||||
|
.height(6)
|
||||||
|
.fill(device.status === 1 ? '#00FF88' : '#FF6B6B')
|
||||||
|
Text(device.status === 1 ? "正常运行中" : "异常报警")
|
||||||
|
.fontColor(device.status === 1 ? '#00FF88' : '#FF6B6B')
|
||||||
|
.fontSize(12)
|
||||||
|
}
|
||||||
|
.margin({ top: 8 })
|
||||||
|
}
|
||||||
|
.alignItems(HorizontalAlign.Start)
|
||||||
|
.margin({ left: 16 })
|
||||||
|
|
||||||
|
Blank()
|
||||||
|
|
||||||
|
Image($rawfile("Equipment/右括号.png"))
|
||||||
|
.width(18)
|
||||||
|
.height(18)
|
||||||
|
.fillColor('rgba(255,255,255,0.5)')
|
||||||
|
.margin({ right: 16 })
|
||||||
|
}
|
||||||
|
.width("92%")
|
||||||
|
.height(80)
|
||||||
|
.backgroundColor('rgba(22,119,255,0.15)')
|
||||||
|
.borderRadius(12)
|
||||||
|
.border({ width: 1, color: 'rgba(22,119,255,0.3)' })
|
||||||
|
.justifyContent(FlexAlign.SpaceBetween)
|
||||||
|
.alignItems(VerticalAlign.Center)
|
||||||
|
.onClick(() => {
|
||||||
|
// 存储当前设备信息供详情页使用(使用point作为key,与ApiService保持一致)
|
||||||
|
AppStorage.setOrCreate('point', device);
|
||||||
|
router.pushUrl({ url: 'pages/Details' });
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.width("100%")
|
||||||
|
.layoutWeight(1)
|
||||||
|
.padding({ top: 16 })
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.linearGradient({
|
||||||
|
angle: 180,
|
||||||
|
colors: [['#0a1628', 0], ['#0d2847', 0.5], ['#1677FF', 1]]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,354 @@
|
|||||||
|
import { webview } from '@kit.ArkWeb';
|
||||||
|
import MqttService, { WaterQualityData, SimpleEmitterUtil } from '../utils/MqttService';
|
||||||
|
|
||||||
|
// 水质指标数据接口
|
||||||
|
interface IndicatorData {
|
||||||
|
name: string;
|
||||||
|
value: number;
|
||||||
|
unit: string;
|
||||||
|
color: string;
|
||||||
|
bgColor: string;
|
||||||
|
minValue: number;
|
||||||
|
maxValue: number;
|
||||||
|
normalRange: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 水情检测组件
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
export struct WaterMonitor {
|
||||||
|
chartWebviewController: webview.WebviewController = new webview.WebviewController();
|
||||||
|
private mqttService: MqttService = MqttService.getInstance();
|
||||||
|
private refreshTimer: number = -1;
|
||||||
|
private chartUpdateTimer: number = -1;
|
||||||
|
|
||||||
|
// 最新MQTT数据缓冲
|
||||||
|
private latestData: WaterQualityData = {
|
||||||
|
temperature: 0, ph: 0, conductivity: 0,
|
||||||
|
cod: 0, ammonia: 0, chlorine: 0, timestamp: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
// 当前UI显示的水质数据
|
||||||
|
@State currentData: WaterQualityData = {
|
||||||
|
temperature: 25.5, ph: 7.2, conductivity: 850,
|
||||||
|
cod: 28.5, ammonia: 0.35, chlorine: 0.42, timestamp: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
// 连接状态
|
||||||
|
@State isConnected: boolean = false;
|
||||||
|
|
||||||
|
// 是否有真实数据
|
||||||
|
@State hasRealData: boolean = false;
|
||||||
|
|
||||||
|
// WebView是否加载完成
|
||||||
|
@State isWebViewReady: boolean = false;
|
||||||
|
|
||||||
|
// 基本水质指标配置
|
||||||
|
private basicIndicators: IndicatorData[] = [
|
||||||
|
{ name: '水温', value: 0, unit: '°C', color: '#1677FF', bgColor: '#E6F4FF', minValue: 0, maxValue: 40, normalRange: '15-30°C' },
|
||||||
|
{ name: 'pH值', value: 0, unit: '', color: '#52C41A', bgColor: '#F6FFED', minValue: 0, maxValue: 14, normalRange: '6.5-8.5' },
|
||||||
|
{ name: '电导率', value: 0, unit: 'μS/cm', color: '#722ED1', bgColor: '#F9F0FF', minValue: 0, maxValue: 2000, normalRange: '500-1500' },
|
||||||
|
{ name: 'COD', value: 0, unit: 'mg/L', color: '#13C2C2', bgColor: '#E6FFFB', minValue: 0, maxValue: 100, normalRange: '≤40' }
|
||||||
|
];
|
||||||
|
|
||||||
|
aboutToAppear(): void {
|
||||||
|
// 配置MQTT连接参数
|
||||||
|
this.mqttService.setConfig('tcp://192.168.31.100:1883', 'sea_status');
|
||||||
|
|
||||||
|
// 使用SimpleEmitterUtil订阅MQTT消息
|
||||||
|
SimpleEmitterUtil.on((payload: string) => {
|
||||||
|
if (payload) {
|
||||||
|
this.latestData = this.mqttService.parseWaterQualityData(payload);
|
||||||
|
this.isConnected = true;
|
||||||
|
this.hasRealData = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 异步连接MQTT
|
||||||
|
this.mqttService.connect().then(() => {
|
||||||
|
this.isConnected = this.mqttService.getConnectionStatus();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 每4秒刷新一次UI数据
|
||||||
|
this.refreshTimer = setInterval(() => {
|
||||||
|
if (this.hasRealData) {
|
||||||
|
// 使用真实MQTT数据
|
||||||
|
this.currentData = this.latestData;
|
||||||
|
} else {
|
||||||
|
// 使用模拟数据
|
||||||
|
try {
|
||||||
|
|
||||||
|
}catch (e) {
|
||||||
|
console.error("mqtt_err:", JSON.stringify(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 4000);
|
||||||
|
|
||||||
|
// 每5秒更新图表(延迟启动,等待WebView加载)
|
||||||
|
setTimeout(() => {
|
||||||
|
this.startChartUpdate();
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
aboutToDisappear(): void {
|
||||||
|
// 清除定时器
|
||||||
|
if (this.refreshTimer >= 0) {
|
||||||
|
clearInterval(this.refreshTimer);
|
||||||
|
this.refreshTimer = -1;
|
||||||
|
}
|
||||||
|
if (this.chartUpdateTimer >= 0) {
|
||||||
|
clearInterval(this.chartUpdateTimer);
|
||||||
|
this.chartUpdateTimer = -1;
|
||||||
|
}
|
||||||
|
// 取消订阅
|
||||||
|
SimpleEmitterUtil.off();
|
||||||
|
// 断开MQTT
|
||||||
|
this.mqttService.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 启动图表更新定时器
|
||||||
|
startChartUpdate(): void {
|
||||||
|
this.isWebViewReady = true;
|
||||||
|
// 立即更新一次
|
||||||
|
this.updateChartData();
|
||||||
|
|
||||||
|
// 每5秒更新图表
|
||||||
|
this.chartUpdateTimer = setInterval(() => {
|
||||||
|
this.updateChartData();
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateChartData(): void {
|
||||||
|
try {
|
||||||
|
const chlorine: number = this.currentData.chlorine ?? 0;
|
||||||
|
const ammonia: number = this.currentData.ammonia ?? 0;
|
||||||
|
const jsCode: string = `updateData(${chlorine.toFixed(3)}, ${ammonia.toFixed(3)})`;
|
||||||
|
this.chartWebviewController.runJavaScript(jsCode);
|
||||||
|
console.info('图表更新: chlorine=' + chlorine.toFixed(3) + ', ammonia=' + ammonia.toFixed(3));
|
||||||
|
} catch (error) {
|
||||||
|
const errMsg: string = String(error);
|
||||||
|
console.error('更新图表失败: ' + errMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取数值显示
|
||||||
|
getValueByIndex(index: number): string {
|
||||||
|
const temp = this.currentData.temperature ?? 0;
|
||||||
|
const ph = this.currentData.ph ?? 0;
|
||||||
|
const conductivity = this.currentData.conductivity ?? 0;
|
||||||
|
const cod = this.currentData.cod ?? 0;
|
||||||
|
if (index === 0) return temp.toFixed(1);
|
||||||
|
if (index === 1) return ph.toFixed(2);
|
||||||
|
if (index === 2) return conductivity.toFixed(0);
|
||||||
|
return cod.toFixed(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
Stack({ alignContent: Alignment.TopStart }) {
|
||||||
|
// 渐变背景(底层)
|
||||||
|
Column()
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.linearGradient({
|
||||||
|
angle: 180,
|
||||||
|
colors: [['#0a1628', 0], ['#0d2847', 0.4], ['#1677FF', 1]]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 内容区域(上层)
|
||||||
|
Scroll() {
|
||||||
|
Column({ space: 16 }) {
|
||||||
|
// 标题区域
|
||||||
|
Row() {
|
||||||
|
Text("水情监测")
|
||||||
|
.fontSize(18)
|
||||||
|
.fontWeight(600)
|
||||||
|
.fontColor('#FFFFFF')
|
||||||
|
|
||||||
|
Blank()
|
||||||
|
|
||||||
|
// 连接状态指示
|
||||||
|
Row({ space: 6 }) {
|
||||||
|
Circle()
|
||||||
|
.width(8)
|
||||||
|
.height(8)
|
||||||
|
.fill(this.hasRealData ? '#52C41A' : '#FAAD14')
|
||||||
|
Text(this.hasRealData ? '实时数据' : '模拟数据')
|
||||||
|
.fontSize(11)
|
||||||
|
.fontColor('rgba(255,255,255,0.7)')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
.padding({ top: 16, bottom: 8 })
|
||||||
|
|
||||||
|
// ===== 基本水质指标 =====
|
||||||
|
Column({ space: 10 }) {
|
||||||
|
Row({ space: 10 }) {
|
||||||
|
this.IndicatorCard(0)
|
||||||
|
this.IndicatorCard(1)
|
||||||
|
}
|
||||||
|
Row({ space: 10 }) {
|
||||||
|
this.IndicatorCard(2)
|
||||||
|
this.IndicatorCard(3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
.padding({ top: 8, bottom: 8 })
|
||||||
|
|
||||||
|
// ===== 营养盐指标 =====
|
||||||
|
Row({ space: 10 }) {
|
||||||
|
// 氨氮
|
||||||
|
Column({ space: 8 }) {
|
||||||
|
Text("氨氮")
|
||||||
|
.fontSize(13)
|
||||||
|
.fontWeight(500)
|
||||||
|
.fontColor('#333333')
|
||||||
|
.width('100%')
|
||||||
|
|
||||||
|
Row({ space: 4 }) {
|
||||||
|
Text((this.currentData.ammonia ?? 0).toFixed(3))
|
||||||
|
.fontSize(24)
|
||||||
|
.fontWeight(600)
|
||||||
|
.fontColor('#FF4D4F')
|
||||||
|
Text("mg/L")
|
||||||
|
.fontSize(12)
|
||||||
|
.fontColor('#999999')
|
||||||
|
}
|
||||||
|
|
||||||
|
Text('正常范围: ≤1.0')
|
||||||
|
.fontSize(10)
|
||||||
|
.fontColor('#999999')
|
||||||
|
}
|
||||||
|
.width('48%')
|
||||||
|
.height(90)
|
||||||
|
.linearGradient({
|
||||||
|
angle: 135,
|
||||||
|
colors: [['#FFFFFF', 0], ['#FFF1F0', 1]]
|
||||||
|
})
|
||||||
|
.borderRadius(12)
|
||||||
|
.padding(12)
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
.borderWidth(1)
|
||||||
|
.borderColor('rgba(255,77,79,0.15)')
|
||||||
|
|
||||||
|
// 余氯
|
||||||
|
Column({ space: 8 }) {
|
||||||
|
Text("余氯")
|
||||||
|
.fontSize(13)
|
||||||
|
.fontWeight(500)
|
||||||
|
.fontColor('#333333')
|
||||||
|
.width('100%')
|
||||||
|
|
||||||
|
Row({ space: 4 }) {
|
||||||
|
Text((this.currentData.chlorine ?? 0).toFixed(3))
|
||||||
|
.fontSize(24)
|
||||||
|
.fontWeight(600)
|
||||||
|
.fontColor('#FA8C16')
|
||||||
|
Text("mg/L")
|
||||||
|
.fontSize(12)
|
||||||
|
.fontColor('#999999')
|
||||||
|
}
|
||||||
|
|
||||||
|
Text('正常范围: 0.3-0.5')
|
||||||
|
.fontSize(10)
|
||||||
|
.fontColor('#999999')
|
||||||
|
}
|
||||||
|
.width('48%')
|
||||||
|
.height(90)
|
||||||
|
.linearGradient({
|
||||||
|
angle: 135,
|
||||||
|
colors: [['#FFFFFF', 0], ['#FFF7E6', 1]]
|
||||||
|
})
|
||||||
|
.borderRadius(12)
|
||||||
|
.padding(12)
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
.borderWidth(1)
|
||||||
|
.borderColor('rgba(250,140,22,0.15)')
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
|
||||||
|
// ===== 变化曲线 =====
|
||||||
|
Column() {
|
||||||
|
Text("实时变化曲线")
|
||||||
|
.fontSize(13)
|
||||||
|
.fontWeight(500)
|
||||||
|
.fontColor('#333333')
|
||||||
|
.width('100%')
|
||||||
|
.padding({ left: 14, top: 12, bottom: 6 })
|
||||||
|
|
||||||
|
Web({ src: $rawfile('chart/line_chart.html'), controller: this.chartWebviewController })
|
||||||
|
.width('100%')
|
||||||
|
.height(220)
|
||||||
|
.backgroundColor('#FFFFFF')
|
||||||
|
.javaScriptAccess(true)
|
||||||
|
.domStorageAccess(true)
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
.backgroundColor('#FFFFFF')
|
||||||
|
.borderRadius(12)
|
||||||
|
.borderWidth(1)
|
||||||
|
.borderColor('rgba(22,119,255,0.15)')
|
||||||
|
|
||||||
|
// 底部提示
|
||||||
|
Row() {
|
||||||
|
Text(this.hasRealData ? 'MQTT实时数据' : '模拟数据展示')
|
||||||
|
.fontSize(11)
|
||||||
|
.fontColor('rgba(255,255,255,0.5)')
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
.margin({ top: 8, bottom: 20 })
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.scrollBar(BarState.Off)
|
||||||
|
.edgeEffect(EdgeEffect.Spring)
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
}
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
IndicatorCard(index: number) {
|
||||||
|
Column({ space: 8 }) {
|
||||||
|
// 名称
|
||||||
|
Text(this.basicIndicators[index].name)
|
||||||
|
.fontSize(13)
|
||||||
|
.fontWeight(500)
|
||||||
|
.fontColor('#333333')
|
||||||
|
.width('100%')
|
||||||
|
|
||||||
|
// 数值
|
||||||
|
Row({ space: 2 }) {
|
||||||
|
Text(this.getValueByIndex(index))
|
||||||
|
.fontSize(24)
|
||||||
|
.fontWeight(600)
|
||||||
|
.fontColor(this.basicIndicators[index].color)
|
||||||
|
Text(this.basicIndicators[index].unit)
|
||||||
|
.fontSize(12)
|
||||||
|
.fontColor('#999999')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 正常范围提示
|
||||||
|
Text('正常范围: ' + this.basicIndicators[index].normalRange)
|
||||||
|
.fontSize(10)
|
||||||
|
.fontColor('#999999')
|
||||||
|
}
|
||||||
|
.width('48%')
|
||||||
|
.height(90)
|
||||||
|
.linearGradient({
|
||||||
|
angle: 135,
|
||||||
|
colors: [['#FFFFFF', 0], ['#E6F4FF', 1]]
|
||||||
|
})
|
||||||
|
.borderRadius(12)
|
||||||
|
.padding(12)
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
.borderWidth(1)
|
||||||
|
.borderColor('rgba(22,119,255,0.15)')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,314 @@
|
|||||||
|
import { router } from '@kit.ArkUI';
|
||||||
|
import { promptAction } from '@kit.ArkUI';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户页面组件 - 科技蓝风格
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
export struct User {
|
||||||
|
@State totalNumber: number = 2;
|
||||||
|
@State bugNumber: number = 1;
|
||||||
|
@State userName: string = '管理员';
|
||||||
|
|
||||||
|
// 退出登录
|
||||||
|
handleLogout(): void {
|
||||||
|
promptAction.showDialog({
|
||||||
|
title: '提示',
|
||||||
|
message: '确定要退出登录吗?',
|
||||||
|
buttons: [
|
||||||
|
{ text: '取消', color: 'rgba(255,255,255,0.6)' },
|
||||||
|
{ text: '确定', color: '#00FF88' }
|
||||||
|
]
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.index === 1) {
|
||||||
|
// 清除登录信息
|
||||||
|
AppStorage.delete('token');
|
||||||
|
promptAction.showToast({ message: '已退出登录' });
|
||||||
|
// 跳转到登录页
|
||||||
|
router.pushUrl({ url: 'pages/Login' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 报告生成
|
||||||
|
handleGenerateReport(): void {
|
||||||
|
promptAction.showToast({ message: '报告生成功能开发中...' });
|
||||||
|
// TODO: 实现报告生成功能
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置
|
||||||
|
handleSettings(): void {
|
||||||
|
promptAction.showToast({ message: '设置功能开发中...' });
|
||||||
|
// TODO: 实现设置功能
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
Column() {
|
||||||
|
// 顶部标题栏 - 科技蓝风格
|
||||||
|
Row() {
|
||||||
|
Image($rawfile("user/个人.png"))
|
||||||
|
.width(20)
|
||||||
|
.height(20)
|
||||||
|
.fillColor('#FFFFFF')
|
||||||
|
.margin({ left: 20, right: 10 })
|
||||||
|
Text("个人中心")
|
||||||
|
.fontSize(20)
|
||||||
|
.fontWeight(600)
|
||||||
|
.fontColor("#FFFFFF")
|
||||||
|
|
||||||
|
Blank()
|
||||||
|
|
||||||
|
Text('用户管理')
|
||||||
|
.fontSize(12)
|
||||||
|
.fontColor('rgba(255,255,255,0.5)')
|
||||||
|
}
|
||||||
|
.width("100%")
|
||||||
|
.height(60)
|
||||||
|
.padding({ left: 10, right: 20 })
|
||||||
|
.alignItems(VerticalAlign.Center)
|
||||||
|
|
||||||
|
Scroll() {
|
||||||
|
Column({ space: 16 }) {
|
||||||
|
// 用户信息卡片
|
||||||
|
Column({ space: 12 }) {
|
||||||
|
// 头像区域
|
||||||
|
Row() {
|
||||||
|
Column() {
|
||||||
|
Image($rawfile("user/个人b.png"))
|
||||||
|
.width(40)
|
||||||
|
.height(40)
|
||||||
|
.fillColor('#FFFFFF')
|
||||||
|
}
|
||||||
|
.width(60)
|
||||||
|
.height(60)
|
||||||
|
.borderRadius(30)
|
||||||
|
.backgroundColor('rgba(22,119,255,0.3)')
|
||||||
|
.border({ width: 1, color: 'rgba(22,119,255,0.4)' })
|
||||||
|
|
||||||
|
Column({ space: 4 }) {
|
||||||
|
Text(this.userName)
|
||||||
|
.fontSize(18)
|
||||||
|
.fontWeight(FontWeight.Bold)
|
||||||
|
.fontColor('#FFFFFF')
|
||||||
|
|
||||||
|
Text("鸿蒙水生态系统 · 专业版")
|
||||||
|
.fontSize(12)
|
||||||
|
.fontColor("rgba(255, 255, 255, 0.6)")
|
||||||
|
}
|
||||||
|
.alignItems(HorizontalAlign.Start)
|
||||||
|
.margin({ left: 12 })
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.justifyContent(FlexAlign.Start)
|
||||||
|
.padding({ left: 16, right: 16, top: 16, bottom: 12 })
|
||||||
|
}
|
||||||
|
.width("92%")
|
||||||
|
.backgroundColor('rgba(22,119,255,0.15)')
|
||||||
|
.borderRadius(12)
|
||||||
|
.border({ width: 1, color: 'rgba(22,119,255,0.3)' })
|
||||||
|
|
||||||
|
// 数据统计卡片
|
||||||
|
Row({ space: 12 }) {
|
||||||
|
// 设备总数
|
||||||
|
Column({ space: 6 }) {
|
||||||
|
Text(this.totalNumber.toString())
|
||||||
|
.fontSize(32)
|
||||||
|
.fontWeight(FontWeight.Bold)
|
||||||
|
.fontColor('#00FF88')
|
||||||
|
Text("设备总数")
|
||||||
|
.fontSize(12)
|
||||||
|
.fontColor("rgba(255,255,255,0.6)")
|
||||||
|
}
|
||||||
|
.width('48%')
|
||||||
|
.height(80)
|
||||||
|
.backgroundColor('rgba(0,255,136,0.1)')
|
||||||
|
.borderRadius(12)
|
||||||
|
.border({ width: 1, color: 'rgba(0,255,136,0.3)' })
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
|
||||||
|
// 异常设备
|
||||||
|
Column({ space: 6 }) {
|
||||||
|
Text(this.bugNumber.toString())
|
||||||
|
.fontSize(32)
|
||||||
|
.fontWeight(FontWeight.Bold)
|
||||||
|
.fontColor("#FF6B6B")
|
||||||
|
Text("异常设备")
|
||||||
|
.fontSize(12)
|
||||||
|
.fontColor("rgba(255,255,255,0.6)")
|
||||||
|
}
|
||||||
|
.width('48%')
|
||||||
|
.height(80)
|
||||||
|
.backgroundColor('rgba(255,107,107,0.1)')
|
||||||
|
.borderRadius(12)
|
||||||
|
.border({ width: 1, color: 'rgba(255,107,107,0.3)' })
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
|
||||||
|
// 功能菜单区域
|
||||||
|
Column({ space: 10 }) {
|
||||||
|
// 报告生成
|
||||||
|
Row() {
|
||||||
|
Row({ space: 12 }) {
|
||||||
|
Column() {
|
||||||
|
Image($rawfile("user/报告.png"))
|
||||||
|
.width(20)
|
||||||
|
.height(20)
|
||||||
|
.fillColor('#00FF88')
|
||||||
|
}
|
||||||
|
.width(40)
|
||||||
|
.height(40)
|
||||||
|
.borderRadius(10)
|
||||||
|
.backgroundColor('rgba(0,255,136,0.15)')
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
|
||||||
|
Column({ space: 2 }) {
|
||||||
|
Text("报告生成")
|
||||||
|
.fontSize(14)
|
||||||
|
.fontWeight(FontWeight.Bold)
|
||||||
|
.fontColor('#FFFFFF')
|
||||||
|
Text("生成设备运行报告")
|
||||||
|
.fontSize(11)
|
||||||
|
.fontColor('rgba(255,255,255,0.5)')
|
||||||
|
}
|
||||||
|
.alignItems(HorizontalAlign.Start)
|
||||||
|
}
|
||||||
|
|
||||||
|
Image($rawfile("user/右箭头.png"))
|
||||||
|
.width(16)
|
||||||
|
.height(16)
|
||||||
|
.fillColor('rgba(255,255,255,0.4)')
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height(60)
|
||||||
|
.backgroundColor('rgba(22,119,255,0.15)')
|
||||||
|
.borderRadius(12)
|
||||||
|
.border({ width: 1, color: 'rgba(22,119,255,0.25)' })
|
||||||
|
.padding({ left: 12, right: 12 })
|
||||||
|
.justifyContent(FlexAlign.SpaceBetween)
|
||||||
|
.alignItems(VerticalAlign.Center)
|
||||||
|
.onClick(() => {
|
||||||
|
this.handleGenerateReport();
|
||||||
|
})
|
||||||
|
|
||||||
|
// 设置
|
||||||
|
Row() {
|
||||||
|
Row({ space: 12 }) {
|
||||||
|
Column() {
|
||||||
|
Image($rawfile("user/设置.png"))
|
||||||
|
.width(20)
|
||||||
|
.height(20)
|
||||||
|
.fillColor('#4ECDC4')
|
||||||
|
}
|
||||||
|
.width(40)
|
||||||
|
.height(40)
|
||||||
|
.borderRadius(10)
|
||||||
|
.backgroundColor('rgba(78,205,196,0.15)')
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
|
||||||
|
Column({ space: 2 }) {
|
||||||
|
Text("系统设置")
|
||||||
|
.fontSize(14)
|
||||||
|
.fontWeight(FontWeight.Bold)
|
||||||
|
.fontColor('#FFFFFF')
|
||||||
|
Text("个性化配置与偏好")
|
||||||
|
.fontSize(11)
|
||||||
|
.fontColor('rgba(255,255,255,0.5)')
|
||||||
|
}
|
||||||
|
.alignItems(HorizontalAlign.Start)
|
||||||
|
}
|
||||||
|
|
||||||
|
Image($rawfile("user/右箭头.png"))
|
||||||
|
.width(16)
|
||||||
|
.height(16)
|
||||||
|
.fillColor('rgba(255,255,255,0.4)')
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height(60)
|
||||||
|
.backgroundColor('rgba(22,119,255,0.15)')
|
||||||
|
.borderRadius(12)
|
||||||
|
.border({ width: 1, color: 'rgba(22,119,255,0.25)' })
|
||||||
|
.padding({ left: 12, right: 12 })
|
||||||
|
.justifyContent(FlexAlign.SpaceBetween)
|
||||||
|
.alignItems(VerticalAlign.Center)
|
||||||
|
.onClick(() => {
|
||||||
|
this.handleSettings();
|
||||||
|
})
|
||||||
|
|
||||||
|
// 退出登录
|
||||||
|
Row() {
|
||||||
|
Row({ space: 12 }) {
|
||||||
|
Column() {
|
||||||
|
Image($rawfile("user/退出.png"))
|
||||||
|
.width(20)
|
||||||
|
.height(20)
|
||||||
|
.fillColor('#FF6B6B')
|
||||||
|
}
|
||||||
|
.width(40)
|
||||||
|
.height(40)
|
||||||
|
.borderRadius(10)
|
||||||
|
.backgroundColor('rgba(255,107,107,0.15)')
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
|
||||||
|
Column({ space: 2 }) {
|
||||||
|
Text("退出登录")
|
||||||
|
.fontSize(14)
|
||||||
|
.fontWeight(FontWeight.Bold)
|
||||||
|
.fontColor('#FFFFFF')
|
||||||
|
Text("切换账号或重新登录")
|
||||||
|
.fontSize(11)
|
||||||
|
.fontColor('rgba(255,255,255,0.5)')
|
||||||
|
}
|
||||||
|
.alignItems(HorizontalAlign.Start)
|
||||||
|
}
|
||||||
|
|
||||||
|
Image($rawfile("user/右箭头.png"))
|
||||||
|
.width(16)
|
||||||
|
.height(16)
|
||||||
|
.fillColor('rgba(255,255,255,0.4)')
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height(60)
|
||||||
|
.backgroundColor('rgba(255,107,107,0.1)')
|
||||||
|
.borderRadius(12)
|
||||||
|
.border({ width: 1, color: 'rgba(255,107,107,0.25)' })
|
||||||
|
.padding({ left: 12, right: 12 })
|
||||||
|
.justifyContent(FlexAlign.SpaceBetween)
|
||||||
|
.alignItems(VerticalAlign.Center)
|
||||||
|
.onClick(() => {
|
||||||
|
this.handleLogout();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
|
||||||
|
// 版本信息
|
||||||
|
Column() {
|
||||||
|
Text("版本 v1.0.0")
|
||||||
|
.fontSize(12)
|
||||||
|
.fontColor('rgba(255,255,255,0.4)')
|
||||||
|
Text("© 2024 鸿蒙水生态管理系统")
|
||||||
|
.fontSize(11)
|
||||||
|
.fontColor('rgba(255,255,255,0.3)')
|
||||||
|
.margin({ top: 4 })
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
.margin({ top: 16, bottom: 20 })
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.layoutWeight(1)
|
||||||
|
.scrollBar(BarState.Off)
|
||||||
|
.edgeEffect(EdgeEffect.Spring)
|
||||||
|
.padding({ top: 12 })
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.linearGradient({
|
||||||
|
angle: 180,
|
||||||
|
colors: [['#0a1628', 0], ['#0d2847', 0.5], ['#1677FF', 1]]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
|
||||||
|
import { hilog } from '@kit.PerformanceAnalysisKit';
|
||||||
|
import { window } from '@kit.ArkUI';
|
||||||
|
|
||||||
|
const DOMAIN = 0x0000;
|
||||||
|
|
||||||
|
export default class EntryAbility extends UIAbility {
|
||||||
|
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
|
||||||
|
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
|
||||||
|
}
|
||||||
|
|
||||||
|
onDestroy(): void {
|
||||||
|
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy');
|
||||||
|
}
|
||||||
|
|
||||||
|
onWindowStageCreate(windowStage: window.WindowStage): void {
|
||||||
|
// Main window is created, set main page for this ability
|
||||||
|
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
|
||||||
|
|
||||||
|
windowStage.loadContent('pages/Login', (err) => {
|
||||||
|
if (err.code) {
|
||||||
|
hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onWindowStageDestroy(): void {
|
||||||
|
// Main window is destroyed, release UI related resources
|
||||||
|
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
|
||||||
|
}
|
||||||
|
|
||||||
|
onForeground(): void {
|
||||||
|
// Ability has brought to foreground
|
||||||
|
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground');
|
||||||
|
}
|
||||||
|
|
||||||
|
onBackground(): void {
|
||||||
|
// Ability has back to background
|
||||||
|
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* AI请求消息接口
|
||||||
|
*/
|
||||||
|
export default interface AiMessageInterface {
|
||||||
|
role: string; // 角色: user 或 assistant
|
||||||
|
content: string; // 消息内容
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
export default interface AlarmItemInterface{
|
||||||
|
deviceId:number;
|
||||||
|
id:number;
|
||||||
|
msg:string;
|
||||||
|
value:string;
|
||||||
|
standard:string;
|
||||||
|
coordinate:string;
|
||||||
|
screenPosition:string;
|
||||||
|
createTime:string;
|
||||||
|
name:string;
|
||||||
|
code:string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
export default interface AlarmStatisticsInterface {
|
||||||
|
todayCount: string, //今日异常数量
|
||||||
|
todayTrend: string, //0 下降 1上升 2 等于
|
||||||
|
todayPercentage: string, //比率
|
||||||
|
weekCount: string, //本周异常数量
|
||||||
|
weekTrend: string, //0 下降 1上升 2 等于
|
||||||
|
weekPercentage: string, //比率
|
||||||
|
monthCount: string, //本月异常数量
|
||||||
|
monthTrend: string, //0 下降 1上升 2 等于
|
||||||
|
monthPercentage: string //比率
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
export default interface AlarmItemInterface{
|
||||||
|
deviceId: number;
|
||||||
|
id:number;
|
||||||
|
msg:string;
|
||||||
|
value:string;
|
||||||
|
standard:string;
|
||||||
|
coordinate:string;
|
||||||
|
screenPosition:string;
|
||||||
|
createTime:string;
|
||||||
|
name:string;
|
||||||
|
code:string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export default interface CarbonInfoInterface {
|
||||||
|
carbonSequestration: number
|
||||||
|
ph: number
|
||||||
|
waterTemperature: number
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export default interface DeviceListInterface{
|
||||||
|
id:number;
|
||||||
|
latitudeLongitude:string;
|
||||||
|
name:string;
|
||||||
|
screenPosition:string;
|
||||||
|
status:number
|
||||||
|
code:string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Dify API请求体接口
|
||||||
|
*/
|
||||||
|
export default interface DifyRequestBody {
|
||||||
|
inputs: Record<string, string>;
|
||||||
|
query: string;
|
||||||
|
response_mode: string;
|
||||||
|
conversation_id: string;
|
||||||
|
user: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
export default interface EutrophicationItemInterface{
|
||||||
|
//水温
|
||||||
|
waterTemperature:number
|
||||||
|
//cod
|
||||||
|
cod:number
|
||||||
|
//ph
|
||||||
|
ph:number
|
||||||
|
//余氯
|
||||||
|
residualChlorine:number
|
||||||
|
//氨氮
|
||||||
|
ammoniaNitrogen:number
|
||||||
|
//时间
|
||||||
|
createTime:string
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export default interface LoginInterface{
|
||||||
|
username:string
|
||||||
|
password:string
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import AiMessageInterface from './AiMessageInterface';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OpenAI API请求体接口
|
||||||
|
*/
|
||||||
|
export default interface OpenAiRequestBody {
|
||||||
|
model: string;
|
||||||
|
messages: AiMessageInterface[];
|
||||||
|
stream: boolean;
|
||||||
|
temperature?: number;
|
||||||
|
max_tokens?: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* AI API响应模型 - 支持Dify和OpenAI两种格式
|
||||||
|
*/
|
||||||
|
export default interface RequestModel {
|
||||||
|
// Dify格式字段
|
||||||
|
answer?: string; // Dify: AI回答内容
|
||||||
|
event?: string; // Dify: 事件类型: message, message_end等
|
||||||
|
conversation_id?: string; // Dify: 会话ID
|
||||||
|
message_id?: string; // Dify: 消息ID
|
||||||
|
task_id?: string; // Dify: 任务ID
|
||||||
|
|
||||||
|
// OpenAI格式字段
|
||||||
|
choices?: ChoiceItem[]; // OpenAI: 选择列表
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OpenAI Choice项
|
||||||
|
*/
|
||||||
|
export interface ChoiceItem {
|
||||||
|
delta?: DeltaItem;
|
||||||
|
finish_reason?: string;
|
||||||
|
index?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OpenAI Delta项
|
||||||
|
*/
|
||||||
|
export interface DeltaItem {
|
||||||
|
content?: string;
|
||||||
|
role?: string;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
export default interface SeaRangeInterface {
|
||||||
|
//水温
|
||||||
|
waterTemperatureBottom: number;
|
||||||
|
waterTemperatureTop: number;
|
||||||
|
//cod
|
||||||
|
codBottom: number;
|
||||||
|
codTop: number;
|
||||||
|
//ph
|
||||||
|
phBottom: number;
|
||||||
|
phTop: number;
|
||||||
|
//余氯
|
||||||
|
residualChlorineBottom:number;
|
||||||
|
residualChlorineTop:number;
|
||||||
|
//电导率
|
||||||
|
conductivityBottom:number
|
||||||
|
conductivityTop:number
|
||||||
|
//氨氮
|
||||||
|
ammoniaNitrogenBottom:number
|
||||||
|
ammoniaNitrogenTop:number
|
||||||
|
//id
|
||||||
|
id?:number
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export default interface StatisticalInterface {
|
||||||
|
date: string
|
||||||
|
type: string
|
||||||
|
value: number
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
export default interface UserInfoInterface {
|
||||||
|
phonenumber: string | undefined, //用户姓名
|
||||||
|
avatar?: string | undefined, //用户头像
|
||||||
|
nickName: string
|
||||||
|
status?: string
|
||||||
|
userName?: string;
|
||||||
|
password?: string;
|
||||||
|
remark?: string | null;
|
||||||
|
userId?: string | undefined
|
||||||
|
editFlag?: boolean | undefined
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
export default interface SeaStatusInterface{
|
||||||
|
temp: number;
|
||||||
|
temp_compare:number;
|
||||||
|
cod:number;
|
||||||
|
cod_compare:number;
|
||||||
|
ph:number;
|
||||||
|
ph_compare:number;
|
||||||
|
residualChlorine:number;
|
||||||
|
residualChlorine_compare:number;
|
||||||
|
conductivity:number;
|
||||||
|
conductivity_compare:number;
|
||||||
|
ammoniaNitrogen:number;
|
||||||
|
ammoniaNitrogen_compare:number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import { WaterMonitor } from '../components/WaterMonitor';
|
||||||
|
import { DeviceAlarm } from '../components/DeviceAlarm';
|
||||||
|
import { AiPredict } from '../components/AiPredict';
|
||||||
|
import { ContrastAnalysis } from '../components/ContrastAnalysis';
|
||||||
|
import { CarbonCalc } from '../components/CarbonCalc';
|
||||||
|
import DeviceListInterface from '../model/DeviceListInterface';
|
||||||
|
|
||||||
|
@Entry
|
||||||
|
@Component
|
||||||
|
struct Details {
|
||||||
|
@State currentIndex: number = 0;
|
||||||
|
@State currentDevice: DeviceListInterface | null = null;
|
||||||
|
|
||||||
|
private navItems: string[] = ['水情检测', '设备报警', 'AI预测', '对比分析', '碳汇计算'];
|
||||||
|
|
||||||
|
aboutToAppear(): void {
|
||||||
|
const device = AppStorage.get<DeviceListInterface>('point');
|
||||||
|
if (device) {
|
||||||
|
this.currentDevice = device;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
Column() {
|
||||||
|
// 内容区域
|
||||||
|
Column() {
|
||||||
|
if (this.currentIndex === 0) {
|
||||||
|
WaterMonitor()
|
||||||
|
} else if (this.currentIndex === 1) {
|
||||||
|
DeviceAlarm()
|
||||||
|
} else if (this.currentIndex === 2) {
|
||||||
|
AiPredict()
|
||||||
|
} else if (this.currentIndex === 3) {
|
||||||
|
ContrastAnalysis()
|
||||||
|
} else {
|
||||||
|
CarbonCalc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.layoutWeight(1)
|
||||||
|
|
||||||
|
// 底部导航 - 科技蓝风格
|
||||||
|
Row() {
|
||||||
|
ForEach(this.navItems, (item: string, index: number) => {
|
||||||
|
Column({ space: 4 }) {
|
||||||
|
Image($rawfile(this.currentIndex === index
|
||||||
|
? `daohang/ic_tab_0${index + 1}_active.png`
|
||||||
|
: `daohang/ic_tab_0${index + 1}.png`))
|
||||||
|
.width(24)
|
||||||
|
.height(24)
|
||||||
|
|
||||||
|
Text(item)
|
||||||
|
.fontSize(11)
|
||||||
|
.fontColor(this.currentIndex === index ? '#FFFFFF' : 'rgba(255,255,255,0.5)')
|
||||||
|
.fontWeight(this.currentIndex === index ? FontWeight.Bold : FontWeight.Normal)
|
||||||
|
}
|
||||||
|
.width('20%')
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
.onClick(() => {
|
||||||
|
this.currentIndex = index;
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height(65)
|
||||||
|
.backgroundColor('rgba(13,40,71,0.95)')
|
||||||
|
.borderWidth({ top: 1 })
|
||||||
|
.borderColor('rgba(22,119,255,0.3)')
|
||||||
|
.justifyContent(FlexAlign.SpaceEvenly)
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.linearGradient({
|
||||||
|
angle: 180,
|
||||||
|
colors: [['#0a1628', 0], ['#0d2847', 0.5], ['#1677FF', 1]]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { Equipment } from '../components/Equipment';
|
||||||
|
import { Add } from '../components/Add';
|
||||||
|
import { User } from '../components/user';
|
||||||
|
|
||||||
|
// 导航项接口
|
||||||
|
interface NavItem {
|
||||||
|
title: string;
|
||||||
|
trueIcon: string;
|
||||||
|
falseIcon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entry
|
||||||
|
@Component
|
||||||
|
struct HomePage {
|
||||||
|
// 当前选中的导航索引,0:设备, 1:添加, 2:我的
|
||||||
|
@State currentIndex: number = 0;
|
||||||
|
|
||||||
|
// 导航项配置
|
||||||
|
private navItems: NavItem[] = [
|
||||||
|
{ title: '设备', trueIcon: 'index_true/设备.png', falseIcon: 'index_false/设备.png' },
|
||||||
|
{ title: '添加', trueIcon: 'index_true/添加.png', falseIcon: 'index_false/添加.png' },
|
||||||
|
{ title: '我的', trueIcon: 'index_true/我的.png', falseIcon: 'index_false/我的.png' }
|
||||||
|
];
|
||||||
|
|
||||||
|
build() {
|
||||||
|
Column() {
|
||||||
|
// 上方内容区域 - 显示对应页面
|
||||||
|
Column() {
|
||||||
|
if (this.currentIndex === 0) {
|
||||||
|
Equipment()
|
||||||
|
} else if (this.currentIndex === 1) {
|
||||||
|
Add()
|
||||||
|
} else {
|
||||||
|
User()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.layoutWeight(1)
|
||||||
|
|
||||||
|
// 底部导航栏 - 科技蓝风格
|
||||||
|
Row() {
|
||||||
|
ForEach(this.navItems, (item: NavItem, index: number) => {
|
||||||
|
Column({ space: 5 }) {
|
||||||
|
Image($rawfile(this.currentIndex === index ? item.trueIcon : item.falseIcon))
|
||||||
|
.width(24)
|
||||||
|
.height(24)
|
||||||
|
.fillColor(this.currentIndex === index ? '#00FF88' : 'rgba(255,255,255,0.5)')
|
||||||
|
|
||||||
|
Text(item.title)
|
||||||
|
.fontSize(12)
|
||||||
|
.fontColor(this.currentIndex === index ? '#00FF88' : 'rgba(255,255,255,0.6)')
|
||||||
|
.fontWeight(this.currentIndex === index ? FontWeight.Bold : FontWeight.Normal)
|
||||||
|
}
|
||||||
|
.width('33.33%')
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
.onClick(() => {
|
||||||
|
this.currentIndex = index;
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height(60)
|
||||||
|
.backgroundColor('rgba(13,40,71,0.95)')
|
||||||
|
.borderWidth({ top: 1 })
|
||||||
|
.borderColor('rgba(22,119,255,0.3)')
|
||||||
|
.justifyContent(FlexAlign.SpaceEvenly)
|
||||||
|
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.linearGradient({
|
||||||
|
angle: 180,
|
||||||
|
colors: [['#0a1628', 0], ['#0d2847', 0.5], ['#1677FF', 1]]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,356 @@
|
|||||||
|
import LoginInterface from "../model/LoginInterface";
|
||||||
|
import UserInfoInterface from "../model/UserInfoInterface";
|
||||||
|
import { login, register } from "../utils/ApiService";
|
||||||
|
import { promptAction, router, Router } from '@kit.ArkUI';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Entry
|
||||||
|
@Component
|
||||||
|
struct Login {
|
||||||
|
@State messageOne: string = '鸿蒙+AI水生态全流程管理系统';
|
||||||
|
@State messageTwo: string = 'HarmonyOS·AI·智慧水生态';
|
||||||
|
@State glowOpacity: number = 0.4;
|
||||||
|
@State waveScale: number = 1;
|
||||||
|
@State isLogin: boolean = true; // true: 登录, false: 注册
|
||||||
|
@State userName: string = '';
|
||||||
|
@State password: string = '';
|
||||||
|
@State name: string = '';
|
||||||
|
@State isAgree: boolean = false;
|
||||||
|
@State isLoading: boolean = false; // 加载状态
|
||||||
|
@State errorMsg: string = ''; // 错误信息
|
||||||
|
|
||||||
|
aboutToAppear() {
|
||||||
|
// 光晕呼吸动画
|
||||||
|
setInterval(() => {
|
||||||
|
this.glowOpacity = 0.3 + Math.sin(Date.now() / 1500) * 0.15;
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
// 波纹缩放动画
|
||||||
|
setInterval(() => {
|
||||||
|
this.waveScale = 1 + Math.sin(Date.now() / 2000) * 0.05;
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试账号配置
|
||||||
|
private testAccount: string = 'test';
|
||||||
|
private testPassword: string = '123456';
|
||||||
|
|
||||||
|
// 登录方法
|
||||||
|
async handleLogin(): Promise<void> {
|
||||||
|
if (!this.userName || !this.password) {
|
||||||
|
promptAction.showToast({ message: '请输入账户和密码' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试账号直接登录(无需网络请求)
|
||||||
|
if (this.userName === this.testAccount && this.password === this.testPassword) {
|
||||||
|
promptAction.showToast({ message: '测试账号登录成功' });
|
||||||
|
AppStorage.setOrCreate('token', 'test_token_12345');
|
||||||
|
router.pushUrl({ url: 'pages/Index' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isLoading = true;
|
||||||
|
const loginData: LoginInterface = {
|
||||||
|
username: this.userName,
|
||||||
|
password: this.password
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await login(loginData);
|
||||||
|
if (response.code === 200) {
|
||||||
|
promptAction.showToast({ message: '登录成功' });
|
||||||
|
// 保存token到AppStorage
|
||||||
|
AppStorage.setOrCreate('token', response.data);
|
||||||
|
// 登录成功后跳转到主页
|
||||||
|
router.pushUrl({ url: 'pages/Index' });
|
||||||
|
} else {
|
||||||
|
promptAction.showToast({ message: response.message || '登录失败' });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
promptAction.showToast({ message: '网络请求失败,请检查网络连接' });
|
||||||
|
console.error('登录错误: ' + JSON.stringify(error));
|
||||||
|
} finally {
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册方法
|
||||||
|
async handleRegister(): Promise<void> {
|
||||||
|
if (!this.userName || !this.password || !this.name) {
|
||||||
|
promptAction.showToast({ message: '请填写完整信息' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isLoading = true;
|
||||||
|
const registerData: UserInfoInterface = {
|
||||||
|
phonenumber: this.userName,
|
||||||
|
password: this.password,
|
||||||
|
nickName: this.name
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await register(registerData);
|
||||||
|
|
||||||
|
if (response.code === 200) {
|
||||||
|
promptAction.showToast({ message: '注册成功,请登录' });
|
||||||
|
this.isLogin = true; // 切换到登录
|
||||||
|
this.name = ''; // 清空姓名
|
||||||
|
} else {
|
||||||
|
promptAction.showToast({ message: response.message || '注册失败' });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
promptAction.showToast({ message: '网络请求失败,请检查网络连接' });
|
||||||
|
console.error('注册错误: ' + JSON.stringify(error));
|
||||||
|
} finally {
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
Stack({ alignContent: Alignment.Center }) {
|
||||||
|
// 主渐变背景 - 铺满全屏
|
||||||
|
Column()
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.linearGradient({
|
||||||
|
angle: 180,
|
||||||
|
colors: [
|
||||||
|
['#001E38', 0],
|
||||||
|
['#0958D9', 0.5],
|
||||||
|
['#1677FF', 0.8],
|
||||||
|
['#4096ff', 1]
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 柔和光晕 - 左上
|
||||||
|
Column()
|
||||||
|
.width(350)
|
||||||
|
.height(350)
|
||||||
|
.borderRadius(175)
|
||||||
|
.linearGradient({
|
||||||
|
angle: 135,
|
||||||
|
colors: [
|
||||||
|
['rgba(64, 150, 255, ' + this.glowOpacity + ')', 0],
|
||||||
|
['rgba(64, 150, 255, 0)', 0.7]
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.position({ x: -100, y: -50 })
|
||||||
|
.blur(100)
|
||||||
|
|
||||||
|
// 柔和光晕 - 右下
|
||||||
|
Column()
|
||||||
|
.width(300)
|
||||||
|
.height(300)
|
||||||
|
.borderRadius(150)
|
||||||
|
.linearGradient({
|
||||||
|
angle: 315,
|
||||||
|
colors: [
|
||||||
|
['rgba(22, 119, 255, ' + (this.glowOpacity * 0.8) + ')', 0],
|
||||||
|
['rgba(22, 119, 255, 0)', 0.6]
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.position({ x: '70%', y: '60%' })
|
||||||
|
.blur(80)
|
||||||
|
|
||||||
|
// 淡淡的波纹圆环
|
||||||
|
ForEach([1, 2], (item: number) => {
|
||||||
|
Column()
|
||||||
|
.width(220 + item * 60)
|
||||||
|
.height(220 + item * 60)
|
||||||
|
.borderRadius(110 + item * 30)
|
||||||
|
.borderWidth(1)
|
||||||
|
.borderColor('rgba(255, 255, 255, ' + (0.1 - item * 0.03) + ')')
|
||||||
|
.backgroundColor('transparent')
|
||||||
|
.scale({ x: this.waveScale, y: this.waveScale })
|
||||||
|
})
|
||||||
|
|
||||||
|
// 主内容 - 使用Column布局,标题在上,登录卡片在下
|
||||||
|
Column() {
|
||||||
|
// 标题区域 - 放在上方
|
||||||
|
Column({ space: 8 }) {
|
||||||
|
Text(this.messageOne)
|
||||||
|
.fontSize(25)
|
||||||
|
.fontWeight(500)
|
||||||
|
.fontWeight(FontWeight.Bold)
|
||||||
|
.fontColor(Color.White)
|
||||||
|
.textAlign(TextAlign.Center)
|
||||||
|
.textShadow({
|
||||||
|
radius: 20,
|
||||||
|
color: 'rgba(64, 150, 255, 0.5)',
|
||||||
|
offsetX: 0,
|
||||||
|
offsetY: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
Text(this.messageTwo)
|
||||||
|
.fontSize(13)
|
||||||
|
.fontColor('rgba(255, 255, 255, 0.75)')
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.padding({ top: 80, bottom: 30 })
|
||||||
|
|
||||||
|
// 登录/注册卡片
|
||||||
|
Column({ space: 15 }) {
|
||||||
|
// 登录/注册切换
|
||||||
|
Row({ space: 50 }) {
|
||||||
|
Text("登录")
|
||||||
|
.fontSize(18)
|
||||||
|
.fontColor(this.isLogin ? '#FFFFFF' : 'rgba(255, 255, 255, 0.5)')
|
||||||
|
.fontWeight(this.isLogin ? FontWeight.Bold : FontWeight.Normal)
|
||||||
|
.onClick(() => {
|
||||||
|
this.isLogin = true;
|
||||||
|
})
|
||||||
|
.fontWeight(800)
|
||||||
|
|
||||||
|
Text("注册")
|
||||||
|
.fontSize(18)
|
||||||
|
.fontColor(!this.isLogin ? '#FFFFFF' : 'rgba(255, 255, 255, 0.5)')
|
||||||
|
.fontWeight(!this.isLogin ? FontWeight.Bold : FontWeight.Normal)
|
||||||
|
.onClick(() => {
|
||||||
|
this.isLogin = false;
|
||||||
|
})
|
||||||
|
.fontWeight(800)
|
||||||
|
}
|
||||||
|
.margin({ top: 5, bottom: 10 })
|
||||||
|
|
||||||
|
// 输入框区域
|
||||||
|
Column({ space: 15 }) {
|
||||||
|
// 账户输入框
|
||||||
|
Row(){
|
||||||
|
Image($rawfile('Login/用户.png'))
|
||||||
|
.width(15)
|
||||||
|
.height(15)
|
||||||
|
.margin({left:20})
|
||||||
|
TextInput({ placeholder: '请输入账户', text: this.userName })
|
||||||
|
.placeholderColor("#FFFFFF")
|
||||||
|
.placeholderFont({ size:12 })
|
||||||
|
.onChange((value: string) => {
|
||||||
|
this.userName = value;
|
||||||
|
})
|
||||||
|
.width('85%')
|
||||||
|
.height(45)
|
||||||
|
.borderRadius(8)
|
||||||
|
.fontColor('#FFFFFF')
|
||||||
|
.padding({ left: 15, right: 15 })
|
||||||
|
.backgroundColor(Color.Transparent)
|
||||||
|
}
|
||||||
|
.backgroundColor('rgba(255, 255, 255, 0.12)')
|
||||||
|
.width(300)
|
||||||
|
.borderRadius(10)
|
||||||
|
|
||||||
|
// 密码输入框
|
||||||
|
Row(){
|
||||||
|
Image($rawfile('Login/锁.png'))
|
||||||
|
.width(15)
|
||||||
|
.height(15)
|
||||||
|
.margin({left:20})
|
||||||
|
TextInput({ placeholder: '请输入密码', text: this.password })
|
||||||
|
.placeholderColor("#FFFFFF")
|
||||||
|
.placeholderFont({ size:12 })
|
||||||
|
.onChange((value: string) => {
|
||||||
|
this.password = value;
|
||||||
|
})
|
||||||
|
.width('85%')
|
||||||
|
.height(45)
|
||||||
|
.borderRadius(8)
|
||||||
|
.fontColor('#FFFFFF')
|
||||||
|
.type(InputType.Password)
|
||||||
|
.padding({ left: 15, right: 15 })
|
||||||
|
.backgroundColor(Color.Transparent)
|
||||||
|
}
|
||||||
|
.backgroundColor('rgba(255, 255, 255, 0.12)')
|
||||||
|
.width(300)
|
||||||
|
.borderRadius(10)
|
||||||
|
|
||||||
|
// 注册时显示姓名输入框
|
||||||
|
if (!this.isLogin) {
|
||||||
|
Row(){
|
||||||
|
Image($rawfile('Login/电话.png'))
|
||||||
|
.width(15)
|
||||||
|
.height(15)
|
||||||
|
.margin({left:20})
|
||||||
|
TextInput({ placeholder: '请输入电话', text: this.name })
|
||||||
|
.placeholderColor("#FFFFFF")
|
||||||
|
.placeholderFont({ size:12 })
|
||||||
|
.onChange((value: string) => {
|
||||||
|
this.name = value;
|
||||||
|
})
|
||||||
|
.width('100%')
|
||||||
|
.height(45)
|
||||||
|
.borderRadius(8)
|
||||||
|
.fontColor('#FFFFFF')
|
||||||
|
.padding({ left: 15, right: 15 })
|
||||||
|
.backgroundColor(Color.Transparent)
|
||||||
|
}
|
||||||
|
.backgroundColor('rgba(255, 255, 255, 0.12)')
|
||||||
|
.width(300)
|
||||||
|
.borderRadius(10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
|
||||||
|
// 协议同意
|
||||||
|
Row({ space: 8 }) {
|
||||||
|
Checkbox()
|
||||||
|
.select(this.isAgree)
|
||||||
|
.selectedColor('#4096ff')
|
||||||
|
.onChange((isChecked: boolean) => {
|
||||||
|
this.isAgree = isChecked;
|
||||||
|
})
|
||||||
|
.width(15)
|
||||||
|
.height(15)
|
||||||
|
Text() {
|
||||||
|
Span('我已阅读并同意')
|
||||||
|
.fontColor('rgba(255, 255, 255, 0.7)')
|
||||||
|
.fontSize(12)
|
||||||
|
Span('《用户协议》')
|
||||||
|
.fontColor('#FFFFFF')
|
||||||
|
.fontSize(12)
|
||||||
|
Span('和')
|
||||||
|
.fontColor('rgba(255, 255, 255, 0.7)')
|
||||||
|
.fontSize(12)
|
||||||
|
Span('《隐私政策》')
|
||||||
|
.fontColor('#FFFFFF')
|
||||||
|
.fontSize(12)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
.margin({ top: 10 , left:-30})
|
||||||
|
|
||||||
|
// 登录/注册按钮
|
||||||
|
Button(this.isLoading ? '处理中...' : (this.isLogin ? '登录' : '注册'))
|
||||||
|
.width('90%')
|
||||||
|
.height(45)
|
||||||
|
.fontSize(16)
|
||||||
|
.fontColor('#1677FF')
|
||||||
|
.fontWeight(FontWeight.Bold)
|
||||||
|
.backgroundColor('#FFFFFF')
|
||||||
|
.borderRadius(15)
|
||||||
|
.enabled(this.isAgree && !this.isLoading)
|
||||||
|
.margin({ top: 15 })
|
||||||
|
.onClick(() => {
|
||||||
|
if (this.isLogin) {
|
||||||
|
this.handleLogin();
|
||||||
|
} else {
|
||||||
|
this.handleRegister();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.padding(25)
|
||||||
|
.borderRadius(16)
|
||||||
|
.margin({top:80})
|
||||||
|
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.justifyContent(FlexAlign.Start)
|
||||||
|
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import { iResponseModel, RequestUtil } from "./RequestUtil";
|
||||||
|
|
||||||
|
import LoginInterface from "../model/LoginInterface";
|
||||||
|
import UserInfoInterface from "../model/UserInfoInterface";
|
||||||
|
import DeviceListInterface from "../model/DeviceListInterface";
|
||||||
|
import CarbonInfoInterface from "../model/CarbonInfoInterface";
|
||||||
|
import StatisticalInterface from "../model/StatisticalInterface";
|
||||||
|
import SeaRangeInterface from "../model/SeaRangeInterface";
|
||||||
|
import EutrophicationItemInterface from "../model/EutrophicationItemInterface";
|
||||||
|
import AlarmItemInterface from "../model/AlarmItemInterface";
|
||||||
|
import AlarmStatisticsInterface from "../model/AlarmStatisticsInterface";
|
||||||
|
|
||||||
|
//登录
|
||||||
|
export function login(cls: LoginInterface): Promise<iResponseModel<string>> {
|
||||||
|
return RequestUtil.post("/app/common/login", cls, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
//注册
|
||||||
|
export function register(cls: UserInfoInterface): Promise<iResponseModel<string>> {
|
||||||
|
return RequestUtil.post("/app/common/register", cls, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
//查询长江设备列表-获取四个监测点的设备
|
||||||
|
export function getDeviceList(): Promise<iResponseModel<DeviceListInterface[]>> {
|
||||||
|
return RequestUtil.get("/sea/device/list");
|
||||||
|
}
|
||||||
|
|
||||||
|
//数据监测-查询长江最近7日数据->折线图(Ai的预测服务也是这个接口)
|
||||||
|
export function getSeaEutrophication(): Promise<iResponseModel<EutrophicationItemInterface[]>> {
|
||||||
|
return RequestUtil.get("/sea/day/getLimit7?code=" + AppStorage.get<DeviceListInterface>("point")?.code);
|
||||||
|
}
|
||||||
|
|
||||||
|
//AI预测-ai问答
|
||||||
|
export const chatUrl: string = "/v1/chat-messages";
|
||||||
|
|
||||||
|
//对比分析-> 通过参数类型查询长江按月统计
|
||||||
|
export function getContrast(paramType: number): Promise<iResponseModel<StatisticalInterface[]>> {
|
||||||
|
return RequestUtil.get("/sea/month/monthDetail?paramType=" + paramType + "&deviceCode=" +
|
||||||
|
AppStorage.get<DeviceListInterface>("point")?.code);
|
||||||
|
}
|
||||||
|
|
||||||
|
//最新异常列表
|
||||||
|
export function getAlarmList(): Promise<iResponseModel<AlarmItemInterface[]>> {
|
||||||
|
return RequestUtil.get("/sea/alarm/list?code=" + AppStorage.get<DeviceListInterface>("point")?.code);
|
||||||
|
}
|
||||||
|
|
||||||
|
//最新异常列表
|
||||||
|
export function getAlarmStatistics(): Promise<iResponseModel<AlarmStatisticsInterface>> {
|
||||||
|
return RequestUtil.get("/sea/alarm/getAlarmStatistics?code=" + AppStorage.get<DeviceListInterface>("point")?.code);
|
||||||
|
}
|
||||||
|
|
||||||
|
//异常趋势
|
||||||
|
export function getAlarmTrend(): Promise<iResponseModel<StatisticalInterface[]>> {
|
||||||
|
return RequestUtil.get("/sea/alarm/getAlarmTrend?code=" + AppStorage.get<DeviceListInterface>("point")?.code);
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取长江数据区间范围值详细信息
|
||||||
|
export function getSeaRange(): Promise<iResponseModel<SeaRangeInterface>> {
|
||||||
|
return RequestUtil.get("/sea/range/1");
|
||||||
|
}
|
||||||
|
|
||||||
|
//修改长江数据区间范围值详细信息
|
||||||
|
export function editSeaRange(cls: SeaRangeInterface): Promise<iResponseModel<SeaRangeInterface>> {
|
||||||
|
cls.id = 1;
|
||||||
|
return RequestUtil.put("/sea/range", cls);
|
||||||
|
}
|
||||||
|
|
||||||
|
//碳汇详情
|
||||||
|
export function getCarbonInfo(): Promise<iResponseModel<CarbonInfoInterface>> {
|
||||||
|
let date = new Date().getFullYear() + "年" + new Date().getMonth().toString().padStart(2, "0");
|
||||||
|
return RequestUtil.get(`/sea/month/query?deviceCode=${AppStorage.get<DeviceListInterface>("point")?.code}&remark=${encodeURIComponent(date)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
//固碳6个月
|
||||||
|
export function getCarbonList(): Promise<iResponseModel<StatisticalInterface[]>> {
|
||||||
|
return RequestUtil.get(`/sea/month/sixMonthStatistics?deviceCode=${AppStorage.get<DeviceListInterface>("point")?.code}`);
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { iResponseModel, RequestUtil } from "./RequestUtil";
|
||||||
|
|
||||||
|
import LoginInterface from "../model/LoginInterface";
|
||||||
|
import UserInfoInterface from "../model/UserInfoInterface";
|
||||||
|
import DeviceListInterface from "../model/DeviceListInterface";
|
||||||
|
import CarbonInfoInterface from "../model/CarbonInfoInterface";
|
||||||
|
import StatisticalInterface from "../model/StatisticalInterface";
|
||||||
|
import SeaRangeInterface from "../model/SeaRangeInterface";
|
||||||
|
import EutrophicationItemInterface from "../model/EutrophicationItemInterface";
|
||||||
|
import AlarmItemInterface from "../model/Alarmltemlnterface";
|
||||||
|
import AlarmStatisticsInterface from "../model/AlarmStatisticsInterface";
|
||||||
|
|
||||||
|
// 登录
|
||||||
|
export function login(cls: LoginInterface): Promise<iResponseModel<string>> {
|
||||||
|
return RequestUtil.post("/app/common/login", cls, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册
|
||||||
|
export function register(cls: UserInfoInterface | LoginInterface): Promise<iResponseModel<string>> {
|
||||||
|
return RequestUtil.post("/app/common/register", cls, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询长江设备列表-获取四个监测点的设备
|
||||||
|
export function getDeviceList(): Promise<iResponseModel<DeviceListInterface[]>> {
|
||||||
|
return RequestUtil.get("/sea/device/list");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据监测-查询长江最近7日数据
|
||||||
|
export function getSeaEutrophication(): Promise<iResponseModel<EutrophicationItemInterface[]>> {
|
||||||
|
const code = AppStorage.get<DeviceListInterface>("point")?.code ?? "";
|
||||||
|
return RequestUtil.get("/sea/day/getLimit7?code=" + code);
|
||||||
|
}
|
||||||
|
|
||||||
|
// AI预测-ai问答
|
||||||
|
export const chatUrl: string = "/v1/chat-messages";
|
||||||
|
|
||||||
|
// 对比分析 -> 通过参数类型查询长江按月统计
|
||||||
|
export function getContrast(paramType: number): Promise<iResponseModel<StatisticalInterface[]>> {
|
||||||
|
const code = AppStorage.get<DeviceListInterface>("point")?.code ?? "";
|
||||||
|
if (!code) {
|
||||||
|
// 这里直接抛错,避免发 deviceCode=undefined 的脏请求
|
||||||
|
return Promise.reject(new Error("deviceCode为空:请先在AppStorage写入point.code"));
|
||||||
|
}
|
||||||
|
return RequestUtil.get(`/sea/month/monthDetail?paramType=${paramType}&deviceCode=${code}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最新异常列表
|
||||||
|
export function getAlarmList(): Promise<iResponseModel<AlarmItemInterface[]>> {
|
||||||
|
return RequestUtil.get("/sea/alarm/list?code=" + (AppStorage.get<DeviceListInterface>("point")?.code ?? ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最新异常统计
|
||||||
|
export function getAlarmStatistics(): Promise<iResponseModel<AlarmStatisticsInterface>> {
|
||||||
|
return RequestUtil.get("/sea/alarm/getAlarmStatistics?code=" + (AppStorage.get<DeviceListInterface>("point")?.code
|
||||||
|
?? ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 异常趋势
|
||||||
|
export function getAlarmTrend(): Promise<iResponseModel<StatisticalInterface[]>> {
|
||||||
|
return RequestUtil.get("/sea/alarm/getAlarmTrend?code=" + (AppStorage.get<DeviceListInterface>("point")?.code ??
|
||||||
|
""));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取长江数据区间范围值详细信息
|
||||||
|
export function getSeaRange(): Promise<iResponseModel<SeaRangeInterface>> {
|
||||||
|
return RequestUtil.get("/sea/range/1");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改长江数据区间范围值详细信息
|
||||||
|
export function editSeaRange(cls: SeaRangeInterface): Promise<iResponseModel<SeaRangeInterface>> {
|
||||||
|
cls.id = 1;
|
||||||
|
return RequestUtil.put("/sea/range", cls);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 碳汇详情
|
||||||
|
export function getCarbonInfo(): Promise<iResponseModel<CarbonInfoInterface>> {
|
||||||
|
const date = new Date().getFullYear() + "年" + new Date().getMonth().toString().padStart(2, "0");
|
||||||
|
const code = AppStorage.get<DeviceListInterface>("point")?.code ?? "";
|
||||||
|
return RequestUtil.get(`/sea/month/query?deviceCode=${code}&remark=${encodeURIComponent(date)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 固碳6个月
|
||||||
|
export function getCarbonList(): Promise<iResponseModel<StatisticalInterface[]>> {
|
||||||
|
const code = AppStorage.get<DeviceListInterface>("point")?.code ?? "";
|
||||||
|
return RequestUtil.get(`/sea/month/sixMonthStatistics?deviceCode=${code}`);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* 配置文件 - 存放服务器IP、端口等变量参数
|
||||||
|
*/
|
||||||
|
export class Config {
|
||||||
|
// 后端服务器IP地址
|
||||||
|
static readonly BASE_URL: string = 'http://192.168.31.100:8080';
|
||||||
|
|
||||||
|
// ==================== AI服务配置 ====================
|
||||||
|
// 主API(Dify格式)
|
||||||
|
static readonly AI_PRIMARY_URL: string = 'http://192.168.31.26';
|
||||||
|
static readonly AI_PRIMARY_PATH: string = '/v1';
|
||||||
|
static readonly AI_PRIMARY_KEY: string = "app-druPEUrX7Qw7kWBv1JUfaG4q"
|
||||||
|
// 备用API(OpenAI格式)- 当主API失败时自动切换
|
||||||
|
static readonly AI_BACKUP_URL: string = 'https://ark.cn-beijing.volces.com';
|
||||||
|
static readonly AI_BACKUP_PATH: string = '/api/v3/chat/completions';
|
||||||
|
static readonly AI_BACKUP_KEY: string = 'ark-7bdaff26-adab-4332-a1f5-f97fb03e7d56-aa3a1'; // 填写你的OpenAI密钥
|
||||||
|
static readonly AI_BACKUP_MODEL: string = 'doubao-seed-2-0-code-preview-260215'; // 使用的模型
|
||||||
|
|
||||||
|
// 请求超时时间(毫秒)
|
||||||
|
static readonly TIMEOUT: number = 30000;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Config;
|
||||||
@@ -0,0 +1,309 @@
|
|||||||
|
/**
|
||||||
|
* MQTT服务 - ArkTS版本
|
||||||
|
* 使用 @ohos/mqtt 原生库实现MQTT连接
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
MqttAsync,
|
||||||
|
MqttClient,
|
||||||
|
MqttMessage,
|
||||||
|
MqttQos,
|
||||||
|
MqttResponse,
|
||||||
|
MqttSubscribeOptions
|
||||||
|
} from '@ohos/mqtt';
|
||||||
|
import emitter from '@ohos.events.emitter';
|
||||||
|
|
||||||
|
// MQTT 事件ID (字符串形式)
|
||||||
|
const MQTT_EVENT_ID: string = 'mqtt_water_quality';
|
||||||
|
|
||||||
|
// 事件数据接口
|
||||||
|
interface PayloadData {
|
||||||
|
payload: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简化的Emitter工具类(替代@pura/harmony-utils的EmitterUtil)
|
||||||
|
export class SimpleEmitterUtil {
|
||||||
|
private static storedCallback: ((payload: string) => void) | undefined = undefined;
|
||||||
|
private static emitterCallback: ((eventData: emitter.EventData) => void) | undefined = undefined;
|
||||||
|
|
||||||
|
// 发送事件
|
||||||
|
static emit(data: string): void {
|
||||||
|
const payloadData: PayloadData = { payload: data };
|
||||||
|
const eventData: emitter.EventData = { data: payloadData };
|
||||||
|
emitter.emit(MQTT_EVENT_ID, eventData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订阅事件
|
||||||
|
static on(callback: (payload: string) => void): void {
|
||||||
|
SimpleEmitterUtil.storedCallback = callback;
|
||||||
|
// 创建符合 emitter.EventData 签名的回调
|
||||||
|
const emitterCallback = (eventData: emitter.EventData): void => {
|
||||||
|
if (eventData && eventData.data) {
|
||||||
|
const payloadData = eventData.data as PayloadData;
|
||||||
|
if (payloadData.payload) {
|
||||||
|
callback(payloadData.payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
SimpleEmitterUtil.emitterCallback = emitterCallback;
|
||||||
|
emitter.on(MQTT_EVENT_ID, emitterCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消订阅
|
||||||
|
static off(): void {
|
||||||
|
if (SimpleEmitterUtil.emitterCallback) {
|
||||||
|
emitter.off(MQTT_EVENT_ID, SimpleEmitterUtil.emitterCallback);
|
||||||
|
SimpleEmitterUtil.storedCallback = undefined;
|
||||||
|
SimpleEmitterUtil.emitterCallback = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MQTT 事件ID导出(兼容旧代码使用数字)
|
||||||
|
export const MQTT_EVENT_ID_NUM: number = 1001;
|
||||||
|
|
||||||
|
// 水质数据接口
|
||||||
|
export interface WaterQualityData {
|
||||||
|
temperature: number;
|
||||||
|
ph: number;
|
||||||
|
conductivity: number;
|
||||||
|
cod: number;
|
||||||
|
ammonia: number;
|
||||||
|
chlorine: number;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MQTT配置接口
|
||||||
|
interface MqttConfig {
|
||||||
|
url: string;
|
||||||
|
clientId: string;
|
||||||
|
userName: string;
|
||||||
|
password: string;
|
||||||
|
topic: string;
|
||||||
|
qos: MqttQos;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MqttService {
|
||||||
|
private static instance: MqttService;
|
||||||
|
private mqttClient: MqttClient | null = null;
|
||||||
|
private config: MqttConfig;
|
||||||
|
private isConnectedFlag: boolean = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.config = {
|
||||||
|
url: 'tcp://192.168.31.100:1883',
|
||||||
|
clientId: 'sea-lwq',
|
||||||
|
userName: 'admin',
|
||||||
|
password: 'admin',
|
||||||
|
topic: 'sea_status',
|
||||||
|
qos: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getInstance(): MqttService {
|
||||||
|
if (!MqttService.instance) {
|
||||||
|
MqttService.instance = new MqttService();
|
||||||
|
}
|
||||||
|
return MqttService.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置配置
|
||||||
|
setConfig(url: string, topic: string): void {
|
||||||
|
this.config.url = url;
|
||||||
|
this.config.topic = topic;
|
||||||
|
console.info('MQTT配置: url=' + url + ', topic=' + topic +
|
||||||
|
', clientId=' + this.config.clientId +
|
||||||
|
', userName=' + this.config.userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化连接
|
||||||
|
async connect(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const status = await this.getMqttConnectStatus();
|
||||||
|
if (status) {
|
||||||
|
console.info('MQTT已经连接,跳过重复连接');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.createMqttClient();
|
||||||
|
await this.connectMqtt();
|
||||||
|
await this.subscribe();
|
||||||
|
this.Arrived();
|
||||||
|
|
||||||
|
console.info('MQTT初始化完成');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('MQTT初始化失败: ' + JSON.stringify(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取连接状态
|
||||||
|
private async getMqttConnectStatus(): Promise<boolean> {
|
||||||
|
if (this.mqttClient) {
|
||||||
|
const result = await this.mqttClient.isConnected();
|
||||||
|
return result === true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建mqtt实例
|
||||||
|
createMqttClient(): void {
|
||||||
|
this.mqttClient = MqttAsync.createMqtt({
|
||||||
|
url: this.config.url,
|
||||||
|
clientId: this.config.clientId,
|
||||||
|
persistenceType: 1
|
||||||
|
});
|
||||||
|
console.info('MQTT客户端实例已创建: url=' + this.config.url + ', clientId=' + this.config.clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 连接服务器
|
||||||
|
async connectMqtt(): Promise<void> {
|
||||||
|
if (!this.mqttClient) {
|
||||||
|
console.error('MQTT客户端未创建');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.info('正在连接MQTT服务器: userName=' + this.config.userName);
|
||||||
|
|
||||||
|
await this.mqttClient.connect({
|
||||||
|
userName: this.config.userName,
|
||||||
|
password: this.config.password,
|
||||||
|
connectTimeout: 30,
|
||||||
|
automaticReconnect: true,
|
||||||
|
MQTTVersion: 3
|
||||||
|
}).then((res: MqttResponse) => {
|
||||||
|
this.isConnectedFlag = true;
|
||||||
|
console.info('MQTT服务器连接成功:' + JSON.stringify(res.message));
|
||||||
|
}).catch((err: Error) => {
|
||||||
|
this.isConnectedFlag = false;
|
||||||
|
console.error('MQTT服务器连接失败:' + JSON.stringify(err));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订阅主题
|
||||||
|
async subscribe(): Promise<void> {
|
||||||
|
if (!this.mqttClient) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscribeOption: MqttSubscribeOptions = {
|
||||||
|
topic: this.config.topic,
|
||||||
|
qos: this.config.qos
|
||||||
|
};
|
||||||
|
|
||||||
|
console.info('正在订阅主题: ' + this.config.topic);
|
||||||
|
|
||||||
|
await this.mqttClient.subscribe(subscribeOption).then((res: MqttResponse) => {
|
||||||
|
console.info('MQTT订阅主题成功:' + JSON.stringify(res.message));
|
||||||
|
}).catch((err: Error) => {
|
||||||
|
console.error('MQTT订阅主题失败:' + JSON.stringify(err));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听消息到达 - 使用EmitterUtil发送到事件总线
|
||||||
|
Arrived(): void {
|
||||||
|
if (!this.mqttClient) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.info('开始监听MQTT消息...');
|
||||||
|
|
||||||
|
this.mqttClient.messageArrived((err: Error, data: MqttMessage) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('MQTT消息接收错误: ' + JSON.stringify(err));
|
||||||
|
} else {
|
||||||
|
const payloadStr: string = data.payload as string;
|
||||||
|
console.info('收到MQTT消息: ' + payloadStr);
|
||||||
|
// 通过SimpleEmitterUtil发送到事件总线
|
||||||
|
SimpleEmitterUtil.emit(payloadStr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发布消息
|
||||||
|
async publish(message: string, topic?: string): Promise<void> {
|
||||||
|
if (!this.mqttClient) {
|
||||||
|
console.error('MQTT未连接,无法发送消息');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.mqttClient.publish({
|
||||||
|
topic: topic || this.config.topic,
|
||||||
|
qos: this.config.qos,
|
||||||
|
payload: message
|
||||||
|
}).then((data: MqttResponse) => {
|
||||||
|
console.info('MQTT消息发送成功:' + JSON.stringify(data));
|
||||||
|
}).catch((err: Error) => {
|
||||||
|
console.error('MQTT消息发送失败:' + JSON.stringify(err));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 断开连接
|
||||||
|
async disconnect(): Promise<void> {
|
||||||
|
if (this.mqttClient) {
|
||||||
|
try {
|
||||||
|
await this.mqttClient.destroy();
|
||||||
|
console.info('MQTT连接已断开');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('MQTT断开失败: ' + JSON.stringify(err));
|
||||||
|
}
|
||||||
|
this.mqttClient = null;
|
||||||
|
}
|
||||||
|
this.isConnectedFlag = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 销毁客户端
|
||||||
|
async destroy(): Promise<void> {
|
||||||
|
if (!this.mqttClient) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.mqttClient.destroy().then((data: boolean) => {
|
||||||
|
if (data) {
|
||||||
|
console.info('MQTT实例销毁成功');
|
||||||
|
SimpleEmitterUtil.off();
|
||||||
|
} else {
|
||||||
|
console.error('MQTT实例销毁失败');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.mqttClient = null;
|
||||||
|
this.isConnectedFlag = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析水质数据
|
||||||
|
// 后端: {"temp":21,"cod":44,"ph":0,"residualChlorine":0,"conductivity":0,"ammoniaNitrogen":0,...}
|
||||||
|
parseWaterQualityData(rawMessage: string): WaterQualityData {
|
||||||
|
try {
|
||||||
|
const raw = JSON.parse(rawMessage) as Record<string, number>;
|
||||||
|
const data: WaterQualityData = {
|
||||||
|
temperature: raw['temp'] ?? 0,
|
||||||
|
ph: raw['ph'] ?? 0,
|
||||||
|
conductivity: raw['conductivity'] ?? 0,
|
||||||
|
cod: raw['cod'] ?? 0,
|
||||||
|
ammonia: raw['ammoniaNitrogen'] ?? 0,
|
||||||
|
chlorine: raw['residualChlorine'] ?? 0,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
console.info('解析后水质数据: 温度=' + data.temperature +
|
||||||
|
', pH=' + data.ph +
|
||||||
|
', 电导率=' + data.conductivity +
|
||||||
|
', COD=' + data.cod +
|
||||||
|
', 氨氮=' + data.ammonia +
|
||||||
|
', 余氯=' + data.chlorine);
|
||||||
|
return data;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('水质数据解析失败: ' + rawMessage);
|
||||||
|
return {
|
||||||
|
temperature: 0, ph: 0, conductivity: 0,
|
||||||
|
cod: 0, ammonia: 0, chlorine: 0, timestamp: Date.now()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getConnectionStatus(): boolean {
|
||||||
|
return this.isConnectedFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
getConfig(): MqttConfig {
|
||||||
|
return this.config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MqttService;
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
/**
|
||||||
|
* HTTP请求工具类
|
||||||
|
* 使用 @kit.NetworkKit 进行网络请求
|
||||||
|
*/
|
||||||
|
import { http } from '@kit.NetworkKit';
|
||||||
|
import { BusinessError } from '@kit.BasicServicesKit';
|
||||||
|
import Config from './Config';
|
||||||
|
|
||||||
|
// 响应数据接口
|
||||||
|
export interface iResponseModel<T> {
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
data: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP请求头接口
|
||||||
|
interface HttpHeader {
|
||||||
|
'Content-Type': string;
|
||||||
|
'Accept': string;
|
||||||
|
'Authorization'?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP请求选项接口
|
||||||
|
interface HttpOptions {
|
||||||
|
method: http.RequestMethod;
|
||||||
|
header: HttpHeader;
|
||||||
|
extraData?: Object;
|
||||||
|
connectTimeout: number;
|
||||||
|
readTimeout: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RequestUtil {
|
||||||
|
/**
|
||||||
|
* GET请求
|
||||||
|
*/
|
||||||
|
static get<T>(url: string): Promise<iResponseModel<T>> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let httpRequest = http.createHttp();
|
||||||
|
|
||||||
|
const header: HttpHeader = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
};
|
||||||
|
|
||||||
|
const options: HttpOptions = {
|
||||||
|
method: http.RequestMethod.GET,
|
||||||
|
header: header,
|
||||||
|
connectTimeout: Config.TIMEOUT,
|
||||||
|
readTimeout: Config.TIMEOUT
|
||||||
|
};
|
||||||
|
|
||||||
|
httpRequest.request(
|
||||||
|
Config.BASE_URL + url,
|
||||||
|
options,
|
||||||
|
(err: BusinessError, data: http.HttpResponse) => {
|
||||||
|
if (!err) {
|
||||||
|
try {
|
||||||
|
let result: iResponseModel<T> = JSON.parse(data.result as string);
|
||||||
|
resolve(result);
|
||||||
|
} catch (e) {
|
||||||
|
reject(new Error('JSON解析失败'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reject(new Error(err.message));
|
||||||
|
}
|
||||||
|
httpRequest.destroy();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET请求(带query参数)
|
||||||
|
* @param url 请求地址,例如 /monthDetail
|
||||||
|
* @param params 查询参数,例如 { paramType: 'WATER_TEMPERATURE', deviceCode: 'D001' }
|
||||||
|
*/
|
||||||
|
static getWithParams<T>(url: string, params: Record<string, string | number | boolean>): Promise<iResponseModel<T>>
|
||||||
|
{
|
||||||
|
const query = Object.keys(params)
|
||||||
|
.map((key: string) => `${encodeURIComponent(key)}=${encodeURIComponent(String(params[key]))}`)
|
||||||
|
.join('&');
|
||||||
|
|
||||||
|
const fullUrl = query.length > 0 ? `${url}?${query}` : url;
|
||||||
|
return RequestUtil.get<T>(fullUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST请求
|
||||||
|
* @param url 请求地址
|
||||||
|
* @param cls 请求体对象
|
||||||
|
* @param needToken 是否需要Token
|
||||||
|
*/
|
||||||
|
static post<T>(url: string, cls: Object, needToken: boolean): Promise<iResponseModel<T>> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let httpRequest = http.createHttp();
|
||||||
|
|
||||||
|
let header: HttpHeader;
|
||||||
|
if (needToken) {
|
||||||
|
const token = AppStorage.get<string>('token');
|
||||||
|
header = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': 'Bearer ' + token
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
header = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const options: HttpOptions = {
|
||||||
|
method: http.RequestMethod.POST,
|
||||||
|
header: header,
|
||||||
|
extraData: cls,
|
||||||
|
connectTimeout: Config.TIMEOUT,
|
||||||
|
readTimeout: Config.TIMEOUT
|
||||||
|
};
|
||||||
|
|
||||||
|
httpRequest.request(
|
||||||
|
Config.BASE_URL + url,
|
||||||
|
options,
|
||||||
|
(err: BusinessError, data: http.HttpResponse) => {
|
||||||
|
if (!err) {
|
||||||
|
try {
|
||||||
|
let result: iResponseModel<T> = JSON.parse(data.result as string);
|
||||||
|
resolve(result);
|
||||||
|
} catch (e) {
|
||||||
|
reject(new Error('JSON解析失败'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reject(new Error(err.message));
|
||||||
|
}
|
||||||
|
httpRequest.destroy();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PUT请求
|
||||||
|
* @param url 请求地址
|
||||||
|
* @param cls 请求体对象
|
||||||
|
*/
|
||||||
|
static put<T>(url: string, cls: Object): Promise<iResponseModel<T>> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let httpRequest = http.createHttp();
|
||||||
|
|
||||||
|
const token = AppStorage.get<string>('token');
|
||||||
|
const header: HttpHeader = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': 'Bearer ' + token
|
||||||
|
};
|
||||||
|
|
||||||
|
const options: HttpOptions = {
|
||||||
|
method: http.RequestMethod.PUT,
|
||||||
|
header: header,
|
||||||
|
extraData: cls,
|
||||||
|
connectTimeout: Config.TIMEOUT,
|
||||||
|
readTimeout: Config.TIMEOUT
|
||||||
|
};
|
||||||
|
|
||||||
|
httpRequest.request(
|
||||||
|
Config.BASE_URL + url,
|
||||||
|
options,
|
||||||
|
(err: BusinessError, data: http.HttpResponse) => {
|
||||||
|
if (!err) {
|
||||||
|
try {
|
||||||
|
let result: iResponseModel<T> = JSON.parse(data.result as string);
|
||||||
|
resolve(result);
|
||||||
|
} catch (e) {
|
||||||
|
reject(new Error('JSON解析失败'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reject(new Error(err.message));
|
||||||
|
}
|
||||||
|
httpRequest.destroy();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RequestUtil;
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"module": {
|
||||||
|
"name": "entry",
|
||||||
|
"type": "entry",
|
||||||
|
"description": "$string:module_desc",
|
||||||
|
"mainElement": "EntryAbility",
|
||||||
|
"deviceTypes": [
|
||||||
|
"phone"
|
||||||
|
],
|
||||||
|
"deliveryWithInstall": true,
|
||||||
|
"installationFree": false,
|
||||||
|
"pages": "$profile:main_pages",
|
||||||
|
"abilities": [
|
||||||
|
{
|
||||||
|
"name": "EntryAbility",
|
||||||
|
"srcEntry": "./ets/entryability/EntryAbility.ets",
|
||||||
|
"description": "$string:EntryAbility_desc",
|
||||||
|
"icon": "$media:layered_image",
|
||||||
|
"label": "$string:EntryAbility_label",
|
||||||
|
"startWindowIcon": "$media:startIcon",
|
||||||
|
"startWindowBackground": "$color:start_window_background",
|
||||||
|
"exported": true,
|
||||||
|
"skills": [
|
||||||
|
{
|
||||||
|
"entities": [
|
||||||
|
"entity.system.home"
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
"ohos.want.action.home"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestPermissions": [
|
||||||
|
{
|
||||||
|
"name": "ohos.permission.INTERNET",
|
||||||
|
"reason": "$string:permission_internet_reason",
|
||||||
|
"usedScene": {
|
||||||
|
"abilities": ["EntryAbility"],
|
||||||
|
"when": "inuse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"color": [
|
||||||
|
{
|
||||||
|
"name": "start_window_background",
|
||||||
|
"value": "#FFFFFF"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"float": [
|
||||||
|
{
|
||||||
|
"name": "page_text_font_size",
|
||||||
|
"value": "50fp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"string": [
|
||||||
|
{
|
||||||
|
"name": "module_desc",
|
||||||
|
"value": "module description"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "EntryAbility_desc",
|
||||||
|
"value": "description"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "EntryAbility_label",
|
||||||
|
"value": "label"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "permission_internet_reason",
|
||||||
|
"value": "用于连接MQTT服务器获取实时水质数据"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"layered-image":
|
||||||
|
{
|
||||||
|
"background" : "$media:background",
|
||||||
|
"foreground" : "$media:foreground"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"src": [
|
||||||
|
"pages/Index",
|
||||||
|
"pages/Login",
|
||||||
|
"pages/Details"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 186 KiB |
@@ -0,0 +1,71 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>碳汇趋势图</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body { background: transparent; }
|
||||||
|
#chart { width: 100%; height: 160px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="chart"></div>
|
||||||
|
<script>
|
||||||
|
var chart = echarts.init(document.getElementById('chart'));
|
||||||
|
var months = ['1月', '2月', '3月', '4月', '5月', '6月'];
|
||||||
|
var data = [0];
|
||||||
|
|
||||||
|
var option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
backgroundColor: 'rgba(13,40,71,0.9)',
|
||||||
|
borderColor: '#00FF88',
|
||||||
|
borderWidth: 1,
|
||||||
|
textStyle: { color: '#FFFFFF', fontSize: 12 },
|
||||||
|
formatter: function(p) {
|
||||||
|
return '<div style="font-weight:bold">' + p[0].axisValue + '</div><div style="color:#00FF88">' + p[0].value.toFixed(2) + ' kg</div>';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: { left: 35, right: 15, top: 15, bottom: 25 },
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: months,
|
||||||
|
axisLabel: { fontSize: 11, color: 'rgba(255,255,255,0.7)' },
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(22,119,255,0.4)' } },
|
||||||
|
axisTick: { show: false }
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
axisLabel: { fontSize: 11, color: 'rgba(255,255,255,0.7)', formatter: '{value}' },
|
||||||
|
axisLine: { show: false },
|
||||||
|
axisTick: { show: false },
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(22,119,255,0.2)', type: 'dashed' } }
|
||||||
|
},
|
||||||
|
series: [{
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 5,
|
||||||
|
lineStyle: { color: '#00FF88', width: 2 },
|
||||||
|
itemStyle: { color: '#00FF88' },
|
||||||
|
areaStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [{ offset: 0, color: 'rgba(0,255,136,0.35)' }, { offset: 1, color: 'rgba(0,255,136,0.05)' }]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: data
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
chart.setOption(option);
|
||||||
|
|
||||||
|
function updateData(arr) {
|
||||||
|
data = arr;
|
||||||
|
option.series[0].data = data;
|
||||||
|
chart.setOption(option);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>contrast chart</title>
|
||||||
|
<style>
|
||||||
|
html, body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
#chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="chart"></div>
|
||||||
|
|
||||||
|
<!-- 你项目里若是本地 echarts,请改成你的实际路径 -->
|
||||||
|
<!-- 例如:<script src="./echarts.min.js"></script> -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let chart = null;
|
||||||
|
let currentColor = '#00FF88';
|
||||||
|
let currentData = [];
|
||||||
|
let currentUnit = '';
|
||||||
|
let currentName = '';
|
||||||
|
let xLabels = ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'];
|
||||||
|
|
||||||
|
function getOption(data, unit, name, color) {
|
||||||
|
const max = Math.max.apply(null, data);
|
||||||
|
const min = Math.min.apply(null, data);
|
||||||
|
|
||||||
|
return {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
animation: true,
|
||||||
|
grid: {
|
||||||
|
left: 18,
|
||||||
|
right: 12,
|
||||||
|
top: 24,
|
||||||
|
bottom: 24,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
backgroundColor: 'rgba(10,22,40,0.9)',
|
||||||
|
borderColor: 'rgba(22,119,255,0.5)',
|
||||||
|
borderWidth: 1,
|
||||||
|
textStyle: { color: '#fff', fontSize: 11 },
|
||||||
|
formatter: function (params) {
|
||||||
|
const p = params[0];
|
||||||
|
return `${p.axisValue}<br/>${name}: ${p.value}${unit ? ' ' + unit : ''}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: xLabels,
|
||||||
|
boundaryGap: false,
|
||||||
|
axisLine: { lineStyle: { color: 'rgba(255,255,255,0.25)' } },
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(255,255,255,0.65)',
|
||||||
|
fontSize: 10
|
||||||
|
},
|
||||||
|
splitLine: { show: false }
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
scale: true,
|
||||||
|
axisLine: { show: false },
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgba(255,255,255,0.65)',
|
||||||
|
fontSize: 10
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: { color: 'rgba(255,255,255,0.12)' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: name,
|
||||||
|
type: 'line',
|
||||||
|
data: data,
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6,
|
||||||
|
showSymbol: true,
|
||||||
|
lineStyle: {
|
||||||
|
width: 2,
|
||||||
|
color: color
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: color,
|
||||||
|
borderColor: '#ffffff',
|
||||||
|
borderWidth: 1
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: hexToRgba(color, 0.35) },
|
||||||
|
{ offset: 1, color: hexToRgba(color, 0.02) }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
markPoint: {
|
||||||
|
symbolSize: 30,
|
||||||
|
label: { color: '#fff', fontSize: 9 },
|
||||||
|
itemStyle: { color: color },
|
||||||
|
data: [
|
||||||
|
{ type: 'max', name: '最大值' },
|
||||||
|
{ type: 'min', name: '最小值' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexToRgba(hex, alpha) {
|
||||||
|
const c = hex.replace('#', '');
|
||||||
|
if (c.length !== 6) return `rgba(0,255,136,${alpha})`;
|
||||||
|
const r = parseInt(c.substring(0, 2), 16);
|
||||||
|
const g = parseInt(c.substring(2, 4), 16);
|
||||||
|
const b = parseInt(c.substring(4, 6), 16);
|
||||||
|
return `rgba(${r},${g},${b},${alpha})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureChart() {
|
||||||
|
const dom = document.getElementById('chart');
|
||||||
|
if (!chart) {
|
||||||
|
chart = echarts.init(dom, null, { renderer: 'canvas' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 首次初始化(对应 TS: onPageEnd -> initChart)
|
||||||
|
function initChart(data, unit, name) {
|
||||||
|
ensureChart();
|
||||||
|
currentData = Array.isArray(data) ? data : [];
|
||||||
|
currentUnit = unit || '';
|
||||||
|
currentName = name || '';
|
||||||
|
chart.setOption(getOption(currentData, currentUnit, currentName, currentColor), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新数据(对应 TS: updateChart -> updateData)
|
||||||
|
function updateData(data, unit, name) {
|
||||||
|
ensureChart();
|
||||||
|
currentData = Array.isArray(data) ? data : currentData;
|
||||||
|
currentUnit = unit !== undefined ? unit : currentUnit;
|
||||||
|
currentName = name !== undefined ? name : currentName;
|
||||||
|
chart.setOption(getOption(currentData, currentUnit, currentName, currentColor), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新颜色
|
||||||
|
function setColor(color) {
|
||||||
|
currentColor = color || currentColor;
|
||||||
|
if (!chart) return;
|
||||||
|
chart.setOption(getOption(currentData, currentUnit, currentName, currentColor), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', function () {
|
||||||
|
if (chart) chart.resize();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 暴露给 ArkTS 调用
|
||||||
|
window.initChart = initChart;
|
||||||
|
window.updateData = updateData;
|
||||||
|
window.setColor = setColor;
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>水质实时曲线</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
background-color: transparent;
|
||||||
|
font-family: 'Microsoft YaHei', sans-serif;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
#chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="chart"></div>
|
||||||
|
<script>
|
||||||
|
var chartDom = document.getElementById('chart');
|
||||||
|
var myChart = echarts.init(chartDom);
|
||||||
|
|
||||||
|
// 初始化数据存储
|
||||||
|
var chlorineData = [];
|
||||||
|
var ammoniaData = [];
|
||||||
|
var timeData = [];
|
||||||
|
|
||||||
|
// 初始化20个空数据点
|
||||||
|
for(var i = 19; i >= 0; i--) {
|
||||||
|
timeData.push(formatTime(new Date(Date.now() - i * 5000)));
|
||||||
|
chlorineData.push(0);
|
||||||
|
ammoniaData.push(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间格式化函数
|
||||||
|
function formatTime(date) {
|
||||||
|
var h = date.getHours();
|
||||||
|
var m = date.getMinutes();
|
||||||
|
var s = date.getSeconds();
|
||||||
|
return (h < 10 ? '0' + h : h) + ':' + (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ECharts配置
|
||||||
|
var option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
backgroundColor: 'rgba(255,255,255,0.95)',
|
||||||
|
borderColor: '#E5E5E5',
|
||||||
|
borderWidth: 1,
|
||||||
|
textStyle: {
|
||||||
|
color: '#333',
|
||||||
|
fontSize: 13
|
||||||
|
},
|
||||||
|
formatter: function(params) {
|
||||||
|
var result = '<div style="font-weight:bold;margin-bottom:4px">' + params[0].axisValue + '</div>';
|
||||||
|
params.forEach(function(item) {
|
||||||
|
var color = item.seriesName === '余氯' ? '#FA8C16' : '#FF4D4F';
|
||||||
|
result += '<div style="display:flex;align-items:center;margin:2px 0">';
|
||||||
|
result += '<span style="display:inline-block;width:10px;height:10px;background:' + color + ';border-radius:50%;margin-right:6px"></span>';
|
||||||
|
result += '<span style="flex:1">' + item.seriesName + '</span>';
|
||||||
|
result += '<span style="font-weight:bold;color:' + color + '">' + item.value.toFixed(3) + ' mg/L</span>';
|
||||||
|
result += '</div>';
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: ['余氯', '氨氮'],
|
||||||
|
top: 5,
|
||||||
|
right: 10,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#666'
|
||||||
|
},
|
||||||
|
itemWidth: 16,
|
||||||
|
itemHeight: 8,
|
||||||
|
itemGap: 15
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: 45,
|
||||||
|
right: 15,
|
||||||
|
top: 30,
|
||||||
|
bottom: 30
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: false,
|
||||||
|
data: timeData,
|
||||||
|
axisLabel: {
|
||||||
|
fontSize: 10,
|
||||||
|
color: '#999',
|
||||||
|
interval: 3,
|
||||||
|
rotate: 0
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: { color: '#E5E5E5', width: 1 }
|
||||||
|
},
|
||||||
|
axisTick: { show: false }
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
min: 0,
|
||||||
|
max: 1,
|
||||||
|
interval: 0.2,
|
||||||
|
axisLabel: {
|
||||||
|
fontSize: 10,
|
||||||
|
color: '#999',
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
axisLine: { show: false },
|
||||||
|
axisTick: { show: false },
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#F0F0F0',
|
||||||
|
type: 'dashed'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '余氯',
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
showSymbol: false,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#FA8C16',
|
||||||
|
width: 2.5
|
||||||
|
},
|
||||||
|
itemStyle: { color: '#FA8C16' },
|
||||||
|
areaStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(250,140,22,0.3)' },
|
||||||
|
{ offset: 0.5, color: 'rgba(250,140,22,0.15)' },
|
||||||
|
{ offset: 1, color: 'rgba(250,140,22,0.05)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: chlorineData
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '氨氮',
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
showSymbol: false,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#FF4D4F',
|
||||||
|
width: 2.5
|
||||||
|
},
|
||||||
|
itemStyle: { color: '#FF4D4F' },
|
||||||
|
areaStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(255,77,79,0.3)' },
|
||||||
|
{ offset: 0.5, color: 'rgba(255,77,79,0.15)' },
|
||||||
|
{ offset: 1, color: 'rgba(255,77,79,0.05)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: ammoniaData
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
myChart.setOption(option);
|
||||||
|
|
||||||
|
// 更新数据函数 - 从HarmonyOS调用
|
||||||
|
function updateData(chlorine, ammonia) {
|
||||||
|
var now = formatTime(new Date());
|
||||||
|
|
||||||
|
// 添加新数据
|
||||||
|
timeData.push(now);
|
||||||
|
chlorineData.push(parseFloat(chlorine));
|
||||||
|
ammoniaData.push(parseFloat(ammonia));
|
||||||
|
|
||||||
|
// 保持最多20个数据点
|
||||||
|
if(timeData.length > 20) {
|
||||||
|
timeData.shift();
|
||||||
|
chlorineData.shift();
|
||||||
|
ammoniaData.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新图表
|
||||||
|
myChart.setOption({
|
||||||
|
xAxis: { data: timeData },
|
||||||
|
series: [
|
||||||
|
{ data: chlorineData },
|
||||||
|
{ data: ammoniaData }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空数据函数
|
||||||
|
function clearData() {
|
||||||
|
timeData = [];
|
||||||
|
chlorineData = [];
|
||||||
|
ammoniaData = [];
|
||||||
|
for(var i = 19; i >= 0; i--) {
|
||||||
|
timeData.push(formatTime(new Date(Date.now() - i * 5000)));
|
||||||
|
chlorineData.push(0);
|
||||||
|
ammoniaData.push(0);
|
||||||
|
}
|
||||||
|
myChart.setOption({
|
||||||
|
xAxis: { data: timeData },
|
||||||
|
series: [
|
||||||
|
{ data: chlorineData },
|
||||||
|
{ data: ammoniaData }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应窗口大小变化
|
||||||
|
window.addEventListener('resize', function() {
|
||||||
|
myChart.resize();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.3 KiB |