mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-07 02:46:55 +08:00
update
This commit is contained in:
@@ -18,6 +18,7 @@
|
|||||||
"vite": "^4.3.9",
|
"vite": "^4.3.9",
|
||||||
"vue": "^3.2.13",
|
"vue": "^3.2.13",
|
||||||
"vue-meta": "^3.0.0-alpha.10",
|
"vue-meta": "^3.0.0-alpha.10",
|
||||||
|
"vue-request": "^2.0.3",
|
||||||
"vue-router": "4",
|
"vue-router": "4",
|
||||||
"vue-turnstile": "^1.0.0",
|
"vue-turnstile": "^1.0.0",
|
||||||
"vue3-aplayer": "^1.7.3",
|
"vue3-aplayer": "^1.7.3",
|
||||||
|
|||||||
3
src/Utils.ts
Normal file
3
src/Utils.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export function NavigateToNewTab(url: string) {
|
||||||
|
window.open(url, '_blank')
|
||||||
|
}
|
||||||
@@ -10,12 +10,16 @@ export interface PaginationResponse<T> {
|
|||||||
hasMore: boolean
|
hasMore: boolean
|
||||||
datas: T
|
datas: T
|
||||||
}
|
}
|
||||||
|
export enum IndexTypes {
|
||||||
|
Default,
|
||||||
|
}
|
||||||
export interface UserInfo {
|
export interface UserInfo {
|
||||||
name: string
|
name: string
|
||||||
id: number
|
id: number
|
||||||
createAt: number
|
createAt: number
|
||||||
biliId?: number
|
biliId?: number
|
||||||
biliRoomId?: number
|
biliRoomId?: number
|
||||||
|
indexType: IndexTypes
|
||||||
}
|
}
|
||||||
export interface AccountInfo extends UserInfo {
|
export interface AccountInfo extends UserInfo {
|
||||||
isEmailVerified: boolean
|
isEmailVerified: boolean
|
||||||
@@ -23,6 +27,19 @@ export interface AccountInfo extends UserInfo {
|
|||||||
enableFunctions: string[]
|
enableFunctions: string[]
|
||||||
biliVerifyCode?: string
|
biliVerifyCode?: string
|
||||||
emailVerifyUrl?: string
|
emailVerifyUrl?: string
|
||||||
|
settings: UserSetting
|
||||||
|
}
|
||||||
|
export interface Setting_SendEmail {
|
||||||
|
recieveQA: boolean
|
||||||
|
recieveQAReply: boolean
|
||||||
|
}
|
||||||
|
export interface UserSetting {
|
||||||
|
sendEmail: Setting_SendEmail
|
||||||
|
enableFunctions: FunctionTypes[]
|
||||||
|
}
|
||||||
|
export enum FunctionTypes {
|
||||||
|
SongList = 'SongList',
|
||||||
|
QuestionBox = 'QuestionBox',
|
||||||
}
|
}
|
||||||
export interface SongAuthorInfo {
|
export interface SongAuthorInfo {
|
||||||
name: string
|
name: string
|
||||||
@@ -37,6 +54,7 @@ export interface SongsInfo {
|
|||||||
id: number
|
id: number
|
||||||
key: string
|
key: string
|
||||||
name: string
|
name: string
|
||||||
|
translateName?: string
|
||||||
author: string[]
|
author: string[]
|
||||||
url: string
|
url: string
|
||||||
from: SongFrom
|
from: SongFrom
|
||||||
@@ -52,3 +70,25 @@ export enum SongLanguage {
|
|||||||
French, // 法文
|
French, // 法文
|
||||||
Other, //其他
|
Other, //其他
|
||||||
}
|
}
|
||||||
|
export enum LevelTypes {
|
||||||
|
Info,
|
||||||
|
Success,
|
||||||
|
Warn,
|
||||||
|
Error,
|
||||||
|
}
|
||||||
|
export interface NotifactionInfo {
|
||||||
|
id: string
|
||||||
|
createTime: number
|
||||||
|
title: string
|
||||||
|
message: string
|
||||||
|
type: LevelTypes
|
||||||
|
}
|
||||||
|
export interface QAInfo {
|
||||||
|
id: number
|
||||||
|
sender: UserInfo
|
||||||
|
target: UserInfo
|
||||||
|
question: { message: string; image?: string }
|
||||||
|
answer?: { message: string; image?: string }
|
||||||
|
isReaded?: boolean
|
||||||
|
sendAt: number
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,6 +32,6 @@ export async function QueryGetAPI<T>(urlString: string, params?: any, headers?:
|
|||||||
export async function QueryPostPaginationAPI<T>(url: string, body?: unknown): Promise<APIRoot<PaginationResponse<T>>> {
|
export async function QueryPostPaginationAPI<T>(url: string, body?: unknown): Promise<APIRoot<PaginationResponse<T>>> {
|
||||||
return await QueryPostAPI<PaginationResponse<T>>(url, body)
|
return await QueryPostAPI<PaginationResponse<T>>(url, body)
|
||||||
}
|
}
|
||||||
export async function QueryGetPaginationAPI<T>(urlString: string, params?: any): Promise<APIRoot<PaginationResponse<T>>> {
|
export async function QueryGetPaginationAPI<T>(urlString: string, params?: unknown): Promise<APIRoot<PaginationResponse<T>>> {
|
||||||
return await QueryGetAPI<PaginationResponse<T>>(urlString, params)
|
return await QueryGetAPI<PaginationResponse<T>>(urlString, params)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ import { QueryGetAPI } from '@/api/query'
|
|||||||
import { BASE_API, USER_API_URL } from '@/data/constants'
|
import { BASE_API, USER_API_URL } from '@/data/constants'
|
||||||
import { APIRoot, UserInfo } from './api-models'
|
import { APIRoot, UserInfo } from './api-models'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
import { useRouteParams } from '@vueuse/router'
|
||||||
|
|
||||||
export const USERS = ref<{ [uId: number]: UserInfo }>({})
|
export const USERS = ref<{ [uId: number]: UserInfo }>({})
|
||||||
|
|
||||||
export async function useUser(uId: number) {
|
export async function useUser() {
|
||||||
|
const uId = useRouteParams('id', '-1', { transform: Number }).value
|
||||||
|
if (uId) {
|
||||||
if (!USERS.value[uId]) {
|
if (!USERS.value[uId]) {
|
||||||
const result = await GetInfo(uId)
|
const result = await GetInfo(uId)
|
||||||
if (result.code == 200) {
|
if (result.code == 200) {
|
||||||
@@ -14,9 +17,22 @@ export async function useUser(uId: number) {
|
|||||||
}
|
}
|
||||||
return USERS.value[uId]
|
return USERS.value[uId]
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
console.error('指定uId: ' + uId + ' 无效');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export async function useUserWithUId(id: number) {
|
||||||
|
if (!USERS.value[id]) {
|
||||||
|
const result = await GetInfo(id)
|
||||||
|
if (result.code == 200) {
|
||||||
|
USERS.value[id] = result.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return USERS.value[id]
|
||||||
|
}
|
||||||
|
|
||||||
export async function GetInfo(uId: number): Promise<APIRoot<UserInfo>> {
|
export async function GetInfo(id: number): Promise<APIRoot<UserInfo>> {
|
||||||
return QueryGetAPI<UserInfo>(`${USER_API_URL}info`, {
|
return QueryGetAPI<UserInfo>(`${USER_API_URL}info`, {
|
||||||
uId: uId,
|
id: id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { GetSelfAccount } from '@/api/account'
|
import { GetSelfAccount } from '@/api/account'
|
||||||
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
||||||
import { ACCOUNT_API_URL, TURNSTILE_KEY } from '@/data/constants'
|
import { ACCOUNT_API_URL, TURNSTILE_KEY } from '@/data/constants'
|
||||||
|
import { GetNotifactions } from '@/data/notifactions'
|
||||||
import { useLocalStorage } from '@vueuse/core'
|
import { useLocalStorage } from '@vueuse/core'
|
||||||
import { FormInst, FormItemInst, FormItemRule, FormRules, NAlert, NButton, NCard, NDivider, NForm, NFormItem, NInput, NSpace, NSpin, NTab, NTabPane, NTabs, useMessage } from 'naive-ui'
|
import { FormInst, FormItemInst, FormItemRule, FormRules, NAlert, NButton, NCard, NDivider, NForm, NFormItem, NInput, NSpace, NSpin, NTab, NTabPane, NTabs, useMessage } from 'naive-ui'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
@@ -141,6 +142,7 @@ function onLoginButtonClick(e: MouseEvent) {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
GetSelfAccount().then((data) => {
|
GetSelfAccount().then((data) => {
|
||||||
message.success(`成功登陆为 ${data?.name}`)
|
message.success(`成功登陆为 ${data?.name}`)
|
||||||
|
GetNotifactions();
|
||||||
})
|
})
|
||||||
}, 1)
|
}, 1)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
FormRules,
|
FormRules,
|
||||||
NAvatar,
|
NAvatar,
|
||||||
NButton,
|
NButton,
|
||||||
|
NCard,
|
||||||
NCollapseTransition,
|
NCollapseTransition,
|
||||||
NDataTable,
|
NDataTable,
|
||||||
NDivider,
|
NDivider,
|
||||||
@@ -112,7 +113,7 @@ const createColumns = (): DataTableColumns<SongsInfo> => [
|
|||||||
minWidth: 100,
|
minWidth: 100,
|
||||||
sorter: 'default',
|
sorter: 'default',
|
||||||
render(data) {
|
render(data) {
|
||||||
return h('span', data.name)
|
return h(NSpace, { size: 5 }, () => [h(NText, () => data.name), h(NText, { depth: '3' }, () => data.translateName)])
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -195,10 +196,44 @@ const createColumns = (): DataTableColumns<SongsInfo> => [
|
|||||||
default: () => '删除',
|
default: () => '删除',
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
GetPlayButton(data)
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
function GetPlayButton(song: SongsInfo) {
|
||||||
|
switch (song.from) {
|
||||||
|
case SongFrom.FiveSing: {
|
||||||
|
return h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
color:"#00BBB3",
|
||||||
|
onClick: () => {
|
||||||
|
window.open(`http://5sing.kugou.com/bz/${song.id}.html`)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
default: () => '在5sing打开',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case SongFrom.Netease:
|
||||||
|
return h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
color: '#C20C0C',
|
||||||
|
onClick: () => {
|
||||||
|
window.open(`https://music.163.com/#/song?id=${song.id}`)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
default: () => '在网易云打开',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
function renderCell(value: string | number) {
|
function renderCell(value: string | number) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return h(NText, { depth: 3 }, { default: () => '未填写' })
|
return h(NText, { depth: 3 }, { default: () => '未填写' })
|
||||||
@@ -229,8 +264,11 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
歌单 {{ songsInternal.length }}
|
歌单 {{ songsInternal.length }}
|
||||||
|
<NCard embedded>
|
||||||
|
<NButton> </NButton>
|
||||||
|
</NCard>
|
||||||
<Transition>
|
<Transition>
|
||||||
<APlayer v-if="aplayerMusic" :music="aplayerMusic" />
|
<APlayer v-if="aplayerMusic" :music="aplayerMusic" autoplay/>
|
||||||
</Transition>
|
</Transition>
|
||||||
<NDataTable :columns="columns" :data="songsInternal"> </NDataTable>
|
<NDataTable :columns="columns" :data="songsInternal"> </NDataTable>
|
||||||
<NModal v-model:show="showModal" style="max-width: 600px" preset="card">
|
<NModal v-model:show="showModal" style="max-width: 600px" preset="card">
|
||||||
@@ -256,6 +294,6 @@ onMounted(() => {
|
|||||||
</NFormItem>
|
</NFormItem>
|
||||||
</NForm>
|
</NForm>
|
||||||
<NDivider style="margin: 10px" />
|
<NDivider style="margin: 10px" />
|
||||||
<NButton @click="updateSong"> 更新 </NButton>
|
<NButton @click="updateSong" type="success"> 更新 </NButton>
|
||||||
</NModal>
|
</NModal>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ const debugAPI = import.meta.env.VITE_DEBUG_API
|
|||||||
const releseAPI = `${document.location.protocol}//api.vtsuru.live/`
|
const releseAPI = `${document.location.protocol}//api.vtsuru.live/`
|
||||||
export const BASE_API = process.env.NODE_ENV === 'development' ? debugAPI : releseAPI
|
export const BASE_API = process.env.NODE_ENV === 'development' ? debugAPI : releseAPI
|
||||||
export const FETCH_API = 'https://fetch.vtsuru.live/'
|
export const FETCH_API = 'https://fetch.vtsuru.live/'
|
||||||
|
export const FIVESING_SEARCH_API = 'http://search.5sing.kugou.com/home/json?sort=1&page=1&filter=3&type=0&keyword='
|
||||||
|
|
||||||
export const TURNSTILE_KEY = '0x4AAAAAAAETUSAKbds019h0'
|
export const TURNSTILE_KEY = '0x4AAAAAAAETUSAKbds019h0'
|
||||||
|
|
||||||
@@ -9,3 +10,4 @@ export const USER_API_URL = `${BASE_API}user/`
|
|||||||
export const ACCOUNT_API_URL = `${BASE_API}account/`
|
export const ACCOUNT_API_URL = `${BASE_API}account/`
|
||||||
export const BILI_API_URL = `${BASE_API}bili/`
|
export const BILI_API_URL = `${BASE_API}bili/`
|
||||||
export const SONG_API_URL = `${BASE_API}song-list/`
|
export const SONG_API_URL = `${BASE_API}song-list/`
|
||||||
|
export const NOTIFACTION_API_URL = `${BASE_API}notifaction/`
|
||||||
|
|||||||
24
src/data/notifactions.ts
Normal file
24
src/data/notifactions.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { QueryGetAPI } from '@/api/query'
|
||||||
|
import { useRequest } from 'vue-request'
|
||||||
|
import { NOTIFACTION_API_URL } from './constants'
|
||||||
|
import { NotifactionInfo } from '@/api/api-models'
|
||||||
|
import { useAccount } from '@/api/account'
|
||||||
|
|
||||||
|
const account = useAccount()
|
||||||
|
const { data, loading, run } = useRequest(
|
||||||
|
() => {
|
||||||
|
return QueryGetAPI<NotifactionInfo>(NOTIFACTION_API_URL + 'get')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
errorRetryCount: 5,
|
||||||
|
pollingInterval: 5000,
|
||||||
|
pollingWhenHidden: false,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export const notifactions = () => data
|
||||||
|
export const GetNotifactions = () => {
|
||||||
|
if (account) {
|
||||||
|
run()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,11 @@ import App from './App.vue'
|
|||||||
import router from './router'
|
import router from './router'
|
||||||
import store from './store'
|
import store from './store'
|
||||||
import { GetSelfAccount } from './api/account'
|
import { GetSelfAccount } from './api/account'
|
||||||
|
import { GetNotifactions } from './data/notifactions'
|
||||||
|
|
||||||
createApp(App).use(store).use(router).mount('#app')
|
createApp(App).use(store).use(router).mount('#app')
|
||||||
|
|
||||||
GetSelfAccount()
|
GetSelfAccount()
|
||||||
|
GetNotifactions()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,14 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
title: '歌单',
|
title: '歌单',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'question-box',
|
||||||
|
name: 'user-questionBox',
|
||||||
|
component: () => import('@/views/view/QuestionBoxView.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '棉花糖(提问箱',
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
//管理页面
|
//管理页面
|
||||||
@@ -52,11 +60,21 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
name: 'manage-songList',
|
name: 'manage-songList',
|
||||||
component: () => import('@/views/manage/SongListManageView.vue'),
|
component: () => import('@/views/manage/SongListManageView.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'question-box',
|
||||||
|
name: 'manage-questionBox',
|
||||||
|
component: () => import('@/views/manage/QuestionBoxManageView.vue'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'bili-verify',
|
path: 'bili-verify',
|
||||||
name: 'manage-biliVerify',
|
name: 'manage-biliVerify',
|
||||||
component: () => import('@/views/manage/BiliVerifyView.vue'),
|
component: () => import('@/views/manage/BiliVerifyView.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'setting',
|
||||||
|
name: 'manage-setting',
|
||||||
|
component: () => import('@/views/manage/SettingsManageView.vue'),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -22,14 +22,23 @@ const menuOptions = [
|
|||||||
name: 'manage-songList',
|
name: 'manage-songList',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ default: () => '回家' }
|
{ default: () => '歌单' }
|
||||||
),
|
),
|
||||||
key: 'song-list',
|
key: 'song-list',
|
||||||
icon: renderIcon(BookOutline),
|
icon: renderIcon(BookOutline),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '舞,舞,舞',
|
label: () =>
|
||||||
key: 'dance-dance-dance',
|
h(
|
||||||
|
RouterLink,
|
||||||
|
{
|
||||||
|
to: {
|
||||||
|
name: 'manage-questionBox',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ default: () => '棉花糖(提问箱' }
|
||||||
|
),
|
||||||
|
key: 'song-list',
|
||||||
icon: renderIcon(BookOutline),
|
icon: renderIcon(BookOutline),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { NAvatar, NCard, NIcon, NLayout, NLayoutFooter, NLayoutHeader, NLayoutSider, NMenu, NSpace, NText, NButton, NEmpty, NResult, NPageHeader, NSwitch, useOsTheme } from 'naive-ui'
|
import { NAvatar, NCard, NIcon, NLayout, NLayoutFooter, NLayoutHeader, NLayoutSider, NMenu, NSpace, NText, NButton, NEmpty, NResult, NPageHeader, NSwitch, useOsTheme } from 'naive-ui'
|
||||||
import { computed, h, onMounted, ref } from 'vue'
|
import { computed, h, onMounted, ref } from 'vue'
|
||||||
import { BookOutline as BookIcon, PersonOutline as PersonIcon, WineOutline as WineIcon } from '@vicons/ionicons5'
|
import { BookOutline as BookIcon, PersonOutline as PersonIcon, WineOutline as WineIcon } from '@vicons/ionicons5'
|
||||||
import { GetInfo, useUser } from '@/api/user'
|
import { GetInfo, useUserWithUId } from '@/api/user'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { UserInfo } from '@/api/api-models'
|
import { UserInfo } from '@/api/api-models'
|
||||||
import { FETCH_API } from '@/data/constants'
|
import { FETCH_API } from '@/data/constants'
|
||||||
@@ -35,7 +35,7 @@ const menuOptions = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
async function RequestBiliUserData() {
|
async function RequestBiliUserData() {
|
||||||
await fetch(FETCH_API + `https://account.bilibili.com/api/member/getCardByMid?mid=${uId.value}`)
|
await fetch(FETCH_API + `https://account.bilibili.com/api/member/getCardByMid?mid=${userInfo.value?.biliId}`)
|
||||||
.then(async (respone) => {
|
.then(async (respone) => {
|
||||||
let data = await respone.json()
|
let data = await respone.json()
|
||||||
if (data.code == 0) {
|
if (data.code == 0) {
|
||||||
@@ -50,8 +50,9 @@ async function RequestBiliUserData() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
console.log(route.params)
|
||||||
|
userInfo.value = await useUserWithUId(uId.value)
|
||||||
await RequestBiliUserData()
|
await RequestBiliUserData()
|
||||||
userInfo.value = await useUser(uId.value)
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
18
src/views/manage/QuestionBoxManageView.vue
Normal file
18
src/views/manage/QuestionBoxManageView.vue
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { QAInfo } from '@/api/api-models';
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
const questions = ref([] as QAInfo[]);
|
||||||
|
|
||||||
|
function GetQAInfo() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
1
|
||||||
|
</template>
|
||||||
43
src/views/manage/SettingsManageView.vue
Normal file
43
src/views/manage/SettingsManageView.vue
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useAccount } from '@/api/account'
|
||||||
|
import { NButton, NCard, NCheckbox, NCheckboxGroup, NDivider, NForm, NSwitch, useMessage } from 'naive-ui'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { useRequest } from 'vue-request'
|
||||||
|
import { FunctionTypes } from '@/api/api-models'
|
||||||
|
import { QueryPostAPI } from '@/api/query'
|
||||||
|
import { ACCOUNT_API_URL } from '@/data/constants'
|
||||||
|
|
||||||
|
const account = useAccount()
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
function UpdateEnableFunction(func: FunctionTypes, enable: boolean) {
|
||||||
|
if (account.value) {
|
||||||
|
if (enable) {
|
||||||
|
//从account.value?.settings.enableFunctions中移除指定的func
|
||||||
|
account.value.settings.enableFunctions = account.value?.settings.enableFunctions.filter((f) => f != func)
|
||||||
|
} else {
|
||||||
|
account.value.settings.enableFunctions.push(func)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function SaveSetting() {
|
||||||
|
const data = await QueryPostAPI(ACCOUNT_API_URL + 'update-setting', {
|
||||||
|
setting: JSON.stringify(account.value?.settings),
|
||||||
|
})
|
||||||
|
if (data.code == 200) {
|
||||||
|
message.success('保存成功')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NCard v-if="account">
|
||||||
|
<NDivider> 启用功能 </NDivider>
|
||||||
|
<NCheckboxGroup v-model:value="account.settings.enableFunctions">
|
||||||
|
<NCheckbox :value="FunctionTypes.SongList"> 歌单 </NCheckbox>
|
||||||
|
<NCheckbox :value="FunctionTypes.QuestionBox"> 提问箱(棉花糖 </NCheckbox>
|
||||||
|
</NCheckboxGroup>
|
||||||
|
<NDivider />
|
||||||
|
<NButton @click="SaveSetting"> 保存 </NButton>
|
||||||
|
</NCard>
|
||||||
|
</template>
|
||||||
@@ -3,9 +3,32 @@ import { useAccount } from '@/api/account'
|
|||||||
import { SongFrom, SongLanguage, SongsInfo } from '@/api/api-models'
|
import { SongFrom, SongLanguage, SongsInfo } from '@/api/api-models'
|
||||||
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
||||||
import SongList from '@/components/SongList.vue'
|
import SongList from '@/components/SongList.vue'
|
||||||
import { SONG_API_URL } from '@/data/constants'
|
import { FETCH_API, FIVESING_SEARCH_API, SONG_API_URL } from '@/data/constants'
|
||||||
import { ca } from 'date-fns/locale'
|
import { ca } from 'date-fns/locale'
|
||||||
import { FormInst, FormRules, NButton, NDivider, NForm, NFormItem, NInput, NInputGroup, NInputGroupLabel, NModal, NSelect, NSpace, NSpin, NTabPane, NTabs, NTag, NTransfer, useMessage } from 'naive-ui'
|
import {
|
||||||
|
FormInst,
|
||||||
|
FormRules,
|
||||||
|
NButton,
|
||||||
|
NDivider,
|
||||||
|
NForm,
|
||||||
|
NFormItem,
|
||||||
|
NInput,
|
||||||
|
NInputGroup,
|
||||||
|
NInputGroupLabel,
|
||||||
|
NList,
|
||||||
|
NListItem,
|
||||||
|
NModal,
|
||||||
|
NPagination,
|
||||||
|
NSelect,
|
||||||
|
NSpace,
|
||||||
|
NSpin,
|
||||||
|
NTabPane,
|
||||||
|
NTable,
|
||||||
|
NTabs,
|
||||||
|
NTag,
|
||||||
|
NTransfer,
|
||||||
|
useMessage,
|
||||||
|
} from 'naive-ui'
|
||||||
import { Option } from 'naive-ui/es/transfer/src/interface'
|
import { Option } from 'naive-ui/es/transfer/src/interface'
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
|
||||||
@@ -14,6 +37,7 @@ const accountInfo = useAccount()
|
|||||||
|
|
||||||
const showModal = ref(false)
|
const showModal = ref(false)
|
||||||
const neteaseIdInput = ref()
|
const neteaseIdInput = ref()
|
||||||
|
const fivesingSearchInput = ref()
|
||||||
const isModalLoading = ref(false)
|
const isModalLoading = ref(false)
|
||||||
|
|
||||||
const neteaseSongListId = computed(() => {
|
const neteaseSongListId = computed(() => {
|
||||||
@@ -43,6 +67,12 @@ const neteaseSongs = ref<SongsInfo[]>([])
|
|||||||
const neteaseSongsOptions = ref<Option[]>([])
|
const neteaseSongsOptions = ref<Option[]>([])
|
||||||
const selectedNeteaseSongs = ref<string[]>([])
|
const selectedNeteaseSongs = ref<string[]>([])
|
||||||
|
|
||||||
|
const fivesingResults = ref<SongsInfo[]>([])
|
||||||
|
const fivesingTotalPageCount = ref(1)
|
||||||
|
const fivesingCurrentPage = ref(1)
|
||||||
|
|
||||||
|
const isGettingFivesingSongPlayUrl = ref(0)
|
||||||
|
|
||||||
const formRef = ref<FormInst | null>(null)
|
const formRef = ref<FormInst | null>(null)
|
||||||
const addSongModel = ref<SongsInfo>({} as SongsInfo)
|
const addSongModel = ref<SongsInfo>({} as SongsInfo)
|
||||||
const addSongRules: FormRules = {
|
const addSongRules: FormRules = {
|
||||||
@@ -139,6 +169,25 @@ async function addNeteaseSongs() {
|
|||||||
isModalLoading.value = false
|
isModalLoading.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
async function addFingsingSongs(song: SongsInfo) {
|
||||||
|
isModalLoading.value = true
|
||||||
|
await addSongs([song], SongFrom.FiveSing)
|
||||||
|
.then((data) => {
|
||||||
|
if (data.code == 200) {
|
||||||
|
message.success(`已添加歌曲`)
|
||||||
|
songs.value.push(...data.data)
|
||||||
|
} else {
|
||||||
|
message.error('添加失败: ' + data.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
message.error('添加失败')
|
||||||
|
console.error(err)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
isModalLoading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
async function addSongs(songsShoudAdd: SongsInfo[], from: SongFrom) {
|
async function addSongs(songsShoudAdd: SongsInfo[], from: SongFrom) {
|
||||||
return QueryPostAPI<SongsInfo[]>(
|
return QueryPostAPI<SongsInfo[]>(
|
||||||
SONG_API_URL + 'add',
|
SONG_API_URL + 'add',
|
||||||
@@ -167,19 +216,88 @@ async function getNeteaseSongList() {
|
|||||||
disabled: songs.value.findIndex((exist) => exist.id == s.id) > -1,
|
disabled: songs.value.findIndex((exist) => exist.id == s.id) > -1,
|
||||||
}))
|
}))
|
||||||
message.success(`成功获取歌曲信息, 共 ${data.data.length} 条, 歌单中已存在 ${neteaseSongsOptions.value.filter((s) => s.disabled).length} 首`)
|
message.success(`成功获取歌曲信息, 共 ${data.data.length} 条, 歌单中已存在 ${neteaseSongsOptions.value.filter((s) => s.disabled).length} 首`)
|
||||||
|
} else {
|
||||||
|
message.error('获取歌单失败: ' + data.message)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
message.error(err)
|
message.error('获取歌单失败: ' + err)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
isModalLoading.value = false
|
isModalLoading.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
async function getFivesingSearchList(isRestart = false) {
|
||||||
|
isModalLoading.value = true
|
||||||
|
await fetch(FETCH_API + `http://search.5sing.kugou.com/home/json?keyword=${fivesingSearchInput.value}&sort=1&page=${fivesingCurrentPage.value}&filter=3`)
|
||||||
|
.then(async (data) => {
|
||||||
|
const json = await data.json()
|
||||||
|
if (json.list.length == 0) {
|
||||||
|
message.error('搜索结果为空')
|
||||||
|
}
|
||||||
|
if (isRestart) {
|
||||||
|
fivesingCurrentPage.value = 1
|
||||||
|
}
|
||||||
|
fivesingResults.value = []
|
||||||
|
//fivesingResultsOptions.value = []
|
||||||
|
json.list.forEach((song: any) => {
|
||||||
|
const songInfo = {
|
||||||
|
id: song.songId,
|
||||||
|
name: extractTextFromHtml(song.songName),
|
||||||
|
author: [song.originSinger, song.singer],
|
||||||
|
//url: `http://service.5sing.kugou.com/song/getsongurl?songid=${song.songId}&songtype=bz&from=web&version=6.6.72`,
|
||||||
|
} as SongsInfo
|
||||||
|
fivesingResults.value.push(songInfo)
|
||||||
|
})
|
||||||
|
fivesingTotalPageCount.value = json.pageInfo.totalPages
|
||||||
|
message.success(`成功获取搜索信息, 共 ${json.pageInfo.totalCount} 条, 当前第 ${fivesingCurrentPage.value} 页`)
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err)
|
||||||
|
message.error('获取歌单失败: ' + err)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
isModalLoading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function extractTextFromHtml(html: string): string {
|
||||||
|
const regex = /<em class="keyword">(.*?)<\/em>/
|
||||||
|
const match = regex.exec(html)
|
||||||
|
if (match) {
|
||||||
|
return match[1]
|
||||||
|
} else {
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function playFivesingSong(song: SongsInfo) {
|
||||||
|
isGettingFivesingSongPlayUrl.value = song.id
|
||||||
|
await getFivesingSongUrl(song)
|
||||||
|
.then((data) => {
|
||||||
|
song.url = data
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err)
|
||||||
|
message.error('获取歌曲链接失败: ' + err)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
isGettingFivesingSongPlayUrl.value = 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
async function getFivesingSongUrl(song: SongsInfo): Promise<string> {
|
||||||
|
const data = await fetch(FETCH_API + `http://service.5sing.kugou.com/song/getsongurl?songid=${song.id}&songtype=bz&from=web&version=6.6.72`)
|
||||||
|
const result = await data.text()
|
||||||
|
//忽略掉result的第一个字符和最后一个字符, 并反序列化
|
||||||
|
const json = JSON.parse(result.substring(1, result.length - 1))
|
||||||
|
if (json.code == 0) {
|
||||||
|
return json.data.lqurl
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
async function getSongs() {
|
async function getSongs() {
|
||||||
await QueryGetAPI<any>(SONG_API_URL + 'get', {
|
await QueryGetAPI<any>(SONG_API_URL + 'get', {
|
||||||
uId: accountInfo.value?.biliId,
|
id: accountInfo.value?.id,
|
||||||
}).then((data) => {
|
}).then((data) => {
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
songs.value = data.data
|
songs.value = data.data
|
||||||
@@ -194,7 +312,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NButton @click="showModal = true"> 添加歌曲 </NButton>
|
<NButton @click="showModal = true"> 添加歌曲 </NButton>
|
||||||
<NModal v-model:show="showModal" style="max-width: 600px" preset="card">
|
<NModal v-model:show="showModal" style="max-width: 1000px;" preset="card">
|
||||||
<template #header> 添加歌曲 </template>
|
<template #header> 添加歌曲 </template>
|
||||||
<NSpin :show="isModalLoading">
|
<NSpin :show="isModalLoading">
|
||||||
<NTabs default-value="custom" animated>
|
<NTabs default-value="custom" animated>
|
||||||
@@ -225,7 +343,7 @@ onMounted(async () => {
|
|||||||
</template>
|
</template>
|
||||||
</NInput>
|
</NInput>
|
||||||
<NDivider style="margin: 10px" />
|
<NDivider style="margin: 10px" />
|
||||||
<NButton type="primary" @click="getNeteaseSongList"> 获取 </NButton>
|
<NButton type="primary" @click="getNeteaseSongList" :disabled="!neteaseSongListId"> 获取 </NButton>
|
||||||
<template v-if="neteaseSongsOptions.length > 0">
|
<template v-if="neteaseSongsOptions.length > 0">
|
||||||
<NDivider style="margin: 10px" />
|
<NDivider style="margin: 10px" />
|
||||||
<NTransfer style="height: 500px" ref="transfer" v-model:value="selectedNeteaseSongs" :options="neteaseSongsOptions" source-filterable />
|
<NTransfer style="height: 500px" ref="transfer" v-model:value="selectedNeteaseSongs" :options="neteaseSongsOptions" source-filterable />
|
||||||
@@ -233,7 +351,50 @@ onMounted(async () => {
|
|||||||
<NButton type="primary" @click="addNeteaseSongs"> 添加到歌单 | {{ selectedNeteaseSongs.length }} 首 </NButton>
|
<NButton type="primary" @click="addNeteaseSongs"> 添加到歌单 | {{ selectedNeteaseSongs.length }} 首 </NButton>
|
||||||
</template>
|
</template>
|
||||||
</NTabPane>
|
</NTabPane>
|
||||||
<NTabPane name="5sing" tab="从5sing搜索"> </NTabPane>
|
<NTabPane name="5sing" tab="从5sing搜索">
|
||||||
|
<NInput clearable style="width: 100%" autosize v-model:value="fivesingSearchInput" placeholder="输入要搜索的歌名" maxlength="15" />
|
||||||
|
<NDivider style="margin: 10px" />
|
||||||
|
<NButton type="primary" @click="getFivesingSearchList(true)" :disabled="!fivesingSearchInput"> 搜索 </NButton>
|
||||||
|
<template v-if="fivesingResults.length > 0">
|
||||||
|
<NDivider style="margin: 10px" />
|
||||||
|
<div style="overflow-x: auto">
|
||||||
|
<NTable size="small" style="overflow-x: auto">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>名称</th>
|
||||||
|
<th>作者</th>
|
||||||
|
<th>试听</th>
|
||||||
|
<th>操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="song in fivesingResults" v-bind:key="song.id">
|
||||||
|
<td>{{ song.name }}</td>
|
||||||
|
<td>
|
||||||
|
<NSpace>
|
||||||
|
<NTag size="small" v-for="author in song.author" :key="author">
|
||||||
|
{{ author }}
|
||||||
|
</NTag>
|
||||||
|
</NSpace>
|
||||||
|
</td>
|
||||||
|
<td style="display: flex; justify-content: flex-end;">
|
||||||
|
<!-- 在这里播放song.url链接中的音频 -->
|
||||||
|
<NButton size="small" v-if="!song.url" @click="playFivesingSong(song)" :loading="isGettingFivesingSongPlayUrl == song.id"> 试听 </NButton>
|
||||||
|
<audio v-else controls autoplay style="max-height: 30px">
|
||||||
|
<source :src="song.url" />
|
||||||
|
</audio>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<NButton size="small" color="green" @click="addFingsingSongs(song)" :disabled="songs.findIndex((s) => s.from == SongFrom.FiveSing && s.id == song.id) > -1"> 添加 </NButton>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</NTable>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<NPagination v-model:page="fivesingCurrentPage" :page-count="fivesingTotalPageCount" simple @update-page="getFivesingSearchList(false)" />
|
||||||
|
</template>
|
||||||
|
</NTabPane>
|
||||||
</NTabs>
|
</NTabs>
|
||||||
</NSpin>
|
</NSpin>
|
||||||
</NModal>
|
</NModal>
|
||||||
|
|||||||
@@ -1,13 +1,33 @@
|
|||||||
<template>
|
<template>
|
||||||
<div style="display: flex;justify-content: center;">
|
<div style="display: flex; justify-content: center">
|
||||||
<div>
|
<div>
|
||||||
<NText strong tag="h1">
|
<NText strong tag="h1"> vtsuru </NText>
|
||||||
vtsuru
|
<component :is="indexType" :user-info="userInfo"/>
|
||||||
</NText>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useAccount } from '@/api/account'
|
||||||
|
import { useUser } from '@/api/user'
|
||||||
|
import { IndexTypes } from '@/api/api-models'
|
||||||
import { NButton, NText } from 'naive-ui'
|
import { NButton, NText } from 'naive-ui'
|
||||||
|
import DefaultIndexTemplate from '@/views/view/indexTemplate/DefaultIndexTemplate.vue'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const indexType = computed(() => {
|
||||||
|
if (userInfo) {
|
||||||
|
switch (userInfo.indexType) {
|
||||||
|
case IndexTypes.Default:
|
||||||
|
return DefaultIndexTemplate
|
||||||
|
|
||||||
|
default:
|
||||||
|
return DefaultIndexTemplate
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return DefaultIndexTemplate
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const accountInfo = useAccount()
|
||||||
|
const userInfo = await useUser()
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
11
src/views/view/indexTemplate/DefaultIndexTemplate.vue
Normal file
11
src/views/view/indexTemplate/DefaultIndexTemplate.vue
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { UserInfo } from '@/api/api-models';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
userInfo: UserInfo
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
1
|
||||||
|
</template>
|
||||||
Reference in New Issue
Block a user