Files
Thing/src/modules/music/components/settings/ConnectionsSettings.vue
T
2026-09-12 11:05:26 +08:00

257 lines
9.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>