更新配置页面
This commit is contained in:
@@ -2,24 +2,24 @@
|
||||
<a-modal :open="dialogVisible" :title="isEdit ? '编辑配置项' : '新增配置项'" @cancel="handleCancel" :width="600" :confirm-loading="loading" @ok="handleOk">
|
||||
<a-form ref="formRef" :model="formData" :rules="formRules" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||
<a-form-item label="配置名称" name="name">
|
||||
<a-input v-model:value="formData.name" placeholder="请输入配置名称" :disabled="record?.is_system" />
|
||||
<a-input v-model:value="formData.name" placeholder="请输入配置名称" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="配置键" name="key">
|
||||
<a-input v-model:value="formData.key" :placeholder="isEdit ? '' : '请输入配置键,如: site_name'" :disabled="record?.is_system">
|
||||
<a-form-item v-if="!isEdit" label="配置键" name="key">
|
||||
<a-input v-model:value="formData.key" :placeholder="isEdit ? '' : '请输入配置键,如: site_name'">
|
||||
<template #prefix>
|
||||
<key-outlined />
|
||||
</template>
|
||||
</a-input>
|
||||
<div v-if="!isEdit" class="form-tip">建议使用小写字母、下划线命名</div>
|
||||
<div class="form-tip">建议使用小写字母、下划线命名</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="分组" name="group">
|
||||
<a-select v-model:value="formData.group" placeholder="请选择分组" :options="groupOptions" :disabled="record?.is_system" show-search :filter-option="filterOption" />
|
||||
<a-form-item v-if="!isEdit" label="分组" name="group">
|
||||
<a-select v-model:value="formData.group" placeholder="请选择分组" :options="groupOptions" show-search :filter-option="filterOption" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="配置类型" name="type">
|
||||
<a-select v-model:value="formData.type" placeholder="请选择配置类型" :disabled="record?.is_system" @change="handleTypeChange">
|
||||
<a-form-item v-if="!isEdit" label="配置类型" name="type">
|
||||
<a-select v-model:value="formData.type" placeholder="请选择配置类型" @change="handleTypeChange">
|
||||
<a-select-option value="string">字符串</a-select-option>
|
||||
<a-select-option value="text">文本</a-select-option>
|
||||
<a-select-option value="number">数字</a-select-option>
|
||||
@@ -32,7 +32,7 @@
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item v-if="['select', 'radio', 'checkbox'].includes(formData.type)" label="选项配置" name="options">
|
||||
<a-form-item v-if="!isEdit && ['select', 'radio', 'checkbox'].includes(formData.type)" label="选项配置" name="options">
|
||||
<div class="options-editor">
|
||||
<div v-for="(option, index) in formData.options" :key="index" class="option-item">
|
||||
<a-input v-model:value="option.label" placeholder="选项标签" style="flex: 1; margin-right: 8px" />
|
||||
@@ -60,26 +60,26 @@
|
||||
<span v-else>{{ getDisplayValue(formData.default_value) }}</span>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="验证规则" name="validation">
|
||||
<a-input v-model:value="formData.validation" placeholder="如: required|email|max:100" :disabled="record?.is_system" />
|
||||
<div v-if="!isEdit" class="form-tip">可选,使用 Laravel 验证规则语法</div>
|
||||
<a-form-item v-if="!isEdit" label="验证规则" name="validation">
|
||||
<a-input v-model:value="formData.validation" placeholder="如: required|email|max:100" />
|
||||
<div class="form-tip">可选,使用 Laravel 验证规则语法</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="描述" name="description">
|
||||
<a-textarea v-model:value="formData.description" placeholder="请输入配置描述" :rows="2" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="排序" name="sort">
|
||||
<a-form-item v-if="!isEdit" label="排序" name="sort">
|
||||
<a-input-number v-model:value="formData.sort" :min="0" :max="9999" style="width: 100%" />
|
||||
<div v-if="!isEdit" class="form-tip">数字越小排序越靠前</div>
|
||||
<div class="form-tip">数字越小排序越靠前</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="状态" name="status">
|
||||
<a-switch v-model:checked="formData.status" checked-children="启用" un-checked-children="禁用" :disabled="record?.is_system" />
|
||||
<a-form-item v-if="!isEdit" label="状态" name="status">
|
||||
<a-switch v-model:checked="formData.status" checked-children="启用" un-checked-children="禁用" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item v-if="record?.is_system" label="系统配置">
|
||||
<a-tag color="orange">系统配置项</a-tag>
|
||||
<a-tag color="orange">系统配置项 - 仅可修改配置值</a-tag>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
<a-button type="primary" size="small" @click="handleAddConfig"> <PlusOutlined /> 新增配置项 </a-button>
|
||||
</template>
|
||||
<a-tab-pane v-for="group in groups" :key="group.value" :tab="group.label">
|
||||
<a-form :model="formData">
|
||||
<a-form :model="formData" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
|
||||
<a-form-item :label="config.name" v-for="config in configs[group.value]" :key="config.id" :span="getColumnSpan(config.type)">
|
||||
<div class="config-input-wrapper" @mouseenter="showEditIcon(config.id)" @mouseleave="hideEditIcon">
|
||||
<div class="config-input-wrapper">
|
||||
<!-- 不同类型的配置项 -->
|
||||
<!-- string/text 类型 -->
|
||||
<a-input v-if="['string', 'text'].includes(config.type)" v-model:value="formData[config.key]" :placeholder="config.description" :rows="config.type === 'text' ? 3 : 1" :type="config.type === 'text' ? 'textarea' : 'text'" />
|
||||
@@ -51,7 +51,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 编辑图标 -->
|
||||
<a-button v-show="visibleEditIcon === config.id" type="link" size="small" class="edit-btn" @click="handleEditConfig(config)">
|
||||
<a-button type="link" size="small" class="edit-btn" @click="handleEditConfig(config)">
|
||||
<EditOutlined />
|
||||
编辑
|
||||
</a-button>
|
||||
@@ -95,7 +95,6 @@ const configs = ref({})
|
||||
const formData = reactive({})
|
||||
const jsonStrings = reactive({})
|
||||
const jsonErrors = reactive({})
|
||||
const visibleEditIcon = ref(null)
|
||||
|
||||
// 是否可以编辑系统配置
|
||||
const canEditSystem = ref(false)
|
||||
@@ -122,16 +121,6 @@ const getColumnSpan = (type) => {
|
||||
return 8
|
||||
}
|
||||
|
||||
// ===== 显示编辑图标 =====
|
||||
const showEditIcon = (id) => {
|
||||
visibleEditIcon.value = id
|
||||
}
|
||||
|
||||
// ===== 隐藏编辑图标 =====
|
||||
const hideEditIcon = () => {
|
||||
visibleEditIcon.value = null
|
||||
}
|
||||
|
||||
// ===== 加载配置分组 =====
|
||||
const loadGroups = async () => {
|
||||
try {
|
||||
@@ -262,35 +251,40 @@ const handleSave = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否有需要保存的配置
|
||||
const configList = Object.values(configs.value).flat()
|
||||
if (configList.length === 0) {
|
||||
message.warning('暂无配置项可保存')
|
||||
return
|
||||
}
|
||||
|
||||
// 构建更新数据
|
||||
const updates = {}
|
||||
const updates = []
|
||||
Object.keys(formData).forEach((key) => {
|
||||
const config = Object.values(configs.value)
|
||||
.flat()
|
||||
.find((c) => c.key === key)
|
||||
const config = configList.find((c) => c.key === key)
|
||||
if (config) {
|
||||
let value = formData[key]
|
||||
// 转换 checkbox 类型为数组字符串
|
||||
if (config.type === 'checkbox') {
|
||||
value = JSON.stringify(value)
|
||||
}
|
||||
updates[key] = value
|
||||
// 转换 file 类型为 JSON 字符串
|
||||
else if (config.type === 'file' && Array.isArray(value) && value.length > 0) {
|
||||
value = JSON.stringify(value)
|
||||
}
|
||||
updates.push({ id: config.id, value })
|
||||
}
|
||||
})
|
||||
|
||||
if (updates.length === 0) {
|
||||
message.warning('暂无配置项需要保存')
|
||||
return
|
||||
}
|
||||
|
||||
// 批量更新
|
||||
try {
|
||||
saving.value = true
|
||||
const promises = Object.entries(updates).map(([key, value]) => {
|
||||
const config = Object.values(configs.value)
|
||||
.flat()
|
||||
.find((c) => c.key === key)
|
||||
if (!config || (config.is_system && !canEditSystem.value)) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
return systemApi.config.edit.put(config.id, { value })
|
||||
})
|
||||
|
||||
const promises = updates.map((update) => systemApi.config.edit.put(update.id, { value: update.value }))
|
||||
await Promise.all(promises)
|
||||
message.success('保存成功')
|
||||
// 重新加载配置
|
||||
@@ -320,7 +314,7 @@ const handleReset = () => {
|
||||
|
||||
// ===== 编辑配置项 =====
|
||||
const handleEditConfig = (config) => {
|
||||
currentConfig.value = { ...config }
|
||||
currentConfig.value = config
|
||||
dialog.save = true
|
||||
}
|
||||
|
||||
@@ -356,6 +350,7 @@ onMounted(async () => {
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
background: #f5f5f5;
|
||||
|
||||
.content-wrapper {
|
||||
flex: 1;
|
||||
@@ -363,23 +358,63 @@ onMounted(async () => {
|
||||
display: flex;
|
||||
background: #fff;
|
||||
margin: 16px;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
|
||||
|
||||
.config-tabs {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
:deep(.ant-tabs-content-holder) {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
flex-direction: column;
|
||||
|
||||
:deep(.ant-tabs-nav) {
|
||||
margin-bottom: 0;
|
||||
padding: 0 8px;
|
||||
background: #fafafa;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
border-radius: 8px 8px 0 0;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-tab) {
|
||||
text-align: left;
|
||||
padding: 12px 16px;
|
||||
padding: 12px 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
&.ant-tabs-tab-active {
|
||||
color: #1890ff;
|
||||
background: #fff;
|
||||
border-top: 2px solid #1890ff;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-left > .ant-tabs-nav .ant-tabs-tab-active) {
|
||||
background-color: #e6f7ff;
|
||||
:deep(.ant-tabs-ink-bar) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-content) {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-tabpane) {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-content-holder) {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
:deep(.ant-form) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,74 +424,34 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.footer-bar {
|
||||
padding: 12px 24px;
|
||||
padding: 16px 24px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
background: #fafafa;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
align-items: center;
|
||||
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.04);
|
||||
z-index: 10;
|
||||
|
||||
.config-item {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.config-item-wrapper {
|
||||
padding: 16px;
|
||||
border: 1px solid #f0f0f0;
|
||||
.ant-btn {
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: #d9d9d9;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.config-label {
|
||||
margin-bottom: 8px;
|
||||
|
||||
.label-text {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #262626;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.label-desc {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
}
|
||||
}
|
||||
|
||||
.config-input-wrapper {
|
||||
position: relative;
|
||||
|
||||
.edit-btn {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.config-upload {
|
||||
:deep(.ant-upload-list) {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.json-editor-wrapper {
|
||||
position: relative;
|
||||
|
||||
.json-error {
|
||||
margin-top: 4px;
|
||||
color: #ff4d4f;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
height: 36px;
|
||||
padding: 0 24px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.config-input-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
|
||||
.edit-btn {
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user