diff --git a/backend/dvadmin/system/fixtures/initSerializer.py b/backend/dvadmin/system/fixtures/initSerializer.py index 9ed094f..f983aea 100644 --- a/backend/dvadmin/system/fixtures/initSerializer.py +++ b/backend/dvadmin/system/fixtures/initSerializer.py @@ -19,6 +19,20 @@ class UsersInitSerializer(CustomModelSerializer): """ 初始化获取数信息(用于生成初始化json文件) """ + role_key = serializers.SerializerMethodField() + dept_key = serializers.SerializerMethodField() + + def get_dept_key(self, obj): + if obj.dept: + return obj.dept.key + else: + return None + + def get_role_key(self, obj): + if obj.role.all(): + return [role.key for role in obj.role.all()] + else: + return [] def save(self, **kwargs): instance = super().save(**kwargs) @@ -35,7 +49,7 @@ class UsersInitSerializer(CustomModelSerializer): model = Users fields = ["username", "email", 'mobile', 'avatar', "name", 'gender', 'user_type', "dept", 'user_type', 'first_name', 'last_name', 'email', 'is_staff', 'is_active', 'creator', 'dept_belong_id', - 'password', 'last_login', 'is_superuser'] + 'password', 'last_login', 'is_superuser', 'role_key' ,'dept_key'] read_only_fields = ['id'] extra_kwargs = { 'creator': {'write_only': True}, @@ -175,15 +189,21 @@ class RoleMenuInitSerializer(CustomModelSerializer): """ 初始化角色菜单(用于生成初始化json文件) """ - role__key = serializers.CharField(max_length=100, required=True) - menu__web_path = serializers.CharField(max_length=100, required=True) - menu__component_name = serializers.CharField(max_length=100, required=True, allow_blank=True) + role__key = serializers.CharField(source='role.key') + menu__web_path = serializers.CharField(source='menu.web_path') + menu__component_name = serializers.CharField(source='menu.component_name', allow_blank=True) + + def update(self, instance, validated_data): + init_data = self.initial_data + role_id = Role.objects.filter(key=init_data['role__key']).first() + menu_id = Menu.objects.filter(web_path=init_data['menu__web_path'], component_name=init_data['menu__component_name']).first() + validated_data['role'] = role_id + validated_data['menu'] = menu_id + return super().update(instance, validated_data) + def create(self, validated_data): init_data = self.initial_data - validated_data.pop('menu__web_path') - validated_data.pop('menu__component_name') - validated_data.pop('role__key') role_id = Role.objects.filter(key=init_data['role__key']).first() menu_id = Menu.objects.filter(web_path=init_data['menu__web_path'], component_name=init_data['menu__component_name']).first() validated_data['role'] = role_id @@ -192,7 +212,7 @@ class RoleMenuInitSerializer(CustomModelSerializer): class Meta: model = RoleMenuPermission - fields = ['role__key', 'menu__web_path', 'menu__component_name', 'creator', 'dept_belong_id'] + fields = ['role__key', 'menu__web_path', 'menu__component_name','creator', 'dept_belong_id'] read_only_fields = ["id"] extra_kwargs = { 'role': {'required': False}, @@ -206,14 +226,22 @@ class RoleMenuButtonInitSerializer(CustomModelSerializer): """ 初始化角色菜单按钮(用于生成初始化json文件) """ - role__key = serializers.CharField(max_length=100, required=True) - menu_button__value = serializers.CharField(max_length=100, required=True) + role__key = serializers.CharField(source='role.key') + menu_button__value = serializers.CharField(source='menu_button.value') data_range = serializers.CharField(max_length=100, required=False) + def update(self, instance, validated_data): + init_data = self.initial_data + role_id = Role.objects.filter(key=init_data['role__key']).first() + menu_button_id = MenuButton.objects.filter(value=init_data['menu_button__value']).first() + validated_data['role'] = role_id + validated_data['menu_button'] = menu_button_id + instance = super().create(validated_data) + instance.dept.set([]) + return super().update(instance, validated_data) + def create(self, validated_data): init_data = self.initial_data - validated_data.pop('menu_button__value') - validated_data.pop('role__key') role_id = Role.objects.filter(key=init_data['role__key']).first() menu_button_id = MenuButton.objects.filter(value=init_data['menu_button__value']).first() validated_data['role'] = role_id @@ -223,7 +251,7 @@ class RoleMenuButtonInitSerializer(CustomModelSerializer): return instance def save(self, **kwargs): - if self.instance and self.initial_data.get('reset'): + if not self.instance or self.initial_data.get('reset'): return super().save(**kwargs) return self.instance diff --git a/backend/dvadmin/system/management/commands/generate_init_json.py b/backend/dvadmin/system/management/commands/generate_init_json.py index b074344..6ce83b0 100644 --- a/backend/dvadmin/system/management/commands/generate_init_json.py +++ b/backend/dvadmin/system/management/commands/generate_init_json.py @@ -10,7 +10,7 @@ django.setup() from django.core.management.base import BaseCommand from application.settings import BASE_DIR -from dvadmin.system.models import Menu, Users, Dept, Role, ApiWhiteList, Dictionary, SystemConfig +from dvadmin.system.models import Menu, Users, Dept, Role, ApiWhiteList, Dictionary, SystemConfig, RoleMenuButtonPermission, RoleMenuPermission from dvadmin.system.fixtures.initSerializer import UsersInitSerializer, DeptInitSerializer, RoleInitSerializer, \ MenuInitSerializer, ApiWhiteListInitSerializer, DictionaryInitSerializer, SystemConfigInitSerializer, \ RoleMenuInitSerializer, RoleMenuButtonInitSerializer @@ -57,6 +57,12 @@ class Command(BaseCommand): def generate_system_config(self): self.serializer_data(SystemConfigInitSerializer, SystemConfig.objects.filter(parent_id__isnull=True)) + def generate_role_menu(self): + self.serializer_data(RoleMenuInitSerializer, RoleMenuPermission.objects.all()) + + def generate_role_menu_button(self): + self.serializer_data(RoleMenuButtonInitSerializer, RoleMenuButtonPermission.objects.all()) + def handle(self, *args, **options): generate_name = options.get('generate_name') generate_name_dict = { @@ -67,6 +73,8 @@ class Command(BaseCommand): "api_white_list": self.generate_api_white_list, "dictionary": self.generate_dictionary, "system_config": self.generate_system_config, + "role_menu": self.generate_role_menu, + "role_menu_button": self.generate_role_menu_button, } if not generate_name: for ele in generate_name_dict.keys(): diff --git a/crud-gen.sh b/crud-gen.sh new file mode 100644 index 0000000..51fe5f3 --- /dev/null +++ b/crud-gen.sh @@ -0,0 +1,87 @@ +if ! [ -f ".env" ];then + echo ".env file not found" + exit 1 +fi + +if [ -z "$3" ]; then + echo "Use: $0 " + exit 1 +fi + + +DIR=./web/src/views/$1/$2 + + +# 设置数据库连接信息 +HOST="177.10.0.13" +USER="root" +PASSWORD=$(cat .env | grep MYSQL_PASSWORD | sed 's/^.*MYSQL_PASSWORD=//g') +DATABASE="django-vue3-admin" +TABLE=$3 +TARGET_FILE="./web/src/views/$1/$2/crud.tsx" + + +# 表是否存在 +TABLE_EXISTS=$(mysql -h $HOST -u $USER -p$PASSWORD -D $DATABASE -e "SHOW TABLES LIKE '$TABLE';" -N | grep "$TABLE" | wc -l) + +if [ "$TABLE_EXISTS" -eq 0 ]; then + echo "Table $TABLE does not exist in database $DATABASE." + exit 1 +fi + +mkdir -p $DIR +cp -r ./web/src/views/template/* $DIR +sed -i "s/VIEWSETNAME/$2/g" $DIR/* + +sed -n -e :a -e '1,5!{P;N;D;};N;ba' -i $TARGET_FILE + +# 查询表结构 +QUERY="SELECT COLUMN_NAME, DATA_TYPE, COLUMN_COMMENT, IS_NULLABLE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '$DATABASE' AND TABLE_NAME = '$TABLE' ORDER BY ORDINAL_POSITION;" + +# 使用 MySQL 查询获取字段信息,并生成 fast-crud 配置 +mysql -h $HOST -u $USER -p$PASSWORD -D $DATABASE -e "$QUERY" -N | while read COLUMN_NAME DATA_TYPE COLUMN_COMMENT IS_NULLABLE; do + # 映射 MySQL 数据类型到 fast-crud 类型 + case "$DATA_TYPE" in + "int"|"bigint"|"smallint"|"mediumint"|"tinyint"|"decimal"|"float"|"double") + TYPE="number" + ;; + "date"|"datetime"|"timestamp") + TYPE="date" + ;; + *) + TYPE="text" + ;; + esac + + echo " $COLUMN_NAME: { + title: '$COLUMN_NAME', + type: '$TYPE', + search: { show: true }, + column: { + minWidth: 120, + sortable: 'custom', + }, + form: {" >> $TARGET_FILE + + if [ "$IS_NULLABLE" = "NO" ]; then + echo " helper: { + render() { + return
$COLUMN_NAME 是必填的
; + } + }, + rules: [{ + required: true, message: '$COLUMN_NAME 是必填的' + }]," >> $TARGET_FILE + fi + + echo " component: { + placeholder: '请输入 $COLUMN_NAME', + }, + }, + }," >> $TARGET_FILE +done + +echo " }, + }, + }; +}" >> $TARGET_FILE diff --git a/web/src/views/template/api.ts b/web/src/views/template/api.ts new file mode 100644 index 0000000..1ec69a9 --- /dev/null +++ b/web/src/views/template/api.ts @@ -0,0 +1,50 @@ +import { request,downloadFile } from '/@/utils/service'; +import { PageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud'; + +export const apiPrefix = '/api/VIEWSETNAME/'; + +export function GetList(query: PageQuery) { + return request({ + url: apiPrefix, + method: 'get', + params: query, + }); +} +export function GetObj(id: InfoReq) { + return request({ + url: apiPrefix + id, + method: 'get', + }); +} + +export function AddObj(obj: AddReq) { + return request({ + url: apiPrefix, + method: 'post', + data: obj, + }); +} + +export function UpdateObj(obj: EditReq) { + return request({ + url: apiPrefix + obj.id + '/', + method: 'put', + data: obj, + }); +} + +export function DelObj(id: DelReq) { + return request({ + url: apiPrefix + id + '/', + method: 'delete', + data: { id }, + }); +} + +export function exportData(params:any){ + return downloadFile({ + url: apiPrefix + 'export_data/', + params: params, + method: 'get' + }) +} \ No newline at end of file diff --git a/web/src/views/template/crud.tsx b/web/src/views/template/crud.tsx new file mode 100644 index 0000000..8ef7a7d --- /dev/null +++ b/web/src/views/template/crud.tsx @@ -0,0 +1,86 @@ +import { CrudOptions, AddReq, DelReq, EditReq, dict, CrudExpose, UserPageQuery, CreateCrudOptionsRet } from '@fast-crud/fast-crud'; +import _ from 'lodash-es'; +import * as api from './api'; +import { request } from '/@/utils/service'; +import { auth } from "/@/utils/authFunction"; + +//此处为crudOptions配置 +export default function ({ crudExpose }: { crudExpose: CrudExpose }): CreateCrudOptionsRet { + const pageRequest = async (query: any) => { + return await api.GetList(query); + }; + const editRequest = async ({ form, row }: EditReq) => { + if (row.id) { + form.id = row.id; + } + return await api.UpdateObj(form); + }; + const delRequest = async ({ row }: DelReq) => { + return await api.DelObj(row.id); + }; + const addRequest = async ({ form }: AddReq) => { + return await api.AddObj(form); + }; + + const exportRequest = async (query: UserPageQuery) => { + return await api.exportData(query) + }; + + return { + crudOptions: { + request: { + pageRequest, + addRequest, + editRequest, + delRequest, + }, + actionbar: { + buttons: { + export: { + // 注释编号:django-vue3-admin-crud210716:注意这个auth里面的值,最好是使用index.vue文件里面的name值并加上请求动作的单词 + show: auth('VIEWSETNAME:Export'), + text: "导出",//按钮文字 + title: "导出",//鼠标停留显示的信息 + click() { + return exportRequest(crudExpose.getSearchFormData()) + // return exportRequest(crudExpose!.getSearchFormData()) // 注意这个crudExpose!.getSearchFormData(),一些低版本的环境是需要添加!的 + } + }, + add: { + show: auth('VIEWSETNAME:Create'), + }, + } + }, + rowHandle: { + //固定右侧 + fixed: 'right', + width: 200, + buttons: { + view: { + type: 'text', + order: 1, + show: auth('VIEWSETNAME:Retrieve') + }, + edit: { + type: 'text', + order: 2, + show: auth('VIEWSETNAME:Update') + }, + copy: { + type: 'text', + order: 3, + show: auth('VIEWSETNAME:Copy') + }, + remove: { + type: 'text', + order: 4, + show: auth('VIEWSETNAME:Delete') + }, + }, + }, + columns: { + // COLUMNS_CONFIG + }, + }, + }; +} \ No newline at end of file diff --git a/web/src/views/template/index.vue b/web/src/views/template/index.vue new file mode 100644 index 0000000..be662d5 --- /dev/null +++ b/web/src/views/template/index.vue @@ -0,0 +1,56 @@ + + + \ No newline at end of file