1.1 Excel/CSV 导出:
- 后端 export 支持 format 参数 (json/csv/xlsx)
- rust_xlsxwriter 生成带样式 Excel
- 前端导出按钮改为 Dropdown 格式选择 (JSON/CSV/Excel)
- blob 下载支持 CSV/XLSX 二进制格式
1.2 市场后端 API + 前端对接:
- SeaORM Entity: market_entry, market_review
- API: 浏览/详情/一键安装/评论列表/提交评分
- 一键安装: upload → install → enable 一条龙 + 依赖检查
- 前端 PluginMarket 对接真实 API (搜索/分类/安装/评分)
1.3 对账扫描:
- reconcile_references() 扫描跨插件引用悬空 UUID
- POST /plugins/{plugin_id}/reconcile 端点
282 lines
7.3 KiB
TypeScript
282 lines
7.3 KiB
TypeScript
import client from './client';
|
|
|
|
export interface PluginDataRecord {
|
|
id: string;
|
|
data: Record<string, unknown>;
|
|
created_at?: string;
|
|
updated_at?: string;
|
|
version?: number;
|
|
}
|
|
|
|
interface PaginatedDataResponse {
|
|
data: PluginDataRecord[];
|
|
total: number;
|
|
page: number;
|
|
page_size: number;
|
|
total_pages: number;
|
|
}
|
|
|
|
export interface PluginDataListOptions {
|
|
filter?: Record<string, string>;
|
|
search?: string;
|
|
sort_by?: string;
|
|
sort_order?: 'asc' | 'desc';
|
|
}
|
|
|
|
export async function listPluginData(
|
|
pluginId: string,
|
|
entity: string,
|
|
page = 1,
|
|
pageSize = 20,
|
|
options?: PluginDataListOptions,
|
|
) {
|
|
const params: Record<string, string> = {
|
|
page: String(page),
|
|
page_size: String(pageSize),
|
|
};
|
|
if (options?.filter) params.filter = JSON.stringify(options.filter);
|
|
if (options?.search) params.search = options.search;
|
|
if (options?.sort_by) params.sort_by = options.sort_by;
|
|
if (options?.sort_order) params.sort_order = options.sort_order;
|
|
|
|
const { data } = await client.get<{ success: boolean; data: PaginatedDataResponse }>(
|
|
`/plugins/${pluginId}/${entity}`,
|
|
{ params },
|
|
);
|
|
return data.data;
|
|
}
|
|
|
|
export async function getPluginData(pluginId: string, entity: string, id: string) {
|
|
const { data } = await client.get<{ success: boolean; data: PluginDataRecord }>(
|
|
`/plugins/${pluginId}/${entity}/${id}`,
|
|
);
|
|
return data.data;
|
|
}
|
|
|
|
export async function createPluginData(
|
|
pluginId: string,
|
|
entity: string,
|
|
recordData: Record<string, unknown>,
|
|
) {
|
|
const { data } = await client.post<{ success: boolean; data: PluginDataRecord }>(
|
|
`/plugins/${pluginId}/${entity}`,
|
|
{ data: recordData },
|
|
);
|
|
return data.data;
|
|
}
|
|
|
|
export async function updatePluginData(
|
|
pluginId: string,
|
|
entity: string,
|
|
id: string,
|
|
recordData: Record<string, unknown>,
|
|
version: number,
|
|
) {
|
|
const { data } = await client.put<{ success: boolean; data: PluginDataRecord }>(
|
|
`/plugins/${pluginId}/${entity}/${id}`,
|
|
{ data: recordData, version },
|
|
);
|
|
return data.data;
|
|
}
|
|
|
|
export async function deletePluginData(
|
|
pluginId: string,
|
|
entity: string,
|
|
id: string,
|
|
) {
|
|
await client.delete(`/plugins/${pluginId}/${entity}/${id}`);
|
|
}
|
|
|
|
export async function countPluginData(
|
|
pluginId: string,
|
|
entity: string,
|
|
options?: { filter?: Record<string, string>; search?: string },
|
|
) {
|
|
const params: Record<string, string> = {};
|
|
if (options?.filter) params.filter = JSON.stringify(options.filter);
|
|
if (options?.search) params.search = options.search;
|
|
|
|
const { data } = await client.get<{ success: boolean; data: number }>(
|
|
`/plugins/${pluginId}/${entity}/count`,
|
|
{ params },
|
|
);
|
|
return data.data;
|
|
}
|
|
|
|
export interface AggregateItem {
|
|
key: string;
|
|
count: number;
|
|
}
|
|
|
|
export async function aggregatePluginData(
|
|
pluginId: string,
|
|
entity: string,
|
|
groupBy: string,
|
|
filter?: Record<string, string>,
|
|
) {
|
|
const params: Record<string, string> = { group_by: groupBy };
|
|
if (filter) params.filter = JSON.stringify(filter);
|
|
|
|
const { data } = await client.get<{ success: boolean; data: AggregateItem[] }>(
|
|
`/plugins/${pluginId}/${entity}/aggregate`,
|
|
{ params },
|
|
);
|
|
return data.data;
|
|
}
|
|
|
|
// ── 批量操作 ──
|
|
|
|
export async function batchPluginData(
|
|
pluginId: string,
|
|
entity: string,
|
|
req: { action: string; ids: string[]; data?: Record<string, unknown> },
|
|
) {
|
|
const { data } = await client.post<{ success: boolean; data: unknown }>(
|
|
`/plugins/${pluginId}/${entity}/batch`,
|
|
req,
|
|
);
|
|
return data.data;
|
|
}
|
|
|
|
// ── 部分更新 ──
|
|
|
|
export async function patchPluginData(
|
|
pluginId: string,
|
|
entity: string,
|
|
id: string,
|
|
req: { data: Record<string, unknown>; version: number },
|
|
) {
|
|
const { data } = await client.patch<{ success: boolean; data: PluginDataRecord }>(
|
|
`/plugins/${pluginId}/${entity}/${id}`,
|
|
req,
|
|
);
|
|
return data.data;
|
|
}
|
|
|
|
// ── 时间序列 ──
|
|
|
|
export async function getPluginTimeseries(
|
|
pluginId: string,
|
|
entity: string,
|
|
params: {
|
|
time_field: string;
|
|
time_grain: string;
|
|
start?: string;
|
|
end?: string;
|
|
},
|
|
) {
|
|
const { data } = await client.get<{ success: boolean; data: unknown }>(
|
|
`/plugins/${pluginId}/${entity}/timeseries`,
|
|
{ params },
|
|
);
|
|
return data.data;
|
|
}
|
|
|
|
// ─── 跨插件引用 API ──────────────────────────────────────────────────
|
|
|
|
export interface ResolveLabelsResult {
|
|
labels: Record<string, Record<string, string | null>>;
|
|
meta: Record<string, {
|
|
target_plugin: string;
|
|
target_entity: string;
|
|
label_field: string;
|
|
plugin_installed: boolean;
|
|
}>;
|
|
}
|
|
|
|
export async function resolveRefLabels(
|
|
pluginId: string,
|
|
entity: string,
|
|
fields: Record<string, string[]>,
|
|
): Promise<ResolveLabelsResult> {
|
|
const { data } = await client.post<{ success: boolean; data: ResolveLabelsResult }>(
|
|
`/plugins/${pluginId}/${entity}/resolve-labels`,
|
|
{ fields },
|
|
);
|
|
return data.data;
|
|
}
|
|
|
|
export interface PublicEntity {
|
|
manifest_id: string;
|
|
plugin_id: string;
|
|
entity_name: string;
|
|
display_name: string;
|
|
}
|
|
|
|
export async function getPluginEntityRegistry(): Promise<PublicEntity[]> {
|
|
const { data } = await client.get<{ success: boolean; data: PublicEntity[] }>(
|
|
'/plugin-registry/entities',
|
|
);
|
|
return data.data;
|
|
}
|
|
|
|
// ─── 数据导入导出 API ──────────────────────────────────────────────────
|
|
|
|
export interface ExportOptions {
|
|
filter?: Record<string, string>;
|
|
search?: string;
|
|
sort_by?: string;
|
|
sort_order?: 'asc' | 'desc';
|
|
format?: 'json' | 'csv' | 'xlsx';
|
|
}
|
|
|
|
export async function exportPluginData(
|
|
pluginId: string,
|
|
entity: string,
|
|
options?: ExportOptions,
|
|
): Promise<Record<string, unknown>[]> {
|
|
const params: Record<string, string> = {};
|
|
if (options?.filter) params.filter = JSON.stringify(options.filter);
|
|
if (options?.search) params.search = options.search;
|
|
if (options?.sort_by) params.sort_by = options.sort_by;
|
|
if (options?.sort_order) params.sort_order = options.sort_order;
|
|
|
|
const { data } = await client.get<{ success: boolean; data: Record<string, unknown>[] }>(
|
|
`/plugins/${pluginId}/${entity}/export`,
|
|
{ params },
|
|
);
|
|
return data.data;
|
|
}
|
|
|
|
export async function exportPluginDataAsBlob(
|
|
pluginId: string,
|
|
entity: string,
|
|
format: 'csv' | 'xlsx',
|
|
options?: Omit<ExportOptions, 'format'>,
|
|
): Promise<Blob> {
|
|
const params: Record<string, string> = { format };
|
|
if (options?.filter) params.filter = JSON.stringify(options.filter);
|
|
if (options?.search) params.search = options.search;
|
|
if (options?.sort_by) params.sort_by = options.sort_by;
|
|
if (options?.sort_order) params.sort_order = options.sort_order;
|
|
|
|
const response = await client.get(
|
|
`/plugins/${pluginId}/${entity}/export`,
|
|
{ params, responseType: 'blob' },
|
|
);
|
|
return response.data as Blob;
|
|
}
|
|
|
|
export interface ImportRowError {
|
|
row: number;
|
|
errors: string[];
|
|
}
|
|
|
|
export interface ImportResult {
|
|
success_count: number;
|
|
error_count: number;
|
|
errors: ImportRowError[];
|
|
}
|
|
|
|
export async function importPluginData(
|
|
pluginId: string,
|
|
entity: string,
|
|
rows: Record<string, unknown>[],
|
|
): Promise<ImportResult> {
|
|
const { data } = await client.post<{ success: boolean; data: ImportResult }>(
|
|
`/plugins/${pluginId}/${entity}/import`,
|
|
{ rows },
|
|
);
|
|
return data.data;
|
|
}
|