ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Django 4.2与Layui 2.9.4深度集成实战指南

Django 4.2与Layui 2.9.4深度集成实战指南 简介本资源是一套基于Layui 2.9.4与Django 4.2深度集成的全栈项目前端设计源码面向Python Web开发初学者及中小型后台系统开发者解决Django项目中UI统一、组件化开发效率低、响应式适配难等常见痛点。压缩包共347个文件总大小26.19MB涵盖115个Python后端逻辑文件含视图、模型、表单、17个HTML模板页如LoginPage.html、fileExchange.html、imageSharing.html等、22个图像资源JPG/JPEG/PNG以及Layui核心CSS/JS、字体文件ttf/eot/woff2和SQLite数据库文件db.sqlite3结构清晰体现MTV分层思想。已有424人学习下载资源完整包含可直接运行的前后端协同示例提供标准化布局框架、常用业务页面用户登录、文件交换、图片共享、密码更新等、模块化组件调用范式及配套readme与依赖说明助开发者快速复用界面结构、理解Django模板渲染与Layui交互逻辑降低企业级管理后台前端搭建门槛。1. 这不是“套个UI”的前端而是Django 4.2与Layui 2.9.4深度耦合的生产级界面工程你打开一个Django项目manage.py runserver启动后看到的登录页——如果它用的是Layui 2.9.4的栅格、表单验证、弹层和图标字体且所有HTML模板里没有一行Vue或React语法但又能动态渲染用户组、实时更新密码策略、按权限加载文件交换模块——那它大概率就是这类项目。这不是把Layui当静态资源简单引入而是将Layui的模块化加载机制layui.use()、数据表格table.render()、表单绑定form.on(submit(...))与Django的模板继承{% extends %}、上下文传参context {user_groups: groups}、CSRF保护{% csrf_token %}做了显式对齐。它面向的是需要快速交付、强后台管控、低学习门槛但拒绝“裸HTMLjQuery拼凑”的中型内部系统场景比如企业文档协作平台、部门级资产登记系统、运维工单看板。69个Python文件说明后端逻辑已覆盖权限校验、文件元数据管理、IP白名单控制等真实业务断点13个HTML文件则全部基于base.html统一骨架每个页面都明确声明了lay-modulexxx并预留了Django模板变量插槽。新手能照着LoginPage.html改出新页面老手则会立刻检查static/js/下是否封装了django_ajax.js来统一处理403/401响应重定向——这才是它区别于“Layui教学Demo”的关键分水岭。2. Layui 2.9.4与Django 4.2的模板层对齐从静态资源加载到动态数据注入2.1 静态资源路径的双重声明机制Django STATICFILES_DIRS 与 Layui base path 的协同Layui 2.9.4默认通过layui.config({base: /static/layui/modules/})指定模块路径而Django 4.2要求静态资源必须经由STATIC_URL和STATICFILES_DIRS管理。若直接在HTML中写script src/static/layui/layui.js/script会导致Layui内部模块如laydate,upload因相对路径解析失败而报错Cannot find module laydate。正确做法是在Django的settings.py中显式声明Layui模块路径# settings.py import os from pathlib import Path BASE_DIR Path(__file__).resolve().parent.parent.parent STATIC_URL /static/ STATICFILES_DIRS [ BASE_DIR / static, # 项目根目录下的static文件夹 ] # 关键为Layui模块单独配置别名路径 STATICFILES_STORAGE django.contrib.staticfiles.storage.StaticFilesStorage然后在base.html的head中注入Layui配置!-- base.html -- head meta charsetutf-8 title{% block title %}系统后台{% endblock %}/title !-- Layui CSS -- link relstylesheet href{% static layui/css/layui.css %} !-- 动态注入Layui base路径确保模块加载不依赖硬编码 -- script layui.config({ base: {% static layui/modules/ %}, // Django模板变量动态生成 version: 2.9.4 // 强制版本号避免CDN缓存干扰 }); /script /head提示{% static %}模板标签必须在base.html中提前加载否则static关键字无法识别。需确认TEMPLATES配置中已启用django.template.context_processors.static。2.2 模板继承结构与Layui布局模块的物理映射该项目13个HTML文件全部继承自base.html其结构严格对应Layui的layout模块层级。base.html定义了标准三栏布局!-- base.html -- body classlayui-layout-body !-- 头部 -- div classlayui-header div classlayui-logo系统名称/div ul classlayui-nav layui-layout-left {% for item in nav_items %} li classlayui-nav-item a href{{ item.url }}{{ item.name }}/a /li {% endfor %} /ul /div !-- 左侧菜单 -- div classlayui-side layui-bg-black div classlayui-side-scroll ul classlayui-nav layui-nav-tree lay-filtersideNav {% for menu in side_menu %} li classlayui-nav-item a href{{ menu.url }}i classlayui-icon{{ menu.icon }}/i{{ menu.name }}/a {% if menu.children %} dl classlayui-nav-child {% for child in menu.children %} dda href{{ child.url }}{{ child.name }}/a/dd {% endfor %} /dl {% endif %} /li {% endfor %} /ul /div /div !-- 主体内容区 -- div classlayui-body {% block content %}{% endblock %} /div !-- 底部 -- div classlayui-footer© 2024 系统版权/div /bodyprojectGrouping.html等子页面只需填充content区块并调用Layui组件!-- projectGrouping.html -- {% extends base.html %} {% block content %} div classlayui-card div classlayui-card-header项目分组管理/div div classlayui-card-body table idgroupTable lay-filtergroupTable/table /div /div script layui.use([table, form], function(){ var table layui.table; var form layui.form; // 表格渲染数据源来自Django视图的JSON接口 table.render({ elem: #groupTable, url: {% url api:group_list %}, // Django命名URL反向解析 page: true, cols: [[ {field:id, title:ID, width:80}, {field:name, title:分组名称, edit: text}, {field:description, title:描述, width:200}, {fixed: right, title:操作, toolbar: #groupBar, width:150} ]] }); // 监听工具条事件 table.on(tool(groupTable), function(obj){ var data obj.data; if(obj.event del){ layer.confirm(确定删除, function(index){ $.post({% url api:group_delete %}, {id: data.id}, function(res){ if(res.code 0){ obj.del(); layer.close(index); } }); }); } }); }); /script !-- 工具条模板 -- script typetext/html idgroupBar a classlayui-btn layui-btn-xs lay-eventedit编辑/a a classlayui-btn layui-btn-danger layui-btn-xs lay-eventdel删除/a /script {% endblock %}注意url参数必须使用Django的{% url %}模板标签而非硬编码路径确保路由变更时前端无需修改。table.render()的url指向Django REST API接口而非直接读取{{ groups }}上下文变量——这是前后端分离的轻量级实践避免模板层过度复杂化。2.3 CSRF Token的Layui AJAX统一拦截方案Django 4.2默认启用CSRF保护所有POST请求必须携带X-CSRFToken头。若在Layui的$.post()中手动添加每个请求都要重复写// ❌ 错误每个$.post都需手动加header $.post(/api/group/delete/, {id:1}, function(res){...}) .setRequestHeader(X-CSRFToken, {{ csrf_token }}); // 模板变量无法在JS中直接使用正确做法是利用Layui的layui.jquery即jQuery全局AJAX设置在base.html底部注入!-- base.html 底部 -- script // 全局AJAX设置自动注入CSRF Token $.ajaxSetup({ beforeSend: function(xhr, settings) { if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) !this.crossDomain) { xhr.setRequestHeader(X-CSRFToken, {{ csrf_token }}); } } }); /script但此方案存在隐患{{ csrf_token }}在页面渲染时是字符串若用户长时间未操作导致CSRF Token过期后续请求将失败。更健壮的做法是创建django_ajax.js模块// static/js/django_ajax.js layui.define([jquery], function(exports){ var $ layui.jquery; var djangoAjax { post: function(url, data, success, error) { $.ajax({ url: url, type: POST, data: data, headers: { X-CSRFToken: getCookie(csrftoken) // 从cookie读取实时Token }, success: success, error: function(xhr, status, err) { if (xhr.status 403) { layer.msg(登录已过期请重新登录, {icon: 2}); window.location.href /login/; } else if (error) { error(xhr, status, err); } } }); } }; // 从cookie读取CSRF Token的辅助函数 function getCookie(name) { var cookieArr document.cookie.split(;); for(var i 0; i cookieArr.length; i) { var cookiePair cookieArr[i].split(); if(name cookiePair[0].trim()) { return decodeURIComponent(cookiePair[1]); } } return null; } exports(django_ajax, djangoAjax); });在子页面中调用script layui.use([django_ajax], function(){ var ajax layui.django_ajax; ajax.post({% url api:group_delete %}, {id: data.id}, function(res){ if(res.code 0){ obj.del(); } }); }); /script3. 后端视图与Layui组件的数据契约从Django QuerySet到Layui Table JSON Schema3.1 Django REST API接口设计遵循Layui Table的data字段规范Layui的table.render()要求接口返回JSON格式必须包含code、msg、count、data四个字段其中data为数组。Django视图需严格适配此结构而非直接返回QuerySet# views.py from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_http_methods from django.core.paginator import Paginator from .models import ProjectGroup require_http_methods([GET]) def group_list_api(request): Layui Table数据接口返回分页的项目分组列表 契约{code:0, msg:, count:总记录数, data:[{id:1,name:A组},...]} page int(request.GET.get(page, 1)) limit int(request.GET.get(limit, 10)) # 查询所有分组实际项目中应加权限过滤 queryset ProjectGroup.objects.all().values(id, name, description) paginator Paginator(queryset, limit) page_obj paginator.get_page(page) # 构造Layui所需JSON结构 data list(page_obj) # 转为list否则QuerySet不可JSON序列化 return JsonResponse({ code: 0, msg: , count: paginator.count, data: data })对应URL配置# urls.py from django.urls import path from . import views urlpatterns [ path(api/group/list/, views.group_list_api, namegroup_list), path(api/group/delete/, views.group_delete_api, namegroup_delete), ]提示values()方法确保只返回字典列表避免模型实例无法JSON序列化。若需关联字段如owner__usernamevalues()同样支持链式调用。3.2 表单提交的双向验证Django Form与Layui Form模块的联动Layui的form.on(submit(...))仅做前端校验Django后端必须做二次校验。以personaIPasswordUpdate.html为例其表单结构为form classlayui-form action{% url user:password_update %} methodpost {% csrf_token %} div classlayui-form-item label classlayui-form-label原密码/label div classlayui-input-block input typepassword nameold_password required lay-verifyrequired placeholder请输入原密码 classlayui-input /div /div div classlayui-form-item label classlayui-form-label新密码/label div classlayui-input-block input typepassword namenew_password1 required lay-verifyrequired|password placeholder请输入新密码 classlayui-input /div /div div classlayui-form-item label classlayui-form-label确认新密码/label div classlayui-input-block input typepassword namenew_password2 required lay-verifyrequired|confirm[new_password1] placeholder请再次输入新密码 classlayui-input /div /div div classlayui-form-item div classlayui-input-block button classlayui-btn lay-submit lay-filterpasswordUpdate立即提交/button /div /div /formDjango视图需接收并校验# views.py from django.contrib.auth import authenticate, update_session_auth_hash from django.contrib.auth.forms import PasswordChangeForm from django.contrib import messages from django.shortcuts import render, redirect def password_update_view(request): if request.method POST: form PasswordChangeForm(userrequest.user, datarequest.POST) if form.is_valid(): user form.save() update_session_auth_hash(request, user) # 保持登录状态 messages.success(request, 密码修改成功) return redirect(user:profile) else: # 将Django表单错误注入Layui弹层 errors [] for field, errs in form.errors.items(): for err in errs: errors.append(f{field}: {err}) return render(request, personaIPasswordUpdate.html, { form_errors: errors }) else: form PasswordChangeForm(userrequest.user) return render(request, personaIPasswordUpdate.html, {form: form})前端JavaScript监听提交并处理错误// personaIPasswordUpdate.html script layui.use([form, layer], function(){ var form layui.form; var layer layui.layer; form.on(submit(passwordUpdate), function(data){ // 禁用按钮防止重复提交 $(this).addClass(layui-btn-disabled).attr(disabled, true); $.post(data.form.action, data.field, function(res){ if(res.code 0 || res.redirect_url) { layer.msg(修改成功, {icon: 1}); setTimeout(function(){ window.location.href res.redirect_url || /profile/; }, 1000); } else { layer.msg(res.msg || 操作失败, {icon: 2}); } }).fail(function(){ layer.msg(网络错误请重试, {icon: 2}); }).always(function(){ // 恢复按钮状态 $(button[lay-filterpasswordUpdate]).removeClass(layui-btn-disabled).attr(disabled, false); }); return false; // 阻止表单默认提交 }); }); /script3.3 权限控制在模板层的显式表达Layui元素的动态显隐Django的user.has_perm()或user.is_staff不能直接在Layui的lay-filter中使用需在视图中预计算并传入模板# views.py def file_exchange_view(request): context { can_upload: request.user.has_perm(app.upload_file), can_download: request.user.has_perm(app.download_file), can_manage: request.user.is_staff or request.user.groups.filter(nameAdmin).exists() } return render(request, fileExchange.html, context)在fileExchange.html中控制按钮显隐!-- fileExchange.html -- div classlayui-btn-container {% if can_upload %} button classlayui-btn layui-btn-normal iduploadBtn上传文件/button {% endif %} {% if can_download %} button classlayui-btn layui-btn-warm iddownloadBtn批量下载/button {% endif %} {% if can_manage %} button classlayui-btn layui-btn-danger idmanageBtn管理设置/button {% endif %} /div script // 根据权限初始化Layui按钮 layui.use([element], function(){ var element layui.element; // 若无权限移除对应按钮的click事件绑定 {% if not can_upload %}$(#uploadBtn).remove();{% endif%} {% if not can_download %}$(#downloadBtn).remove();{% endif%} {% if not can_manage %}$(#manageBtn).remove();{% endif%} }); /script4. 生产环境部署关键配置Nginx静态资源代理与Layui模块路径修正4.1 Nginx配置中的Layui静态资源路径重写Django开发时STATIC_URL/static/可直接访问但生产环境Nginx需将/static/layui/路径代理到磁盘真实位置。若Nginx配置遗漏alias指令会导致layui.js加载404# nginx.conf server { listen 80; server_name example.com; location /static/ { alias /var/www/myproject/static/; # 必须用alias非root expires 1y; add_header Cache-Control public, immutable; } # 关键Layui模块路径需精确匹配 location /static/layui/modules/ { alias /var/www/myproject/static/layui/modules/; expires 1y; } location / { include proxy_params; proxy_pass http://127.0.0.1:8000; } }注意location /static/必须用alias而非root否则/static/layui/css/layui.css会被解析为/var/www/myproject/static//static/layui/css/layui.css多一层static。4.2 Django 4.2的SECURE_CONTENT_TYPE_NOSNIFF与Layui字体文件兼容性Layui 2.9.4的iconfont.eot等字体文件在Django 4.2中可能被SECURE_CONTENT_TYPE_NOSNIFFTrue默认开启拦截浏览器报错Refused to apply style from ...iconfont.eot because its MIME type (application/vnd.ms-fontobject) is not a supported stylesheet MIME type。解决方案是在settings.py中为字体文件类型添加白名单# settings.py import mimetypes # 修复Layui字体MIME类型 mimetypes.add_type(application/vnd.ms-fontobject, .eot) mimetypes.add_type(application/x-font-opentype, .otf) mimetypes.add_type(application/x-font-truetype, .ttf) mimetypes.add_type(application/x-font-woff, .woff) mimetypes.add_type(application/font-woff2, .woff2) mimetypes.add_type(image/svgxml, .svg) # 确保Django正确识别 SECURE_CONTENT_TYPE_NOSNIFF True4.3 Layui日期控件的最大日期动态设为当前日Django模板与JS的协同Layui的laydate.render()支持max参数设最大可选日期但若写死为2024-12-31则不灵活。需在模板中动态注入今日日期!-- nodeInformation.html -- script layui.use([laydate], function(){ var laydate layui.laydate; // 从Django模板获取今日日期ISO格式 var today {{ today|date:Y-m-d }}; // 在视图中传入context{today: timezone.now()} laydate.render({ elem: #datePicker, format: yyyy-MM-dd, max: today, // 最大日期为今日 trigger: click, done: function(value, date, endDate){ console.log(选择日期 value); } }); }); /script对应视图# views.py from django.utils import timezone def node_information_view(request): return render(request, nodeInformation.html, { today: timezone.now() # 传入datetime对象模板中用date过滤器格式化 })5. 排查Layui组件失效的三大高频现场从控制台报错到Django日志追踪5.1 “Cannot read property use of undefined” —— Layui未正确加载的链式诊断此错误表明layui全局对象未定义常见于静态资源404检查浏览器开发者工具Network标签过滤layui.js确认状态码是否为200。若为404检查Nginxalias路径是否多了一层/static/。加载顺序错误layui.js必须在所有layui.use()调用之前加载。检查HTML中script src{% static layui/layui.js %}是否位于body底部且在任何layui.use()之前。Django DEBUGFalse时STATICFILES_STORAGE配置错误若使用ManifestStaticFilesStorage需运行python manage.py collectstatic并确认staticfiles.json中layui/layui.js的哈希值已生成。5.2 表格数据为空但接口返回正常 —— Layui Table的data字段解析陷阱当table.render()的url接口返回{code:0,msg:,count:10,data:[]}但表格显示“暂无数据”问题往往在JSON响应头缺失Django视图必须返回Content-Type: application/json。JsonResponse默认设置但若用HttpResponse(json.dumps(...), content_typeapplication/json)需手动指定。data字段为None而非空数组JsonResponse({data: None})会导致Layui解析失败。必须确保data为list类型空时传[]。跨域问题若前端域名与Django API域名不同如http://localhost:3000调用http://localhost:8000需在Django中安装django-cors-headers并配置CORS_ALLOWED_ORIGINS。5.3 表单提交后页面跳转而非AJAX响应 —— Layui submit事件未被正确拦截form.on(submit(...))未生效的典型表现是点击按钮后整个页面刷新。排查步骤确认form标签有lay-filter属性form classlayui-form lay-filterpasswordUpdate且lay-filter值与form.on()中一致。检查JavaScript执行时机layui.use()必须在DOM加载完成后执行。若脚本放在head中需加defer属性或包裹在$(document).ready()中。Django模板中CSRF Token未渲染{% csrf_token %}必须在form内否则Django后端拒绝POST请求返回403并重定向到登录页造成“页面跳转”假象。提示在form.on()回调函数末尾必须return false;否则浏览器执行默认表单提交。这是Layui文档明确要求的但极易被忽略。本文还有配套的精品资源点击获取
返回列表