mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-07 02:46:55 +08:00
feat: 优化弹幕动效, 开始自动操作编写
This commit is contained in:
313
src/client/components/autoaction/AutoReplyConfig.vue
Normal file
313
src/client/components/autoaction/AutoReplyConfig.vue
Normal file
@@ -0,0 +1,313 @@
|
||||
<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>
|
||||
120
src/client/components/autoaction/CommonConfigItems.vue
Normal file
120
src/client/components/autoaction/CommonConfigItems.vue
Normal file
@@ -0,0 +1,120 @@
|
||||
<script setup lang="ts">
|
||||
import { NSpace, NSwitch, NInputNumber, NSelect, NCheckbox, NDivider } from 'naive-ui';
|
||||
|
||||
defineProps({
|
||||
config: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
showLiveOnly: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showDelay: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showUserFilter: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showTianXuan: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="common-config-section">
|
||||
<NSpace
|
||||
vertical
|
||||
size="medium"
|
||||
>
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>启用功能:</span>
|
||||
<NSwitch v-model:value="config.enabled" />
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
v-if="showLiveOnly"
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>仅直播中开启:</span>
|
||||
<NSwitch v-model:value="config.onlyDuringLive" />
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
v-if="showDelay"
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>延迟时间 (秒):</span>
|
||||
<NInputNumber
|
||||
v-model:value="config.delaySeconds"
|
||||
:min="0"
|
||||
:max="300"
|
||||
style="width: 120px"
|
||||
/>
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
v-if="showTianXuan"
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>屏蔽天选时刻:</span>
|
||||
<NSwitch v-model:value="config.ignoreTianXuan" />
|
||||
</NSpace>
|
||||
|
||||
<template v-if="showUserFilter">
|
||||
<NDivider title-placement="left">
|
||||
用户过滤设置
|
||||
</NDivider>
|
||||
|
||||
<NSpace
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>启用用户过滤:</span>
|
||||
<NSwitch v-model:value="config.userFilterEnabled" />
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
v-if="config.userFilterEnabled"
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>要求本房间勋章:</span>
|
||||
<NSwitch v-model:value="config.requireMedal" />
|
||||
</NSpace>
|
||||
|
||||
<NSpace
|
||||
v-if="config.userFilterEnabled"
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>要求任意舰长:</span>
|
||||
<NSwitch v-model:value="config.requireCaptain" />
|
||||
</NSpace>
|
||||
</template>
|
||||
</NSpace>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.common-config-section {
|
||||
padding: 16px 0;
|
||||
}
|
||||
</style>
|
||||
59
src/client/components/autoaction/EntryWelcomeConfig.vue
Normal file
59
src/client/components/autoaction/EntryWelcomeConfig.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<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>
|
||||
59
src/client/components/autoaction/FollowThankConfig.vue
Normal file
59
src/client/components/autoaction/FollowThankConfig.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<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>
|
||||
161
src/client/components/autoaction/GiftThankConfig.vue
Normal file
161
src/client/components/autoaction/GiftThankConfig.vue
Normal file
@@ -0,0 +1,161 @@
|
||||
<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>
|
||||
223
src/client/components/autoaction/GuardPmConfig.vue
Normal file
223
src/client/components/autoaction/GuardPmConfig.vue
Normal file
@@ -0,0 +1,223 @@
|
||||
<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>
|
||||
78
src/client/components/autoaction/ScheduledDanmakuConfig.vue
Normal file
78
src/client/components/autoaction/ScheduledDanmakuConfig.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<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>
|
||||
167
src/client/components/autoaction/TemplateEditor.vue
Normal file
167
src/client/components/autoaction/TemplateEditor.vue
Normal file
@@ -0,0 +1,167 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { NInput, NInputNumber, NButton, NSpace, NCard, NDivider, NList, NListItem, NPopconfirm, NTooltip } from 'naive-ui';
|
||||
|
||||
const props = defineProps({
|
||||
templates: {
|
||||
type: Array as () => string[],
|
||||
required: true
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '模板编辑'
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
placeholders: {
|
||||
type: Array as () => { name: string, description: string }[],
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
|
||||
// 添加默认的弹幕相关占位符
|
||||
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: '表情符号' }
|
||||
];
|
||||
|
||||
// 返回自定义占位符和默认占位符,但不合并它们
|
||||
return { custom: props.placeholders, default: defaultPlaceholders };
|
||||
});
|
||||
|
||||
const newTemplate = ref('');
|
||||
|
||||
function addTemplate() {
|
||||
if (newTemplate.value.trim()) {
|
||||
props.templates.push(newTemplate.value.trim());
|
||||
newTemplate.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function removeTemplate(index: number) {
|
||||
props.templates.splice(index, 1);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard
|
||||
:title="title"
|
||||
size="small"
|
||||
>
|
||||
<template
|
||||
v-if="mergedPlaceholders.custom.length > 0 || mergedPlaceholders.default.length > 0"
|
||||
#header-extra
|
||||
>
|
||||
<NTooltip
|
||||
trigger="hover"
|
||||
placement="top"
|
||||
>
|
||||
<template #trigger>
|
||||
<NButton
|
||||
quaternary
|
||||
size="small"
|
||||
>
|
||||
变量说明
|
||||
</NButton>
|
||||
</template>
|
||||
<div style="max-width: 300px">
|
||||
<div
|
||||
v-for="(ph, idx) in mergedPlaceholders.custom"
|
||||
:key="'custom-' + 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>
|
||||
</NTooltip>
|
||||
</template>
|
||||
|
||||
<p
|
||||
v-if="description"
|
||||
class="template-description"
|
||||
>
|
||||
{{ description }}
|
||||
</p>
|
||||
|
||||
<NList bordered>
|
||||
<NListItem
|
||||
v-for="(template, index) in templates"
|
||||
:key="index"
|
||||
>
|
||||
<NSpace
|
||||
justify="space-between"
|
||||
align="center"
|
||||
style="width: 100%"
|
||||
>
|
||||
<span>{{ template }}</span>
|
||||
<NPopconfirm @positive-click="removeTemplate(index)">
|
||||
<template #trigger>
|
||||
<NButton
|
||||
size="small"
|
||||
quaternary
|
||||
type="error"
|
||||
>
|
||||
删除
|
||||
</NButton>
|
||||
</template>
|
||||
确定要删除此模板吗?
|
||||
</NPopconfirm>
|
||||
</NSpace>
|
||||
</NListItem>
|
||||
</NList>
|
||||
|
||||
<NDivider />
|
||||
|
||||
<NSpace vertical>
|
||||
<NInput
|
||||
v-model:value="newTemplate"
|
||||
placeholder="输入新模板内容"
|
||||
clearable
|
||||
/>
|
||||
<NButton
|
||||
type="primary"
|
||||
block
|
||||
@click="addTemplate"
|
||||
>
|
||||
添加模板
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.template-description {
|
||||
margin-bottom: 16px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user