签入版本

This commit is contained in:
2026-09-12 12:07:15 +08:00
commit 0277034d24
939 changed files with 129356 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.
+82
View File
@@ -0,0 +1,82 @@
# +======================================================================
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
# +----------------------------------------------------------------------
# | Copyright (c) 2021-2026 小蚂蚁云团队 版权所有
# +----------------------------------------------------------------------
# | 开源协议: Apache-2.0
# | 重要声明: 未经书面许可,不得移除或修改本框架相关版权标识。
# +----------------------------------------------------------------------
# | 官方网站: https://www.djangoadmin.cn
# +----------------------------------------------------------------------
# | 研发团队: 小蚂蚁云团队 荣誉出品
# +======================================================================
# | 版权和免责声明:
# | 一、知识产权声明
# | 本团队对该软件框架产品(以下简称“本产品”)拥有完整知识产权,包括但不限于商标权、专利权、
# | 著作权、商业秘密等。前述权利均受中华人民共和国及相关国际法律法规保护。未经本团队书面授权,
# | 任何个人或单位不得基于本产品本身申请任何形式的知识产权。
# | 二、使用限制与禁止条款
# | 您理解并同意,禁止将本产品用于以下任何行为:
# | 1. 任何违反中华人民共和国法律法规的用途;
# | 2. 任何侵害他人合法权益(包括但不限于人身权、财产权、知识产权等)的行为;
# | 3. 任何危害国家安全、公共利益及社会道德风尚的行为。
# | 三、责任豁免
# | 因使用本产品而产生的任何损失或法律纠纷(包括但不限于直接损失、间接损失、附带损失、衍生损失
# | 以及因违约、侵权等引发的责任),本团队均不承担任何法律责任。若因违规使用本产品导致任何第三
# | 方提起索赔或行政机关进行调查,您应独立承担全部责任。
# | 四、违约与法律责任
# | 如发现任何违反本声明的行为,本团队有权:
# | 1. 无条件配合公安机关及其他有权机关的调查取证工作;
# | 2. 依法追究相关方的民事责任,并要求赔偿全部损失;
# | 3. 保留进一步采取刑事控告等法律措施的权利。
# | 五、许可范围说明
# | 1、被授权方应视本产品源代码为核心机密信息,负有最高标准的保密与保管义务。禁止以任何形式(包
# | 括但不限于直接或间接的分享、复制、传播、分发、转售、出租、开源、反向工程、反编译、反汇编或通
# | 过网络服务提供给任何第三方)泄露、披露或处置源代码的全部或部分内容。任何未经授权的复制、使用、
# | 传播行为均构成对本团队知识产权的严重侵权,本团队有权依法追究其全部法律责任,并要求赔偿因此造
# | 成的一切经济损失及商誉损害。
# | 2、本产品仅限于公司或个人在法律允许范围内,用于内部且合规的软件产品研发。具体权利义务以《软件
# | 许可协议》附件为准。
# +======================================================================
"""
{{ module_comment }}应用配置模块模板
=============================================================
该模板用于生成{{ module_comment }}管理应用的配置类,用于Django应用注册和初始化。
模板变量说明:
- module_comment: 模块注释,如"文章管理"
- app_name: 应用名称,如"article"
- model_class_name: 模型类名,如"Article"
"""
from django.apps import AppConfig
class {{ model_class_name }}Config(AppConfig):
"""
{{ module_comment }}应用配置类
该类用于配置 'application.{{ app_name }}' 应用的基本属性和行为。
继承自 Django 的 AppConfig 基类,在 Django 启动时自动加载。
主要功能:
- 指定应用的默认主键字段类型
- 定义应用的 Python 导入路径
- 可在 ready() 方法中执行应用启动时的初始化操作
"""
# 默认主键字段类型配置
# BigAutoField:64位整数自增主键,范围更大(-9223372036854775808 到 9223372036854775807)
# 相比默认的 AutoField(32位整数),BigAutoField 的优势:
# 1. 支持更大的数据量,适合高并发、大数据量场景
# 2. 避免主键溢出的风险,提高系统稳定性
# 3. 与分布式系统兼容性更好
default_auto_field = 'django.db.models.BigAutoField'
# 应用导入路径配置
# 指定该应用在项目中的 Python 导入路径,格式为 '模块名.应用名'
# 该路径必须与 INSTALLED_APPS 设置中的路径完全一致
# 例如:'application.{{ app_name }}' 表示应用位于 application/{{ app_name }} 目录下
name = 'application.{{ app_name }}'
+183
View File
@@ -0,0 +1,183 @@
# +======================================================================
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
# +----------------------------------------------------------------------
# | Copyright (c) 2021-2026 小蚂蚁云团队 版权所有
# +----------------------------------------------------------------------
# | 开源协议: Apache-2.0
# | 重要声明: 未经书面许可,不得移除或修改本框架相关版权标识。
# +----------------------------------------------------------------------
# | 官方网站: https://www.djangoadmin.cn
# +----------------------------------------------------------------------
# | 研发团队: 小蚂蚁云团队 荣誉出品
# +======================================================================
# | 版权和免责声明:
# | 一、知识产权声明
# | 本团队对该软件框架产品(以下简称“本产品”)拥有完整知识产权,包括但不限于商标权、专利权、
# | 著作权、商业秘密等。前述权利均受中华人民共和国及相关国际法律法规保护。未经本团队书面授权,
# | 任何个人或单位不得基于本产品本身申请任何形式的知识产权。
# | 二、使用限制与禁止条款
# | 您理解并同意,禁止将本产品用于以下任何行为:
# | 1. 任何违反中华人民共和国法律法规的用途;
# | 2. 任何侵害他人合法权益(包括但不限于人身权、财产权、知识产权等)的行为;
# | 3. 任何危害国家安全、公共利益及社会道德风尚的行为。
# | 三、责任豁免
# | 因使用本产品而产生的任何损失或法律纠纷(包括但不限于直接损失、间接损失、附带损失、衍生损失
# | 以及因违约、侵权等引发的责任),本团队均不承担任何法律责任。若因违规使用本产品导致任何第三
# | 方提起索赔或行政机关进行调查,您应独立承担全部责任。
# | 四、违约与法律责任
# | 如发现任何违反本声明的行为,本团队有权:
# | 1. 无条件配合公安机关及其他有权机关的调查取证工作;
# | 2. 依法追究相关方的民事责任,并要求赔偿全部损失;
# | 3. 保留进一步采取刑事控告等法律措施的权利。
# | 五、许可范围说明
# | 1、被授权方应视本产品源代码为核心机密信息,负有最高标准的保密与保管义务。禁止以任何形式(包
# | 括但不限于直接或间接的分享、复制、传播、分发、转售、出租、开源、反向工程、反编译、反汇编或通
# | 过网络服务提供给任何第三方)泄露、披露或处置源代码的全部或部分内容。任何未经授权的复制、使用、
# | 传播行为均构成对本团队知识产权的严重侵权,本团队有权依法追究其全部法律责任,并要求赔偿因此造
# | 成的一切经济损失及商誉损害。
# | 2、本产品仅限于公司或个人在法律允许范围内,用于内部且合规的软件产品研发。具体权利义务以《软件
# | 许可协议》附件为准。
# +======================================================================
"""
{{ module_comment }}表单验证模块模板
=============================================================
该模板用于生成{{ module_comment }}管理功能中使用的表单验证类。
用于对前端提交的{{ module_comment }}数据进行验证,确保数据符合业务规则和数据库约束。
{{ module_comment }}用于{{ business_description }}。
模板变量说明:
- module_comment: 模块注释,如"文章管理"
- app_name: 应用名称,如"article"
- model_class_name: 模型类名,如"Article"
- form_fields: 表单字段列表
- has_unique_code: 是否包含唯一编码字段
- business_description: 业务描述
- business_description_detail: 详细业务描述
"""
from django import forms
from application.{{ app_name }} import models
# =============================================================
# {{ module_comment }}表单验证类
# =============================================================
class {{ model_class_name }}Form(forms.ModelForm):
"""
{{ module_comment }}表单验证类
用于创建和更新{{ module_comment }}信息时的表单验证。
{{ business_description_detail }}
验证字段包括:
{% for field in form_fields %}
- {{ field.camel_name }}: {{ field.comment }}
{% endfor %}
"""
{% for field in form_fields %}
# ----------------------------------------------------------
# {{ field.comment }}
# ----------------------------------------------------------
# 字段说明: {{ field.purpose }}
# 字段类型: {{ field.type_display }}
# 验证规则: {% if field.required %}必填(required=True){% else %}可选填(required=False){% endif %}{% if field.max_length %},最大长度{{ field.max_length }}字符{% endif %}{% if field.min_value != None %},最小值{{ field.min_value }}{% endif %}{% if field.max_value != None %},最大值{{ field.max_value }}{% endif %}
# 业务说明: {{ field.purpose }}
# 示例: {{ field.example }}
{{ field.camel_name }} = forms.{{ field.form_field_type }}(
{% if field.required %}
required=True,
{% else %}
required=False,
{% endif %}
{% if field.max_length %}
max_length={{ field.max_length }},
{% endif %}
{% if field.min_value != None %}
min_value={{ field.min_value }},
{% endif %}
{% if field.max_value != None %}
max_value={{ field.max_value }},
{% endif %}
error_messages={
{% if field.required %}
'required': '{{ field.label }}不能为空',
{% endif %}
{% if field.max_length %}
'max_length': '{{ field.label }}长度不得超过{{ field.max_length }}个字符',
{% endif %}
{% if field.min_value != None %}
'min_value': '{{ field.label }}不能小于{{ field.min_value }}',
{% endif %}
{% if field.max_value != None %}
'max_value': '{{ field.label }}不能大于{{ field.max_value }}',
{% endif %}
}
)
{% endfor %}
# =============================================================
# 表单元数据配置
# =============================================================
class Meta:
"""
表单元数据配置类
定义表单绑定的模型和需要验证的字段。
"""
# 绑定模型
# 指定该表单对应的数据模型为 {{ model_class_name }}({{ module_comment }})
model = models.{{ model_class_name }}
# 指定需要验证的字段
# 只验证以下字段,其他字段不进行表单验证
fields = [
{% for field in form_fields %}
'{{ field.camel_name }}',
{% endfor %}
]
{% if has_unique_code %}
# =============================================================
# 自定义验证方法
# =============================================================
def clean_code(self):
"""
自定义编码唯一性验证
验证编码的唯一性,确保编码不重复。
Returns:
str: 清洗后的编码
Raises:
forms.ValidationError: 编码格式错误或编码已存在时抛出
"""
code = self.cleaned_data.get('code')
if code:
# 验证编码格式:只允许大写字母、数字和下划线
if not code.replace('_', '').isalnum() or not code.isupper():
raise forms.ValidationError('编码只能包含大写字母、数字和下划线')
# 验证编码唯一性(排除当前实例)
instance = getattr(self, 'instance', None)
queryset = models.{{ model_class_name }}.objects.filter(code=code)
# 如果是更新操作,排除当前记录本身
if instance and instance.pk:
queryset = queryset.exclude(pk=instance.pk)
# 如果编码已存在,抛出验证错误
if queryset.exists():
raise forms.ValidationError('编码已存在,请使用其他编码')
return code
{% endif %}
+185
View File
@@ -0,0 +1,185 @@
# +======================================================================
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
# +----------------------------------------------------------------------
# | Copyright (c) 2021-2026 小蚂蚁云团队 版权所有
# +----------------------------------------------------------------------
# | 开源协议: Apache-2.0
# | 重要声明: 未经书面许可,不得移除或修改本框架相关版权标识。
# +----------------------------------------------------------------------
# | 官方网站: https://www.djangoadmin.cn
# +----------------------------------------------------------------------
# | 研发团队: 小蚂蚁云团队 荣誉出品
# +======================================================================
# | 版权和免责声明:
# | 一、知识产权声明
# | 本团队对该软件框架产品(以下简称“本产品”)拥有完整知识产权,包括但不限于商标权、专利权、
# | 著作权、商业秘密等。前述权利均受中华人民共和国及相关国际法律法规保护。未经本团队书面授权,
# | 任何个人或单位不得基于本产品本身申请任何形式的知识产权。
# | 二、使用限制与禁止条款
# | 您理解并同意,禁止将本产品用于以下任何行为:
# | 1. 任何违反中华人民共和国法律法规的用途;
# | 2. 任何侵害他人合法权益(包括但不限于人身权、财产权、知识产权等)的行为;
# | 3. 任何危害国家安全、公共利益及社会道德风尚的行为。
# | 三、责任豁免
# | 因使用本产品而产生的任何损失或法律纠纷(包括但不限于直接损失、间接损失、附带损失、衍生损失
# | 以及因违约、侵权等引发的责任),本团队均不承担任何法律责任。若因违规使用本产品导致任何第三
# | 方提起索赔或行政机关进行调查,您应独立承担全部责任。
# | 四、违约与法律责任
# | 如发现任何违反本声明的行为,本团队有权:
# | 1. 无条件配合公安机关及其他有权机关的调查取证工作;
# | 2. 依法追究相关方的民事责任,并要求赔偿全部损失;
# | 3. 保留进一步采取刑事控告等法律措施的权利。
# | 五、许可范围说明
# | 1、被授权方应视本产品源代码为核心机密信息,负有最高标准的保密与保管义务。禁止以任何形式(包
# | 括但不限于直接或间接的分享、复制、传播、分发、转售、出租、开源、反向工程、反编译、反汇编或通
# | 过网络服务提供给任何第三方)泄露、披露或处置源代码的全部或部分内容。任何未经授权的复制、使用、
# | 传播行为均构成对本团队知识产权的严重侵权,本团队有权依法追究其全部法律责任,并要求赔偿因此造
# | 成的一切经济损失及商誉损害。
# | 2、本产品仅限于公司或个人在法律允许范围内,用于内部且合规的软件产品研发。具体权利义务以《软件
# | 许可协议》附件为准。
# +======================================================================
"""
{{ module_comment }}模型模块模板
=============================================================
该模板用于生成{{ module_comment }}相关的数据库模型。
{{ module_comment }}用于{{ business_description }}。
模板变量说明:
- module_comment: 模块注释,如"文章管理"
- model_class_name: 模型类名,如"Article"
- table_name: 数据表名,如"article"
- fields: 字段列表
- has_sort: 是否包含排序字段
- display_field: 显示字段名
- business_description: 业务描述
- business_description_detail: 详细业务描述
"""
from django.db import models
from application.models import BaseModel
from utils.common import get_table_name
# =============================================================
# {{ module_comment }}模型类
# =============================================================
class {{ model_class_name }}(BaseModel):
"""
{{ module_comment }}模型类
用于存储系统中的{{ module_comment }}信息。
{{ business_description_detail }}
继承自 BaseModel,自动包含:
- id: 主键
- is_delete: 逻辑删除标识
- create_user: 创建人
- create_time: 创建时间
- update_user: 更新人
- update_time: 更新时间
主要业务字段:
{% for field in fields %}
- {{ field.name }}: {{ field.comment }}
{% endfor %}
"""
{% for field in fields %}
# ----------------------------------------------------------
# {{ field.comment }}
# ----------------------------------------------------------
# 字段说明: {{ field.purpose }}
# 字段类型: {{ field.type_display }}
# 约束: {% if field.nullable %}允许为空(null=True){% else %}非空(null=False){% endif %}{% if field.max_length %},最大长度{{ field.max_length }}字符{% endif %}{% if field.is_unique %},唯一约束{% endif %}
# 索引: {% if field.db_index %}添加了数据库索引(db_index=True)以提升查询效率{% else %}未添加索引{% endif %}
# 示例: {{ field.example }}
# 作用: {{ field.purpose }}
{% if field.choices %}
# 选项常量定义
{{ field.name|upper }}_CHOICES = (
{% for choice in field.choices %}
({{ choice.value }}, "{{ choice.label }}"), # {{ choice.label }}状态
{% endfor %}
)
{% endif %}
{{ field.name }} = models.{{ field.model_field_type }}(
{% if field.nullable %}
null=True,
blank=True,
{% else %}
null=False,
{% endif %}
{% if field.max_length %}
max_length={{ field.max_length }},
{% endif %}
{% if field.is_unique %}
unique=True,
{% endif %}
{% if field.choices %}
choices={{ field.name|upper }}_CHOICES,
{% endif %}
{% if field.default != None and field.default != 'None' %}
{% if not field.nullable and field.default == 'None' %}
default='',
db_default='',
{% else %}
default={{ field.default }},
db_default={{ field.default }},
{% endif %}
{% endif %}
{% if field.db_index %}
db_index=True,
{% endif %}
verbose_name="{{ field.comment }}",
help_text="{{ field.comment }}",
db_comment="{{ field.comment }}" # 数据库字段注释
)
{% endfor %}
# =============================================================
# 模型元数据配置
# =============================================================
class Meta:
"""
模型元数据配置类
定义模型在数据库中的表名、显示名称、默认排序规则等。
"""
# 数据表名
# 使用配置文件中的表前缀 + "{{ table_name }}",避免与其他应用表名冲突
db_table = get_table_name('{{ table_name }}')
# 表注释(Django 3.2+ 支持)
# 在数据库中生成 COMMENT 语句,便于数据库管理
db_table_comment = "{{ module_comment }}表"
# 模型在admin后台显示的友好名称(单数)
verbose_name = "{{ module_comment }}表"
# 模型在admin后台显示的友好名称(复数)
verbose_name_plural = verbose_name
# 默认排序规则
# 按照 {% if has_sort %}sort 字段升序排列(数值小的在前){% else %}创建时间倒序排列(最新的在前){% endif %},确保显示顺序可控
ordering = ({% if has_sort %}"sort"{% else %}"-create_time"{% endif %},)
# =============================================================
# 实例方法
# =============================================================
def __str__(self):
"""
模型的字符串表示方法
当需要将模型实例转换为字符串时调用,如admin后台显示、日志输出等。
Returns:
str: 包含{{ module_comment }}标识信息的字符串
"""
return f"{{ module_comment }}{self.{{ display_field }}}"
+524
View File
@@ -0,0 +1,524 @@
# -*- coding: utf-8 -*-
"""
{{ module_comment }}管理服务模块模板
=============================================================
该模板用于生成{{ module_comment }}管理相关的所有业务逻辑。
包括{{ module_comment }}的分页查询、详情查询、添加、更新、删除等功能。
{{ business_description_detail }}
模板变量说明:
- module_comment: 模块注释,如"文章管理"
- app_name: 应用名称,如"article"
- model_class_name: 模型类名,如"Article"
- primary_key: 主键字段名,如"id"
- filter_fields: 筛选字段列表
- fields: 所有字段列表
- form_fields: 表单字段列表
- list_fields: 列表展示字段列表
- has_status: 是否包含状态字段
- has_sort: 是否包含排序字段
"""
import json
import logging
from django.core.paginator import Paginator, PageNotAnInteger, InvalidPage, EmptyPage
from application.{{ app_name }} import forms
from application.{{ app_name }}.models import {{ model_class_name }}
from config.env import DJANGO_FILE_URL
from constant.constants import PAGE_SIZE
from utils import R, regular
from utils.common import get_file_url, format_datetime, parse_request_body, save_file, save_edit_content
from utils.security import get_username
# =============================================================
# 分页查询服务
# =============================================================
def get_{{ app_name }}_page(request):
"""
查询{{ module_comment }}分页数据
Args:
request: HttpRequest对象,包含查询参数
- pageNo: 当前页码(可选,默认为1)
- pageSize: 每页记录数(可选,默认为PAGE_SIZE)
{% for filter_field in filter_fields %}
- {{ filter_field.name }}: {{ filter_field.comment }}(可选,{{ filter_field.filter_type }})
{% endfor %}
Returns:
R: 包含分页数据的JSON响应
"""
try:
# 获取分页参数
page_no = int(request.GET.get('pageNo', 1))
page_size = int(request.GET.get('pageSize', PAGE_SIZE))
# 构建基础查询(只查询未删除的记录)
query = {{ model_class_name }}.objects.filter(is_delete=False)
# 应用筛选条件
query = _apply_filters(query, request)
# 按排序字段排列
query = query.order_by({% if has_sort %}"sort"{% else %}"-create_time"{% endif %})
# 执行分页查询
paginator = Paginator(query, page_size)
try:
page_list = paginator.page(page_no)
except (PageNotAnInteger, InvalidPage, EmptyPage):
# 页码无效时默认返回第一页
page_list = paginator.page(1)
page_no = 1
# 构建并返回分页数据
page_data = _build_page_data(page_list, paginator, page_no, page_size)
return R.ok(data=page_data)
except Exception as e:
logging.error(f"查询{{ module_comment }}分页数据异常: {str(e)}")
return R.failed(msg="查询失败,请稍后重试")
def _apply_filters(queryset, request):
"""
应用查询筛选条件(私有辅助函数)
Args:
queryset: 初始查询集
request: HttpRequest对象,包含筛选参数
Returns:
QuerySet: 应用筛选后的查询集
"""
{% for filter_field in filter_fields %}
# {{ filter_field.comment }}筛选
{{ filter_field.name }} = request.GET.get('{{ filter_field.name }}')
if {{ filter_field.name }}:
{% if filter_field.filter_type == '模糊查询' %}
queryset = queryset.filter({{ filter_field.name }}__contains={{ filter_field.name }})
{% else %}
queryset = queryset.filter({{ filter_field.name }}={{ filter_field.name }})
{% endif %}
{% endfor %}
return queryset
def _build_page_data(page_list, paginator, page_no, page_size):
"""
构建分页返回数据(私有辅助函数)
Args:
page_list: 当前页数据列表(Page对象)
paginator: 分页器对象
page_no: 当前页码
page_size: 每页大小
Returns:
dict: 标准格式的分页数据
"""
records = []
for item in page_list:
record = {
'id': item.id,
{% for field in list_fields %}
'{{ field.camel_name }}': {% if field.is_image %}get_file_url(item.{{ field.name }}) if item.{{ field.name }} else ""{% else %}item.{{ field.name }}{% endif %},
{% endfor %}
'createUser': item.create_user,
'createTime': format_datetime(item.create_time),
'updateUser': item.update_user,
'updateTime': format_datetime(item.update_time),
}
records.append(record)
return {
'records': records,
'total': paginator.count, # 总记录数
'size': page_size, # 每页大小
'current': page_no, # 当前页码
'pages': paginator.num_pages # 总页数
}
# =============================================================
# 列表查询服务(不分页)
# =============================================================
def get_{{ app_name }}_list(request):
"""
获取{{ module_comment }}列表(不分页,支持筛选条件)
与分页查询的区别:返回所有符合条件的记录,不分页。
通常用于导出数据或内部调用。
Args:
request: HttpRequest对象,包含查询参数
{% for filter_field in filter_fields %}
- {{ filter_field.name }}: {{ filter_field.comment }}(可选,{{ filter_field.filter_type }})
{% endfor %}
Returns:
list: {{ module_comment }}列表,每个元素为{{ module_comment }}模型字典
查询失败时返回空列表
"""
try:
# 构建基础查询
query = {{ model_class_name }}.objects.filter(is_delete=False)
# 应用筛选条件(复用 _apply_filters 方法)
query = _apply_filters(query, request)
# 按排序字段排列
query = query.order_by({% if has_sort %}"sort"{% else %}"-create_time"{% endif %})
# 构建返回数据
records = []
for item in query:
record = {
'id': item.id,
{% for field in list_fields %}
'{{ field.camel_name }}': {% if field.is_image %}get_file_url(item.{{ field.name }}) if item.{{ field.name }} else ""{% else %}item.{{ field.name }}{% endif %},
{% endfor %}
'createUser': item.create_user,
'createTime': format_datetime(item.create_time),
'updateUser': item.update_user,
'updateTime': format_datetime(item.update_time),
}
records.append(record)
return records
except Exception as e:
logging.error(f"查询{{ module_comment }}列表异常: {str(e)}")
return []
# =============================================================
# 详情查询服务
# =============================================================
def get_{{ app_name }}_detail({{ primary_key }}):
"""
根据ID查询{{ module_comment }}详情
Args:
{{ primary_key }}: {{ module_comment }}ID
Returns:
dict/None: {{ module_comment }}详情字典,不存在时返回None
"""
try:
# 查询未删除的{{ module_comment }}记录
instance = {{ model_class_name }}.objects.filter(is_delete=False, id={{ primary_key }}).first()
if not instance:
return None
# 处理富文本字段
{% for field in fields %}
{% if field.is_rich_text %}
{{ field.name }} = instance.{{ field.name }}
if {{ field.name }}:
{{ field.name }} = {{ field.name }}.replace("[IMG_URL]", DJANGO_FILE_URL)
{% endif %}
{% endfor %}
# 返回详情数据
return {
'id': instance.id,
{% for field in fields %}
'{{ field.camel_name }}': {% if field.is_image %}get_file_url(instance.{{ field.name }}) if instance.{{ field.name }} else ""{% elif field.is_rich_text %}{{ field.name }}{% else %}instance.{{ field.name }}{% endif %},
{% endfor %}
}
except Exception as e:
logging.error(f"查询{{ module_comment }}详情异常,ID: { {{ primary_key }} }, 错误: {str(e)}")
return None
# =============================================================
# 添加服务
# =============================================================
def add_{{ app_name }}(request):
"""
添加{{ module_comment }}
处理流程:
1. 解析请求体中的JSON数据
2. 表单验证
{% for field in form_fields %}{% if field.is_image %}3. 处理{{ field.comment }}(保存到 {{ app_name }} 目录){% endif %}{% endfor %}
{% for field in form_fields %}{% if field.is_rich_text %}4. 处理{{ field.comment }}(保存富文本内容){% endif %}{% endfor %}
5. 创建数据库记录
Args:
request: HttpRequest对象,包含{{ module_comment }}信息的JSON数据
Returns:
R: 操作结果(成功/失败)
"""
# 解析请求参数
data, error = parse_request_body(request)
if error:
return R.failed(msg=error)
# 表单验证
form = forms.{{ model_class_name }}Form(data)
if not form.is_valid():
return R.failed(msg=regular.get_err(form))
try:
cleaned_data = form.cleaned_data
# 处理图片字段
{% for field in form_fields %}
{% if field.is_image %}
{{ field.camel_name }} = cleaned_data.get('{{ field.camel_name }}')
if {{ field.camel_name }}:
{{ field.camel_name }} = save_file({{ field.camel_name }}, "{{ app_name }}")
{% endif %}
{% endfor %}
# 处理富文本字段
{% for field in form_fields %}
{% if field.is_rich_text %}
{{ field.camel_name }} = cleaned_data.get('{{ field.camel_name }}')
if {{ field.camel_name }}:
{{ field.camel_name }} = save_edit_content({{ field.camel_name }}, None, "{{ app_name }}")
{% endif %}
{% endfor %}
# 创建{{ module_comment }}记录
{{ model_class_name }}.objects.create(
{% for field in form_fields %}
{{ field.name }}={% if field.is_image %}{{ field.camel_name }}{% elif field.is_rich_text %}{{ field.camel_name }}{% else %}cleaned_data.get('{{ field.camel_name }}'){% endif %},
{% endfor %}
create_user=get_username(request),
update_user=get_username(request)
)
return R.ok(msg="创建成功")
except Exception as e:
logging.error(f"添加{{ module_comment }}异常: {str(e)}")
return R.failed(msg="添加失败,请稍后重试")
# =============================================================
# 更新服务
# =============================================================
def update_{{ app_name }}(request):
"""
更新{{ module_comment }}
处理流程:
1. 解析请求体中的JSON数据
2. 验证{{ module_comment }}ID
3. 表单验证
4. 查询现有记录
5. 处理图片字段(如有新图片则保存)
6. 处理富文本字段
7. 更新各字段
8. 保存更新
Args:
request: HttpRequest对象,包含{{ module_comment }}ID和更新信息的JSON数据
Returns:
R: 操作结果(成功/失败)
"""
# 解析请求参数
data, error = parse_request_body(request)
if error:
return R.failed(msg=error)
# 验证{{ module_comment }}ID
instance_id = data.get('id')
if not instance_id or not str(instance_id).isdigit() or int(instance_id) <= 0:
return R.failed("{{ module_comment }}ID无效")
instance_id = int(instance_id)
# 表单验证
form = forms.{{ model_class_name }}Form(data)
if not form.is_valid():
return R.failed(msg=regular.get_err(form))
try:
# 查询{{ module_comment }}记录
instance = {{ model_class_name }}.objects.filter(id=instance_id, is_delete=False).first()
if not instance:
return R.failed("{{ module_comment }}不存在")
cleaned_data = form.cleaned_data
# 处理图片字段(仅当有新图片且与当前不同时保存)
{% for field in form_fields %}
{% if field.is_image %}
{{ field.camel_name }} = cleaned_data.get('{{ field.camel_name }}')
if {{ field.camel_name }} and {{ field.camel_name }} != instance.{{ field.name }}:
{{ field.camel_name }} = save_file({{ field.camel_name }}, "{{ app_name }}")
else:
{{ field.camel_name }} = instance.{{ field.name }}
{% endif %}
{% endfor %}
# 处理富文本字段
{% for field in form_fields %}
{% if field.is_rich_text %}
{{ field.camel_name }} = cleaned_data.get('{{ field.camel_name }}')
if {{ field.camel_name }}:
{{ field.camel_name }} = save_edit_content({{ field.camel_name }}, None, "{{ app_name }}")
else:
{{ field.camel_name }} = instance.{{ field.name }}
{% endif %}
{% endfor %}
# 更新各字段
{% for field in form_fields %}
{% if field.is_image %}
instance.{{ field.name }} = {{ field.camel_name }}
{% elif field.is_rich_text %}
instance.{{ field.name }} = {{ field.camel_name }}
{% else %}
instance.{{ field.name }} = cleaned_data.get('{{ field.camel_name }}')
{% endif %}
{% endfor %}
instance.update_user = get_username(request)
instance.save()
return R.ok(msg="更新成功")
except Exception as e:
logging.error(f"更新{{ module_comment }}异常,ID: {instance_id}, 错误: {str(e)}")
return R.failed(msg="更新失败,请稍后重试")
# =============================================================
# 删除服务(支持批量)
# =============================================================
def delete_{{ app_name }}s(instance_ids):
"""
删除{{ module_comment }}(批量删除)
采用逻辑删除方式(is_delete=True),而非物理删除。
支持逗号分隔的多个ID。
Args:
instance_ids: {{ module_comment }}ID字符串,多个ID用逗号分隔
例如: "1" 或 "1,2,3"
Returns:
R: 操作结果,包含删除数量
"""
if not instance_ids:
return R.failed("记录ID不存在")
try:
# 解析ID列表
id_list = [int(id_str.strip()) for id_str in instance_ids.split(',') if id_str.strip()]
if not id_list:
return R.failed("无效的记录ID")
# 批量更新删除标识(逻辑删除)
updated_count = {{ model_class_name }}.objects.filter(
id__in=id_list,
is_delete=False
).update(is_delete=True)
return R.ok(msg=f"本次共删除{updated_count}条数据")
except ValueError:
return R.failed("记录ID格式错误")
except Exception as e:
logging.error(f"删除{{ module_comment }}异常,IDs: {instance_ids}, 错误: {str(e)}")
return R.failed(msg="删除失败,请稍后重试")
{% if has_status %}
# =============================================================
# 状态更新服务
# =============================================================
def update_{{ app_name }}_status(request):
"""
更新{{ module_comment }}状态
单独更新{{ module_comment }}的启用/禁用状态,不涉及其他字段。
Args:
request: HttpRequest对象
- id: {{ module_comment }}ID
- status: 目标状态值(1=启用, 2=禁用)
Returns:
R: 操作结果(成功/失败)
"""
# 解析请求参数
data, error = parse_request_body(request)
if error:
return R.failed(msg=error)
# 验证{{ module_comment }}ID
instance_id = data.get('id')
if not instance_id or not str(instance_id).isdigit() or int(instance_id) <= 0:
return R.failed("{{ module_comment }}ID无效")
instance_id = int(instance_id)
# 验证状态值(1=启用, 2=禁用)
status = data.get('status')
if status is None:
return R.failed("状态值不能为空")
try:
status = int(status)
if status not in [1, 2]:
return R.failed("状态值无效")
except ValueError:
return R.failed("状态值格式错误")
try:
# 查询{{ module_comment }}记录
instance = {{ model_class_name }}.objects.filter(id=instance_id, is_delete=False).first()
if not instance:
return R.failed("{{ module_comment }}不存在")
# 更新状态
instance.status = status
instance.update_user = get_username(request)
instance.save()
return R.ok(msg="状态更新成功")
except Exception as e:
logging.error(f"更新{{ module_comment }}状态异常,ID: {instance_id}, 错误: {str(e)}")
return R.failed(msg="状态更新失败,请稍后重试")
{% endif %}
# =============================================================
# 数据下拉列表服务
# =============================================================
def get_{{ app_name }}_data():
"""
获取所有启用的{{ module_comment }}列表
通常用于前端下拉选择框等场景。
只返回 status=1(启用)且未删除的记录。
Returns:
list: {{ module_comment }}列表,每个元素为{{ module_comment }}模型字典
查询失败时返回空列表
"""
try:
# 查询启用的{{ module_comment }}数据列表
# status=1 表示启用,is_delete=False 表示未删除
queryset = {{ model_class_name }}.objects.filter(
is_delete=False,
status=1
).order_by({% if has_sort %}"sort"{% else %}"-create_time"{% endif %}).values()
return list(queryset)
except Exception as e:
logging.error(f"获取{{ module_comment }}列表异常: {str(e)}")
return []
+83
View File
@@ -0,0 +1,83 @@
import { http } from '@/utils/http/axios';
/**
* @description: {{ module_comment }}分页列表
*/
export function get{{ model_class_name }}Page(params?) {
return http.request({
url: '/{{ route_prefix }}/page',
method: 'GET',
params,
});
}
/**
* 获取全部{{ module_comment }}列表
* @param params 参数
* @returns 返回结果
*/
export function get{{ model_class_name }}List(params?) {
return http.request({
url: '/{{ route_prefix }}/list',
method: 'GET',
params,
});
}
/**
* @description: 根据ID获取{{ module_comment }}详情
* @param {{ primary_key }} {{ module_comment }}ID
*/
export function get{{ model_class_name }}Detail({{ primary_key }}) {
return http.request({
url: '/{{ route_prefix }}/detail/' + {{ primary_key }},
method: 'GET',
});
}
/**
* @description: 添加{{ module_comment }}
* @param data {{ module_comment }}数据
*/
export function {{ model_class_name_camel }}Add(data: any) {
return http.request({
url: '/{{ route_prefix }}/add',
method: 'POST',
data,
});
}
/**
* @description: 更新{{ module_comment }}
* @param data {{ module_comment }}数据
*/
export function {{ model_class_name_camel }}Update(data: any) {
return http.request({
url: '/{{ route_prefix }}/update',
method: 'PUT',
data,
});
}
/**
* @description: 删除{{ module_comment }}
* @param {{ primary_key }} {{ module_comment }}ID
*/
export function {{ model_class_name_camel }}Delete({{ primary_key }}) {
return http.request({
url: '/{{ route_prefix }}/delete/' + {{ primary_key }},
method: 'DELETE',
});
}
/**
* @description: 批量删除{{ module_comment }}
* @param data {{ module_comment }}ID数组
*/
export function {{ model_class_name_camel }}BatchDelete(data: any) {
return http.request({
url: '/{{ route_prefix }}/batchDelete',
method: 'DELETE',
data,
});
}
+108
View File
@@ -0,0 +1,108 @@
import { h } from 'vue';
import { NTag, NAvatar, NImage } from 'naive-ui';
/**
* 表格列配置
* 定义表格显示的列
*/
export const columns = [
{
type: 'selection', // 多选框列
width: 50,
fixed: 'left',
},
{
title: 'ID',
key: 'id',
fixed: 'left',
width: 50,
},
{% for field in list_fields %}
{
title: '{{ field.label }}',
key: '{{ field.camel_name }}',
width: {% if field.type == 'CharField' %}150{% elif field.type == 'IntegerField' %}100{% elif field.type == 'DateTimeField' %}180{% else %}120{% endif %},
{% if field.is_image %}
// 图片渲染
render(record) {
return record.{{ field.camel_name }}
? h(NAvatar, {
src: record.{{ field.camel_name }},
size: 48,
shape: 'square',
fit: 'cover',
style: {
borderRadius: '4px',
},
})
: h('span', '-');
},
{% elif field.choices %}
// 选项标签渲染
render(record) {
const statusMap = {
{% for choice in field.choices %}
{{ choice.value }}: { text: '{{ choice.label }}', type: {% if choice.value == 1 %}'success'{% elif choice.value == 2 %}'error'{% elif choice.value == 3 %}'warning'{% else %}'info'{% endif %} },
{% endfor %}
};
const status = statusMap[record.{{ field.camel_name }}];
return h(
NTag,
{
bordered: false,
type: status?.type || 'info',
},
{
default: () => status?.text || record.{{ field.camel_name }},
},
);
},
{% elif field.camel_name == 'status' %}
// 状态标签渲染
render(record) {
return h(
NTag,
{
bordered: false,
type: record.status == 1 ? 'success' : 'error',
},
{
default: () => (record.status == 1 ? '正常' : '停用'),
},
);
},
{% elif field.type == 'CharField' and field.camel_name|lower in ['website', 'site', 'url', 'link'] %}
// 链接渲染
render(record) {
const url = record.{{ field.camel_name }};
return h(
'a',
{
href: url,
target: '_blank',
rel: 'noopener noreferrer',
},
{
default: () => url || '点击查看',
},
);
},
{% else %}
// 普通文本渲染(无需额外配置)
ellipsis: {
tooltip: true,
},
{% endif %}
},
{% endfor %}
{
title: '创建人',
key: 'createUser',
width: 100,
},
{
title: '创建时间',
key: 'createTime',
width: 180,
},
];
+216
View File
@@ -0,0 +1,216 @@
<template>
<basicModal
@register="modalRegister"
ref="modalRef"
class="basicModal basicFormModal"
>
<template #default>
<n-descriptions
class="margin-top"
:column="2"
bordered
:label-style="{ width: '120px' }"
label-placement="left"
>
{% for field in form_fields %}
<n-descriptions-item label="{{ field.label }}:">
{% if field.is_image %}
<n-image
v-if="formData.{{ field.camel_name }}"
:src="formData.{{ field.camel_name }}"
width="80"
height="80"
fit="cover"
:preview-disabled="false"
/>
<span v-else>-</span>
{% elif field.is_rich_text %}
<div class="rich-text-content" v-html="formData.{{ field.camel_name }}"></div>
{% elif field.type == 'BooleanField' %}
<n-tag :type="formData.{{ field.camel_name }} ? 'success' : 'error'" :bordered="false">
{{ '{{ formData.' + field.camel_name + ' ? "是" : "否" }}' }}
</n-tag>
{% elif field.type == 'IntegerField' and field.choices %}
<span>{{ '{{ getChoiceLabel(\'' + field.camel_name + '\', formData.' + field.camel_name + ') }}' }}</span>
{% else %}
<span>{{ '{{ formData.' + field.camel_name + ' || \'-\' }}' }}</span>
{% endif %}
</n-descriptions-item>
{% endfor %}
</n-descriptions>
</template>
<template #action>
<n-button @click="dialogClose">关闭</n-button>
</template>
</basicModal>
</template>
<script lang="ts" setup>
import { get{{ model_class_name }}Detail } from '@/api/{{ api_path }}';
import { onMounted, reactive, watch } from 'vue';
import { useModal } from '@/components/Modal';
{% if has_image_field %}
import { NImage } from 'naive-ui';
{% endif %}
// 定义事件
const emit = defineEmits(['update:visible']);
/**
* 定义表单参数
*/
const formData = reactive({
id: '',
{% for field in form_fields %}
{{ field.camel_name }}: {% if field.type == 'CharField' %}''{% elif field.type == 'IntegerField' %}0{% elif field.type == 'BooleanField' %}false{% elif field.type == 'FloatField' %}0{% else %}''{% endif %},
{% endfor %}
});
/**
* 定义接收的参数
*/
const props = defineProps({
visible: {
type: Boolean,
required: true,
default: false,
},
{{ primary_key }}: {
type: Number,
required: true,
default: 0,
},
});
/**
* 定义模态框
*/
const [modalRegister, { openModal, closeModal }] = useModal({
title: '{{ module_comment }}详情',
width: 750,
});
/**
* 获取选项字段的标签
*/
const getChoiceLabel = (fieldName: string, value: any) => {
const choiceMap: Record<string, Record<string, string>> = {
{% for field in form_fields %}
{% if field.type == 'IntegerField' and field.choices %}
{{ field.camel_name }}: {
{% for choice in field.choices %}
{{ choice.value }}: '{{ choice.label }}',
{% endfor %}
},
{% endif %}
{% endfor %}
};
return choiceMap[fieldName]?.[value] || value || '-';
};
/**
* 关闭窗体
*/
const dialogClose = () => {
closeModal();
emit('update:visible', false);
};
/**
* 设置表单数据
*/
const setFormData = async () => {
try {
const data = await get{{ model_class_name }}Detail(props.{{ primary_key }});
for (const key in formData) {
if (data[key] != null && data[key] != undefined) {
//@ts-ignore
formData[key] = data[key];
}
}
} catch (error) {
console.error('获取{{ module_comment }}详情失败', error);
}
};
/**
* 重置表单数据
*/
const resetFormData = () => {
for (const key in formData) {
//@ts-ignore
if (typeof formData[key] === 'string') {
//@ts-ignore
formData[key] = '';
} else if (typeof formData[key] === 'number') {
//@ts-ignore
formData[key] = 0;
} else if (typeof formData[key] === 'boolean') {
//@ts-ignore
formData[key] = false;
}
}
};
/**
* 监听visible变化,重新加载数据
*/
watch(
() => props.visible,
(newVal) => {
if (newVal && props.{{ primary_key }}) {
setFormData();
openModal();
} else if (newVal && !props.{{ primary_key }}) {
resetFormData();
openModal();
}
},
);
/**
* 钩子函数
*/
onMounted(() => {
if (props.visible) {
if (props.{{ primary_key }}) {
setFormData();
} else {
resetFormData();
}
openModal();
}
});
/**
* 暴露openModal方法
*/
defineExpose({
openModal,
});
</script>
<style lang="less" scoped>
.rich-text-content {
max-height: 300px;
overflow-y: auto;
border: 1px solid #e4e7ed;
padding: 10px;
border-radius: 4px;
background-color: #f8f9fa;
:deep(img) {
max-width: 100%;
}
}
:deep(.n-descriptions-table) {
.n-descriptions-table-body {
.n-descriptions-table-row {
.n-descriptions-table-body-cell {
word-break: break-all;
}
}
}
}
</style>
+447
View File
@@ -0,0 +1,447 @@
<template>
<basicModal
@register="modalRegister"
ref="modalRef"
class="basicModal basicFormModal"
@on-ok="handleSubmit"
@on-close="dialogClose"
>
<template #default>
<n-form
class="ls-form"
ref="formRef"
:model="formData"
label-placement="left"
label-width="80px"
>
{% for field in form_fields %}
{% if field.is_image %}
<!-- {{ field.comment }}(图片上传) -->
<n-form-item
label="{{ field.label }}"
path="{{ field.camel_name }}"
{% if field.required %}
:rule="{ required: true, message: '请上传{{ field.comment }}', trigger: 'change' }"
{% endif %}
>
<UploadImg
@change-file-name="(name) => (formData.{{ field.camel_name }}ImgName = name)"
:fileType="['image/jpeg', 'image/png', 'image/jpg', 'image/gif', 'image/webp']"
name="{{ app_name }}"
:fileSize="20"
v-model:image-url="formData.{{ field.camel_name }}"
>
<template #tip>支持扩展名: jpg png jpeg gif webp; 文件大小不超过20M</template>
</UploadImg>
</n-form-item>
{% elif field.is_rich_text %}
<!-- {{ field.comment }}(富文本编辑器) -->
<n-form-item
label="{{ field.label }}"
path="{{ field.camel_name }}"
{% if field.required %}
:rule="{ required: true, message: '请输入{{ field.comment }}', trigger: 'blur' }"
{% endif %}
>
<div class="flex w-full">
<Editor
ref="{{ field.camel_name }}EditorRef"
:height="editorHeight"
class="flex-1"
name="{{ app_name }}"
/>
</div>
</n-form-item>
{% elif field.type == 'CharField' and ('备注' in field.comment or '介绍' in field.comment or '描述' in field.comment or '说明' in field.comment) %}
<!-- {{ field.comment }}(多行文本) -->
<n-form-item
label="{{ field.label }}"
path="{{ field.camel_name }}"
{% if field.required %}
:rule="{ required: true, message: '请输入{{ field.comment }}', trigger: 'blur' }"
{% endif %}
>
<n-input
type="textarea"
v-model:value="formData.{{ field.camel_name }}"
placeholder="请输入{{ field.comment }}"
:rows="3"
clearable
/>
</n-form-item>
{% elif field.type == 'CharField' %}
<!-- {{ field.comment }} -->
<n-form-item
label="{{ field.label }}"
path="{{ field.camel_name }}"
{% if field.required %}
:rule="{ required: true, message: '请输入{{ field.comment }}', trigger: 'blur' }"
{% endif %}
>
<n-input
class="ls-input"
v-model:value="formData.{{ field.camel_name }}"
placeholder="请输入{{ field.comment }}"
clearable
/>
</n-form-item>
{% elif field.type == 'IntegerField' and field.choices %}
<!-- {{ field.comment }}({% if field.choices|length <= 2 %}单选{% else %}下拉选择{% endif %}) -->
<n-form-item
label="{{ field.label }}"
path="{{ field.camel_name }}"
{% if field.required %}
:rule="{ type: 'number', required: true, message: '请选择{{ field.comment }}', trigger: 'change' }"
{% endif %}
>
{% if field.choices|length <= 2 %}
<n-radio-group v-model:value="formData.{{ field.camel_name }}">
{% for choice in field.choices %}
<n-radio :value="{{ choice.value }}">{{ choice.label }}</n-radio>
{% endfor %}
</n-radio-group>
{% else %}
<n-select
v-model:value="formData.{{ field.camel_name }}"
placeholder="请选择{{ field.comment }}"
clearable
filterable
:options="optionsData.{{ field.camel_name }}List"
/>
{% endif %}
</n-form-item>
{% elif field.type == 'IntegerField' and not field.choices %}
<!-- {{ field.comment }}(数字输入) -->
<n-form-item
label="{{ field.label }}"
path="{{ field.camel_name }}"
{% if field.required %}
:rule="{ type: 'number', required: true, message: '请输入{{ field.comment }}', trigger: 'blur' }"
{% endif %}
>
<n-input-number
v-model:value="formData.{{ field.camel_name }}"
:min="{{ field.min_value or 0 }}"
:max="{{ field.max_value or 99999 }}"
/>
</n-form-item>
{% elif field.type == 'BooleanField' %}
<!-- {{ field.comment }}(开关) -->
<n-form-item label="{{ field.label }}" path="{{ field.camel_name }}">
<n-switch
v-model:value="formData.{{ field.camel_name }}"
:checked-value="1"
:unchecked-value="0"
/>
</n-form-item>
{% elif field.type == 'DateField' %}
<!-- {{ field.comment }}(日期选择) -->
<n-form-item
label="{{ field.label }}"
path="{{ field.camel_name }}"
{% if field.required %}
:rule="{ required: true, message: '请选择{{ field.comment }}', trigger: 'change' }"
{% endif %}
>
<n-date-picker
v-model:value="formData.{{ field.camel_name }}"
type="date"
placeholder="请选择{{ field.comment }}"
format="yyyy-MM-dd"
/>
</n-form-item>
{% elif field.type == 'DateTimeField' %}
<!-- {{ field.comment }}(日期时间选择) -->
<n-form-item
label="{{ field.label }}"
path="{{ field.camel_name }}"
{% if field.required %}
:rule="{ required: true, message: '请选择{{ field.comment }}', trigger: 'change' }"
{% endif %}
>
<n-date-picker
v-model:value="formData.{{ field.camel_name }}"
type="datetime"
placeholder="请选择{{ field.comment }}"
format="yyyy-MM-dd HH:mm:ss"
/>
</n-form-item>
{% elif field.type == 'TextField' %}
<!-- {{ field.comment }}(文本域) -->
<n-form-item
label="{{ field.label }}"
path="{{ field.camel_name }}"
{% if field.required %}
:rule="{ required: true, message: '请输入{{ field.comment }}', trigger: 'blur' }"
{% endif %}
>
<n-input
type="textarea"
v-model:value="formData.{{ field.camel_name }}"
placeholder="请输入{{ field.comment }}"
:rows="3"
/>
</n-form-item>
{% elif field.type == 'FloatField' or field.type == 'DecimalField' %}
<!-- {{ field.comment }}(数字输入) -->
<n-form-item
label="{{ field.label }}"
path="{{ field.camel_name }}"
{% if field.required %}
:rule="{ type: 'number', required: true, message: '请输入{{ field.comment }}', trigger: 'blur' }"
{% endif %}
>
<n-input-number
v-model:value="formData.{{ field.camel_name }}"
:min="{{ field.min_value or 0 }}"
:max="{{ field.max_value or 999999 }}"
:precision="2"
:step="0.1"
/>
</n-form-item>
{% endif %}
{% endfor %}
</n-form>
</template>
</basicModal>
</template>
<script lang="ts" setup>
import { get{{ model_class_name }}Detail, {{ model_class_name_camel }}Add, {{ model_class_name_camel }}Update } from '@/api/{{ api_path }}';
import { onMounted, reactive, ref, watch } from 'vue';
import { useMessage } from 'naive-ui';
import { useModal } from '@/components/Modal';
{% if has_image_field %}
import UploadImg from '@/components/Upload/Image.vue';
{% endif %}
{% if has_rich_text_field %}
import Editor from '@/components/Editor/tinymce.vue';
{% endif %}
// 定义事件
const emit = defineEmits(['success', 'update:visible']);
// 消息提示
const message = useMessage();
// 表单引用
const formRef = ref();
{% if has_rich_text_field %}
// 富文本编辑器高度
const editorHeight = document.body.clientHeight - 400;
// 富文本编辑器引用
{% for field in form_fields %}
{% if field.is_rich_text %}
const {{ field.camel_name }}EditorRef = ref();
{% endif %}
{% endfor %}
{% endif %}
/**
* 定义选项数据
*/
const optionsData = reactive({
{% for field in form_fields %}
{% if field.type == 'IntegerField' and field.choices and field.choices|length > 2 %}
{{ field.camel_name }}List: [
{% for choice in field.choices %}
{
label: '{{ choice.label }}',
value: {% if choice.value_type == 'string' %}'{{ choice.value }}'{% else %}{{ choice.value }}{% endif %},
},
{% endfor %}
],
{% endif %}
{% endfor %}
});
/**
* 定义表单数据
* 初始化默认值
*/
const formData = reactive({
id: '', // ID字段,新增时为0
{% for field in form_fields %}
{{ field.camel_name }}: {% if field.type == 'CharField' %}''{% elif field.type == 'IntegerField' %}{{ field.default or 0 }}{% elif field.type == 'BooleanField' %}{{ field.default or 0 }}{% elif field.type == 'FloatField' %}{{ field.default or 0 }}{% else %}''{% endif %},
{% endfor %}
{% for field in form_fields %}
{% if field.is_image %}
{{ field.camel_name }}ImgName: '', // {{ field.comment }}文件名
{% endif %}
{% endfor %}
});
/**
* 定义接收的参数
*/
const props = defineProps({
visible: {
type: Boolean,
required: true,
default: false,
},
{{ primary_key }}: {
type: Number,
required: true,
default: 0,
},
});
/**
* 定义模态框
*/
const [modalRegister, { openModal, setSubLoading, closeModal }] = useModal({
title: props.{{ primary_key }} ? '编辑{{ module_comment }}' : '新增{{ module_comment }}',
subBtuText: '确定',
width: {% if has_rich_text_field %}850{% else %}500{% endif %},
});
/**
* 执行提交表单
* 根据是否有ID判断是新增还是编辑
*/
const handleSubmit = async () => {
// 获取富文本编辑器内容
{% for field in form_fields %}
{% if field.is_rich_text %}
if ({{ field.camel_name }}EditorRef.value) {
formData.{{ field.camel_name }} = {{ field.camel_name }}EditorRef.value.myValue;
}
{% endif %}
{% endfor %}
formRef.value
.validate()
.then(async () => {
try {
props.{{ primary_key }}
? await {{ model_class_name_camel }}Update(formData)
: await {{ model_class_name_camel }}Add(formData);
message.success('操作成功');
setSubLoading(false);
emit('update:visible', false);
emit('success');
} catch (error) {
message.error('操作失败');
setSubLoading(false);
}
})
.catch(() => {
setSubLoading(false);
});
};
/**
* 关闭窗体
*/
const dialogClose = () => {
emit('update:visible', false);
};
/**
* 设置表单数据
* 编辑时获取详情并填充表单
*/
const setFormData = async () => {
try {
const data = await get{{ model_class_name }}Detail(props.{{ primary_key }});
for (const key in formData) {
if (data[key] != null && data[key] != undefined) {
// @ts-ignore
formData[key] = data[key];
}
}
// 设置富文本编辑器内容
{% for field in form_fields %}
{% if field.is_rich_text %}
if ({{ field.camel_name }}EditorRef.value && formData.{{ field.camel_name }}) {
{{ field.camel_name }}EditorRef.value.myValue = formData.{{ field.camel_name }};
}
{% endif %}
{% endfor %}
} catch (error) {
console.error('获取{{ module_comment }}详情失败', error);
message.error('获取数据失败');
}
};
/**
* 重置表单
*/
const resetForm = () => {
if (formRef.value) {
formRef.value.restoreValidation();
}
// 重置表单数据
for (const key in formData) {
if (key === 'id') continue;
// @ts-ignore
if (typeof formData[key] === 'string') {
// @ts-ignore
formData[key] = '';
} else if (typeof formData[key] === 'number') {
// @ts-ignore
formData[key] = 0;
}
}
// 清空富文本编辑器内容
{% for field in form_fields %}
{% if field.is_rich_text %}
if ({{ field.camel_name }}EditorRef.value) {
{{ field.camel_name }}EditorRef.value.myValue = '';
}
{% endif %}
{% endfor %}
};
/**
* 监听visible变化,重新加载数据
*/
watch(
() => props.visible,
(newVal) => {
if (newVal && props.{{ primary_key }}) {
setFormData();
openModal();
} else if (newVal && !props.{{ primary_key }}) {
resetForm();
openModal();
}
},
);
/**
* 钩子函数
*/
onMounted(() => {
if (props.visible) {
if (props.{{ primary_key }}) {
setFormData();
} else {
resetForm();
}
openModal();
}
});
/**
* 暴露openModal方法
*/
defineExpose({
openModal,
});
</script>
<style lang="less" scoped>
.flex {
display: flex;
}
.flex-1 {
flex: 1;
}
.w-full {
width: 100%;
}
</style>
+322
View File
@@ -0,0 +1,322 @@
<template>
<div>
<!-- 搜索表单卡片 -->
<n-card :bordered="false" class="pt-3 mb-3 proCard">
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset" />
</n-card>
<!-- 数据表格卡片 -->
<n-card :bordered="false" class="proCard">
<BasicTable
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
ref="basicTableRef"
:actionColumn="actionColumn"
@update:checked-row-keys="onCheckedRow"
:auto-scroll-x="true"
>
<!-- 表格顶部工具栏 -->
<template #tableTitle>
<n-space>
<!-- 新增按钮 -->
<n-button type="primary" @click="handleAdd" v-perm="['{{ permission_prefix }}:add']">
<template #icon>
<n-icon>
<PlusOutlined />
</n-icon>
</template>
添加{{ module_comment }}
</n-button>
<!-- 批量删除按钮 -->
<n-button
type="error"
@click="handleDelete()"
:disabled="!rowKeys.length"
v-perm="['{{ permission_prefix }}:batchDelete']"
>
<template #icon>
<n-icon>
<DeleteOutlined />
</n-icon>
</template>
批量删除
</n-button>
{% if has_export %}
<!-- 导出按钮 -->
<n-button
type="success"
@click="handleExport"
v-perm="['{{ permission_prefix }}:export']"
>
<template #icon>
<n-icon>
<DownloadOutlined />
</n-icon>
</template>
导出
</n-button>
{% endif %}
</n-space>
</template>
</BasicTable>
</n-card>
<!-- 编辑对话框组件 -->
<editDialog
ref="editModalRef"
v-if="editVisible"
:{{ primary_key }}="currentId"
v-model:visible="editVisible"
@success="reloadTable('noRefresh')"
/>
<!-- 查看详情对话框组件 -->
<detailDialog
ref="detailModalRef"
v-if="detailVisible"
:{{ primary_key }}="currentId"
v-model:visible="detailVisible"
@success="reloadTable('noRefresh')"
/>
</div>
</template>
<script lang="ts" setup>
import { h, nextTick, reactive, ref } from 'vue';
import { useMessage, useDialog } from 'naive-ui';
import { PlusOutlined, DeleteOutlined, EditOutlined, EyeOutlined } from '@vicons/antd';
import { TableAction } from '@/components/Table';
import { BasicForm, useForm } from '@/components/Form/index';
import { get{{ model_class_name }}Page, {{ model_class_name_camel }}Delete, {{ model_class_name_camel }}BatchDelete } from '@/api/{{ api_path }}';
import { renderIcon } from '@/utils';
import { columns } from './columns';
import { schemas } from './querySchemas';
// 动态导入编辑对话框组件
import editDialog from './edit.vue';
// 动态导入详情对话框组件
import detailDialog from './detail.vue';
// 消息提示和对话框
const message = useMessage();
const dialog = useDialog();
// 表格引用
const basicTableRef = ref();
const editModalRef = ref();
const detailModalRef = ref();
// 响应式状态定义
const currentId = ref(0); // 当前操作的ID
const editVisible = ref(false); // 编辑对话框显示状态
const detailVisible = ref(false); // 详情对话框显示状态
const rowKeys = ref([]); // 表格选中行Key
/**
* 定义查询参数
* 用于存储搜索表单的值
*/
const formParams = reactive({
{% for field in filter_fields %}
{{ field.name }}: '',
{% endfor %}
});
/**
* 定义操作栏配置
* 包含编辑、详情和删除按钮
*/
const actionColumn = reactive({
width: 280,
title: '操作',
align: 'center',
key: 'action',
fixed: 'right',
render(record) {
return h(TableAction as any, {
style: 'button',
actions: [
{
label: '编辑',
icon: renderIcon(EditOutlined),
type: 'info',
onClick: handleEdit.bind(null, record),
auth: ['{{ permission_prefix }}:update'],
},
{
label: '详情',
icon: renderIcon(EyeOutlined),
type: 'default',
onClick: handleDetail.bind(null, record),
auth: ['{{ permission_prefix }}:detail'],
},
{
label: '删除',
icon: renderIcon(DeleteOutlined),
type: 'error',
onClick: handleDelete.bind(null, record),
auth: ['{{ permission_prefix }}:delete'],
},
],
});
},
});
/**
* 加载数据列表
* @param params 分页参数
* @returns 表格数据
*/
const loadDataTable = async (params: any) => {
// 清空选中行
rowKeys.value = [];
const result = await get{{ model_class_name }}Page({ ...formParams, ...params });
return result;
};
/**
* 刷新表格数据
* @param noRefresh 是否重置页码
*/
function reloadTable(noRefresh = '') {
basicTableRef.value.reload(noRefresh ? {} : { pageNo: 1 });
}
/**
* 注册搜索表单
* 配置表单布局和字段
*/
const [register, {}] = useForm({
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
labelWidth: 80,
schemas,
});
/**
* 提交搜索表单
* @param values 表单值
*/
function handleSubmit(values: Recordable) {
for (const key in formParams) {
formParams[key] = '';
}
for (const key in values) {
formParams[key] = values[key];
}
reloadTable();
}
/**
* 重置搜索表单
*/
function handleReset(values: Recordable) {
for (const key in formParams) {
formParams[key] = '';
}
for (const key in values) {
formParams[key] = values[key];
}
reloadTable();
}
/**
* 表格选择项变化时的回调
* @param keys 选中的key数组
*/
function onCheckedRow(keys: (string | number)[]) {
rowKeys.value = keys;
}
/**
* 处理新增按钮点击
*/
const handleAdd = async () => {
currentId.value = 0;
editVisible.value = true;
await nextTick();
editModalRef.value?.openModal();
};
/**
* 处理编辑按钮点击
* @param record 当前行数据
*/
const handleEdit = async (record: Recordable) => {
currentId.value = record.id;
editVisible.value = true;
await nextTick();
editModalRef.value?.openModal();
};
/**
* 处理详情按钮点击
* @param record 当前行数据
*/
const handleDetail = async (record: Recordable) => {
currentId.value = record.id;
detailVisible.value = true;
await nextTick();
detailModalRef.value?.openModal();
};
/**
* 处理删除操作
* @param record 当前行数据(可选,不传时表示批量删除)
*/
async function handleDelete(record?: Recordable) {
let ids = [];
if (!record) {
// 批量删除:获取选中行的ID
ids = rowKeys.value;
if (!ids.length) {
message.warning('请选择要删除的数据');
return;
}
}
dialog.warning({
title: '提示',
content: '确定要删除选中的{{ module_comment }}吗?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
try {
record
? await {{ model_class_name_camel }}Delete(record.id)
: await {{ model_class_name_camel }}BatchDelete(ids);
message.success('删除成功');
reloadTable();
} catch (error) {
message.error('删除失败');
}
},
});
}
{% if has_export %}
/**
* 处理导出操作
*/
async function handleExport() {
dialog.warning({
title: '提示',
content: '确定要导出{{ module_comment }}数据吗?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
try {
// TODO: 调用导出API
message.success('导出成功');
} catch (error) {
message.error('导出失败');
console.error('导出失败', error);
}
},
});
}
{% endif %}
</script>
<style lang="less" scoped></style>
+30
View File
@@ -0,0 +1,30 @@
import { FormSchema } from '@/components/Form/index';
/**
* 查询表单配置
* 定义搜索区域的表单项
*/
export const schemas: FormSchema[] = [
{% for field in filter_fields %}
{
field: '{{ field.name }}',
component: '{% if field.type == 'CharField' %}NInput{% elif field.choices %}NSelect{% elif field.type == 'IntegerField' and not field.choices %}NInputNumber{% elif field.type == 'DateField' %}NDatePicker{% else %}NInput{% endif %}',
label: '{{ field.label }}',
componentProps: {
placeholder: '请输入{{ field.label }}',
clearable: true,
filterable: {% if field.choices %}true{% else %}false{% endif %},
{% if field.choices %}
options: [
{% for choice in field.choices %}
{
label: '{{ choice.label }}',
value: {{ choice.value }},
},
{% endfor %}
],
{% endif %}
},
},
{% endfor %}
];
+99
View File
@@ -0,0 +1,99 @@
import { h } from 'vue';
import { BasicColumn } from '@/components/Table';
import { NTag, NSwitch, NImage } from 'naive-ui';
import { renderIcon } from '@/utils';
{% for field in fields %}
{% if field.choices %}
/**
* {{ field.label }} 选项映射
*/
const {{ field.name }}Map: Record<number, { text: string; type: string }> = {
{% for choice in field.choices %}
{{ choice.value }}: { text: '{{ choice.label }}', type: '{% if loop.index0 == 0 %}success{% elif loop.index0 == 1 %}error{% elif loop.index0 == 2 %}warning{% else %}info{% endif %}' },
{% endfor %}
};
{% endif %}
{% endfor %}
export const columns: BasicColumn[] = [
{% for field in list_fields %}
{
title: '{{ field.label }}',
key: '{{ field.camel_name }}',
width: {% if field.name == 'name' %}250{% elif field.name == 'sort' %}100{% elif field.type == 'DateTimeField' %}180{% else %}150{% endif %},
align: 'left',
{% if field.is_image %}
// 图片渲染
render(record) {
return record.{{ field.camel_name }}
? h(NImage, {
src: record.{{ field.camel_name }},
width: 40,
height: 40,
fit: 'cover',
style: {
borderRadius: '4px',
},
previewDisabled: false,
})
: h('span', '-');
},
{% elif field.choices %}
// 选项标签渲染
render(record) {
const status = {{ field.name }}Map[record.{{ field.camel_name }}];
return h(
NTag,
{
bordered: false,
type: status?.type || 'info',
},
{
default: () => status?.text || record.{{ field.camel_name }},
},
);
},
{% elif field.name == 'status' %}
// 状态开关渲染
render(record) {
return h(NSwitch, {
value: record.status == 1,
onUpdateValue: (val: boolean) => {
// 状态变更事件由父组件处理
record.status = val ? 1 : 0;
},
});
},
{% elif field.type == 'CharField' and field.camel_name|lower in ['website', 'site', 'url', 'link'] %}
// 链接渲染
render(record) {
const url = record.{{ field.camel_name }};
return h(
'a',
{
href: url,
target: '_blank',
rel: 'noopener noreferrer',
},
{
default: () => url || '点击查看',
},
);
},
{% else %}
// 普通文本渲染
{% endif %}
},
{% endfor %}
{
title: '创建人',
key: 'createUser',
width: 100,
},
{
title: '创建时间',
key: 'createTime',
width: 180,
},
];
+216
View File
@@ -0,0 +1,216 @@
<template>
<basicModal
@register="modalRegister"
ref="modalRef"
class="basicModal basicFormModal"
>
<template #default>
<n-descriptions
class="margin-top"
:column="2"
bordered
:label-style="{ width: '120px' }"
label-placement="left"
>
{% for field in form_fields %}
<n-descriptions-item label="{{ field.label }}:">
{% if field.is_image %}
<n-image
v-if="formData.{{ field.camel_name }}"
:src="formData.{{ field.camel_name }}"
width="80"
height="80"
fit="cover"
:preview-disabled="false"
/>
<span v-else>-</span>
{% elif field.is_rich_text %}
<div class="rich-text-content" v-html="formData.{{ field.camel_name }}"></div>
{% elif field.type == 'BooleanField' %}
<n-tag :type="formData.{{ field.camel_name }} ? 'success' : 'error'" :bordered="false">
{{ '{{ formData.' + field.camel_name + ' ? "是" : "否" }}' }}
</n-tag>
{% elif field.type == 'IntegerField' and field.choices %}
<span>{{ '{{ getChoiceLabel(\'' + field.camel_name + '\', formData.' + field.camel_name + ') }}' }}</span>
{% else %}
<span>{{ '{{ formData.' + field.camel_name + ' || \'-\' }}' }}</span>
{% endif %}
</n-descriptions-item>
{% endfor %}
</n-descriptions>
</template>
<template #action>
<n-button @click="dialogClose">关闭</n-button>
</template>
</basicModal>
</template>
<script lang="ts" setup>
import { get{{ model_class_name }}Detail } from '@/api/{{ api_path }}';
import { onMounted, reactive, watch } from 'vue';
import { useModal } from '@/components/Modal';
{% if has_image_field %}
import { NImage } from 'naive-ui';
{% endif %}
// 定义事件
const emit = defineEmits(['update:visible']);
/**
* 定义表单参数
*/
const formData = reactive({
id: '',
{% for field in form_fields %}
{{ field.camel_name }}: {% if field.type == 'CharField' %}''{% elif field.type == 'IntegerField' %}0{% elif field.type == 'BooleanField' %}false{% elif field.type == 'FloatField' %}0{% else %}''{% endif %},
{% endfor %}
});
/**
* 定义接收的参数
*/
const props = defineProps({
visible: {
type: Boolean,
required: true,
default: false,
},
{{ primary_key }}: {
type: Number,
required: true,
default: 0,
},
});
/**
* 定义模态框
*/
const [modalRegister, { openModal, closeModal }] = useModal({
title: '{{ module_comment }}详情',
width: 750,
});
/**
* 获取选项字段的标签
*/
const getChoiceLabel = (fieldName: string, value: any) => {
const choiceMap: Record<string, Record<string, string>> = {
{% for field in form_fields %}
{% if field.type == 'IntegerField' and field.choices %}
{{ field.camel_name }}: {
{% for choice in field.choices %}
{{ choice.value }}: '{{ choice.label }}',
{% endfor %}
},
{% endif %}
{% endfor %}
};
return choiceMap[fieldName]?.[value] || value || '-';
};
/**
* 关闭窗体
*/
const dialogClose = () => {
closeModal();
emit('update:visible', false);
};
/**
* 设置表单数据
*/
const setFormData = async () => {
try {
const data = await get{{ model_class_name }}Detail(props.{{ primary_key }});
for (const key in formData) {
if (data[key] != null && data[key] != undefined) {
//@ts-ignore
formData[key] = data[key];
}
}
} catch (error) {
console.error('获取{{ module_comment }}详情失败', error);
}
};
/**
* 重置表单数据
*/
const resetFormData = () => {
for (const key in formData) {
//@ts-ignore
if (typeof formData[key] === 'string') {
//@ts-ignore
formData[key] = '';
} else if (typeof formData[key] === 'number') {
//@ts-ignore
formData[key] = 0;
} else if (typeof formData[key] === 'boolean') {
//@ts-ignore
formData[key] = false;
}
}
};
/**
* 监听visible变化,重新加载数据
*/
watch(
() => props.visible,
(newVal) => {
if (newVal && props.{{ primary_key }}) {
setFormData();
openModal();
} else if (newVal && !props.{{ primary_key }}) {
resetFormData();
openModal();
}
},
);
/**
* 钩子函数
*/
onMounted(() => {
if (props.visible) {
if (props.{{ primary_key }}) {
setFormData();
} else {
resetFormData();
}
openModal();
}
});
/**
* 暴露openModal方法
*/
defineExpose({
openModal,
});
</script>
<style lang="less" scoped>
.rich-text-content {
max-height: 300px;
overflow-y: auto;
border: 1px solid #e4e7ed;
padding: 10px;
border-radius: 4px;
background-color: #f8f9fa;
:deep(img) {
max-width: 100%;
}
}
:deep(.n-descriptions-table) {
.n-descriptions-table-body {
.n-descriptions-table-row {
.n-descriptions-table-body-cell {
word-break: break-all;
}
}
}
}
</style>
+436
View File
@@ -0,0 +1,436 @@
<template>
<basicModal
@register="modalRegister"
ref="modalRef"
class="basicModal basicFormModal"
@on-ok="handleSubmit"
@on-close="dialogClose"
>
<template #default>
<n-form
ref="formRef"
:model="formData"
label-placement="left"
label-width="80px"
>
{% for field in form_fields %}
{% if field.camel_name == parent_id_field %}
<!-- {{ field.comment }}(树形选择器) -->
<n-form-item
label="{{ field.label }}"
path="{{ field.camel_name }}"
{% if field.required %}
:rule="{ type: 'number', required: true, message: '请选择{{ field.comment }}', trigger: 'change' }"
{% endif %}
>
<n-tree-select
class="flex-1"
v-model:value="formData.{{ field.camel_name }}"
:options="treeOptions"
label-field="{{ display_field_camel }}"
key-field="{{ primary_key }}"
default-expand-all
placeholder="请选择{{ field.comment }}"
clearable
/>
</n-form-item>
{% elif field.is_image %}
<!-- {{ field.comment }}(图片上传) -->
<n-form-item
label="{{ field.label }}"
path="{{ field.camel_name }}"
{% if field.required %}
:rule="{ required: true, message: '请上传{{ field.comment }}', trigger: 'change' }"
{% endif %}
>
<UploadImg
@change-file-name="(name) => (formData.{{ field.camel_name }}ImgName = name)"
:fileType="['image/jpeg', 'image/png', 'image/jpg', 'image/gif', 'image/webp']"
name="{{ app_name }}"
:fileSize="20"
v-model:image-url="formData.{{ field.camel_name }}"
>
<template #tip>支持扩展名: jpg png jpeg gif webp; 文件大小不超过20M</template>
</UploadImg>
</n-form-item>
{% elif field.type == 'CharField' and ('备注' in field.comment or '介绍' in field.comment or '描述' in field.comment or '说明' in field.comment) %}
<!-- {{ field.comment }}(多行文本) -->
<n-form-item
label="{{ field.label }}"
path="{{ field.camel_name }}"
{% if field.required %}
:rule="{ required: true, message: '请输入{{ field.comment }}', trigger: 'blur' }"
{% endif %}
>
<n-input
type="textarea"
v-model:value="formData.{{ field.camel_name }}"
placeholder="请输入{{ field.comment }}"
:rows="3"
clearable
/>
</n-form-item>
{% elif field.type == 'CharField' %}
<!-- {{ field.comment }} -->
<n-form-item
label="{{ field.label }}"
path="{{ field.camel_name }}"
{% if field.required %}
:rule="{ required: true, message: '请输入{{ field.comment }}', trigger: 'blur' }"
{% endif %}
>
<n-input
class="ls-input"
v-model:value="formData.{{ field.camel_name }}"
placeholder="请输入{{ field.comment }}"
clearable
/>
</n-form-item>
{% elif field.type == 'IntegerField' and field.choices %}
<!-- {{ field.comment }}({% if field.choices|length <= 2 %}单选{% else %}下拉选择{% endif %}) -->
<n-form-item
label="{{ field.label }}"
path="{{ field.camel_name }}"
{% if field.required %}
:rule="{ type: 'number', required: true, message: '请选择{{ field.comment }}', trigger: 'change' }"
{% endif %}
>
{% if field.choices|length <= 2 %}
<n-radio-group v-model:value="formData.{{ field.camel_name }}">
{% for choice in field.choices %}
<n-radio :value="{{ choice.value }}">{{ choice.label }}</n-radio>
{% endfor %}
</n-radio-group>
{% else %}
<n-select
v-model:value="formData.{{ field.camel_name }}"
placeholder="请选择{{ field.comment }}"
clearable
filterable
:options="optionsData.{{ field.camel_name }}List"
/>
{% endif %}
</n-form-item>
{% elif field.type == 'IntegerField' and not field.choices %}
<!-- {{ field.comment }}(数字输入) -->
<n-form-item
label="{{ field.label }}"
path="{{ field.camel_name }}"
{% if field.required %}
:rule="{ type: 'number', required: true, message: '请输入{{ field.comment }}', trigger: 'blur' }"
{% endif %}
>
<n-input-number
v-model:value="formData.{{ field.camel_name }}"
:min="{{ field.min_value or 0 }}"
:max="{{ field.max_value or 99999 }}"
/>
</n-form-item>
{% elif field.type == 'BooleanField' %}
<!-- {{ field.comment }}(开关) -->
<n-form-item label="{{ field.label }}" path="{{ field.camel_name }}">
<n-switch
v-model:value="formData.{{ field.camel_name }}"
:checked-value="1"
:unchecked-value="0"
/>
</n-form-item>
{% elif field.type == 'DateField' %}
<!-- {{ field.comment }}(日期选择) -->
<n-form-item
label="{{ field.label }}"
path="{{ field.camel_name }}"
{% if field.required %}
:rule="{ required: true, message: '请选择{{ field.comment }}', trigger: 'change' }"
{% endif %}
>
<n-date-picker
v-model:value="formData.{{ field.camel_name }}"
type="date"
placeholder="请选择{{ field.comment }}"
format="yyyy-MM-dd"
style="width: 100%"
/>
</n-form-item>
{% elif field.type == 'DateTimeField' %}
<!-- {{ field.comment }}(日期时间选择) -->
<n-form-item
label="{{ field.label }}"
path="{{ field.camel_name }}"
{% if field.required %}
:rule="{ required: true, message: '请选择{{ field.comment }}', trigger: 'change' }"
{% endif %}
>
<n-date-picker
v-model:value="formData.{{ field.camel_name }}"
type="datetime"
placeholder="请选择{{ field.comment }}"
format="yyyy-MM-dd HH:mm:ss"
style="width: 100%"
/>
</n-form-item>
{% elif field.type == 'TextField' %}
<!-- {{ field.comment }}(文本域) -->
<n-form-item
label="{{ field.label }}"
path="{{ field.camel_name }}"
{% if field.required %}
:rule="{ required: true, message: '请输入{{ field.comment }}', trigger: 'blur' }"
{% endif %}
>
<n-input
type="textarea"
v-model:value="formData.{{ field.camel_name }}"
placeholder="请输入{{ field.comment }}"
:rows="3"
/>
</n-form-item>
{% elif field.type == 'FloatField' or field.type == 'DecimalField' %}
<!-- {{ field.comment }}(数字输入) -->
<n-form-item
label="{{ field.label }}"
path="{{ field.camel_name }}"
{% if field.required %}
:rule="{ type: 'number', required: true, message: '请输入{{ field.comment }}', trigger: 'blur' }"
{% endif %}
>
<n-input-number
v-model:value="formData.{{ field.camel_name }}"
:min="{{ field.min_value or 0 }}"
:max="{{ field.max_value or 999999 }}"
:precision="2"
:step="0.1"
/>
</n-form-item>
{% endif %}
{% endfor %}
</n-form>
</template>
</basicModal>
</template>
<script lang="ts" setup>
import { get{{ model_class_name }}List, get{{ model_class_name }}Detail, {{ model_class_name_camel }}Add, {{ model_class_name_camel }}Update } from '@/api/{{ api_path }}';
import { onMounted, reactive, ref, watch } from 'vue';
import { useMessage } from 'naive-ui';
import { useModal } from '@/components/Modal';
import { buildTree } from '@/utils/auth';
{% if has_image_field %}
import UploadImg from '@/components/Upload/Image.vue';
{% endif %}
// 定义事件
const emit = defineEmits(['success', 'update:visible']);
// 消息提示
const message = useMessage();
// 表单引用
const formRef = ref();
// 树形选项数据
const treeOptions = ref<any[]>([]);
/**
* 定义选项数据
*/
const optionsData = reactive({
{% for field in form_fields %}
{% if field.type == 'IntegerField' and field.choices and field.choices|length > 2 %}
{{ field.camel_name }}List: [
{% for choice in field.choices %}
{
label: '{{ choice.label }}',
value: {% if choice.value_type == 'string' %}'{{ choice.value }}'{% else %}{{ choice.value }}{% endif %},
},
{% endfor %}
],
{% endif %}
{% endfor %}
});
/**
* 定义表单数据
* 初始化默认值
*/
const formData = reactive({
id: '', // ID字段,新增时为0
{% for field in form_fields %}
{{ field.camel_name }}: {% if field.type == 'CharField' %}''{% elif field.type == 'IntegerField' %}{{ field.default or 0 }}{% elif field.type == 'BooleanField' %}{{ field.default or 0 }}{% elif field.type == 'FloatField' %}{{ field.default or 0 }}{% else %}''{% endif %},
{% endfor %}
{% for field in form_fields %}
{% if field.is_image %}
{{ field.camel_name }}ImgName: '', // {{ field.comment }}文件名
{% endif %}
{% endfor %}
});
/**
* 定义接收的参数
*/
const props = defineProps({
visible: {
type: Boolean,
required: true,
default: false,
},
{{ primary_key }}: {
type: Number,
required: true,
default: 0,
},
// 新增时传入的父级ID(驼峰格式)
parentId: {
type: Number,
default: 0,
},
});
/**
* 定义模态框
*/
const [modalRegister, { openModal, setSubLoading, closeModal }] = useModal({
title: props.{{ primary_key }} ? '编辑{{ module_comment }}' : '新增{{ module_comment }}',
subBtuText: '确定',
width: 500,
});
/**
* 获取树形数据
*/
const get{{ model_class_name }}Tree = async () => {
const data = await get{{ model_class_name }}List();
// 构建顶级节点
const tree: any = [{ id: 0, {{ display_field_camel }}: '顶级', children: [] }];
const lists = buildTree(data);
tree[0].children.push(...lists);
treeOptions.value = tree;
};
/**
* 执行提交表单
* 根据是否有ID判断是新增还是编辑
*/
const handleSubmit = async () => {
formRef.value
.validate()
.then(async () => {
try {
props.{{ primary_key }}
? await {{ model_class_name_camel }}Update(formData)
: await {{ model_class_name_camel }}Add(formData);
message.success('操作成功');
setSubLoading(false);
emit('update:visible', false);
emit('success');
} catch (error) {
message.error('操作失败');
setSubLoading(false);
}
})
.catch(() => {
setSubLoading(false);
});
};
/**
* 关闭窗体
*/
const dialogClose = () => {
closeModal();
emit('update:visible', false);
};
/**
* 设置表单数据
* 编辑时获取详情并填充表单
*/
const setFormData = async () => {
try {
const data = await get{{ model_class_name }}Detail(props.{{ primary_key }});
for (const key in formData) {
if (data[key] != null && data[key] != undefined) {
// @ts-ignore
formData[key] = data[key];
}
}
} catch (error) {
console.error('获取{{ module_comment }}详情失败', error);
message.error('获取数据失败');
}
};
/**
* 重置表单
*/
const resetForm = () => {
if (formRef.value) {
formRef.value.restoreValidation();
}
// 重置表单数据
for (const key in formData) {
if (key === 'id') continue;
// @ts-ignore
if (typeof formData[key] === 'string') {
// @ts-ignore
formData[key] = '';
} else if (typeof formData[key] === 'number') {
// @ts-ignore
formData[key] = 0;
}
}
// 设置父级ID默认值(使用驼峰格式)
formData.{{ parent_id_field }} = props.parentId || 0;
};
/**
* 监听visible变化,重新加载数据
*/
watch(
() => props.visible,
(newVal) => {
if (newVal && props.{{ primary_key }}) {
setFormData();
openModal();
} else if (newVal && !props.{{ primary_key }}) {
resetForm();
get{{ model_class_name }}Tree();
openModal();
}
},
);
/**
* 钩子函数
*/
onMounted(() => {
get{{ model_class_name }}Tree();
if (props.visible) {
if (props.{{ primary_key }}) {
setFormData();
} else {
resetForm();
}
openModal();
}
});
/**
* 暴露openModal方法
*/
defineExpose({
openModal,
});
</script>
<style lang="less" scoped>
.flex-1 {
flex: 1;
}
.form-tips {
font-size: 12px;
color: #999;
line-height: 1.5;
margin-top: 4px;
}
</style>
+238
View File
@@ -0,0 +1,238 @@
<template>
<div class="{{ app_name }}-index">
<n-card :bordered="false" class="pt-3 mb-3 proCard">
<n-spin :show="loading">
<BasicTable
ref="tableRef"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
:auto-scroll-x="true"
:action-column="actionColumn"
v-model:expanded-row-keys="expandKeys"
>
<template #tableTitle>
<n-space>
<n-button type="primary" @click="handleAdd()" v-perm="['{{ permission_prefix }}:add']">
<template #icon>
<n-icon>
<PlusOutlined />
</n-icon>
</template>
新增{{ module_comment }}
</n-button>
<n-button @click="handleExpand">展开/折叠</n-button>
</n-space>
</template>
</BasicTable>
</n-spin>
</n-card>
<!-- 编辑对话框组件 -->
<editDialog
ref="editModalRef"
v-if="editVisible"
:{{ primary_key }}="{{ primary_key }}Value"
:parent-id="parentId"
v-model:visible="editVisible"
@success="reloadTable"
/>
<!-- 查看详情对话框组件 -->
<detailDialog
ref="detailModalRef"
v-if="detailVisible"
:{{ primary_key }}="{{ primary_key }}Value"
v-model:visible="detailVisible"
@success="reloadTable"
/>
</div>
</template>
<script lang="ts" setup name="{{ model_class_name }}Index">
import { defineAsyncComponent, nextTick, onMounted, ref, reactive, h } from 'vue';
import { PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined } from '@vicons/antd';
import { TableAction } from '@/components/Table';
import { renderIcon } from '@/utils';
import { get{{ model_class_name }}List, {{ model_class_name_camel }}Delete } from '@/api/{{ api_path }}';
import { useMessage, useDialog } from 'naive-ui';
import { columns } from './columns';
import { buildTree } from '@/utils/auth';
import { getTreeValues } from '@/utils/helper/treeHelper';
// 动态导入编辑对话框组件
const editDialog = defineAsyncComponent(() => import('./edit.vue'));
// 动态导入详情对话框组件
const detailDialog = defineAsyncComponent(() => import('./detail.vue'));
// 消息提示和对话框
const message = useMessage();
const dialog = useDialog();
// 表格引用
const tableRef = ref();
const editModalRef = ref();
const detailModalRef = ref();
// 响应式状态
const loading = ref(false);
const editVisible = ref(false);
const detailVisible = ref(false);
const {{ primary_key }}Value = ref(0);
const parentId = ref(0);
const expandKeys = ref<(string | number)[]>([]);
/**
* 定义操作栏配置
* 包含新增、编辑、详情和删除按钮
*/
const actionColumn = reactive({
width: 280,
title: '操作',
align: 'center',
key: 'action',
fixed: 'right',
render(record) {
return h(TableAction as any, {
style: 'button',
actions: [
{
label: '新增',
icon: renderIcon(PlusOutlined),
type: 'primary',
onClick: handleAdd.bind(null, record),
auth: ['{{ permission_prefix }}:add'],
},
{
label: '编辑',
icon: renderIcon(EditOutlined),
type: 'warning',
onClick: handleEdit.bind(null, record),
auth: ['{{ permission_prefix }}:update'],
},
{
label: '详情',
icon: renderIcon(EyeOutlined),
type: 'info',
onClick: handleDetail.bind(null, record),
auth: ['{{ permission_prefix }}:detail'],
},
{
label: '删除',
icon: renderIcon(DeleteOutlined),
type: 'error',
onClick: handleDelete.bind(null, record),
auth: ['{{ permission_prefix }}:delete'],
},
],
});
},
});
/**
* 加载数据列表
*/
const loadDataTable = async () => {
loading.value = true;
try {
const data = await get{{ model_class_name }}List();
const treeData = buildTree(data);
const result = {
records: treeData,
total: 1,
};
loading.value = false;
return result;
} catch (error) {
loading.value = false;
return { records: [], total: 0 };
}
};
/**
* 刷新表格数据
*/
const reloadTable = () => {
tableRef.value?.reload();
};
/**
* 执行添加
* @param record 当前行数据(用于获取父级ID)
*/
const handleAdd = async (record?: any) => {
{{ primary_key }}Value.value = 0;
parentId.value = record ? record.id : 0;
editVisible.value = true;
await nextTick();
editModalRef.value?.openModal();
};
/**
* 执行编辑
* @param record 当前行数据
*/
const handleEdit = async (record: any) => {
{{ primary_key }}Value.value = record.id;
editVisible.value = true;
await nextTick();
editModalRef.value?.openModal();
};
/**
* 执行查看详情
* @param record 当前行数据
*/
const handleDetail = async (record: any) => {
{{ primary_key }}Value.value = record.id;
detailVisible.value = true;
await nextTick();
detailModalRef.value?.openModal();
};
/**
* 执行删除
* @param record 当前行数据
*/
const handleDelete = (record: any) => {
dialog.warning({
title: '提示',
content: '确定要删除选中的{{ module_comment }}吗?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
loading.value = true;
try {
await {{ model_class_name_camel }}Delete(record.id);
message.success('删除成功');
reloadTable();
} catch (error) {
message.error('删除失败');
} finally {
loading.value = false;
}
},
});
};
/**
* 执行展开/折叠
*/
const handleExpand = () => {
loading.value = true;
if (!expandKeys.value.length) {
expandKeys.value = getTreeValues(tableRef.value?.getDataSource() || [], 'id');
} else {
expandKeys.value = [];
}
loading.value = false;
};
/**
* 初始化
*/
onMounted(() => {
loadDataTable();
});
</script>
<style scoped></style>
+135
View File
@@ -0,0 +1,135 @@
# +======================================================================
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
# +----------------------------------------------------------------------
# | Copyright (c) 2021-2026 小蚂蚁云团队 版权所有
# +----------------------------------------------------------------------
# | 开源协议: Apache-2.0
# | 重要声明: 未经书面许可,不得移除或修改本框架相关版权标识。
# +----------------------------------------------------------------------
# | 官方网站: https://www.djangoadmin.cn
# +----------------------------------------------------------------------
# | 研发团队: 小蚂蚁云团队 荣誉出品
# +======================================================================
# | 版权和免责声明:
# | 一、知识产权声明
# | 本团队对该软件框架产品(以下简称“本产品”)拥有完整知识产权,包括但不限于商标权、专利权、
# | 著作权、商业秘密等。前述权利均受中华人民共和国及相关国际法律法规保护。未经本团队书面授权,
# | 任何个人或单位不得基于本产品本身申请任何形式的知识产权。
# | 二、使用限制与禁止条款
# | 您理解并同意,禁止将本产品用于以下任何行为:
# | 1. 任何违反中华人民共和国法律法规的用途;
# | 2. 任何侵害他人合法权益(包括但不限于人身权、财产权、知识产权等)的行为;
# | 3. 任何危害国家安全、公共利益及社会道德风尚的行为。
# | 三、责任豁免
# | 因使用本产品而产生的任何损失或法律纠纷(包括但不限于直接损失、间接损失、附带损失、衍生损失
# | 以及因违约、侵权等引发的责任),本团队均不承担任何法律责任。若因违规使用本产品导致任何第三
# | 方提起索赔或行政机关进行调查,您应独立承担全部责任。
# | 四、违约与法律责任
# | 如发现任何违反本声明的行为,本团队有权:
# | 1. 无条件配合公安机关及其他有权机关的调查取证工作;
# | 2. 依法追究相关方的民事责任,并要求赔偿全部损失;
# | 3. 保留进一步采取刑事控告等法律措施的权利。
# | 五、许可范围说明
# | 1、被授权方应视本产品源代码为核心机密信息,负有最高标准的保密与保管义务。禁止以任何形式(包
# | 括但不限于直接或间接的分享、复制、传播、分发、转售、出租、开源、反向工程、反编译、反汇编或通
# | 过网络服务提供给任何第三方)泄露、披露或处置源代码的全部或部分内容。任何未经授权的复制、使用、
# | 传播行为均构成对本团队知识产权的严重侵权,本团队有权依法追究其全部法律责任,并要求赔偿因此造
# | 成的一切经济损失及商誉损害。
# | 2、本产品仅限于公司或个人在法律允许范围内,用于内部且合规的软件产品研发。具体权利义务以《软件
# | 许可协议》附件为准。
# +======================================================================
from django.urls import path
from application.{{ app_name }} import views
"""
{{ module_comment }}模块路由配置
=============================================================
URL端点概览:
------------------------------------------------------------------------
| 功能说明 | 请求方法 | URL路径 | 视图类 |
|-----------------|----------|-----------------------------|-------------------------|
| 分页查询 | GET | /{{ route_prefix }}/page | {{ model_class_name }}PageView |
| 列表查询 | GET | /{{ route_prefix }}/list | {{ model_class_name }}ListView |
| 详情查询 | GET | /{{ route_prefix }}/detail/<id> | {{ model_class_name }}DetailView |
| 添加{{ module_comment }} | POST | /{{ route_prefix }}/add | {{ model_class_name }}AddView |
| 更新{{ module_comment }} | PUT | /{{ route_prefix }}/update | {{ model_class_name }}UpdateView |
| 删除{{ module_comment }} | DELETE | /{{ route_prefix }}/delete/<ids>| {{ model_class_name }}DeleteView |
{% if has_status %}
| 设置状态 | PUT | /{{ route_prefix }}/status | {{ model_class_name }}StatusView |
{% endif %}
| 获取列表 | GET | /{{ route_prefix }}/data | {{ model_class_name }}DataView |
------------------------------------------------------------------------
URL参数说明:
- {{ primary_key }}: int类型,{{ module_comment }}ID(用于查询详情)
- {{ primary_key }}: str类型,逗号分隔的{{ module_comment }}ID字符串(用于批量删除)
使用示例:
1. 分页查询:GET /api/{{ route_prefix }}/page?pageNo=1&pageSize=10
2. 列表查询:GET /api/{{ route_prefix }}/list
3. 详情查询:GET /api/{{ route_prefix }}/detail/5
4. 添加{{ module_comment }}:POST /api/{{ route_prefix }}/add
5. 更新{{ module_comment }}:PUT /api/{{ route_prefix }}/update
6. 删除{{ module_comment }}:DELETE /api/{{ route_prefix }}/delete/1,2,3
{% if has_status %}
7. 设置状态:PUT /api/{{ route_prefix }}/status
{% endif %}
8. 获取列表:GET /api/{{ route_prefix }}/data
"""
# {{ module_comment }}模块路由配置
urlpatterns = [
# 查询{{ module_comment }}分页列表
# URL: /{{ route_prefix }}/page
# 请求方法: GET
# 功能: 获取{{ module_comment }}分页数据,支持按{% for filter_field in filter_fields %}{{ filter_field.comment }}{% if not loop.last %}、{% endif %}{% endfor %}等条件筛选
path('page', views.{{ model_class_name }}PageView.as_view()),
# 查询{{ module_comment }}数据列表(支持筛选)
# URL: /{{ route_prefix }}/list
# 请求方法: GET
# 功能: 获取所有{{ module_comment }}的列表数据,支持按条件筛选,用于下拉选择等前端组件
path('list', views.{{ model_class_name }}ListView.as_view()),
# 查询{{ module_comment }}详情
# URL: /{{ route_prefix }}/detail/<int:{{ primary_key }}>
# 请求方法: GET
# URL参数: {{ primary_key }} - {{ module_comment }}ID(整数类型)
# 功能: 根据ID获取指定{{ module_comment }}的详细信息
path('detail/<int:{{ primary_key }}>', views.{{ model_class_name }}DetailView.as_view()),
# 添加{{ module_comment }}
# URL: /{{ route_prefix }}/add
# 请求方法: POST
# 功能: 创建新的{{ module_comment }}记录,请求体需包含{{ module_comment }}信息({% for field in form_fields %}{{ field.name }}{% if not loop.last %}、{% endif %}{% endfor %}等)
path('add', views.{{ model_class_name }}AddView.as_view()),
# 更新{{ module_comment }}
# URL: /{{ route_prefix }}/update
# 请求方法: PUT
# 功能: 更新现有{{ module_comment }}信息,请求体需包含{{ module_comment }}ID和更新内容
path('update', views.{{ model_class_name }}UpdateView.as_view()),
# 删除{{ module_comment }}
# URL: /{{ route_prefix }}/delete/<str:{{ primary_key }}>
# 请求方法: DELETE
# URL参数: {{ primary_key }} - {{ module_comment }}ID字符串,支持单个ID或逗号分隔的多个ID(如 "1,2,3")
# 功能: 删除指定{{ module_comment }}(逻辑删除,设置is_delete=True)
path('delete/<str:{{ primary_key }}>', views.{{ model_class_name }}DeleteView.as_view()),
{% if has_status %}
# 设置{{ module_comment }}状态
# URL: /{{ route_prefix }}/status
# 请求方法: PUT
# 功能: 更新{{ module_comment }}的启用/禁用状态
path('status', views.{{ model_class_name }}StatusView.as_view()),
{% endif %}
# 查询{{ module_comment }}数据列表
# URL: /{{ route_prefix }}/list
# 请求方法: GET
# 功能: 获取所有启用{{ module_comment }}的列表数据,用于下拉选择等前端组件
path('data', views.{{ model_class_name }}DataView.as_view()),
]
+384
View File
@@ -0,0 +1,384 @@
# +======================================================================
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
# +----------------------------------------------------------------------
# | Copyright (c) 2021-2026 小蚂蚁云团队 版权所有
# +----------------------------------------------------------------------
# | 开源协议: Apache-2.0
# | 重要声明: 未经书面许可,不得移除或修改本框架相关版权标识。
# +----------------------------------------------------------------------
# | 官方网站: https://www.djangoadmin.cn
# +----------------------------------------------------------------------
# | 研发团队: 小蚂蚁云团队 荣誉出品
# +======================================================================
# | 版权和免责声明:
# | 一、知识产权声明
# | 本团队对该软件框架产品(以下简称“本产品”)拥有完整知识产权,包括但不限于商标权、专利权、
# | 著作权、商业秘密等。前述权利均受中华人民共和国及相关国际法律法规保护。未经本团队书面授权,
# | 任何个人或单位不得基于本产品本身申请任何形式的知识产权。
# | 二、使用限制与禁止条款
# | 您理解并同意,禁止将本产品用于以下任何行为:
# | 1. 任何违反中华人民共和国法律法规的用途;
# | 2. 任何侵害他人合法权益(包括但不限于人身权、财产权、知识产权等)的行为;
# | 3. 任何危害国家安全、公共利益及社会道德风尚的行为。
# | 三、责任豁免
# | 因使用本产品而产生的任何损失或法律纠纷(包括但不限于直接损失、间接损失、附带损失、衍生损失
# | 以及因违约、侵权等引发的责任),本团队均不承担任何法律责任。若因违规使用本产品导致任何第三
# | 方提起索赔或行政机关进行调查,您应独立承担全部责任。
# | 四、违约与法律责任
# | 如发现任何违反本声明的行为,本团队有权:
# | 1. 无条件配合公安机关及其他有权机关的调查取证工作;
# | 2. 依法追究相关方的民事责任,并要求赔偿全部损失;
# | 3. 保留进一步采取刑事控告等法律措施的权利。
# | 五、许可范围说明
# | 1、被授权方应视本产品源代码为核心机密信息,负有最高标准的保密与保管义务。禁止以任何形式(包
# | 括但不限于直接或间接的分享、复制、传播、分发、转售、出租、开源、反向工程、反编译、反汇编或通
# | 过网络服务提供给任何第三方)泄露、披露或处置源代码的全部或部分内容。任何未经授权的复制、使用、
# | 传播行为均构成对本团队知识产权的严重侵权,本团队有权依法追究其全部法律责任,并要求赔偿因此造
# | 成的一切经济损失及商誉损害。
# | 2、本产品仅限于公司或个人在法律允许范围内,用于内部且合规的软件产品研发。具体权利义务以《软件
# | 许可协议》附件为准。
# +======================================================================
"""
{{ module_comment }}视图模块模板
=============================================================
该模板用于生成{{ module_comment }}管理功能中使用的所有视图类。
用于处理{{ module_comment }}信息的HTTP请求,包括分页查询、详情查询、添加、更新、删除、获取列表等操作。
{{ module_comment }}用于{{ business_description }}。
每个视图类对应一个特定的API端点,处理相应的业务逻辑。
模板变量说明:
- module_comment: 模块注释,如"文章管理"
- app_name: 应用名称,如"article"
- model_class_name: 模型类名,如"Article"
- permission_prefix: 权限前缀,如"sys:article"
- route_prefix: 路由前缀,如"article"
- primary_key: 主键字段名,如"id"
- filter_fields: 筛选字段列表
- has_status: 是否包含状态字段
- business_description: 业务描述
- filter_fields_comment: 筛选字段注释
"""
from django.utils.decorators import method_decorator
from django.views import View
from application.{{ app_name }} import services
from config.env import DJANGO_DEMO
from middleware.login_middleware import check_login
from middleware.permission_middleware import PermissionRequired
from utils import R
from application.operation_log.constants import LogType
from application.operation_log.decorators import operation_log
# =============================================================
# 分页查询视图
# =============================================================
@method_decorator(check_login, name="get")
class {{ model_class_name }}PageView(PermissionRequired, View):
"""
{{ module_comment }}分页数据查询视图
处理GET请求,返回{{ module_comment }}列表的分页数据。
支持按{{ filter_fields_comment }}等条件筛选。
URL: /{{ route_prefix }}/page
请求方法: GET
权限要求:{{ permission_prefix }}:page
"""
# 方法权限标识
permission_required = ("{{ permission_prefix }}:page",)
@operation_log(
title="{{ module_comment }}管理-查询分页列表",
log_type=LogType.QUERY
)
def get(self, request):
"""
处理GET请求,返回{{ module_comment }}分页数据
Args:
request: HttpRequest对象,包含分页参数和筛选条件
- pageNo: 当前页码
- pageSize: 每页记录数
{% for filter_field in filter_fields %}
- {{ filter_field.name }}: {{ filter_field.comment }}(可选,{{ filter_field.filter_type }})
{% endfor %}
Returns:
JsonResponse: 包含分页数据的JSON响应
"""
result = services.get_{{ app_name }}_page(request)
return result
# =============================================================
# 列表查询视图(不分页)
# =============================================================
@method_decorator(check_login, name='get')
class {{ model_class_name }}ListView(View):
"""
{{ module_comment }}列表获取视图(支持筛选,不分页)
处理GET请求,返回所有{{ module_comment }}的列表数据。
支持按{% for filter_field in filter_fields %}{{ filter_field.comment }}{% if not loop.last %}、{% endif %}{% endfor %}等条件筛选。
URL: /{{ route_prefix }}/list
请求方法: GET
权限要求:需要登录
"""
def get(self, request):
"""
处理GET请求,返回{{ module_comment }}列表
Args:
request: HttpRequest对象
Returns:
JsonResponse: 包含{{ module_comment }}列表的JSON响应
"""
result = services.get_{{ app_name }}_list(request)
return R.ok(data=result)
# =============================================================
# 详情查询视图
# =============================================================
@method_decorator(check_login, name="get")
class {{ model_class_name }}DetailView(PermissionRequired, View):
"""
{{ module_comment }}详情查询视图
处理GET请求,根据{{ module_comment }}ID返回{{ module_comment }}详细信息。
URL: /{{ route_prefix }}/detail/<int:{{ primary_key }}>
请求方法: GET
URL参数: {{ primary_key }} - {{ module_comment }}ID
权限要求:{{ permission_prefix }}:detail
"""
# 方法权限标识
permission_required = ("{{ permission_prefix }}:detail",)
@operation_log(
title="{{ module_comment }}管理-查询记录详情",
log_type=LogType.QUERY
)
def get(self, request, {{ primary_key }}):
"""
处理GET请求,返回{{ module_comment }}详情
Args:
request: HttpRequest对象
{{ primary_key }}: int, {{ module_comment }}ID(从URL中获取)
Returns:
JsonResponse: 包含{{ module_comment }}详细信息的JSON响应
"""
data = services.get_{{ app_name }}_detail({{ primary_key }})
return R.ok(data=data)
# =============================================================
# 添加视图
# =============================================================
@method_decorator(check_login, name="post")
class {{ model_class_name }}AddView(PermissionRequired, View):
"""
添加{{ module_comment }}视图
处理POST请求,创建新的{{ module_comment }}记录。
URL: /{{ route_prefix }}/add
请求方法: POST
权限要求:{{ permission_prefix }}:add
"""
# 方法权限标识
permission_required = ("{{ permission_prefix }}:add",)
@operation_log(
title="{{ module_comment }}管理-添加记录",
log_type=LogType.ADD
)
def post(self, request):
"""
处理POST请求,添加新{{ module_comment }}
Args:
request: HttpRequest对象,包含{{ module_comment }}信息的JSON数据
Returns:
JsonResponse: 包含操作结果的JSON响应
"""
# 演示环境禁止操作
if DJANGO_DEMO:
return R.failed("演示环境,暂无操作权限")
result = services.add_{{ app_name }}(request)
return result
# =============================================================
# 更新视图
# =============================================================
@method_decorator(check_login, name="put")
class {{ model_class_name }}UpdateView(PermissionRequired, View):
"""
更新{{ module_comment }}视图
处理PUT请求,更新现有{{ module_comment }}信息。
URL: /{{ route_prefix }}/update
请求方法: PUT
权限要求:{{ permission_prefix }}:update
"""
# 方法权限标识
permission_required = ('{{ permission_prefix }}:update',)
@operation_log(
title="{{ module_comment }}管理-更新记录",
log_type=LogType.UPDATE
)
def put(self, request):
"""
处理PUT请求,更新{{ module_comment }}信息
Args:
request: HttpRequest对象,包含{{ module_comment }}ID和更新信息的JSON数据
Returns:
JsonResponse: 包含操作结果的JSON响应
"""
# 演示环境禁止操作
if DJANGO_DEMO:
return R.failed("演示环境,暂无操作权限")
result = services.update_{{ app_name }}(request)
return result
# =============================================================
# 删除视图(支持批量)
# =============================================================
@method_decorator(check_login, name="delete")
class {{ model_class_name }}DeleteView(PermissionRequired, View):
"""
删除{{ module_comment }}视图
处理DELETE请求,删除指定{{ module_comment }}(支持批量删除)。
URL: /{{ route_prefix }}/delete/<str:{{ primary_key }}>
请求方法: DELETE
URL参数: {{ primary_key }} - {{ module_comment }}ID字符串,多个ID用逗号分隔
权限要求:{{ permission_prefix }}:delete
注意: 删除{{ module_comment }}前请确认是否被其他数据关联
"""
# 方法权限标识
permission_required = ('{{ permission_prefix }}:delete',)
@operation_log(
title="{{ module_comment }}管理-删除记录",
log_type=LogType.DELETE
)
def delete(self, request, {{ primary_key }}):
"""
处理DELETE请求,删除{{ module_comment }}
Args:
request: HttpRequest对象
{{ primary_key }}: str, {{ module_comment }}ID字符串,多个ID用逗号分隔(从URL中获取)
例如: "1" 或 "1,2,3"
Returns:
JsonResponse: 包含操作结果的JSON响应
"""
# 演示环境禁止操作
if DJANGO_DEMO:
return R.failed("演示环境,暂无操作权限")
result = services.delete_{{ app_name }}s({{ primary_key }})
return result
{% if has_status %}
# =============================================================
# 状态更新视图
# =============================================================
@method_decorator(check_login, name="put")
class {{ model_class_name }}StatusView(PermissionRequired, View):
"""
设置{{ module_comment }}状态视图
处理PUT请求,更新{{ module_comment }}的启用/禁用状态。
URL: /{{ route_prefix }}/status
请求方法: PUT
权限要求:{{ permission_prefix }}:status
"""
# 方法权限标识
permission_required = ('{{ permission_prefix }}:status',)
def put(self, request):
"""
处理PUT请求,更新{{ module_comment }}状态
Args:
request: HttpRequest对象,包含{{ module_comment }}ID和状态值的JSON数据
Returns:
JsonResponse: 包含操作结果的JSON响应
"""
# 演示环境禁止操作
if DJANGO_DEMO:
return R.failed("演示环境,暂无操作权限")
result = services.update_{{ app_name }}_status(request)
return result
{% endif %}
# =============================================================
# 数据下拉列表视图
# =============================================================
@method_decorator(check_login, name='get')
class {{ model_class_name }}DataView(View):
"""
{{ module_comment }}列表获取视图
处理GET请求,返回所有启用{{ module_comment }}的列表数据。
通常用于前端下拉选择框等场景。
URL: /{{ route_prefix }}/data
请求方法: GET
权限要求:需要登录
"""
def get(self, request):
"""
处理GET请求,返回{{ module_comment }}列表
Args:
request: HttpRequest对象
Returns:
JsonResponse: 包含{{ module_comment }}列表的JSON响应
"""
result = services.get_{{ app_name }}_data()
return R.ok(data=result)