fix admin video batch delete and spider91 form

This commit is contained in:
nianzhibai
2026-06-04 23:18:53 +08:00
parent abe335cea0
commit 76ae3cea7d
6 changed files with 89 additions and 23 deletions
+8 -7
View File
@@ -152,15 +152,16 @@ export function VideosPage() {
if (ids.length === 0) return;
setBatchDeleting(true);
try {
const results = await Promise.allSettled(
ids.map((id) => api.deleteVideo(id))
);
let success = 0;
let deletedSources = 0;
for (const r of results) {
if (r.status !== "fulfilled") continue;
success++;
if (r.value.deletedSource) deletedSources++;
for (const id of ids) {
try {
const result = await api.deleteVideo(id);
success++;
if (result.deletedSource) deletedSources++;
} catch {
// Keep deleting the rest of the selected videos; report aggregate failure below.
}
}
const failed = ids.length - success;
if (failed === 0) {
+16 -10
View File
@@ -1,4 +1,5 @@
import { useId } from "react";
import { ChevronDown } from "lucide-react";
import { kindLabel } from "./constants";
import * as api from "../api";
@@ -16,16 +17,21 @@ export function Spider91UploadTargetField({
return (
<div className="admin-form__row">
<label htmlFor={targetId}></label>
<select id={targetId} value={value} onChange={(e) => onChange(e.target.value)}>
<option value=""></option>
{uploadTargets.map((d) => (
<option key={d.id} value={d.id}>
{kindLabel[d.kind] ?? d.kind} · {d.name || d.id}
</option>
))}
</select>
<div className="admin-form__help">
115 123 PikPak OneDrive 91 Spider
<div className="admin-form-select-wrap">
<select
id={targetId}
className="admin-form-select"
value={value}
onChange={(e) => onChange(e.target.value)}
>
<option value=""></option>
{uploadTargets.map((d) => (
<option key={d.id} value={d.id}>
{kindLabel[d.kind] ?? d.kind} · {d.name || d.id}
</option>
))}
</select>
<ChevronDown size={15} className="admin-form-select__icon" aria-hidden="true" />
</div>
</div>
);
+1 -1
View File
@@ -264,7 +264,7 @@ export function credentialFields(kind: Kind): Array<{
key: "proxy",
label: "代理地址(可选)",
placeholder: "http://127.0.0.1:7890",
help: "仅用于 91Spider 的列表/详情请求和视频、封面下载;留空则使用服务器环境变量 HTTP_PROXY / HTTPS_PROXY 或直连。支持 http://、https://、socks5://socks5h://",
help: "支持 http://、https://、socks5://socks5h://代理",
},
];
}
+33
View File
@@ -404,6 +404,39 @@
color: var(--text-strong);
}
.admin-form-select-wrap {
position: relative;
display: block;
width: 100%;
}
.admin-form__row .admin-form-select {
appearance: none;
-webkit-appearance: none;
width: 100%;
min-height: 40px;
padding-right: 36px;
line-height: 1.2;
cursor: pointer;
}
.admin-form__row .admin-form-select::-ms-expand {
display: none;
}
.admin-form-select__icon {
position: absolute;
top: 50%;
right: 12px;
transform: translateY(-50%);
color: var(--text-faint);
pointer-events: none;
}
.admin-form-select:focus + .admin-form-select__icon {
color: var(--accent);
}
.admin-form__row--inline {
display: flex;
gap: var(--space-2);
+22 -5
View File
@@ -10,10 +10,18 @@ const driveComponentsSource = readFileSync(
new URL("../src/admin/drive/DriveComponents.tsx", import.meta.url),
"utf8"
);
const spider91UploadTargetSource = readFileSync(
new URL("../src/admin/drive/Spider91UploadTargetField.tsx", import.meta.url),
"utf8"
);
const driveFormSource = readFileSync(
new URL("../src/admin/drive/DriveForm.tsx", import.meta.url),
"utf8"
);
const adminCss = readFileSync(
new URL("../src/styles/admin.css", import.meta.url),
"utf8"
);
const apiSource = readFileSync(
new URL("../src/admin/api.ts", import.meta.url),
"utf8"
@@ -23,10 +31,7 @@ const constantsSource = readFileSync(
"utf8"
);
const combinedSource = drivesPageSource + "\n" + driveFormSource + "\n" + constantsSource + "\n" + readFileSync(
new URL("../src/admin/drive/Spider91UploadTargetField.tsx", import.meta.url),
"utf8"
);
const combinedSource = drivesPageSource + "\n" + driveFormSource + "\n" + constantsSource + "\n" + spider91UploadTargetSource;
function driveTypeOptions() {
const match = /const DRIVE_OPTIONS:\s*DriveOption\[]\s*=\s*\[([\s\S]*?)\];/.exec(
@@ -49,7 +54,7 @@ function assertDriveTypeOption(value: string, label: string) {
test("spider91 drive form does not expose advanced crawler credentials", () => {
assert.match(combinedSource, /key: "proxy"/);
assert.match(combinedSource, /label: "代理地址(可选)"/);
assert.match(combinedSource, /支持 http:\/\/、https:\/\/、socks5:\/\/socks5h:\/\//);
assert.match(combinedSource, /支持 http:\/\/、https:\/\/、socks5:\/\/socks5h:\/\/代理/);
assert.doesNotMatch(combinedSource, /target_new/);
assert.doesNotMatch(combinedSource, /crawl_hour/);
assert.doesNotMatch(combinedSource, /python_path/);
@@ -64,6 +69,18 @@ test("spider91 upload target uses explicit local-save option instead of auto tar
);
assert.doesNotMatch(combinedSource, /自动:唯一/);
assert.doesNotMatch(combinedSource, /自动模式/);
assert.doesNotMatch(combinedSource, /较早的视频会上传到该云盘根目录下的 91 Spider 文件夹/);
});
test("spider91 upload target select uses an aligned custom arrow", () => {
assert.match(spider91UploadTargetSource, /className="admin-form-select-wrap"/);
assert.match(spider91UploadTargetSource, /className="admin-form-select"/);
assert.match(spider91UploadTargetSource, /className="admin-form-select__icon"/);
assert.match(adminCss, /\.admin-form__row \.admin-form-select\s*\{[^}]*appearance\s*:\s*none/s);
assert.match(
adminCss,
/\.admin-form-select__icon\s*\{[^}]*top\s*:\s*50%[^}]*right\s*:\s*12px[^}]*transform\s*:\s*translateY\(-50%\)/s
);
});
test("drive form hides root directory id for localstorage and spider91", () => {
+9
View File
@@ -11,3 +11,12 @@ test("admin videos page uses responsive page size", () => {
assert.match(videosPageSource, /window\.matchMedia\(VIDEOS_MOBILE_QUERY\)/);
assert.match(videosPageSource, /api\.listVideos\(\{ driveId, page, size: pageSize, keyword: searchKeyword \}\)/);
});
test("admin videos batch delete runs deletions sequentially", () => {
assert.match(videosPageSource, /for \(const id of ids\) \{/);
assert.match(videosPageSource, /const result = await api\.deleteVideo\(id\);/);
assert.doesNotMatch(
videosPageSource,
/Promise\.allSettled\(\s*ids\.map\(\(id\) => api\.deleteVideo\(id\)\)\s*\)/
);
});