e48950d260
* fix: localize provider source ui * feat: localize provider metadata keys * chore: add provider metadata translations * chore: format provider i18n changes * fix: preserve metadata fields in i18n conversion * fix: internationalize platform config and dialog * fix: add Weixin official account platform icon --------- Co-authored-by: Soulter <905617992@qq.com>
1067 lines
38 KiB
Vue
1067 lines
38 KiB
Vue
<template>
|
||
<v-dialog v-model="showDialog" max-width="800px" height="90%" @after-enter="prepareData">
|
||
<v-card
|
||
:title="updatingMode ? `${tm('dialog.edit')} ${updatingPlatformConfig.id} ${tm('dialog.adapter')}` : tm('dialog.addPlatform')">
|
||
<v-card-text ref="dialogScrollContainer" class="pa-4 ml-2" style="overflow-y: auto;">
|
||
<div class="d-flex align-start" style="width: 100%;">
|
||
<div>
|
||
<v-icon icon="mdi-numeric-1-circle" class="mr-3"></v-icon>
|
||
</div>
|
||
<div style="flex: 1;">
|
||
<h3>
|
||
{{ tm('createDialog.step1Title') }}
|
||
</h3>
|
||
<small style="color: grey;">{{ tm('createDialog.step1Hint') }}</small>
|
||
<div>
|
||
|
||
<div v-if="!updatingMode">
|
||
<v-select v-model="selectedPlatformType" :items="Object.keys(platformTemplates)" item-title="name"
|
||
item-value="name" :label="tm('createDialog.platformTypeLabel')" variant="outlined" rounded="md" dense hide-details class="mt-6"
|
||
style="max-width: 30%; min-width: 300px;">
|
||
|
||
<template v-slot:item="{ props: itemProps, item }">
|
||
<v-list-item v-bind="itemProps">
|
||
<template v-slot:prepend>
|
||
<img :src="getPlatformIcon(platformTemplates[item.raw].type)"
|
||
style="width: 32px; height: 32px; object-fit: contain; margin-right: 16px;" />
|
||
</template>
|
||
</v-list-item>
|
||
</template>
|
||
|
||
</v-select>
|
||
<div class="mt-3" v-if="selectedPlatformConfig">
|
||
<v-btn color="info" variant="tonal" @click="openTutorial" class="mt-2">
|
||
<v-icon start>mdi-book-open-variant</v-icon>
|
||
{{ tm('dialog.viewTutorial') }}
|
||
</v-btn>
|
||
<div class="mt-2">
|
||
<AstrBotConfig :iterable="selectedPlatformConfig" :metadata="metadata['platform_group']?.metadata"
|
||
metadataKey="platform" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div v-else>
|
||
<v-text-field :label="tm('createDialog.platformTypeLabel')" variant="outlined" rounded="md" dense hide-details class="mt-6"
|
||
style="max-width: 30%; min-width: 300px;" v-model="updatingPlatformConfig.type"
|
||
disabled></v-text-field>
|
||
<div class="mt-3">
|
||
<div class="mt-2">
|
||
<AstrBotConfig :iterable="updatingPlatformConfig" :metadata="metadata['platform_group']?.metadata"
|
||
metadataKey="platform" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="d-flex align-start mt-6">
|
||
<div>
|
||
<v-icon icon="mdi-numeric-2-circle" class="mr-3"></v-icon>
|
||
</div>
|
||
<div style="flex: 1;">
|
||
<div class="d-flex align-center justify-space-between">
|
||
<div>
|
||
<div class="d-flex align-center">
|
||
<h3>
|
||
{{ tm('createDialog.configFileTitle') }}
|
||
</h3>
|
||
<v-chip size="x-small" color="primary" variant="tonal" rounded="sm" class="ml-2"
|
||
v-if="!updatingMode">{{ tm('createDialog.optional') }}</v-chip>
|
||
</div>
|
||
<small style="color: grey;">{{ tm('createDialog.configHint') }}</small>
|
||
<small style="color: grey;" v-if="!updatingMode">{{ tm('createDialog.configDefaultHint') }}</small>
|
||
</div>
|
||
<div>
|
||
<v-btn variant="plain" icon @click="toggleConfigSection" class="mt-2">
|
||
<v-icon>{{ showConfigSection ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
|
||
</v-btn>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div v-if="showConfigSection">
|
||
<div v-if="!updatingMode">
|
||
<v-radio-group class="mt-2" v-model="aBConfigRadioVal" hide-details="true">
|
||
<v-radio value="0">
|
||
<template v-slot:label>
|
||
<span>{{ tm('createDialog.useExistingConfig') }}</span>
|
||
</template>
|
||
</v-radio>
|
||
<div class="d-flex align-center ml-10 my-2" v-if="aBConfigRadioVal === '0'">
|
||
<v-select v-model="selectedAbConfId" :items="configInfoList" item-title="name"
|
||
item-value="id" :label="tm('createDialog.selectConfigLabel')" variant="outlined" rounded="md" dense hide-details
|
||
style="max-width: 30%; min-width: 200px;">
|
||
</v-select>
|
||
<v-btn icon variant="text" density="comfortable" class="ml-2"
|
||
:disabled="!selectedAbConfId" @click="openConfigDrawer(selectedAbConfId)">
|
||
<v-icon>mdi-arrow-top-right-thick</v-icon>
|
||
</v-btn>
|
||
</div>
|
||
<v-radio value="1" :label="tm('createDialog.createNewConfig')">
|
||
</v-radio>
|
||
<div class="d-flex align-center" v-if="aBConfigRadioVal === '1'">
|
||
<v-text-field v-model="selectedAbConfId" :label="tm('createDialog.newConfigNameLabel')" variant="outlined" rounded="md" dense
|
||
hide-details style="max-width: 30%; min-width: 200px;" class="ml-10 my-2">
|
||
</v-text-field>
|
||
</div>
|
||
|
||
</v-radio-group>
|
||
|
||
<!-- 现有配置文件预览区域 -->
|
||
<!-- <div v-if="aBConfigRadioVal === '0' && selectedAbConfId" class="mt-4">
|
||
<div v-if="configPreviewLoading" class="d-flex justify-center py-4">
|
||
<v-progress-circular indeterminate color="primary"></v-progress-circular>
|
||
</div>
|
||
<div v-else-if="selectedConfigData && selectedConfigMetadata" class="config-preview-container">
|
||
<h4 class="mb-3">配置文件预览</h4>
|
||
<AstrBotCoreConfigWrapper :metadata="selectedConfigMetadata" :config_data="selectedConfigData"
|
||
readonly="true" />
|
||
</div>
|
||
<div v-else class="text-center py-4 text-grey">
|
||
<v-icon>mdi-information-outline</v-icon>
|
||
<p class="mt-2">无法加载配置文件预览</p>
|
||
</div>
|
||
</div> -->
|
||
|
||
<!-- 新配置文件编辑区域 -->
|
||
<div v-if="aBConfigRadioVal === '1'" class="mt-4">
|
||
<div v-if="newConfigLoading" class="d-flex justify-center py-4">
|
||
<v-progress-circular indeterminate color="primary"></v-progress-circular>
|
||
</div>
|
||
<div v-else-if="newConfigData && newConfigMetadata" class="config-preview-container">
|
||
<h4 class="mb-3">{{ tm('createDialog.newConfigTitle') }}</h4>
|
||
<AstrBotCoreConfigWrapper :metadata="newConfigMetadata" :config_data="newConfigData" />
|
||
</div>
|
||
<div v-else class="text-center py-4 text-grey">
|
||
<v-icon>mdi-information-outline</v-icon>
|
||
<p class="mt-2">{{ tm('createDialog.newConfigLoadFailed') }}</p>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div v-else>
|
||
<div class="mb-3 d-flex align-center justify-space-between">
|
||
<div>
|
||
<v-btn v-if="isEditingRoutes" color="primary" variant="tonal" @click="addNewRoute" size="small">
|
||
<v-icon start>mdi-plus</v-icon>
|
||
{{ tm('createDialog.addRouteRule') }}
|
||
</v-btn>
|
||
</div>
|
||
<v-btn :color="isEditingRoutes ? 'grey' : 'primary'" variant="tonal" size="small"
|
||
@click="toggleEditMode">
|
||
<v-icon start>{{ isEditingRoutes ? 'mdi-eye' : 'mdi-pencil' }}</v-icon>
|
||
{{ isEditingRoutes ? tm('createDialog.viewMode') : tm('createDialog.editMode') }}
|
||
</v-btn>
|
||
</div>
|
||
|
||
<v-data-table :headers="routeTableHeaders" :items="platformRoutes" item-value="umop"
|
||
:no-data-text="tm('createDialog.noRouteRules')" hide-default-footer :items-per-page="-1" class="mt-2"
|
||
variant="outlined">
|
||
|
||
<template v-slot:item.source="{ item }">
|
||
<div class="d-flex align-center" style="min-width: 250px;">
|
||
<v-select v-if="isEditingRoutes" v-model="item.messageType" :items="messageTypeOptions"
|
||
item-title="label" item-value="value" variant="outlined" density="compact" hide-details
|
||
style="max-width: 140px;">
|
||
</v-select>
|
||
<small v-else>{{ getMessageTypeLabel(item.messageType) }}</small>
|
||
<small class="mx-1">:</small>
|
||
<v-text-field v-if="isEditingRoutes" v-model="item.sessionId" variant="outlined" density="compact"
|
||
hide-details :placeholder="tm('createDialog.sessionIdPlaceholder')">
|
||
</v-text-field>
|
||
<small v-else>{{ item.sessionId === '*' ? tm('createDialog.allSessions') : item.sessionId }}</small>
|
||
</div>
|
||
</template>
|
||
|
||
<template v-slot:item.configId="{ item }">
|
||
<div class="d-flex align-center">
|
||
<v-select v-if="isEditingRoutes" v-model="item.configId" :items="configInfoList"
|
||
item-title="name" item-value="id" variant="outlined" density="compact"
|
||
style="min-width: 200px;" hide-details>
|
||
</v-select>
|
||
<div v-else>
|
||
<small>{{ getConfigName(item.configId) }}</small>
|
||
</div>
|
||
<v-btn icon variant="text" density="compact" class="ml-2"
|
||
:disabled="!item.configId" @click="openConfigDrawer(item.configId)">
|
||
<v-icon size="18">mdi-arrow-top-right-thick</v-icon>
|
||
</v-btn>
|
||
</div>
|
||
<small v-if="configInfoList.findIndex(c => c.id === item.configId) === -1" style="color: red;"
|
||
class="ml-2">{{ tm('createDialog.configMissing') }}</small>
|
||
</template>
|
||
|
||
<template v-slot:item.actions="{ item, index }">
|
||
<div v-if="isEditingRoutes" class="d-flex align-center">
|
||
<v-btn icon size="x-small" variant="text" @click="moveRouteUp(index)" :disabled="index === 0">
|
||
<v-icon>mdi-arrow-up</v-icon>
|
||
</v-btn>
|
||
<v-btn icon size="x-small" variant="text" @click="moveRouteDown(index)"
|
||
:disabled="index === platformRoutes.length - 1">
|
||
<v-icon>mdi-arrow-down</v-icon>
|
||
</v-btn>
|
||
<v-btn icon size="x-small" variant="text" color="error" @click="deleteRoute(index)">
|
||
<v-icon>mdi-delete</v-icon>
|
||
</v-btn>
|
||
</div>
|
||
<span v-else class="text-grey">-</span>
|
||
</template>
|
||
|
||
</v-data-table>
|
||
<small class="ml-2 mt-2 d-block" style="color: grey">{{ tm('createDialog.routeHint') }}</small>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
</div>
|
||
</div>
|
||
|
||
</v-card-text>
|
||
|
||
<v-card-actions>
|
||
<v-spacer></v-spacer>
|
||
<v-btn text @click="closeDialog">{{ tm('dialog.cancel') }}</v-btn>
|
||
<v-btn :disabled="!canSave" color="primary" v-if="!updatingMode" @click="newPlatform" :loading="loading">{{
|
||
tm('dialog.save') }}</v-btn>
|
||
<v-btn :disabled="!selectedAbConfId" color="primary" v-else @click="newPlatform" :loading="loading">{{
|
||
tm('dialog.save') }}</v-btn>
|
||
</v-card-actions>
|
||
</v-card>
|
||
</v-dialog>
|
||
|
||
<!-- ID冲突确认对话框 -->
|
||
<v-dialog v-model="showIdConflictDialog" max-width="450" persistent>
|
||
<v-card>
|
||
<v-card-title class="text-h6 bg-warning d-flex align-center">
|
||
<v-icon start class="me-2">mdi-alert-circle-outline</v-icon>
|
||
{{ tm('dialog.idConflict.title') }}
|
||
</v-card-title>
|
||
<v-card-text class="py-4 text-body-1 text-medium-emphasis">
|
||
{{ tm('dialog.idConflict.message', { id: conflictId }) }}
|
||
</v-card-text>
|
||
<v-card-actions>
|
||
<v-spacer></v-spacer>
|
||
<v-btn color="grey" variant="text" @click="handleIdConflictConfirm(false)">{{ tm('dialog.idConflict.confirm')
|
||
}}</v-btn>
|
||
</v-card-actions>
|
||
</v-card>
|
||
</v-dialog>
|
||
|
||
<!-- 安全警告对话框 -->
|
||
<v-dialog v-model="showOneBotEmptyTokenWarnDialog" max-width="600" persistent>
|
||
<v-card>
|
||
<v-card-title>
|
||
{{ tm('dialog.securityWarning.title') }}
|
||
</v-card-title>
|
||
<v-card-text class="py-4">
|
||
<p>{{ tm('dialog.securityWarning.aiocqhttpTokenMissing') }}</p>
|
||
<span><a
|
||
href="https://docs.astrbot.app/deploy/platform/aiocqhttp/napcat.html#%E9%99%84%E5%BD%95-%E5%A2%9E%E5%BC%BA%E8%BF%9E%E6%8E%A5%E5%AE%89%E5%85%A8%E6%80%A7"
|
||
target="_blank">{{ tm('dialog.securityWarning.learnMore') }}</a></span>
|
||
</v-card-text>
|
||
<v-card-actions class="px-4 pb-4">
|
||
<v-spacer></v-spacer>
|
||
<v-btn color="error" @click="handleOneBotEmptyTokenWarningDismiss(true)">
|
||
{{ tm('createDialog.warningContinue') }}
|
||
</v-btn>
|
||
<v-btn color="primary" @click="handleOneBotEmptyTokenWarningDismiss(false)">
|
||
{{ tm('createDialog.warningEditAgain') }}
|
||
</v-btn>
|
||
</v-card-actions>
|
||
</v-card>
|
||
</v-dialog>
|
||
|
||
<v-overlay
|
||
v-model="showConfigDrawer"
|
||
class="config-drawer-overlay"
|
||
location="right"
|
||
transition="slide-x-reverse-transition"
|
||
:scrim="true"
|
||
@click:outside="closeConfigDrawer"
|
||
>
|
||
<v-card class="config-drawer-card" elevation="12">
|
||
<div class="config-drawer-header">
|
||
<div>
|
||
<span class="text-h6">{{ tm('createDialog.configDrawerTitle') }}</span>
|
||
<div v-if="configDrawerTargetId" class="text-caption text-grey">
|
||
{{ tm('createDialog.configDrawerIdLabel') }}: {{ configDrawerTargetId }}
|
||
</div>
|
||
</div>
|
||
<v-btn icon variant="text" @click="closeConfigDrawer">
|
||
<v-icon>mdi-close</v-icon>
|
||
</v-btn>
|
||
</div>
|
||
<v-divider></v-divider>
|
||
<div class="config-drawer-content">
|
||
<ConfigPage v-if="showConfigDrawer" :initial-config-id="configDrawerTargetId" />
|
||
</div>
|
||
</v-card>
|
||
</v-overlay>
|
||
</template>
|
||
|
||
|
||
<script>
|
||
import axios from 'axios';
|
||
import { useModuleI18n } from '@/i18n/composables';
|
||
import { getPlatformIcon, getPlatformDescription, getTutorialLink } from '@/utils/platformUtils';
|
||
import AstrBotConfig from '@/components/shared/AstrBotConfig.vue';
|
||
import AstrBotCoreConfigWrapper from '@/components/config/AstrBotCoreConfigWrapper.vue';
|
||
import ConfigPage from '@/views/ConfigPage.vue';
|
||
|
||
export default {
|
||
name: 'AddNewPlatform',
|
||
components: { AstrBotConfig, AstrBotCoreConfigWrapper, ConfigPage },
|
||
emits: ['update:show', 'show-toast', 'refresh-config'],
|
||
props: {
|
||
show: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
metadata: {
|
||
type: Object,
|
||
default: () => ({})
|
||
},
|
||
config_data: {
|
||
type: Object,
|
||
default: () => ({})
|
||
},
|
||
updatingMode: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
updatingPlatformConfig: {
|
||
type: Object,
|
||
default: null
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
selectedPlatformType: null,
|
||
selectedPlatformConfig: null,
|
||
|
||
aBConfigRadioVal: '0',
|
||
selectedAbConfId: 'default',
|
||
configInfoList: [],
|
||
|
||
// 选中的配置文件预览数据
|
||
selectedConfigData: null,
|
||
selectedConfigMetadata: null,
|
||
configPreviewLoading: false,
|
||
|
||
// 新配置文件相关数据
|
||
newConfigData: null,
|
||
newConfigMetadata: null,
|
||
newConfigLoading: false,
|
||
|
||
// 平台配置文件表格(已弃用,改用 platformRoutes)
|
||
platformConfigs: [],
|
||
|
||
// 平台路由表
|
||
platformRoutes: [],
|
||
isEditingRoutes: false, // 编辑模式开关
|
||
|
||
// ID冲突确认对话框
|
||
showIdConflictDialog: false,
|
||
conflictId: '',
|
||
idConflictResolve: null,
|
||
|
||
// OneBot Empty Token Warning #2639
|
||
showOneBotEmptyTokenWarnDialog: false,
|
||
oneBotEmptyTokenWarningResolve: null,
|
||
|
||
loading: false,
|
||
|
||
showConfigSection: false,
|
||
|
||
// 配置抽屉
|
||
showConfigDrawer: false,
|
||
configDrawerTargetId: null,
|
||
|
||
// 保存更新前的平台 ID,防止用户修改 ID 后丢失原始定位
|
||
originalUpdatingPlatformId: null,
|
||
};
|
||
},
|
||
setup() {
|
||
const { tm } = useModuleI18n('features/platform');
|
||
return { tm };
|
||
},
|
||
computed: {
|
||
showDialog: {
|
||
get() {
|
||
return this.show;
|
||
},
|
||
set(value) {
|
||
this.$emit('update:show', value);
|
||
}
|
||
},
|
||
platformTemplates() {
|
||
return this.metadata['platform_group']?.metadata?.platform?.config_template || {};
|
||
},
|
||
canSave() {
|
||
// 基本条件:必须选择平台类型
|
||
if (!this.selectedPlatformType) {
|
||
return false;
|
||
}
|
||
|
||
if (!this.isPlatformIdValid(this.selectedPlatformConfig?.id)) {
|
||
return false;
|
||
}
|
||
|
||
// 如果是使用现有配置文件模式
|
||
if (this.aBConfigRadioVal === '0') {
|
||
return !!this.selectedAbConfId;
|
||
}
|
||
|
||
// 如果是创建新配置文件模式
|
||
if (this.aBConfigRadioVal === '1') {
|
||
// 需要配置文件名称,且新配置数据已加载
|
||
return !!(this.selectedAbConfId && this.newConfigData);
|
||
}
|
||
|
||
return false;
|
||
},
|
||
configTableHeaders() {
|
||
return [
|
||
{ title: this.tm('createDialog.configTableHeaders.configId'), key: 'name', sortable: false },
|
||
{ title: this.tm('createDialog.configTableHeaders.scope'), key: 'scope', sortable: false },
|
||
];
|
||
},
|
||
routeTableHeaders() {
|
||
return [
|
||
{ title: this.tm('createDialog.routeTableHeaders.source'), key: 'source', sortable: false, width: '60%' },
|
||
{ title: this.tm('createDialog.routeTableHeaders.config'), key: 'configId', sortable: false, width: '20%' },
|
||
{ title: this.tm('createDialog.routeTableHeaders.actions'), key: 'actions', sortable: false, align: 'center', width: '20%' },
|
||
];
|
||
},
|
||
messageTypeOptions() {
|
||
return [
|
||
{ label: this.tm('createDialog.messageTypeOptions.all'), value: '*' },
|
||
{ label: this.tm('createDialog.messageTypeOptions.group'), value: 'GroupMessage' },
|
||
{ label: this.tm('createDialog.messageTypeOptions.friend'), value: 'FriendMessage' },
|
||
];
|
||
}
|
||
},
|
||
watch: {
|
||
selectedPlatformType(newType) {
|
||
if (newType && this.platformTemplates[newType]) {
|
||
this.selectedPlatformConfig = JSON.parse(JSON.stringify(this.platformTemplates[newType]));
|
||
} else {
|
||
this.selectedPlatformConfig = null;
|
||
}
|
||
},
|
||
selectedAbConfId(newConfigId) {
|
||
// 当选择配置文件改变时,获取配置文件数据用于预览
|
||
if (!this.updatingMode && this.aBConfigRadioVal === '0' && newConfigId) {
|
||
this.getConfigForPreview(newConfigId);
|
||
} else {
|
||
this.selectedConfigData = null;
|
||
this.selectedConfigMetadata = null;
|
||
}
|
||
},
|
||
aBConfigRadioVal(newVal) {
|
||
// 当切换到创建新配置文件时,获取默认配置模板
|
||
if (newVal === '1') {
|
||
this.selectedConfigData = null;
|
||
this.selectedConfigMetadata = null;
|
||
this.selectedAbConfId = null;
|
||
this.getDefaultConfigTemplate();
|
||
} else if (newVal === '0') {
|
||
// 如果切换回使用现有配置文件但没有选择配置文件,重置为默认
|
||
this.newConfigData = null;
|
||
this.newConfigMetadata = null;
|
||
if (!this.selectedAbConfId) {
|
||
this.selectedAbConfId = 'default';
|
||
}
|
||
}
|
||
},
|
||
showIdConflictDialog(newValue) {
|
||
if (!newValue && this.idConflictResolve) {
|
||
this.idConflictResolve(false);
|
||
this.idConflictResolve = null;
|
||
}
|
||
},
|
||
showOneBotEmptyTokenWarnDialog(newValue) {
|
||
if (!newValue && this.oneBotEmptyTokenWarningResolve) {
|
||
this.oneBotEmptyTokenWarningResolve(true);
|
||
this.oneBotEmptyTokenWarningResolve = null;
|
||
}
|
||
},
|
||
// 监听更新模式变化,获取相关配置文件
|
||
updatingPlatformConfig: {
|
||
handler(newConfig) {
|
||
if (this.updatingMode && newConfig && newConfig.id) {
|
||
this.originalUpdatingPlatformId = newConfig.id;
|
||
this.getPlatformConfigs(newConfig.id);
|
||
}
|
||
},
|
||
immediate: true
|
||
},
|
||
showConfigSection(newValue) {
|
||
if (newValue && !this.updatingMode && this.aBConfigRadioVal === '0') {
|
||
this.getConfigForPreview(this.selectedAbConfId);
|
||
}
|
||
if (newValue) {
|
||
this.$nextTick(() => {
|
||
this.scrollDialogToBottom();
|
||
});
|
||
}
|
||
},
|
||
// 监听编辑模式变化,自动展开配置文件部分
|
||
updatingMode: {
|
||
handler(newValue) {
|
||
if (newValue) {
|
||
this.showConfigSection = true;
|
||
// 编辑模式下默认不开启路由编辑模式,用户需要手动点击
|
||
this.isEditingRoutes = false;
|
||
}
|
||
},
|
||
immediate: true
|
||
}
|
||
},
|
||
methods: {
|
||
getPlatformIcon,
|
||
getPlatformDescription,
|
||
resetForm() {
|
||
this.selectedPlatformType = null;
|
||
this.selectedPlatformConfig = null;
|
||
|
||
this.aBConfigRadioVal = '0';
|
||
this.selectedAbConfId = 'default';
|
||
|
||
// 重置配置预览数据
|
||
this.selectedConfigData = null;
|
||
this.selectedConfigMetadata = null;
|
||
this.configPreviewLoading = false;
|
||
|
||
// 重置新配置文件数据
|
||
this.newConfigData = null;
|
||
this.newConfigMetadata = null;
|
||
this.newConfigLoading = false;
|
||
|
||
this.showConfigSection = false;
|
||
this.isEditingRoutes = false; // 重置编辑模式
|
||
|
||
this.showConfigDrawer = false;
|
||
this.configDrawerTargetId = null;
|
||
|
||
this.originalUpdatingPlatformId = null;
|
||
},
|
||
closeDialog() {
|
||
this.resetForm();
|
||
|
||
this.showDialog = false;
|
||
},
|
||
async getConfigInfoList() {
|
||
await axios.get('/api/config/abconfs').then((res) => {
|
||
this.configInfoList = res.data.data.info_list;
|
||
})
|
||
},
|
||
|
||
// 获取配置文件数据用于预览
|
||
async getConfigForPreview(configId) {
|
||
if (!configId) {
|
||
this.selectedConfigData = null;
|
||
this.selectedConfigMetadata = null;
|
||
return;
|
||
}
|
||
|
||
this.configPreviewLoading = true;
|
||
try {
|
||
const response = await axios.get('/api/config/abconf', {
|
||
params: { id: configId }
|
||
});
|
||
|
||
this.selectedConfigData = response.data.data.config;
|
||
this.selectedConfigMetadata = response.data.data.metadata;
|
||
} catch (error) {
|
||
console.error('获取配置文件预览数据失败:', error);
|
||
this.selectedConfigData = null;
|
||
this.selectedConfigMetadata = null;
|
||
} finally {
|
||
this.configPreviewLoading = false;
|
||
}
|
||
},
|
||
|
||
// 获取默认配置模板用于创建新配置文件
|
||
async getDefaultConfigTemplate() {
|
||
this.newConfigLoading = true;
|
||
try {
|
||
const response = await axios.get('/api/config/default');
|
||
this.newConfigData = response.data.data.config;
|
||
this.newConfigMetadata = response.data.data.metadata;
|
||
} catch (error) {
|
||
console.error('获取默认配置模板失败:', error);
|
||
this.newConfigData = null;
|
||
this.newConfigMetadata = null;
|
||
} finally {
|
||
this.newConfigLoading = false;
|
||
}
|
||
},
|
||
openTutorial() {
|
||
const tutorialUrl = getTutorialLink(this.selectedPlatformConfig.type);
|
||
window.open(tutorialUrl, '_blank');
|
||
},
|
||
openConfigDrawer(configId) {
|
||
const targetId = configId || 'default';
|
||
|
||
if (configId && this.configInfoList.findIndex(c => c.id === configId) === -1) {
|
||
this.showError(this.tm('messages.configNotFoundOpenConfig'));
|
||
}
|
||
|
||
this.configDrawerTargetId = targetId;
|
||
this.showConfigDrawer = true;
|
||
},
|
||
closeConfigDrawer() {
|
||
this.showConfigDrawer = false;
|
||
},
|
||
newPlatform() {
|
||
this.loading = true;
|
||
if (this.updatingMode) {
|
||
if (this.updatingPlatformConfig.type === 'aiocqhttp') {
|
||
const token = this.updatingPlatformConfig.ws_reverse_token;
|
||
if (!token || token.trim() === '') {
|
||
this.showOneBotEmptyTokenWarning().then((continueWithWarning) => {
|
||
if (continueWithWarning) {
|
||
this.updatePlatform();
|
||
} else {
|
||
this.loading = false;
|
||
}
|
||
});
|
||
return;
|
||
}
|
||
}
|
||
this.updatePlatform();
|
||
} else {
|
||
this.savePlatform();
|
||
}
|
||
},
|
||
async updatePlatform() {
|
||
const id = this.originalUpdatingPlatformId || this.updatingPlatformConfig.id;
|
||
if (!id) {
|
||
this.loading = false;
|
||
this.showError(this.tm('messages.updateMissingPlatformId'));
|
||
return;
|
||
}
|
||
|
||
if (!this.isPlatformIdValid(id)) {
|
||
this.loading = false;
|
||
this.showError(this.tm('dialog.invalidPlatformId'));
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 更新平台配置
|
||
let resp = await axios.post('/api/config/platform/update', {
|
||
id: id,
|
||
config: this.updatingPlatformConfig
|
||
})
|
||
|
||
if (resp.data.status === 'error') {
|
||
throw new Error(resp.data.message || this.tm('messages.platformUpdateFailed'));
|
||
}
|
||
|
||
// 同时更新路由表
|
||
await this.saveRoutesInternal();
|
||
|
||
this.loading = false;
|
||
this.showDialog = false;
|
||
this.resetForm();
|
||
this.$emit('refresh-config');
|
||
this.showSuccess(this.tm('messages.updateSuccess'));
|
||
} catch (err) {
|
||
this.loading = false;
|
||
this.showError(err.response?.data?.message || err.message);
|
||
}
|
||
},
|
||
async savePlatform() {
|
||
if (!this.isPlatformIdValid(this.selectedPlatformConfig?.id)) {
|
||
this.loading = false;
|
||
this.showError(this.tm('dialog.invalidPlatformId'));
|
||
return;
|
||
}
|
||
|
||
// 检查 ID 是否已存在
|
||
const existingPlatform = this.config_data.platform?.find(p => p.id === this.selectedPlatformConfig.id);
|
||
if (existingPlatform || this.selectedPlatformConfig.id === 'webchat') {
|
||
const confirmed = await this.confirmIdConflict(this.selectedPlatformConfig.id);
|
||
if (!confirmed) {
|
||
this.loading = false;
|
||
return; // 如果用户取消,则中止保存
|
||
}
|
||
}
|
||
|
||
// 检查 aiocqhttp 适配器的安全设置
|
||
if (this.selectedPlatformConfig.type === 'aiocqhttp') {
|
||
const token = this.selectedPlatformConfig.ws_reverse_token;
|
||
if (!token || token.trim() === '') {
|
||
const continueWithWarning = await this.showOneBotEmptyTokenWarning();
|
||
if (!continueWithWarning) {
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
try {
|
||
// 先保存平台配置
|
||
const res = await axios.post('/api/config/platform/new', this.selectedPlatformConfig);
|
||
|
||
// 平台保存成功后,处理配置文件
|
||
await this.handleConfigFile();
|
||
|
||
this.loading = false;
|
||
this.showDialog = false;
|
||
this.resetForm();
|
||
this.$emit('refresh-config');
|
||
this.showSuccess(res.data.message || this.tm('messages.addSuccessWithConfig'));
|
||
} catch (err) {
|
||
this.loading = false;
|
||
this.showError(err.response?.data?.message || err.message);
|
||
}
|
||
},
|
||
|
||
async handleConfigFile() {
|
||
if (!this.selectedAbConfId) {
|
||
return;
|
||
}
|
||
|
||
const platformId = this.selectedPlatformConfig.id;
|
||
// 生成默认的UMOP:平台ID:*:*(表示该平台的所有消息类型和会话)
|
||
const newUmop = `${platformId}:*:*`;
|
||
|
||
let configId = null;
|
||
|
||
// 第一步:创建或获取配置文件ID
|
||
if (this.aBConfigRadioVal === '0') {
|
||
// 使用现有配置文件
|
||
configId = this.selectedAbConfId;
|
||
} else if (this.aBConfigRadioVal === '1') {
|
||
// 创建新配置文件
|
||
configId = await this.createNewConfigFile(this.selectedAbConfId);
|
||
}
|
||
|
||
if (!configId) {
|
||
throw new Error(this.tm('messages.configIdMissing'));
|
||
}
|
||
|
||
// 第二步:统一更新路由表
|
||
await this.updateRoutingTable(newUmop, configId);
|
||
},
|
||
|
||
async updateRoutingTable(umop, configId) {
|
||
try {
|
||
await axios.post('/api/config/umo_abconf_route/update', {
|
||
umo: umop,
|
||
conf_id: configId
|
||
});
|
||
|
||
console.log(`成功更新路由表: ${umop} -> ${configId}`);
|
||
} catch (err) {
|
||
console.error('更新路由表失败:', err);
|
||
const errorMessage = err.response?.data?.message || err.message;
|
||
throw new Error(this.tm('messages.routingUpdateFailed', { message: errorMessage }));
|
||
}
|
||
},
|
||
|
||
async createNewConfigFile(configName) {
|
||
try {
|
||
// 准备配置数据,如果是创建模式且有新配置数据,使用用户填写的配置
|
||
const configData = this.aBConfigRadioVal === '1' && this.newConfigData
|
||
? this.newConfigData
|
||
: undefined;
|
||
|
||
// 创建新的配置文件(不传入umop)
|
||
const createRes = await axios.post('/api/config/abconf/new', {
|
||
name: configName,
|
||
config: configData // 传入用户配置的数据
|
||
});
|
||
|
||
const newConfigId = createRes.data.data.conf_id;
|
||
console.log(`成功创建新配置文件 ${configName},ID: ${newConfigId}`);
|
||
|
||
return newConfigId;
|
||
} catch (err) {
|
||
console.error('创建新配置文件失败:', err);
|
||
const errorMessage = err.response?.data?.message || err.message;
|
||
throw new Error(this.tm('messages.createConfigFailed', { message: errorMessage }));
|
||
}
|
||
},
|
||
|
||
confirmIdConflict(id) {
|
||
this.conflictId = id;
|
||
this.showIdConflictDialog = true;
|
||
return new Promise((resolve) => {
|
||
this.idConflictResolve = resolve;
|
||
});
|
||
},
|
||
|
||
handleIdConflictConfirm(confirmed) {
|
||
if (this.idConflictResolve) {
|
||
this.idConflictResolve(confirmed);
|
||
}
|
||
this.showIdConflictDialog = false;
|
||
},
|
||
|
||
showOneBotEmptyTokenWarning() {
|
||
this.showOneBotEmptyTokenWarnDialog = true;
|
||
return new Promise((resolve) => {
|
||
this.oneBotEmptyTokenWarningResolve = resolve;
|
||
});
|
||
},
|
||
|
||
handleOneBotEmptyTokenWarningDismiss(continueWithWarning) {
|
||
this.showOneBotEmptyTokenWarnDialog = false;
|
||
if (this.oneBotEmptyTokenWarningResolve) {
|
||
this.oneBotEmptyTokenWarningResolve(continueWithWarning);
|
||
this.oneBotEmptyTokenWarningResolve = null;
|
||
}
|
||
|
||
if (!continueWithWarning) {
|
||
this.loading = false;
|
||
}
|
||
},
|
||
|
||
showSuccess(message) {
|
||
this.$emit('show-toast', { message: message, type: 'success' });
|
||
},
|
||
|
||
showError(message) {
|
||
this.$emit('show-toast', { message: message, type: 'error' });
|
||
},
|
||
|
||
isPlatformIdValid(id) {
|
||
if (!id) {
|
||
return false;
|
||
}
|
||
return !/[!:]/.test(id);
|
||
},
|
||
|
||
// 获取该平台适配器使用的所有配置文件(新版本:直接操作路由表)
|
||
async getPlatformConfigs(platformId) {
|
||
if (!platformId) {
|
||
this.platformRoutes = [];
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 获取路由表 (UMOP -> conf_id)
|
||
const routesRes = await axios.get('/api/config/umo_abconf_routes');
|
||
const routingTable = routesRes.data.data.routing;
|
||
|
||
// 过滤出属于该平台的路由,并保持顺序
|
||
const routes = [];
|
||
for (const [umop, confId] of Object.entries(routingTable)) {
|
||
if (this.isUmopMatchPlatform(umop, platformId)) {
|
||
const parts = umop.split(':');
|
||
if (parts.length === 3) {
|
||
routes.push({
|
||
umop: umop,
|
||
originalUmop: umop, // 保存原始 UMOP 用于更新时查找
|
||
messageType: parts[1] === '' || parts[1] === '*' ? '*' : parts[1],
|
||
sessionId: parts[2] === '' || parts[2] === '*' ? '*' : parts[2],
|
||
configId: confId
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
this.platformRoutes = routes;
|
||
|
||
// 如果没有路由,添加一个默认的空路由供用户编辑
|
||
if (this.platformRoutes.length === 0) {
|
||
this.platformRoutes.push({
|
||
umop: null,
|
||
originalUmop: null,
|
||
messageType: '*',
|
||
sessionId: '*',
|
||
configId: 'default'
|
||
});
|
||
}
|
||
} catch (err) {
|
||
console.error('获取平台路由配置失败:', err);
|
||
this.platformRoutes = [];
|
||
}
|
||
},
|
||
|
||
// 添加新路由
|
||
addNewRoute() {
|
||
this.platformRoutes.push({
|
||
umop: null,
|
||
originalUmop: null,
|
||
messageType: '*',
|
||
sessionId: '*',
|
||
configId: 'default'
|
||
});
|
||
},
|
||
|
||
// 删除路由
|
||
deleteRoute(index) {
|
||
this.platformRoutes.splice(index, 1);
|
||
},
|
||
|
||
// 上移路由
|
||
moveRouteUp(index) {
|
||
if (index > 0) {
|
||
const temp = this.platformRoutes[index];
|
||
this.platformRoutes[index] = this.platformRoutes[index - 1];
|
||
this.platformRoutes[index - 1] = temp;
|
||
// 强制更新视图
|
||
this.platformRoutes = [...this.platformRoutes];
|
||
}
|
||
},
|
||
|
||
// 下移路由
|
||
moveRouteDown(index) {
|
||
if (index < this.platformRoutes.length - 1) {
|
||
const temp = this.platformRoutes[index];
|
||
this.platformRoutes[index] = this.platformRoutes[index + 1];
|
||
this.platformRoutes[index + 1] = temp;
|
||
// 强制更新视图
|
||
this.platformRoutes = [...this.platformRoutes];
|
||
}
|
||
},
|
||
|
||
// 内部保存路由表方法(不显示成功提示)
|
||
async saveRoutesInternal() {
|
||
const originalPlatformId = this.originalUpdatingPlatformId || this.updatingPlatformConfig?.id;
|
||
const newPlatformId = this.updatingPlatformConfig?.id || originalPlatformId;
|
||
|
||
if (!originalPlatformId && !newPlatformId) {
|
||
throw new Error(this.tm('messages.platformIdMissing'));
|
||
}
|
||
|
||
try {
|
||
// 获取完整的路由表
|
||
const routesRes = await axios.get('/api/config/umo_abconf_routes');
|
||
const fullRoutingTable = routesRes.data.data.routing;
|
||
|
||
// 删除该平台的所有旧路由
|
||
for (const umop in fullRoutingTable) {
|
||
if (
|
||
(originalPlatformId && this.isUmopMatchPlatform(umop, originalPlatformId)) ||
|
||
(newPlatformId && this.isUmopMatchPlatform(umop, newPlatformId))
|
||
) {
|
||
delete fullRoutingTable[umop];
|
||
}
|
||
}
|
||
|
||
// 添加新路由(按顺序)
|
||
for (const route of this.platformRoutes) {
|
||
const messageType = route.messageType === '*' ? '*' : route.messageType;
|
||
const sessionId = route.sessionId === '*' ? '*' : route.sessionId;
|
||
const platformIdForRoute = newPlatformId || originalPlatformId;
|
||
const newUmop = `${platformIdForRoute}:${messageType}:${sessionId}`;
|
||
|
||
if (route.configId) {
|
||
fullRoutingTable[newUmop] = route.configId;
|
||
}
|
||
}
|
||
|
||
// 使用 update_all 更新整个路由表
|
||
await axios.post('/api/config/umo_abconf_route/update_all', {
|
||
routing: fullRoutingTable
|
||
});
|
||
} catch (err) {
|
||
console.error('保存路由表失败:', err);
|
||
const errorMessage = err.response?.data?.message || err.message;
|
||
throw new Error(this.tm('messages.routingSaveFailed', { message: errorMessage }));
|
||
}
|
||
},
|
||
|
||
// 切换编辑模式
|
||
toggleEditMode() {
|
||
this.isEditingRoutes = !this.isEditingRoutes;
|
||
},
|
||
toggleConfigSection() {
|
||
this.showConfigSection = !this.showConfigSection;
|
||
},
|
||
|
||
// 根据配置文件ID获取名称
|
||
getConfigName(configId) {
|
||
const config = this.configInfoList.find(c => c.id === configId);
|
||
return config ? config.name : configId;
|
||
},
|
||
|
||
isUmopMatchPlatform(umop, platformId) {
|
||
if (!umop) return false;
|
||
const parts = umop.split(':');
|
||
if (parts.length !== 3) return false;
|
||
const platform = parts[0];
|
||
return platform === platformId || platform === '' || platform === '*';
|
||
},
|
||
|
||
// 获取消息类型标签
|
||
getMessageTypeLabel(messageType) {
|
||
const typeMap = {
|
||
'*': this.tm('createDialog.messageTypeLabels.all'),
|
||
'': this.tm('createDialog.messageTypeLabels.all'),
|
||
'GroupMessage': this.tm('createDialog.messageTypeLabels.group'),
|
||
'FriendMessage': this.tm('createDialog.messageTypeLabels.friend')
|
||
};
|
||
return typeMap[messageType] || messageType;
|
||
},
|
||
|
||
toggleShowConfigSection() {
|
||
this.showConfigSection = false;
|
||
this.showConfigSection = true;
|
||
},
|
||
|
||
prepareData() {
|
||
this.getConfigInfoList();
|
||
this.getConfigForPreview(this.selectedAbConfId);
|
||
if (this.updatingMode && this.updatingPlatformConfig && this.updatingPlatformConfig.id) {
|
||
this.getPlatformConfigs(this.updatingPlatformConfig.id);
|
||
}
|
||
},
|
||
scrollDialogToBottom() {
|
||
const containerRef = this.$refs.dialogScrollContainer;
|
||
const el = containerRef?.$el || containerRef;
|
||
if (!el) {
|
||
return;
|
||
}
|
||
const scrollOptions = { top: el.scrollHeight, behavior: 'smooth' };
|
||
if (typeof el.scrollTo === 'function') {
|
||
el.scrollTo(scrollOptions);
|
||
} else {
|
||
el.scrollTop = el.scrollHeight;
|
||
}
|
||
}
|
||
|
||
},
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
.v-select__selection-text {
|
||
font-size: 12px;
|
||
}
|
||
|
||
.config-drawer-overlay {
|
||
align-items: stretch;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.config-drawer-card {
|
||
width: clamp(320px, 60vw, 820px);
|
||
height: calc(100vh - 32px);
|
||
display: flex;
|
||
flex-direction: column;
|
||
margin: 16px;
|
||
}
|
||
|
||
.config-drawer-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 16px 20px 12px 20px;
|
||
}
|
||
|
||
.config-drawer-content {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 16px 16px 24px 16px;
|
||
}
|
||
</style>
|