后端代码:
package main
import (
"html/template"
"net/http"
"strconv"
)
type Book struct {
Title string // 标题
Price float64 // 价格
Publisher string // 出版商
PublishDate string // 发布日期
Authors []string // 作者
}
// Books 定义切片将值存入切片
var Books []*Book
var Temps = make(map[string]*template.Template, 8)
func init() {
// 自定义一个add函数用于前端页面index+1使序号从1开始
myAdd := func(a, b int) int {
return a + b
}
// isIn函数用于前端页面判断作者是否存在,如果存在那么在编辑页面作者一栏会显示作者名字
isIn := func(a string, list []string) bool {
for _, s := range list {
if s == a {
return true
}
}
return false
}
Temps["home"] = template.Must(template.ParseFiles("./static/htmls/home.html"))
Temps["login"] = template.Must(template.ParseFiles("./static/htmls/home.html", "./static/htmls/login.html"))
Temps["list"] = template.Must(template.New("book_list.html").Funcs(template.FuncMap{"add": myAdd}).ParseFiles("./static/htmls/home.html", "./static/htmls/book_list.html"))
Temps["add"] = template.Must(template.ParseFiles("./static/htmls/home.html", "./static/htmls/book_add.html"))
Temps["edit"] = template.Must(template.New("book_edit.html").Funcs(template.FuncMap{"isIn": isIn}).ParseFiles("./static/htmls/home.html", "./static/htmls/book_edit.html"))
}
// 全局校验
func loginAuth(fn func(w http.ResponseWriter, r *http.Request)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
cookie, _ := r.Cookie("admin")
if cookie == nil || cookie.Value != "adminValue" {
http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
}
fn(w, r)
}
}
// Authors Publishers 直接给定值
var Authors = []string{"张三", "李四"}
var Publishers = []string{"人民出版社", "北京出版社"}
func main() {
// 定义路由
http.HandleFunc("/", home)
http.HandleFunc("/login", login)
http.HandleFunc("/book_list", list)
http.HandleFunc("/book_add", loginAuth(add))
http.HandleFunc("/book_edit/", loginAuth(edit))
http.HandleFunc("/book_delete/", loginAuth(delete))
// 将文件暴露出来否则找不到字体
//http.Handle("/static/", http.FileServer(http.Dir(".")))
http.ListenAndServe("localhost:8080", nil)
}
func login(w http.ResponseWriter, r *http.Request) {
tmp := Temps["login"]
switch r.Method {
case "GET":
tmp.Execute(w, nil)
case "POST":
// 登录 校验逻辑
name := r.PostFormValue("username")
pwd := r.PostFormValue("password")
if name == "admin" && pwd == "admin" {
cookie := &http.Cookie{Name: "admin", Value: "adminValue"}
http.SetCookie(w, cookie)
http.Redirect(w, r, "/book_list", 302)
} else {
tmp.Execute(w, "用户名或密码错误")
}
}
}
// 前端首页的后端逻辑
func home(w http.ResponseWriter, r *http.Request) {
// ParseFiles方法: 创建一个新模板,并从中解析模板定义
tmp := Temps["home"]
tmp.Execute(w, nil)
}
// 前端图书列表的后端逻辑
func list(w http.ResponseWriter, r *http.Request) {
// Books = append(Books, &Book{"人生", 19.8, "北京出版社", "2023-2-5", []string{"张三"}})
// tmp, _ := template.ParseFiles("home.html", "book_list.html")
tmp, _ := Temps["list"]
tmp.Execute(w, Books)
}
// 前端增加图书的后端逻辑
func add(w http.ResponseWriter, r *http.Request) {
// 校验cookie
//cookie, _ := r.Cookie("admin")
//if cookie == nil || cookie.Value != "adminValue" {
// // http.NotFound(w, r)
// http.Redirect(w, r, "login", http.StatusTemporaryRedirect)
// return
//}
switch r.Method {
case "GET":
tmp := Temps["add"]
info := map[string][]string{"authors": Authors, "publishers": Publishers}
tmp.Execute(w, info)
case "POST":
// 保存数据,增加一个Book放在Books
// ParseFloat方法: 将字符串转换为浮点数
// FormValue方法: 返回查询的命名组件的第一个值
price, _ := strconv.ParseFloat(r.FormValue("price"), 64)
book := &Book{
Title: r.FormValue("title"),
Price: price,
Publisher: r.FormValue("publisher"),
PublishDate: r.FormValue("publish_date"),
Authors: r.PostForm["authors"],
}
Books = append(Books, book)
// 跳转
http.Redirect(w, r, "/book_list", 302)
}
}
// 前端编辑页面的后端逻辑
func edit(w http.ResponseWriter, r *http.Request) {
// 将图书的索引index(id)通过切片的方式切出来
// 因为完整的URL为/book_edit/x(x表示id),所以将/book_edit/切掉就得到了id
idStr := r.URL.Path[len("/book_edit/"):]
// 将id从字符串类型转换成int类型
id, _ := strconv.Atoi(idStr)
// 如果id大于书籍数量就返回错误终止掉程序
if id >= len(Books) {
http.NotFound(w, r)
return
}
// Method方法: 用于指定HTTP方法(GET,POST,PUT等)
switch r.Method {
case "GET":
// tmp, _ := template.ParseFiles("home.html", "book_edit.html")
// New方法: 使用给定的名称分配一个新的HTML模板
// Func方法: 将参数映射的元素添加到模板的函数映射中
// FuncMap方法: 是定义从名称到函数的映射的映射类型
// ParseFiles解析命名文件并生成模板
tmp := Temps["edit"]
// interface方法: 指定接收的类型为所有的类型
info := map[string]interface{}{"authors": Authors, "publishers": Publishers, "book": Books[id], "id": id}
// Execute方法: 将解析的模板应用于指定的数据对象
tmp.Execute(w, info)
case "POST":
// 编辑索引为id的book
book := Books[id]
// 将前端页面返回的字符串转换为浮点数
price, _ := strconv.ParseFloat(r.FormValue("price"), 64)
// 更新数据
book.Price = price
book.Title = r.FormValue("title")
book.PublishDate = r.FormValue("publish_date")
book.Publisher = r.FormValue("publisher")
book.Authors = r.PostForm["authors"]
http.Redirect(w, r, "/book_list", 307)
}
}
func delete(w http.ResponseWriter, r *http.Request) {
// 将图书的索引index(id)通过切片的方式切出来
// 因为完整的URL为/book_edit/x(x表示id),所以将/book_edit/切掉就得到了id
idStr := r.URL.Path[len("/book_delete/"):]
// 将id从字符串类型转换成int类型
id, _ := strconv.Atoi(idStr)
// 如果id大于书籍数量就返回错误终止掉程序
if id >= len(Books) {
http.NotFound(w, r)
return
}
// 将索引为id的元素从Books中删除
// 下述代码的解释为:首先将0-id的值切出来然后将id+1及以后的值切出来然后将二者结合达到删除id的效果
Books = append(Books[:id], Books[id+1:]...)
http.Redirect(w, r, "/book_list", 307)
}
前端代码:首页
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>BMS</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<!-- <link rel="stylesheet" href="../font-awesome-4.7.0/css/font-awesome.min.css">-->
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
</head>
<body>
<div class="container-fluid">
<div class="row" style="margin-top: 70px;">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Book Manage System</h3>
</div>
<div class="panel-body">
{{block "content" .}}
<div class="jumbotron">
<h1>欢迎来到电子信息图书平台</h1>
<p>你想要的书籍全都有</p>
<p><a class="btn btn-primary btn-lg" role="button" href="/book_list">点击阅读吧~</a></p>
</div>
{{end}}
</div>
</div>
</div>
</div>
</div>
</body>
</html>
图书列表界面:
{{template "home.html" .}}
{{define "content"}}
<a href="/book_add" class="btn btn-success">添加</a>
<br>
<br>
<table class="table table-hover table-striped">
<thead>
<tr>
<th>ID</th>
<th>书名</th>
<th>价格</th>
<th>出版社</th>
<th>出版日期</th>
<th>作者</th>
<th>操作</th>
</tr>
</thead>
<tboby>
{{range $index, $book := .}}
<tr>
<td>{{add $index 1}}</td>
<td>{{$book.Title}}</td>
<td>{{$book.Price}}</td>
<td>{{$book.Publisher}}</td>
<td>{{$book.PublishDate}}</td>
<td>
{{range $author := $book.Authors}}
{{$author}},
{{end}}
</td>
<td>
<a href="/book_edit/{{$index}}" class="btn btn-primary btn-xs">编辑</a>
<button class="btn btn-danger btn-xs" onclick="del({{$index}})">删除</button>
</td>
</tr>
{{end}}
</tboby>
</table>
<script>
function del(id) {
swal({
title: "温馨提示",
text: "您确认要删除该图书吗?",
icon: "warning",
buttons: true,
dangerMode: true,
})
.then((willDelete) => {
if (willDelete) {
swal("删除成功!", {
icon: "success",
});
window.location.href = "/book_delete/" + id
} else {
swal("取消成功!");
}
});
}
</script>
{{end}}
增加图书界面:
{{template "home.html" .}}
{{define "content"}}
<h1 class="text-center">书籍添加</h1>
<form action="/book_add" method="post">
<p>
书名
<input type="text" name="title" class="form-control">
</p>
<p>
价格
<input type="text" name="price" class="form-control">
</p>
<p>
出版日期
<input type="date" name="publish_data" class="form-control">
</p>
<p>
出版社
<select name="publisher" id="" class="form-control">
{{range $p := .publishers}}
<option value="{{$p}}">{{$p}}</option>
{{end}}
</select>
</p>
<p>
作者
<select name="authors" id="" multiple class="form-control">
{{range $a := .authors}}
<option value="{{$a}}">{{$a}}</option>
{{end}}
</select>
</p>
<input type="submit" value="新增" class="btn btn-primary btn-block">
</form>
{{end}}
编辑图书界面:
{{template "home.html" .}}
{{define "content"}}
<h1 class="text-center">编辑书籍</h1>
<form action="/book_edit/{{.id}}" method="post">
<p>
书名
<input type="text" name="title" class="form-control" value="{{.book.Title}}">
</p>
<p>
价格
<input type="text" name="price" class="form-control" value="{{.book.Price}}">
</p>
<p>
出版日期
<input type="date" name="publish_date" class="form-control" value="{{.book.PublishDate}}">
</p>
<p>
出版社
{{$publisher := .book.Publisher}}
<select name="publisher" id="" class="form-control">
{{range $p := .publishers}}
{{if eq $p $publisher}}
<option value="{{$p}}" selected>{{$p}}</option>
{{else}}
<option value="{{$p}}">{{$p}}</option>
{{end}}
{{end}}
</select>
</p>
<p>
作者
{{$authors := .book.Authors}}
<select name="authors" id="" multiple class="form-control">
{{range $a := .authors}}
{{if isIn $a $authors}}
<option value="{{$a}}" selected>{{$a}}</option>
{{else}}
<option value="{{$a}}">{{$a}}</option>
{{end}}
{{end}}
</select>
</p>
<input type="submit" value="编辑" class="btn btn-primary btn-block">
</form>
{{end}}
登录界面:
{{template "home.html" .}}
{{define "content"}}
<h1 class="test-center">登录</h1>
<form action="/login" method="post">
<p>
用户名:
<input type="text" name="username" class="form-control">
</p>
<p>
密码:
<input type="password" name="password" class="form-control">
</p>
<input type="submit" value="登录" class="btn btn-primary btn-block">
<p style="color: crimson">{{ . }}</p>
</form>
{{end}}
代码运行以后,浏览器访问localhost:8080即可,默认管理员账户密码均为:admin