169 lines
7.6 KiB
Vue
169 lines
7.6 KiB
Vue
<template>
|
||
<div class="page">
|
||
<div class="toolbar">
|
||
<el-input v-model="q" placeholder="搜索昵称" style="width: 220px" clearable @keyup.enter="search" />
|
||
<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="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="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="150">
|
||
<template #default="{ row }">{{ fmt(row.lastLoginAt) }}</template>
|
||
</el-table-column>
|
||
<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="220">
|
||
<template #default="{ row }">
|
||
<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="增加次数">
|
||
<el-input-number v-model="quotaAmount" :min="1" :max="100" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="quotaDlg = false">取消</el-button>
|
||
<el-button type="primary" @click="addQuota">确认</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<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
|
||
try {
|
||
const r = await apiGet(`/api/admin/users?page=${page.value}&limit=${pageSize}&search=${encodeURIComponent(q.value)}`)
|
||
list.value = r.items || []
|
||
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 {
|
||
await apiPost('/api/admin/ai-chat/add-quota', { openid: quotaUser.value.openid, amount: quotaAmount.value })
|
||
ElMessage.success('已增加')
|
||
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>
|