feat: 优化弹幕动效, 开始自动操作编写

This commit is contained in:
2025-04-18 02:34:20 +08:00
parent d53295bb0c
commit 5891f20f86
29 changed files with 2528 additions and 184 deletions

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>