Compare commits

..

3 Commits

Author SHA1 Message Date
a5420e5914 feat: 更新API模型和组件以支持备注功能
- 在api-models.ts中为订单模型添加备注字段
- 在PointOrderCard.vue中新增备注列并调整显示逻辑
- 在PointOrderManage.vue中导出数据时包含备注信息
- 在PointGoodsView.vue中添加备注输入框以供用户填写
2025-05-06 08:50:21 +08:00
4ebfeaec69 feat: 在SongList组件中新增试听和链接开关的显示逻辑
- 添加计算属性以判断是否显示试听和链接开关
- 调整操作列的宽度计算逻辑
- 优化按钮的动态显示逻辑
2025-05-06 02:32:43 +08:00
8f734af8b3 feat: 更新API模型和组件以支持签到排行开关功能
- 在api-models.ts中将SongRequest更改为LiveRequest,并添加CheckInRanking
- 更新CheckInSettings.vue以支持签到排行的开关
- 在多个视图中调整签到排行的显示逻辑
- 移除不再使用的allowCheckInRanking字段
2025-05-06 02:19:23 +08:00
13 changed files with 131 additions and 85 deletions

View File

@@ -239,7 +239,6 @@ export interface Setting_Point {
maxBonusPoints: number // 最大奖励积分
allowSelfCheckIn: boolean // 是否允许自己签到
requireAuth: boolean // 是否需要认证
allowCheckInRanking: boolean // 是否允许查询签到排行
}
export interface Setting_QuestionDisplay {
font?: string // Optional string, with a maximum length of 30 characters
@@ -293,10 +292,11 @@ export enum FunctionTypes {
SongList,
QuestionBox,
Schedule,
SongRequest,
LiveRequest,
Queue,
Point,
VideoCollect
VideoCollect,
CheckInRanking,
}
export interface SongAuthorInfo {
name: string
@@ -777,7 +777,7 @@ export interface ResponsePointOrder2OwnerModel {
createAt: number
updateAt: number
status: PointOrderStatus
remark?: string
trackingNumber?: string
expressCompany?: string
}
@@ -791,6 +791,7 @@ export interface ResponsePointOrder2UserModel {
goods: ResponsePointGoodModel
status: PointOrderStatus
createAt: number
remark?: string
trackingNumber?: string
expressCompany?: string

View File

@@ -148,8 +148,8 @@
<NFormItem label="允许查看签到排行">
<NSwitch
v-model:value="serverSetting.allowCheckInRanking"
@update:value="updateServerSettings"
:value="accountInfo.settings.enableFunctions.includes(FunctionTypes.CheckInRanking)"
@update:value="updateCheckInRanking"
/>
<template #feedback>
启用后用户可以查看签到排行榜
@@ -381,8 +381,8 @@
</template>
<script lang="ts" setup>
import { SaveSetting, useAccount } from '@/api/account';
import { CheckInRankingInfo, CheckInResult } from '@/api/api-models';
import { SaveEnableFunctions, SaveSetting, useAccount } from '@/api/account';
import { CheckInRankingInfo, CheckInResult, FunctionTypes } from '@/api/api-models';
import { QueryGetAPI } from '@/api/query';
import { useAutoAction } from '@/client/store/useAutoAction';
import { CHECKIN_API_URL } from '@/data/constants';
@@ -791,6 +791,10 @@ async function handleTestCheckIn() {
});
}
}
function updateCheckInRanking(value: boolean) {
accountInfo.value.settings.enableFunctions = value ? [...accountInfo.value.settings.enableFunctions, FunctionTypes.CheckInRanking] : accountInfo.value.settings.enableFunctions.filter(f => f !== FunctionTypes.CheckInRanking);
SaveEnableFunctions(accountInfo.value.settings.enableFunctions);
}
// 组件挂载时加载排行榜
onMounted(() => {

View File

@@ -156,7 +156,6 @@ function getScoreColor(score: number | undefined): string {
lazy
/>
</NSpace>
<NDivider style="margin: 10px 0;" />
</template>
<NText

View File

@@ -119,20 +119,32 @@ defineExpose({
// --- 计算属性 ---
// 新增:计算是否需要显示试听开关
const canShowListenSwitch = computed(() => {
const audioRegex = /\.(mp3|flac|ogg|wav|m4a)$/i;
return songsInternal.value.some(song => song.url && audioRegex.test(song.url));
});
// 新增:计算是否需要显示链接开关
const canShowLinkSwitch = computed(() => {
const linkSources = [SongFrom.Netease, SongFrom.FiveSing, SongFrom.Kugou]; // Corrected sources
return songsInternal.value.some(song => song.url || (song.from != null && linkSources.includes(song.from))); // Check url OR valid from source
});
// 计算操作列的预定义宽度
const actionColumnWidth = computed(() => {
const baseSelfWidth = 85; // 基础宽度 (isSelf=true, 编辑+删除)
const baseSelfWidth = 80; // 基础宽度 (isSelf=true, 编辑+删除)
const basePublicWidth = 40; // 基础宽度 (isSelf=false)
const listenButtonWidth = 40;
const linkButtonWidth = 40;
const linkButtonWidth = 50;
const extraButtonWidth = 40; // 假设的额外按钮宽度
let width = props.isSelf ? baseSelfWidth : basePublicWidth;
if (showListenButton.value) {
if (showListenButton.value && canShowListenSwitch.value) {
width += listenButtonWidth;
}
if (showLinkButton.value) {
if (showLinkButton.value && canShowLinkSwitch.value) {
width += linkButtonWidth;
}
if (props.extraButton) {
@@ -351,7 +363,7 @@ function createColumns(): DataTableColumns<SongsInfo> {
{
title: '标签',
key: 'tags',
width: 150, // 调整宽度
minWidth: 100,
resizable: true,
// 列筛选选项
filterOptions: tagsSelectOption.value,
@@ -452,17 +464,7 @@ function createColumns(): DataTableColumns<SongsInfo> {
// 使用 NSpace 渲染所有按钮
return h(NSpace, { justify: 'end', size: 8, wrap: false }, () => buttons); // 增加间距,禁止换行
},
// --- 动态计算宽度 --- START
/* width: (() => {
let calculatedWidth = 20; // 基础内边距
if (showLinkButton.value) calculatedWidth += 40; // 链接按钮宽度
if (showListenButton.value) calculatedWidth += 40; // 试听按钮宽度
if (props.isSelf) calculatedWidth += 80; // 编辑 + 删除按钮宽度
if (props.extraButton) calculatedWidth += 40; // 额外按钮预估宽度
return Math.max(calculatedWidth, props.isSelf ? 160 : 80); // 设置最小宽度防止太窄
})(), */
width: actionColumnWidth.value, // 使用计算属性
// --- 动态计算宽度 --- END
},
]
}
@@ -763,20 +765,24 @@ onMounted(() => {
item-style="display: flex; align-items: center;"
size="small"
>
<NSwitch
v-model:value="showListenButton"
size="small"
/>
<NText style="font-size: 12px;">
试听
</NText>
<NSwitch
v-model:value="showLinkButton"
size="small"
/>
<NText style="font-size: 12px;">
链接
</NText>
<template v-if="canShowListenSwitch">
<NSwitch
v-model:value="showListenButton"
size="small"
/>
<NText style="font-size: 12px;">
试听
</NText>
</template>
<template v-if="canShowLinkSwitch">
<NSwitch
v-model:value="showLinkButton"
size="small"
/>
<NText style="font-size: 12px;">
链接
</NText>
</template>
</NSpace>
</NSpace>
</NCard>

View File

@@ -239,6 +239,17 @@ const orderColumn: DataTableColumns<OrderType> = [
}, () => row.type === GoodsTypes.Physical ? '实体礼物' : '虚拟礼物')
},
},
{
title: '备注',
key: 'remark',
minWidth: 100,
render: (row: OrderType) => {
if (!row.remark) {
return h(NText, { depth: 3, italic: true }, () => '无')
}
return h(NEllipsis, { style: { maxWidth: '100px' } }, () => row.remark)
},
},
{
title: '地址',
key: 'address',
@@ -281,6 +292,7 @@ const orderColumn: DataTableColumns<OrderType> = [
{
title: '操作',
key: 'action',
fixed: 'right',
render: (row: OrderType) => {
return h(
NButton,
@@ -462,26 +474,41 @@ onMounted(() => {
trigger="none"
>
<div class="order-detail-content">
<NDivider style="margin-top: 0">
礼物快照
<NTooltip>
<template #trigger>
<NIcon :component="Info24Filled" />
</template>
兑换成功时生成的礼物快照, 即使主播对礼物内容进行了修改这个地方也不会变化
</NTooltip>
</NDivider>
<NFlex justify="center">
<PointGoodsItem
v-if="currentGoods"
class="goods-item"
:goods="currentGoods"
/>
</NFlex>
<!-- 移动并修改备注信息 -->
<template v-if="orderDetail.remark">
<NAlert
title="备注信息"
type="info"
style="margin-top: 16px; margin-bottom: 16px;"
closable
>
<template #icon>
<NIcon :component="Info24Filled" />
</template>
<NText>{{ orderDetail.remark }}</NText>
</NAlert>
</template>
<!-- 用户视图 -->
<template v-if="orderDetail.instanceOf === 'user'">
<NDivider style="margin-top: 0">
礼物快照
<NTooltip>
<template #trigger>
<NIcon :component="Info24Filled" />
</template>
兑换成功时生成的礼物快照, 即使主播对礼物内容进行了修改这个地方也不会变化
</NTooltip>
</NDivider>
<NFlex justify="center">
<PointGoodsItem
v-if="currentGoods"
class="goods-item"
:goods="currentGoods"
/>
</NFlex>
<!-- 虚拟礼物内容 -->
<template v-if="orderDetail.type === GoodsTypes.Virtual">
<NDivider>虚拟礼物内容</NDivider>
@@ -531,14 +558,6 @@ onMounted(() => {
<!-- 主播视图 -->
<template v-else-if="orderDetail.instanceOf === 'owner'">
<NFlex justify="center">
<PointGoodsItem
v-if="currentGoods"
class="goods-item"
:goods="currentGoods"
/>
</NFlex>
<NDivider>订单状态管理</NDivider>
<!-- 虚拟礼物提示 -->

View File

@@ -121,7 +121,7 @@
{
label: () => h(RouterLink, { to: { name: 'user-checkin' } }, { default: () => '签到排行' }),
key: 'user-checkin', icon: renderIcon(CheckmarkCircle24Filled),
show: userInfo.value?.extra?.allowCheckInRanking
show: userInfo.value?.extra?.enableFunctions.includes(FunctionTypes.CheckInRanking)
},
].filter(option => option.show !== false) as MenuOption[]; // 过滤掉 show 为 false 的菜单项
}

View File

@@ -633,12 +633,15 @@
<NCheckbox :value="FunctionTypes.Schedule">
日程
</NCheckbox>
<NCheckbox :value="FunctionTypes.SongRequest">
<NCheckbox :value="FunctionTypes.LiveRequest">
点歌
</NCheckbox>
<NCheckbox :value="FunctionTypes.Queue">
排队
</NCheckbox>
<NCheckbox :value="FunctionTypes.CheckInRanking">
签到排行
</NCheckbox>
</NCheckboxGroup>
<NDivider> 通知 </NDivider>

View File

@@ -188,6 +188,7 @@ function exportData() {
礼物总价: s.point,
快递公司: s.expressCompany,
快递单号: s.trackingNumber,
备注: s.remark ?? '',
创建时间: format(s.createAt, 'yyyy-MM-dd HH:mm:ss'),
更新时间: s.updateAt ? format(s.updateAt, 'yyyy-MM-dd HH:mm:ss') : '未更新',
}

View File

@@ -54,7 +54,6 @@ const defaultSettingPoint: Setting_Point = {
maxBonusPoints: 0,
allowSelfCheckIn: false,
requireAuth: false,
allowCheckInRanking: false
}
// 响应式设置对象

View File

@@ -76,12 +76,12 @@ const props = defineProps<{
async function onUpdateFunctionEnable() {
if (accountInfo.value.id) {
const oldValue = JSON.parse(JSON.stringify(accountInfo.value.settings.enableFunctions))
if (accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.SongRequest)) {
if (accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.LiveRequest)) {
accountInfo.value.settings.enableFunctions = accountInfo.value.settings.enableFunctions.filter(
(f) => f != FunctionTypes.SongRequest,
(f) => f != FunctionTypes.LiveRequest,
)
} else {
accountInfo.value.settings.enableFunctions.push(FunctionTypes.SongRequest)
accountInfo.value.settings.enableFunctions.push(FunctionTypes.LiveRequest)
}
if (!accountInfo.value.settings.songRequest.orderPrefix) {
accountInfo.value.settings.songRequest.orderPrefix = songRequest.defaultPrefix
@@ -90,20 +90,20 @@ async function onUpdateFunctionEnable() {
.then((data) => {
if (data.code == 200) {
message.success(
`${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.SongRequest) ? '启用' : '禁用'}点播功能`,
`${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.LiveRequest) ? '启用' : '禁用'}点播功能`,
)
} else {
if (accountInfo.value.id) {
accountInfo.value.settings.enableFunctions = oldValue
}
message.error(
`点播功能${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.SongRequest) ? '启用' : '禁用'}失败: ${data.message}`,
`点播功能${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.LiveRequest) ? '启用' : '禁用'}失败: ${data.message}`,
)
}
})
.catch((err) => {
message.error(
`点播功能${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.SongRequest) ? '启用' : '禁用'}失败: ${err}`,
`点播功能${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.LiveRequest) ? '启用' : '禁用'}失败: ${err}`,
)
})
}
@@ -158,11 +158,11 @@ onUnmounted(() => {
<template>
<NAlert
v-if="accountInfo.id"
:type="accountInfo.settings.enableFunctions.includes(FunctionTypes.SongRequest) ? 'success' : 'warning'"
:type="accountInfo.settings.enableFunctions.includes(FunctionTypes.LiveRequest) ? 'success' : 'warning'"
>
启用弹幕点播功能
<NSwitch
:value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.SongRequest)"
:value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.LiveRequest)"
@update:value="onUpdateFunctionEnable"
/>
@@ -215,7 +215,7 @@ onUnmounted(() => {
<br>
<NCard>
<NTabs
v-if="!accountInfo || accountInfo.settings.enableFunctions.includes(FunctionTypes.SongRequest)"
v-if="!accountInfo || accountInfo.settings.enableFunctions.includes(FunctionTypes.LiveRequest)"
animated
display-directive="show:lazy"
>

View File

@@ -24,7 +24,7 @@ const message = useMessage()
const enableSongRequest = computed({
get: () => {
return accountInfo.value?.settings?.enableFunctions?.includes(FunctionTypes.SongRequest) || false
return accountInfo.value?.settings?.enableFunctions?.includes(FunctionTypes.LiveRequest) || false
},
set: async () => {
await updateEnableFunctions()
@@ -35,12 +35,12 @@ const enableSongRequest = computed({
async function updateEnableFunctions() {
if (accountInfo.value.id) {
const oldValue = JSON.parse(JSON.stringify(accountInfo.value.settings.enableFunctions))
if (accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.SongRequest)) {
if (accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.LiveRequest)) {
accountInfo.value.settings.enableFunctions = accountInfo.value.settings.enableFunctions.filter(
(f: number) => f != FunctionTypes.SongRequest,
(f: number) => f != FunctionTypes.LiveRequest,
)
} else {
accountInfo.value.settings.enableFunctions.push(FunctionTypes.SongRequest)
accountInfo.value.settings.enableFunctions.push(FunctionTypes.LiveRequest)
}
if (!accountInfo.value.settings.songRequest.orderPrefix) {
accountInfo.value.settings.songRequest.orderPrefix = liveRequest.defaultPrefix
@@ -49,20 +49,20 @@ async function updateEnableFunctions() {
.then((data) => {
if (data.code == 200) {
message.success(
`${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.SongRequest) ? '启用' : '禁用'}点播功能`,
`${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.LiveRequest) ? '启用' : '禁用'}点播功能`,
)
} else {
if (accountInfo.value.id) {
accountInfo.value.settings.enableFunctions = oldValue
}
message.error(
`点播功能${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.SongRequest) ? '启用' : '禁用'}失败: ${data.message}`,
`点播功能${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.LiveRequest) ? '启用' : '禁用'}失败: ${data.message}`,
)
}
})
.catch((err) => {
message.error(
`点播功能${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.SongRequest) ? '启用' : '禁用'}失败: ${err}`,
`点播功能${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.LiveRequest) ? '启用' : '禁用'}失败: ${err}`,
)
})
}

View File

@@ -62,6 +62,7 @@ const showAddressSelect = ref(false)
const currentGoods = ref<ResponsePointGoodModel>() // 当前选中的礼物
const buyCount = ref(1) // 购买数量
const selectedAddress = ref<AddressInfo>() // 选中的地址
const remark = ref('') // 新增:用于存储用户备注
// 筛选相关状态
const selectedTag = ref<string>() // 选中的标签
@@ -217,6 +218,7 @@ function resetBuyModalState() {
selectedAddress.value = undefined
buyCount.value = 1
currentGoods.value = undefined
remark.value = '' // 新增:重置备注
}
// 处理模态框显示状态变化
@@ -260,6 +262,7 @@ async function buyGoods() {
goodsId: currentGoods.value?.id,
count: buyCount.value,
addressId: selectedAddress.value?.id ?? null, // 如果地址未选择,则传 null
remark: remark.value, // 新增:将备注添加到请求中
})
if (data.code === 200) {
@@ -638,7 +641,7 @@ onMounted(async () => {
/>
<!-- 兑换选项 (仅对实物或需要数量选择的礼物显示) -->
<template v-if="currentGoods.type === GoodsTypes.Physical || (currentGoods.maxBuyCount ?? 1) > 1">
<template v-if="currentGoods.type === GoodsTypes.Physical || (currentGoods.maxBuyCount ?? 1) > 1 || true">
<NDivider style="margin-top: 12px; margin-bottom: 12px;">
兑换选项
</NDivider>
@@ -689,6 +692,17 @@ onMounted(async () => {
管理地址
</NButton>
</NFormItem>
<!-- 备注输入 -->
<NFormItem label="备注">
<NInput
v-model:value="remark"
type="textarea"
placeholder="可以在这里留下备注信息(可选)"
:autosize="{ minRows: 2, maxRows: 4 }"
maxlength="100"
show-count
/>
</NFormItem>
</NForm>
</template>

View File

@@ -153,7 +153,7 @@ function loadMore() {
clearable
/>
<NDivider />
<LiveRequestOBS v-if="userInfo?.extra?.enableFunctions.includes(FunctionTypes.SongRequest)" />
<LiveRequestOBS v-if="userInfo?.extra?.enableFunctions.includes(FunctionTypes.LiveRequest)" />
</NSpace>
</NCard>
<NEmpty