mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-29 11:36:36 +08:00
feat: add clouddrive2 storage bridge
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { api } from './client'
|
||||
|
||||
export type StorageType = 'alist' | 's3' | 'webdav' | 'cloud115' | 'quark'
|
||||
export type StorageType = 'alist' | 's3' | 'webdav' | 'cloud115' | 'quark' | 'clouddrive2'
|
||||
|
||||
export interface CloudEntry {
|
||||
id: string
|
||||
|
||||
+167
-54
@@ -5,7 +5,7 @@ import toast from 'react-hot-toast'
|
||||
import {
|
||||
Activity, Bell, Clock, CloudDownload, Compass, Film,
|
||||
Cast, Globe, HardDrive, Heart, Home, Image, KeySquare,
|
||||
ListMusic, LogOut, Rss, Search,
|
||||
ListMusic, LogOut, Rss, Search, Trash2,
|
||||
Settings, Sliders, Sparkles, UserCog,
|
||||
Library as LibraryIcon, User as UserIcon, ChevronDown, Menu, X
|
||||
} from 'lucide-react'
|
||||
@@ -32,6 +32,7 @@ export function Layout() {
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState(true)
|
||||
const [isMobileDrawerOpen, setIsMobileDrawerOpen] = useState(false)
|
||||
const [isProfileOpen, setIsProfileOpen] = useState(false)
|
||||
const [openGroups, setOpenGroups] = useState<Record<string, boolean>>({ media: true })
|
||||
const [profiles, setProfiles] = useState<PlayProfile[]>([])
|
||||
const [searchFocused, setSearchFocused] = useState(false)
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
@@ -82,6 +83,26 @@ export function Layout() {
|
||||
const isAdmin = user?.role === 'admin'
|
||||
const can = (key: string) => isAdmin || isSuper || (permissions ?? {})[key] === true
|
||||
const activeProfile = profiles.find((p) => p.id === activeProfileId) ?? null
|
||||
const sidebarExpanded = isSidebarOpen || isMobileDrawerOpen
|
||||
const isRouteIn = (paths: string[]) =>
|
||||
paths.some((path) => (path === '/' ? location.pathname === '/' : location.pathname.startsWith(path)))
|
||||
const toggleGroup = (key: string) =>
|
||||
setOpenGroups((current) => ({ ...current, [key]: !current[key] }))
|
||||
|
||||
useEffect(() => {
|
||||
const groupPaths: Record<string, string[]> = {
|
||||
media: ['/', '/libraries', '/library', '/poster-wall', '/discover', '/search', '/dlna', '/ai'],
|
||||
personal: ['/favourites', '/playlists', '/playlist', '/history', '/profile', '/play-profiles'],
|
||||
downloads: ['/downloads', '/download-clients', '/subscriptions', '/site-search'],
|
||||
tools: ['/storage', '/storage-config', '/files', '/strm', '/duplicates', '/tasks', '/scheduler', '/recycle', '/stats'],
|
||||
system: ['/admin', '/sites', '/notify-channels', '/license', '/settings', '/assistant'],
|
||||
}
|
||||
const active = Object.entries(groupPaths).find(([, paths]) => isRouteIn(paths))?.[0]
|
||||
if (active) {
|
||||
setOpenGroups((current) => (current[active] ? current : { ...current, [active]: true }))
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [location.pathname])
|
||||
|
||||
const handleSearchSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
@@ -150,56 +171,97 @@ export function Layout() {
|
||||
</div>
|
||||
|
||||
{/* Navigation List */}
|
||||
<nav className="flex-1 overflow-y-auto px-4 py-6 space-y-6 scrollbar-hide">
|
||||
{/* Navigation Group: Main */}
|
||||
<div>
|
||||
<SectionHeader label="影音中心" visible={isSidebarOpen || isMobileDrawerOpen} />
|
||||
<div className="space-y-1">
|
||||
<SidebarLink to="/" icon={<Home size={18} />} label="系统首页" end collapsed={!isSidebarOpen && !isMobileDrawerOpen} />
|
||||
<SidebarLink to="/libraries" icon={<LibraryIcon size={18} />} label="媒体库" collapsed={!isSidebarOpen && !isMobileDrawerOpen} />
|
||||
<SidebarLink to="/poster-wall" icon={<Image size={18} />} label="海报墙" collapsed={!isSidebarOpen && !isMobileDrawerOpen} />
|
||||
{can('can_view_discover') && <SidebarLink to="/discover" icon={<Compass size={18} />} label="精彩发现" collapsed={!isSidebarOpen && !isMobileDrawerOpen} />}
|
||||
{can('can_use_ai') && <SidebarLink to="/search" icon={<Search size={18} />} label="智能搜索" collapsed={!isSidebarOpen && !isMobileDrawerOpen} />}
|
||||
{can('can_cast') && <SidebarLink to="/dlna" icon={<Cast size={18} />} label="DLNA 投屏" collapsed={!isSidebarOpen && !isMobileDrawerOpen} />}
|
||||
{can('can_use_ai_assistant') && <SidebarLink to="/ai" icon={<Sparkles size={18} />} label="AI 助理" collapsed={!isSidebarOpen && !isMobileDrawerOpen} />}
|
||||
</div>
|
||||
</div>
|
||||
<nav className="flex-1 overflow-y-auto px-4 py-5 space-y-2 scrollbar-hide">
|
||||
<SidebarGroup
|
||||
id="media"
|
||||
icon={<Home size={18} />}
|
||||
label="影音中心"
|
||||
collapsed={!sidebarExpanded}
|
||||
open={openGroups.media ?? true}
|
||||
active={isRouteIn(['/', '/libraries', '/library', '/poster-wall', '/discover', '/search', '/dlna', '/ai'])}
|
||||
onToggle={toggleGroup}
|
||||
>
|
||||
<SidebarLink to="/" icon={<Home size={16} />} label="系统首页" end child />
|
||||
<SidebarLink to="/libraries" icon={<LibraryIcon size={16} />} label="媒体库" child />
|
||||
<SidebarLink to="/poster-wall" icon={<Image size={16} />} label="海报墙" child />
|
||||
{can('can_view_discover') && <SidebarLink to="/discover" icon={<Compass size={16} />} label="精彩发现" child />}
|
||||
{can('can_use_ai') && <SidebarLink to="/search" icon={<Search size={16} />} label="智能搜索" child />}
|
||||
{can('can_cast') && <SidebarLink to="/dlna" icon={<Cast size={16} />} label="DLNA 投屏" child />}
|
||||
{can('can_use_ai_assistant') && <SidebarLink to="/ai" icon={<Sparkles size={16} />} label="AI 助理" child />}
|
||||
</SidebarGroup>
|
||||
|
||||
{/* Navigation Group: Personal */}
|
||||
<div>
|
||||
<SectionHeader label="个人空间" visible={isSidebarOpen || isMobileDrawerOpen} />
|
||||
<div className="space-y-1">
|
||||
<SidebarLink to="/favourites" icon={<Heart size={18} />} label="我的收藏" collapsed={!isSidebarOpen && !isMobileDrawerOpen} />
|
||||
<SidebarLink to="/playlists" icon={<ListMusic size={18} />} label="播放列表" collapsed={!isSidebarOpen && !isMobileDrawerOpen} />
|
||||
<SidebarLink to="/history" icon={<Clock size={18} />} label="观看历史" collapsed={!isSidebarOpen && !isMobileDrawerOpen} />
|
||||
<SidebarLink to="/profile" icon={<UserIcon size={18} />} label="账号信息" collapsed={!isSidebarOpen && !isMobileDrawerOpen} />
|
||||
</div>
|
||||
</div>
|
||||
<SidebarGroup
|
||||
id="personal"
|
||||
icon={<UserIcon size={18} />}
|
||||
label="个人空间"
|
||||
collapsed={!sidebarExpanded}
|
||||
open={openGroups.personal ?? false}
|
||||
active={isRouteIn(['/favourites', '/playlists', '/playlist', '/history', '/profile', '/play-profiles'])}
|
||||
onToggle={toggleGroup}
|
||||
>
|
||||
<SidebarLink to="/favourites" icon={<Heart size={16} />} label="我的收藏" child />
|
||||
<SidebarLink to="/playlists" icon={<ListMusic size={16} />} label="播放列表" child />
|
||||
<SidebarLink to="/history" icon={<Clock size={16} />} label="观看历史" child />
|
||||
<SidebarLink to="/profile" icon={<UserIcon size={16} />} label="账号信息" child />
|
||||
<SidebarLink to="/play-profiles" icon={<UserCog size={16} />} label="观影 Profile" child />
|
||||
</SidebarGroup>
|
||||
|
||||
{/* Navigation Group: Automation */}
|
||||
{(can('can_manage_downloads') || can('can_manage_subscriptions') || can('can_manage_sites')) && <div>
|
||||
<SectionHeader label="下载订阅" visible={isSidebarOpen || isMobileDrawerOpen} />
|
||||
<div className="space-y-1">
|
||||
{can('can_manage_downloads') && <SidebarLink to="/downloads" icon={<CloudDownload size={18} />} label="下载中心" collapsed={!isSidebarOpen && !isMobileDrawerOpen} />}
|
||||
{isAdmin && <SidebarLink to="/download-clients" icon={<Sliders size={18} />} label="下载器管理" collapsed={!isSidebarOpen && !isMobileDrawerOpen} />}
|
||||
{can('can_manage_subscriptions') && <SidebarLink to="/subscriptions" icon={<Rss size={18} />} label="订阅管理" collapsed={!isSidebarOpen && !isMobileDrawerOpen} />}
|
||||
{can('can_manage_sites') && <SidebarLink to="/site-search" icon={<Search size={18} />} label="站点检索" collapsed={!isSidebarOpen && !isMobileDrawerOpen} />}
|
||||
</div>
|
||||
</div>}
|
||||
{(can('can_manage_downloads') || can('can_manage_subscriptions') || can('can_manage_sites')) && (
|
||||
<SidebarGroup
|
||||
id="downloads"
|
||||
icon={<CloudDownload size={18} />}
|
||||
label="下载订阅"
|
||||
collapsed={!sidebarExpanded}
|
||||
open={openGroups.downloads ?? false}
|
||||
active={isRouteIn(['/downloads', '/download-clients', '/subscriptions', '/site-search'])}
|
||||
onToggle={toggleGroup}
|
||||
>
|
||||
{can('can_manage_downloads') && <SidebarLink to="/downloads" icon={<CloudDownload size={16} />} label="下载中心" child />}
|
||||
{can('can_manage_subscriptions') && <SidebarLink to="/subscriptions" icon={<Rss size={16} />} label="订阅管理" child />}
|
||||
{can('can_manage_sites') && <SidebarLink to="/site-search" icon={<Search size={16} />} label="站点检索" child />}
|
||||
{isAdmin && <SidebarLink to="/download-clients" icon={<Sliders size={16} />} label="下载器管理" child />}
|
||||
</SidebarGroup>
|
||||
)}
|
||||
|
||||
{/* Navigation Group: Admin Dashboard */}
|
||||
{isAdmin && (
|
||||
<div>
|
||||
<SectionHeader label="统一管理" visible={isSidebarOpen || isMobileDrawerOpen} />
|
||||
<div className="space-y-1">
|
||||
<SidebarLink to="/admin" icon={<Settings size={18} />} label="媒体与用户" collapsed={!isSidebarOpen && !isMobileDrawerOpen} />
|
||||
<SidebarLink to="/sites" icon={<Globe size={18} />} label="站点与下载器" collapsed={!isSidebarOpen && !isMobileDrawerOpen} />
|
||||
<SidebarLink to="/storage" icon={<HardDrive size={18} />} label="存储与文件" collapsed={!isSidebarOpen && !isMobileDrawerOpen} />
|
||||
<SidebarLink to="/stats" icon={<Activity size={18} />} label="运行状态" collapsed={!isSidebarOpen && !isMobileDrawerOpen} />
|
||||
<SidebarLink to="/license" icon={<KeySquare size={18} />} label="授权许可" collapsed={!isSidebarOpen && !isMobileDrawerOpen} />
|
||||
<SidebarLink to="/settings" icon={<Sliders size={18} />} label="系统设置" collapsed={!isSidebarOpen && !isMobileDrawerOpen} />
|
||||
</div>
|
||||
</div>
|
||||
<>
|
||||
<SidebarGroup
|
||||
id="tools"
|
||||
icon={<HardDrive size={18} />}
|
||||
label="存储工具"
|
||||
collapsed={!sidebarExpanded}
|
||||
open={openGroups.tools ?? false}
|
||||
active={isRouteIn(['/storage', '/storage-config', '/files', '/strm', '/duplicates', '/tasks', '/scheduler', '/recycle', '/stats'])}
|
||||
onToggle={toggleGroup}
|
||||
>
|
||||
<SidebarLink to="/storage" icon={<HardDrive size={16} />} label="存储与文件" child />
|
||||
<SidebarLink to="/storage-config" icon={<CloudDownload size={16} />} label="外部存储" child />
|
||||
<SidebarLink to="/files" icon={<LibraryIcon size={16} />} label="文件管理" child />
|
||||
<SidebarLink to="/strm" icon={<Cast size={16} />} label="STRM 管理" child />
|
||||
<SidebarLink to="/duplicates" icon={<Image size={16} />} label="重复文件" child />
|
||||
<SidebarLink to="/tasks" icon={<Activity size={16} />} label="任务队列" child />
|
||||
<SidebarLink to="/scheduler" icon={<Clock size={16} />} label="计划任务" child />
|
||||
<SidebarLink to="/recycle" icon={<Trash2 size={16} />} label="回收站" child />
|
||||
<SidebarLink to="/stats" icon={<Activity size={16} />} label="运行状态" child />
|
||||
</SidebarGroup>
|
||||
|
||||
<SidebarGroup
|
||||
id="system"
|
||||
icon={<Settings size={18} />}
|
||||
label="系统管理"
|
||||
collapsed={!sidebarExpanded}
|
||||
open={openGroups.system ?? false}
|
||||
active={isRouteIn(['/admin', '/sites', '/notify-channels', '/license', '/settings', '/assistant'])}
|
||||
onToggle={toggleGroup}
|
||||
>
|
||||
<SidebarLink to="/admin" icon={<Settings size={16} />} label="媒体与用户" child />
|
||||
<SidebarLink to="/sites" icon={<Globe size={16} />} label="站点管理" child />
|
||||
<SidebarLink to="/notify-channels" icon={<Bell size={16} />} label="通知渠道" child />
|
||||
<SidebarLink to="/assistant" icon={<Sparkles size={16} />} label="AI 会话" child />
|
||||
<SidebarLink to="/license" icon={<KeySquare size={16} />} label="授权许可" child />
|
||||
<SidebarLink to="/settings" icon={<Sliders size={16} />} label="系统设置" child />
|
||||
</SidebarGroup>
|
||||
</>
|
||||
)}
|
||||
</nav>
|
||||
|
||||
@@ -469,13 +531,62 @@ export function Layout() {
|
||||
)
|
||||
}
|
||||
|
||||
function SectionHeader({ label, visible }: { label: string; visible: boolean }) {
|
||||
if (!visible) return <div className="h-5" />;
|
||||
interface SidebarGroupProps {
|
||||
id: string;
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
children: React.ReactNode;
|
||||
collapsed?: boolean;
|
||||
open?: boolean;
|
||||
active?: boolean;
|
||||
onToggle: (id: string) => void;
|
||||
}
|
||||
|
||||
function SidebarGroup({ id, icon, label, children, collapsed, open, active, onToggle }: SidebarGroupProps) {
|
||||
return (
|
||||
<div className="px-4 mb-2 mt-4">
|
||||
<span className="text-[10px] font-bold uppercase tracking-[0.25em] text-gray-500">
|
||||
{label}
|
||||
</span>
|
||||
<div className="space-y-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onToggle(id)}
|
||||
className={clsx(
|
||||
'group relative flex w-full items-center gap-3.5 rounded-xl px-4 py-3 text-sm font-bold transition-all duration-300',
|
||||
active ? 'bg-gray-950 text-white shadow-sm' : 'text-gray-500 hover:bg-gray-50 hover:text-gray-900',
|
||||
collapsed && 'justify-center px-0',
|
||||
)}
|
||||
>
|
||||
<span className={clsx('flex h-5 w-5 shrink-0 items-center justify-center', active ? 'text-[#c9954a]' : 'text-gray-500 group-hover:text-gray-700')}>
|
||||
{icon}
|
||||
</span>
|
||||
{!collapsed && (
|
||||
<>
|
||||
<span className="flex-1 truncate text-left">{label}</span>
|
||||
<ChevronDown
|
||||
size={14}
|
||||
className={clsx('transition-transform duration-200', open && 'rotate-180')}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{collapsed && (
|
||||
<div className="absolute left-full ml-3 rounded-xl bg-gray-900 px-2.5 py-1.5 text-xs font-semibold text-white opacity-0 shadow-lg transition-opacity group-hover:opacity-100">
|
||||
{label}
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
<AnimatePresence initial={false}>
|
||||
{!collapsed && open && (
|
||||
<motion.div
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
animate={{ height: 'auto', opacity: 1 }}
|
||||
exit={{ height: 0, opacity: 0 }}
|
||||
transition={{ duration: 0.18, ease: 'easeOut' }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<div className="space-y-1 pb-1 pl-3">
|
||||
{children}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -486,9 +597,10 @@ interface SidebarLinkProps {
|
||||
label: string;
|
||||
end?: boolean;
|
||||
collapsed?: boolean;
|
||||
child?: boolean;
|
||||
}
|
||||
|
||||
function SidebarLink({ to, icon, label, end, collapsed }: SidebarLinkProps) {
|
||||
function SidebarLink({ to, icon, label, end, collapsed, child }: SidebarLinkProps) {
|
||||
return (
|
||||
<NavLink
|
||||
to={to}
|
||||
@@ -496,6 +608,7 @@ function SidebarLink({ to, icon, label, end, collapsed }: SidebarLinkProps) {
|
||||
className={({ isActive }) =>
|
||||
clsx(
|
||||
"flex items-center gap-3.5 rounded-xl px-4 py-3 text-sm font-semibold transition-all duration-300 relative group",
|
||||
child && "py-2.5 text-[13px]",
|
||||
isActive
|
||||
? "bg-[#111827] text-white shadow-sm"
|
||||
: "text-gray-500 hover:bg-gray-50 hover:text-gray-900"
|
||||
|
||||
@@ -291,7 +291,7 @@ const GROUPS: SettingGroup[] = [
|
||||
{
|
||||
key: 'cloud-upload',
|
||||
label: '网盘转存',
|
||||
description: '把本地媒体复制上传到外部存储。推荐:将 115/夸克挂载到 Alist 后,使用 Alist 作为转存目标。',
|
||||
description: '把本地媒体复制上传到外部存储。推荐:将 115、123、夸克等挂载到 CloudDrive2 或 Alist 后,使用桥接目标转存。',
|
||||
items: [
|
||||
{
|
||||
key: 'cloud.upload_auto_enabled',
|
||||
@@ -306,7 +306,8 @@ const GROUPS: SettingGroup[] = [
|
||||
type: 'select',
|
||||
defaultValue: 'alist',
|
||||
options: [
|
||||
{ value: 'alist', label: 'Alist(推荐,可桥接 115/夸克)' },
|
||||
{ value: 'clouddrive2', label: 'CloudDrive2(推荐,可桥接 115/123/阿里/夸克)' },
|
||||
{ value: 'alist', label: 'Alist(可桥接多网盘)' },
|
||||
{ value: 'webdav', label: 'WebDAV' },
|
||||
{ value: 'cloud115', label: '115 原生(待接分片上传)' },
|
||||
{ value: 'quark', label: '夸克原生(待接分片上传)' },
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
type StorageType,
|
||||
} from '../api/storage_config'
|
||||
|
||||
const CLOUD_TYPES: StorageType[] = ['cloud115', 'quark']
|
||||
const CLOUD_TYPES: StorageType[] = ['cloud115', 'quark', 'clouddrive2']
|
||||
const isCloud = (t: StorageType) => CLOUD_TYPES.includes(t)
|
||||
const TYPE_LABEL: Record<string, string> = {
|
||||
alist: 'ALIST',
|
||||
@@ -18,6 +18,7 @@ const TYPE_LABEL: Record<string, string> = {
|
||||
s3: 'S3',
|
||||
cloud115: '115网盘',
|
||||
quark: '夸克网盘',
|
||||
clouddrive2: 'CloudDrive2',
|
||||
}
|
||||
|
||||
// StorageConfigPage manages the Alist / S3 / WebDAV adapters used by
|
||||
@@ -34,13 +35,13 @@ export function StorageConfigPage() {
|
||||
<div>
|
||||
<h1 className="font-display text-3xl font-bold text-ink-600">外部存储</h1>
|
||||
<p className="text-sm text-ink-50">
|
||||
配置 Alist / S3 / WebDAV / 网盘(115 / 夸克)后端,Cookie 加密存储 + 在线测试,网盘资源通过 302 直链播放
|
||||
配置 Alist / S3 / WebDAV / CloudDrive2 / 网盘(115 / 夸克)后端,支持本地转存、网盘挂载和 302/反代播放
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 border-b border-gray-200">
|
||||
{(['alist', 'webdav', 's3', 'cloud115', 'quark'] as StorageType[]).map((t) => (
|
||||
{(['alist', 'webdav', 'clouddrive2', 's3', 'cloud115', 'quark'] as StorageType[]).map((t) => (
|
||||
<button
|
||||
key={t}
|
||||
onClick={() => setActive(t)}
|
||||
@@ -87,6 +88,13 @@ const FIELD_DEFS: Record<StorageType, { key: string; label: string; secret?: boo
|
||||
{ key: 'cookie', label: 'Cookie(从 pan.quark.cn 复制整段)', secret: true, placeholder: '__pus=...; __kp=...; kps=...' },
|
||||
{ key: 'force_302', label: '强制 302 直链(true/false,默认反代)' },
|
||||
],
|
||||
clouddrive2: [
|
||||
{ key: 'url', label: 'CloudDrive2 WebDAV URL', placeholder: 'http://host.docker.internal:19798/dav 或 http://NAS-IP:19798/dav' },
|
||||
{ key: 'username', label: '用户名' },
|
||||
{ key: 'password', label: '密码 / Token', secret: true },
|
||||
{ key: 'token', label: 'Authorization Token(可选)', secret: true, placeholder: 'Bearer ... 或 Basic ...' },
|
||||
{ key: 'force_302', label: '强制 302 直链(true/false,默认反代)' },
|
||||
],
|
||||
}
|
||||
|
||||
function StorageForm({ type }: { type: StorageType }) {
|
||||
@@ -226,11 +234,11 @@ function StorageUploadPanel({ type }: { type: StorageType }) {
|
||||
const [overwrite, setOverwrite] = useState(false)
|
||||
const [busy, setBusy] = useState(false)
|
||||
|
||||
const supported = type === 'alist' || type === 'webdav'
|
||||
const supported = type === 'alist' || type === 'webdav' || type === 'clouddrive2'
|
||||
|
||||
const submit = async () => {
|
||||
if (!supported) {
|
||||
toast.error('本地直传目前支持 Alist / WebDAV。115/夸克建议先挂载到 Alist,再通过 Alist 转存。')
|
||||
toast.error('本地直传目前支持 Alist / WebDAV / CloudDrive2。115/123/夸克建议通过 CloudDrive2 或 Alist 桥接后转存。')
|
||||
return
|
||||
}
|
||||
if (!sourcePath.trim()) {
|
||||
@@ -274,7 +282,12 @@ function StorageUploadPanel({ type }: { type: StorageType }) {
|
||||
</div>
|
||||
{!supported && (
|
||||
<p className="mb-3 rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800">
|
||||
115 / 夸克原生上传需要私有分片上传协议。本版本先支持 Alist / WebDAV:把 115 或夸克挂到 Alist 后,选择 Alist 即可把本地文件转存到对应网盘。
|
||||
115 / 夸克原生上传需要私有分片上传协议。推荐把 115、123、夸克等挂载到 CloudDrive2 或 Alist 后,在这里选择 CloudDrive2 / Alist 转存。
|
||||
</p>
|
||||
)}
|
||||
{type === 'clouddrive2' && (
|
||||
<p className="mb-3 rounded-lg border border-blue-200 bg-blue-50 px-3 py-2 text-xs text-blue-800">
|
||||
CloudDrive2 已经对接 115、123、阿里、夸克等网盘;这里通过它的 WebDAV 入口浏览、挂载和上传,播放默认走服务端反代以携带认证头。
|
||||
</p>
|
||||
)}
|
||||
<div className="grid gap-3 lg:grid-cols-2">
|
||||
|
||||
Reference in New Issue
Block a user