Files
AstrBot/dashboard/src/components/shared/ListConfigItem.vue
T
Soulter 369eab18ab Refactor: 重构配置文件管理,以支持更灵活的、会话粒度的(基于 umo part)配置文件隔离 (#2328)
* refactor: 重构配置文件管理,以支持更灵活的、基于 umo part 的配置文件隔离

* Refactor: 重构配置前端页面,新增数个配置项 (#2331)

* refactor: 重构配置前端页面,新增数个配置项

* feat: 完善多配置文件结构

* perf: 系统配置入口

* fix: normal config item list not display

* fix: 修复 axios 请求中的上下文引用问题
2025-08-13 09:18:49 +08:00

216 lines
5.8 KiB
Vue

<template>
<div class="d-flex align-center justify-space-between">
<div>
<span v-if="!modelValue || modelValue.length === 0" style="color: rgb(var(--v-theme-primaryText));">
暂无项目
</span>
<div v-else class="d-flex flex-wrap ga-2">
<v-chip v-for="item in displayItems" :key="item" size="x-small" label color="primary">
{{ item.length > 20 ? item.slice(0, 20) + '...' : item }}
</v-chip>
<v-chip v-if="modelValue.length > maxDisplayItems" size="x-small" label color="grey-lighten-1">
+{{ modelValue.length - maxDisplayItems }}
</v-chip>
</div>
</div>
<v-btn size="small" color="primary" variant="tonal" @click="openDialog">
{{ buttonText }}
</v-btn>
</div>
<!-- List Management Dialog -->
<v-dialog v-model="dialog" max-width="600px">
<v-card>
<v-card-title class="text-h3 py-4" style="font-weight: normal;">
{{ dialogTitle }}
</v-card-title>
<v-card-text class="pa-0" style="max-height: 400px; overflow-y: auto;">
<v-list v-if="localItems.length > 0" density="compact">
<v-list-item
v-for="(item, index) in localItems"
:key="index"
rounded="md"
class="ma-1">
<v-list-item-title v-if="editIndex !== index">
{{ item }}
</v-list-item-title>
<v-text-field
v-else
v-model="editItem"
hide-details
variant="outlined"
density="compact"
@keyup.enter="saveEdit"
@keyup.esc="cancelEdit"
autofocus
></v-text-field>
<template v-slot:append>
<div v-if="editIndex !== index" class="d-flex">
<v-btn @click="startEdit(index, item)" variant="plain" icon size="small">
<v-icon>mdi-pencil</v-icon>
</v-btn>
<v-btn @click="removeItem(index)" variant="plain" icon size="small">
<v-icon>mdi-close</v-icon>
</v-btn>
</div>
<div v-else class="d-flex">
<v-btn @click="saveEdit" variant="plain" color="success" icon size="small">
<v-icon>mdi-check</v-icon>
</v-btn>
<v-btn @click="cancelEdit" variant="plain" color="error" icon size="small">
<v-icon>mdi-close</v-icon>
</v-btn>
</div>
</template>
</v-list-item>
</v-list>
<div v-else class="text-center py-8">
<v-icon size="64" color="grey-lighten-1">mdi-format-list-bulleted</v-icon>
<p class="text-grey mt-4">暂无项目</p>
</div>
</v-card-text>
<!-- Add new item section -->
<v-card-text class="pa-4">
<div class="d-flex align-center ga-2">
<v-text-field
v-model="newItem"
:label="t('core.common.list.addItemPlaceholder')"
@keyup.enter="addItem"
clearable
hide-details
variant="outlined"
density="compact"
class="flex-grow-1">
</v-text-field>
<v-btn @click="addItem" variant="tonal" color="primary">
<v-icon>mdi-plus</v-icon>
{{ t('core.common.list.addButton') }}
</v-btn>
</div>
</v-card-text>
<v-card-actions class="pa-4">
<v-spacer></v-spacer>
<v-btn variant="text" @click="cancelDialog">取消</v-btn>
<v-btn color="primary" @click="confirmDialog">确认</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import { useI18n } from '@/i18n/composables'
const { t } = useI18n()
const props = defineProps({
modelValue: {
type: Array,
default: () => []
},
label: {
type: String,
default: ''
},
buttonText: {
type: String,
default: '修改'
},
dialogTitle: {
type: String,
default: '修改列表项'
},
maxDisplayItems: {
type: Number,
default: 1
}
})
const emit = defineEmits(['update:modelValue'])
const dialog = ref(false)
const localItems = ref([])
const originalItems = ref([])
const newItem = ref('')
const editIndex = ref(-1)
const editItem = ref('')
// 计算要显示的项目
const displayItems = computed(() => {
return props.modelValue.slice(0, props.maxDisplayItems)
})
// 监听 modelValue 变化,同步到 localItems
watch(() => props.modelValue, (newValue) => {
localItems.value = [...(newValue || [])]
}, { immediate: true })
function openDialog() {
localItems.value = [...(props.modelValue || [])]
originalItems.value = [...(props.modelValue || [])]
dialog.value = true
editIndex.value = -1
editItem.value = ''
newItem.value = ''
}
function addItem() {
if (newItem.value.trim() !== '') {
localItems.value.push(newItem.value.trim())
newItem.value = ''
}
}
function removeItem(index) {
localItems.value.splice(index, 1)
}
function startEdit(index, item) {
editIndex.value = index
editItem.value = item
}
function saveEdit() {
if (editItem.value.trim() !== '') {
localItems.value[editIndex.value] = editItem.value.trim()
cancelEdit()
}
}
function cancelEdit() {
editIndex.value = -1
editItem.value = ''
}
function confirmDialog() {
emit('update:modelValue', [...localItems.value])
dialog.value = false
}
function cancelDialog() {
localItems.value = [...originalItems.value]
editIndex.value = -1
editItem.value = ''
newItem.value = ''
dialog.value = false
}
</script>
<style scoped>
.v-list-item {
transition: all 0.2s ease;
}
.v-list-item:hover {
background-color: rgba(var(--v-theme-primary), 0.04);
}
.v-chip {
margin: 2px;
}
</style>