You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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&hellip;"/></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&hellip;"/></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&hellip;"/></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&hellip;"/></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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 21:25:53