From 661a595fa7418bcf620d2add298960e9c795b114 Mon Sep 17 00:00:00 2001 From: Rogee Date: Sat, 20 Dec 2025 23:30:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E4=BB=85=E5=B7=B2?= =?UTF-8?q?=E8=B4=AD=E7=94=A8=E6=88=B7=E7=AD=9B=E9=80=89=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend_v1/app/http/dto/user.go | 4 ++- backend_v1/app/services/users.go | 47 +++++++++++++++++++++++++-- frontend/admin/src/api/userService.js | 5 +-- frontend/admin/src/pages/UserPage.vue | 19 +++++++++-- 4 files changed, 67 insertions(+), 8 deletions(-) diff --git a/backend_v1/app/http/dto/user.go b/backend_v1/app/http/dto/user.go index fdd97ca..ff78e99 100644 --- a/backend_v1/app/http/dto/user.go +++ b/backend_v1/app/http/dto/user.go @@ -4,5 +4,7 @@ import "quyun/v2/app/requests" type UserListQuery struct { *requests.Pagination - Keyword *string `query:"keyword"` + Keyword *string `query:"keyword"` // 关键词(模糊匹配手机号/用户名;若为数字且>0,则同时按用户 ID 精确匹配) + + OnlyBought *bool `query:"onlyBought"` // 是否仅返回“购买数量>0”的用户(true=仅已购用户;false/空=全部用户) } diff --git a/backend_v1/app/services/users.go b/backend_v1/app/services/users.go index becd6f3..0cbff36 100644 --- a/backend_v1/app/services/users.go +++ b/backend_v1/app/services/users.go @@ -50,16 +50,57 @@ func (m *users) List( query = query.Order(tbl.ID.Desc()) + keyword := "" if filter.Keyword != nil && *filter.Keyword != "" { + keyword = strings.TrimSpace(*filter.Keyword) query = query. - Where(tbl.Phone.Like(database.WrapLike(*filter.Keyword))). - Or(tbl.Username.Like(database.WrapLike(*filter.Keyword))) + Where(tbl.Phone.Like(database.WrapLike(keyword))). + Or(tbl.Username.Like(database.WrapLike(keyword))) - if id, err := strconv.ParseInt(strings.TrimSpace(*filter.Keyword), 10, 64); err == nil && id > 0 { + if id, err := strconv.ParseInt(keyword, 10, 64); err == nil && id > 0 { query = query.Or(tbl.ID.Eq(id)) } } + if filter.OnlyBought != nil && *filter.OnlyBought { + // 仅返回“购买数量>0”的用户:通过 JOIN user_posts 做存在性过滤。 + // 注意:FindByPage 内部用 Count(),在 GROUP BY 场景下可能不准确,这里改为手动 Count(DISTINCT users.id)。 + tblUserPost, _ := models.UserPostQuery.QueryContext(ctx) + query = query.Join(tblUserPost, tbl.ID.EqCol(tblUserPost.UserID)).Group(tbl.ID) + + offset := int(filter.Pagination.Offset()) + limit := int(filter.Pagination.Limit) + + items, err := query.Offset(offset).Limit(limit).Find() + if err != nil { + return nil, errors.Wrap(err, "query users error") + } + + db := _db.WithContext(ctx).Model(&models.User{}). + Joins("JOIN user_posts ON user_posts.user_id = users.id") + if keyword != "" { + like := database.WrapLike(keyword) + args := []any{like, like} + where := "(users.phone LIKE ? OR users.username LIKE ?)" + if id, err := strconv.ParseInt(keyword, 10, 64); err == nil && id > 0 { + where = "(users.phone LIKE ? OR users.username LIKE ? OR users.id = ?)" + args = append(args, id) + } + db = db.Where(where, args...) + } + + var cnt int64 + if err := db.Distinct("users.id").Count(&cnt).Error; err != nil { + return nil, errors.Wrap(err, "count users error") + } + + return &requests.Pager{ + Items: items, + Total: cnt, + Pagination: *filter.Pagination, + }, nil + } + items, cnt, err := query.FindByPage(int(filter.Pagination.Offset()), int(filter.Pagination.Limit)) if err != nil { return nil, errors.Wrap(err, "query users error") diff --git a/frontend/admin/src/api/userService.js b/frontend/admin/src/api/userService.js index 157962c..e65af2c 100644 --- a/frontend/admin/src/api/userService.js +++ b/frontend/admin/src/api/userService.js @@ -1,12 +1,13 @@ import httpClient from './httpClient'; export const userService = { - getUsers({ page = 1, limit = 10, keyword = '' } = {}) { + getUsers({ page = 1, limit = 10, keyword = '', onlyBought = false } = {}) { return httpClient.get('/users', { params: { page, limit, - keyword: keyword.trim() + keyword: keyword.trim(), + onlyBought } }); }, diff --git a/frontend/admin/src/pages/UserPage.vue b/frontend/admin/src/pages/UserPage.vue index 11ec802..c84cd55 100644 --- a/frontend/admin/src/pages/UserPage.vue +++ b/frontend/admin/src/pages/UserPage.vue @@ -7,6 +7,7 @@ import Column from 'primevue/column'; import ConfirmDialog from 'primevue/confirmdialog'; import DataTable from 'primevue/datatable'; import Dialog from 'primevue/dialog'; +import Dropdown from 'primevue/dropdown'; import InputText from 'primevue/inputtext'; import ProgressSpinner from 'primevue/progressspinner'; import Toast from 'primevue/toast'; @@ -25,6 +26,12 @@ const filters = ref({ status: { value: null, matchMode: 'equals' } }); +const boughtFilterOptions = ref([ + { name: '全部用户', value: false }, + { name: '仅已购用户', value: true } +]); +const onlyBought = ref(false); + // Table state const users = ref({ items: [], @@ -58,7 +65,8 @@ const fetchUsers = async () => { const response = await userService.getUsers({ page: currentPage, limit: rows.value, - keyword: globalFilterValue.value + keyword: globalFilterValue.value, + onlyBought: onlyBought.value }); users.value = response.data; } catch (error) { @@ -188,6 +196,11 @@ const savePhone = async () => { onMounted(() => { fetchUsers(); }); + +const onOnlyBoughtChange = () => { + first.value = 0; + fetchUsers(); +};