forked from erp-dev/erp
feat: added frontend_pages models(basic_info) and api(v2), support employees query pages that they can visit by themself token
This commit is contained in:
0
basic_info/management/commands/__init__.py
Normal file
0
basic_info/management/commands/__init__.py
Normal file
164
basic_info/management/commands/sync_frontend_pages.py
Normal file
164
basic_info/management/commands/sync_frontend_pages.py
Normal file
@@ -0,0 +1,164 @@
|
||||
"""
|
||||
同步前端页面数据
|
||||
|
||||
从 docs/menu_keys.json 文件同步前端页面到数据库。
|
||||
|
||||
用法:
|
||||
python manage.py sync_frontend_pages
|
||||
python manage.py sync_frontend_pages --dry-run # 仅预览,不实际写入
|
||||
python manage.py sync_frontend_pages --clear # 清空后重新同步
|
||||
"""
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from basic_info.models import FrontendPage, FrontendPageTypeEnum
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '从 docs/menu_keys.json 同步前端页面到数据库'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--dry-run',
|
||||
action='store_true',
|
||||
help='仅预览变更,不实际写入数据库',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--clear',
|
||||
action='store_true',
|
||||
help='清空现有数据后重新同步(谨慎使用)',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--file',
|
||||
type=str,
|
||||
default='docs/menu_keys.json',
|
||||
help='JSON 文件路径(相对于项目根目录)',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
dry_run = options['dry_run']
|
||||
clear = options['clear']
|
||||
file_path = options['file']
|
||||
|
||||
# 读取 JSON 文件
|
||||
# __file__ = .../basic_info/management/commands/sync_frontend_pages.py
|
||||
# parents[3] = 项目根目录 (flower/)
|
||||
json_path = Path(__file__).resolve().parents[3] / file_path
|
||||
if not json_path.exists():
|
||||
self.stderr.write(self.style.ERROR(f'文件不存在: {json_path}'))
|
||||
return
|
||||
|
||||
with open(json_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
menus = data.get('menus', [])
|
||||
if not menus:
|
||||
self.stderr.write(self.style.ERROR('JSON 文件中没有 menus 数据'))
|
||||
return
|
||||
|
||||
self.stdout.write(f'读取到 {len(menus)} 个主菜单')
|
||||
|
||||
# 统计
|
||||
created_count = 0
|
||||
updated_count = 0
|
||||
skipped_count = 0
|
||||
|
||||
try:
|
||||
with transaction.atomic():
|
||||
# 清空现有数据
|
||||
if clear:
|
||||
if dry_run:
|
||||
self.stdout.write(self.style.WARNING('[DRY-RUN] 将清空所有前端页面数据'))
|
||||
else:
|
||||
deleted_count, _ = FrontendPage.objects.all().delete()
|
||||
self.stdout.write(self.style.WARNING(f'已清空 {deleted_count} 条记录'))
|
||||
|
||||
# 第一轮:创建/更新主菜单
|
||||
main_menu_map = {} # key -> FrontendPage instance
|
||||
for sort_order, menu in enumerate(menus):
|
||||
key = menu['key']
|
||||
label = menu['label']
|
||||
|
||||
if dry_run:
|
||||
self.stdout.write(f'[DRY-RUN] 主菜单: {key} ({label})')
|
||||
created_count += 1
|
||||
else:
|
||||
page, created = FrontendPage.objects.update_or_create(
|
||||
key=key,
|
||||
defaults={
|
||||
'label': label,
|
||||
'page_type': FrontendPageTypeEnum.MAIN,
|
||||
'parent': None,
|
||||
'sort_order': sort_order * 100, # 主菜单间隔100
|
||||
'is_active': True,
|
||||
}
|
||||
)
|
||||
main_menu_map[key] = page
|
||||
if created:
|
||||
created_count += 1
|
||||
self.stdout.write(self.style.SUCCESS(f'创建主菜单: {key} ({label})'))
|
||||
else:
|
||||
updated_count += 1
|
||||
self.stdout.write(f'更新主菜单: {key} ({label})')
|
||||
|
||||
# 第二轮:创建/更新子菜单
|
||||
for menu in menus:
|
||||
parent_key = menu['key']
|
||||
children = menu.get('children', [])
|
||||
|
||||
parent_page = main_menu_map.get(parent_key) if not dry_run else None
|
||||
|
||||
for sub_order, child in enumerate(children):
|
||||
child_key = child['key']
|
||||
child_label = child['label']
|
||||
|
||||
if dry_run:
|
||||
self.stdout.write(f'[DRY-RUN] 子菜单: {child_key} ({child_label}) <- {parent_key}')
|
||||
created_count += 1
|
||||
else:
|
||||
# 计算排序:父级排序 + 子级偏移
|
||||
parent_sort = parent_page.sort_order if parent_page else 0
|
||||
child_sort = parent_sort + sub_order + 1
|
||||
|
||||
page, created = FrontendPage.objects.update_or_create(
|
||||
key=child_key,
|
||||
defaults={
|
||||
'label': child_label,
|
||||
'page_type': FrontendPageTypeEnum.SUB,
|
||||
'parent': parent_page,
|
||||
'sort_order': child_sort,
|
||||
'is_active': True,
|
||||
}
|
||||
)
|
||||
if created:
|
||||
created_count += 1
|
||||
self.stdout.write(self.style.SUCCESS(f' 创建子菜单: {child_key} ({child_label})'))
|
||||
else:
|
||||
updated_count += 1
|
||||
self.stdout.write(f' 更新子菜单: {child_key} ({child_label})')
|
||||
|
||||
if dry_run:
|
||||
# 回滚事务
|
||||
raise DryRunException()
|
||||
|
||||
except DryRunException:
|
||||
pass
|
||||
|
||||
# 输出统计
|
||||
self.stdout.write('')
|
||||
self.stdout.write(self.style.SUCCESS('=' * 40))
|
||||
if dry_run:
|
||||
self.stdout.write(self.style.WARNING(f'[DRY-RUN] 预计创建: {created_count} 条'))
|
||||
else:
|
||||
self.stdout.write(self.style.SUCCESS(f'创建: {created_count} 条'))
|
||||
self.stdout.write(f'更新: {updated_count} 条')
|
||||
self.stdout.write(f'总计: {FrontendPage.objects.count()} 条')
|
||||
|
||||
|
||||
class DryRunException(Exception):
|
||||
"""用于 dry-run 模式回滚事务"""
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user