diff --git a/backend/app/http/admin/routes.gen.go b/backend/app/http/admin/routes.gen.go
index d9fa8c8..560c066 100644
--- a/backend/app/http/admin/routes.gen.go
+++ b/backend/app/http/admin/routes.gen.go
@@ -116,4 +116,15 @@ func (r *Routes) Register(router fiber.Router) {
Query[UserListQuery]("query"),
))
+ router.Get("/v1/admin/users/:id", DataFunc1(
+ r.users.Show,
+ PathParam[int64]("id"),
+ ))
+
+ router.Get("/v1/admin/users/:id/articles", DataFunc2(
+ r.users.Articles,
+ PathParam[int64]("id"),
+ Query[requests.Pagination]("pagination"),
+ ))
+
}
diff --git a/backend/app/http/admin/users.go b/backend/app/http/admin/users.go
index c6469b8..2d0c845 100644
--- a/backend/app/http/admin/users.go
+++ b/backend/app/http/admin/users.go
@@ -3,6 +3,7 @@ package admin
import (
"quyun/app/models"
"quyun/app/requests"
+ "quyun/database/schemas/public/model"
"github.com/gofiber/fiber/v3"
)
@@ -22,3 +23,18 @@ func (ctl *users) List(ctx fiber.Ctx, pagination *requests.Pagination, query *Us
cond := models.Users.BuildConditionWithKey(query.Keyword)
return models.Users.List(ctx.Context(), pagination, cond)
}
+
+// Show user
+// @Router /v1/admin/users/:id [get]
+// @Bind id path
+func (ctl *users) Show(ctx fiber.Ctx, id int64) (*model.Users, error) {
+ return models.Users.GetByID(ctx.Context(), id)
+}
+
+// Articles show user bought articles
+// @Router /v1/admin/users/:id/articles [get]
+// @Bind id path
+// @Bind pagination query
+func (ctl *users) Articles(ctx fiber.Ctx, id int64, pagination *requests.Pagination) (*requests.Pager, error) {
+ return models.Posts.Bought(ctx.Context(), id, pagination)
+}
diff --git a/backend/app/models/posts.go b/backend/app/models/posts.go
index b0344e7..3888954 100644
--- a/backend/app/models/posts.go
+++ b/backend/app/models/posts.go
@@ -286,3 +286,60 @@ func (m *postsModel) BoughtStatistics(ctx context.Context, postIds []int64) (map
return resultMap, nil
}
+
+// Bought
+func (m *postsModel) Bought(ctx context.Context, userId int64, pagination *requests.Pagination) (*requests.Pager, error) {
+ pagination.Format()
+
+ // select up.price,up.created_at,p.* from user_posts up left join posts p on up.post_id = p.id where up.user_id =1
+ tbl := table.UserPosts
+ stmt := tbl.
+ SELECT(
+ tbl.Price.AS("price"),
+ tbl.CreatedAt.AS("bought_at"),
+ table.Posts.Title.AS("title"),
+ ).
+ FROM(
+ tbl.INNER_JOIN(table.Posts, table.Posts.ID.EQ(tbl.PostID)),
+ ).
+ WHERE(
+ tbl.UserID.EQ(Int64(1)),
+ ).
+ ORDER_BY(tbl.ID.DESC()).
+ LIMIT(pagination.Limit).
+ OFFSET(pagination.Offset)
+
+ m.log.Infof("sql: %s", stmt.DebugSql())
+
+ var items []struct {
+ Title string `json:"title"`
+ Price int64 `json:"price"`
+ BoughtAt time.Time `json:"bought_at"`
+ }
+
+ if err := stmt.QueryContext(ctx, db, &items); err != nil {
+ m.log.Errorf("error getting bought posts: %v", err)
+ return nil, err
+ }
+
+ // convert to model.Posts
+ var cnt struct {
+ Cnt int64
+ }
+ stmtCnt := tbl.
+ SELECT(COUNT(tbl.ID).AS("cnt")).
+ WHERE(
+ tbl.UserID.EQ(Int64(userId)),
+ )
+
+ if err := stmtCnt.QueryContext(ctx, db, &cnt); err != nil {
+ m.log.Errorf("error getting bought posts count: %v", err)
+ return nil, err
+ }
+
+ return &requests.Pager{
+ Items: items,
+ Total: cnt.Cnt,
+ Pagination: *pagination,
+ }, nil
+}
diff --git a/frontend/admin/src/api/userService.js b/frontend/admin/src/api/userService.js
index 10bb024..84fa150 100644
--- a/frontend/admin/src/api/userService.js
+++ b/frontend/admin/src/api/userService.js
@@ -19,4 +19,10 @@ export const userService = {
deleteUser(id) {
return httpClient.delete(`/admin/users/${id}`);
},
+ getUserById(id) {
+ return httpClient.get(`/admin/users/${id}`);
+ },
+ getUserArticles(userId) {
+ return httpClient.get(`/admin/users/${userId}/articles`);
+ }
}
\ No newline at end of file
diff --git a/frontend/admin/src/api/user_articles.json b/frontend/admin/src/api/user_articles.json
new file mode 100644
index 0000000..6061ccb
--- /dev/null
+++ b/frontend/admin/src/api/user_articles.json
@@ -0,0 +1,57 @@
+{
+ "page": 1,
+ "limit": 10,
+ "total": 10,
+ "items": [
+ {
+ "title": "test-title-9",
+ "price": 0,
+ "bought_at": "2025-04-11T15:08:06.629569Z"
+ },
+ {
+ "title": "test-title-8",
+ "price": 0,
+ "bought_at": "2025-04-11T15:08:06.625099Z"
+ },
+ {
+ "title": "test-title-7",
+ "price": 0,
+ "bought_at": "2025-04-11T15:08:06.62019Z"
+ },
+ {
+ "title": "test-title-6",
+ "price": 0,
+ "bought_at": "2025-04-11T15:08:06.614768Z"
+ },
+ {
+ "title": "test-title-5",
+ "price": 0,
+ "bought_at": "2025-04-11T15:08:06.610985Z"
+ },
+ {
+ "title": "test-title-4",
+ "price": 0,
+ "bought_at": "2025-04-11T15:08:06.605659Z"
+ },
+ {
+ "title": "test-title-3",
+ "price": 0,
+ "bought_at": "2025-04-11T15:08:06.602181Z"
+ },
+ {
+ "title": "test-title-2",
+ "price": 0,
+ "bought_at": "2025-04-11T15:08:06.599001Z"
+ },
+ {
+ "title": "test-title-1",
+ "price": 0,
+ "bought_at": "2025-04-11T15:08:06.594752Z"
+ },
+ {
+ "title": "test-title-0",
+ "price": 0,
+ "bought_at": "2025-04-11T15:08:06.585365Z"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/frontend/admin/src/pages/UserDetail.vue b/frontend/admin/src/pages/UserDetail.vue
new file mode 100644
index 0000000..e205fda
--- /dev/null
+++ b/frontend/admin/src/pages/UserDetail.vue
@@ -0,0 +1,127 @@
+
+
+
+ OpenID {{ user.open_id }} 状态 创建时间 {{ formatDate(user.created_at) }} 更新时间 {{ formatDate(user.updated_at) }}用户详情
+
+
{{ user.username }}
+ 购买的文章
+