fix(admin): add wxOpenid to fuzzy search, enhance admin tab info

- Backend getUsers: add wxOpenid to  regex search
- Backend getAdmins: return gravity, plan, wxOpenid, email fields
- Frontend admin tab: show full user info (ID copy, email, gravity, plan)
- Frontend search result: show complete user details like user list rows
This commit is contained in:
yuzhiran
2026-06-22 12:07:52 +08:00
parent d37bbd7a61
commit 70c4f28eb5
2 changed files with 45 additions and 17 deletions
@@ -86,6 +86,7 @@ export class AdminController {
{ phone: { $regex: escaped, $options: 'i' } }, { phone: { $regex: escaped, $options: 'i' } },
{ nickname: { $regex: escaped, $options: 'i' } }, { nickname: { $regex: escaped, $options: 'i' } },
{ email: { $regex: escaped, $options: 'i' } }, { email: { $regex: escaped, $options: 'i' } },
{ wxOpenid: { $regex: escaped, $options: 'i' } },
] ]
// 支持按 MongoDB _id 搜索(24位 hex // 支持按 MongoDB _id 搜索(24位 hex
if (/^[0-9a-f]{24}$/i.test(keyword)) { if (/^[0-9a-f]{24}$/i.test(keyword)) {
@@ -250,7 +251,7 @@ export class AdminController {
@Get('admins') @Get('admins')
async getAdmins() { async getAdmins() {
const admins = await this.userModel.find({ role: 'admin' }).select('phone nickname email createdAt isSystemAdmin').lean().exec() const admins = await this.userModel.find({ role: 'admin' }).select('phone nickname email wxOpenid gravity plan role createdAt isSystemAdmin').lean().exec()
return { admins } return { admins }
} }
+40 -13
View File
@@ -491,27 +491,57 @@
</view> </view>
<view class="section-label">当前管理员</view> <view class="section-label">当前管理员</view>
<view class="user-list"> <view class="user-list">
<view class="admin-row" v-for="a in adminList" :key="a._id"> <view class="user-row" v-for="a in adminList" :key="a._id">
<text class="admin-phone">{{ a.phone || '--' }}</text> <view class="user-main">
<text class="admin-name">{{ a.nickname || '--' }}</text> <text class="user-phone">{{ a.phone || '--' }}</text>
<text class="admin-email" v-if="a.email">{{ a.email }}</text> <text class="user-name">{{ a.nickname || '--' }}</text>
<text class="user-badge-role">管理</text>
<text class="admin-badge" v-if="a.isSystemAdmin">系统</text> <text class="admin-badge" v-if="a.isSystemAdmin">系统</text>
<text class="time-label" style="margin-left:auto">设置:{{ a.createdAt?.slice(0,10) }}</text> </view>
<view class="user-meta-row">
<text class="meta-tag email" v-if="a.email">{{ a.email }}</text>
<text class="meta-tag" v-if="a.wxOpenid">openid:{{ a.wxOpenid.slice(0,12) }}..</text>
</view>
<view class="user-meta-row">
<text class="meta-tag id-tag" @click="copyId(a._id)">ID: {{ a._id }}</text>
</view>
<view class="user-meta-row">
<text class="meta-tag">引力:{{ a.gravity ?? 0 }}</text>
<text class="user-plan" :class="{ vip: a.plan === 'growth' || a.plan === 'sprint' }">{{ a.plan === 'growth' || a.plan === 'sprint' ? a.plan==='sprint'?'冲刺':'会员' : '免费' }}</text>
</view>
<view class="user-meta-row time-row">
<text class="time-label">设置:{{ a.createdAt?.slice(0,16).replace('T',' ') }}</text>
</view>
</view> </view>
<text class="empty-text" v-if="adminList.length === 0">暂无管理员</text> <text class="empty-text" v-if="adminList.length === 0">暂无管理员</text>
</view> </view>
<view class="section-label" v-if="searchResult">搜索结果</view> <view class="section-label" v-if="searchResult" style="margin-top:24rpx">搜索结果</view>
<view class="user-list" v-if="searchResult"> <view class="user-list" v-if="searchResult">
<view class="admin-row"> <view class="user-row">
<text class="admin-phone">{{ searchResult.phone || '--' }}</text> <view class="user-main">
<text class="admin-name">{{ searchResult.nickname || '--' }}</text> <text class="user-phone">{{ searchResult.phone || '--' }}</text>
<text class="admin-email" v-if="searchResult.email">{{ searchResult.email }}</text> <text class="user-name">{{ searchResult.nickname || '--' }}</text>
<text class="user-badge-role" v-if="searchResult.role === 'admin'">管理</text>
</view>
<view class="user-meta-row">
<text class="meta-tag email" v-if="searchResult.email">{{ searchResult.email }}</text>
<text class="meta-tag" v-if="searchResult.wxOpenid">openid:{{ searchResult.wxOpenid.slice(0,12) }}..</text>
</view>
<view class="user-meta-row">
<text class="meta-tag id-tag" @click="copyId(searchResult._id)">ID: {{ searchResult._id }}</text>
</view>
<view class="user-meta-row">
<text class="meta-tag">引力:{{ searchResult.gravity ?? 0 }}</text>
<text class="user-plan" :class="{ vip: searchResult.plan === 'growth' || searchResult.plan === 'sprint' }">{{ searchResult.plan === 'growth' || searchResult.plan === 'sprint' ? searchResult.plan==='sprint'?'冲刺':'会员' : '免费' }}</text>
</view>
<view class="user-actions" style="margin-top:8rpx">
<text class="admin-set-btn" v-if="searchResult.role !== 'admin'" @click="setAdmin(searchResult._id)">设为管理员</text> <text class="admin-set-btn" v-if="searchResult.role !== 'admin'" @click="setAdmin(searchResult._id)">设为管理员</text>
<text class="admin-set-btn done" v-else>已是管理员</text> <text class="admin-set-btn done" v-else>已是管理员</text>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
</view>
</template> </template>
<script setup> <script setup>
@@ -1090,9 +1120,6 @@ onMounted(() => { doVerify() })
.iv-tag.score { background: #EEF2FF; color: var(--color-primary); } .iv-tag.score { background: #EEF2FF; color: var(--color-primary); }
.iv-tag.filler { background: #FFF7ED; color: #D97706; } .iv-tag.filler { background: #FFF7ED; color: #D97706; }
.section-label { font-size: 24rpx; font-weight: 600; color: var(--color-text); margin-bottom: 12rpx; margin-top: 12rpx; } .section-label { font-size: 24rpx; font-weight: 600; color: var(--color-text); margin-bottom: 12rpx; margin-top: 12rpx; }
.admin-row { background: #FFF; padding: 20rpx; border-radius: var(--radius-sm); margin-bottom: 8rpx; display: flex; flex-wrap: wrap; gap: 8rpx; align-items: center; }
.admin-phone { font-size: 24rpx; font-weight: 600; color: var(--color-text); }
.admin-name { font-size: 22rpx; color: var(--color-text-secondary); flex: 1; }
.admin-set-btn { font-size: 22rpx; color: var(--color-primary); padding: 4rpx 16rpx; border: 2rpx solid var(--color-primary); border-radius: var(--radius-round); } .admin-set-btn { font-size: 22rpx; color: var(--color-primary); padding: 4rpx 16rpx; border: 2rpx solid var(--color-primary); border-radius: var(--radius-round); }
.admin-set-btn.done { color: var(--color-success); border-color: var(--color-success); } .admin-set-btn.done { color: var(--color-success); border-color: var(--color-success); }
.admin-badge { font-size: 18rpx; background: var(--color-primary); color: #FFF; padding: 2rpx 10rpx; border-radius: var(--radius-round); } .admin-badge { font-size: 18rpx; background: var(--color-primary); color: #FFF; padding: 2rpx 10rpx; border-radius: var(--radius-round); }