Fms-Java版开发实录:22 预算 - 增删改功能

Fms-Java版开发实录:22 预算 - 增删改功能

一层一层继续编写,今天是预算条目的增删改查

增删改功能没有什么新意了,主要时间都花在更新表单上,不过其中还有一些小细节值的注意,一起来看看吧:

新增预算

依然是熟悉的套路,先编写表单,然后是对应的控制器

表单设计

<form class="needs-validation" action="/pms/budget/add" th:action="${project.addBudgetUrl()}" method="post"
      th:object="${budget}">
    <div class="row g-3 mb-3">
        <div class="col-sm-6">
            <label for="projectName" class="form-label">预算条目名称</label>
            <input type="text" class="form-control" id="projectName"
                   th:field="*{budgetName}"
                   th:classappend="${#fields.hasErrors('budgetName')}? 'is-invalid'" required>
            <div id="nameError" th:if="${#fields.hasErrors('budgetName')}"
                 th:errors="*{budgetName}"
                 class="invalid-feedback"></div>
        </div>

        <div class="col-sm-6">
            <label for="description" class="form-label">说明(可不填写)</label>
            <input type="text" class="form-control" id="description"
                   th:field="*{description}"
                   th:classappend="${#fields.hasErrors('description')}? 'is-invalid'" >
            <div id="descriptionError" th:if="${#fields.hasErrors('description')}"
                 th:errors="*{description}"
                 class="invalid-feedback"></div>
        </div>
        

        <div class="col-sm-6">
            <label for="grossIncome" class="form-label">含税收入</label>
            <input type="number" step="0.01" class="form-control" id="grossIncome" th:field="*{grossIncome}"
                   th:classappend="${#fields.hasErrors('grossIncome')}? 'is-invalid'" required>
            <div id="grossIncomeError" th:if="${#fields.hasErrors('grossIncome')}"
                 th:errors="*{grossIncome}"
                 class="invalid-feedback"></div>
        </div>

        <div class="col-sm-6">
            <label for="netIncome" class="form-label">不含税收入</label>
            <input type="number" step="0.01" class="form-control" id="netIncome" th:field="*{netIncome}"
                   th:classappend="${#fields.hasErrors('netIncome')}? 'is-invalid'" required>
            <div id="netIncomeError" th:if="${#fields.hasErrors('netIncome')}"
                 th:errors="*{netIncome}"
                 class="invalid-feedback"></div>
        </div>


        <div class="col-sm-6">
            <label for="grossCost" class="form-label">含税成本</label>
            <input type="number" step="0.01" class="form-control" id="grossCost" th:field="*{grossCost}"
                   th:classappend="${#fields.hasErrors('grossCost')}? 'is-invalid'" required>
            <div id="grossCostError" th:if="${#fields.hasErrors('grossCost')}"
                 th:errors="*{grossCost}"
                 class="invalid-feedback"></div>
        </div>

        <div class="col-sm-6">
            <label for="netCost" class="form-label">不含税成本</label>
            <input type="number" step="0.01" class="form-control" id="netCost" th:field="*{netCost}"
                   th:classappend="${#fields.hasErrors('netCost')}? 'is-invalid'" required>
            <div id="netCostError" th:if="${#fields.hasErrors('netCost')}"
                 th:errors="*{netCost}"
                 class="invalid-feedback"></div>
        </div>

        <div class="col-sm-6">
            <label for="expenditure" class="form-label">费用</label>
            <input type="number" step="0.01" class="form-control" id="expenditure" th:field="*{expenditure}"
                   th:classappend="${#fields.hasErrors('expenditure')}? 'is-invalid'" required>
            <div id="expenditureError" th:if="${#fields.hasErrors('expenditure')}"
                 th:errors="*{expenditure}"
                 class="invalid-feedback"></div>
        </div>

        <div>
            <p>虚拟预算条目</p>
        <div class="form-check form-check-inline">
            <input class="form-check-input" type="radio" name="virtual" id="trueOption" value="true"
                   th:checked="${budget.isVirtual()}">
            <label class="form-check-label" for="trueOption">是</label>
        </div>
        <div class="form-check form-check-inline">
            <input class="form-check-input" type="radio" name="virtual" id="falseOption" value="false"
                   th:checked="${!budget.isVirtual()}">
            <label class="form-check-label" for="falseOption">否</label>
        </div>
        </div>
        
    </div>
    <div class="text-center">
        <button class="btn btn-primary btn-lg" type="submit"><i class="fas fa-check"></i> 提交</button>
        <a class="btn btn-secondary btn-lg" th:href="${project.budgetUrl()}" href="/pms/budget/list"><i class="fas fa-undo-alt"></i> 返回</a>
    </div>
