mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
feat: 替换认证存储逻辑为BiliAuth
- 将所有使用useAuthStore的地方替换为useBiliAuth - 删除useAuthStore文件,整合认证逻辑 - 更新相关视图和组件以适应新的认证存储
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { GetSelfAccount, useAccount, UpdateAccountLoop } from "@/api/account";
|
||||
import { QueryGetAPI } from "@/api/query";
|
||||
import { useAuthStore } from "@/store/useAuthStore";
|
||||
import { useBiliAuth } from "@/store/useBiliAuth";
|
||||
import { useNotificationStore } from "@/store/useNotificationStore";
|
||||
import { createDiscreteApi, NText, NFlex, NButton } from "naive-ui";
|
||||
import { BASE_API_URL, isTauri, apiFail } from "./constants";
|
||||
@@ -64,7 +64,7 @@ async function InitOther() {
|
||||
InitTTS()
|
||||
await GetSelfAccount()
|
||||
const account = useAccount()
|
||||
const useAuth = useAuthStore()
|
||||
const useAuth = useBiliAuth()
|
||||
if (account.value.id) {
|
||||
if (account.value.biliUserAuthInfo && !useAuth.currentToken) {
|
||||
useAuth.currentToken = account.value.biliUserAuthInfo.token
|
||||
|
||||
@@ -6,7 +6,7 @@ import { MessageApiInjection } from 'naive-ui/es/message/src/MessageProvider'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
export const useAuthStore = defineStore('BiliAuth', () => {
|
||||
export const useBiliAuth = defineStore('BiliAuth', () => {
|
||||
const biliAuth = ref<BiliAuthModel>({} as BiliAuthModel)
|
||||
|
||||
const biliTokens = useStorage<
|
||||
@@ -3,11 +3,11 @@ import { QueryGetAPI } from "@/api/query";
|
||||
import { POINT_API_URL } from "@/data/constants";
|
||||
import { MessageApiInjection } from "naive-ui/es/message/src/MessageProvider";
|
||||
import { defineStore } from "pinia";
|
||||
import { useAuthStore } from "./useAuthStore";
|
||||
import { useBiliAuth } from "./useBiliAuth";
|
||||
import { GuidUtils } from "@/Utils";
|
||||
|
||||
export const usePointStore = defineStore('point', () => {
|
||||
const useAuth = useAuthStore()
|
||||
const useAuth = useBiliAuth()
|
||||
|
||||
async function GetSpecificPoint(id: number) {
|
||||
try {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { QueryGetAPI } from '@/api/query'
|
||||
import { BILI_AUTH_API_URL, CURRENT_HOST } from '@/data/constants'
|
||||
import { useAuthStore } from '@/store/useAuthStore'
|
||||
import { useBiliAuth } from '@/store/useBiliAuth'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import {
|
||||
NAlert,
|
||||
@@ -33,7 +33,7 @@ const message = useMessage()
|
||||
|
||||
const guidKey = useStorage('Bili.Auth.Key', uuidv4())
|
||||
const currentToken = useStorage<string>('Bili.Auth.Selected', null)
|
||||
const useAuth = useAuthStore()
|
||||
const useAuth = useBiliAuth()
|
||||
|
||||
const startModel = ref<AuthStartModel>()
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { QueryGetAPI } from '@/api/query'
|
||||
import RegisterAndLogin from '@/components/RegisterAndLogin.vue'
|
||||
import { checkUpdateNote } from '@/data/UpdateNote';
|
||||
import { ACCOUNT_API_URL } from '@/data/constants'
|
||||
import { useAuthStore } from '@/store/useAuthStore'
|
||||
import { useBiliAuth } from '@/store/useBiliAuth'
|
||||
import { useMusicRequestProvider } from '@/store/useMusicRequest'
|
||||
import {
|
||||
BookCoins20Filled,
|
||||
@@ -369,7 +369,7 @@ function gotoAuthPage() {
|
||||
message.error('你尚未进行 Bilibili 认证, 请前往面板进行认证和绑定')
|
||||
return
|
||||
}
|
||||
useAuthStore()
|
||||
useBiliAuth()
|
||||
.setCurrentAuth(accountInfo.value?.biliUserAuthInfo.token)
|
||||
.then(() => {
|
||||
NavigateToNewTab('/bili-user')
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { useUser } from '@/api/user';
|
||||
import RegisterAndLogin from '@/components/RegisterAndLogin.vue';
|
||||
import { FETCH_API } from '@/data/constants'; // 移除了未使用的 AVATAR_URL
|
||||
import { useAuthStore } from '@/store/useAuthStore';
|
||||
import { useBiliAuth } from '@/store/useBiliAuth';
|
||||
import {
|
||||
BookCoins20Filled,
|
||||
CalendarClock24Filled,
|
||||
@@ -47,7 +47,7 @@
|
||||
const router = useRouter(); // 获取 router 实例
|
||||
const message = useMessage();
|
||||
const accountInfo = useAccount(); // 获取当前登录账户信息
|
||||
const useAuth = useAuthStore(); // 获取认证状态 Store
|
||||
const useAuth = useBiliAuth(); // 获取认证状态 Store
|
||||
|
||||
// 路由参数
|
||||
const id = computed(() => route.params.id);
|
||||
|
||||
@@ -6,7 +6,7 @@ import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
||||
import EventFetcherStatusCard from '@/components/EventFetcherStatusCard.vue'
|
||||
import PointGoodsItem from '@/components/manage/PointGoodsItem.vue'
|
||||
import { CN_HOST, CURRENT_HOST, FILE_BASE_URL, POINT_API_URL } from '@/data/constants'
|
||||
import { useAuthStore } from '@/store/useAuthStore'
|
||||
import { useBiliAuth } from '@/store/useBiliAuth'
|
||||
import { Info24Filled } from '@vicons/fluent'
|
||||
import { useRouteHash } from '@vueuse/router'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
@@ -52,7 +52,7 @@ import PointUserManage from './PointUserManage.vue'
|
||||
const message = useMessage()
|
||||
const accountInfo = useAccount()
|
||||
const dialog = useDialog()
|
||||
const useBiliAuth = useAuthStore()
|
||||
const useBiliAuth = useBiliAuth()
|
||||
const formRef = ref()
|
||||
const isUpdating = ref(false)
|
||||
const isAllowedPrivacyPolicy = ref(false)
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import AddressDisplay from '@/components/manage/AddressDisplay.vue'
|
||||
import PointGoodsItem from '@/components/manage/PointGoodsItem.vue'
|
||||
import { POINT_API_URL } from '@/data/constants'
|
||||
import { useAuthStore } from '@/store/useAuthStore'
|
||||
import { useBiliAuth } from '@/store/useBiliAuth'
|
||||
import {
|
||||
NAlert,
|
||||
NButton,
|
||||
@@ -43,7 +43,7 @@ const props = defineProps<{
|
||||
}>()
|
||||
const router = useRouter()
|
||||
|
||||
const useAuth = useAuthStore()
|
||||
const useAuth = useBiliAuth()
|
||||
// 移除未使用的 accountInfo
|
||||
const isLoading = ref(false)
|
||||
const message = useMessage()
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
import { ResponsePointOrder2UserModel } from '@/api/api-models'
|
||||
import PointOrderCard from '@/components/manage/PointOrderCard.vue'
|
||||
import { POINT_API_URL } from '@/data/constants'
|
||||
import { useAuthStore } from '@/store/useAuthStore'
|
||||
import { useBiliAuth } from '@/store/useBiliAuth'
|
||||
import { NButton, NEmpty, NFlex, NSpin, useMessage } from 'naive-ui'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const message = useMessage()
|
||||
const useAuth = useAuthStore()
|
||||
const useAuth = useBiliAuth()
|
||||
|
||||
const orders = ref<ResponsePointOrder2UserModel[]>([])
|
||||
const isLoading = ref(false)
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
import { ResponsePointHisrotyModel } from '@/api/api-models'
|
||||
import PointHistoryCard from '@/components/manage/PointHistoryCard.vue'
|
||||
import { POINT_API_URL } from '@/data/constants'
|
||||
import { useAuthStore } from '@/store/useAuthStore'
|
||||
import { useBiliAuth } from '@/store/useBiliAuth'
|
||||
import { NButton, NEmpty, NFlex, NSpin, useMessage } from 'naive-ui'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const message = useMessage()
|
||||
const useAuth = useAuthStore()
|
||||
const useAuth = useBiliAuth()
|
||||
const isLoading = ref(false)
|
||||
|
||||
const history = ref<ResponsePointHisrotyModel[]>([])
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { UserInfo } from '@/api/api-models'
|
||||
import { POINT_API_URL } from '@/data/constants'
|
||||
import { useAuthStore } from '@/store/useAuthStore'
|
||||
import { useBiliAuth } from '@/store/useBiliAuth'
|
||||
import { useRouteHash } from '@vueuse/router'
|
||||
import {
|
||||
NAlert,
|
||||
@@ -48,7 +48,7 @@ interface SettingsViewInstance extends ComponentWithReset {
|
||||
// 设置组件可能需要的方法
|
||||
}
|
||||
|
||||
const useAuth = useAuthStore()
|
||||
const useAuth = useBiliAuth()
|
||||
const message = useMessage()
|
||||
const realHash = useRouteHash('points', {
|
||||
mode: 'replace',
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { AddressInfo } from '@/api/api-models'
|
||||
import AddressDisplay from '@/components/manage/AddressDisplay.vue'
|
||||
import { CURRENT_HOST, POINT_API_URL, THINGS_URL } from '@/data/constants'
|
||||
import { useAuthStore } from '@/store/useAuthStore'
|
||||
import { useBiliAuth } from '@/store/useBiliAuth'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import {
|
||||
FormRules,
|
||||
@@ -41,7 +41,7 @@ type AreaData = {
|
||||
}
|
||||
}
|
||||
|
||||
const useAuth = useAuthStore()
|
||||
const useAuth = useBiliAuth()
|
||||
const message = useMessage()
|
||||
const isLoading = ref(false)
|
||||
const userAgree = ref(false)
|
||||
|
||||
@@ -341,15 +341,15 @@ onMounted(() => {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
/* 使用官方阴影变量 */
|
||||
box-shadow: var(--n-boxShadow1, 0 1px 2px -2px rgba(0, 0, 0, .24), 0 3px 6px 0 rgba(0, 0, 0, .18), 0 5px 12px 4px rgba(0, 0, 0, .12));
|
||||
box-shadow: var(--box-shadow-1);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.ranking-header {
|
||||
/* 使用官方背景色变量 */
|
||||
background-color: var(--n-tableHeaderColor, rgba(255, 255, 255, 0.06));
|
||||
font-weight: var(--n-fontWeightStrong, 500);
|
||||
color: var(--n-textColor2, rgba(255, 255, 255, 0.82));
|
||||
background-color: var(--table-header-color);
|
||||
font-weight: var(--font-weight-strong);
|
||||
color: var(--text-color-2);
|
||||
}
|
||||
|
||||
.ranking-row {
|
||||
@@ -357,13 +357,13 @@ onMounted(() => {
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
/* 使用官方分割线变量 */
|
||||
border-bottom: 1px solid var(--n-dividerColor, rgba(255, 255, 255, 0.09));
|
||||
transition: background-color 0.3s var(--n-cubicBezierEaseInOut, cubic-bezier(.4, 0, .2, 1));
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
transition: background-color 0.3s var(--cubic-bezier-ease-in-out);
|
||||
}
|
||||
|
||||
.ranking-body .ranking-row:hover {
|
||||
/* 使用官方悬停背景色变量 */
|
||||
background-color: var(--n-hoverColor, rgba(255, 255, 255, 0.09));
|
||||
background-color: var(--hover-color);
|
||||
}
|
||||
|
||||
.ranking-body .ranking-row:last-child {
|
||||
@@ -372,7 +372,7 @@ onMounted(() => {
|
||||
|
||||
.top-three {
|
||||
/* 使用官方条纹背景色变量 */
|
||||
background-color: var(--n-tableColorStriped, rgba(255, 255, 255, 0.05));
|
||||
background-color: var(--table-color-striped);
|
||||
}
|
||||
|
||||
.col-rank {
|
||||
@@ -409,10 +409,10 @@ onMounted(() => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: var(--n-fontWeightStrong, 500);
|
||||
font-weight: var(--font-weight-strong);
|
||||
/* 使用官方文本和背景色变量 */
|
||||
color: var(--n-textColor2, rgba(255, 255, 255, 0.82));
|
||||
background-color: var(--n-actionColor, rgba(255, 255, 255, 0.06));
|
||||
color: var(--text-color-2);
|
||||
background-color: var(--action-color);
|
||||
}
|
||||
|
||||
/* 保持奖牌颜色在暗色模式下也清晰可见 */
|
||||
@@ -432,30 +432,30 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-weight: var(--n-fontWeightStrong, 500);
|
||||
font-weight: var(--font-weight-strong);
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.user-authed {
|
||||
background-color: var(--n-successColor, #63e2b7);
|
||||
background-color: var(--success-color);
|
||||
color: white;
|
||||
padding: 2px 6px;
|
||||
border-radius: 10px;
|
||||
font-size: var(--n-fontSizeTiny, 12px);
|
||||
font-size: var(--font-size-tiny);
|
||||
}
|
||||
|
||||
.days-count,
|
||||
.count-value {
|
||||
font-weight: var(--n-fontWeightStrong, 500);
|
||||
font-size: var(--n-fontSizeLarge, 15px);
|
||||
color: var(--n-infoColor, #70c0e8);
|
||||
font-weight: var(--font-weight-strong);
|
||||
font-size: var(--font-size-large);
|
||||
color: var(--info-color);
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.days-text,
|
||||
.count-text {
|
||||
color: var(--n-textColor3, rgba(255, 255, 255, 0.52));
|
||||
font-size: var(--n-fontSizeTiny, 12px);
|
||||
color: var(--text-color-3);
|
||||
font-size: var(--font-size-tiny);
|
||||
}
|
||||
|
||||
.ranking-footer {
|
||||
@@ -463,6 +463,6 @@ onMounted(() => {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
/* 使用官方背景色变量 */
|
||||
background-color: var(--n-tableHeaderColor, rgba(255, 255, 255, 0.06));
|
||||
background-color: var(--table-header-color);
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 加载中显示加载动画 -->
|
||||
<NSpin :show="isLoading">
|
||||
<component
|
||||
:is="selectedTemplate?.component"
|
||||
@@ -14,8 +15,10 @@
|
||||
@request-song="requestSong"
|
||||
/>
|
||||
</NSpin>
|
||||
|
||||
<!-- 主播自定义按钮 -->
|
||||
<NButton
|
||||
v-if="selectedTemplate?.settingName && userInfo?.id == accountInfo.id"
|
||||
v-if="selectedTemplate?.settingName && userInfo?.id === accountInfo.id"
|
||||
type="info"
|
||||
size="small"
|
||||
style="position: absolute; right: 32px; top: 20px; z-index: 1000; border: solid 3px #dfdfdf;"
|
||||
@@ -23,6 +26,8 @@
|
||||
>
|
||||
自定义
|
||||
</NButton>
|
||||
|
||||
<!-- 设置弹窗 -->
|
||||
<NModal
|
||||
v-model:show="showSettingModal"
|
||||
style="max-width: 90vw; width: 800px;"
|
||||
@@ -39,152 +44,244 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DownloadConfig, useAccount } from '@/api/account';
|
||||
import { DownloadConfig, useAccount } from '@/api/account';
|
||||
import { Setting_LiveRequest, SongRequestInfo, SongsInfo, UserInfo } from '@/api/api-models';
|
||||
import { QueryGetAPI, QueryPostAPIWithParams } from '@/api/query';
|
||||
import { SONG_API_URL, SONG_REQUEST_API_URL, SongListTemplateMap } from '@/data/constants';
|
||||
import { ConfigItemDefinition } from '@/data/VTsuruTypes';
|
||||
import { useBiliAuth } from '@/store/useBiliAuth';
|
||||
import { useStorage } from '@vueuse/core';
|
||||
import { addSeconds } from 'date-fns';
|
||||
import { NButton, NModal, NSpin, useMessage, NFlex, NIcon, NInput, NInputGroup, NInputGroupLabel, NTag, NTooltip, NSelect, NSpace } from 'naive-ui';
|
||||
import { NButton, NModal, NSpin, useMessage } from 'naive-ui';
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { GetGuardColor, getUserAvatarUrl, isDarkMode } from '@/Utils';
|
||||
|
||||
const accountInfo = useAccount();
|
||||
const nextRequestTime = useStorage('SongList.NextRequestTime', new Date());
|
||||
// 用户账号信息
|
||||
const accountInfo = useAccount();
|
||||
// 下次点歌时间
|
||||
const nextRequestTime = useStorage('SongList.NextRequestTime', new Date());
|
||||
// 点歌冷却时间(秒)
|
||||
const minRequestTime = 30;
|
||||
// 设置弹窗显示状态
|
||||
const showSettingModal = ref(false);
|
||||
|
||||
const minRequestTime = 30;
|
||||
const showSettingModal = ref(false);
|
||||
// 组件属性
|
||||
const props = defineProps<{
|
||||
biliInfo: any | undefined; // B站信息
|
||||
userInfo: UserInfo | undefined; // 用户信息
|
||||
template?: string | undefined; // 模板名称
|
||||
fakeData?: SongsInfo[]; // 测试数据
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
biliInfo: any | undefined;
|
||||
userInfo: UserInfo | undefined;
|
||||
template?: string | undefined;
|
||||
fakeData?: SongsInfo[];
|
||||
}>();
|
||||
|
||||
const componentType = computed(() => {
|
||||
// 计算当前使用的模板类型
|
||||
const componentType = computed(() => {
|
||||
return props.template ?? props.userInfo?.extra?.templateTypes['songlist']?.toLowerCase();
|
||||
});
|
||||
const currentData = ref<SongsInfo[]>();
|
||||
const dynamicConfigRef = ref();
|
||||
const selectedTemplateConfig = computed(() => {
|
||||
});
|
||||
|
||||
// 数据状态
|
||||
const currentData = ref<SongsInfo[]>([]); // 歌单数据
|
||||
const dynamicConfigRef = ref(); // 动态配置引用
|
||||
const songsActive = ref<SongRequestInfo[]>([]); // 当前点歌列表
|
||||
const settings = ref<Setting_LiveRequest>({ // 点歌设置
|
||||
allowFromWeb: false,
|
||||
allowAnonymousFromWeb: false,
|
||||
orderPrefix: '',
|
||||
} as Setting_LiveRequest);
|
||||
|
||||
// 加载状态
|
||||
const isDataLoading = ref(true);
|
||||
const isConfigLoading = ref(true);
|
||||
const isLoading = computed(() => isDataLoading.value || isConfigLoading.value);
|
||||
|
||||
// 计算属性
|
||||
const selectedTemplateConfig = computed(() => {
|
||||
if (dynamicConfigRef.value?.Config) {
|
||||
return dynamicConfigRef.value?.Config as ConfigItemDefinition[];
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
const selectedTemplate = computed(() => {
|
||||
if (componentType.value) {
|
||||
return SongListTemplateMap[componentType.value];
|
||||
}
|
||||
return SongListTemplateMap[''];
|
||||
});
|
||||
const currentConfig = ref();
|
||||
});
|
||||
|
||||
const isDataLoading = ref(true);
|
||||
const isConfigLoading = ref(true);
|
||||
const isLoading = computed(() => isDataLoading.value || isConfigLoading.value);
|
||||
const selectedTemplate = computed(() => {
|
||||
const type = componentType.value;
|
||||
return type ? SongListTemplateMap[type] : SongListTemplateMap[''];
|
||||
});
|
||||
|
||||
const message = useMessage();
|
||||
const currentConfig = ref({}); // 当前配置
|
||||
const message = useMessage(); // 消息提示
|
||||
const biliAuth = useBiliAuth(); // B站授权
|
||||
|
||||
const errMessage = ref('');
|
||||
const songsActive = ref<SongRequestInfo[]>([]);
|
||||
const settings = ref<Setting_LiveRequest>({} as Setting_LiveRequest);
|
||||
/**
|
||||
* 获取点歌设置和当前点歌列表
|
||||
*/
|
||||
async function getSongRequestInfo() {
|
||||
if (!props.userInfo?.id) return { songs: [], setting: settings.value };
|
||||
|
||||
async function getSongRequestInfo() {
|
||||
try {
|
||||
const data = await QueryGetAPI<{ songs: SongRequestInfo[]; setting: Setting_LiveRequest; }>(
|
||||
SONG_REQUEST_API_URL + 'get-active-and-settings',
|
||||
{
|
||||
id: props.userInfo?.id,
|
||||
id: props.userInfo.id,
|
||||
},
|
||||
);
|
||||
if (data.code == 200) {
|
||||
|
||||
if (data.code === 200) {
|
||||
return data.data;
|
||||
}
|
||||
} catch (err) { }
|
||||
return {} as { songs: SongRequestInfo[]; setting: Setting_LiveRequest; };
|
||||
}
|
||||
async function getSongs() {
|
||||
isDataLoading.value = true;
|
||||
await QueryGetAPI<SongsInfo[]>(SONG_API_URL + 'get', {
|
||||
id: props.userInfo?.id,
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.code == 200) {
|
||||
currentData.value = data.data;
|
||||
} else {
|
||||
errMessage.value = data.message;
|
||||
message.warning(`获取点歌设置失败: ${data.message}`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('获取点歌设置出错:', err);
|
||||
message.error(`获取点歌设置出错: ${err instanceof Error ? err.message : String(err)}`);
|
||||
}
|
||||
|
||||
return { songs: [], setting: settings.value };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取歌单数据
|
||||
*/
|
||||
async function getSongs() {
|
||||
if (!props.userInfo?.id) {
|
||||
isDataLoading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
isDataLoading.value = true;
|
||||
|
||||
try {
|
||||
const data = await QueryGetAPI<SongsInfo[]>(SONG_API_URL + 'get', {
|
||||
id: props.userInfo.id,
|
||||
});
|
||||
|
||||
if (data.code === 200) {
|
||||
currentData.value = data.data || [];
|
||||
} else {
|
||||
message.error('加载歌单失败: ' + data.message);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
message.error('加载失败: ' + err);
|
||||
})
|
||||
.finally(() => {
|
||||
} catch (err) {
|
||||
console.error('加载歌单出错:', err);
|
||||
message.error(`加载歌单失败: ${err instanceof Error ? err.message : String(err)}`);
|
||||
} finally {
|
||||
isDataLoading.value = false;
|
||||
});
|
||||
}
|
||||
async function getConfig() {
|
||||
if (!selectedTemplateConfig.value || !selectedTemplate.value!.settingName) {
|
||||
if (!selectedTemplate.value!.settingName) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模板配置
|
||||
*/
|
||||
async function getConfig() {
|
||||
if (!selectedTemplate.value?.settingName) {
|
||||
isConfigLoading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedTemplateConfig.value) {
|
||||
// 等待模板配置加载完成后再获取配置
|
||||
setTimeout(() => getConfig(), 100);
|
||||
return;
|
||||
}
|
||||
|
||||
isConfigLoading.value = true;
|
||||
|
||||
try {
|
||||
const data = await DownloadConfig(selectedTemplate.value!.settingName, props.userInfo?.id);
|
||||
const data = await DownloadConfig(
|
||||
selectedTemplate.value.settingName,
|
||||
props.userInfo?.id
|
||||
);
|
||||
|
||||
if (data.msg) {
|
||||
currentConfig.value = dynamicConfigRef.value?.DefaultConfig ?? {};
|
||||
} else {
|
||||
currentConfig.value = data.data;
|
||||
currentConfig.value = data.data || {};
|
||||
}
|
||||
} catch (err) {
|
||||
message.error('加载配置失败: ' + err);
|
||||
console.error('加载配置出错:', err);
|
||||
message.error(`加载配置失败: ${err instanceof Error ? err.message : String(err)}`);
|
||||
currentConfig.value = dynamicConfigRef.value?.DefaultConfig ?? {};
|
||||
} finally {
|
||||
isConfigLoading.value = false;
|
||||
}
|
||||
}
|
||||
async function requestSong(song: SongsInfo) {
|
||||
if (song.options || !settings.value.allowFromWeb || (settings.value.allowFromWeb && !settings.value.allowAnonymousFromWeb)) {
|
||||
navigator.clipboard.writeText(`${settings.value.orderPrefix} ${song.name}`);
|
||||
if (!settings.value.allowAnonymousFromWeb) {
|
||||
message.warning('主播不允许匿名点歌, 需要从网页点歌的话请注册登录, 点歌弹幕已复制到剪切板');
|
||||
}
|
||||
else if (!accountInfo.value.id) {
|
||||
message.warning('要从网页点歌请先登录, 点歌弹幕已复制到剪切板');
|
||||
} else {
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制文本到剪贴板
|
||||
*/
|
||||
function copyToClipboard(text: string, sendMessage: boolean = true) {
|
||||
navigator.clipboard.writeText(text)
|
||||
.then(() => {
|
||||
if (sendMessage) {
|
||||
message.success('复制成功');
|
||||
}
|
||||
} else {
|
||||
if (props.userInfo) {
|
||||
if (!accountInfo.value.id && nextRequestTime.value > new Date()) {
|
||||
message.warning('距离点歌冷却还有' + (nextRequestTime.value.getTime() - new Date().getTime()) / 1000 + '秒');
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('复制失败:', err);
|
||||
message.error('复制失败,请重试');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 点歌处理
|
||||
*/
|
||||
async function requestSong(song: SongsInfo) {
|
||||
if (!song) return;
|
||||
|
||||
const orderText = `${settings.value.orderPrefix || ''} ${song.name}`;
|
||||
|
||||
// 检查是否需要复制到剪贴板而不是直接点歌
|
||||
const shouldCopyOnly = song.options ||
|
||||
!settings.value.allowFromWeb ||
|
||||
(settings.value.allowFromWeb && !settings.value.allowAnonymousFromWeb && !accountInfo.value.id && !biliAuth.isAuthed);
|
||||
|
||||
if (shouldCopyOnly) {
|
||||
copyToClipboard(orderText, false);
|
||||
|
||||
if (song.options) {
|
||||
message.info('此项目有特殊要求, 请在直播间内点歌, 点歌弹幕已复制到剪切板');
|
||||
} else if (!settings.value.allowAnonymousFromWeb && !accountInfo.value.id && !biliAuth.isAuthed) {
|
||||
message.info('主播不允许匿名点歌, 需要从网页点歌的话请注册登录, 点歌弹幕已复制到剪切板');
|
||||
} else if (!settings.value.allowFromWeb) {
|
||||
message.info('主播不允许从网页点歌, 点歌弹幕已复制到剪切板');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 执行网页点歌
|
||||
if (!props.userInfo?.id) {
|
||||
message.error('无法获取主播信息,无法完成点歌');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查点歌冷却时间
|
||||
if (!accountInfo.value.id && nextRequestTime.value > new Date()) {
|
||||
const remainingSeconds = Math.ceil((nextRequestTime.value.getTime() - new Date().getTime()) / 1000);
|
||||
message.warning(`距离点歌冷却还有${remainingSeconds}秒`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await QueryPostAPIWithParams(SONG_REQUEST_API_URL + 'add-from-web', {
|
||||
target: props.userInfo?.id,
|
||||
target: props.userInfo.id,
|
||||
song: song.key,
|
||||
});
|
||||
|
||||
if (data.code == 200) {
|
||||
if (data.code === 200) {
|
||||
message.success('点歌成功');
|
||||
nextRequestTime.value = addSeconds(new Date(), minRequestTime);
|
||||
// 重新获取当前点歌列表,更新界面
|
||||
const songRequestInfo = await getSongRequestInfo();
|
||||
if (songRequestInfo) {
|
||||
songsActive.value = songRequestInfo.songs;
|
||||
}
|
||||
} else {
|
||||
message.error('点歌失败: ' + data.message);
|
||||
}
|
||||
} catch (err) {
|
||||
message.error('点歌失败: ' + err);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.error('点歌出错:', err);
|
||||
message.error(`点歌失败: ${err instanceof Error ? err.message : String(err)}`);
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
// 监听动态配置变化,重新获取配置
|
||||
watch(
|
||||
() => dynamicConfigRef.value,
|
||||
(newValue) => {
|
||||
if (newValue?.Config) {
|
||||
@@ -192,33 +289,52 @@ import { GetGuardColor, getUserAvatarUrl, isDarkMode } from '@/Utils';
|
||||
}
|
||||
},
|
||||
{ immediate: false }
|
||||
);
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
isDataLoading.value = true;
|
||||
// 监听用户ID变化,重新加载数据
|
||||
watch(
|
||||
() => props.userInfo?.id,
|
||||
() => {
|
||||
if (!props.fakeData) {
|
||||
getSongs();
|
||||
getSongRequestInfo().then(info => {
|
||||
if (info) {
|
||||
songsActive.value = info.songs || [];
|
||||
settings.value = info.setting || settings.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 组件挂载时初始化
|
||||
onMounted(async () => {
|
||||
if (!props.fakeData) {
|
||||
try {
|
||||
await getSongs();
|
||||
const r = await getSongRequestInfo();
|
||||
if (r) {
|
||||
songsActive.value = r.songs;
|
||||
settings.value = r.setting;
|
||||
// 并行加载歌单和点歌设置
|
||||
await Promise.all([
|
||||
getSongs(),
|
||||
getSongRequestInfo().then(info => {
|
||||
if (info) {
|
||||
songsActive.value = info.songs || [];
|
||||
settings.value = info.setting || settings.value;
|
||||
}
|
||||
|
||||
})
|
||||
]);
|
||||
} catch (err) {
|
||||
message.error('加载失败: ' + err);
|
||||
console.error(err);
|
||||
console.error('初始化失败:', err);
|
||||
message.error(`初始化失败: ${err instanceof Error ? err.message : String(err)}`);
|
||||
} finally {
|
||||
isDataLoading.value = false;
|
||||
isConfigLoading.value = false;
|
||||
}
|
||||
} else {
|
||||
currentData.value = props.fakeData;
|
||||
// 测试模式使用假数据
|
||||
currentData.value = props.fakeData || [];
|
||||
isDataLoading.value = false;
|
||||
isConfigLoading.value = false;
|
||||
}
|
||||
|
||||
if (!selectedTemplate.value?.settingName) {
|
||||
isConfigLoading.value = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -4,11 +4,14 @@ import { SongsInfo } from '@/api/api-models'
|
||||
import SongList from '@/components/SongList.vue'
|
||||
import { SongListConfigType } from '@/data/TemplateTypes'
|
||||
import LiveRequestOBS from '@/views/obs/LiveRequestOBS.vue'
|
||||
import { getSongRequestTooltip } from './utils/songRequestUtils'
|
||||
import { CloudAdd20Filled, ChevronLeft24Filled, ChevronRight24Filled } from '@vicons/fluent'
|
||||
import { NButton, NCard, NCollapse, NCollapseItem, NDivider, NIcon, NTooltip, useMessage } from 'naive-ui'
|
||||
import { h, ref, onMounted, onUnmounted } from 'vue'
|
||||
import { useBiliAuth } from '@/store/useBiliAuth'
|
||||
|
||||
const accountInfo = useAccount()
|
||||
const biliAuth = useBiliAuth()
|
||||
|
||||
//所有模板都应该有这些
|
||||
const props = defineProps<SongListConfigType>()
|
||||
@@ -75,7 +78,7 @@ const buttons = (song: SongsInfo) => [
|
||||
size: 'small',
|
||||
circle: true,
|
||||
loading: isLoading.value == song.key,
|
||||
disabled: !accountInfo,
|
||||
disabled: !accountInfo.value,
|
||||
onClick: () => {
|
||||
isLoading.value = song.key
|
||||
emits('requestSong', song)
|
||||
@@ -86,12 +89,7 @@ const buttons = (song: SongsInfo) => [
|
||||
icon: () => h(NIcon, { component: CloudAdd20Filled }),
|
||||
},
|
||||
),
|
||||
default: () =>
|
||||
!props.liveRequestSettings?.allowFromWeb || song.options
|
||||
? '点歌 | 用户不允许从网页点歌, 点击后将复制点歌内容到剪切板'
|
||||
: !accountInfo
|
||||
? '点歌 | 你需要登录后才能点歌'
|
||||
: '点歌',
|
||||
default: () => getSongRequestTooltip(song, props.liveRequestSettings)
|
||||
},
|
||||
)
|
||||
: undefined,
|
||||
|
||||
@@ -5,9 +5,11 @@ import { FunctionTypes, SongsInfo } from '@/api/api-models'
|
||||
import SongPlayer from '@/components/SongPlayer.vue'
|
||||
import { SongListConfigType } from '@/data/TemplateTypes'
|
||||
import LiveRequestOBS from '@/views/obs/LiveRequestOBS.vue'
|
||||
import { getSongRequestTooltip, getSongRequestButtonType } from './utils/songRequestUtils'
|
||||
import { CloudAdd20Filled, Play24Filled } from '@vicons/fluent'
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
import { throttle } from 'lodash'
|
||||
import { useBiliAuth } from '@/store/useBiliAuth'
|
||||
import {
|
||||
NButton,
|
||||
NCard,
|
||||
@@ -36,6 +38,7 @@ const container = ref()
|
||||
const index = ref(20)
|
||||
|
||||
const accountInfo = useAccount()
|
||||
const biliAuth = useBiliAuth()
|
||||
|
||||
const selectedTag = ref('')
|
||||
const selectedSong = ref<SongsInfo>()
|
||||
@@ -296,7 +299,7 @@ function loadMore() {
|
||||
<template #trigger>
|
||||
<NButton
|
||||
size="small"
|
||||
:type="liveRequestSettings?.allowFromWeb == false || item.options ? 'warning' : 'info'"
|
||||
:type="getSongRequestButtonType(item, liveRequestSettings, !!accountInfo, biliAuth.isAuthed)"
|
||||
:loading="isLoading == item.key"
|
||||
@click="() => {
|
||||
isLoading = item.key
|
||||
@@ -310,13 +313,7 @@ function loadMore() {
|
||||
</template>
|
||||
</NButton>
|
||||
</template>
|
||||
{{
|
||||
liveRequestSettings?.allowFromWeb == false || item.options
|
||||
? '点歌 | 用户或此歌曲不允许从网页点歌, 点击后将复制点歌内容到剪切板'
|
||||
: !accountInfo
|
||||
? '点歌 | 你需要登录后才能点歌'
|
||||
: '点歌'
|
||||
}}
|
||||
{{ getSongRequestTooltip(item, liveRequestSettings) }}
|
||||
</NTooltip>
|
||||
|
||||
<NPopover
|
||||
|
||||
@@ -13,6 +13,9 @@ import { SongFrom, SongsInfo, SongRequestOption } from '@/api/api-models';
|
||||
import FiveSingIcon from '@/svgs/fivesing.svg';
|
||||
import { SquareArrowForward24Filled, ArrowCounterclockwise20Filled, ArrowSortDown20Filled, ArrowSortUp20Filled } from '@vicons/fluent';
|
||||
import { List } from 'linqts';
|
||||
import { useAccount } from '@/api/account';
|
||||
import { getSongRequestTooltip, getSongRequestConfirmText } from './utils/songRequestUtils';
|
||||
import { useBiliAuth } from '@/store/useBiliAuth';
|
||||
|
||||
// Interface Tab - can be reused for both language and tag buttons
|
||||
interface FilterButton {
|
||||
@@ -326,6 +329,9 @@ watch(allArtists, (newArtists) => {
|
||||
}
|
||||
});
|
||||
|
||||
const accountInfo = useAccount();
|
||||
const biliAuth = useBiliAuth();
|
||||
|
||||
const randomOrder = () => {
|
||||
const songsToChooseFrom = filteredAndSortedSongs.value.length > 0 ? filteredAndSortedSongs.value : props.data ?? [];
|
||||
if (songsToChooseFrom.length === 0) {
|
||||
@@ -347,10 +353,12 @@ const randomOrder = () => {
|
||||
};
|
||||
|
||||
function onSongClick(song: SongsInfo) {
|
||||
const tooltip = getSongRequestTooltip(song, props.liveRequestSettings);
|
||||
const confirmText = getSongRequestConfirmText(song);
|
||||
window.$modal.create({
|
||||
preset: 'dialog',
|
||||
title: '点歌',
|
||||
content: `确定要点 ${song.name} 么`,
|
||||
content: `${confirmText}${tooltip !== '点歌' ? '\n' + tooltip : ''}`,
|
||||
positiveText: '点歌',
|
||||
negativeText: '算了',
|
||||
onPositiveClick: () => {
|
||||
@@ -928,12 +936,17 @@ export const Config = defineTemplateConfig([
|
||||
<td>
|
||||
<span class="song-name">
|
||||
<component :is="GetPlayButton(song)" />
|
||||
<NTooltip>
|
||||
<template #trigger>
|
||||
<span
|
||||
style="cursor: pointer;"
|
||||
@click="onSongClick(song)"
|
||||
>
|
||||
{{ song.name }}
|
||||
</span>
|
||||
</template>
|
||||
{{ getSongRequestTooltip(song, props.liveRequestSettings) }}
|
||||
</NTooltip>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
135
src/views/view/songListTemplate/utils/songRequestUtils.ts
Normal file
135
src/views/view/songListTemplate/utils/songRequestUtils.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { useAccount } from '@/api/account'
|
||||
import { Setting_LiveRequest, SongsInfo, UserInfo } from '@/api/api-models'
|
||||
import { useBiliAuth } from '@/store/useBiliAuth';
|
||||
|
||||
/**
|
||||
* 获取点歌按钮的tooltip文本
|
||||
* @param song 歌曲信息
|
||||
* @param liveRequestSettings 直播点歌设置
|
||||
* @param isLoggedIn 用户是否已登录
|
||||
* @param isBiliAuthed B站是否已授权
|
||||
* @returns tooltip文本
|
||||
*/
|
||||
export function getSongRequestTooltip(
|
||||
song: SongsInfo,
|
||||
liveRequestSettings: Setting_LiveRequest | undefined
|
||||
): string {
|
||||
const accountInfo = useAccount();
|
||||
const biliAuth = useBiliAuth();
|
||||
// 歌曲有特殊要求
|
||||
if (song.options) {
|
||||
return '点歌 | 此项目有特殊要求, 请在直播间内点歌, 点击后将复制点歌内容到剪切板'
|
||||
}
|
||||
|
||||
// 主播不允许从网页点歌
|
||||
if (liveRequestSettings?.allowFromWeb === false) {
|
||||
return '点歌 | 主播不允许从网页点歌, 点击后将复制点歌内容到剪切板'
|
||||
}
|
||||
|
||||
// 主播不允许匿名点歌且用户未登录
|
||||
if (liveRequestSettings?.allowFromWeb &&
|
||||
!liveRequestSettings.allowAnonymousFromWeb &&
|
||||
!accountInfo.value.id &&
|
||||
!biliAuth.isAuthed) {
|
||||
return '点歌 | 主播不允许匿名点歌, 需要从网页点歌的话请注册登录, 点击后将复制点歌内容到剪切板'
|
||||
}
|
||||
|
||||
// 用户未登录
|
||||
if (!accountInfo.value.id && !biliAuth.isAuthed) {
|
||||
return '点歌 | 根据主播设置, 需要登录后才能点歌'
|
||||
}
|
||||
|
||||
return '点歌'
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取点歌按钮的类型
|
||||
* @param song 歌曲信息
|
||||
* @param liveRequestSettings 直播点歌设置
|
||||
* @param isLoggedIn 用户是否已登录
|
||||
* @param isBiliAuthed B站是否已授权
|
||||
* @returns 按钮类型
|
||||
*/
|
||||
export function getSongRequestButtonType(
|
||||
song: SongsInfo,
|
||||
liveRequestSettings: Setting_LiveRequest | undefined,
|
||||
isLoggedIn: boolean = true,
|
||||
isBiliAuthed: boolean = false
|
||||
): 'warning' | 'info' {
|
||||
if (song.options ||
|
||||
liveRequestSettings?.allowFromWeb === false ||
|
||||
(liveRequestSettings?.allowFromWeb &&
|
||||
!liveRequestSettings.allowAnonymousFromWeb &&
|
||||
!isLoggedIn &&
|
||||
!isBiliAuthed)) {
|
||||
return 'warning'
|
||||
}
|
||||
|
||||
return 'info'
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断用户是否可以点歌
|
||||
* @param song 歌曲信息
|
||||
* @param userInfo 主播信息
|
||||
* @param liveRequestSettings 直播点歌设置
|
||||
* @param isLoggedIn 用户是否已登录
|
||||
* @param isBiliAuthed B站是否已授权
|
||||
* @param nextRequestTime 下次点歌时间
|
||||
* @returns 是否可以点歌和原因
|
||||
*/
|
||||
export function canRequestSong(
|
||||
song: SongsInfo,
|
||||
userInfo: UserInfo | undefined,
|
||||
liveRequestSettings: Setting_LiveRequest | undefined,
|
||||
isLoggedIn: boolean,
|
||||
isBiliAuthed: boolean = false,
|
||||
nextRequestTime?: Date
|
||||
): { canRequest: boolean; reason?: string; shouldCopyOnly?: boolean } {
|
||||
// 检查主播信息
|
||||
if (!userInfo?.id) {
|
||||
return { canRequest: false, reason: '无法获取主播信息,无法完成点歌' }
|
||||
}
|
||||
|
||||
// 判断是否应该只复制到剪贴板
|
||||
const shouldCopyOnly = song.options ||
|
||||
!liveRequestSettings?.allowFromWeb ||
|
||||
(liveRequestSettings?.allowFromWeb &&
|
||||
!liveRequestSettings.allowAnonymousFromWeb &&
|
||||
!isLoggedIn &&
|
||||
!isBiliAuthed)
|
||||
|
||||
if (shouldCopyOnly) {
|
||||
let reason = ''
|
||||
|
||||
if (song.options) {
|
||||
reason = '此项目有特殊要求, 请在直播间内点歌, 点歌弹幕已复制到剪切板'
|
||||
} else if (!liveRequestSettings?.allowAnonymousFromWeb && !isLoggedIn && !isBiliAuthed) {
|
||||
reason = '主播不允许匿名点歌, 需要从网页点歌的话请注册登录, 点歌弹幕已复制到剪切板'
|
||||
} else if (!liveRequestSettings?.allowFromWeb) {
|
||||
reason = '主播不允许从网页点歌, 点歌弹幕已复制到剪切板'
|
||||
}
|
||||
|
||||
return { canRequest: false, reason, shouldCopyOnly: true }
|
||||
}
|
||||
|
||||
// 检查点歌冷却时间
|
||||
if (!isLoggedIn && nextRequestTime && nextRequestTime > new Date()) {
|
||||
const remainingSeconds = Math.ceil((nextRequestTime.getTime() - new Date().getTime()) / 1000)
|
||||
return {
|
||||
canRequest: false,
|
||||
reason: `距离点歌冷却还有${remainingSeconds}秒`
|
||||
}
|
||||
}
|
||||
|
||||
return { canRequest: true }
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成点歌的提示文本
|
||||
* @param song 歌曲信息
|
||||
* @returns 点歌提示文本
|
||||
*/
|
||||
export function getSongRequestConfirmText(song: SongsInfo): string {
|
||||
return `确定要点 ${song.name} 么`
|
||||
}
|
||||
Reference in New Issue
Block a user