mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
add 'pinky' template
This commit is contained in:
10
src/App.vue
10
src/App.vue
@@ -2,7 +2,7 @@
|
||||
<NMessageProvider>
|
||||
<NNotificationProvider>
|
||||
<NConfigProvider :theme-overrides="themeOverrides" :theme="theme" style="height: 100vh" :locale="zhCN" :date-locale="dateZhCN">
|
||||
<NElement style="height: 100vh;">
|
||||
<NElement style="height: 100vh">
|
||||
<ViewerLayout v-if="layout == 'viewer'" />
|
||||
<ManageLayout v-else-if="layout == 'manage'" />
|
||||
<template v-else>
|
||||
@@ -19,15 +19,19 @@ import ViewerLayout from '@/views/ViewerLayout.vue'
|
||||
import ManageLayout from '@/views/ManageLayout.vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { NConfigProvider, NMessageProvider, NNotificationProvider, zhCN, dateZhCN, useOsTheme, darkTheme, NElement } from 'naive-ui'
|
||||
import { computed } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { ThemeType } from './api/api-models'
|
||||
import { ThemeType, UserInfo } from './api/api-models'
|
||||
import { useUser } from './api/user'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const layout = computed(() => {
|
||||
if (route.path.startsWith('/user')) {
|
||||
document.title = route.meta.title + ' · ' + route.params.id + ' · VTsuru'
|
||||
return 'viewer'
|
||||
} else if (route.path.startsWith('/manage')) {
|
||||
document.title = route.meta.title + ' · 管理 · VTsuru'
|
||||
return 'manage'
|
||||
} else {
|
||||
return ''
|
||||
|
||||
@@ -23,7 +23,9 @@ export async function GetSelfAccount() {
|
||||
localStorage.removeItem('JWT_Token')
|
||||
console.warn('[vtsuru] Cookie 已失效, 需要重新登陆')
|
||||
message.error('Cookie 已失效, 需要重新登陆')
|
||||
location.reload()
|
||||
setTimeout(() => {
|
||||
location.reload()
|
||||
}, 1500);
|
||||
} else {
|
||||
console.warn('[vtsuru] ' + result.message)
|
||||
message.error(result.message)
|
||||
|
||||
40
src/components/SaveCompoent.vue
Normal file
40
src/components/SaveCompoent.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
import { saveAs } from 'file-saver'
|
||||
import html2canvas from 'html2canvas'
|
||||
import { NButton, useMessage } from 'naive-ui'
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const props = defineProps<{
|
||||
compoent: any
|
||||
fileName: string
|
||||
buttonText?: string
|
||||
}>()
|
||||
function saveCompoent() {
|
||||
if (!props.compoent) {
|
||||
message.error('未找到要保存的组件')
|
||||
}
|
||||
html2canvas(props.compoent, {
|
||||
width: props.compoent.clientWidth, //dom 原始宽度
|
||||
height: props.compoent.clientHeight,
|
||||
backgroundColor: null,
|
||||
scrollY: 0, // html2canvas默认绘制视图内的页面,需要把scrollY,scrollX设置为0
|
||||
scrollX: 0,
|
||||
useCORS: true, //支持跨域,但好像没什么用
|
||||
allowTaint: true, //允许跨域(默认false)
|
||||
scale: window.devicePixelRatio,
|
||||
}).then((canvas) => {
|
||||
canvas.toBlob(
|
||||
(data) => {
|
||||
saveAs(data, props.fileName + '.png')
|
||||
},
|
||||
'image/png',
|
||||
1
|
||||
)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NButton type="primary" secondary @click="saveCompoent"> {{ buttonText ?? '保存为图片' }} </NButton>
|
||||
</template>
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ref } from 'vue'
|
||||
import { defineAsyncComponent, ref } from 'vue'
|
||||
|
||||
const debugAPI = import.meta.env.VITE_DEBUG_API
|
||||
const releseAPI = `https://vtsuru.suki.club/api/`
|
||||
@@ -21,3 +21,14 @@ export const QUESTION_API_URL = `${BASE_API}qa/`
|
||||
export const LOTTERY_API_URL = `${BASE_API}lottery/`
|
||||
export const HISTORY_API_URL = `${BASE_API}history/`
|
||||
export const SCHEDULE_API_URL = `${BASE_API}schedule/`
|
||||
|
||||
export const ScheduleTemplateMap = {
|
||||
'': { name: '默认', compoent: defineAsyncComponent(() => import('@/views/view/scheduleTemplate/DefaultScheduleTemplate.vue')) },
|
||||
pinky: { name: '粉粉', compoent: defineAsyncComponent(() => import('@/views/view/scheduleTemplate/PinkySchedule.vue')) },
|
||||
} as { [key: string]: { name: string; compoent: any } }
|
||||
export const SongListTemplateMap = {
|
||||
'': { name: '默认', compoent: defineAsyncComponent(() => import('@/views/view/songListTemplate/DefaultSongListTemplate.vue')) },
|
||||
} as { [key: string]: { name: string; compoent: any } }
|
||||
export const IndexTemplateMap = {
|
||||
'': { name: '默认', compoent: defineAsyncComponent(() => import('@/views/view/indexTemplate/DefaultIndexTemplate.vue')) },
|
||||
} as { [key: string]: { name: string; compoent: any } }
|
||||
|
||||
13
src/mitt.ts
Normal file
13
src/mitt.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import mitt, { Emitter } from 'mitt'
|
||||
|
||||
declare type MittType<T = any> = {
|
||||
onOpenTemplateSettings: {
|
||||
template: string,
|
||||
|
||||
}
|
||||
};
|
||||
// 类型
|
||||
const emitter: Emitter<MittType> = mitt<MittType>()
|
||||
|
||||
// 导出
|
||||
export default emitter
|
||||
@@ -42,7 +42,7 @@ const routes: Array<RouteRecordRaw> = [
|
||||
name: 'user-questionBox',
|
||||
component: () => import('@/views/view/QuestionBoxView.vue'),
|
||||
meta: {
|
||||
title: '棉花糖 (提问箱',
|
||||
title: '提问箱',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -65,48 +65,72 @@ const routes: Array<RouteRecordRaw> = [
|
||||
name: 'manage-index',
|
||||
component: () => import('@/views/manage/DashboardView.vue'),
|
||||
meta: {
|
||||
title: '管理',
|
||||
title: '面板',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'song-list',
|
||||
name: 'manage-songList',
|
||||
component: () => import('@/views/manage/SongListManageView.vue'),
|
||||
meta: {
|
||||
title: '歌单',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'question-box',
|
||||
name: 'manage-questionBox',
|
||||
component: () => import('@/views/manage/QuestionBoxManageView.vue'),
|
||||
meta: {
|
||||
title: '提问箱',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'lottery',
|
||||
name: 'manage-lottery',
|
||||
component: () => import('@/views/manage/LotteryView.vue'),
|
||||
meta: {
|
||||
title: '动态抽奖',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'bili-verify',
|
||||
name: 'manage-biliVerify',
|
||||
component: () => import('@/views/manage/BiliVerifyView.vue'),
|
||||
meta: {
|
||||
title: 'Bilibili认证',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'history',
|
||||
name: 'manage-history',
|
||||
component: () => import('@/views/manage/HistoryView.vue'),
|
||||
meta: {
|
||||
title: '数据跟踪',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'schedule',
|
||||
name: 'manage-schedule',
|
||||
component: () => import('@/views/manage/ScheduleManageView.vue'),
|
||||
meta: {
|
||||
title: '日程',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'event',
|
||||
name: 'manage-event',
|
||||
component: () => import('@/views/manage/EventView.vue'),
|
||||
meta: {
|
||||
title: '事件记录',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'video-collect',
|
||||
name: 'manage-videoCollect',
|
||||
component: () => import('@/views/manage/VideoCollectManageView.vue'),
|
||||
meta: {
|
||||
title: '视频征集',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -33,6 +33,7 @@ import { NButton, NCard, NDivider, NLayoutContent, NSpace, NText, NTimeline, NTi
|
||||
</NSpace>
|
||||
<NDivider title-placement="left"> 更新日志 </NDivider>
|
||||
<NTimeline>
|
||||
<NTimelineItem type="info" title="功能更新" content="日程表添加 '粉粉' 模板" time="2023-10-27" />
|
||||
<NTimelineItem type="info" title="功能更新" content="提问箱新增公开选项" time="2023-10-26" />
|
||||
<NTimelineItem type="success" title="功能添加" content="提问箱分享卡片" time="2023-10-25" />
|
||||
<NTimelineItem type="success" title="功能添加" content="舰长及SC记录" time="2023-10-24" line-type="dashed" />
|
||||
|
||||
@@ -1,6 +1,26 @@
|
||||
<!-- eslint-disable vue/component-name-in-template-casing -->
|
||||
<script setup lang="ts">
|
||||
import { NAvatar, NIcon, NLayout, NLayoutHeader, NLayoutSider, NMenu, NSpace, NText, NButton, NResult, NPageHeader, NSwitch, NModal, NEllipsis, MenuOption, NSpin, NLayoutContent, NBackTop } from 'naive-ui'
|
||||
import {
|
||||
NAvatar,
|
||||
NIcon,
|
||||
NLayout,
|
||||
NLayoutHeader,
|
||||
NLayoutSider,
|
||||
NMenu,
|
||||
NSpace,
|
||||
NText,
|
||||
NButton,
|
||||
NResult,
|
||||
NPageHeader,
|
||||
NSwitch,
|
||||
NModal,
|
||||
NEllipsis,
|
||||
MenuOption,
|
||||
NSpin,
|
||||
NLayoutContent,
|
||||
NBackTop,
|
||||
NScrollbar,
|
||||
} from 'naive-ui'
|
||||
import { computed, h, onMounted, ref } from 'vue'
|
||||
import { BookOutline as BookIcon, Chatbox, Home, Moon, MusicalNote, PersonOutline as PersonIcon, Sunny, WineOutline as WineIcon } from '@vicons/ionicons5'
|
||||
import { GetInfo, useUser, useUserWithUId } from '@/api/user'
|
||||
@@ -49,10 +69,8 @@ async function RequestBiliUserData() {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const result = await GetInfo(id.value?.toString())
|
||||
if (result.code == 200) {
|
||||
userInfo.value = result.data
|
||||
} else {
|
||||
userInfo.value = await useUser(id.value?.toString())
|
||||
if (!userInfo.value) {
|
||||
notfount.value = true
|
||||
}
|
||||
|
||||
@@ -181,15 +199,19 @@ onMounted(async () => {
|
||||
class="viewer-page-content"
|
||||
:style="`box-shadow:${isDarkMode() ? 'rgb(28 28 28 / 9%) 5px 5px 6px inset, rgba(139, 139, 139, 0.09) -5px -5px 6px inset' : 'inset 5px 5px 6px #8b8b8b17, inset -5px -5px 6px #8b8b8b17;'}`"
|
||||
>
|
||||
<RouterView v-if="userInfo" v-slot="{ Component }">
|
||||
<KeepAlive>
|
||||
<component :is="Component" :bili-info="biliUserInfo" :user-info="userInfo" />
|
||||
</KeepAlive>
|
||||
<RouterView v-if="userInfo" v-slot="{ Component, route }">
|
||||
<Transition name="fade" mode="out-in">
|
||||
<KeepAlive>
|
||||
<div :key="route.name">
|
||||
<component :is="Component" :bili-info="biliUserInfo" :user-info="userInfo" />
|
||||
</div>
|
||||
</KeepAlive>
|
||||
</Transition>
|
||||
</RouterView>
|
||||
<template v-else>
|
||||
<NSpin show />
|
||||
</template>
|
||||
<NBackTop/>
|
||||
<NBackTop />
|
||||
</div>
|
||||
</NLayout>
|
||||
</NLayout>
|
||||
@@ -206,5 +228,6 @@ onMounted(async () => {
|
||||
padding: 15px;
|
||||
margin-right: 10px;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -79,26 +79,27 @@ const exportType = ref<'json' | 'xml' | 'csv'>('csv')
|
||||
|
||||
async function onDateChange() {
|
||||
isLoading.value = true
|
||||
await QueryGetAPI<EventModel[]>(BASE_API + 'event/get', {
|
||||
start: selectedDate.value[0],
|
||||
end: selectedDate.value[1],
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.code == 200) {
|
||||
events.value = new List(data.data).OrderByDescending((d) => d.time).ToArray()
|
||||
message.success('已获取数据')
|
||||
//selectedType.value = type
|
||||
} else {
|
||||
message.error('获取数据失败: ' + data.message)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
message.error('获取数据失败')
|
||||
})
|
||||
.finally(() => {
|
||||
isLoading.value = false
|
||||
const data = await get()
|
||||
events.value = data
|
||||
isLoading.value = false
|
||||
}
|
||||
async function get() {
|
||||
try {
|
||||
const data = await QueryGetAPI<EventModel[]>(BASE_API + 'event/get', {
|
||||
start: selectedDate.value[0],
|
||||
end: selectedDate.value[1],
|
||||
})
|
||||
if (data.code == 200) {
|
||||
message.success('已获取数据')
|
||||
return new List(data.data).OrderByDescending((d) => d.time).ToArray()
|
||||
} else {
|
||||
message.error('获取数据失败: ' + data.message)
|
||||
return []
|
||||
}
|
||||
} catch (err) {
|
||||
message.error('获取数据失败')
|
||||
return []
|
||||
}
|
||||
}
|
||||
function GetSCColor(price: number): string {
|
||||
if (price === 0) return `#2a60b2`
|
||||
@@ -219,7 +220,13 @@ onMounted(() => {
|
||||
<NAvatar round lazy borderd :size="64" :src="AVATAR_URL + item.uId" :img-props="{ referrerpolicy: 'no-referrer' }" style="box-shadow: 0 3px 5px rgba(0, 0, 0, 0.2)" />
|
||||
<NSpace>
|
||||
<NTag size="tiny" v-if="selectedType == EventType.Guard" :bordered="false"> {{ item.msg }} </NTag>
|
||||
<NTag size="tiny" round :color="{ color: selectedType == EventType.Guard ? GetGuardColor(item.price) : GetSCColor(item.price), textColor: 'white', borderColor: isDarkMode() ? 'white' : '#00000000' }"> {{ item.price }} </NTag>
|
||||
<NTag
|
||||
size="tiny"
|
||||
round
|
||||
:color="{ color: selectedType == EventType.Guard ? GetGuardColor(item.price) : GetSCColor(item.price), textColor: 'white', borderColor: isDarkMode() ? 'white' : '#00000000' }"
|
||||
>
|
||||
{{ item.price }}
|
||||
</NTag>
|
||||
</NSpace>
|
||||
<NText>
|
||||
{{ item.name }}
|
||||
|
||||
@@ -360,10 +360,9 @@ onMounted(() => {
|
||||
<NCard :bordered="false" size="small">
|
||||
<template #header>
|
||||
<NSpace align="center">
|
||||
<NTag size="small" type="warning" :bordered="false">
|
||||
{{ item.target.name }}
|
||||
</NTag>
|
||||
<NText depth="3">
|
||||
回复
|
||||
</NText>
|
||||
</NSpace>
|
||||
</template>
|
||||
{{ item.answer.message }}
|
||||
|
||||
@@ -1,18 +1,158 @@
|
||||
<script setup lang="ts">
|
||||
import { useAccount } from '@/api/account'
|
||||
import { NButton, NCard, NCheckbox, NCheckboxGroup, NDivider, NForm, NSelect, NSpace, NSwitch, NTabPane, NTabs, SelectOption, useMessage } from 'naive-ui'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useRequest } from 'vue-request'
|
||||
import { NButton, NCard, NCheckbox, NCheckboxGroup, NDivider, NForm, NModal, NSelect, NSpace, NSpin, NSwitch, NTabPane, NTabs, SelectOption, useMessage } from 'naive-ui'
|
||||
import { Ref, computed, h, onMounted, ref, defineAsyncComponent } from 'vue'
|
||||
import { FunctionTypes, ScheduleWeekInfo, SongFrom, SongLanguage, SongsInfo } from '@/api/api-models'
|
||||
import { QueryPostAPI } from '@/api/query'
|
||||
import { ACCOUNT_API_URL, FETCH_API } from '@/data/constants'
|
||||
import ScheduleView from '../view/ScheduleView.vue'
|
||||
import UserIndexView from '../view/UserIndexView.vue'
|
||||
import SongListView from '../view/SongListView.vue'
|
||||
import { ACCOUNT_API_URL, FETCH_API, IndexTemplateMap, ScheduleTemplateMap, SongListTemplateMap } from '@/data/constants'
|
||||
|
||||
interface TemplateDefineTypes {
|
||||
TemplateMap: { [name: string]: { name: string; compoent: any } }
|
||||
Options: SelectOption[]
|
||||
Data: any
|
||||
Selected: Ref<string>
|
||||
}
|
||||
|
||||
const accountInfo = useAccount()
|
||||
const message = useMessage()
|
||||
|
||||
const isSaving = ref(false)
|
||||
|
||||
const templates = {
|
||||
index: {
|
||||
TemplateMap: IndexTemplateMap,
|
||||
Options: Object.entries(IndexTemplateMap).map((v) => ({
|
||||
label: v[1].name,
|
||||
value: v[0],
|
||||
})),
|
||||
Data: null,
|
||||
Selected: ref(accountInfo.value?.settings.indexTemplate ?? ''),
|
||||
},
|
||||
schedule: {
|
||||
TemplateMap: ScheduleTemplateMap,
|
||||
Options: Object.entries(ScheduleTemplateMap).map((v) => ({
|
||||
label: v[1].name,
|
||||
value: v[0],
|
||||
})),
|
||||
Data: [
|
||||
{
|
||||
year: 2023,
|
||||
week: 30,
|
||||
days: [
|
||||
{
|
||||
title: '唱唱歌!',
|
||||
tag: '歌回',
|
||||
tagColor: '#61B589',
|
||||
time: '10:00 AM',
|
||||
},
|
||||
{
|
||||
title: '玩点游戏',
|
||||
tag: '游戏',
|
||||
tagColor: '#A36565',
|
||||
time: '20:00 PM',
|
||||
},
|
||||
{
|
||||
title: 'Title 3',
|
||||
tag: 'Tag 3',
|
||||
tagColor: '#7BCDEF',
|
||||
time: '11:00 PM',
|
||||
},
|
||||
{
|
||||
title: null,
|
||||
tag: null,
|
||||
tagColor: null,
|
||||
time: null,
|
||||
},
|
||||
{
|
||||
title: null,
|
||||
tag: null,
|
||||
tagColor: null,
|
||||
time: null,
|
||||
},
|
||||
{
|
||||
title: null,
|
||||
tag: null,
|
||||
tagColor: null,
|
||||
time: null,
|
||||
},
|
||||
{
|
||||
title: null,
|
||||
tag: null,
|
||||
tagColor: null,
|
||||
time: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
] as ScheduleWeekInfo[],
|
||||
Selected: ref(accountInfo.value?.settings.scheduleTemplate ?? ''),
|
||||
},
|
||||
songlist: {
|
||||
TemplateMap: SongListTemplateMap,
|
||||
Options: Object.entries(SongListTemplateMap).map((v) => ({
|
||||
label: v[1].name,
|
||||
value: v[0],
|
||||
})),
|
||||
Data: [
|
||||
{
|
||||
id: 1,
|
||||
key: 'song1',
|
||||
name: '歌曲1',
|
||||
author: ['作者1'],
|
||||
url: 'https://example.com/song1.mp3',
|
||||
from: SongFrom.Custom,
|
||||
language: [SongLanguage.Chinese],
|
||||
createTime: Date.now(),
|
||||
updateTime: Date.now(),
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
key: 'song2',
|
||||
name: '歌曲2',
|
||||
author: ['作者2'],
|
||||
url: 'https://example.com/song2.mp3',
|
||||
from: SongFrom.Custom,
|
||||
language: [SongLanguage.Chinese],
|
||||
createTime: Date.now(),
|
||||
updateTime: Date.now(),
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
key: 'song3',
|
||||
name: '歌曲3',
|
||||
author: ['作者3'],
|
||||
url: 'https://example.com/song3.mp3',
|
||||
from: SongFrom.Custom,
|
||||
language: [SongLanguage.Chinese],
|
||||
createTime: Date.now(),
|
||||
updateTime: Date.now(),
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
key: 'song4',
|
||||
name: '歌曲4',
|
||||
author: ['作者4'],
|
||||
url: 'https://example.com/song4.mp3',
|
||||
from: SongFrom.Custom,
|
||||
language: [SongLanguage.Chinese],
|
||||
createTime: Date.now(),
|
||||
updateTime: Date.now(),
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
key: 'song5',
|
||||
name: '歌曲5',
|
||||
author: ['作者5'],
|
||||
url: 'https://example.com/song5.mp3',
|
||||
from: SongFrom.Custom,
|
||||
language: [SongLanguage.Chinese],
|
||||
createTime: Date.now(),
|
||||
updateTime: Date.now(),
|
||||
},
|
||||
] as SongsInfo[],
|
||||
Selected: ref(accountInfo.value?.settings.songListTemplate ?? ''),
|
||||
},
|
||||
} as { [type: string]: TemplateDefineTypes }
|
||||
|
||||
const templateOptions = [
|
||||
{
|
||||
label: '主页',
|
||||
@@ -27,147 +167,14 @@ const templateOptions = [
|
||||
value: 'schedule',
|
||||
},
|
||||
] as SelectOption[]
|
||||
const fakeSongList = [
|
||||
{
|
||||
id: 1,
|
||||
key: 'song1',
|
||||
name: '歌曲1',
|
||||
author: ['作者1'],
|
||||
url: 'https://example.com/song1.mp3',
|
||||
from: SongFrom.Custom,
|
||||
language: [SongLanguage.Chinese],
|
||||
createTime: Date.now(),
|
||||
updateTime: Date.now(),
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
key: 'song2',
|
||||
name: '歌曲2',
|
||||
author: ['作者2'],
|
||||
url: 'https://example.com/song2.mp3',
|
||||
from: SongFrom.Custom,
|
||||
language: [SongLanguage.Chinese],
|
||||
createTime: Date.now(),
|
||||
updateTime: Date.now(),
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
key: 'song3',
|
||||
name: '歌曲3',
|
||||
author: ['作者3'],
|
||||
url: 'https://example.com/song3.mp3',
|
||||
from: SongFrom.Custom,
|
||||
language: [SongLanguage.Chinese],
|
||||
createTime: Date.now(),
|
||||
updateTime: Date.now(),
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
key: 'song4',
|
||||
name: '歌曲4',
|
||||
author: ['作者4'],
|
||||
url: 'https://example.com/song4.mp3',
|
||||
from: SongFrom.Custom,
|
||||
language: [SongLanguage.Chinese],
|
||||
createTime: Date.now(),
|
||||
updateTime: Date.now(),
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
key: 'song5',
|
||||
name: '歌曲5',
|
||||
author: ['作者5'],
|
||||
url: 'https://example.com/song5.mp3',
|
||||
from: SongFrom.Custom,
|
||||
language: [SongLanguage.Chinese],
|
||||
createTime: Date.now(),
|
||||
updateTime: Date.now(),
|
||||
},
|
||||
] as SongsInfo[]
|
||||
const fakeSchedule = [
|
||||
{
|
||||
year: 2023,
|
||||
week: 30,
|
||||
days: [
|
||||
{
|
||||
title: '唱唱歌!',
|
||||
tag: '歌回',
|
||||
tagColor: '#61B589',
|
||||
time: '10:00',
|
||||
},
|
||||
{
|
||||
title: '玩点游戏',
|
||||
tag: '游戏',
|
||||
tagColor: '#A36565',
|
||||
time: '20:00',
|
||||
},
|
||||
{
|
||||
title: 'Title 3',
|
||||
tag: 'Tag 3',
|
||||
tagColor: '#7BCDEF',
|
||||
time: '12:00 PM',
|
||||
},
|
||||
{
|
||||
title: null,
|
||||
tag: null,
|
||||
tagColor: null,
|
||||
time: null,
|
||||
},
|
||||
{
|
||||
title: null,
|
||||
tag: null,
|
||||
tagColor: null,
|
||||
time: null,
|
||||
},
|
||||
{
|
||||
title: null,
|
||||
tag: null,
|
||||
tagColor: null,
|
||||
time: null,
|
||||
},
|
||||
{
|
||||
title: null,
|
||||
tag: null,
|
||||
tagColor: null,
|
||||
time: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
] as ScheduleWeekInfo[]
|
||||
const selectedOption = ref(templateOptions[0].value)
|
||||
const selectedOption = ref('index')
|
||||
const selectedTab = ref('general')
|
||||
|
||||
const scheduleTemplateOptions = [
|
||||
{
|
||||
label: '默认',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
label: '粉粉',
|
||||
value: 'PinkySchedule',
|
||||
},
|
||||
] as SelectOption[]
|
||||
const selectedScheduleTemplate = ref(accountInfo.value?.settings.scheduleTemplate ?? scheduleTemplateOptions[0].value?.toString())
|
||||
const songListTemplateOptions = [
|
||||
{
|
||||
label: '默认',
|
||||
value: '',
|
||||
},
|
||||
] as SelectOption[]
|
||||
const selectedSongListTemplate = ref(accountInfo.value?.settings.songListTemplate ?? songListTemplateOptions[0].value)
|
||||
const selectedTemplateData = computed(() => templates[selectedOption.value])
|
||||
|
||||
const biliUserInfo = ref()
|
||||
const settingModalVisiable = ref(false)
|
||||
|
||||
function UpdateEnableFunction(func: FunctionTypes, enable: boolean) {
|
||||
if (accountInfo.value) {
|
||||
if (enable) {
|
||||
//从account.value?.settings.enableFunctions中移除指定的func
|
||||
accountInfo.value.settings.enableFunctions = accountInfo.value?.settings.enableFunctions.filter((f) => f != func)
|
||||
} else {
|
||||
accountInfo.value.settings.enableFunctions.push(func)
|
||||
}
|
||||
}
|
||||
}
|
||||
async function RequestBiliUserData() {
|
||||
await fetch(FETCH_API + `https://account.bilibili.com/api/member/getCardByMid?mid=10021741`)
|
||||
.then(async (respone) => {
|
||||
@@ -184,6 +191,7 @@ async function RequestBiliUserData() {
|
||||
}
|
||||
async function SaveComboGroupSetting(value: (string | number)[], meta: { actionType: 'check' | 'uncheck'; value: string | number }) {
|
||||
if (accountInfo.value) {
|
||||
isSaving.value = true
|
||||
//UpdateEnableFunction(meta.value as FunctionTypes, meta.actionType == 'check')
|
||||
await QueryPostAPI(ACCOUNT_API_URL + 'update-setting', accountInfo.value?.settings)
|
||||
.then((data) => {
|
||||
@@ -200,15 +208,19 @@ async function SaveComboGroupSetting(value: (string | number)[], meta: { actionT
|
||||
console.error(err)
|
||||
message.error('修改失败')
|
||||
})
|
||||
.finally(() => {
|
||||
isSaving.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
async function SaveComboSetting(value: boolean) {
|
||||
async function SaveComboSetting() {
|
||||
if (accountInfo.value) {
|
||||
isSaving.value = true
|
||||
//UpdateEnableFunction(meta.value as FunctionTypes, meta.actionType == 'check')
|
||||
await QueryPostAPI(ACCOUNT_API_URL + 'update-setting', accountInfo.value?.settings)
|
||||
.then((data) => {
|
||||
if (data.code == 200) {
|
||||
//message.success('保存成功')
|
||||
message.success('已保存')
|
||||
} else {
|
||||
message.error('修改失败')
|
||||
}
|
||||
@@ -217,19 +229,35 @@ async function SaveComboSetting(value: boolean) {
|
||||
console.error(err)
|
||||
message.error('修改失败')
|
||||
})
|
||||
.finally(() => {
|
||||
isSaving.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
const componentType = computed(() => {
|
||||
switch (selectedOption.value) {
|
||||
case 'index':
|
||||
return UserIndexView
|
||||
case 'songlist':
|
||||
return SongListView
|
||||
case 'schedule':
|
||||
return ScheduleView
|
||||
default:
|
||||
return UserIndexView
|
||||
async function SaveTemplateSetting() {
|
||||
if (accountInfo.value) {
|
||||
switch (selectedOption.value) {
|
||||
case 'index': {
|
||||
accountInfo.value.settings.indexTemplate = selectedTemplateData.value.Selected.value ?? ''
|
||||
break
|
||||
}
|
||||
case 'songlist': {
|
||||
accountInfo.value.settings.songListTemplate = selectedTemplateData.value.Selected.value ?? ''
|
||||
break
|
||||
}
|
||||
case 'schedule': {
|
||||
accountInfo.value.settings.scheduleTemplate = selectedTemplateData.value.Selected.value ?? ''
|
||||
break
|
||||
}
|
||||
}
|
||||
await SaveComboSetting()
|
||||
}
|
||||
}
|
||||
function onOpenTemplateSettings() {
|
||||
settingModalVisiable.value = true
|
||||
}
|
||||
const buttonGroup = computed(() => {
|
||||
return h(NSpace, () => [h(NButton, { type: 'primary', onClick: () => SaveTemplateSetting() }, () => '设为展示模板'), h(NButton, { type: 'info', onClick: onOpenTemplateSettings }, () => '模板设置')])
|
||||
})
|
||||
onMounted(() => {
|
||||
RequestBiliUserData()
|
||||
@@ -238,42 +266,46 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<NCard v-if="accountInfo" title="设置" :style="`${selectedTab === 'general' ? '' : 'min-height: 800px;'}`">
|
||||
<NTabs>
|
||||
<NTabPane tab="常规" name="general">
|
||||
<NDivider style="margin: 0"> 启用功能 </NDivider>
|
||||
<NCheckboxGroup v-model:value="accountInfo.settings.enableFunctions" @update:value="SaveComboGroupSetting">
|
||||
<NCheckbox :value="FunctionTypes.SongList"> 歌单 </NCheckbox>
|
||||
<NCheckbox :value="FunctionTypes.QuestionBox"> 提问箱(棉花糖 </NCheckbox>
|
||||
<NCheckbox :value="FunctionTypes.Schedule"> 日程 </NCheckbox>
|
||||
</NCheckboxGroup>
|
||||
<NDivider> 通知 </NDivider>
|
||||
<NSpace>
|
||||
<NCheckbox v-model:checked="accountInfo.settings.sendEmail.recieveQA" @update:checked="SaveComboSetting"> 收到新提问时发送邮件 </NCheckbox>
|
||||
<NCheckbox v-model:checked="accountInfo.settings.sendEmail.recieveQAReply" @update:checked="SaveComboSetting"> 提问收到回复时发送邮件 </NCheckbox>
|
||||
</NSpace>
|
||||
<NDivider> 提问箱 </NDivider>
|
||||
<NSpace>
|
||||
<NCheckbox v-model:checked="accountInfo.settings.questionBox.allowUnregistedUser" @update:checked="SaveComboSetting"> 允许未注册用户提问 </NCheckbox>
|
||||
</NSpace>
|
||||
</NTabPane>
|
||||
<NTabPane tab="模板" name="template">
|
||||
<NSpace vertical>
|
||||
<NSpace align="center"> 页面 <NSelect :options="templateOptions" v-model:value="selectedOption" style="width: 150px" /> </NSpace>
|
||||
<NDivider style="margin: 5px 0 5px 0" title-placement="left"> 模板 </NDivider>
|
||||
<template v-if="selectedOption == 'index'">
|
||||
<NSelect style="width: 150px" />
|
||||
<UserIndexView :user-info="accountInfo" :bili-info="biliUserInfo" />
|
||||
</template>
|
||||
<template v-else-if="selectedOption == 'songlist'">
|
||||
<NSelect :options="songListTemplateOptions" v-model:value="selectedSongListTemplate" style="width: 150px" />
|
||||
<SongListView :user-info="accountInfo" :bili-info="biliUserInfo" :fake-data="fakeSongList" :is-self="false" />
|
||||
</template>
|
||||
<template v-else-if="selectedOption == 'schedule'">
|
||||
<NSelect :options="scheduleTemplateOptions" v-model:value="selectedScheduleTemplate" style="width: 150px" />
|
||||
<ScheduleView :user-info="accountInfo" :bili-info="biliUserInfo" :fake-data="fakeSchedule" :is-self="false" :template="selectedScheduleTemplate" />
|
||||
</template>
|
||||
</NSpace>
|
||||
</NTabPane>
|
||||
</NTabs>
|
||||
<NSpin :show="isSaving">
|
||||
<NTabs>
|
||||
<NTabPane tab="常规" name="general">
|
||||
<NDivider style="margin: 0"> 启用功能 </NDivider>
|
||||
<NCheckboxGroup v-model:value="accountInfo.settings.enableFunctions" @update:value="SaveComboGroupSetting">
|
||||
<NCheckbox :value="FunctionTypes.SongList"> 歌单 </NCheckbox>
|
||||
<NCheckbox :value="FunctionTypes.QuestionBox"> 提问箱(棉花糖 </NCheckbox>
|
||||
<NCheckbox :value="FunctionTypes.Schedule"> 日程 </NCheckbox>
|
||||
</NCheckboxGroup>
|
||||
<NDivider> 通知 </NDivider>
|
||||
<NSpace>
|
||||
<NCheckbox v-model:checked="accountInfo.settings.sendEmail.recieveQA" @update:checked="SaveComboSetting"> 收到新提问时发送邮件 </NCheckbox>
|
||||
<NCheckbox v-model:checked="accountInfo.settings.sendEmail.recieveQAReply" @update:checked="SaveComboSetting"> 提问收到回复时发送邮件 </NCheckbox>
|
||||
</NSpace>
|
||||
<NDivider> 提问箱 </NDivider>
|
||||
<NSpace>
|
||||
<NCheckbox v-model:checked="accountInfo.settings.questionBox.allowUnregistedUser" @update:checked="SaveComboSetting"> 允许未注册用户提问 </NCheckbox>
|
||||
</NSpace>
|
||||
</NTabPane>
|
||||
<NTabPane tab="模板" name="template">
|
||||
<NSpace vertical>
|
||||
<NSpace align="center"> 页面 <NSelect :options="templateOptions" v-model:value="selectedOption" style="width: 150px" /> </NSpace>
|
||||
<NDivider style="margin: 5px 0 5px 0" title-placement="left"> 模板 </NDivider>
|
||||
<div>
|
||||
<NSpace>
|
||||
<NSelect style="width: 150px" :options="selectedTemplateData.Options" v-model:value="selectedTemplateData.Selected.value" />
|
||||
<component :is="buttonGroup" />
|
||||
</NSpace>
|
||||
<NDivider />
|
||||
<component
|
||||
:is="selectedTemplateData.TemplateMap[selectedTemplateData.Selected.value].compoent"
|
||||
:user-info="accountInfo"
|
||||
:bili-info="biliUserInfo"
|
||||
:current-data="selectedTemplateData.Data"
|
||||
/>
|
||||
</div>
|
||||
</NSpace>
|
||||
</NTabPane>
|
||||
</NTabs>
|
||||
</NSpin>
|
||||
</NCard>
|
||||
<NModal preset="card" v-model:show="settingModalVisiable" closable style="width: 600px; max-width: 90vw" title="模板设置"> 开发中... </NModal>
|
||||
</template>
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
<template>
|
||||
<NSpin v-if="isLoading" show />
|
||||
<component v-else v-bind="$attrs" :is="componentType" :user-info="userInfo" :currentData="currentData" />
|
||||
<component v-else :is="ScheduleTemplateMap[componentType ?? ''].compoent" :bili-info="biliInfo" :user-info="userInfo" :currentData="currentData" v-bind="$attrs" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ScheduleWeekInfo } from '@/api/api-models'
|
||||
import DefaultScheduleTemplate from '@/views/view/scheduleTemplate/DefaultScheduleTemplate.vue'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { UserInfo } from '@/api/api-models'
|
||||
import { QueryGetAPI } from '@/api/query'
|
||||
import { SCHEDULE_API_URL } from '@/data/constants'
|
||||
import { SCHEDULE_API_URL, ScheduleTemplateMap } from '@/data/constants'
|
||||
import { NSpin, useMessage } from 'naive-ui'
|
||||
import PinkySchedule from './scheduleTemplate/PinkySchedule.vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -22,19 +20,7 @@ const props = defineProps<{
|
||||
}>()
|
||||
|
||||
const componentType = computed(() => {
|
||||
const type = props.template ?? props.userInfo?.extra?.templateTypes['schedule']?.toLowerCase()
|
||||
if (props.userInfo) {
|
||||
switch (type?.toLocaleLowerCase()) {
|
||||
case 'test':
|
||||
return DefaultScheduleTemplate
|
||||
case 'pinkyschedule':
|
||||
return PinkySchedule
|
||||
default:
|
||||
return DefaultScheduleTemplate
|
||||
}
|
||||
} else {
|
||||
return DefaultScheduleTemplate
|
||||
}
|
||||
return props.template ?? props.userInfo?.extra?.templateTypes['schedule']?.toLowerCase()
|
||||
})
|
||||
const currentData = ref<ScheduleWeekInfo[]>()
|
||||
const isLoading = ref(true)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<NSpin v-if="isLoading" show />
|
||||
<component v-else :is="componentType" :user-info="userInfo" :currentData="currentData" />
|
||||
<component v-else :is="componentType" :user-info="userInfo" :bili-info="biliInfo" :currentData="currentData" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
@@ -8,6 +8,7 @@ const width = window.innerWidth
|
||||
const props = defineProps<{
|
||||
userInfo: UserInfo | undefined
|
||||
biliInfo: any | undefined
|
||||
currentData: any
|
||||
}>()
|
||||
function navigate(url: string) {
|
||||
window.open(url, '_blank')
|
||||
@@ -24,6 +25,9 @@ function navigate(url: string) {
|
||||
:size="width > 750 ? 175 : 100"
|
||||
round
|
||||
bordered
|
||||
:img-props="{
|
||||
referrerpolicy: 'no-referrer'
|
||||
}"
|
||||
:style="{ boxShadow: isDarkMode() ? 'rgb(195 192 192 / 35%) 0px 5px 20px' : '0 5px 15px rgba(0, 0, 0, 0.2)' }"
|
||||
/>
|
||||
<NSpace align="baseline" justify="center">
|
||||
|
||||
@@ -8,6 +8,7 @@ const accountInfo = useAccount()
|
||||
|
||||
defineProps<{
|
||||
userInfo: UserInfo | undefined
|
||||
biliInfo: any | undefined
|
||||
currentData: ScheduleWeekInfo[] | undefined
|
||||
}>()
|
||||
</script>
|
||||
|
||||
@@ -1,23 +1,167 @@
|
||||
<script setup lang="ts">
|
||||
import { ScheduleWeekInfo, UserInfo } from '@/api/api-models';
|
||||
import { ScheduleWeekInfo, UserInfo } from '@/api/api-models'
|
||||
import { getYear, getWeek, startOfWeek } from 'date-fns'
|
||||
import { NDivider, NSelect, NSpace } from 'naive-ui'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import SaveCompoent from '@/components/SaveCompoent.vue'
|
||||
|
||||
defineProps<{
|
||||
const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
|
||||
const table = ref()
|
||||
|
||||
const props = defineProps<{
|
||||
userInfo: UserInfo | undefined
|
||||
biliInfo: any | undefined
|
||||
currentData: ScheduleWeekInfo[] | undefined
|
||||
}>()
|
||||
|
||||
const currentWeek = computed(() => {
|
||||
if (props.currentData?.length == 1) {
|
||||
return props.currentData[0]
|
||||
}
|
||||
return props.currentData?.find((item) => {
|
||||
if (selectedDate.value) {
|
||||
return item.year + '-' + item.week === selectedDate.value
|
||||
}
|
||||
return isTodayInWeek(item.year, item.week)
|
||||
})
|
||||
})
|
||||
const options = computed(() => {
|
||||
return props.currentData?.map((item) => {
|
||||
return {
|
||||
label: item.year + '年' + item.week + '周',
|
||||
value: item.year + '-' + item.week,
|
||||
}
|
||||
})
|
||||
})
|
||||
const selectedDate = ref<string>()
|
||||
|
||||
function isTodayInWeek(year: number, week: number): boolean {
|
||||
const today = new Date()
|
||||
const todayYear = getYear(today)
|
||||
const todayWeek = getWeek(today, { weekStartsOn: 1 })
|
||||
|
||||
return todayYear === year && todayWeek === week
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (currentWeek.value) {
|
||||
selectedDate.value = currentWeek.value.year + '-' + currentWeek.value.week
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="schedule-template pinky">
|
||||
<div v-for="item in currentData" :key="item.week">
|
||||
test
|
||||
<NSpace>
|
||||
<NSelect :options="options" v-model:value="selectedDate" style="max-width: 200px" placeholder="选择其他周表" />
|
||||
<SaveCompoent :compoent="table" :file-name="`周表_${selectedDate}_${userInfo?.name}`" />
|
||||
</NSpace>
|
||||
<NDivider />
|
||||
<div ref="table" class="schedule-template pinky container">
|
||||
<div class="schedule-template pinky day-container">
|
||||
<div class="schedule-template pinky day-item" :id="index.toString()" v-for="(item, index) in currentWeek?.days" :key="index">
|
||||
<div class="schedule-template pinky header">
|
||||
<span class="schedule-template pinky week">
|
||||
{{ days[index] }}
|
||||
</span>
|
||||
<span class="schedule-template pinky time">
|
||||
{{ item.time }}
|
||||
</span>
|
||||
<span class="schedule-template pinky tag">
|
||||
{{ item.tag }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="schedule-template pinky day-content-container">
|
||||
<span v-if="item.tag" class="schedule-template pinky day-content" id="work">
|
||||
{{ item.title }}
|
||||
</span>
|
||||
<span v-else class="schedule-template pinky day-content" id="rest"> 休息 </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="schedule-template pinky title-container">
|
||||
<div class="schedule-template pinky title">S C H E D U L E</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.schedule-template pinky {
|
||||
background-color: #ebf0fa;
|
||||
.schedule-template.pinky.container {
|
||||
--pinky-font-color-dark: #dba2a2c9;
|
||||
--pinky-border-color-dark: #eeb9b9;
|
||||
width: 540px;
|
||||
height: 700px;
|
||||
border-radius: 20px;
|
||||
background-color: #faebeb;
|
||||
background-image: linear-gradient(90deg, #ffffff 10%, rgba(0, 0, 0, 0) 10%), linear-gradient(#ffffff 10%, rgba(0, 0, 0, 0) 10%);
|
||||
background-size: 20px 20px;
|
||||
border: 3px solid #e0cbcb;
|
||||
}
|
||||
.schedule-template.pinky.day-container {
|
||||
display: flex;
|
||||
width: 500px;
|
||||
margin-top: 10px;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: column;
|
||||
margin-left: 20px;
|
||||
}
|
||||
.schedule-template.pinky.day-content-container {
|
||||
height: 50px;
|
||||
border-radius: 15px;
|
||||
border: 3px solid var(--pinky-border-color-dark);
|
||||
border-top: none;
|
||||
border-left: none;
|
||||
background-color: #f5dadac9;
|
||||
}
|
||||
.schedule-template.pinky.header {
|
||||
position: relative;
|
||||
height: 30px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #dba2a2c9;
|
||||
top: 2px;
|
||||
}
|
||||
.schedule-template.pinky.week {
|
||||
position: relative;
|
||||
left: 5px;
|
||||
}
|
||||
.schedule-template.pinky.time {
|
||||
position: relative;
|
||||
left: 15px;
|
||||
}
|
||||
.schedule-template.pinky.tag {
|
||||
position: absolute;
|
||||
text-align: right;
|
||||
right: 5px;
|
||||
}
|
||||
.schedule-template.pinky.day-content {
|
||||
position: relative;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
font-size: 18px;
|
||||
font-weight: 550;
|
||||
color: #af8080c9;
|
||||
}
|
||||
.schedule-template.pinky.day-content#rest {
|
||||
color: #cfb7b7c9;
|
||||
font-style: italic;
|
||||
}
|
||||
.schedule-template.pinky.title-container {
|
||||
position: relative;
|
||||
background: #fdf8f8c9;
|
||||
width: 400px;
|
||||
height: 70px;
|
||||
left: 70px;
|
||||
top: 20px;
|
||||
border-radius: 20px;
|
||||
border: 3px solid var(--pinky-border-color-dark);
|
||||
border-top: none;
|
||||
border-left: none;
|
||||
}
|
||||
.schedule-template.pinky.title {
|
||||
position: relative;
|
||||
top: -5px;
|
||||
font-size: 50px;
|
||||
text-align: center;
|
||||
color: var(--pinky-font-color-dark);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,6 +8,7 @@ const accountInfo = useAccount()
|
||||
|
||||
defineProps<{
|
||||
userInfo: UserInfo | undefined
|
||||
biliInfo: any | undefined
|
||||
currentData: SongsInfo[] | undefined
|
||||
}>()
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user