| <template> |
| <v-dialog v-model="showDialog" max-width="800px" max-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 === '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> |
| |
| |
| <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, |
| |
| |
| platformConfigs: [], |
| |
| |
| platformRoutes: [], |
| isEditingRoutes: false, |
| |
| |
| showIdConflictDialog: false, |
| conflictId: '', |
| idConflictResolve: null, |
| |
| |
| showOneBotEmptyTokenWarnDialog: false, |
| oneBotEmptyTokenWarningResolve: null, |
| |
| loading: false, |
| |
| showConfigSection: false, |
| |
| |
| showConfigDrawer: false, |
| configDrawerTargetId: null, |
| |
| |
| 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(platformType) { |
| |
| const template = this.platformTemplates?.[platformType]; |
| if (template && template.logo_token) { |
| return `/api/file/${template.logo_token}`; |
| } |
| return getPlatformIcon(platformType); |
| }, |
| 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; |
| } |
| |
| |
| 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; |
| } |
| } |
| |
| |
| 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; |
| |
| const newUmop = `${platformId}:*:*`; |
| |
| let configId = null; |
| |
| |
| 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; |
| |
| |
| 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 { |
| |
| 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, |
| 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; |
| } |
| } |
| |
| |
| 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; |
| }, |
| |
| |
| 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> |
| |