</form>

这个表单本身平淡无奇,然后来编写控制器。

控制器

控制器也很简单,一起放出来了:

//添加预算页面
@GetMapping("/{pid}/add")
public String addBudgetPage(@ModelAttribute("budget") Budget budget, @PathVariable("pid") int id, Model model) {

    Project project = projectService.findById(id);
    model.addAttribute("project", project);

    return "pms/budget/budgetAdd";
}

//接受新增预算
@PostMapping("/{pid}/add")
public String editBudget(@Valid @ModelAttribute("budget") Budget budget, BindingResult rs,
                         @PathVariable("pid") int id, Model model,
                         RedirectAttributes attributes) {

    Project project = projectService.findById(id);
    model.addAttribute("project", project);

    if (rs.hasErrors()) {
        return "pms/budget/budgetAdd";
    } else {
        System.out.println(budget);
        budget.setProject(project);
        budgetService.save(budget);
        attributes.addFlashAttribute("created", budget.getBudgetName());
        return "redirect:" + project.budgetUrl();
    }

}

有了前边的设计,我们这里依然使用RedirectAttributes来传递一个成功信息。

修改预算条目

这个也如出一辙,之前的表单可以直接拿过来用,我打算直接就使用基本相同的页面,只要把项目和预算条目的id埋入页面就行了。这里页面就不放了,只放出来控制器。

//修改预算条目页面
@GetMapping("/{pid}/edit/{id}")
public String editBudgetPage(@PathVariable("pid") int pid, @PathVariable("id") int id, Model model) {
    Project project = projectService.findById(pid);
    model.addAttribute("project", project);

    Budget budget = budgetService.findById(id);
    model.addAttribute("budget", budget);

    return "pms/budget/budgetEdit";
}

//接受修改预算条目
@PostMapping("/{pid}/edit/{id}")
public String editBudget(@Valid @ModelAttribute("budget") Budget budget, BindingResult rs,
                         @PathVariable("pid") int pid, @PathVariable("id") int id,
                         @RequestParam("projectId") int projectId, @RequestParam("budgetId") int budgetId,
                         RedirectAttributes attributes, Model model) {
    if (pid != projectId || id != budgetId) {
        throw new RuntimeException("POST请求内容与路径不符");
    }

    Project project = projectService.findById(pid);
    model.addAttribute("project", project);

    if (rs.hasErrors()) {
        return "pms/budget/budgetEdit";
    } else {
        budget.setId(id);
        budget.setProject(project);
        budgetService.save(budget);
        attributes.addFlashAttribute("updated", budget.getBudgetName());
        return "redirect:" + project.budgetUrl();
    }
}

预算条目详情页与删除预算条目

这个页面也比较简单,我打算参考项目的详情页,以后因为预算下边挂合同和明细,所以预算页面也要设计一些功能。

