feat: recreate bot platforms through QR scanning

This commit is contained in:
Soulter
2026-09-14 20:19:05 +08:00
parent bd046ed299
commit eff3d9587e
7 changed files with 115 additions and 9 deletions
@@ -9,10 +9,10 @@
<v-card-title class="text-h3 pa-4 pb-0 pl-6">
{{
updatingMode
? `${tm('dialog.edit')} ${updatingPlatformConfig.id} ${tm(
'dialog.adapter',
? `${tm("dialog.edit")} ${updatingPlatformConfig.id} ${tm(
"dialog.adapter",
)}`
: tm('dialog.addPlatform')
: tm("dialog.addPlatform")
}}
</v-card-title>
<v-card-text
@@ -73,7 +73,9 @@
activator="parent"
:text="
tm(
`createDialog.platformTooltips.${platformTemplates[item.raw].type}`,
`createDialog.platformTooltips.${
platformTemplates[item.raw].type
}`,
)
"
location="end"
@@ -734,7 +736,9 @@
<v-card-actions>
<v-spacer></v-spacer>
<v-btn variant="text" @click="closeDialog">{{ tm("dialog.cancel") }}</v-btn>
<v-btn variant="text" @click="closeDialog">{{
tm("dialog.cancel")
}}</v-btn>
<v-btn
:disabled="!canSave"
color="primary"
@@ -850,7 +854,13 @@
</template>
<script>
import { botApi, configProfileApi, configRouteApi, fileApi, sessionApi } from "@/api/v1";
import {
botApi,
configProfileApi,
configRouteApi,
fileApi,
sessionApi,
} from "@/api/v1";
import { useModuleI18n } from "@/i18n/composables";
import {
getPlatformIcon,
@@ -894,6 +904,10 @@ export default {
type: Object,
default: null,
},
initialScanPlatform: {
type: Object,
default: null,
},
},
data() {
return {
@@ -959,6 +973,7 @@ export default {
return this.show;
},
set(value) {
if (!value) this.resetForm();
this.$emit("update:show", value);
},
},
@@ -1588,7 +1603,9 @@ export default {
}
let suffix = "";
const explicitSuffix = this.sanitizePlatformIdPart(data.platform_id_suffix);
const explicitSuffix = this.sanitizePlatformIdPart(
data.platform_id_suffix,
);
if (explicitSuffix) {
suffix =
explicitSuffix.startsWith("_") || explicitSuffix.startsWith("-")
@@ -1944,7 +1961,24 @@ export default {
this.showConfigSection = true;
},
prepareData() {
async prepareData() {
if (!this.updatingMode && this.initialScanPlatform) {
const scanPlatform = this.initialScanPlatform;
this.resetForm();
await this.$nextTick();
this.selectedPlatformType =
Object.keys(this.platformTemplates).find(
(name) => this.platformTemplates[name].type === scanPlatform.type,
) || null;
await this.$nextTick();
if (this.selectedPlatformConfig) {
this.selectedPlatformConfig.id = scanPlatform.id;
this.scanPlatformIdCustomized = true;
this.larkCreationMode = "scan";
this.dingtalkCreationMode = "scan";
this.qqOfficialCreationMode = "scan";
}
}
this.getConfigInfoList();
this.getConfigForPreview(this.selectedAbConfId);
if (
@@ -15,6 +15,25 @@
</div>
<div class="bot-editor__actions">
<v-btn
v-if="
[
'lark',
'dingtalk',
'qq_official',
'qq_official_webhook',
'weixin_oc',
].includes(platform.type)
"
prepend-icon="mdi-qrcode"
variant="text"
rounded="xl"
:loading="rescanning"
:disabled="saving"
@click="$emit('rescan')"
>
{{ tm("workspace.rescan") }}
</v-btn>
<v-btn
v-if="runtimeStat?.error_count > 0"
color="error"
@@ -49,7 +68,7 @@
variant="tonal"
rounded="xl"
:loading="saving"
:disabled="!canSave"
:disabled="!canSave || rescanning"
@click="save"
>
{{ tm("workspace.save") }}
@@ -391,6 +410,10 @@ const props = defineProps({
type: Boolean,
default: false,
},
rescanning: {
type: Boolean,
default: false,
},
});
const emit = defineEmits([
@@ -398,6 +421,7 @@ const emit = defineEmits([
"show-toast",
"show-error",
"show-qr",
"rescan",
"show-webhook",
]);
@@ -5,6 +5,8 @@
"addAdapter": "Add Adapter",
"emptyText": "No platform adapters yet, click Add Adapter to create one",
"workspace": {
"rescan": "Scan again",
"rescanConfirm": "This will delete platform {id} and open QR code creation for the same platform. Existing connection settings and unsaved changes will be lost. Cancelling creation will not restore the old platform. Continue?",
"listTitle": "Bots",
"selectHint": "Select a bot to configure",
"empty": "No bots yet. Create one to start configuring it.",
@@ -5,6 +5,8 @@
"addAdapter": "ボットを作成",
"emptyText": "プラットフォームアダプターがありません。「ボットを作成」をクリックして追加してください",
"workspace": {
"rescan": "再スキャン",
"rescanConfirm": "プラットフォーム {id} を削除し、同じプラットフォームのQRコード作成画面を開きます。接続設定と未保存の変更は失われます。作成をキャンセルしても元に戻りません。続行しますか?",
"listTitle": "ボット",
"selectHint": "設定するボットを選択してください",
"empty": "ボットがありません。作成すると設定を開始できます。",
@@ -5,6 +5,8 @@
"addAdapter": "Создать бота",
"emptyText": "Боты не настроены. Нажмите «Создать бота», чтобы начать.",
"workspace": {
"rescan": "Сканировать заново",
"rescanConfirm": "Платформа {id} будет удалена, затем откроется создание той же платформы по QR-коду. Настройки подключения и несохранённые изменения будут потеряны. Отмена создания не восстановит платформу. Продолжить?",
"listTitle": "Боты",
"selectHint": "Выберите бота для настройки",
"empty": "Ботов пока нет. Создайте бота, чтобы начать настройку.",
@@ -5,6 +5,8 @@
"addAdapter": "创建机器人",
"emptyText": "暂无平台适配器,点击 创建机器人 添加",
"workspace": {
"rescan": "重新扫码",
"rescanConfirm": "将删除平台 {id},并打开同一平台的扫码创建界面。现有连接配置和未保存的修改会丢失,取消创建也不会恢复旧平台。确定继续吗?",
"listTitle": "机器人",
"selectHint": "选择一个机器人进行配置",
"empty": "还没有机器人,创建后即可开始配置。",
+40
View File
@@ -151,6 +151,8 @@
:metadata="metadata"
:runtime-stat="getPlatformStat(selectedPlatform.id)"
:has-qr-payload="hasQrPayload(selectedPlatform.id)"
:rescanning="rescanning"
@rescan="rescanPlatform(selectedPlatform)"
@saved="handlePlatformSaved"
@show-toast="showToast"
@show-error="showErrorDetails(selectedPlatform)"
@@ -174,6 +176,12 @@
:metadata="metadata"
:config_data="configData"
:updating-mode="false"
:initial-scan-platform="scanPlatform"
@update:show="
(show) => {
if (!show) scanPlatform = null;
}
"
@show-toast="showToast"
@refresh-config="handlePlatformCreated"
/>
@@ -338,6 +346,8 @@ const metadata = ref({});
const loadingPlatforms = ref(true);
const selectedPlatformId = ref(null);
const showAddPlatformDialog = ref(false);
const scanPlatform = ref(null);
const rescanning = ref(false);
const platformStats = ref({});
const showWebhookDialog = ref(false);
const currentWebhookUuid = ref("");
@@ -478,6 +488,36 @@ async function deletePlatform(platform) {
}
}
async function rescanPlatform(platform) {
if (rescanning.value) return;
rescanning.value = true;
try {
if (
!(await askForConfirmationDialog(
tm("workspace.rescanConfirm", { id: platform.id }),
confirmDialog,
))
)
return;
const response = await botApi.delete(platform.id);
if (response.data.status !== "ok") {
throw new Error(response.data.message || tm("status.error"));
}
configData.value.platform = platforms.value.filter(
(item) => item.id !== platform.id,
);
selectedPlatformId.value = null;
scanPlatform.value = { id: platform.id, type: platform.type };
showAddPlatformDialog.value = true;
await getConfig();
} catch (error) {
showError(error);
} finally {
rescanning.value = false;
}
}
function handlePlatformCreated(platformId) {
getConfig(platformId || null);
getPlatformStats();