安装

提前配置

安装以下:

  • Java (JDK-17或JDK-19)

  • git

可选安装以下:

  • Solr-9.6.x (用于内联网中的搜索栏)

  • Postgresql

Java版本

使用以下命令查看版本号:

java -fullversion
openjdk full version "17.0.5+1"

Mac系统可使用brew命令安装Jdk-17:

brew install openjdk@17

克隆内联网模板项目

git clone https://github.com/Taack/intranet.git

该模板项目为精简版的内联网,您可以在其基础上构建更多应用。目前已包含人员管理系统“Crew”应用以及其安全依赖项:Spring Security Plugin

运行您的内联网

进入克隆完成的内联网项目中,以下述命令运行其服务器:

./gradlew server:bootRun

如果您希望Gradle能实时监测文件改动以热重启服务器,请在运行服务器时添加 -Dgrails.run.active=true 选项:

./gradlew -Dgrails.run.active=true server:bootRun

短暂时间后,您应当能在控制台中看见:

Grails application running at http://localhost:9442

此时您就可以通过浏览器访问该网址以进入内联网,并用默认账户登陆 (用户名:admin;密码:ChangeIt)。密码可在 server/grails-app/conf/application.yml 中更改。

配置持久化数据库

如果您希望长久保存数据,请至 server/grails-app/conf/application.yml 文件中更改 dataSource 项。

例如处于开发者模式时我们希望使用H2存储数据:

environments:
    development:
        dataSource:
            dbCreate: update        (1)
            url: jdbc:h2:./prodDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE (2)
1 模式:创建 (具体请见Grails GORM文档)
2 prodDb: 用于存储数据的文件的根目录

为Tomcat生成Jar文件

在这一步中,取代 bootRun 使用的是 assemble

./gradlew server:assemble

Jar文件将生成至 server/build/libs 文件夹。利用下述命令将其导入至您的Tomcat中:

cd server/build/libs
java -jar server-[version].jar

该次执行会更快, 并且静态文件将被压缩与串联。

请确保此时您尚未运行服务器

配置您的IDE

我们强烈建议您使用最新版IntelliJ Ultimate Edition,基于它对GroovyGrails的全面支持。此外,我们也在探索更多可供选择的IDE,例如EclipseVisual Studio Code,又例如支持非Ultimate Edition版的IntelliJ。

IntelliJ Ultimate Edition

我们建议一并安装Taack插件,可从Intellij市场TaackUiASTAutocomplete或直接从源码库安装。

如何打开项目: File  Open project ,然后选择 intranet/settings.gradle

请确保Gradle使用的JDK版本为17 (或19):

打开 Gradle Settings

open settings gradle intellij

检查 Gradle Jdk 版本

settings gradle intellij

Visual Studio Code

如果您强烈希望使用Visual Studio Code,我们建议以下扩展包以此使得Taack框架正常工作:

请谨记,即便VSCode无法识别Grails和Taack框架的大部分功能,但其仍然可正常使用。此外,VSCode也不会帮助您进行导入、代码导航等诸多便捷功能,因为其对Groovy的支持始终不如Intellij全面。

创建第一个应用 (app)

应用是如何与Taack框架协作的

您的内联网项目分为2个部分:

  • 子应用 (app plugins):位于 app 文件夹中。每个子应用都能看成是一个微型项目,它们有自己的依赖、自己的build文件等等,例如 Crew 就是其中一个子应用。 点击此处以获取更多如何向服务器声明子应用的深入解释。

  • 主应用 (main server):通过 build.gradle 文件装载所有子应用,并将其呈现在内联网主页面上。

创建一个新的子应用 (app plugin)

要创建新的子应用 (基于Gradle模块),请先确保已经从我们的github库中拉取了最新版的内联网项目 (见安装)。

若已完成,您可于内联网根目录中执行以下命令:

./gradlew -PmodName=myToto server:generateTaackAppTask

一个新创建的子应用将出现在 app 文件夹下。

请重新加载Gradle (如下图所示点击目标按钮),使得IDE成功检测到该子应用:

Reload
Figure 1. Gradle Reload

未来的更新