<main class="col-lg-8 mx-auto container">
    <nav style="--bs-breadcrumb-divider: '>';" aria-label="breadcrumb" class="mt-3">
        <ol class="breadcrumb">
            <li class="breadcrumb-item"><a href="/home" th:href="@{/home}" class="link-secondary">首页</a></li>
            <li class="breadcrumb-item" aria-current="page"><a href="/pms/project/list" th:href="@{/pms/project/list}"
                                                               class="link-secondary">项目管理</a></li>
            <li class="breadcrumb-item" aria-current="page"><a href="/pms/project/list"
                                                               th:href="${project.absoluteUrl()}"
                                                               class="link-secondary">[[${project.projectName}]]</a>
            </li>
            <li class="breadcrumb-item" aria-current="page"><a href="/pms/budget/list" th:href="${project.budgetUrl()}"
                                                               class="link-secondary">项目预算</a></li>
            <li class="breadcrumb-item" aria-current="page"><a href="/pms/budget/detail"
                                                               th:href="${'/pms/budget/' + project.getId() +'/detail/'+ budget.getId()}"
                                                               class="link-secondary">[[${budget.budgetName}]]</a></li>
        </ol>
    </nav>
    <h1 th:text="${budget.budgetName}">预算条目详情</h1>
    <hr>
    <div class="row g-5">
        <div class="col-md-9">
            <h2>金额详细</h2>
            <table class="table table-sm table-striped">
                <thead>
                <tr>
                    <th>内容</th>
                    <th>金额</th>
                </tr>
                </thead>
                <tbody>
                <tr>
                    <td>含税收入</td>
                    <td th:text="${#numbers.formatDecimal(budget.grossIncome,0,'COMMA',2,'POINT')}"></td>
                </tr>
                <tr>
                    <td>不含税收入</td>
                    <td th:text="${#numbers.formatDecimal(budget.netIncome,0,'COMMA',2,'POINT')}"></td>
                </tr>
                <tr>
                    <td>含税成本</td>
                    <td th:text="${#numbers.formatDecimal(budget.grossCost,0,'COMMA',2,'POINT')}"></td>
                </tr>
                <tr>
                    <td>不含税成本</td>
                    <td th:text="${#numbers.formatDecimal(budget.netCost,0,'COMMA',2,'POINT')}"></td>
                </tr>
                <tr>
                    <td>费用</td>
                    <td th:text="${#numbers.formatDecimal(budget.expenditure,0,'COMMA',2,'POINT')}"></td>
                </tr>
                <tr>
                    <td>本条目利润</td>
                    <td th:text="${#numbers.formatDecimal(budget.profit(),0,'COMMA',2,'POINT')}"></td>
                </tr>
                </tbody>
            </table>
        </div>

        <div class="col-md-3">
            <h2>功能导航</h2>
            <ul class="list-group list-group-flush">
                <li class="list-group-item"><a href="/pms/project/detail"
                                               th:href="${'/pms/budget/'+project.getId()+'/edit/'+budget.getId()}"><i
                        class="fas fa-pencil-alt"></i> 编辑预算信息</a></li>
                <li class="list-group-item"><a href="" class="link-success"><i class="fas fa-list"></i> 所属合同清单</a>
                </li>
                <li class="list-group-item"><a href="/pms/budget/list" th:href="${project.budgetUrl()}"
                                               class="link-success"><i class="fas fa-chart-line"></i> 项目预算</a>
                </li>
                <li class="list-group-item"><a href="" class="link-danger" data-bs-toggle="modal"
                                               data-bs-target="#deleteModal"><i class="fas fa-trash-alt"></i> &nbsp;删除本条目</a>
                </li>
            </ul>
        </div>
    </div>

    <div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteLabel" aria-hidden="true">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title" id="deleteLabel">删除预算条目</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                </div>
                <div class="modal-body">
                    将删除[[${budget.budgetName}]]
                </div>
                <form class="modal-footer" method="post" action="/pms/project/delete"
                      th:action="@{/pms/budget/delete}">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><i class="fas fa-undo"></i>
                        返回
                    </button>
                    <input type="hidden" name="deleteId" th:value="${budget.getId()}">
                    <input type="hidden" name="name" th:value="${budget.budgetName}">
                    <button type="submit" class="btn btn-danger"><i class="fas fa-info-circle"></i>
                        确定删除
                    </button>
                </form>
            </div>
        </div>
    </div>
</main>

然后是控制器,记得要限制访问权限:

//删除预算条目
@PostMapping("/delete")
public String deleteBudget(@RequestParam("deleteId") int id, @RequestParam("name") String name, RedirectAttributes attributes) {
    String url = budgetService.findById(id).getProject().budgetUrl();
    budgetService.deleteBudget(id);
    attributes.addFlashAttribute("deleted", name);
    return "redirect:" + url;
}

剩下的工作,就是修改项目的详情页,在控制器中引入BudgetService,可以直接查出这个项目的预算的合计数目,展示在项目的详情页中。

写完了项目的内容。这里使用了业务来计算一个BO对象,也为今后的思路打下了基础,之后的合同要对应预算条目来添加。重型的计算都交给业务层来实现。到了合同层面,合同的外键需要关联到预算条目,在合同的业务层就要编制按照预算进行汇集和按照项目进行汇集的两个业务方法,也会密集进行计算。当然现在因为还没有明细条目,编写不了那么细,但是在创建完所有的PO之后,重点就是编写这些方法了。

LICENSED UNDER CC BY-NC-SA 4.0
Comment