feat: 添加系统配置表单

This commit is contained in:
XIE7654
2025-09-27 10:33:54 +08:00
parent aaace900a6
commit 667ab9c02a
12 changed files with 596 additions and 0 deletions

View File

@@ -3,7 +3,13 @@ from datetime import datetime
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from system.models import Menu, MenuMeta from system.models import Menu, MenuMeta
import re import re
"""
自动生成 菜单 代码的 Django 管理命令
使用方法: python manage.py gen_menu_json <app> <model> <parent>
例如: python manage.py gen_menu_json system Config 系统管理
"""
# gen_menu_json --app system --model Config --parent 系统管理
def camel_to_snake(name): def camel_to_snake(name):
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()

View File

@@ -0,0 +1,128 @@
# Generated by Django 5.2.1 on 2025-09-27 02:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("system", "0003_loginlog_location"),
]
operations = [
migrations.CreateModel(
name="Config",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"remark",
models.CharField(
blank=True,
db_comment="备注",
help_text="备注",
max_length=256,
null=True,
verbose_name="备注",
),
),
(
"creator",
models.CharField(
blank=True,
db_comment="创建人",
help_text="创建人",
max_length=64,
null=True,
verbose_name="创建人",
),
),
(
"modifier",
models.CharField(
blank=True,
db_comment="修改人",
help_text="修改人",
max_length=64,
null=True,
verbose_name="修改人",
),
),
(
"update_time",
models.DateTimeField(
auto_now=True,
db_comment="修改时间",
help_text="修改时间",
null=True,
verbose_name="修改时间",
),
),
(
"create_time",
models.DateTimeField(
auto_now_add=True,
db_comment="创建时间",
help_text="创建时间",
null=True,
verbose_name="创建时间",
),
),
(
"is_deleted",
models.BooleanField(
db_comment="是否软删除",
default=False,
verbose_name="是否软删除",
),
),
(
"name",
models.CharField(
db_comment="参数名称",
default="",
max_length=100,
verbose_name="参数名称",
),
),
(
"key",
models.CharField(
db_comment="参数键名",
default="",
max_length=100,
verbose_name="参数键名",
),
),
(
"value",
models.CharField(
db_comment="参数键值",
default="",
max_length=500,
verbose_name="参数键值",
),
),
(
"config_type",
models.BooleanField(
db_comment="系统内置1是 0否",
default=False,
verbose_name="系统内置",
),
),
],
options={
"verbose_name": "参数配置",
"verbose_name_plural": "参数配置",
"ordering": ["-id"],
},
),
]

View File

@@ -306,3 +306,26 @@ class LoginLog(CoreModel):
def __str__(self): def __str__(self):
return f"{self.username} - {self.user_ip}" return f"{self.username} - {self.user_ip}"
class Config(CoreModel):
"""
参数配置表
"""
name = models.CharField(max_length=100, default='', db_comment='参数名称', verbose_name='参数名称')
key = models.CharField(max_length=100, default='', db_comment='参数键名', verbose_name='参数键名')
value = models.CharField(max_length=500, default='', db_comment='参数键值', verbose_name='参数键值')
config_type = models.BooleanField(
default=False,
db_comment='系统内置1是 0否',
verbose_name='系统内置'
)
class Meta:
verbose_name = '参数配置'
verbose_name_plural = verbose_name
ordering = ['-id']
def __str__(self):
return f"{self.name}({self.key})"

View File

