Files
cwd/cwd-admin/src/views/SettingsView/components/DomainSettings.vue
anghunk daab04867f feat(设置页面): 同步标签页状态到URL并更新图标
- 将标签页状态与URL查询参数同步,支持直接通过URL访问特定标签页
- 替换域设置组件中的全部移动按钮图标,使用更直观的双箭头图标
2026-02-05 17:29:03 +08:00

341 lines
7.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="domain-settings">
<div class="domain-settings-desc">
配置后台可见的域名左侧为后台下拉框中显示的域名右侧为数据库中发现的所有域名支持拖拽或点击按钮移动
</div>
<div class="domain-transfer">
<!-- Visible Domains (Left) -->
<div class="transfer-panel">
<div class="transfer-header">
后台显示域名 ({{ visibleList.length }})
</div>
<div
class="transfer-body"
@dragover.prevent
@drop="onDrop($event, 'visible')"
>
<div
v-if="visibleList.length === 0"
class="transfer-empty"
>
无域名 (默认显示全部)
</div>
<div
v-for="domain in visibleList"
:key="domain"
class="transfer-item"
draggable="true"
@dragstart="onDragStart($event, domain, 'visible')"
@dblclick="moveToHidden(domain)"
>
<span class="domain-text">{{ domain }}</span>
<button class="move-btn" @click="moveToHidden(domain)" title="移出">
<PhArrowRight :size="16" />
</button>
</div>
</div>
</div>
<!-- Actions -->
<div class="transfer-actions">
<button class="action-btn" @click="moveAllToVisible" title="全部左移">
<PhCaretDoubleLeft />
</button>
<button class="action-btn" @click="moveAllToHidden" title="全部右移">
<PhCaretDoubleRight />
</button>
</div>
<!-- Hidden/All Domains (Right) -->
<div class="transfer-panel">
<div class="transfer-header">
其他域名 ({{ hiddenList.length }})
</div>
<div
class="transfer-body"
@dragover.prevent
@drop="onDrop($event, 'hidden')"
>
<div
v-if="hiddenList.length === 0"
class="transfer-empty"
>
无更多域名
</div>
<div
v-for="domain in hiddenList"
:key="domain"
class="transfer-item"
draggable="true"
@dragstart="onDragStart($event, domain, 'hidden')"
@dblclick="moveToVisible(domain)"
>
<button class="move-btn" @click="moveToVisible(domain)" title="移入">
<PhArrowLeft :size="16" />
</button>
<span class="domain-text">{{ domain }}</span>
</div>
</div>
</div>
</div>
<div class="form-actions">
<button class="btn-primary" @click="handleSave" :disabled="loading">
{{ loading ? "保存中..." : "保存" }}
</button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue';
import { fetchDomainList, fetchFeatureSettings, saveFeatureSettings } from '../../../api/admin';
const loading = ref(false);
const allDomains = ref<string[]>([]);
const visibleList = ref<string[]>([]);
const hiddenList = computed(() => {
const visibleSet = new Set(visibleList.value);
return allDomains.value.filter(d => !visibleSet.has(d));
});
async function loadData() {
try {
const [domainRes, settingsRes] = await Promise.all([
fetchDomainList(),
fetchFeatureSettings()
]);
allDomains.value = domainRes.domains || [];
if (settingsRes.visibleDomains) {
visibleList.value = settingsRes.visibleDomains;
} else {
visibleList.value = [];
}
} catch (e) {
console.error(e);
}
}
function moveToHidden(domain: string) {
visibleList.value = visibleList.value.filter(d => d !== domain);
}
function moveToVisible(domain: string) {
if (!visibleList.value.includes(domain)) {
visibleList.value.push(domain);
}
}
function moveAllToVisible() {
const current = new Set(visibleList.value);
hiddenList.value.forEach(d => current.add(d));
visibleList.value = Array.from(current);
}
function moveAllToHidden() {
visibleList.value = [];
}
function onDragStart(event: DragEvent, domain: string, source: 'visible' | 'hidden') {
if (event.dataTransfer) {
event.dataTransfer.setData('text/plain', JSON.stringify({ domain, source }));
event.dataTransfer.effectAllowed = 'move';
}
}
function onDrop(event: DragEvent, target: 'visible' | 'hidden') {
const data = event.dataTransfer?.getData('text/plain');
if (data) {
try {
const { domain, source } = JSON.parse(data);
if (source !== target) {
if (target === 'visible') {
moveToVisible(domain);
} else {
moveToHidden(domain);
}
}
} catch (e) {
console.error(e);
}
}
}
async function handleSave() {
loading.value = true;
try {
await saveFeatureSettings({
visibleDomains: visibleList.value
});
// 保存成功后刷新页面以应用更改LayoutView 重新加载)
window.location.reload();
} catch (e) {
alert('保存失败');
} finally {
loading.value = false;
}
}
onMounted(() => {
loadData();
});
</script>
<style lang="less" scoped>
.domain-settings {
display: flex;
flex-direction: column;
gap: 20px;
}
.domain-settings-desc {
color: var(--text-secondary);
font-size: 14px;
}
.domain-transfer {
display: flex;
gap: 20px;
align-items: flex-start;
height: 400px;
@media (max-width: 768px) {
flex-direction: column;
height: auto;
}
}
.transfer-panel {
flex: 1;
border: 1px solid var(--border-color);
border-radius: 8px;
display: flex;
flex-direction: column;
height: 100%;
background: var(--bg-card);
min-height: 300px;
width: 100%;
}
.transfer-header {
padding: 12px 16px;
border-bottom: 1px solid var(--border-color);
font-weight: 600;
background: var(--bg-body);
border-radius: 8px 8px 0 0;
color: var(--text-primary);
}
.transfer-body {
flex: 1;
overflow-y: auto;
padding: 10px;
display: flex;
flex-direction: column;
gap: 4px;
}
.transfer-empty {
text-align: center;
color: var(--text-secondary);
margin-top: 40px;
font-size: 13px;
}
.transfer-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
background: var(--bg-body);
border-radius: 6px;
cursor: grab;
user-select: none;
transition: background 0.2s;
color: var(--text-primary);
&:hover {
background: var(--bg-hover);
}
&:active {
cursor: grabbing;
}
.domain-text {
flex: 1;
margin: 0 10px;
word-break: break-all;
}
}
.move-btn {
background: none;
border: none;
cursor: pointer;
color: var(--text-secondary);
padding: 4px;
border-radius: 4px;
display: flex;
align-items: center;
&:hover {
background: var(--bg-active);
color: var(--primary-color);
}
}
.transfer-actions {
display: flex;
flex-direction: column;
gap: 10px;
align-self: center;
@media (max-width: 768px) {
flex-direction: row;
}
}
.action-btn {
width: 36px;
height: 36px;
border-radius: 50%;
border: 1px solid var(--border-color);
background: var(--bg-card);
color: var(--text-primary);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
&:hover {
border-color: var(--primary-color);
color: var(--primary-color);
}
}
.form-actions {
margin-top: 10px;
display: flex;
justify-content: flex-end;
}
.btn-primary {
padding: 8px 24px;
background: var(--primary-color);
color: #fff;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
&:disabled {
opacity: 0.7;
cursor: not-allowed;
}
}
</style>