feat: 系统配置
Some checks failed
构建并部署前端到测试环境 / build-and-deploy (push) Has been cancelled

This commit is contained in:
luo
2026-07-25 14:13:49 +08:00
parent ee508c3e1e
commit dd6bceeeb7
20 changed files with 805 additions and 41 deletions

View File

@@ -0,0 +1,351 @@
<template>
<ArtTableFullScreen>
<div class="system-configs-page" id="table-full-screen">
<ArtSearchBar
v-model:filter="searchForm"
:items="searchFormItems"
:show-expand="false"
label-width="90px"
@reset="handleReset"
@search="handleSearch"
/>
<ElCard shadow="never" class="art-table-card">
<ArtTableHeader
:columnList="columnOptions"
v-model:columns="columnChecks"
@refresh="loadConfigs"
/>
<ArtTable
ref="tableRef"
row-key="config_key"
:loading="loading"
:data="configList"
:currentPage="pagination.page"
:pageSize="pagination.page_size"
:total="pagination.total"
:marginTop="10"
:actions="getActions"
:actionsWidth="100"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<template #default>
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
</template>
</ArtTable>
</ElCard>
<ElDialog
v-model="dialogVisible"
title="编辑系统配置"
width="560px"
:close-on-click-modal="false"
>
<ElForm label-width="120px">
<ElFormItem label="配置 Key">
<ElInput :model-value="currentConfig?.config_key || '-'" disabled />
</ElFormItem>
<ElFormItem label="配置说明">
<span>{{ currentConfig?.description || '-' }}</span>
</ElFormItem>
<ElFormItem label="配置值">
<ElSwitch v-if="isBooleanConfig" v-model="editBoolean" />
<ElInputNumber
v-else-if="isIntegerConfig"
v-model="editNumber"
:min="currentConfig?.min ?? undefined"
:max="currentConfig?.max ?? undefined"
:precision="0"
controls-position="right"
style="width: 100%"
/>
<ElSelect v-else-if="isEnumConfig" v-model="editValue" style="width: 100%">
<ElOption
v-for="item in currentConfig?.enum_values"
:key="item"
:value="item"
:label="item"
/>
</ElSelect>
<ElInput
v-else-if="isJsonConfig"
v-model="editValue"
type="textarea"
:rows="6"
placeholder="请输入 JSON 配置"
/>
<ElInput
v-else
v-model="editValue"
:type="currentConfig?.sensitive ? 'password' : 'text'"
:show-password="Boolean(currentConfig?.sensitive)"
placeholder="请输入配置值"
/>
</ElFormItem>
<div v-if="valueHint" class="config-hint">{{ valueHint }}</div>
</ElForm>
<template #footer>
<ElButton @click="dialogVisible = false">取消</ElButton>
<ElButton type="primary" :loading="submitLoading" @click="handleSubmit"> 保存 </ElButton>
</template>
</ElDialog>
</div>
</ArtTableFullScreen>
</template>
<script setup lang="ts">
import { computed, h, onMounted, reactive, ref } from 'vue'
import { ElMessage, ElTag } from 'element-plus'
import { SystemConfigService } from '@/api/modules'
import type { SearchFormItem } from '@/types'
import type { SystemConfigItem, SystemConfigModule } from '@/types/api/systemConfig'
import { formatDateTime } from '@/utils/business/format'
import { useCheckedColumns } from '@/composables/useCheckedColumns'
defineOptions({ name: 'SystemConfigs' })
const moduleOptions = [
{ label: '运营商回调配置', value: 'carrier_callback' },
{ label: 'C 端支付方式配置', value: 'c2b.payment' }
]
const searchForm = reactive<{ module?: SystemConfigModule }>({ module: undefined })
const searchFormItems: SearchFormItem[] = [
{
label: '配置模块',
prop: 'module',
type: 'select',
options: moduleOptions,
config: { clearable: true, placeholder: '请选择配置模块' }
}
]
const loading = ref(false)
const submitLoading = ref(false)
const dialogVisible = ref(false)
const tableRef = ref()
const currentConfig = ref<SystemConfigItem | null>(null)
const configList = ref<SystemConfigItem[]>([])
const editValue = ref('')
const originalValue = ref('')
const editBoolean = ref(false)
const editNumber = ref<number | null>(null)
const pagination = reactive({ page: 1, page_size: 20, total: 0 })
const columnOptions = [
{ label: '配置 Key', prop: 'config_key' },
{ label: '模块', prop: 'module' },
{ label: '配置说明', prop: 'description' },
{ label: '配置值', prop: 'value' },
{ label: '控件', prop: 'control' },
{ label: '注册状态', prop: 'registered' },
{ label: '只读', prop: 'readonly' },
{ label: '敏感', prop: 'sensitive' },
{ label: '更新时间', prop: 'updated_at' }
]
const moduleLabel = (module: SystemConfigModule) =>
moduleOptions.find((item) => item.value === module)?.label || module
const { columnChecks, columns } = useCheckedColumns(() => [
{ prop: 'config_key', label: '配置 Key', minWidth: 220, showOverflowTooltip: true },
{
prop: 'module',
label: '模块',
minWidth: 150,
formatter: (row: SystemConfigItem) => moduleLabel(row.module)
},
{ prop: 'description', label: '配置说明', minWidth: 180, showOverflowTooltip: true },
{
prop: 'value',
label: '配置值',
minWidth: 150,
formatter: (row: SystemConfigItem) => row.value || '-'
},
{ prop: 'control', label: '控件', width: 100 },
{
prop: 'registered',
label: '注册状态',
width: 100,
formatter: (row: SystemConfigItem) =>
h(ElTag, { type: row.registered ? 'success' : 'info' }, () =>
row.registered ? '已注册' : '未注册'
)
},
{
prop: 'readonly',
label: '只读',
width: 80,
formatter: (row: SystemConfigItem) => (row.readonly ? '是' : '否')
},
{
prop: 'sensitive',
label: '敏感',
width: 80,
formatter: (row: SystemConfigItem) => (row.sensitive ? '是' : '否')
},
{
prop: 'updated_at',
label: '更新时间',
width: 180,
formatter: (row: SystemConfigItem) => formatDateTime(row.updated_at)
}
])
const isBooleanConfig = computed(
() => currentConfig.value?.value_type === 'bool' || currentConfig.value?.control === 'switch'
)
const isIntegerConfig = computed(
() => !isBooleanConfig.value && currentConfig.value?.value_type === 'int'
)
const isEnumConfig = computed(
() =>
!isBooleanConfig.value &&
!isIntegerConfig.value &&
Boolean(currentConfig.value?.enum_values?.length)
)
const isJsonConfig = computed(
() =>
!isBooleanConfig.value && !isIntegerConfig.value && currentConfig.value?.value_type === 'json'
)
const valueHint = computed(() => {
if (!currentConfig.value) return ''
const { min, max } = currentConfig.value
if (min !== null && max !== null) return `取值范围:${min}${max}`
if (min !== null) return `最小值:${min}`
if (max !== null) return `最大值:${max}`
return ''
})
const getSubmittedValue = () => {
if (isBooleanConfig.value) return String(editBoolean.value)
if (isIntegerConfig.value) return editNumber.value === null ? '' : String(editNumber.value)
return editValue.value
}
const validateValue = (value: string) => {
const config = currentConfig.value
if (!config) return '配置不存在'
if (config.enum_values.length > 0 && !config.enum_values.includes(value)) {
return '配置值不在允许的枚举范围内'
}
if (config.value_type === 'int') {
if (!/^-?\d+$/.test(value)) return '请输入整数'
const numberValue = Number(value)
if (config.min !== null && numberValue < config.min) return `配置值不能小于 ${config.min}`
if (config.max !== null && numberValue > config.max) return `配置值不能大于 ${config.max}`
}
if (config.value_type === 'json') {
try {
JSON.parse(value)
} catch {
return '请输入有效的 JSON'
}
}
if (value === '') return '配置值不能为空'
return ''
}
const getActions = (row: SystemConfigItem) =>
row.readonly
? []
: [{ label: '编辑', handler: () => showEditDialog(row), type: 'primary' as const }]
const loadConfigs = async () => {
loading.value = true
try {
const res = await SystemConfigService.getSystemConfigs({
page: pagination.page,
page_size: pagination.page_size,
module: searchForm.module
})
if (res.code === 0) {
configList.value = res.data.list || []
pagination.total = res.data.total || 0
}
} catch (error) {
console.error('获取系统配置失败:', error)
} finally {
loading.value = false
}
}
const showEditDialog = (row: SystemConfigItem) => {
currentConfig.value = row
originalValue.value = row.value
editValue.value = row.value
editBoolean.value = row.value === 'true'
editNumber.value = /^-?\d+$/.test(row.value) ? Number(row.value) : null
dialogVisible.value = true
}
const handleSubmit = async () => {
if (!currentConfig.value || currentConfig.value.readonly) return
const value = getSubmittedValue()
if (currentConfig.value.sensitive && value === originalValue.value) {
ElMessage.warning('敏感配置未输入新值,无需保存')
return
}
const errorMessage = validateValue(value)
if (errorMessage) {
ElMessage.warning(errorMessage)
return
}
submitLoading.value = true
try {
const res = await SystemConfigService.updateSystemConfig(currentConfig.value.config_key, {
key: currentConfig.value.config_key,
value
})
if (res.code === 0) {
ElMessage.success('系统配置更新成功')
dialogVisible.value = false
await loadConfigs()
} else {
ElMessage.error(res.msg || '系统配置更新失败')
}
} catch (error) {
console.error('更新系统配置失败:', error)
} finally {
submitLoading.value = false
}
}
const handleReset = () => {
searchForm.module = undefined
pagination.page = 1
loadConfigs()
}
const handleSearch = () => {
pagination.page = 1
loadConfigs()
}
const handleSizeChange = (size: number) => {
pagination.page_size = size
loadConfigs()
}
const handleCurrentChange = (page: number) => {
pagination.page = page
loadConfigs()
}
onMounted(loadConfigs)
</script>
<style scoped lang="scss">
.system-configs-page {
.config-hint {
margin: -12px 0 0 120px;
font-size: 12px;
color: var(--el-text-color-secondary);
}
}
</style>