@@ -13,6 +13,7 @@ router.register(r'dict_type', views.DictTypeViewSet)
router.register(r'post', views.PostViewSet) router.register(r'post', views.PostViewSet)
router.register(r'user', views.UserViewSet) router.register(r'user', views.UserViewSet)
router.register(r'login_log', views.LoginLogViewSet) router.register(r'login_log', views.LoginLogViewSet)
router.register(r'config', views.ConfigViewSet)
urlpatterns = [ urlpatterns = [
path('', include(router.urls)), path('', include(router.urls)),

View File

@@ -7,6 +7,7 @@ __all__ = [
'DictTypeViewSet', 'DictTypeViewSet',
'PostViewSet', 'PostViewSet',
'UserViewSet', 'UserViewSet',
'ConfigViewSet',
'LoginLogViewSet', 'LoginLogViewSet',
] ]
@@ -18,4 +19,5 @@ from system.views.role import RoleViewSet
from system.views.dept import DeptViewSet from system.views.dept import DeptViewSet
from system.views.post import PostViewSet from system.views.post import PostViewSet
from system.views.login_log import LoginLogViewSet from system.views.login_log import LoginLogViewSet
from system.views.config import ConfigViewSet
from system.views.user import * from system.views.user import *

View File

@@ -0,0 +1,37 @@
from system.models import Config
from utils.serializers import CustomModelSerializer
from utils.custom_model_viewSet import CustomModelViewSet
from django_filters import rest_framework as filters, CharFilter
class ConfigSerializer(CustomModelSerializer):
"""
参数配置 序列化器
"""
class Meta:
model = Config
fields = '__all__'
read_only_fields = ['id', 'create_time', 'update_time']
class ConfigFilter(filters.FilterSet):
name = CharFilter(field_name='name', lookup_expr='icontains')
key = CharFilter(field_name='key', lookup_expr='icontains')
value = CharFilter(field_name='value', lookup_expr='icontains')
remark = CharFilter(field_name='remark', lookup_expr='icontains')
class Meta:
model = Config
fields = ['id', 'remark', 'creator', 'modifier', 'is_deleted', 'name', 'key', 'value', 'config_type']
class ConfigViewSet(CustomModelViewSet):
"""
参数配置 视图集
"""
queryset = Config.objects.filter(is_deleted=False).order_by('-id')
serializer_class = ConfigSerializer
filterset_class = ConfigFilter
search_fields = ['name'] # 根据实际字段调整
ordering_fields = ['create_time', 'id']
ordering = ['-create_time']

View File

@@ -103,6 +103,10 @@
"login_log": { "login_log": {
"name": "login log", "name": "login log",
"title": "login log" "title": "login log"
},
"config": {
"name": "System Config",
"title": "System Config"
}, },
"status": "Status", "status": "Status",
"remark": "Remarks", "remark": "Remarks",

View File

@@ -105,6 +105,10 @@
"name": "登录日志", "name": "登录日志",
"title": "登录日志" "title": "登录日志"
}, },
"config": {
"name": "系统配置",
"title": "系统配置"
},
"status": "状态", "status": "状态",
"remark": "备注", "remark": "备注",
"creator": "创建人", "creator": "创建人",

View File

@@ -0,0 +1,23 @@
import { BaseModel } from '#/models/base';
export namespace SystemConfigApi {
export interface SystemConfig {
id: number;
remark: string;
creator: string;
modifier: string;
update_time: string;
create_time: string;
is_deleted: boolean;
name: string;
key: string;
value: string;
config_type: boolean;
}
}
export class SystemConfigModel extends BaseModel<SystemConfigApi.SystemConfig> {
constructor() {
super('/system/config/');
}
}

View File

@@ -0,0 +1,148 @@
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn } from '#/adapter/vxe-table';
import type { SystemConfigApi } from '#/models/system/config';
import { z } from '#/adapter/form';
import { $t } from '#/locales';
import { format_datetime } from '#/utils/date';
import { op } from '#/utils/permission';
/**
* 获取编辑表单的字段配置
*/
export function useSchema(): VbenFormSchema[] {
return [
{
component: 'Input',
fieldName: 'name',
label: '参数名称',
rules: z
.string()
.min(1, $t('ui.formRules.required', ['参数名称']))
.max(100, $t('ui.formRules.maxLength', ['参数名称', 100])),
},
{
component: 'Input',
fieldName: 'key',
label: '参数键名',
rules: z
.string()
.min(1, $t('ui.formRules.required', ['参数键名']))
.max(100, $t('ui.formRules.maxLength', ['参数键名', 100])),
},
{
component: 'Input',
fieldName: 'value',
label: '参数键值',
rules: z
.string()
.min(1, $t('ui.formRules.required', ['参数键值']))
.max(100, $t('ui.formRules.maxLength', ['参数键值', 100])),
},
{
component: 'RadioGroup',
componentProps: {
buttonStyle: 'solid',
options: [
{ label: '开启', value: 1 },
{ label: '关闭', value: 0 },
],
optionType: 'button',
},
defaultValue: 1,
fieldName: 'config_type',
label: '系统内置',
},
{
component: 'Input',
fieldName: 'remark',
label: '备注'
},
];
}
/**
* 获取编辑表单的字段配置
*/
export function useGridFormSchema(): VbenFormSchema[] {
return [
{
component: 'Input',
fieldName: 'name',
label: '参数名称',
},
{
component: 'Input',
fieldName: 'key',
label: '参数键名',
},
{
component: 'Input',
fieldName: 'value',
label: '参数键值',
},
];
}
/**
* 获取表格列配置
* @description 使用函数的形式返回列数据而不是直接export一个Array常量是为了响应语言切换时重新翻译表头
* @param onActionClick 表格操作按钮点击事件
*/
export function useColumns(
onActionClick?: OnActionClickFn<SystemConfigApi.SystemConfig>,
): VxeTableGridOptions<SystemConfigApi.SystemConfig>['columns'] {
return [
{
field: 'id',
title: 'ID',
},
{
field: 'name',
title: '参数名称',
},
{
field: 'key',
title: '参数键名',
},
{
field: 'value',
title: '参数键值',
},
{
field: 'config_type',
title: '系统内置',
},
{
field: 'remark',
title: '备注',
},
{
field: 'create_time',
title: '创建时间',
width: 150,
formatter: ({ cellValue }) => format_datetime(cellValue),
},
{
align: 'center',
cellRender: {
attrs: {
nameField: 'name',
nameTitle: $t('system.config.name'),
onClick: onActionClick,
},
name: 'CellOperation',
options: [
op('system:config:edit', 'edit'),
op('system:config:delete', 'delete'),
],
},
field: 'action',
fixed: 'right',
title: '操作',
width: 120,
},
];
}

