mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-07 02:46:55 +08:00
add switch
This commit is contained in:
@@ -2,7 +2,19 @@
|
||||
import { GetSelfAccount, useAccount } from '@/api/account'
|
||||
import { QueryGetAPI } from '@/api/query'
|
||||
import { BILI_API_URL } from '@/data/constants'
|
||||
import { NAlert, NButton, NCard, NCountdown, NInput, NInputGroup, NInputNumber, NSpace, NSpin, NText, useMessage } from 'naive-ui'
|
||||
import {
|
||||
NAlert,
|
||||
NButton,
|
||||
NCard,
|
||||
NCountdown,
|
||||
NInput,
|
||||
NInputGroup,
|
||||
NInputNumber,
|
||||
NSpace,
|
||||
NSpin,
|
||||
NText,
|
||||
useMessage,
|
||||
} from 'naive-ui'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const message = useMessage()
|
||||
@@ -102,7 +114,9 @@ onMounted(async () => {
|
||||
<NInput :allow-input="() => false" v-model:value="accountInfo.biliVerifyCode" />
|
||||
<NButton @click="copyCode"> 复制认证码 </NButton>
|
||||
</NInputGroup>
|
||||
<NButton v-if="roomId" type="primary" tag="a" :href="'https://live.bilibili.com/' + roomId" target="_blank"> 前往直播间 </NButton>
|
||||
<NButton v-if="roomId" type="primary" tag="a" :href="'https://live.bilibili.com/' + roomId" target="_blank">
|
||||
前往直播间
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
<template v-else>
|
||||
|
||||
@@ -30,6 +30,11 @@ onUnmounted(() => {
|
||||
<NAlert v-if="accountInfo?.isBiliVerified != true" type="info"> 尚未进行Bilibili认证 </NAlert>
|
||||
<NSpin v-else-if="isClientLoading" show />
|
||||
<KeepAlive v-else>
|
||||
<component :is="component" :client="client" :room-info="client.roomAuthInfo?.value" :code="accountInfo?.biliAuthCode" />
|
||||
<component
|
||||
:is="component"
|
||||
:client="client"
|
||||
:room-info="client.roomAuthInfo?.value"
|
||||
:code="accountInfo?.biliAuthCode"
|
||||
/>
|
||||
</KeepAlive>
|
||||
</template>
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
NUl,
|
||||
useMessage,
|
||||
} from 'naive-ui'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
enum EventType {
|
||||
Guard,
|
||||
@@ -57,7 +57,10 @@ const rangeShortcuts = {
|
||||
上个月: () => {
|
||||
const cur = new Date()
|
||||
const lastMonth = new Date(cur.getFullYear(), cur.getMonth() - 1)
|
||||
return [new Date(cur.getFullYear(), cur.getMonth() - 1, 1).getTime(), new Date(cur.getFullYear(), cur.getMonth(), 1).getTime()] as const
|
||||
return [
|
||||
new Date(cur.getFullYear(), cur.getMonth() - 1, 1).getTime(),
|
||||
new Date(cur.getFullYear(), cur.getMonth(), 1).getTime(),
|
||||
] as const
|
||||
},
|
||||
本月: () => {
|
||||
const cur = new Date()
|
||||
@@ -143,8 +146,9 @@ function exportData() {
|
||||
}
|
||||
saveAs(
|
||||
new Blob([text], { type: 'text/plain;charset=utf-8' }),
|
||||
`${format(Date.now(), 'yyyy-MM-dd HH:mm:ss')}_${format(selectedDate.value[0], 'yyyy-MM-dd HH:mm:ss')}_${format(selectedDate.value[1], 'yyyy-MM-dd HH:mm:ss')}}_${accountInfo.value
|
||||
?.id}_${accountInfo.value?.name}_${selectedType.value}.${exportType.value}`,
|
||||
`${format(Date.now(), 'yyyy-MM-dd HH:mm:ss')}_${format(selectedDate.value[0], 'yyyy-MM-dd HH:mm:ss')}_${format(selectedDate.value[1], 'yyyy-MM-dd HH:mm:ss')}}_${
|
||||
accountInfo.value?.id
|
||||
}_${accountInfo.value?.name}_${selectedType.value}.${exportType.value}`,
|
||||
)
|
||||
}
|
||||
function objectsToCSV(arr: any[]) {
|
||||
@@ -163,12 +167,19 @@ function objectsToCSV(arr: any[]) {
|
||||
|
||||
<template>
|
||||
<NSpace vertical>
|
||||
<NAlert v-if="!accountInfo?.isBiliVerified" type="warning"> 使用此功能前你需要先<NButton type="info" text @click="$router.push({ name: 'manage-biliVerify' })">认证Bilibili账号</NButton> </NAlert>
|
||||
<NAlert v-if="!accountInfo?.isBiliVerified" type="warning">
|
||||
使用此功能前你需要先<NButton type="info" text @click="$router.push({ name: 'manage-biliVerify' })"
|
||||
>认证Bilibili账号</NButton
|
||||
>
|
||||
</NAlert>
|
||||
<NAlert type="info">
|
||||
当前本站正在测试为粉丝数大于 1000 或至少拥有一位舰长的主播直接从服务端记录并储存弹幕数据, 不过并不清楚B站的风控策略, 此功能不一定会长期启用
|
||||
当前本站正在测试为粉丝数大于 1000 或至少拥有一位舰长的主播直接从服务端记录并储存弹幕数据,
|
||||
不过并不清楚B站的风控策略, 此功能不一定会长期启用
|
||||
<br />
|
||||
在我们被限制连接之前满足以上条件的主播无需部署
|
||||
<NButton tag="a" href="https://www.yuque.com/megghy/dez70g/vfvcyv3024xvaa1p" target="_blank" type="primary" text> VtsuruEventFetcher </NButton>
|
||||
<NButton tag="a" href="https://www.yuque.com/megghy/dez70g/vfvcyv3024xvaa1p" target="_blank" type="primary" text>
|
||||
VtsuruEventFetcher
|
||||
</NButton>
|
||||
即可使用相关功能 (如记录上舰和SC, 直播场记录等) 😊
|
||||
</NAlert>
|
||||
<EventFetcherStatusCard />
|
||||
@@ -177,7 +188,14 @@ function objectsToCSV(arr: any[]) {
|
||||
<NCard size="small" style="witdh: 100%">
|
||||
<template v-if="accountInfo?.isBiliVerified">
|
||||
<NSpace justify="center" align="center">
|
||||
<NDatePicker v-model:value="selectedDate" @update:value="onDateChange" type="datetimerange" :shortcuts="rangeShortcuts" start-placeholder="开始时间" end-placeholder="结束时间" />
|
||||
<NDatePicker
|
||||
v-model:value="selectedDate"
|
||||
@update:value="onDateChange"
|
||||
type="datetimerange"
|
||||
:shortcuts="rangeShortcuts"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
/>
|
||||
<NRadioGroup v-model:value="selectedType">
|
||||
<NRadioButton :value="EventType.Guard">舰长</NRadioButton>
|
||||
<NRadioButton :value="EventType.SC">Superchat</NRadioButton>
|
||||
@@ -209,15 +227,32 @@ function objectsToCSV(arr: any[]) {
|
||||
<div v-if="displayMode == 'grid'">
|
||||
<NGrid cols="1 500:2 800:3 1000:4" :x-gap="12" :y-gap="8">
|
||||
<NGridItem v-for="item in selectedEvents" v-bind:key="item.time">
|
||||
<NCard size="small" :style="`height: ${selectedType == EventType.Guard ? '175px' : '220'}px`" embedded hoverable>
|
||||
<NCard
|
||||
size="small"
|
||||
:style="`height: ${selectedType == EventType.Guard ? '175px' : '220'}px`"
|
||||
embedded
|
||||
hoverable
|
||||
>
|
||||
<NSpace align="center" vertical :size="5">
|
||||
<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)" />
|
||||
<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' }"
|
||||
:color="{
|
||||
color: selectedType == EventType.Guard ? GetGuardColor(item.price) : GetSCColor(item.price),
|
||||
textColor: 'white',
|
||||
borderColor: isDarkMode() ? 'white' : '#00000000',
|
||||
}"
|
||||
>
|
||||
{{ item.price }}
|
||||
</NTag>
|
||||
@@ -252,7 +287,15 @@ function objectsToCSV(arr: any[]) {
|
||||
<td><NTime :time="item.time" /></td>
|
||||
<td v-if="selectedType == EventType.Guard">{{ item.msg }}</td>
|
||||
<td>
|
||||
<NTag :color="{ color: selectedType == EventType.Guard ? GetGuardColor(item.price) : GetSCColor(item.price), textColor: 'white', borderColor: 'white' }"> {{ item.price }} </NTag>
|
||||
<NTag
|
||||
:color="{
|
||||
color: selectedType == EventType.Guard ? GetGuardColor(item.price) : GetSCColor(item.price),
|
||||
textColor: 'white',
|
||||
borderColor: 'white',
|
||||
}"
|
||||
>
|
||||
{{ item.price }}
|
||||
</NTag>
|
||||
</td>
|
||||
<td v-if="selectedType == EventType.SC">
|
||||
<NEllipsis style="max-width: 300px">{{ item.msg }}</NEllipsis>
|
||||
@@ -265,8 +308,13 @@ function objectsToCSV(arr: any[]) {
|
||||
</template>
|
||||
<template v-else>
|
||||
<NCollapse :default-expanded-names="['1']">
|
||||
<NCollapseItem title="这是什么?" name="1"> 可以查看曾经收到的Superchat以及上舰记录, 并导出为 CSV 之类的表格 </NCollapseItem>
|
||||
<NCollapseItem title="可以直接用吗"> 遗憾的是并不能, 使用这个功能需要你拥有一个可以7*24小时运行 Docker 容器或者 Node.js 脚本的环境, 并且可以访问互联网 </NCollapseItem>
|
||||
<NCollapseItem title="这是什么?" name="1">
|
||||
可以查看曾经收到的Superchat以及上舰记录, 并导出为 CSV 之类的表格
|
||||
</NCollapseItem>
|
||||
<NCollapseItem title="可以直接用吗">
|
||||
遗憾的是并不能, 使用这个功能需要你拥有一个可以7*24小时运行 Docker 容器或者 Node.js 脚本的环境,
|
||||
并且可以访问互联网
|
||||
</NCollapseItem>
|
||||
<NCollapseItem title="有没有什么要求?">
|
||||
关于环境的话理论上能够运行 Docker 或者 Node.js 的环境都能可以
|
||||
<br /><br />
|
||||
@@ -278,13 +326,18 @@ function objectsToCSV(arr: any[]) {
|
||||
<NLi>拥有掌握以上技能的 stf 或者朋友</NLi>
|
||||
</NUl>
|
||||
<NH3>
|
||||
<NText strong> 即使你对相关知识一窍不通也不用担心, 跟着后面的傻瓜教程中的 Koyeb 也可以完成部署. 理论上这玩意里头的免费套餐就够用了, 当然如果你想要更稳一点上个付费套餐也不影响 </NText>
|
||||
<NText strong>
|
||||
即使你对相关知识一窍不通也不用担心, 跟着后面的傻瓜教程中的 Koyeb 也可以完成部署.
|
||||
理论上这玩意里头的免费套餐就够用了, 当然如果你想要更稳一点上个付费套餐也不影响
|
||||
</NText>
|
||||
</NH3>
|
||||
</NCollapseItem>
|
||||
</NCollapse>
|
||||
<NDivider style="margin-bottom: 10px" />
|
||||
<NSpace justify="center">
|
||||
<NButton tag="a" href="https://www.yuque.com/megghy/dez70g/vfvcyv3024xvaa1p" target="_blank" type="primary"> 部署指南 </NButton>
|
||||
<NButton tag="a" href="https://www.yuque.com/megghy/dez70g/vfvcyv3024xvaa1p" target="_blank" type="primary">
|
||||
部署指南
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NCard>
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
} from 'echarts/components'
|
||||
import { use } from 'echarts/core'
|
||||
import { CanvasRenderer } from 'echarts/renderers'
|
||||
import { NAlert, NButton, NCard, NDivider, NIcon, NSpace, NSpin, NText, NTime, NTooltip, useMessage } from 'naive-ui'
|
||||
import { NAlert, NButton, NCard, NDivider, NIcon, NSpace, NSpin, NTime, NTooltip, useMessage } from 'naive-ui'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import VChart from 'vue-echarts'
|
||||
|
||||
@@ -97,8 +97,8 @@ function isSameDaySimple(time1: number, time2: number) {
|
||||
const statisticStartDate = new Date(2023, 10, 4)
|
||||
const statisticStartDateTime = statisticStartDate.getTime()
|
||||
function getOptions() {
|
||||
let fansIncreacement = [] as { time: Date; count: number }[]
|
||||
let completeTimeSeries: {
|
||||
const fansIncreacement = [] as { time: Date; count: number }[]
|
||||
const completeTimeSeries: {
|
||||
time: Date
|
||||
count: number
|
||||
change: boolean
|
||||
@@ -147,7 +147,7 @@ function getOptions() {
|
||||
completeTimeSeries.forEach((entry, index, array) => {
|
||||
if (index === 0 || !isSameDay(entry.time, array[index - 1].time)) {
|
||||
if (index > 0) {
|
||||
let dailyIncrement = entry.count - previousDayCount
|
||||
const dailyIncrement = entry.count - previousDayCount
|
||||
fansIncreacement.push({
|
||||
time: startOfHour(array[index - 1].time),
|
||||
count: dailyIncrement,
|
||||
@@ -156,7 +156,7 @@ function getOptions() {
|
||||
}
|
||||
previousDayCount = entry.count
|
||||
} else if (index === array.length - 1) {
|
||||
let dailyIncrement = entry.count - previousDayCount
|
||||
const dailyIncrement = entry.count - previousDayCount
|
||||
fansIncreacement.push({
|
||||
time: startOfHour(entry.time),
|
||||
count: dailyIncrement,
|
||||
@@ -168,19 +168,19 @@ function getOptions() {
|
||||
|
||||
let lastDayGuards = 0
|
||||
let lastDay = 0
|
||||
let guardsIncreacement = [] as { time: number; count: number; timeString: string }[]
|
||||
let guards = [] as { time: number; count: number; timeString: string }[]
|
||||
const guardsIncreacement = [] as { time: number; count: number; timeString: string }[]
|
||||
const guards = [] as { time: number; count: number; timeString: string }[]
|
||||
|
||||
// 生成完整的天序列
|
||||
let currentGuardTime = startTime
|
||||
let lastDayGuardCount = 0
|
||||
let completeGuardTimeSeries: {
|
||||
const completeGuardTimeSeries: {
|
||||
time: Date
|
||||
count: number
|
||||
}[] = []
|
||||
while (currentGuardTime <= endTime) {
|
||||
let found = guardHistory.value?.find((f) => isSameDay(currentGuardTime, f.time))
|
||||
let count = found ? found.count : lastDayGuardCount
|
||||
const found = guardHistory.value?.find((f) => isSameDay(currentGuardTime, f.time))
|
||||
const count = found ? found.count : lastDayGuardCount
|
||||
lastDayGuardCount = count
|
||||
|
||||
completeGuardTimeSeries.push({
|
||||
@@ -208,8 +208,8 @@ function getOptions() {
|
||||
lastDayGuards = g.count
|
||||
}
|
||||
})
|
||||
let upstatViewIncreace: { time: number; value: number }[] = []
|
||||
let upstatLikeIncreace: { time: number; value: number }[] = []
|
||||
const upstatViewIncreace: { time: number; value: number }[] = []
|
||||
const upstatLikeIncreace: { time: number; value: number }[] = []
|
||||
if (upstatHistory.value && upstatHistory.value.length > 0) {
|
||||
let lastUpstatView = upstatHistory.value[0].stats.views
|
||||
let lastUpstatLike = upstatHistory.value[0].stats.likes
|
||||
@@ -247,7 +247,7 @@ function getOptions() {
|
||||
},
|
||||
},
|
||||
formatter: (param: any) => {
|
||||
let name = param[0].name + '<br>'
|
||||
const name = param[0].name + '<br>'
|
||||
let str = ''
|
||||
for (var i = 0; i < param.length; i++) {
|
||||
const status =
|
||||
|
||||
@@ -6,7 +6,7 @@ import { onMounted, onUnmounted } from 'vue'
|
||||
import OpenLottery from '../open_live/OpenLottery.vue'
|
||||
|
||||
const accountInfo = useAccount()
|
||||
let client = new DanmakuClient(null)
|
||||
const client = new DanmakuClient(null)
|
||||
|
||||
onMounted(() => {
|
||||
client.Start()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { copyToClipboard, downloadImage } from '@/Utils'
|
||||
import { SaveAccountSettings, useAccount } from '@/api/account'
|
||||
import { QAInfo } from '@/api/api-models'
|
||||
import { DisableFunction, EnableFunction, SaveAccountSettings, useAccount } from '@/api/account'
|
||||
import { FunctionTypes, QAInfo } from '@/api/api-models'
|
||||
import { QueryGetAPI } from '@/api/query'
|
||||
import { QUESTION_API_URL } from '@/data/constants'
|
||||
import router from '@/router'
|
||||
@@ -10,6 +10,7 @@ import { saveAs } from 'file-saver'
|
||||
import html2canvas from 'html2canvas'
|
||||
import {
|
||||
NAffix,
|
||||
NAlert,
|
||||
NButton,
|
||||
NCard,
|
||||
NCheckbox,
|
||||
@@ -123,6 +124,20 @@ async function saveSettings() {
|
||||
|
||||
const parentRef = ref<HTMLElement | null>(null)
|
||||
|
||||
async function setFunctionEnable(enable: boolean) {
|
||||
let success = false
|
||||
if (enable) {
|
||||
success = await EnableFunction(FunctionTypes.QuestionBox)
|
||||
} else {
|
||||
success = await DisableFunction(FunctionTypes.QuestionBox)
|
||||
}
|
||||
if (success) {
|
||||
message.success('已' + (enable ? '启用' : '禁用'))
|
||||
} else {
|
||||
message.error('无法' + (enable ? '启用' : '禁用'))
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (selectedTabItem.value == '0') {
|
||||
useQB.GetRecieveQAInfo()
|
||||
@@ -137,7 +152,15 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NSpace>
|
||||
<NSpace align="center">
|
||||
<NAlert type="info" style="max-width: 200px">
|
||||
启用提问箱
|
||||
<NDivider vertical />
|
||||
<NSwitch
|
||||
:value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.QuestionBox)"
|
||||
@update:value="setFunctionEnable"
|
||||
/>
|
||||
</NAlert>
|
||||
<NButton type="primary" @click="refresh"> 刷新 </NButton>
|
||||
<NButton type="primary" @click="shareModalVisiable = true" secondary> 分享 </NButton>
|
||||
</NSpace>
|
||||
@@ -145,9 +168,7 @@ onMounted(() => {
|
||||
<NSpin v-if="useQB.isLoading" show />
|
||||
<NTabs v-else animated @update:value="onTabChange" v-model:value="selectedTabItem">
|
||||
<NTabPane tab="我收到的" name="0">
|
||||
<NButton @click="$router.push({ name: 'question-display' })" type="primary">
|
||||
打开展示页
|
||||
</NButton>
|
||||
<NButton @click="$router.push({ name: 'question-display' })" type="primary"> 打开展示页 </NButton>
|
||||
<NDivider vertical />
|
||||
<NCheckbox v-model:checked="useQB.onlyFavorite"> 只显示收藏 </NCheckbox>
|
||||
<NCheckbox v-model:checked="useQB.onlyPublic"> 只显示公开 </NCheckbox>
|
||||
|
||||
@@ -1,11 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import { useAccount } from '@/api/account'
|
||||
import { ScheduleWeekInfo } from '@/api/api-models'
|
||||
import { DisableFunction, EnableFunction, useAccount } from '@/api/account'
|
||||
import { FunctionTypes, ScheduleWeekInfo } from '@/api/api-models'
|
||||
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
||||
import ScheduleList from '@/components/ScheduleList.vue'
|
||||
import { SCHEDULE_API_URL } from '@/data/constants'
|
||||
import { addWeeks, endOfWeek, endOfYear, format, isBefore, startOfWeek, startOfYear } from 'date-fns'
|
||||
import { NAlert, NBadge, NButton, NColorPicker, NDivider, NInput, NInputGroup, NInputGroupLabel, NModal, NSelect, NSpace, NSpin, NTimePicker, useMessage } from 'naive-ui'
|
||||
import {
|
||||
NAlert,
|
||||
NBadge,
|
||||
NButton,
|
||||
NColorPicker,
|
||||
NDivider,
|
||||
NInput,
|
||||
NInputGroup,
|
||||
NInputGroupLabel,
|
||||
NModal,
|
||||
NSelect,
|
||||
NSpace,
|
||||
NSpin,
|
||||
NSwitch,
|
||||
NTimePicker,
|
||||
useMessage,
|
||||
} from 'naive-ui'
|
||||
import { SelectMixedOption, SelectOption } from 'naive-ui/es/select/src/interface'
|
||||
import { VNode, computed, h, onMounted, ref } from 'vue'
|
||||
|
||||
@@ -40,10 +56,11 @@ const yearOptions = [
|
||||
},
|
||||
] as SelectMixedOption[]
|
||||
const weekOptions = computed(() => {
|
||||
let weeks = [] as SelectMixedOption[]
|
||||
const weeks = [] as SelectMixedOption[]
|
||||
const all = getAllWeeks(selectedScheduleYear.value)
|
||||
all.forEach((week) => {
|
||||
const isExist = (schedules.value?.findIndex((s) => s.year == selectedScheduleYear.value && s.week == week[0] + 1) ?? -1) > -1
|
||||
const isExist =
|
||||
(schedules.value?.findIndex((s) => s.year == selectedScheduleYear.value && s.week == week[0] + 1) ?? -1) > -1
|
||||
weeks.push({
|
||||
label: `${isExist ? '(已安排)' : ''} 第${week[0] + 1}周 (${week[1]})`,
|
||||
value: week[0] + 1,
|
||||
@@ -53,7 +70,7 @@ const weekOptions = computed(() => {
|
||||
return weeks
|
||||
})
|
||||
const dayOptions = computed(() => {
|
||||
let days = [] as SelectMixedOption[]
|
||||
const days = [] as SelectMixedOption[]
|
||||
for (let i = 0; i < 7; i++) {
|
||||
try {
|
||||
days.push({
|
||||
@@ -67,7 +84,7 @@ const dayOptions = computed(() => {
|
||||
return days
|
||||
})
|
||||
const existTagOptions = computed(() => {
|
||||
let colors = [] as SelectMixedOption[]
|
||||
const colors = [] as SelectMixedOption[]
|
||||
schedules.value?.forEach((s) => {
|
||||
s.days.forEach((d) => {
|
||||
if (d.tag && !colors.find((c) => c.value == d.tagColor && c.label == d.tag)) {
|
||||
@@ -85,7 +102,7 @@ function getAllWeeks(year: number) {
|
||||
const endDate = endOfYear(new Date(year, 11, 31))
|
||||
|
||||
let date = startOfWeek(startDate, { weekStartsOn: 1 })
|
||||
let weeks: [number, string][] = []
|
||||
const weeks: [number, string][] = []
|
||||
|
||||
let index = 0
|
||||
|
||||
@@ -175,7 +192,9 @@ async function onUpdateSchedule() {
|
||||
.then((data) => {
|
||||
if (data.code == 200) {
|
||||
message.success('成功')
|
||||
const s = schedules.value?.find((s) => s.year == selectedScheduleYear.value && s.week == selectedScheduleWeek.value)
|
||||
const s = schedules.value?.find(
|
||||
(s) => s.year == selectedScheduleYear.value && s.week == selectedScheduleWeek.value,
|
||||
)
|
||||
if (s) {
|
||||
s.days[selectedDay.value] = updateScheduleModel.value.days[selectedDay.value]
|
||||
} else {
|
||||
@@ -218,7 +237,23 @@ function onSelectChange(value: string | null, option: SelectMixedOption) {
|
||||
}
|
||||
}
|
||||
const renderOption = ({ node, option }: { node: VNode; option: SelectOption }) =>
|
||||
h(NSpace, { align: 'center', size: 3, style: 'margin-left: 5px' }, () => [option.value ? h(NBadge, { dot: true, color: option.value?.toString() }) : null, node])
|
||||
h(NSpace, { align: 'center', size: 3, style: 'margin-left: 5px' }, () => [
|
||||
option.value ? h(NBadge, { dot: true, color: option.value?.toString() }) : null,
|
||||
node,
|
||||
])
|
||||
async function setFunctionEnable(enable: boolean) {
|
||||
let success = false
|
||||
if (enable) {
|
||||
success = await EnableFunction(FunctionTypes.Schedule)
|
||||
} else {
|
||||
success = await DisableFunction(FunctionTypes.Schedule)
|
||||
}
|
||||
if (success) {
|
||||
message.success('已' + (enable ? '启用' : '禁用'))
|
||||
} else {
|
||||
message.error('无法' + (enable ? '启用' : '禁用'))
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
get()
|
||||
@@ -226,9 +261,19 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NSpace>
|
||||
<NSpace align="center">
|
||||
<NAlert type="info" style="max-width: 200px">
|
||||
启用日程表
|
||||
<NDivider vertical />
|
||||
<NSwitch
|
||||
:value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.Schedule)"
|
||||
@update:value="setFunctionEnable"
|
||||
/>
|
||||
</NAlert>
|
||||
<NButton @click="showAddModal = true" type="primary"> 添加周程 </NButton>
|
||||
<NButton @click="$router.push({ name: 'manage-index', query: { tab: 'template', template: 'schedule' } })"> 修改模板 </NButton>
|
||||
<NButton @click="$router.push({ name: 'manage-index', query: { tab: 'template', template: 'schedule' } })">
|
||||
修改模板
|
||||
</NButton>
|
||||
</NSpace>
|
||||
<NDivider />
|
||||
<NModal v-model:show="showAddModal" style="width: 600px; max-width: 90vw" preset="card" title="添加周程">
|
||||
@@ -260,7 +305,13 @@ onMounted(() => {
|
||||
<NSpace>
|
||||
<NInputGroup>
|
||||
<NInputGroupLabel type="primary"> 标签 </NInputGroupLabel>
|
||||
<NInput v-model:value="updateScheduleModel.days[selectedDay].tag" placeholder="标签 | 留空视为无安排" style="max-width: 300px" maxlength="10" show-count />
|
||||
<NInput
|
||||
v-model:value="updateScheduleModel.days[selectedDay].tag"
|
||||
placeholder="标签 | 留空视为无安排"
|
||||
style="max-width: 300px"
|
||||
maxlength="10"
|
||||
show-count
|
||||
/>
|
||||
</NInputGroup>
|
||||
<NSelect
|
||||
v-model:value="selectedExistTag"
|
||||
@@ -275,9 +326,19 @@ onMounted(() => {
|
||||
</NSpace>
|
||||
<NInputGroup>
|
||||
<NInputGroupLabel> 内容 </NInputGroupLabel>
|
||||
<NInput v-model:value="updateScheduleModel.days[selectedDay].title" placeholder="内容" style="max-width: 200px" maxlength="30" show-count />
|
||||
<NInput
|
||||
v-model:value="updateScheduleModel.days[selectedDay].title"
|
||||
placeholder="内容"
|
||||
style="max-width: 200px"
|
||||
maxlength="30"
|
||||
show-count
|
||||
/>
|
||||
</NInputGroup>
|
||||
<NTimePicker default-formatted-value="20:00" v-model:formatted-value="updateScheduleModel.days[selectedDay].time" format="HH:mm" />
|
||||
<NTimePicker
|
||||
default-formatted-value="20:00"
|
||||
v-model:formatted-value="updateScheduleModel.days[selectedDay].time"
|
||||
format="HH:mm"
|
||||
/>
|
||||
<NColorPicker
|
||||
v-model:value="updateScheduleModel.days[selectedDay].tagColor"
|
||||
:swatches="['#FFFFFF', '#18A058', '#2080F0', '#F0A020', 'rgba(208, 48, 80, 1)']"
|
||||
@@ -290,5 +351,12 @@ onMounted(() => {
|
||||
</template>
|
||||
</NModal>
|
||||
<NSpin v-if="isLoading" show />
|
||||
<ScheduleList v-else :schedules="schedules ?? []" @on-update="onOpenUpdateModal" @on-delete="onDeleteSchedule" @on-copy="onOpenCopyModal" is-self />
|
||||
<ScheduleList
|
||||
v-else
|
||||
:schedules="schedules ?? []"
|
||||
@on-update="onOpenUpdateModal"
|
||||
@on-delete="onDeleteSchedule"
|
||||
@on-copy="onOpenCopyModal"
|
||||
is-self
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,10 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
import { DelBiliBlackList, SaveAccountSettings, SaveEnableFunctions, downloadConfigDirect, useAccount } from '@/api/account'
|
||||
import {
|
||||
DelBiliBlackList,
|
||||
SaveAccountSettings,
|
||||
SaveEnableFunctions,
|
||||
downloadConfigDirect,
|
||||
useAccount,
|
||||
} from '@/api/account'
|
||||
import { FunctionTypes, ScheduleWeekInfo, SongFrom, SongLanguage, SongRequestOption, SongsInfo } from '@/api/api-models'
|
||||
import DynamicForm from '@/components/DynamicForm.vue'
|
||||
import { TemplateConfig } from '@/data/VTsuruTypes'
|
||||
import { FETCH_API, IndexTemplateMap, ScheduleTemplateMap, SongListTemplateMap } from '@/data/constants'
|
||||
import { NAlert, NButton, NCard, NCheckbox, NCheckboxGroup, NDivider, NEmpty, NList, NListItem, NModal, NSelect, NSpace, NSpin, NTabPane, NTabs, NText, SelectOption, useMessage } from 'naive-ui'
|
||||
import {
|
||||
NAlert,
|
||||
NButton,
|
||||
NCard,
|
||||
NCheckbox,
|
||||
NCheckboxGroup,
|
||||
NDivider,
|
||||
NEmpty,
|
||||
NList,
|
||||
NListItem,
|
||||
NModal,
|
||||
NSelect,
|
||||
NSpace,
|
||||
NSpin,
|
||||
NTabPane,
|
||||
NTabs,
|
||||
NText,
|
||||
SelectOption,
|
||||
useMessage,
|
||||
} from 'naive-ui'
|
||||
import { computed, h, nextTick, onActivated, onMounted, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
@@ -188,7 +213,9 @@ const selectedTab = ref(route.query.tab?.toString() ?? 'general')
|
||||
|
||||
const dynamicConfigRef = ref()
|
||||
const selectedTemplateData = computed(() => templates.value[selectedOption.value])
|
||||
const selectedComponent = computed(() => selectedTemplateData.value?.TemplateMap[selectedTemplateData.value.Selected].compoent)
|
||||
const selectedComponent = computed(
|
||||
() => selectedTemplateData.value?.TemplateMap[selectedTemplateData.value.Selected].compoent,
|
||||
)
|
||||
const selectedTemplateConfig = computed(() => {
|
||||
if (dynamicConfigRef.value?.Config) {
|
||||
return dynamicConfigRef.value?.Config as TemplateConfig<any>
|
||||
@@ -201,7 +228,7 @@ const settingModalVisiable = ref(false)
|
||||
|
||||
async function RequestBiliUserData() {
|
||||
await fetch(FETCH_API + `https://account.bilibili.com/api/member/getCardByMid?mid=10021741`).then(async (respone) => {
|
||||
let data = await respone.json()
|
||||
const data = await respone.json()
|
||||
if (data.code == 0) {
|
||||
biliUserInfo.value = data.card
|
||||
} else {
|
||||
@@ -209,7 +236,10 @@ async function RequestBiliUserData() {
|
||||
}
|
||||
})
|
||||
}
|
||||
async function SaveComboGroupSetting(value: (string | number)[], meta: { actionType: 'check' | 'uncheck'; value: string | number }) {
|
||||
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')
|
||||
@@ -220,12 +250,14 @@ async function SaveComboGroupSetting(value: (string | number)[], meta: { actionT
|
||||
} else {
|
||||
message.error('修改失败')
|
||||
if (accountInfo.value) {
|
||||
accountInfo.value.settings.enableFunctions = accountInfo.value.settings.enableFunctions.filter((f) => f != (meta.value as FunctionTypes))
|
||||
accountInfo.value.settings.enableFunctions = accountInfo.value.settings.enableFunctions.filter(
|
||||
(f) => f != (meta.value as FunctionTypes),
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
message.error('修改失败')
|
||||
message.error('修改失败: ' + err)
|
||||
})
|
||||
.finally(() => {
|
||||
isSaving.value = false
|
||||
@@ -245,7 +277,7 @@ async function SaveComboSetting() {
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
message.error('修改失败')
|
||||
message.error('修改失败: ' + err)
|
||||
})
|
||||
.finally(() => {
|
||||
isSaving.value = false
|
||||
@@ -300,7 +332,10 @@ async function getTemplateConfig() {
|
||||
}
|
||||
}
|
||||
const buttonGroup = computed(() => {
|
||||
return h(NSpace, () => [h(NButton, { type: 'primary', onClick: () => SaveTemplateSetting() }, () => '设为展示模板'), h(NButton, { type: 'info', onClick: onOpenTemplateSettings }, () => '模板设置')])
|
||||
return h(NSpace, () => [
|
||||
h(NButton, { type: 'primary', onClick: () => SaveTemplateSetting() }, () => '设为展示模板'),
|
||||
h(NButton, { type: 'info', onClick: onOpenTemplateSettings }, () => '模板设置'),
|
||||
])
|
||||
})
|
||||
|
||||
function unblockUser(id: number) {
|
||||
@@ -348,12 +383,24 @@ onMounted(async () => {
|
||||
</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>
|
||||
<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>
|
||||
<NCheckbox
|
||||
v-model:checked="accountInfo.settings.questionBox.allowUnregistedUser"
|
||||
@update:checked="SaveComboSetting"
|
||||
>
|
||||
允许未注册用户提问
|
||||
</NCheckbox>
|
||||
</NSpace>
|
||||
</NTabPane>
|
||||
<NTabPane tab="黑名单" name="blacklist">
|
||||
@@ -373,22 +420,32 @@ onMounted(async () => {
|
||||
<NEmpty v-else />
|
||||
</NTabPane>
|
||||
<NTabPane tab="模板" name="template">
|
||||
<NAlert type="success">
|
||||
如果有合适的设计稿或者想法可以给我说然后做成模板捏
|
||||
</NAlert>
|
||||
<br/>
|
||||
<NAlert type="success"> 如果有合适的设计稿或者想法可以给我说然后做成模板捏 </NAlert>
|
||||
<br />
|
||||
<NSpace vertical>
|
||||
<NSpace align="center"> 页面 <NSelect :options="templateOptions" v-model:value="selectedOption" style="width: 150px" /> </NSpace>
|
||||
<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" />
|
||||
<NSelect
|
||||
style="width: 150px"
|
||||
:options="selectedTemplateData.Options"
|
||||
v-model:value="selectedTemplateData.Selected"
|
||||
/>
|
||||
<component :is="buttonGroup" />
|
||||
</NSpace>
|
||||
<NDivider />
|
||||
<Transition name="fade" mode="out-in">
|
||||
<div v-if="selectedComponent" :key="selectedTemplateData.Selected">
|
||||
<component ref="dynamicConfigRef" :is="selectedComponent" :user-info="accountInfo" :bili-info="biliUserInfo" :current-data="selectedTemplateData.Data" />
|
||||
<component
|
||||
ref="dynamicConfigRef"
|
||||
:is="selectedComponent"
|
||||
:user-info="accountInfo"
|
||||
:bili-info="biliUserInfo"
|
||||
:current-data="selectedTemplateData.Data"
|
||||
/>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
@@ -397,9 +454,20 @@ onMounted(async () => {
|
||||
</NTabs>
|
||||
</NSpin>
|
||||
</NCard>
|
||||
<NModal preset="card" v-model:show="settingModalVisiable" closable style="width: 600px; max-width: 90vw" title="模板设置">
|
||||
<NModal
|
||||
preset="card"
|
||||
v-model:show="settingModalVisiable"
|
||||
closable
|
||||
style="width: 600px; max-width: 90vw"
|
||||
title="模板设置"
|
||||
>
|
||||
只是测试, 没用
|
||||
<NSpin v-if="!selectedTemplateData.Config" show />
|
||||
<DynamicForm v-else :key="selectedTemplateData.Selected" :configData="selectedTemplateData.Config" :config="selectedTemplateConfig" />
|
||||
<DynamicForm
|
||||
v-else
|
||||
:key="selectedTemplateData.Selected"
|
||||
:configData="selectedTemplateData.Config"
|
||||
:config="selectedTemplateConfig"
|
||||
/>
|
||||
</NModal>
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { objectsToCSV } from '@/Utils'
|
||||
import { useAccount } from '@/api/account'
|
||||
import { SongFrom, SongLanguage, SongRequestOption, SongsInfo } from '@/api/api-models'
|
||||
import { DisableFunction, EnableFunction, useAccount } from '@/api/account'
|
||||
import { FunctionTypes, SongFrom, SongLanguage, SongRequestOption, SongsInfo } from '@/api/api-models'
|
||||
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
||||
import SongList from '@/components/SongList.vue'
|
||||
import { FETCH_API, SONG_API_URL } from '@/data/constants'
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
NSelect,
|
||||
NSpace,
|
||||
NSpin,
|
||||
NSwitch,
|
||||
NTabPane,
|
||||
NTable,
|
||||
NTabs,
|
||||
@@ -545,6 +546,19 @@ function beforeUpload(data: { file: UploadFileInfo; fileList: UploadFileInfo[] }
|
||||
message.error('只能选择xlsx和xls和csv')
|
||||
return false
|
||||
}
|
||||
async function setFunctionEnable(enable: boolean) {
|
||||
let success = false
|
||||
if (enable) {
|
||||
success = await EnableFunction(FunctionTypes.SongList)
|
||||
} else {
|
||||
success = await DisableFunction(FunctionTypes.SongList)
|
||||
}
|
||||
if (success) {
|
||||
message.success('已' + (enable ? '启用' : '禁用'))
|
||||
} else {
|
||||
message.error('无法' + (enable ? '启用' : '禁用'))
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await getSongs()
|
||||
@@ -552,7 +566,15 @@ onMounted(async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NSpace>
|
||||
<NSpace align="center">
|
||||
<NAlert type="info" style="max-width: 200px">
|
||||
启用歌单
|
||||
<NDivider vertical />
|
||||
<NSwitch
|
||||
:value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.SongList)"
|
||||
@update:value="setFunctionEnable"
|
||||
/>
|
||||
</NAlert>
|
||||
<NButton @click="showModal = true" type="primary"> 添加歌曲 </NButton>
|
||||
<NButton @click="exportData" type="primary" secondary> 导出为 CSV </NButton>
|
||||
<NButton
|
||||
@@ -833,9 +855,9 @@ onMounted(async () => {
|
||||
>
|
||||
<NUploadDragger>
|
||||
<div style="margin-bottom: 12px">
|
||||
<n-icon size="48" :depth="3">
|
||||
<NIcon size="48" :depth="3">
|
||||
<ArchiveOutline />
|
||||
</n-icon>
|
||||
</NIcon>
|
||||
</div>
|
||||
<NText style="font-size: 16px"> 点击或者拖动文件到该区域来上传 </NText>
|
||||
<NP depth="3" style="margin: 8px 0 0 0"> 仅限 Excel 文件(.xlsx和.xls) 以及 csv 文件 </NP>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { onMounted, onUnmounted } from 'vue'
|
||||
import MusicRequest from '../open_live/MusicRequest.vue'
|
||||
|
||||
const accountInfo = useAccount()
|
||||
let client = new DanmakuClient(null)
|
||||
const client = new DanmakuClient(null)
|
||||
|
||||
onMounted(() => {
|
||||
client.Start()
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { downloadImage } from '@/Utils'
|
||||
import { VideoCollectCreateModel, VideoCollectDetail, VideoCollectTable, VideoCollectVideo, VideoInfo, VideoStatus } from '@/api/api-models'
|
||||
import {
|
||||
VideoCollectCreateModel,
|
||||
VideoCollectDetail,
|
||||
VideoCollectTable,
|
||||
VideoCollectVideo,
|
||||
VideoInfo,
|
||||
VideoStatus,
|
||||
} from '@/api/api-models'
|
||||
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
||||
import VideoCollectInfoCard from '@/components/VideoCollectInfoCard.vue'
|
||||
import { VIDEO_COLLECT_API_URL } from '@/data/constants'
|
||||
@@ -140,63 +147,114 @@ const gridRender = (type: 'padding' | 'reject' | 'accept') => {
|
||||
}
|
||||
return videos.length == 0
|
||||
? h(NEmpty)
|
||||
: h(
|
||||
NGrid,
|
||||
{ cols: '1 500:2 700:3 900:4 1200:5 ', xGap: '12', yGap: '12', responsive: 'self' },
|
||||
() =>
|
||||
videos?.map((v) =>
|
||||
h(NGridItem, () =>
|
||||
h(
|
||||
NCard,
|
||||
{ style: 'height: 330px;', embedded: true, size: 'small' },
|
||||
{
|
||||
cover: () =>
|
||||
h('div', { style: 'position: relative;height: 150px;' }, [
|
||||
h('img', {
|
||||
src: v.video.cover.replace('http://', 'https://'),
|
||||
referrerpolicy: 'no-referrer',
|
||||
style: 'max-height: 100%; object-fit: contain;cursor: pointer',
|
||||
onClick: () => window.open('https://www.bilibili.com/video/' + v.info.bvid, '_blank'),
|
||||
}),
|
||||
h(NSpace, { style: { position: 'relative', bottom: '20px', background: '#00000073' }, justify: 'space-around' }, () => [
|
||||
h('span', [h(NIcon, { component: Clock24Filled, color: 'lightgrey' }), h(NText, { style: 'color: lightgrey;size:small;' }, () => formatSeconds(v.video.length))]),
|
||||
h('span', [h(NIcon, { component: Person24Filled, color: 'lightgrey' }), h(NText, { style: 'color: lightgrey;size:small;' }, () => v.video.ownerName)]),
|
||||
]),
|
||||
]),
|
||||
header: () =>
|
||||
h(NButton, { style: 'width: 100%;', text: true, onClick: () => window.open('https://www.bilibili.com/video/' + v.info.bvid, '_blank') }, () =>
|
||||
h(NEllipsis, { style: 'max-width: 100%;' }, { default: () => v.video.title, tooltip: () => h('div', { style: 'max-width: 300px' }, v.video.title) }),
|
||||
),
|
||||
default: () =>
|
||||
h(NScrollbar, { style: 'height: 65px;' }, () =>
|
||||
h(NCard, { contentStyle: 'padding: 5px;' }, () =>
|
||||
v.info.senders.map((s) => [
|
||||
h('div', { style: 'font-size: 12px;' }, [h('div', `推荐人: ${s.sender ?? '未填写'} [${s.senderId ?? '未填写'}]`), h('div', `推荐理由: ${s.description ?? '未填写'}`)]),
|
||||
h(NSpace, { style: 'margin: 0;' }),
|
||||
: h(NGrid, { cols: '1 500:2 700:3 900:4 1200:5 ', xGap: '12', yGap: '12', responsive: 'self' }, () =>
|
||||
videos?.map((v) =>
|
||||
h(NGridItem, () =>
|
||||
h(
|
||||
NCard,
|
||||
{ style: 'height: 330px;', embedded: true, size: 'small' },
|
||||
{
|
||||
cover: () =>
|
||||
h('div', { style: 'position: relative;height: 150px;' }, [
|
||||
h('img', {
|
||||
src: v.video.cover.replace('http://', 'https://'),
|
||||
referrerpolicy: 'no-referrer',
|
||||
style: 'max-height: 100%; object-fit: contain;cursor: pointer',
|
||||
onClick: () => window.open('https://www.bilibili.com/video/' + v.info.bvid, '_blank'),
|
||||
}),
|
||||
h(
|
||||
NSpace,
|
||||
{
|
||||
style: { position: 'relative', bottom: '20px', background: '#00000073' },
|
||||
justify: 'space-around',
|
||||
},
|
||||
() => [
|
||||
h('span', [
|
||||
h(NIcon, { component: Clock24Filled, color: 'lightgrey' }),
|
||||
h(NText, { style: 'color: lightgrey;size:small;' }, () => formatSeconds(v.video.length)),
|
||||
]),
|
||||
),
|
||||
h('span', [
|
||||
h(NIcon, { component: Person24Filled, color: 'lightgrey' }),
|
||||
h(NText, { style: 'color: lightgrey;size:small;' }, () => v.video.ownerName),
|
||||
]),
|
||||
],
|
||||
),
|
||||
footer: () => footer(v.info),
|
||||
},
|
||||
),
|
||||
]),
|
||||
header: () =>
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
style: 'width: 100%;',
|
||||
text: true,
|
||||
onClick: () => window.open('https://www.bilibili.com/video/' + v.info.bvid, '_blank'),
|
||||
},
|
||||
() =>
|
||||
h(
|
||||
NEllipsis,
|
||||
{ style: 'max-width: 100%;' },
|
||||
{
|
||||
default: () => v.video.title,
|
||||
tooltip: () => h('div', { style: 'max-width: 300px' }, v.video.title),
|
||||
},
|
||||
),
|
||||
),
|
||||
default: () =>
|
||||
h(NScrollbar, { style: 'height: 65px;' }, () =>
|
||||
h(NCard, { contentStyle: 'padding: 5px;' }, () =>
|
||||
v.info.senders.map((s) => [
|
||||
h('div', { style: 'font-size: 12px;' }, [
|
||||
h('div', `推荐人: ${s.sender ?? '未填写'} [${s.senderId ?? '未填写'}]`),
|
||||
h('div', `推荐理由: ${s.description ?? '未填写'}`),
|
||||
]),
|
||||
h(NSpace, { style: 'margin: 0;' }),
|
||||
]),
|
||||
),
|
||||
),
|
||||
footer: () => footer(v.info),
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
const paddingButtonGroup = (v: VideoInfo) =>
|
||||
h(NSpace, { size: 'small', justify: 'space-around' }, () => [
|
||||
h(NButton, { type: 'success', loading: isLoading.value, onClick: () => setStatus(VideoStatus.Accepted, v) }, () => '通过'),
|
||||
h(NButton, { type: 'error', loading: isLoading.value, onClick: () => setStatus(VideoStatus.Rejected, v) }, () => '拒绝'),
|
||||
h(
|
||||
NButton,
|
||||
{ type: 'success', loading: isLoading.value, onClick: () => setStatus(VideoStatus.Accepted, v) },
|
||||
() => '通过',
|
||||
),
|
||||
h(
|
||||
NButton,
|
||||
{ type: 'error', loading: isLoading.value, onClick: () => setStatus(VideoStatus.Rejected, v) },
|
||||
() => '拒绝',
|
||||
),
|
||||
])
|
||||
const acceptButtonGroup = (v: VideoInfo) =>
|
||||
h(NSpace, { size: 'small', justify: 'space-around' }, () => [
|
||||
h(NButton, { type: 'info', loading: isLoading.value, onClick: () => setStatus(VideoStatus.Pending, v) }, () => '重设为未审核'),
|
||||
h(NButton, { type: 'error', loading: isLoading.value, onClick: () => setStatus(VideoStatus.Rejected, v) }, () => '拒绝'),
|
||||
h(
|
||||
NButton,
|
||||
{ type: 'info', loading: isLoading.value, onClick: () => setStatus(VideoStatus.Pending, v) },
|
||||
() => '重设为未审核',
|
||||
),
|
||||
h(
|
||||
NButton,
|
||||
{ type: 'error', loading: isLoading.value, onClick: () => setStatus(VideoStatus.Rejected, v) },
|
||||
() => '拒绝',
|
||||
),
|
||||
])
|
||||
const rejectButtonGroup = (v: VideoInfo) =>
|
||||
h(NSpace, { size: 'small', justify: 'space-around' }, () => [
|
||||
h(NButton, { type: 'success', loading: isLoading.value, onClick: () => setStatus(VideoStatus.Accepted, v) }, () => '通过'),
|
||||
h(NButton, { type: 'info', loading: isLoading.value, onClick: () => setStatus(VideoStatus.Pending, v) }, () => '重设为未审核'),
|
||||
h(
|
||||
NButton,
|
||||
{ type: 'success', loading: isLoading.value, onClick: () => setStatus(VideoStatus.Accepted, v) },
|
||||
() => '通过',
|
||||
),
|
||||
h(
|
||||
NButton,
|
||||
{ type: 'info', loading: isLoading.value, onClick: () => setStatus(VideoStatus.Pending, v) },
|
||||
() => '重设为未审核',
|
||||
),
|
||||
])
|
||||
function setStatus(status: VideoStatus, video: VideoInfo) {
|
||||
isLoading.value = true
|
||||
@@ -295,7 +353,10 @@ function closeTable() {
|
||||
})
|
||||
}
|
||||
function saveQRCode() {
|
||||
downloadImage(`https://api.qrserver.com/v1/create-qr-code/?data=${'https://vtsuru.live/video-collect/' + videoDetail.value.table.shortId}`, `vtsuru-视频征集二维码-${videoDetail.value.table.name}.png`)
|
||||
downloadImage(
|
||||
`https://api.qrserver.com/v1/create-qr-code/?data=${'https://vtsuru.live/video-collect/' + videoDetail.value.table.shortId}`,
|
||||
`vtsuru-视频征集二维码-${videoDetail.value.table.name}.png`,
|
||||
)
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
@@ -306,8 +367,12 @@ function saveQRCode() {
|
||||
<template v-if="width <= 1000">
|
||||
<NButton type="success" size="small" @click="shareModalVisiable = true"> 分享 </NButton>
|
||||
<NButton type="info" size="small" @click="editModalVisiable = true"> 更新 </NButton>
|
||||
<NButton type="warning" size="small" @click="closeTable"> {{ videoDetail.table.isFinish ? '开启表' : '关闭表' }} </NButton>
|
||||
<NButton size="small" @click="$router.push({ name: 'video-collect-list', params: { id: videoDetail.table.id } })"> 结果页面 </NButton>
|
||||
<NButton type="warning" size="small" @click="closeTable">
|
||||
{{ videoDetail.table.isFinish ? '开启表' : '关闭表' }}
|
||||
</NButton>
|
||||
<NButton size="small" @click="$router.push({ name: 'video-collect-list', params: { id: videoDetail.table.id } })">
|
||||
结果页面
|
||||
</NButton>
|
||||
<NPopconfirm :on-positive-click="deleteTable">
|
||||
<template #trigger>
|
||||
<NButton type="error" size="small"> 删除 </NButton>
|
||||
@@ -321,8 +386,15 @@ function saveQRCode() {
|
||||
<NSpace>
|
||||
<NButton type="success" size="small" @click="shareModalVisiable = true"> 分享 </NButton>
|
||||
<NButton type="info" size="small" @click="editModalVisiable = true"> 更新 </NButton>
|
||||
<NButton type="warning" size="small" @click="closeTable"> {{ videoDetail.table.isFinish ? '开启表' : '关闭表' }} </NButton>
|
||||
<NButton size="small" @click="$router.push({ name: 'video-collect-list', params: { id: videoDetail.table.id } })"> 结果表 </NButton>
|
||||
<NButton type="warning" size="small" @click="closeTable">
|
||||
{{ videoDetail.table.isFinish ? '开启表' : '关闭表' }}
|
||||
</NButton>
|
||||
<NButton
|
||||
size="small"
|
||||
@click="$router.push({ name: 'video-collect-list', params: { id: videoDetail.table.id } })"
|
||||
>
|
||||
结果表
|
||||
</NButton>
|
||||
<NPopconfirm :on-positive-click="deleteTable">
|
||||
<template #trigger>
|
||||
<NButton type="error" size="small"> 删除 </NButton>
|
||||
@@ -369,9 +441,15 @@ function saveQRCode() {
|
||||
</NTabs>
|
||||
</template>
|
||||
<NModal v-model:show="shareModalVisiable" title="分享" preset="card" style="width: 600px; max-width: 90vw">
|
||||
<Qrcode :value="'https://vtsuru.live/video-collect/' + videoDetail.table.shortId" level="Q" :size="100" background="#fff" :margin="1" />
|
||||
<Qrcode
|
||||
:value="'https://vtsuru.live/video-collect/' + videoDetail.table.shortId"
|
||||
level="Q"
|
||||
:size="100"
|
||||
background="#fff"
|
||||
:margin="1"
|
||||
/>
|
||||
<NInput :value="'https://vtsuru.live/video-collect/' + videoDetail.table.shortId" />
|
||||
<NDivider/>
|
||||
<NDivider />
|
||||
<NSpace justify="center">
|
||||
<NButton type="primary" @click="saveQRCode"> 保存二维码 </NButton>
|
||||
</NSpace>
|
||||
@@ -385,10 +463,20 @@ function saveQRCode() {
|
||||
<NInput v-model:value="updateModel.description" placeholder="可以是备注之类的" maxlength="300" show-count />
|
||||
</NFormItem>
|
||||
<NFormItem label="视频数量" path="maxVideoCount">
|
||||
<NInputNumber v-model:value="updateModel.maxVideoCount" placeholder="最大数量" type="number" style="max-width: 150px" />
|
||||
<NInputNumber
|
||||
v-model:value="updateModel.maxVideoCount"
|
||||
placeholder="最大数量"
|
||||
type="number"
|
||||
style="max-width: 150px"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="结束时间" path="endAt">
|
||||
<NDatePicker v-model:value="updateModel.endAt" type="datetime" placeholder="结束征集的时间" :isDateDisabled="dateDisabled" />
|
||||
<NDatePicker
|
||||
v-model:value="updateModel.endAt"
|
||||
type="datetime"
|
||||
placeholder="结束征集的时间"
|
||||
:isDateDisabled="dateDisabled"
|
||||
/>
|
||||
<NDivider vertical />
|
||||
<NText depth="3"> 最低为一小时 </NText>
|
||||
</NFormItem>
|
||||
|
||||
@@ -4,7 +4,24 @@ import { VideoCollectTable } from '@/api/api-models'
|
||||
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
||||
import VideoCollectInfoCard from '@/components/VideoCollectInfoCard.vue'
|
||||
import { VIDEO_COLLECT_API_URL } from '@/data/constants'
|
||||
import { FormRules, NButton, NDatePicker, NDivider, NEmpty, NForm, NFormItem, NInput, NInputNumber, NList, NListItem, NModal, NSpace, NSpin, NText, useMessage } from 'naive-ui'
|
||||
import {
|
||||
FormRules,
|
||||
NButton,
|
||||
NDatePicker,
|
||||
NDivider,
|
||||
NEmpty,
|
||||
NForm,
|
||||
NFormItem,
|
||||
NInput,
|
||||
NInputNumber,
|
||||
NList,
|
||||
NListItem,
|
||||
NModal,
|
||||
NSpace,
|
||||
NSpin,
|
||||
NText,
|
||||
useMessage,
|
||||
} from 'naive-ui'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const accountInfo = useAccount()
|
||||
@@ -128,13 +145,28 @@ function createTable() {
|
||||
<NInput v-model:value="createVideoModel.name" placeholder="征集表的标题" maxlength="30" show-count />
|
||||
</NFormItem>
|
||||
<NFormItem label="描述" path="description">
|
||||
<NInput v-model:value="createVideoModel.description" placeholder="可以是备注之类的" maxlength="300" show-count />
|
||||
<NInput
|
||||
v-model:value="createVideoModel.description"
|
||||
placeholder="可以是备注之类的"
|
||||
maxlength="300"
|
||||
show-count
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="视频数量" path="maxVideoCount">
|
||||
<NInputNumber v-model:value="createVideoModel.maxVideoCount" placeholder="最大数量" type="number" style="max-width: 150px" />
|
||||
<NInputNumber
|
||||
v-model:value="createVideoModel.maxVideoCount"
|
||||
placeholder="最大数量"
|
||||
type="number"
|
||||
style="max-width: 150px"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="结束时间" path="endAt">
|
||||
<NDatePicker v-model:value="createVideoModel.endAt" type="datetime" placeholder="结束征集的时间" :isDateDisabled="dateDisabled" />
|
||||
<NDatePicker
|
||||
v-model:value="createVideoModel.endAt"
|
||||
type="datetime"
|
||||
placeholder="结束征集的时间"
|
||||
:isDateDisabled="dateDisabled"
|
||||
/>
|
||||
<NDivider vertical />
|
||||
<NText depth="3"> 最低为一小时 </NText>
|
||||
</NFormItem>
|
||||
|
||||
@@ -1,62 +1,50 @@
|
||||
<script setup lang="ts">
|
||||
import { getBase64, getImageUploadModel } from '@/Utils'
|
||||
import { getImageUploadModel } from '@/Utils'
|
||||
import { DisableFunction, EnableFunction, useAccount } from '@/api/account'
|
||||
import {
|
||||
ResponsePointGoodModel,
|
||||
FunctionTypes,
|
||||
PointGoodsModel,
|
||||
GoodsTypes,
|
||||
GoodsStatus,
|
||||
TagInfo,
|
||||
} from '@/api/api-models'
|
||||
import { FunctionTypes, GoodsStatus, GoodsTypes, PointGoodsModel, ResponsePointGoodModel } from '@/api/api-models'
|
||||
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
||||
import EventFetcherStatusCard from '@/components/EventFetcherStatusCard.vue'
|
||||
import PointGoodsItem from '@/components/manage/PointGoodsItem.vue'
|
||||
import { FILE_BASE_URL, POINT_API_URL } from '@/data/constants'
|
||||
import { Info24Filled, MoreVertical16Filled } from '@vicons/fluent'
|
||||
import { List } from 'linqts'
|
||||
import { useAuthStore } from '@/store/useAuthStore'
|
||||
import { Info24Filled } from '@vicons/fluent'
|
||||
import { useRouteHash } from '@vueuse/router'
|
||||
import {
|
||||
FormItemRule,
|
||||
NAlert,
|
||||
NButton,
|
||||
NCheckbox,
|
||||
NDivider,
|
||||
NEmpty,
|
||||
NFlex,
|
||||
NForm,
|
||||
NFormItem,
|
||||
NGrid,
|
||||
NGridItem,
|
||||
NIcon,
|
||||
NImage,
|
||||
NInput,
|
||||
NInputNumber,
|
||||
NModal,
|
||||
NPopconfirm,
|
||||
NRadioButton,
|
||||
NRadioGroup,
|
||||
NScrollbar,
|
||||
NSelect,
|
||||
NSwitch,
|
||||
NTabPane,
|
||||
NTabs,
|
||||
NText,
|
||||
useMessage,
|
||||
NFlex,
|
||||
NInputNumber,
|
||||
NRadioGroup,
|
||||
NRadio,
|
||||
NTooltip,
|
||||
NIcon,
|
||||
NCheckbox,
|
||||
NRadioButton,
|
||||
NUpload,
|
||||
UploadFileInfo,
|
||||
FormItemRule,
|
||||
NScrollbar,
|
||||
FormValidationError,
|
||||
NSelect,
|
||||
NGrid,
|
||||
NGridItem,
|
||||
NDropdown,
|
||||
NImage,
|
||||
useDialog,
|
||||
NPopconfirm,
|
||||
NEmpty,
|
||||
useMessage,
|
||||
} from 'naive-ui'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import PointOrderManage from './PointOrderManage.vue'
|
||||
import PointUserManage from './PointUserManage.vue'
|
||||
import { cloneFnJSON } from '@vueuse/core'
|
||||
import { useAuthStore } from '@/store/useAuthStore'
|
||||
import PointSettings from './PointSettings.vue'
|
||||
import { useRouteHash } from '@vueuse/router'
|
||||
import EventFetcherStatusCard from '@/components/EventFetcherStatusCard.vue'
|
||||
import PointUserManage from './PointUserManage.vue'
|
||||
|
||||
const message = useMessage()
|
||||
const accountInfo = useAccount()
|
||||
@@ -240,7 +228,7 @@ async function updateGoods(e: MouseEvent) {
|
||||
}
|
||||
function OnFileListChange(files: UploadFileInfo[]) {
|
||||
if (files.length == 1) {
|
||||
var file = files[0]
|
||||
const file = files[0]
|
||||
if ((file.file?.size ?? 0) > 10 * 1024 * 1024) {
|
||||
message.error('文件大小不能超过10MB')
|
||||
currentGoodsModel.value.fileList = []
|
||||
|
||||
@@ -3,8 +3,8 @@ import { ResponsePointGoodModel, ResponsePointOrder2OwnerModel } from '@/api/api
|
||||
import { QueryGetAPI } from '@/api/query'
|
||||
import PointOrderCard from '@/components/manage/PointOrderCard.vue'
|
||||
import { POINT_API_URL } from '@/data/constants'
|
||||
import { NButton, NCard, NEmpty, NList, NListItem, useMessage } from 'naive-ui'
|
||||
import { h, onMounted, ref } from 'vue'
|
||||
import { NEmpty, useMessage } from 'naive-ui'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
goods: ResponsePointGoodModel[]
|
||||
|
||||
@@ -188,13 +188,20 @@ async function updateGift() {
|
||||
</template>
|
||||
<NCard>
|
||||
<NFlex vertical>
|
||||
<NButton @click="showAddGiftModal = true" type="primary" :disabled="!canEdit" style="max-width: 200px"> 添加礼物 </NButton>
|
||||
<NButton @click="showAddGiftModal = true" type="primary" :disabled="!canEdit" style="max-width: 200px">
|
||||
添加礼物
|
||||
</NButton>
|
||||
<NList bordered>
|
||||
<NListItem v-for="item in Object.entries(setting.giftPercentMap)" :key="item[0]">
|
||||
<NFlex align="center">
|
||||
<NTag :bordered="false" size="small" type="success"> {{ item[0] }} </NTag>
|
||||
<NInputGroup style="width: 200px" :disabled="!canEdit">
|
||||
<NInputNumber :value="setting.giftPercentMap[item[0]]" @update:value="(v) => (setting.giftPercentMap[item[0]] = v ?? 0)" :disabled="!canEdit" min="0" />
|
||||
<NInputNumber
|
||||
:value="setting.giftPercentMap[item[0]]"
|
||||
@update:value="(v) => (setting.giftPercentMap[item[0]] = v ?? 0)"
|
||||
:disabled="!canEdit"
|
||||
min="0"
|
||||
/>
|
||||
<NButton @click="updateSettings" type="info" :disabled="!canEdit">确定</NButton>
|
||||
</NInputGroup>
|
||||
<NPopconfirm @positive-click="deleteGift(item[0])">
|
||||
|
||||
@@ -9,12 +9,9 @@ import { QueryGetAPI } from '@/api/query'
|
||||
import PointHistoryCard from '@/components/manage/PointHistoryCard.vue'
|
||||
import PointOrderCard from '@/components/manage/PointOrderCard.vue'
|
||||
import { POINT_API_URL } from '@/data/constants'
|
||||
import { useAuthStore } from '@/store/useAuthStore'
|
||||
import {
|
||||
DataTableColumns,
|
||||
NButton,
|
||||
NCard,
|
||||
NDataTable,
|
||||
NDescriptions,
|
||||
NDescriptionsItem,
|
||||
NDivider,
|
||||
@@ -22,8 +19,6 @@ import {
|
||||
NFlex,
|
||||
NInput,
|
||||
NInputNumber,
|
||||
NList,
|
||||
NListItem,
|
||||
NModal,
|
||||
NSpin,
|
||||
NTag,
|
||||
@@ -32,7 +27,7 @@ import {
|
||||
NTooltip,
|
||||
useMessage,
|
||||
} from 'naive-ui'
|
||||
import { h, onMounted, ref } from 'vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
user: ResponsePointUserModel
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { ResponsePointGoodModel, ResponsePointUserModel } from '@/api/api-models'
|
||||
import { QueryGetAPI } from '@/api/query'
|
||||
import { POINT_API_URL } from '@/data/constants'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import {
|
||||
DataTableColumns,
|
||||
NButton,
|
||||
@@ -11,8 +12,6 @@ import {
|
||||
NDivider,
|
||||
NEmpty,
|
||||
NFlex,
|
||||
NList,
|
||||
NListItem,
|
||||
NModal,
|
||||
NPopconfirm,
|
||||
NScrollbar,
|
||||
@@ -24,7 +23,6 @@ import {
|
||||
} from 'naive-ui'
|
||||
import { computed, h, onMounted, ref } from 'vue'
|
||||
import PointUserDetailCard from './PointUserDetailCard.vue'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
|
||||
const props = defineProps<{
|
||||
goods: ResponsePointGoodModel[]
|
||||
|
||||
Reference in New Issue
Block a user