feat: 更新API模型和组件以支持售罄自动下架功能

- 在api-models.ts中为设置添加shouldDiscontinueWhenSoldOut字段
- 在PointSettings.vue中新增售罄自动下架的选项
- 在多个组件中调整按钮文本和状态逻辑
- 在PointUserHistoryView.vue中添加主播筛选功能
This commit is contained in:
2025-05-06 10:06:34 +08:00
parent a5420e5914
commit 7614880d34
6 changed files with 123 additions and 19 deletions

View File

@@ -228,6 +228,7 @@ export interface Setting_Point {
scPointPercent: number // double maps to number in TypeScript scPointPercent: number // double maps to number in TypeScript
giftPointPercent: number // double maps to number in TypeScript giftPointPercent: number // double maps to number in TypeScript
giftAllowType: SettingPointGiftAllowType giftAllowType: SettingPointGiftAllowType
shouldDiscontinueWhenSoldOut: boolean
// 签到系统设置 // 签到系统设置
enableCheckIn: boolean // 是否启用签到功能 enableCheckIn: boolean // 是否启用签到功能

View File

@@ -94,13 +94,43 @@ const historyColumn: DataTableColumns<ResponsePointHisrotyModel> = [
: null, : null,
]) ])
case PointFrom.Manual: case PointFrom.Manual:
return h( return h(NFlex, { align: 'center' }, () => [
NTag, h(
{ type: row.point > 0 ? 'success' : 'error', bordered: false, size: 'small' }, NTag,
() => '主播' + (row.point > 0 ? '赠予' : '扣除'), { type: row.point > 0 ? 'success' : 'error', bordered: false, size: 'small' },
) () => '主播' + (row.point > 0 ? '赠予' : '扣除'),
),
row.extra?.user
? h(
NButton,
{
tag: 'a',
href: '/@' + row.extra.user?.name,
target: '_blank',
text: true,
type: 'info',
},
() => row.extra.user?.name,
)
: null,
])
case PointFrom.Use: case PointFrom.Use:
return h(NTag, { type: 'warning', bordered: false, size: 'small' }, () => '使用') return h(NFlex, { align: 'center' }, () => [
h(NTag, { type: 'warning', bordered: false, size: 'small' }, () => '使用'),
row.extra?.user
? h(
NButton,
{
tag: 'a',
href: '/@' + row.extra.user?.name,
target: '_blank',
text: true,
type: 'success',
},
() => row.extra.user?.name,
)
: null,
])
case PointFrom.CheckIn: case PointFrom.CheckIn:
return h(NFlex, { align: 'center' }, () => [ return h(NFlex, { align: 'center' }, () => [
h(NTag, { type: 'success', bordered: false, size: 'small' }, () => '签到'), h(NTag, { type: 'success', bordered: false, size: 'small' }, () => '签到'),
@@ -131,12 +161,12 @@ const historyColumn: DataTableColumns<ResponsePointHisrotyModel> = [
case PointFrom.Danmaku: case PointFrom.Danmaku:
switch (row.type) { switch (row.type) {
case EventDataTypes.Guard: case EventDataTypes.Guard:
return h(NFlex, { justify: 'center', align: 'center' }, () => [ return h(NFlex, { align: 'center' }, () => [
h(NTag, { type: 'error', size: 'small' }, () => '上舰'), h(NTag, { type: 'error', size: 'small' }, () => '上舰'),
row.extra?.danmaku.msg, row.extra?.danmaku.msg,
]) ])
case EventDataTypes.Gift: case EventDataTypes.Gift:
return h(NFlex, { justify: 'center', align: 'center' }, () => [ return h(NFlex, { align: 'center' }, () => [
h(NTag, { type: 'info', size: 'small', style: { margin: '0' } }, () => '礼物'), h(NTag, { type: 'info', size: 'small', style: { margin: '0' } }, () => '礼物'),
row.extra?.danmaku.msg, row.extra?.danmaku.msg,
h( h(
@@ -146,7 +176,7 @@ const historyColumn: DataTableColumns<ResponsePointHisrotyModel> = [
), ),
]) ])
case EventDataTypes.SC: case EventDataTypes.SC:
return h(NFlex, { justify: 'center' }, () => [ return h(NFlex, { align: 'center' }, () => [
h(NTag, { type: 'warning', size: 'small', style: { margin: '0' } }, () => 'SC'), h(NTag, { type: 'warning', size: 'small', style: { margin: '0' } }, () => 'SC'),
row.extra?.danmaku.price, row.extra?.danmaku.price,
]) ])

View File

@@ -54,6 +54,7 @@ const defaultSettingPoint: Setting_Point = {
maxBonusPoints: 0, maxBonusPoints: 0,
allowSelfCheckIn: false, allowSelfCheckIn: false,
requireAuth: false, requireAuth: false,
shouldDiscontinueWhenSoldOut: false,
} }
// 响应式设置对象 // 响应式设置对象
@@ -262,6 +263,20 @@ async function SaveComboSetting() {
</NCheckbox> </NCheckbox>
</NFlex> </NFlex>
<NFlex
align="center"
:gap="12"
>
<span>其他: </span>
<NCheckbox
v-model:checked="setting.shouldDiscontinueWhenSoldOut"
:disabled="!canEdit"
@update:checked="updateSettings"
>
礼物售罄时自动下架
</NCheckbox>
</NFlex>
<!-- 积分来源设置 --> <!-- 积分来源设置 -->
<NFlex <NFlex
align="center" align="center"

View File

@@ -328,9 +328,10 @@ onMounted(async () => {
<NButton <NButton
type="primary" type="primary"
:loading="isLoading" :loading="isLoading"
:disabled="!addPointCount || addPointCount === 0"
@click="givePoint" @click="givePoint"
> >
{{ addPointCount > 0 ? '给予' : '扣除' }} {{ !addPointCount || addPointCount === 0 ? '确定' : (addPointCount > 0 ? '给予' : '扣除') }}
</NButton> </NButton>
</NFlex> </NFlex>
</NModal> </NModal>

View File

@@ -577,9 +577,10 @@ onMounted(async () => {
<NButton <NButton
type="primary" type="primary"
:loading="isLoading" :loading="isLoading"
:disabled="!addPointCount || addPointCount === 0"
@click="givePoint" @click="givePoint"
> >
{{ addPointCount > 0 ? '给予' : '扣除' }} {{ !addPointCount || addPointCount === 0 ? '确定' : (addPointCount > 0 ? '给予' : '扣除') }}
</NButton> </NButton>
</NFlex> </NFlex>
</NModal> </NModal>

View File

@@ -1,16 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
import { ResponsePointHisrotyModel } from '@/api/api-models' import { ResponsePointHisrotyModel, PointFrom } from '@/api/api-models'
import PointHistoryCard from '@/components/manage/PointHistoryCard.vue' import PointHistoryCard from '@/components/manage/PointHistoryCard.vue'
import { POINT_API_URL } from '@/data/constants' import { POINT_API_URL } from '@/data/constants'
import { useBiliAuth } from '@/store/useBiliAuth' import { useBiliAuth } from '@/store/useBiliAuth'
import { NButton, NEmpty, NFlex, NSpin, useMessage } from 'naive-ui' import { NButton, NEmpty, NFlex, NSelect, NSpin, useMessage } from 'naive-ui'
import { onMounted, ref } from 'vue' import { computed, onMounted, ref } from 'vue'
const message = useMessage() const message = useMessage()
const useAuth = useBiliAuth() const useAuth = useBiliAuth()
const isLoading = ref(false) const isLoading = ref(false)
const history = ref<ResponsePointHisrotyModel[]>([]) const history = ref<ResponsePointHisrotyModel[]>([])
const streamerFilter = ref<string | null>('')
// 定义加载完成的事件 // 定义加载完成的事件
const emit = defineEmits(['dataLoaded']) const emit = defineEmits(['dataLoaded'])
@@ -42,6 +43,7 @@ async function getHistories() {
// 提供给父组件调用的重置方法 // 提供给父组件调用的重置方法
function reset() { function reset() {
history.value = [] history.value = []
streamerFilter.value = ''
} }
// 暴露方法给父组件 // 暴露方法给父组件
@@ -53,20 +55,74 @@ defineExpose({
onMounted(async () => { onMounted(async () => {
history.value = await getHistories() history.value = await getHistories()
}) })
// 根据主播名称筛选历史记录
const filteredHistory = computed(() => {
if (streamerFilter.value === '' || streamerFilter.value === null) {
return history.value
}
return history.value.filter(item => {
// 只筛选主播操作、弹幕来源和签到
if ([PointFrom.Manual, PointFrom.Danmaku, PointFrom.CheckIn].includes(item.from)) {
// 精确匹配主播名称
return item.extra?.user?.name === streamerFilter.value
}
// 如果是使用积分的记录,则始终显示
if (item.from === PointFrom.Use) {
return true
}
// 其他类型的记录,在筛选时隐藏
return false
})
})
// 计算可选的主播列表
const streamerOptions = computed(() => {
const names = new Set<string>()
history.value.forEach(item => {
if ([PointFrom.Manual, PointFrom.Danmaku, PointFrom.CheckIn].includes(item.from) && item.extra?.user?.name) {
names.add(item.extra.user.name)
}
})
// 添加"全部主播"选项
const options = [{ label: '全部主播', value: '' }]
names.forEach(name => {
options.push({ label: name, value: name })
})
return options
})
</script> </script>
<template> <template>
<NSpin :show="isLoading"> <NSpin :show="isLoading">
<NFlex justify="end" style="margin-bottom: 10px"> <NFlex
<NButton size="small" type="primary" @click="getHistories">刷新记录</NButton> justify="end"
align="center"
style="margin-bottom: 10px"
>
<NSelect
v-model:value="streamerFilter"
:options="streamerOptions"
placeholder="按主播筛选"
clearable
size="small"
style="max-width: 200px; margin-right: 10px"
/>
<NButton
size="small"
type="primary"
@click="getHistories"
>
刷新记录
</NButton>
</NFlex> </NFlex>
<NEmpty <NEmpty
v-if="history.length == 0" v-if="filteredHistory.length == 0"
description="暂无积分记录" description="暂无符合条件的积分记录"
/> />
<PointHistoryCard <PointHistoryCard
v-else v-else
:histories="history" :histories="filteredHistory"
/> />
</NSpin> </NSpin>
</template> </template>