mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-07 02:46:55 +08:00
chore: format code style and update linting configuration
This commit is contained in:
@@ -1,68 +1,69 @@
|
||||
<script setup lang="ts">
|
||||
import { NSpace, NSwitch, NInputNumber, NInput, NCollapseItem, NCollapse } from 'naive-ui';
|
||||
import { AutoActionItem, TriggerType } from '@/client/store/useAutoAction';
|
||||
import { computed } from 'vue';
|
||||
import { createDefaultAutoAction } from '@/client/store/autoAction/utils';
|
||||
import type { AutoActionItem } from '@/client/store/useAutoAction'
|
||||
import { NCollapse, NCollapseItem, NInput, NInputNumber, NSpace, NSwitch } from 'naive-ui'
|
||||
import { computed } from 'vue'
|
||||
import { createDefaultAutoAction } from '@/client/store/autoAction/utils'
|
||||
import { TriggerType } from '@/client/store/useAutoAction'
|
||||
|
||||
const props = defineProps({
|
||||
action: {
|
||||
type: Object as () => AutoActionItem,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
// 根据触发类型判断是否显示用户过滤选项
|
||||
const showUserFilter = computed(() => {
|
||||
return ![TriggerType.SCHEDULED].includes(props.action.triggerType);
|
||||
});
|
||||
return ![TriggerType.SCHEDULED].includes(props.action.triggerType)
|
||||
})
|
||||
|
||||
// 获取默认配置作为比较基准
|
||||
const defaultAction = computed(() => createDefaultAutoAction(props.action.triggerType));
|
||||
const defaultAction = computed(() => createDefaultAutoAction(props.action.triggerType))
|
||||
|
||||
// 检查设置项是否被修改
|
||||
const isModified = (path: string, value: any) => {
|
||||
const pathParts = path.split('.');
|
||||
let defaultValue: any = defaultAction.value;
|
||||
let currentValue: any = props.action;
|
||||
function isModified(path: string, value: any) {
|
||||
const pathParts = path.split('.')
|
||||
let defaultValue: any = defaultAction.value
|
||||
let currentValue: any = props.action
|
||||
|
||||
// 遍历路径获取值
|
||||
for (const part of pathParts) {
|
||||
defaultValue = defaultValue && typeof defaultValue === 'object' ? defaultValue[part as keyof typeof defaultValue] : undefined;
|
||||
currentValue = currentValue && typeof currentValue === 'object' ? currentValue[part as keyof typeof currentValue] : undefined;
|
||||
defaultValue = defaultValue && typeof defaultValue === 'object' ? defaultValue[part as keyof typeof defaultValue] : undefined
|
||||
currentValue = currentValue && typeof currentValue === 'object' ? currentValue[part as keyof typeof currentValue] : undefined
|
||||
}
|
||||
|
||||
// 处理特殊情况,如果指定了具体值进行比较
|
||||
if (value !== undefined) {
|
||||
return value !== defaultValue;
|
||||
return value !== defaultValue
|
||||
}
|
||||
|
||||
return currentValue !== defaultValue;
|
||||
};
|
||||
return currentValue !== defaultValue
|
||||
}
|
||||
|
||||
// 检查用户过滤区域是否有修改
|
||||
const userFilterModified = computed(() => {
|
||||
if (!showUserFilter.value) return false;
|
||||
return isModified('triggerConfig.userFilterEnabled', props.action.triggerConfig.userFilterEnabled) ||
|
||||
isModified('triggerConfig.requireMedal', props.action.triggerConfig.requireMedal) ||
|
||||
isModified('triggerConfig.requireCaptain', props.action.triggerConfig.requireCaptain);
|
||||
});
|
||||
if (!showUserFilter.value) return false
|
||||
return isModified('triggerConfig.userFilterEnabled', props.action.triggerConfig.userFilterEnabled)
|
||||
|| isModified('triggerConfig.requireMedal', props.action.triggerConfig.requireMedal)
|
||||
|| isModified('triggerConfig.requireCaptain', props.action.triggerConfig.requireCaptain)
|
||||
})
|
||||
|
||||
// 检查冷却控制区域是否有修改
|
||||
const cooldownModified = computed(() => {
|
||||
return isModified('ignoreCooldown', props.action.ignoreCooldown) ||
|
||||
isModified('actionConfig.delaySeconds', props.action.actionConfig.delaySeconds) ||
|
||||
isModified('actionConfig.cooldownSeconds', props.action.actionConfig.cooldownSeconds);
|
||||
});
|
||||
return isModified('ignoreCooldown', props.action.ignoreCooldown)
|
||||
|| isModified('actionConfig.delaySeconds', props.action.actionConfig.delaySeconds)
|
||||
|| isModified('actionConfig.cooldownSeconds', props.action.actionConfig.cooldownSeconds)
|
||||
})
|
||||
|
||||
// 检查逻辑表达式是否有修改
|
||||
const logicalExpressionModified = computed(() => {
|
||||
return isModified('logicalExpression', props.action.logicalExpression);
|
||||
});
|
||||
return isModified('logicalExpression', props.action.logicalExpression)
|
||||
})
|
||||
|
||||
// 检查自定义JS是否有修改
|
||||
const customJsModified = computed(() => {
|
||||
return isModified('executeCommand', props.action.executeCommand);
|
||||
});
|
||||
return isModified('executeCommand', props.action.executeCommand)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -73,7 +74,7 @@ const customJsModified = computed(() => {
|
||||
:title="userFilterModified ? '用户过滤 *' : '用户过滤'"
|
||||
:title-extra="userFilterModified ? '已修改' : ''"
|
||||
class="settings-section"
|
||||
:class="{'section-modified': userFilterModified}"
|
||||
:class="{ 'section-modified': userFilterModified }"
|
||||
>
|
||||
<div>
|
||||
<NSpace
|
||||
@@ -85,7 +86,7 @@ const customJsModified = computed(() => {
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
:class="['setting-item', {'setting-modified': isModified('triggerConfig.userFilterEnabled', action.triggerConfig.userFilterEnabled)}]"
|
||||
class="setting-item" :class="[{ 'setting-modified': isModified('triggerConfig.userFilterEnabled', action.triggerConfig.userFilterEnabled) }]"
|
||||
>
|
||||
<span>启用用户过滤:</span>
|
||||
<NSwitch v-model:value="action.triggerConfig.userFilterEnabled" />
|
||||
@@ -97,7 +98,7 @@ const customJsModified = computed(() => {
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
:class="['setting-item', {'setting-modified': isModified('triggerConfig.requireMedal', action.triggerConfig.requireMedal)}]"
|
||||
class="setting-item" :class="[{ 'setting-modified': isModified('triggerConfig.requireMedal', action.triggerConfig.requireMedal) }]"
|
||||
>
|
||||
<span>要求本房间勋章:</span>
|
||||
<NSwitch v-model:value="action.triggerConfig.requireMedal" />
|
||||
@@ -108,7 +109,7 @@ const customJsModified = computed(() => {
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
:class="['setting-item', {'setting-modified': isModified('triggerConfig.requireCaptain', action.triggerConfig.requireCaptain)}]"
|
||||
class="setting-item" :class="[{ 'setting-modified': isModified('triggerConfig.requireCaptain', action.triggerConfig.requireCaptain) }]"
|
||||
>
|
||||
<span>要求任意舰长:</span>
|
||||
<NSwitch v-model:value="action.triggerConfig.requireCaptain" />
|
||||
@@ -123,7 +124,7 @@ const customJsModified = computed(() => {
|
||||
:title="cooldownModified ? '冷却控制 *' : '冷却控制'"
|
||||
:title-extra="cooldownModified ? '已修改' : ''"
|
||||
class="settings-section"
|
||||
:class="{'section-modified': cooldownModified}"
|
||||
:class="{ 'section-modified': cooldownModified }"
|
||||
>
|
||||
<div>
|
||||
<NSpace
|
||||
@@ -131,7 +132,7 @@ const customJsModified = computed(() => {
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
:class="['setting-item', {'setting-modified': isModified('ignoreCooldown', action.ignoreCooldown)}]"
|
||||
class="setting-item" :class="[{ 'setting-modified': isModified('ignoreCooldown', action.ignoreCooldown) }]"
|
||||
>
|
||||
<span>忽略全局冷却:</span>
|
||||
<NSwitch v-model:value="action.ignoreCooldown" />
|
||||
@@ -142,7 +143,7 @@ const customJsModified = computed(() => {
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
:class="['setting-item', {'setting-modified': isModified('actionConfig.delaySeconds', action.actionConfig.delaySeconds)}]"
|
||||
class="setting-item" :class="[{ 'setting-modified': isModified('actionConfig.delaySeconds', action.actionConfig.delaySeconds) }]"
|
||||
>
|
||||
<span>延迟执行(秒):</span>
|
||||
<NInputNumber
|
||||
@@ -158,7 +159,7 @@ const customJsModified = computed(() => {
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
:class="['setting-item', {'setting-modified': isModified('actionConfig.cooldownSeconds', action.actionConfig.cooldownSeconds)}]"
|
||||
class="setting-item" :class="[{ 'setting-modified': isModified('actionConfig.cooldownSeconds', action.actionConfig.cooldownSeconds) }]"
|
||||
>
|
||||
<span>冷却时间(秒):</span>
|
||||
<NInputNumber
|
||||
@@ -176,7 +177,7 @@ const customJsModified = computed(() => {
|
||||
:title="logicalExpressionModified ? '逻辑条件表达式 *' : '逻辑条件表达式'"
|
||||
:title-extra="logicalExpressionModified ? '已修改' : ''"
|
||||
class="settings-section"
|
||||
:class="{'section-modified': logicalExpressionModified}"
|
||||
:class="{ 'section-modified': logicalExpressionModified }"
|
||||
>
|
||||
<div>
|
||||
<NSpace vertical>
|
||||
@@ -188,7 +189,7 @@ const customJsModified = computed(() => {
|
||||
type="textarea"
|
||||
placeholder="输入表达式,留空则始终为真"
|
||||
:autosize="{ minRows: 2, maxRows: 5 }"
|
||||
:class="{'input-modified': isModified('logicalExpression', action.logicalExpression)}"
|
||||
:class="{ 'input-modified': isModified('logicalExpression', action.logicalExpression) }"
|
||||
/>
|
||||
</NSpace>
|
||||
</div>
|
||||
@@ -199,7 +200,7 @@ const customJsModified = computed(() => {
|
||||
:title="customJsModified ? '自定义JS执行 *' : '自定义JS执行'"
|
||||
:title-extra="customJsModified ? '已修改' : ''"
|
||||
class="settings-section"
|
||||
:class="{'section-modified': customJsModified}"
|
||||
:class="{ 'section-modified': customJsModified }"
|
||||
>
|
||||
<div>
|
||||
<NSpace vertical>
|
||||
@@ -211,7 +212,7 @@ const customJsModified = computed(() => {
|
||||
type="textarea"
|
||||
placeholder="输入要执行的JS代码"
|
||||
:autosize="{ minRows: 3, maxRows: 8 }"
|
||||
:class="{'input-modified': isModified('executeCommand', action.executeCommand)}"
|
||||
:class="{ 'input-modified': isModified('executeCommand', action.executeCommand) }"
|
||||
/>
|
||||
</NSpace>
|
||||
</div>
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { NSpace, NInput, NSwitch, NSelect } from 'naive-ui';
|
||||
import { AutoActionItem, TriggerType, ActionType, Priority } from '@/client/store/useAutoAction';
|
||||
import type { AutoActionItem } from '@/client/store/useAutoAction'
|
||||
import { NInput, NSelect, NSpace, NSwitch } from 'naive-ui'
|
||||
import { ActionType, Priority, TriggerType } from '@/client/store/useAutoAction'
|
||||
|
||||
const props = defineProps({
|
||||
action: {
|
||||
type: Object as () => AutoActionItem,
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
hideName: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
hideEnabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
// 触发类型选项
|
||||
const triggerTypeOptions = [
|
||||
@@ -26,14 +27,14 @@ const triggerTypeOptions = [
|
||||
{ label: '入场欢迎', value: TriggerType.ENTER },
|
||||
{ label: '定时发送', value: TriggerType.SCHEDULED },
|
||||
{ label: 'SC感谢', value: TriggerType.SUPER_CHAT },
|
||||
];
|
||||
]
|
||||
|
||||
// 操作类型选项
|
||||
const actionTypeOptions = [
|
||||
{ label: '发送弹幕', value: ActionType.SEND_DANMAKU },
|
||||
{ label: '发送私信', value: ActionType.SEND_PRIVATE_MSG },
|
||||
{ label: '执行命令', value: ActionType.EXECUTE_COMMAND },
|
||||
];
|
||||
]
|
||||
|
||||
// 优先级选项
|
||||
const priorityOptions = [
|
||||
@@ -42,7 +43,7 @@ const priorityOptions = [
|
||||
{ label: '普通', value: Priority.NORMAL },
|
||||
{ label: '低', value: Priority.LOW },
|
||||
{ label: '最低', value: Priority.LOWEST },
|
||||
];
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,3 +1,425 @@
|
||||
<script lang="ts" setup>
|
||||
import type { DataTableColumns } from 'naive-ui'
|
||||
import type { CheckInRankingInfo, CheckInResult } from '@/api/api-models'
|
||||
import { Info24Filled } from '@vicons/fluent'
|
||||
import { NAlert, NButton, NCard, NDataTable, NDivider, NForm, NFormItem, NIcon, NInput, NInputGroup, NInputNumber, NPopconfirm, NSelect, NSpace, NSpin, NSwitch, NTabPane, NTabs, NText, NTime, NTooltip } from 'naive-ui'
|
||||
import { computed, h, onMounted, ref } from 'vue'
|
||||
import { SaveEnableFunctions, SaveSetting, useAccount } from '@/api/account'
|
||||
import { FunctionTypes } from '@/api/api-models'
|
||||
import { QueryGetAPI } from '@/api/query'
|
||||
import { useAutoAction } from '@/client/store/useAutoAction'
|
||||
import { CHECKIN_API_URL } from '@/data/constants'
|
||||
import AutoActionEditor from '../AutoActionEditor.vue'
|
||||
import TemplateHelper from '../TemplateHelper.vue'
|
||||
|
||||
interface LiveInfo {
|
||||
roomId?: number
|
||||
}
|
||||
|
||||
const autoActionStore = useAutoAction()
|
||||
const config = autoActionStore.checkInModule.checkInConfig
|
||||
const accountInfo = useAccount()
|
||||
const isLoading = ref(false)
|
||||
|
||||
const customTestContext = ref({
|
||||
checkin: {
|
||||
points: 0,
|
||||
consecutiveDays: 0,
|
||||
todayRank: 0,
|
||||
time: new Date(),
|
||||
},
|
||||
})
|
||||
|
||||
// 签到模板的特定占位符
|
||||
const checkInPlaceholders = [
|
||||
{ name: '{{checkin.points}}', description: '获得的总积分' },
|
||||
{ name: '{{checkin.consecutiveDays}}', description: '连续签到天数' },
|
||||
{ name: '{{checkin.todayRank}}', description: '今日签到排名' },
|
||||
{ name: '{{checkin.time}}', description: '签到时间对象' },
|
||||
]
|
||||
|
||||
// 服务端签到设置
|
||||
const serverSetting = computed(() => {
|
||||
return accountInfo.value?.settings?.point || {}
|
||||
})
|
||||
|
||||
// 是否可以编辑设置
|
||||
const canEdit = computed(() => {
|
||||
return accountInfo.value && accountInfo.value.settings && accountInfo.value.settings.point
|
||||
})
|
||||
|
||||
// 更新所有设置
|
||||
async function updateSettings() {
|
||||
// 先保存服务端设置
|
||||
const serverSaved = await updateServerSettings()
|
||||
|
||||
if (serverSaved) {
|
||||
window.$notification.success({
|
||||
title: '设置已保存',
|
||||
duration: 3000,
|
||||
})
|
||||
}
|
||||
|
||||
return serverSaved
|
||||
}
|
||||
|
||||
// 更新服务端签到设置
|
||||
async function updateServerSettings() {
|
||||
if (!canEdit.value) {
|
||||
return false
|
||||
}
|
||||
|
||||
isLoading.value = true
|
||||
|
||||
try {
|
||||
const msg = await SaveSetting('Point', accountInfo.value.settings.point)
|
||||
if (msg) {
|
||||
return true
|
||||
} else {
|
||||
window.$notification.error({
|
||||
title: '保存失败',
|
||||
content: msg,
|
||||
duration: 5000,
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
window.$notification.error({
|
||||
title: '保存失败',
|
||||
content: String(err),
|
||||
duration: 5000,
|
||||
})
|
||||
console.error('保存签到设置失败:', err)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// 排行榜数据
|
||||
const rankingData = ref<CheckInRankingInfo[]>([])
|
||||
const isLoadingRanking = ref(false)
|
||||
const timeRange = ref<string>('all')
|
||||
const userFilter = ref<string>('')
|
||||
const pagination = ref({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
})
|
||||
|
||||
// 时间段选项
|
||||
const timeRangeOptions = [
|
||||
{ label: '全部时间', value: 'all' },
|
||||
{ label: '今日', value: 'today' },
|
||||
{ label: '本周', value: 'week' },
|
||||
{ label: '本月', value: 'month' },
|
||||
{ label: '上个月', value: 'lastMonth' },
|
||||
]
|
||||
|
||||
// 过滤后的排行榜数据
|
||||
const filteredRankingData = computed(() => {
|
||||
let filtered = rankingData.value
|
||||
|
||||
// 按时间范围筛选
|
||||
if (timeRange.value !== 'all') {
|
||||
const now = new Date()
|
||||
let startTime: Date
|
||||
|
||||
if (timeRange.value === 'today') {
|
||||
// 今天凌晨
|
||||
startTime = new Date(now)
|
||||
startTime.setHours(0, 0, 0, 0)
|
||||
} else if (timeRange.value === 'week') {
|
||||
// 本周一
|
||||
const dayOfWeek = now.getDay() || 7 // 把周日作为7处理
|
||||
startTime = new Date(now)
|
||||
startTime.setDate(now.getDate() - (dayOfWeek - 1))
|
||||
startTime.setHours(0, 0, 0, 0)
|
||||
} else if (timeRange.value === 'month') {
|
||||
// 本月1号
|
||||
startTime = new Date(now.getFullYear(), now.getMonth(), 1)
|
||||
} else if (timeRange.value === 'lastMonth') {
|
||||
// 上月1号
|
||||
startTime = new Date(now.getFullYear(), now.getMonth() - 1, 1)
|
||||
// 本月1号作为结束时间
|
||||
const endTime = new Date(now.getFullYear(), now.getMonth(), 1)
|
||||
filtered = filtered.filter((user) => {
|
||||
const checkInTime = new Date(user.lastCheckInTime)
|
||||
return checkInTime >= startTime && checkInTime < endTime
|
||||
})
|
||||
// 已经筛选完成,不需要再次筛选
|
||||
startTime = new Date(0)
|
||||
}
|
||||
|
||||
// 如果不是上个月,用通用筛选逻辑
|
||||
if (timeRange.value !== 'lastMonth') {
|
||||
filtered = filtered.filter((user) => {
|
||||
const checkInTime = new Date(user.lastCheckInTime)
|
||||
return checkInTime >= startTime
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 按用户名筛选
|
||||
if (userFilter.value) {
|
||||
const keyword = userFilter.value.toLowerCase()
|
||||
filtered = filtered.filter(user =>
|
||||
user.name.toLowerCase().includes(keyword),
|
||||
)
|
||||
}
|
||||
|
||||
return filtered
|
||||
})
|
||||
|
||||
// 排行榜列定义
|
||||
const rankingColumns: DataTableColumns<CheckInRankingInfo> = [
|
||||
{
|
||||
title: '排名',
|
||||
key: 'rank',
|
||||
render: (row: CheckInRankingInfo, index: number) => h('span', {}, index + 1),
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '连续签到天数',
|
||||
key: 'consecutiveDays',
|
||||
sorter: 'default',
|
||||
},
|
||||
{
|
||||
title: '积分',
|
||||
key: 'points',
|
||||
sorter: 'default',
|
||||
},
|
||||
{
|
||||
title: '最近签到时间',
|
||||
key: 'lastCheckInTime',
|
||||
render(row: CheckInRankingInfo) {
|
||||
return h(NTooltip, {
|
||||
}, {
|
||||
trigger: () => h(NTime, {
|
||||
time: row.lastCheckInTime,
|
||||
type: 'relative',
|
||||
}),
|
||||
default: () => new Date(row.lastCheckInTime).toLocaleString(),
|
||||
})
|
||||
},
|
||||
sorter: 'default',
|
||||
},
|
||||
{
|
||||
title: '已认证',
|
||||
key: 'isAuthed',
|
||||
render(row: CheckInRankingInfo) {
|
||||
return h('span', {}, row.isAuthed ? '是' : '否')
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
render(row: CheckInRankingInfo) {
|
||||
return h(
|
||||
NPopconfirm,
|
||||
{
|
||||
onPositiveClick: () => resetUserCheckInByGuid(row.ouId),
|
||||
},
|
||||
{
|
||||
trigger: () => h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
type: 'warning',
|
||||
disabled: isResetting.value,
|
||||
loading: isResetting.value && resetTargetId.value === row.ouId,
|
||||
onClick: e => e.stopPropagation(),
|
||||
},
|
||||
{ default: () => '重置签到' },
|
||||
),
|
||||
default: () => '确定要重置该用户的所有签到数据吗?此操作不可撤销。',
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
// 加载签到排行榜数据
|
||||
async function loadCheckInRanking() {
|
||||
if (isLoadingRanking.value) return
|
||||
|
||||
isLoadingRanking.value = true
|
||||
try {
|
||||
// 获取所有用户数据,不再根据时间范围过滤
|
||||
const response = await QueryGetAPI<CheckInRankingInfo[]>(`${CHECKIN_API_URL}admin/users`)
|
||||
|
||||
if (response.code == 200) {
|
||||
rankingData.value = response.data
|
||||
pagination.value.page = 1 // 重置为第一页
|
||||
} else {
|
||||
rankingData.value = []
|
||||
window.$message.error(`获取签到排行榜失败: ${response.message}`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载签到排行榜失败:', error)
|
||||
window.$notification.error({
|
||||
title: '加载失败',
|
||||
content: '无法加载签到排行榜数据',
|
||||
duration: 5000,
|
||||
})
|
||||
rankingData.value = []
|
||||
} finally {
|
||||
isLoadingRanking.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 重置签到数据相关
|
||||
const isResetting = ref(false)
|
||||
const resetTargetId = ref<string>()
|
||||
|
||||
// 重置单个用户签到数据
|
||||
async function resetUserCheckInByGuid(ouId: string) {
|
||||
if (!ouId || isResetting.value) return
|
||||
|
||||
isResetting.value = true
|
||||
resetTargetId.value = ouId
|
||||
|
||||
try {
|
||||
const response = await QueryGetAPI(`${CHECKIN_API_URL}admin/reset`, {
|
||||
ouId,
|
||||
})
|
||||
|
||||
if (response && response.code === 200) {
|
||||
window.$notification.success({
|
||||
title: '重置成功',
|
||||
content: '用户签到数据已重置',
|
||||
duration: 3000,
|
||||
})
|
||||
|
||||
// 重置成功后重新加载排行榜
|
||||
await loadCheckInRanking()
|
||||
} else {
|
||||
window.$notification.error({
|
||||
title: '重置失败',
|
||||
content: response?.message || '无法重置用户签到数据',
|
||||
duration: 5000,
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('重置用户签到数据失败:', error)
|
||||
window.$notification.error({
|
||||
title: '重置失败',
|
||||
content: '重置用户签到数据时发生错误',
|
||||
duration: 5000,
|
||||
})
|
||||
} finally {
|
||||
isResetting.value = false
|
||||
resetTargetId.value = undefined
|
||||
}
|
||||
}
|
||||
|
||||
// 重置所有用户签到数据
|
||||
async function resetAllCheckIn() {
|
||||
if (isResetting.value) return
|
||||
|
||||
isResetting.value = true
|
||||
try {
|
||||
const response = await QueryGetAPI(`${CHECKIN_API_URL}admin/reset`, {})
|
||||
|
||||
if (response && response.code === 200) {
|
||||
window.$notification.success({
|
||||
title: '重置成功',
|
||||
content: '所有用户的签到数据已重置',
|
||||
duration: 3000,
|
||||
})
|
||||
|
||||
// 重置成功后重新加载排行榜
|
||||
await loadCheckInRanking()
|
||||
} else {
|
||||
window.$notification.error({
|
||||
title: '重置失败',
|
||||
content: response?.message || '无法重置所有用户签到数据',
|
||||
duration: 5000,
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('重置所有用户签到数据失败:', error)
|
||||
window.$notification.error({
|
||||
title: '重置失败',
|
||||
content: '重置所有用户签到数据时发生错误',
|
||||
duration: 5000,
|
||||
})
|
||||
} finally {
|
||||
isResetting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 测试签到功能
|
||||
const testUid = ref<number>()
|
||||
const testUsername = ref<string>('测试用户')
|
||||
const testResult = ref<{ success: boolean, message: string }>()
|
||||
|
||||
// 处理测试签到
|
||||
async function handleTestCheckIn() {
|
||||
if (!testUid.value || !serverSetting.value.enableCheckIn) {
|
||||
testResult.value = {
|
||||
success: false,
|
||||
message: '请输入有效的UID或确保签到功能已启用',
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 直接调用服务端签到API
|
||||
const response = await QueryGetAPI<CheckInResult>(`${CHECKIN_API_URL}check-in-for`, {
|
||||
uId: testUid.value,
|
||||
name: testUsername.value || '测试用户',
|
||||
})
|
||||
|
||||
if (response.code === 200 && response.data) {
|
||||
const result = response.data
|
||||
|
||||
testResult.value = {
|
||||
success: result.success,
|
||||
message: result.success
|
||||
? `签到成功!用户 ${testUsername.value || '测试用户'} 获得 ${result.points} 积分,连续签到 ${result.consecutiveDays} 天`
|
||||
: result.message || '签到失败,可能今天已经签到过了',
|
||||
}
|
||||
|
||||
// 显示通知
|
||||
window.$notification[result.success ? 'success' : 'info']({
|
||||
title: result.success ? '测试签到成功' : '测试签到失败',
|
||||
content: testResult.value.message,
|
||||
duration: 3000,
|
||||
})
|
||||
} else {
|
||||
testResult.value = {
|
||||
success: false,
|
||||
message: `API返回错误: ${response.message || '未知错误'}`,
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
testResult.value = {
|
||||
success: false,
|
||||
message: `签到操作失败: ${error instanceof Error ? error.message : String(error)}`,
|
||||
}
|
||||
|
||||
// 显示错误通知
|
||||
window.$notification.error({
|
||||
title: '测试签到失败',
|
||||
content: testResult.value.message,
|
||||
duration: 5000,
|
||||
})
|
||||
}
|
||||
}
|
||||
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(() => {
|
||||
loadCheckInRanking()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard
|
||||
v-if="config"
|
||||
@@ -24,7 +446,7 @@
|
||||
label-placement="left"
|
||||
:label-width="120"
|
||||
:style="{
|
||||
maxWidth: '650px'
|
||||
maxWidth: '650px',
|
||||
}"
|
||||
>
|
||||
<!-- 服务端签到设置 -->
|
||||
@@ -272,7 +694,7 @@
|
||||
showSizePicker: true,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
onChange: (page: number) => pagination.page = page,
|
||||
onUpdatePageSize: (pageSize: number) => pagination.pageSize = pageSize
|
||||
onUpdatePageSize: (pageSize: number) => pagination.pageSize = pageSize,
|
||||
}"
|
||||
:bordered="false"
|
||||
:loading="isLoadingRanking"
|
||||
@@ -380,428 +802,6 @@
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
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';
|
||||
import { GuidUtils } from '@/Utils';
|
||||
import { Info24Filled } from '@vicons/fluent';
|
||||
import type { DataTableColumns } from 'naive-ui';
|
||||
import { NAlert, NButton, NCard, NDataTable, NDivider, NEmpty, NForm, NFormItem, NIcon, NInput, NInputGroup, NInputNumber, NPopconfirm, NSelect, NSpace, NSpin, NSwitch, NTabPane, NTabs, NText, NTime, NTooltip } from 'naive-ui';
|
||||
import { computed, h, onMounted, ref, watch } from 'vue';
|
||||
import AutoActionEditor from '../AutoActionEditor.vue';
|
||||
import TemplateHelper from '../TemplateHelper.vue';
|
||||
|
||||
interface LiveInfo {
|
||||
roomId?: number;
|
||||
}
|
||||
|
||||
const autoActionStore = useAutoAction();
|
||||
const config = autoActionStore.checkInModule.checkInConfig;
|
||||
const accountInfo = useAccount();
|
||||
const isLoading = ref(false);
|
||||
|
||||
const customTestContext = ref({
|
||||
checkin: {
|
||||
points: 0,
|
||||
consecutiveDays: 0,
|
||||
todayRank: 0,
|
||||
time: new Date()
|
||||
}
|
||||
});
|
||||
|
||||
// 签到模板的特定占位符
|
||||
const checkInPlaceholders = [
|
||||
{ name: '{{checkin.points}}', description: '获得的总积分' },
|
||||
{ name: '{{checkin.consecutiveDays}}', description: '连续签到天数' },
|
||||
{ name: '{{checkin.todayRank}}', description: '今日签到排名' },
|
||||
{ name: '{{checkin.time}}', description: '签到时间对象' }
|
||||
];
|
||||
|
||||
// 服务端签到设置
|
||||
const serverSetting = computed(() => {
|
||||
return accountInfo.value?.settings?.point || {};
|
||||
});
|
||||
|
||||
// 是否可以编辑设置
|
||||
const canEdit = computed(() => {
|
||||
return accountInfo.value && accountInfo.value.settings && accountInfo.value.settings.point;
|
||||
});
|
||||
|
||||
// 更新所有设置
|
||||
async function updateSettings() {
|
||||
// 先保存服务端设置
|
||||
const serverSaved = await updateServerSettings();
|
||||
|
||||
if (serverSaved) {
|
||||
window.$notification.success({
|
||||
title: '设置已保存',
|
||||
duration: 3000
|
||||
});
|
||||
}
|
||||
|
||||
return serverSaved;
|
||||
}
|
||||
|
||||
// 更新服务端签到设置
|
||||
async function updateServerSettings() {
|
||||
if (!canEdit.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
isLoading.value = true;
|
||||
|
||||
try {
|
||||
const msg = await SaveSetting('Point', accountInfo.value.settings.point);
|
||||
if (msg) {
|
||||
return true;
|
||||
} else {
|
||||
window.$notification.error({
|
||||
title: '保存失败',
|
||||
content: msg,
|
||||
duration: 5000
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
window.$notification.error({
|
||||
title: '保存失败',
|
||||
content: String(err),
|
||||
duration: 5000
|
||||
});
|
||||
console.error('保存签到设置失败:', err);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 排行榜数据
|
||||
const rankingData = ref<CheckInRankingInfo[]>([]);
|
||||
const isLoadingRanking = ref(false);
|
||||
const timeRange = ref<string>('all');
|
||||
const userFilter = ref<string>('');
|
||||
const pagination = ref({
|
||||
page: 1,
|
||||
pageSize: 10
|
||||
});
|
||||
|
||||
// 时间段选项
|
||||
const timeRangeOptions = [
|
||||
{ label: '全部时间', value: 'all' },
|
||||
{ label: '今日', value: 'today' },
|
||||
{ label: '本周', value: 'week' },
|
||||
{ label: '本月', value: 'month' },
|
||||
{ label: '上个月', value: 'lastMonth' },
|
||||
];
|
||||
|
||||
// 过滤后的排行榜数据
|
||||
const filteredRankingData = computed(() => {
|
||||
let filtered = rankingData.value;
|
||||
|
||||
// 按时间范围筛选
|
||||
if (timeRange.value !== 'all') {
|
||||
const now = new Date();
|
||||
let startTime: Date;
|
||||
|
||||
if (timeRange.value === 'today') {
|
||||
// 今天凌晨
|
||||
startTime = new Date(now);
|
||||
startTime.setHours(0, 0, 0, 0);
|
||||
} else if (timeRange.value === 'week') {
|
||||
// 本周一
|
||||
const dayOfWeek = now.getDay() || 7; // 把周日作为7处理
|
||||
startTime = new Date(now);
|
||||
startTime.setDate(now.getDate() - (dayOfWeek - 1));
|
||||
startTime.setHours(0, 0, 0, 0);
|
||||
} else if (timeRange.value === 'month') {
|
||||
// 本月1号
|
||||
startTime = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
} else if (timeRange.value === 'lastMonth') {
|
||||
// 上月1号
|
||||
startTime = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||
// 本月1号作为结束时间
|
||||
const endTime = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
filtered = filtered.filter(user => {
|
||||
const checkInTime = new Date(user.lastCheckInTime);
|
||||
return checkInTime >= startTime && checkInTime < endTime;
|
||||
});
|
||||
// 已经筛选完成,不需要再次筛选
|
||||
startTime = new Date(0);
|
||||
}
|
||||
|
||||
// 如果不是上个月,用通用筛选逻辑
|
||||
if (timeRange.value !== 'lastMonth') {
|
||||
filtered = filtered.filter(user => {
|
||||
const checkInTime = new Date(user.lastCheckInTime);
|
||||
return checkInTime >= startTime;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 按用户名筛选
|
||||
if (userFilter.value) {
|
||||
const keyword = userFilter.value.toLowerCase();
|
||||
filtered = filtered.filter(user =>
|
||||
user.name.toLowerCase().includes(keyword)
|
||||
);
|
||||
}
|
||||
|
||||
return filtered;
|
||||
});
|
||||
|
||||
// 排行榜列定义
|
||||
const rankingColumns: DataTableColumns<CheckInRankingInfo> = [
|
||||
{
|
||||
title: '排名',
|
||||
key: 'rank',
|
||||
render: (row: CheckInRankingInfo, index: number) => h('span', {}, index + 1)
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
key: 'name'
|
||||
},
|
||||
{
|
||||
title: '连续签到天数',
|
||||
key: 'consecutiveDays',
|
||||
sorter: 'default'
|
||||
},
|
||||
{
|
||||
title: '积分',
|
||||
key: 'points',
|
||||
sorter: 'default'
|
||||
},
|
||||
{
|
||||
title: '最近签到时间',
|
||||
key: 'lastCheckInTime',
|
||||
render(row: CheckInRankingInfo) {
|
||||
return h(NTooltip, {
|
||||
}, {
|
||||
trigger: () => h(NTime, {
|
||||
time: row.lastCheckInTime,
|
||||
type: 'relative'
|
||||
}),
|
||||
default: () => new Date(row.lastCheckInTime).toLocaleString()
|
||||
});
|
||||
},
|
||||
sorter: 'default'
|
||||
},
|
||||
{
|
||||
title: '已认证',
|
||||
key: 'isAuthed',
|
||||
render(row: CheckInRankingInfo) {
|
||||
return h('span', {}, row.isAuthed ? '是' : '否');
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
render(row: CheckInRankingInfo) {
|
||||
return h(
|
||||
NPopconfirm,
|
||||
{
|
||||
onPositiveClick: () => resetUserCheckInByGuid(row.ouId)
|
||||
},
|
||||
{
|
||||
trigger: () => h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
type: 'warning',
|
||||
disabled: isResetting.value,
|
||||
loading: isResetting.value && resetTargetId.value === row.ouId,
|
||||
onClick: (e) => e.stopPropagation()
|
||||
},
|
||||
{ default: () => '重置签到' }
|
||||
),
|
||||
default: () => '确定要重置该用户的所有签到数据吗?此操作不可撤销。'
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
// 加载签到排行榜数据
|
||||
async function loadCheckInRanking() {
|
||||
if (isLoadingRanking.value) return;
|
||||
|
||||
isLoadingRanking.value = true;
|
||||
try {
|
||||
// 获取所有用户数据,不再根据时间范围过滤
|
||||
const response = await QueryGetAPI<CheckInRankingInfo[]>(`${CHECKIN_API_URL}admin/users`);
|
||||
|
||||
if (response.code == 200) {
|
||||
rankingData.value = response.data;
|
||||
pagination.value.page = 1; // 重置为第一页
|
||||
} else {
|
||||
rankingData.value = [];
|
||||
window.$message.error(`获取签到排行榜失败: ${response.message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载签到排行榜失败:', error);
|
||||
window.$notification.error({
|
||||
title: '加载失败',
|
||||
content: '无法加载签到排行榜数据',
|
||||
duration: 5000
|
||||
});
|
||||
rankingData.value = [];
|
||||
} finally {
|
||||
isLoadingRanking.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 重置签到数据相关
|
||||
const isResetting = ref(false);
|
||||
const resetTargetId = ref<string>();
|
||||
|
||||
// 重置单个用户签到数据
|
||||
async function resetUserCheckInByGuid(ouId: string) {
|
||||
if (!ouId || isResetting.value) return;
|
||||
|
||||
isResetting.value = true;
|
||||
resetTargetId.value = ouId;
|
||||
|
||||
try {
|
||||
const response = await QueryGetAPI(`${CHECKIN_API_URL}admin/reset`, {
|
||||
ouId: ouId
|
||||
});
|
||||
|
||||
if (response && response.code === 200) {
|
||||
window.$notification.success({
|
||||
title: '重置成功',
|
||||
content: '用户签到数据已重置',
|
||||
duration: 3000
|
||||
});
|
||||
|
||||
// 重置成功后重新加载排行榜
|
||||
await loadCheckInRanking();
|
||||
} else {
|
||||
window.$notification.error({
|
||||
title: '重置失败',
|
||||
content: response?.message || '无法重置用户签到数据',
|
||||
duration: 5000
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('重置用户签到数据失败:', error);
|
||||
window.$notification.error({
|
||||
title: '重置失败',
|
||||
content: '重置用户签到数据时发生错误',
|
||||
duration: 5000
|
||||
});
|
||||
} finally {
|
||||
isResetting.value = false;
|
||||
resetTargetId.value = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// 重置所有用户签到数据
|
||||
async function resetAllCheckIn() {
|
||||
if (isResetting.value) return;
|
||||
|
||||
isResetting.value = true;
|
||||
try {
|
||||
const response = await QueryGetAPI(`${CHECKIN_API_URL}admin/reset`, {});
|
||||
|
||||
if (response && response.code === 200) {
|
||||
window.$notification.success({
|
||||
title: '重置成功',
|
||||
content: '所有用户的签到数据已重置',
|
||||
duration: 3000
|
||||
});
|
||||
|
||||
// 重置成功后重新加载排行榜
|
||||
await loadCheckInRanking();
|
||||
} else {
|
||||
window.$notification.error({
|
||||
title: '重置失败',
|
||||
content: response?.message || '无法重置所有用户签到数据',
|
||||
duration: 5000
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('重置所有用户签到数据失败:', error);
|
||||
window.$notification.error({
|
||||
title: '重置失败',
|
||||
content: '重置所有用户签到数据时发生错误',
|
||||
duration: 5000
|
||||
});
|
||||
} finally {
|
||||
isResetting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试签到功能
|
||||
const testUid = ref<number>();
|
||||
const testUsername = ref<string>('测试用户');
|
||||
const testResult = ref<{ success: boolean; message: string }>();
|
||||
|
||||
// 处理测试签到
|
||||
async function handleTestCheckIn() {
|
||||
if (!testUid.value || !serverSetting.value.enableCheckIn) {
|
||||
testResult.value = {
|
||||
success: false,
|
||||
message: '请输入有效的UID或确保签到功能已启用'
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 直接调用服务端签到API
|
||||
const response = await QueryGetAPI<CheckInResult>(`${CHECKIN_API_URL}check-in-for`, {
|
||||
uId: testUid.value,
|
||||
name: testUsername.value || '测试用户'
|
||||
});
|
||||
|
||||
if (response.code === 200 && response.data) {
|
||||
const result = response.data;
|
||||
|
||||
testResult.value = {
|
||||
success: result.success,
|
||||
message: result.success
|
||||
? `签到成功!用户 ${testUsername.value || '测试用户'} 获得 ${result.points} 积分,连续签到 ${result.consecutiveDays} 天`
|
||||
: result.message || '签到失败,可能今天已经签到过了'
|
||||
};
|
||||
|
||||
// 显示通知
|
||||
window.$notification[result.success ? 'success' : 'info']({
|
||||
title: result.success ? '测试签到成功' : '测试签到失败',
|
||||
content: testResult.value.message,
|
||||
duration: 3000
|
||||
});
|
||||
} else {
|
||||
testResult.value = {
|
||||
success: false,
|
||||
message: `API返回错误: ${response.message || '未知错误'}`
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
testResult.value = {
|
||||
success: false,
|
||||
message: `签到操作失败: ${error instanceof Error ? error.message : String(error)}`
|
||||
};
|
||||
|
||||
// 显示错误通知
|
||||
window.$notification.error({
|
||||
title: '测试签到失败',
|
||||
content: testResult.value.message,
|
||||
duration: 5000
|
||||
});
|
||||
}
|
||||
}
|
||||
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(() => {
|
||||
loadCheckInRanking();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.settings-section {
|
||||
margin: 8px 0;
|
||||
@@ -810,4 +810,4 @@ onMounted(() => {
|
||||
.ranking-filter {
|
||||
margin: 10px 0;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,75 +1,75 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { NSpace, NInput, NButton, NTag, NDivider, NCollapseItem, useMessage, NRadioGroup, NRadioButton } from 'naive-ui';
|
||||
import { AutoActionItem, TriggerType } from '@/client/store/useAutoAction';
|
||||
import { KeywordMatchType } from '@/client/store/autoAction/types';
|
||||
import SingleTemplateEditor from '../SingleTemplateEditor.vue';
|
||||
import type { AutoActionItem } from '@/client/store/useAutoAction'
|
||||
import { NButton, NCollapseItem, NDivider, NInput, NRadioButton, NRadioGroup, NSpace, NTag, useMessage } from 'naive-ui'
|
||||
import { ref } from 'vue'
|
||||
import { KeywordMatchType } from '@/client/store/autoAction/types'
|
||||
import { TriggerType } from '@/client/store/useAutoAction'
|
||||
|
||||
const props = defineProps({
|
||||
action: {
|
||||
type: Object as () => AutoActionItem,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const message = useMessage();
|
||||
const message = useMessage()
|
||||
|
||||
// 弹幕关键词相关
|
||||
const tempKeyword = ref('');
|
||||
const tempBlockword = ref('');
|
||||
const tempKeyword = ref('')
|
||||
const tempBlockword = ref('')
|
||||
|
||||
// 初始化匹配类型配置
|
||||
if (!props.action.triggerConfig.keywordMatchType) {
|
||||
props.action.triggerConfig.keywordMatchType = KeywordMatchType.Contains;
|
||||
props.action.triggerConfig.keywordMatchType = KeywordMatchType.Contains
|
||||
}
|
||||
|
||||
if (!props.action.triggerConfig.blockwordMatchType) {
|
||||
props.action.triggerConfig.blockwordMatchType = KeywordMatchType.Contains;
|
||||
props.action.triggerConfig.blockwordMatchType = KeywordMatchType.Contains
|
||||
}
|
||||
|
||||
// 添加关键词
|
||||
function addKeyword() {
|
||||
if (!tempKeyword.value.trim()) return;
|
||||
if (!tempKeyword.value.trim()) return
|
||||
|
||||
if (!props.action.triggerConfig.keywords) {
|
||||
props.action.triggerConfig.keywords = [];
|
||||
props.action.triggerConfig.keywords = []
|
||||
}
|
||||
|
||||
if (!props.action.triggerConfig.keywords.includes(tempKeyword.value.trim())) {
|
||||
props.action.triggerConfig.keywords.push(tempKeyword.value.trim());
|
||||
tempKeyword.value = '';
|
||||
props.action.triggerConfig.keywords.push(tempKeyword.value.trim())
|
||||
tempKeyword.value = ''
|
||||
} else {
|
||||
message.warning('此关键词已存在');
|
||||
message.warning('此关键词已存在')
|
||||
}
|
||||
}
|
||||
|
||||
// 移除关键词
|
||||
function removeKeyword(index: number) {
|
||||
if (props.action.triggerConfig.keywords) {
|
||||
props.action.triggerConfig.keywords.splice(index, 1);
|
||||
props.action.triggerConfig.keywords.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// 添加屏蔽词
|
||||
function addBlockword() {
|
||||
if (!tempBlockword.value.trim()) return;
|
||||
if (!tempBlockword.value.trim()) return
|
||||
|
||||
if (!props.action.triggerConfig.blockwords) {
|
||||
props.action.triggerConfig.blockwords = [];
|
||||
props.action.triggerConfig.blockwords = []
|
||||
}
|
||||
|
||||
if (!props.action.triggerConfig.blockwords.includes(tempBlockword.value.trim())) {
|
||||
props.action.triggerConfig.blockwords.push(tempBlockword.value.trim());
|
||||
tempBlockword.value = '';
|
||||
props.action.triggerConfig.blockwords.push(tempBlockword.value.trim())
|
||||
tempBlockword.value = ''
|
||||
} else {
|
||||
message.warning('此屏蔽词已存在');
|
||||
message.warning('此屏蔽词已存在')
|
||||
}
|
||||
}
|
||||
|
||||
// 移除屏蔽词
|
||||
function removeBlockword(index: number) {
|
||||
if (props.action.triggerConfig.blockwords) {
|
||||
props.action.triggerConfig.blockwords.splice(index, 1);
|
||||
props.action.triggerConfig.blockwords.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ const danmakuPlaceholders = [
|
||||
{ name: '{{user.medalName}}', description: '粉丝勋章名称' },
|
||||
{ name: '{{message}}', description: '弹幕内容' },
|
||||
{ name: '{{js: message.substring(0, 10)}}', description: '弹幕内容的前10个字符' },
|
||||
];
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { NSpace, NSwitch, NInputNumber, NSelect, NCollapseItem } from 'naive-ui';
|
||||
import { AutoActionItem, TriggerType } from '@/client/store/useAutoAction';
|
||||
import type { AutoActionItem } from '@/client/store/useAutoAction'
|
||||
import { NCollapseItem, NInputNumber, NSelect, NSpace, NSwitch } from 'naive-ui'
|
||||
import { TriggerType } from '@/client/store/useAutoAction'
|
||||
|
||||
const props = defineProps({
|
||||
action: {
|
||||
type: Object as () => AutoActionItem,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
// 入场过滤模式选项
|
||||
const enterFilterModeOptions = [
|
||||
@@ -15,8 +16,8 @@ const enterFilterModeOptions = [
|
||||
{ label: '用户黑名单', value: 'blacklist' },
|
||||
{ label: '用户白名单', value: 'whitelist' },
|
||||
{ label: '仅舰长', value: 'guard' },
|
||||
{ label: '仅勋章', value: 'medal' }
|
||||
];
|
||||
{ label: '仅勋章', value: 'medal' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -62,4 +63,4 @@ const enterFilterModeOptions = [
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
</NCollapseItem>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { NSpace, NSwitch, NInputNumber, NCollapseItem } from 'naive-ui';
|
||||
import { AutoActionItem, TriggerType } from '@/client/store/useAutoAction';
|
||||
import type { AutoActionItem } from '@/client/store/useAutoAction'
|
||||
import { NCollapseItem, NInputNumber, NSpace, NSwitch } from 'naive-ui'
|
||||
import { TriggerType } from '@/client/store/useAutoAction'
|
||||
|
||||
const props = defineProps({
|
||||
action: {
|
||||
type: Object as () => AutoActionItem,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -40,4 +41,4 @@ const props = defineProps({
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
</NCollapseItem>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { NSpace, NInput, NButton, NTag, NSelect, NInputNumber, NSwitch, NCollapseItem, useMessage } from 'naive-ui';
|
||||
import { AutoActionItem, TriggerType } from '@/client/store/useAutoAction';
|
||||
import type { AutoActionItem } from '@/client/store/useAutoAction'
|
||||
import { NButton, NCollapseItem, NInput, NInputNumber, NSelect, NSpace, NSwitch, NTag, useMessage } from 'naive-ui'
|
||||
import { ref } from 'vue'
|
||||
import { TriggerType } from '@/client/store/useAutoAction'
|
||||
|
||||
const props = defineProps({
|
||||
action: {
|
||||
type: Object as () => AutoActionItem,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const message = useMessage();
|
||||
const message = useMessage()
|
||||
|
||||
// 礼物过滤模式选项
|
||||
const giftFilterModeOptions = [
|
||||
@@ -18,32 +19,32 @@ const giftFilterModeOptions = [
|
||||
{ label: '礼物黑名单', value: 'blacklist' },
|
||||
{ label: '礼物白名单', value: 'whitelist' },
|
||||
{ label: '最低价值', value: 'value' },
|
||||
{ label: '过滤免费礼物', value: 'free' }
|
||||
];
|
||||
{ label: '过滤免费礼物', value: 'free' },
|
||||
]
|
||||
|
||||
// 礼物名称相关
|
||||
const tempGiftName = ref('');
|
||||
const tempGiftName = ref('')
|
||||
|
||||
// 添加礼物名称到过滤列表
|
||||
function addGiftName() {
|
||||
if (!tempGiftName.value.trim()) return;
|
||||
if (!tempGiftName.value.trim()) return
|
||||
|
||||
if (!props.action.triggerConfig.filterGiftNames) {
|
||||
props.action.triggerConfig.filterGiftNames = [];
|
||||
props.action.triggerConfig.filterGiftNames = []
|
||||
}
|
||||
|
||||
if (!props.action.triggerConfig.filterGiftNames.includes(tempGiftName.value.trim())) {
|
||||
props.action.triggerConfig.filterGiftNames.push(tempGiftName.value.trim());
|
||||
tempGiftName.value = '';
|
||||
props.action.triggerConfig.filterGiftNames.push(tempGiftName.value.trim())
|
||||
tempGiftName.value = ''
|
||||
} else {
|
||||
message.warning('此礼物名称已存在');
|
||||
message.warning('此礼物名称已存在')
|
||||
}
|
||||
}
|
||||
|
||||
// 移除礼物名称
|
||||
function removeGiftName(index: number) {
|
||||
if (props.action.triggerConfig.filterGiftNames) {
|
||||
props.action.triggerConfig.filterGiftNames.splice(index, 1);
|
||||
props.action.triggerConfig.filterGiftNames.splice(index, 1)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,29 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
NCard,
|
||||
NSpace,
|
||||
NDivider,
|
||||
NInputNumber,
|
||||
NRadioGroup,
|
||||
NRadio,
|
||||
NRadioGroup,
|
||||
NSpace,
|
||||
NText,
|
||||
NDivider
|
||||
} from 'naive-ui';
|
||||
import { useAutoAction } from '@/client/store/useAutoAction';
|
||||
import { watch } from 'vue';
|
||||
} from 'naive-ui'
|
||||
import { watch } from 'vue'
|
||||
import { useAutoAction } from '@/client/store/useAutoAction'
|
||||
|
||||
const autoActionStore = useAutoAction();
|
||||
const autoActionStore = useAutoAction()
|
||||
|
||||
// 定时模式选项
|
||||
const schedulingModeOptions = [
|
||||
{ label: '随机模式', value: 'random' },
|
||||
{ label: '顺序模式', value: 'sequential' }
|
||||
];
|
||||
{ label: '顺序模式', value: 'sequential' },
|
||||
]
|
||||
|
||||
// 监听变化,触发定时器重启(如果间隔改变)
|
||||
watch(() => autoActionStore.globalIntervalSeconds, () => {
|
||||
autoActionStore.restartGlobalTimer(); // 确保间隔改变时定时器更新
|
||||
});
|
||||
|
||||
autoActionStore.restartGlobalTimer() // 确保间隔改变时定时器更新
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -82,4 +81,4 @@ watch(() => autoActionStore.globalIntervalSeconds, () => {
|
||||
|
||||
<style scoped>
|
||||
/* 可以添加一些特定样式 */
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,57 +1,58 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { NSpace, NInput, NButton, NTag, NSelect, NSwitch, NDivider, NCollapseItem, useMessage } from 'naive-ui';
|
||||
import { AutoActionItem, TriggerType, ActionType } from '@/client/store/useAutoAction';
|
||||
import type { AutoActionItem } from '@/client/store/useAutoAction'
|
||||
import { NButton, NCollapseItem, NDivider, NInput, NSelect, NSpace, NSwitch, NTag, useMessage } from 'naive-ui'
|
||||
import { ref } from 'vue'
|
||||
import { ActionType, TriggerType } from '@/client/store/useAutoAction'
|
||||
|
||||
const props = defineProps({
|
||||
action: {
|
||||
type: Object as () => AutoActionItem,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const message = useMessage();
|
||||
const message = useMessage()
|
||||
|
||||
// 舰长礼品码相关
|
||||
const tempGiftCodeLevel = ref(3); // 默认为舰长等级
|
||||
const tempGiftCode = ref('');
|
||||
const tempGiftCodeLevel = ref(3) // 默认为舰长等级
|
||||
const tempGiftCode = ref('')
|
||||
|
||||
// 添加礼品码
|
||||
function addGiftCode() {
|
||||
if (!tempGiftCode.value.trim()) return;
|
||||
if (!tempGiftCode.value.trim()) return
|
||||
|
||||
if (!props.action.triggerConfig.giftCodes) {
|
||||
props.action.triggerConfig.giftCodes = [];
|
||||
props.action.triggerConfig.giftCodes = []
|
||||
}
|
||||
|
||||
// 查找对应等级的礼品码数组
|
||||
let levelCodes = props.action.triggerConfig.giftCodes.find(gc => gc.level === tempGiftCodeLevel.value);
|
||||
let levelCodes = props.action.triggerConfig.giftCodes.find(gc => gc.level === tempGiftCodeLevel.value)
|
||||
|
||||
if (!levelCodes) {
|
||||
// 如果没有此等级的礼品码数组,创建一个
|
||||
levelCodes = { level: tempGiftCodeLevel.value, codes: [] };
|
||||
props.action.triggerConfig.giftCodes.push(levelCodes);
|
||||
levelCodes = { level: tempGiftCodeLevel.value, codes: [] }
|
||||
props.action.triggerConfig.giftCodes.push(levelCodes)
|
||||
}
|
||||
|
||||
// 添加礼品码
|
||||
if (!levelCodes.codes.includes(tempGiftCode.value.trim())) {
|
||||
levelCodes.codes.push(tempGiftCode.value.trim());
|
||||
tempGiftCode.value = '';
|
||||
levelCodes.codes.push(tempGiftCode.value.trim())
|
||||
tempGiftCode.value = ''
|
||||
} else {
|
||||
message.warning('此礼品码已存在');
|
||||
message.warning('此礼品码已存在')
|
||||
}
|
||||
}
|
||||
|
||||
// 移除礼品码
|
||||
function removeGiftCode(levelIndex: number, codeIndex: number) {
|
||||
if (props.action.triggerConfig.giftCodes &&
|
||||
props.action.triggerConfig.giftCodes[levelIndex] &&
|
||||
props.action.triggerConfig.giftCodes[levelIndex].codes) {
|
||||
props.action.triggerConfig.giftCodes[levelIndex].codes.splice(codeIndex, 1);
|
||||
if (props.action.triggerConfig.giftCodes
|
||||
&& props.action.triggerConfig.giftCodes[levelIndex]
|
||||
&& props.action.triggerConfig.giftCodes[levelIndex].codes) {
|
||||
props.action.triggerConfig.giftCodes[levelIndex].codes.splice(codeIndex, 1)
|
||||
|
||||
// 如果该等级没有礼品码了,移除这个等级
|
||||
if (props.action.triggerConfig.giftCodes[levelIndex].codes.length === 0) {
|
||||
props.action.triggerConfig.giftCodes.splice(levelIndex, 1);
|
||||
props.action.triggerConfig.giftCodes.splice(levelIndex, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,10 +60,10 @@ function removeGiftCode(levelIndex: number, codeIndex: number) {
|
||||
// 舰长等级名称映射
|
||||
function getGuardLevelName(level: number): string {
|
||||
switch (level) {
|
||||
case 1: return '总督';
|
||||
case 2: return '提督';
|
||||
case 3: return '舰长';
|
||||
default: return '通用';
|
||||
case 1: return '总督'
|
||||
case 2: return '提督'
|
||||
case 3: return '舰长'
|
||||
default: return '通用'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -95,7 +96,7 @@ function getGuardLevelName(level: number): string {
|
||||
{ label: '总督', value: 1 },
|
||||
{ label: '提督', value: 2 },
|
||||
{ label: '舰长', value: 3 },
|
||||
{ label: '通用', value: 0 }
|
||||
{ label: '通用', value: 0 },
|
||||
]"
|
||||
/>
|
||||
<NInput
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
import type { AutoActionItem } from '@/client/store/useAutoAction'
|
||||
import {
|
||||
NSpace,
|
||||
NInputNumber,
|
||||
NRadioGroup,
|
||||
NRadio,
|
||||
NCollapseItem,
|
||||
NSwitch,
|
||||
NDivider,
|
||||
NText
|
||||
} from 'naive-ui';
|
||||
import { AutoActionItem, TriggerType } from '@/client/store/useAutoAction';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
NInputNumber,
|
||||
NRadio,
|
||||
NRadioGroup,
|
||||
NSpace,
|
||||
NSwitch,
|
||||
NText,
|
||||
} from 'naive-ui'
|
||||
import { ref, watch } from 'vue'
|
||||
import { TriggerType } from '@/client/store/useAutoAction'
|
||||
|
||||
const props = defineProps({
|
||||
action: {
|
||||
type: Object as () => AutoActionItem,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
// 初始化配置项
|
||||
if (props.action.triggerConfig.useGlobalTimer === undefined) {
|
||||
props.action.triggerConfig.useGlobalTimer = false; // 默认不使用全局定时器
|
||||
props.action.triggerConfig.useGlobalTimer = false // 默认不使用全局定时器
|
||||
}
|
||||
if (props.action.triggerConfig.schedulingMode === undefined) {
|
||||
props.action.triggerConfig.schedulingMode = 'random'; // 默认随机模式
|
||||
props.action.triggerConfig.schedulingMode = 'random' // 默认随机模式
|
||||
}
|
||||
if (props.action.triggerConfig.intervalSeconds === undefined) {
|
||||
props.action.triggerConfig.intervalSeconds = 300; // 默认5分钟
|
||||
props.action.triggerConfig.intervalSeconds = 300 // 默认5分钟
|
||||
}
|
||||
|
||||
const useGlobalTimer = ref(props.action.triggerConfig.useGlobalTimer);
|
||||
const useGlobalTimer = ref(props.action.triggerConfig.useGlobalTimer)
|
||||
|
||||
// 同步到 action
|
||||
watch(useGlobalTimer, (value) => {
|
||||
props.action.triggerConfig.useGlobalTimer = value;
|
||||
});
|
||||
props.action.triggerConfig.useGlobalTimer = value
|
||||
})
|
||||
|
||||
// 定时模式选项
|
||||
const schedulingModeOptions = [
|
||||
{ label: '随机模式', value: 'random' },
|
||||
{ label: '顺序模式', value: 'sequential' }
|
||||
];
|
||||
|
||||
{ label: '顺序模式', value: 'sequential' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { NSpace, NSwitch, NInputNumber, NSelect, NCollapseItem } from 'naive-ui';
|
||||
import { AutoActionItem, TriggerType } from '@/client/store/useAutoAction';
|
||||
import type { AutoActionItem } from '@/client/store/useAutoAction'
|
||||
import { NCollapseItem, NInputNumber, NSelect, NSpace, NSwitch } from 'naive-ui'
|
||||
import { TriggerType } from '@/client/store/useAutoAction'
|
||||
|
||||
const props = defineProps({
|
||||
action: {
|
||||
type: Object as () => AutoActionItem,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
// SC过滤模式选项
|
||||
const scFilterModeOptions = [
|
||||
{ label: '不过滤', value: 'none' },
|
||||
{ label: '最低价格', value: 'price' }
|
||||
];
|
||||
{ label: '最低价格', value: 'price' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -74,4 +75,4 @@ const scFilterModeOptions = [
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
</NCollapseItem>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -1,54 +1,54 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { NDivider } from 'naive-ui';
|
||||
import TemplateEditor from '../TemplateEditor.vue';
|
||||
import { AutoActionItem, TriggerType, ActionType } from '@/client/store/useAutoAction';
|
||||
import type { AutoActionItem } from '@/client/store/useAutoAction'
|
||||
import { computed } from 'vue'
|
||||
import { ActionType } from '@/client/store/useAutoAction'
|
||||
import TemplateEditor from '../TemplateEditor.vue'
|
||||
|
||||
const props = defineProps({
|
||||
action: {
|
||||
type: Object as () => AutoActionItem,
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
customTestContext: {
|
||||
type: Object,
|
||||
default: undefined
|
||||
}
|
||||
});
|
||||
default: undefined,
|
||||
},
|
||||
})
|
||||
|
||||
// 根据操作类型获取模板标题
|
||||
const templateTitle = computed(() => {
|
||||
switch (props.action.actionType) {
|
||||
case ActionType.SEND_DANMAKU:
|
||||
return '弹幕模板';
|
||||
return '弹幕模板'
|
||||
case ActionType.SEND_PRIVATE_MSG:
|
||||
return '私信模板';
|
||||
return '私信模板'
|
||||
case ActionType.EXECUTE_COMMAND:
|
||||
return '命令模板';
|
||||
return '命令模板'
|
||||
default:
|
||||
return '消息模板';
|
||||
return '消息模板'
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// 根据操作类型获取模板描述
|
||||
const templateDescription = computed(() => {
|
||||
switch (props.action.actionType) {
|
||||
case ActionType.SEND_DANMAKU:
|
||||
return '发送到直播间的弹幕内容';
|
||||
return '发送到直播间的弹幕内容'
|
||||
case ActionType.SEND_PRIVATE_MSG:
|
||||
return '发送给用户的私信内容';
|
||||
return '发送给用户的私信内容'
|
||||
case ActionType.EXECUTE_COMMAND:
|
||||
return '执行的命令模板';
|
||||
return '执行的命令模板'
|
||||
default:
|
||||
return '消息内容模板';
|
||||
return '消息内容模板'
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// Handle template updates from TemplateEditor
|
||||
function handleTemplateUpdate(payload: { index: number, value: string }) {
|
||||
// Assuming index will always be 0 here as we only render one editor
|
||||
// And assuming action.templates is a string based on previous findings
|
||||
if (payload.index === 0) {
|
||||
props.action.template = payload.value;
|
||||
props.action.template = payload.value
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user