refactor(panel): rename injected globals + collapse QR modal entries

Rename the SPA globals injected by Go to drop the ad-hoc dunder shape
and free up the bare `webBasePath` name (still the DB setting key)
from colliding with the JS global it used to share:
  window.__X_UI_BASE_PATH__ -> window.X_UI_BASE_PATH
  window.__X_UI_CUR_VER__   -> window.X_UI_CUR_VER

Also rework the QR-Code modal to fold every QR (subscription + JSON
sub URL, share links, WireGuard config/peer links) into a single
a-collapse with one panel per QR. Subscription panels are listed
first and open by default; everything else stays collapsed so a
multi-link inbound no longer scrolls forever.
This commit is contained in:
MHSanaei
2026-05-10 23:40:39 +02:00
parent 737300b14b
commit 745e394c74
15 changed files with 97 additions and 82 deletions
+75 -16
View File
@@ -1,26 +1,21 @@
<script setup>
import { ref, watch } from 'vue';
import { computed, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { Protocols } from '@/models/inbound.js';
import QrPanel from './QrPanel.vue';
const { t } = useI18n();
// Light QR-only modal — used for the "qrcode" row action on
// single-user Shadowsocks and WireGuard inbounds. The big info modal
// (InboundInfoModal) is too detailed when the user just wants the
// share link as a QR.
const props = defineProps({
open: { type: Boolean, default: false },
dbInbound: { type: Object, default: null },
client: { type: Object, default: null },
remarkModel: { type: String, default: '-ieo' },
// Address of the node hosting this inbound (empty string for local).
// When set, share/QR links use it as the host instead of the panel's
// origin — node-managed inbounds proxy from the node, not the panel.
nodeAddress: { type: String, default: '' },
subSettings: {
type: Object,
default: () => ({ enable: false, subURI: '', subJsonURI: '', subJsonEnable: false }),
},
});
const emit = defineEmits(['update:open']);
@@ -28,6 +23,50 @@ const emit = defineEmits(['update:open']);
const links = ref([]);
const wireguardConfigs = ref([]);
const wireguardLinks = ref([]);
const subLink = ref('');
const subJsonLink = ref('');
const activeKeys = ref([]);
const qrItems = computed(() => {
const items = [];
if (subLink.value) {
items.push({
key: 'sub',
header: t('subscription.title'),
value: subLink.value,
});
}
if (subJsonLink.value) {
items.push({
key: 'sub-json',
header: `${t('subscription.title')} (JSON)`,
value: subJsonLink.value,
});
}
links.value.forEach((link, idx) => {
items.push({
key: `l${idx}`,
header: link.remark || `Link ${idx + 1}`,
value: link.link,
});
});
wireguardConfigs.value.forEach((cfg, idx) => {
items.push({
key: `wc${idx}`,
header: `Peer ${idx + 1} config`,
value: cfg,
downloadName: `peer-${idx + 1}.conf`,
});
if (wireguardLinks.value[idx]) {
items.push({
key: `wl${idx}`,
header: `Peer ${idx + 1} link`,
value: wireguardLinks.value[idx],
});
}
});
return items;
});
watch(() => props.open, (next) => {
if (!next || !props.dbInbound) return;
@@ -46,6 +85,21 @@ watch(() => props.open, (next) => {
wireguardConfigs.value = [];
wireguardLinks.value = [];
}
const subId = props.client?.subId;
if (props.subSettings?.enable && subId) {
subLink.value = (props.subSettings.subURI || '') + subId;
subJsonLink.value = props.subSettings.subJsonEnable
? (props.subSettings.subJsonURI || '') + subId
: '';
} else {
subLink.value = '';
subJsonLink.value = '';
}
const open = [];
if (subLink.value) open.push('sub');
if (subJsonLink.value) open.push('sub-json');
activeKeys.value = open;
});
function close() {
@@ -56,12 +110,17 @@ function close() {
<template>
<a-modal :open="open" :title="t('qrCode')" :footer="null" width="420px" @cancel="close">
<template v-if="dbInbound">
<QrPanel v-for="(link, idx) in links" :key="`l${idx}`" :value="link.link"
:remark="link.remark || `Link ${idx + 1}`" />
<template v-for="(cfg, idx) in wireguardConfigs" :key="`w${idx}`">
<QrPanel :value="cfg" :remark="`Peer ${idx + 1} config`" :download-name="`peer-${idx + 1}.conf`" />
<QrPanel v-if="wireguardLinks[idx]" :value="wireguardLinks[idx]" :remark="`Peer ${idx + 1} link`" />
</template>
<a-collapse v-model:active-key="activeKeys" ghost class="qr-collapse">
<a-collapse-panel v-for="item in qrItems" :key="item.key" :header="item.header">
<QrPanel :value="item.value" :remark="item.header" :download-name="item.downloadName || ''" />
</a-collapse-panel>
</a-collapse>
</template>
</a-modal>
</template>
<style scoped>
.qr-collapse :deep(.ant-collapse-content-box) {
padding: 8px 0 0;
}
</style>