mirror of
https://github.com/lanyeeee/bilibili-video-downloader.git
synced 2026-09-08 09:06:53 +08:00
feat: 登录Dialog
This commit is contained in:
Vendored
+5
@@ -8,12 +8,14 @@ export {}
|
|||||||
/* prettier-ignore */
|
/* prettier-ignore */
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
|
FloatLabelInput: typeof import('./src/components/FloatLabelInput.vue')['default']
|
||||||
NA: typeof import('naive-ui')['NA']
|
NA: typeof import('naive-ui')['NA']
|
||||||
NButton: typeof import('naive-ui')['NButton']
|
NButton: typeof import('naive-ui')['NButton']
|
||||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||||
NDialog: typeof import('naive-ui')['NDialog']
|
NDialog: typeof import('naive-ui')['NDialog']
|
||||||
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
|
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
|
||||||
|
NEl: typeof import('naive-ui')['NEl']
|
||||||
NIcon: typeof import('naive-ui')['NIcon']
|
NIcon: typeof import('naive-ui')['NIcon']
|
||||||
NInput: typeof import('naive-ui')['NInput']
|
NInput: typeof import('naive-ui')['NInput']
|
||||||
NInputGroup: typeof import('naive-ui')['NInputGroup']
|
NInputGroup: typeof import('naive-ui')['NInputGroup']
|
||||||
@@ -21,7 +23,10 @@ declare module 'vue' {
|
|||||||
NModal: typeof import('naive-ui')['NModal']
|
NModal: typeof import('naive-ui')['NModal']
|
||||||
NModalProvider: typeof import('naive-ui')['NModalProvider']
|
NModalProvider: typeof import('naive-ui')['NModalProvider']
|
||||||
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
|
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
|
||||||
|
NQrCode: typeof import('naive-ui')['NQrCode']
|
||||||
NSelect: typeof import('naive-ui')['NSelect']
|
NSelect: typeof import('naive-ui')['NSelect']
|
||||||
|
NTabPane: typeof import('naive-ui')['NTabPane']
|
||||||
|
NTabs: typeof import('naive-ui')['NTabs']
|
||||||
NTooltip: typeof import('naive-ui')['NTooltip']
|
NTooltip: typeof import('naive-ui')['NTooltip']
|
||||||
NVirtualList: typeof import('naive-ui')['NVirtualList']
|
NVirtualList: typeof import('naive-ui')['NVirtualList']
|
||||||
TitleBar: typeof import('./src/components/TitleBar.vue')['default']
|
TitleBar: typeof import('./src/components/TitleBar.vue')['default']
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="referrer" content="no-referrer" />
|
||||||
<title>Tauri + Vue + Typescript App</title>
|
<title>Tauri + Vue + Typescript App</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import { InputInst, InputProps } from 'naive-ui'
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
label: string
|
||||||
|
size?: InputProps['size']
|
||||||
|
type?: InputProps['type']
|
||||||
|
clearable?: InputProps['clearable']
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
size: 'medium',
|
||||||
|
type: 'text',
|
||||||
|
clearable: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const value = defineModel<InputProps['value']>('value', { required: true })
|
||||||
|
|
||||||
|
const focused = ref(false)
|
||||||
|
const NInputRef = ref<InputInst>()
|
||||||
|
|
||||||
|
const floating = computed(() => value.value !== '' || focused.value)
|
||||||
|
|
||||||
|
const translateY = computed(() => {
|
||||||
|
if (props.size === 'tiny') {
|
||||||
|
return 'translate-y-[-90%]'
|
||||||
|
} else if (props.size === 'small') {
|
||||||
|
return 'translate-y-[-120%]'
|
||||||
|
} else if (props.size === 'medium') {
|
||||||
|
return 'translate-y-[-140%]'
|
||||||
|
} else if (props.size === 'large') {
|
||||||
|
return 'translate-y-[-160%]'
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
|
||||||
|
defineExpose({ NInputRef })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<n-input
|
||||||
|
ref="NInputRef"
|
||||||
|
:size="size"
|
||||||
|
:type="type"
|
||||||
|
:clearable="clearable"
|
||||||
|
placeholder=""
|
||||||
|
v-model:value="value"
|
||||||
|
@focus="focused = true"
|
||||||
|
@blur="focused = false">
|
||||||
|
<template #prefix>
|
||||||
|
<n-el
|
||||||
|
tag="span"
|
||||||
|
:class="[
|
||||||
|
'float-label bg-white transition-all duration-200 ease-in-out',
|
||||||
|
floating ? `text-0.75rem px-0.5 ${translateY}` : '',
|
||||||
|
]">
|
||||||
|
{{ label }}
|
||||||
|
</n-el>
|
||||||
|
</template>
|
||||||
|
</n-input>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.n-input-wrapper) {
|
||||||
|
@apply overflow-visible flex items-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-input__prefix) {
|
||||||
|
@apply absolute leading-none z-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-input__input-el) {
|
||||||
|
@apply relative z-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.n-input--focus .float-label,
|
||||||
|
.n-input:hover .float-label {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -3,7 +3,12 @@ import icon from '../../src-tauri/icons/128x128.png'
|
|||||||
import { PhCopySimple, PhMinus, PhSquare, PhX } from '@phosphor-icons/vue'
|
import { PhCopySimple, PhMinus, PhSquare, PhX } from '@phosphor-icons/vue'
|
||||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
|
import { useStore } from '../store.ts'
|
||||||
import { platform } from '@tauri-apps/plugin-os'
|
import { platform } from '@tauri-apps/plugin-os'
|
||||||
|
import { ensureHttps } from '../utils.tsx'
|
||||||
|
import LoginDialog from '../dialogs/LoginDialog.vue'
|
||||||
|
|
||||||
|
const store = useStore()
|
||||||
|
|
||||||
const appWindow = getCurrentWindow()
|
const appWindow = getCurrentWindow()
|
||||||
const windowMaximised = ref<boolean>(false)
|
const windowMaximised = ref<boolean>(false)
|
||||||
@@ -15,7 +20,6 @@ const loginDialogShowing = ref<boolean>(false)
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
windowMaximised.value = await appWindow.isMaximized()
|
windowMaximised.value = await appWindow.isMaximized()
|
||||||
windowFullscreen.value = await appWindow.isFullscreen()
|
|
||||||
|
|
||||||
await appWindow.onResized(async () => {
|
await appWindow.onResized(async () => {
|
||||||
windowMaximised.value = await appWindow.isMaximized()
|
windowMaximised.value = await appWindow.isMaximized()
|
||||||
@@ -32,7 +36,24 @@ onMounted(async () => {
|
|||||||
<img data-tauri-drag-region :src="icon" alt="icon" class="ml-2 mr-2 w-6 h-6" draggable="false" />
|
<img data-tauri-drag-region :src="icon" alt="icon" class="ml-2 mr-2 w-6 h-6" draggable="false" />
|
||||||
<span data-tauri-drag-region class="text-base select-none">哔哩哔哩视频下载器</span>
|
<span data-tauri-drag-region class="text-base select-none">哔哩哔哩视频下载器</span>
|
||||||
|
|
||||||
<div class="ml-auto" />
|
<div class="flex whitespace-nowrap ml-auto mr-4" @click="loginDialogShowing = true">
|
||||||
|
<div v-if="store.userInfo !== undefined" class="flex items-center whitespace-nowrap cursor-pointer">
|
||||||
|
<img
|
||||||
|
class="w-8 h-8 rounded-full"
|
||||||
|
:src="`${ensureHttps(store.userInfo.face)}@128w_128h`"
|
||||||
|
alt=""
|
||||||
|
draggable="false" />
|
||||||
|
<div class="line-clamp-1">{{ store.userInfo.uname }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex items-center whitespace-nowrap cursor-pointer" @click="loginDialogShowing = true">
|
||||||
|
<img
|
||||||
|
class="w-8 h-8 rounded-full"
|
||||||
|
src="https://i0.hdslb.com/bfs/face/member/noface.jpg@128w_128h"
|
||||||
|
alt=""
|
||||||
|
draggable="false" />
|
||||||
|
<div class="line-clamp-1">未登录</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="currentPlatform !== 'macos'" class="flex items-center select-none">
|
<div v-if="currentPlatform !== 'macos'" class="flex items-center select-none">
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { commands, QrcodeData, QrcodeStatus } from '../bindings.ts'
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { useMessage } from 'naive-ui'
|
||||||
|
import { useStore } from '../store.ts'
|
||||||
|
import icon from '../../src-tauri/icons/128x128.png'
|
||||||
|
import FloatLabelInput from '../components/FloatLabelInput.vue'
|
||||||
|
|
||||||
|
const store = useStore()
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
const showing = defineModel<boolean>('showing', { required: true })
|
||||||
|
|
||||||
|
const currentTabName = ref<'cookie' | 'qrcode'>('cookie')
|
||||||
|
|
||||||
|
// 只要showing有任何变动,currentTabName就改为cookie
|
||||||
|
watch(showing, () => {
|
||||||
|
currentTabName.value = 'cookie'
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => store.config?.sessdata,
|
||||||
|
async (value, oldValue) => {
|
||||||
|
if (store.config === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (oldValue !== undefined && oldValue !== '' && value === '') {
|
||||||
|
// 如果旧的 sessdata 不为空,新的 sessdata 为空,相当于退出登录
|
||||||
|
store.userInfo = undefined
|
||||||
|
message.success('已退出登录')
|
||||||
|
return
|
||||||
|
} else if (value === undefined || value === '') {
|
||||||
|
// 如果 sessdata 为空,说明用户没有登录
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const result = await commands.getUserInfo(value)
|
||||||
|
if (result.status === 'error') {
|
||||||
|
console.error(result.error)
|
||||||
|
store.userInfo = undefined
|
||||||
|
return
|
||||||
|
}
|
||||||
|
store.userInfo = result.data
|
||||||
|
message.success('获取用户信息成功')
|
||||||
|
showing.value = false
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
watch([showing, currentTabName], async () => {
|
||||||
|
if (currentTabName.value !== 'qrcode' || !showing.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 如果当前选项卡是二维码,并且dialog正在显示,则生成二维码
|
||||||
|
await generateQrcode()
|
||||||
|
})
|
||||||
|
|
||||||
|
const qrcodeData = ref<QrcodeData>()
|
||||||
|
const qrcodeStatus = ref<QrcodeStatus>()
|
||||||
|
|
||||||
|
async function generateQrcode() {
|
||||||
|
const result = await commands.generateQrcode()
|
||||||
|
if (result.status === 'error') {
|
||||||
|
console.error(result.error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
qrcodeData.value = result.data
|
||||||
|
// 每隔一秒获取一次二维码状态,直到showing为false
|
||||||
|
const interval = setInterval(async () => {
|
||||||
|
if (!showing.value) {
|
||||||
|
clearInterval(interval)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await getQrcodeStatus()
|
||||||
|
handleQrcodeStatus()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getQrcodeStatus() {
|
||||||
|
if (qrcodeData.value === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const result = await commands.getQrcodeStatus(qrcodeData.value?.qrcode_key)
|
||||||
|
if (result.status === 'error') {
|
||||||
|
console.error(result.error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
qrcodeStatus.value = result.data
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleQrcodeStatus() {
|
||||||
|
if (qrcodeStatus.value === undefined || store.config === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qrcodeStatus.value.code === 0) {
|
||||||
|
const sessdata = qrcodeStatus.value.url.split('SESSDATA=')[1].split('&')[0]
|
||||||
|
store.config.sessdata = encodeURIComponent(sessdata)
|
||||||
|
showing.value = false
|
||||||
|
message.success('登录成功')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<n-modal v-if="store.config !== undefined" v-model:show="showing">
|
||||||
|
<n-dialog :showIcon="false" @close="showing = false" title="登录方式">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<n-tabs class="h-full" v-model:value="currentTabName" type="line" size="small" animated>
|
||||||
|
<n-tab-pane name="cookie" tab="Cookie">
|
||||||
|
<FloatLabelInput v-model:value="store.config.sessdata" label="SESSDATA" clearable />
|
||||||
|
</n-tab-pane>
|
||||||
|
<n-tab-pane name="qrcode" tab="二维码" display-directive="show:lazy">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
二维码状态:{{ qrcodeStatus?.message }}
|
||||||
|
<n-qr-code
|
||||||
|
v-if="qrcodeData !== undefined"
|
||||||
|
class="mx-auto my-4"
|
||||||
|
error-correction-level="H"
|
||||||
|
:size="360"
|
||||||
|
:value="qrcodeData.url"
|
||||||
|
:icon-src="icon"
|
||||||
|
icon-background-color="transparent"
|
||||||
|
:icon-size="96" />
|
||||||
|
<div v-else class="w-90 h-90 mx-auto my-4 p-3" />
|
||||||
|
</div>
|
||||||
|
</n-tab-pane>
|
||||||
|
</n-tabs>
|
||||||
|
</div>
|
||||||
|
</n-dialog>
|
||||||
|
</n-modal>
|
||||||
|
</template>
|
||||||
+3
-2
@@ -1,9 +1,10 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { Config } from './bindings.ts'
|
import { Config, UserInfo } from './bindings.ts'
|
||||||
|
|
||||||
export const useStore = defineStore('store', () => {
|
export const useStore = defineStore('store', () => {
|
||||||
const config = ref<Config>()
|
const config = ref<Config>()
|
||||||
|
const userInfo = ref<UserInfo>()
|
||||||
|
|
||||||
return { config }
|
return { config, userInfo }
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export function ensureHttps(url: string): string {
|
||||||
|
if (url.startsWith('http://')) {
|
||||||
|
return url.replace('http://', 'https://')
|
||||||
|
}
|
||||||
|
return url
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user