更新配置页面
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-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 ref="formRef" :model="formData" :rules="formRules" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||||
<a-form-item label="配置名称" name="name">
|
<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>
|
||||||
|
|
||||||
<a-form-item label="配置键" name="key">
|
<a-form-item v-if="!isEdit" label="配置键" name="key">
|
||||||
<a-input v-model:value="formData.key" :placeholder="isEdit ? '' : '请输入配置键,如: site_name'" :disabled="record?.is_system">
|
<a-input v-model:value="formData.key" :placeholder="isEdit ? '' : '请输入配置键,如: site_name'">
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<key-outlined />
|
<key-outlined />
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
<div v-if="!isEdit" class="form-tip">建议使用小写字母、下划线命名</div>
|
<div class="form-tip">建议使用小写字母、下划线命名</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="分组" name="group">
|
<a-form-item v-if="!isEdit" label="分组" name="group">
|
||||||
<a-select v-model:value="formData.group" placeholder="请选择分组" :options="groupOptions" :disabled="record?.is_system" show-search :filter-option="filterOption" />
|
<a-select v-model:value="formData.group" placeholder="请选择分组" :options="groupOptions" show-search :filter-option="filterOption" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="配置类型" name="type">
|
<a-form-item v-if="!isEdit" label="配置类型" name="type">
|
||||||
<a-select v-model:value="formData.type" placeholder="请选择配置类型" :disabled="record?.is_system" @change="handleTypeChange">
|
<a-select v-model:value="formData.type" placeholder="请选择配置类型" @change="handleTypeChange">
|
||||||
<a-select-option value="string">字符串</a-select-option>
|
<a-select-option value="string">字符串</a-select-option>
|
||||||
<a-select-option value="text">文本</a-select-option>
|
<a-select-option value="text">文本</a-select-option>
|
||||||
<a-select-option value="number">数字</a-select-option>
|
<a-select-option value="number">数字</a-select-option>
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</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 class="options-editor">
|
||||||
<div v-for="(option, index) in formData.options" :key="index" class="option-item">
|
<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" />
|
<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>
|
<span v-else>{{ getDisplayValue(formData.default_value) }}</span>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="验证规则" name="validation">
|
<a-form-item v-if="!isEdit" label="验证规则" name="validation">
|
||||||
<a-input v-model:value="formData.validation" placeholder="如: required|email|max:100" :disabled="record?.is_system" />
|
<a-input v-model:value="formData.validation" placeholder="如: required|email|max:100" />
|
||||||
<div v-if="!isEdit" class="form-tip">可选,使用 Laravel 验证规则语法</div>
|
<div class="form-tip">可选,使用 Laravel 验证规则语法</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="描述" name="description">
|
<a-form-item label="描述" name="description">
|
||||||
<a-textarea v-model:value="formData.description" placeholder="请输入配置描述" :rows="2" />
|
<a-textarea v-model:value="formData.description" placeholder="请输入配置描述" :rows="2" />
|
||||||
</a-form-item>
|
</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%" />
|
<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>
|
||||||
|
|
||||||
<a-form-item label="状态" name="status">
|
<a-form-item v-if="!isEdit" label="状态" name="status">
|
||||||
<a-switch v-model:checked="formData.status" checked-children="启用" un-checked-children="禁用" :disabled="record?.is_system" />
|
<a-switch v-model:checked="formData.status" checked-children="启用" un-checked-children="禁用" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item v-if="record?.is_system" label="系统配置">
|
<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-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
<a-button type="primary" size="small" @click="handleAddConfig"> <PlusOutlined /> 新增配置项 </a-button>
|
<a-button type="primary" size="small" @click="handleAddConfig"> <PlusOutlined /> 新增配置项 </a-button>
|
||||||
</template>
|
</template>
|
||||||
<a-tab-pane v-for="group in groups" :key="group.value" :tab="group.label">
|
<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)">
|
<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 类型 -->
|
<!-- 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'" />
|
<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>
|
</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 />
|
<EditOutlined />
|
||||||
编辑
|
编辑
|
||||||
</a-button>
|
</a-button>
|
||||||
@@ -95,7 +95,6 @@ const configs = ref({})
|
|||||||
const formData = reactive({})
|
const formData = reactive({})
|
||||||
const jsonStrings = reactive({})
|
const jsonStrings = reactive({})
|
||||||
const jsonErrors = reactive({})
|
const jsonErrors = reactive({})
|
||||||
const visibleEditIcon = ref(null)
|
|
||||||
|
|
||||||
// 是否可以编辑系统配置
|
// 是否可以编辑系统配置
|
||||||
const canEditSystem = ref(false)
|
const canEditSystem = ref(false)
|
||||||
@@ -122,16 +121,6 @@ const getColumnSpan = (type) => {
|
|||||||
return 8
|
return 8
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== 显示编辑图标 =====
|
|
||||||
const showEditIcon = (id) => {
|
|
||||||
visibleEditIcon.value = id
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== 隐藏编辑图标 =====
|
|
||||||
const hideEditIcon = () => {
|
|
||||||
visibleEditIcon.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== 加载配置分组 =====
|
// ===== 加载配置分组 =====
|
||||||
const loadGroups = async () => {
|
const loadGroups = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -262,35 +251,40 @@ const handleSave = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查是否有需要保存的配置
|
||||||
|
const configList = Object.values(configs.value).flat()
|
||||||
|
if (configList.length === 0) {
|
||||||
|
message.warning('暂无配置项可保存')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 构建更新数据
|
// 构建更新数据
|
||||||
const updates = {}
|
const updates = []
|
||||||
Object.keys(formData).forEach((key) => {
|
Object.keys(formData).forEach((key) => {
|
||||||
const config = Object.values(configs.value)
|
const config = configList.find((c) => c.key === key)
|
||||||
.flat()
|
|
||||||
.find((c) => c.key === key)
|
|
||||||
if (config) {
|
if (config) {
|
||||||
let value = formData[key]
|
let value = formData[key]
|
||||||
// 转换 checkbox 类型为数组字符串
|
// 转换 checkbox 类型为数组字符串
|
||||||
if (config.type === 'checkbox') {
|
if (config.type === 'checkbox') {
|
||||||
value = JSON.stringify(value)
|
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 {
|
try {
|
||||||
saving.value = true
|
saving.value = true
|
||||||
const promises = Object.entries(updates).map(([key, value]) => {
|
const promises = updates.map((update) => systemApi.config.edit.put(update.id, { value: update.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 })
|
|
||||||
})
|
|
||||||
|
|
||||||
await Promise.all(promises)
|
await Promise.all(promises)
|
||||||
message.success('保存成功')
|
message.success('保存成功')
|
||||||
// 重新加载配置
|
// 重新加载配置
|
||||||
@@ -320,7 +314,7 @@ const handleReset = () => {
|
|||||||
|
|
||||||
// ===== 编辑配置项 =====
|
// ===== 编辑配置项 =====
|
||||||
const handleEditConfig = (config) => {
|
const handleEditConfig = (config) => {
|
||||||
currentConfig.value = { ...config }
|
currentConfig.value = config
|
||||||
dialog.save = true
|
dialog.save = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,6 +350,7 @@ onMounted(async () => {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
background: #f5f5f5;
|
||||||
|
|
||||||
.content-wrapper {
|
.content-wrapper {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -363,23 +358,63 @@ onMounted(async () => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
margin: 16px;
|
margin: 16px;
|
||||||
padding: 10px;
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
|
||||||
|
|
||||||
.config-tabs {
|
.config-tabs {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
:deep(.ant-tabs-content-holder) {
|
flex-direction: column;
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
: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) {
|
:deep(.ant-tabs-tab) {
|
||||||
text-align: left;
|
padding: 12px 20px;
|
||||||
padding: 12px 16px;
|
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) {
|
:deep(.ant-tabs-ink-bar) {
|
||||||
background-color: #e6f7ff;
|
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 {
|
.footer-bar {
|
||||||
padding: 12px 24px;
|
padding: 16px 24px;
|
||||||
border-top: 1px solid #f0f0f0;
|
border-top: 1px solid #f0f0f0;
|
||||||
background: #fafafa;
|
background: #fff;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
align-items: center;
|
||||||
|
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.04);
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
.config-item {
|
.ant-btn {
|
||||||
margin-bottom: 16px;
|
|
||||||
|
|
||||||
.config-item-wrapper {
|
|
||||||
padding: 16px;
|
|
||||||
border: 1px solid #f0f0f0;
|
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
transition: all 0.2s;
|
height: 36px;
|
||||||
|
padding: 0 24px;
|
||||||
&:hover {
|
font-weight: 500;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user