新增积分余额显示功能,优化积分格式化逻辑,完善积分获取接口文档

This commit is contained in:
zyh
2025-08-29 23:24:26 +08:00
parent ccc022f5d1
commit 9ff0e7aa2d
4 changed files with 240 additions and 2 deletions

View File

@@ -0,0 +1,155 @@
# 用户积分余额接口文档
## 接口概述
获取当前登录用户的积分余额信息通过JWT token自动识别用户身份。
---
## 接口详情
### 基本信息
- **接口路径**: `/api/admin/accounts/me/points-balance`
- **请求方法**: `GET`
- **接口描述**: 根据token解析用户ID并获取当前用户的积分余额
- **认证方式**: JWT Bearer Token
---
## 请求参数
### Headers
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| Authorization | string | 是 | Bearer {token}JWT认证令牌 |
### 请求示例
```http
GET /api/admin/accounts/me/points-balance HTTP/1.1
Host: localhost:8080
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Content-Type: application/json
```
---
## 响应结果
### 成功响应 (200 OK)
```json
{
"userId": 12345,
"username": "agent001",
"userType": "AGENT",
"pointsBalance": 15000
}
```
### 响应字段说明
| 字段名 | 类型 | 说明 |
|--------|------|------|
| userId | Long | 用户ID |
| username | String | 用户名 |
| userType | String | 用户类型ADMIN管理员或 AGENT代理 |
| pointsBalance | Long | 积分余额(单位:积分点数) |
---
## 错误响应
### 401 未授权
```json
{
"error": "Unauthorized",
"message": "Authorization header is required"
}
```
### 400 请求错误
```json
{
"error": "Bad Request",
"message": "Invalid token: userId not found"
}
```
### 404 用户不存在
```json
{
"error": "Not Found",
"message": "用户不存在"
}
```
---
## 调用示例
### JavaScript (fetch)
```javascript
const token = 'your-jwt-token-here';
fetch('/api/admin/accounts/me/points-balance', {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
console.log('用户积分余额:', data.pointsBalance);
console.log('用户信息:', data);
})
.catch(error => {
console.error('获取积分余额失败:', error);
});
```
### curl
```bash
curl -X GET "http://localhost:8080/api/admin/accounts/me/points-balance" \
-H "Authorization: Bearer your-jwt-token-here" \
-H "Content-Type: application/json"
```
### Java (Spring WebFlux)
```java
WebClient webClient = WebClient.create("http://localhost:8080");
Mono<PointsBalanceResponse> response = webClient
.get()
.uri("/api/admin/accounts/me/points-balance")
.header("Authorization", "Bearer " + jwtToken)
.retrieve()
.bodyToMono(PointsBalanceResponse.class);
response.subscribe(
pointsBalance -> System.out.println("积分余额: " + pointsBalance.getPointsBalance()),
error -> System.err.println("请求失败: " + error.getMessage())
);
```
---
## 注意事项
1. **Token有效性**: JWT token必须有效且未过期
2. **用户类型**:
- ADMIN用户的积分余额通常为0
- AGENT用户才有实际的积分余额
3. **权限控制**: 只能查询当前登录用户自己的积分余额
4. **数据格式**: 积分余额以长整型返回,单位为积分点数
---
## 状态码说明
| 状态码 | 说明 |
|--------|------|
| 200 | 请求成功,返回积分余额信息 |
| 400 | 请求参数错误或token无效 |
| 401 | 未提供认证信息或认证失败 |
| 404 | 用户不存在 |
| 500 | 服务器内部错误 |

15
src/api/points.js Normal file
View File

@@ -0,0 +1,15 @@
import http from '@/plugins/http'
/**
* 获取当前用户的积分余额
* @returns {Promise<Object>} 包含用户信息和积分余额的对象
*/
export async function getPointsBalance() {
try {
const response = await http.get('/admin/accounts/me/points-balance')
return response.data
} catch (error) {
console.error('获取积分余额失败:', error)
throw error
}
}

