This commit is contained in:
Yakumo Hokori
2025-07-12 23:06:30 +08:00
commit 93ce1ae6ad
44 changed files with 10380 additions and 0 deletions

7
src-tauri/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# Generated by Tauri
# will have schema files for capabilities auto-completion
/gen/schemas

5917
src-tauri/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

32
src-tauri/Cargo.toml Normal file
View File

@@ -0,0 +1,32 @@
[package]
name = "tms_tools"
version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
# The `_lib` suffix may seem redundant but it is necessary
# to make the lib name unique and wouldn't conflict with the bin name.
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
name = "tms_tools_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio-postgres = { version = "0.7.10", features = ["with-chrono-0_4"] }
tokio = { version = "1", features = ["full"] }
chrono = { version = "0.4", features = ["serde"] }
reqwest = { version = "0.12.22", features = ["json", "cookies"] }
lazy_static = "1.4.0"
scraper = "0.19.0"
tms_service = { path = "./tms_service" }

3
src-tauri/build.rs Normal file
View File

@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

View File

@@ -0,0 +1,10 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": [
"core:default",
"opener:default"
]
}

BIN
src-tauri/icons/128x128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
src-tauri/icons/32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src-tauri/icons/icon.icns Normal file

Binary file not shown.

BIN
src-tauri/icons/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
src-tauri/icons/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

1
src-tauri/openai/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

1671
src-tauri/openai/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
[package]
name = "openai"
version = "0.1.0"
edition = "2021"
[dependencies]
reqwest = "0.12.22"
tokio = { version = "1.38.0", features = ["full"] }
serde = { version = "1.0.203", features = ["derive"] }
serde_json = "1.0.120"

View File

@@ -0,0 +1,93 @@
use serde::{Deserialize, Serialize};
const API_KEY: &str = "AIzaSyDBienhzI5Y3o9N4foi7QQRarTtuOIsVJg"; // 请替换为您的OpenAI API密钥
const API_URL: &str = "https://melodic-bonbon-359e24.netlify.app/edge/v1/chat/completions";
// --- Structs for Serialization (Request) ---
#[derive(Serialize)]
struct ChatCompletionRequest<'a> {
model: &'a str,
messages: Vec<RequestMessage<'a>>,
}
#[derive(Serialize)]
struct RequestMessage<'a> {
role: &'a str,
content: &'a str,
}
// --- Structs for Deserialization (Response) ---
#[derive(Deserialize)]
struct ChatCompletionResponse {
choices: Vec<Choice>,
}
#[derive(Deserialize)]
struct Choice {
message: ResponseMessage,
}
#[derive(Deserialize)]
struct ResponseMessage {
#[allow(dead_code)]
role: String,
content: String,
}
pub async fn ask_openai(user_prompt: &str, system_prompt: &str) -> Result<String, reqwest::Error> {
let client = reqwest::Client::new();
let messages = vec![
RequestMessage {
role: "system",
content: system_prompt,
},
RequestMessage {
role: "user",
content: user_prompt,
},
];
let request_body = ChatCompletionRequest {
model: "gemini-2.5-flash",
messages,
};
let response = client
.post(API_URL)
.bearer_auth(API_KEY)
.json(&request_body)
.send()
.await?;
let response = response.error_for_status()?;
let chat_response = response.json::<ChatCompletionResponse>().await?;
if let Some(choice) = chat_response.choices.get(0) {
Ok(choice.message.content.clone())
} else {
Ok("No response from AI.".to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_ask_openai() {
// 请注意此测试会真实调用OpenAI API并产生费用。
// 请确保您已经设置了正确的API_KEY。
// 在实际测试中您可能希望使用模拟mocking来避免网络调用。
let user_prompt = "Hello, who are you?";
let system_prompt = "You are a helpful assistant.";
let result = ask_openai(user_prompt, system_prompt).await;
if let Err(e) = &result {
eprintln!("API call failed with error: {}", e);
}
assert!(result.is_ok());
println!("AI Response: {}", result.unwrap());
}
}

585
src-tauri/src/lib.rs Normal file
View File

@@ -0,0 +1,585 @@
use std::sync::Mutex;
use lazy_static::lazy_static;
use tokio_postgres::{NoTls, Error};
use serde::Deserialize;
use std::collections::HashMap;
use scraper::{Html, Selector};
use tms_service;
use std::time::Duration;
use tokio::time::sleep;
lazy_static! {
static ref COOKIE_STORAGE: Mutex<Option<String>> = Mutex::new(None);
static ref USERNAME_STORAGE: Mutex<Option<String>> = Mutex::new(None);
}
#[derive(serde::Serialize)]
struct LoginResult {
success: bool,
message: String,
}
#[derive(Deserialize, Debug)]
struct ApiLoginResponse {
msg: String,
// a_id: i32,
// username: String,
// code: i32,
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
struct WorkOrder {
gdid: String,
n2n: String,
messageq: String,
messagea: Option<String>,
iscreate: i32,
isfeedback: i32,
isclose: i32,
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
struct WorkOrderResponse {
orders: Vec<WorkOrder>,
total: i32,
}
#[tauri::command]
async fn login(username: String, password: String) -> Result<LoginResult, String> {
// First, authenticate against the external API
let client = reqwest::Client::builder().cookie_store(true).build().unwrap();
let mut params = HashMap::new();
params.insert("username", username.clone());
params.insert("password", password);
let res = client.post("https://c.baobaot.com/user/ajax_login")
.form(&params)
.send()
.await
.map_err(|e| format!("API请求失败: {}", e))?;
if res.status().is_success() {
if let Some(cookie) = res.headers().get("set-cookie") {
if let Ok(cookie_str) = cookie.to_str() {
let mut storage = COOKIE_STORAGE.lock().unwrap();
*storage = Some(cookie_str.to_string());
}
}
let api_response = res.json::<ApiLoginResponse>().await.map_err(|e| format!("解析API响应失败: {}", e))?;
if api_response.msg != "登录成功" {
return Ok(LoginResult {
success: false,
message: api_response.msg,
});
}
let mut user_storage = USERNAME_STORAGE.lock().unwrap();
*user_storage = Some(username.clone());
} else {
let error_text = res.text().await.unwrap_or_else(|_| "无法读取API错误响应".to_string());
return Err(format!("API请求返回错误状态: {}", error_text));
}
// If API login is successful, proceed with database operations
let db_url = "postgres://tmstools:521707@honulla.com:5432/tmstools";
let (mut client, connection) = match tokio_postgres::connect(db_url, NoTls).await {
Ok(res) => res,
Err(e) => return Err(format!("数据库连接失败: {}", e)),
};
tokio::spawn(async move {
if let Err(e) = connection.await {
eprintln!("connection error: {}", e);
}
});
let query = format!("SELECT EXISTS (SELECT FROM pg_tables WHERE schemaname = 'public' AND tablename = '{}');", username);
let row = match client.query_one(&query, &[]).await {
Ok(row) => row,
Err(e) => return Err(format!("查询表是否存在时出错: {}", e)),
};
let exists: bool = row.get(0);
if exists {
Ok(LoginResult {
success: true,
message: format!("用户 {} 登录成功", username),
})
} else {
let create_table_query = format!(
"CREATE TABLE \"{}\" (
id SERIAL PRIMARY KEY,
code VARCHAR(255),
time TIMESTAMP,
n2n VARCHAR(255),
q TEXT,
a TEXT,
isfeedback INTEGER DEFAULT 0,
isclose INTEGER DEFAULT 0
)",
username
);
match client.batch_execute(&create_table_query).await {
Ok(_) => Ok(LoginResult {
success: true,
message: format!("用户 {} 的表已创建", username),
}),
Err(e) => Err(format!("创建表时出错: {}", e)),
}
}
}
#[tauri::command]
fn get_cookie() -> Option<String> {
COOKIE_STORAGE.lock().unwrap().clone()
}
#[tauri::command]
async fn get_dashboard_username() -> Result<String, String> {
let cookie = COOKIE_STORAGE.lock().unwrap().clone();
if cookie.is_none() {
return Err("未找到登录Cookie".to_string());
}
let cookie = cookie.unwrap();
let client = reqwest::Client::new();
let res = client.get("http://c.baobaot.com/admin/dashboard")
.header("Cookie", cookie)
.send()
.await
.map_err(|e| format!("请求失败: {}", e))?;
if res.status().is_success() {
let body = res.text().await.map_err(|e| format!("读取响应失败: {}", e))?;
let document = Html::parse_document(&body);
let selector = Selector::parse("span.username").map_err(|_| "无效的选择器".to_string())?;
if let Some(element) = document.select(&selector).next() {
let username = element.inner_html();
Ok(username)
} else {
Err("未找到指定的元素".to_string())
}
} else {
Err(format!("请求失败,状态码: {}", res.status()))
}
}
#[tauri::command]
async fn get_workorders(range: String) -> Result<WorkOrderResponse, String> {
let username = USERNAME_STORAGE.lock().unwrap().clone().ok_or("用户未登录".to_string())?;
let db_url = "postgres://tmstools:521707@honulla.com:5432/tmstools";
let (mut client, connection) = tokio_postgres::connect(db_url, NoTls)
.await
.map_err(|e| format!("数据库连接失败: {}", e))?;
tokio::spawn(async move {
if let Err(e) = connection.await {
eprintln!("connection error: {}", e);
}
});
let query_string = if range == "month" {
format!(
"SELECT code, n2n, q, a, isfeedback, isclose FROM \"{}\" WHERE date_trunc('month', time) = date_trunc('month', current_date) ORDER BY time DESC",
username
)
} else { // Default to "today"
format!(
"SELECT code, n2n, q, a, isfeedback, isclose FROM \"{}\" WHERE time >= current_date ORDER BY time DESC",
username
)
};
let rows = client
.query(&query_string, &[])
.await
.map_err(|e| format!("查询工单失败: {}", e))?;
let orders: Vec<WorkOrder> = rows
.into_iter()
.map(|row| {
WorkOrder {
gdid: row.get(0),
n2n: row.get(1),
messageq: row.get(2),
messagea: row.get(3),
iscreate: 0, // All from DB are considered created
isfeedback: row.get(4),
isclose: row.get(5),
}
})
.collect();
let total = orders.len() as i32;
Ok(WorkOrderResponse {
orders,
total,
})
}
#[tauri::command]
async fn create_ticket_command(n2p: String, massage_q: String, wx: String) -> Result<String, String> {
let cookie = COOKIE_STORAGE.lock().unwrap().clone().ok_or("未找到登录Cookie")?;
tms_service::create_ticket(&cookie, &n2p, &massage_q, &wx)
.await
.map_err(|e| e.to_string())
}
#[tauri::command]
async fn insert_workorder(code: String, n2n: String, q: String, a: String) -> Result<(), String> {
let username = USERNAME_STORAGE.lock().unwrap().clone().ok_or("用户未登录".to_string())?;
let db_url = "postgres://tmstools:521707@honulla.com:5432/tmstools";
let (mut client, connection) = tokio_postgres::connect(db_url, NoTls)
.await
.map_err(|e| format!("数据库连接失败: {}", e))?;
tokio::spawn(async move {
if let Err(e) = connection.await {
eprintln!("connection error: {}", e);
}
});
let query = format!(
"INSERT INTO \"{}\" (code, time, n2n, q, a, isfeedback, isclose) VALUES ($1, NOW(), $2, $3, $4, 0, 0)",
username
);
client
.execute(&query, &[&code, &n2n, &q, &a])
.await
.map_err(|e| format!("插入工单失败: {}", e))?;
Ok(())
}
#[tauri::command]
async fn feedback_workorder(gdid: String, messagea: String) -> Result<(), String> {
// Step 1: Call the remote feedback service
let cookie = COOKIE_STORAGE.lock().unwrap().clone().ok_or("未找到登录Cookie")?;
tms_service::feedback(&cookie, &messagea, &gdid)
.await
.map_err(|e| format!("远程反馈失败: {}", e))?;
// Step 2: If remote feedback is successful, update the local database
let username = USERNAME_STORAGE.lock().unwrap().clone().ok_or("用户未登录".to_string())?;
let db_url = "postgres://tmstools:521707@honulla.com:5432/tmstools";
let (mut client, connection) = tokio_postgres::connect(db_url, NoTls)
.await
.map_err(|e| format!("数据库连接失败: {}", e))?;
tokio::spawn(async move {
if let Err(e) = connection.await {
eprintln!("connection error: {}", e);
}
});
let query = format!(
"UPDATE \"{}\" SET a = $1, isfeedback = 1 WHERE code = $2",
username
);
client
.execute(&query, &[&messagea, &gdid])
.await
.map_err(|e| format!("更新数据库失败: {}", e))?;
Ok(())
}
#[tauri::command]
async fn feedback_today_workorders(messagea: String) -> Result<String, String> {
let username = USERNAME_STORAGE.lock().unwrap().clone().ok_or("用户未登录".to_string())?;
let cookie = COOKIE_STORAGE.lock().unwrap().clone().ok_or("未找到登录Cookie")?;
let db_url = "postgres://tmstools:521707@honulla.com:5432/tmstools";
let (mut client, connection) = tokio_postgres::connect(db_url, NoTls)
.await
.map_err(|e| format!("数据库连接失败: {}", e))?;
tokio::spawn(async move {
if let Err(e) = connection.await {
eprintln!("connection error: {}", e);
}
});
let query_today_unfeedbacked = format!(
"SELECT code FROM \"{}\" WHERE time >= current_date AND isfeedback = 0",
username
);
let rows = client
.query(&query_today_unfeedbacked, &[])
.await
.map_err(|e| format!("查询今日未反馈工单失败: {}", e))?;
let mut success_count = 0;
let total_count = rows.len();
for row in rows {
sleep(Duration::from_millis(200)).await;
let gdid: String = row.get(0);
if !messagea.is_empty() {
match tms_service::feedback(&cookie, &messagea, &gdid).await {
Ok(_) => {
let update_query = format!(
"UPDATE \"{}\" SET isfeedback = 1, a = $1 WHERE code = $2",
username
);
if client.execute(&update_query, &[&messagea, &gdid]).await.is_ok() {
success_count += 1;
}
}
Err(_) => {
// Log or handle feedback error
}
}
}
}
Ok(format!("成功反馈 {}/{} 个工单", success_count, total_count))
}
#[tauri::command]
async fn feedback_selected_workorders(gdids: Vec<String>, messagea: String) -> Result<String, String> {
let username = USERNAME_STORAGE.lock().unwrap().clone().ok_or("用户未登录".to_string())?;
let cookie = COOKIE_STORAGE.lock().unwrap().clone().ok_or("未找到登录Cookie")?;
let db_url = "postgres://tmstools:521707@honulla.com:5432/tmstools";
let (mut client, connection) = tokio_postgres::connect(db_url, NoTls)
.await
.map_err(|e| format!("数据库连接失败: {}", e))?;
tokio::spawn(async move {
if let Err(e) = connection.await {
eprintln!("connection error: {}", e);
}
});
let mut success_count = 0;
let total_count = gdids.len();
for gdid in gdids {
sleep(Duration::from_millis(200)).await;
if !messagea.is_empty() {
match tms_service::feedback(&cookie, &messagea, &gdid).await {
Ok(_) => {
let update_query = format!(
"UPDATE \"{}\" SET isfeedback = 1, a = $1 WHERE code = $2",
username
);
if client.execute(&update_query, &[&messagea, &gdid]).await.is_ok() {
success_count += 1;
}
}
Err(_) => {
// Log or handle feedback error
}
}
}
}
Ok(format!("成功反馈 {}/{} 个工单", success_count, total_count))
}
#[tauri::command]
async fn close_workorder(gdid: String) -> Result<(), String> {
// Step 1: Call the remote close service
let cookie = COOKIE_STORAGE.lock().unwrap().clone().ok_or("未找到登录Cookie")?;
tms_service::close(&cookie, &gdid)
.await
.map_err(|e| format!("远程关闭失败: {}", e))?;
// Step 2: If remote close is successful, update the local database
let username = USERNAME_STORAGE.lock().unwrap().clone().ok_or("用户未登录".to_string())?;
let db_url = "postgres://tmstools:521707@honulla.com:5432/tmstools";
let (mut client, connection) = tokio_postgres::connect(db_url, NoTls)
.await
.map_err(|e| format!("数据库连接失败: {}", e))?;
tokio::spawn(async move {
if let Err(e) = connection.await {
eprintln!("connection error: {}", e);
}
});
let query = format!(
"UPDATE \"{}\" SET isclose = 1 WHERE code = $1",
username
);
client
.execute(&query, &[&gdid])
.await
.map_err(|e| format!("关闭工单失败: {}", e))?;
Ok(())
}
#[tauri::command]
async fn close_today_workorders() -> Result<String, String> {
let username = USERNAME_STORAGE.lock().unwrap().clone().ok_or("用户未登录".to_string())?;
let cookie = COOKIE_STORAGE.lock().unwrap().clone().ok_or("未找到登录Cookie")?;
let db_url = "postgres://tmstools:521707@honulla.com:5432/tmstools";
let (mut client, connection) = tokio_postgres::connect(db_url, NoTls)
.await
.map_err(|e| format!("数据库连接失败: {}", e))?;
tokio::spawn(async move {
if let Err(e) = connection.await {
eprintln!("connection error: {}", e);
}
});
let query_today_unclosed = format!(
"SELECT code FROM \"{}\" WHERE time >= current_date AND isclose = 0",
username
);
let rows = client
.query(&query_today_unclosed, &[])
.await
.map_err(|e| format!("查询今日未关闭工单失败: {}", e))?;
let mut success_count = 0;
let total_count = rows.len();
for row in rows {
sleep(Duration::from_millis(200)).await;
let gdid: String = row.get(0);
if tms_service::close(&cookie, &gdid).await.is_ok() {
let update_query = format!(
"UPDATE \"{}\" SET isclose = 1 WHERE code = $1",
username
);
if client.execute(&update_query, &[&gdid]).await.is_ok() {
success_count += 1;
}
}
}
Ok(format!("成功关闭 {}/{} 个工单", success_count, total_count))
}
#[tauri::command]
async fn close_selected_workorders(gdids: Vec<String>) -> Result<String, String> {
let username = USERNAME_STORAGE.lock().unwrap().clone().ok_or("用户未登录".to_string())?;
let cookie = COOKIE_STORAGE.lock().unwrap().clone().ok_or("未找到登录Cookie")?;
let db_url = "postgres://tmstools:521707@honulla.com:5432/tmstools";
let (mut client, connection) = tokio_postgres::connect(db_url, NoTls)
.await
.map_err(|e| format!("数据库连接失败: {}", e))?;
tokio::spawn(async move {
if let Err(e) = connection.await {
eprintln!("connection error: {}", e);
}
});
let mut success_count = 0;
let total_count = gdids.len();
for gdid in gdids {
sleep(Duration::from_millis(200)).await;
if tms_service::close(&cookie, &gdid).await.is_ok() {
let update_query = format!(
"UPDATE \"{}\" SET isclose = 1 WHERE code = $1",
username
);
if client.execute(&update_query, &[&gdid]).await.is_ok() {
success_count += 1;
}
}
}
Ok(format!("成功关闭 {}/{} 个工单", success_count, total_count))
}
#[tauri::command]
async fn feedback_and_close_today_workorders(messagea: String) -> Result<String, String> {
let username = USERNAME_STORAGE.lock().unwrap().clone().ok_or("用户未登录".to_string())?;
let cookie = COOKIE_STORAGE.lock().unwrap().clone().ok_or("未找到登录Cookie")?;
let db_url = "postgres://tmstools:521707@honulla.com:5432/tmstools";
let (mut client, connection) = tokio_postgres::connect(db_url, NoTls)
.await
.map_err(|e| format!("数据库连接失败: {}", e))?;
tokio::spawn(async move {
if let Err(e) = connection.await {
eprintln!("connection error: {}", e);
}
});
let query_today = format!(
"SELECT code, isfeedback, isclose FROM \"{}\" WHERE time >= current_date",
username
);
let rows = client
.query(&query_today, &[])
.await
.map_err(|e| format!("查询今日工单失败: {}", e))?;
let mut feedback_success_count = 0;
let mut close_success_count = 0;
let total_count = rows.len();
for row in rows {
sleep(Duration::from_millis(200)).await;
let gdid: String = row.get(0);
let isfeedback: i32 = row.get(1);
let isclose: i32 = row.get(2);
if isfeedback != 1 && !messagea.is_empty() {
if tms_service::feedback(&cookie, &messagea, &gdid).await.is_ok() {
let update_query = format!(
"UPDATE \"{}\" SET isfeedback = 1, a = $1 WHERE code = $2",
username
);
if client.execute(&update_query, &[&messagea, &gdid]).await.is_ok() {
feedback_success_count += 1;
}
}
}
if isclose != 1 {
if tms_service::close(&cookie, &gdid).await.is_ok() {
let update_query = format!(
"UPDATE \"{}\" SET isclose = 1 WHERE code = $1",
username
);
if client.execute(&update_query, &[&gdid]).await.is_ok() {
close_success_count += 1;
}
}
}
}
Ok(format!("成功反馈 {}/{} 个, 成功关闭 {}/{}", feedback_success_count, total_count, close_success_count, total_count))
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.invoke_handler(tauri::generate_handler![
login,
get_cookie,
get_dashboard_username,
get_workorders,
create_ticket_command,
insert_workorder,
feedback_workorder,
feedback_today_workorders,
feedback_selected_workorders,
close_workorder,
close_today_workorders,
close_selected_workorders,
feedback_and_close_today_workorders
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

7
src-tauri/src/main.rs Normal file
View File

@@ -0,0 +1,7 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
tms_tools_lib::run()
}

35
src-tauri/tauri.conf.json Normal file
View File

@@ -0,0 +1,35 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "tms_tools",
"version": "0.1.0",
"identifier": "com.tms_tools.app",
"build": {
"beforeDevCommand": "pnpm dev",
"devUrl": "http://localhost:1420",
"beforeBuildCommand": "pnpm build",
"frontendDist": "../dist"
},
"app": {
"windows": [
{
"title": "tms_tools",
"width": 800,
"height": 600
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
}
}

View File

@@ -0,0 +1,11 @@
[package]
name = "tms_service"
version = "0.1.0"
edition = "2021"
[dependencies]
reqwest = "0.12.22"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
openai = { path = "../openai" }

View File

@@ -0,0 +1,180 @@
use reqwest::cookie::Jar;
use reqwest::header;
use serde::Deserialize;
use std::sync::Arc;
#[derive(Deserialize)]
struct ApiResponse {
msg: String,
}
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
pub async fn create_ticket(cookie: &str, n2p: &str, massageQ: &str, wx: &str) -> Result<String, reqwest::Error> {
println!("cookie={},n2n={},问题={}, 微信={}", cookie, n2p, massageQ, wx);
// 1. Create a cookie jar and a single client for all requests
let jar = Arc::new(Jar::default());
let initial_url: reqwest::Url = "http://c.baobaot.com".parse().unwrap();
// The cookie string can contain multiple cookies separated by ';'.
// We need to add them one by one.
for part in cookie.split(';') {
jar.add_cookie_str(part.trim(), &initial_url);
}
let client = reqwest::Client::builder()
.cookie_provider(jar.clone())
.build()?;
println!("开始获取平台id");
// 2. Get the edit page URL
let url = format!("http://c.baobaot.com/admin/etms/n2nip?n2nip=&cinemacode={}", n2p);
let res = client.get(&url).send().await?;
let body_bytes = res.bytes().await?;
let body = String::from_utf8_lossy(&body_bytes).to_string();
println!("源码获取完成");
let system_prompt = "告诉我编辑按钮的链接只要告诉我链接的url就可以不要说其他的任何内容";
let edit_page_url = openai::ask_openai(&body, system_prompt).await?;
println!("平台id{}", edit_page_url);
// 3. Visit the edit page to get its source
let res2 = client.get(&edit_page_url).send().await?;
let body2_bytes = res2.bytes().await?;
let body2 = String::from_utf8_lossy(&body2_bytes).to_string();
// 4. Get the TMS config URL
let system_prompt2 = "告诉我TMS配置按钮的链接只要告诉我链接的url就可以不要说其他的任何内容";
let tms_config_url = openai::ask_openai(&body2, system_prompt2).await?;
println!("tms配置链接{}", tms_config_url);
// 5. Visit the TMS config URL to get the token
let res3 = client.get(&tms_config_url).send().await?;
let body3_bytes = res3.bytes().await?;
let body3 = String::from_utf8_lossy(&body3_bytes).to_string();
let system_prompt3 = "告诉我权限认证(token)的值,只要告诉我对应的值就行,不要说其他的任何内容";
let token = openai::ask_openai(&body3, system_prompt3).await?;
println!("token={}", token);
// 6. Visit the final URL to get the session cookies
let final_url = format!("https://c.baobaot.com/cinema/workorder?token={}", token);
let _final_res = client.get(&final_url).send().await?;
// 7. Now the jar should contain all necessary cookies.
// We can proceed to create the work order.
let form_data = &[
("type", "99"),
("describe", massageQ),
("input_file[]", ""),
("contact_way", "wx"),
("contact", wx),
("visit_way", "1"),
];
let post_url = "https://c.baobaot.com/cinema/workorder/ajax_create";
let res = client.post(post_url).form(form_data).send().await?;
let response_bytes = res.bytes().await?;
let response_body = String::from_utf8_lossy(&response_bytes).to_string();
match serde_json::from_str::<ApiResponse>(&response_body) {
Ok(api_response) => {
if api_response.msg != "提交成功" {
return Ok(response_body);
}
}
Err(_) => {
return Ok(response_body);
}
}
// 8. Get new cookie before fetching work order list
let final_url = format!("https://c.baobaot.com/cinema/workorder?token={}", token);
let _final_res = client.get(&final_url).send().await?;
// 9. Get the work order page to find the latest order
let workorder_url = "https://c.baobaot.com/cinema/workorder";
let workorder_res = client.get(workorder_url).send().await?;
let workorder_bytes = workorder_res.bytes().await?;
let workorder_html = String::from_utf8_lossy(&workorder_bytes).to_string();
let system_prompt_details = "找到最新的一个工单所对应的详情按钮的url地址中id的值只要告诉我对应的值就可以不要说其他任何内容";
let details_url = openai::ask_openai(&workorder_html, system_prompt_details).await?;
Ok(details_url)
}
pub async fn feedback(cookie: &str, messageA: &str, gdID: &str) -> Result<String, reqwest::Error> {
// 1. Create a cookie jar and a single client for all requests
let jar = Arc::new(Jar::default());
let initial_url: reqwest::Url = "https://c.baobaot.com".parse().unwrap();
// The cookie string can contain multiple cookies separated by ';'.
// We need to add them one by one.
for part in cookie.split(';') {
jar.add_cookie_str(part.trim(), &initial_url);
}
let client = reqwest::Client::builder()
.cookie_provider(jar.clone())
.build()?;
// 2. Prepare form data
let form_data = &[
("message", messageA),
("input_file[]", ""),
("id", gdID),
("act", "confirm_feedback"),
];
// 3. Send POST request
let post_url = "https://c.baobaot.com/admin/workorder/ajax_update";
let res = client.post(post_url).form(form_data).send().await?;
let response_bytes = res.bytes().await?;
let response_body = String::from_utf8_lossy(&response_bytes).to_string();
Ok(response_body)
}
pub async fn close(cookie: &str, gdID: &str) -> Result<String, reqwest::Error> {
// 1. Create a cookie jar and a single client for all requests
let jar = Arc::new(Jar::default());
let initial_url: reqwest::Url = "https://c.baobaot.com".parse().unwrap();
// The cookie string can contain multiple cookies separated by ';'.
// We need to add them one by one.
for part in cookie.split(';') {
jar.add_cookie_str(part.trim(), &initial_url);
}
let client = reqwest::Client::builder()
.cookie_provider(jar.clone())
.build()?;
// 2. Prepare form data
let form_data = &[
("id", gdID),
("act", "close_workorder"),
];
// 3. Send POST request
let post_url = "https://c.baobaot.com/admin/workorder/ajax_update";
let res = client.post(post_url).form(form_data).send().await?;
let response_bytes = res.bytes().await?;
let response_body = String::from_utf8_lossy(&response_bytes).to_string();
Ok(response_body)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}