mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-07 02:46:55 +08:00
492 lines
15 KiB
Vue
492 lines
15 KiB
Vue
<script setup lang="ts">
|
|
import { NavigateToNewTab } from '@/Utils'
|
|
import { LotteryUserInfo } from '@/api/api-models'
|
|
import { QueryGetAPI } from '@/api/query'
|
|
import { LOTTERY_API_URL, TURNSTILE_KEY } from '@/data/constants'
|
|
import { useLocalStorage, useStorage } from '@vueuse/core'
|
|
import { format } from 'date-fns'
|
|
import { List } from 'linqts'
|
|
import {
|
|
NAvatar,
|
|
NButton,
|
|
NCard,
|
|
NCheckbox,
|
|
NCollapseTransition,
|
|
NCountdown,
|
|
NDivider,
|
|
NEmpty,
|
|
NGrid,
|
|
NGridItem,
|
|
NInput,
|
|
NInputGroup,
|
|
NInputGroupLabel,
|
|
NInputNumber,
|
|
NList,
|
|
NListItem,
|
|
NModal,
|
|
NRadioButton,
|
|
NRadioGroup,
|
|
NScrollbar,
|
|
NSpace,
|
|
NTag,
|
|
NTime,
|
|
NTooltip,
|
|
useMessage,
|
|
useNotification,
|
|
} from 'naive-ui'
|
|
import { computed, h, onUnmounted, ref } from 'vue'
|
|
import VueTurnstile from 'vue-turnstile'
|
|
|
|
interface TempLotteryResponseModel {
|
|
users: LotteryUserInfo[]
|
|
createTime: number
|
|
total: number
|
|
}
|
|
interface LotteryOption {
|
|
resultCount: number
|
|
lotteryType: 'single' | 'half'
|
|
needVIP: boolean
|
|
needFanCard: boolean
|
|
needGuard: boolean
|
|
needCharge: boolean
|
|
fanCardLevel: number
|
|
}
|
|
interface LotteryHistory {
|
|
users: LotteryUserInfo[]
|
|
time: number
|
|
type: 'comment' | 'forward'
|
|
url: string
|
|
}
|
|
|
|
const lotteryHistory = useStorage<LotteryHistory[]>('LotteryHistory', [])
|
|
|
|
const message = useMessage()
|
|
const notification = useNotification()
|
|
const token = ref('')
|
|
const turnstile = ref()
|
|
const defaultOption = {
|
|
resultCount: 1,
|
|
lotteryType: 'single',
|
|
needVIP: false,
|
|
needFanCard: false,
|
|
needGuard: false,
|
|
needCharge: false,
|
|
fanCardLevel: 1,
|
|
} as LotteryOption
|
|
const lotteryOption = useLocalStorage('Settings.LotteryOption', defaultOption)
|
|
|
|
const isLoading = ref(false)
|
|
const isLottering = ref(false)
|
|
const isLotteried = ref(false)
|
|
|
|
const showModal = ref(false)
|
|
|
|
const inputDynamic = ref<string>()
|
|
const inputDynamicId = computed(() => {
|
|
try {
|
|
const id = BigInt(inputDynamic.value ?? '')
|
|
return id
|
|
} catch {
|
|
try {
|
|
const url = new URL(inputDynamic.value ?? '')
|
|
if (url.host.endsWith('bilibili.com')) {
|
|
const sp = url.pathname.split('/')
|
|
const id = BigInt(sp.length > 1 ? sp[sp.length - 1] : sp[0])
|
|
return id
|
|
}
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
return null
|
|
})
|
|
const isCommentCountDown = ref(true)
|
|
const originCommentUsers = ref<TempLotteryResponseModel>()
|
|
const originForwardUsers = ref<TempLotteryResponseModel>()
|
|
const currentType = ref<'comment' | 'forward'>('comment')
|
|
|
|
const resultUsers = ref<LotteryUserInfo[]>()
|
|
|
|
const commentUsers = ref<TempLotteryResponseModel>()
|
|
const forwardUsers = ref<TempLotteryResponseModel>()
|
|
|
|
const currentUsers = computed(() => {
|
|
return getCurrentUsers()
|
|
})
|
|
function getCurrentUsers() {
|
|
switch (currentType.value) {
|
|
case 'comment': {
|
|
return commentUsers.value
|
|
}
|
|
case 'forward': {
|
|
return forwardUsers.value
|
|
}
|
|
}
|
|
return undefined
|
|
}
|
|
const validUsers = computed(() => {
|
|
return currentUsers.value?.users.filter((u) => isUserValid(u))
|
|
})
|
|
|
|
async function onGet() {
|
|
switch (currentType.value) {
|
|
case 'comment': {
|
|
await getCommentsUsers()
|
|
break
|
|
}
|
|
case 'forward': {
|
|
await getForwardUsers()
|
|
break
|
|
}
|
|
}
|
|
currentUsers.value?.users.forEach((u) => (u.visiable = true))
|
|
}
|
|
async function getCommentsUsers() {
|
|
isLoading.value = true
|
|
await QueryGetAPI<TempLotteryResponseModel>(
|
|
LOTTERY_API_URL + 'comments',
|
|
{
|
|
id: inputDynamicId.value,
|
|
},
|
|
[['Turnstile', token.value]],
|
|
)
|
|
.then((data) => {
|
|
if (data.code == 200) {
|
|
data.data.users = new List(data.data.users).DistinctBy((u) => u.uId).ToArray()
|
|
data.data.total = data.data.users.length
|
|
|
|
originCommentUsers.value = JSON.parse(JSON.stringify(data.data))
|
|
commentUsers.value = data.data
|
|
isCommentCountDown.value = false
|
|
} else {
|
|
message.error('获取用户失败: ' + data.message)
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
message.error('获取失败')
|
|
})
|
|
.finally(() => {
|
|
turnstile.value?.reset()
|
|
isLoading.value = false
|
|
})
|
|
}
|
|
async function getForwardUsers() {
|
|
isLoading.value = true
|
|
await QueryGetAPI<TempLotteryResponseModel>(
|
|
LOTTERY_API_URL + 'forward',
|
|
{
|
|
id: inputDynamicId.value,
|
|
},
|
|
[['Turnstile', token.value]],
|
|
)
|
|
.then((data) => {
|
|
if (data.code == 200) {
|
|
data.data.users = new List(data.data.users).DistinctBy((u) => u.uId).ToArray()
|
|
data.data.total = data.data.users.length
|
|
|
|
originForwardUsers.value = JSON.parse(JSON.stringify(data.data))
|
|
forwardUsers.value = data.data
|
|
isCommentCountDown.value = false
|
|
} else {
|
|
message.error('获取用户失败: ' + data.message)
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
message.error('获取失败')
|
|
})
|
|
.finally(() => {
|
|
turnstile.value?.reset()
|
|
isLoading.value = false
|
|
})
|
|
}
|
|
function getRandomInt(max: number) {
|
|
return Math.floor(Math.random() * max)
|
|
}
|
|
function isUserValid(u: LotteryUserInfo) {
|
|
if (lotteryOption.value.needVIP) {
|
|
if (u.isVIP != true) return false
|
|
}
|
|
if (lotteryOption.value.needFanCard) {
|
|
if ((u.card?.level ?? -1) < lotteryOption.value.fanCardLevel) return false
|
|
}
|
|
if (lotteryOption.value.needGuard) {
|
|
if (u.card?.isGuard != true) return false
|
|
}
|
|
if (lotteryOption.value.needCharge) {
|
|
if (u.card?.isCharge != true) return false
|
|
}
|
|
return true
|
|
}
|
|
function startLottery() {
|
|
if (!isLottering.value && currentUsers.value) {
|
|
isLottering.value = true
|
|
try {
|
|
const data = currentUsers.value
|
|
if ((validUsers.value?.length ?? -1) < lotteryOption.value.resultCount) {
|
|
message.warning('符合条件的抽奖人数达不到抽选人数')
|
|
isLottering.value = false
|
|
return
|
|
}
|
|
|
|
const tempUsers = getCurrentUsers()
|
|
if (tempUsers) tempUsers.users = validUsers.value ?? []
|
|
|
|
switch (lotteryOption.value.lotteryType) {
|
|
case 'single': {
|
|
console.log('开始抽取单个用户')
|
|
removeSingleUser()
|
|
function removeSingleUser() {
|
|
if (data.users.length > lotteryOption.value.resultCount) {
|
|
console.log(`[${data.users.length}] 移除` + data.users.splice(getRandomInt(data.users.length), 1)[0].name)
|
|
setTimeout(() => {
|
|
removeSingleUser()
|
|
}, 500)
|
|
} else {
|
|
onFinishLottery()
|
|
}
|
|
}
|
|
break
|
|
}
|
|
}
|
|
} catch (err) {
|
|
message.error('发生错误')
|
|
}
|
|
}
|
|
}
|
|
function onFinishLottery() {
|
|
resultUsers.value = JSON.parse(JSON.stringify(currentUsers.value?.users))
|
|
isLottering.value = false
|
|
isLotteried.value = true
|
|
notification.create({
|
|
title: '抽奖完成',
|
|
description: '共' + resultUsers.value?.length + '人',
|
|
duration: 3000,
|
|
content: () =>
|
|
h(
|
|
NSpace,
|
|
{ vertical: true },
|
|
resultUsers.value?.map((user) =>
|
|
h(NSpace, null, [
|
|
h(NAvatar, { src: user.avatar + '@32w_32h', imgProps: { referrerpolicy: 'no-referrer' } }),
|
|
h('span', user.name),
|
|
]),
|
|
),
|
|
),
|
|
meta: format(new Date(), 'yyyy-MM-dd HH:mm:ss'),
|
|
onAfterLeave: () => {
|
|
message.success('已保存至历史')
|
|
},
|
|
})
|
|
lotteryHistory.value.push({
|
|
users: currentUsers.value?.users ?? [],
|
|
time: Date.now(),
|
|
type: currentType.value,
|
|
url: inputDynamicId.value ? 'https://t.bilibili.com/' + inputDynamicId.value : inputDynamic.value ?? '',
|
|
})
|
|
}
|
|
function reset() {
|
|
switch (currentType.value) {
|
|
case 'comment': {
|
|
commentUsers.value = JSON.parse(JSON.stringify(originCommentUsers.value))
|
|
break
|
|
}
|
|
case 'forward': {
|
|
forwardUsers.value = JSON.parse(JSON.stringify(originForwardUsers.value))
|
|
break
|
|
}
|
|
}
|
|
isLotteried.value = false
|
|
}
|
|
function getLevelColor(level: number) {
|
|
switch (level) {
|
|
case 1: {
|
|
return 'gray'
|
|
}
|
|
case 2: {
|
|
return '#8bd29d'
|
|
}
|
|
case 4: {
|
|
return '#FEBB8B'
|
|
}
|
|
case 3: {
|
|
return '#7BCDEF'
|
|
}
|
|
case 5: {
|
|
return '#EE672A'
|
|
}
|
|
case 6: {
|
|
return '#F04C49'
|
|
}
|
|
default: {
|
|
return 'gray'
|
|
}
|
|
}
|
|
}
|
|
|
|
onUnmounted(() => {
|
|
turnstile.value?.remove()
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<NCard size="medium" embedded title="动态抽奖">
|
|
<template #header-extra>
|
|
<NButton @click="showModal = true"> 历史记录 </NButton>
|
|
</template>
|
|
<NInput
|
|
v-model:value="inputDynamic"
|
|
placeholder="动态链接或直接输入动态Id"
|
|
:status="inputDynamicId ? 'success' : 'warning'"
|
|
:disabled="isLoading || isLottering"
|
|
/>
|
|
<NDivider style="margin: 10px 0 10px 0" />
|
|
<NCard size="small" embedded title="选项">
|
|
<template #header-extra>
|
|
<NButton size="small" secondary @click="lotteryOption = defaultOption"> 恢复默认 </NButton>
|
|
</template>
|
|
<NSpace justify="center">
|
|
<NRadioGroup v-model:value="currentType" name="用户类型" :disabled="isLottering">
|
|
<NRadioButton value="comment"> 评论 </NRadioButton>
|
|
<NRadioButton value="forward"> 转发 </NRadioButton>
|
|
</NRadioGroup>
|
|
</NSpace>
|
|
<NDivider style="margin: 10px 0 10px 0"></NDivider>
|
|
<NSpace align="center">
|
|
<NInputGroup style="max-width: 200px">
|
|
<NInputGroupLabel> 抽选人数 </NInputGroupLabel>
|
|
<NInputNumber v-model:value="lotteryOption.resultCount" placeholder="" min="1" />
|
|
</NInputGroup>
|
|
<NCheckbox v-model:checked="lotteryOption.needVIP"> 需要大会员 </NCheckbox>
|
|
<template v-if="currentType == 'comment'">
|
|
<NCheckbox v-model:checked="lotteryOption.needCharge"> 需要充电 </NCheckbox>
|
|
<NCheckbox v-model:checked="lotteryOption.needFanCard"> 需要粉丝牌 </NCheckbox>
|
|
<NCollapseTransition>
|
|
<NInputGroup v-if="lotteryOption.needFanCard" style="max-width: 200px">
|
|
<NInputGroupLabel> 最低粉丝牌等级 </NInputGroupLabel>
|
|
<NInputNumber
|
|
v-model:value="lotteryOption.fanCardLevel"
|
|
min="1"
|
|
max="50"
|
|
:default-value="1"
|
|
:disabled="isLottering"
|
|
/>
|
|
</NInputGroup>
|
|
</NCollapseTransition>
|
|
</template>
|
|
<NRadioGroup v-model:value="lotteryOption.lotteryType" name="抽取类型" size="small" :disabled="isLottering">
|
|
<NRadioButton value="single"> 单个 </NRadioButton>
|
|
<NRadioButton value="half"> 减半 </NRadioButton>
|
|
</NRadioGroup>
|
|
</NSpace>
|
|
</NCard>
|
|
<br />
|
|
<NSpace justify="center" align="center">
|
|
<NButton
|
|
:disabled="!inputDynamicId || !isCommentCountDown || !token || isLottering"
|
|
:loading="!token || isLoading"
|
|
@click="onGet"
|
|
type="primary"
|
|
>
|
|
加载用户
|
|
</NButton>
|
|
<NCountdown
|
|
v-if="!isCommentCountDown"
|
|
:duration="(currentUsers?.createTime ?? -1) + 60000 - Date.now()"
|
|
@finish="isCommentCountDown = true"
|
|
/>
|
|
</NSpace>
|
|
<br />
|
|
<NCard v-if="currentUsers" size="small">
|
|
<NSpace justify="center">
|
|
<NButton type="primary" @click="startLottery" :loading="isLottering" :disabled="isLotteried">
|
|
开始抽取
|
|
</NButton>
|
|
<NButton type="info" :disabled="isLottering || !isLotteried" @click="reset"> 重置 </NButton>
|
|
</NSpace>
|
|
<NDivider style="margin: 10px 0 10px 0"> 共 {{ validUsers?.length }} 人</NDivider>
|
|
<NGrid cols="1 500:2 800:3 1000:4" :x-gap="12" :y-gap="8">
|
|
<NGridItem v-for="item in validUsers" v-bind:key="item.uId">
|
|
<NCard size="small" :title="item.name" style="height: 155px">
|
|
<template #header>
|
|
<NSpace align="center" vertical :size="5">
|
|
<NAvatar
|
|
round
|
|
lazy
|
|
borderd
|
|
:size="64"
|
|
:src="item.avatar + '@64w_64h'"
|
|
:img-props="{ referrerpolicy: 'no-referrer' }"
|
|
style="box-shadow: 0 3px 5px rgba(0, 0, 0, 0.2)"
|
|
/>
|
|
<NSpace>
|
|
<NTag
|
|
v-if="item.isVIP"
|
|
size="tiny"
|
|
round
|
|
:color="{ color: '#fb7299', textColor: 'white', borderColor: 'white' }"
|
|
>
|
|
大会员
|
|
</NTag>
|
|
<NTooltip>
|
|
<template #trigger>
|
|
<NTag
|
|
v-if="item.level"
|
|
size="tiny"
|
|
:color="{ color: getLevelColor(item.level), textColor: 'white', borderColor: 'white' }"
|
|
:borderd="false"
|
|
>
|
|
LV {{ item.level }}
|
|
</NTag>
|
|
</template>
|
|
用户等级
|
|
</NTooltip>
|
|
<NTag v-if="item.card" size="tiny" round>
|
|
<NTag size="tiny" round :bordered="false">
|
|
{{ item.card.level }}
|
|
</NTag>
|
|
<span style="color: #577fb8">
|
|
{{ item.card.name }}
|
|
</span>
|
|
</NTag>
|
|
</NSpace>
|
|
{{ item.name }}
|
|
</NSpace>
|
|
</template>
|
|
</NCard>
|
|
</NGridItem>
|
|
</NGrid>
|
|
</NCard>
|
|
</NCard>
|
|
<NModal v-model:show="showModal" preset="card" title="抽奖结果" style="max-width: 90%; width: 800px" closable>
|
|
<template #header-extra>
|
|
<NButton type="error" size="small" @click="lotteryHistory = []"> 清空 </NButton>
|
|
</template>
|
|
<NScrollbar v-if="lotteryHistory.length > 0" style="max-height: 80vh">
|
|
<NList>
|
|
<NListItem v-for="item in lotteryHistory" :key="item.time">
|
|
<NCard size="small">
|
|
<template #header>
|
|
<NTime :time="item.time" />
|
|
</template>
|
|
<template #header-extra>
|
|
<NButton type="error" size="small" @click="lotteryHistory.splice(lotteryHistory.indexOf(item), 1)">
|
|
删除
|
|
</NButton>
|
|
</template>
|
|
<NSpace vertical>
|
|
<NSpace v-for="user in item.users" :key="user.uId">
|
|
<NAvatar round lazy :src="user.avatar + '@64w_64h'" :img-props="{ referrerpolicy: 'no-referrer' }" />
|
|
{{ user.name }}
|
|
</NSpace>
|
|
</NSpace>
|
|
<NDivider style="margin: 10px 0 10px 0" />
|
|
<NButton secondary @click="NavigateToNewTab(item.url)"> 目标动态 </NButton>
|
|
</NCard>
|
|
</NListItem>
|
|
</NList>
|
|
</NScrollbar>
|
|
<NEmpty v-else description="暂无记录" />
|
|
</NModal>
|
|
<VueTurnstile ref="turnstile" :site-key="TURNSTILE_KEY" v-model="token" theme="auto" style="text-align: center" />
|
|
</template>
|