修改页面
This commit is contained in:
parent
b4b45eeb14
commit
9c7bb9e568
|
@ -59,20 +59,22 @@ android {
|
|||
chaquopy {
|
||||
defaultConfig {
|
||||
version = "3.11"
|
||||
buildPython("C:\\Users\\xuyvqi\\AppData\\Local\\Programs\\Python\\Python311\\python.exe")
|
||||
buildPython("C:\\Users\\cuizhibin\\AppData\\Local\\Programs\\Python\\Python311\\python.exe")
|
||||
pip {
|
||||
install("typing_extensions")
|
||||
install("lxml==5.3.0") // python-docx 的依赖
|
||||
install("python-docx") // 使用兼容性较好的版本
|
||||
install("Pillow") // 可选,处理图片
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.room.runtime)
|
||||
implementation(libs.firebase.crashlytics.buildtools)
|
||||
implementation(libs.activity)
|
||||
annotationProcessor(libs.room.compiler)
|
||||
implementation(libs.appcompat)
|
||||
implementation(libs.material)
|
||||
|
@ -83,7 +85,9 @@ dependencies {
|
|||
androidTestImplementation(libs.ext.junit)
|
||||
androidTestImplementation(libs.espresso.core)
|
||||
implementation(libs.okhttp)
|
||||
|
||||
implementation ("androidx.core:core-ktx:1.12.0")
|
||||
implementation ("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0")
|
||||
implementation ("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
|
||||
implementation("com.google.android.gms:play-services-location:21.0.1")
|
||||
implementation("androidx.exifinterface:exifinterface:1.3.3")
|
||||
implementation ("commons-io:commons-io:2.11.0")
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 4,
|
||||
"identityHash": "ab7260384e2d58c0d78dc0dae5130632",
|
||||
"identityHash": "6390dc3df9684c38dfe932d1189a35a8",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "images",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `path` TEXT, `time` INTEGER NOT NULL, `latitude` REAL NOT NULL, `longitude` REAL NOT NULL, `altitude` REAL NOT NULL, `user` TEXT, `audioPath` TEXT, `blade` INTEGER NOT NULL, `unit` TEXT, `unitName` TEXT, `project` TEXT, `b` INTEGER NOT NULL, `temperature` TEXT, `humidity` TEXT, `weather` TEXT)",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `path` TEXT, `time` INTEGER NOT NULL, `latitude` REAL NOT NULL, `longitude` REAL NOT NULL, `altitude` REAL NOT NULL, `user` TEXT, `audioPath` TEXT, `turbineId` TEXT, `turbineName` TEXT, `partId` TEXT, `partName` TEXT, `project` TEXT, `b` INTEGER NOT NULL, `temperature` TEXT, `humidity` TEXT, `weather` TEXT, `imageSource` TEXT)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
|
@ -57,20 +57,26 @@
|
|||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "blade",
|
||||
"columnName": "blade",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unit",
|
||||
"columnName": "unit",
|
||||
"fieldPath": "turbineId",
|
||||
"columnName": "turbineId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "unitName",
|
||||
"columnName": "unitName",
|
||||
"fieldPath": "turbineName",
|
||||
"columnName": "turbineName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "partId",
|
||||
"columnName": "partId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "partName",
|
||||
"columnName": "partName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
|
@ -103,6 +109,12 @@
|
|||
"columnName": "weather",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "imageSource",
|
||||
"columnName": "imageSource",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
|
@ -148,7 +160,7 @@
|
|||
},
|
||||
{
|
||||
"tableName": "audio_entities",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `fileName` TEXT, `AudioPath` TEXT, `ImagePath` TEXT, `fileSize` INTEGER NOT NULL, `time` TEXT, `duration` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL)",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `isupdated` INTEGER NOT NULL, `fileName` TEXT, `AudioPath` TEXT, `ImagePath` TEXT, `fileSize` INTEGER NOT NULL, `time` TEXT, `duration` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
|
@ -156,6 +168,12 @@
|
|||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isupdated",
|
||||
"columnName": "isupdated",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "fileName",
|
||||
"columnName": "fileName",
|
||||
|
@ -212,7 +230,7 @@
|
|||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ab7260384e2d58c0d78dc0dae5130632')"
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6390dc3df9684c38dfe932d1189a35a8')"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,101 +1,132 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- 基础权限 -->
|
||||
<!-- Android 14+ 新增权限 -->
|
||||
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||
tools:ignore="ScopedStorage" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"
|
||||
tools:ignore="SelectedPhotoAccess" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<!--位置权限-->
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32"
|
||||
tools:ignore="ScopedStorage" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" /> <!-- Android 14+ 必须 -->
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:supportsRtl="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:windowSoftInputMode="stateVisible|adjustResize"
|
||||
android:theme="@style/Theme.MyApplication"
|
||||
android:name="com.chaquo.python.android.PyApplication"
|
||||
tools:targetApi="31"
|
||||
tools:ignore="ForegroundServicePermission">
|
||||
<activity
|
||||
android:name=".MainActivity">
|
||||
</activity>
|
||||
<activity android:name=".AdminActivity" />
|
||||
<activity android:name=".LoginActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<service
|
||||
android:name=".Service.FloatingWindowService"
|
||||
android:foregroundServiceType="specialUse"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
<service android:name=".Service.RecordingService"
|
||||
android:foregroundServiceType="microphone"
|
||||
android:enabled="true"
|
||||
android:exported="false"/>
|
||||
<service
|
||||
android:name=".Service.ForegroundService"
|
||||
android:foregroundServiceType="microphone"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
<queries>
|
||||
<!-- 允许查询所有能处理图片选择的 Intent -->
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PICK" />
|
||||
<data android:mimeType="image/*" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.provider.action.PICK_IMAGES" />
|
||||
<data android:mimeType="image/*" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.GET_CONTENT" />
|
||||
<data android:mimeType="image/*" />
|
||||
</intent>
|
||||
</queries>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools" >
|
||||
<!-- 基础权限 -->
|
||||
<!-- Android 14+ 新增权限 -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
<uses-permission
|
||||
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||
tools:ignore="ScopedStorage" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_MEDIA_IMAGES"
|
||||
tools:ignore="SelectedPhotoAccess" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <!-- 位置权限 -->
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32"
|
||||
tools:ignore="ScopedStorage" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" /> <!-- Android 14+ 必须 -->
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<queries>
|
||||
|
||||
<!-- 允许查询所有能处理图片选择的 Intent -->
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PICK" />
|
||||
|
||||
<data android:mimeType="image/*" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.provider.action.PICK_IMAGES" />
|
||||
|
||||
<data android:mimeType="image/*" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.GET_CONTENT" />
|
||||
|
||||
<data android:mimeType="image/*" />
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<application
|
||||
android:name="com.chaquo.python.android.PyApplication"
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.MyApplication"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:windowSoftInputMode="stateVisible|adjustResize"
|
||||
tools:ignore="ForegroundServicePermission"
|
||||
tools:targetApi="31" >
|
||||
<activity android:name=".MainActivity" >
|
||||
</activity>
|
||||
<activity android:name=".AdminActivity" />
|
||||
<activity
|
||||
android:name=".LoginActivity"
|
||||
android:exported="true" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".Service.PhotoMonitoringService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="camera" />
|
||||
<service
|
||||
android:name=".Service.FloatingWindowService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="specialUse" />
|
||||
<service
|
||||
android:name=".Service.RecordingService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="microphone" />
|
||||
<service
|
||||
android:name=".Service.ForegroundService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="microphone" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true" >
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
<receiver
|
||||
android:name=".Tool.NotificationReceiver"
|
||||
android:exported="false" > <!-- 安全限制:只接收本应用广播 -->
|
||||
<intent-filter>
|
||||
<action android:name="ACTION_STOP_SERVICE" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
|
||||
</manifest>
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -1,96 +1,96 @@
|
|||
package com.example.myapplication;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ListView;
|
||||
import android.widget.SimpleCursorAdapter;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.example.myapplication.DataBase.DatabaseHelper;
|
||||
|
||||
public class AdminActivity extends AppCompatActivity {
|
||||
private ListView lvUsers;
|
||||
private DatabaseHelper dbHelper;
|
||||
private SimpleCursorAdapter adapter;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_admin);
|
||||
|
||||
dbHelper = new DatabaseHelper(this);
|
||||
lvUsers = findViewById(R.id.lvUsers);
|
||||
Button btnBack = findViewById(R.id.btnBack);
|
||||
|
||||
loadUsers();
|
||||
|
||||
btnBack.setOnClickListener(v -> finish());
|
||||
}
|
||||
|
||||
private void loadUsers() {
|
||||
Cursor cursor = dbHelper.getAllUsers();
|
||||
|
||||
String[] from = new String[]{
|
||||
DatabaseHelper.COLUMN_USERNAME,
|
||||
DatabaseHelper.COLUMN_PASSWORD,
|
||||
DatabaseHelper.COLUMN_USER_PROJECT_NAME,
|
||||
DatabaseHelper.COLUMN_USER_PROJECT_ID
|
||||
};
|
||||
|
||||
int[] to = new int[]{
|
||||
R.id.tvUsername,
|
||||
R.id.tvPassword,
|
||||
R.id.tvProjectName,
|
||||
R.id.tvProjectId
|
||||
};
|
||||
|
||||
adapter = new SimpleCursorAdapter(this, R.layout.user_item,
|
||||
cursor, from, to, 0) {
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View view = super.getView(position, convertView, parent);
|
||||
|
||||
// 获取当前行的数据
|
||||
Cursor cursor = (Cursor) getItem(position);
|
||||
@SuppressLint("Range") String username = cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_USERNAME));
|
||||
|
||||
// 设置删除按钮点击事件
|
||||
Button btnDelete = view.findViewById(R.id.btnDelete);
|
||||
btnDelete.setOnClickListener(v -> {
|
||||
if (dbHelper.deleteUser(username)) {
|
||||
Toast.makeText(AdminActivity.this, "用户已删除", Toast.LENGTH_SHORT).show();
|
||||
// 重新加载数据
|
||||
Cursor newCursor = dbHelper.getAllUsers();
|
||||
changeCursor(newCursor);
|
||||
} else {
|
||||
Toast.makeText(AdminActivity.this, "删除失败", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
TextView tvProjectName = view.findViewById(R.id.tvProjectName);
|
||||
tvProjectName.setOnClickListener(v -> {
|
||||
if (tvProjectName.getMaxLines() == 1) {
|
||||
tvProjectName.setMaxLines(Integer.MAX_VALUE); // 展开
|
||||
} else {
|
||||
tvProjectName.setMaxLines(1); // 收起
|
||||
}
|
||||
});
|
||||
return view;
|
||||
}
|
||||
};
|
||||
|
||||
lvUsers.setAdapter(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
dbHelper.close();
|
||||
super.onDestroy();
|
||||
}
|
||||
package com.example.myapplication;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ListView;
|
||||
import android.widget.SimpleCursorAdapter;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.example.myapplication.DataBase.DatabaseHelper;
|
||||
|
||||
public class AdminActivity extends AppCompatActivity {
|
||||
private ListView lvUsers;
|
||||
private DatabaseHelper dbHelper;
|
||||
private SimpleCursorAdapter adapter;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_admin);
|
||||
|
||||
dbHelper = new DatabaseHelper(this);
|
||||
lvUsers = findViewById(R.id.lvUsers);
|
||||
Button btnBack = findViewById(R.id.btnBack);
|
||||
|
||||
loadUsers();
|
||||
|
||||
btnBack.setOnClickListener(v -> finish());
|
||||
}
|
||||
|
||||
private void loadUsers() {
|
||||
Cursor cursor = dbHelper.getAllUsers();
|
||||
|
||||
String[] from = new String[]{
|
||||
DatabaseHelper.COLUMN_USERNAME,
|
||||
DatabaseHelper.COLUMN_PASSWORD,
|
||||
DatabaseHelper.COLUMN_USER_PROJECT_NAME,
|
||||
DatabaseHelper.COLUMN_USER_PROJECT_ID
|
||||
};
|
||||
|
||||
int[] to = new int[]{
|
||||
R.id.tvUsername,
|
||||
R.id.tvPassword,
|
||||
R.id.tvProjectName,
|
||||
R.id.tvProjectId
|
||||
};
|
||||
|
||||
adapter = new SimpleCursorAdapter(this, R.layout.user_item,
|
||||
cursor, from, to, 0) {
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View view = super.getView(position, convertView, parent);
|
||||
|
||||
// 获取当前行的数据
|
||||
Cursor cursor = (Cursor) getItem(position);
|
||||
@SuppressLint("Range") String username = cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_USERNAME));
|
||||
|
||||
// 设置删除按钮点击事件
|
||||
Button btnDelete = view.findViewById(R.id.btnDelete);
|
||||
btnDelete.setOnClickListener(v -> {
|
||||
if (dbHelper.deleteUser(username)) {
|
||||
Toast.makeText(AdminActivity.this, "用户已删除", Toast.LENGTH_SHORT).show();
|
||||
// 重新加载数据
|
||||
Cursor newCursor = dbHelper.getAllUsers();
|
||||
changeCursor(newCursor);
|
||||
} else {
|
||||
Toast.makeText(AdminActivity.this, "删除失败", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
TextView tvProjectName = view.findViewById(R.id.tvProjectName);
|
||||
tvProjectName.setOnClickListener(v -> {
|
||||
if (tvProjectName.getMaxLines() == 1) {
|
||||
tvProjectName.setMaxLines(Integer.MAX_VALUE); // 展开
|
||||
} else {
|
||||
tvProjectName.setMaxLines(1); // 收起
|
||||
}
|
||||
});
|
||||
return view;
|
||||
}
|
||||
};
|
||||
|
||||
lvUsers.setAdapter(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
dbHelper.close();
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
|
@ -1,45 +1,40 @@
|
|||
package com.example.myapplication.DataBase;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.room.Database;
|
||||
import androidx.room.Room;
|
||||
import androidx.room.RoomDatabase;
|
||||
import androidx.room.migration.Migration;
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
|
||||
import com.example.myapplication.model.AudioEntity;
|
||||
import com.example.myapplication.model.ImageEntity;
|
||||
import com.example.myapplication.model.Turbine;
|
||||
|
||||
@Database(entities = {ImageEntity.class, Turbine.class, AudioEntity.class}, version = 4) // 版本号增加,添加新实体
|
||||
public abstract class AppDatabase extends RoomDatabase {
|
||||
public abstract ImageDao imageDao();
|
||||
public abstract TurbineDao turbineDao(); // 添加新的DAO
|
||||
public abstract AudioDao AudioDao();
|
||||
private static volatile AppDatabase INSTANCE;
|
||||
|
||||
|
||||
|
||||
private static final Migration MIGRATION_4_5 = new Migration(4, 5) {
|
||||
@Override
|
||||
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
||||
|
||||
}
|
||||
};
|
||||
public static AppDatabase getDatabase(Context context) {
|
||||
if (INSTANCE == null) {
|
||||
synchronized (AppDatabase.class) {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
|
||||
AppDatabase.class, "app_database")
|
||||
.fallbackToDestructiveMigration() // 简单处理,正式项目应该实现Migration
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
package com.example.myapplication.DataBase;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.room.Database;
|
||||
import androidx.room.Room;
|
||||
import androidx.room.RoomDatabase;
|
||||
import androidx.room.migration.Migration;
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
|
||||
import com.example.myapplication.model.AudioEntity;
|
||||
import com.example.myapplication.model.ImageEntity;
|
||||
import com.example.myapplication.model.Turbine;
|
||||
|
||||
@Database(entities = {ImageEntity.class, Turbine.class, AudioEntity.class}, version = 4) // 版本号增加,添加新实体
|
||||
public abstract class AppDatabase extends RoomDatabase {
|
||||
public abstract ImageDao imageDao();
|
||||
public abstract TurbineDao turbineDao(); // 添加新的DAO
|
||||
public abstract AudioDao AudioDao();
|
||||
private static volatile AppDatabase INSTANCE;
|
||||
|
||||
|
||||
|
||||
|
||||
public static AppDatabase getDatabase(Context context) {
|
||||
if (INSTANCE == null) {
|
||||
synchronized (AppDatabase.class) {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
|
||||
AppDatabase.class, "app_database")
|
||||
.fallbackToDestructiveMigration() // 简单处理,正式项目应该实现Migration
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,38 +1,39 @@
|
|||
package com.example.myapplication.DataBase;
|
||||
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Update;
|
||||
|
||||
import com.example.myapplication.model.AudioEntity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface AudioDao {
|
||||
@Insert
|
||||
void insert(AudioEntity audioEntity);
|
||||
|
||||
@Update
|
||||
void update(AudioEntity audioEntity);
|
||||
|
||||
@Delete
|
||||
void delete(AudioEntity audioEntity);
|
||||
|
||||
@Query("SELECT * FROM audio_entities ORDER BY createdAt DESC")
|
||||
List<AudioEntity> getAllAudios();
|
||||
|
||||
@Query("SELECT * FROM audio_entities WHERE id = :id")
|
||||
AudioEntity getAudioById(int id);
|
||||
|
||||
@Query("SELECT * FROM audio_entities WHERE AudioPath = :path")
|
||||
AudioEntity getAudioByPath(String path);
|
||||
|
||||
@Query("DELETE FROM audio_entities WHERE AudioPath = :path")
|
||||
void deleteByPath(String path);
|
||||
|
||||
package com.example.myapplication.DataBase;
|
||||
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Update;
|
||||
|
||||
import com.example.myapplication.model.AudioEntity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface AudioDao {
|
||||
@Insert
|
||||
void insert(AudioEntity audioEntity);
|
||||
|
||||
@Update
|
||||
void update(AudioEntity audioEntity);
|
||||
|
||||
@Delete
|
||||
void delete(AudioEntity audioEntity);
|
||||
|
||||
@Query("SELECT * FROM audio_entities ORDER BY createdAt DESC")
|
||||
List<AudioEntity> getAllAudios();
|
||||
|
||||
@Query("SELECT * FROM audio_entities WHERE id = :id")
|
||||
AudioEntity getAudioById(int id);
|
||||
|
||||
@Query("SELECT * FROM audio_entities WHERE AudioPath = :path")
|
||||
AudioEntity getAudioByPath(String path);
|
||||
|
||||
@Query("DELETE FROM audio_entities WHERE AudioPath = :path")
|
||||
void deleteByPath(String path);
|
||||
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,45 +1,45 @@
|
|||
package com.example.myapplication.DataBase;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Update;
|
||||
|
||||
import com.example.myapplication.model.ImageEntity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface ImageDao {
|
||||
@Insert
|
||||
void insert(ImageEntity image);
|
||||
|
||||
@Query("SELECT * FROM images ORDER BY time DESC")
|
||||
List<ImageEntity> getAll();
|
||||
@Delete
|
||||
void delete(ImageEntity item);
|
||||
|
||||
@Query("DELETE FROM images")
|
||||
void deleteAll();
|
||||
// 在ImageEntity类中添加查询方法
|
||||
@Query("SELECT * FROM images ORDER BY time DESC LIMIT 1")
|
||||
ImageEntity getLatestImage();
|
||||
|
||||
|
||||
// 定时间删除:根据 ID 删除特定记录
|
||||
@Query("DELETE FROM images WHERE time = :time")
|
||||
void deleteBytime(long time);
|
||||
@Query("SELECT * FROM images WHERE id = :id")
|
||||
ImageEntity getById(long id);
|
||||
@Update
|
||||
void update(ImageEntity latestImage);
|
||||
|
||||
@Query("DELETE FROM images WHERE id IN (:ids)")
|
||||
void deleteByIds(List<Integer> ids);
|
||||
|
||||
@Query("DELETE FROM images WHERE time BETWEEN :startTime AND :endTime")
|
||||
int deleteByTimeRange(long startTime, long endTime);
|
||||
@Query("SELECT * FROM images WHERE time BETWEEN :startTime AND :endTime")
|
||||
List<ImageEntity> getImagesByTimeRange(long startTime, long endTime);
|
||||
}
|
||||
package com.example.myapplication.DataBase;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Update;
|
||||
|
||||
import com.example.myapplication.model.ImageEntity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface ImageDao {
|
||||
@Insert
|
||||
void insert(ImageEntity image);
|
||||
|
||||
@Query("SELECT * FROM images ORDER BY time DESC")
|
||||
List<ImageEntity> getAll();
|
||||
@Delete
|
||||
void delete(ImageEntity item);
|
||||
|
||||
@Query("DELETE FROM images")
|
||||
void deleteAll();
|
||||
// 在ImageEntity类中添加查询方法
|
||||
@Query("SELECT * FROM images ORDER BY time DESC LIMIT 1")
|
||||
ImageEntity getLatestImage();
|
||||
|
||||
|
||||
// 定时间删除:根据 ID 删除特定记录
|
||||
@Query("DELETE FROM images WHERE time = :time")
|
||||
void deleteBytime(long time);
|
||||
@Query("SELECT * FROM images WHERE id = :id")
|
||||
ImageEntity getById(long id);
|
||||
@Update
|
||||
void update(ImageEntity latestImage);
|
||||
|
||||
@Query("DELETE FROM images WHERE id IN (:ids)")
|
||||
void deleteByIds(List<Integer> ids);
|
||||
|
||||
@Query("DELETE FROM images WHERE time BETWEEN :startTime AND :endTime")
|
||||
int deleteByTimeRange(long startTime, long endTime);
|
||||
@Query("SELECT * FROM images WHERE time BETWEEN :startTime AND :endTime")
|
||||
List<ImageEntity> getImagesByTimeRange(long startTime, long endTime);
|
||||
}
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
package com.example.myapplication.DataBase;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
import androidx.room.Query;
|
||||
|
||||
import com.example.myapplication.model.Turbine;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface TurbineDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
void insertAll(List<Turbine> turbines);
|
||||
|
||||
@Query("SELECT * FROM turbines WHERE projectId = :projectId")
|
||||
List<Turbine> getTurbinesByProject(String projectId);
|
||||
@Query("DELETE FROM turbines WHERE projectId = :projectId")
|
||||
void deleteByProjectId(String projectId);
|
||||
@Query("SELECT * FROM turbines WHERE projectId = :projectId AND " +
|
||||
"(turbineId LIKE :query OR turbineName LIKE :query)")
|
||||
List<Turbine> searchTurbines(String projectId, String query);
|
||||
package com.example.myapplication.DataBase;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
import androidx.room.Query;
|
||||
|
||||
import com.example.myapplication.model.Turbine;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface TurbineDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
void insertAll(List<Turbine> turbines);
|
||||
|
||||
@Query("SELECT * FROM turbines WHERE projectId = :projectId")
|
||||
List<Turbine> getTurbinesByProject(String projectId);
|
||||
@Query("DELETE FROM turbines WHERE projectId = :projectId")
|
||||
void deleteByProjectId(String projectId);
|
||||
@Query("SELECT * FROM turbines WHERE projectId = :projectId AND " +
|
||||
"(turbineId LIKE :query OR turbineName LIKE :query)")
|
||||
List<Turbine> searchTurbines(String projectId, String query);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,54 +1,54 @@
|
|||
package com.example.myapplication.Service;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import com.example.myapplication.R;
|
||||
|
||||
public class ForegroundService extends Service {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
startForeground(1, createNotification());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private Notification createNotification() {
|
||||
NotificationChannel channel = null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
channel = new NotificationChannel(
|
||||
"channel_id",
|
||||
"Foreground Service",
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
);
|
||||
NotificationManager manager = getSystemService(NotificationManager.class);
|
||||
manager.createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
return new NotificationCompat.Builder(this, "channel_id")
|
||||
.setContentTitle("录音服务运行中")
|
||||
.setContentText("悬浮球正在运行")
|
||||
.setSmallIcon(R.drawable.ic_mic_off)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
package com.example.myapplication.Service;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import com.example.myapplication.R;
|
||||
|
||||
public class ForegroundService extends Service {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
startForeground(1, createNotification());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private Notification createNotification() {
|
||||
NotificationChannel channel = null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
channel = new NotificationChannel(
|
||||
"channel_id",
|
||||
"Foreground Service",
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
);
|
||||
NotificationManager manager = getSystemService(NotificationManager.class);
|
||||
manager.createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
return new NotificationCompat.Builder(this, "channel_id")
|
||||
.setContentTitle("录音服务运行中")
|
||||
.setContentText("悬浮球正在运行")
|
||||
.setSmallIcon(R.drawable.ic_mic_off)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,558 @@
|
|||
package com.example.myapplication.Service;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.ContentObserver;
|
||||
import android.database.Cursor;
|
||||
import android.location.Location;
|
||||
import android.location.LocationManager;
|
||||
import android.media.ExifInterface;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.room.Room;
|
||||
|
||||
import com.example.myapplication.DataBase.AppDatabase;
|
||||
import com.example.myapplication.R;
|
||||
import com.example.myapplication.Tool.BackgroundToast;
|
||||
import com.example.myapplication.Tool.NotificationReceiver;
|
||||
import com.example.myapplication.model.ImageEntity;
|
||||
import com.example.myapplication.model.SharedDataManager;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class PhotoMonitoringService extends Service implements SharedDataManager.DataChangeListener {
|
||||
private static final String CHANNEL_ID = "PhotoMonitoringChannel";
|
||||
private static final int NOTIFICATION_ID = 101;
|
||||
private static final int THUMBNAIL_SIZE = 100;
|
||||
|
||||
// 锁对象
|
||||
private final Object monitoringLock = new Object();
|
||||
private final Object uploadLock = new Object();
|
||||
private long lastSuccessToastTime = 0;
|
||||
private static final long TOAST_THROTTLE_INTERVAL = 1500;
|
||||
|
||||
// 状态标志
|
||||
private boolean isMonitoring = false;
|
||||
private boolean isUploading = false;
|
||||
private boolean isRecording = false;
|
||||
private int successCount = 0;
|
||||
private int totalUploadedCount = 0; // 新增:总上传成功计数
|
||||
|
||||
// 组件
|
||||
private ContentObserver contentObserver;
|
||||
private final ExecutorService executor = Executors.newFixedThreadPool(5);
|
||||
private final OkHttpClient httpClient = new OkHttpClient.Builder()
|
||||
.connectTimeout(30, TimeUnit.SECONDS) // 连接超时
|
||||
.readTimeout(60, TimeUnit.SECONDS) // 读取超时
|
||||
.writeTimeout(60, TimeUnit.SECONDS) // 写入超时
|
||||
.build();
|
||||
|
||||
// 数据库
|
||||
private AppDatabase db;
|
||||
private AppDatabase db2;
|
||||
|
||||
// 位置相关
|
||||
private Location lastKnownLocation;
|
||||
private LocationManager locationManager;
|
||||
|
||||
// 配置参数
|
||||
private String token;
|
||||
private String user;
|
||||
private String projectId;
|
||||
private String partId;
|
||||
private String partName;
|
||||
private String turbineId;
|
||||
private String turbineName;
|
||||
private String Temperator;
|
||||
private String Humidity;
|
||||
private String Weather;
|
||||
private String ChooseImageSource;
|
||||
|
||||
// 其他状态
|
||||
private long lastPhotoId = 0;
|
||||
private String currentAudioPath="-1";
|
||||
private String imgpath="-1";
|
||||
private String UserId;
|
||||
|
||||
|
||||
@Override
|
||||
public void onImageIdChanged(String newId) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTokenChanged(String newToken) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserChanged(String newUser) {
|
||||
user=newUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioPathChanged(String newAudioPath) {
|
||||
currentAudioPath=newAudioPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProjectIdChanged(String newProjectId) {
|
||||
projectId=newProjectId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPartIdChanged(String newPartId) {
|
||||
partId =newPartId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPartNameChanged(String newPartName) {
|
||||
partName = newPartName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTurbineIdChanged(String newTurbineId) {
|
||||
turbineId =newTurbineId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTurbineNameChanged(String newTurbineName) {
|
||||
turbineName = newTurbineName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChooseImageSourceChanged(String newChooseImageSource) {
|
||||
ChooseImageSource=newChooseImageSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIsRecordingChanged(boolean newisRecording) {
|
||||
isRecording=newisRecording;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUploadingStatusChanged(boolean isUploading) {
|
||||
this.isUploading=isUploading;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChanged(Location newLocation) {
|
||||
lastKnownLocation=newLocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOtherMsgChanged(String msg) {
|
||||
|
||||
}
|
||||
|
||||
SharedDataManager dataManager;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
createNotificationChannel();
|
||||
SharedPreferences prefs = getSharedPreferences("WeatherPrefs", MODE_PRIVATE);
|
||||
Weather = prefs.getString("weather", "0");
|
||||
Temperator = prefs.getString("temperature", "0");
|
||||
Humidity = prefs.getString("humidity", "0");
|
||||
SharedPreferences sharedPreferences = getSharedPreferences("LoginPrefs", Context.MODE_PRIVATE);
|
||||
// 初始化数据库
|
||||
UserId=sharedPreferences.getString("userid","-1");
|
||||
db = Room.databaseBuilder(getApplicationContext(),
|
||||
AppDatabase.class, "image-database").build();
|
||||
db2 = Room.databaseBuilder(getApplicationContext(),
|
||||
AppDatabase.class, "image-database2").build();
|
||||
|
||||
// 初始化位置管理器
|
||||
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
|
||||
dataManager=SharedDataManager.getInstance();
|
||||
|
||||
dataManager.addListener(this);
|
||||
updateFromSharedData();
|
||||
|
||||
// 初始化时从数据库获取已上传成功的数量
|
||||
|
||||
totalUploadedCount = 0;
|
||||
updateNotification("partId"+partId);
|
||||
|
||||
}
|
||||
|
||||
private void updateFromSharedData() {
|
||||
dataManager = SharedDataManager.getInstance();
|
||||
this.token = dataManager.getToken();
|
||||
this.user = dataManager.getUser();
|
||||
this.currentAudioPath = dataManager.getAudioPath();
|
||||
this.projectId = dataManager.getProjectId();
|
||||
this.partId = dataManager.getPartId();
|
||||
this.turbineId = dataManager.getTurbineId();
|
||||
this.ChooseImageSource = dataManager.getChooseImageSource();
|
||||
this.isUploading=dataManager.getisUploading();
|
||||
this.lastKnownLocation=dataManager.getLocation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (intent != null && "start_monitoring".equals(intent.getStringExtra("command"))) {
|
||||
startMonitoring();
|
||||
}
|
||||
|
||||
startForeground(NOTIFICATION_ID, buildNotification("服务正在运行", "监听照片变化中"));
|
||||
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
stopMonitoring();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
public void startMonitoring() {
|
||||
synchronized (monitoringLock) {
|
||||
if (isMonitoring) return;
|
||||
|
||||
lastPhotoId = getLastPhotoId();
|
||||
|
||||
contentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange, Uri uri) {
|
||||
super.onChange(selfChange, uri);
|
||||
if (!isRecording && uri != null && uri.toString().contains("images/media")) {
|
||||
checkNewPhotos();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
getContentResolver().registerContentObserver(
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||
true,
|
||||
contentObserver
|
||||
);
|
||||
|
||||
isMonitoring = true;
|
||||
|
||||
executor.execute(() -> {
|
||||
List<ImageEntity> history = db2.imageDao().getAll();
|
||||
if (history != null && !history.isEmpty()) {
|
||||
synchronized (uploadLock) {
|
||||
for (ImageEntity image : history) {
|
||||
uploadPhoto(image.path, image.time, lastKnownLocation, image.user,
|
||||
image.audioPath, image.project, image.partId, image.turbineId,
|
||||
image.imageSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
db2.imageDao().deleteAll();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void stopMonitoring() {
|
||||
synchronized (monitoringLock) {
|
||||
if (!isMonitoring) return;
|
||||
|
||||
if (contentObserver != null) {
|
||||
getContentResolver().unregisterContentObserver(contentObserver);
|
||||
contentObserver = null;
|
||||
}
|
||||
|
||||
isMonitoring=false;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkNewPhotos() {
|
||||
String[] projection = {
|
||||
MediaStore.Images.Media._ID,
|
||||
MediaStore.Images.Media.DATA,
|
||||
MediaStore.Images.Media.DATE_ADDED,
|
||||
MediaStore.Images.Media.DATE_TAKEN,
|
||||
MediaStore.Images.Media.LATITUDE,
|
||||
MediaStore.Images.Media.LONGITUDE
|
||||
};
|
||||
|
||||
String selection = MediaStore.Images.Media._ID + " > ? AND " +
|
||||
MediaStore.Images.Media.DATE_TAKEN + " > 0 AND " +
|
||||
"(" + MediaStore.Images.Media.DATA + " LIKE '%/DCIM/%' OR " +
|
||||
MediaStore.Images.Media.DATA + " LIKE '%/Camera/%')";
|
||||
String[] selectionArgs = new String[]{String.valueOf(lastPhotoId)};
|
||||
|
||||
try (Cursor cursor = getContentResolver().query(
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||
projection,
|
||||
selection,
|
||||
selectionArgs,
|
||||
MediaStore.Images.Media._ID + " DESC"
|
||||
)) {
|
||||
if (cursor != null) {
|
||||
while (cursor.moveToNext()) {
|
||||
processNewPhoto(cursor);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("PhotoMonitoringService", "Error checking new photos", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void processNewPhoto(Cursor cursor) {
|
||||
long id = cursor.getLong(0);
|
||||
String path = cursor.getString(1);
|
||||
imgpath = path;
|
||||
|
||||
long time = cursor.getLong(2) * 1000;
|
||||
double latitude = cursor.getDouble(3);
|
||||
double longitude = cursor.getDouble(4);
|
||||
double altitude = 0.0;
|
||||
|
||||
if (lastKnownLocation != null) {
|
||||
latitude = lastKnownLocation.getLatitude();
|
||||
longitude = lastKnownLocation.getLongitude();
|
||||
altitude = lastKnownLocation.getAltitude();
|
||||
}
|
||||
|
||||
try {
|
||||
ExifInterface exif = new ExifInterface(path);
|
||||
String altitudeStr = exif.getAttribute(ExifInterface.TAG_GPS_ALTITUDE);
|
||||
if (altitudeStr != null) {
|
||||
altitude = Double.parseDouble(altitudeStr);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("PhotoMonitoringService", "Error reading EXIF data", e);
|
||||
}
|
||||
|
||||
String audioPath = currentAudioPath;
|
||||
lastPhotoId = Math.max(lastPhotoId, id);
|
||||
|
||||
if (isNetworkAvailable() && !isRecording) {
|
||||
uploadNewPhoto(path, time, audioPath);
|
||||
} else {
|
||||
queuePhotoForLater(path, time, audioPath, latitude, longitude, altitude);
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadNewPhoto(String path, long time, String audioPath) {
|
||||
executor.execute(() -> {
|
||||
List<ImageEntity> history = db2.imageDao().getAll();
|
||||
if (history != null && !history.isEmpty()) {
|
||||
synchronized (uploadLock) {
|
||||
for (ImageEntity image : history) {
|
||||
uploadPhoto(image.path, image.time, lastKnownLocation,
|
||||
image.user, image.audioPath, image.project,
|
||||
image.turbineId, image.partId, image.imageSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
db2.imageDao().deleteAll();
|
||||
|
||||
uploadPhoto(path, time, lastKnownLocation, user,
|
||||
audioPath, projectId, partId, turbineId, partId);
|
||||
});
|
||||
}
|
||||
|
||||
private void queuePhotoForLater(String path, long time, String audioPath,
|
||||
double latitude, double longitude, double altitude) {
|
||||
executor.execute(() -> {
|
||||
db2.imageDao().insert(new ImageEntity(path, time, latitude, longitude, altitude,
|
||||
user, audioPath, projectId, partId, partName, turbineId, turbineName, false,
|
||||
Temperator, Humidity, Weather, ChooseImageSource));
|
||||
});
|
||||
}
|
||||
|
||||
private void uploadPhoto(String filePath, long time, Location location, String user,
|
||||
String audioPath, String project, String turbineId,
|
||||
String partId, String imageSource) {
|
||||
synchronized (uploadLock) {
|
||||
if (isUploading) {
|
||||
queuePhotoForLater(filePath, time, audioPath,
|
||||
location != null ? location.getLatitude() : 0,
|
||||
location != null ? location.getLongitude() : 0,
|
||||
location != null ? location.getAltitude() : 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
File imageFile = new File(filePath);
|
||||
if (!imageFile.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
double latitude = location != null ? location.getLatitude() : 0;
|
||||
double longitude = location != null ? location.getLongitude() : 0;
|
||||
double altitude = location != null ? location.getAltitude() : 0;
|
||||
|
||||
// 构建请求URL
|
||||
HttpUrl.Builder urlBuilder = HttpUrl.parse("http://pms.dtyx.net:9158/image/" + imageSource + "/upload/" + partId)
|
||||
.newBuilder()
|
||||
.addQueryParameter("collectorId",UserId)
|
||||
.addQueryParameter("collectorName", user)
|
||||
.addQueryParameter("humidness", String.valueOf(Humidity))
|
||||
.addQueryParameter("shootingDistance", String.valueOf(0)) // 根据需要设置实际值
|
||||
.addQueryParameter("temperatureMax", String.valueOf(Temperator))
|
||||
.addQueryParameter("temperatureMin", String.valueOf(Temperator))
|
||||
.addQueryParameter("weather", Weather)
|
||||
.addQueryParameter("windLevel", String.valueOf(0)); // 根据需要设置实际值
|
||||
|
||||
// 创建请求体
|
||||
RequestBody requestBody = new MultipartBody.Builder()
|
||||
.setType(MultipartBody.FORM)
|
||||
.addFormDataPart("file", imageFile.getName(),
|
||||
RequestBody.create(imageFile, MediaType.parse("image/*")))
|
||||
.build();
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(urlBuilder.build())
|
||||
.post(requestBody)
|
||||
.addHeader("Authorization", token)
|
||||
.addHeader("Content-Type", "application/x-www-form-urlencoded")
|
||||
.build();
|
||||
|
||||
try (Response response = httpClient.newCall(request).execute()) {
|
||||
if (response.isSuccessful()) {
|
||||
// 解析响应数据
|
||||
String responseData = response.body().string();
|
||||
JSONObject jsonResponse = new JSONObject(responseData);
|
||||
JSONObject data = jsonResponse.optJSONObject("data");
|
||||
System.out.println(responseData);
|
||||
String imageId = data != null ? data.optString("imageId") : "";
|
||||
dataManager.setImageId(imageId);
|
||||
dataManager.setOthermsg("partid:"+partId+"\n"+"imageid:"+imageId+"\n"+"response:"+responseData);
|
||||
db.imageDao().insert(new ImageEntity(filePath, time, latitude, longitude, altitude,
|
||||
user, audioPath, project, partId, partName, turbineId, turbineName, true,
|
||||
Temperator, Humidity, Weather, ChooseImageSource));
|
||||
|
||||
totalUploadedCount++;
|
||||
updateNotification(imageId);
|
||||
Intent intent = new Intent("RESPONSE_ACTION");
|
||||
intent.putExtra("response", response.toString());
|
||||
sendBroadcast(intent);
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if (currentTime - lastSuccessToastTime > TOAST_THROTTLE_INTERVAL) {
|
||||
BackgroundToast.show(this, "图片上传成功,已上传" + totalUploadedCount + "张");
|
||||
lastSuccessToastTime = currentTime;
|
||||
}
|
||||
} else {
|
||||
db2.imageDao().insert(new ImageEntity(filePath, time, latitude, longitude, altitude,
|
||||
user, audioPath, project, partId, partName, turbineId, turbineName, true,
|
||||
Temperator, Humidity, Weather, ChooseImageSource));
|
||||
dataManager.setImageId("-1");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
db2.imageDao().insert(new ImageEntity(filePath, time, 0, 0, 0,
|
||||
user, audioPath, project, partId, partName, turbineId, turbineName, true,
|
||||
Temperator, Humidity, Weather, ChooseImageSource));
|
||||
dataManager.setImageId("-1");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateNotification(String msg) {
|
||||
String content = "已成功上传 " + totalUploadedCount + " 张照片"+msg;
|
||||
Notification notification = buildNotification("照片上传服务运行中", content);
|
||||
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
if (manager != null) {
|
||||
manager.notify(NOTIFICATION_ID, notification);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isNetworkAvailable() {
|
||||
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
if (cm == null) return false;
|
||||
|
||||
NetworkCapabilities capabilities = cm.getNetworkCapabilities(cm.getActiveNetwork());
|
||||
return capabilities != null &&
|
||||
(capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
|
||||
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
|
||||
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET));
|
||||
}
|
||||
|
||||
private void createNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = new NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
"照片上传服务",
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
);
|
||||
channel.setDescription("显示照片上传状态和进度");
|
||||
NotificationManager manager = getSystemService(NotificationManager.class);
|
||||
manager.createNotificationChannel(channel);
|
||||
}
|
||||
}
|
||||
|
||||
private Notification buildNotification(String title, String content) {
|
||||
return new NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.setContentTitle(title)
|
||||
.setContentText(content)
|
||||
.setSmallIcon(R.drawable.ic_upload)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setOngoing(true)
|
||||
.setOnlyAlertOnce(true)
|
||||
.addAction(createStopAction())
|
||||
.build();
|
||||
}
|
||||
|
||||
private NotificationCompat.Action createStopAction() {
|
||||
Intent stopIntent = new Intent(this, NotificationReceiver.class);
|
||||
stopIntent.setAction("ACTION_STOP_SERVICE");
|
||||
PendingIntent stopPendingIntent = PendingIntent.getBroadcast(
|
||||
this,
|
||||
0,
|
||||
stopIntent,
|
||||
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
return new NotificationCompat.Action(
|
||||
R.drawable.ic_stop, "停止", stopPendingIntent);
|
||||
}
|
||||
|
||||
private long getLastPhotoId() {
|
||||
long id = -1;
|
||||
Cursor cursor = getContentResolver().query(
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||
new String[]{"MAX(" + MediaStore.Images.Media._ID + ")"},
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
id = cursor.getLong(0);
|
||||
cursor.close();
|
||||
}
|
||||
return id;
|
||||
}
|
||||
}
|
|
@ -1,95 +1,431 @@
|
|||
package com.example.myapplication.Service;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
|
||||
import com.example.myapplication.R;
|
||||
|
||||
public class RecordingService extends Service {
|
||||
private static final String CHANNEL_ID = "recording_channel";
|
||||
private static final int NOTIFICATION_ID = 1;
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.R)
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
try {
|
||||
createNotificationChannel();
|
||||
// 启动前台服务(必须5秒内调用startForeground)
|
||||
Notification notification = buildNotification("录音服务运行中");
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
// Android 10+ 需要指定类型
|
||||
startForeground(NOTIFICATION_ID, notification,
|
||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
|
||||
} else {
|
||||
// Android 8.0-9.0
|
||||
startForeground(NOTIFICATION_ID, notification);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(this,"错误:"+e,Toast.LENGTH_LONG).show();
|
||||
stopSelf();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void createNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = new NotificationChannel(
|
||||
"recording_channel",
|
||||
"Recording Service",
|
||||
NotificationManager.IMPORTANCE_LOW);
|
||||
NotificationManager manager = getSystemService(NotificationManager.class);
|
||||
if (manager != null) {
|
||||
manager.createNotificationChannel(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Notification buildNotification(String text) {
|
||||
// 确保图标有效(R.drawable.ic_mic_on必须存在)
|
||||
return new NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_mic_on)
|
||||
.setContentTitle("录音服务")
|
||||
.setContentText(text)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.build();
|
||||
}
|
||||
|
||||
public void updateNotification(String text) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
if (ActivityCompat.checkSelfPermission(this,
|
||||
android.Manifest.permission.POST_NOTIFICATIONS)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
// 无权限时不显示通知(避免崩溃)
|
||||
return;
|
||||
}
|
||||
}
|
||||
NotificationManagerCompat.from(this)
|
||||
.notify(NOTIFICATION_ID, buildNotification(text));
|
||||
}
|
||||
package com.example.myapplication.Service;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.location.Location;
|
||||
import android.media.MediaRecorder;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
|
||||
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.room.Room;
|
||||
|
||||
import com.example.myapplication.DataBase.AppDatabase;
|
||||
|
||||
import com.example.myapplication.R;
|
||||
import com.example.myapplication.Tool.BackgroundToast;
|
||||
import com.example.myapplication.model.AudioEntity;
|
||||
import com.example.myapplication.model.SharedDataManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class RecordingService extends Service implements SharedDataManager.DataChangeListener {
|
||||
private static final String CHANNEL_ID = "recording_channel";
|
||||
private static final int NOTIFICATION_ID = 2;
|
||||
private MediaRecorder mediaRecorder;
|
||||
private String currentAudioPath;
|
||||
private Handler handler;
|
||||
private Runnable updateTimerRunnable;
|
||||
private AppDatabase db;
|
||||
private AppDatabase db2;
|
||||
private final ExecutorService executor = Executors.newFixedThreadPool(4);
|
||||
private String ImageId="-1";
|
||||
private String currentToken="-1";
|
||||
private SharedDataManager dataManager;
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private final OkHttpClient httpClient = new OkHttpClient.Builder()
|
||||
.connectTimeout(30, TimeUnit.SECONDS) // 连接超时
|
||||
.readTimeout(60, TimeUnit.SECONDS) // 读取超时
|
||||
.writeTimeout(60, TimeUnit.SECONDS) // 写入超时
|
||||
.build();
|
||||
private int recordingSeconds = 0;
|
||||
@RequiresApi(api = Build.VERSION_CODES.R)
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
try {
|
||||
createNotificationChannel();
|
||||
// 启动前台服务(必须5秒内调用startForeground)
|
||||
Notification notification = buildNotification("录音服务运行中");
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
// Android 10+ 需要指定类型
|
||||
startForeground(NOTIFICATION_ID, notification,
|
||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
|
||||
} else {
|
||||
// Android 8.0-9.0
|
||||
startForeground(NOTIFICATION_ID, notification);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(this,"错误:"+e,Toast.LENGTH_LONG).show();
|
||||
stopSelf();
|
||||
}
|
||||
handler = new Handler(Looper.getMainLooper());
|
||||
db = Room.databaseBuilder(getApplicationContext(),
|
||||
AppDatabase.class, "image-database").build();
|
||||
db2 = Room.databaseBuilder(getApplicationContext(),
|
||||
AppDatabase.class, "image-database2").build();
|
||||
createNotificationChannel();
|
||||
|
||||
|
||||
// 在 onCreate() 中改进 ViewModel 初始化
|
||||
|
||||
// 观察LiveData变化
|
||||
// 在类成员变量中添加
|
||||
|
||||
dataManager=SharedDataManager.getInstance();
|
||||
dataManager.addListener(this);
|
||||
|
||||
currentToken = dataManager.getToken();
|
||||
ImageId=dataManager.getImageId();
|
||||
// 在onCreate()中改为使用成员变量观察
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onImageIdChanged(String newId) {
|
||||
ImageId=newId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTokenChanged(String newToken) {
|
||||
currentToken = newToken;
|
||||
Log.d("Service", "Token updated: " + newToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserChanged(String newUser) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioPathChanged(String newAudioPath) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProjectIdChanged(String newProjectId) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPartIdChanged(String newUnit) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTurbineIdChanged(String newTurbineId) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPartNameChanged(String newPartName) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTurbineNameChanged(String newTurbineName) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChooseImageSourceChanged(String newChooseImageSource) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIsRecordingChanged(boolean newisRecording) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUploadingStatusChanged(boolean isUploading) {
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChanged(Location newLocation) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOtherMsgChanged(String msg) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 获取当前数据(可选,如果 Activity 需要读取)
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
|
||||
if (intent != null && "start_recording".equals(intent.getAction())) {
|
||||
startRecording();
|
||||
} else if (intent != null && "stop_recording".equals(intent.getAction())) {
|
||||
stopRecording();
|
||||
}
|
||||
return START_REDELIVER_INTENT;
|
||||
}
|
||||
private void startRecording() {
|
||||
|
||||
try {
|
||||
dataManager.setIsRecording(true);
|
||||
// 1. 设置录音文件路径
|
||||
currentAudioPath = getExternalCacheDir().getAbsolutePath()
|
||||
+ "/" + System.currentTimeMillis() + ".3gp";
|
||||
dataManager.setAudioPath(currentAudioPath);
|
||||
// 2. 初始化MediaRecorder
|
||||
mediaRecorder = new MediaRecorder();
|
||||
|
||||
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
|
||||
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
|
||||
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
|
||||
mediaRecorder.setOutputFile(currentAudioPath);
|
||||
mediaRecorder.prepare();
|
||||
mediaRecorder.start();
|
||||
|
||||
// 3. 启动前台服务
|
||||
startForegroundService();
|
||||
|
||||
// 4. 开始计时更新通知
|
||||
startUpdatingTimer();
|
||||
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(this, "录音失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
stopSelf();
|
||||
}
|
||||
}
|
||||
|
||||
private void startForegroundService() {
|
||||
Notification notification = buildNotification("录音中... 00:00");
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
startForeground(NOTIFICATION_ID, notification,
|
||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
|
||||
} else {
|
||||
startForeground(NOTIFICATION_ID, notification);
|
||||
}
|
||||
}
|
||||
|
||||
private void startUpdatingTimer() {
|
||||
updateTimerRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
recordingSeconds++;
|
||||
String time = String.format("%02d:%02d",
|
||||
recordingSeconds / 60, recordingSeconds % 60);
|
||||
updateNotification("录音中... " + time);
|
||||
handler.postDelayed(this, 1000);
|
||||
}
|
||||
};
|
||||
handler.post(updateTimerRunnable);
|
||||
}
|
||||
private void uploadAudio(String imageId, String audioPath, String token) {
|
||||
// 1. 参数校验(在调用线程执行,避免不必要的后台任务)
|
||||
if (audioPath == null || audioPath.equals("0")) {
|
||||
BackgroundToast.show(this, "文件路径为空");
|
||||
return;
|
||||
}
|
||||
|
||||
File audioFile = new File(audioPath);
|
||||
if (!audioFile.exists()) {
|
||||
BackgroundToast.show(this, "文件不存在");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 在后台线程执行上传逻辑
|
||||
executor.execute(() -> {
|
||||
// 3. 构建请求体(已在后台线程)
|
||||
RequestBody requestBody = new MultipartBody.Builder()
|
||||
.setType(MultipartBody.FORM)
|
||||
.addFormDataPart("file", audioFile.getName(),
|
||||
RequestBody.create(audioFile, MediaType.parse("audio/*")))
|
||||
.build();
|
||||
|
||||
// 4. 构建请求
|
||||
Request.Builder requestBuilder = new Request.Builder()
|
||||
.url("http://pms.dtyx.net:9158/audio/upload/" + imageId)
|
||||
.post(requestBody);
|
||||
|
||||
if (token != null && !token.isEmpty()) {
|
||||
requestBuilder.addHeader("Authorization", token);
|
||||
}
|
||||
|
||||
// 5. 同步执行请求(try-with-resources 自动关闭 Response)
|
||||
try (Response response = httpClient.newCall(requestBuilder.build()).execute()) {
|
||||
String currentTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
|
||||
.format(new Date());
|
||||
|
||||
if (response.isSuccessful()) {
|
||||
// 上传成功
|
||||
executor.execute(()->{ db.AudioDao().deleteByPath(audioPath);
|
||||
|
||||
db2.AudioDao().insert(new AudioEntity(audioPath, imageId, currentTime,true));});
|
||||
BackgroundToast.show(this, "录音上传成功: " + imageId);
|
||||
stopSelf();
|
||||
} else {
|
||||
// 上传失败(HTTP 错误)
|
||||
executor.execute(()->{ db.AudioDao().insert(new AudioEntity(audioPath, imageId, currentTime,false));});
|
||||
BackgroundToast.show(this,
|
||||
"录音上传失败: " + response.code() + " " + response.message());
|
||||
stopSelf();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// 网络异常
|
||||
String currentTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
|
||||
.format(new Date());
|
||||
|
||||
executor.execute(() -> {
|
||||
db.AudioDao().insert(new AudioEntity(currentAudioPath, ImageId, currentToken, false));
|
||||
});
|
||||
BackgroundToast.show(this, "录音上传失败: " + e.getMessage());
|
||||
Log.e("录音上传失败:",e.toString());
|
||||
stopSelf();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void stopRecording() {
|
||||
try {
|
||||
// 1. 停止录音
|
||||
if (mediaRecorder != null) {
|
||||
mediaRecorder.stop();
|
||||
mediaRecorder.release();
|
||||
mediaRecorder = null;
|
||||
}
|
||||
dataManager.setIsRecording(false);
|
||||
// 2. 停止计时器
|
||||
if (handler != null && updateTimerRunnable != null) {
|
||||
handler.removeCallbacks(updateTimerRunnable);
|
||||
}
|
||||
// 3. 更新通知
|
||||
updateNotification("录音已保存");
|
||||
// 4. 上传录音文件(如果有需要)
|
||||
if (currentAudioPath != null && ImageId != "-1") {
|
||||
BackgroundToast.show(this,"开始上传录音了");
|
||||
uploadAudio(ImageId, currentAudioPath, currentToken);
|
||||
} else
|
||||
{
|
||||
BackgroundToast.show(this,"Imageid不存在,上传录音失败");
|
||||
executor.execute(() -> {
|
||||
db.AudioDao().insert(new AudioEntity(currentAudioPath, ImageId, currentToken, false));
|
||||
});
|
||||
}
|
||||
|
||||
// 5. 不立即停止服务,等待上传完成
|
||||
// 可以通过其他机制(如广播或LiveData)在上传完成后通知服务停止
|
||||
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(this, "停止录音失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
dataManager.setIsRecording(false);
|
||||
stopSelf();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void createNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = new NotificationChannel(
|
||||
"recording_channel",
|
||||
"Recording Service",
|
||||
NotificationManager.IMPORTANCE_LOW);
|
||||
NotificationManager manager = getSystemService(NotificationManager.class);
|
||||
if (manager != null) {
|
||||
manager.createNotificationChannel(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Notification buildNotification(String text) {
|
||||
// 确保图标有效(R.drawable.ic_mic_on必须存在)
|
||||
return new NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_mic_on)
|
||||
.setContentTitle("录音服务")
|
||||
.setContentText(text)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.build();
|
||||
}
|
||||
|
||||
public void updateNotification(String text) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
if (ActivityCompat.checkSelfPermission(this,
|
||||
android.Manifest.permission.POST_NOTIFICATIONS)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
// 无权限时不显示通知(避免崩溃)
|
||||
return;
|
||||
}
|
||||
}
|
||||
NotificationManagerCompat.from(this)
|
||||
.notify(NOTIFICATION_ID, buildNotification(text));
|
||||
}
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
|
||||
// 2. 停止并释放MediaRecorder
|
||||
if (mediaRecorder != null) {
|
||||
try {
|
||||
mediaRecorder.stop();
|
||||
mediaRecorder.release();
|
||||
} catch (IllegalStateException e) {
|
||||
Log.e("Service", "MediaRecorder释放异常", e);
|
||||
}
|
||||
mediaRecorder = null;
|
||||
}
|
||||
|
||||
// 3. 移除Handler回调
|
||||
if (handler != null && updateTimerRunnable != null) {
|
||||
handler.removeCallbacks(updateTimerRunnable);
|
||||
}
|
||||
|
||||
|
||||
dataManager.removeListener(this);
|
||||
// 5. 关闭数据库(如果Room支持)
|
||||
if (db != null) {
|
||||
db.close();
|
||||
}
|
||||
if (db2 != null) {
|
||||
db2.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +1,26 @@
|
|||
package com.example.myapplication.Tool;
|
||||
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import java.io.IOException;
|
||||
|
||||
public class AuthInterceptor implements Interceptor {
|
||||
private String authToken;
|
||||
|
||||
public AuthInterceptor(String token) {
|
||||
this.authToken = token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response intercept(Chain chain) throws IOException {
|
||||
Request originalRequest = chain.request();
|
||||
|
||||
// 添加 Authorization header
|
||||
Request newRequest = originalRequest.newBuilder()
|
||||
.header("Authorization", authToken)
|
||||
.build();
|
||||
|
||||
return chain.proceed(newRequest);
|
||||
}
|
||||
package com.example.myapplication.Tool;
|
||||
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import java.io.IOException;
|
||||
|
||||
public class AuthInterceptor implements Interceptor {
|
||||
private String authToken;
|
||||
|
||||
public AuthInterceptor(String token) {
|
||||
this.authToken = token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response intercept(Chain chain) throws IOException {
|
||||
Request originalRequest = chain.request();
|
||||
|
||||
// 添加 Authorization header
|
||||
Request newRequest = originalRequest.newBuilder()
|
||||
.header("Authorization", authToken)
|
||||
.build();
|
||||
|
||||
return chain.proceed(newRequest);
|
||||
}
|
||||
}
|
|
@ -1,26 +1,33 @@
|
|||
package com.example.myapplication.Tool;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class BackgroundToast {
|
||||
private static Handler handler;
|
||||
private static Toast currentToast;
|
||||
|
||||
public static void show(final Context context, final String message) {
|
||||
if (handler == null) {
|
||||
handler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
handler.post(() -> {
|
||||
if (currentToast != null) {
|
||||
currentToast.cancel();
|
||||
}
|
||||
|
||||
currentToast = Toast.makeText(context.getApplicationContext(), message, Toast.LENGTH_SHORT);
|
||||
currentToast.show();
|
||||
});
|
||||
}
|
||||
package com.example.myapplication.Tool;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.Gravity;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class BackgroundToast {
|
||||
private static Handler handler;
|
||||
private static Toast currentToast;
|
||||
private static long lastShowTime = 0;
|
||||
|
||||
public static void show(final Context context, final String message) {
|
||||
if (handler == null) {
|
||||
handler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
long currentTime = System.currentTimeMillis();
|
||||
|
||||
|
||||
handler.post(() -> {
|
||||
if (currentToast != null) {
|
||||
currentToast.cancel();
|
||||
}
|
||||
|
||||
currentToast = Toast.makeText(context.getApplicationContext(), message, Toast.LENGTH_LONG);
|
||||
currentToast.setGravity(Gravity.CENTER, 0, 0);
|
||||
currentToast.show();
|
||||
lastShowTime = currentTime;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,115 +1,115 @@
|
|||
package com.example.myapplication.Tool;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.util.Log;
|
||||
|
||||
import com.example.myapplication.DataBase.DatabaseHelper;
|
||||
import com.example.myapplication.api.CommonService;
|
||||
import com.example.myapplication.model.ApiResponse;
|
||||
import com.example.myapplication.model.ImageSourceItem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
import retrofit2.Retrofit;
|
||||
|
||||
|
||||
public class CommonImageSourceFetcher {
|
||||
private String token;
|
||||
private final CommonService commonService;
|
||||
private final DatabaseHelper dbHelper;
|
||||
private final Context context;
|
||||
public CommonImageSourceFetcher(String token,Context context) {
|
||||
|
||||
this.token=token;
|
||||
this.dbHelper = new DatabaseHelper(context);
|
||||
this.context = context;
|
||||
Retrofit retrofit =RetrofitClient.getClient(token);
|
||||
|
||||
|
||||
this.commonService = retrofit.create(CommonService.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步获取图像来源列表
|
||||
*/
|
||||
public List<ImageSourceItem> fetchImageSourceListSync() throws IOException {
|
||||
if (isNetworkAvailable()) {
|
||||
Call<ApiResponse<List<ImageSourceItem>>> call = commonService.getImageSourceList();
|
||||
|
||||
Response<ApiResponse<List<ImageSourceItem>>> response = call.execute();
|
||||
|
||||
if (response.isSuccessful() && response.body() != null && response.body().isSuccess()) {
|
||||
List<ImageSourceItem> result = response.body().getData();
|
||||
// 保存到数据库
|
||||
dbHelper.saveImageSources(result);
|
||||
return result;
|
||||
} else {
|
||||
throw new IOException("请求失败,状态码: " + response.code());
|
||||
}
|
||||
} else {
|
||||
// 无网络时从数据库获取
|
||||
return dbHelper.getImageSources();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步获取图像来源列表
|
||||
*/
|
||||
public void fetchImageSourceListAsync(ImageSourceCallback callback) {
|
||||
List<ImageSourceItem> cachedSources = dbHelper.getImageSources();
|
||||
if (!cachedSources.isEmpty()) {
|
||||
callback.onSuccess(cachedSources);
|
||||
}
|
||||
|
||||
// 检查网络连接
|
||||
if (isNetworkAvailable()) {
|
||||
Call<ApiResponse<List<ImageSourceItem>>> call = commonService.getImageSourceList();
|
||||
|
||||
call.enqueue(new Callback<ApiResponse<List<ImageSourceItem>>>() {
|
||||
@Override
|
||||
public void onResponse(Call<ApiResponse<List<ImageSourceItem>>> call,
|
||||
Response<ApiResponse<List<ImageSourceItem>>> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().isSuccess()) {
|
||||
List<ImageSourceItem> result = response.body().getData();
|
||||
|
||||
Log.e("data信息:", result != null ? result.toString() : "Result is null");
|
||||
// 保存到数据库
|
||||
dbHelper.saveImageSources(result);
|
||||
callback.onSuccess(result);
|
||||
} else if (cachedSources.isEmpty()) {
|
||||
callback.onFailure(new IOException("请求失败,状态码: " + response.code()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<ApiResponse<List<ImageSourceItem>>> call, Throwable t) {
|
||||
if (cachedSources.isEmpty()) {
|
||||
callback.onFailure(t);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (cachedSources.isEmpty()) {
|
||||
callback.onFailure(new IOException("无网络连接且无缓存数据"));
|
||||
}
|
||||
}
|
||||
private boolean isNetworkAvailable() {
|
||||
ConnectivityManager connectivityManager =
|
||||
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
if (connectivityManager != null) {
|
||||
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
|
||||
return activeNetworkInfo != null && activeNetworkInfo.isConnected();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public interface ImageSourceCallback {
|
||||
void onSuccess(List<ImageSourceItem> imageSources);
|
||||
void onFailure(Throwable t);
|
||||
}
|
||||
}
|
||||
package com.example.myapplication.Tool;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.util.Log;
|
||||
|
||||
import com.example.myapplication.DataBase.DatabaseHelper;
|
||||
import com.example.myapplication.api.CommonService;
|
||||
import com.example.myapplication.model.ApiResponse;
|
||||
import com.example.myapplication.model.ImageSourceItem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
import retrofit2.Retrofit;
|
||||
|
||||
|
||||
public class CommonImageSourceFetcher {
|
||||
private String token;
|
||||
private final CommonService commonService;
|
||||
private final DatabaseHelper dbHelper;
|
||||
private final Context context;
|
||||
public CommonImageSourceFetcher(String token,Context context) {
|
||||
|
||||
this.token=token;
|
||||
this.dbHelper = new DatabaseHelper(context);
|
||||
this.context = context;
|
||||
Retrofit retrofit =RetrofitClient.getClient(token);
|
||||
|
||||
|
||||
this.commonService = retrofit.create(CommonService.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步获取图像来源列表
|
||||
*/
|
||||
public List<ImageSourceItem> fetchImageSourceListSync() throws IOException {
|
||||
if (isNetworkAvailable()) {
|
||||
Call<ApiResponse<List<ImageSourceItem>>> call = commonService.getImageSourceList();
|
||||
|
||||
Response<ApiResponse<List<ImageSourceItem>>> response = call.execute();
|
||||
|
||||
if (response.isSuccessful() && response.body() != null && response.body().isSuccess()) {
|
||||
List<ImageSourceItem> result = response.body().getData();
|
||||
// 保存到数据库
|
||||
dbHelper.saveImageSources(result);
|
||||
return result;
|
||||
} else {
|
||||
throw new IOException("请求失败,状态码: " + response.code());
|
||||
}
|
||||
} else {
|
||||
// 无网络时从数据库获取
|
||||
return dbHelper.getImageSources();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步获取图像来源列表
|
||||
*/
|
||||
public void fetchImageSourceListAsync(ImageSourceCallback callback) {
|
||||
List<ImageSourceItem> cachedSources = dbHelper.getImageSources();
|
||||
if (!cachedSources.isEmpty()) {
|
||||
callback.onSuccess(cachedSources);
|
||||
}
|
||||
|
||||
// 检查网络连接
|
||||
if (isNetworkAvailable()) {
|
||||
Call<ApiResponse<List<ImageSourceItem>>> call = commonService.getImageSourceList();
|
||||
|
||||
call.enqueue(new Callback<ApiResponse<List<ImageSourceItem>>>() {
|
||||
@Override
|
||||
public void onResponse(Call<ApiResponse<List<ImageSourceItem>>> call,
|
||||
Response<ApiResponse<List<ImageSourceItem>>> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().isSuccess()) {
|
||||
List<ImageSourceItem> result = response.body().getData();
|
||||
|
||||
Log.e("data信息:", result != null ? result.toString() : "Result is null");
|
||||
// 保存到数据库
|
||||
dbHelper.saveImageSources(result);
|
||||
callback.onSuccess(result);
|
||||
} else if (cachedSources.isEmpty()) {
|
||||
callback.onFailure(new IOException("请求失败,状态码: " + response.code()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<ApiResponse<List<ImageSourceItem>>> call, Throwable t) {
|
||||
if (cachedSources.isEmpty()) {
|
||||
callback.onFailure(t);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (cachedSources.isEmpty()) {
|
||||
callback.onFailure(new IOException("无网络连接且无缓存数据"));
|
||||
}
|
||||
}
|
||||
private boolean isNetworkAvailable() {
|
||||
ConnectivityManager connectivityManager =
|
||||
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
if (connectivityManager != null) {
|
||||
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
|
||||
return activeNetworkInfo != null && activeNetworkInfo.isConnected();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public interface ImageSourceCallback {
|
||||
void onSuccess(List<ImageSourceItem> imageSources);
|
||||
void onFailure(Throwable t);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
package com.example.myapplication.Tool;
|
||||
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.chaquo.python.PyObject;
|
||||
import com.chaquo.python.Python;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
public class DocxHelper {
|
||||
private static final String TAG = "DocxHelper";
|
||||
public static File convertJsonToDocx(Context context, String jsonAssetName, String outputFileName) {
|
||||
// 1. 从assets读取JSON数据
|
||||
String jsonData = readJsonFromAssets(context, jsonAssetName);
|
||||
|
||||
if (jsonData == null) {
|
||||
Toast.makeText(context, "Failed to read JSON from assets",Toast.LENGTH_SHORT).show();
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. 准备输出文件路径
|
||||
File outputFile = new File(context.getExternalFilesDir(null), outputFileName);
|
||||
String outputPath = outputFile.getAbsolutePath();
|
||||
|
||||
// 3. 调用Python处理
|
||||
try {
|
||||
Python py = Python.getInstance();
|
||||
PyObject module = py.getModule("test");
|
||||
|
||||
// 调用Python函数
|
||||
module.callAttr("json_to_docx", jsonData, outputPath);
|
||||
|
||||
Toast.makeText(context, "DOCX file generated at:"+outputPath, Toast.LENGTH_SHORT).show();
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Python processing failed", e);
|
||||
}
|
||||
finally {
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
}
|
||||
private static String readJsonFromAssets(Context context, String filename) {
|
||||
try {
|
||||
// 1. 打开assets中的文件输入流
|
||||
InputStream is = context.getAssets().open(filename);
|
||||
|
||||
|
||||
// 2. 创建缓冲读取器
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
||||
// 3. 使用StringBuilder高效拼接字符串
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
// 4. 逐行读取文件内容
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
// 5. 关闭资源
|
||||
reader.close();
|
||||
// 6. 返回完整的JSON字符串
|
||||
return sb.toString();
|
||||
} catch (IOException e) {
|
||||
Toast.makeText(context, "Error reading JSON file"+e.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,136 +1,136 @@
|
|||
package com.example.myapplication.Tool;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.util.Log;
|
||||
|
||||
import com.example.myapplication.DataBase.DatabaseHelper;
|
||||
import com.example.myapplication.api.CommonService;
|
||||
import com.example.myapplication.model.ApiResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
import retrofit2.Retrofit;
|
||||
|
||||
public class DynamicDataFetcher {
|
||||
private final String token;
|
||||
private final CommonService commonService;
|
||||
private final DatabaseHelper dbHelper;
|
||||
private final Context context;
|
||||
private final String dataType; // 新增:数据类型标识
|
||||
|
||||
/**
|
||||
* @param token 用户令牌
|
||||
* @param context 上下文
|
||||
* @param dataType 数据类型标识(如"work_types"、"image_sources"等)
|
||||
*/
|
||||
public DynamicDataFetcher(String token, Context context, String dataType) {
|
||||
this.token = token;
|
||||
this.context = context;
|
||||
this.dataType = dataType;
|
||||
this.dbHelper = new DatabaseHelper(context);
|
||||
Retrofit retrofit = RetrofitClient.getClient(token);
|
||||
this.commonService = retrofit.create(CommonService.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步获取动态数据列表
|
||||
*/
|
||||
public List<Map<String, String>> fetchDynamicDataSync() throws IOException {
|
||||
if (isNetworkAvailable()) {
|
||||
Call<ApiResponse<List<Map<String, String>>>> call = commonService.getDynamicDataList();
|
||||
|
||||
Response<ApiResponse<List<Map<String, String>>>> response = call.execute();
|
||||
|
||||
if (response.isSuccessful() && response.body() != null && response.body().isSuccess()) {
|
||||
List<Map<String, String>> result = response.body().getData();
|
||||
// 保存到数据库(带数据类型标识)
|
||||
dbHelper.saveDynamicData(dataType, result);
|
||||
return result;
|
||||
} else {
|
||||
throw new IOException("请求失败,状态码: " + response.code());
|
||||
}
|
||||
} else {
|
||||
// 无网络时从数据库获取(带数据类型标识)
|
||||
return dbHelper.getDynamicData(dataType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步获取动态数据列表
|
||||
*/
|
||||
public void fetchDynamicDataAsync(DynamicDataCallback callback) {
|
||||
List<Map<String, String>> cachedData = dbHelper.getDynamicData(dataType);
|
||||
if (!cachedData.isEmpty()) {
|
||||
callback.onSuccess(cachedData);
|
||||
}
|
||||
|
||||
if (isNetworkAvailable()) {
|
||||
Call<ApiResponse<List<Map<String, String>>>> call = commonService.getDynamicDataList();
|
||||
|
||||
call.enqueue(new Callback<ApiResponse<List<Map<String, String>>>>() {
|
||||
@Override
|
||||
public void onResponse(Call<ApiResponse<List<Map<String, String>>>> call,
|
||||
Response<ApiResponse<List<Map<String, String>>>> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().isSuccess()) {
|
||||
Log.e("图片源列表:",response.body().toString());
|
||||
List<Map<String, String>> result = response.body().getData();
|
||||
Log.e("result",result.toString());
|
||||
// 保存到数据库(带数据类型标识)
|
||||
dbHelper.saveDynamicData(dataType, result);
|
||||
callback.onSuccess(result);
|
||||
} else if (cachedData.isEmpty()) {
|
||||
callback.onFailure(new IOException("请求失败,状态码: " + response.code()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<ApiResponse<List<Map<String, String>>>> call, Throwable t) {
|
||||
if (cachedData.isEmpty()) {
|
||||
callback.onFailure(t);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (cachedData.isEmpty()) {
|
||||
callback.onFailure(new IOException("无网络连接且无缓存数据"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查网络是否可用
|
||||
*/
|
||||
private boolean isNetworkAvailable() {
|
||||
ConnectivityManager connectivityManager =
|
||||
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
if (connectivityManager != null) {
|
||||
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
|
||||
return activeNetworkInfo != null && activeNetworkInfo.isConnected();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取该数据类型的最后同步时间
|
||||
*/
|
||||
public long getLastSyncTime() {
|
||||
return dbHelper.getDynamicDataLastSync(dataType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除当前数据类型的缓存
|
||||
*/
|
||||
public void clearCache() {
|
||||
dbHelper.clearDynamicData(dataType);
|
||||
}
|
||||
|
||||
public interface DynamicDataCallback {
|
||||
void onSuccess(List<Map<String, String>> dataList);
|
||||
void onFailure(Throwable t);
|
||||
}
|
||||
package com.example.myapplication.Tool;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.util.Log;
|
||||
|
||||
import com.example.myapplication.DataBase.DatabaseHelper;
|
||||
import com.example.myapplication.api.CommonService;
|
||||
import com.example.myapplication.model.ApiResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
import retrofit2.Retrofit;
|
||||
|
||||
public class DynamicDataFetcher {
|
||||
private final String token;
|
||||
private final CommonService commonService;
|
||||
private final DatabaseHelper dbHelper;
|
||||
private final Context context;
|
||||
private final String dataType; // 新增:数据类型标识
|
||||
|
||||
/**
|
||||
* @param token 用户令牌
|
||||
* @param context 上下文
|
||||
* @param dataType 数据类型标识(如"work_types"、"image_sources"等)
|
||||
*/
|
||||
public DynamicDataFetcher(String token, Context context, String dataType) {
|
||||
this.token = token;
|
||||
this.context = context;
|
||||
this.dataType = dataType;
|
||||
this.dbHelper = new DatabaseHelper(context);
|
||||
Retrofit retrofit = RetrofitClient.getClient(token);
|
||||
this.commonService = retrofit.create(CommonService.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步获取动态数据列表
|
||||
*/
|
||||
public List<Map<String, String>> fetchDynamicDataSync() throws IOException {
|
||||
if (isNetworkAvailable()) {
|
||||
Call<ApiResponse<List<Map<String, String>>>> call = commonService.getDynamicDataList();
|
||||
|
||||
Response<ApiResponse<List<Map<String, String>>>> response = call.execute();
|
||||
|
||||
if (response.isSuccessful() && response.body() != null && response.body().isSuccess()) {
|
||||
List<Map<String, String>> result = response.body().getData();
|
||||
// 保存到数据库(带数据类型标识)
|
||||
dbHelper.saveDynamicData(dataType, result);
|
||||
return result;
|
||||
} else {
|
||||
throw new IOException("请求失败,状态码: " + response.code());
|
||||
}
|
||||
} else {
|
||||
// 无网络时从数据库获取(带数据类型标识)
|
||||
return dbHelper.getDynamicData(dataType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步获取动态数据列表
|
||||
*/
|
||||
public void fetchDynamicDataAsync(DynamicDataCallback callback) {
|
||||
List<Map<String, String>> cachedData = dbHelper.getDynamicData(dataType);
|
||||
if (!cachedData.isEmpty()) {
|
||||
callback.onSuccess(cachedData);
|
||||
}
|
||||
|
||||
if (isNetworkAvailable()) {
|
||||
Call<ApiResponse<List<Map<String, String>>>> call = commonService.getDynamicDataList();
|
||||
|
||||
call.enqueue(new Callback<ApiResponse<List<Map<String, String>>>>() {
|
||||
@Override
|
||||
public void onResponse(Call<ApiResponse<List<Map<String, String>>>> call,
|
||||
Response<ApiResponse<List<Map<String, String>>>> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().isSuccess()) {
|
||||
|
||||
List<Map<String, String>> result = response.body().getData();
|
||||
|
||||
// 保存到数据库(带数据类型标识)
|
||||
dbHelper.saveDynamicData(dataType, result);
|
||||
callback.onSuccess(result);
|
||||
} else if (cachedData.isEmpty()) {
|
||||
callback.onFailure(new IOException("请求失败,状态码: " + response.code()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<ApiResponse<List<Map<String, String>>>> call, Throwable t) {
|
||||
if (cachedData.isEmpty()) {
|
||||
callback.onFailure(t);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (cachedData.isEmpty()) {
|
||||
callback.onFailure(new IOException("无网络连接且无缓存数据"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查网络是否可用
|
||||
*/
|
||||
private boolean isNetworkAvailable() {
|
||||
ConnectivityManager connectivityManager =
|
||||
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
if (connectivityManager != null) {
|
||||
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
|
||||
return activeNetworkInfo != null && activeNetworkInfo.isConnected();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取该数据类型的最后同步时间
|
||||
*/
|
||||
public long getLastSyncTime() {
|
||||
return dbHelper.getDynamicDataLastSync(dataType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除当前数据类型的缓存
|
||||
*/
|
||||
public void clearCache() {
|
||||
dbHelper.clearDynamicData(dataType);
|
||||
}
|
||||
|
||||
public interface DynamicDataCallback {
|
||||
void onSuccess(List<Map<String, String>> dataList);
|
||||
void onFailure(Throwable t);
|
||||
}
|
||||
}
|
|
@ -1,70 +1,70 @@
|
|||
package com.example.myapplication.Tool;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.os.Handler;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
//测试时使用
|
||||
public class ErrorDisplayUtil {
|
||||
|
||||
// 显示全量错误信息的对话框
|
||||
public static void showErrorDialog(Activity activity, String title, String errorDetail) {
|
||||
activity.runOnUiThread(() -> {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
builder.setTitle(title)
|
||||
.setMessage(errorDetail)
|
||||
.setPositiveButton("复制错误", (dialog, which) -> {
|
||||
ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText("错误详情", errorDetail);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
Toast.makeText(activity, "已复制到剪贴板", Toast.LENGTH_SHORT).show();
|
||||
})
|
||||
.setNegativeButton("关闭", null)
|
||||
.show();
|
||||
});
|
||||
}
|
||||
|
||||
// 在界面上固定位置显示错误面板(建议放在登录按钮下方)
|
||||
public static void showErrorPanel(Activity activity, String errorType, String solution) {
|
||||
activity.runOnUiThread(() -> {
|
||||
ViewGroup rootView = activity.findViewById(android.R.id.content);
|
||||
|
||||
// 创建错误面板
|
||||
LinearLayout errorPanel = new LinearLayout(activity);
|
||||
errorPanel.setOrientation(LinearLayout.VERTICAL);
|
||||
errorPanel.setBackgroundColor(0x22FF0000); // 半透明红色背景
|
||||
errorPanel.setPadding(16, 16, 16, 16);
|
||||
|
||||
TextView errorView = new TextView(activity);
|
||||
errorView.setTextColor(Color.RED);
|
||||
errorView.setText("⚠️ 错误类型: " + errorType);
|
||||
|
||||
TextView solutionView = new TextView(activity);
|
||||
solutionView.setTextColor(Color.BLACK);
|
||||
solutionView.setText("💡 解决方案: " + solution);
|
||||
|
||||
Button detailBtn = new Button(activity);
|
||||
detailBtn.setText("查看技术详情");
|
||||
detailBtn.setOnClickListener(v -> showErrorDialog(activity, "技术详情", errorType + "\n\n" + solution));
|
||||
|
||||
errorPanel.addView(errorView);
|
||||
errorPanel.addView(solutionView);
|
||||
errorPanel.addView(detailBtn);
|
||||
|
||||
// 添加到界面底部
|
||||
rootView.addView(errorPanel);
|
||||
|
||||
// 5秒后自动隐藏
|
||||
new Handler().postDelayed(() -> rootView.removeView(errorPanel), 5000);
|
||||
});
|
||||
}
|
||||
}
|
||||
package com.example.myapplication.Tool;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.os.Handler;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
//测试时使用
|
||||
public class ErrorDisplayUtil {
|
||||
|
||||
// 显示全量错误信息的对话框
|
||||
public static void showErrorDialog(Activity activity, String title, String errorDetail) {
|
||||
activity.runOnUiThread(() -> {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
builder.setTitle(title)
|
||||
.setMessage(errorDetail)
|
||||
.setPositiveButton("复制错误", (dialog, which) -> {
|
||||
ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText("错误详情", errorDetail);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
Toast.makeText(activity, "已复制到剪贴板", Toast.LENGTH_SHORT).show();
|
||||
})
|
||||
.setNegativeButton("关闭", null)
|
||||
.show();
|
||||
});
|
||||
}
|
||||
|
||||
// 在界面上固定位置显示错误面板(建议放在登录按钮下方)
|
||||
public static void showErrorPanel(Activity activity, String errorType, String solution) {
|
||||
activity.runOnUiThread(() -> {
|
||||
ViewGroup rootView = activity.findViewById(android.R.id.content);
|
||||
|
||||
// 创建错误面板
|
||||
LinearLayout errorPanel = new LinearLayout(activity);
|
||||
errorPanel.setOrientation(LinearLayout.VERTICAL);
|
||||
errorPanel.setBackgroundColor(0x22FF0000); // 半透明红色背景
|
||||
errorPanel.setPadding(16, 16, 16, 16);
|
||||
|
||||
TextView errorView = new TextView(activity);
|
||||
errorView.setTextColor(Color.RED);
|
||||
errorView.setText("⚠️ 错误类型: " + errorType);
|
||||
|
||||
TextView solutionView = new TextView(activity);
|
||||
solutionView.setTextColor(Color.BLACK);
|
||||
solutionView.setText("💡 解决方案: " + solution);
|
||||
|
||||
Button detailBtn = new Button(activity);
|
||||
detailBtn.setText("查看技术详情");
|
||||
detailBtn.setOnClickListener(v -> showErrorDialog(activity, "技术详情", errorType + "\n\n" + solution));
|
||||
|
||||
errorPanel.addView(errorView);
|
||||
errorPanel.addView(solutionView);
|
||||
errorPanel.addView(detailBtn);
|
||||
|
||||
// 添加到界面底部
|
||||
rootView.addView(errorPanel);
|
||||
|
||||
// 5秒后自动隐藏
|
||||
new Handler().postDelayed(() -> rootView.removeView(errorPanel), 5000);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
package com.example.myapplication.Tool;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.GridView;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.example.myapplication.model.ImageInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ImageAdapter extends BaseAdapter {
|
||||
private final Context context;
|
||||
private final List<ImageInfo> imageList;
|
||||
private final SparseBooleanArray selectedItems;
|
||||
|
||||
public ImageAdapter(Context context, List<ImageInfo> imageList) {
|
||||
this.context = context;
|
||||
this.imageList = imageList;
|
||||
this.selectedItems = new SparseBooleanArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return imageList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
return imageList.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
ImageView imageView;
|
||||
if (convertView == null) {
|
||||
imageView = new ImageView(context);
|
||||
imageView.setLayoutParams(new GridView.LayoutParams(200, 200));
|
||||
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
||||
imageView.setPadding(5, 5, 5, 5);
|
||||
} else {
|
||||
imageView = (ImageView) convertView;
|
||||
}
|
||||
|
||||
ImageInfo imageInfo = imageList.get(position);
|
||||
imageView.setImageBitmap(imageInfo.thumbnail);
|
||||
|
||||
// 设置选中状态
|
||||
if (selectedItems.get(position, false)) {
|
||||
imageView.setBackgroundColor(Color.BLUE);
|
||||
} else {
|
||||
imageView.setBackgroundColor(Color.TRANSPARENT);
|
||||
}
|
||||
|
||||
imageView.setOnClickListener(v -> {
|
||||
if (selectedItems.get(position, false)) {
|
||||
selectedItems.delete(position);
|
||||
imageView.setBackgroundColor(Color.TRANSPARENT);
|
||||
} else {
|
||||
selectedItems.put(position, true);
|
||||
imageView.setBackgroundColor(Color.BLUE);
|
||||
}
|
||||
});
|
||||
|
||||
return imageView;
|
||||
}
|
||||
|
||||
public List<ImageInfo> getSelectedImages() {
|
||||
List<ImageInfo> selected = new ArrayList<>();
|
||||
for (int i = 0; i < imageList.size(); i++) {
|
||||
if (selectedItems.get(i, false)) {
|
||||
selected.add(imageList.get(i));
|
||||
}
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package com.example.myapplication.Tool;
|
||||
|
||||
import static android.content.Context.ACTIVITY_SERVICE;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import com.example.myapplication.Service.PhotoMonitoringService;
|
||||
|
||||
// 更健壮的广播接收器实现示例
|
||||
public class NotificationReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent == null) return;
|
||||
|
||||
try {
|
||||
if ("ACTION_STOP_SERVICE".equals(intent.getAction())) {
|
||||
// 防止重复操作
|
||||
if (isServiceRunning(context)) {
|
||||
context.stopService(new Intent(context, PhotoMonitoringService.class));
|
||||
}
|
||||
|
||||
// 清除所有相关通知
|
||||
NotificationManager manager = context.getSystemService(NotificationManager.class);
|
||||
manager.cancelAll(); // 或指定ID
|
||||
|
||||
// 可选:发送结果广播
|
||||
Intent doneIntent = new Intent("ACTION_SERVICE_STOPPED");
|
||||
context.sendBroadcast(doneIntent);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("NotificationReceiver", "Error handling action", e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isServiceRunning(Context context) {
|
||||
ActivityManager manager = (ActivityManager) context.getSystemService(ACTIVITY_SERVICE);
|
||||
for (ActivityManager.RunningServiceInfo service :
|
||||
manager.getRunningServices(Integer.MAX_VALUE)) {
|
||||
if (PhotoMonitoringService.class.getName().equals(service.service.getClassName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,282 +1,283 @@
|
|||
package com.example.myapplication.Tool;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
|
||||
import com.example.myapplication.DataBase.DatabaseHelper;
|
||||
import com.example.myapplication.api.PartService;
|
||||
import com.example.myapplication.model.ApiResponse;
|
||||
import com.example.myapplication.model.PartResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Response;
|
||||
import retrofit2.Retrofit;
|
||||
import retrofit2.converter.gson.GsonConverterFactory;
|
||||
|
||||
public class PartListFetcher {
|
||||
private final Context context;
|
||||
private final String token;
|
||||
private final String projectId;
|
||||
|
||||
private final PartService partService;
|
||||
private final DatabaseHelper dbHelper;
|
||||
|
||||
public PartListFetcher(Context context, String token, String projectId) {
|
||||
this.context = context;
|
||||
this.token = token;
|
||||
this.projectId = projectId;
|
||||
|
||||
this.dbHelper = new DatabaseHelper(context);
|
||||
|
||||
// 初始化Retrofit
|
||||
OkHttpClient okHttpClient = new OkHttpClient.Builder()
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(30, TimeUnit.SECONDS)
|
||||
.writeTimeout(30, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
Retrofit retrofit = RetrofitClient.getClient(token);
|
||||
|
||||
this.partService = retrofit.create(PartService.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步获取部件列表
|
||||
* @return 部件列表
|
||||
* @throws IOException 网络异常
|
||||
* @throws ApiException API异常
|
||||
*/
|
||||
public List<PartResponse> fetchPartListSync() throws IOException, ApiException {
|
||||
if (isNetworkAvailable()) {
|
||||
// 有网络时从服务器获取并更新数据库
|
||||
List<PartResponse> serverParts = fetchFromServer();
|
||||
dbHelper.saveParts(serverParts);
|
||||
return serverParts;
|
||||
} else {
|
||||
// 无网络时从数据库获取
|
||||
return dbHelper.getAllParts();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步获取部件列表
|
||||
* @param callback 回调接口
|
||||
*/
|
||||
public void fetchPartListAsync(final PartListCallback callback) {
|
||||
// 先从数据库获取数据快速显示
|
||||
List<PartResponse> cachedParts = dbHelper.getAllParts();
|
||||
if (!cachedParts.isEmpty()) {
|
||||
callback.onSuccess(cachedParts);
|
||||
}
|
||||
|
||||
// 检查网络连接
|
||||
if (isNetworkAvailable()) {
|
||||
// 有网络时从服务器获取并更新数据库
|
||||
fetchFromServerAsync(new PartListCallback() {
|
||||
@Override
|
||||
public void onSuccess(List<PartResponse> partList) {
|
||||
dbHelper.saveParts(partList);
|
||||
callback.onSuccess(partList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
// 服务器获取失败,如果之前有缓存数据则不报错
|
||||
if (cachedParts.isEmpty()) {
|
||||
callback.onFailure(t);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (cachedParts.isEmpty()) {
|
||||
// 无网络且无缓存数据
|
||||
callback.onFailure(new IOException("无网络连接且无缓存数据"));
|
||||
}
|
||||
}
|
||||
public String getPartIdByName(String partName) {
|
||||
// 先从数据库查询
|
||||
String partId = dbHelper.getPartIdByName(partName);
|
||||
if (partId != null) {
|
||||
return partId;
|
||||
}
|
||||
|
||||
// 数据库中没有则尝试从服务器获取
|
||||
if (isNetworkAvailable()) {
|
||||
try {
|
||||
List<PartResponse> parts = fetchFromServer();
|
||||
dbHelper.saveParts(parts);
|
||||
// 再次从数据库查询
|
||||
return dbHelper.getPartIdByName(partName);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
private List<PartResponse> fetchFromServer() throws IOException, ApiException {
|
||||
Call<ApiResponse<List<PartResponse>>> call = partService.getPartList(
|
||||
projectId,
|
||||
|
||||
null, null, null, null
|
||||
|
||||
);
|
||||
|
||||
Response<ApiResponse<List<PartResponse>>> response = call.execute();
|
||||
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
ApiResponse<List<PartResponse>> apiResponse = response.body();
|
||||
if (apiResponse.isSuccess()) {
|
||||
return apiResponse.getData();
|
||||
} else {
|
||||
throw new ApiException(apiResponse.getCode(), apiResponse.getMsg());
|
||||
}
|
||||
} else {
|
||||
throw new IOException("请求失败,状态码: " + response.code());
|
||||
}
|
||||
}
|
||||
private void fetchFromServerAsync(final PartListCallback callback) {
|
||||
Call<ApiResponse<List<PartResponse>>> call = partService.getPartList(
|
||||
projectId,
|
||||
|
||||
null, null, null, null
|
||||
|
||||
);
|
||||
|
||||
call.enqueue(new retrofit2.Callback<ApiResponse<List<PartResponse>>>() {
|
||||
@Override
|
||||
public void onResponse(Call<ApiResponse<List<PartResponse>>> call,
|
||||
Response<ApiResponse<List<PartResponse>>> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
ApiResponse<List<PartResponse>> apiResponse = response.body();
|
||||
if (apiResponse.isSuccess()) {
|
||||
callback.onSuccess(apiResponse.getData());
|
||||
} else {
|
||||
callback.onFailure(new ApiException(apiResponse.getCode(), apiResponse.getMsg()));
|
||||
}
|
||||
} else {
|
||||
callback.onFailure(new IOException("请求失败,状态码: " + response.code()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<ApiResponse<List<PartResponse>>> call, Throwable t) {
|
||||
callback.onFailure(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isNetworkAvailable() {
|
||||
ConnectivityManager connectivityManager =
|
||||
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
if (connectivityManager != null) {
|
||||
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
|
||||
return activeNetworkInfo != null && activeNetworkInfo.isConnected();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 带筛选条件的异步获取部件列表
|
||||
* @param keyword 关键字
|
||||
* @param manufacturer 厂商
|
||||
* @param model 型号
|
||||
* @param type 类型
|
||||
* @param callback 回调接口
|
||||
*/
|
||||
public void fetchPartListWithFilterAsync(String keyword, String manufacturer,
|
||||
String model, String type,
|
||||
PartListCallback callback) {
|
||||
Call<ApiResponse<List<PartResponse>>> call = partService.getPartList(
|
||||
projectId,
|
||||
|
||||
keyword,
|
||||
manufacturer,
|
||||
model,
|
||||
type
|
||||
);
|
||||
|
||||
call.enqueue(new retrofit2.Callback<ApiResponse<List<PartResponse>>>() {
|
||||
@Override
|
||||
public void onResponse(Call<ApiResponse<List<PartResponse>>> call,
|
||||
Response<ApiResponse<List<PartResponse>>> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
ApiResponse<List<PartResponse>> apiResponse = response.body();
|
||||
if (apiResponse.isSuccess()) {
|
||||
callback.onSuccess(apiResponse.getData());
|
||||
} else {
|
||||
callback.onFailure(new ApiException(apiResponse.getCode(), apiResponse.getMsg()));
|
||||
}
|
||||
} else {
|
||||
callback.onFailure(new IOException("请求失败,状态码: " + response.code()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<ApiResponse<List<PartResponse>>> call, Throwable t) {
|
||||
callback.onFailure(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public interface PartListCallback {
|
||||
void onSuccess(List<PartResponse> partList);
|
||||
void onFailure(Throwable t);
|
||||
}
|
||||
public String getPartIdByNameSync(String partName) throws IOException, ApiException {
|
||||
List<PartResponse> partList = fetchPartListSync();
|
||||
for (PartResponse part : partList) {
|
||||
if (partName.equals(part.getPartName())) {
|
||||
return part.getPartId();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步根据部件名称获取部件ID
|
||||
* @param partName 要查找的部件名称
|
||||
* @param callback 回调接口
|
||||
*/
|
||||
public void getPartIdByNameAsync(String partName, PartIdCallback callback) {
|
||||
fetchPartListAsync(new PartListCallback() {
|
||||
@Override
|
||||
public void onSuccess(List<PartResponse> partList) {
|
||||
for (PartResponse part : partList) {
|
||||
if (partName.equals(part.getPartName())) {
|
||||
callback.onSuccess(part.getPartId());
|
||||
return;
|
||||
}
|
||||
}
|
||||
callback.onSuccess(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
callback.onFailure(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public interface PartIdCallback {
|
||||
void onSuccess(String partId);
|
||||
void onFailure(Throwable t);
|
||||
}
|
||||
public static class ApiException extends Exception {
|
||||
private final int code;
|
||||
|
||||
public ApiException(int code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
}
|
||||
package com.example.myapplication.Tool;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
|
||||
import com.example.myapplication.DataBase.DatabaseHelper;
|
||||
import com.example.myapplication.api.PartService;
|
||||
import com.example.myapplication.model.ApiResponse;
|
||||
import com.example.myapplication.model.PartResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Response;
|
||||
import retrofit2.Retrofit;
|
||||
import retrofit2.converter.gson.GsonConverterFactory;
|
||||
|
||||
public class PartListFetcher {
|
||||
private final Context context;
|
||||
private final String token;
|
||||
private final String projectId;
|
||||
|
||||
private final PartService partService;
|
||||
private final DatabaseHelper dbHelper;
|
||||
|
||||
public PartListFetcher(Context context, String token, String projectId) {
|
||||
this.context = context;
|
||||
this.token = token;
|
||||
this.projectId = projectId;
|
||||
|
||||
this.dbHelper = new DatabaseHelper(context);
|
||||
|
||||
// 初始化Retrofit
|
||||
OkHttpClient okHttpClient = new OkHttpClient.Builder()
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(30, TimeUnit.SECONDS)
|
||||
.writeTimeout(30, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
Retrofit retrofit = RetrofitClient.getClient(token);
|
||||
|
||||
this.partService = retrofit.create(PartService.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步获取部件列表
|
||||
* @return 部件列表
|
||||
* @throws IOException 网络异常
|
||||
* @throws ApiException API异常
|
||||
*/
|
||||
public List<PartResponse> fetchPartListSync() throws IOException, ApiException {
|
||||
if (isNetworkAvailable()) {
|
||||
// 有网络时从服务器获取并更新数据库
|
||||
List<PartResponse> serverParts = fetchFromServer();
|
||||
dbHelper.saveParts(serverParts);
|
||||
return serverParts;
|
||||
} else {
|
||||
// 无网络时从数据库获取
|
||||
return dbHelper.getAllParts();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 异步获取部件列表
|
||||
* @param callback 回调接口
|
||||
*/
|
||||
public void fetchPartListAsync(final PartListCallback callback) {
|
||||
// 先从数据库获取数据快速显示
|
||||
List<PartResponse> cachedParts = dbHelper.getAllParts();
|
||||
if (!cachedParts.isEmpty()) {
|
||||
callback.onSuccess(cachedParts);
|
||||
}
|
||||
|
||||
// 检查网络连接
|
||||
if (isNetworkAvailable()) {
|
||||
// 有网络时从服务器获取并更新数据库
|
||||
fetchFromServerAsync(new PartListCallback() {
|
||||
@Override
|
||||
public void onSuccess(List<PartResponse> partList) {
|
||||
dbHelper.saveParts(partList);
|
||||
callback.onSuccess(partList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
// 服务器获取失败,如果之前有缓存数据则不报错
|
||||
if (cachedParts.isEmpty()) {
|
||||
callback.onFailure(t);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (cachedParts.isEmpty()) {
|
||||
// 无网络且无缓存数据
|
||||
callback.onFailure(new IOException("无网络连接且无缓存数据"));
|
||||
}
|
||||
}
|
||||
public String getPartIdByName(String partName) {
|
||||
// 先从数据库查询
|
||||
String partId = dbHelper.getPartIdByName(partName);
|
||||
if (partId != null) {
|
||||
return partId;
|
||||
}
|
||||
|
||||
// 数据库中没有则尝试从服务器获取
|
||||
if (isNetworkAvailable()) {
|
||||
try {
|
||||
List<PartResponse> parts = fetchFromServer();
|
||||
dbHelper.saveParts(parts);
|
||||
// 再次从数据库查询
|
||||
return dbHelper.getPartIdByName(partName);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
private List<PartResponse> fetchFromServer() throws IOException, ApiException {
|
||||
Call<ApiResponse<List<PartResponse>>> call = partService.getPartList(
|
||||
projectId,
|
||||
|
||||
null, null, null, null
|
||||
|
||||
);
|
||||
|
||||
Response<ApiResponse<List<PartResponse>>> response = call.execute();
|
||||
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
ApiResponse<List<PartResponse>> apiResponse = response.body();
|
||||
if (apiResponse.isSuccess()) {
|
||||
return apiResponse.getData();
|
||||
} else {
|
||||
throw new ApiException(apiResponse.getCode(), apiResponse.getMsg());
|
||||
}
|
||||
} else {
|
||||
throw new IOException("请求失败,状态码: " + response.code());
|
||||
}
|
||||
}
|
||||
private void fetchFromServerAsync(final PartListCallback callback) {
|
||||
Call<ApiResponse<List<PartResponse>>> call = partService.getPartList(
|
||||
projectId,
|
||||
|
||||
null, null, null, null
|
||||
|
||||
);
|
||||
|
||||
call.enqueue(new retrofit2.Callback<ApiResponse<List<PartResponse>>>() {
|
||||
@Override
|
||||
public void onResponse(Call<ApiResponse<List<PartResponse>>> call,
|
||||
Response<ApiResponse<List<PartResponse>>> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
ApiResponse<List<PartResponse>> apiResponse = response.body();
|
||||
if (apiResponse.isSuccess()) {
|
||||
callback.onSuccess(apiResponse.getData());
|
||||
} else {
|
||||
callback.onFailure(new ApiException(apiResponse.getCode(), apiResponse.getMsg()));
|
||||
}
|
||||
} else {
|
||||
callback.onFailure(new IOException("请求失败,状态码: " + response.code()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<ApiResponse<List<PartResponse>>> call, Throwable t) {
|
||||
callback.onFailure(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isNetworkAvailable() {
|
||||
ConnectivityManager connectivityManager =
|
||||
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
if (connectivityManager != null) {
|
||||
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
|
||||
return activeNetworkInfo != null && activeNetworkInfo.isConnected();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 带筛选条件的异步获取部件列表
|
||||
* @param keyword 关键字
|
||||
* @param manufacturer 厂商
|
||||
* @param model 型号
|
||||
* @param type 类型
|
||||
* @param callback 回调接口
|
||||
*/
|
||||
public void fetchPartListWithFilterAsync(String keyword, String manufacturer,
|
||||
String model, String type,
|
||||
PartListCallback callback) {
|
||||
Call<ApiResponse<List<PartResponse>>> call = partService.getPartList(
|
||||
projectId,
|
||||
|
||||
keyword,
|
||||
manufacturer,
|
||||
model,
|
||||
type
|
||||
);
|
||||
|
||||
call.enqueue(new retrofit2.Callback<ApiResponse<List<PartResponse>>>() {
|
||||
@Override
|
||||
public void onResponse(Call<ApiResponse<List<PartResponse>>> call,
|
||||
Response<ApiResponse<List<PartResponse>>> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
ApiResponse<List<PartResponse>> apiResponse = response.body();
|
||||
if (apiResponse.isSuccess()) {
|
||||
callback.onSuccess(apiResponse.getData());
|
||||
} else {
|
||||
callback.onFailure(new ApiException(apiResponse.getCode(), apiResponse.getMsg()));
|
||||
}
|
||||
} else {
|
||||
callback.onFailure(new IOException("请求失败,状态码: " + response.code()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<ApiResponse<List<PartResponse>>> call, Throwable t) {
|
||||
callback.onFailure(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public interface PartListCallback {
|
||||
void onSuccess(List<PartResponse> partList);
|
||||
void onFailure(Throwable t);
|
||||
}
|
||||
public String getPartIdByNameSync(String partName) throws IOException, ApiException {
|
||||
List<PartResponse> partList = fetchPartListSync();
|
||||
for (PartResponse part : partList) {
|
||||
if (partName.equals(part.getPartName())) {
|
||||
return part.getPartId();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步根据部件名称获取部件ID
|
||||
* @param partName 要查找的部件名称
|
||||
* @param callback 回调接口
|
||||
*/
|
||||
public void getPartIdByNameAsync(String partName, PartIdCallback callback) {
|
||||
fetchPartListAsync(new PartListCallback() {
|
||||
@Override
|
||||
public void onSuccess(List<PartResponse> partList) {
|
||||
for (PartResponse part : partList) {
|
||||
if (partName.equals(part.getPartName())) {
|
||||
callback.onSuccess(part.getPartId());
|
||||
return;
|
||||
}
|
||||
}
|
||||
callback.onSuccess(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
callback.onFailure(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public interface PartIdCallback {
|
||||
void onSuccess(String partId);
|
||||
void onFailure(Throwable t);
|
||||
}
|
||||
public static class ApiException extends Exception {
|
||||
private final int code;
|
||||
|
||||
public ApiException(int code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,86 +1,86 @@
|
|||
package com.example.myapplication.Tool;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
public class PermissionUtils {
|
||||
// 请求码
|
||||
public static final int REQUEST_STORAGE_PERMISSION = 101;
|
||||
public static final int REQUEST_MEDIA_PERMISSION = 102; // Android 13+ 媒体权限
|
||||
|
||||
// 检查并请求所有必要权限
|
||||
public static boolean checkAndRequestPermissions(Activity activity) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
// Android 14+ 需要检查媒体权限(如果要访问媒体文件)
|
||||
return checkMediaPermissions(activity);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
// Android 10-13 使用 SAF 或 MediaStore,不需要存储权限
|
||||
return true;
|
||||
} else {
|
||||
// Android 9 及以下需要传统存储权限
|
||||
return checkLegacyStoragePermission(activity);
|
||||
}
|
||||
}
|
||||
|
||||
// Android 14+ 媒体权限检查
|
||||
private static boolean checkMediaPermissions(Activity activity) {
|
||||
String[] permissions;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
permissions = new String[]{
|
||||
Manifest.permission.READ_MEDIA_IMAGES,
|
||||
Manifest.permission.READ_MEDIA_VIDEO,
|
||||
Manifest.permission.READ_MEDIA_AUDIO
|
||||
};
|
||||
} else {
|
||||
permissions = new String[]{Manifest.permission.READ_EXTERNAL_STORAGE};
|
||||
}
|
||||
|
||||
boolean allGranted = true;
|
||||
for (String perm : permissions) {
|
||||
if (ContextCompat.checkSelfPermission(activity, perm) != PackageManager.PERMISSION_GRANTED) {
|
||||
allGranted = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!allGranted) {
|
||||
ActivityCompat.requestPermissions(activity, permissions, REQUEST_MEDIA_PERMISSION);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Android 9 及以下传统存储权限
|
||||
private static boolean checkLegacyStoragePermission(Activity activity) {
|
||||
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(activity,
|
||||
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||
REQUEST_STORAGE_PERMISSION);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 处理权限请求结果
|
||||
public static boolean handlePermissionResult(int requestCode,
|
||||
String[] permissions, int[] grantResults) {
|
||||
if (grantResults.length == 0) return false;
|
||||
|
||||
if (requestCode == REQUEST_STORAGE_PERMISSION ||
|
||||
requestCode == REQUEST_MEDIA_PERMISSION) {
|
||||
for (int result : grantResults) {
|
||||
if (result != PackageManager.PERMISSION_GRANTED) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
package com.example.myapplication.Tool;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
public class PermissionUtils {
|
||||
// 请求码
|
||||
public static final int REQUEST_STORAGE_PERMISSION = 101;
|
||||
public static final int REQUEST_MEDIA_PERMISSION = 102; // Android 13+ 媒体权限
|
||||
|
||||
// 检查并请求所有必要权限
|
||||
public static boolean checkAndRequestPermissions(Activity activity) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
// Android 14+ 需要检查媒体权限(如果要访问媒体文件)
|
||||
return checkMediaPermissions(activity);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
// Android 10-13 使用 SAF 或 MediaStore,不需要存储权限
|
||||
return true;
|
||||
} else {
|
||||
// Android 9 及以下需要传统存储权限
|
||||
return checkLegacyStoragePermission(activity);
|
||||
}
|
||||
}
|
||||
|
||||
// Android 14+ 媒体权限检查
|
||||
private static boolean checkMediaPermissions(Activity activity) {
|
||||
String[] permissions;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
permissions = new String[]{
|
||||
Manifest.permission.READ_MEDIA_IMAGES,
|
||||
Manifest.permission.READ_MEDIA_VIDEO,
|
||||
Manifest.permission.READ_MEDIA_AUDIO
|
||||
};
|
||||
} else {
|
||||
permissions = new String[]{Manifest.permission.READ_EXTERNAL_STORAGE};
|
||||
}
|
||||
|
||||
boolean allGranted = true;
|
||||
for (String perm : permissions) {
|
||||
if (ContextCompat.checkSelfPermission(activity, perm) != PackageManager.PERMISSION_GRANTED) {
|
||||
allGranted = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!allGranted) {
|
||||
ActivityCompat.requestPermissions(activity, permissions, REQUEST_MEDIA_PERMISSION);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Android 9 及以下传统存储权限
|
||||
private static boolean checkLegacyStoragePermission(Activity activity) {
|
||||
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(activity,
|
||||
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||
REQUEST_STORAGE_PERMISSION);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 处理权限请求结果
|
||||
public static boolean handlePermissionResult(int requestCode,
|
||||
String[] permissions, int[] grantResults) {
|
||||
if (grantResults.length == 0) return false;
|
||||
|
||||
if (requestCode == REQUEST_STORAGE_PERMISSION ||
|
||||
requestCode == REQUEST_MEDIA_PERMISSION) {
|
||||
for (int result : grantResults) {
|
||||
if (result != PackageManager.PERMISSION_GRANTED) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
package com.example.myapplication.Tool;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import com.chaquo.python.Python;
|
||||
import com.chaquo.python.android.AndroidPlatform;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
public class ReportGenerator {
|
||||
|
||||
|
||||
private static final String TAG = "ReportGenerator";
|
||||
private static final String TEMPLATE_DIR = "templates";
|
||||
private final Context context;
|
||||
|
||||
public ReportGenerator(Context context) {
|
||||
this.context = context;
|
||||
initPython();
|
||||
prepareTemplates();
|
||||
}
|
||||
|
||||
private void initPython() {
|
||||
if (!Python.isStarted()) {
|
||||
Python.start(new AndroidPlatform(context));
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareTemplates() {
|
||||
File templateDir = new File(context.getFilesDir(), TEMPLATE_DIR);
|
||||
if (!templateDir.exists() || isTemplateUpdateNeeded(templateDir)) {
|
||||
copyAssetsTemplates(templateDir);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isTemplateUpdateNeeded(File templateDir) {
|
||||
return templateDir.listFiles() == null || templateDir.listFiles().length == 0;
|
||||
}
|
||||
|
||||
private void copyAssetsTemplates(File targetDir) {
|
||||
try {
|
||||
if (!targetDir.exists()) {
|
||||
targetDir.mkdirs();
|
||||
}
|
||||
|
||||
String[] files = context.getAssets().list(TEMPLATE_DIR);
|
||||
if (files != null) {
|
||||
for (String filename : files) {
|
||||
File outFile = new File(targetDir, filename);
|
||||
try (InputStream in = context.getAssets().open(TEMPLATE_DIR + "/" + filename);
|
||||
OutputStream out = new FileOutputStream(outFile)) {
|
||||
byte[] buffer = new byte[1024];
|
||||
int read;
|
||||
while ((read = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to copy templates", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String generateReport() {
|
||||
try {
|
||||
// 创建模拟数据
|
||||
JSONObject jsonData1 = createMockJsonData1();
|
||||
JSONObject jsonData2 = createMockJsonData2();
|
||||
|
||||
Python py = Python.getInstance();
|
||||
|
||||
py.getModule("report_generate_server.Generate_Report")
|
||||
.callAttr("main",
|
||||
jsonData1.toString(),
|
||||
jsonData2.toString());
|
||||
return jsonData2.getString("shengcheng_dir");
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Report generation failed", e);
|
||||
return "Error: " + e.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private JSONObject createMockJsonData1() throws JSONException {
|
||||
JSONObject jsonData1 = new JSONObject();
|
||||
|
||||
// 项目基本信息
|
||||
JSONObject jizuData = new JSONObject();
|
||||
jizuData.put("turbineName", "风力发电机001");
|
||||
|
||||
JSONObject projectData = new JSONObject();
|
||||
projectData.put("fengmian_dir", "storage/emulated/0/DCIM/Camera/IMG_20250721_173921.jpg");
|
||||
projectData.put("farmName", "华北风电场");
|
||||
projectData.put("inspectionUnit", "华北检测公司");
|
||||
projectData.put("inspectionContact", "李检测员");
|
||||
projectData.put("inspectionPhone", "13800138000");
|
||||
projectData.put("farmAddress", "河北省张家口市");
|
||||
projectData.put("client", "国家电网");
|
||||
projectData.put("clientContact", "王经理");
|
||||
projectData.put("clientPhone", "13900139000");
|
||||
projectData.put("scale", "50MW");
|
||||
projectData.put("turbineModel", "GW-1500");
|
||||
projectData.put("projectName", "华北风电场年度检测");
|
||||
projectData.put("startDate", "2025-07-01");
|
||||
projectData.put("endDate", "2025-07-31");
|
||||
|
||||
JSONObject shigongData = new JSONObject();
|
||||
shigongData.put("startTime", "2025-07-21 08:00");
|
||||
shigongData.put("endTime", "2025-07-21 17:00");
|
||||
shigongData.put("weatherCode", "晴");
|
||||
shigongData.put("windSpeed", "5.2m/s");
|
||||
shigongData.put("imageCount", "120");
|
||||
shigongData.put("temperature", "28℃");
|
||||
|
||||
jsonData1.put("jizu_data", jizuData);
|
||||
jsonData1.put("project_data", projectData);
|
||||
jsonData1.put("shigong_data", shigongData);
|
||||
jsonData1.put("partManufacturer", "金风科技");
|
||||
|
||||
// Y叶片数据
|
||||
String imagePath = "storage/emulated/0/DCIM/Camera/IMG_20250721_173921.jpg";
|
||||
|
||||
JSONObject y1 = new JSONObject();
|
||||
y1.put("Code", "1");
|
||||
JSONObject y1List = new JSONObject();
|
||||
y1List.put("叶片前缘", imagePath);
|
||||
y1List.put("叶片后缘", imagePath);
|
||||
y1List.put("叶片表面", imagePath);
|
||||
y1.put("list_dict", y1List);
|
||||
|
||||
JSONObject y2 = new JSONObject();
|
||||
y2.put("Code", "2");
|
||||
JSONObject y2List = new JSONObject();
|
||||
y2List.put("叶片前缘", imagePath);
|
||||
y2List.put("叶片后缘", imagePath);
|
||||
y2List.put("叶片表面", imagePath);
|
||||
y2.put("list_dict", y2List);
|
||||
|
||||
JSONObject y3 = new JSONObject();
|
||||
y3.put("Code", "3");
|
||||
JSONObject y3List = new JSONObject();
|
||||
y3List.put("叶片前缘", imagePath);
|
||||
y3List.put("叶片后缘", imagePath);
|
||||
y3List.put("叶片表面", imagePath);
|
||||
y3.put("list_dict", y3List);
|
||||
|
||||
jsonData1.put("Y1", y1);
|
||||
jsonData1.put("Y2", y2);
|
||||
jsonData1.put("Y3", y3);
|
||||
|
||||
return jsonData1;
|
||||
}
|
||||
|
||||
private JSONObject createMockJsonData2() throws JSONException {
|
||||
JSONObject jsonData2 = new JSONObject();
|
||||
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.getDefault());
|
||||
String templatePath = new File(context.getFilesDir(), TEMPLATE_DIR).getAbsolutePath();
|
||||
String outputPath = context.getExternalFilesDir(null).getAbsolutePath();
|
||||
jsonData2.put("shengcheng_dir", outputPath);
|
||||
jsonData2.put("muban_dir", templatePath);
|
||||
jsonData2.put("if_waibu", false);
|
||||
jsonData2.put("if_neibu", true);
|
||||
jsonData2.put("if_fanglei", true);
|
||||
jsonData2.put("userName", "admin");
|
||||
jsonData2.put("baogaoCheck", "未审核");
|
||||
jsonData2.put("key_words", "缺,损,裂,脱,污");
|
||||
jsonData2.put("shigong_fangan", "标准检测方案");
|
||||
jsonData2.put("jiancha_renyuan", "张三,李四");
|
||||
jsonData2.put("baogao_zongjie", "本次检测共发现3处缺陷,需要及时处理");
|
||||
jsonData2.put("total_pic_num", "120");
|
||||
|
||||
return jsonData2;
|
||||
}
|
||||
}
|
|
@ -1,89 +1,89 @@
|
|||
package com.example.myapplication.Tool;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.chaquo.python.Python;
|
||||
import com.chaquo.python.android.AndroidPlatform;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ReportGeneratorHelper {
|
||||
|
||||
private Context context;
|
||||
|
||||
public ReportGeneratorHelper(Context context) {
|
||||
this.context = context;
|
||||
if (!Python.isStarted()) {
|
||||
Python.start(new AndroidPlatform(context));
|
||||
}
|
||||
}
|
||||
|
||||
public void generateReport(String templateContent, String outputPath,
|
||||
String turbineId, String testDate,
|
||||
List<ImageData> imageDataList,
|
||||
String weather, String temperature, String humidity,
|
||||
String startDate, String endDate,
|
||||
ReportGenerationCallback callback) {
|
||||
try {
|
||||
Python py = Python.getInstance();
|
||||
|
||||
// 转换Java的ImageData列表为Python可接受的格式
|
||||
List<List<Object>> pyImageData = new ArrayList<>();
|
||||
for (ImageData data : imageDataList) {
|
||||
List<Object> item = new ArrayList<>();
|
||||
item.add(data.getImagePath());
|
||||
item.add(data.getResistance());
|
||||
pyImageData.add(item);
|
||||
}
|
||||
|
||||
// 准备配置字典
|
||||
Map<String, Object> config = new HashMap<>();
|
||||
config.put("template_content", templateContent);
|
||||
config.put("output_path", outputPath);
|
||||
config.put("turbine_id", turbineId);
|
||||
config.put("test_date", testDate);
|
||||
config.put("image_data", pyImageData);
|
||||
config.put("weather", weather);
|
||||
config.put("temperature", temperature);
|
||||
config.put("humidity", humidity);
|
||||
config.put("start_date", startDate);
|
||||
config.put("end_date", endDate);
|
||||
String jsonConfig = new Gson().toJson(config);
|
||||
// 调用Python模块
|
||||
py.getModule("report_generator")
|
||||
.callAttr("generate_report_from_java", jsonConfig);
|
||||
// 成功回调
|
||||
callback.onSuccess(outputPath);
|
||||
|
||||
} catch (Exception e) {
|
||||
callback.onError(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public interface ReportGenerationCallback {
|
||||
void onSuccess(String outputPath);
|
||||
void onError(String errorMessage);
|
||||
}
|
||||
|
||||
public static class ImageData {
|
||||
private String imagePath;
|
||||
private String resistance;
|
||||
|
||||
public ImageData(String imagePath, String resistance) {
|
||||
this.imagePath = imagePath;
|
||||
this.resistance = resistance;
|
||||
}
|
||||
|
||||
public String getImagePath() {
|
||||
return imagePath;
|
||||
}
|
||||
|
||||
public String getResistance() {
|
||||
return resistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
package com.example.myapplication.Tool;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.chaquo.python.Python;
|
||||
import com.chaquo.python.android.AndroidPlatform;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ReportGeneratorHelper {
|
||||
|
||||
private Context context;
|
||||
|
||||
public ReportGeneratorHelper(Context context) {
|
||||
this.context = context;
|
||||
if (!Python.isStarted()) {
|
||||
Python.start(new AndroidPlatform(context));
|
||||
}
|
||||
}
|
||||
|
||||
public void generateReport(String templateContent, String outputPath,
|
||||
String turbineId, String testDate,
|
||||
List<ImageData> imageDataList,
|
||||
String weather, String temperature, String humidity,
|
||||
String startDate, String endDate,
|
||||
ReportGenerationCallback callback) {
|
||||
try {
|
||||
Python py = Python.getInstance();
|
||||
|
||||
// 转换Java的ImageData列表为Python可接受的格式
|
||||
List<List<Object>> pyImageData = new ArrayList<>();
|
||||
for (ImageData data : imageDataList) {
|
||||
List<Object> item = new ArrayList<>();
|
||||
item.add(data.getImagePath());
|
||||
item.add(data.getResistance());
|
||||
pyImageData.add(item);
|
||||
}
|
||||
|
||||
// 准备配置字典
|
||||
Map<String, Object> config = new HashMap<>();
|
||||
config.put("template_content", templateContent);
|
||||
config.put("output_path", outputPath);
|
||||
config.put("turbine_id", turbineId);
|
||||
config.put("test_date", testDate);
|
||||
config.put("image_data", pyImageData);
|
||||
config.put("weather", weather);
|
||||
config.put("temperature", temperature);
|
||||
config.put("humidity", humidity);
|
||||
config.put("start_date", startDate);
|
||||
config.put("end_date", endDate);
|
||||
String jsonConfig = new Gson().toJson(config);
|
||||
// 调用Python模块
|
||||
py.getModule("report_generator")
|
||||
.callAttr("generate_report_from_java", jsonConfig);
|
||||
// 成功回调
|
||||
callback.onSuccess(outputPath);
|
||||
|
||||
} catch (Exception e) {
|
||||
callback.onError(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public interface ReportGenerationCallback {
|
||||
void onSuccess(String outputPath);
|
||||
void onError(String errorMessage);
|
||||
}
|
||||
|
||||
public static class ImageData {
|
||||
private String imagePath;
|
||||
private String resistance;
|
||||
|
||||
public ImageData(String imagePath, String resistance) {
|
||||
this.imagePath = imagePath;
|
||||
this.resistance = resistance;
|
||||
}
|
||||
|
||||
public String getImagePath() {
|
||||
return imagePath;
|
||||
}
|
||||
|
||||
public String getResistance() {
|
||||
return resistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,38 +1,38 @@
|
|||
package com.example.myapplication.Tool;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.logging.HttpLoggingInterceptor;
|
||||
import retrofit2.Retrofit;
|
||||
import retrofit2.converter.gson.GsonConverterFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class RetrofitClient {
|
||||
private static final String BASE_URL = "http://pms.dtyx.net:9158/";
|
||||
private static Retrofit retrofit;
|
||||
|
||||
public static Retrofit getClient(String authToken) {
|
||||
if (retrofit == null) {
|
||||
// 创建 OkHttpClient 并添加拦截器
|
||||
OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder()
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(30, TimeUnit.SECONDS)
|
||||
.writeTimeout(30, TimeUnit.SECONDS);
|
||||
|
||||
// 添加日志拦截器(可选)
|
||||
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
|
||||
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
|
||||
okHttpClientBuilder.addInterceptor(loggingInterceptor);
|
||||
|
||||
// 添加认证拦截器(自动添加 Authorization header)
|
||||
okHttpClientBuilder.addInterceptor(new AuthInterceptor(authToken));
|
||||
|
||||
// 构建 Retrofit
|
||||
retrofit = new Retrofit.Builder()
|
||||
.baseUrl(BASE_URL)
|
||||
.client(okHttpClientBuilder.build())
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build();
|
||||
}
|
||||
return retrofit;
|
||||
}
|
||||
package com.example.myapplication.Tool;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.logging.HttpLoggingInterceptor;
|
||||
import retrofit2.Retrofit;
|
||||
import retrofit2.converter.gson.GsonConverterFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class RetrofitClient {
|
||||
private static final String BASE_URL = "http://pms.dtyx.net:9158/";
|
||||
private static Retrofit retrofit;
|
||||
|
||||
public static Retrofit getClient(String authToken) {
|
||||
if (retrofit == null) {
|
||||
// 创建 OkHttpClient 并添加拦截器
|
||||
OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder()
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(30, TimeUnit.SECONDS)
|
||||
.writeTimeout(30, TimeUnit.SECONDS);
|
||||
|
||||
// 添加日志拦截器(可选)
|
||||
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
|
||||
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
|
||||
okHttpClientBuilder.addInterceptor(loggingInterceptor);
|
||||
|
||||
// 添加认证拦截器(自动添加 Authorization header)
|
||||
okHttpClientBuilder.addInterceptor(new AuthInterceptor(authToken));
|
||||
|
||||
// 构建 Retrofit
|
||||
retrofit = new Retrofit.Builder()
|
||||
.baseUrl(BASE_URL)
|
||||
.client(okHttpClientBuilder.build())
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build();
|
||||
}
|
||||
return retrofit;
|
||||
}
|
||||
}
|
|
@ -1,47 +1,47 @@
|
|||
package com.example.myapplication.Tool;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.ClipData;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
public class ShowError {
|
||||
private Context context;
|
||||
private final Handler mainHandler;
|
||||
public ShowError(Context context) {
|
||||
this.context = context;
|
||||
this.mainHandler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
public void showErrorDialog(String title, String message) {
|
||||
if (context instanceof AppCompatActivity) {
|
||||
AppCompatActivity activity = (AppCompatActivity) context;
|
||||
activity.runOnUiThread(() -> {
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton("确定", null)
|
||||
.setNegativeButton("复制错误", (dialog, which) -> {
|
||||
// 将错误复制到剪贴板
|
||||
ClipboardManager clipboard =
|
||||
(ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clip =
|
||||
ClipData.newPlainText("错误信息", message);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
showToast("已复制错误信息");
|
||||
})
|
||||
.show();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void showToast(String message) {
|
||||
mainHandler.post(() -> Toast.makeText(context, message, Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
|
||||
package com.example.myapplication.Tool;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.ClipData;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
public class ShowError {
|
||||
private Context context;
|
||||
private final Handler mainHandler;
|
||||
public ShowError(Context context) {
|
||||
this.context = context;
|
||||
this.mainHandler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
public void showErrorDialog(String title, String message) {
|
||||
if (context instanceof AppCompatActivity) {
|
||||
AppCompatActivity activity = (AppCompatActivity) context;
|
||||
activity.runOnUiThread(() -> {
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton("确定", null)
|
||||
.setNegativeButton("复制错误", (dialog, which) -> {
|
||||
// 将错误复制到剪贴板
|
||||
ClipboardManager clipboard =
|
||||
(ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clip =
|
||||
ClipData.newPlainText("错误信息", message);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
showToast("已复制错误信息");
|
||||
})
|
||||
.show();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void showToast(String message) {
|
||||
mainHandler.post(() -> Toast.makeText(context, message, Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,21 +1,22 @@
|
|||
package com.example.myapplication.api;
|
||||
|
||||
import com.example.myapplication.LoginActivity;
|
||||
import com.example.myapplication.model.ApiResponse;
|
||||
import com.example.myapplication.model.LoginEntity;
|
||||
import com.example.myapplication.model.LoginRequest;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.Body;
|
||||
import retrofit2.http.POST;
|
||||
import retrofit2.http.PUT;
|
||||
|
||||
public interface AuthApi {
|
||||
@POST("/auth/login")
|
||||
Call<ApiResponse<LoginEntity>> login(@Body LoginRequest loginRequest);
|
||||
@PUT("/auth/modify-password")
|
||||
|
||||
Call<ApiResponse<Void>> modifyPassword(
|
||||
@Body LoginActivity.ChangePasswordRequest request
|
||||
);
|
||||
}
|
||||
package com.example.myapplication.api;
|
||||
|
||||
import com.example.myapplication.LoginActivity;
|
||||
import com.example.myapplication.model.ApiResponse;
|
||||
import com.example.myapplication.model.ChangePasswordRequest;
|
||||
import com.example.myapplication.model.LoginEntity;
|
||||
import com.example.myapplication.model.LoginRequest;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.Body;
|
||||
import retrofit2.http.POST;
|
||||
import retrofit2.http.PUT;
|
||||
|
||||
public interface AuthApi {
|
||||
@POST("/auth/login")
|
||||
Call<ApiResponse<LoginEntity>> login(@Body LoginRequest loginRequest);
|
||||
@PUT("/auth/modify-password")
|
||||
|
||||
Call<ApiResponse<Void>> modifyPassword(
|
||||
@Body ChangePasswordRequest request
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
package com.example.myapplication.api;
|
||||
|
||||
import com.example.myapplication.model.ApiResponse;
|
||||
import com.example.myapplication.model.ImageSourceItem;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Header;
|
||||
|
||||
public interface CommonService {
|
||||
@GET("/common/list/common-image-source")
|
||||
Call<ApiResponse<List<ImageSourceItem>>> getImageSourceList(
|
||||
|
||||
);
|
||||
@GET("/common/list/common-image-source")
|
||||
Call<ApiResponse<List<Map<String, String>>>> getDynamicDataList();
|
||||
}
|
||||
package com.example.myapplication.api;
|
||||
|
||||
import com.example.myapplication.model.ApiResponse;
|
||||
import com.example.myapplication.model.ImageSourceItem;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Header;
|
||||
|
||||
public interface CommonService {
|
||||
@GET("/common/list/common-image-source")
|
||||
Call<ApiResponse<List<ImageSourceItem>>> getImageSourceList(
|
||||
|
||||
);
|
||||
@GET("/common/list/common-image-source")
|
||||
Call<ApiResponse<List<Map<String, String>>>> getDynamicDataList();
|
||||
}
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
package com.example.myapplication.api;
|
||||
|
||||
import com.example.myapplication.model.ApiResponse;
|
||||
import com.example.myapplication.model.PartResponse;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Query;
|
||||
|
||||
public interface PartService {
|
||||
@GET("/part/list")
|
||||
Call<ApiResponse<List<PartResponse>>> getPartList(
|
||||
@Query("projectId") String projectId,
|
||||
@Query("keyword") String keyword,
|
||||
@Query("partManufacturer") String partManufacturer,
|
||||
@Query("partModel") String partModel,
|
||||
@Query("partType") String partType
|
||||
|
||||
);
|
||||
}
|
||||
package com.example.myapplication.api;
|
||||
|
||||
import com.example.myapplication.model.ApiResponse;
|
||||
import com.example.myapplication.model.PartResponse;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Query;
|
||||
|
||||
public interface PartService {
|
||||
@GET("/part/list")
|
||||
Call<ApiResponse<List<PartResponse>>> getPartList(
|
||||
@Query("projectId") String projectId,
|
||||
@Query("keyword") String keyword,
|
||||
@Query("partManufacturer") String partManufacturer,
|
||||
@Query("partModel") String partModel,
|
||||
@Query("partType") String partType
|
||||
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
package com.example.myapplication.api;
|
||||
|
||||
import com.example.myapplication.model.ApiResponse;
|
||||
import com.example.myapplication.model.Project;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Headers;
|
||||
|
||||
|
||||
// ApiService.java
|
||||
|
||||
|
||||
public interface ProjectApi {
|
||||
@GET("project/list")
|
||||
Call<ApiResponse<List<Project>>> getProjectList();
|
||||
}
|
||||
package com.example.myapplication.api;
|
||||
|
||||
import com.example.myapplication.model.ApiResponse;
|
||||
import com.example.myapplication.model.Project;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
|
||||
|
||||
// ApiService.java
|
||||
|
||||
|
||||
public interface ProjectApi {
|
||||
@GET("project/list")
|
||||
Call<ApiResponse<List<Project>>> getMyProjectList();
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
package com.example.myapplication.api;
|
||||
|
||||
|
||||
|
||||
import com.example.myapplication.model.ApiResponse;
|
||||
import com.example.myapplication.model.Turbine;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Query;
|
||||
|
||||
public interface TurbineApiService {
|
||||
@GET("turbine/list")
|
||||
Call<ApiResponse<List<Turbine>>> getTurbineList(@Query("projectId") String projectId);
|
||||
package com.example.myapplication.api;
|
||||
|
||||
|
||||
|
||||
import com.example.myapplication.model.ApiResponse;
|
||||
import com.example.myapplication.model.Turbine;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Query;
|
||||
|
||||
public interface TurbineApiService {
|
||||
@GET("turbine/list")
|
||||
Call<ApiResponse<List<Turbine>>> getTurbineList(@Query("projectId") String projectId);
|
||||
}
|
|
@ -1,23 +1,23 @@
|
|||
package com.example.myapplication.api;
|
||||
|
||||
import com.example.myapplication.model.PageResult;
|
||||
import com.example.myapplication.model.UserInfo;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Header;
|
||||
import retrofit2.http.Query;
|
||||
|
||||
public interface UserApi {
|
||||
@GET("/user/list")
|
||||
Call<PageResult<UserInfo>> getUserList(
|
||||
@Header("Authorization") String token,
|
||||
@Query("account") String account,
|
||||
@Query("deptId") String deptId,
|
||||
@Query("mobile") String mobile,
|
||||
@Query("name") String name,
|
||||
@Query("userCode") String userCode,
|
||||
@Query("userStatus") String userStatus,
|
||||
@Query("userType") String userType
|
||||
);
|
||||
}
|
||||
package com.example.myapplication.api;
|
||||
|
||||
import com.example.myapplication.model.PageResult;
|
||||
import com.example.myapplication.model.UserInfo;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Header;
|
||||
import retrofit2.http.Query;
|
||||
|
||||
public interface UserApi {
|
||||
@GET("/user/list")
|
||||
Call<PageResult<UserInfo>> getUserList(
|
||||
@Header("Authorization") String token,
|
||||
@Query("account") String account,
|
||||
@Query("deptId") String deptId,
|
||||
@Query("mobile") String mobile,
|
||||
@Query("name") String name,
|
||||
@Query("userCode") String userCode,
|
||||
@Query("userStatus") String userStatus,
|
||||
@Query("userType") String userType
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,61 +1,61 @@
|
|||
package com.example.myapplication.model;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class ApiResponse<T> {
|
||||
|
||||
|
||||
|
||||
@SerializedName("code")
|
||||
private int code;
|
||||
|
||||
@SerializedName("msg")
|
||||
private String msg; // 严格匹配服务器字段名 "msg"
|
||||
|
||||
@SerializedName("data")
|
||||
private T data;
|
||||
|
||||
@SerializedName("status")
|
||||
private int status;
|
||||
|
||||
@SerializedName("success")
|
||||
private boolean success;
|
||||
|
||||
// Getters
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getMsg() { // 方法名改为 getMsg() 与字段名一致
|
||||
return msg;
|
||||
}
|
||||
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
// Setters(按需添加)
|
||||
public void setData(T data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ApiResponse{" +
|
||||
"code=" + code +
|
||||
", msg='" + msg + '\'' +
|
||||
", data=" + data +
|
||||
", status=" + status +
|
||||
", success=" + success +
|
||||
'}';
|
||||
}
|
||||
|
||||
package com.example.myapplication.model;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class ApiResponse<T> {
|
||||
|
||||
|
||||
|
||||
@SerializedName("code")
|
||||
private int code;
|
||||
|
||||
@SerializedName("msg")
|
||||
private String msg; // 严格匹配服务器字段名 "msg"
|
||||
|
||||
@SerializedName("data")
|
||||
private T data;
|
||||
|
||||
@SerializedName("status")
|
||||
private int status;
|
||||
|
||||
@SerializedName("success")
|
||||
private boolean success;
|
||||
|
||||
// Getters
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getMsg() { // 方法名改为 getMsg() 与字段名一致
|
||||
return msg;
|
||||
}
|
||||
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
// Setters(按需添加)
|
||||
public void setData(T data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ApiResponse{" +
|
||||
"code=" + code +
|
||||
", msg='" + msg + '\'' +
|
||||
", data=" + data +
|
||||
", status=" + status +
|
||||
", success=" + success +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
|
@ -1,98 +1,99 @@
|
|||
package com.example.myapplication.model;
|
||||
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
@Entity(tableName = "audio_entities")
|
||||
public class AudioEntity {
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
private int id;
|
||||
|
||||
private String fileName;
|
||||
public String AudioPath;
|
||||
|
||||
public AudioEntity() {
|
||||
}
|
||||
|
||||
public String ImagePath;
|
||||
|
||||
public String getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(String time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
private long fileSize;
|
||||
public String time;
|
||||
|
||||
public AudioEntity(String audioPath, String imagePath,String time) {
|
||||
this.AudioPath = audioPath;
|
||||
this.ImagePath = imagePath;
|
||||
this.time=time;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
public String getAudioPath() {
|
||||
return AudioPath;
|
||||
}
|
||||
|
||||
public void setAudioPath(String audioPath) {
|
||||
AudioPath = audioPath;
|
||||
}
|
||||
|
||||
public String getImagePath() {
|
||||
return ImagePath;
|
||||
}
|
||||
|
||||
public void setImagePath(String imagePath) {
|
||||
ImagePath = imagePath;
|
||||
}
|
||||
|
||||
public long getFileSize() {
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
public void setFileSize(long fileSize) {
|
||||
this.fileSize = fileSize;
|
||||
}
|
||||
|
||||
public long getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
public void setDuration(long duration) {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
public long getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(long createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
private long duration; // 音频时长(毫秒)
|
||||
private long createdAt; // 创建时间戳
|
||||
|
||||
// 构造方法、getter 和 setter
|
||||
|
||||
|
||||
package com.example.myapplication.model;
|
||||
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
@Entity(tableName = "audio_entities")
|
||||
public class AudioEntity {
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
private int id;
|
||||
public boolean isupdated;
|
||||
private String fileName;
|
||||
public String AudioPath;
|
||||
|
||||
public AudioEntity() {
|
||||
}
|
||||
|
||||
public String ImagePath;
|
||||
|
||||
public String getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(String time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
private long fileSize;
|
||||
public String time;
|
||||
|
||||
public AudioEntity(String audioPath, String imagePath,String time,boolean isupdated) {
|
||||
this.AudioPath = audioPath;
|
||||
this.ImagePath = imagePath;
|
||||
this.time=time;
|
||||
this.isupdated=isupdated;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
public String getAudioPath() {
|
||||
return AudioPath;
|
||||
}
|
||||
|
||||
public void setAudioPath(String audioPath) {
|
||||
AudioPath = audioPath;
|
||||
}
|
||||
|
||||
public String getImagePath() {
|
||||
return ImagePath;
|
||||
}
|
||||
|
||||
public void setImagePath(String imagePath) {
|
||||
ImagePath = imagePath;
|
||||
}
|
||||
|
||||
public long getFileSize() {
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
public void setFileSize(long fileSize) {
|
||||
this.fileSize = fileSize;
|
||||
}
|
||||
|
||||
public long getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
public void setDuration(long duration) {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
public long getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(long createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
private long duration; // 音频时长(毫秒)
|
||||
private long createdAt; // 创建时间戳
|
||||
|
||||
// 构造方法、getter 和 setter
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package com.example.myapplication.model;
|
||||
|
||||
public class ChangePasswordRequest {
|
||||
private String account;
|
||||
private String newPassword;
|
||||
private String oldPassword;
|
||||
|
||||
public ChangePasswordRequest(String account, String newPassword, String oldPassword) {
|
||||
this.account = account;
|
||||
this.newPassword = newPassword;
|
||||
this.oldPassword = oldPassword;
|
||||
}
|
||||
|
||||
// getters and setters
|
||||
public String getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
public void setAccount(String account) {
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
public String getNewPassword() {
|
||||
return newPassword;
|
||||
}
|
||||
|
||||
public void setNewPassword(String newPassword) {
|
||||
this.newPassword = newPassword;
|
||||
}
|
||||
|
||||
public String getOldPassword() {
|
||||
return oldPassword;
|
||||
}
|
||||
|
||||
public void setOldPassword(String oldPassword) {
|
||||
this.oldPassword = oldPassword;
|
||||
}
|
||||
}
|
|
@ -1,57 +1,58 @@
|
|||
package com.example.myapplication.model;
|
||||
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
|
||||
@Entity(tableName = "images")
|
||||
public class ImageEntity {
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
public int id;
|
||||
public String path;
|
||||
public long time;
|
||||
public double latitude;
|
||||
public double longitude;
|
||||
public double altitude;
|
||||
public String user;
|
||||
public String audioPath;
|
||||
public int blade;
|
||||
public String unit;
|
||||
public String unitName;
|
||||
public String project;
|
||||
public boolean b;
|
||||
public String temperature="0"; // 温度
|
||||
public String humidity="0"; // 湿度
|
||||
public String weather="0"; // 天气状况
|
||||
public String imageSource="-1";
|
||||
|
||||
public ImageEntity(String path, long time, double latitude, double longitude, double altitude, String user, String audioPath, String project, String unit, int blade, boolean b,String unitName,String temperature,String humidity,String weather,String imageSource) {
|
||||
|
||||
this.path = path;
|
||||
this.time = time;
|
||||
this.latitude = latitude;
|
||||
this.longitude = longitude;
|
||||
this.altitude = altitude;
|
||||
this.user = user;
|
||||
this.audioPath = audioPath;
|
||||
this.project=project;
|
||||
this.unit=unit;
|
||||
this.blade=blade;
|
||||
this.b=b;
|
||||
this.weather=weather;
|
||||
this.temperature=temperature;
|
||||
this.humidity=humidity;
|
||||
this.unitName=unitName;
|
||||
this.imageSource=imageSource;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 构造方法
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
// ImageDao.java
|
||||
|
||||
package com.example.myapplication.model;
|
||||
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
|
||||
@Entity(tableName = "images")
|
||||
public class ImageEntity {
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
public int id;
|
||||
public String path;
|
||||
public long time;
|
||||
public double latitude;
|
||||
public double longitude;
|
||||
public double altitude;
|
||||
public String user;
|
||||
public String audioPath;
|
||||
public String turbineId;
|
||||
public String turbineName;
|
||||
public String partId;
|
||||
public String partName;
|
||||
public String project;
|
||||
public boolean b;
|
||||
public String temperature="0"; // 温度
|
||||
public String humidity="0"; // 湿度
|
||||
public String weather="0"; // 天气状况
|
||||
public String imageSource="-1";
|
||||
public ImageEntity(String path, long time, double latitude, double longitude, double altitude, String user, String audioPath, String project, String partId, String partName, String turbineId, String turbineName, boolean b, String temperature, String humidity, String weather, String imageSource) {
|
||||
|
||||
this.path = path;
|
||||
this.time = time;
|
||||
this.latitude = latitude;
|
||||
this.longitude = longitude;
|
||||
this.altitude = altitude;
|
||||
this.user = user;
|
||||
this.audioPath = audioPath;
|
||||
this.project=project;
|
||||
this.partId = partId;
|
||||
this.partName = partName;
|
||||
this.turbineId=turbineId;
|
||||
this.turbineName=turbineName;
|
||||
this.b=b;
|
||||
this.weather=weather;
|
||||
this.temperature=temperature;
|
||||
this.humidity=humidity;
|
||||
this.imageSource=imageSource;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 构造方法
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
// ImageDao.java
|
||||
|
||||
|
|
|
@ -1,169 +0,0 @@
|
|||
package com.example.myapplication.model;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
public class ImageInfo {
|
||||
private String path; // 图片路径
|
||||
private long time; // 时间戳(单位:秒)
|
||||
private Bitmap bitmap; // 图片缩略图
|
||||
private double latitude;
|
||||
private double longitude;
|
||||
private double altitude;
|
||||
private String user;
|
||||
|
||||
public boolean isB() {
|
||||
return b;
|
||||
}
|
||||
|
||||
public void setB(boolean b) {
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
public ImageInfo(String path, long time, Bitmap bitmap, double latitude, double longitude, double altitude, String user, String audioPath, boolean b, Bitmap thumbnail, String unit, String project, int blade) {
|
||||
this.path = path;
|
||||
this.time = time;
|
||||
this.bitmap = bitmap;
|
||||
this.latitude = latitude;
|
||||
this.longitude = longitude;
|
||||
this.altitude = altitude;
|
||||
this.user = user;
|
||||
this.audioPath = audioPath;
|
||||
this.b = b;
|
||||
this.thumbnail = thumbnail;
|
||||
this.unit = unit;
|
||||
this.project = project;
|
||||
this.blade = blade;
|
||||
}
|
||||
|
||||
boolean b;
|
||||
|
||||
public Bitmap getThumbnail() {
|
||||
return thumbnail;
|
||||
}
|
||||
|
||||
public void setThumbnail(Bitmap thumbnail) {
|
||||
this.thumbnail = thumbnail;
|
||||
}
|
||||
|
||||
public String getProject() {
|
||||
return project;
|
||||
}
|
||||
|
||||
public void setProject(String project) {
|
||||
this.project = project;
|
||||
}
|
||||
|
||||
public String getUnit() {
|
||||
return unit;
|
||||
}
|
||||
|
||||
public void setUnit(String unit) {
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
public int getBlade() {
|
||||
return blade;
|
||||
}
|
||||
|
||||
public void setBlade(int blade) {
|
||||
this.blade = blade;
|
||||
}
|
||||
|
||||
private String audioPath;
|
||||
public Bitmap thumbnail;
|
||||
private String project;
|
||||
private String unit;
|
||||
private int blade;
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public void setTime(long time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public void setBitmap(Bitmap bitmap) {
|
||||
this.bitmap = bitmap;
|
||||
}
|
||||
|
||||
public void setLatitude(double latitude) {
|
||||
this.latitude = latitude;
|
||||
}
|
||||
|
||||
public void setLongitude(double longitude) {
|
||||
this.longitude = longitude;
|
||||
}
|
||||
|
||||
public void setAltitude(double altitude) {
|
||||
this.altitude = altitude;
|
||||
}
|
||||
|
||||
public void setUser(String user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public String getAudioPath() {
|
||||
return audioPath;
|
||||
}
|
||||
|
||||
public void setAudioPath(String audioPath) {
|
||||
this.audioPath = audioPath;
|
||||
}
|
||||
|
||||
// 修改构造函数
|
||||
public ImageInfo(String path, long time, Bitmap thumbnail, double latitude, double longitude, double altitude, String user, String audioPath) {
|
||||
this.path = path;
|
||||
this.time = time;
|
||||
this.bitmap = bitmap;
|
||||
this.latitude = latitude;
|
||||
this.longitude = longitude;
|
||||
this.altitude=altitude;
|
||||
this.user=user;
|
||||
this.audioPath=audioPath;
|
||||
}
|
||||
|
||||
public ImageInfo(String path, long time, Bitmap bitmap, double latitude, double longitude, double altitude, String user, String audioPath, Bitmap thumbnail, String project, String unit, int blade) {
|
||||
this.path = path;
|
||||
this.time = time;
|
||||
this.bitmap = bitmap;
|
||||
this.latitude = latitude;
|
||||
this.longitude = longitude;
|
||||
this.altitude = altitude;
|
||||
this.user = user;
|
||||
this.audioPath = audioPath;
|
||||
this.thumbnail = thumbnail;
|
||||
this.project = project;
|
||||
this.unit = unit;
|
||||
this.blade = blade;
|
||||
}
|
||||
|
||||
public double getLongitude() {
|
||||
return longitude;
|
||||
}
|
||||
|
||||
public double getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
|
||||
public String getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
|
||||
public double getAltitude() {
|
||||
return altitude;
|
||||
}
|
||||
|
||||
public ImageInfo(String path, long time, Bitmap bitmap) {
|
||||
this.path = path;
|
||||
this.time = time;
|
||||
this.bitmap = bitmap;
|
||||
}
|
||||
|
||||
// Getter 方法
|
||||
public String getPath() { return path; }
|
||||
public long getTime() { return time; }
|
||||
public Bitmap getBitmap() { return bitmap; }
|
||||
|
||||
}
|
||||
|
|
@ -1,29 +1,29 @@
|
|||
package com.example.myapplication.model;
|
||||
|
||||
public class ImageSourceItem {
|
||||
private String key;
|
||||
private String value;
|
||||
|
||||
public ImageSourceItem(String key, String value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
// 用于JSON解析的无参构造
|
||||
public ImageSourceItem() {}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ImageSourceItem{" +
|
||||
"key='" + key + '\'' +
|
||||
", 类型='" + value + '\'' +
|
||||
'}';
|
||||
}
|
||||
// Getters and Setters
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
package com.example.myapplication.model;
|
||||
|
||||
public class ImageSourceItem {
|
||||
private String key;
|
||||
private String value;
|
||||
|
||||
public ImageSourceItem(String key, String value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
// 用于JSON解析的无参构造
|
||||
public ImageSourceItem() {}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ImageSourceItem{" +
|
||||
"key='" + key + '\'' +
|
||||
", 类型='" + value + '\'' +
|
||||
'}';
|
||||
}
|
||||
// Getters and Setters
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -1,125 +1,125 @@
|
|||
package com.example.myapplication.model;
|
||||
|
||||
public class LoginEntity {
|
||||
private boolean isLogin;
|
||||
private String loginDeviceType;
|
||||
private Object loginId; // Using Object as specified
|
||||
private String loginType;
|
||||
private long sessionTimeout; // Changed to long for int64
|
||||
private String tag;
|
||||
private long tokenActiveTimeout; // Changed to long for int64
|
||||
private String tokenName;
|
||||
private long tokenSessionTimeout; // Changed to long for int64
|
||||
private long tokenTimeout; // Changed to long for int64
|
||||
private String tokenValue;
|
||||
|
||||
// Default constructor
|
||||
public LoginEntity() {
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public boolean isLogin() {
|
||||
return isLogin;
|
||||
}
|
||||
|
||||
public void setLogin(boolean login) {
|
||||
isLogin = login;
|
||||
}
|
||||
|
||||
public String getLoginDeviceType() {
|
||||
return loginDeviceType;
|
||||
}
|
||||
|
||||
public void setLoginDeviceType(String loginDeviceType) {
|
||||
this.loginDeviceType = loginDeviceType;
|
||||
}
|
||||
|
||||
public Object getLoginId() {
|
||||
return loginId;
|
||||
}
|
||||
|
||||
public void setLoginId(Object loginId) {
|
||||
this.loginId = loginId;
|
||||
}
|
||||
|
||||
public String getLoginType() {
|
||||
return loginType;
|
||||
}
|
||||
|
||||
public void setLoginType(String loginType) {
|
||||
this.loginType = loginType;
|
||||
}
|
||||
|
||||
public long getSessionTimeout() {
|
||||
return sessionTimeout;
|
||||
}
|
||||
|
||||
public void setSessionTimeout(long sessionTimeout) {
|
||||
this.sessionTimeout = sessionTimeout;
|
||||
}
|
||||
|
||||
public String getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
public void setTag(String tag) {
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
public long getTokenActiveTimeout() {
|
||||
return tokenActiveTimeout;
|
||||
}
|
||||
|
||||
public void setTokenActiveTimeout(long tokenActiveTimeout) {
|
||||
this.tokenActiveTimeout = tokenActiveTimeout;
|
||||
}
|
||||
|
||||
public String getTokenName() {
|
||||
return tokenName;
|
||||
}
|
||||
|
||||
public void setTokenName(String tokenName) {
|
||||
this.tokenName = tokenName;
|
||||
}
|
||||
|
||||
public long getTokenSessionTimeout() {
|
||||
return tokenSessionTimeout;
|
||||
}
|
||||
|
||||
public void setTokenSessionTimeout(long tokenSessionTimeout) {
|
||||
this.tokenSessionTimeout = tokenSessionTimeout;
|
||||
}
|
||||
|
||||
public long getTokenTimeout() {
|
||||
return tokenTimeout;
|
||||
}
|
||||
|
||||
public void setTokenTimeout(long tokenTimeout) {
|
||||
this.tokenTimeout = tokenTimeout;
|
||||
}
|
||||
|
||||
public String getTokenValue() {
|
||||
return tokenValue;
|
||||
}
|
||||
|
||||
public void setTokenValue(String tokenValue) {
|
||||
this.tokenValue = tokenValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Login{" +
|
||||
"isLogin=" + isLogin +
|
||||
", loginDeviceType='" + loginDeviceType + '\'' +
|
||||
", loginId=" + loginId +
|
||||
", loginType='" + loginType + '\'' +
|
||||
", sessionTimeout=" + sessionTimeout +
|
||||
", tag='" + tag + '\'' +
|
||||
", tokenActiveTimeout=" + tokenActiveTimeout +
|
||||
", tokenName='" + tokenName + '\'' +
|
||||
", tokenSessionTimeout=" + tokenSessionTimeout +
|
||||
", tokenTimeout=" + tokenTimeout +
|
||||
", tokenValue='" + tokenValue + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
package com.example.myapplication.model;
|
||||
|
||||
public class LoginEntity {
|
||||
private boolean isLogin;
|
||||
private String loginDeviceType;
|
||||
private Object loginId; // Using Object as specified
|
||||
private String loginType;
|
||||
private long sessionTimeout; // Changed to long for int64
|
||||
private String tag;
|
||||
private long tokenActiveTimeout; // Changed to long for int64
|
||||
private String tokenName;
|
||||
private long tokenSessionTimeout; // Changed to long for int64
|
||||
private long tokenTimeout; // Changed to long for int64
|
||||
private String tokenValue;
|
||||
|
||||
// Default constructor
|
||||
public LoginEntity() {
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public boolean isLogin() {
|
||||
return isLogin;
|
||||
}
|
||||
|
||||
public void setLogin(boolean login) {
|
||||
isLogin = login;
|
||||
}
|
||||
|
||||
public String getLoginDeviceType() {
|
||||
return loginDeviceType;
|
||||
}
|
||||
|
||||
public void setLoginDeviceType(String loginDeviceType) {
|
||||
this.loginDeviceType = loginDeviceType;
|
||||
}
|
||||
|
||||
public Object getLoginId() {
|
||||
return loginId;
|
||||
}
|
||||
|
||||
public void setLoginId(Object loginId) {
|
||||
this.loginId = loginId;
|
||||
}
|
||||
|
||||
public String getLoginType() {
|
||||
return loginType;
|
||||
}
|
||||
|
||||
public void setLoginType(String loginType) {
|
||||
this.loginType = loginType;
|
||||
}
|
||||
|
||||
public long getSessionTimeout() {
|
||||
return sessionTimeout;
|
||||
}
|
||||
|
||||
public void setSessionTimeout(long sessionTimeout) {
|
||||
this.sessionTimeout = sessionTimeout;
|
||||
}
|
||||
|
||||
public String getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
public void setTag(String tag) {
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
public long getTokenActiveTimeout() {
|
||||
return tokenActiveTimeout;
|
||||
}
|
||||
|
||||
public void setTokenActiveTimeout(long tokenActiveTimeout) {
|
||||
this.tokenActiveTimeout = tokenActiveTimeout;
|
||||
}
|
||||
|
||||
public String getTokenName() {
|
||||
return tokenName;
|
||||
}
|
||||
|
||||
public void setTokenName(String tokenName) {
|
||||
this.tokenName = tokenName;
|
||||
}
|
||||
|
||||
public long getTokenSessionTimeout() {
|
||||
return tokenSessionTimeout;
|
||||
}
|
||||
|
||||
public void setTokenSessionTimeout(long tokenSessionTimeout) {
|
||||
this.tokenSessionTimeout = tokenSessionTimeout;
|
||||
}
|
||||
|
||||
public long getTokenTimeout() {
|
||||
return tokenTimeout;
|
||||
}
|
||||
|
||||
public void setTokenTimeout(long tokenTimeout) {
|
||||
this.tokenTimeout = tokenTimeout;
|
||||
}
|
||||
|
||||
public String getTokenValue() {
|
||||
return tokenValue;
|
||||
}
|
||||
|
||||
public void setTokenValue(String tokenValue) {
|
||||
this.tokenValue = tokenValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Login{" +
|
||||
"isLogin=" + isLogin +
|
||||
", loginDeviceType='" + loginDeviceType + '\'' +
|
||||
", loginId=" + loginId +
|
||||
", loginType='" + loginType + '\'' +
|
||||
", sessionTimeout=" + sessionTimeout +
|
||||
", tag='" + tag + '\'' +
|
||||
", tokenActiveTimeout=" + tokenActiveTimeout +
|
||||
", tokenName='" + tokenName + '\'' +
|
||||
", tokenSessionTimeout=" + tokenSessionTimeout +
|
||||
", tokenTimeout=" + tokenTimeout +
|
||||
", tokenValue='" + tokenValue + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
package com.example.myapplication.model;
|
||||
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class LoginRequest {
|
||||
@SerializedName("account")
|
||||
private String account;
|
||||
@SerializedName("password")
|
||||
private String password;
|
||||
|
||||
public LoginRequest(String account, String password) {
|
||||
this.account = account;
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
|
||||
package com.example.myapplication.model;
|
||||
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class LoginRequest {
|
||||
@SerializedName("account")
|
||||
private String account;
|
||||
@SerializedName("password")
|
||||
private String password;
|
||||
|
||||
public LoginRequest(String account, String password) {
|
||||
this.account = account;
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,54 +1,54 @@
|
|||
package com.example.myapplication.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PageResult<T> {
|
||||
private int code;
|
||||
private String msg;
|
||||
private List<T> rows;
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public PageResult() {
|
||||
}
|
||||
|
||||
public void setMsg(String msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public List<T> getRows() {
|
||||
return rows;
|
||||
}
|
||||
|
||||
public void setRows(List<T> rows) {
|
||||
this.rows = rows;
|
||||
}
|
||||
|
||||
public long getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public void setTotal(long total) {
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
public PageResult(int code, String msg, List<T> rows, long total) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.rows = rows;
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
private long total;
|
||||
|
||||
}
|
||||
package com.example.myapplication.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PageResult<T> {
|
||||
private int code;
|
||||
private String msg;
|
||||
private List<T> rows;
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public PageResult() {
|
||||
}
|
||||
|
||||
public void setMsg(String msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public List<T> getRows() {
|
||||
return rows;
|
||||
}
|
||||
|
||||
public void setRows(List<T> rows) {
|
||||
this.rows = rows;
|
||||
}
|
||||
|
||||
public long getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public void setTotal(long total) {
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
public PageResult(int code, String msg, List<T> rows, long total) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.rows = rows;
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
private long total;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,85 +1,86 @@
|
|||
package com.example.myapplication.model;
|
||||
|
||||
public class PartResponse {
|
||||
private String partCode;
|
||||
private String partId;
|
||||
private String partName;
|
||||
private String partType;
|
||||
private String partTypeLabel;
|
||||
private String projectId;
|
||||
private String projectName;
|
||||
private String turbineId;
|
||||
private String turbineName;
|
||||
|
||||
public String getPartCode() {
|
||||
return partCode;
|
||||
}
|
||||
|
||||
public void setPartCode(String partCode) {
|
||||
this.partCode = partCode;
|
||||
}
|
||||
|
||||
public String getPartId() {
|
||||
return partId;
|
||||
}
|
||||
|
||||
public void setPartId(String partId) {
|
||||
this.partId = partId;
|
||||
}
|
||||
|
||||
public String getPartTypeLabel() {
|
||||
return partTypeLabel;
|
||||
}
|
||||
|
||||
public void setPartTypeLabel(String partTypeLabel) {
|
||||
this.partTypeLabel = partTypeLabel;
|
||||
}
|
||||
|
||||
public String getPartType() {
|
||||
return partType;
|
||||
}
|
||||
|
||||
public void setPartType(String partType) {
|
||||
this.partType = partType;
|
||||
}
|
||||
|
||||
public void setPartName(String partName) {
|
||||
this.partName = partName;
|
||||
}
|
||||
|
||||
public String getProjectId() {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
public void setProjectId(String projectId) {
|
||||
this.projectId = projectId;
|
||||
}
|
||||
|
||||
public String getProjectName() {
|
||||
return projectName;
|
||||
}
|
||||
|
||||
public void setProjectName(String projectName) {
|
||||
this.projectName = projectName;
|
||||
}
|
||||
|
||||
public String getTurbineId() {
|
||||
return turbineId;
|
||||
}
|
||||
|
||||
public void setTurbineId(String turbineId) {
|
||||
this.turbineId = turbineId;
|
||||
}
|
||||
|
||||
public String getTurbineName() {
|
||||
return turbineName;
|
||||
}
|
||||
|
||||
public void setTurbineName(String turbineName) {
|
||||
this.turbineName = turbineName;
|
||||
}
|
||||
|
||||
public String getPartName() {
|
||||
return partName;
|
||||
}
|
||||
}
|
||||
package com.example.myapplication.model;
|
||||
|
||||
|
||||
public class PartResponse {
|
||||
private String partCode;
|
||||
private String partId;
|
||||
private String partName;
|
||||
private String partType;
|
||||
private String partTypeLabel;
|
||||
private String projectId;
|
||||
private String projectName;
|
||||
private String turbineId;
|
||||
private String turbineName;
|
||||
|
||||
public String getPartCode() {
|
||||
return partCode;
|
||||
}
|
||||
|
||||
public void setPartCode(String partCode) {
|
||||
this.partCode = partCode;
|
||||
}
|
||||
|
||||
public String getPartId() {
|
||||
return partId;
|
||||
}
|
||||
|
||||
public void setPartId(String partId) {
|
||||
this.partId = partId;
|
||||
}
|
||||
|
||||
public String getPartTypeLabel() {
|
||||
return partTypeLabel;
|
||||
}
|
||||
|
||||
public void setPartTypeLabel(String partTypeLabel) {
|
||||
this.partTypeLabel = partTypeLabel;
|
||||
}
|
||||
|
||||
public String getPartType() {
|
||||
return partType;
|
||||
}
|
||||
|
||||
public void setPartType(String partType) {
|
||||
this.partType = partType;
|
||||
}
|
||||
|
||||
public void setPartName(String partName) {
|
||||
this.partName = partName;
|
||||
}
|
||||
|
||||
public String getProjectId() {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
public void setProjectId(String projectId) {
|
||||
this.projectId = projectId;
|
||||
}
|
||||
|
||||
public String getProjectName() {
|
||||
return projectName;
|
||||
}
|
||||
|
||||
public void setProjectName(String projectName) {
|
||||
this.projectName = projectName;
|
||||
}
|
||||
|
||||
public String getTurbineId() {
|
||||
return turbineId;
|
||||
}
|
||||
|
||||
public void setTurbineId(String turbineId) {
|
||||
this.turbineId = turbineId;
|
||||
}
|
||||
|
||||
public String getTurbineName() {
|
||||
return turbineName;
|
||||
}
|
||||
|
||||
public void setTurbineName(String turbineName) {
|
||||
this.turbineName = turbineName;
|
||||
}
|
||||
|
||||
public String getPartName() {
|
||||
return partName;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,239 +1,239 @@
|
|||
package com.example.myapplication.model;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
// Project.java
|
||||
public class Project {
|
||||
@SerializedName("projectId")
|
||||
private String projectId;
|
||||
|
||||
@SerializedName("projectName")
|
||||
private String projectName;
|
||||
|
||||
@SerializedName("status")
|
||||
private int status;
|
||||
|
||||
@SerializedName("client")
|
||||
private String client;
|
||||
|
||||
@SerializedName("clientContact")
|
||||
private String clientContact;
|
||||
|
||||
@SerializedName("clientPhone")
|
||||
private String clientPhone;
|
||||
|
||||
@SerializedName("constructorIds")
|
||||
private String constructorIds;
|
||||
|
||||
@SerializedName("constructorName")
|
||||
private String constructorName;
|
||||
|
||||
@SerializedName("coverUrl")
|
||||
private String coverUrl;
|
||||
|
||||
@SerializedName("createTime")
|
||||
private String createTime;
|
||||
|
||||
@SerializedName("farmAddress")
|
||||
private String farmAddress;
|
||||
|
||||
@SerializedName("farmName")
|
||||
private String farmName;
|
||||
|
||||
@SerializedName("inspectionContact")
|
||||
private String inspectionContact;
|
||||
|
||||
@SerializedName("inspectionPhone")
|
||||
private String inspectionPhone;
|
||||
|
||||
@SerializedName("inspectionUnit")
|
||||
private String inspectionUnit;
|
||||
|
||||
@SerializedName("projectManagerId")
|
||||
private String projectManagerId;
|
||||
|
||||
@SerializedName("projectManagerName")
|
||||
private String projectManagerName;
|
||||
|
||||
@SerializedName("scale")
|
||||
private String scale;
|
||||
|
||||
@SerializedName("statusLabel")
|
||||
private String statusLabel;
|
||||
|
||||
@SerializedName("turbineModel")
|
||||
private String turbineModel;
|
||||
|
||||
public String getProjectName() {
|
||||
return projectName;
|
||||
}
|
||||
|
||||
public void setProjectName(String projectName) {
|
||||
this.projectName = projectName;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(int status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void setClient(String client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public String getClientPhone() {
|
||||
return clientPhone;
|
||||
}
|
||||
|
||||
public void setClientPhone(String clientPhone) {
|
||||
this.clientPhone = clientPhone;
|
||||
}
|
||||
|
||||
public String getConstructorIds() {
|
||||
return constructorIds;
|
||||
}
|
||||
|
||||
public void setConstructorIds(String constructorIds) {
|
||||
this.constructorIds = constructorIds;
|
||||
}
|
||||
|
||||
public String getClientContact() {
|
||||
return clientContact;
|
||||
}
|
||||
|
||||
public void setClientContact(String clientContact) {
|
||||
this.clientContact = clientContact;
|
||||
}
|
||||
|
||||
public String getCoverUrl() {
|
||||
return coverUrl;
|
||||
}
|
||||
|
||||
public void setCoverUrl(String coverUrl) {
|
||||
this.coverUrl = coverUrl;
|
||||
}
|
||||
|
||||
public String getConstructorName() {
|
||||
return constructorName;
|
||||
}
|
||||
|
||||
public void setConstructorName(String constructorName) {
|
||||
this.constructorName = constructorName;
|
||||
}
|
||||
|
||||
public String getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(String createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
|
||||
public String getFarmName() {
|
||||
return farmName;
|
||||
}
|
||||
|
||||
public void setFarmName(String farmName) {
|
||||
this.farmName = farmName;
|
||||
}
|
||||
|
||||
public String getFarmAddress() {
|
||||
return farmAddress;
|
||||
}
|
||||
|
||||
public void setFarmAddress(String farmAddress) {
|
||||
this.farmAddress = farmAddress;
|
||||
}
|
||||
|
||||
public String getInspectionPhone() {
|
||||
return inspectionPhone;
|
||||
}
|
||||
|
||||
public void setInspectionPhone(String inspectionPhone) {
|
||||
this.inspectionPhone = inspectionPhone;
|
||||
}
|
||||
|
||||
public String getInspectionContact() {
|
||||
return inspectionContact;
|
||||
}
|
||||
|
||||
public void setInspectionContact(String inspectionContact) {
|
||||
this.inspectionContact = inspectionContact;
|
||||
}
|
||||
|
||||
public String getInspectionUnit() {
|
||||
return inspectionUnit;
|
||||
}
|
||||
|
||||
public void setInspectionUnit(String inspectionUnit) {
|
||||
this.inspectionUnit = inspectionUnit;
|
||||
}
|
||||
|
||||
public String getProjectManagerId() {
|
||||
return projectManagerId;
|
||||
}
|
||||
|
||||
public void setProjectManagerId(String projectManagerId) {
|
||||
this.projectManagerId = projectManagerId;
|
||||
}
|
||||
|
||||
public String getScale() {
|
||||
return scale;
|
||||
}
|
||||
|
||||
public void setScale(String scale) {
|
||||
this.scale = scale;
|
||||
}
|
||||
|
||||
public String getProjectManagerName() {
|
||||
return projectManagerName;
|
||||
}
|
||||
|
||||
public void setProjectManagerName(String projectManagerName) {
|
||||
this.projectManagerName = projectManagerName;
|
||||
}
|
||||
|
||||
public String getStatusLabel() {
|
||||
return statusLabel;
|
||||
}
|
||||
|
||||
public void setStatusLabel(String statusLabel) {
|
||||
this.statusLabel = statusLabel;
|
||||
}
|
||||
|
||||
public String getTurbineModel() {
|
||||
return turbineModel;
|
||||
}
|
||||
|
||||
public void setTurbineModel(String turbineModel) {
|
||||
this.turbineModel = turbineModel;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public String getProjectId() {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
public void setProjectId(String projectId) {
|
||||
this.projectId = projectId;
|
||||
}
|
||||
public Project(String projectId, String projectName) {
|
||||
this.projectId = projectId;
|
||||
this.projectName = projectName;
|
||||
}
|
||||
// 其他字段的 getter/setter 按需添加...
|
||||
// 建议使用 Android Studio 的 "Generate" -> "Getter and Setter" 自动生成
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return projectName + " (" + projectId + ")";
|
||||
}
|
||||
package com.example.myapplication.model;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
// Project.java
|
||||
public class Project {
|
||||
@SerializedName("projectId")
|
||||
private String projectId;
|
||||
|
||||
@SerializedName("projectName")
|
||||
private String projectName;
|
||||
|
||||
@SerializedName("status")
|
||||
private int status;
|
||||
|
||||
@SerializedName("client")
|
||||
private String client;
|
||||
|
||||
@SerializedName("clientContact")
|
||||
private String clientContact;
|
||||
|
||||
@SerializedName("clientPhone")
|
||||
private String clientPhone;
|
||||
|
||||
@SerializedName("constructorIds")
|
||||
private String constructorIds;
|
||||
|
||||
@SerializedName("constructorName")
|
||||
private String constructorName;
|
||||
|
||||
@SerializedName("coverUrl")
|
||||
private String coverUrl;
|
||||
|
||||
@SerializedName("createTime")
|
||||
private String createTime;
|
||||
|
||||
@SerializedName("farmAddress")
|
||||
private String farmAddress;
|
||||
|
||||
@SerializedName("farmName")
|
||||
private String farmName;
|
||||
|
||||
@SerializedName("inspectionContact")
|
||||
private String inspectionContact;
|
||||
|
||||
@SerializedName("inspectionPhone")
|
||||
private String inspectionPhone;
|
||||
|
||||
@SerializedName("inspectionUnit")
|
||||
private String inspectionUnit;
|
||||
|
||||
@SerializedName("projectManagerId")
|
||||
private String projectManagerId;
|
||||
|
||||
@SerializedName("projectManagerName")
|
||||
private String projectManagerName;
|
||||
|
||||
@SerializedName("scale")
|
||||
private String scale;
|
||||
|
||||
@SerializedName("statusLabel")
|
||||
private String statusLabel;
|
||||
|
||||
@SerializedName("turbineModel")
|
||||
private String turbineModel;
|
||||
|
||||
public String getProjectName() {
|
||||
return projectName;
|
||||
}
|
||||
|
||||
public void setProjectName(String projectName) {
|
||||
this.projectName = projectName;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(int status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void setClient(String client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public String getClientPhone() {
|
||||
return clientPhone;
|
||||
}
|
||||
|
||||
public void setClientPhone(String clientPhone) {
|
||||
this.clientPhone = clientPhone;
|
||||
}
|
||||
|
||||
public String getConstructorIds() {
|
||||
return constructorIds;
|
||||
}
|
||||
|
||||
public void setConstructorIds(String constructorIds) {
|
||||
this.constructorIds = constructorIds;
|
||||
}
|
||||
|
||||
public String getClientContact() {
|
||||
return clientContact;
|
||||
}
|
||||
|
||||
public void setClientContact(String clientContact) {
|
||||
this.clientContact = clientContact;
|
||||
}
|
||||
|
||||
public String getCoverUrl() {
|
||||
return coverUrl;
|
||||
}
|
||||
|
||||
public void setCoverUrl(String coverUrl) {
|
||||
this.coverUrl = coverUrl;
|
||||
}
|
||||
|
||||
public String getConstructorName() {
|
||||
return constructorName;
|
||||
}
|
||||
|
||||
public void setConstructorName(String constructorName) {
|
||||
this.constructorName = constructorName;
|
||||
}
|
||||
|
||||
public String getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(String createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
|
||||
public String getFarmName() {
|
||||
return farmName;
|
||||
}
|
||||
|
||||
public void setFarmName(String farmName) {
|
||||
this.farmName = farmName;
|
||||
}
|
||||
|
||||
public String getFarmAddress() {
|
||||
return farmAddress;
|
||||
}
|
||||
|
||||
public void setFarmAddress(String farmAddress) {
|
||||
this.farmAddress = farmAddress;
|
||||
}
|
||||
|
||||
public String getInspectionPhone() {
|
||||
return inspectionPhone;
|
||||
}
|
||||
|
||||
public void setInspectionPhone(String inspectionPhone) {
|
||||
this.inspectionPhone = inspectionPhone;
|
||||
}
|
||||
|
||||
public String getInspectionContact() {
|
||||
return inspectionContact;
|
||||
}
|
||||
|
||||
public void setInspectionContact(String inspectionContact) {
|
||||
this.inspectionContact = inspectionContact;
|
||||
}
|
||||
|
||||
public String getInspectionUnit() {
|
||||
return inspectionUnit;
|
||||
}
|
||||
|
||||
public void setInspectionUnit(String inspectionUnit) {
|
||||
this.inspectionUnit = inspectionUnit;
|
||||
}
|
||||
|
||||
public String getProjectManagerId() {
|
||||
return projectManagerId;
|
||||
}
|
||||
|
||||
public void setProjectManagerId(String projectManagerId) {
|
||||
this.projectManagerId = projectManagerId;
|
||||
}
|
||||
|
||||
public String getScale() {
|
||||
return scale;
|
||||
}
|
||||
|
||||
public void setScale(String scale) {
|
||||
this.scale = scale;
|
||||
}
|
||||
|
||||
public String getProjectManagerName() {
|
||||
return projectManagerName;
|
||||
}
|
||||
|
||||
public void setProjectManagerName(String projectManagerName) {
|
||||
this.projectManagerName = projectManagerName;
|
||||
}
|
||||
|
||||
public String getStatusLabel() {
|
||||
return statusLabel;
|
||||
}
|
||||
|
||||
public void setStatusLabel(String statusLabel) {
|
||||
this.statusLabel = statusLabel;
|
||||
}
|
||||
|
||||
public String getTurbineModel() {
|
||||
return turbineModel;
|
||||
}
|
||||
|
||||
public void setTurbineModel(String turbineModel) {
|
||||
this.turbineModel = turbineModel;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public String getProjectId() {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
public void setProjectId(String projectId) {
|
||||
this.projectId = projectId;
|
||||
}
|
||||
public Project(String projectId, String projectName) {
|
||||
this.projectId = projectId;
|
||||
this.projectName = projectName;
|
||||
}
|
||||
// 其他字段的 getter/setter 按需添加...
|
||||
// 建议使用 Android Studio 的 "Generate" -> "Getter and Setter" 自动生成
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return projectName + " (" + projectId + ")";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
package com.example.myapplication.model;
|
||||
|
||||
import android.location.Location;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
// SharedDataManager.java
|
||||
public class SharedDataManager {
|
||||
private static SharedDataManager instance;
|
||||
private String imageId ="-1";
|
||||
private String token="-1";
|
||||
private String user="-1";
|
||||
private String audioPath="-1";
|
||||
private String projectId="-1";
|
||||
private String turbineId ="-1";
|
||||
private String turbineName ="-1";
|
||||
private String partId ="-1";
|
||||
private String partName ="-1";
|
||||
private String chooseImageSource="-1";
|
||||
private boolean isRecording=false;
|
||||
private boolean isUploading=false; // 新增上传状态字段
|
||||
private Location location = null; // 新增位置字段
|
||||
private final List<DataChangeListener> listeners = new ArrayList<>();
|
||||
private String othermsg;
|
||||
public interface DataChangeListener {
|
||||
void onImageIdChanged(String newId);
|
||||
void onTokenChanged(String newToken);
|
||||
void onUserChanged(String newUser);
|
||||
void onAudioPathChanged(String newAudioPath);
|
||||
void onProjectIdChanged(String newProjectId);
|
||||
void onPartIdChanged(String newPartId);
|
||||
void onPartNameChanged(String newPartName);
|
||||
void onTurbineIdChanged(String newTurbineId);
|
||||
void onTurbineNameChanged(String newTurbineName);
|
||||
void onChooseImageSourceChanged(String newChooseImageSource);
|
||||
void onIsRecordingChanged(boolean newisRecording);
|
||||
void onUploadingStatusChanged(boolean isUploading); // 新增上传状态变化回调
|
||||
|
||||
void onLocationChanged(Location newLocation); // 新增位置变化回调
|
||||
void onOtherMsgChanged(String msg); // 新增位置变化回调
|
||||
}
|
||||
|
||||
private SharedDataManager() {}
|
||||
|
||||
public static synchronized SharedDataManager getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new SharedDataManager();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
public boolean getIsRecording(){return isRecording;}
|
||||
public String getImageId() {return imageId;}
|
||||
public String getUser() { return user; }
|
||||
public String getAudioPath() { return audioPath; }
|
||||
public String getProjectId() { return projectId; }
|
||||
public String getPartId() { return partId; }
|
||||
public String getChooseImageSource() { return chooseImageSource; }
|
||||
public String getToken() {return token;}
|
||||
public boolean getisUploading() {
|
||||
return isUploading;
|
||||
}
|
||||
public Location getLocation() {
|
||||
return location;
|
||||
}
|
||||
public String getOthermsg()
|
||||
{
|
||||
return othermsg;
|
||||
|
||||
}
|
||||
|
||||
public String getTurbineId() {
|
||||
return turbineId;
|
||||
}
|
||||
|
||||
public void setOthermsg(String othermsg)
|
||||
{
|
||||
this.othermsg=othermsg;
|
||||
notifyOtherMsgChanged(othermsg);
|
||||
}
|
||||
public void setLocation(Location location) {
|
||||
this.location = location;
|
||||
notifyLocationChanged(location);
|
||||
}
|
||||
public void setUploading(boolean uploading) {
|
||||
if (this.isUploading != uploading) {
|
||||
this.isUploading = uploading;
|
||||
notifyUploadingStatusChanged(uploading);
|
||||
}
|
||||
}
|
||||
public void setImageId(String imageId) {
|
||||
this.imageId = imageId;
|
||||
notifyImageIdChanged(imageId);
|
||||
}
|
||||
|
||||
public void setUser(String user) {
|
||||
this.user = user;
|
||||
notifyUserChanged(user);
|
||||
}
|
||||
|
||||
public void setAudioPath(String audioPath) {
|
||||
this.audioPath = audioPath;
|
||||
notifyAudioPathChanged(audioPath);
|
||||
}
|
||||
|
||||
public void setProjectId(String projectId) {
|
||||
this.projectId = projectId;
|
||||
notifyProjectIdChanged(projectId);
|
||||
}
|
||||
|
||||
public void setPartId(String partId) {
|
||||
this.partId = partId;
|
||||
notifyPartIdChanged(partId);
|
||||
}
|
||||
|
||||
public void setPartName(String partName) {
|
||||
this.partName = partName;
|
||||
notifyPartNameChanged(partName);
|
||||
}
|
||||
|
||||
public void setChooseImageSource(String chooseImageSource) {
|
||||
this.chooseImageSource = chooseImageSource;
|
||||
notifyChooseImageSourceChanged(chooseImageSource);
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
notifyTokenChanged(token);
|
||||
}
|
||||
public void setIsRecording(boolean isRecording) {
|
||||
this.isRecording =isRecording ;
|
||||
notifyIsRecordingChanged(isRecording);
|
||||
}
|
||||
|
||||
public void setTurbineId(String turbineId) {
|
||||
this.turbineId = turbineId;
|
||||
notifyTurbineIdChanged(turbineId);
|
||||
}
|
||||
|
||||
public void setTurbineName(String turbineName) {
|
||||
this.turbineName = turbineName;
|
||||
notifyTurbineNameChanged(turbineName);
|
||||
}
|
||||
|
||||
public void addListener(DataChangeListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeListener(DataChangeListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
private void notifyImageIdChanged(String newPath) {
|
||||
for (DataChangeListener listener : listeners) {
|
||||
listener.onImageIdChanged(newPath);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyTokenChanged(String newToken) {
|
||||
for (DataChangeListener listener : listeners) {
|
||||
listener.onTokenChanged(newToken);
|
||||
}
|
||||
}
|
||||
private void notifyUserChanged(String newUser) {
|
||||
for (DataChangeListener listener : listeners) {
|
||||
listener.onUserChanged(newUser);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyAudioPathChanged(String newAudioPath) {
|
||||
for (DataChangeListener listener : listeners) {
|
||||
listener.onAudioPathChanged(newAudioPath);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyProjectIdChanged(String newProjectId) {
|
||||
for (DataChangeListener listener : listeners) {
|
||||
listener.onProjectIdChanged(newProjectId);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyPartIdChanged(String newPartId) {
|
||||
for (DataChangeListener listener : listeners) {
|
||||
listener.onPartIdChanged(newPartId);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyPartNameChanged(String newPartName) {
|
||||
for (DataChangeListener listener : listeners) {
|
||||
listener.onPartNameChanged(newPartName);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyTurbineIdChanged(String newTurbineId) {
|
||||
for (DataChangeListener listener : listeners) {
|
||||
listener.onTurbineIdChanged(newTurbineId);
|
||||
}
|
||||
}
|
||||
private void notifyTurbineNameChanged(String newTurbineName) {
|
||||
for (DataChangeListener listener : listeners) {
|
||||
listener.onTurbineNameChanged(newTurbineName);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyChooseImageSourceChanged(String newChooseImageSource) {
|
||||
for (DataChangeListener listener : listeners) {
|
||||
listener.onChooseImageSourceChanged(newChooseImageSource);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyIsRecordingChanged(boolean newIsRecording) {
|
||||
for (DataChangeListener listener : listeners) {
|
||||
listener.onIsRecordingChanged(newIsRecording);
|
||||
}
|
||||
}
|
||||
private void notifyUploadingStatusChanged(boolean isUploading) {
|
||||
for (DataChangeListener listener : listeners) {
|
||||
listener.onUploadingStatusChanged(isUploading);
|
||||
}
|
||||
}
|
||||
private void notifyLocationChanged(Location newLocation) {
|
||||
for (DataChangeListener listener : listeners) {
|
||||
listener.onLocationChanged(newLocation);
|
||||
}
|
||||
}
|
||||
private void notifyOtherMsgChanged(String newothermsg) {
|
||||
for (DataChangeListener listener : listeners) {
|
||||
listener.onOtherMsgChanged(newothermsg);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +1,48 @@
|
|||
package com.example.myapplication.model;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
@Entity(tableName = "turbines")
|
||||
public class Turbine {
|
||||
@PrimaryKey
|
||||
@NonNull
|
||||
public String turbineId;
|
||||
public String turbineName;
|
||||
public String projectId;
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return
|
||||
|
||||
turbineName +'('+ turbineId + ')';
|
||||
|
||||
}
|
||||
// 构造方法、getter/setter省略
|
||||
}
|
||||
package com.example.myapplication.model;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
@Entity(tableName = "turbines")
|
||||
public class Turbine {
|
||||
@PrimaryKey
|
||||
@NonNull
|
||||
public String turbineId;
|
||||
public String turbineName;
|
||||
public String projectId;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return
|
||||
|
||||
turbineName +'('+ turbineId + ')';
|
||||
|
||||
}
|
||||
// 构造方法、getter/setter省略
|
||||
|
||||
@NonNull
|
||||
public String getTurbineId() {
|
||||
return turbineId;
|
||||
}
|
||||
|
||||
public void setTurbineId(@NonNull String turbineId) {
|
||||
this.turbineId = turbineId;
|
||||
}
|
||||
|
||||
public String getTurbineName() {
|
||||
return turbineName;
|
||||
}
|
||||
|
||||
public void setTurbineName(String turbineName) {
|
||||
this.turbineName = turbineName;
|
||||
}
|
||||
|
||||
public String getProjectId() {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
public void setProjectId(String projectId) {
|
||||
this.projectId = projectId;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,74 +1,74 @@
|
|||
package com.example.myapplication.model;
|
||||
|
||||
public class UserInfo {
|
||||
private String account;
|
||||
private String name;
|
||||
private String deptName;
|
||||
|
||||
public String getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
public void setAccount(String account) {
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public UserInfo() {
|
||||
}
|
||||
|
||||
public UserInfo(String account, String name, String deptName, String mobile, String userCode, String userId) {
|
||||
this.account = account;
|
||||
this.name = name;
|
||||
this.deptName = deptName;
|
||||
this.mobile = mobile;
|
||||
this.userCode = userCode;
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public String getDeptName() {
|
||||
return deptName;
|
||||
}
|
||||
|
||||
public void setDeptName(String deptName) {
|
||||
this.deptName = deptName;
|
||||
}
|
||||
|
||||
public String getMobile() {
|
||||
return mobile;
|
||||
}
|
||||
|
||||
public void setMobile(String mobile) {
|
||||
this.mobile = mobile;
|
||||
}
|
||||
|
||||
public String getUserCode() {
|
||||
return userCode;
|
||||
}
|
||||
|
||||
public void setUserCode(String userCode) {
|
||||
this.userCode = userCode;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
private String mobile;
|
||||
private String userCode;
|
||||
private String userId;
|
||||
// 其他字段根据需要添加
|
||||
|
||||
// getters and setters
|
||||
}
|
||||
package com.example.myapplication.model;
|
||||
|
||||
public class UserInfo {
|
||||
private String account;
|
||||
private String name;
|
||||
private String deptName;
|
||||
|
||||
public String getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
public void setAccount(String account) {
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public UserInfo() {
|
||||
}
|
||||
|
||||
public UserInfo(String account, String name, String deptName, String mobile, String userCode, String userId) {
|
||||
this.account = account;
|
||||
this.name = name;
|
||||
this.deptName = deptName;
|
||||
this.mobile = mobile;
|
||||
this.userCode = userCode;
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public String getDeptName() {
|
||||
return deptName;
|
||||
}
|
||||
|
||||
public void setDeptName(String deptName) {
|
||||
this.deptName = deptName;
|
||||
}
|
||||
|
||||
public String getMobile() {
|
||||
return mobile;
|
||||
}
|
||||
|
||||
public void setMobile(String mobile) {
|
||||
this.mobile = mobile;
|
||||
}
|
||||
|
||||
public String getUserCode() {
|
||||
return userCode;
|
||||
}
|
||||
|
||||
public void setUserCode(String userCode) {
|
||||
this.userCode = userCode;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
private String mobile;
|
||||
private String userCode;
|
||||
private String userId;
|
||||
// 其他字段根据需要添加
|
||||
|
||||
// getters and setters
|
||||
}
|
||||
|
|
|
@ -0,0 +1,373 @@
|
|||
# 文档处理工具
|
||||
import json
|
||||
|
||||
from .tools.document_tools import (
|
||||
create_document, add_documents,add_table_and_replace,
|
||||
add_table_to_document,process_images_table,
|
||||
)
|
||||
|
||||
# 内容处理工具
|
||||
from .tools.content_tools import (
|
||||
add_picture,split_table_by_row_content,
|
||||
search_and_replace,add_picture_to_table
|
||||
)
|
||||
|
||||
from .tools.get_pictures import (
|
||||
get_dict,
|
||||
)
|
||||
|
||||
from .tools.dataproccess import (
|
||||
caculate_work_days,
|
||||
get_year_month,merge_info,
|
||||
)
|
||||
|
||||
import asyncio
|
||||
|
||||
|
||||
from .tools.defines import *
|
||||
import os, re, datetime
|
||||
|
||||
async def generate_report(base_info, baogao_info):
|
||||
#获取模板编号、模板名称
|
||||
num_to_chinese = {1 : '一', 2 : '二', 3 : '三', 4 : '四', 5 : '五', 6 : '六', 7 : '七', 8 : '八', 9 : '九', 10 : '十', 11 : '十一', 12 : '十二'}
|
||||
cover_encode = "encode"
|
||||
cover_project = "project"
|
||||
baogao_name1 = "baogaoname1"
|
||||
baogao_name2 = "baogaoname2"
|
||||
company_name_yi = "company_name_yi"
|
||||
cover_date = "time"
|
||||
TITLE_OF_REPORT = "companyencode"
|
||||
jiegou_xuhao = 'num'
|
||||
|
||||
|
||||
|
||||
try:
|
||||
base_info = merge_info(base_info, DEFAULT_BASE_INFO)
|
||||
jizu_data = base_info["jizu_data"]
|
||||
project_data = base_info["project_data"]
|
||||
shigong_data = base_info["shigong_data"]
|
||||
|
||||
fengchang_name = project_data['farmName']
|
||||
Yi_company = project_data['inspectionUnit']
|
||||
yi_fuzeren = project_data['inspectionContact']
|
||||
yi_phone = project_data['inspectionPhone']
|
||||
fengchang_location = project_data['farmAddress']
|
||||
Jia_company = project_data['client']
|
||||
jia_fuzeren = project_data['clientContact']
|
||||
jia_phone = project_data['clientPhone']
|
||||
jizu_num = project_data['scale']
|
||||
jizu_xinghao = project_data['turbineModel']
|
||||
project_name = project_data['projectName']
|
||||
jizu_bianhao = jizu_data["turbineName"]
|
||||
start_date = project_data['startDate']
|
||||
end_date = project_data['endDate']
|
||||
|
||||
fengmian_dir = project_data["fengmian_dir"]
|
||||
try:
|
||||
gongqi = caculate_work_days(start_date, end_date)
|
||||
except:
|
||||
gongqi = "日期获取失败"
|
||||
except Exception as e:
|
||||
print(f"数据库的项目-机组基本信息获取失败:{e}")
|
||||
return
|
||||
|
||||
try:
|
||||
baogao_date = datetime.datetime.now().strftime("%Y年%m月%d日 %H:%M") #现在的时间
|
||||
date_year_month = get_year_month(baogao_date)
|
||||
|
||||
#前端信息
|
||||
baogao_info = merge_info(baogao_info, DEFAULT_BAOGAO_INFO)
|
||||
|
||||
key_words= re.compile('|'.join(map(re.escape, baogao_info['key_words'].split(','))))
|
||||
|
||||
shengcheng_dir = baogao_info['shengcheng_dir']
|
||||
muban_dir = baogao_info['muban_dir']
|
||||
if muban_dir == "" or shengcheng_dir == "":
|
||||
print("未配置模板/生成路径,请检查配置")
|
||||
return
|
||||
|
||||
if_waibu = baogao_info["if_waibu"]
|
||||
if_neibu = baogao_info["if_neibu"]
|
||||
if_fanglei = baogao_info["if_fanglei"]
|
||||
total_pic_num = baogao_info["total_pic_num"]
|
||||
jiancha_renyuan = baogao_info["jiancha_renyuan"]
|
||||
baogao_zongjie = baogao_info["baogao_zongjie"]
|
||||
|
||||
try:
|
||||
Jiancha_date = shigong_data["startTime"].replace("T", " ") #检查日期
|
||||
except:
|
||||
Jiancha_date = "日期获取失败"
|
||||
image_count = shigong_data['imageCount'] #从施工方案获取的图片数量,待定!!!
|
||||
temperature = shigong_data['temperature'] #温度
|
||||
wind_speed = shigong_data['windSpeed'] #风速
|
||||
weather = shigong_data["weatherCode"] #天气 不从此接口获取,待定!!!
|
||||
|
||||
Y1 = base_info["Y1"]
|
||||
Y2 = base_info["Y2"]
|
||||
Y3 = base_info["Y3"]
|
||||
Y_Code = [Y1["Code"], Y2["Code"], Y3["Code"]] #Y1,Y2,Y3的编号
|
||||
partManufacturer = base_info["partManufacturer"] #厂商
|
||||
print(f"找到叶片号{Y_Code},厂商{partManufacturer}")
|
||||
|
||||
baogao_label = []
|
||||
renyuan_peizhi = []
|
||||
gongzuo_neirong = []
|
||||
shigong_fangan = []
|
||||
shebei_peizhi = []
|
||||
beizhu = []
|
||||
jiancha = []
|
||||
neirong = []
|
||||
#获取对应枚举字段
|
||||
if if_waibu:
|
||||
baogao_label.append("外观")
|
||||
if baogao_info["shigong_fangan"] == "None":
|
||||
print("未传入施工方案,使用已有枚举")
|
||||
renyuan_peizhi.append(SHIGONG_FANGAN_ENUM.WAIBU.RENYUAN_PEIZHI)
|
||||
gongzuo_neirong.append(SHIGONG_FANGAN_ENUM.WAIBU.GONGZUO_NEIRONG)
|
||||
shebei_peizhi.append(SHIGONG_FANGAN_ENUM.WAIBU.SHEBEI_PEIZHI)
|
||||
shigong_fangan.append(SHIGONG_FANGAN_ENUM.WAIBU.SHIGONG_FANGAN)
|
||||
else:
|
||||
pass #待添加如果从平台传入枚举,但目前没必要,如果这种枚举标准信息库有更好的优化则可以实现
|
||||
jiancha.append("无人机近距离外观检查")
|
||||
neirong.append(f"、".join(Y_Code) + f"{len(Y_Code)}支叶片的前缘、后缘、迎风面、背风面。")
|
||||
if if_neibu:
|
||||
baogao_label.append("内部")
|
||||
if baogao_info["shigong_fangan"] == "None":
|
||||
renyuan_peizhi.append(SHIGONG_FANGAN_ENUM.NEIBU.RENYUAN_PEIZHI)
|
||||
gongzuo_neirong.append(SHIGONG_FANGAN_ENUM.NEIBU.GONGZUO_NEIRONG)
|
||||
shebei_peizhi.append(SHIGONG_FANGAN_ENUM.NEIBU.SHEBEI_PEIZHI)
|
||||
shigong_fangan.append(SHIGONG_FANGAN_ENUM.NEIBU.SHIGONG_FANGAN)
|
||||
else:
|
||||
pass
|
||||
jiancha.append("人工内部拍摄")
|
||||
neirong.append(f"、".join(Y_Code) + f"{len(Y_Code)}支叶片的内部导雷卡、腹板、透光、人孔盖版、叶根盖板...")
|
||||
if if_fanglei:
|
||||
baogao_label.append("防雷")
|
||||
if baogao_info["shigong_fangan"] == "None":
|
||||
renyuan_peizhi.append(SHIGONG_FANGAN_ENUM.FANGLEI.YEPIAN.RENYUAN_PEIZHI)
|
||||
gongzuo_neirong.append(SHIGONG_FANGAN_ENUM.FANGLEI.YEPIAN.GONGZUO_NEIRONG)
|
||||
shebei_peizhi.append(SHIGONG_FANGAN_ENUM.FANGLEI.YEPIAN.SHEBEI_PEIZHI)
|
||||
shigong_fangan.append(SHIGONG_FANGAN_ENUM.FANGLEI.YEPIAN.SHIGONG_FANGAN)
|
||||
else:
|
||||
pass
|
||||
jiancha.append("人工防雷")
|
||||
neirong.append(f"轮毂至塔基导通、内部导线线阻、外部导线线阻...")
|
||||
|
||||
|
||||
baogao_bianzhi = baogao_info["userName"]
|
||||
baogao_shenghe = baogao_info["baogaoCheck"]
|
||||
tatong_dir = baogao_info["tatong_dir"]
|
||||
total_pic_dir = baogao_info["total_pic_dir"]
|
||||
except Exception as e:
|
||||
print(f"报告基本信息获取失败:{e}")
|
||||
return
|
||||
|
||||
output_doc = None
|
||||
head_num = 1
|
||||
###封面创建###
|
||||
cover_dirs = [os.path.join(muban_dir,"fengmian1.docx"),fengmian_dir,os.path.join(muban_dir,"fengmian2.docx")]
|
||||
#输出目录
|
||||
baogao_name = "叶片" + "、".join(baogao_label) + "检查报告"
|
||||
output_dir = os.path.normpath(f"{shengcheng_dir}/{project_name}项目{baogao_name}{jizu_bianhao}{baogao_date.split(' ')[0]}版.docx")
|
||||
|
||||
version = 1
|
||||
while os.path.exists(output_dir):
|
||||
if version != 1:
|
||||
output_dir = output_dir.replace(f"版{version - 1}",f"版{version}")
|
||||
else:
|
||||
output_dir = output_dir.replace("版",f"版{version}")
|
||||
version += 1
|
||||
|
||||
mianzhe_shengming = f"本报告仅涵盖{'、'.join(baogao_label)}检测内容"
|
||||
|
||||
#创建文档、添加封面
|
||||
print(await create_document(output_dir))
|
||||
print(add_documents(output_dir, cover_dirs[0]))
|
||||
print(await add_picture(output_dir, cover_dirs[1], 6.41, 4))
|
||||
print(add_documents(output_dir, cover_dirs[2]))
|
||||
print("封面创建成功")
|
||||
|
||||
#更改文档信息
|
||||
print(await search_and_replace(output_dir, TITLE_OF_REPORT, jizu_bianhao))
|
||||
print(await search_and_replace(output_dir, baogao_name1, baogao_name))
|
||||
print(await search_and_replace(output_dir, baogao_name2, baogao_name))
|
||||
print(await search_and_replace(output_dir, company_name_yi, Yi_company))
|
||||
print(await search_and_replace(output_dir, cover_project, fengchang_name))
|
||||
print(await search_and_replace(output_dir, cover_encode, jizu_bianhao))
|
||||
print(await search_and_replace(output_dir, cover_date, date_year_month))
|
||||
print(await search_and_replace(output_dir, 'bianzhi', baogao_bianzhi))
|
||||
print(await search_and_replace(output_dir, 'shenghe', baogao_shenghe))
|
||||
print(await search_and_replace(output_dir, 'mianzhe_shengming', mianzhe_shengming))
|
||||
|
||||
total_table_num = 0
|
||||
#项目概况表
|
||||
print("开始添加项目概况表")
|
||||
XIANG_MU_GAI_KUANG = os.path.join(muban_dir,"xiangmugaikuo.docx")
|
||||
print(f"查找模板,找到模板:{XIANG_MU_GAI_KUANG}")
|
||||
project_location = fengchang_location
|
||||
company_name_jia = Jia_company
|
||||
fuzeren = yi_fuzeren
|
||||
phone_fuzeren = yi_phone
|
||||
xiangmuguige = jizu_num
|
||||
Yi_company = Yi_company
|
||||
XIANGMU_GAIKUO = list(list("" for i in range(6)) for j in range(5))
|
||||
XIANGMU_GAIKUO[0][1] = fengchang_name
|
||||
#XIANGMU_GAIKUO[0][3]=XIANGMU_GAIKUO[0][4] = "盐城市滨海县"
|
||||
XIANGMU_GAIKUO[0][4] = project_location
|
||||
#XIANGMU_GAIKUO[1][1]=XIANGMU_GAIKUO[2,1]=XIANGMU_GAIKUO[3,1] = "国家电投集团滨海风力发电有限公司"
|
||||
XIANGMU_GAIKUO[1][1] = company_name_jia
|
||||
XIANGMU_GAIKUO[1][4] = Yi_company
|
||||
XIANGMU_GAIKUO[2][1] = jia_fuzeren
|
||||
XIANGMU_GAIKUO[2][4] = fuzeren
|
||||
XIANGMU_GAIKUO[3][2] = jia_phone
|
||||
XIANGMU_GAIKUO[3][5] = phone_fuzeren
|
||||
XIANGMU_GAIKUO[4][1] = jizu_xinghao
|
||||
XIANGMU_GAIKUO[4][3] = xiangmuguige
|
||||
XIANGMU_GAIKUO[4][5] = gongqi
|
||||
print("建立表结构完毕,开始插入模板")
|
||||
#添加项目概况表
|
||||
print(f"输出路径:{output_dir},模板路径:{XIANG_MU_GAI_KUANG},插入数据:{XIANGMU_GAIKUO}")
|
||||
output_doc, message = await add_table_to_document(output_dir, XIANG_MU_GAI_KUANG,5,5,total_table_num,XIANGMU_GAIKUO)
|
||||
print(message)
|
||||
print("模板插入完毕,开始替换内容")
|
||||
total_table_num += 1
|
||||
print(await search_and_replace(output_dir, jiegou_xuhao, num_to_chinese[head_num]))
|
||||
head_num += 1
|
||||
|
||||
#检查方案描述
|
||||
FANGAN_JIANCHA_DIR = os.path.join(muban_dir,"checkmethod.docx")
|
||||
list_to_replace = {
|
||||
'renyuan_peizhi' : "\n".join(renyuan_peizhi),
|
||||
'shebei_peizhi' : "\n".join(shebei_peizhi),
|
||||
'shigong_fangan' : "\n".join(shigong_fangan),
|
||||
'gongzuo_neirong' : "\n".join(gongzuo_neirong),
|
||||
'beizhu' : beizhu,
|
||||
'num' : num_to_chinese[head_num],
|
||||
}
|
||||
print(await add_table_and_replace(output_dir, FANGAN_JIANCHA_DIR, 0, list_to_replace))
|
||||
print(split_table_by_row_content(output_dir, output_dir, total_table_num))
|
||||
total_table_num += 1
|
||||
head_num += 1
|
||||
|
||||
|
||||
JIANCHA_XINGXI_DIR = os.path.join(muban_dir,"checkinfo.docx")
|
||||
JIANCHA_XINGXI = list(list("" for i in range(4)) for j in range(9))
|
||||
JIANCHA_XINGXI[0][1] = jiancha_renyuan
|
||||
JIANCHA_XINGXI[1][1] = Jiancha_date.split(' ')[0]
|
||||
JIANCHA_XINGXI[1][3] = jizu_bianhao
|
||||
JIANCHA_XINGXI[2][1] = "风力发电机组" + baogao_name
|
||||
JIANCHA_XINGXI[2][3] = "、".join(jiancha)
|
||||
JIANCHA_XINGXI[3][2] = partManufacturer
|
||||
JIANCHA_XINGXI[4][1] = '叶片型号:' + jizu_xinghao
|
||||
JIANCHA_XINGXI[5][1] = Y_Code[0] if len(Y_Code) > 0 else "无"
|
||||
JIANCHA_XINGXI[6][1] = Y_Code[1] if len(Y_Code) > 1 else "无"
|
||||
JIANCHA_XINGXI[7][1] = Y_Code[2] if len(Y_Code) > 2 else "无"
|
||||
JIANCHA_XINGXI[8][0] = "本次" + "、".join(_ for _ in jiancha) + f"检查,采集叶片图片{total_pic_num}张,内容覆盖" + ";".join(_ for _ in neirong)
|
||||
#新建检查信息表
|
||||
output_doc, message = await add_table_to_document(output_dir, JIANCHA_XINGXI_DIR,9,4,total_table_num ,JIANCHA_XINGXI,False)
|
||||
#添加塔筒图片
|
||||
print(await add_picture_to_table(output_doc, output_dir, 4, 2, tatong_dir, total_table_num, width = 1.18))
|
||||
#添加略缩图
|
||||
print(await add_picture_to_table(output_doc, output_dir, 8, 0, total_pic_dir, total_table_num))
|
||||
print(message)
|
||||
print(await search_and_replace(output_dir, jiegou_xuhao, num_to_chinese[head_num]))
|
||||
head_num += 1
|
||||
total_table_num += 1
|
||||
|
||||
# 添加成果递交表
|
||||
CHENGGUO_DIJIAO_DIR = os.path.join(muban_dir,"chengguo_sub.docx")
|
||||
CHENGGUO_DIJIAO = list(list("" for i in range(4)) for j in range(6))
|
||||
CHENGGUO_DIJIAO[0][1] = jiancha_renyuan
|
||||
CHENGGUO_DIJIAO[1][1] = jia_fuzeren
|
||||
try:
|
||||
CHENGGUO_DIJIAO[2][1] = Jiancha_date.split(' ')[0]
|
||||
except:
|
||||
CHENGGUO_DIJIAO[2][1] = "日期获取失败"
|
||||
CHENGGUO_DIJIAO[3][1] = jiancha_renyuan
|
||||
CHENGGUO_DIJIAO[4][1] = baogao_bianzhi
|
||||
CHENGGUO_DIJIAO[5][1] = baogao_shenghe
|
||||
try:
|
||||
CHENGGUO_DIJIAO[2][3] = Jiancha_date.split(' ')[1]
|
||||
CHENGGUO_DIJIAO[3][3] = baogao_date.split(' ')[0]
|
||||
CHENGGUO_DIJIAO[4][3] = baogao_date.split(' ')[0]
|
||||
except:
|
||||
CHENGGUO_DIJIAO[2][3] = "日期获取失败"
|
||||
CHENGGUO_DIJIAO[3][3] = "日期获取失败"
|
||||
CHENGGUO_DIJIAO[4][3] = "日期获取失败"
|
||||
CHENGGUO_DIJIAO[5][3] = "未审核"
|
||||
|
||||
output_doc, message = await add_table_to_document(output_dir, CHENGGUO_DIJIAO_DIR,5,5,total_table_num,CHENGGUO_DIJIAO,True,0.04)
|
||||
print(message)
|
||||
print(await search_and_replace(output_dir, jiegou_xuhao, num_to_chinese[head_num]))
|
||||
head_num += 1
|
||||
total_table_num += 1
|
||||
|
||||
#主要部位图片展示表/检查内容表
|
||||
#获取典型图信息
|
||||
"""
|
||||
需要获取:
|
||||
Y1、Y2、Y3叶片的图片数量,字典{描述:图片路径}
|
||||
"""
|
||||
try:
|
||||
Y1_dict = get_dict(Y1)
|
||||
Y2_dict = get_dict(Y2)
|
||||
Y3_dict = get_dict(Y3)
|
||||
picture_Y1_num = len(Y1_dict)
|
||||
picture_Y2_num = len(Y2_dict)
|
||||
picture_Y3_num = len(Y3_dict)
|
||||
except Exception as e:
|
||||
print(f"获取图失败:{e}")
|
||||
print(f"图片、文字数量:{picture_Y1_num} {picture_Y2_num} {picture_Y3_num}")
|
||||
JIANCHA_NEIRONG_TOTAL_NUM = picture_Y1_num+ picture_Y2_num + picture_Y3_num
|
||||
col ,row = 3, 0
|
||||
JIANCHA_NEIRONG_PICTURES_TABLE = os.path.join(muban_dir,"check2.docx")
|
||||
JIANCHA_NEIRONG_Y1_DIR = os.path.join(muban_dir,"check_content.docx")
|
||||
JIANCHA_NEIRONG_Y1 = list(list("" for _ in range(3)) for j in range(1))
|
||||
JIANCHA_NEIRONG_Y1[0][0] = f"叶片1:{Y_Code[0]}检查内容"
|
||||
print(f"Y1标题内容:{JIANCHA_NEIRONG_Y1}")
|
||||
JIANCHA_NEIRONG_Y2_DIR = os.path.join(muban_dir,"check3.docx")
|
||||
JIANCHA_NEIRONG_Y2 = list(list("" for _ in range(3)) for j in range(1))
|
||||
JIANCHA_NEIRONG_Y2[0][0] = f"叶片2:{Y_Code[1]}检查内容"
|
||||
print(f"Y2标题内容:{JIANCHA_NEIRONG_Y2}")
|
||||
JIANCHA_NEIRONG_Y3_DIR = os.path.join(muban_dir,"check3.docx")
|
||||
JIANCHA_NEIRONG_Y3 = list(list("" for _ in range(3)) for j in range(1))
|
||||
JIANCHA_NEIRONG_Y3[0][0] = f"叶片3:{Y_Code[2]}检查内容"
|
||||
print(f"Y3标题内容:{JIANCHA_NEIRONG_Y3}")
|
||||
print(f"当前表格序号为 {total_table_num}")
|
||||
print(key_words)
|
||||
output_doc, message = await add_table_to_document(output_dir, JIANCHA_NEIRONG_Y1_DIR,1,3,total_table_num,JIANCHA_NEIRONG_Y1,True, 1)
|
||||
print(message)
|
||||
total_table_num += 1
|
||||
|
||||
total_table_num = await process_images_table(Y1_dict, output_dir, total_table_num, JIANCHA_NEIRONG_PICTURES_TABLE, key_words)
|
||||
|
||||
output_doc, message = await add_table_to_document(output_dir, JIANCHA_NEIRONG_Y2_DIR,1,3,total_table_num,JIANCHA_NEIRONG_Y2,True, 1)
|
||||
print(message)
|
||||
total_table_num += 1
|
||||
|
||||
total_table_num = await process_images_table(Y2_dict, output_dir, total_table_num, JIANCHA_NEIRONG_PICTURES_TABLE, key_words)
|
||||
|
||||
output_doc, message = await add_table_to_document(output_dir, JIANCHA_NEIRONG_Y3_DIR,1,3,total_table_num,JIANCHA_NEIRONG_Y3,True, 1)
|
||||
print(message)
|
||||
total_table_num += 1
|
||||
|
||||
total_table_num = await process_images_table(Y3_dict, output_dir, total_table_num, JIANCHA_NEIRONG_PICTURES_TABLE, key_words)
|
||||
print(await search_and_replace(output_dir, jiegou_xuhao, num_to_chinese[head_num]))
|
||||
head_num += 1
|
||||
|
||||
|
||||
#总结
|
||||
ZONG_JIE_DIR = os.path.join(muban_dir,"result.docx")
|
||||
ZONG_JIE_BEFORE = "result"
|
||||
ZONG_JIE = baogao_zongjie
|
||||
print(add_documents(output_dir, ZONG_JIE_DIR))
|
||||
print(await search_and_replace(output_dir, ZONG_JIE_BEFORE, ZONG_JIE))
|
||||
print(await search_and_replace(output_dir, 'company_yi', Yi_company))
|
||||
print(await search_and_replace(output_dir, 'baogao_date', baogao_date.split(' ')[0]))
|
||||
print(await search_and_replace(output_dir, jiegou_xuhao, num_to_chinese[head_num]))
|
||||
return output_dir
|
||||
def main(json_data1,json_data2):
|
||||
json_data1=json.loads(json_data1)
|
||||
json_data2=json.loads(json_data2)
|
||||
asyncio.run(generate_report(json_data1,json_data2))
|
||||
print('文档生成完毕')
|
|
@ -0,0 +1 @@
|
|||
报告生成接入服务器的测试开发(调用服务器的api测试)
|
|
@ -0,0 +1,138 @@
|
|||
"""
|
||||
Style-related functions for Word Document Server.
|
||||
"""
|
||||
from docx.shared import Pt
|
||||
from docx.enum.style import WD_STYLE_TYPE
|
||||
|
||||
|
||||
def ensure_heading_style(doc):
|
||||
"""
|
||||
Ensure Heading styles exist in the document.
|
||||
|
||||
Args:
|
||||
doc: Document object
|
||||
"""
|
||||
for i in range(1, 10): # Create Heading 1 through Heading 9
|
||||
style_name = f'Heading {i}'
|
||||
try:
|
||||
# Try to access the style to see if it exists
|
||||
style = doc.styles[style_name]
|
||||
except KeyError:
|
||||
# Create the style if it doesn't exist
|
||||
try:
|
||||
from docx.oxml.ns import qn
|
||||
style = doc.styles.add_style(style_name, WD_STYLE_TYPE.PARAGRAPH)
|
||||
style.font.name = '宋体(中文正文)'
|
||||
style.font.size = Pt(22) # 根据需要设置字体大小
|
||||
style._element.rPr.rFonts.set(qn('w:eastAsia'), '宋体(中文正文)')
|
||||
if i == 1:
|
||||
style.font.size = Pt(16)
|
||||
style.font.bold = True
|
||||
elif i == 2:
|
||||
style.font.size = Pt(14)
|
||||
style.font.bold = True
|
||||
else:
|
||||
style.font.size = Pt(12)
|
||||
style.font.bold = True
|
||||
except Exception:
|
||||
# If style creation fails, we'll just use default formatting
|
||||
pass
|
||||
|
||||
|
||||
def ensure_table_style(doc):
|
||||
"""
|
||||
Ensure Table Grid style exists in the document.
|
||||
|
||||
Args:
|
||||
doc: Document object
|
||||
"""
|
||||
try:
|
||||
# Try to access the style to see if it exists
|
||||
style = doc.styles['Table Grid']
|
||||
except KeyError:
|
||||
# If style doesn't exist, we'll handle it at usage time
|
||||
pass
|
||||
|
||||
|
||||
def create_style(doc, style_name, style_type, base_style=None, font_properties=None, paragraph_properties=None):
|
||||
"""
|
||||
Create a new style in the document.
|
||||
|
||||
Args:
|
||||
doc: Document object
|
||||
style_name: Name for the new style
|
||||
style_type: Type of style (WD_STYLE_TYPE)
|
||||
base_style: Optional base style to inherit from
|
||||
font_properties: Dictionary of font properties (bold, italic, size, name, color)
|
||||
paragraph_properties: Dictionary of paragraph properties (alignment, spacing)
|
||||
|
||||
Returns:
|
||||
The created style
|
||||
"""
|
||||
from docx.shared import Pt
|
||||
|
||||
try:
|
||||
# Check if style already exists
|
||||
style = doc.styles.get_by_id(style_name, WD_STYLE_TYPE.PARAGRAPH)
|
||||
return style
|
||||
except:
|
||||
# Create new style
|
||||
new_style = doc.styles.add_style(style_name, style_type)
|
||||
|
||||
# Set base style if specified
|
||||
if base_style:
|
||||
new_style.base_style = doc.styles[base_style]
|
||||
|
||||
# Set font properties
|
||||
if font_properties:
|
||||
font = new_style.font
|
||||
if 'bold' in font_properties:
|
||||
font.bold = font_properties['bold']
|
||||
if 'italic' in font_properties:
|
||||
font.italic = font_properties['italic']
|
||||
if 'size' in font_properties:
|
||||
font.size = Pt(font_properties['size'])
|
||||
if 'name' in font_properties:
|
||||
font.name = font_properties['name']
|
||||
if 'color' in font_properties:
|
||||
from docx.shared import RGBColor
|
||||
|
||||
# Define common RGB colors
|
||||
color_map = {
|
||||
'red': RGBColor(255, 0, 0),
|
||||
'blue': RGBColor(0, 0, 255),
|
||||
'green': RGBColor(0, 128, 0),
|
||||
'yellow': RGBColor(255, 255, 0),
|
||||
'black': RGBColor(0, 0, 0),
|
||||
'gray': RGBColor(128, 128, 128),
|
||||
'white': RGBColor(255, 255, 255),
|
||||
'purple': RGBColor(128, 0, 128),
|
||||
'orange': RGBColor(255, 165, 0)
|
||||
}
|
||||
|
||||
color_value = font_properties['color']
|
||||
try:
|
||||
# Handle string color names
|
||||
if isinstance(color_value, str) and color_value.lower() in color_map:
|
||||
font.color.rgb = color_map[color_value.lower()]
|
||||
# Handle RGBColor objects
|
||||
elif hasattr(color_value, 'rgb'):
|
||||
font.color.rgb = color_value
|
||||
# Try to parse as RGB string
|
||||
elif isinstance(color_value, str):
|
||||
font.color.rgb = RGBColor.from_string(color_value)
|
||||
# Use directly if it's already an RGB value
|
||||
else:
|
||||
font.color.rgb = color_value
|
||||
except Exception as e:
|
||||
# Fallback to black if all else fails
|
||||
font.color.rgb = RGBColor(0, 0, 0)
|
||||
|
||||
# Set paragraph properties
|
||||
if paragraph_properties:
|
||||
if 'alignment' in paragraph_properties:
|
||||
new_style.paragraph_format.alignment = paragraph_properties['alignment']
|
||||
if 'spacing' in paragraph_properties:
|
||||
new_style.paragraph_format.line_spacing = paragraph_properties['spacing']
|
||||
|
||||
return new_style
|
|
@ -0,0 +1,283 @@
|
|||
"""
|
||||
Table-related operations for Word Document Server.
|
||||
"""
|
||||
from docx.oxml.shared import OxmlElement, qn
|
||||
from docx.oxml.ns import nsdecls
|
||||
from docx.oxml import parse_xml
|
||||
|
||||
|
||||
def set_cell_border(cell, **kwargs):
|
||||
"""
|
||||
Set cell border properties.
|
||||
|
||||
Args:
|
||||
cell: The cell to modify
|
||||
**kwargs: Border properties (top, bottom, left, right, val, color)
|
||||
"""
|
||||
tc = cell._tc
|
||||
tcPr = tc.get_or_add_tcPr()
|
||||
|
||||
# Create border elements
|
||||
for key, value in kwargs.items():
|
||||
if key in ['top', 'left', 'bottom', 'right']:
|
||||
tag = 'w:{}'.format(key)
|
||||
|
||||
element = OxmlElement(tag)
|
||||
element.set(qn('w:val'), kwargs.get('val', 'single'))
|
||||
element.set(qn('w:sz'), kwargs.get('sz', '4'))
|
||||
element.set(qn('w:space'), kwargs.get('space', '0'))
|
||||
element.set(qn('w:color'), kwargs.get('color', 'auto'))
|
||||
|
||||
tcBorders = tcPr.first_child_found_in("w:tcBorders")
|
||||
if tcBorders is None:
|
||||
tcBorders = OxmlElement('w:tcBorders')
|
||||
tcPr.append(tcBorders)
|
||||
|
||||
tcBorders.append(element)
|
||||
|
||||
|
||||
def apply_table_style(table, has_header_row=False, border_style=None, shading=None):
|
||||
"""
|
||||
Apply formatting to a table.
|
||||
|
||||
Args:
|
||||
table: The table to format
|
||||
has_header_row: If True, formats the first row as a header
|
||||
border_style: Style for borders ('none', 'single', 'double', 'thick')
|
||||
shading: 2D list of cell background colors (by row and column)
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
# Format header row if requested
|
||||
if has_header_row and table.rows:
|
||||
header_row = table.rows[0]
|
||||
for cell in header_row.cells:
|
||||
for paragraph in cell.paragraphs:
|
||||
if paragraph.runs:
|
||||
for run in paragraph.runs:
|
||||
run.bold = True
|
||||
|
||||
# Apply border style if specified
|
||||
if border_style:
|
||||
val_map = {
|
||||
'none': 'nil',
|
||||
'single': 'single',
|
||||
'double': 'double',
|
||||
'thick': 'thick'
|
||||
}
|
||||
val = val_map.get(border_style.lower(), 'single')
|
||||
|
||||
# Apply to all cells
|
||||
for row in table.rows:
|
||||
for cell in row.cells:
|
||||
set_cell_border(
|
||||
cell,
|
||||
top=True,
|
||||
bottom=True,
|
||||
left=True,
|
||||
right=True,
|
||||
val=val,
|
||||
color="000000"
|
||||
)
|
||||
|
||||
# Apply cell shading if specified
|
||||
if shading:
|
||||
for i, row_colors in enumerate(shading):
|
||||
if i >= len(table.rows):
|
||||
break
|
||||
for j, color in enumerate(row_colors):
|
||||
if j >= len(table.rows[i].cells):
|
||||
break
|
||||
try:
|
||||
# Apply shading to cell
|
||||
cell = table.rows[i].cells[j]
|
||||
shading_elm = parse_xml(f'<w:shd {nsdecls("w")} w:fill="{color}"/>')
|
||||
cell._tc.get_or_add_tcPr().append(shading_elm)
|
||||
except:
|
||||
# Skip if color format is invalid
|
||||
pass
|
||||
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
|
||||
def copy_table(source_table, target_doc, ifadjustheight=True, height = 1):
|
||||
"""
|
||||
Copy a table from one document to another.
|
||||
|
||||
Args:
|
||||
source_table: The table to copy
|
||||
target_doc: The document to copy the table to
|
||||
|
||||
Returns:
|
||||
The new table in the target document
|
||||
"""
|
||||
# Create a new table with the same dimensions
|
||||
new_table = target_doc.add_table(rows=len(source_table.rows), cols=len(source_table.columns))
|
||||
|
||||
# Try to apply the same style
|
||||
try:
|
||||
if source_table.style:
|
||||
new_table.style = 'Table Grid'
|
||||
except:
|
||||
# Fall back to default grid style
|
||||
try:
|
||||
new_table.style = 'Table Grid'
|
||||
except:
|
||||
pass
|
||||
from docx.enum.table import WD_TABLE_ALIGNMENT
|
||||
from docx.enum.table import WD_ALIGN_VERTICAL
|
||||
from docx.shared import Pt, Inches, Cm, RGBColor
|
||||
# Copy cell contents
|
||||
for i, row in enumerate(source_table.rows):
|
||||
for j, cell in enumerate(row.cells):
|
||||
for paragraph in cell.paragraphs:
|
||||
average_char_width_in_points = 6
|
||||
if paragraph.text:
|
||||
new_table.cell(i,j).text = paragraph.text
|
||||
new_table.cell(i,j).paragraphs[0].runs[0].font.name = "Times New Roman" #设置英文字体
|
||||
new_table.cell(i,j).paragraphs[0].runs[0].font.size = Pt(10.5) # 字体大小
|
||||
new_table.cell(i,j).paragraphs[0].runs[0]._element.rPr.rFonts.set(qn('w:eastAsia'), '仿宋') #设置中文字体
|
||||
new_table.cell(i,j).paragraphs[0].paragraph_format.alignment = WD_TABLE_ALIGNMENT.CENTER
|
||||
new_table.cell(i,j).vertical_alignment = WD_ALIGN_VERTICAL.CENTER
|
||||
"""
|
||||
待添加:如何让表格自适应大小(autofit目前不知为何没有作用)
|
||||
"""
|
||||
if ifadjustheight:
|
||||
new_table.rows[i].height = Cm(height)
|
||||
try:
|
||||
new_table = merge_tables(new_table)
|
||||
except Exception as e:
|
||||
print(f"合并表格失败:{e}")
|
||||
from docx.shared import Inches
|
||||
|
||||
return new_table
|
||||
|
||||
|
||||
from collections import deque
|
||||
|
||||
def merge_tables(table):
|
||||
"""BFS遍历,将相邻且相同单元格合并
|
||||
|
||||
Args:
|
||||
table: 表格,docx库的Table类型
|
||||
Returns:
|
||||
合并后的表格
|
||||
"""
|
||||
if not table or len(table.rows) == 0:
|
||||
return table
|
||||
|
||||
rows = len(table.rows)
|
||||
cols = len(table.columns)
|
||||
|
||||
# 创建访问标记矩阵
|
||||
visited = [[False for _ in range(cols)] for _ in range(rows)]
|
||||
|
||||
# 定义四个方向的移动:上、右、下、左
|
||||
directions = [(0, 0), (0, 1), (1, 0), (0, 0)]
|
||||
|
||||
for i in range(rows):
|
||||
for j in range(cols):
|
||||
if not visited[i][j]:
|
||||
current_cell = table.cell(i, j)
|
||||
current_text = current_cell.text.strip()
|
||||
|
||||
if not current_text: # 跳过空单元格
|
||||
visited[i][j] = True
|
||||
continue
|
||||
|
||||
# BFS队列
|
||||
queue = deque()
|
||||
queue.append((i, j))
|
||||
visited[i][j] = True
|
||||
|
||||
# 记录需要合并的单元格
|
||||
cells_to_merge = []
|
||||
|
||||
while queue:
|
||||
x, y = queue.popleft()
|
||||
cells_to_merge.append((x, y))
|
||||
|
||||
for dx, dy in directions:
|
||||
nx, ny = x + dx, y + dy
|
||||
|
||||
# 检查边界和访问状态
|
||||
if 0 <= nx < rows and 0 <= ny < cols and not visited[nx][ny]:
|
||||
neighbor_cell = table.cell(nx, ny)
|
||||
neighbor_text = neighbor_cell.text.strip()
|
||||
|
||||
if neighbor_text == current_text:
|
||||
visited[nx][ny] = True
|
||||
queue.append((nx, ny))
|
||||
|
||||
# 如果有需要合并的单元格
|
||||
if len(cells_to_merge) > 1:
|
||||
# 按行和列排序,确保左上角是第一个单元格
|
||||
cells_to_merge.sort()
|
||||
min_row, min_col = cells_to_merge[0]
|
||||
max_row, max_col = cells_to_merge[-1]
|
||||
|
||||
# 清空所有待合并单元格(包括换行符)
|
||||
for x, y in cells_to_merge[1:]:
|
||||
cell = table.cell(x, y)
|
||||
# 删除所有段落(彻底清空)
|
||||
for paragraph in list(cell.paragraphs):
|
||||
p = paragraph._element
|
||||
p.getparent().remove(p)
|
||||
# 可选:添加一个空段落防止格式问题
|
||||
cell.add_paragraph()
|
||||
|
||||
# 执行合并
|
||||
if max_row > min_row or max_col > min_col:
|
||||
table.cell(min_row, min_col).merge(table.cell(max_row, max_col))
|
||||
|
||||
return table
|
||||
|
||||
def fill_tables(Y_table_list, row, col, Y_Table_num, Y):
|
||||
"""根据前端返回json块填写表格list,并实时跟进已填写表格数量
|
||||
目前只支持固定的缺陷图的填写
|
||||
|
||||
Args:
|
||||
Y_table_list (list): 前端返回的json块
|
||||
row (int): 表格行数
|
||||
col (int): 表格列数
|
||||
Y_Table_num: json块中有几个表格
|
||||
Xu_Hao: 是第几个json块
|
||||
Y: 其他参数
|
||||
|
||||
Return:
|
||||
Y1_TABLES: 三维,表和对应元素
|
||||
table_index_to_images: 字典,表索引到图片路径列表的映射
|
||||
Xu_Hao:到达第几个表了
|
||||
"""
|
||||
table_index_to_images = {}
|
||||
Y_TABLES = [[["" for _ in range(row)] for _ in range(col)] for _ in range(Y_Table_num)]
|
||||
|
||||
# 处理前端返回数据
|
||||
for l, table_dict in enumerate(Y_table_list):
|
||||
if table_dict:
|
||||
Y_TABLES[l][1][0] = Y
|
||||
Y_TABLES[l][1][1] = table_dict["QueXianLeiXing"]
|
||||
Y_TABLES[l][1][2] = table_dict["QueXianWeiZhi"]
|
||||
Y_TABLES[l][1][3] = table_dict["QueXianChiCun"]
|
||||
Y_TABLES[l][3][0] = table_dict["WeiZongDengJi"]
|
||||
Y_TABLES[l][3][1] = table_dict["visibility"]
|
||||
Y_TABLES[l][3][2] = table_dict["urgency"]
|
||||
Y_TABLES[l][3][3] = table_dict["repair_suggestion"]
|
||||
|
||||
# 获取图片路径
|
||||
image_path = table_dict['Tupian_Dir']
|
||||
if image_path:
|
||||
# 确保路径是字符串形式
|
||||
if isinstance(image_path, list):
|
||||
table_index_to_images[l] = image_path.copy()
|
||||
else:
|
||||
table_index_to_images[l] = [str(image_path)]
|
||||
|
||||
return Y_TABLES, table_index_to_images
|
||||
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,637 @@
|
|||
"""
|
||||
Content tools for Word Document Server.
|
||||
|
||||
These tools add various types of content to Word documents,
|
||||
including headings, paragraphs, tables, images, and page breaks.
|
||||
"""
|
||||
import os
|
||||
from typing import List, Optional, Dict, Any
|
||||
from docx import Document
|
||||
from docx.shared import Inches, Pt
|
||||
from docx.oxml.shared import qn
|
||||
|
||||
|
||||
from ..utils.file_utils import check_file_writeable, ensure_docx_extension
|
||||
from ..utils.document_utils import find_and_replace_text
|
||||
from ..core.styles import ensure_heading_style, ensure_table_style
|
||||
|
||||
def split_table_by_row_content(
|
||||
doc_path: str,
|
||||
output_path: str,
|
||||
table_num: int = 0
|
||||
) -> str:
|
||||
"""
|
||||
根据表格第二行第一列内容的行数对指定表格进行分行处理,
|
||||
并将每列内容按相同行数分割,不足则重复
|
||||
|
||||
参数:
|
||||
doc_path: 输入Word文档路径
|
||||
output_path: 输出Word文档路径
|
||||
table_num: 要处理的表格序号(从0开始)
|
||||
"""
|
||||
try:
|
||||
from docx import Document
|
||||
from docx.shared import Pt
|
||||
from docx.oxml.shared import qn
|
||||
from docx.enum.table import WD_TABLE_ALIGNMENT, WD_ALIGN_VERTICAL
|
||||
|
||||
# 打开文档
|
||||
doc = Document(doc_path)
|
||||
|
||||
# 检查表格是否存在
|
||||
if len(doc.tables) <= table_num:
|
||||
return f"文档中不存在第{table_num+1}个表格"
|
||||
|
||||
# 获取指定表格
|
||||
table = doc.tables[table_num]
|
||||
|
||||
# 获取表格行数和列数
|
||||
row_count = len(table.rows)
|
||||
col_count = len(table.columns)
|
||||
|
||||
# 如果表格行数小于2,无法处理
|
||||
if row_count < 2:
|
||||
doc.save(output_path)
|
||||
return "表格行数少于2行,无法按照要求分行"
|
||||
|
||||
# 获取第二行第一列的文本内容
|
||||
second_row_first_cell = table.cell(1, 0)
|
||||
second_row_text = second_row_first_cell.text
|
||||
|
||||
# 计算第二行第一列文本的行数(按换行符分割)
|
||||
lines_in_second_row = len(second_row_text.split('\n'))
|
||||
|
||||
# 如果行数为0,设置为1(至少分为1部分)
|
||||
split_count = max(1, lines_in_second_row)
|
||||
|
||||
print(f'原表格行数:{row_count},第二行第一列内容行数:{split_count},需要分割为:{split_count}部分')
|
||||
|
||||
# 创建新表格来替代原表格(分割后的表格)
|
||||
# 新表格的行数 = 标题行(1) + 原数据行数 × 分割部分数
|
||||
new_table = doc.add_table(rows=1 + (row_count-1)*split_count, cols=col_count)
|
||||
|
||||
# 设置表格样式
|
||||
new_table.style = table.style
|
||||
new_table.autofit = True
|
||||
|
||||
# 1. 处理标题行(第一行)保持不变
|
||||
for col_idx in range(col_count):
|
||||
orig_cell = table.cell(0, col_idx)
|
||||
new_cell = new_table.cell(0, col_idx)
|
||||
|
||||
# 复制内容并设置格式
|
||||
new_cell.text = orig_cell.text
|
||||
if orig_cell.paragraphs:
|
||||
# 设置格式
|
||||
new_cell.paragraphs[0].runs[0].font.name = "Times New Roman"
|
||||
new_cell.paragraphs[0].runs[0].font.size = Pt(10.5)
|
||||
new_cell.paragraphs[0].runs[0]._element.rPr.rFonts.set(qn('w:eastAsia'), '仿宋')
|
||||
new_cell.paragraphs[0].paragraph_format.alignment = WD_TABLE_ALIGNMENT.CENTER
|
||||
new_cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
|
||||
new_cell.width = orig_cell.width
|
||||
|
||||
# 2. 处理数据行(从第二行开始)
|
||||
for orig_row_idx in range(1, row_count): # 遍历原表格的每一行数据
|
||||
for col_idx in range(col_count): # 遍历每一列
|
||||
orig_cell = table.cell(orig_row_idx, col_idx)
|
||||
cell_text = orig_cell.text
|
||||
|
||||
# 分割当前单元格内容
|
||||
cell_lines = cell_text.split('\n')
|
||||
cell_line_count = len(cell_lines)
|
||||
|
||||
# 如果内容行数不足分割数,则重复最后一行
|
||||
if cell_line_count < split_count:
|
||||
cell_lines += [cell_lines[-1]] * (split_count - cell_line_count)
|
||||
|
||||
# 在新表格中对应的位置写入分割后的内容
|
||||
for part_idx in range(split_count):
|
||||
# 计算新表格中的行位置
|
||||
new_row_idx = 1 + (orig_row_idx-1)*split_count + part_idx
|
||||
|
||||
# 获取新单元格
|
||||
new_cell = new_table.cell(new_row_idx, col_idx)
|
||||
|
||||
# 写入分割后的内容
|
||||
line_text = cell_lines[part_idx] if part_idx < len(cell_lines) else cell_lines[-1]
|
||||
new_cell.text = line_text
|
||||
|
||||
# 设置格式
|
||||
if new_cell.paragraphs:
|
||||
new_cell.paragraphs[0].runs[0].font.name = "Times New Roman"
|
||||
new_cell.paragraphs[0].runs[0].font.size = Pt(10.5)
|
||||
new_cell.paragraphs[0].runs[0]._element.rPr.rFonts.set(qn('w:eastAsia'), '仿宋')
|
||||
new_cell.paragraphs[0].paragraph_format.alignment = WD_TABLE_ALIGNMENT.CENTER
|
||||
new_cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
|
||||
|
||||
# 复制单元格宽度
|
||||
new_cell.width = orig_cell.width
|
||||
|
||||
# 删除原表格
|
||||
table._element.getparent().remove(table._element)
|
||||
|
||||
# 保存文档
|
||||
doc.save(output_path)
|
||||
return f"第{table_num+1}个表格已成功分行处理"
|
||||
|
||||
except Exception as e:
|
||||
return f"处理表格时出错: {str(e)}"
|
||||
|
||||
|
||||
async def add_heading(filename: str, text: str, level: int = 1) -> str:
|
||||
"""对文档增加标题
|
||||
|
||||
Args:
|
||||
filename: 目标文档路径
|
||||
text: 标题文本
|
||||
level: 标题级别,1为最高级
|
||||
"""
|
||||
filename = ensure_docx_extension(filename)
|
||||
|
||||
# Ensure level is converted to integer
|
||||
try:
|
||||
level = int(level)
|
||||
except (ValueError, TypeError):
|
||||
return "Invalid parameter: level must be an integer between 1 and 9"
|
||||
|
||||
# Validate level range
|
||||
if level < 1 or level > 9:
|
||||
return f"Invalid heading level: {level}. Level must be between 1 and 9."
|
||||
|
||||
if not os.path.exists(filename):
|
||||
return f"Document {filename} does not exist"
|
||||
|
||||
# Check if file is writeable
|
||||
is_writeable, error_message = check_file_writeable(filename)
|
||||
if not is_writeable:
|
||||
# Suggest creating a copy
|
||||
return f"Cannot modify document: {error_message}. Consider creating a copy first or creating a new document."
|
||||
|
||||
try:
|
||||
doc = Document(filename)
|
||||
|
||||
# Ensure heading styles exist
|
||||
ensure_heading_style(doc)
|
||||
|
||||
# Try to add heading with style
|
||||
try:
|
||||
heading = doc.add_heading(text, level=level)
|
||||
doc.save(filename)
|
||||
return f"Heading '{text}' (level {level}) added to {filename}"
|
||||
except Exception as style_error:
|
||||
# If style-based approach fails, use direct formatting
|
||||
paragraph = doc.add_paragraph(text)
|
||||
paragraph.style = doc.styles['Normal']
|
||||
run = paragraph.runs[0]
|
||||
run.bold = True
|
||||
rPr = run.element.get_or_add_rPr()
|
||||
rFonts = rPr.get_or_add_rFonts()
|
||||
from docx.oxml.shared import qn
|
||||
rFonts.set(qn('w:eastAsia'), '宋体(中文正文)')
|
||||
# Adjust size based on heading level
|
||||
if level == 1:
|
||||
run.font.size = Pt(12)
|
||||
elif level == 2:
|
||||
run.font.size = Pt(14)
|
||||
else:
|
||||
run.font.size = Pt(12)
|
||||
|
||||
doc.save(filename)
|
||||
return f"Heading '{text}' added to {filename} with direct formatting (style not available)"
|
||||
except Exception as e:
|
||||
return f"Failed to add heading: {str(e)}"
|
||||
|
||||
async def add_paragraph(filename: str, text: str, style: Optional[str] = None) -> str:
|
||||
"""对文档添加一个段落(一行)
|
||||
|
||||
Args:
|
||||
filename: 目标文档路径
|
||||
text: 段落内容
|
||||
style: 段落样式,可选
|
||||
"""
|
||||
filename = ensure_docx_extension(filename)
|
||||
|
||||
if not os.path.exists(filename):
|
||||
return f"Document {filename} does not exist"
|
||||
|
||||
# Check if file is writeable
|
||||
is_writeable, error_message = check_file_writeable(filename)
|
||||
if not is_writeable:
|
||||
# Suggest creating a copy
|
||||
return f"Cannot modify document: {error_message}. Consider creating a copy first or creating a new document."
|
||||
|
||||
try:
|
||||
doc = Document(filename)
|
||||
paragraph = doc.add_paragraph(text)
|
||||
|
||||
if style:
|
||||
try:
|
||||
paragraph.style = style
|
||||
except KeyError:
|
||||
# Style doesn't exist, use normal and report it
|
||||
paragraph.style = doc.styles['Normal']
|
||||
# Copy run formatting
|
||||
# for i, run in enumerate(paragraph.runs):
|
||||
# if i < len(paragraph.runs):
|
||||
# new_run = paragraph.runs[i]
|
||||
# # Copy basic formatting
|
||||
# new_run.bold = run.bold
|
||||
# new_run.italic = run.italic
|
||||
# new_run.underline = run.underline
|
||||
# #添加同时合并字体2025427
|
||||
# new_run.font.name = run.font.name
|
||||
# rPr = new_run.element.get_or_add_rPr()
|
||||
# rFonts = rPr.get_or_add_rFonts()
|
||||
# # 检查 run.font.name 是否为 None
|
||||
# if run.font.name is None:
|
||||
# # 设置默认的中文字体名称
|
||||
# run.font.name = '宋体 (中文正文)' # 或者使用其他你喜欢的中文字体
|
||||
# rFonts.set(qn('w:eastAsia'), run.font.name)
|
||||
# new_run.font.color.rgb = run.font.color.rgb
|
||||
|
||||
# # Font size if specified
|
||||
# if run.font.size:
|
||||
# new_run.font.size = run.font.size
|
||||
doc.save(filename)
|
||||
return f"Style '{style}' not found, paragraph added with default style to {filename}"
|
||||
|
||||
doc.save(filename)
|
||||
return f"Paragraph added to {filename}"
|
||||
except Exception as e:
|
||||
return f"Failed to add paragraph: {str(e)}"
|
||||
|
||||
|
||||
async def add_table(filename: str, rows: int, cols: int, data: Optional[List[List[str]]] = None) -> str:
|
||||
"""对文档添加一个表格
|
||||
|
||||
Args:
|
||||
filename: 目标文档路径
|
||||
rows: 表格行数
|
||||
cols: 表格列数
|
||||
data: 二维数组列表,每一项为单元格内容,默认为空
|
||||
"""
|
||||
filename = ensure_docx_extension(filename)
|
||||
|
||||
if not os.path.exists(filename):
|
||||
return f"Document {filename} does not exist"
|
||||
|
||||
# Check if file is writeable
|
||||
is_writeable, error_message = check_file_writeable(filename)
|
||||
if not is_writeable:
|
||||
# Suggest creating a copy
|
||||
return f"Cannot modify document: {error_message}. Consider creating a copy first or creating a new document."
|
||||
|
||||
try:
|
||||
doc = Document(filename)
|
||||
table = doc.add_table(rows=rows, cols=cols)
|
||||
|
||||
# Try to set the table style
|
||||
try:
|
||||
table.style = 'Table Grid'
|
||||
except KeyError:
|
||||
# If style doesn't exist, add basic borders
|
||||
pass
|
||||
|
||||
# Fill table with data if provided
|
||||
if data:
|
||||
for i, row_data in enumerate(data):
|
||||
if i >= rows:
|
||||
break
|
||||
for j, cell_text in enumerate(row_data):
|
||||
if j >= cols:
|
||||
break
|
||||
table.cell(i, j).text = str(cell_text)
|
||||
|
||||
doc.save(filename)
|
||||
return f"Table ({rows}x{cols}) added to {filename}"
|
||||
except Exception as e:
|
||||
return f"Failed to add table: {str(e)}"
|
||||
|
||||
async def add_picture_to_table(target_doc: Document, target_filename: str, row: int, col: int, image_path: str,table_num: int = -1, width: Optional[float] = None) -> str:
|
||||
"""向文档中对应表格添加图片
|
||||
|
||||
Args:
|
||||
target_doc: 目标文档
|
||||
target_filename: 目标文档保存路径
|
||||
row: 表格行数
|
||||
col: 表格列数
|
||||
image_path: 图片路径
|
||||
table_num: 表格序号,默认为-1,即最后一个表格
|
||||
width: 图片宽度,默认为None,表示使用原始图片大小
|
||||
"""
|
||||
from PIL import Image
|
||||
if not os.path.exists(image_path):
|
||||
return f"Image file not found: {image_path}"
|
||||
|
||||
# Check image file size
|
||||
try:
|
||||
image_size = os.path.getsize(image_path) / 1024 # Size in KB
|
||||
if image_size <= 0:
|
||||
return f"Image file appears to be empty: {image_path} (0 KB)"
|
||||
elif image_size > 9126:
|
||||
# Create the output directory if it doesn't exist
|
||||
output_dir = os.path.join(os.path.dirname(image_path), "压缩图片")
|
||||
if not os.path.exists(output_dir):
|
||||
os.makedirs(output_dir)
|
||||
|
||||
# Define the output path for the compressed image
|
||||
image_name = os.path.basename(image_path)
|
||||
output_path = os.path.join(output_dir, image_name)
|
||||
|
||||
# Compress the image
|
||||
while image_size > 9126:
|
||||
print(f"压缩图片:{image_path} ({image_size:.2f} KB) -> {output_path} (9126 KB)")
|
||||
with Image.open(image_path) as img:
|
||||
img.save(output_path, optimize=True, quality=85)
|
||||
image_size = os.path.getsize(output_path) / 1024 # Size in KB
|
||||
|
||||
# Update the image path to the compressed image path
|
||||
image_path = output_path
|
||||
except Exception as size_error:
|
||||
return f"Error checking image file: {str(size_error)}"
|
||||
|
||||
try:
|
||||
table = target_doc.tables[table_num]
|
||||
# Add the picture to the cell
|
||||
cell = table.cell(row, col)
|
||||
if len(cell.text) == 1: cell.text = ""
|
||||
paragraph = cell.paragraphs[-1]
|
||||
run = paragraph.add_run()
|
||||
try:
|
||||
if width:
|
||||
run.add_picture(image_path, width=Inches(width))
|
||||
else:
|
||||
run.add_picture(image_path)
|
||||
except Exception as e:
|
||||
# 如果添加图片时出现问题,尝试将图片转换为PNG格式
|
||||
try:
|
||||
print(f"正常添加失败,尝试转换图片后添加:{image_path}")
|
||||
# 打开图片
|
||||
img = Image.open(image_path)
|
||||
# 转换为PNG格式
|
||||
temp_image_path = os.path.splitext(image_path)[0] + '.png'
|
||||
img.save(temp_image_path, 'PNG')
|
||||
|
||||
# 尝试添加转换后的图片
|
||||
if width:
|
||||
run.add_picture(temp_image_path, width=Inches(width))
|
||||
else:
|
||||
run.add_picture(temp_image_path)
|
||||
|
||||
# 添加完成后删除转换后的图片
|
||||
os.remove(temp_image_path)
|
||||
except Exception as e:
|
||||
# 如果转换或添加转换后的图片时出现问题,返回错误信息
|
||||
return f"调用add_picture函数出现问题: {str(e)}"
|
||||
from docx.enum.table import WD_TABLE_ALIGNMENT
|
||||
from docx.enum.table import WD_ALIGN_VERTICAL
|
||||
cell.paragraphs[0].paragraph_format.alignment = WD_TABLE_ALIGNMENT.CENTER
|
||||
cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
|
||||
|
||||
target_doc.save(target_filename)
|
||||
return f"Picture {image_path} added to table {table_num} cell ({row},{col})"
|
||||
except Exception as e:
|
||||
return f"Failed to add picture to table: {str(e)}"
|
||||
|
||||
async def add_picture(filename: str, image_path: str, width: Optional[float] = None, height: Optional[float] = None) -> str:
|
||||
"""添加一个图片到文档中
|
||||
|
||||
Args:
|
||||
filename: 文档路径
|
||||
image_path: 图片路径
|
||||
width: 图片大小
|
||||
"""
|
||||
filename = ensure_docx_extension(filename)
|
||||
|
||||
# Validate document existence
|
||||
if not os.path.exists(filename):
|
||||
return f"Document {filename} does not exist"
|
||||
|
||||
# Get absolute paths for better diagnostics
|
||||
abs_filename = os.path.abspath(filename)
|
||||
abs_image_path = os.path.abspath(image_path)
|
||||
|
||||
# Validate image existence with improved error message
|
||||
if not os.path.exists(abs_image_path):
|
||||
return f"Image file not found: {abs_image_path}"
|
||||
|
||||
# Check image file size
|
||||
try:
|
||||
image_size = os.path.getsize(abs_image_path) / 1024 # Size in KB
|
||||
if image_size <= 0:
|
||||
return f"Image file appears to be empty: {abs_image_path} (0 KB)"
|
||||
except Exception as size_error:
|
||||
return f"Error checking image file: {str(size_error)}"
|
||||
|
||||
# Check if file is writeable
|
||||
is_writeable, error_message = check_file_writeable(abs_filename)
|
||||
if not is_writeable:
|
||||
return f"Cannot modify document: {error_message}. Consider creating a copy first or creating a new document."
|
||||
|
||||
try:
|
||||
doc = Document(abs_filename)
|
||||
# Additional diagnostic info
|
||||
diagnostic = f"Attempting to add image ({abs_image_path}, {image_size:.2f} KB) to document ({abs_filename})"
|
||||
|
||||
try:
|
||||
if width:
|
||||
doc.add_picture(abs_image_path, width=Inches(width), height=Inches(height))
|
||||
else:
|
||||
doc.add_picture(abs_image_path)
|
||||
doc.save(abs_filename)
|
||||
return f"Picture {image_path} added to {filename}"
|
||||
except Exception as inner_error:
|
||||
# More detailed error for the specific operation
|
||||
error_type = type(inner_error).__name__
|
||||
error_msg = str(inner_error)
|
||||
return f"Failed to add picture: {error_type} - {error_msg or 'No error details available'}\nDiagnostic info: {diagnostic}"
|
||||
except Exception as outer_error:
|
||||
# Fallback error handling
|
||||
error_type = type(outer_error).__name__
|
||||
error_msg = str(outer_error)
|
||||
return f"Document processing error: {error_type} - {error_msg or 'No error details available'}"
|
||||
|
||||
|
||||
async def add_page_break(filename: str) -> str:
|
||||
"""增加分页符
|
||||
|
||||
Args:
|
||||
filename: 目标文档
|
||||
"""
|
||||
filename = ensure_docx_extension(filename)
|
||||
|
||||
if not os.path.exists(filename):
|
||||
return f"Document {filename} does not exist"
|
||||
|
||||
# Check if file is writeable
|
||||
is_writeable, error_message = check_file_writeable(filename)
|
||||
if not is_writeable:
|
||||
return f"Cannot modify document: {error_message}. Consider creating a copy first."
|
||||
|
||||
try:
|
||||
doc = Document(filename)
|
||||
doc.add_page_break()
|
||||
doc.save(filename)
|
||||
return f"Page break added to {filename}."
|
||||
except Exception as e:
|
||||
return f"Failed to add page break: {str(e)}"
|
||||
|
||||
|
||||
async def add_table_of_contents(filename: str, title: str = "Table of Contents", max_level: int = 3) -> str:
|
||||
"""根据标题样式向Word文档添加目录。
|
||||
|
||||
参数:
|
||||
filename: Word文档的路径
|
||||
title: 可自行选择的一个标题
|
||||
max_level: 要包含的最大标题级别(1-9)
|
||||
"""
|
||||
|
||||
filename = ensure_docx_extension(filename)
|
||||
|
||||
if not os.path.exists(filename):
|
||||
return f"Document {filename} does not exist"
|
||||
|
||||
# Check if file is writeable
|
||||
is_writeable, error_message = check_file_writeable(filename)
|
||||
if not is_writeable:
|
||||
return f"Cannot modify document: {error_message}. Consider creating a copy first."
|
||||
|
||||
try:
|
||||
# Ensure max_level is within valid range
|
||||
max_level = max(1, min(max_level, 9))
|
||||
|
||||
doc = Document(filename)
|
||||
|
||||
# Collect headings and their positions
|
||||
headings = []
|
||||
for i, paragraph in enumerate(doc.paragraphs):
|
||||
# Check if paragraph style is a heading
|
||||
if paragraph.style and paragraph.style.name.startswith('Heading '):
|
||||
try:
|
||||
# Extract heading level from style name
|
||||
level = int(paragraph.style.name.split(' ')[1])
|
||||
if level <= max_level:
|
||||
headings.append({
|
||||
'level': level,
|
||||
'text': paragraph.text,
|
||||
'position': i
|
||||
})
|
||||
except (ValueError, IndexError):
|
||||
# Skip if heading level can't be determined
|
||||
pass
|
||||
|
||||
if not headings:
|
||||
return f"No headings found in document {filename}. Table of contents not created."
|
||||
|
||||
# Create a new document with the TOC
|
||||
toc_doc = Document()
|
||||
|
||||
# Add title
|
||||
if title:
|
||||
toc_doc.add_heading(title, level=1)
|
||||
|
||||
# Add TOC entries
|
||||
for heading in headings:
|
||||
# Indent based on level (using tab characters)
|
||||
indent = ' ' * (heading['level'] - 1)
|
||||
toc_doc.add_paragraph(f"{indent}{heading['text']}")
|
||||
|
||||
# Add page break
|
||||
toc_doc.add_page_break()
|
||||
|
||||
# Get content from original document
|
||||
for paragraph in doc.paragraphs:
|
||||
p = toc_doc.add_paragraph(paragraph.text)
|
||||
# Copy style if possible
|
||||
try:
|
||||
if paragraph.style:
|
||||
p.style = paragraph.style.name
|
||||
except:
|
||||
pass
|
||||
|
||||
# Copy tables
|
||||
for table in doc.tables:
|
||||
# Create a new table with the same dimensions
|
||||
new_table = toc_doc.add_table(rows=len(table.rows), cols=len(table.columns))
|
||||
# Copy cell contents
|
||||
for i, row in enumerate(table.rows):
|
||||
for j, cell in enumerate(row.cells):
|
||||
for paragraph in cell.paragraphs:
|
||||
new_table.cell(i, j).text = paragraph.text
|
||||
|
||||
# Save the new document with TOC
|
||||
toc_doc.save(filename)
|
||||
|
||||
return f"Table of contents with {len(headings)} entries added to {filename}"
|
||||
except Exception as e:
|
||||
return f"Failed to add table of contents: {str(e)}"
|
||||
|
||||
|
||||
async def delete_paragraph(filename: str, paragraph_index: int) -> str:
|
||||
"""通过行索引从文档中删除一段
|
||||
|
||||
Args:
|
||||
filename: Path to the Word document
|
||||
paragraph_index: 段落位置(第几行)
|
||||
"""
|
||||
filename = ensure_docx_extension(filename)
|
||||
|
||||
if not os.path.exists(filename):
|
||||
return f"Document {filename} does not exist"
|
||||
|
||||
# Check if file is writeable
|
||||
is_writeable, error_message = check_file_writeable(filename)
|
||||
if not is_writeable:
|
||||
return f"Cannot modify document: {error_message}. Consider creating a copy first."
|
||||
|
||||
try:
|
||||
doc = Document(filename)
|
||||
|
||||
# Validate paragraph index
|
||||
if paragraph_index < 0 or paragraph_index >= len(doc.paragraphs):
|
||||
return f"Invalid paragraph index. Document has {len(doc.paragraphs)} paragraphs (0-{len(doc.paragraphs)-1})."
|
||||
|
||||
# Delete the paragraph (by removing its content and setting it empty)
|
||||
# Note: python-docx doesn't support true paragraph deletion, this is a workaround
|
||||
paragraph = doc.paragraphs[paragraph_index]
|
||||
p = paragraph._p
|
||||
p.getparent().remove(p)
|
||||
|
||||
doc.save(filename)
|
||||
return f"Paragraph at index {paragraph_index} deleted successfully."
|
||||
except Exception as e:
|
||||
return f"Failed to delete paragraph: {str(e)}"
|
||||
|
||||
|
||||
async def search_and_replace(filename: str, find_text: str, replace_text: str) -> str:
|
||||
"""替换所有find_text为replace_text
|
||||
|
||||
Args:
|
||||
filename: Path to the Word document
|
||||
find_text: Text to search for
|
||||
replace_text: Text to replace with
|
||||
"""
|
||||
filename = ensure_docx_extension(filename)
|
||||
|
||||
if not os.path.exists(filename):
|
||||
return f"Document {filename} does not exist"
|
||||
|
||||
# Check if file is writeable
|
||||
is_writeable, error_message = check_file_writeable(filename)
|
||||
if not is_writeable:
|
||||
return f"Cannot modify document: {error_message}. Consider creating a copy first."
|
||||
|
||||
try:
|
||||
doc = Document(filename)
|
||||
|
||||
# Perform find and replace
|
||||
count = find_and_replace_text(doc, find_text, replace_text)
|
||||
|
||||
if count > 0:
|
||||
doc.save(filename)
|
||||
return f"Replaced {count} occurrence(s) of '{find_text}' with '{replace_text}'."
|
||||
else:
|
||||
return f"No occurrences of '{find_text}' found."
|
||||
except Exception as e:
|
||||
return f"Failed to search and replace: {str(e)}"
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
|
||||
from .content_tools import add_picture_to_table
|
||||
from .document_tools import add_table_to_document,search_and_replace
|
||||
from .get_pictures import resize_and_reduce_quality
|
||||
|
||||
def caculate_work_days(start_date : str, end_date : str) -> str:
|
||||
"""根据起止日期计算工期
|
||||
|
||||
Args:
|
||||
start_date (str): 格式: yyyy-mm-ddThh:mm:ss
|
||||
end_date (str): 格式: yyyy-mm-ddThh:mm:ss
|
||||
|
||||
Returns:
|
||||
str: 计算出来的总工期,单位为天
|
||||
"""
|
||||
import datetime
|
||||
if start_date == None or end_date == None:
|
||||
return f"开始时间{start_date} ---- 结束时间{end_date} 日期格式错误"
|
||||
|
||||
start_date = datetime.datetime.strptime(start_date, '%Y-%m-%dT%H:%M:%S')
|
||||
end_date = datetime.datetime.strptime(end_date, '%Y-%m-%dT%H:%M:%S')
|
||||
|
||||
return (end_date - start_date).days
|
||||
|
||||
async def add_dynamic_table(output_doc, output_dir, table_num, TABLES, JIANCHA_XIANGQING_DIR, PICTURES, row, col, i, FLAG, xuhao):
|
||||
"""创建动态表
|
||||
|
||||
Args:
|
||||
output_doc (Document): 文档对象
|
||||
output_dir (str): 输出目录
|
||||
table_num (int): 表格序号
|
||||
TABLES (list): 表格数据
|
||||
JIANCHA_XIANGQING_DIR (str): 检查详情表目录
|
||||
PICTURES (dict): 图片数据字典,键为表索引,值为图片路径列表
|
||||
row (int): 行数
|
||||
col (int): 列数
|
||||
i (int): 表格序号
|
||||
FLAG: 其他标志
|
||||
|
||||
Returns:
|
||||
tuple: (i, table_num) 更新后的表格序号和表格数量
|
||||
"""
|
||||
for table_idx, Table in enumerate(TABLES):
|
||||
print(Table)
|
||||
output_doc, message = await add_table_to_document(output_dir, JIANCHA_XIANGQING_DIR, row, col, i, Table, FLAG)
|
||||
print(message)
|
||||
|
||||
# 获取当前表格对应的图片
|
||||
current_table_pictures = PICTURES.get(table_idx, [])
|
||||
print(f"开始处理图片列表: {current_table_pictures}")
|
||||
|
||||
for picturedir in current_table_pictures:
|
||||
try:
|
||||
print(f"添加 {picturedir} {type(picturedir)}到表格{table_idx}")
|
||||
resize_and_reduce_quality(picturedir, picturedir)
|
||||
await add_picture_to_table(output_doc, output_dir, 4, 0, picturedir, i, 4.7232)
|
||||
except Exception as e:
|
||||
print(f"添加图片失败:{e}")
|
||||
|
||||
print(await search_and_replace(output_dir, 'tupian_xuhao', f'{xuhao}'))
|
||||
table_num += 1
|
||||
i += 1
|
||||
xuhao += 1
|
||||
return i, table_num, xuhao
|
||||
|
||||
def get_year_month(date):
|
||||
"""根据格式化date字符串获取年月 'date': '二〇二一年十二月十日 9:00'
|
||||
|
||||
Args: date (str): 日期字符串
|
||||
|
||||
Returns: 年月字符串 '二〇二一年十二月'
|
||||
"""
|
||||
unit_map = {'1' : '一', '2' : '二', '3' : '三', '4' : '四', '5' : '五', '6' : '六', '7' : '七', '8' : '八', '9' : '九', '0' : '〇'}
|
||||
unit_map_month = {1 : '一', 2 : '二', 3 : '三', 4 : '四', 5 : '五', 6 : '六', 7 : '七', 8 : '八', 9 : '九', 10 : '十', 11 : '十一', 12 : '十二'}
|
||||
year = date.split('年')[0]
|
||||
month = date.split('年')[1].split('月')[0]
|
||||
year = ''.join([unit_map[i] for i in year])
|
||||
month = unit_map_month[int(month)]
|
||||
return f"{year}年{month}月"
|
||||
|
||||
def merge_info(frontend_info, default_info):
|
||||
"""
|
||||
合并前端传入的 info 和默认 info
|
||||
规则:如果前端传入的值为空(None 或空字符串),则使用默认值
|
||||
|
||||
Args:
|
||||
frontend_info: 前端传入的字典
|
||||
default_info: 默认的完整字典
|
||||
Returns:
|
||||
合并后的完整字典
|
||||
"""
|
||||
if not isinstance(frontend_info, dict) or frontend_info is None:
|
||||
return default_info.copy()
|
||||
|
||||
merged_info = {}
|
||||
|
||||
for key, default_value in default_info.items():
|
||||
# 获取前端传入的值
|
||||
frontend_value = frontend_info.get(key)
|
||||
|
||||
# 判断前端值是否为空(None 或空字符串)
|
||||
if frontend_value is None or frontend_value == "":
|
||||
merged_info[key] = default_value
|
||||
else:
|
||||
merged_info[key] = frontend_value
|
||||
|
||||
return merged_info
|
|
@ -0,0 +1,205 @@
|
|||
"""
|
||||
缺陷图目录格式:
|
||||
|
||||
缺陷图期望格式 _隔开
|
||||
|
||||
外部内部命名格式都如下:
|
||||
图片名:xuhao_缺陷类型_缺陷位置_缺陷尺寸_可见程度_紧急程度_危重等级_维修建议
|
||||
例:涂层损伤_叶片ps面距叶根3m处_缺陷尺寸弦向100mm,轴向800mm_轻微_紧急_重要_建议打磨维修
|
||||
每个的选项:见我发的图
|
||||
|
||||
防雷:
|
||||
例:轮毂至塔基导通阻值_169mΩ
|
||||
缺陷例:轮毂至塔基未导通 #即标明未导通即可
|
||||
"""
|
||||
DEFAULT_BASE_INFO = { #项目基本信息
|
||||
"jizu_data" : {
|
||||
'turbineName' : "",
|
||||
},
|
||||
"project_data" : {
|
||||
"fengmian_dir" : "",
|
||||
'farmName' : "",
|
||||
'inspectionUnit' : "",
|
||||
'inspectionContact' : "",
|
||||
'inspectionPhone' : "",
|
||||
'farmAddress' : "",
|
||||
'client' : "",
|
||||
'clientContact' : "",
|
||||
'clientPhone' : "",
|
||||
'scale' : "",
|
||||
'turbineModel' : "",
|
||||
'projectName' : "",
|
||||
'startDate' : "",
|
||||
'endDate' : "",
|
||||
},
|
||||
"shigong_data" : {
|
||||
"startTime" : "",
|
||||
"endTime" : "",
|
||||
"weatherCode" : "",
|
||||
"windSpeed" : "",
|
||||
"imageCount" : "",
|
||||
"temperature" : "",
|
||||
},
|
||||
"partManufacturer" : "",
|
||||
"Y1" : {
|
||||
"Code" : "1",
|
||||
"list_dict" : {
|
||||
"描述" : "路径",
|
||||
#.....
|
||||
},
|
||||
},
|
||||
"Y2" : {
|
||||
"Code" : "2",
|
||||
"list_dict" : {
|
||||
"描述" : "路径",
|
||||
#.....
|
||||
},
|
||||
},
|
||||
"Y3" : {
|
||||
"Code" : "3",
|
||||
"list_dict" : {
|
||||
"描述" : "路径",
|
||||
#.....
|
||||
},
|
||||
}
|
||||
}
|
||||
DEFAULT_BAOGAO_INFO = {
|
||||
#目录
|
||||
'shengcheng_dir': "", #报告生成的路径
|
||||
'muban_dir': "", #文档模板存放路径
|
||||
|
||||
"if_waibu" : False,
|
||||
"if_neibu" : True,
|
||||
"if_fanglei" : True,
|
||||
"userName" : "admin",
|
||||
"baogaoCheck" : "未审核",
|
||||
'key_words': '缺,损,裂,脱,污', #关键字,用于汇总图的名字包含缺陷时标红,匹配逻辑为正则匹配单个字则为红 后续可优化
|
||||
|
||||
"shigong_fangan" : "None",
|
||||
'jiancha_renyuan': '张三',
|
||||
"baogao_zongjie" : "总结",
|
||||
"total_pic_num" : "总图片数量",
|
||||
"tatong_dir" : "塔筒图片",
|
||||
"total_pic_dir" : "略缩图",
|
||||
}
|
||||
|
||||
class JIANCHA_ENUM :
|
||||
class WAIBU:
|
||||
PART = "无人机外部高精度飞行"
|
||||
#NEIRONG =
|
||||
|
||||
class SHIGONG_FANGAN_ENUM :
|
||||
class WAIBU:
|
||||
GONGZUO_NEIRONG = "无人机叶片外观巡检"
|
||||
RENYUAN_PEIZHI = "1人;主检飞手1人"
|
||||
SHEBEI_PEIZHI = "1、大疆无人机1台(M350rtk,M300rtk,M30T,M30,精灵4PRO)2、大疆精灵4PRO+索尼A7R2机身+索尼200-600mm镜头/适马150-600mm镜头"
|
||||
SHIGONG_FANGAN = ""
|
||||
class NEIBU:
|
||||
GONGZUO_NEIRONG = "叶片内部检查"
|
||||
RENYUAN_PEIZHI = "2人;轮毂作业检查2人"
|
||||
SHEBEI_PEIZHI = "1、人工检查:照明设备2套,视频记录手机2台,含氧量监测仪1台,电动扳手2套,卷尺1个。2、爬壁机器人检查:无人作业校车+视频图传1套,照明设备2套,含氧量监测仪1台,电动扳手2套,卷尺1个。"
|
||||
SHIGONG_FANGAN = ""
|
||||
class FANGLEI:
|
||||
class YEPIAN:
|
||||
GONGZUO_NEIRONG = "无人机叶片防雷导通测试"
|
||||
RENYUAN_PEIZHI = "2人;主检飞手1人,副检抄表1人"
|
||||
SHEBEI_PEIZHI = "1四轴电阻无人机1套,电子微欧计1台,视频记录手机1台"
|
||||
SHIGONG_FANGAN = ""
|
||||
class DIAOLAN:
|
||||
GONGZUO_NEIRONG = "无人吊篮叶片导通测试(含机舱设备、)"
|
||||
RENYUAN_PEIZHI = "3人,轮毂机舱作业1人,揽风绳作业1人,无人设备操作员及抄表1人"
|
||||
SHEBEI_PEIZHI = "无人吊篮系统1套(爬绳器+接触平台)、电子微欧计1套,视频记录手机1台,对讲机2台"
|
||||
SHIGONG_FANGAN = ""
|
||||
class SHESHI:
|
||||
GONGZUO_NEIRONG = "风机基础、办公楼、变电站防雷接地检测及浪涌保护器测试"
|
||||
RENYUAN_PEIZHI = "1人;抄表人员1人,检测人员1人,监护1人。"
|
||||
SHEBEI_PEIZHI = "接地电阻测试仪1套、SPD测试仪1套、对讲机2个、"
|
||||
SHIGONG_FANGAN = ""
|
||||
FEISHOURENYUAN_PEIZHI = "1人;主检飞手1人"
|
||||
LUNGUZUOYERENYUAN_PEIZHI = "2人;轮毂作业检查2人"
|
||||
|
||||
|
||||
oneproject = {
|
||||
"status": 200,
|
||||
"data": {
|
||||
"projectId": "96e0debf78187300f144d7f3450a2477",
|
||||
"projectName": "三峡能源阿城万兴风电场防雷通道检测项目",
|
||||
"coverUrl": "",
|
||||
"farmName": "三峡能源阿城万兴风电场",
|
||||
"farmAddress": "哈尔滨市阿城区",
|
||||
"client": "辽宁信达检测有限公司",
|
||||
"clientContact": "李经理",
|
||||
"clientPhone": "13504783720",
|
||||
"inspectionUnit": "武汉市迪特影像科技有限公司",
|
||||
"inspectionContact": "吴名州",
|
||||
"inspectionPhone": "18807109269",
|
||||
"scale": "",
|
||||
"turbineModel": "",
|
||||
"constructorIds": "5709ccfece2685090ff700a3469f2539,a76d78f1325deda1790a12bdad4aad4e",
|
||||
"auditorId": "ca37c4337df8673a5c045b6c25acf74a",
|
||||
"qualityOfficerId": "862e027910c2562d2b67d88ec33d77ba",
|
||||
"projectManagerId": "fbaa9e0aecf2ce287138c38a4b654085",
|
||||
"constructionTeamLeaderId": "",
|
||||
"status": 0,
|
||||
"startDate": "",
|
||||
"endDate": "",
|
||||
"constructorName": "",
|
||||
"auditorName": "李四",
|
||||
"qualityOfficerName": "辛奇",
|
||||
"projectManagerName": "张三",
|
||||
"constructionTeamLeaderName": "",
|
||||
"statusLabel": "待施工"
|
||||
},
|
||||
"msg": "",
|
||||
"code": 200,
|
||||
"success": True
|
||||
}
|
||||
onejizu = {
|
||||
"status": 200,
|
||||
"data": [
|
||||
{
|
||||
"turbineId": "183463dbf40d9278549a76b82b175dd9",
|
||||
"projectId": "96e0debf78187300f144d7f3450a2477",
|
||||
"projectName": "三峡能源阿城万兴风电场防雷通道检测项目",
|
||||
"turbineName": "一期012号",
|
||||
"turbineCode": "00000",
|
||||
"turbineDesc": "一期012号,全新设备",
|
||||
"turbineManufacturer": "",
|
||||
"turbineModel": "",
|
||||
"turbineCoverUrl": ""
|
||||
}
|
||||
],
|
||||
"msg": "",
|
||||
"code": 200,
|
||||
"success": True
|
||||
}
|
||||
yepian = {
|
||||
"status": 200,
|
||||
"data": [
|
||||
{
|
||||
"partId": "12bc30fb209f3af3bf530541c5b062bc",
|
||||
"projectId": "96e0debf78187300f144d7f3450a2477",
|
||||
"projectName": "三峡能源阿城万兴风电场防雷通道检测项目",
|
||||
"turbineId": "183463dbf40d9278549a76b82b175dd9",
|
||||
"turbineName": "一期012号",
|
||||
"partName": "叶片2",
|
||||
"partCode": "0001",
|
||||
"partType": "VANE-2",
|
||||
"partTypeLabel": "叶片2"
|
||||
},
|
||||
{
|
||||
"partId": "12bc30fb209f3af3bf530541c5b062bd",
|
||||
"projectId": "96e0debf78187300f144d7f3450a2477",
|
||||
"projectName": "三峡能源阿城万兴风电场防雷通道检测项目",
|
||||
"turbineId": "183463dbf40d9278549a76b82b175dd9",
|
||||
"turbineName": "一期012号",
|
||||
"partName": "叶片1",
|
||||
"partCode": "0000",
|
||||
"partType": "VANE-1",
|
||||
"partTypeLabel": "叶片1"
|
||||
}
|
||||
],
|
||||
"msg": "",
|
||||
"code": 200,
|
||||
"success": True
|
||||
}
|
|
@ -0,0 +1,610 @@
|
|||
"""
|
||||
Document creation and manipulation tools for Word Document Server.
|
||||
"""
|
||||
import os
|
||||
import json, re
|
||||
from typing import Dict, List, Optional, Any
|
||||
from docx import Document
|
||||
|
||||
|
||||
from ..utils.file_utils import check_file_writeable, ensure_docx_extension, create_document_copy
|
||||
from ..utils.document_utils import get_document_properties, extract_document_text, get_document_structure
|
||||
from ..core.styles import ensure_heading_style, ensure_table_style
|
||||
from docx.oxml.shared import qn
|
||||
from docx.oxml import OxmlElement
|
||||
from .content_tools import search_and_replace,add_picture_to_table
|
||||
|
||||
async def create_document(filename: str, title: Optional[str] = None, author: Optional[str] = None) -> str:
|
||||
"""创建一个包含可选元数据的新Word文档。
|
||||
|
||||
参数:
|
||||
filename: 要创建的文档名称(带或不带.docx扩展名)
|
||||
title: 可选标题
|
||||
author: 可选作者
|
||||
"""
|
||||
filename = ensure_docx_extension(filename)
|
||||
|
||||
# Check if file is writeable
|
||||
is_writeable, error_message = check_file_writeable(filename)
|
||||
if not is_writeable:
|
||||
return f"Cannot create document: {error_message}"
|
||||
|
||||
try:
|
||||
doc = Document()
|
||||
|
||||
# Set properties if provided
|
||||
if title:
|
||||
doc.core_properties.title = title
|
||||
if author:
|
||||
doc.core_properties.author = author
|
||||
|
||||
# Ensure necessary styles exist
|
||||
ensure_heading_style(doc)
|
||||
ensure_table_style(doc)
|
||||
# 更改纸张大小为A4
|
||||
from docx.shared import Mm, Inches
|
||||
sections = doc.sections
|
||||
for section in sections:
|
||||
section.page_height = Mm(297)
|
||||
section.page_width = Mm(210)
|
||||
section.left_margin = Inches(0.94)
|
||||
section.right_margin = Inches(0.94)
|
||||
# Save the document
|
||||
doc.save(filename)
|
||||
|
||||
return f"Document {filename} created successfully"
|
||||
except Exception as e:
|
||||
return f"Failed to create document: {str(e)}"
|
||||
|
||||
|
||||
async def get_document_info(filename: str) -> str:
|
||||
"""获得文档信息
|
||||
|
||||
Args:
|
||||
filename: 目标文档
|
||||
"""
|
||||
filename = ensure_docx_extension(filename)
|
||||
|
||||
if not os.path.exists(filename):
|
||||
return f"Document {filename} does not exist"
|
||||
|
||||
try:
|
||||
properties = get_document_properties(filename)
|
||||
return json.dumps(properties, indent=2)
|
||||
except Exception as e:
|
||||
return f"Failed to get document info: {str(e)}"
|
||||
|
||||
|
||||
async def get_document_text(filename: str) -> str:
|
||||
"""获得文档的所有文本
|
||||
|
||||
Args:
|
||||
filename: 目标文档
|
||||
"""
|
||||
filename = ensure_docx_extension(filename)
|
||||
|
||||
return extract_document_text(filename)
|
||||
|
||||
|
||||
async def get_document_outline(filename: str) -> str:
|
||||
"""获得文档的所有结构信息
|
||||
|
||||
Args:
|
||||
filename: 目标文档
|
||||
"""
|
||||
filename = ensure_docx_extension(filename)
|
||||
|
||||
structure = get_document_structure(filename)
|
||||
return json.dumps(structure, indent=2)
|
||||
|
||||
|
||||
async def list_available_documents(directory: str = ".") -> str:
|
||||
"""列出目录下所有Word文档
|
||||
|
||||
Args:
|
||||
directory: 目录
|
||||
"""
|
||||
try:
|
||||
if not os.path.exists(directory):
|
||||
return f"Directory {directory} does not exist"
|
||||
|
||||
docx_files = [f for f in os.listdir(directory) if f.endswith('.docx')]
|
||||
|
||||
if not docx_files:
|
||||
return f"No Word documents found in {directory}"
|
||||
|
||||
result = f"Found {len(docx_files)} Word documents in {directory}:\n"
|
||||
for file in docx_files:
|
||||
file_path = os.path.join(directory, file)
|
||||
size = os.path.getsize(file_path) / 1024 # KB
|
||||
result += f"- {file} ({size:.2f} KB)\n"
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
return f"Failed to list documents: {str(e)}"
|
||||
|
||||
|
||||
async def copy_document(source_filename: str, destination_filename: Optional[str] = None) -> str:
|
||||
"""创建文档的副本
|
||||
|
||||
Args:
|
||||
source_filename: 源文档路径
|
||||
destination_filename: 目标文档路径,为空则为当前目录
|
||||
"""
|
||||
source_filename = ensure_docx_extension(source_filename)
|
||||
|
||||
if destination_filename:
|
||||
destination_filename = ensure_docx_extension(destination_filename)
|
||||
|
||||
success, message, new_path = create_document_copy(source_filename, destination_filename)
|
||||
if success:
|
||||
return message
|
||||
else:
|
||||
return f"Failed to copy document: {message}"
|
||||
|
||||
def add_documents(target_filename: str, source_filename: str) -> str:
|
||||
"""将源文档(文本)添加到目标文档尾部
|
||||
Args:
|
||||
target_doc: 目标文档
|
||||
source_filename: 源文档路径
|
||||
"""
|
||||
target_doc = Document(target_filename)
|
||||
source_filename = ensure_docx_extension(source_filename)
|
||||
source_doc = Document(source_filename)
|
||||
for source_paragraph in source_doc.paragraphs:
|
||||
new_paragraph = target_doc.add_paragraph(source_paragraph.text)
|
||||
new_paragraph.style = target_doc.styles['Normal'] # Default style
|
||||
|
||||
#获取合并等样式2025427
|
||||
new_paragraph.alignment = source_paragraph.alignment
|
||||
print(f"Source paragraph alignment: {source_paragraph.alignment}")
|
||||
|
||||
# Try to match the style if possible
|
||||
try:
|
||||
if source_paragraph.style and source_paragraph.style.name in target_doc.styles:
|
||||
new_paragraph.style = target_doc.styles[source_paragraph.style.name]
|
||||
except Exception as e:
|
||||
print(f"Failed to apply style: {e}")
|
||||
|
||||
# Copy run formatting
|
||||
for i, run in enumerate(source_paragraph.runs):
|
||||
if i < len(new_paragraph.runs):
|
||||
new_run = new_paragraph.runs[i]
|
||||
# Copy basic formatting
|
||||
new_run.bold = run.bold
|
||||
new_run.italic = run.italic
|
||||
new_run.underline = run.underline
|
||||
#添加同时合并字体2025427
|
||||
new_run.font.name = run.font.name
|
||||
rPr = new_run.element.get_or_add_rPr()
|
||||
rFonts = rPr.get_or_add_rFonts()
|
||||
# 检查 run.font.name 是否为 None
|
||||
if run.font.name is None:
|
||||
# 设置默认的中文字体名称
|
||||
run.font.name = '宋体 (中文正文)' # 或者使用其他你喜欢的中文字体
|
||||
rFonts.set(qn('w:eastAsia'), run.font.name)
|
||||
new_run.font.color.rgb = run.font.color.rgb
|
||||
|
||||
# Font size if specified
|
||||
if run.font.size:
|
||||
new_run.font.size = run.font.size
|
||||
target_doc.save(target_filename)
|
||||
return f"{target_filename}添加{source_filename}成功"
|
||||
|
||||
|
||||
|
||||
def write_table(target_filename: str, rows: int, cols: int, table_num: int, data: Optional[List[List[str]]] = None, ifadjustheight: Optional[bool] = True, height: Optional[float] = 1, key_words: re.Pattern[str] = None, ALIGMENT: Optional[str] = 'CENTER') -> Document:
|
||||
"""填写word文档里的表格,返回填写后的文档
|
||||
|
||||
Args:
|
||||
target_filename: 目标文档路径
|
||||
rows: 表格行数
|
||||
cols: 表格列数
|
||||
table_num: 表格序号
|
||||
data: 表格数据,二维列表,每个单元格为字符串
|
||||
ifadjustheight: bool,为真则表格行高自动调整
|
||||
"""
|
||||
target_filename = ensure_docx_extension(target_filename)
|
||||
# Check if target file is writeable
|
||||
is_writeable, error_message = check_file_writeable(target_filename)
|
||||
if not is_writeable:
|
||||
return f"Cannot create target document: {error_message}"
|
||||
|
||||
try:
|
||||
target_filename = ensure_docx_extension(target_filename)
|
||||
target_doc = Document(target_filename)
|
||||
except Exception as e:
|
||||
print(f"获取{target_filename}失败:{str(e)}")
|
||||
|
||||
# Try to set the table style
|
||||
try:
|
||||
target_doc.tables[table_num].style = 'Table Grid'
|
||||
except KeyError as k:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"{target_doc}最后一个表格更改样式失败: {str(e)}")
|
||||
|
||||
print("开始写入表格")
|
||||
from docx.enum.table import WD_TABLE_ALIGNMENT
|
||||
from docx.enum.table import WD_ALIGN_VERTICAL
|
||||
from docx.shared import Pt, Inches, Cm, RGBColor
|
||||
try:
|
||||
if data:
|
||||
for i, row_data in enumerate(data):
|
||||
if i >= rows + 1:
|
||||
break
|
||||
for j, cell_text in enumerate(row_data):
|
||||
if j >= cols + 1:
|
||||
break
|
||||
if str(cell_text) == "": continue
|
||||
print(f"在[{i},{j}]处写入{str(cell_text)}")
|
||||
target_doc.tables[table_num].cell(i,j).text = str(cell_text)
|
||||
print(key_words, cell_text)
|
||||
if key_words and key_words.search(str(cell_text)):
|
||||
print(f'{cell_text}包含关键之,已置红')
|
||||
target_doc.tables[table_num].cell(i,j).paragraphs[0].runs[0].font.color.rgb = RGBColor(255, 0, 0)
|
||||
target_doc.tables[table_num].cell(i,j).paragraphs[0].runs[0].font.name = "Times New Roman" #设置英文字体
|
||||
target_doc.tables[table_num].cell(i,j).paragraphs[0].runs[0].font.size = Pt(10.5) # 字体大小
|
||||
target_doc.tables[table_num].cell(i,j).paragraphs[0].runs[0]._element.rPr.rFonts.set(qn('w:eastAsia'), '仿宋') #设置中文字体
|
||||
if ALIGMENT == 'CENTER':
|
||||
target_doc.tables[table_num].cell(i,j).paragraphs[0].paragraph_format.alignment = WD_TABLE_ALIGNMENT.CENTER
|
||||
elif ALIGMENT == 'LEFT':
|
||||
target_doc.tables[table_num].cell(i,j).paragraphs[0].paragraph_format.alignment = WD_TABLE_ALIGNMENT.LEFT
|
||||
target_doc.tables[table_num].cell(i,j).vertical_alignment = WD_ALIGN_VERTICAL.CENTER
|
||||
if ifadjustheight:
|
||||
target_doc.tables[table_num].rows[i].height = Cm(height)
|
||||
except Exception as e:
|
||||
print(f"写入{target_filename}tables.cell({i},{j})失败:{str(e)}")
|
||||
print("表格写入完成")
|
||||
return target_doc
|
||||
|
||||
def set_document_para(target_doc: Document) -> Document:
|
||||
"""设置文档的段落格式
|
||||
"""
|
||||
paragraphs_to_remove = []
|
||||
for i, paragraph in enumerate(target_doc.paragraphs):
|
||||
if i <= 11:
|
||||
continue
|
||||
if not paragraph.text.strip():
|
||||
paragraphs_to_remove.append(paragraph)
|
||||
|
||||
for paragraph in paragraphs_to_remove:
|
||||
p = paragraph._element
|
||||
p.getparent().remove(p)
|
||||
|
||||
return target_doc
|
||||
|
||||
|
||||
async def add_table_to_document(target_filename: str, source_filename: str, rows: int, cols: int, table_num: int, data: Optional[List[List[str]]] = None, ifadjustheight: Optional[bool] = True, height: Optional[float] = 1, key_words: re.Pattern[str] = None, ALIGMENT: Optional[str] = 'CENTER') -> str:
|
||||
"""复制源文件中的文字与表格(先文字后表格格式)到目标文档
|
||||
Args:
|
||||
target_filename: 目标文档路径
|
||||
source_doc: 源文档路径
|
||||
rows: 表格行数
|
||||
cols: 表格列数
|
||||
table_num: 表格序号
|
||||
data: 表格数据,二维列表,每个单元格为字符串
|
||||
ifadjustheight: bool,为真则表格行高自动调整
|
||||
key_words: list, 关键字
|
||||
"""
|
||||
target_filename = ensure_docx_extension(target_filename)
|
||||
source_filename = ensure_docx_extension(source_filename)
|
||||
source_doc = Document(source_filename)
|
||||
|
||||
target_doc = Document(target_filename)
|
||||
try:
|
||||
# Copy all paragraphs
|
||||
for paragraph in source_doc.paragraphs:
|
||||
# Create a new paragraph with the same text and style
|
||||
new_paragraph = target_doc.add_paragraph(paragraph.text)
|
||||
new_paragraph.style = target_doc.styles['Normal'] # Default style
|
||||
#获取合并等样式2025427
|
||||
new_paragraph.alignment = paragraph.alignment
|
||||
|
||||
# 复制段落分页属性
|
||||
new_paragraph.paragraph_format.page_break_before = paragraph.paragraph_format.page_break_before
|
||||
# Try to match the style if possible
|
||||
try:
|
||||
if paragraph.style and paragraph.style.name in target_doc.styles:
|
||||
new_paragraph.style = target_doc.styles[paragraph.style.name]
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
# Copy run formatting
|
||||
for i, run in enumerate(paragraph.runs):
|
||||
if i < len(new_paragraph.runs):
|
||||
new_run = new_paragraph.runs[i]
|
||||
# Copy basic formatting
|
||||
new_run.bold = run.bold
|
||||
new_run.italic = run.italic
|
||||
new_run.underline = run.underline
|
||||
#添加同时合并字体2025427
|
||||
new_run.font.name = run.font.name
|
||||
rPr = new_run.element.get_or_add_rPr()
|
||||
rFonts = rPr.get_or_add_rFonts()
|
||||
# 检查 run.font.name 是否为 None
|
||||
if run.font.name is None:
|
||||
# 设置默认的中文字体名称
|
||||
run.font.name = '宋体(中文正文)' # 或者使用其他你喜欢的中文字体
|
||||
rFonts.set(qn('w:eastAsia'), run.font.name)
|
||||
new_run.font.color.rgb = run.font.color.rgb
|
||||
|
||||
|
||||
# Font size if specified
|
||||
if run.font.size:
|
||||
new_run.font.size = run.font.size
|
||||
|
||||
# 复制分页符(处理w:br标签)
|
||||
for element in run._element:
|
||||
if element.tag.endswith('br'):
|
||||
br_type = element.get(qn('type'), '')
|
||||
if br_type == 'page':
|
||||
new_br = OxmlElement('w:br')
|
||||
new_br.set(qn('type'), 'page')
|
||||
new_run._element.append(new_br)
|
||||
|
||||
except Exception as e:
|
||||
print(f"添加表格前文章失败:{str(e)}")
|
||||
|
||||
try:# Copy all tables
|
||||
from ..core.tables import copy_table
|
||||
copy_table(source_doc.tables[0], target_doc, ifadjustheight, height)
|
||||
except Exception as e:
|
||||
print(f"添加表格失败:{str(e)}")
|
||||
print(f"{target_doc}写入表格{source_doc.tables[0]}成功")
|
||||
target_doc = set_document_para(target_doc)
|
||||
target_doc.save(target_filename)
|
||||
target_doc = Document(target_filename)
|
||||
try:
|
||||
target_doc = write_table(target_filename, rows, cols, table_num, data, ifadjustheight, height, key_words, ALIGMENT)
|
||||
except Exception as e:
|
||||
print(f"{target_filename}写入{data}失败:{str(e)}")
|
||||
target_doc.save(target_filename)
|
||||
return target_doc,f"{target_filename}添加表格{source_doc}成功"
|
||||
|
||||
async def add_table_and_replace(target_filename: str, source_filename: str, ifadjustheight: Optional[bool] = True, list_to_replace: dict = {}, height: Optional[float] = 1):
|
||||
"""复制源文件中的文字与表格(先文字后表格格式)到目标文档
|
||||
Args:
|
||||
target_filename: 目标文档路径
|
||||
source_doc: 源文档路径
|
||||
ifadjustheight: bool,为真则表格行高自动调整
|
||||
list_to_replace: dict, 待替换内容和替换内容
|
||||
"""
|
||||
target_filename = ensure_docx_extension(target_filename)
|
||||
source_filename = ensure_docx_extension(source_filename)
|
||||
source_doc = Document(source_filename)
|
||||
|
||||
target_doc = Document(target_filename)
|
||||
try:
|
||||
# Copy all paragraphs
|
||||
for paragraph in source_doc.paragraphs:
|
||||
# Create a new paragraph with the same text and style
|
||||
new_paragraph = target_doc.add_paragraph(paragraph.text)
|
||||
new_paragraph.style = target_doc.styles['Normal'] # Default style
|
||||
#获取合并等样式2025427
|
||||
new_paragraph.alignment = paragraph.alignment
|
||||
|
||||
# 复制段落分页属性
|
||||
new_paragraph.paragraph_format.page_break_before = paragraph.paragraph_format.page_break_before
|
||||
# Try to match the style if possible
|
||||
try:
|
||||
if paragraph.style and paragraph.style.name in target_doc.styles:
|
||||
new_paragraph.style = target_doc.styles[paragraph.style.name]
|
||||
except:
|
||||
pass
|
||||
# Copy run formatting
|
||||
for i, run in enumerate(paragraph.runs):
|
||||
if i < len(new_paragraph.runs):
|
||||
new_run = new_paragraph.runs[i]
|
||||
# Copy basic formatting
|
||||
new_run.bold = run.bold
|
||||
new_run.italic = run.italic
|
||||
new_run.underline = run.underline
|
||||
#添加同时合并字体2025427
|
||||
new_run.font.name = run.font.name
|
||||
rPr = new_run.element.get_or_add_rPr()
|
||||
rFonts = rPr.get_or_add_rFonts()
|
||||
# 检查 run.font.name 是否为 None
|
||||
if run.font.name is None:
|
||||
# 设置默认的中文字体名称
|
||||
run.font.name = '宋体(中文正文)' # 或者使用其他你喜欢的中文字体
|
||||
rFonts.set(qn('w:eastAsia'), run.font.name)
|
||||
new_run.font.color.rgb = run.font.color.rgb
|
||||
|
||||
|
||||
# Font size if specified
|
||||
if run.font.size:
|
||||
new_run.font.size = run.font.size
|
||||
|
||||
# 复制分页符(处理w:br标签)
|
||||
for element in run._element:
|
||||
if element.tag.endswith('br'):
|
||||
br_type = element.get(qn('type'), '')
|
||||
if br_type == 'page':
|
||||
new_br = OxmlElement('w:br')
|
||||
new_br.set(qn('type'), 'page')
|
||||
new_run._element.append(new_br)
|
||||
except Exception as e:
|
||||
print(f"添加表格前文章失败:{str(e)}")
|
||||
try:# Copy all tables
|
||||
from ..core.tables import copy_table
|
||||
copy_table(source_doc.tables[0], target_doc, ifadjustheight, height)
|
||||
target_doc.save(target_filename)
|
||||
except Exception as e:
|
||||
print(f"添加表格失败:{str(e)}")
|
||||
for find_text, replace_text in list_to_replace.items():
|
||||
print(await search_and_replace(target_filename, find_text, replace_text))
|
||||
|
||||
async def merge_documents(target_filename: str, source_filenames: List[str], add_page_breaks: bool = True) -> str:
|
||||
"""合并文档(文本) 表格会添加到最后
|
||||
|
||||
Args:
|
||||
target_filename: 合并后文档路径
|
||||
source_filenames: 源文档路径(列表)
|
||||
add_page_breaks: bool,为真则每个源文档中间加入分页符
|
||||
"""
|
||||
from ..core.tables import copy_table
|
||||
|
||||
target_filename = ensure_docx_extension(target_filename)
|
||||
|
||||
# Check if target file is writeable
|
||||
is_writeable, error_message = check_file_writeable(target_filename)
|
||||
if not is_writeable:
|
||||
return f"Cannot create target document: {error_message}"
|
||||
|
||||
# Validate all source documents exist
|
||||
missing_files = []
|
||||
for filename in source_filenames:
|
||||
doc_filename = ensure_docx_extension(filename)
|
||||
if not os.path.exists(doc_filename):
|
||||
missing_files.append(doc_filename)
|
||||
|
||||
if missing_files:
|
||||
return f"Cannot merge documents. The following source files do not exist: {', '.join(missing_files)}"
|
||||
|
||||
try:
|
||||
# Create a new document for the merged result
|
||||
target_doc = Document()
|
||||
|
||||
# Process each source document
|
||||
for i, filename in enumerate(source_filenames):
|
||||
doc_filename = ensure_docx_extension(filename)
|
||||
source_doc = Document(doc_filename)
|
||||
|
||||
# Add page break between documents (except before the first one)
|
||||
if add_page_breaks and i > 0:
|
||||
target_doc.add_page_break()
|
||||
|
||||
# Copy all paragraphs
|
||||
for paragraph in source_doc.paragraphs:
|
||||
# Create a new paragraph with the same text and style
|
||||
new_paragraph = target_doc.add_paragraph(paragraph.text)
|
||||
new_paragraph.style = target_doc.styles['Normal'] # Default style
|
||||
#获取合并等样式2025427
|
||||
new_paragraph.alignment = paragraph.alignment
|
||||
|
||||
# Try to match the style if possible
|
||||
try:
|
||||
if paragraph.style and paragraph.style.name in target_doc.styles:
|
||||
new_paragraph.style = target_doc.styles[paragraph.style.name]
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
# Copy run formatting
|
||||
for i, run in enumerate(paragraph.runs):
|
||||
if i < len(new_paragraph.runs):
|
||||
new_run = new_paragraph.runs[i]
|
||||
# Copy basic formatting
|
||||
new_run.bold = run.bold
|
||||
new_run.italic = run.italic
|
||||
new_run.underline = run.underline
|
||||
#添加同时合并字体2025427
|
||||
new_run.font.name = run.font.name
|
||||
rPr = new_run.element.get_or_add_rPr()
|
||||
rFonts = rPr.get_or_add_rFonts()
|
||||
# 检查 run.font.name 是否为 None
|
||||
if run.font.name is None:
|
||||
# 设置默认的中文字体名称
|
||||
run.font.name = '宋体(中文正文)' # 或者使用其他你喜欢的中文字体
|
||||
rFonts.set(qn('w:eastAsia'), run.font.name)
|
||||
new_run.font.color.rgb = run.font.color.rgb
|
||||
|
||||
|
||||
# Font size if specified
|
||||
if run.font.size:
|
||||
new_run.font.size = run.font.size
|
||||
|
||||
# Copy all tables
|
||||
for table in source_doc.tables:
|
||||
copy_table(table, target_doc)
|
||||
|
||||
# Save the merged document
|
||||
target_doc.save(target_filename)
|
||||
return f"Successfully merged {len(source_filenames)} documents into {target_filename}"
|
||||
except Exception as e:
|
||||
return f"Failed to merge documents: {str(e)}"
|
||||
|
||||
|
||||
async def right_align_last_three_para(target_filename: str) -> str:
|
||||
"""右对齐最后三个段落
|
||||
|
||||
Args:
|
||||
target_filename: 目标文档路径
|
||||
"""
|
||||
target_filename = ensure_docx_extension(target_filename)
|
||||
|
||||
# Check if target file is writeable
|
||||
is_writeable, error_message = check_file_writeable(target_filename)
|
||||
if not is_writeable:
|
||||
return f"Cannot right align paragraphs: {error_message}"
|
||||
|
||||
try:
|
||||
# Open the target document
|
||||
target_doc = Document(target_filename)
|
||||
|
||||
# Get the last three paragraphs
|
||||
paragraphs = target_doc.paragraphs[-3:]
|
||||
|
||||
# Set the alignment of each paragraph to right
|
||||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||
for paragraph in paragraphs:
|
||||
paragraph.alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
||||
|
||||
# Save the modified document
|
||||
target_doc.save(target_filename)
|
||||
return f"Successfully right aligned the last three paragraphs in {target_filename}"
|
||||
except Exception as e:
|
||||
return f"Failed to right align paragraphs: {str(e)}"
|
||||
|
||||
async def process_images_table(data_dict, output_dir, start_i, JIANCHA_NEIRONG_PICTURES_TABLE, key_words = None):
|
||||
"""添加对应表格且填写图片名与插入图片
|
||||
|
||||
Args:
|
||||
data_dict (dict): dict内容,图片:图片路径
|
||||
output_dir (str): 输出路径
|
||||
start_i (int): 总表格数量
|
||||
JIANCHA_NEIRONG_PICTURES_TABLE (str): 二维表模板路径
|
||||
|
||||
Returns:
|
||||
int: 最后使用的表格序号
|
||||
"""
|
||||
items = list(data_dict.items())
|
||||
picture_num = len(items)
|
||||
line_index = 0
|
||||
picture_index = 0
|
||||
i = start_i
|
||||
|
||||
for content_row in range(((picture_num + 2) // 3) * 2):
|
||||
if content_row % 2 == 1:
|
||||
# 文字行(从 items 取图片名)
|
||||
JIANCHA_NEIRONG_TEXT = [["" for _ in range(3)] for _ in range(1)] # 1行3列
|
||||
for k in range(1): # 只有1行
|
||||
for l in range(3):
|
||||
if line_index >= picture_num:
|
||||
break
|
||||
JIANCHA_NEIRONG_TEXT[k][l] = items[line_index][0] # 图片名
|
||||
print(f'当前为文字表格,在({k},{l})位置插入文字: {items[line_index][0]}')
|
||||
line_index += 1
|
||||
print(f"当前待插入表格: {JIANCHA_NEIRONG_TEXT}")
|
||||
print(f"当前表格序号为 {i}")
|
||||
output_doc, message = await add_table_to_document(
|
||||
output_dir, JIANCHA_NEIRONG_PICTURES_TABLE, 1, 3, i, JIANCHA_NEIRONG_TEXT, False, None, key_words
|
||||
)
|
||||
i += 1
|
||||
else:
|
||||
# 图片行(从 items 取图片路径)
|
||||
print(f"当前表格序号为 {i}")
|
||||
output_doc, message = await add_table_to_document(
|
||||
output_dir, JIANCHA_NEIRONG_PICTURES_TABLE, 1, 3, i, None, False
|
||||
)
|
||||
for k in range(3):
|
||||
if picture_index < picture_num:
|
||||
pic_path = items[picture_index][1] # 图片路径
|
||||
print(f"当前为图片表格,在(0,{k})位置插入图片: {pic_path}")
|
||||
print(await add_picture_to_table(output_doc, output_dir, 0, k, pic_path, i, 1.8898))
|
||||
picture_index += 1
|
||||
i += 1
|
||||
print(message)
|
||||
return i # 返回最后使用的表格序号
|
|
@ -0,0 +1,182 @@
|
|||
from .content_tools import add_picture_to_table, search_and_replace
|
||||
from .get_pictures import resize_and_reduce_quality
|
||||
from .document_tools import add_table_to_document
|
||||
|
||||
def fill_tables(Y_table_list, row, col, Y_Table_num, Y):
|
||||
"""根据前端返回json块填写表格list,并实时跟进已填写表格数量
|
||||
目前只支持固定的缺陷图的填写
|
||||
|
||||
Args:
|
||||
Y_table_list (list): 前端返回的json块
|
||||
row (int): 表格行数
|
||||
col (int): 表格列数
|
||||
Y_Table_num: json块中有几个表格
|
||||
Xu_Hao: 是第几个json块
|
||||
Y: 其他参数
|
||||
|
||||
Return:
|
||||
Y1_TABLES: 三维,表和对应元素
|
||||
table_index_to_images: 字典,表索引到图片路径列表的映射
|
||||
Xu_Hao:到达第几个表了
|
||||
"""
|
||||
table_index_to_images = {}
|
||||
Y_TABLES = [[["" for _ in range(row)] for _ in range(col)] for _ in range(Y_Table_num)]
|
||||
|
||||
# 处理前端返回数据
|
||||
for l, table_dict in enumerate(Y_table_list):
|
||||
if table_dict:
|
||||
Y_TABLES[l][1][0] = Y
|
||||
Y_TABLES[l][1][1] = table_dict["QueXianLeiXing"]
|
||||
Y_TABLES[l][1][2] = table_dict["QueXianWeiZhi"]
|
||||
Y_TABLES[l][1][3] = table_dict["QueXianChiCun"]
|
||||
Y_TABLES[l][3][0] = table_dict["WeiZongDengJi"]
|
||||
Y_TABLES[l][3][1] = table_dict["visibility"]
|
||||
Y_TABLES[l][3][2] = table_dict["urgency"]
|
||||
Y_TABLES[l][3][3] = table_dict["repair_suggestion"]
|
||||
|
||||
# 获取图片路径
|
||||
image_path = table_dict['Tupian_Dir']
|
||||
if image_path:
|
||||
# 确保路径是字符串形式
|
||||
if isinstance(image_path, list):
|
||||
table_index_to_images[l] = image_path.copy()
|
||||
else:
|
||||
table_index_to_images[l] = [str(image_path)]
|
||||
|
||||
return Y_TABLES, table_index_to_images
|
||||
|
||||
async def process_images_table(data_dict, output_dir, start_i, JIANCHA_NEIRONG_PICTURES_TABLE, key_words = None):
|
||||
"""添加对应表格且填写图片名与插入图片
|
||||
|
||||
Args:
|
||||
data_dict (dict): dict内容,图片:图片路径
|
||||
output_dir (str): 输出路径
|
||||
start_i (int): 总表格数量
|
||||
JIANCHA_NEIRONG_PICTURES_TABLE (str): 二维表模板路径
|
||||
|
||||
Returns:
|
||||
int: 最后使用的表格序号
|
||||
"""
|
||||
items = list(data_dict.items())
|
||||
picture_num = len(items)
|
||||
line_index = 0
|
||||
picture_index = 0
|
||||
i = start_i
|
||||
|
||||
for content_row in range(((picture_num + 2) // 3) * 2):
|
||||
if content_row % 2 == 1:
|
||||
# 文字行(从 items 取图片名)
|
||||
JIANCHA_NEIRONG_TEXT = [["" for _ in range(3)] for _ in range(1)] # 1行3列
|
||||
for k in range(1): # 只有1行
|
||||
for l in range(3):
|
||||
if line_index >= picture_num:
|
||||
break
|
||||
JIANCHA_NEIRONG_TEXT[k][l] = items[line_index][0] # 图片名
|
||||
print(f'当前为文字表格,在({k},{l})位置插入文字: {items[line_index][0]}')
|
||||
line_index += 1
|
||||
print(f"当前待插入表格: {JIANCHA_NEIRONG_TEXT}")
|
||||
print(f"当前表格序号为 {i}")
|
||||
output_doc, message = await add_table_to_document(
|
||||
output_dir, JIANCHA_NEIRONG_PICTURES_TABLE, 1, 3, i, JIANCHA_NEIRONG_TEXT, False, None, key_words
|
||||
)
|
||||
i += 1
|
||||
else:
|
||||
# 图片行(从 items 取图片路径)
|
||||
print(f"当前表格序号为 {i}")
|
||||
output_doc, message = await add_table_to_document(
|
||||
output_dir, JIANCHA_NEIRONG_PICTURES_TABLE, 1, 3, i, None, False
|
||||
)
|
||||
for k in range(3):
|
||||
if picture_index < picture_num:
|
||||
pic_path = items[picture_index][1] # 图片路径
|
||||
print(f"当前为图片表格,在(0,{k})位置插入图片: {pic_path}")
|
||||
print(await add_picture_to_table(output_doc, output_dir, 0, k, pic_path, i, 1.8898))
|
||||
picture_index += 1
|
||||
i += 1
|
||||
print(message)
|
||||
return i # 返回最后使用的表格序号
|
||||
|
||||
async def add_dynamic_table(output_doc, output_dir, table_num, TABLES, JIANCHA_XIANGQING_DIR, PICTURES, row, col, i, FLAG, xuhao):
|
||||
"""创建动态表
|
||||
|
||||
Args:
|
||||
output_doc (Document): 文档对象
|
||||
output_dir (str): 输出目录
|
||||
table_num (int): 表格序号
|
||||
TABLES (list): 表格数据
|
||||
JIANCHA_XIANGQING_DIR (str): 检查详情表目录
|
||||
PICTURES (dict): 图片数据字典,键为表索引,值为图片路径列表
|
||||
row (int): 行数
|
||||
col (int): 列数
|
||||
i (int): 表格序号
|
||||
FLAG: 其他标志
|
||||
|
||||
Returns:
|
||||
tuple: (i, table_num) 更新后的表格序号和表格数量
|
||||
"""
|
||||
for table_idx, Table in enumerate(TABLES):
|
||||
print(Table)
|
||||
output_doc, message = await add_table_to_document(output_dir, JIANCHA_XIANGQING_DIR, row, col, i, Table, FLAG)
|
||||
print(message)
|
||||
|
||||
# 获取当前表格对应的图片
|
||||
current_table_pictures = PICTURES.get(table_idx, [])
|
||||
print(f"开始处理图片列表: {current_table_pictures}")
|
||||
|
||||
for picturedir in current_table_pictures:
|
||||
try:
|
||||
print(f"添加 {picturedir} {type(picturedir)}到表格{table_idx}")
|
||||
resize_and_reduce_quality(picturedir, picturedir)
|
||||
await add_picture_to_table(output_doc, output_dir, 4, 0, picturedir, i, 4.7232)
|
||||
except Exception as e:
|
||||
print(f"添加图片失败:{e}")
|
||||
|
||||
print(await search_and_replace(output_dir, 'tupian_xuhao', f'{xuhao}'))
|
||||
table_num += 1
|
||||
i += 1
|
||||
xuhao += 1
|
||||
return i, table_num, xuhao
|
||||
|
||||
def get_year_month(date):
|
||||
"""根据格式化date字符串获取年月 'date': '二〇二一年十二月十日 9:00'
|
||||
|
||||
Args: date (str): 日期字符串
|
||||
|
||||
Returns: 年月字符串 '二〇二一年十二月'
|
||||
"""
|
||||
unit_map = {'1' : '一', '2' : '二', '3' : '三', '4' : '四', '5' : '五', '6' : '六', '7' : '七', '8' : '八', '9' : '九', '0' : '〇'}
|
||||
unit_map_month = {1 : '一', 2 : '二', 3 : '三', 4 : '四', 5 : '五', 6 : '六', 7 : '七', 8 : '八', 9 : '九', 10 : '十', 11 : '十一', 12 : '十二'}
|
||||
year = date.split('年')[0]
|
||||
month = date.split('年')[1].split('月')[0]
|
||||
year = ''.join([unit_map[i] for i in year])
|
||||
month = unit_map_month[int(month)]
|
||||
return f"{year}年{month}月"
|
||||
|
||||
def merge_info(frontend_info, default_info):
|
||||
"""
|
||||
合并前端传入的 info 和默认 info
|
||||
规则:如果前端传入的值为空(None 或空字符串),则使用默认值
|
||||
|
||||
Args:
|
||||
frontend_info: 前端传入的字典
|
||||
default_info: 默认的完整字典
|
||||
Returns:
|
||||
合并后的完整字典
|
||||
"""
|
||||
if not isinstance(frontend_info, dict) or frontend_info is None:
|
||||
return default_info.copy()
|
||||
|
||||
merged_info = {}
|
||||
|
||||
for key, default_value in default_info.items():
|
||||
# 获取前端传入的值
|
||||
frontend_value = frontend_info.get(key)
|
||||
|
||||
# 判断前端值是否为空(None 或空字符串)
|
||||
if frontend_value is None or frontend_value == "":
|
||||
merged_info[key] = default_value
|
||||
else:
|
||||
merged_info[key] = frontend_value
|
||||
|
||||
return merged_info
|
||||
|
|
@ -0,0 +1,283 @@
|
|||
import os
|
||||
import math
|
||||
from PIL import Image
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
def get_dict(Y : dict) -> dict:
|
||||
"""给指定格式的Y字典,返回字典的除第一行外的内容
|
||||
|
||||
Args:
|
||||
Y (str): Y字典,形如:
|
||||
"Y" : {
|
||||
"Code" : "1",
|
||||
"list_dict" : {
|
||||
"描述" : "路径",
|
||||
#.....
|
||||
},
|
||||
},
|
||||
Returns:
|
||||
dict: list_dict
|
||||
"""
|
||||
return Y['list_dict']
|
||||
|
||||
|
||||
def resize_and_reduce_quality(image_path, output_path, target_width = None):
|
||||
try:
|
||||
# 检查图片文件大小
|
||||
if os.path.getsize(image_path) < 10 * 1024 * 1024: # 10MB
|
||||
print("图片文件大小小于10MB,不进行调整")
|
||||
return image_path
|
||||
|
||||
|
||||
# 打开图片
|
||||
with Image.open(image_path) as img:
|
||||
# 计算新的高度以保持宽高比
|
||||
if target_width is None:
|
||||
target_width = img.width
|
||||
aspect_ratio = img.height / img.width
|
||||
new_height = int(target_width * aspect_ratio)
|
||||
|
||||
# 调整图片大小
|
||||
img_resized = img.resize((target_width, new_height), Image.LANCZOS)
|
||||
|
||||
# 降低图片质量
|
||||
quality = 70 # 质量从1(最差)到95(最好),可以根据需要调整
|
||||
img_resized.save(output_path, quality=quality)
|
||||
|
||||
return output_path
|
||||
except Exception as e:
|
||||
return f"调整图片大小和质量时出现问题: {str(e)}"
|
||||
|
||||
def get_picture_nums(source_path: str) -> int:
|
||||
picture_count = 0
|
||||
for root, dirs, files in os.walk(source_path):
|
||||
for file in files:
|
||||
if file.lower().endswith(('.jpg', '.jpeg', '.png')) and not file.startswith('merged_thumbnail'):
|
||||
picture_count += 1
|
||||
return picture_count
|
||||
|
||||
def collect_defect_data(
|
||||
Y: str,
|
||||
picture_dir: str,
|
||||
search_file_list: list = [],
|
||||
) -> tuple[int, dict]:
|
||||
"""
|
||||
收集指定年份的缺陷图片数据,并根据布尔值决定是否扫描特定类型的缺陷图。
|
||||
|
||||
Args:
|
||||
Y: 叶片号,如 "Y1"、"Y2"、"Y3"
|
||||
picture_dir: 图片根目录
|
||||
search_file_list (list, optional): 要搜索的文件列表.规定为3个元素
|
||||
|
||||
Returns:
|
||||
(缺陷图片总数, 缺陷图片文件名字典)
|
||||
"""
|
||||
|
||||
total_num = 0
|
||||
result_dict = {}
|
||||
|
||||
try:
|
||||
for defect_type in search_file_list:
|
||||
dir_path = os.path.join(picture_dir, Y, defect_type)
|
||||
num, img_dict = get_picture_nums_and_image_with_name(dir_path)
|
||||
total_num += num
|
||||
result_dict.update(img_dict)
|
||||
except Exception as e:
|
||||
print(f"获取图片数据时出现问题: {str(e)},搜寻的目录:{dir_path}")
|
||||
|
||||
return total_num, result_dict
|
||||
|
||||
def get_picture_nums_and_image_with_name(source_path: str) -> tuple[int, dict]:
|
||||
"""
|
||||
获取指定目录下图片的数量,并返回每个图片的路径和名称(字典)
|
||||
|
||||
Args:
|
||||
source_path (str): 要搜索的目录路径
|
||||
|
||||
Returns:
|
||||
tuple: 包含两个元素的元组
|
||||
picture_count (int): 图片数量
|
||||
image_with_name (dict): 图片路径和名称的字典,格式为 {图片名称: 图片完整路径}
|
||||
"""
|
||||
picture_count = 0
|
||||
image_with_name = {}
|
||||
name_list = []
|
||||
|
||||
for root, dirs, files in os.walk(source_path):
|
||||
for file in files:
|
||||
if file.lower().endswith(('.jpg', '.jpeg', '.png')) and not file.startswith('merged_thumbnail'):
|
||||
picture_count += 1
|
||||
image_with_name[os.path.splitext(file)[0]] = os.path.join(root, file)
|
||||
|
||||
return picture_count, image_with_name
|
||||
|
||||
|
||||
def find_image(directory, image_name):
|
||||
"""
|
||||
在指定目录中查找指定名称的图片文件
|
||||
|
||||
参数:
|
||||
directory (str): 要搜索的目录路径
|
||||
image_name (str): 要查找的图片文件名(可带扩展名或不带)
|
||||
|
||||
返回:
|
||||
str: 找到的图片完整路径,如果未找到则返回None
|
||||
"""
|
||||
# 支持的图片扩展名列表
|
||||
image_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp']
|
||||
|
||||
# 遍历目录中的所有文件
|
||||
for root, dirs, files in os.walk(directory):
|
||||
for file in files:
|
||||
# 获取文件名和扩展名
|
||||
filename, ext = os.path.splitext(file)
|
||||
|
||||
# 检查是否匹配图片名称(带或不带扩展名)
|
||||
if (file.lower() == image_name.lower() or
|
||||
filename.lower() == image_name.lower() and ext.lower() in image_extensions):
|
||||
return os.path.join(root, file)
|
||||
|
||||
return None
|
||||
|
||||
async def make_Thumbnail(source_path: str, output_path: str, size: tuple = (436, 233)) -> str:
|
||||
"""获取目录下所有图片,将所有图片合并制作略缩图并保存
|
||||
|
||||
Args:
|
||||
source_path: 源目录
|
||||
output_path: 输出目录
|
||||
size: 合并后的略缩图总大小 (宽度, 高度)
|
||||
"""
|
||||
print("略缩图处理中")
|
||||
|
||||
try:
|
||||
if not os.path.exists(output_path):
|
||||
print(f"无输出目录,创建中,输出目录为:{output_path}")
|
||||
os.makedirs(output_path)
|
||||
except Exception as e:
|
||||
print(f"输出目录有问题:{e}")
|
||||
return ""
|
||||
#如果存在merged_thumbnail.jpg文件,则直接返回该文件路径
|
||||
if os.path.exists(os.path.join(output_path,'merged_thumbnail.jpg')):
|
||||
print(f"已有略缩图,不用处理, 目前如需重新生成,请去往{output_path}目录 删除 merged_thumbnail.jpg 图片")
|
||||
"""
|
||||
此处可预留接口,询问用户是否重新生成一份略缩图
|
||||
"""
|
||||
|
||||
return os.path.join(output_path,'merged_thumbnail.jpg')
|
||||
print("目录中无略缩图,合并略缩图中")
|
||||
# 获取源目录下所有的图片文件
|
||||
try:
|
||||
image_files = []
|
||||
for root, dirs, files in os.walk(source_path):
|
||||
for file in files:
|
||||
if file.lower().endswith(('.jpg', '.jpeg', '.png')):
|
||||
image_files.append(os.path.join(root, file))
|
||||
except Exception as e:
|
||||
print(f"递归获取图片失败,原因:{e}")
|
||||
|
||||
if not image_files:
|
||||
print("源目录中没有找到图片文件")
|
||||
return ""
|
||||
|
||||
# 计算每个缩略图的大小
|
||||
num_images = len(image_files)
|
||||
target_width, target_height = size
|
||||
|
||||
# 计算最佳的缩略图排列方式
|
||||
# 先尝试计算每行可以放多少个缩略图
|
||||
aspect_ratio = target_width / target_height
|
||||
cols = math.ceil(math.sqrt(num_images * aspect_ratio))
|
||||
rows = math.ceil(num_images / cols)
|
||||
|
||||
# 计算单个缩略图的大小
|
||||
thumb_width = target_width // cols
|
||||
thumb_height = target_height // rows
|
||||
|
||||
# 创建线程池处理图片
|
||||
with ThreadPoolExecutor() as executor:
|
||||
thumbnails = list(executor.map(
|
||||
lambda file: create_thumbnail(file, (thumb_width, thumb_height)),
|
||||
image_files
|
||||
))
|
||||
|
||||
# 过滤掉 None 值
|
||||
thumbnails = [thumb for thumb in thumbnails if thumb is not None]
|
||||
|
||||
if not thumbnails:
|
||||
print("没有成功创建任何略缩图")
|
||||
return ""
|
||||
|
||||
# 计算实际需要的行数和列数
|
||||
actual_cols = min(len(thumbnails), cols)
|
||||
actual_rows = math.ceil(len(thumbnails) / actual_cols)
|
||||
|
||||
# 创建合并后的图像
|
||||
merged_image = Image.new('RGB', (actual_cols * thumb_width, actual_rows * thumb_height))
|
||||
|
||||
# 粘贴缩略图
|
||||
for index, thumb in enumerate(thumbnails):
|
||||
row = index // actual_cols
|
||||
col = index % actual_cols
|
||||
merged_image.paste(thumb, (col * thumb_width, row * thumb_height))
|
||||
|
||||
# 如果最终尺寸不完全匹配,调整大小
|
||||
if merged_image.size != size:
|
||||
merged_image = merged_image.resize(size, Image.LANCZOS)
|
||||
|
||||
# 保存合并后的略缩图
|
||||
merged_thumbnail_path = os.path.join(output_path, 'merged_thumbnail.jpg')
|
||||
merged_image.save(merged_thumbnail_path)
|
||||
|
||||
print(f"合并后的略缩图已保存到:{merged_thumbnail_path}")
|
||||
return merged_thumbnail_path
|
||||
|
||||
def create_thumbnail(file_path: str, size: tuple) -> Image:
|
||||
"""创建单个图片的略缩图
|
||||
|
||||
Args:
|
||||
file_path: 图片文件路径
|
||||
size: 缩略图大小
|
||||
"""
|
||||
try:
|
||||
with Image.open(file_path) as img:
|
||||
# 保持原始宽高比
|
||||
img.thumbnail(size, Image.LANCZOS)
|
||||
|
||||
# 创建新图像确保尺寸一致
|
||||
new_img = Image.new('RGB', size)
|
||||
new_img.paste(img, ((size[0] - img.width) // 2, (size[1] - img.height) // 2))
|
||||
return new_img
|
||||
except Exception as e:
|
||||
print(f"图片处理有问题:{e}")
|
||||
return None
|
||||
|
||||
def process_picture_data(picture_data : list[dict],
|
||||
image_source_to_find : list[str],
|
||||
quexian_type : str,
|
||||
dianxing_type : str,
|
||||
other_type : str
|
||||
) -> tuple[list[str], list[str]]:
|
||||
"""处理从数据库获取的图片数据
|
||||
|
||||
Args:
|
||||
picture_data (list[dict]): 图片数据
|
||||
image_source_to_find (list[str]): 要查找的图片来源枚举(外内防雷)
|
||||
quexian_type (str): 缺陷类型枚举值
|
||||
dianxing_type (str): 典型类型枚举值
|
||||
other_type (str): 其他类型枚举值
|
||||
Returns:
|
||||
tuple(
|
||||
defct_pictures, 缺陷图列表
|
||||
dianxing_pictures, 典型图列表
|
||||
other_pictures, 其他图列表
|
||||
total_num 总图片数量
|
||||
)
|
||||
"""
|
||||
#过滤目标来源的图片数据
|
||||
picture_data = [pic for pic in picture_data if pic['imageSource'] in image_source_to_find]
|
||||
#分别择出缺陷图和典型图
|
||||
return ([pic for pic in picture_data if pic['imageType'] == quexian_type],
|
||||
[pic for pic in picture_data if pic['imageType'] == dianxing_type],
|
||||
[pic for pic in picture_data if pic['imageType'] == other_type],
|
||||
len(picture_data))
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
"""
|
||||
Utility functions for the Word Document Server.
|
||||
|
||||
This package contains utility modules for file operations and document handling.
|
||||
"""
|
||||
|
||||
from .file_utils import check_file_writeable, create_document_copy, ensure_docx_extension
|
||||
from .document_utils import get_document_properties, extract_document_text, get_document_structure, find_paragraph_by_text, find_and_replace_text
|
|
@ -0,0 +1,167 @@
|
|||
"""
|
||||
Document utility functions for Word Document Server.
|
||||
"""
|
||||
import json
|
||||
from typing import Dict, List, Any
|
||||
from docx import Document
|
||||
|
||||
|
||||
def get_document_properties(doc_path: str) -> Dict[str, Any]:
|
||||
"""Get properties of a Word document."""
|
||||
import os
|
||||
if not os.path.exists(doc_path):
|
||||
return {"error": f"Document {doc_path} does not exist"}
|
||||
|
||||
try:
|
||||
doc = Document(doc_path)
|
||||
core_props = doc.core_properties
|
||||
|
||||
return {
|
||||
"title": core_props.title or "",
|
||||
"author": core_props.author or "",
|
||||
"subject": core_props.subject or "",
|
||||
"keywords": core_props.keywords or "",
|
||||
"created": str(core_props.created) if core_props.created else "",
|
||||
"modified": str(core_props.modified) if core_props.modified else "",
|
||||
"last_modified_by": core_props.last_modified_by or "",
|
||||
"revision": core_props.revision or 0,
|
||||
"page_count": len(doc.sections),
|
||||
"word_count": sum(len(paragraph.text.split()) for paragraph in doc.paragraphs),
|
||||
"paragraph_count": len(doc.paragraphs),
|
||||
"table_count": len(doc.tables)
|
||||
}
|
||||
except Exception as e:
|
||||
return {"error": f"Failed to get document properties: {str(e)}"}
|
||||
|
||||
|
||||
def extract_document_text(doc_path: str) -> str:
|
||||
"""Extract all text from a Word document."""
|
||||
import os
|
||||
if not os.path.exists(doc_path):
|
||||
return f"Document {doc_path} does not exist"
|
||||
|
||||
try:
|
||||
doc = Document(doc_path)
|
||||
text = []
|
||||
|
||||
for paragraph in doc.paragraphs:
|
||||
text.append(paragraph.text)
|
||||
|
||||
for table in doc.tables:
|
||||
for row in table.rows:
|
||||
for cell in row.cells:
|
||||
for paragraph in cell.paragraphs:
|
||||
text.append(paragraph.text)
|
||||
|
||||
return "\n".join(text)
|
||||
except Exception as e:
|
||||
return f"Failed to extract text: {str(e)}"
|
||||
|
||||
|
||||
def get_document_structure(doc_path: str) -> Dict[str, Any]:
|
||||
"""Get the structure of a Word document."""
|
||||
import os
|
||||
if not os.path.exists(doc_path):
|
||||
return {"error": f"Document {doc_path} does not exist"}
|
||||
|
||||
try:
|
||||
doc = Document(doc_path)
|
||||
structure = {
|
||||
"paragraphs": [],
|
||||
"tables": []
|
||||
}
|
||||
|
||||
# Get paragraphs
|
||||
for i, para in enumerate(doc.paragraphs):
|
||||
structure["paragraphs"].append({
|
||||
"index": i,
|
||||
"text": para.text[:100] + ("..." if len(para.text) > 100 else ""),
|
||||
"style": para.style.name if para.style else "Normal"
|
||||
})
|
||||
|
||||
# Get tables
|
||||
for i, table in enumerate(doc.tables):
|
||||
table_data = {
|
||||
"index": i,
|
||||
"rows": len(table.rows),
|
||||
"columns": len(table.columns),
|
||||
"preview": []
|
||||
}
|
||||
|
||||
# Get sample of table data
|
||||
max_rows = min(3, len(table.rows))
|
||||
for row_idx in range(max_rows):
|
||||
row_data = []
|
||||
max_cols = min(3, len(table.columns))
|
||||
for col_idx in range(max_cols):
|
||||
try:
|
||||
cell_text = table.cell(row_idx, col_idx).text
|
||||
row_data.append(cell_text[:20] + ("..." if len(cell_text) > 20 else ""))
|
||||
except IndexError:
|
||||
row_data.append("N/A")
|
||||
table_data["preview"].append(row_data)
|
||||
|
||||
structure["tables"].append(table_data)
|
||||
|
||||
return structure
|
||||
except Exception as e:
|
||||
return {"error": f"Failed to get document structure: {str(e)}"}
|
||||
|
||||
|
||||
def find_paragraph_by_text(doc, text, partial_match=False):
|
||||
"""
|
||||
Find paragraphs containing specific text.
|
||||
|
||||
Args:
|
||||
doc: Document object
|
||||
text: Text to search for
|
||||
partial_match: If True, matches paragraphs containing the text; if False, matches exact text
|
||||
|
||||
Returns:
|
||||
List of paragraph indices that match the criteria
|
||||
"""
|
||||
matching_paragraphs = []
|
||||
|
||||
for i, para in enumerate(doc.paragraphs):
|
||||
if partial_match and text in para.text:
|
||||
matching_paragraphs.append(i)
|
||||
elif not partial_match and para.text == text:
|
||||
matching_paragraphs.append(i)
|
||||
|
||||
return matching_paragraphs
|
||||
|
||||
|
||||
def find_and_replace_text(doc, old_text, new_text):
|
||||
"""
|
||||
Find and replace text throughout the document.
|
||||
|
||||
Args:
|
||||
doc: Document object
|
||||
old_text: Text to find
|
||||
new_text: Text to replace with
|
||||
|
||||
Returns:
|
||||
Number of replacements made
|
||||
"""
|
||||
count = 0
|
||||
|
||||
# Search in paragraphs
|
||||
for para in doc.paragraphs:
|
||||
if old_text in para.text:
|
||||
for run in para.runs:
|
||||
if old_text in run.text:
|
||||
run.text = run.text.replace(old_text, new_text)
|
||||
count += 1
|
||||
|
||||
# Search in tables
|
||||
for table in doc.tables:
|
||||
for row in table.rows:
|
||||
for cell in row.cells:
|
||||
for para in cell.paragraphs:
|
||||
if old_text in para.text:
|
||||
for run in para.runs:
|
||||
if old_text in run.text:
|
||||
run.text = run.text.replace(old_text, new_text)
|
||||
count += 1
|
||||
|
||||
return count
|
|
@ -0,0 +1,165 @@
|
|||
"""
|
||||
Extended document utilities for Word Document Server.
|
||||
"""
|
||||
from typing import Dict, List, Any, Tuple
|
||||
from docx import Document
|
||||
|
||||
|
||||
def get_paragraph_text(doc_path: str, paragraph_index: int) -> Dict[str, Any]:
|
||||
"""
|
||||
Get text from a specific paragraph in a Word document.
|
||||
|
||||
Args:
|
||||
doc_path: Path to the Word document
|
||||
paragraph_index: Index of the paragraph to extract (0-based)
|
||||
|
||||
Returns:
|
||||
Dictionary with paragraph text and metadata
|
||||
"""
|
||||
import os
|
||||
if not os.path.exists(doc_path):
|
||||
return {"error": f"Document {doc_path} does not exist"}
|
||||
|
||||
try:
|
||||
doc = Document(doc_path)
|
||||
|
||||
# Check if paragraph index is valid
|
||||
if paragraph_index < 0 or paragraph_index >= len(doc.paragraphs):
|
||||
return {"error": f"Invalid paragraph index: {paragraph_index}. Document has {len(doc.paragraphs)} paragraphs."}
|
||||
|
||||
paragraph = doc.paragraphs[paragraph_index]
|
||||
|
||||
return {
|
||||
"index": paragraph_index,
|
||||
"text": paragraph.text,
|
||||
"style": paragraph.style.name if paragraph.style else "Normal",
|
||||
"is_heading": paragraph.style.name.startswith("Heading") if paragraph.style else False
|
||||
}
|
||||
except Exception as e:
|
||||
return {"error": f"Failed to get paragraph text: {str(e)}"}
|
||||
|
||||
|
||||
def find_text(doc_path: str, text_to_find: str, match_case: bool = True, whole_word: bool = False) -> Dict[str, Any]:
|
||||
"""
|
||||
Find all occurrences of specific text in a Word document.
|
||||
|
||||
Args:
|
||||
doc_path: Path to the Word document
|
||||
text_to_find: Text to search for
|
||||
match_case: Whether to perform case-sensitive search
|
||||
whole_word: Whether to match whole words only
|
||||
|
||||
Returns:
|
||||
Dictionary with search results
|
||||
"""
|
||||
import os
|
||||
if not os.path.exists(doc_path):
|
||||
return {"error": f"Document {doc_path} does not exist"}
|
||||
|
||||
if not text_to_find:
|
||||
return {"error": "Search text cannot be empty"}
|
||||
|
||||
try:
|
||||
doc = Document(doc_path)
|
||||
results = {
|
||||
"query": text_to_find,
|
||||
"match_case": match_case,
|
||||
"whole_word": whole_word,
|
||||
"occurrences": [],
|
||||
"total_count": 0
|
||||
}
|
||||
|
||||
# Search in paragraphs
|
||||
for i, para in enumerate(doc.paragraphs):
|
||||
# Prepare text for comparison
|
||||
para_text = para.text
|
||||
search_text = text_to_find
|
||||
|
||||
if not match_case:
|
||||
para_text = para_text.lower()
|
||||
search_text = search_text.lower()
|
||||
|
||||
# Find all occurrences (simple implementation)
|
||||
start_pos = 0
|
||||
while True:
|
||||
if whole_word:
|
||||
# For whole word search, we need to check word boundaries
|
||||
words = para_text.split()
|
||||
found = False
|
||||
for word_idx, word in enumerate(words):
|
||||
if (word == search_text or
|
||||
(not match_case and word.lower() == search_text.lower())):
|
||||
results["occurrences"].append({
|
||||
"paragraph_index": i,
|
||||
"position": word_idx,
|
||||
"context": para.text[:100] + ("..." if len(para.text) > 100 else "")
|
||||
})
|
||||
results["total_count"] += 1
|
||||
found = True
|
||||
|
||||
# Break after checking all words
|
||||
break
|
||||
else:
|
||||
# For substring search
|
||||
pos = para_text.find(search_text, start_pos)
|
||||
if pos == -1:
|
||||
break
|
||||
|
||||
results["occurrences"].append({
|
||||
"paragraph_index": i,
|
||||
"position": pos,
|
||||
"context": para.text[:100] + ("..." if len(para.text) > 100 else "")
|
||||
})
|
||||
results["total_count"] += 1
|
||||
start_pos = pos + len(search_text)
|
||||
|
||||
# Search in tables
|
||||
for table_idx, table in enumerate(doc.tables):
|
||||
for row_idx, row in enumerate(table.rows):
|
||||
for col_idx, cell in enumerate(row.cells):
|
||||
for para_idx, para in enumerate(cell.paragraphs):
|
||||
# Prepare text for comparison
|
||||
para_text = para.text
|
||||
search_text = text_to_find
|
||||
|
||||
if not match_case:
|
||||
para_text = para_text.lower()
|
||||
search_text = search_text.lower()
|
||||
|
||||
# Find all occurrences (simple implementation)
|
||||
start_pos = 0
|
||||
while True:
|
||||
if whole_word:
|
||||
# For whole word search, check word boundaries
|
||||
words = para_text.split()
|
||||
found = False
|
||||
for word_idx, word in enumerate(words):
|
||||
if (word == search_text or
|
||||
(not match_case and word.lower() == search_text.lower())):
|
||||
results["occurrences"].append({
|
||||
"location": f"Table {table_idx}, Row {row_idx}, Column {col_idx}",
|
||||
"position": word_idx,
|
||||
"context": para.text[:100] + ("..." if len(para.text) > 100 else "")
|
||||
})
|
||||
results["total_count"] += 1
|
||||
found = True
|
||||
|
||||
# Break after checking all words
|
||||
break
|
||||
else:
|
||||
# For substring search
|
||||
pos = para_text.find(search_text, start_pos)
|
||||
if pos == -1:
|
||||
break
|
||||
|
||||
results["occurrences"].append({
|
||||
"location": f"Table {table_idx}, Row {row_idx}, Column {col_idx}",
|
||||
"position": pos,
|
||||
"context": para.text[:100] + ("..." if len(para.text) > 100 else "")
|
||||
})
|
||||
results["total_count"] += 1
|
||||
start_pos = pos + len(search_text)
|
||||
|
||||
return results
|
||||
except Exception as e:
|
||||
return {"error": f"Failed to search for text: {str(e)}"}
|
|
@ -0,0 +1,85 @@
|
|||
"""
|
||||
File utility functions for Word Document Server.
|
||||
"""
|
||||
import os
|
||||
from typing import Tuple, Optional
|
||||
import shutil
|
||||
|
||||
|
||||
def check_file_writeable(filepath: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Check if a file can be written to.
|
||||
|
||||
Args:
|
||||
filepath: Path to the file
|
||||
|
||||
Returns:
|
||||
Tuple of (is_writeable, error_message)
|
||||
"""
|
||||
# If file doesn't exist, check if directory is writeable
|
||||
if not os.path.exists(filepath):
|
||||
directory = os.path.dirname(filepath)
|
||||
# If no directory is specified (empty string), use current directory
|
||||
if directory == '':
|
||||
directory = '.'
|
||||
if not os.path.exists(directory):
|
||||
return False, f"Directory {directory} does not exist"
|
||||
if not os.access(directory, os.W_OK):
|
||||
return False, f"Directory {directory} is not writeable"
|
||||
return True, ""
|
||||
|
||||
# If file exists, check if it's writeable
|
||||
if not os.access(filepath, os.W_OK):
|
||||
return False, f"File {filepath} is not writeable (permission denied)"
|
||||
|
||||
# Try to open the file for writing to see if it's locked
|
||||
try:
|
||||
with open(filepath, 'a'):
|
||||
pass
|
||||
return True, ""
|
||||
except IOError as e:
|
||||
return False, f"File {filepath} is not writeable: {str(e)}"
|
||||
except Exception as e:
|
||||
return False, f"Unknown error checking file permissions: {str(e)}"
|
||||
|
||||
|
||||
def create_document_copy(source_path: str, dest_path: Optional[str] = None) -> Tuple[bool, str, Optional[str]]:
|
||||
"""
|
||||
Create a copy of a document.
|
||||
|
||||
Args:
|
||||
source_path: Path to the source document
|
||||
dest_path: Optional path for the new document. If not provided, will use source_path + '_copy.docx'
|
||||
|
||||
Returns:
|
||||
Tuple of (success, message, new_filepath)
|
||||
"""
|
||||
if not os.path.exists(source_path):
|
||||
return False, f"Source document {source_path} does not exist", None
|
||||
|
||||
if not dest_path:
|
||||
# Generate a new filename if not provided
|
||||
base, ext = os.path.splitext(source_path)
|
||||
dest_path = f"{base}_copy{ext}"
|
||||
|
||||
try:
|
||||
# Simple file copy
|
||||
shutil.copy2(source_path, dest_path)
|
||||
return True, f"Document copied to {dest_path}", dest_path
|
||||
except Exception as e:
|
||||
return False, f"Failed to copy document: {str(e)}", None
|
||||
|
||||
|
||||
def ensure_docx_extension(filename: str) -> str:
|
||||
"""
|
||||
Ensure filename has .docx extension.
|
||||
|
||||
Args:
|
||||
filename: The filename to check
|
||||
|
||||
Returns:
|
||||
Filename with .docx extension
|
||||
"""
|
||||
if not filename.endswith('.docx'):
|
||||
return filename + '.docx'
|
||||
return filename
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue