diff --git a/frontend/src/components/charactor_edit.tsx b/frontend/src/components/charactor_edit.tsx index 8217917..cff457c 100644 --- a/frontend/src/components/charactor_edit.tsx +++ b/frontend/src/components/charactor_edit.tsx @@ -1,12 +1,13 @@ import { useState } from "react"; -import { CharactorParams } from "../network/models"; +import { CharacterParams } from "../network/models"; import { network } from "../network/network"; import showToast from "./toast"; import { useTranslation } from "../utils/i18n"; +import Button from "./button"; export default function CharactorEditor({charactor, setCharactor, onDelete}: { - charactor: CharactorParams; - setCharactor: (charactor: CharactorParams) => void; + charactor: CharacterParams; + setCharactor: (charactor: CharacterParams) => void; onDelete: () => void; }) { const { t } = useTranslation(); @@ -39,7 +40,7 @@ export default function CharactorEditor({charactor, setCharactor, onDelete}: { input.click(); } - return
+ return
{ isUploading ? @@ -94,4 +95,37 @@ export default function CharactorEditor({charactor, setCharactor, onDelete}: {
; +} + +export function FetchVndbCharactersButton({vnID, onFetch}: { + vnID: string; + onFetch: (characters: CharacterParams[]) => void; +}) { + const { t } = useTranslation(); + const [isFetching, setFetching] = useState(false); + const fetchCharacters = async () => { + // validate vnID (v123456) + if (!/^v\d+$/.test(vnID)) { + showToast({ + type: "error", + message: t("Invalid VNDB ID format"), + }); + return; + } + setFetching(true); + const res = await network.getCharactersFromVNDB(vnID); + setFetching(false); + if (res.success && res.data) { + onFetch(res.data); + } else { + showToast({ + type: "error", + message: t("Failed to fetch characters from VNDB"), + }); + } + }; + + return ; } \ No newline at end of file diff --git a/frontend/src/network/models.ts b/frontend/src/network/models.ts index 2a33747..8ea005f 100644 --- a/frontend/src/network/models.ts +++ b/frontend/src/network/models.ts @@ -49,10 +49,10 @@ export interface CreateResourceParams { images: number[]; gallery: number[]; gallery_nsfw: number[]; - charactors: CharactorParams[]; + characters: CharacterParams[]; } -export interface CharactorParams { +export interface CharacterParams { name: string; alias: string[]; cv: string; @@ -96,7 +96,7 @@ export interface ResourceDetails { related: Resource[]; gallery: number[]; galleryNsfw: number[]; - charactors: CharactorParams[]; + charactors: CharacterParams[]; } export interface Storage { diff --git a/frontend/src/network/network.ts b/frontend/src/network/network.ts index 1a0cd9d..363ef98 100644 --- a/frontend/src/network/network.ts +++ b/frontend/src/network/network.ts @@ -21,6 +21,7 @@ import { CommentWithRef, Collection, Statistics, + CharacterParams, } from "./models.ts"; class Network { @@ -802,6 +803,14 @@ class Network { axios.get(`${this.apiBaseUrl}/config/statistics`), ); } + + async getCharactersFromVNDB(vnID: string): Promise> { + return this._callApi(() => + axios.get(`${this.apiBaseUrl}/resource/vndb/characters`, { + params: { vnid: vnID }, + }), + ); + } } export const network = new Network(); diff --git a/frontend/src/pages/edit_resource_page.tsx b/frontend/src/pages/edit_resource_page.tsx index 14b94a6..05bcba0 100644 --- a/frontend/src/pages/edit_resource_page.tsx +++ b/frontend/src/pages/edit_resource_page.tsx @@ -6,7 +6,7 @@ import { MdDelete, MdOutlineInfo, } from "react-icons/md"; -import { CharactorParams, Tag } from "../network/models.ts"; +import { CharacterParams, Tag } from "../network/models.ts"; import { network } from "../network/network.ts"; import { useNavigate, useParams } from "react-router"; import showToast from "../components/toast.ts"; @@ -20,7 +20,7 @@ import { SelectAndUploadImageButton, UploadClipboardImageButton, } from "../components/image_selector.tsx"; -import CharactorEditor from "../components/charactor_edit.tsx"; +import CharactorEditor, { FetchVndbCharactersButton } from "../components/charactor_edit.tsx"; export default function EditResourcePage() { const [title, setTitle] = useState(""); @@ -31,7 +31,7 @@ export default function EditResourcePage() { const [links, setLinks] = useState<{ label: string; url: string }[]>([]); const [galleryImages, setGalleryImages] = useState([]); const [galleryNsfw, setGalleryNsfw] = useState([]); - const [charactors, setCharactors] = useState([]); + const [charactors, setCharactors] = useState([]); const [error, setError] = useState(null); const [isSubmitting, setSubmitting] = useState(false); const [isLoading, setLoading] = useState(true); @@ -107,7 +107,7 @@ export default function EditResourcePage() { links: links, gallery: galleryImages, gallery_nsfw: galleryNsfw, - charactors: charactors, + characters: charactors, }); if (res.success) { setSubmitting(false); @@ -444,9 +444,9 @@ export default function EditResourcePage() { }) } -
+
+ { + links.find(link => link.label.toLowerCase() === "vndb") && +
+ link.label.toLowerCase() === "vndb")?.url.split("/").pop() ?? ""} + onFetch={(fetchedCharacters) => { + setCharactors(fetchedCharacters); + }} + /> +
+ }
{error && ( diff --git a/frontend/src/pages/publish_page.tsx b/frontend/src/pages/publish_page.tsx index 34e1124..12186f3 100644 --- a/frontend/src/pages/publish_page.tsx +++ b/frontend/src/pages/publish_page.tsx @@ -6,7 +6,7 @@ import { MdDelete, MdOutlineInfo, } from "react-icons/md"; -import { CharactorParams, Tag } from "../network/models.ts"; +import { CharacterParams, Tag } from "../network/models.ts"; import { network } from "../network/network.ts"; import { useNavigate } from "react-router"; import { useTranslation } from "../utils/i18n"; @@ -32,7 +32,7 @@ export default function PublishPage() { const [galleryNsfw, setGalleryNsfw] = useState([]); const [error, setError] = useState(null); const [isSubmitting, setSubmitting] = useState(false); - const [charactors, setCharactors] = useState([]); + const [charactors, setCharactors] = useState([]); const isFirstLoad = useRef(true); useEffect(() => { @@ -111,7 +111,7 @@ export default function PublishPage() { links: links, gallery: galleryImages, gallery_nsfw: galleryNsfw, - charactors: charactors, + characters: charactors, }); if (res.success) { localStorage.removeItem("publish_data"); diff --git a/frontend/src/pages/resource_details_page.tsx b/frontend/src/pages/resource_details_page.tsx index afd903c..e8aeedc 100644 --- a/frontend/src/pages/resource_details_page.tsx +++ b/frontend/src/pages/resource_details_page.tsx @@ -18,7 +18,7 @@ import { Tag, Resource, Collection, - CharactorParams, + CharacterParams, } from "../network/models.ts"; import { network } from "../network/network.ts"; import showToast from "../components/toast.ts"; @@ -1931,6 +1931,9 @@ function Gallery({ images, nsfw }: { images: number[], nsfw: number[] }) { nsfw = []; } + // 如果图片数量超过8张,显示数字而不是圆点 + const showDots = images.length <= 8; + return ( <> )} - {/* 底部圆点 */} + {/* 底部指示器 */} {images.length > 1 && ( -
- {images.map((_, index) => ( -
+ ) : ( + /* 数字指示器 */ +
+ {currentIndex + 1} / {images.length} +
+ )} )} @@ -2065,7 +2078,7 @@ function GalleryImage({src, nfsw}: {src: string, nfsw: boolean}) { ); } -function Characters({ charactors }: { charactors: CharactorParams[] }) { +function Characters({ charactors }: { charactors: CharacterParams[] }) { const { t } = useTranslation(); if (!charactors || charactors.length === 0) { @@ -2084,7 +2097,7 @@ function Characters({ charactors }: { charactors: CharactorParams[] }) { ); } -function CharacterCard({ charactor }: { charactor: CharactorParams }) { +function CharacterCard({ charactor }: { charactor: CharacterParams }) { const navigate = useNavigate(); const handleCVClick = (e: React.MouseEvent) => { diff --git a/server/service/resource.go b/server/service/resource.go index 5ff9ad3..cb83739 100644 --- a/server/service/resource.go +++ b/server/service/resource.go @@ -35,7 +35,7 @@ type ResourceParams struct { Images []uint `json:"images"` Gallery []uint `json:"gallery"` GalleryNsfw []uint `json:"gallery_nsfw"` - Charactors []CharactorParams `json:"charactors"` + Charactors []CharactorParams `json:"characters"` } type CharactorParams struct {