feat(web): 添加动态模板和表单编辑功能
- 新增 dynamic.go 文件处理 HTML 模板请求 - 实现模板函数 intVal用于字符串到整数转换 - 添加 edit.html 模板支持产品数据编辑- 创建 forms.go 处理表单提交和数据更新 - 新增静态文件服务支持 /static/ 路径访问 - 添加 products.html 模板显示产品列表 - 实现 JSON 数据接口 /json 返回产品列表 - 添加 Bootstrap 样式支持改善界面显示- 实现产品编辑链接和表单提交功能 - 添加输入验证和错误处理机制
This commit is contained in:
45
24-httpserver/httpserver/dynamic.go
Normal file
45
24-httpserver/httpserver/dynamic.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
Request *http.Request
|
||||
Data []Product
|
||||
}
|
||||
|
||||
var htmlTemplates *template.Template
|
||||
|
||||
func HandleTemplateRequest(writer http.ResponseWriter, request *http.Request) {
|
||||
path := request.URL.Path
|
||||
if path == "" {
|
||||
path = "products.html"
|
||||
}
|
||||
t := htmlTemplates.Lookup(path)
|
||||
if t == nil {
|
||||
http.NotFound(writer, request)
|
||||
return
|
||||
}
|
||||
err := t.Execute(writer, Context{request, ProductList})
|
||||
if err != nil {
|
||||
http.Error(writer, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
htmlTemplates = template.New("all")
|
||||
htmlTemplates.Funcs(map[string]interface{}{
|
||||
"intVal": strconv.Atoi,
|
||||
})
|
||||
htmlTemplates, err = htmlTemplates.ParseGlob("templates/*.html")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return
|
||||
}
|
||||
http.Handle("/templates/", http.StripPrefix("/templates/", http.HandlerFunc(HandleTemplateRequest)))
|
||||
}
|
||||
29
24-httpserver/httpserver/forms.go
Normal file
29
24-httpserver/httpserver/forms.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func ProcessFormData(writer http.ResponseWriter, request *http.Request) {
|
||||
if request.Method == http.MethodPost {
|
||||
index, _ := strconv.Atoi(request.PostFormValue("index"))
|
||||
price, err := strconv.ParseFloat(request.PostFormValue("price"), 64)
|
||||
if err != nil {
|
||||
Printfln("Error: %v", err.Error())
|
||||
http.Redirect(writer, request, "/templates/", http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
p := Product{
|
||||
Name: request.PostFormValue("name"),
|
||||
Category: request.PostFormValue("category"),
|
||||
Price: price,
|
||||
}
|
||||
ProductList[index] = p
|
||||
}
|
||||
http.Redirect(writer, request, "/templates/", http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func init() {
|
||||
http.HandleFunc("/forms/edit", ProcessFormData)
|
||||
}
|
||||
19
24-httpserver/httpserver/json.go
Normal file
19
24-httpserver/httpserver/json.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func HandleJsonRequest(writer http.ResponseWriter, request *http.Request) {
|
||||
writer.Header().Set("Content-Type", "application/json")
|
||||
err := json.NewEncoder(writer).Encode(ProductList)
|
||||
if err != nil {
|
||||
Printfln("Error: %v", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
http.HandleFunc("/json", HandleJsonRequest)
|
||||
}
|
||||
@@ -18,6 +18,10 @@ func main3() {
|
||||
http.Handle("/message", &StringHandle3{message: "Hello World!"})
|
||||
http.Handle("/favicon.ico", http.NotFoundHandler())
|
||||
http.Handle("/", http.RedirectHandler("/message", http.StatusTemporaryRedirect))
|
||||
|
||||
fsHandler := http.FileServer(http.Dir("./static"))
|
||||
http.Handle("/static/", http.StripPrefix("/static/", fsHandler))
|
||||
|
||||
err := http.ListenAndServe("localhost:5000", nil)
|
||||
if err != nil {
|
||||
Printfln("ListenAndServe Error: %v", err)
|
||||
|
||||
7
24-httpserver/httpserver/static/bootstrap.min.css
vendored
Normal file
7
24-httpserver/httpserver/static/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
14
24-httpserver/httpserver/static/index.html
Normal file
14
24-httpserver/httpserver/static/index.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Pro Go</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="bootstrap.min.css" rel="stylesheet"/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="m-1 p-2 bg-primary text-white h2">
|
||||
Hello World
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
36
24-httpserver/httpserver/static/store.html
Normal file
36
24-httpserver/httpserver/static/store.html
Normal file
@@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Pro Go</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="bootstrap.min.css" rel="stylesheet"/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="m-1 p-2 bg-primary text-white h2 text-center">
|
||||
Products
|
||||
</div>
|
||||
<table class="table table-sm table-bordered table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Category</th>
|
||||
<th>Price</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Kayak</td>
|
||||
<td>Watersports</td>
|
||||
<td>$275.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Lifejacket</td>
|
||||
<td>Watersports</td>
|
||||
<td>$49.95</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
44
24-httpserver/httpserver/templates/edit.html
Normal file
44
24-httpserver/httpserver/templates/edit.html
Normal file
@@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Pro go</title>
|
||||
<link href="/static/bootstrap.min.css" rel="stylesheet"/>
|
||||
</head>
|
||||
<body>
|
||||
{{ $index := intVal (index (index .Request.URL.Query "index") 0) }}
|
||||
{{ if lt $index (len .Data) }}
|
||||
{{ with index .Data $index }}
|
||||
<h3 class="bg-primary text-white text-center p-2 m-2">Product</h3>
|
||||
<form method="POST" action="/forms/edit">
|
||||
<div class="form-group">
|
||||
<label>Index</label>
|
||||
<input name="index" type="text" value="{{ $index }}" class="form-control" disabled/>
|
||||
<input name="index" value="{{ $index }}" type="hidden"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Name</label>
|
||||
<input name="name" type="text" value="{{ .Name }}" class="form-control"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Category</label>
|
||||
<input name="category" type="text" value="{{ .Category }}" class="form-control"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Price</label>
|
||||
<input name="price" type="text" value="{{ .Price }}" class="form-control"/>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
<a href="/templates/" class="btn btn-secondary">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
<h3 class="bg-danger text-white text-center p-2">
|
||||
No Product At Specified Index
|
||||
</h3>
|
||||
{{ end }}
|
||||
</body>
|
||||
</html>
|
||||
37
24-httpserver/httpserver/templates/products.html
Normal file
37
24-httpserver/httpserver/templates/products.html
Normal file
@@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Pro Go</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="/static/bootstrap.min.css" rel="stylesheet"/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="m-1 p-2 bg-primary text-white h2 text-center">
|
||||
Products
|
||||
</div>
|
||||
<table class="table table-sm table-bordered table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Index</th>
|
||||
<th>Name</th>
|
||||
<th>Category</th>
|
||||
<th class="text-end">Price</th>
|
||||
<th class="text-center">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range $index,$product := .Data }}
|
||||
<tr>
|
||||
<td>{{ $index }}</td>
|
||||
<td>{{ $product.Name }}</td>
|
||||
<td>{{ $product.Category }}</td>
|
||||
<td class="text-end">{{ printf "$%.2f" $product.Price }}</td>
|
||||
<th class="text-center"><a href="edit.html/index{{ $index }}">Edit</a></th>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user