Grails 3.0.17项目域类字段在localhost:8080页面不显示求助
Grails 3.0.17中CRUD页面不显示name字段的问题
我是Grails新手,使用Grails 3.0.17版本创建了名为A的测试项目,域类B定义了String类型的name字段,自动生成了对应的控制器和视图,但运行localhost:8080后,create、index等页面都无法显示name字段,尝试动态脚手架也无效,求解决方法实现基础CRUD操作。
域类B代码
class B { String name static constraints = { } }
BController代码
package a import static org.springframework.http.HttpStatus.* import grails.transaction.Transactional @Transactional(readOnly = true) class BController { static allowedMethods = [save: "POST", update: "PUT", delete: "DELETE"] def index(Integer max) { params.max = Math.min(max ?: 10, 100) respond B.list(params), model:[bCount: B.count()] } def show(B b) { respond b } def create() { respond new B(params) } @Transactional def save(B b) { if (b == null) { transactionStatus.setRollbackOnly() notFound() return } if (b.hasErrors()) { transactionStatus.setRollbackOnly() respond b.errors, view:'create' return } b.save flush:true request.withFormat { form multipartForm { flash.message = message(code: 'default.created.message', args: [message(code: 'b.label', default: 'B'), b.id]) redirect b } '*' { respond b, [status: CREATED] } } } def edit(B b) { respond b } @Transactional def update(B b) { if (b == null) { transactionStatus.setRollbackOnly() notFound() return } if (b.hasErrors()) { transactionStatus.setRollbackOnly() respond b.errors, view:'edit' return } b.save flush:true request.withFormat { form multipartForm { flash.message = message(code: 'default.updated.message', args: [message(code: 'b.label', default: 'B'), b.id]) redirect b } '*'{ respond b, [status: OK] } } } @Transactional def delete(B b) { if (b == null) { transactionStatus.setRollbackOnly() notFound() return } b.delete flush:true request.withFormat { form multipartForm { flash.message = message(code: 'default.deleted.message', args: [message(code: 'b.label', default: 'B'), b.id]) redirect action:"index", method:"GET" } '*'{ render status: NO_CONTENT } } } protected void notFound() { request.withFormat { form multipartForm { flash.message = message(code: 'default.not.found.message', args: [message(code: 'b.label', default: 'B'), params.id]) redirect action: "index", method: "GET" } '*'{ render status: NOT_FOUND } } } }
Create.gsp代码
<!DOCTYPE html> <html> <head> <meta name="layout" content="main" /> <g:set var="entityName" value="${message(code: 'b.label', default: 'B')}" /> <title><g:message code="default.create.label" args="[entityName]" /></title> </head> <body> <a href="#create-b" class="skip" tabindex="-1"><g:message code="default.link.skip.label" default="Skip to content…"/></a> <div class="nav" role="navigation"> <ul> <li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li> <li><g:link class="list" action="index"><g:message code="default.list.label" args="[entityName]" /></g:link></li> </ul> </div> <div id="create-b" class="content scaffold-create" role="main"> <h1><g:message code="default.create.label" args="[entityName]" /></h1> <g:if test="${flash.message}"> <div class="message" role="status">${flash.message}</div> </g:if> <g:hasErrors bean="${this.b}"> <ul class="errors" role="alert"> <g:eachError bean="${this.b}" var="error"> <li <g:if test="${error in org.springframework.validation.FieldError}">data-field-id="${error.field}"</g:if>><g:message error="${error}"/></li> </g:eachError> </ul> </g:hasErrors> <g:form action="save"> <fieldset class="form"> <f:all bean="b"/> </fieldset> <fieldset class="buttons"> <g:submitButton name="create" class="save" value="${message(code: 'default.button.create.label', default: 'Create')}" /> </fieldset> </g:form> </div> </body> </html>
Edit.gsp代码
<!DOCTYPE html> <html> <head> <meta name="layout" content="main" /> <g:set var="entityName" value="${message(code: 'b.label', default: 'B')}" /> <title><g:message code="default.edit.label" args="[entityName]" /></title> </head> <body> <a href="#edit-b" class="skip" tabindex="-1"><g:message code="default.link.skip.label" default="Skip to content…"/></a> <div class="nav" role="navigation"> <ul> <li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li> <li><g:link class="list" action="index"><g:message code="default.list.label" args="[entityName]" /></g:link></li> <li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li> </ul> </div> <div id="edit-b" class="content scaffold-edit" role="main"> <h1><g:message code="default.edit.label" args="[entityName]" /></h1> <g:if test="${flash.message}"> <div class="message" role="status">${flash.message}</div> </g:if> <g:hasErrors bean="${this.b}"> <ul class="errors" role="alert"> <g:eachError bean="${this.b}" var="error"> <li <g:if test="${error in org.springframework.validation.FieldError}">data-field-id="${error.field}"</g:if>><g:message error="${error}"/></li> </g:eachError> </ul> </g:hasErrors> <g:form resource="${this.b}" method="PUT"> <g:hiddenField name="version" value="${this.b?.version}" /> <fieldset class="form"> <f:all bean="b"/> </fieldset> <fieldset class="buttons"> <input class="save" type="submit" value="${message(code: 'default.button.update.label', default: 'Update')}" /> </fieldset> </g:form> </div> </body> </html>
Index.gsp代码
<!DOCTYPE html> <html> <head> <meta name="layout" content="main" /> <g:set var="entityName" value="${message(code: 'b.label', default: 'B')}" /> <title><g:message code="default.list.label" args="[entityName]" /></title> </head> <body> <a href="#list-b" class="skip" tabindex="-1"><g:message code="default.link.skip.label" default="Skip to content…"/></a> <div class="nav" role="navigation"> <ul> <li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li> <li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li> </ul> </div> <div id="list-b" class="content scaffold-list" role="main"> <h1><g:message code="default.list.label" args="[entityName]" /></h1> <g:if test="${flash.message}"> <div class="message" role="status">${flash.message}</div> </g:if> <f:table collection="${bList}" /> <div class="pagination"> <g:paginate total="${bCount ?: 0}" /> </div> </div> </body> </html>
Show.gsp代码
<!DOCTYPE html> <html> <head> <meta name="layout" content="main" /> <g:set var="entityName" value="${message(code: 'b.label', default: 'B')}" /> <title><g:message code="default.show.label" args="[entityName]" /></title> </head> <body> <a href="#show-b" class="skip" tabindex="-1"><g:message code="default.link.skip.label" default="Skip to content…"/></a> <div class="nav" role="navigation"> <ul> <li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li> <li><g:link class="list" action="index"><g:message code="default.list.label" args="[entityName]" /></g:link></li> <li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li> </ul> </div> <div id="show-b" class="content scaffold-show" role="main"> <h1><g:message code="default.show.label" args="[entityName]" /></h1> <g:if test="${flash.message}"> <div class="message" role="status">${flash.message}</div> </g:if> <f:display bean="b" /> <g:form resource="${this.b}" method="DELETE"> <fieldset class="buttons"> <g:link class="edit" action="edit" resource="${this.b}"><g:message code="default.button.edit.label" default="Edit" /></g:link> <input class="delete" type="submit" value="${message(code: 'default.button.delete.label', default: 'Delete')}" onclick="return confirm('${message(code: 'default.button.delete.confirm.message', default: 'Are you sure?')}')" /> </fieldset> </g:form> </div> </body> </html>
问题解决方法
核心原因
你使用的<f:all>、<f:table>、<f:display>这类Grails表单脚手架标签,依赖域类的约束配置来识别并渲染字段。如果域类字段没有任何约束(比如你的name字段),标签会默认忽略该字段,导致页面不显示。
具体修复方案
方案1:给域类字段添加约束
修改域类B的constraints块,给name字段添加至少一个基础约束,让脚手架标签能识别到该字段:
class B { String name static constraints = { name nullable: false // 也可设置为blank: false,根据业务需求调整 } }
修改后重启项目,页面即可自动渲染name字段。
方案2:手动编写表单/表格元素
如果不想依赖自动脚手架标签,可直接在GSP中手动编写字段的UI元素:
- Create.gsp/Edit.gsp:替换
<f:all bean="b"/>为:
<div class="fieldcontain required"> <label for="name"> 名称 <span class="required-indicator">*</span> </label> <g:textField name="name" value="${b.name}"/> </div>
- Index.gsp:替换
<f:table collection="${bList}"/>为:
<table> <thead> <tr> <th>名称</th> <th>操作</th> </tr> </thead> <tbody> <g:each in="${bList}" var="b"> <tr> <td>${b.name}</td> <td> <g:link action="show" resource="${b}">查看</g:link> <g:link action="edit" resource="${b}">编辑</g:link> </td> </tr> </g:each> </tbody> </table>
- Show.gsp:替换
<f:display bean="b"/>为:
<div class="fieldcontain"> <label for="name">名称</label> <span>${b.name}</span> </div>
方案3:重新生成脚手架
如果自动生成的脚手架存在异常,可先删除现有BController和grails-app/views/b目录下的所有文件,然后运行命令重新生成:
grails generate-all a.B
运行前确保域类已经保存,项目处于正常编译状态。
内容的提问来源于stack exchange,提问作者Karthik KK
相关产品推荐
相关产品推荐

