v0.11.0 后台 CRM 化:媒体上传/单页面 Elementor/修订/分类标签/菜单/用户/审计/设置/Dashboard v2 + dusuniot-cms MCP 服务器
This commit is contained in:
+40
-12
@@ -3,11 +3,11 @@
|
||||
* 表格 + 缩略图预览;编辑替代文本(alt)与标题。
|
||||
*/
|
||||
import { createFileRoute, useRouter } from "@tanstack/react-router";
|
||||
import { App, Button, Card, Form, Image, Input, Modal, Space, Table, Typography } from "antd";
|
||||
import { EditOutlined, SearchOutlined } from "@ant-design/icons";
|
||||
import { App, Button, Card, Form, Image, Input, Modal, Space, Table, Typography, Upload } from "antd";
|
||||
import { EditOutlined, SearchOutlined, UploadOutlined } from "@ant-design/icons";
|
||||
import { File as FileIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { adminListMedia, adminUpdateMedia } from "~/server/admin";
|
||||
import { adminListMedia, adminUpdateMedia, adminUploadMedia } from "~/server/admin";
|
||||
import { AdminEmpty, Forbidden, PageHeader, fmtDateTime, guardAdmin, tablePagination, type AdminSearch } from "~/components/admin/common";
|
||||
import type { media } from "~/db/schema";
|
||||
|
||||
@@ -74,15 +74,43 @@ function MediaPage() {
|
||||
<PageHeader
|
||||
title="媒体库"
|
||||
extra={
|
||||
<Input
|
||||
allowClear
|
||||
placeholder="搜索文件路径"
|
||||
prefix={<SearchOutlined aria-hidden />}
|
||||
value={q}
|
||||
onChange={(e) => setQ(e.target.value)}
|
||||
onPressEnter={() => updateSearch({ q })}
|
||||
style={{ width: 240 }}
|
||||
/>
|
||||
<Space>
|
||||
<Input
|
||||
allowClear
|
||||
placeholder="搜索文件路径"
|
||||
prefix={<SearchOutlined aria-hidden />}
|
||||
value={q}
|
||||
onChange={(e) => setQ(e.target.value)}
|
||||
onPressEnter={() => updateSearch({ q })}
|
||||
style={{ width: 240 }}
|
||||
/>
|
||||
{/* 上传走 base64 JSON(server fn RPC),单文件 ≤20MB;成功后刷新列表 */}
|
||||
<Upload
|
||||
multiple
|
||||
showUploadList={false}
|
||||
customRequest={async ({ file, onSuccess, onError }) => {
|
||||
try {
|
||||
const f = file as File;
|
||||
const buf = new Uint8Array(await f.arrayBuffer());
|
||||
// 分块 btoa:直接 btoa(二进制串) 在大文件下栈溢出,按 0x8000 分块
|
||||
let bin = "";
|
||||
const CHUNK = 0x8000;
|
||||
for (let i = 0; i < buf.length; i += CHUNK) bin += String.fromCharCode(...buf.subarray(i, i + CHUNK));
|
||||
const res = await adminUploadMedia({ data: { filename: f.name, mime: f.type || "", dataBase64: btoa(bin) } });
|
||||
message.success(`已上传 ${f.name}(#${res.id})`);
|
||||
onSuccess?.(res);
|
||||
await router.invalidate();
|
||||
} catch (e) {
|
||||
message.error(e instanceof Error ? e.message : "上传失败");
|
||||
onError?.(e as Error);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button type="primary" icon={<UploadOutlined aria-hidden />}>
|
||||
上传媒体
|
||||
</Button>
|
||||
</Upload>
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
<Table
|
||||
|
||||
Reference in New Issue
Block a user