调整,音乐模块

This commit is contained in:
zhongluofeng
2026-09-12 11:05:26 +08:00
parent 27ad5d89a5
commit d702ed0d31
71 changed files with 13647 additions and 387 deletions
@@ -0,0 +1,257 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { toast } from 'vue-sonner'
import { Check, Cloud, Loader2, Pencil, Plus, Power, Trash2 } from '@lucide/vue'
import { useFeiniuStore, type FeiniuConnection } from '@/stores/feiniuStore'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Switch } from '@/components/ui/switch'
import { Badge } from '@/components/ui/badge'
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle
} from '@/components/ui/dialog'
const store = useFeiniuStore()
const editing = ref<Partial<FeiniuConnection> | null>(null)
const editOpen = ref(false)
const password = ref('')
const testing = ref(false)
const fnosPassword = ref('')
const fnosFormOpen = ref(false)
/** 目标 fnOS 登录连接 */
const fnosTarget = ref<FeiniuConnection | null>(null)
function newForm() {
editing.value = { name: '', kind: 'lan', baseUrl: '', username: '', accessCode: '', insecure: false, fnId: '' }
password.value = ''
editOpen.value = true
}
function editForm(c: FeiniuConnection) {
editing.value = { ...c }
password.value = ''
editOpen.value = true
}
function openFnosLogin(c: FeiniuConnection) {
fnosTarget.value = c
fnosPassword.value = ''
fnosFormOpen.value = true
}
async function save() {
if (!editing.value) return
if (!editing.value.name?.trim() || !editing.value.baseUrl?.trim()) {
toast.error('请填写名称与服务器地址')
return
}
try {
const id = await store.saveConnection({
id: editing.value.id || '',
name: editing.value.name.trim(),
kind: editing.value.kind || 'lan',
baseUrl: editing.value.baseUrl.trim(),
username: editing.value.username || '',
accessCode: editing.value.accessCode || '',
insecure: !!editing.value.insecure,
fnId: editing.value.fnId || ''
})
if (password.value) {
await store.login(id, editing.value.username || '', password.value)
}
editOpen.value = false
toast.success('已保存')
} catch (e) {
toast.error(String(e))
}
}
async function test(c: FeiniuConnection) {
if (!password.value) {
toast.error('请输入密码再测试')
return
}
testing.value = true
try {
await store.testConnection(c.id, c.username, password.value)
toast.success('连接成功')
} catch (e) {
toast.error(String(e))
} finally {
testing.value = false
}
}
async function activate(c: FeiniuConnection) {
await store.activateConnection(c.id)
toast.success('已切换为激活连接')
}
async function logout(c: FeiniuConnection) {
await store.logout(c.id)
toast.success('已登出')
}
async function remove(c: FeiniuConnection) {
await store.deleteConnection(c.id)
toast.success('已删除')
}
async function fnosLogin() {
if (!fnosTarget.value) return
if (!fnosPassword.value) {
toast.error('请输入 NAS 密码')
return
}
try {
await store.fnosLogin(fnosTarget.value.username, fnosPassword.value)
fnosFormOpen.value = false
toast.success('NAS 文件服务已连接')
} catch (e) {
toast.error(String(e))
}
}
function kindLabel(k: string) {
return k === 'lan' ? '局域网' : k === 'frp' ? 'frp 域名' : 'FnConnect'
}
onMounted(() => store.refreshConnections())
</script>
<template>
<div class="space-y-3">
<div class="flex items-center justify-between">
<Label class="text-muted-foreground">飞牛音乐连接</Label>
<Button variant="outline" size="sm" @click="newForm">
<Plus class="size-4" /> 新建
</Button>
</div>
<div class="flex flex-col gap-2">
<div
v-for="c in store.connections"
:key="c.id"
class="flex items-center gap-3 rounded-lg border bg-card px-3 py-2"
>
<div class="min-w-0 flex-1">
<div class="flex items-center gap-2">
<span class="truncate text-sm font-medium">{{ c.name }}</span>
<Badge variant="secondary" class="text-[10px]">{{ kindLabel(c.kind) }}</Badge>
<Badge v-if="store.activeId === c.id" variant="default" class="text-[10px]">激活</Badge>
</div>
<div class="truncate text-xs text-muted-foreground">
{{ c.baseUrl }}<template v-if="c.username"> · {{ c.username }}</template>
</div>
</div>
<div class="flex shrink-0 items-center gap-1">
<span v-if="c.loggedIn" class="mr-1 flex items-center gap-1 text-xs text-emerald-600">
<Check class="size-3.5" /> 已登录
</span>
<Button v-if="store.activeId !== c.id" variant="ghost" size="icon" class="size-8" @click="activate(c)">
<Power class="size-4" />
</Button>
<Button v-if="store.activeId === c.id && !store.fnosLoggedIn" variant="ghost" size="icon" class="size-8" :title="`连接 NAS 文件服务(${c.username}`" @click="openFnosLogin(c)">
<Cloud class="size-4" />
</Button>
<Button variant="ghost" size="icon" class="size-8" @click="editForm(c)">
<Pencil class="size-4" />
</Button>
<Button v-if="c.loggedIn" variant="ghost" size="icon" class="size-8" @click="logout(c)">
<Power class="size-4" />
</Button>
<Button variant="ghost" size="icon" class="size-8 text-destructive" @click="remove(c)">
<Trash2 class="size-4" />
</Button>
</div>
</div>
<p v-if="!store.connections.length" class="text-xs text-muted-foreground">
还没有连接新建一个并填写 NAS 地址局域网 http://192.168.x.x:5666frp 域名或 FnConnect fnId
</p>
</div>
<Dialog v-model:open="editOpen">
<DialogContent class="sm:max-w-md">
<DialogHeader>
<DialogTitle>{{ editing?.id ? '编辑连接' : '新建连接' }}</DialogTitle>
</DialogHeader>
<div class="flex flex-col gap-3">
<div class="space-y-1.5">
<Label>名称</Label>
<Input v-model="editing!.name" placeholder="如:家里 NAS / frp 远程 / FnConnect" />
</div>
<div class="space-y-1.5">
<Label>类型</Label>
<div class="flex gap-2">
<Button
v-for="k in (['lan', 'frp', 'fnconnect'] as const)"
:key="k"
type="button"
size="sm"
:variant="editing!.kind === k ? 'default' : 'outline'"
@click="editing!.kind = k"
>
{{ kindLabel(k) }}
</Button>
</div>
</div>
<div v-if="editing!.kind === 'fnconnect'" class="space-y-1.5">
<Label>FnConnect fnIdfnos.net/xxx 或裸 id</Label>
<Input v-model="editing!.fnId" placeholder="fnos.net/zy2060537" />
<p class="text-xs text-muted-foreground">
保存后点登录会自动解析到可达地址服务器地址会回填
</p>
</div>
<div v-else class="space-y-1.5">
<Label>服务器地址</Label>
<Input v-model="editing!.baseUrl" placeholder="http://192.168.1.10:5666 或 https://xxx.xxx.com" />
</div>
<div class="space-y-1.5">
<Label>账号</Label>
<Input v-model="editing!.username" placeholder="飞牛音乐账号" />
</div>
<div class="space-y-1.5">
<Label>密码{{ password ? '' : '(留空则保留已存 token' }}</Label>
<Input v-model="password" type="password" placeholder="登录密码" />
</div>
<div class="flex items-center justify-between">
<Label class="text-muted-foreground">忽略 HTTPS 证书校验自签证书时勾选</Label>
<Switch v-model:model-value="editing!.insecure" />
</div>
</div>
<DialogFooter class="gap-2">
<Button variant="outline" :disabled="testing" @click="test(editing as unknown as FeiniuConnection)">
<Loader2 v-if="testing" class="size-4 animate-spin" /> 测试
</Button>
<Button @click="save">保存</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<!-- fnOS 文件服务登录上传到飞牛用 -->
<Dialog v-model:open="fnosFormOpen">
<DialogContent class="sm:max-w-sm">
<DialogHeader>
<DialogTitle>连接 NAS 文件服务</DialogTitle>
</DialogHeader>
<p class="text-xs text-muted-foreground">
账号{{ fnosTarget?.username }}连接后即可把本地音乐上传到飞牛曲库目录从曲库删除音乐
</p>
<div class="space-y-1.5">
<Label>NAS 密码</Label>
<Input v-model="fnosPassword" type="password" placeholder="NAS 登录密码" @keydown.enter="fnosLogin" />
</div>
<DialogFooter>
<Button variant="outline" @click="fnosFormOpen = false">取消</Button>
<Button @click="fnosLogin">连接</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</template>