View File

@@ -0,0 +1,141 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { SystemConfigApi } from '#/models/system/config';
import { Page, useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { Button, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { $t } from '#/locales';
import { SystemConfigModel } from '#/models/system/config';
import { useColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
const formModel = new SystemConfigModel();
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
destroyOnClose: true,
});
/**
* 编辑参数配置
*/
function onEdit(row: SystemConfigApi.SystemConfig) {
formModalApi.setData(row).open();
}
/**
* 创建新参数配置
*/
function onCreate() {
formModalApi.setData(null).open();
}
/**
* 删除参数配置
*/
function onDelete(row: SystemConfigApi.SystemConfig) {
const hideLoading = message.loading({
content: '删除参数配置',
duration: 0,
key: 'action_process_msg',
});
formModel
.delete(row.id)
.then(() => {
message.success({
content: '删除成功',
key: 'action_process_msg',
});
refreshGrid();
})
.catch(() => {
hideLoading();
});
}
/**
* 表格操作按钮的回调函数
*/
function onActionClick({
code,
row,
}: OnActionClickParams<SystemConfigApi.SystemConfig>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
submitOnChange: true,
},
gridEvents: {},
gridOptions: {
columns: useColumns(onActionClick),
height: 'auto',
keepSource: true,
pagerConfig: {
enabled: true,
},
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await formModel.list({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
toolbarConfig: {
custom: true,
export: false,
refresh: { code: 'query' },
zoom: true,
search: true,
},
} as VxeTableGridOptions,
});
/**
* 刷新表格
*/
function refreshGrid() {
gridApi.query();
}
</script>
<template>
<Page auto-content-height>
<FormModal @success="refreshGrid" />
<Grid table-title="参数配置">
<template #toolbar-tools>
<Button
type="primary"
@click="onCreate"
v-permission="'system:config:create'"
>
<Plus class="size-5" />
{{ $t('ui.actionTitle.create') }}
</Button>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,79 @@
<script lang="ts" setup>
import type { SystemConfigApi } from '#/models/system/config';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Button } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { $t } from '#/locales';
import { SystemConfigModel } from '#/models/system/config';
import { useSchema } from '../data';
const emit = defineEmits(['success']);
const formModel = new SystemConfigModel();
const formData = ref<SystemConfigApi.SystemConfig>();
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', [$t('system.config.name')])
: $t('ui.actionTitle.create', [$t('system.config.name')]);
});
const [Form, formApi] = useVbenForm({
layout: 'horizontal',
schema: useSchema(),
showDefaultActions: false,
});
function resetForm() {
formApi.resetForm();
formApi.setValues(formData.value || {});
}
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
const { valid } = await formApi.validate();
if (valid) {
modalApi.lock();
const data = await formApi.getValues();
try {
await (formData.value?.id
? formModel.update(formData.value.id, data)
: formModel.create(data));
await modalApi.close();
emit('success');
} finally {
modalApi.lock(false);
}
}
},
onOpenChange(isOpen) {
if (isOpen) {
const data = modalApi.getData<SystemConfigApi.SystemConfig>();
if (data) {
formData.value = data;
formApi.setValues(formData.value);
}
}
},
});
</script>
<template>
<Modal :title="getTitle">
<Form />
<template #prepend-footer>
<div class="flex-auto">
<Button type="primary" danger @click="resetForm">
{{ $t('common.reset') }}
</Button>
</div>
</template>
</Modal>
</template>
<style lang="css" scoped></style>