mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-07 02:46:55 +08:00
feat: 更新组件和配置,增强功能和用户体验, 添加签到功能
- 在 .editorconfig 中调整文件格式设置,统一代码风格。 - 在 default.d.ts 中为 naive-ui 添加 TabPaneSlots 接口声明,增强类型支持。 - 在多个组件中优化了模板和样式,提升用户交互体验。 - 在 ClientAutoAction.vue 中新增签到设置标签页,丰富功能选项。 - 在 Utils.ts 中增强 GUID 处理逻辑,增加输入验证和错误处理。 - 更新多个组件的逻辑,简化代码结构,提升可读性和维护性。
This commit is contained in:
155
src/client/components/autoaction/CheckInTemplateHelper.vue
Normal file
155
src/client/components/autoaction/CheckInTemplateHelper.vue
Normal file
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<div class="checkin-template-helper">
|
||||
<TemplateHelper :placeholders="checkInPlaceholders" />
|
||||
<NAlert
|
||||
type="info"
|
||||
:show-icon="false"
|
||||
style="margin-top: 8px;"
|
||||
>
|
||||
<template #header>
|
||||
<div class="alert-header">
|
||||
<NIcon
|
||||
:component="Info24Filled"
|
||||
style="margin-right: 4px;"
|
||||
/>
|
||||
签到模板可用变量列表
|
||||
</div>
|
||||
</template>
|
||||
<NDivider style="margin: 6px 0;" />
|
||||
<div class="placeholder-groups">
|
||||
<div class="placeholder-group">
|
||||
<div class="group-title">
|
||||
用户信息
|
||||
</div> <div class="placeholder-item">
|
||||
<code>{{user.name}}</code> - 用户名称
|
||||
</div>
|
||||
<div class="placeholder-item">
|
||||
<code>{{user.uid}}</code> - 用户ID
|
||||
</div>
|
||||
</div>
|
||||
<div class="placeholder-group">
|
||||
<div class="group-title">
|
||||
签到信息
|
||||
</div> <div class="placeholder-item">
|
||||
<code>{{checkin.points}}</code> - 基础签到积分
|
||||
</div>
|
||||
<div class="placeholder-item">
|
||||
<code>{{checkin.bonusPoints}}</code> - 早鸟额外积分 (普通签到为0)
|
||||
</div>
|
||||
<div class="placeholder-item">
|
||||
<code>{{checkin.totalPoints}}</code> - 总获得积分
|
||||
</div>
|
||||
<div class="placeholder-item">
|
||||
<code>{{checkin.isEarlyBird}}</code> - 是否是早鸟签到 (true/false)
|
||||
</div>
|
||||
<div class="placeholder-item">
|
||||
<code>{{checkin.cooldownSeconds}}</code> - 签到冷却时间(秒)
|
||||
</div>
|
||||
<div class="placeholder-item">
|
||||
<code>{{checkin.time}}</code> - 签到时间对象
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<NDivider style="margin: 6px 0;" />
|
||||
<div class="placeholder-example">
|
||||
<div class="example-title">
|
||||
示例模板:
|
||||
</div> <div class="example-item">
|
||||
普通签到: <code>{{user.name}} 签到成功!获得 {{checkin.totalPoints}} 积分。</code>
|
||||
</div>
|
||||
<div class="example-item">
|
||||
早鸟签到: <code>恭喜 {{user.name}} 完成早鸟签到!额外获得 {{checkin.bonusPoints}} 积分,共获得 {{checkin.totalPoints}} 积分!</code>
|
||||
</div>
|
||||
<div class="example-item">
|
||||
条件表达式: <code>{{js: checkin.isEarlyBird ? `恭喜 ${user.name} 获得早鸟奖励!` : `${user.name} 签到成功!`}} 获得 {{checkin.totalPoints}} 积分。</code>
|
||||
</div>
|
||||
</div>
|
||||
</NAlert>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { NAlert, NDivider, NIcon } from 'naive-ui';
|
||||
import { Info24Filled } from '@vicons/fluent';
|
||||
import TemplateHelper from './TemplateHelper.vue';
|
||||
|
||||
// 签到模板的特定占位符
|
||||
const checkInPlaceholders = [
|
||||
{ name: '{{user.name}}', description: '用户名称' },
|
||||
{ name: '{{user.uid}}', description: '用户ID' },
|
||||
{ name: '{{checkin.points}}', description: '基础签到积分' },
|
||||
{ name: '{{checkin.bonusPoints}}', description: '早鸟额外积分 (普通签到为0)' },
|
||||
{ name: '{{checkin.totalPoints}}', description: '总获得积分' },
|
||||
{ name: '{{checkin.isEarlyBird}}', description: '是否是早鸟签到 (true/false)' },
|
||||
{ name: '{{checkin.cooldownSeconds}}', description: '签到冷却时间(秒)' },
|
||||
{ name: '{{checkin.time}}', description: '签到时间对象' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.checkin-template-helper {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.alert-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.placeholder-groups {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.placeholder-group {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.group-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 6px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.placeholder-item {
|
||||
margin-bottom: 4px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.placeholder-item code {
|
||||
padding: 1px 4px;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.placeholder-example {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.example-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 6px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.example-item {
|
||||
margin-bottom: 4px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.example-item code {
|
||||
display: block;
|
||||
padding: 4px 8px;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 3px;
|
||||
margin-top: 2px;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -31,7 +31,7 @@ function handleTemplateUpdate(payload: { index: number, value: string }) {
|
||||
|
||||
<template>
|
||||
<TemplateEditor
|
||||
:action="props.action"
|
||||
:template="props.action"
|
||||
:template-index="0"
|
||||
:title="title"
|
||||
:description="description"
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { NButton, NCard, NDivider, NHighlight, NInput, NScrollbar, NSpace, NModal, useMessage, NTabs, NTabPane, NFlex, NAlert, NIcon, NCollapse, NCollapseItem, NBadge, NText } from 'naive-ui';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import TemplateHelper from './TemplateHelper.vue';
|
||||
import TemplateTester from './TemplateTester.vue';
|
||||
import { containsJsExpression, convertToJsExpressions, evaluateTemplateExpressions, extractJsExpressions, JS_EXPRESSION_REGEX } from '@/client/store/autoAction/expressionEvaluator';
|
||||
import { buildExecutionContext } from '@/client/store/autoAction/utils';
|
||||
import { AutoActionItem, TriggerType } from '@/client/store/autoAction/types';
|
||||
@@ -11,7 +10,7 @@ import { EventDataTypes, EventModel } from '@/api/api-models';
|
||||
import GraphemeSplitter from 'grapheme-splitter';
|
||||
|
||||
const props = defineProps({
|
||||
action: {
|
||||
template: {
|
||||
type: Object as () => AutoActionItem,
|
||||
required: true
|
||||
},
|
||||
@@ -26,6 +25,10 @@ const props = defineProps({
|
||||
checkLength: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
customTestContext: {
|
||||
type: Object,
|
||||
default: undefined
|
||||
}
|
||||
});
|
||||
|
||||
@@ -52,7 +55,7 @@ const mergedPlaceholders = computed(() => {
|
||||
|
||||
const specificPlaceholders: { name: string, description: string }[] = [];
|
||||
|
||||
switch (props.action.triggerType) {
|
||||
switch (props.template.triggerType) {
|
||||
case TriggerType.DANMAKU:
|
||||
specificPlaceholders.push(
|
||||
{ name: '{{message}}', description: '弹幕内容' },
|
||||
@@ -87,8 +90,27 @@ const mergedPlaceholders = computed(() => {
|
||||
return Array.from(new Map(finalPlaceholders.map(item => [item.name, item])).values());
|
||||
});
|
||||
|
||||
// 深度合并两个对象的辅助函数
|
||||
function deepMerge(target: any, source: any): any {
|
||||
if (!source) return target;
|
||||
if (!target) return source;
|
||||
|
||||
const result = { ...target };
|
||||
|
||||
Object.keys(source).forEach(key => {
|
||||
if (typeof source[key] === 'object' && source[key] !== null && typeof target[key] === 'object' && target[key] !== null) {
|
||||
result[key] = deepMerge(target[key], source[key]);
|
||||
} else if (source[key] !== undefined) {
|
||||
result[key] = source[key];
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const testContext = computed(() => {
|
||||
return buildExecutionContext({
|
||||
// 创建默认上下文
|
||||
const defaultContext = buildExecutionContext({
|
||||
msg: '测试',
|
||||
time: 1713542400,
|
||||
num: 1,
|
||||
@@ -105,8 +127,14 @@ const testContext = computed(() => {
|
||||
fans_medal_wearing_status: true,
|
||||
guard_level_name: '测试舰队',
|
||||
guard_level_price: 100,
|
||||
}, undefined, props.template.triggerType);
|
||||
|
||||
}, undefined, props.action.triggerType);
|
||||
// 如果有自定义上下文,将其与默认上下文合并
|
||||
if (props.customTestContext) {
|
||||
return deepMerge(defaultContext, props.customTestContext);
|
||||
}
|
||||
|
||||
return defaultContext;
|
||||
});
|
||||
|
||||
const message = useMessage();
|
||||
@@ -120,13 +148,13 @@ function countGraphemes(value: string) {
|
||||
}
|
||||
|
||||
function convertPlaceholders() {
|
||||
if (!props.action.template) {
|
||||
if (!props.template.template) {
|
||||
message.warning('请先输入模板内容');
|
||||
return;
|
||||
}
|
||||
const converted = convertToJsExpressions(props.action.template, mergedPlaceholders.value);
|
||||
if (converted !== props.action.template) {
|
||||
props.action.template = converted;
|
||||
const converted = convertToJsExpressions(props.template.template, mergedPlaceholders.value);
|
||||
if (converted !== props.template.template) {
|
||||
props.template.template = converted;
|
||||
message.success('已转换占位符为表达式格式');
|
||||
} else {
|
||||
message.info('模板中没有需要转换的占位符');
|
||||
@@ -139,7 +167,7 @@ function hasJsExpression(template: string): boolean {
|
||||
|
||||
const highlightPatterns = computed(() => {
|
||||
const simplePlaceholders = mergedPlaceholders.value.map(p => p.name);
|
||||
const jsExpressionsInTemplate = extractJsExpressions(props.action.template || '');
|
||||
const jsExpressionsInTemplate = extractJsExpressions(props.template.template || '');
|
||||
const allPatterns = [...new Set([...simplePlaceholders, ...jsExpressionsInTemplate])];
|
||||
return allPatterns;
|
||||
});
|
||||
@@ -148,7 +176,20 @@ const MAX_LENGTH = 20;
|
||||
const WARNING_THRESHOLD = 16;
|
||||
|
||||
function evaluateTemplateForUI(template: string): string {
|
||||
const executionContext = buildExecutionContext(testContext.value.event, undefined, props.action.triggerType);
|
||||
// 深度合并默认上下文和自定义上下文
|
||||
const executionContext = buildExecutionContext(testContext.value.event, undefined, props.template.triggerType);
|
||||
|
||||
// 如果有自定义上下文,将其深度合并到执行上下文中
|
||||
if (props.customTestContext) {
|
||||
Object.keys(props.customTestContext).forEach(key => {
|
||||
if (typeof props.customTestContext?.[key] === 'object' && props.customTestContext[key] !== null) {
|
||||
executionContext.variables[key] = deepMerge(executionContext.variables[key] || {}, props.customTestContext[key]);
|
||||
} else {
|
||||
executionContext.variables[key] = props.customTestContext?.[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
return evaluateTemplateExpressions(template, executionContext);
|
||||
} catch (error) {
|
||||
@@ -158,8 +199,8 @@ function evaluateTemplateForUI(template: string): string {
|
||||
}
|
||||
|
||||
const evaluatedTemplateResult = computed(() => {
|
||||
if (!props.action.template || !showLivePreview.value) return '';
|
||||
return evaluateTemplateForUI(props.action.template);
|
||||
if (!props.template.template || !showLivePreview.value) return '';
|
||||
return evaluateTemplateForUI(props.template.template);
|
||||
});
|
||||
|
||||
const previewResult = computed(() => {
|
||||
@@ -167,7 +208,7 @@ const previewResult = computed(() => {
|
||||
});
|
||||
|
||||
const lengthStatus = computed(() => {
|
||||
if (!props.action.template || !props.checkLength || !showLivePreview.value) {
|
||||
if (!props.template.template || !props.checkLength || !showLivePreview.value) {
|
||||
return { status: 'normal' as const, message: '' };
|
||||
}
|
||||
try {
|
||||
@@ -230,7 +271,7 @@ const templateExamples = [
|
||||
];
|
||||
|
||||
function insertExample(template: string) {
|
||||
props.action.template = template;
|
||||
props.template.template = template;
|
||||
message.success('已插入示例模板');
|
||||
}
|
||||
</script>
|
||||
@@ -286,7 +327,7 @@ function insertExample(template: string) {
|
||||
|
||||
<!-- 当前模板预览 -->
|
||||
<NInput
|
||||
v-model:value="action.template"
|
||||
v-model:value="template.template"
|
||||
type="textarea"
|
||||
placeholder="输入模板内容... 使用 {{变量名}} 插入变量, {{js: 表达式}} 执行JS"
|
||||
:autosize="{ minRows: 3, maxRows: 6 }"
|
||||
@@ -352,15 +393,6 @@ function insertExample(template: string) {
|
||||
>
|
||||
占位符转表达式
|
||||
</NButton>
|
||||
|
||||
<NButton
|
||||
type="primary"
|
||||
size="small"
|
||||
class="btn-with-transition"
|
||||
@click="activeTab = 'test'"
|
||||
>
|
||||
测试模板
|
||||
</NButton>
|
||||
</NFlex>
|
||||
|
||||
<!-- 模板示例 -->
|
||||
@@ -403,17 +435,6 @@ function insertExample(template: string) {
|
||||
</NCollapse>
|
||||
</NFlex>
|
||||
</NTabPane>
|
||||
|
||||
<NTabPane
|
||||
name="test"
|
||||
tab="测试"
|
||||
>
|
||||
<TemplateTester
|
||||
:default-template="action.template"
|
||||
:context="testContext"
|
||||
:placeholders="mergedPlaceholders"
|
||||
/>
|
||||
</NTabPane>
|
||||
</NTabs>
|
||||
|
||||
<!-- 新增 Modal 组件 -->
|
||||
|
||||
398
src/client/components/autoaction/settings/CheckInSettings.vue
Normal file
398
src/client/components/autoaction/settings/CheckInSettings.vue
Normal file
@@ -0,0 +1,398 @@
|
||||
<template>
|
||||
<NCard
|
||||
v-if="config"
|
||||
title="弹幕签到设置"
|
||||
size="small"
|
||||
>
|
||||
<NTabs
|
||||
type="line"
|
||||
animated
|
||||
>
|
||||
<NTabPane
|
||||
name="settings"
|
||||
tab="签到设置"
|
||||
>
|
||||
<NForm
|
||||
label-placement="left"
|
||||
label-width="auto"
|
||||
>
|
||||
<NFormItem label="启用签到功能">
|
||||
<NSwitch v-model:value="config.enabled" />
|
||||
</NFormItem>
|
||||
|
||||
<template v-if="config.enabled">
|
||||
<NFormItem label="签到指令">
|
||||
<NInput
|
||||
v-model:value="config.command"
|
||||
placeholder="例如:签到"
|
||||
/>
|
||||
<template #feedback>
|
||||
观众发送此指令触发签到。
|
||||
</template>
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="仅在直播时可签到">
|
||||
<NSwitch v-model:value="config.onlyDuringLive" />
|
||||
<template #feedback>
|
||||
启用后,仅在直播进行中才能签到,否则任何时候都可以签到。
|
||||
</template>
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="发送签到回复">
|
||||
<NSwitch v-model:value="config.sendReply" />
|
||||
<template #feedback>
|
||||
启用后,签到成功或重复签到时会发送弹幕回复,关闭则只显示通知不发送弹幕。
|
||||
</template>
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="签到成功获得积分">
|
||||
<NInputNumber
|
||||
v-model:value="config.points"
|
||||
:min="0"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="用户签到冷却时间 (秒)">
|
||||
<NInputNumber
|
||||
v-model:value="config.cooldownSeconds"
|
||||
:min="0"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<template #feedback>
|
||||
每个用户在指定秒数内只能签到一次。
|
||||
</template>
|
||||
</NFormItem>
|
||||
|
||||
<NDivider title-placement="left">
|
||||
回复消息设置
|
||||
</NDivider>
|
||||
|
||||
<!-- 签到模板帮助信息组件 -->
|
||||
<div style="margin-bottom: 12px">
|
||||
<TemplateHelper :placeholders="checkInPlaceholders" />
|
||||
<NAlert
|
||||
type="info"
|
||||
:show-icon="false"
|
||||
style="margin-top: 8px"
|
||||
>
|
||||
<template #header>
|
||||
<div
|
||||
style="display: flex; align-items: center; font-weight: bold"
|
||||
>
|
||||
<NIcon
|
||||
:component="Info24Filled"
|
||||
style="margin-right: 4px"
|
||||
/>
|
||||
签到模板可用变量列表
|
||||
</div>
|
||||
</template>
|
||||
</NAlert>
|
||||
</div>
|
||||
|
||||
<TemplateEditor
|
||||
v-model:template="config.successAction"
|
||||
title="签到成功回复模板"
|
||||
:custom-test-context="checkInTestContext"
|
||||
/>
|
||||
<TemplateEditor
|
||||
v-model:template="config.cooldownAction"
|
||||
title="冷却中回复模板"
|
||||
:custom-test-context="checkInTestContext"
|
||||
/>
|
||||
|
||||
<NDivider title-placement="left">
|
||||
早鸟奖励设置
|
||||
</NDivider>
|
||||
|
||||
<NFormItem label="启用早鸟奖励">
|
||||
<NSwitch v-model:value="config.earlyBird.enabled" />
|
||||
<template #feedback>
|
||||
在直播开始后的一段时间内签到可获得额外奖励。
|
||||
</template>
|
||||
</NFormItem>
|
||||
|
||||
<template v-if="config.earlyBird.enabled">
|
||||
<NFormItem label="早鸟时间窗口 (分钟)">
|
||||
<NInputNumber
|
||||
v-model:value="config.earlyBird.windowMinutes"
|
||||
:min="1"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<template #feedback>
|
||||
直播开始后多少分钟内视为早鸟。
|
||||
</template>
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="早鸟额外奖励积分">
|
||||
<NInputNumber
|
||||
v-model:value="config.earlyBird.bonusPoints"
|
||||
:min="0"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<template #feedback>
|
||||
成功触发早鸟签到的用户额外获得的积分。
|
||||
</template>
|
||||
</NFormItem> <TemplateEditor
|
||||
v-model:template="config.earlyBird.successAction"
|
||||
title="早鸟成功回复模板"
|
||||
description="用户成功触发早鸟奖励时发送的回复消息,可用变量: {{user.name}}, {{checkin.bonusPoints}}, {{checkin.totalPoints}}, {{checkin.userPoints}}"
|
||||
:custom-test-context="checkInTestContext"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</NForm>
|
||||
</NTabPane>
|
||||
|
||||
<NTabPane
|
||||
name="userStats"
|
||||
tab="用户签到情况"
|
||||
>
|
||||
<div class="checkin-stats">
|
||||
<NSpace vertical>
|
||||
<NAlert type="info">
|
||||
以下显示用户的签到统计信息。包括累计签到次数、连续签到天数和早鸟签到次数等。
|
||||
</NAlert>
|
||||
|
||||
<NDataTable
|
||||
:columns="userStatsColumns"
|
||||
:data="userStatsData"
|
||||
:pagination="{ pageSize: 10 }"
|
||||
:bordered="false"
|
||||
striped
|
||||
/>
|
||||
|
||||
<NEmpty
|
||||
v-if="!userStatsData.length"
|
||||
description="暂无用户签到数据"
|
||||
/>
|
||||
</NSpace>
|
||||
</div>
|
||||
</NTabPane>
|
||||
|
||||
<NTabPane
|
||||
name="testCheckIn"
|
||||
tab="测试签到"
|
||||
>
|
||||
<div class="test-checkin">
|
||||
<NSpace vertical>
|
||||
<NAlert type="info">
|
||||
在此可以模拟用户签到,测试签到功能是否正常工作。
|
||||
</NAlert>
|
||||
|
||||
<NForm>
|
||||
<NFormItem label="用户UID">
|
||||
<NInputNumber
|
||||
v-model:value="testUid"
|
||||
:min="1"
|
||||
style="width: 100%"
|
||||
placeholder="输入用户数字ID"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="用户名">
|
||||
<NInput
|
||||
v-model:value="testUsername"
|
||||
placeholder="输入用户名,默认为'测试用户'"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem>
|
||||
<NButton
|
||||
type="primary"
|
||||
:disabled="!testUid || !config?.enabled"
|
||||
@click="handleTestCheckIn"
|
||||
>
|
||||
模拟签到
|
||||
</NButton>
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
|
||||
<NDivider title-placement="left">
|
||||
测试结果
|
||||
</NDivider>
|
||||
|
||||
<NCard
|
||||
v-if="testResult"
|
||||
size="small"
|
||||
:title="testResult.success ? '签到成功' : '签到失败'"
|
||||
>
|
||||
<NText>{{ testResult.message }}</NText>
|
||||
</NCard>
|
||||
</NSpace>
|
||||
</div>
|
||||
</NTabPane>
|
||||
</NTabs>
|
||||
|
||||
<NText
|
||||
:depth="3"
|
||||
style="font-size: 12px; margin-top: 15px; display: block"
|
||||
>
|
||||
提示:签到成功发送的回复消息会遵循全局的弹幕发送设置(如频率限制、弹幕长度等)。
|
||||
</NText>
|
||||
</NCard>
|
||||
<NCard
|
||||
v-else
|
||||
title="加载中..."
|
||||
size="small"
|
||||
>
|
||||
<NText>正在加载签到设置...</NText>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { NCard, NForm, NFormItem, NSwitch, NInput, NInputNumber, NSpace, NText, NDivider, NAlert, NIcon, NTabs, NTabPane, NDataTable, NEmpty, NButton } from 'naive-ui';
|
||||
import { useAutoAction } from '@/client/store/useAutoAction';
|
||||
import TemplateEditor from '../TemplateEditor.vue';
|
||||
import TemplateHelper from '../TemplateHelper.vue';
|
||||
import { TriggerType, ActionType, Priority, RuntimeState } from '@/client/store/autoAction/types';
|
||||
import { EventModel, EventDataTypes } from '@/api/api-models';
|
||||
import { Info24Filled } from '@vicons/fluent';
|
||||
import { computed, h, ref } from 'vue';
|
||||
import type { UserCheckInData } from '@/client/store/autoAction/modules/checkin';
|
||||
|
||||
const autoActionStore = useAutoAction();
|
||||
const config = autoActionStore.checkInModule.checkInConfig;
|
||||
const checkInStorage = autoActionStore.checkInModule.checkInStorage;
|
||||
|
||||
// 签到模板的特定占位符
|
||||
const checkInPlaceholders = [
|
||||
{ name: '{{checkin.points}}', description: '基础签到积分' },
|
||||
{ name: '{{checkin.bonusPoints}}', description: '早鸟额外积分 (普通签到为0)' },
|
||||
{ name: '{{checkin.totalPoints}}', description: '本次总获得积分' },
|
||||
{ name: '{{checkin.userPoints}}', description: '用户当前积分' },
|
||||
{ name: '{{checkin.isEarlyBird}}', description: '是否是早鸟签到 (true/false)' },
|
||||
{ name: '{{checkin.cooldownSeconds}}', description: '签到冷却时间(秒)' },
|
||||
{ name: '{{checkin.time}}', description: '签到时间对象' }
|
||||
];
|
||||
|
||||
// 为签到模板自定义的测试上下文
|
||||
const checkInTestContext = computed(() => {
|
||||
if (!config) return undefined;
|
||||
|
||||
return {
|
||||
checkin: {
|
||||
points: config.points || 0,
|
||||
bonusPoints: config.earlyBird.enabled ? config.earlyBird.bonusPoints : 0,
|
||||
totalPoints: (config.points || 0) + (config.earlyBird.enabled ? config.earlyBird.bonusPoints : 0),
|
||||
userPoints: 1000, // 模拟用户当前积分
|
||||
isEarlyBird: false,
|
||||
cooldownSeconds: config.cooldownSeconds || 0,
|
||||
time: new Date()
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// 用户签到数据表格列定义
|
||||
const userStatsColumns = [
|
||||
{
|
||||
title: '用户ID',
|
||||
key: 'uid'
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
key: 'username'
|
||||
},
|
||||
{
|
||||
title: '首次签到时间',
|
||||
key: 'firstCheckInTime',
|
||||
render(row: UserCheckInData) {
|
||||
return h('span', {}, new Date(row.firstCheckInTime).toLocaleString());
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '最近签到时间',
|
||||
key: 'lastCheckInTime',
|
||||
sorter: true,
|
||||
defaultSortOrder: 'descend' as const,
|
||||
render(row: UserCheckInData) {
|
||||
return h('span', {}, new Date(row.lastCheckInTime).toLocaleString());
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '累计签到',
|
||||
key: 'totalCheckins',
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '连续签到',
|
||||
key: 'streakDays',
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '早鸟签到次数',
|
||||
key: 'earlyBirdCount',
|
||||
sorter: true
|
||||
}
|
||||
];
|
||||
|
||||
// 转换用户签到数据为表格可用格式
|
||||
const userStatsData = computed<UserCheckInData[]>(() => {
|
||||
if (!checkInStorage?.users) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 将对象转换为数组
|
||||
return Object.values(checkInStorage.users);
|
||||
});
|
||||
|
||||
// 测试签到功能
|
||||
const testUid = ref<number>();
|
||||
const testUsername = ref<string>('测试用户');
|
||||
const testResult = ref<{ success: boolean; message: string }>();
|
||||
|
||||
// 处理测试签到
|
||||
async function handleTestCheckIn() {
|
||||
if (!testUid.value || !config?.enabled) {
|
||||
testResult.value = {
|
||||
success: false,
|
||||
message: '请输入有效的UID或确保签到功能已启用'
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建唯一标识符ouid,基于用户输入的uid
|
||||
const userOuid = testUid.value.toString();
|
||||
|
||||
// 创建模拟的事件对象
|
||||
const mockEvent: EventModel = {
|
||||
type: EventDataTypes.Message,
|
||||
uname: testUsername.value || '测试用户',
|
||||
uface: '',
|
||||
uid: testUid.value,
|
||||
open_id: '',
|
||||
msg: config.command,
|
||||
time: Date.now(),
|
||||
num: 0,
|
||||
price: 0,
|
||||
guard_level: 0,
|
||||
fans_medal_level: 0,
|
||||
fans_medal_name: '',
|
||||
fans_medal_wearing_status: false,
|
||||
ouid: userOuid
|
||||
};
|
||||
|
||||
// 创建模拟的运行时状态
|
||||
const mockRuntimeState: RuntimeState = {
|
||||
lastExecutionTime: {},
|
||||
aggregatedEvents: {},
|
||||
scheduledTimers: {},
|
||||
timerStartTimes: {},
|
||||
globalTimerStartTime: null,
|
||||
sentGuardPms: new Set<number>()
|
||||
};
|
||||
|
||||
// 处理签到请求
|
||||
await autoActionStore.checkInModule.processCheckIn(mockEvent, mockRuntimeState);
|
||||
|
||||
testResult.value = {
|
||||
success: true,
|
||||
message: `已为用户 ${testUsername.value || '测试用户'}(UID: ${testUid.value}) 模拟签到操作,请查看用户签到情况选项卡确认结果`
|
||||
};
|
||||
} catch (error) {
|
||||
testResult.value = {
|
||||
success: false,
|
||||
message: `签到操作失败: ${error instanceof Error ? error.message : String(error)}`
|
||||
};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -56,7 +56,7 @@ function handleTemplateUpdate(payload: { index: number, value: string }) {
|
||||
appear
|
||||
>
|
||||
<TemplateEditor
|
||||
:action="props.action"
|
||||
:template="props.action"
|
||||
:template-index="0"
|
||||
:title="templateTitle"
|
||||
:description="templateDescription"
|
||||
|
||||
Reference in New Issue
Block a user