mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-07 02:46:55 +08:00
feat: 更新依赖项和配置,添加新通知类型
- 在 package.json 中添加了 @types/md5 和 @vueuse/integrations 依赖。 - 更新了 tsconfig.json 中的模块解析方式为 bundler。 - 在组件声明中移除了不再使用的 Naive UI 组件。 - 在弹幕窗口和设置中添加了启用动画的选项,并更新了相关样式。 - 实现了私信发送失败的通知功能,增强了用户体验。
This commit is contained in:
95
src/client/components/autoaction/AutoActionEditor.vue
Normal file
95
src/client/components/autoaction/AutoActionEditor.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<script setup lang="ts">
|
||||
import { NCard, NSpace, NCollapse, NDivider } from 'naive-ui';
|
||||
import { AutoActionItem, TriggerType } from '@/client/store/useAutoAction';
|
||||
|
||||
// 引入拆分的子组件
|
||||
import BasicSettings from './settings/BasicSettings.vue';
|
||||
import AdvancedSettings from './settings/AdvancedSettings.vue';
|
||||
import DanmakuSettings from './settings/DanmakuSettings.vue';
|
||||
import GiftSettings from './settings/GiftSettings.vue';
|
||||
import GuardSettings from './settings/GuardSettings.vue';
|
||||
import ScheduledSettings from './settings/ScheduledSettings.vue';
|
||||
import TemplateSettings from './settings/TemplateSettings.vue';
|
||||
import FollowSettings from './settings/FollowSettings.vue';
|
||||
import EnterSettings from './settings/EnterSettings.vue';
|
||||
import SuperChatSettings from './settings/SuperChatSettings.vue';
|
||||
|
||||
const props = defineProps({
|
||||
action: {
|
||||
type: Object as () => AutoActionItem,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
// 根据触发类型获取对应的设置组件
|
||||
const getTriggerSettings = () => {
|
||||
switch (props.action.triggerType) {
|
||||
case TriggerType.DANMAKU:
|
||||
return DanmakuSettings;
|
||||
case TriggerType.GIFT:
|
||||
return GiftSettings;
|
||||
case TriggerType.GUARD:
|
||||
return GuardSettings;
|
||||
case TriggerType.FOLLOW:
|
||||
return FollowSettings;
|
||||
case TriggerType.ENTER:
|
||||
return EnterSettings;
|
||||
case TriggerType.SCHEDULED:
|
||||
return ScheduledSettings;
|
||||
case TriggerType.SUPER_CHAT:
|
||||
return SuperChatSettings;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const TriggerSettings = getTriggerSettings();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="auto-action-editor">
|
||||
<NCard
|
||||
:title="action.name"
|
||||
size="small"
|
||||
class="editor-card"
|
||||
>
|
||||
<NSpace vertical>
|
||||
<!-- 模板设置 - 移到最上面 -->
|
||||
<TemplateSettings :action="action" />
|
||||
|
||||
<!-- 基本设置 -->
|
||||
<BasicSettings :action="action" />
|
||||
<!-- 高级选项 - 所有高级设置放在一个折叠面板中 -->
|
||||
<NCollapse class="settings-collapse">
|
||||
<template #default>
|
||||
<!-- 触发类型特定设置 -->
|
||||
<component
|
||||
:is="TriggerSettings"
|
||||
v-if="TriggerSettings"
|
||||
:action="action"
|
||||
class="trigger-settings"
|
||||
/>
|
||||
|
||||
<NDivider style="margin: 10px 0;">
|
||||
高级选项
|
||||
</NDivider>
|
||||
<!-- 通用高级设置 -->
|
||||
<AdvancedSettings
|
||||
:action="action"
|
||||
class="advanced-settings"
|
||||
/>
|
||||
</template>
|
||||
<template #header>
|
||||
高级选项
|
||||
</template>
|
||||
</NCollapse>
|
||||
</NSpace>
|
||||
</NCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.auto-action-editor {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,313 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { NSpace, NDivider, NInputNumber, NCard, NButton, NInput, NCollapse, NCollapseItem, NPopconfirm, NTag } from 'naive-ui';
|
||||
import CommonConfigItems from './CommonConfigItems.vue';
|
||||
import { AutoReplyConfig } from '@/client/store/useAutoAction';
|
||||
|
||||
const props = defineProps({
|
||||
config: {
|
||||
type: Object as () => AutoReplyConfig,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
// 新增规则表单数据
|
||||
const newRule = ref({
|
||||
keywords: [] as string[],
|
||||
replies: [] as string[],
|
||||
blockwords: [] as string[]
|
||||
});
|
||||
|
||||
// 临时输入字段
|
||||
const tempKeyword = ref('');
|
||||
const tempReply = ref('');
|
||||
const tempBlockword = ref('');
|
||||
|
||||
function addKeyword() {
|
||||
if (tempKeyword.value.trim() && !newRule.value.keywords.includes(tempKeyword.value.trim())) {
|
||||
newRule.value.keywords.push(tempKeyword.value.trim());
|
||||
tempKeyword.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function addReply() {
|
||||
if (tempReply.value.trim() && !newRule.value.replies.includes(tempReply.value.trim())) {
|
||||
newRule.value.replies.push(tempReply.value.trim());
|
||||
tempReply.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function addBlockword() {
|
||||
if (tempBlockword.value.trim() && !newRule.value.blockwords.includes(tempBlockword.value.trim())) {
|
||||
newRule.value.blockwords.push(tempBlockword.value.trim());
|
||||
tempBlockword.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function addRule() {
|
||||
if (newRule.value.keywords.length > 0 && newRule.value.replies.length > 0) {
|
||||
props.config.rules.push({
|
||||
keywords: [...newRule.value.keywords],
|
||||
replies: [...newRule.value.replies],
|
||||
blockwords: [...newRule.value.blockwords]
|
||||
});
|
||||
|
||||
// 重置表单
|
||||
newRule.value = {
|
||||
keywords: [],
|
||||
replies: [],
|
||||
blockwords: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function removeRule(index: number) {
|
||||
props.config.rules.splice(index, 1);
|
||||
}
|
||||
|
||||
function removeKeyword(index: number) {
|
||||
newRule.value.keywords.splice(index, 1);
|
||||
}
|
||||
|
||||
function removeReply(index: number) {
|
||||
newRule.value.replies.splice(index, 1);
|
||||
}
|
||||
|
||||
function removeBlockword(index: number) {
|
||||
newRule.value.blockwords.splice(index, 1);
|
||||
}
|
||||
|
||||
function removeRuleKeyword(ruleIndex: number, keywordIndex: number) {
|
||||
props.config.rules[ruleIndex].keywords.splice(keywordIndex, 1);
|
||||
}
|
||||
|
||||
function removeRuleReply(ruleIndex: number, replyIndex: number) {
|
||||
props.config.rules[ruleIndex].replies.splice(replyIndex, 1);
|
||||
}
|
||||
|
||||
function removeRuleBlockword(ruleIndex: number, blockwordIndex: number) {
|
||||
props.config.rules[ruleIndex].blockwords.splice(blockwordIndex, 1);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="auto-reply-config">
|
||||
<CommonConfigItems
|
||||
:config="config"
|
||||
:show-live-only="true"
|
||||
:show-delay="false"
|
||||
:show-user-filter="true"
|
||||
:show-tian-xuan="false"
|
||||
/>
|
||||
|
||||
<NDivider title-placement="left">
|
||||
自动回复设置
|
||||
</NDivider>
|
||||
|
||||
<NSpace
|
||||
vertical
|
||||
size="medium"
|
||||
>
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>冷却时间 (秒):</span>
|
||||
<NInputNumber
|
||||
v-model:value="config.cooldownSeconds"
|
||||
:min="0"
|
||||
:max="300"
|
||||
style="width: 120px"
|
||||
/>
|
||||
</NSpace>
|
||||
|
||||
<NCard
|
||||
title="规则列表"
|
||||
size="small"
|
||||
>
|
||||
<NCollapse>
|
||||
<NCollapseItem
|
||||
v-for="(rule, ruleIndex) in config.rules"
|
||||
:key="ruleIndex"
|
||||
:title="`规则 ${ruleIndex + 1}: ${rule.keywords.join(', ')}`"
|
||||
>
|
||||
<NSpace vertical>
|
||||
<NSpace vertical>
|
||||
<div class="rule-section-title">
|
||||
触发关键词:
|
||||
</div>
|
||||
<NSpace>
|
||||
<NTag
|
||||
v-for="(keyword, keywordIndex) in rule.keywords"
|
||||
:key="keywordIndex"
|
||||
closable
|
||||
@close="removeRuleKeyword(ruleIndex, keywordIndex)"
|
||||
>
|
||||
{{ keyword }}
|
||||
</NTag>
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
|
||||
<NSpace vertical>
|
||||
<div class="rule-section-title">
|
||||
回复内容:
|
||||
</div>
|
||||
<NSpace>
|
||||
<NTag
|
||||
v-for="(reply, replyIndex) in rule.replies"
|
||||
:key="replyIndex"
|
||||
closable
|
||||
@close="removeRuleReply(ruleIndex, replyIndex)"
|
||||
>
|
||||
{{ reply }}
|
||||
</NTag>
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
v-if="rule.blockwords.length > 0"
|
||||
vertical
|
||||
>
|
||||
<div class="rule-section-title">
|
||||
屏蔽词:
|
||||
</div>
|
||||
<NSpace>
|
||||
<NTag
|
||||
v-for="(blockword, blockwordIndex) in rule.blockwords"
|
||||
:key="blockwordIndex"
|
||||
closable
|
||||
type="warning"
|
||||
@close="removeRuleBlockword(ruleIndex, blockwordIndex)"
|
||||
>
|
||||
{{ blockword }}
|
||||
</NTag>
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
|
||||
<NPopconfirm @positive-click="removeRule(ruleIndex)">
|
||||
<template #trigger>
|
||||
<NButton
|
||||
size="small"
|
||||
type="error"
|
||||
>
|
||||
删除规则
|
||||
</NButton>
|
||||
</template>
|
||||
确定要删除此规则吗?
|
||||
</NPopconfirm>
|
||||
</NSpace>
|
||||
</NCollapseItem>
|
||||
</NCollapse>
|
||||
</NCard>
|
||||
|
||||
<NCard
|
||||
title="添加新规则"
|
||||
size="small"
|
||||
>
|
||||
<NSpace vertical>
|
||||
<NSpace vertical>
|
||||
<div class="rule-section-title">
|
||||
触发关键词:
|
||||
</div>
|
||||
<NSpace align="center">
|
||||
<NInput
|
||||
v-model:value="tempKeyword"
|
||||
placeholder="输入关键词"
|
||||
@keyup.enter="addKeyword"
|
||||
/>
|
||||
<NButton @click="addKeyword">
|
||||
添加
|
||||
</NButton>
|
||||
</NSpace>
|
||||
<NSpace>
|
||||
<NTag
|
||||
v-for="(keyword, index) in newRule.keywords"
|
||||
:key="index"
|
||||
closable
|
||||
@close="removeKeyword(index)"
|
||||
>
|
||||
{{ keyword }}
|
||||
</NTag>
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
|
||||
<NSpace vertical>
|
||||
<div class="rule-section-title">
|
||||
回复内容: <span class="hint">(可以使用 {{ '\{\{ user.name \}\}' }} 作为用户名变量)</span>
|
||||
</div>
|
||||
<NSpace align="center">
|
||||
<NInput
|
||||
v-model:value="tempReply"
|
||||
placeholder="输入回复内容"
|
||||
@keyup.enter="addReply"
|
||||
/>
|
||||
<NButton @click="addReply">
|
||||
添加
|
||||
</NButton>
|
||||
</NSpace>
|
||||
<NSpace>
|
||||
<NTag
|
||||
v-for="(reply, index) in newRule.replies"
|
||||
:key="index"
|
||||
closable
|
||||
@close="removeReply(index)"
|
||||
>
|
||||
{{ reply }}
|
||||
</NTag>
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
|
||||
<NSpace vertical>
|
||||
<div class="rule-section-title">
|
||||
屏蔽词: <span class="hint">(可选,当弹幕中包含屏蔽词时不触发)</span>
|
||||
</div>
|
||||
<NSpace align="center">
|
||||
<NInput
|
||||
v-model:value="tempBlockword"
|
||||
placeholder="输入屏蔽词"
|
||||
@keyup.enter="addBlockword"
|
||||
/>
|
||||
<NButton @click="addBlockword">
|
||||
添加
|
||||
</NButton>
|
||||
</NSpace>
|
||||
<NSpace>
|
||||
<NTag
|
||||
v-for="(blockword, index) in newRule.blockwords"
|
||||
:key="index"
|
||||
closable
|
||||
type="warning"
|
||||
@close="removeBlockword(index)"
|
||||
>
|
||||
{{ blockword }}
|
||||
</NTag>
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
|
||||
<NButton
|
||||
type="primary"
|
||||
block
|
||||
:disabled="newRule.keywords.length === 0 || newRule.replies.length === 0"
|
||||
@click="addRule"
|
||||
>
|
||||
保存规则
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NCard>
|
||||
</NSpace>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.rule-section-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.hint {
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
@@ -1,59 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { NSpace, NDivider, NInputNumber } from 'naive-ui';
|
||||
import CommonConfigItems from './CommonConfigItems.vue';
|
||||
import TemplateEditor from './TemplateEditor.vue';
|
||||
import { EntryWelcomeConfig } from '@/client/store/useAutoAction';
|
||||
|
||||
const props = defineProps({
|
||||
config: {
|
||||
type: Object as () => EntryWelcomeConfig,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const placeholders = [
|
||||
{ name: '{{user.name}}', description: '被欢迎的用户名或用户列表' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="entry-welcome-config">
|
||||
<CommonConfigItems
|
||||
:config="config"
|
||||
:show-live-only="true"
|
||||
:show-delay="true"
|
||||
:show-user-filter="true"
|
||||
:show-tian-xuan="true"
|
||||
/>
|
||||
|
||||
<NDivider title-placement="left">
|
||||
入场欢迎设置
|
||||
</NDivider>
|
||||
|
||||
<NSpace
|
||||
vertical
|
||||
size="medium"
|
||||
>
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>每次欢迎最大用户数:</span>
|
||||
<NInputNumber
|
||||
v-model:value="config.maxUsersPerMsg"
|
||||
:min="1"
|
||||
:max="20"
|
||||
style="width: 120px"
|
||||
/>
|
||||
</NSpace>
|
||||
|
||||
<TemplateEditor
|
||||
:templates="config.templates"
|
||||
title="欢迎模板"
|
||||
description="可以使用变量来个性化欢迎内容"
|
||||
:placeholders="placeholders"
|
||||
/>
|
||||
</NSpace>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,59 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { NSpace, NDivider, NInputNumber } from 'naive-ui';
|
||||
import CommonConfigItems from './CommonConfigItems.vue';
|
||||
import TemplateEditor from './TemplateEditor.vue';
|
||||
import { FollowThankConfig } from '@/client/store/useAutoAction';
|
||||
|
||||
const props = defineProps({
|
||||
config: {
|
||||
type: Object as () => FollowThankConfig,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const placeholders = [
|
||||
{ name: '{{user.name}}', description: '被感谢的用户名或用户列表' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="follow-thank-config">
|
||||
<CommonConfigItems
|
||||
:config="config"
|
||||
:show-live-only="true"
|
||||
:show-delay="true"
|
||||
:show-user-filter="false"
|
||||
:show-tian-xuan="true"
|
||||
/>
|
||||
|
||||
<NDivider title-placement="left">
|
||||
关注感谢设置
|
||||
</NDivider>
|
||||
|
||||
<NSpace
|
||||
vertical
|
||||
size="medium"
|
||||
>
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>每次感谢最大用户数:</span>
|
||||
<NInputNumber
|
||||
v-model:value="config.maxUsersPerMsg"
|
||||
:min="1"
|
||||
:max="20"
|
||||
style="width: 120px"
|
||||
/>
|
||||
</NSpace>
|
||||
|
||||
<TemplateEditor
|
||||
:templates="config.templates"
|
||||
title="感谢模板"
|
||||
description="可以使用变量来个性化感谢内容"
|
||||
:placeholders="placeholders"
|
||||
/>
|
||||
</NSpace>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,161 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { NCard, NSpace, NDivider, NSelect, NInputNumber, NSwitch, NRadioGroup, NRadio } from 'naive-ui';
|
||||
import CommonConfigItems from './CommonConfigItems.vue';
|
||||
import TemplateEditor from './TemplateEditor.vue';
|
||||
import { GiftThankConfig } from '@/client/store/useAutoAction';
|
||||
|
||||
const props = defineProps({
|
||||
config: {
|
||||
type: Object as () => GiftThankConfig,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const filterModeOptions = [
|
||||
{ label: '不过滤', value: 'none' },
|
||||
{ label: '礼物黑名单', value: 'blacklist' },
|
||||
{ label: '礼物白名单', value: 'whitelist' },
|
||||
{ label: '最低价值', value: 'value' },
|
||||
{ label: '过滤免费礼物', value: 'free' }
|
||||
];
|
||||
|
||||
const thankModeOptions = [
|
||||
{ label: '单用户单礼物', value: 'singleGift' },
|
||||
{ label: '单用户多礼物', value: 'singleUserMultiGift' },
|
||||
{ label: '多用户多礼物', value: 'multiUserMultiGift' }
|
||||
];
|
||||
|
||||
const placeholders = [
|
||||
{ name: '{{user.name}}', description: '用户名称' },
|
||||
{ name: '{{gift.summary}}', description: '礼物摘要,包含礼物名称和数量' },
|
||||
{ name: '{{gift.totalPrice}}', description: '礼物总价值' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="gift-thank-config">
|
||||
<CommonConfigItems
|
||||
:config="config"
|
||||
:show-live-only="true"
|
||||
:show-delay="true"
|
||||
:show-user-filter="true"
|
||||
:show-tian-xuan="true"
|
||||
/>
|
||||
|
||||
<NDivider title-placement="left">
|
||||
礼物过滤设置
|
||||
</NDivider>
|
||||
|
||||
<NSpace
|
||||
vertical
|
||||
size="medium"
|
||||
>
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>过滤模式:</span>
|
||||
<NSelect
|
||||
v-model:value="config.filterMode"
|
||||
:options="filterModeOptions"
|
||||
style="width: 200px"
|
||||
/>
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
v-if="config.filterMode === 'value'"
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>最低价值 (元):</span>
|
||||
<NInputNumber
|
||||
v-model:value="config.minValue"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
style="width: 120px"
|
||||
/>
|
||||
</NSpace>
|
||||
|
||||
<TemplateEditor
|
||||
:templates="config.filterGiftNames"
|
||||
title="礼物名称列表"
|
||||
:description="config.filterMode === 'blacklist' ? '以下礼物将被过滤不触发感谢' : config.filterMode === 'whitelist' ? '只有以下礼物会触发感谢' : '请添加礼物名称'"
|
||||
/>
|
||||
</NSpace>
|
||||
|
||||
<NDivider title-placement="left">
|
||||
感谢设置
|
||||
</NDivider>
|
||||
|
||||
<NSpace
|
||||
vertical
|
||||
size="medium"
|
||||
>
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>感谢模式:</span>
|
||||
<NRadioGroup v-model:value="config.thankMode">
|
||||
<NSpace>
|
||||
<NRadio
|
||||
v-for="option in thankModeOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.label }}
|
||||
</NRadio>
|
||||
</NSpace>
|
||||
</NRadioGroup>
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>每次感谢最大用户数:</span>
|
||||
<NInputNumber
|
||||
v-model:value="config.maxUsersPerMsg"
|
||||
:min="1"
|
||||
:max="20"
|
||||
style="width: 120px"
|
||||
/>
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
v-if="config.thankMode === 'singleUserMultiGift'"
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>每用户最大礼物数:</span>
|
||||
<NInputNumber
|
||||
v-model:value="config.maxGiftsPerUser"
|
||||
:min="1"
|
||||
:max="10"
|
||||
style="width: 120px"
|
||||
/>
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>包含礼物数量:</span>
|
||||
<NSwitch v-model:value="config.includeQuantity" />
|
||||
</NSpace>
|
||||
|
||||
<TemplateEditor
|
||||
:templates="config.templates"
|
||||
title="感谢模板"
|
||||
description="可以使用变量来个性化感谢内容"
|
||||
:placeholders="placeholders"
|
||||
/>
|
||||
</NSpace>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,223 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { NCard, NSpace, NDivider, NInput, NSwitch, NButton, NSelect, NPopconfirm } from 'naive-ui';
|
||||
import CommonConfigItems from './CommonConfigItems.vue';
|
||||
import { GuardPmConfig, GuardLevel } from '@/client/store/useAutoAction';
|
||||
|
||||
const props = defineProps({
|
||||
config: {
|
||||
type: Object as () => GuardPmConfig,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const newCode = ref('');
|
||||
const selectedLevel = ref(GuardLevel.Jianzhang);
|
||||
|
||||
const levelOptions = [
|
||||
{ label: '通用', value: GuardLevel.None },
|
||||
{ label: '舰长', value: GuardLevel.Jianzhang },
|
||||
{ label: '提督', value: GuardLevel.Tidu },
|
||||
{ label: '总督', value: GuardLevel.Zongdu }
|
||||
];
|
||||
|
||||
function getLevelName(level: GuardLevel): string {
|
||||
const opt = levelOptions.find(o => o.value === level);
|
||||
return opt ? opt.label : '未知';
|
||||
}
|
||||
|
||||
function addGiftCode() {
|
||||
if (!newCode.value.trim()) return;
|
||||
|
||||
const level = selectedLevel.value;
|
||||
const levelCodes = props.config.giftCodes.find(gc => gc.level === level);
|
||||
|
||||
if (levelCodes) {
|
||||
levelCodes.codes.push(newCode.value.trim());
|
||||
} else {
|
||||
props.config.giftCodes.push({
|
||||
level: level,
|
||||
codes: [newCode.value.trim()]
|
||||
});
|
||||
}
|
||||
|
||||
newCode.value = '';
|
||||
}
|
||||
|
||||
function removeCode(level: GuardLevel, index: number) {
|
||||
const levelCodes = props.config.giftCodes.find(gc => gc.level === level);
|
||||
if (levelCodes) {
|
||||
levelCodes.codes.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
const placeholders = [
|
||||
{ name: '{{user.name}}', description: '用户名称' },
|
||||
{ name: '{{guard.levelName}}', description: '舰长等级名称' },
|
||||
{ name: '{{guard.giftCode}}', description: '礼品码(礼品码模式下可用)' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="guard-pm-config">
|
||||
<CommonConfigItems
|
||||
:config="config"
|
||||
:show-live-only="true"
|
||||
:show-delay="false"
|
||||
:show-user-filter="false"
|
||||
:show-tian-xuan="false"
|
||||
/>
|
||||
|
||||
<NDivider title-placement="left">
|
||||
私信设置
|
||||
</NDivider>
|
||||
|
||||
<NSpace
|
||||
vertical
|
||||
size="medium"
|
||||
>
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>私信模板:</span>
|
||||
<NInput
|
||||
v-model:value="config.template"
|
||||
placeholder="例如: 感谢 {{user.name}} 成为 {{guard.levelName}}!"
|
||||
style="width: 350px"
|
||||
>
|
||||
<template #prefix>
|
||||
<NPopconfirm placement="bottom">
|
||||
<template #trigger>
|
||||
<NButton
|
||||
quaternary
|
||||
circle
|
||||
size="small"
|
||||
>
|
||||
?
|
||||
</NButton>
|
||||
</template>
|
||||
<div>
|
||||
<div
|
||||
v-for="ph in placeholders"
|
||||
:key="ph.name"
|
||||
>
|
||||
<strong>{{ ph.name }}</strong>: {{ ph.description }}
|
||||
</div>
|
||||
</div>
|
||||
</NPopconfirm>
|
||||
</template>
|
||||
</NInput>
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>发送弹幕确认:</span>
|
||||
<NSwitch v-model:value="config.sendDanmakuConfirm" />
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
v-if="config.sendDanmakuConfirm"
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>弹幕确认模板:</span>
|
||||
<NInput
|
||||
v-model:value="config.danmakuTemplate"
|
||||
placeholder="例如: 已私信 {{user.name}} 舰长福利!"
|
||||
style="width: 350px"
|
||||
/>
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>防止重复发送:</span>
|
||||
<NSwitch v-model:value="config.preventRepeat" />
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
|
||||
<NDivider title-placement="left">
|
||||
礼品码模式
|
||||
</NDivider>
|
||||
|
||||
<NSpace
|
||||
vertical
|
||||
size="medium"
|
||||
>
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>启用礼品码模式:</span>
|
||||
<NSwitch v-model:value="config.giftCodeMode" />
|
||||
</NSpace>
|
||||
|
||||
<div v-if="config.giftCodeMode">
|
||||
<NCard
|
||||
title="添加礼品码"
|
||||
size="small"
|
||||
>
|
||||
<NSpace vertical>
|
||||
<NSpace justify="space-between">
|
||||
<NSelect
|
||||
v-model:value="selectedLevel"
|
||||
:options="levelOptions"
|
||||
style="width: 120px"
|
||||
/>
|
||||
<NInput
|
||||
v-model:value="newCode"
|
||||
placeholder="输入礼品码"
|
||||
style="flex: 1"
|
||||
/>
|
||||
<NButton
|
||||
type="primary"
|
||||
@click="addGiftCode"
|
||||
>
|
||||
添加
|
||||
</NButton>
|
||||
</NSpace>
|
||||
|
||||
<NDivider />
|
||||
|
||||
<div
|
||||
v-for="levelData in config.giftCodes"
|
||||
:key="levelData.level"
|
||||
>
|
||||
<NCard
|
||||
v-if="levelData.codes.length > 0"
|
||||
:title="getLevelName(levelData.level) + ' 礼品码'"
|
||||
size="small"
|
||||
>
|
||||
<NSpace
|
||||
v-for="(code, index) in levelData.codes"
|
||||
:key="index"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>{{ code }}</span>
|
||||
<NButton
|
||||
size="small"
|
||||
type="error"
|
||||
quaternary
|
||||
@click="removeCode(levelData.level, index)"
|
||||
>
|
||||
删除
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NCard>
|
||||
</div>
|
||||
</NSpace>
|
||||
</NCard>
|
||||
</div>
|
||||
</NSpace>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,78 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { NSpace, NDivider, NInputNumber, NRadioGroup, NRadio } from 'naive-ui';
|
||||
import CommonConfigItems from './CommonConfigItems.vue';
|
||||
import TemplateEditor from './TemplateEditor.vue';
|
||||
import { ScheduledDanmakuConfig } from '@/client/store/useAutoAction';
|
||||
|
||||
const props = defineProps({
|
||||
config: {
|
||||
type: Object as () => ScheduledDanmakuConfig,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const modeOptions = [
|
||||
{ label: '随机模式', value: 'random' },
|
||||
{ label: '顺序模式', value: 'sequential' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="scheduled-danmaku-config">
|
||||
<CommonConfigItems
|
||||
:config="config"
|
||||
:show-live-only="true"
|
||||
:show-delay="false"
|
||||
:show-user-filter="false"
|
||||
:show-tian-xuan="false"
|
||||
/>
|
||||
|
||||
<NDivider title-placement="left">
|
||||
定时弹幕设置
|
||||
</NDivider>
|
||||
|
||||
<NSpace
|
||||
vertical
|
||||
size="medium"
|
||||
>
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>发送间隔 (秒):</span>
|
||||
<NInputNumber
|
||||
v-model:value="config.intervalSeconds"
|
||||
:min="60"
|
||||
:max="3600"
|
||||
style="width: 120px"
|
||||
/>
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>发送模式:</span>
|
||||
<NRadioGroup v-model:value="config.mode">
|
||||
<NSpace>
|
||||
<NRadio
|
||||
v-for="option in modeOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.label }}
|
||||
</NRadio>
|
||||
</NSpace>
|
||||
</NRadioGroup>
|
||||
</NSpace>
|
||||
|
||||
<TemplateEditor
|
||||
:templates="config.messages"
|
||||
title="弹幕内容列表"
|
||||
description="每条消息将按照设定的模式定时发送"
|
||||
/>
|
||||
</NSpace>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,6 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { NInput, NInputNumber, NButton, NSpace, NCard, NDivider, NList, NListItem, NPopconfirm, NTooltip } from 'naive-ui';
|
||||
import { NButton, NCard, NDivider, NHighlight, NInput, NList, NListItem, NPopconfirm, NScrollbar, NSpace, NTooltip, useMessage, NTabs, NTabPane, NFlex, NAlert, NIcon } from 'naive-ui';
|
||||
import { computed, ref } from 'vue';
|
||||
import TemplateHelper from './TemplateHelper.vue';
|
||||
import TemplateTester from './TemplateTester.vue';
|
||||
import { containsJsExpression, convertToJsExpressions } from '@/client/store/autoAction/expressionEvaluator';
|
||||
import { Info24Filled } from '@vicons/fluent';
|
||||
|
||||
const props = defineProps({
|
||||
templates: {
|
||||
@@ -18,57 +22,133 @@ const props = defineProps({
|
||||
placeholders: {
|
||||
type: Array as () => { name: string, description: string }[],
|
||||
default: () => []
|
||||
},
|
||||
// 新增:提供测试上下文对象
|
||||
testContext: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
user: { uid: 12345, name: '测试用户' },
|
||||
gift: { name: '测试礼物', count: 1, price: 100 }
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
// 添加默认的弹幕相关占位符
|
||||
const mergedPlaceholders = computed(() => {
|
||||
const defaultPlaceholders = [
|
||||
{ name: '{{danmaku.type}}', description: '事件类型' },
|
||||
{ name: '{{danmaku.uname}}', description: '用户名称' },
|
||||
{ name: '{{danmaku.uface}}', description: '用户头像URL' },
|
||||
{ name: '{{danmaku.uid}}', description: '用户ID(直接连接)' },
|
||||
{ name: '{{danmaku.open_id}}', description: '用户开放平台ID' },
|
||||
{ name: '{{danmaku.msg}}', description: '消息内容' },
|
||||
{ name: '{{danmaku.time}}', description: '时间戳' },
|
||||
{ name: '{{danmaku.num}}', description: '数量' },
|
||||
{ name: '{{danmaku.price}}', description: '价格' },
|
||||
{ name: '{{danmaku.guard_level}}', description: '大航海等级' },
|
||||
{ name: '{{danmaku.fans_medal_level}}', description: '粉丝牌等级' },
|
||||
{ name: '{{danmaku.fans_medal_name}}', description: '粉丝牌名称' },
|
||||
{ name: '{{danmaku.fans_medal_wearing_status}}', description: '是否佩戴粉丝牌' },
|
||||
{ name: '{{danmaku.emoji}}', description: '表情符号' }
|
||||
{ name: '{{user.name}}', description: '用户名称' },
|
||||
{ name: '{{user.uid}}', description: '用户ID' },
|
||||
{ name: '{{user.nameLength}}', description: '用户名长度' },
|
||||
{ name: '{{date.formatted}}', description: '当前日期格式化' },
|
||||
{ name: '{{timeOfDay()}}', description: '获取当前时段(早上/下午/晚上)' }
|
||||
];
|
||||
|
||||
// 返回自定义占位符和默认占位符,但不合并它们
|
||||
return { custom: props.placeholders, default: defaultPlaceholders };
|
||||
// 合并自定义占位符和默认占位符
|
||||
return [...props.placeholders, ...defaultPlaceholders];
|
||||
});
|
||||
|
||||
const newTemplate = ref('');
|
||||
const message = useMessage();
|
||||
const activeTab = ref('editor'); // 新增:标签页控制
|
||||
|
||||
// 新增:跟踪编辑状态
|
||||
const isEditing = ref(false);
|
||||
const editIndex = ref(-1);
|
||||
const editTemplate = ref('');
|
||||
|
||||
// 新增:测试选中的模板
|
||||
const selectedTemplateForTest = ref('');
|
||||
|
||||
function addTemplate() {
|
||||
if (newTemplate.value.trim()) {
|
||||
props.templates.push(newTemplate.value.trim());
|
||||
newTemplate.value = '';
|
||||
const val = newTemplate.value.trim();
|
||||
if (!val) return;
|
||||
if (props.templates.includes(val)) {
|
||||
message.warning('模板已存在');
|
||||
return;
|
||||
}
|
||||
props.templates.push(val);
|
||||
newTemplate.value = '';
|
||||
}
|
||||
|
||||
function removeTemplate(index: number) {
|
||||
props.templates.splice(index, 1);
|
||||
}
|
||||
|
||||
// 新增:开始编辑模板
|
||||
function startEditTemplate(index: number) {
|
||||
editIndex.value = index;
|
||||
editTemplate.value = props.templates[index];
|
||||
isEditing.value = true;
|
||||
newTemplate.value = editTemplate.value;
|
||||
}
|
||||
|
||||
// 新增:取消编辑
|
||||
function cancelEdit() {
|
||||
isEditing.value = false;
|
||||
editIndex.value = -1;
|
||||
newTemplate.value = '';
|
||||
}
|
||||
|
||||
// 新增:保存编辑后的模板
|
||||
function saveEditedTemplate() {
|
||||
const val = newTemplate.value.trim();
|
||||
if (!val) {
|
||||
message.warning('模板内容不能为空');
|
||||
return;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 检查是否与其他模板重复(排除当前编辑的模板)
|
||||
const otherTemplates = props.templates.filter((_, idx) => idx !== editIndex.value);
|
||||
if (otherTemplates.includes(val)) {
|
||||
message.warning('模板已存在');
|
||||
return;
|
||||
}
|
||||
|
||||
})
|
||||
props.templates[editIndex.value] = val;
|
||||
message.success('模板更新成功');
|
||||
cancelEdit();
|
||||
}
|
||||
|
||||
// 新增:转换为表达式
|
||||
function convertPlaceholders() {
|
||||
if (!newTemplate.value) {
|
||||
message.warning('请先输入模板内容');
|
||||
return;
|
||||
}
|
||||
newTemplate.value = convertToJsExpressions(newTemplate.value, mergedPlaceholders.value);
|
||||
message.success('已转换占位符为表达式格式');
|
||||
}
|
||||
|
||||
// 新增:测试模板
|
||||
function testTemplate(template: string) {
|
||||
selectedTemplateForTest.value = template;
|
||||
activeTab.value = 'test';
|
||||
}
|
||||
|
||||
// 新增:高亮JavaScript表达式
|
||||
function hasJsExpression(template: string): boolean {
|
||||
return containsJsExpression(template);
|
||||
}
|
||||
|
||||
// 新增:高亮规则
|
||||
const highlightPatterns = computed(() => {
|
||||
return [
|
||||
// 普通占位符高亮
|
||||
...mergedPlaceholders.value.map(p => p.name),
|
||||
// JS表达式高亮
|
||||
'{{js:'
|
||||
];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard
|
||||
:title="title"
|
||||
size="small"
|
||||
class="template-editor-card"
|
||||
>
|
||||
<template
|
||||
v-if="mergedPlaceholders.custom.length > 0 || mergedPlaceholders.default.length > 0"
|
||||
v-if="mergedPlaceholders.length > 0"
|
||||
#header-extra
|
||||
>
|
||||
<NTooltip
|
||||
@@ -79,29 +159,46 @@ function removeTemplate(index: number) {
|
||||
<NButton
|
||||
quaternary
|
||||
size="small"
|
||||
class="btn-with-transition"
|
||||
>
|
||||
变量说明
|
||||
</NButton>
|
||||
</template>
|
||||
<div style="max-width: 300px">
|
||||
|
||||
<NAlert
|
||||
type="info"
|
||||
closable
|
||||
style="margin-bottom: 8px"
|
||||
>
|
||||
<template #header>
|
||||
<div class="alert-header">
|
||||
<NIcon
|
||||
:component="Info24Filled"
|
||||
size="18"
|
||||
style="margin-right: 8px"
|
||||
/>
|
||||
模板支持简单的JavaScript表达式
|
||||
</div>
|
||||
</template>
|
||||
在模板中使用 <code>{{ '\{\{js:\}\}' }}</code> 语法可以执行简单的JavaScript表达式
|
||||
|
||||
<NFlex vertical>
|
||||
<span>
|
||||
<code>{{ '\{\{js: user.name.toUpperCase()\}\}' }}</code> → 将用户名转为大写
|
||||
</span>
|
||||
<span>
|
||||
<code>{{ '\{\{js: gift.count > 10 ? "大量" : "少量"\}\}' }}</code> → 根据数量显示不同文本
|
||||
</span>
|
||||
</NFlex>
|
||||
</NAlert>
|
||||
<NScrollbar style="max-height: 200px; max-width: 300px">
|
||||
<div
|
||||
v-for="(ph, idx) in mergedPlaceholders.custom"
|
||||
:key="'custom-' + idx"
|
||||
v-for="(ph, idx) in mergedPlaceholders"
|
||||
:key="idx"
|
||||
>
|
||||
<strong>{{ ph.name }}</strong>: {{ ph.description }}
|
||||
</div>
|
||||
<NDivider
|
||||
v-if="mergedPlaceholders.custom.length > 0 && mergedPlaceholders.default.length > 0"
|
||||
style="margin: 10px 0;">
|
||||
默认变量
|
||||
</NDivider>
|
||||
<div
|
||||
v-for="(ph, idx) in mergedPlaceholders.default"
|
||||
:key="'default-' + idx"
|
||||
>
|
||||
<strong>{{ ph.name }}</strong>: {{ ph.description }}
|
||||
</div>
|
||||
</div>
|
||||
</NScrollbar>
|
||||
</NTooltip>
|
||||
</template>
|
||||
|
||||
@@ -112,56 +209,284 @@ function removeTemplate(index: number) {
|
||||
{{ description }}
|
||||
</p>
|
||||
|
||||
<NList bordered>
|
||||
<NListItem
|
||||
v-for="(template, index) in templates"
|
||||
:key="index"
|
||||
<!-- 新增:添加标签页支持 -->
|
||||
<NTabs
|
||||
v-model:value="activeTab"
|
||||
type="line"
|
||||
animated
|
||||
class="editor-tabs"
|
||||
>
|
||||
<NTabPane
|
||||
name="editor"
|
||||
tab="编辑模板"
|
||||
>
|
||||
<NSpace
|
||||
justify="space-between"
|
||||
align="center"
|
||||
style="width: 100%"
|
||||
<!-- 新增:添加模板帮助组件 -->
|
||||
<transition
|
||||
name="fade"
|
||||
mode="out-in"
|
||||
appear
|
||||
>
|
||||
<span>{{ template }}</span>
|
||||
<NPopconfirm @positive-click="removeTemplate(index)">
|
||||
<template #trigger>
|
||||
<NButton
|
||||
size="small"
|
||||
quaternary
|
||||
type="error"
|
||||
<TemplateHelper :placeholders="mergedPlaceholders" />
|
||||
</transition>
|
||||
|
||||
<NList
|
||||
bordered
|
||||
class="template-list"
|
||||
>
|
||||
<transition-group
|
||||
name="list-slide"
|
||||
tag="div"
|
||||
appear
|
||||
>
|
||||
<NListItem
|
||||
v-for="(template, index) in templates"
|
||||
:key="index"
|
||||
class="template-list-item"
|
||||
>
|
||||
<NSpace
|
||||
justify="space-between"
|
||||
align="center"
|
||||
style="width: 100%"
|
||||
>
|
||||
删除
|
||||
</NButton>
|
||||
</template>
|
||||
确定要删除此模板吗?
|
||||
</NPopconfirm>
|
||||
</NSpace>
|
||||
</NListItem>
|
||||
</NList>
|
||||
<!-- 更新:使用自定义高亮规则 -->
|
||||
<div
|
||||
class="template-content"
|
||||
:class="{ 'has-js-expr': hasJsExpression(template) }"
|
||||
>
|
||||
<NHighlight
|
||||
:patterns="highlightPatterns"
|
||||
:text="template"
|
||||
/>
|
||||
<div
|
||||
v-if="hasJsExpression(template)"
|
||||
class="js-expr-badge"
|
||||
>
|
||||
JS
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NDivider />
|
||||
<NSpace>
|
||||
<NButton
|
||||
size="small"
|
||||
class="btn-with-transition"
|
||||
@click="testTemplate(template)"
|
||||
>
|
||||
测试
|
||||
</NButton>
|
||||
<NButton
|
||||
size="small"
|
||||
class="btn-with-transition"
|
||||
@click="startEditTemplate(index)"
|
||||
>
|
||||
编辑
|
||||
</NButton>
|
||||
<NPopconfirm
|
||||
@positive-click="removeTemplate(index)"
|
||||
>
|
||||
<template #trigger>
|
||||
<NButton
|
||||
size="small"
|
||||
class="btn-with-transition"
|
||||
>
|
||||
删除
|
||||
</NButton>
|
||||
</template>
|
||||
确定要删除这个模板吗?
|
||||
</NPopconfirm>
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
</NListItem>
|
||||
</transition-group>
|
||||
</NList>
|
||||
|
||||
<NSpace vertical>
|
||||
<NInput
|
||||
v-model:value="newTemplate"
|
||||
placeholder="输入新模板内容"
|
||||
clearable
|
||||
/>
|
||||
<NButton
|
||||
type="primary"
|
||||
block
|
||||
@click="addTemplate"
|
||||
<NDivider />
|
||||
|
||||
<transition
|
||||
name="fade-scale"
|
||||
appear
|
||||
>
|
||||
<NSpace
|
||||
vertical
|
||||
style="width: 100%"
|
||||
>
|
||||
<NInput
|
||||
v-model:value="newTemplate"
|
||||
type="textarea"
|
||||
placeholder="输入新模板"
|
||||
:autosize="{ minRows: 2, maxRows: 5 }"
|
||||
class="template-input"
|
||||
@keydown.enter.ctrl="isEditing ? saveEditedTemplate() : addTemplate()"
|
||||
/>
|
||||
|
||||
<NSpace justify="space-between">
|
||||
<NSpace>
|
||||
<NButton
|
||||
type="default"
|
||||
class="btn-with-transition"
|
||||
@click="convertPlaceholders"
|
||||
>
|
||||
转换为表达式
|
||||
</NButton>
|
||||
</NSpace>
|
||||
|
||||
<NSpace>
|
||||
<NButton
|
||||
v-if="isEditing"
|
||||
class="btn-with-transition"
|
||||
@click="cancelEdit"
|
||||
>
|
||||
取消
|
||||
</NButton>
|
||||
<NButton
|
||||
type="primary"
|
||||
class="btn-with-transition"
|
||||
@click="isEditing ? saveEditedTemplate() : addTemplate()"
|
||||
>
|
||||
{{ isEditing ? '保存' : '添加' }}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
</transition>
|
||||
</NTabPane>
|
||||
|
||||
<NTabPane
|
||||
name="test"
|
||||
tab="测试模板"
|
||||
>
|
||||
添加模板
|
||||
</NButton>
|
||||
</NSpace>
|
||||
<transition
|
||||
name="fade"
|
||||
mode="out-in"
|
||||
appear
|
||||
>
|
||||
<TemplateTester
|
||||
:default-template="selectedTemplateForTest"
|
||||
:context="testContext"
|
||||
:placeholders="mergedPlaceholders"
|
||||
/>
|
||||
</transition>
|
||||
</NTabPane>
|
||||
</NTabs>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.template-editor-card {
|
||||
transition: all 0.3s ease;
|
||||
animation: card-appear 0.4s ease-out;
|
||||
}
|
||||
|
||||
@keyframes card-appear {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.template-description {
|
||||
margin-bottom: 16px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.editor-tabs {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.template-list {
|
||||
margin-top: 16px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.template-list-item {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.template-list-item:hover {
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
.template-content {
|
||||
position: relative;
|
||||
padding-right: 30px;
|
||||
word-break: break-all;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.has-js-expr {
|
||||
background-color: rgba(64, 158, 255, 0.05);
|
||||
border-radius: 4px;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.js-expr-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background-color: #409EFF;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
padding: 1px 4px;
|
||||
border-radius: 3px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.template-input {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.template-input:focus {
|
||||
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||||
}
|
||||
|
||||
/* 列表动画 */
|
||||
.list-slide-enter-active,
|
||||
.list-slide-leave-active {
|
||||
transition: all 0.4s ease;
|
||||
}
|
||||
.list-slide-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateX(-20px);
|
||||
}
|
||||
.list-slide-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
.list-slide-move {
|
||||
transition: transform 0.4s ease;
|
||||
}
|
||||
|
||||
/* 淡入缩放 */
|
||||
.fade-scale-enter-active,
|
||||
.fade-scale-leave-active {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
.fade-scale-enter-from,
|
||||
.fade-scale-leave-to {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
/* 淡入淡出 */
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* 按钮过渡 */
|
||||
.btn-with-transition {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.btn-with-transition:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
</style>
|
||||
|
||||
74
src/client/components/autoaction/TemplateHelper.vue
Normal file
74
src/client/components/autoaction/TemplateHelper.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div class="template-helper">
|
||||
<NSpace vertical>
|
||||
<NSpace align="center">
|
||||
<div style="font-weight: bold">
|
||||
可用变量:
|
||||
</div>
|
||||
<NSpace style="flex-wrap: wrap">
|
||||
<NTooltip
|
||||
v-for="item in props.placeholders"
|
||||
:key="item.name"
|
||||
trigger="hover"
|
||||
>
|
||||
<template #trigger>
|
||||
<NTag
|
||||
:bordered="false"
|
||||
type="info"
|
||||
size="small"
|
||||
style="cursor: pointer"
|
||||
@click="copyToClipboard(item.name)"
|
||||
>
|
||||
{{ item.name }}
|
||||
</NTag>
|
||||
</template>
|
||||
{{ item.description }}
|
||||
</NTooltip>
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Info24Filled } from '@vicons/fluent';
|
||||
import { NSpace, NTag, NAlert, NTooltip, NIcon, useMessage, NDivider } from 'naive-ui';
|
||||
|
||||
const props = defineProps({
|
||||
placeholders: {
|
||||
type: Array as () => { name: string; description: string }[],
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const message = useMessage();
|
||||
|
||||
function copyToClipboard(text: string) {
|
||||
navigator.clipboard.writeText(text)
|
||||
.then(() => {
|
||||
message.success('已复制到剪贴板');
|
||||
})
|
||||
.catch(() => {
|
||||
message.error('复制失败');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.template-helper {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.alert-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: rgba(0, 0, 0, 0.06);
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
}
|
||||
</style>
|
||||
107
src/client/components/autoaction/TemplateTester.vue
Normal file
107
src/client/components/autoaction/TemplateTester.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<div class="template-tester">
|
||||
<NSpace vertical>
|
||||
<NInput
|
||||
v-model:value="template"
|
||||
type="textarea"
|
||||
placeholder="输入包含表达式的模板"
|
||||
/>
|
||||
|
||||
<NSpace>
|
||||
<NButton
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="testTemplate"
|
||||
>
|
||||
测试模板
|
||||
</NButton>
|
||||
<NButton
|
||||
size="small"
|
||||
@click="resetTemplate"
|
||||
>
|
||||
重置
|
||||
</NButton>
|
||||
</NSpace>
|
||||
|
||||
<template
|
||||
v-if="hasResult"
|
||||
>
|
||||
<NDivider style="margin: 5px;" />
|
||||
<NCard
|
||||
title="结果预览"
|
||||
size="small"
|
||||
>
|
||||
<NInput
|
||||
type="textarea"
|
||||
:value="result"
|
||||
readonly
|
||||
/>
|
||||
</NCard>
|
||||
</template>
|
||||
</NSpace>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { NSpace, NInput, NInputGroup, NInputGroupLabel, NButton, useMessage, NDivider } from 'naive-ui';
|
||||
import { evaluateTemplateExpressions } from '@/client/store/autoAction/expressionEvaluator';
|
||||
|
||||
const props = defineProps({
|
||||
defaultTemplate: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
context: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const template = ref(props.defaultTemplate || '');
|
||||
const result = ref('');
|
||||
const hasResult = computed(() => result.value !== '');
|
||||
const message = useMessage();
|
||||
|
||||
function testTemplate() {
|
||||
try {
|
||||
result.value = evaluateTemplateExpressions(template.value, props.context);
|
||||
} catch (error) {
|
||||
message.error(`表达式求值错误: ${(error as Error).message}`);
|
||||
result.value = `[错误] ${(error as Error).message}`;
|
||||
}
|
||||
}
|
||||
|
||||
function resetTemplate() {
|
||||
template.value = props.defaultTemplate;
|
||||
result.value = '';
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.template-tester {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.result-container {
|
||||
margin-top: 8px;
|
||||
padding: 8px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.result-content {
|
||||
padding: 8px;
|
||||
background-color: white;
|
||||
border-radius: 4px;
|
||||
border: 1px dashed #d9d9d9;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
196
src/client/components/autoaction/settings/AdvancedSettings.vue
Normal file
196
src/client/components/autoaction/settings/AdvancedSettings.vue
Normal file
@@ -0,0 +1,196 @@
|
||||
<script setup lang="ts">
|
||||
import { NSpace, NSwitch, NInputNumber, NInput, NCollapseItem } from 'naive-ui';
|
||||
import { AutoActionItem, TriggerType } from '@/client/store/useAutoAction';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
action: {
|
||||
type: Object as () => AutoActionItem,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
// 根据触发类型判断是否显示用户过滤选项
|
||||
const showUserFilter = computed(() => {
|
||||
return ![TriggerType.SCHEDULED].includes(props.action.triggerType);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCollapse>
|
||||
<NCollapseItem
|
||||
v-if="showUserFilter"
|
||||
key="user-filter"
|
||||
title="用户过滤"
|
||||
class="settings-section"
|
||||
>
|
||||
<div>
|
||||
<NSpace
|
||||
key="user-filter-enabled"
|
||||
vertical
|
||||
class="settings-subsection"
|
||||
>
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
class="setting-item"
|
||||
>
|
||||
<span>启用用户过滤:</span>
|
||||
<NSwitch v-model:value="action.triggerConfig.userFilterEnabled" />
|
||||
</NSpace>
|
||||
|
||||
<template v-if="action.triggerConfig.userFilterEnabled">
|
||||
<NSpace
|
||||
key="require-medal"
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
class="setting-item"
|
||||
>
|
||||
<span>要求本房间勋章:</span>
|
||||
<NSwitch v-model:value="action.triggerConfig.requireMedal" />
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
key="require-captain"
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
class="setting-item"
|
||||
>
|
||||
<span>要求任意舰长:</span>
|
||||
<NSwitch v-model:value="action.triggerConfig.requireCaptain" />
|
||||
</NSpace>
|
||||
</template>
|
||||
</NSpace>
|
||||
</div>
|
||||
</NCollapseItem>
|
||||
|
||||
<NCollapseItem
|
||||
key="cooldown"
|
||||
title="冷却控制"
|
||||
class="settings-section"
|
||||
>
|
||||
<div>
|
||||
<NSpace
|
||||
key="ignore-cooldown"
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
class="setting-item"
|
||||
>
|
||||
<span>忽略全局冷却:</span>
|
||||
<NSwitch v-model:value="action.ignoreCooldown" />
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
key="delay-seconds"
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
class="setting-item"
|
||||
>
|
||||
<span>延迟执行(秒):</span>
|
||||
<NInputNumber
|
||||
v-model:value="action.actionConfig.delaySeconds"
|
||||
:min="0"
|
||||
:max="600"
|
||||
style="width: 120px"
|
||||
/>
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
key="cooldown-seconds"
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
class="setting-item"
|
||||
>
|
||||
<span>冷却时间(秒):</span>
|
||||
<NInputNumber
|
||||
v-model:value="action.actionConfig.cooldownSeconds"
|
||||
:min="0"
|
||||
:max="3600"
|
||||
style="width: 120px"
|
||||
/>
|
||||
</NSpace>
|
||||
</div>
|
||||
</NCollapseItem>
|
||||
|
||||
<NCollapseItem
|
||||
key="logical-expression"
|
||||
title="逻辑条件表达式"
|
||||
class="settings-section"
|
||||
>
|
||||
<div>
|
||||
<NSpace vertical>
|
||||
<p class="description">
|
||||
当表达式为真时才会执行此操作。可使用JS语法,例如: <code>user.guardLevel > 0 || gift.price > 10</code>
|
||||
</p>
|
||||
<NInput
|
||||
v-model:value="action.logicalExpression"
|
||||
type="textarea"
|
||||
placeholder="输入表达式,留空则始终为真"
|
||||
:autosize="{ minRows: 2, maxRows: 5 }"
|
||||
/>
|
||||
</NSpace>
|
||||
</div>
|
||||
</NCollapseItem>
|
||||
|
||||
<NCollapseItem
|
||||
key="custom-js"
|
||||
title="自定义JS执行"
|
||||
class="settings-section"
|
||||
>
|
||||
<div>
|
||||
<NSpace vertical>
|
||||
<p class="description">
|
||||
可访问 context, event, biliFunc, roomId 等变量
|
||||
</p>
|
||||
<NInput
|
||||
v-model:value="action.executeCommand"
|
||||
type="textarea"
|
||||
placeholder="输入要执行的JS代码"
|
||||
:autosize="{ minRows: 3, maxRows: 8 }"
|
||||
/>
|
||||
</NSpace>
|
||||
</div>
|
||||
</NCollapseItem>
|
||||
</NCollapse>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.settings-section {
|
||||
margin-bottom: 12px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.settings-subsection {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.setting-item {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.setting-item:hover {
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: rgba(0, 0, 0, 0.06);
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
/* 已移除所有动画相关样式 */
|
||||
</style>
|
||||
143
src/client/components/autoaction/settings/BasicSettings.vue
Normal file
143
src/client/components/autoaction/settings/BasicSettings.vue
Normal file
@@ -0,0 +1,143 @@
|
||||
<script setup lang="ts">
|
||||
import { NSpace, NInput, NSwitch, NSelect } from 'naive-ui';
|
||||
import { AutoActionItem, TriggerType, ActionType, Priority } from '@/client/store/useAutoAction';
|
||||
|
||||
const props = defineProps({
|
||||
action: {
|
||||
type: Object as () => AutoActionItem,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
// 触发类型选项
|
||||
const triggerTypeOptions = [
|
||||
{ label: '弹幕触发', value: TriggerType.DANMAKU },
|
||||
{ label: '礼物感谢', value: TriggerType.GIFT },
|
||||
{ label: '上舰感谢', value: TriggerType.GUARD },
|
||||
{ label: '关注感谢', value: TriggerType.FOLLOW },
|
||||
{ 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 = [
|
||||
{ label: '最高', value: Priority.HIGHEST },
|
||||
{ label: '高', value: Priority.HIGH },
|
||||
{ label: '普通', value: Priority.NORMAL },
|
||||
{ label: '低', value: Priority.LOW },
|
||||
{ label: '最低', value: Priority.LOWEST },
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="basic-settings-container">
|
||||
<NSpace
|
||||
vertical
|
||||
class="basic-settings"
|
||||
>
|
||||
<NSpace
|
||||
key="name"
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
class="setting-item"
|
||||
>
|
||||
<span>名称:</span>
|
||||
<NInput
|
||||
v-model:value="action.name"
|
||||
style="width: 300px"
|
||||
/>
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
key="enabled"
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
class="setting-item"
|
||||
>
|
||||
<span>启用:</span>
|
||||
<NSwitch v-model:value="action.enabled" />
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
key="only-during-live"
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
class="setting-item"
|
||||
>
|
||||
<span>仅直播中启用:</span>
|
||||
<NSwitch v-model:value="action.triggerConfig.onlyDuringLive" />
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
key="ignore-tianxuan"
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
class="setting-item"
|
||||
>
|
||||
<span>天选时刻忽略:</span>
|
||||
<NSwitch v-model:value="action.triggerConfig.ignoreTianXuan" />
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
key="triggerType"
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
class="setting-item"
|
||||
>
|
||||
<span>触发类型:</span>
|
||||
<NSelect
|
||||
v-model:value="action.triggerType"
|
||||
style="width: 300px"
|
||||
:options="triggerTypeOptions"
|
||||
disabled
|
||||
/>
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
key="actionType"
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
class="setting-item"
|
||||
>
|
||||
<span>操作类型:</span>
|
||||
<NSelect
|
||||
v-model:value="action.actionType"
|
||||
style="width: 300px"
|
||||
:options="actionTypeOptions"
|
||||
/>
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
key="priority"
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
class="setting-item"
|
||||
>
|
||||
<span>优先级:</span>
|
||||
<NSelect
|
||||
v-model:value="action.priority"
|
||||
style="width: 300px"
|
||||
:options="priorityOptions"
|
||||
/>
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
137
src/client/components/autoaction/settings/DanmakuSettings.vue
Normal file
137
src/client/components/autoaction/settings/DanmakuSettings.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { NSpace, NInput, NButton, NTag, NDivider, NCollapseItem, useMessage } from 'naive-ui';
|
||||
import { AutoActionItem, TriggerType } from '@/client/store/useAutoAction';
|
||||
|
||||
const props = defineProps({
|
||||
action: {
|
||||
type: Object as () => AutoActionItem,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const message = useMessage();
|
||||
|
||||
// 弹幕关键词相关
|
||||
const tempKeyword = ref('');
|
||||
const tempBlockword = ref('');
|
||||
|
||||
// 添加关键词
|
||||
function addKeyword() {
|
||||
if (!tempKeyword.value.trim()) return;
|
||||
|
||||
if (!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 = '';
|
||||
} else {
|
||||
message.warning('此关键词已存在');
|
||||
}
|
||||
}
|
||||
|
||||
// 移除关键词
|
||||
function removeKeyword(index: number) {
|
||||
if (props.action.triggerConfig.keywords) {
|
||||
props.action.triggerConfig.keywords.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加屏蔽词
|
||||
function addBlockword() {
|
||||
if (!tempBlockword.value.trim()) return;
|
||||
|
||||
if (!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 = '';
|
||||
} else {
|
||||
message.warning('此屏蔽词已存在');
|
||||
}
|
||||
}
|
||||
|
||||
// 移除屏蔽词
|
||||
function removeBlockword(index: number) {
|
||||
if (props.action.triggerConfig.blockwords) {
|
||||
props.action.triggerConfig.blockwords.splice(index, 1);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCollapseItem
|
||||
v-if="action.triggerType === TriggerType.DANMAKU"
|
||||
title="自动回复设置"
|
||||
>
|
||||
<NSpace vertical>
|
||||
<div class="section-title">
|
||||
触发关键词:
|
||||
</div>
|
||||
<NSpace>
|
||||
<NInput
|
||||
v-model:value="tempKeyword"
|
||||
placeholder="输入关键词"
|
||||
@keyup.enter="addKeyword"
|
||||
/>
|
||||
<NButton @click="addKeyword">
|
||||
添加
|
||||
</NButton>
|
||||
</NSpace>
|
||||
|
||||
<NSpace>
|
||||
<template v-if="action.triggerConfig.keywords">
|
||||
<NTag
|
||||
v-for="(keyword, index) in action.triggerConfig.keywords"
|
||||
:key="index"
|
||||
closable
|
||||
@close="removeKeyword(index)"
|
||||
>
|
||||
{{ keyword }}
|
||||
</NTag>
|
||||
</template>
|
||||
</NSpace>
|
||||
|
||||
<NDivider />
|
||||
|
||||
<div class="section-title">
|
||||
屏蔽词:
|
||||
</div>
|
||||
<NSpace>
|
||||
<NInput
|
||||
v-model:value="tempBlockword"
|
||||
placeholder="输入屏蔽词"
|
||||
@keyup.enter="addBlockword"
|
||||
/>
|
||||
<NButton @click="addBlockword">
|
||||
添加
|
||||
</NButton>
|
||||
</NSpace>
|
||||
|
||||
<NSpace>
|
||||
<template v-if="action.triggerConfig.blockwords">
|
||||
<NTag
|
||||
v-for="(blockword, index) in action.triggerConfig.blockwords"
|
||||
:key="index"
|
||||
closable
|
||||
type="warning"
|
||||
@close="removeBlockword(index)"
|
||||
>
|
||||
{{ blockword }}
|
||||
</NTag>
|
||||
</template>
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
</NCollapseItem>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.section-title {
|
||||
font-weight: bold;
|
||||
margin: 8px 0;
|
||||
}
|
||||
</style>
|
||||
65
src/client/components/autoaction/settings/EnterSettings.vue
Normal file
65
src/client/components/autoaction/settings/EnterSettings.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<script setup lang="ts">
|
||||
import { NSpace, NSwitch, NInputNumber, NSelect, NCollapseItem } from 'naive-ui';
|
||||
import { AutoActionItem, TriggerType } from '@/client/store/useAutoAction';
|
||||
|
||||
const props = defineProps({
|
||||
action: {
|
||||
type: Object as () => AutoActionItem,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
// 入场过滤模式选项
|
||||
const enterFilterModeOptions = [
|
||||
{ label: '不过滤', value: 'none' },
|
||||
{ label: '用户黑名单', value: 'blacklist' },
|
||||
{ label: '用户白名单', value: 'whitelist' },
|
||||
{ label: '仅舰长', value: 'guard' },
|
||||
{ label: '仅勋章', value: 'medal' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCollapseItem
|
||||
v-if="action.triggerType === TriggerType.ENTER"
|
||||
title="入场触发设置"
|
||||
>
|
||||
<NSpace vertical>
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>入场过滤模式:</span>
|
||||
<NSelect
|
||||
v-model:value="action.triggerConfig.enterFilterMode"
|
||||
style="width: 200px"
|
||||
:options="enterFilterModeOptions"
|
||||
/>
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>防止重复发送:</span>
|
||||
<NSwitch v-model:value="action.triggerConfig.preventRepeat" />
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>每次处理的最大用户数:</span>
|
||||
<NInputNumber
|
||||
v-model:value="action.actionConfig.maxUsersPerMsg"
|
||||
:min="1"
|
||||
:max="20"
|
||||
style="width: 120px"
|
||||
/>
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
</NCollapseItem>
|
||||
</template>
|
||||
43
src/client/components/autoaction/settings/FollowSettings.vue
Normal file
43
src/client/components/autoaction/settings/FollowSettings.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
import { NSpace, NSwitch, NInputNumber, NCollapseItem } from 'naive-ui';
|
||||
import { AutoActionItem, TriggerType } from '@/client/store/useAutoAction';
|
||||
|
||||
const props = defineProps({
|
||||
action: {
|
||||
type: Object as () => AutoActionItem,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCollapseItem
|
||||
v-if="action.triggerType === TriggerType.FOLLOW"
|
||||
title="关注触发设置"
|
||||
>
|
||||
<NSpace vertical>
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>防止重复发送:</span>
|
||||
<NSwitch v-model:value="action.triggerConfig.preventRepeat" />
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>每次处理的最大用户数:</span>
|
||||
<NInputNumber
|
||||
v-model:value="action.actionConfig.maxUsersPerMsg"
|
||||
:min="1"
|
||||
:max="20"
|
||||
style="width: 120px"
|
||||
/>
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
</NCollapseItem>
|
||||
</template>
|
||||
149
src/client/components/autoaction/settings/GiftSettings.vue
Normal file
149
src/client/components/autoaction/settings/GiftSettings.vue
Normal file
@@ -0,0 +1,149 @@
|
||||
<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';
|
||||
|
||||
const props = defineProps({
|
||||
action: {
|
||||
type: Object as () => AutoActionItem,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const message = useMessage();
|
||||
|
||||
// 礼物过滤模式选项
|
||||
const giftFilterModeOptions = [
|
||||
{ label: '不过滤', value: 'none' },
|
||||
{ label: '礼物黑名单', value: 'blacklist' },
|
||||
{ label: '礼物白名单', value: 'whitelist' },
|
||||
{ label: '最低价值', value: 'value' },
|
||||
{ label: '过滤免费礼物', value: 'free' }
|
||||
];
|
||||
|
||||
// 礼物名称相关
|
||||
const tempGiftName = ref('');
|
||||
|
||||
// 添加礼物名称到过滤列表
|
||||
function addGiftName() {
|
||||
if (!tempGiftName.value.trim()) return;
|
||||
|
||||
if (!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 = '';
|
||||
} else {
|
||||
message.warning('此礼物名称已存在');
|
||||
}
|
||||
}
|
||||
|
||||
// 移除礼物名称
|
||||
function removeGiftName(index: number) {
|
||||
if (props.action.triggerConfig.filterGiftNames) {
|
||||
props.action.triggerConfig.filterGiftNames.splice(index, 1);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCollapseItem
|
||||
v-if="action.triggerType === TriggerType.GIFT"
|
||||
title="礼物触发设置"
|
||||
>
|
||||
<NSpace vertical>
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>礼物过滤模式:</span>
|
||||
<NSelect
|
||||
v-model:value="action.triggerConfig.giftFilterMode"
|
||||
style="width: 200px"
|
||||
:options="giftFilterModeOptions"
|
||||
/>
|
||||
</NSpace>
|
||||
|
||||
<template v-if="action.triggerConfig.giftFilterMode === 'blacklist' || action.triggerConfig.giftFilterMode === 'whitelist'">
|
||||
<NSpace>
|
||||
<NInput
|
||||
v-model:value="tempGiftName"
|
||||
placeholder="输入礼物名称"
|
||||
@keyup.enter="addGiftName"
|
||||
/>
|
||||
<NButton @click="addGiftName">
|
||||
添加
|
||||
</NButton>
|
||||
</NSpace>
|
||||
|
||||
<NSpace>
|
||||
<template v-if="action.triggerConfig.filterGiftNames">
|
||||
<NTag
|
||||
v-for="(giftName, index) in action.triggerConfig.filterGiftNames"
|
||||
:key="index"
|
||||
closable
|
||||
@close="removeGiftName(index)"
|
||||
>
|
||||
{{ giftName }}
|
||||
</NTag>
|
||||
</template>
|
||||
</NSpace>
|
||||
</template>
|
||||
|
||||
<template v-if="action.triggerConfig.giftFilterMode === 'value'">
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>最低价值 (元):</span>
|
||||
<NInputNumber
|
||||
v-model:value="action.triggerConfig.minValue"
|
||||
:min="0"
|
||||
style="width: 120px"
|
||||
/>
|
||||
</NSpace>
|
||||
</template>
|
||||
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>包含礼物数量:</span>
|
||||
<NSwitch v-model:value="action.triggerConfig.includeQuantity" />
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>每次处理的最大用户数:</span>
|
||||
<NInputNumber
|
||||
v-model:value="action.actionConfig.maxUsersPerMsg"
|
||||
:min="1"
|
||||
:max="20"
|
||||
style="width: 120px"
|
||||
/>
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>每用户最大礼物种类数:</span>
|
||||
<NInputNumber
|
||||
v-model:value="action.actionConfig.maxItemsPerUser"
|
||||
:min="1"
|
||||
:max="10"
|
||||
style="width: 120px"
|
||||
/>
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
</NCollapseItem>
|
||||
</template>
|
||||
152
src/client/components/autoaction/settings/GuardSettings.vue
Normal file
152
src/client/components/autoaction/settings/GuardSettings.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<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';
|
||||
|
||||
const props = defineProps({
|
||||
action: {
|
||||
type: Object as () => AutoActionItem,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const message = useMessage();
|
||||
|
||||
// 舰长礼品码相关
|
||||
const tempGiftCodeLevel = ref(3); // 默认为舰长等级
|
||||
const tempGiftCode = ref('');
|
||||
|
||||
// 添加礼品码
|
||||
function addGiftCode() {
|
||||
if (!tempGiftCode.value.trim()) return;
|
||||
|
||||
if (!props.action.triggerConfig.giftCodes) {
|
||||
props.action.triggerConfig.giftCodes = [];
|
||||
}
|
||||
|
||||
// 查找对应等级的礼品码数组
|
||||
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);
|
||||
}
|
||||
|
||||
// 添加礼品码
|
||||
if (!levelCodes.codes.includes(tempGiftCode.value.trim())) {
|
||||
levelCodes.codes.push(tempGiftCode.value.trim());
|
||||
tempGiftCode.value = '';
|
||||
} else {
|
||||
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[levelIndex].codes.length === 0) {
|
||||
props.action.triggerConfig.giftCodes.splice(levelIndex, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 舰长等级名称映射
|
||||
function getGuardLevelName(level: number): string {
|
||||
switch (level) {
|
||||
case 1: return '总督';
|
||||
case 2: return '提督';
|
||||
case 3: return '舰长';
|
||||
default: return '通用';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCollapseItem
|
||||
v-if="action.triggerType === TriggerType.GUARD"
|
||||
title="上舰触发设置"
|
||||
>
|
||||
<NSpace vertical>
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>防止重复发送:</span>
|
||||
<NSwitch v-model:value="action.triggerConfig.preventRepeat" />
|
||||
</NSpace>
|
||||
|
||||
<template v-if="action.actionType === ActionType.SEND_PRIVATE_MSG">
|
||||
<NDivider title-placement="left">
|
||||
礼品码设置
|
||||
</NDivider>
|
||||
|
||||
<NSpace>
|
||||
<NSelect
|
||||
v-model:value="tempGiftCodeLevel"
|
||||
style="width: 120px"
|
||||
:options="[
|
||||
{ label: '总督', value: 1 },
|
||||
{ label: '提督', value: 2 },
|
||||
{ label: '舰长', value: 3 },
|
||||
{ label: '通用', value: 0 }
|
||||
]"
|
||||
/>
|
||||
<NInput
|
||||
v-model:value="tempGiftCode"
|
||||
placeholder="输入礼品码"
|
||||
@keyup.enter="addGiftCode"
|
||||
/>
|
||||
<NButton @click="addGiftCode">
|
||||
添加
|
||||
</NButton>
|
||||
</NSpace>
|
||||
|
||||
<div v-if="action.triggerConfig.giftCodes && action.triggerConfig.giftCodes.length > 0">
|
||||
<div
|
||||
v-for="(levelCodes, levelIndex) in action.triggerConfig.giftCodes"
|
||||
:key="levelIndex"
|
||||
class="gift-code-section"
|
||||
>
|
||||
<div class="gift-code-level">
|
||||
{{ getGuardLevelName(levelCodes.level) }}礼品码:
|
||||
</div>
|
||||
<div class="gift-code-list">
|
||||
<NTag
|
||||
v-for="(code, codeIndex) in levelCodes.codes"
|
||||
:key="codeIndex"
|
||||
closable
|
||||
@close="removeGiftCode(levelIndex, codeIndex)"
|
||||
>
|
||||
{{ code }}
|
||||
</NTag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</NSpace>
|
||||
</NCollapseItem>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.gift-code-section {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.gift-code-level {
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.gift-code-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,60 @@
|
||||
<script setup lang="ts">
|
||||
import { NSpace, NInputNumber, NRadioGroup, NRadio, NCollapseItem } from 'naive-ui';
|
||||
import { AutoActionItem, TriggerType } from '@/client/store/useAutoAction';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
action: {
|
||||
type: Object as () => AutoActionItem,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
// 定时模式选项
|
||||
const schedulingModeOptions = [
|
||||
{ label: '随机模式', value: 'random' },
|
||||
{ label: '顺序模式', value: 'sequential' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCollapseItem
|
||||
v-if="action.triggerType === TriggerType.SCHEDULED"
|
||||
title="定时触发设置"
|
||||
>
|
||||
<NSpace vertical>
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>发送间隔 (秒):</span>
|
||||
<NInputNumber
|
||||
v-model:value="action.triggerConfig.intervalSeconds"
|
||||
:min="60"
|
||||
:max="3600"
|
||||
style="width: 120px"
|
||||
/>
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>发送模式:</span>
|
||||
<NRadioGroup v-model:value="action.triggerConfig.schedulingMode">
|
||||
<NSpace>
|
||||
<NRadio
|
||||
v-for="option in schedulingModeOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.label }}
|
||||
</NRadio>
|
||||
</NSpace>
|
||||
</NRadioGroup>
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
</NCollapseItem>
|
||||
</template>
|
||||
@@ -0,0 +1,77 @@
|
||||
<script setup lang="ts">
|
||||
import { NSpace, NSwitch, NInputNumber, NSelect, NCollapseItem } from 'naive-ui';
|
||||
import { AutoActionItem, TriggerType } from '@/client/store/useAutoAction';
|
||||
|
||||
const props = defineProps({
|
||||
action: {
|
||||
type: Object as () => AutoActionItem,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
// SC过滤模式选项
|
||||
const scFilterModeOptions = [
|
||||
{ label: '不过滤', value: 'none' },
|
||||
{ label: '最低价格', value: 'price' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCollapseItem
|
||||
v-if="action.triggerType === TriggerType.SUPER_CHAT"
|
||||
title="SC触发设置"
|
||||
>
|
||||
<NSpace vertical>
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>SC过滤模式:</span>
|
||||
<NSelect
|
||||
v-model:value="action.triggerConfig.scFilterMode"
|
||||
style="width: 200px"
|
||||
:options="scFilterModeOptions"
|
||||
/>
|
||||
</NSpace>
|
||||
|
||||
<template v-if="action.triggerConfig.scFilterMode === 'price'">
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>最低价格 (元):</span>
|
||||
<NInputNumber
|
||||
v-model:value="action.triggerConfig.minPrice"
|
||||
:min="0"
|
||||
style="width: 120px"
|
||||
/>
|
||||
</NSpace>
|
||||
</template>
|
||||
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>防止重复发送:</span>
|
||||
<NSwitch v-model:value="action.triggerConfig.preventRepeat" />
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>每次处理的最大用户数:</span>
|
||||
<NInputNumber
|
||||
v-model:value="action.actionConfig.maxUsersPerMsg"
|
||||
:min="1"
|
||||
:max="20"
|
||||
style="width: 120px"
|
||||
/>
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
</NCollapseItem>
|
||||
</template>
|
||||
136
src/client/components/autoaction/settings/TemplateSettings.vue
Normal file
136
src/client/components/autoaction/settings/TemplateSettings.vue
Normal file
@@ -0,0 +1,136 @@
|
||||
<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';
|
||||
|
||||
const props = defineProps({
|
||||
action: {
|
||||
type: Object as () => AutoActionItem,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
// 模板变量占位符选项,根据触发类型动态生成
|
||||
const placeholders = computed(() => {
|
||||
const commonPlaceholders = [
|
||||
{ name: '{{user.name}}', description: '用户名称' },
|
||||
{ name: '{{user.uid}}', description: '用户ID' },
|
||||
{ name: '{{date.formatted}}', description: '当前日期时间' },
|
||||
{ name: '{{timeOfDay()}}', description: '当前时段(早上/下午/晚上)' },
|
||||
];
|
||||
|
||||
let specificPlaceholders: { name: string, description: string }[] = [];
|
||||
|
||||
switch (props.action.triggerType) {
|
||||
case TriggerType.GIFT:
|
||||
specificPlaceholders = [
|
||||
{ name: '{{gift.name}}', description: '礼物名称' },
|
||||
{ name: '{{gift.count}}', description: '礼物数量' },
|
||||
{ name: '{{gift.price}}', description: '礼物单价' },
|
||||
{ name: '{{gift.totalPrice}}', description: '礼物总价值' },
|
||||
{ name: '{{gift.summary}}', description: '礼物摘要(如:5个辣条)' },
|
||||
];
|
||||
break;
|
||||
case TriggerType.GUARD:
|
||||
specificPlaceholders = [
|
||||
{ name: '{{guard.level}}', description: '舰长等级' },
|
||||
{ name: '{{guard.levelName}}', description: '舰长等级名称' },
|
||||
{ name: '{{guard.giftCode}}', description: '礼品码(如已配置)' },
|
||||
];
|
||||
break;
|
||||
case TriggerType.SUPER_CHAT:
|
||||
specificPlaceholders = [
|
||||
{ name: '{{sc.message}}', description: 'SC消息内容' },
|
||||
{ name: '{{sc.price}}', description: 'SC价格' },
|
||||
];
|
||||
break;
|
||||
case TriggerType.FOLLOW:
|
||||
specificPlaceholders = [
|
||||
{ name: '{{follow.time}}', description: '关注时间' },
|
||||
{ name: '{{follow.isNew}}', description: '是否新关注' },
|
||||
];
|
||||
break;
|
||||
case TriggerType.ENTER:
|
||||
specificPlaceholders = [
|
||||
{ name: '{{enter.time}}', description: '入场时间' },
|
||||
{ name: '{{enter.guardLevel}}', description: '舰长等级' },
|
||||
{ name: '{{enter.medalName}}', description: '勋章名称' },
|
||||
{ name: '{{enter.medalLevel}}', description: '勋章等级' },
|
||||
];
|
||||
break;
|
||||
}
|
||||
|
||||
return [...commonPlaceholders, ...specificPlaceholders];
|
||||
});
|
||||
|
||||
// 根据操作类型获取模板标题
|
||||
const templateTitle = computed(() => {
|
||||
switch (props.action.actionType) {
|
||||
case ActionType.SEND_DANMAKU:
|
||||
return '弹幕模板';
|
||||
case ActionType.SEND_PRIVATE_MSG:
|
||||
return '私信模板';
|
||||
case ActionType.EXECUTE_COMMAND:
|
||||
return '命令模板';
|
||||
default:
|
||||
return '消息模板';
|
||||
}
|
||||
});
|
||||
|
||||
// 根据操作类型获取模板描述
|
||||
const templateDescription = computed(() => {
|
||||
switch (props.action.actionType) {
|
||||
case ActionType.SEND_DANMAKU:
|
||||
return '发送到直播间的弹幕内容';
|
||||
case ActionType.SEND_PRIVATE_MSG:
|
||||
return '发送给用户的私信内容';
|
||||
case ActionType.EXECUTE_COMMAND:
|
||||
return '执行的命令模板';
|
||||
default:
|
||||
return '消息内容模板';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="template-settings">
|
||||
<transition
|
||||
name="fade-scale"
|
||||
appear
|
||||
>
|
||||
<TemplateEditor
|
||||
:templates="action.templates"
|
||||
:placeholders="placeholders"
|
||||
:title="templateTitle"
|
||||
:description="templateDescription"
|
||||
class="template-editor"
|
||||
/>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.template-settings {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.settings-divider {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.template-editor {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* 淡入缩放效果 */
|
||||
.fade-scale-enter-active,
|
||||
.fade-scale-leave-active {
|
||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
.fade-scale-enter-from,
|
||||
.fade-scale-leave-to {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user