feat: 用户管理页增强——星尘余额列+调整星尘按钮(免搜openid)+查看订单弹窗+保留加额度

This commit is contained in:
2026-09-14 13:51:25 +08:00
parent b5d6071a3e
commit e307fe2e50
+103 -9
View File
@@ -5,30 +5,75 @@
<el-button type="primary" @click="search">搜索</el-button>
</div>
<el-table :data="list" stripe v-loading="loading">
<el-table-column prop="nickName" label="昵称" min-width="110" />
<el-table-column label="OpenID" min-width="150">
<template #default="{ row }">{{ (row.openid || '').slice(0, 14) }}…</template>
<el-table-column prop="nickName" label="昵称" min-width="100" />
<el-table-column label="OpenID" min-width="130">
<template #default="{ row }">{{ (row.openid || '').slice(0, 12) }}…</template>
</el-table-column>
<el-table-column label="注册时间" width="160">
<el-table-column label="星尘" width="70" align="center">
<template #default="{ row }">
<span :style="{ fontWeight: 700, color: (row.vpayCoins || 0) > 0 ? '#5b5ff8' : '#999' }">{{ row.vpayCoins || 0 }}</span>
</template>
</el-table-column>
<el-table-column label="注册时间" width="150">
<template #default="{ row }">{{ fmt(row.createdAt) }}</template>
</el-table-column>
<el-table-column label="最近登录" width="160">
<el-table-column label="最近登录" width="150">
<template #default="{ row }">{{ fmt(row.lastLoginAt) }}</template>
</el-table-column>
<el-table-column label="状态" width="80">
<el-table-column label="状态" width="60">
<template #default="{ row }"><el-tag :type="(row.status || 'active') === 'active' ? 'success' : 'info'" size="small">{{ (row.status || 'active') === 'active' ? '正常' : row.status }}</el-tag></template>
</el-table-column>
<el-table-column label="操作" width="110">
<el-table-column label="操作" width="220">
<template #default="{ row }">
<el-button size="small" @click="openQuota(row)">加问答额度</el-button>
<el-button size="small" type="primary" @click="openAdjust(row)">调整星尘</el-button>
<el-button size="small" @click="viewOrders(row)">订单</el-button>
<el-button size="small" @click="openQuota(row)">加额度</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination background layout="total, prev, pager, next" :total="total" :page-size="pageSize" v-model:current-page="page" @current-change="load" style="margin-top: 12px" />
<!-- 调整星尘 -->
<el-dialog v-model="adjustDlg" title="调整星尘余额" width="min(400px, 92%)" style="max-width: 400px">
<div class="adjust-user-info">
<b>{{ adjustUser?.nickName || '未命名' }}</b>
<span>当前星尘:{{ adjustUser?.vpayCoins || 0 }}</span>
</div>
<el-form label-width="70px" style="margin-top: 12px">
<el-form-item label="变动值">
<el-input-number v-model="adjustAmount" :min="-100000" :max="100000" />
<span class="adjust-hint">正=赠送 负=扣减</span>
</el-form-item>
<el-form-item label="原因"><el-input v-model="adjustReason" placeholder="如:活动赠送/异常补偿" /></el-form-item>
</el-form>
<template #footer>
<el-button @click="adjustDlg = false">取消</el-button>
<el-button type="primary" @click="doAdjust">确认</el-button>
</template>
</el-dialog>
<!-- 用户订单 -->
<el-dialog v-model="ordersDlg" :title="(ordersUser?.nickName || '用户') + '的订单'" width="min(520px, 94%)" style="max-width: 520px">
<el-table :data="ordersList" stripe v-loading="ordersLoading" size="small">
<el-table-column prop="orderNo" label="订单号" min-width="180" show-overflow-tooltip />
<el-table-column prop="productName" label="商品" width="100" />
<el-table-column label="金额" width="60"><template #default="{ row }">"¥" + row.amount</template></el-table-column>
<el-table-column label="状态" width="70">
<template #default="{ row }">
<el-tag :type="row.status === 'paid' ? 'success' : row.status === 'refunded' ? 'danger' : 'warning'" size="small">
{{ row.status === 'paid' ? '已支付' : row.status === 'refunded' ? '已退款' : row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="时间" width="140"><template #default="{ row }">{{ fmt(row.createdAt) }}</template></el-table-column>
</el-table>
<p v-if="!ordersLoading && !ordersList.length" style="text-align:center;color:#999;padding:20px 0">暂无订单</p>
</el-dialog>
<!-- 加问答额度(保留原有) -->
<el-dialog v-model="quotaDlg" title="增加问答额度" width="min(360px, 92%)" style="max-width: 360px">
<el-form label-width="90px">
<el-form-item label="用户">{{ quotaUser?.nickName }}</el-form-item>
<el-form-item label="OpenID">{{ (quotaUser?.openid || '').slice(0, 16) }}…</el-form-item>
<el-form-item label="增加次数">
<el-input-number v-model="quotaAmount" :min="1" :max="100" />
</el-form-item>
@@ -44,10 +89,15 @@
<script setup>
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { apiGet, apiPost } from '../api'
const list = ref([]), total = ref(0), page = ref(1), pageSize = 20, q = ref(''), loading = ref(false)
const quotaDlg = ref(false), quotaUser = ref(null), quotaAmount = ref(5)
const adjustDlg = ref(false), adjustUser = ref(null), adjustAmount = ref(100), adjustReason = ref('')
function fmt(t) { return t ? new Date(t).toLocaleString('zh-CN', { hour12: false }) : '—' }
function search() { page.value = 1; load() }
async function load() {
loading.value = true
@@ -57,6 +107,42 @@ async function load() {
total.value = r.pagination?.total || 0
} finally { loading.value = false }
}
// 调整星尘
function openAdjust(row) {
adjustUser.value = row
adjustAmount.value = 100
adjustReason.value = ''
adjustDlg.value = true
}
async function doAdjust() {
if (!adjustUser.value) return
try {
await apiPost('/api/admin/vpay/adjust', {
openid: adjustUser.value.openid,
amount: adjustAmount.value,
reason: adjustReason.value || '管理员调整',
})
ElMessage.success(`已${adjustAmount.value > 0 ? '赠送' : '扣减'} ${Math.abs(adjustAmount.value)} 星尘`)
adjustDlg.value = false
load() // 刷新列表更新余额列
} catch (e) { ElMessage.error(e.message || '调整失败') }
}
// 查看该用户的订单(弹窗展示)
const ordersDlg = ref(false), ordersUser = ref(null), ordersList = ref([]), ordersLoading = ref(false)
async function viewOrders(row) {
ordersUser.value = row
ordersDlg.value = true
ordersLoading.value = true
try {
const r = await apiGet(`/api/admin/vpay/orders?keyword=${encodeURIComponent(row.openid)}&limit=20`)
ordersList.value = r.items || []
} catch (e) { ordersList.value = [] }
finally { ordersLoading.value = false }
}
// 加问答额度(保留原有)
function openQuota(row) { quotaUser.value = row; quotaAmount.value = 5; quotaDlg.value = true }
async function addQuota() {
try {
@@ -65,10 +151,18 @@ async function addQuota() {
quotaDlg.value = false
} catch (e) { ElMessage.error(e.message || '操作失败') }
}
onMounted(load)
</script>
<style scoped>
.page { background: #fff; border-radius: 12px; padding: 16px; box-shadow: 0 1px 4px rgba(0,0,0,.06); }
.toolbar { display: flex; gap: 8px; margin-bottom: 12px; }
.adjust-user-info {
display: flex; align-items: center; gap: 12px;
background: #f8f9fb; border-radius: 8px; padding: 10px 14px;
}
.adjust-user-info b { font-size: 15px; color: #1f2333; }
.adjust-user-info span { font-size: 13px; color: #5b5ff8; font-weight: 600; }
.adjust-hint { margin-left: 8px; font-size: 11px; color: #999; }
</style>