feat: 整合elements主题

This commit is contained in:
2026-01-08 11:20:30 +08:00
parent b174a57b7f
commit e673ee2479
9 changed files with 92 additions and 31 deletions

8
go.mod
View File

@@ -6,7 +6,7 @@ toolchain go1.24.2
require (
git.zhangdeman.cn/zhangdeman/api-doc v1.0.3-0.20260107152122-a877079712ee
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20260106015608-42e268aea545
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20260108031051-bf46e2083c84
git.zhangdeman.cn/zhangdeman/dynamic-struct v0.0.0-20251207071238-9aa24be3d708
git.zhangdeman.cn/zhangdeman/exception v0.0.0-20250510123912-a0d52fc093ab
git.zhangdeman.cn/zhangdeman/graceful v0.0.0-20250529070945-92833db6f3a4
@@ -22,8 +22,11 @@ require (
github.com/gin-contrib/timeout v1.1.0
github.com/gin-gonic/gin v1.11.0
github.com/go-playground/validator/v10 v10.30.1
github.com/go-webtools/knife4go v1.0.4
github.com/jedib0t/go-pretty/v6 v6.7.8
github.com/mcuadros/go-defaults v1.2.0
github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.1
go.uber.org/zap v1.27.1
)
@@ -57,7 +60,6 @@ require (
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-redis/redis_rate/v10 v10.0.1 // indirect
github.com/go-webtools/knife4go v1.0.4 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.19.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
@@ -87,8 +89,6 @@ require (
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sbabiv/xml2map v1.2.1 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/swaggo/files v1.0.1 // indirect
github.com/swaggo/gin-swagger v1.6.1 // indirect
github.com/swaggo/swag v1.16.6 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.2.0 // indirect

4
go.sum
View File

@@ -1,7 +1,7 @@
git.zhangdeman.cn/zhangdeman/api-doc v1.0.3-0.20260107152122-a877079712ee h1:cUrlM+87fQW8StLbGBA8EB+Ke3ZR1QslWyZqbXyD5C0=
git.zhangdeman.cn/zhangdeman/api-doc v1.0.3-0.20260107152122-a877079712ee/go.mod h1:EO06OJ2rIS+s3CaXoGiX6yTuyhKFeSrgHukOY7FofHw=
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20260106015608-42e268aea545 h1:A6UeeMcSqAlHUmA2coWIuiCS/W9ySylhZMa/0HbFDcQ=
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20260106015608-42e268aea545/go.mod h1:5p8CEKGBxi7qPtTXDI3HDmqKAfIm5i/aBWdrbkbdNjc=
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20260108031051-bf46e2083c84 h1:1+erCrsFvz5QuHKdraVuH7KmokUoqJULaKpWXMT6m3Y=
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20260108031051-bf46e2083c84/go.mod h1:5p8CEKGBxi7qPtTXDI3HDmqKAfIm5i/aBWdrbkbdNjc=
git.zhangdeman.cn/zhangdeman/dynamic-struct v0.0.0-20251207071238-9aa24be3d708 h1:yVr38JAgPwS/6JeYdHLDa8PXU3fTa2dnENL1JGdu3ns=
git.zhangdeman.cn/zhangdeman/dynamic-struct v0.0.0-20251207071238-9aa24be3d708/go.mod h1:iwegDT/6mX2INyxMAYxsL9pZdpEixiu1GdpHIG2It1k=
git.zhangdeman.cn/zhangdeman/easylock v0.0.0-20230731062340-983985c12eda h1:bMD6r9gjRy7cO+T4zRQVYAesgIblBdTnhzT1vN5wjvI=

View File

@@ -19,6 +19,7 @@ import (
apiDocDefine "git.zhangdeman.cn/zhangdeman/api-doc/define"
"git.zhangdeman.cn/zhangdeman/api-doc/enums"
"git.zhangdeman.cn/zhangdeman/api-doc/openapi"
"git.zhangdeman.cn/zhangdeman/consts"
"git.zhangdeman.cn/zhangdeman/gin/define"
"git.zhangdeman.cn/zhangdeman/graceful"
"github.com/jedib0t/go-pretty/v6/table"
@@ -172,7 +173,7 @@ func (s *server) RegisterDocHandler() {
})
ctx.Abort()
}
}, NewSwaggerUI(fmt.Sprintf("[%v]接口文档", s.option.docConfig.Flag), s.option.docConfig.BaseUri, enums.SwaggerUITheme(s.option.docConfig.UiTheme)).Handler())
}, NewSwaggerUI(fmt.Sprintf("[%v]接口文档", s.option.docConfig.Flag), s.option.docConfig.BaseUri, consts.SwaggerUITheme(s.option.docConfig.UiTheme)).Handler())
s.router.GET("/swagger-resources", func(ctx *gin.Context) { // lucky UI获取分组信息
ctx.Writer.Header().Set("Access-Control-Allow-Origin", "*") // 允许访问所有域
ctx.JSON(http.StatusOK, []map[string]any{

View File

@@ -9,25 +9,17 @@ package router
import (
"testing"
"time"
"git.zhangdeman.cn/zhangdeman/rate_limit"
"github.com/gin-gonic/gin"
)
type testCommon struct {
UserID uint `json:"user_id"`
}
type testForm struct {
Meta `json:"-" method:"get" path:"test" rate-limit:"1/5/60" tag:"测试,验证" summary:"本地调试"`
testCommon
Name string `json:"name"`
}
func TestNewServer(t *testing.T) {
s := NewServer(9087, WithRateLimitInstance(rate_limit.MemoryClient), WithDocConfig(&DocConfig{
Enable: true,
UiTheme: "ydoc-lucky-ui",
UiTheme: "elements",
BaseUri: "",
Flag: "test-server",
ServerList: nil,
@@ -42,9 +34,27 @@ func TestNewServer(t *testing.T) {
s.Start()
}
type testCategory struct {
Meta `json:"-" method:"get" path:"test" rate-limit:"1/5/60" tag:"测试,验证" summary:"本地调试"`
ID int64 `json:"id" description:"分类ID" eg:"123" binding:"required,min=10,max=100"`
Sex string `json:"sex" dc:"性别" binding:"required,oneof=man woman" enum-desc:"man:男||woman:女"`
Name string `json:"name" description:"分类名称" eg:"baichaqinghuan" binding:"required,min=8"`
}
type testProduct struct {
ID int64 `json:"id,omitempty" description:"产品ID" example:"1001" required:"true" binding:"required"`
Name string `json:"name" description:"产品名称" example:"iPhone 13" minLength:"2" maxLength:"100" required:"true"`
Price float64 `json:"price" description:"价格" example:"6999.99" min:"0"`
Stock int `json:"stock" description:"库存" example:"100" min:"0"`
Tags []string `json:"tags" description:"标签"`
Attributes map[string]string `json:"attributes" description:"属性"`
CreatedAt time.Time `json:"created_at" description:"创建时间"`
UpdatedAt *time.Time `json:"updated_at,omitempty" description:"更新时间"`
Category *testCategory `json:"category,omitempty" description:"分类"`
}
type testController struct {
}
func (tc testController) Test(ctx *gin.Context, requestData *testForm) (*testCommon, error) {
return &testCommon{UserID: requestData.UserID}, nil
func (tc testController) Test(ctx *gin.Context, requestData *testCategory) (*testProduct, error) {
return &testProduct{}, nil
}

View File

@@ -13,8 +13,8 @@ import (
"strings"
"git.zhangdeman.cn/zhangdeman/api-doc/enums"
"git.zhangdeman.cn/zhangdeman/api-doc/theme"
"git.zhangdeman.cn/zhangdeman/consts"
"git.zhangdeman.cn/zhangdeman/gin/router/theme"
"github.com/gin-gonic/gin"
knife4goFiles "github.com/go-webtools/knife4go"
knife4goGin "github.com/go-webtools/knife4go/gin"
@@ -23,7 +23,7 @@ import (
)
// NewSwaggerUI ...
func NewSwaggerUI(docTitle string, docBaseUri string, uiTheme enums.SwaggerUITheme) *SwaggerUI {
func NewSwaggerUI(docTitle string, docBaseUri string, uiTheme consts.SwaggerUITheme) *SwaggerUI {
return &SwaggerUI{
baseUri: docBaseUri,
docTitle: docTitle,
@@ -34,27 +34,55 @@ func NewSwaggerUI(docTitle string, docBaseUri string, uiTheme enums.SwaggerUIThe
type SwaggerUI struct {
baseUri string
docTitle string
uiTheme enums.SwaggerUITheme // 文档主题, swaggerUI / knife4go, 默认 knife4go
uiTheme consts.SwaggerUITheme // 文档主题, swaggerUI / knife4go, 默认 knife4go
}
// Handler 访问文档的接口处理
func (su *SwaggerUI) Handler() func(ctx *gin.Context) {
switch su.uiTheme {
case enums.SwaggerUIThemeKnife4go:
case consts.SwaggerUIThemeKnife4go:
return su.HandleKnife4goUI()
case enums.SwaggerUIThemeYDocLucky:
case consts.SwaggerUIThemeYDocLucky:
// YDoc-Lucky-UI 主题处理
return su.HandleLuckyUI()
case enums.SwaggerUIThemeDefault:
case consts.SwaggerUIThemeDefault:
return su.HandleSwaggerUI()
case enums.SwaggerUIThemeRedocFree:
case consts.SwaggerUIThemeRedocFree:
// redoc免费版
return su.HandleRedocFreeUI()
case consts.SwaggerUIThemeElements:
return su.HandleElements()
default:
return su.HandleSwaggerUI()
}
}
func (su *SwaggerUI) HandleElements() gin.HandlerFunc {
return func(ctx *gin.Context) {
fileRealPath := strings.TrimPrefix(ctx.Request.RequestURI, su.baseUri)
byteData, _ := theme.ElementsFiles.ReadFile(filepath.Join("elements", fileRealPath))
if strings.Contains(ctx.Request.RequestURI, ".html") {
byteData = []byte(strings.ReplaceAll(string(byteData), "{{BASE_URI}}", su.baseUri))
byteData = []byte(strings.ReplaceAll(string(byteData), "{{DOC_TITLE}}", su.docTitle))
byteData = []byte(strings.ReplaceAll(string(byteData), "{{DOC_PATH}}", "doc.json"))
ctx.Header(consts.HeaderKeyContentType.String(), "text/html; charset=utf-8")
ctx.String(http.StatusOK, string(byteData))
ctx.Abort()
return
}
uriArr := strings.Split(ctx.Request.RequestURI, ".")
contentType := "text/" + uriArr[len(uriArr)-1]
if strings.HasSuffix(ctx.Request.RequestURI, "png") {
contentType = "image/png"
} else if strings.HasSuffix(ctx.Request.RequestURI, "js") {
contentType = "application/javascript"
}
ctx.Header(consts.HeaderKeyContentType.String(), contentType)
ctx.String(http.StatusOK, string(byteData))
ctx.Abort()
}
}
// HandleLuckyUI ...
func (su *SwaggerUI) HandleLuckyUI() func(ctx *gin.Context) {
// su.router.StaticFS(su.baseUri+"/assets", http.FS(ydocUIFiles))

View File

@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1, shrink-to-fit=no" name="viewport">
<title>{{DOC_TITLE}}</title>
<!-- Embed elements Elements via Web Component -->
<script src="web-components.min.js"></script>
<link href="styles.min.css" rel="stylesheet">
</head>
<body>
<!--github 地址: https://github.com/stoplightio/elements-->
<elements-api apiDescriptionUrl="{{DOC_PATH}}" layout="sidebar" router="hash"/>
</body>
</html>

1
router/theme/elements/styles.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,16 +1,19 @@
// Package router ...
// Package theme ...
//
// Description : theme ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2025-08-23 09:23
package router
package theme
import "embed"
//go:embed theme/ydoc-lucky-ui/*
//go:embed ydoc-lucky-ui/*
var YdocUIFiles embed.FS
//go:embed theme/redoc-free/index.html
//go:embed redoc-free/index.html
var RedocFreeIndexContent string
//go:embed elements/*
var ElementsFiles embed.FS