- 分类导航由顶部横条改为左侧 sticky 侧栏(≥1025px),含快速导航/我常访问/热门/书签分类/站点页面 - 首页左右去留白:.container 与 .topbar-inner 改为 max-width:100%,布局铺满视口 - 卡片网格按屏宽自适应(minmax(var(--card-min),1fr)),1920 固定 6 列 - 移除顶部 .actions 菜单(与侧栏站点页面重复),主题切换改由侧栏按钮循环切换 - 新增 get_nav_categories() 统一锚点(侧栏/顶部/抽屉/section id 四者同源),修复空分类错位;后台分类增删改/启停后失效前台缓存
176 lines
5.7 KiB
Python
176 lines
5.7 KiB
Python
"""
|
||
导航分类管理后台视图 — navManager 插件内的子模块。
|
||
|
||
URL:/system/nav-category
|
||
权限:system:nav-category:*
|
||
"""
|
||
import os
|
||
|
||
from flask import Blueprint, render_template, request
|
||
from flask_login import current_user
|
||
|
||
from applications.common import curd
|
||
from applications.common.utils.http import table_api, fail_api, success_api
|
||
from applications.common.utils.rights import authorize
|
||
from applications.common.utils.validate import str_escape
|
||
from applications.extensions import db
|
||
from applications.extensions.init_limit import limiter
|
||
from applications.models import NavCategory
|
||
from applications.schemas import NavCategorySchema
|
||
|
||
dir_path = os.path.dirname(os.path.abspath(__file__))
|
||
bp = Blueprint(
|
||
'nav_category', __name__,
|
||
url_prefix='/system/nav-category',
|
||
template_folder=os.path.join(dir_path, '..', 'templates'),
|
||
)
|
||
|
||
|
||
def _invalidate_nav_cache():
|
||
"""分类变更后立即失效前台侧栏分类缓存(延迟导入避免循环依赖)。"""
|
||
try:
|
||
from applications.view.public.nav import clear_nav_categories_cache
|
||
clear_nav_categories_cache()
|
||
except Exception: # 缓存失效失败不影响主流程
|
||
pass
|
||
|
||
|
||
@bp.get('/')
|
||
@authorize("system:nav-category:main")
|
||
def main():
|
||
return render_template('system/nav/category_main.html')
|
||
|
||
|
||
@bp.get('/data')
|
||
@limiter.limit("60 per minute")
|
||
@authorize("system:nav-category:main")
|
||
def data():
|
||
keyword = str_escape(request.args.get('keyword', type=str))
|
||
query = NavCategory.query.filter()
|
||
if keyword:
|
||
query = query.filter(
|
||
db.or_(
|
||
NavCategory.name.contains(keyword),
|
||
NavCategory.description.contains(keyword),
|
||
)
|
||
)
|
||
items = query.order_by(NavCategory.sort.desc(), NavCategory.id.asc()).layui_paginate()
|
||
return table_api(
|
||
msg='请求成功',
|
||
data=curd.model_to_dicts(schema=NavCategorySchema, data=items.items),
|
||
count=items.total,
|
||
)
|
||
|
||
|
||
@bp.get('/add')
|
||
@authorize("system:nav-category:add", log=True)
|
||
def add():
|
||
return render_template('system/nav/category_add.html')
|
||
|
||
|
||
@bp.post('/save')
|
||
@authorize("system:nav-category:add", log=True)
|
||
def save():
|
||
req_json = request.get_json(force=True, silent=True) or {}
|
||
name = str_escape(req_json.get('name'))
|
||
if not name:
|
||
return fail_api(msg='分类名不能为空')
|
||
if NavCategory.query.filter_by(name=name).first():
|
||
return fail_api(msg=f'分类"{name}"已存在')
|
||
|
||
try:
|
||
cat = NavCategory(
|
||
name=name,
|
||
icon=str_escape(req_json.get('icon')) or 'layui-icon-list',
|
||
description=str_escape(req_json.get('description')),
|
||
sort=int(req_json.get('sort') or 0),
|
||
enable=int(req_json.get('enable') or 1),
|
||
)
|
||
db.session.add(cat)
|
||
db.session.commit()
|
||
except Exception as e:
|
||
db.session.rollback()
|
||
return fail_api(msg=f'新增失败:{e}')
|
||
return success_api(msg='新增成功')
|
||
|
||
|
||
@bp.get('/edit')
|
||
@authorize("system:nav-category:edit", log=True)
|
||
def edit():
|
||
cat_id = request.args.get('catId', type=int)
|
||
cat = curd.get_one_by_id(NavCategory, cat_id)
|
||
if not cat:
|
||
return fail_api(msg='分类不存在')
|
||
return render_template('system/nav/category_edit.html', cat=cat)
|
||
|
||
|
||
@bp.put('/update')
|
||
@authorize("system:nav-category:edit", log=True)
|
||
def update():
|
||
req_json = request.get_json(force=True, silent=True) or {}
|
||
cat_id = req_json.get('catId')
|
||
cat = curd.get_one_by_id(NavCategory, cat_id)
|
||
if not cat:
|
||
return fail_api(msg='分类不存在')
|
||
name = str_escape(req_json.get('name'))
|
||
if not name:
|
||
return fail_api(msg='分类名不能为空')
|
||
# name 是否被其他行占用
|
||
other = NavCategory.query.filter(NavCategory.name == name, NavCategory.id != cat_id).first()
|
||
if other:
|
||
return fail_api(msg=f'分类"{name}"已被占用')
|
||
|
||
try:
|
||
cat.name = name
|
||
cat.icon = str_escape(req_json.get('icon')) or 'layui-icon-list'
|
||
cat.description = str_escape(req_json.get('description'))
|
||
cat.sort = int(req_json.get('sort') or 0)
|
||
cat.enable = int(req_json.get('enable') or 1)
|
||
db.session.commit()
|
||
_invalidate_nav_cache()
|
||
except Exception as e:
|
||
db.session.rollback()
|
||
return fail_api(msg=f'更新失败:{e}')
|
||
return success_api(msg='更新成功')
|
||
|
||
|
||
@bp.delete('/remove/<int:cat_id>')
|
||
@authorize("system:nav-category:remove", log=True)
|
||
def remove(cat_id):
|
||
"""删除分类时不会动 Nav 记录,但 Nav.category 字段会孤立成 '未分组' 显示"""
|
||
cat = curd.get_one_by_id(NavCategory, cat_id)
|
||
if cat is None:
|
||
return fail_api(msg='分类不存在')
|
||
res = curd.delete_one_by_id(NavCategory, cat_id)
|
||
if not res:
|
||
return fail_api(msg='删除失败')
|
||
return success_api(msg='删除成功(分类下导航已自动归入「未分组」)')
|
||
|
||
|
||
@bp.put('/enable')
|
||
@authorize("system:nav-category:edit", log=True)
|
||
def enable():
|
||
req_json = request.get_json(force=True, silent=True) or {}
|
||
cat_id = req_json.get('catId')
|
||
if not cat_id:
|
||
return fail_api(msg='数据错误')
|
||
res = curd.enable_status(NavCategory, cat_id)
|
||
if not res:
|
||
return fail_api(msg='操作失败')
|
||
_invalidate_nav_cache()
|
||
return success_api(msg='已启用')
|
||
|
||
|
||
@bp.put('/disable')
|
||
@authorize("system:nav-category:edit", log=True)
|
||
def dis_enable():
|
||
req_json = request.get_json(force=True, silent=True) or {}
|
||
cat_id = req_json.get('catId')
|
||
if not cat_id:
|
||
return fail_api(msg='数据错误')
|
||
res = curd.disable_status(NavCategory, cat_id)
|
||
if not res:
|
||
return fail_api(msg='操作失败')
|
||
_invalidate_nav_cache()
|
||
return success_api(msg='已禁用')
|