feat: 添加仅已购用户筛选功能
This commit is contained in:
@@ -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/空=全部用户)
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -263,7 +276,9 @@ onMounted(() => {
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="pb-10 flex">
|
||||
<div class="pb-10 flex gap-3 items-center">
|
||||
<Dropdown v-model="onlyBought" :options="boughtFilterOptions" optionLabel="name" optionValue="value"
|
||||
class="w-48" @change="onOnlyBoughtChange" />
|
||||
<InputText v-model="globalFilterValue" placeholder="搜索用户..." class="flex-1" @input="onSearch" />
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user