257 lines
9.0 KiB
Vue
257 lines
9.0 KiB
Vue
<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:5666、frp 域名或 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 fnId(fnos.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> |