From e79bbbfcc4827145d386965314b3d26376785c7c Mon Sep 17 00:00:00 2001 From: che Date: Tue, 28 Jul 2026 08:53:19 +0500 Subject: [PATCH] fix --- src-tauri/Cargo.lock | 2 + src-tauri/Cargo.toml | 2 + src-tauri/gen/android/app/build.gradle.kts | 35 +- .../java/com/che/ewa_mobile/MainActivity.kt | 10 + src-tauri/src/apps/internalmemoapp/filters.rs | 31 ++ src-tauri/src/apps/internalmemoapp/mod.rs | 32 ++ src-tauri/src/apps/internalmemoapp/models.rs | 53 +++ .../src/apps/internalmemoapp/serializers.rs | 80 ++++ src-tauri/src/apps/mod.rs | 2 + .../src/apps/personemanagment/filters.rs | 1 + src-tauri/src/lib.rs | 29 +- src-tauri/src/sync.rs | 4 +- src/App.vue | 4 + src/app/router/index.ts | 2 + src/apps/documents/views/DocumentsView.vue | 5 + .../memos/components/MemoCategorySelect.vue | 140 +++++++ src/apps/memos/routes.ts | 17 + src/apps/memos/views/MemoDetailView.vue | 196 ++++++++++ src/apps/memos/views/MemosView.vue | 364 ++++++++++++++++++ src/apps/tasks/views/TaskDetailView.vue | 123 +++--- src/generated/api.ts | 10 + src/generated/models.ts | 123 ++++++ .../components/DocumentApprovalTaskCard.vue | 134 +++---- .../components/DocumentApprovalTasks.vue | 2 +- src/shared/components/RemoteImage.vue | 50 ++- 25 files changed, 1303 insertions(+), 148 deletions(-) create mode 100644 src-tauri/src/apps/internalmemoapp/filters.rs create mode 100644 src-tauri/src/apps/internalmemoapp/mod.rs create mode 100644 src-tauri/src/apps/internalmemoapp/models.rs create mode 100644 src-tauri/src/apps/internalmemoapp/serializers.rs create mode 100644 src/apps/memos/components/MemoCategorySelect.vue create mode 100644 src/apps/memos/routes.ts create mode 100644 src/apps/memos/views/MemoDetailView.vue create mode 100644 src/apps/memos/views/MemosView.vue diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index fbc7cb9..cbe62a6 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1196,7 +1196,9 @@ version = "0.1.0" dependencies = [ "che-orm", "che-tauri", + "jni", "reqwest", + "rustls-platform-verifier", "serde", "serde_json", "tauri", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 2d53401..a2a20bf 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -27,3 +27,5 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1", features = ["macros", "rt-multi-thread"] } reqwest = { version = "0.13", default-features = false, features = ["json", "query", "rustls"] } +jni = "0.21" +rustls-platform-verifier = "0.6" diff --git a/src-tauri/gen/android/app/build.gradle.kts b/src-tauri/gen/android/app/build.gradle.kts index 5de8426..363fe99 100644 --- a/src-tauri/gen/android/app/build.gradle.kts +++ b/src-tauri/gen/android/app/build.gradle.kts @@ -1,4 +1,5 @@ import java.util.Properties +import groovy.json.JsonSlurper plugins { id("com.android.application") @@ -53,11 +54,19 @@ android { } } +repositories { + maven { + url = uri(rustlsPlatformVerifierMavenDir()) + metadataSources.artifact() + } +} + rust { rootDirRel = "../../../" } dependencies { + implementation("rustls:rustls-platform-verifier:latest.release") implementation("androidx.webkit:webkit:1.14.0") implementation("androidx.appcompat:appcompat:1.7.1") implementation("androidx.activity:activity-ktx:1.10.1") @@ -68,4 +77,28 @@ dependencies { androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0") } -apply(from = "tauri.build.gradle.kts") \ No newline at end of file +apply(from = "tauri.build.gradle.kts") + +fun rustlsPlatformVerifierMavenDir(): String { + val manifestPath = providers.exec { + commandLine( + "cargo", + "metadata", + "--format-version", + "1", + "--filter-platform", + "aarch64-linux-android", + "--manifest-path", + rootProject.file("../../Cargo.toml").absolutePath, + ) + }.standardOutput.asText.get() + + val metadata = JsonSlurper().parseText(manifestPath) as Map<*, *> + val packages = metadata["packages"] as List<*> + val rustlsAndroidPackage = packages + .map { it as Map<*, *> } + .first { it["name"] == "rustls-platform-verifier-android" } + val rustlsManifest = file(rustlsAndroidPackage["manifest_path"] as String) + + return File(rustlsManifest.parentFile, "maven").path +} diff --git a/src-tauri/gen/android/app/src/main/java/com/che/ewa_mobile/MainActivity.kt b/src-tauri/gen/android/app/src/main/java/com/che/ewa_mobile/MainActivity.kt index 7d99eab..7d63e7f 100644 --- a/src-tauri/gen/android/app/src/main/java/com/che/ewa_mobile/MainActivity.kt +++ b/src-tauri/gen/android/app/src/main/java/com/che/ewa_mobile/MainActivity.kt @@ -1,10 +1,20 @@ package com.che.ewa_mobile +import android.content.Context import android.os.Bundle import androidx.activity.enableEdgeToEdge class MainActivity : TauriActivity() { + companion object { + init { + System.loadLibrary("ewa_mobile_lib") + } + + @JvmStatic external fun initRustlsPlatformVerifier(context: Context) + } + override fun onCreate(savedInstanceState: Bundle?) { + initRustlsPlatformVerifier(applicationContext) enableEdgeToEdge() super.onCreate(savedInstanceState) } diff --git a/src-tauri/src/apps/internalmemoapp/filters.rs b/src-tauri/src/apps/internalmemoapp/filters.rs new file mode 100644 index 0000000..85c6495 --- /dev/null +++ b/src-tauri/src/apps/internalmemoapp/filters.rs @@ -0,0 +1,31 @@ +use che_tauri::{Filter, FilterSet}; + +use super::models::{Memo, MemoCategory}; + +static MEMO_FILTERS: &[Filter] = &[ + Filter::exact("id"), + Filter::contains("text"), + Filter::exact("date"), + Filter::exact("priority"), + Filter::exact("archive"), + Filter::exact("cancel"), + Filter::exact_source("recipient", "recipient_id").remote("recipient"), + Filter::exact_source("sender", "sender_id").remote("sender"), + Filter::exact_source("category", "category_id").remote("category"), + Filter::exact_source("project", "project_id").remote("project"), +]; + +static MEMO_CATEGORY_FILTERS: &[Filter] = &[ + Filter::exact("id"), + Filter::exact("name"), + Filter::contains("name"), + Filter::exact("code"), +]; + +pub fn memo_filterset() -> FilterSet { + FilterSet::new(MEMO_FILTERS).remote_ordering("order_by") +} + +pub fn memo_category_filterset() -> FilterSet { + FilterSet::new(MEMO_CATEGORY_FILTERS).remote_ordering("order_by") +} diff --git a/src-tauri/src/apps/internalmemoapp/mod.rs b/src-tauri/src/apps/internalmemoapp/mod.rs new file mode 100644 index 0000000..3f1deab --- /dev/null +++ b/src-tauri/src/apps/internalmemoapp/mod.rs @@ -0,0 +1,32 @@ +pub mod filters; +pub mod models; +pub mod serializers; + +use che_tauri::{AppModule, ModuleContext}; + +pub fn module() -> InternalmemoappModule { + InternalmemoappModule +} + +pub struct InternalmemoappModule; + +impl AppModule for InternalmemoappModule { + fn name(&self) -> &'static str { + "internalmemoapp" + } + + fn init(&self, ctx: &mut ModuleContext) { + ctx.mapped_remote_resource::( + "memo", + "/api/internalmemo/memo/", + serializers::memo_serializer(), + filters::memo_filterset(), + ); + ctx.mapped_remote_resource::( + "memo_category", + "/api/internalmemo/category/", + serializers::memo_category_serializer(), + filters::memo_category_filterset(), + ); + } +} diff --git a/src-tauri/src/apps/internalmemoapp/models.rs b/src-tauri/src/apps/internalmemoapp/models.rs new file mode 100644 index 0000000..21dcb63 --- /dev/null +++ b/src-tauri/src/apps/internalmemoapp/models.rs @@ -0,0 +1,53 @@ +use che_orm::Model; + +use crate::apps::{personemanagment::models::Employee, projectapp::models::Project}; + +#[derive(Debug, Clone, Model)] +#[model(table = "internal_memo")] +pub struct Memo { + #[field(primary_key)] + pub id: i64, + + #[field(foreign_key = Employee)] + pub recipient_id: Option, + + #[field(foreign_key = Employee)] + pub sender_id: Option, + + #[field(foreign_key = MemoCategory)] + pub category_id: Option, + + #[field(foreign_key = Project)] + pub project_id: Option, + + pub task_set: Option, + pub priority_display: String, + pub absolute_url: String, + pub date: String, + pub my_approve: String, + pub text: String, + pub resalution: String, + pub priority: String, + pub archive: bool, + pub cancel: bool, + pub date_start: Option, + pub date_end: Option, + pub value: f64, + pub archive_s: bool, + pub current_step: i64, + pub subject: Option, + pub employee_acl: Option, +} + +#[derive(Debug, Clone, Model)] +#[model(table = "internal_memo_category")] +pub struct MemoCategory { + #[field(primary_key)] + pub id: i64, + + pub name: String, + pub template: String, + pub template_text: String, + pub code: String, + pub in_month_limit: bool, +} diff --git a/src-tauri/src/apps/internalmemoapp/serializers.rs b/src-tauri/src/apps/internalmemoapp/serializers.rs new file mode 100644 index 0000000..197dcde --- /dev/null +++ b/src-tauri/src/apps/internalmemoapp/serializers.rs @@ -0,0 +1,80 @@ +use che_tauri::{Field, ModelSerializer, RelatedModel}; + +use crate::apps::{ + personemanagment::{models::Employee, serializers::employee_serializer}, + projectapp::{models::Project, serializers::project_serializer}, +}; + +use super::models::{Memo, MemoCategory}; + +static MEMO_FIELDS: &[Field] = &[ + Field::new("id").read_only(), + Field::new("recipient_id") + .source("recipient") + .required(false) + .nullable(), + Field::new("sender_id") + .source("sender") + .required(false) + .nullable(), + Field::new("category_id") + .source("category") + .required(false) + .nullable(), + Field::new("project_id") + .source("project") + .required(false) + .nullable(), + Field::json("task_set") + .ts_type("unknown[]") + .required(false) + .nullable(), + Field::new("priority_display").source("get_priority_display"), + Field::new("absolute_url").source("get_absolute_url"), + Field::new("date"), + Field::new("my_approve"), + Field::new("text"), + Field::new("resalution"), + Field::new("priority"), + Field::new("archive"), + Field::new("cancel"), + Field::new("date_start").required(false).nullable(), + Field::new("date_end").required(false).nullable(), + Field::new("value"), + Field::new("archive_s"), + Field::new("current_step"), + Field::json("subject") + .ts_type("number[]") + .required(false) + .nullable(), + Field::json("employee_acl") + .ts_type("number[]") + .required(false) + .nullable(), + Field::related("recipient", "recipient_id", &EMPLOYEE_RELATION), + Field::related("sender", "sender_id", &EMPLOYEE_RELATION), + Field::related("category", "category_id", &MEMO_CATEGORY_RELATION), + Field::related("project", "project_id", &PROJECT_RELATION), +]; + +static MEMO_CATEGORY_FIELDS: &[Field] = &[ + Field::new("id").read_only(), + Field::new("name"), + Field::new("template"), + Field::new("template_text"), + Field::new("code"), + Field::new("in_month_limit"), +]; + +static EMPLOYEE_RELATION: RelatedModel = RelatedModel::new(employee_serializer); +static PROJECT_RELATION: RelatedModel = RelatedModel::new(project_serializer); +static MEMO_CATEGORY_RELATION: RelatedModel = + RelatedModel::new(memo_category_serializer); + +pub fn memo_serializer() -> ModelSerializer { + ModelSerializer::new(MEMO_FIELDS) +} + +pub fn memo_category_serializer() -> ModelSerializer { + ModelSerializer::new(MEMO_CATEGORY_FIELDS) +} diff --git a/src-tauri/src/apps/mod.rs b/src-tauri/src/apps/mod.rs index b0c3c26..8ffdbf4 100644 --- a/src-tauri/src/apps/mod.rs +++ b/src-tauri/src/apps/mod.rs @@ -1,4 +1,5 @@ pub mod frcapp; +pub mod internalmemoapp; pub mod personemanagment; pub mod projectapp; pub mod supplyapp; @@ -14,5 +15,6 @@ pub fn installed_apps() -> InstalledApps { .add(personemanagment::module()) .add(contractapp::module()) .add(supplyapp::module()) + .add(internalmemoapp::module()) } pub mod contractapp; diff --git a/src-tauri/src/apps/personemanagment/filters.rs b/src-tauri/src/apps/personemanagment/filters.rs index f55a4b1..1df9450 100644 --- a/src-tauri/src/apps/personemanagment/filters.rs +++ b/src-tauri/src/apps/personemanagment/filters.rs @@ -15,6 +15,7 @@ static TASK_FILTERS: &[Filter] = &[ Filter::contains("text"), Filter::remote_only("contract"), Filter::remote_only("bill"), + Filter::remote_only("memo"), Filter::remote_only("doer"), Filter::remote_only("author"), Filter::exact("archive"), diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 599d4e0..4b6a6d4 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,7 +1,10 @@ pub mod apps; pub mod sync; -use std::path::{Path, PathBuf}; +use std::{ + path::{Path, PathBuf}, + time::Duration, +}; use che_orm::SqliteBackend; use che_tauri::{ @@ -13,6 +16,18 @@ use tauri_plugin_opener::OpenerExt; use crate::sync::SyncContractsResult; +#[cfg(target_os = "android")] +#[no_mangle] +pub unsafe extern "system" fn Java_com_che_ewa_1mobile_MainActivity_initRustlsPlatformVerifier( + mut env: jni::JNIEnv, + _class: jni::objects::JClass, + context: jni::objects::JObject, +) { + if let Err(error) = rustls_platform_verifier::android::init_with_env(&mut env, context) { + eprintln!("failed to initialize rustls platform verifier: {error}"); + } +} + #[cfg(target_os = "android")] const REMOTE_BASE_URL: &str = "http://10.0.2.2:8000"; @@ -89,6 +104,14 @@ fn validate_settings(settings: &AppSettings) -> Result<(), ApiError> { Ok(()) } +pub(crate) fn remote_http_client() -> reqwest::Client { + reqwest::Client::builder() + .timeout(Duration::from_secs(15)) + .connect_timeout(Duration::from_secs(5)) + .build() + .expect("failed to build reqwest client") +} + fn settings_path(app_data_dir: &Path) -> PathBuf { app_data_dir.join("settings.json") } @@ -219,7 +242,7 @@ async fn current_employee(api: tauri::State<'_, TauriApi>) -> Result +import { computed, ref, watch } from "vue"; +import { memoCategoryApi } from "../../../generated/api"; +import type { MemoCategoryListParams } from "../../../generated/models"; +import { useModelApi } from "../../../shared/composables/useModelApi"; + +const PAGE_SIZE = 20; + +const selectedCategoryId = defineModel({ required: true }); +const search = ref(""); +const showSelector = ref(false); + +const { + items: categories, + filters, + loading, +} = useModelApi(memoCategoryApi, { + defaultListParams: { ordering: "id", limit: PAGE_SIZE, offset: 0 } as MemoCategoryListParams, + loadErrorMessage: "Не удалось загрузить категории", + cleanListParams(params) { + params.name__contains = params.name__contains?.trim() || undefined; + }, +}); + +const selectedCategory = computed(() => + categories.value.find((category) => category.id === selectedCategoryId.value), +); +const selectedCategoryName = computed(() => selectedCategory.value?.name || "все"); + +function openSelector() { + search.value = ""; + showSelector.value = true; +} + +function selectCategory(id: number) { + selectedCategoryId.value = id; + showSelector.value = false; +} + +watch(search, (value) => { + filters.name__contains = value.trim(); + filters.offset = 0; +}); + + + + + diff --git a/src/apps/memos/routes.ts b/src/apps/memos/routes.ts new file mode 100644 index 0000000..7d8ef3d --- /dev/null +++ b/src/apps/memos/routes.ts @@ -0,0 +1,17 @@ +import MemoDetailView from "./views/MemoDetailView.vue"; +import MemosView from "./views/MemosView.vue"; + +export const memosRoutes = [ + { + path: "/memos", + name: "memos", + component: MemosView, + meta: { title: "Служебные записки", requiresAuth: true }, + }, + { + path: "/memos/:id", + name: "memo-detail", + component: MemoDetailView, + meta: { title: "Служебная записка", back: true, requiresAuth: true }, + }, +]; diff --git a/src/apps/memos/views/MemoDetailView.vue b/src/apps/memos/views/MemoDetailView.vue new file mode 100644 index 0000000..e3f4fd8 --- /dev/null +++ b/src/apps/memos/views/MemoDetailView.vue @@ -0,0 +1,196 @@ + + + + + diff --git a/src/apps/memos/views/MemosView.vue b/src/apps/memos/views/MemosView.vue new file mode 100644 index 0000000..5eb2115 --- /dev/null +++ b/src/apps/memos/views/MemosView.vue @@ -0,0 +1,364 @@ + + + + + diff --git a/src/apps/tasks/views/TaskDetailView.vue b/src/apps/tasks/views/TaskDetailView.vue index b65b523..fec181d 100644 --- a/src/apps/tasks/views/TaskDetailView.vue +++ b/src/apps/tasks/views/TaskDetailView.vue @@ -1,45 +1,43 @@ + +