Files
lgp-admin-plus/apps/web-antd/src/views/material/api/index.ts
年糕崽崽 0cdfb7e956
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Deploy Website on push / Rerun on failure (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
更新若干功能
2026-08-20 19:18:39 +08:00

229 lines
6.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { requestClient } from '#/api/request';
import { createCrudApi } from '#/components/crud';
/** 素材记录,字段与后端 material 表一致 */
export interface MaterialItem {
created_at: string;
ext: string;
folder_id: number;
height: number;
id: number;
last_scan_at: string;
name: string;
oss_config_id: number;
path: string;
/** 被业务表引用的次数0 表示可回收 */
ref_count: number;
size: number;
/** 后端存 0~6兼容历史字符串 */
type: number | string;
url: string;
width: number;
}
export interface MaterialListResult {
items: MaterialItem[];
page: number;
page_count: number;
size: number;
total: number;
}
export interface MaterialStat {
total: number;
total_size: number;
types: { count: number; type: number | string }[];
unused: number;
unused_size: number;
}
export interface MaterialSyncResult {
finished: boolean;
inserted: number;
/** 本批从 OSS 列举到的对象数;为 0 且 finished 说明前缀或 Bucket 对不上 */
listed: number;
next_marker: string;
updated: number;
}
export interface MaterialScanResult {
referenced: number;
scanned: number;
unused: number;
}
/**
* 回收失败项:契约只冻结了 failed 是数组,元素形状没定,
* 字符串和对象两种都兜住,免得后端改一次前端就白屏
*/
export type MaterialReclaimFailure =
| string
| { id?: number; name?: string; path?: string; reason?: string };
export interface MaterialReclaimResult {
deleted: number;
failed: MaterialReclaimFailure[];
}
export interface FileFolderItem {
children?: FileFolderItem[];
id: number;
name: string;
pid: number;
}
export const materialApi = createCrudApi('material');
export const fileFolderApi = createCrudApi('file-folder');
/** 素材列表folder_id / type / ext / unused 都是可选筛选项 */
export async function getMaterialList(
params: Record<string, any> = {},
): Promise<MaterialListResult> {
return await materialApi.list(params);
}
/** 顶部统计卡片数据 */
export async function getMaterialStat(): Promise<MaterialStat> {
return await requestClient.get<MaterialStat>('material/stat');
}
/**
* 单批拉取 OSS 对象finished 为假时带着 next_marker 再调一次
*
* 七牛要先查区域再列举再入库,默认 10s 必超时,这一口单独放到 10 分钟
*/
export async function syncMaterialFromOss(data: {
limit: number;
marker: string;
oss_config_id: number;
prefix: string;
}): Promise<MaterialSyncResult> {
return await requestClient.post<MaterialSyncResult>(
'material/sync-from-oss',
data,
{ timeout: 600_000 },
);
}
/** 全量扫描业务表里的图片字段,回写 ref_count */
export async function scanMaterialReferences(): Promise<MaterialScanResult> {
return await requestClient.post<MaterialScanResult>(
'material/scan-references',
{},
);
}
/** 回收建议列表days 天内没被引用过的素材 */
export async function getMaterialUnusedList(params: {
days: number;
page: number;
pageSize: number;
}): Promise<MaterialListResult> {
return await requestClient.get<MaterialListResult>('material/unused-list', {
params,
});
}
/** 真删:连 OSS 上的对象一起删,调用方必须做二次确认 */
export async function reclaimMaterials(
ids: number[],
): Promise<MaterialReclaimResult> {
return await requestClient.post<MaterialReclaimResult>('material/reclaim', {
ids,
});
}
/** 批量归档到文件夹folder_id 传 0 表示移出文件夹 */
export async function moveMaterialsToFolder(data: {
folder_id: number;
ids: number[];
}) {
return await requestClient.post<any>('material/move-to-folder', data);
}
/**
* 文件夹树
*
* option 接口可能返回扁平数据(后端未建树),这里按 pid 兜一层,
* 否则子文件夹会全部平铺在根上,层级信息白丢
*/
export async function getFileFolderTree(): Promise<FileFolderItem[]> {
const list: FileFolderItem[] = (await fileFolderApi.option()) ?? [];
if (list.length === 0 || list.some((item) => item.children)) {
return list;
}
const nodes = new Map<number, FileFolderItem>(
list.map((item) => [item.id, { ...item, children: [] }]),
);
const roots: FileFolderItem[] = [];
for (const item of nodes.values()) {
const parent = nodes.get(item.pid);
if (parent) {
parent.children?.push(item);
} else {
roots.push(item);
}
}
return roots;
}
/** 表单选上级用首位补虚拟顶级节点pid=0 表示挂在最外层 */
export async function getFileFolderTreeWithRoot(): Promise<any[]> {
return [{ id: 0, name: '顶级文件夹' }, ...(await getFileFolderTree())];
}
const SIZE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB'];
/**
* 字节转可读体积
*
* 统计卡片、素材卡片、回收列表都要展示体积,格式必须一致,
* 各处自己算会出现 1024KB 和 1MB 混着显示。
*/
export function formatSize(bytes?: null | number): string {
const value = Number(bytes ?? 0);
if (!Number.isFinite(value) || value <= 0) {
return '0 B';
}
const index = Math.min(
Math.floor(Math.log(value) / Math.log(1024)),
SIZE_UNITS.length - 1,
);
const scaled = value / 1024 ** index;
const text =
index === 0 ? String(scaled) : scaled.toFixed(scaled >= 100 ? 0 : 1);
return `${text} ${SIZE_UNITS[index]}`;
}
/** 与 FileModel::TYPE_* 对齐0 图片 / 1 视频 / 2 音频 / 3 表格 / 4 压缩包 / 5 文档 / 6 其他 */
const TYPE_LABELS: Record<string, string> = {
'0': '图片',
'1': '视频',
'2': '音频',
'3': '表格',
'4': '压缩包',
'5': '文档',
'6': '其他',
archive: '压缩包',
audio: '音频',
doc: '文档',
excel: '表格',
image: '图片',
other: '其他',
video: '视频',
};
/** 类型中文名;数字、字符串两种都认,避免拉取成功后卡片上写「未知」 */
export function materialTypeText(type?: number | string): string {
if (type === undefined || type === null || type === '') {
return '未知';
}
return TYPE_LABELS[String(type)] ?? String(type);
}
/** 只有图片能出缩略图与水印预览;后端 type=0 就是图片 */
export function isImageMaterial(item: Pick<MaterialItem, 'type'>): boolean {
return Number(item.type) === 0 || item.type === 'image';
}