新增积分余额显示功能,优化积分格式化逻辑,完善积分获取接口文档
This commit is contained in:
155
docs/代理商积分获取接口.md
Normal file
155
docs/代理商积分获取接口.md
Normal 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
15
src/api/points.js
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -60,9 +60,13 @@
|
|||||||
<!-- 移动端显示当前页面标题 -->
|
<!-- 移动端显示当前页面标题 -->
|
||||||
<span v-if="isMobile" class="mobile-page-title">{{ currentPageTitle }}</span>
|
<span v-if="isMobile" class="mobile-page-title">{{ currentPageTitle }}</span>
|
||||||
<div v-if="!isMobile" class="spacer" />
|
<div v-if="!isMobile" class="spacer" />
|
||||||
|
<!-- 积分显示 -->
|
||||||
|
<span v-if="isAgent() && pointsBalance !== null" class="points-display">
|
||||||
|
积分: {{ formatPoints(pointsBalance) }}
|
||||||
|
</span>
|
||||||
<el-dropdown>
|
<el-dropdown>
|
||||||
<span class="el-dropdown-link">
|
<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>
|
<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>
|
</span>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
@@ -85,10 +89,13 @@
|
|||||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||||
import { clearTokens } from '@/utils/auth'
|
import { clearTokens } from '@/utils/auth'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
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 collapsed = ref(false)
|
||||||
const isMobile = ref(false)
|
const isMobile = ref(false)
|
||||||
|
const pointsBalance = ref(null)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
@@ -125,6 +132,19 @@ const canAccessRefund = computed(() => canAccessRoute('Refund'))
|
|||||||
const canAccessAnnouncements = computed(() => canAccessRoute('Announcements'))
|
const canAccessAnnouncements = computed(() => canAccessRoute('Announcements'))
|
||||||
const canAccessSettings = computed(() => canAccessRoute('Settings'))
|
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 = () => {
|
const toggleSidebar = () => {
|
||||||
collapsed.value = !collapsed.value
|
collapsed.value = !collapsed.value
|
||||||
@@ -150,6 +170,7 @@ function onLogout() {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
checkMobile()
|
checkMobile()
|
||||||
window.addEventListener('resize', checkMobile)
|
window.addEventListener('resize', checkMobile)
|
||||||
|
fetchPointsBalance() // 页面加载时获取积分余额
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
@@ -267,6 +288,13 @@ onUnmounted(() => {
|
|||||||
color: #303133;
|
color: #303133;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.points-display {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #409eff;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.el-dropdown-link {
|
.el-dropdown-link {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -280,6 +308,10 @@ onUnmounted(() => {
|
|||||||
color: #409eff;
|
color: #409eff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.username {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
@@ -310,11 +342,20 @@ onUnmounted(() => {
|
|||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.points-display {
|
||||||
|
font-size: 14px;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.el-dropdown-link {
|
.el-dropdown-link {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
min-height: var(--mobile-touch-target);
|
min-height: var(--mobile-touch-target);
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.username {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
@@ -342,6 +383,11 @@ onUnmounted(() => {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.points-display {
|
||||||
|
font-size: 13px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.el-dropdown-link {
|
.el-dropdown-link {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
@@ -353,6 +399,10 @@ onUnmounted(() => {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.username {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 桌面端样式 */
|
/* 桌面端样式 */
|
||||||
|
|||||||
18
src/utils/points.js
Normal file
18
src/utils/points.js
Normal 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()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user