Files
pear-admin-flask/plugins/navManager/view/nav.py
T
bwstudio 0ffc103e46 feat(plugins): 4 个业务插件代码 + 前台公开页面改造
plugins/navManager:导航 CRUD + 导航分类 CRUD(独立 blueprint)
plugins/friendManager:友情链接 CRUD
plugins/aboutManager:关于本站 CRUD
plugins/siteStats:访问统计主页 + JSON 接口 + /site/* GET 埋点 + nav 卡片点击埋点

applications/view/public/nav.py:导航聚合页 / 分类详情 / JSON 接口,支持 visit_count 注入
applications/view/public/about.py:关于本站公开页
applications/view/public/friend.py:友情链接公开页

templates/public/base.html:响应式首页(搜索框 / 分类抽屉 / 5 主题切换 / footer 隐私小字)
templates/public/index.html / category.html:卡片右下角访问次数
templates/public/about.html / friend.html:关于 + 友链前台页

docs/plugins-development.md:插件开发完整指南(生命周期 + 4 个示例 + FAQ + framework 迁移)

scripts/:探针与端到端验证脚本(probe_*/verify_*/test_*)
2026-09-06 18:18:06 +08:00

177 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
后台导航管理视图(通过插件方式开发)
URL 前缀变更:
- 原蓝图 url_prefix='/nav'(注册到 system_bp → /system/nav/...
- 改为 url_prefix='/system/nav' 直接挂到 app,这样去掉了对 framework 内 system_bp 的依赖
- 与用户原始菜单路径 /system/nav 完全兼容,权限码 system:nav:* 保持不变
"""
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 Nav
from applications.schemas import NavManageSchema
# 插件级 Blueprint:直接挂到 app,前缀 /system/nav,独立模板目录
dir_path = os.path.dirname(os.path.abspath(__file__))
bp = Blueprint(
'nav', __name__,
url_prefix='/system/nav',
template_folder=os.path.join(dir_path, '..', 'templates'),
)
@bp.get('/')
@authorize("system:nav:main")
def main():
return render_template('system/nav/main.html')
@bp.get('/data')
@limiter.limit("60 per minute")
@authorize("system:nav:main")
def data():
keyword = str_escape(request.args.get('keyword', type=str))
category = str_escape(request.args.get('category', type=str))
query = Nav.query.filter()
if keyword:
query = query.filter(
db.or_(
Nav.title.contains(keyword),
Nav.url.contains(keyword),
Nav.description.contains(keyword),
)
)
if category:
query = query.filter(Nav.category == category)
items = query.order_by(Nav.category.asc(), Nav.sort.asc(), Nav.id.asc()).layui_paginate()
return table_api(
msg="请求成功",
data=curd.model_to_dicts(schema=NavManageSchema, data=items.items),
count=items.total,
)
@bp.get('/add')
@authorize("system:nav:add", log=True)
def add():
return render_template('system/nav/add.html')
@bp.post('/save')
@authorize("system:nav:add", log=True)
def save():
req_json = request.get_json(force=True, silent=True) or {}
category = str_escape(req_json.get('category'))
title = str_escape(req_json.get('title'))
url = str_escape(req_json.get('url'))
icon = str_escape(req_json.get('icon')) or 'layui-icon-link'
if not all([category, title, url]):
return fail_api(msg="分类/标题/链接不得为空")
try:
nav = Nav(
category=category,
title=title,
url=url,
description=str_escape(req_json.get('description')),
icon=icon,
sort=int(req_json.get('sort') or 0),
enable=int(req_json.get('enable') or 1),
is_external=int(req_json.get('isExternal') or 1),
create_by=current_user.username if current_user.is_authenticated else 'admin',
)
db.session.add(nav)
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:edit", log=True)
def edit():
nav_id = request.args.get('navId', type=int)
nav = curd.get_one_by_id(Nav, nav_id)
if not nav:
return fail_api(msg="导航不存在")
return render_template('system/nav/edit.html', nav=nav)
@bp.put('/update')
@authorize("system:nav:edit", log=True)
def update():
req_json = request.get_json(force=True, silent=True) or {}
nav_id = req_json.get('navId')
nav = curd.get_one_by_id(Nav, nav_id)
if not nav:
return fail_api(msg="导航不存在")
category = str_escape(req_json.get('category'))
title = str_escape(req_json.get('title'))
url = str_escape(req_json.get('url'))
if not all([category, title, url]):
return fail_api(msg="分类/标题/链接不得为空")
try:
nav.category = category
nav.title = title
nav.url = url
nav.description = str_escape(req_json.get('description'))
nav.icon = str_escape(req_json.get('icon')) or 'layui-icon-link'
nav.sort = int(req_json.get('sort') or 0)
nav.enable = int(req_json.get('enable') or 1)
nav.is_external = int(req_json.get('isExternal') or 1)
db.session.commit()
except Exception as e:
db.session.rollback()
return fail_api(msg=f"更新失败:{e}")
return success_api(msg="更新成功")
@bp.delete('/remove/<int:nav_id>')
@authorize("system:nav:remove", log=True)
def remove(nav_id):
res = curd.delete_one_by_id(Nav, nav_id)
if not res:
return fail_api(msg="删除失败")
return success_api(msg="删除成功")
@bp.put('/enable')
@authorize("system:nav:edit", log=True)
def enable():
req_json = request.get_json(force=True, silent=True) or {}
nav_id = req_json.get('navId')
if not nav_id:
return fail_api(msg="数据错误")
res = curd.enable_status(Nav, nav_id)
if not res:
return fail_api(msg="操作失败")
return success_api(msg="已启用")
@bp.put('/disable')
@authorize("system:nav:edit", log=True)
def dis_enable():
req_json = request.get_json(force=True, silent=True) or {}
nav_id = req_json.get('navId')
if not nav_id:
return fail_api(msg="数据错误")
res = curd.disable_status(Nav, nav_id)
if not res:
return fail_api(msg="操作失败")
return success_api(msg="已禁用")