View File

@@ -60,9 +60,13 @@
<!-- 移动端显示当前页面标题 -->
<span v-if="isMobile" class="mobile-page-title">{{ currentPageTitle }}</span>
<div v-if="!isMobile" class="spacer" />
<!-- 积分显示 -->
<span v-if="isAgent() && pointsBalance !== null" class="points-display">
积分: {{ formatPoints(pointsBalance) }}
</span>
<el-dropdown>
<span class="el-dropdown-link">
<span>{{ currentUser?.username || '用户' }}</span>
<span class="username">{{ currentUser?.username || '用户' }}</span>
<i class="el-icon el-icon--right"><svg width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="M7 10l5 5 5-5z"/></svg></i>
</span>
<template #dropdown>
@@ -85,10 +89,13 @@
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { clearTokens } from '@/utils/auth'
import { useRouter, useRoute } from 'vue-router'
import { canAccessRoute, getCurrentUser } from '@/utils/permission'
import { canAccessRoute, getCurrentUser, isAgent } from '@/utils/permission'
import { getPointsBalance } from '@/api/points'
import { formatPoints } from '@/utils/points'
const collapsed = ref(false)
const isMobile = ref(false)
const pointsBalance = ref(null)
const router = useRouter()
const route = useRoute()
@@ -125,6 +132,19 @@ const canAccessRefund = computed(() => canAccessRoute('Refund'))
const canAccessAnnouncements = computed(() => canAccessRoute('Announcements'))
const canAccessSettings = computed(() => canAccessRoute('Settings'))
// 获取积分余额
const fetchPointsBalance = async () => {
if (!isAgent()) return // 只有代理商才需要显示积分
try {
const data = await getPointsBalance()
pointsBalance.value = data.pointsBalance
} catch (error) {
console.error('获取积分余额失败:', error)
pointsBalance.value = null
}
}
// 切换侧边栏
const toggleSidebar = () => {
collapsed.value = !collapsed.value
@@ -150,6 +170,7 @@ function onLogout() {
onMounted(() => {
checkMobile()
window.addEventListener('resize', checkMobile)
fetchPointsBalance() // 页面加载时获取积分余额
})
onUnmounted(() => {
@@ -267,6 +288,13 @@ onUnmounted(() => {
color: #303133;
}
.points-display {
font-size: 16px;
color: #409eff;
font-weight: 500;
margin-right: 16px;
}
.el-dropdown-link {
cursor: pointer;
display: flex;
@@ -280,6 +308,10 @@ onUnmounted(() => {
color: #409eff;
}
.username {
font-weight: 500;
}
.content {
padding: 16px;
overflow: auto;
@@ -310,11 +342,20 @@ onUnmounted(() => {
font-size: 15px;
}
.points-display {
font-size: 14px;
margin-right: 12px;
}
.el-dropdown-link {
font-size: 16px;
min-height: var(--mobile-touch-target);
padding: 8px;
}
.username {
font-size: 16px;
}
}
@media (max-width: 480px) {
@@ -342,6 +383,11 @@ onUnmounted(() => {
white-space: nowrap;
}
.points-display {
font-size: 13px;
margin-right: 8px;
}
.el-dropdown-link {
font-size: 14px;
padding: 6px;
@@ -353,6 +399,10 @@ onUnmounted(() => {
text-overflow: ellipsis;
white-space: nowrap;
}
.username {
font-size: 14px;
}
}
/* 桌面端样式 */

18
src/utils/points.js Normal file
View File

@@ -0,0 +1,18 @@
/**
* 格式化积分显示
* @param {number} points 积分数量
* @returns {string} 格式化后的积分字符串
*/
export function formatPoints(points) {
if (points === null || points === undefined) {
return '0'
}
// 如果积分大于等于10000显示万为单位
if (points >= 10000) {
return (points / 10000).toFixed(1) + '万'
}
// 否则直接显示数字
return points.toLocaleString()
}