大多数时候,更新内联网也就意味着会更新 buildSrc 文件夹以及 gradle.properties 文件,偶尔也会更新子应用的 build.gradle 文件。

事实上,这在长周期的软件开发项目中很常见,不应当让新手感到害怕。

创建子应用的菜单

首先,让我们着眼于导航栏。 第一步学习如何构建,基本代码如下:

static private UiMenuSpecifier buildMenu(String q = null) {
    new UiMenuSpecifier().ui {
        menu SomeController.&action as MC
        menu this.&anotherAction as MC
        // import org.codehaus.groovy.runtime.MethodClosure as MC
    }
}

创建一个UiMenuSpecifier实例,并写入导航栏所需内容:

menu TurboController.&action as MC

以该行代码为例,menu 将在导航栏中创建一个简单链接,Action 为该链接显示的文本,TurboController.&action (类型为MethodClosure) 则定义了该链接导向的目的地。

导航栏中可写入更多其他类型,详情请见Menu DSL

创建表格

现在再来看看如何在我们的页面中呈现一张表格,样板如下:

UiTableSpecifier tableSpecifier = new UiTableSpecifier()
tableSpecifier.ui {
    //Add table content inside the closure here
}

我们打算做一张表格,其内列出多个Book实例。 于是第一步应当先定义好表头:

new UiTableSpecifier().ui {
    // -- Header --
    header {
        column {
            fieldHeader object.title_
        }
    }
}

如图所示,表格的各项内容全都定义在一个闭包中,因此条件判断、循环等等的操作都可以实现。举个例子,我们想要某列只有 ROLE_ADMIN 用户才能看见:

// Look if the current user has the Role "ROLE_ADMIN"
User cu = springSecurityService.currentUser as User
boolean isAdmin = cu.authorities*.authority.contains("ROLE_ADMIN")

new UiTableSpecifier().ui {
    header {
        column {
            label object.title_
        }
        // Column only shown to admin
        if (isAdmin) {
            column {
                label "Delete book"
            }
        }
    }
}

“Delete book”这列只会在用户是admin时出现。

接下来我们要填充内容进我们的表格,使用 iterate 遍历数据库中所有的Book实例:

User cu = springSecurityService.currentUser as User
boolean isAdmin = cu.authorities*.authority.contains("ROLE_ADMIN")

new UiTableSpecifier().ui {
    header {
        column {
            label object.title_
        }
        // Column only shown to admin
        if (isAdmin) {
            column {
                label "Delete book"
            }
        }
    }

    iterate(taackFilterService.getBuilder(Book)
            .setMaxNumberOfLine(8)
            .setSortOrder(TaackFilter.Order.DESC, object.title_)
            .build()) { Book book ->
        rowColumn {
            rowField book.title_ //The underscore is needed here
        }
        // If the user is an admin display a column with a button link
        // to redirect towards the book deletion action
        if (isAdmin) {
            rowColumn {
                rowAction ActionIcon.DELETE,
                        this.&index as MC, book.id
            }
        }
    }
}

我们会为每一个Book创建一条新行,第一列为书名,若用户是admin则第二列为 删除 按钮 (由于我们还未定义删除函数,因此我们暂时使该按钮导向至 index )。

表格,即UiTableSpecifier,现已完成,接下来只需将其呈现在页面,请使用 taackUiService (它应当已被 create-taack-app 命令导入至Controller控制器中)。 代码如下:

taackUiService.show(new UiBlockSpecifier().ui {
    table tableSpecifier, BlockSpec.Width.MAX
}, buildMenu())

taackUiService.show(UiBlockSpecifier block, UiMenuSpecifier menu) 负责将我们指定的内容呈现出来。 在本例中,我们想要显示一个 ajaxBlock, 其内包含着名为“Book”的 table,因此我们将创建好的 tableSpecifier 作为传递参数,并设定表格宽度为 MAX 以占据整个页宽。我们还将之前完成的静态 buildMenu() 作为 show() 的第二个传递参数,以此使得该页面自带一条导航栏。

现在您可以启动服务器并进入新子应用中。表格应当已正常呈现,只不过暂无数据,因为此时数据库未有任何book。 于是下一步我们会学习如何创建一个表单,并保存数据对象至数据库中。

