refactor: ♻️ 优化请求接口
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
import { fetch } from "@tauri-apps/plugin-http";
|
||||
import type { ClientOptions } from "@tauri-apps/plugin-http";
|
||||
|
||||
/** CRM API 根地址,从环境变量 VITE_API_BASE 读取 */
|
||||
export const API_BASE = import.meta.env.VITE_API_BASE;
|
||||
|
||||
/**
|
||||
* 请求选项类型,支持请求数据
|
||||
* @template D 请求数据类型(可选)
|
||||
*/
|
||||
export type RequestOptions<D = unknown> = RequestInit &
|
||||
ClientOptions & { data?: D };
|
||||
|
||||
/**
|
||||
* 增强的响应类型,继承 Response 并添加泛型支持
|
||||
* @template T 响应数据类型
|
||||
*/
|
||||
export interface ApiResponse<T = unknown> extends Response {
|
||||
/**
|
||||
* 解析 JSON 响应并返回指定类型的数据
|
||||
* @returns 解析后的响应数据
|
||||
*/
|
||||
json(): Promise<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 基础请求函数,支持请求类型和响应类型泛型
|
||||
* @template T 响应数据类型
|
||||
* @template D 请求数据类型(可选)
|
||||
* @param path API 路径(相对于 API_BASE,如 `/SystemUser/Login`)
|
||||
* @param options 请求配置,可包含请求数据
|
||||
* @returns 返回指定泛型类型的响应数据
|
||||
*/
|
||||
/**
|
||||
* 原始请求函数,返回增强的响应对象
|
||||
* @template T 响应数据类型
|
||||
* @template D 请求数据类型(可选)
|
||||
* @param path API 路径(相对于 API_BASE,如 `/SystemUser/Login`)
|
||||
* @param options 请求配置,可包含请求数据
|
||||
* @returns 返回增强的 ApiResponse 对象
|
||||
*/
|
||||
export async function requestRaw<T = unknown, D = unknown>(
|
||||
path: string,
|
||||
options: RequestOptions<D> = {},
|
||||
): Promise<ApiResponse<T>> {
|
||||
// 如果有 data 参数,将其序列化为 JSON 并设置到 body 中
|
||||
const { data, ...restOptions } = options;
|
||||
const requestOptions: RequestInit & ClientOptions = { ...restOptions };
|
||||
|
||||
if (data !== undefined) {
|
||||
requestOptions.body = JSON.stringify(data);
|
||||
requestOptions.headers ??= {};
|
||||
(requestOptions.headers as Record<string, string>)["Content-Type"] =
|
||||
"application/json";
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_BASE}${path}`, requestOptions);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
// 创建增强的响应对象
|
||||
const enhancedResponse = response as ApiResponse<T>;
|
||||
|
||||
// 重写 json 方法,添加泛型支持
|
||||
const originalJson = response.json.bind(response);
|
||||
enhancedResponse.json = () => originalJson() as Promise<T>;
|
||||
|
||||
return enhancedResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* 基础请求函数,支持请求类型和响应类型泛型
|
||||
* @template T 响应数据类型
|
||||
* @template D 请求数据类型(可选)
|
||||
* @param path API 路径(相对于 API_BASE,如 `/SystemUser/Login`)
|
||||
* @param options 请求配置,可包含请求数据
|
||||
* @returns 返回指定泛型类型的响应数据
|
||||
*/
|
||||
export async function request<T = unknown, D = unknown>(
|
||||
path: string,
|
||||
options: RequestOptions<D> = {},
|
||||
): Promise<T> {
|
||||
const response = await requestRaw<T, D>(path, options);
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* GET 请求辅助函数
|
||||
* @template T 响应数据类型
|
||||
* @param path API 路径
|
||||
* @param options 可选的请求配置
|
||||
* @returns 返回指定泛型类型的响应数据
|
||||
*/
|
||||
export async function get<T = unknown>(
|
||||
path: string,
|
||||
options: Omit<RequestOptions<never>, "data"> = {},
|
||||
): Promise<T> {
|
||||
return request<T>(path, { ...options, method: "GET" });
|
||||
}
|
||||
|
||||
/**
|
||||
* POST 请求辅助函数
|
||||
* @template T 响应数据类型
|
||||
* @template D 请求数据类型
|
||||
* @param path API 路径
|
||||
* @param data 请求数据
|
||||
* @param options 可选的请求配置
|
||||
* @returns 返回指定泛型类型的响应数据
|
||||
*/
|
||||
export async function post<T = unknown, D = unknown>(
|
||||
path: string,
|
||||
data?: D,
|
||||
options: Omit<RequestOptions<D>, "data"> = {},
|
||||
): Promise<T> {
|
||||
return request<T, D>(path, { ...options, method: "POST", data });
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT 请求辅助函数
|
||||
* @template T 响应数据类型
|
||||
* @template D 请求数据类型
|
||||
* @param path API 路径
|
||||
* @param data 请求数据
|
||||
* @param options 可选的请求配置
|
||||
* @returns 返回指定泛型类型的响应数据
|
||||
*/
|
||||
export async function put<T = unknown, D = unknown>(
|
||||
path: string,
|
||||
data?: D,
|
||||
options: Omit<RequestOptions<D>, "data"> = {},
|
||||
): Promise<T> {
|
||||
return request<T, D>(path, { ...options, method: "PUT", data });
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE 请求辅助函数
|
||||
* @template T 响应数据类型
|
||||
* @param path API 路径
|
||||
* @param options 可选的请求配置
|
||||
* @returns 返回指定泛型类型的响应数据
|
||||
*/
|
||||
export async function del<T = unknown>(
|
||||
path: string,
|
||||
options: Omit<RequestOptions<never>, "data"> = {},
|
||||
): Promise<T> {
|
||||
return request<T>(path, { ...options, method: "DELETE" });
|
||||
}
|
||||
|
||||
/**
|
||||
* PATCH 请求辅助函数
|
||||
* @template T 响应数据类型
|
||||
* @template D 请求数据类型
|
||||
* @param path API 路径
|
||||
* @param data 请求数据
|
||||
* @param options 可选的请求配置
|
||||
* @returns 返回指定泛型类型的响应数据
|
||||
*/
|
||||
export async function patch<T = unknown, D = unknown>(
|
||||
path: string,
|
||||
data?: D,
|
||||
options: Omit<RequestOptions<D>, "data"> = {},
|
||||
): Promise<T> {
|
||||
return request<T, D>(path, { ...options, method: "PATCH", data });
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { post, requestRaw } from "@/api/client";
|
||||
import type {
|
||||
LoginResponseData,
|
||||
WorkOrderResponseData,
|
||||
CheckStatusResponse,
|
||||
ApiResponse,
|
||||
} from "@/api/types";
|
||||
|
||||
/**
|
||||
* 登录请求
|
||||
* @param account 账号
|
||||
* @param password 密码
|
||||
* @returns 登录响应数据和会话ID
|
||||
*/
|
||||
export async function login(
|
||||
account: string,
|
||||
password: string,
|
||||
): Promise<{ data: ApiResponse<LoginResponseData>; sessionId: string }> {
|
||||
// 登录接口需要特殊处理,因为需要从响应头中获取 sessionId
|
||||
const loginData = {
|
||||
Account: account,
|
||||
PassWord: password,
|
||||
};
|
||||
|
||||
const resp = await requestRaw<
|
||||
ApiResponse<LoginResponseData>,
|
||||
typeof loginData
|
||||
>("/SystemUser/Login", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Length": String(
|
||||
new TextEncoder().encode(JSON.stringify(loginData)).length,
|
||||
),
|
||||
Host: "crm.yunvip123.com",
|
||||
"Cache-Control": "no-cache",
|
||||
Cookie: "",
|
||||
},
|
||||
credentials: "omit",
|
||||
data: loginData,
|
||||
});
|
||||
|
||||
const setCookie = resp.headers.get("set-cookie") || "";
|
||||
const match = /ASP\.NET_SessionId=([^;]+)/.exec(setCookie);
|
||||
const sessionId = match ? match[1] : "";
|
||||
|
||||
const data = await resp.json();
|
||||
return { data, sessionId };
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询待审核工单数量
|
||||
* @param sessionId 会话ID
|
||||
* @returns 工单数据响应
|
||||
*/
|
||||
export async function checkWorkOrders(
|
||||
sessionId: string,
|
||||
): Promise<ApiResponse<WorkOrderResponseData>> {
|
||||
// 请求数据类型
|
||||
const requestData = {
|
||||
isSelect: 8,
|
||||
isShow: 3,
|
||||
IsExport: 0,
|
||||
PageIndex: 1,
|
||||
PageSize: 20,
|
||||
};
|
||||
|
||||
return post<ApiResponse<WorkOrderResponseData>, typeof requestData>(
|
||||
"/DemandManage/GetWorkOrderListPage",
|
||||
requestData,
|
||||
{
|
||||
headers: {
|
||||
Cookie: `ASP.NET_SessionId=${sessionId}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送工单状态检查请求
|
||||
* @param sessionId 会话ID
|
||||
* @param userGid 用户GID
|
||||
* @returns 状态检查响应
|
||||
*/
|
||||
export async function checkStatus(
|
||||
sessionId: string,
|
||||
userGid?: string,
|
||||
): Promise<ApiResponse<CheckStatusResponse>> {
|
||||
// 请求数据类型
|
||||
const requestData: Record<string, string> = {};
|
||||
if (userGid) requestData.UserGID = userGid;
|
||||
|
||||
return post<ApiResponse<CheckStatusResponse>, typeof requestData>(
|
||||
"/DemandManage/QueryIndexCount",
|
||||
requestData,
|
||||
{
|
||||
headers: {
|
||||
Cookie: `ASP.NET_SessionId=${sessionId}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/** 通用 API 响应 */
|
||||
export interface ApiResponse<T = unknown> {
|
||||
success: boolean;
|
||||
msg: string;
|
||||
data: T;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./common";
|
||||
export * from "./monitor";
|
||||
@@ -0,0 +1,111 @@
|
||||
/** 菜单信息 */
|
||||
export interface MenuInfo {
|
||||
/** 唯一标识 */
|
||||
GID: string;
|
||||
/** 菜单名称 */
|
||||
MM_Name: string;
|
||||
/** 链接地址 */
|
||||
MM_LinkUrl: string;
|
||||
/** 父级 ID */
|
||||
MM_ParentID: string;
|
||||
/** 排序 */
|
||||
MM_Sort: number;
|
||||
/** 备注/标记 */
|
||||
MM_Mark: string | null;
|
||||
/** 图标 */
|
||||
MM_Ico: string;
|
||||
/** 资源列表 */
|
||||
ResourceList: unknown;
|
||||
}
|
||||
|
||||
/** 角色资源权限 */
|
||||
export interface SRRoleItem {
|
||||
/** 唯一标识 */
|
||||
GID: string;
|
||||
/** 菜单 GID */
|
||||
MM_GID: string;
|
||||
/** 资源名称 */
|
||||
RS_Name: string;
|
||||
/** 资源链接 */
|
||||
RS_LinkUrl: string;
|
||||
/** 是否显示 */
|
||||
RS_IsShow: number;
|
||||
/** 排序 */
|
||||
RS_Sort: number;
|
||||
/** 创建时间 */
|
||||
RS_CreateTime: string;
|
||||
/** 资源编码 */
|
||||
RS_Code: string | null;
|
||||
}
|
||||
|
||||
/** 登录 API 响应中的 data 部分 */
|
||||
export interface LoginResponseData {
|
||||
/** 用户唯一标识 GID */
|
||||
GID: string;
|
||||
/** 用户名 */
|
||||
SU_UserName: string;
|
||||
/** 账号/工号 */
|
||||
SU_Account: string;
|
||||
/** 角色名称 */
|
||||
SR_Name: string;
|
||||
/** 菜单列表 */
|
||||
MenuInfoList?: MenuInfo[];
|
||||
/** 角色权限列表 */
|
||||
SRRole?: SRRoleItem[];
|
||||
}
|
||||
|
||||
/** 检查状态接口返回的计数数据 */
|
||||
export interface CheckStatusResponse {
|
||||
PendingCount?: number;
|
||||
StaycloseCount?: number;
|
||||
ConfirmCount?: number;
|
||||
}
|
||||
|
||||
/** 工单列表项 */
|
||||
export interface WorkOrderItem {
|
||||
/** 工单 ID */
|
||||
ID: number;
|
||||
/** 需求名称/标题 */
|
||||
DL_DemandName: string;
|
||||
/** 需求内容 */
|
||||
DL_DemandContent: string;
|
||||
/** 创建人 */
|
||||
DL_Creator: string;
|
||||
/** 创建时间 */
|
||||
DL_CreateTime: string;
|
||||
/** 状态码 (如 350) */
|
||||
DL_Status: number;
|
||||
/** 优先级 */
|
||||
DL_Priority: string;
|
||||
/** 产品名称 */
|
||||
DL_ProductName: string;
|
||||
/** 指派人 */
|
||||
DL_AssignName: string | null;
|
||||
/** 关联项目 ID */
|
||||
DL_ID: number;
|
||||
/** 关联项目名称 */
|
||||
DL_ItemID: string;
|
||||
}
|
||||
|
||||
/** 工单查询接口返回的 data 部分 */
|
||||
export interface WorkOrderResponseData {
|
||||
/** 总页数 */
|
||||
PageTotal: number;
|
||||
/** 每页数量 */
|
||||
PageSize: number;
|
||||
/** 总数 */
|
||||
DataCount: number;
|
||||
/** 当前页码 */
|
||||
PageIndex: number;
|
||||
/** 工单列表 */
|
||||
DataList: WorkOrderItem[];
|
||||
}
|
||||
|
||||
/** 监测状态摘要 */
|
||||
export interface TicketCounts {
|
||||
pending: number;
|
||||
stayclose: number;
|
||||
confirm: number;
|
||||
workOrderCount: number;
|
||||
lastCheck: string;
|
||||
}
|
||||
@@ -14,9 +14,9 @@ import { TrayIcon } from "@tauri-apps/api/tray";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { openUrl } from "@tauri-apps/plugin-opener";
|
||||
import { type Ref } from "vue";
|
||||
import { type TicketCounts } from "@/api/types";
|
||||
import { CRM_URL } from "@/constants/app";
|
||||
import { useLogStore } from "@/stores/log";
|
||||
import { type TicketCounts } from "@/stores/monitor";
|
||||
import { getErrorMessage } from "@/utils/common";
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
/** CRM API 根地址 */
|
||||
export const API_BASE = "https://crm.yunvip123.com/api";
|
||||
|
||||
/** 用户登录接口 */
|
||||
export const LOGIN_URL = `${API_BASE}/SystemUser/Login`;
|
||||
|
||||
/** 工单状态计数查询接口 */
|
||||
export const CHECK_URL = `${API_BASE}/DemandManage/QueryIndexCount`;
|
||||
|
||||
/** 工单列表分页查询接口 */
|
||||
export const WORK_ORDER_URL = `${API_BASE}/DemandManage/GetWorkOrderListPage`;
|
||||
+14
-59
@@ -1,20 +1,12 @@
|
||||
import { fetch } from "@tauri-apps/plugin-http";
|
||||
import { defineStore, acceptHMRUpdate, storeToRefs } from "pinia";
|
||||
import { ref, watch } from "vue";
|
||||
import { login, checkWorkOrders, checkStatus } from "@/api/monitor";
|
||||
import { useNotification } from "@/composables/useNotification";
|
||||
import { useTray } from "@/composables/useTray";
|
||||
import { LOGIN_URL, CHECK_URL, WORK_ORDER_URL } from "@/constants/api";
|
||||
import { useAppStore } from "@/stores/app";
|
||||
import { useLogStore } from "@/stores/log";
|
||||
import { formatTime, getErrorMessage } from "@/utils/common";
|
||||
|
||||
export interface TicketCounts {
|
||||
pending: number;
|
||||
stayclose: number;
|
||||
confirm: number;
|
||||
workOrderCount: number;
|
||||
lastCheck: string;
|
||||
}
|
||||
import type { TicketCounts, CheckStatusResponse } from "@/api/types";
|
||||
|
||||
export const useMonitorStore = defineStore("monitor", () => {
|
||||
// ===== 响应式数据 =====
|
||||
@@ -55,29 +47,13 @@ export const useMonitorStore = defineStore("monitor", () => {
|
||||
/** 使用用户名/密码登录,成功后提取并存储 SessionId,返回是否成功 */
|
||||
async function apiLogin(): Promise<boolean> {
|
||||
try {
|
||||
const body = JSON.stringify({
|
||||
Account: username.value,
|
||||
PassWord: password.value,
|
||||
});
|
||||
const resp = await fetch(LOGIN_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Content-Length": String(new TextEncoder().encode(body).length),
|
||||
Host: "crm.yunvip123.com",
|
||||
"Cache-Control": "no-cache",
|
||||
Cookie: "",
|
||||
},
|
||||
body,
|
||||
credentials: "omit",
|
||||
});
|
||||
|
||||
const setCookie = resp.headers.get("set-cookie") || "";
|
||||
const match = /ASP\.NET_SessionId=([^;]+)/.exec(setCookie);
|
||||
if (match) sessionId = match[1];
|
||||
const { data, sessionId: newSessionId } = await login(
|
||||
username.value,
|
||||
password.value,
|
||||
);
|
||||
if (newSessionId) sessionId = newSessionId;
|
||||
addLog("DEBUG", `SessionId: ${sessionId || "(未提取)"}`, "LOGIN");
|
||||
|
||||
const data = await resp.json();
|
||||
if (data.success && data.data) {
|
||||
userGid = data.data.GID;
|
||||
userName = data.data.SU_UserName || "用户";
|
||||
@@ -100,21 +76,7 @@ export const useMonitorStore = defineStore("monitor", () => {
|
||||
/** 查询待审核工单数量(分页接口,取 DataCount 字段) */
|
||||
async function apiCheckWorkOrders(): Promise<number> {
|
||||
try {
|
||||
const resp = await fetch(WORK_ORDER_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Cookie: `ASP.NET_SessionId=${sessionId}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
isSelect: 8,
|
||||
isShow: 3,
|
||||
IsExport: 0,
|
||||
PageIndex: 1,
|
||||
PageSize: 20,
|
||||
}),
|
||||
});
|
||||
const result = await resp.json();
|
||||
const result = await checkWorkOrders(sessionId);
|
||||
if (result.success && result.data) {
|
||||
const count = result.data.DataCount || 0;
|
||||
addLog("INFO", `待审核检查完成 - 数据条数: ${count}`, "SCHEDULER");
|
||||
@@ -135,21 +97,14 @@ export const useMonitorStore = defineStore("monitor", () => {
|
||||
|
||||
/** 发送工单状态检查请求 */
|
||||
async function performStatusCheckRequest() {
|
||||
const body: Record<string, string> = {};
|
||||
if (userGid) body.UserGID = userGid;
|
||||
const resp = await fetch(CHECK_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Cookie: `ASP.NET_SessionId=${sessionId}`,
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
return await resp.json();
|
||||
return await checkStatus(sessionId, userGid);
|
||||
}
|
||||
|
||||
/** 处理检查到的工单数据并更新状态 */
|
||||
async function updateTicketCountsAndLog(data: any) {
|
||||
/**
|
||||
* 处理检查到的工单数据并更新状态
|
||||
* @param data 接口返回的原始计数数据
|
||||
*/
|
||||
async function updateTicketCountsAndLog(data: CheckStatusResponse) {
|
||||
const pending = data.PendingCount || 0;
|
||||
const stayclose = data.StaycloseCount || 0;
|
||||
const confirm = data.ConfirmCount || 0;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { fetch } from "@tauri-apps/plugin-http";
|
||||
import { defineStore, acceptHMRUpdate } from "pinia";
|
||||
import { ref } from "vue";
|
||||
import { LOGIN_URL } from "@/constants/api";
|
||||
import { API_BASE } from "@/api/client";
|
||||
import { useAppStore } from "@/stores/app";
|
||||
import { useLogStore } from "@/stores/log";
|
||||
import { formatTime } from "@/utils/common";
|
||||
@@ -49,7 +49,7 @@ export const useNetworkStore = defineStore("network", () => {
|
||||
if (connected) {
|
||||
try {
|
||||
const apiStart = Date.now();
|
||||
const resp = await fetch(LOGIN_URL, {
|
||||
const resp = await fetch(`${API_BASE}/SystemUser/Login`, {
|
||||
method: "HEAD",
|
||||
connectTimeout: appStore.config.network_timeout * 1000,
|
||||
});
|
||||
|
||||
Vendored
+1
@@ -2,6 +2,7 @@
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_APP_NAME: string;
|
||||
readonly VITE_API_BASE: string;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
|
||||
Reference in New Issue
Block a user