init: 毛孩子计划 小程序 + Go 后端 + 内嵌后台

- pets-fe: 微信原生小程序(首页/计划/记录/报告/社区/引导),
  服务端驱动、无假数据;弹层改用 scroll-view,打开时隐藏自定义 tabBar
- pets-be: Gin + GORM(MySQL, sundynix_ 前缀) + MinIO,统一响应/分页,
  微信 code2session 登录,provider-neutral AI(DeepSeek),go:embed React 后台
- 修复:分段选择类型不匹配(字符串 vs 数字)导致选不中

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-07-03 15:33:31 +08:00
commit 609f7d06cf
180 changed files with 15259 additions and 0 deletions
+223
View File
@@ -0,0 +1,223 @@
package handler
import (
"github.com/gin-gonic/gin"
"github.com/sundynix/pets-be/internal/middleware"
"github.com/sundynix/pets-be/internal/model"
appjwt "github.com/sundynix/pets-be/pkg/jwt"
"github.com/sundynix/pets-be/pkg/response"
)
type adminLoginReq struct {
Username string `json:"username"`
Password string `json:"password"`
}
// AdminLogin POST /api/admin/login
func (h *Handler) AdminLogin(c *gin.Context) {
var req adminLoginReq
if err := c.ShouldBindJSON(&req); err != nil {
response.FailParams(c, err.Error())
return
}
admin, err := h.svc.AdminLogin(req.Username, req.Password)
if err != nil {
response.Fail(c, 40100, "账号或密码错误")
return
}
token, err := h.jwt.Generate(admin.ID, appjwt.KindAdmin, admin.Username, admin.Role)
if err != nil {
response.FailErr(c, err)
return
}
response.OK(c, gin.H{"token": token, "admin": admin})
}
// AdminMe GET /api/admin/me
func (h *Handler) AdminMe(c *gin.Context) {
admin, err := h.svc.GetAdmin(middleware.AdminID(c))
if err != nil {
respondErr(c, err)
return
}
response.OK(c, admin)
}
// AdminStats GET /api/admin/stats
func (h *Handler) AdminStats(c *gin.Context) {
stats, err := h.svc.Stats()
if err != nil {
response.FailErr(c, err)
return
}
response.OK(c, stats)
}
type adminListReq struct {
response.PageQuery
Keyword string `form:"keyword"`
Status string `form:"status"`
}
// AdminListUsers GET /api/admin/users
func (h *Handler) AdminListUsers(c *gin.Context) {
var req adminListReq
_ = c.ShouldBindQuery(&req)
req.Normalize()
users, total, err := h.svc.ListUsers(req.Keyword, req.Offset(), req.Limit())
if err != nil {
response.FailErr(c, err)
return
}
response.OK(c, response.NewPage(users, total, req.PageQuery))
}
type disableReq struct {
Disabled bool `json:"disabled"`
}
// AdminSetUserDisabled PUT /api/admin/users/:id/disabled
func (h *Handler) AdminSetUserDisabled(c *gin.Context) {
var req disableReq
if err := c.ShouldBindJSON(&req); err != nil {
response.FailParams(c, err.Error())
return
}
if err := h.svc.SetUserDisabled(uintParam(c, "id"), req.Disabled); err != nil {
response.FailErr(c, err)
return
}
response.OK(c, gin.H{"ok": true})
}
// AdminListPets GET /api/admin/pets
func (h *Handler) AdminListPets(c *gin.Context) {
var req adminListReq
_ = c.ShouldBindQuery(&req)
req.Normalize()
pets, total, err := h.svc.ListPetsAdmin(req.Offset(), req.Limit())
if err != nil {
response.FailErr(c, err)
return
}
response.OK(c, response.NewPage(pets, total, req.PageQuery))
}
// AdminListPosts GET /api/admin/posts
func (h *Handler) AdminListPosts(c *gin.Context) {
var req adminListReq
_ = c.ShouldBindQuery(&req)
req.Normalize()
posts, total, err := h.svc.ListPostsAdmin(req.Status, req.Offset(), req.Limit())
if err != nil {
response.FailErr(c, err)
return
}
response.OK(c, response.NewPage(posts, total, req.PageQuery))
}
type postStatusReq struct {
Status string `json:"status"`
}
// AdminSetPostStatus PUT /api/admin/posts/:id/status
func (h *Handler) AdminSetPostStatus(c *gin.Context) {
var req postStatusReq
if err := c.ShouldBindJSON(&req); err != nil {
response.FailParams(c, err.Error())
return
}
if err := h.svc.SetPostStatus(uintParam(c, "id"), req.Status); err != nil {
respondErr(c, err)
return
}
response.OK(c, gin.H{"ok": true})
}
// AdminListComments GET /api/admin/comments
func (h *Handler) AdminListComments(c *gin.Context) {
var req adminListReq
_ = c.ShouldBindQuery(&req)
req.Normalize()
comments, total, err := h.svc.ListCommentsAdmin(req.Offset(), req.Limit())
if err != nil {
response.FailErr(c, err)
return
}
response.OK(c, response.NewPage(comments, total, req.PageQuery))
}
// AdminDeleteComment DELETE /api/admin/comments/:id
func (h *Handler) AdminDeleteComment(c *gin.Context) {
if err := h.svc.DeleteCommentAdmin(uintParam(c, "id")); err != nil {
response.FailErr(c, err)
return
}
response.OK(c, gin.H{"ok": true})
}
// AdminListArticles GET /api/admin/articles
func (h *Handler) AdminListArticles(c *gin.Context) {
var req adminListReq
_ = c.ShouldBindQuery(&req)
req.Normalize()
articles, total, err := h.svc.ListArticlesAdmin(req.Offset(), req.Limit())
if err != nil {
response.FailErr(c, err)
return
}
response.OK(c, response.NewPage(articles, total, req.PageQuery))
}
type articleReq struct {
ID uint `json:"id"`
Icon string `json:"icon"`
Title string `json:"title"`
Description string `json:"description"`
Content string `json:"content"`
Category string `json:"category"`
RelatedSheetType string `json:"related_sheet_type"`
Published bool `json:"published"`
}
// AdminSaveArticle POST /api/admin/articlesid>0 为更新)
func (h *Handler) AdminSaveArticle(c *gin.Context) {
var req articleReq
if err := c.ShouldBindJSON(&req); err != nil {
response.FailParams(c, err.Error())
return
}
article := &model.Article{
Icon: req.Icon, Title: req.Title, Description: req.Description, Content: req.Content,
Category: req.Category, RelatedSheetType: req.RelatedSheetType, Published: req.Published,
}
article.ID = req.ID
if err := h.svc.SaveArticle(article); err != nil {
response.FailErr(c, err)
return
}
response.OK(c, article)
}
// AdminDeleteArticle DELETE /api/admin/articles/:id
func (h *Handler) AdminDeleteArticle(c *gin.Context) {
if err := h.svc.DeleteArticle(uintParam(c, "id")); err != nil {
respondErr(c, err)
return
}
response.OK(c, gin.H{"ok": true})
}
// AdminListPro GET /api/admin/memberships
func (h *Handler) AdminListPro(c *gin.Context) {
var req adminListReq
_ = c.ShouldBindQuery(&req)
req.Normalize()
list, total, err := h.svc.ListProAdmin(req.Offset(), req.Limit())
if err != nil {
response.FailErr(c, err)
return
}
response.OK(c, response.NewPage(list, total, req.PageQuery))
}