为表格添加按钮

我们将为您的Book表格添加一个按钮,点击后会跳出弹窗 (使用AJAX技术) 用于创建新book。 在代码中,我们只需给table添加一个闭包,如下图:

taackUiService.show(new UiBlockSpecifier().ui {
    table 'Book table', tableSpecifier, BlockSpec.Width.MAX, {
        //Added Closure here
        if (isAdmin())
            action ActionIcon.CREATE, this.&bookForm as MC
    }
}, buildMenu())

仅需如此,admin用户就能在表格的右上角看见一个 创建 按钮。

action 方法由以下参数构成:

  1. 图标:必须为 ActionIcon 枚举类。

  2. action:该按钮的重定向

创建表单并保存数据对象

接下来我们将要制作一个既能创建又可以更新数据对象的表单。 首先需要定义一个 bookForm 方法,并初始化一个实例:要么新建实例,要么根据传递而来的id参数直接读取已有实例。

def bookForm(Book book) {
    book ?= new Book(params)
}

然后创建 FormSpecifier 以填充我们的表单内容。

UiFormSpecifier form = new UiFormSpecifier()
form.ui book, {
    //Section of fields
    section "Book details", {
        field book.title_
        field book.author_
    }
    //Save button
    formAction this.&saveBook as MC
}

内容填充完成后,使用 taackUiService.show() 将其呈现在页面上。

UiBlockSpecifier b = new UiBlockSpecifier()
b.ui {
    modal {
        form form, BlockSpec.Width.MAX
    }
}
taackUiService.show(b)

这一次我们不打算传递 buildMenu 进入show中,因为我们并不希望又一条导航栏出现在当前弹窗中!

也记得创建一个 saveBook 方法:

@Secured("ROLE_ADMIN")
@Transactional
def saveBook(String redirectAction) {
    taackSaveService.saveThenReloadOrRenderErrors(Book)
}

备注:保存后不需要 redirectAction,见Close Modal and reload page

由于我们希望只有admin才能创建book,因此我们在方法上方添加了 @Secured 声明,更多安全验证类信息请查阅grails-spring-security-core文档。

显示数据对象详情

目前我们已经能够创建数据对象,并将所有对象显示在一个表格中。接下来我们将学习如何在弹窗中显示某个具体对象的详细信息。 我们要再次定义一个specifier,并且最后同样要使用 taackUiService.show() 将其呈现出来:

def showUser(Book book) {
    // Define the show displayed fields
    UiShowSpecifier showSpec = new UiShowSpecifier().ui(book, {
        fieldLabeled book.title_
        fieldLabeled book.author_
    })

    taackUiService.show(new UiBlockSpecifier().ui {
        modal {
            show showSpec
        }
    })
}

为了能够打开该弹窗,我们需要为表格的每行book都添加一个链接。请在每行的目标rowColumn中添加下列代码:

rowAction
        ActionIcon.SHOW * StyleModifier.SCALE_DOWN, (1)
        TurboController.&showBook as MC, book.id
1 这里我们使用了乘法运算符以减小图标的尺寸

它将在表格的单元格中创建一个小型图标,点击后会打开弹窗,其内显示该book的详细信息。

请注意这里我们让 ActionIcon 被一个 IconStyle 乘算,使得图标的尺寸被改变。

删除一个数据对象

记得我们在表格里添加过一个 删除 按钮吗? 现在就来让它发挥作用:将方法名改为"&deleteBook",然后在控制器中创建一个相同名字的方法:

@Transactional
@Secured(['ROLE_ADMIN'])
def deleteBook(Book book) {
    book.delete()
    redirect action: 'index'
}

备注:有些时候,给数据添加一个 enable 属性来决定遮盖或显示该数据,会比直接删除它更好。

完毕! 我们使用了Grails的 delete 方法来从数据库删除一个book对象,并重定向至 index 方法以此回到表格页面。

现在您就拥有了一个针对book的完整CRUD流程,而无需任何HTML或GSP文件!

您已经做好了充分准备来探索Taack Ui框架的更多高级功能。

欢迎!