用户列表渲染完毕

This commit is contained in:
WindowBird 2025-08-19 14:01:32 +08:00
parent 1942e317bc
commit f82e8c1aa9
5 changed files with 448 additions and 226 deletions

View File

@ -1,78 +1,21 @@
# 用户API模块 # 用户模块 API 文档
## 概述 ## 接口列表
用户API模块提供了用户信息、财务数据和统计信息的获取功能。
## API列表
### 1. 获取用户信息 ### 1. 获取用户信息
```javascript ```javascript
import { getUserInfo } from '@/api/user/user.js' import { getUserInfo } from '@/api/user/user.js'
// 获取用户基本信息
const response = await getUserInfo() const response = await getUserInfo()
if (response.code === 200) {
const userInfo = response.data
// userInfo包含userId, nickName, phonenumber, avatar等
}
``` ```
### 2. 获取财务数据 **接口地址:** `GET /app/user/info`
```javascript
import { getUserFinancialData } from '@/api/user/user.js'
// 获取用户财务信息 **响应数据:**
const response = await getUserFinancialData()
if (response.code === 200) {
const financialData = response.data
// financialData包含balance, waitBalance, withdrawBalance, withdrawedBalance等
}
```
### 3. 获取代理统计
```javascript
import { getAgentCount } from '@/api/user/user.js'
// 获取代理统计信息
const response = await getAgentCount()
if (response.code === 200) {
const agentStats = response.data
// agentStats包含userNum, deviceNum, rentAmount等
}
```
### 4. 获取用户列表
```javascript
import { getUserList } from '@/api/user/user.js'
// 获取用户列表
const response = await getUserList()
if (response.code === 200) {
const userList = response.data
// userList包含用户列表数据
}
```
### 5. 更新用户信息
```javascript
import { updateUserInfo } from '@/api/user/user.js'
// 更新用户信息
const userData = {
nickName: '新昵称',
phonenumber: '13800138000'
}
const response = await updateUserInfo(userData)
```
## 数据结构
### 用户信息 (UserInfo)
```javascript ```javascript
{ {
userId: '1', // 用户ID userId: '1', // 用户ID
nickName: '超级管理员', // 昵称 nickName: '超级管理员', // 用户昵称
phonenumber: '15888888888', // 手机号 phonenumber: '15888888888', // 手机号
avatar: 'https://...', // 头像URL avatar: 'https://...', // 头像URL
email: null, // 邮箱 email: null, // 邮箱
@ -83,17 +26,35 @@ const response = await updateUserInfo(userData)
} }
``` ```
### 财务数据 (FinancialData) ### 2. 获取用户财务数据
```javascript
import { getUserFinancialData } from '@/api/user/user.js'
const response = await getUserFinancialData()
```
**接口地址:** `GET /app/user/financial`
**响应数据:**
```javascript ```javascript
{ {
balance: 10000.00, // 可提现金额 balance: 10000.00, //
waitBalance: 0, // 待入账金额 waitBalance: 0, // 待结算余
withdrawBalance: 0, // 提现中金额 withdrawBalance: 0, // 可提现余
withdrawedBalance: 0 // 已提现金额 withdrawedBalance: 0 // 已提现
} }
``` ```
### 代理统计 (AgentStats) ### 3. 获取代理统计
```javascript
import { getAgentCount } from '@/api/user/user.js'
const response = await getAgentCount()
```
**接口地址:** `GET /app/order/agentCount`
**响应数据:**
```javascript ```javascript
{ {
userNum: 4, // 名下用户数 userNum: 4, // 名下用户数
@ -102,84 +63,135 @@ const response = await updateUserInfo(userData)
} }
``` ```
### 用户列表 (UserList) ### 4. 获取代理用户列表
```javascript
import { getAgentList } from '@/api/user/user.js'
const response = await getAgentList({
beginTime: '2024-10-10 12:10:00', // 开始时间(筛选)
endTime: '2026-10-10 12:10:00', // 结束时间(筛选)
name: '李' // 用户昵称(搜索)
})
```
**接口地址:** `GET /app/order/agentList`
**请求参数:**
- `beginTime` (string, 可选): 开始时间,格式: YYYY-MM-DD HH:mm:ss
- `endTime` (string, 可选): 结束时间,格式: YYYY-MM-DD HH:mm:ss
- `name` (string, 可选): 用户昵称,支持模糊搜索
**响应数据:**
```javascript ```javascript
[ [
{ {
id: 1, // 用户ID userId: '28', // 用户ID
username: '张三', // 用户名 nickName: '李四', // 用户昵称
totalAmount: '5000', // 总金额 avatar: '', // 头像
deviceCount: 2, // 设备数量 totalAmount: 730.00, // 总金额
devices: [ // 设备列表 deviceNum: 2, // 设备数量
orders: [ // 订单列表
{ {
type: '智能门锁', // 设备类型 id: '3', // 订单ID
amount: '1000', // 金额 userId: '28', // 用户ID
rentDate: '2025.01.15', // 租赁日期 name: '派大星', // 联系人姓名
period: '1年', // 租赁周期 phone: '13777777777', // 联系电话
expiryDate: '2026.01.15' // 到期日期 address: '广西南宁市西乡塘区', // 地址
detailed: '详细地址', // 详细地址
typeName: '单头灶', // 设备类型名称
suitName: '一年', // 套餐名称
suitDay: '365', // 套餐天数
amount: 365.00, // 金额
status: '2', // 订单状态
leaseTime: '2025-08-15 10:50:22', // 租赁时间
expirationTime: '2025-11-15 10:50:25', // 到期时间
orderNumber: '123456789101114' // 订单号
} }
] ]
} }
] ]
``` ```
### 5. 更新用户信息
```javascript
import { updateUserInfo } from '@/api/user/user.js'
const response = await updateUserInfo({
nickName: '新昵称',
email: 'new@example.com'
})
```
**接口地址:** `PUT /app/user/info`
## 使用示例
### 在页面中获取用户数据
```javascript
// 在 Profile 页面中
async fetchUserData() {
try {
const [userInfo, financial, stats] = await Promise.all([
getUserInfo(),
getUserFinancialData(),
getAgentCount()
])
this.userInfo = userInfo.data
this.financialData = financial.data
this.userStats = stats.data
} catch (error) {
console.error('获取用户数据失败:', error)
}
}
```
### 在用户列表页面中
```javascript
// 在用户列表页面中
async fetchUserList() {
try {
const params = {
beginTime: '2024-10-10 00:00:00',
endTime: '2026-10-10 23:59:59',
name: '李'
}
const response = await getAgentList(params)
if (response.code === 200) {
// 转换数据格式
this.users = response.data.map(user => ({
...user,
devices: user.orders.map(order => ({
type: order.typeName,
amount: order.amount,
rentDate: this.formatDate(order.leaseTime),
period: order.suitName,
expiryDate: this.formatDate(order.expirationTime)
}))
}))
}
} catch (error) {
console.error('获取用户列表失败:', error)
}
}
```
## 错误处理 ## 错误处理
所有API都包含错误处理机制 所有API函数都包含错误处理机制如果API调用失败会自动返回模拟数据
1. **网络错误**:自动使用模拟数据 ```javascript
2. **API错误**:返回错误信息 // 如果API调用失败会返回模拟数据
3. **数据验证**:确保返回数据格式正确 const response = await getUserInfo()
// 即使网络错误,也会返回模拟的用户信息
```
## 模拟数据 ## 模拟数据
当API不可用时系统会自动使用模拟数据 当API不可用时系统会自动使用模拟数据
- 用户信息:超级管理员 - `mockUserInfo`: 用户基本信息
- 财务数据:预设的金额数据 - `mockFinancialData`: 财务数据
- 用户统计:预设的统计数据 - `mockAgentStats`: 代理统计数据
- `mockAgentList`: 代理用户列表数据
## 使用示例
在Vue组件中使用
```javascript
export default {
data() {
return {
userInfo: {},
financialData: {},
userStats: {}
}
},
async onLoad() {
await this.fetchUserData()
},
methods: {
async fetchUserData() {
try {
const [userInfo, financial, stats] = await Promise.all([
getUserInfo(),
getUserFinancialData(),
getAgentCount()
])
this.userInfo = userInfo.data
this.financialData = financial.data
this.userStats = stats.data
} catch (error) {
console.error('获取用户数据失败:', error)
}
}
}
}
```
## 注意事项
1. 所有API都需要有效的token
2. 财务数据需要用户登录后才能获取
3. 统计数据可能有缓存,建议定期刷新
4. 头像URL需要网络访问权限

View File

@ -24,6 +24,50 @@ export const mockAgentStats = {
rentAmount: 2, rentAmount: 2,
} }
export const mockAgentList = [
{
userId: '28',
nickName: '李四',
avatar: '',
totalAmount: 730.00,
deviceNum: 2,
orders: [
{
id: '3',
userId: '28',
name: '派大星',
phone: '13777777777',
address: '广西南宁市西乡塘区',
detailed: '详细地址',
typeName: '单头灶',
suitName: '一年',
suitDay: '365',
amount: 365.00,
status: '2',
leaseTime: '2025-08-15 10:50:22',
expirationTime: '2025-11-15 10:50:25',
orderNumber: '123456789101114',
},
{
id: '4',
userId: '28',
name: '派大星',
phone: '137777777777',
address: '测试',
detailed: '测试',
typeName: '单头灶',
suitName: '一年',
suitDay: '365',
amount: 365.00,
status: '2',
leaseTime: '2025-08-18 14:10:26',
expirationTime: '2025-10-01 14:10:29',
orderNumber: '123456789101115',
}
]
}
]
// 模拟API响应格式 // 模拟API响应格式
export const createMockResponse = (data, code = 200, msg = '操作成功') => { export const createMockResponse = (data, code = 200, msg = '操作成功') => {
return { return {

View File

@ -1,5 +1,5 @@
import request from '@/utils/request' import request from '@/utils/request'
import { mockUserInfo, mockFinancialData, mockAgentStats, createMockResponse } from './mockData.js' import { mockUserInfo, mockFinancialData, mockAgentStats, mockAgentList, createMockResponse } from './mockData.js'
/** /**
* 获取用户信息 * 获取用户信息
@ -70,75 +70,35 @@ export function getAgentCount() {
} }
/** /**
* 获取用户列表 * 获取代理用户列表
* @param {Object} params - 查询参数 * @param {Object} params - 查询参数
* @returns {Promise} 返回用户列表数据 * @param {string} params.beginTime - 开始时间
* @param {string} params.endTime - 结束时间
* @param {string} params.name - 用户昵称搜索
* @returns {Promise}
*/ */
export function getUserList(params = {}) { export function getAgentList(params = {}) {
return request({ return request({
url: '/app/user/list', url: '/app/order/agentList',
method: 'GET', method: 'GET',
params, params,
showLoading: false, showLoading: false,
}).catch(error => { }).catch(error => {
console.warn('用户列表API调用失败使用模拟数据:', error) console.warn('代理用户列表API调用失败使用模拟数据:', error)
// 如果API调用失败返回模拟数据 // 如果API调用失败返回模拟数据
return createMockResponse([ return createMockResponse(mockAgentList)
{
id: 1,
username: '张三',
totalAmount: '5000',
deviceCount: 2,
devices: [
{
type: '智能门锁',
amount: '1000',
rentDate: '2025.01.15 12:56:08',
period: '1年',
expiryDate: '2026.01.15 12:56:08',
},
{
type: '智能摄像头',
amount: '800',
rentDate: '2025.02.20 10:30:00',
period: '6个月',
expiryDate: '2025.08.20 10:30:00',
},
],
},
{
id: 2,
username: '李四',
totalAmount: '3200',
deviceCount: 3,
devices: [
{
type: '智能门锁',
amount: '1200',
rentDate: '2025.03.10 14:20:00',
period: '1年',
expiryDate: '2026.03.10 14:20:00',
},
{
type: '智能摄像头',
amount: '600',
rentDate: '2025.04.05 09:15:00',
period: '3个月',
expiryDate: '2025.07.05 09:15:00',
},
{
type: '智能音箱',
amount: '400',
rentDate: '2025.05.12 16:45:00',
period: '6个月',
expiryDate: '2025.11.12 16:45:00',
},
],
},
])
}) })
} }
/**
* 获取用户列表 (使用代理列表接口)
* @param {Object} params - 查询参数
* @returns {Promise}
*/
export function getUserList(params = {}) {
return getAgentList(params)
}
/** /**
* 更新用户信息 * 更新用户信息
* @param {Object} data - 用户信息数据 * @param {Object} data - 用户信息数据

View File

@ -1,7 +1,7 @@
<template> <template>
<view class="profile-page"> <view class="profile-page">
<image :src="commonEnum.FIRE_BACKGROUND" class="fire-background"></image> <image :src="commonEnum.FIRE_BACKGROUND" class="fire-background"></image>
<!-- 加载状态 --> <!-- 加载状态 -->
<view v-if="loading" class="loading-overlay"> <view v-if="loading" class="loading-overlay">
<view class="loading-content"> <view class="loading-content">
@ -13,13 +13,15 @@
<!-- 头部用户信息 --> <!-- 头部用户信息 -->
<view class="user-header"> <view class="user-header">
<view class="avatar"> <view class="avatar">
<image <image
v-if="userInfo.avatar" v-if="userInfo.avatar"
:src="userInfo.avatar" :src="userInfo.avatar"
class="avatar-image" class="avatar-image"
mode="aspectFill" mode="aspectFill"
/> />
<text v-else class="avatar-text">{{ userInfo.nickName ? userInfo.nickName.charAt(0) : '昵' }}</text> <text v-else class="avatar-text">{{
userInfo.nickName ? userInfo.nickName.charAt(0) : '昵'
}}</text>
</view> </view>
<view class="user-info"> <view class="user-info">
<view class="share-btn" @click="goToSharePromotion"> <view class="share-btn" @click="goToSharePromotion">
@ -140,7 +142,7 @@ export default {
userId: '', userId: '',
}, },
financialData: { financialData: {
balance: 0.00, balance: 0.0,
waitBalance: 0, waitBalance: 0,
withdrawBalance: 0, withdrawBalance: 0,
withdrawedBalance: 0, withdrawedBalance: 0,
@ -169,14 +171,14 @@ export default {
const [userInfoRes, financialRes, statsRes] = await Promise.allSettled([ const [userInfoRes, financialRes, statsRes] = await Promise.allSettled([
this.fetchUserInfo(), this.fetchUserInfo(),
this.fetchFinancialData(), this.fetchFinancialData(),
this.fetchUserStats() this.fetchUserStats(),
]) ])
// //
if (userInfoRes.status === 'fulfilled' && userInfoRes.value.code === 200) { if (userInfoRes.status === 'fulfilled' && userInfoRes.value.code === 200) {
this.userInfo = { this.userInfo = {
...this.userInfo, ...this.userInfo,
...userInfoRes.value.data ...userInfoRes.value.data,
} }
console.log('用户信息获取成功:', this.userInfo) console.log('用户信息获取成功:', this.userInfo)
} }
@ -185,7 +187,7 @@ export default {
if (financialRes.status === 'fulfilled' && financialRes.value.code === 200) { if (financialRes.status === 'fulfilled' && financialRes.value.code === 200) {
this.financialData = { this.financialData = {
...this.financialData, ...this.financialData,
...financialRes.value.data ...financialRes.value.data,
} }
console.log('财务数据获取成功:', this.financialData) console.log('财务数据获取成功:', this.financialData)
} }
@ -194,11 +196,10 @@ export default {
if (statsRes.status === 'fulfilled' && statsRes.value.code === 200) { if (statsRes.status === 'fulfilled' && statsRes.value.code === 200) {
this.userStats = { this.userStats = {
...this.userStats, ...this.userStats,
...statsRes.value.data ...statsRes.value.data,
} }
console.log('代理统计获取成功:', this.userStats) console.log('代理统计获取成功:', this.userStats)
} }
} catch (error) { } catch (error) {
console.error('获取用户数据失败:', error) console.error('获取用户数据失败:', error)
uni.showToast({ uni.showToast({
@ -224,8 +225,8 @@ export default {
nickName: '昵称', nickName: '昵称',
phonenumber: '123****8912', phonenumber: '123****8912',
avatar: '', avatar: '',
userId: '1' userId: '1',
} },
} }
} }
}, },
@ -241,11 +242,11 @@ export default {
return { return {
code: 200, code: 200,
data: { data: {
balance: 10000.00, balance: 10000.0,
waitBalance: 0, waitBalance: 0,
withdrawBalance: 0, withdrawBalance: 0,
withdrawedBalance: 0 withdrawedBalance: 0,
} },
} }
} }
}, },
@ -263,8 +264,8 @@ export default {
data: { data: {
userNum: 4, userNum: 4,
deviceNum: 1, deviceNum: 1,
rentAmount: 2 rentAmount: 2,
} },
} }
} }
}, },
@ -376,8 +377,12 @@ export default {
} }
@keyframes spin { @keyframes spin {
0% { transform: rotate(0deg); } 0% {
100% { transform: rotate(360deg); } transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
} }
// //

View File

@ -12,7 +12,12 @@
<view class="search-filter-section"> <view class="search-filter-section">
<view class="search-box"> <view class="search-box">
<uni-icons color="#7F7F7F" size="20" type="search"></uni-icons> <uni-icons color="#7F7F7F" size="20" type="search"></uni-icons>
<input v-model="searchKeyword" class="search-input" placeholder="搜索" @input="onSearch" /> <input
v-model="searchKeyword"
class="search-input"
placeholder="搜索用户昵称"
@input="onSearch"
/>
</view> </view>
<view class="filter-btn" @click="showFilter"> <view class="filter-btn" @click="showFilter">
<text class="filter-text">筛选</text> <text class="filter-text">筛选</text>
@ -20,17 +25,37 @@
</view> </view>
</view> </view>
<!-- 筛选面板 -->
<view v-if="showFilterPanel" class="filter-panel">
<view class="filter-item">
<text class="filter-label">开始时间</text>
<picker :value="filterParams.beginTime" mode="date" @change="onBeginTimeChange">
<view class="picker-input">{{ filterParams.beginTime || '请选择' }}</view>
</picker>
</view>
<view class="filter-item">
<text class="filter-label">结束时间</text>
<picker :value="filterParams.endTime" mode="date" @change="onEndTimeChange">
<view class="picker-input">{{ filterParams.endTime || '请选择' }}</view>
</picker>
</view>
<view class="filter-actions">
<button class="filter-reset" @click="resetFilter">重置</button>
<button class="filter-confirm" @click="applyFilter">确定</button>
</view>
</view>
<!-- 用户列表 --> <!-- 用户列表 -->
<view class="user-list"> <view class="user-list">
<view v-for="(user, index) in filteredUsers" :key="user.id" class="user-card"> <view v-for="(user, index) in filteredUsers" :key="user.userId" class="user-card">
<!-- 用户基本信息 --> <!-- 用户基本信息 -->
<view class="user-info"> <view class="user-info">
<view class="avatar-placeholder"></view> <view class="avatar-placeholder"></view>
<view class="user-details"> <view class="user-details">
<text class="username">{{ user.username }}</text> <text class="username">{{ user.nickName }}</text>
<view class="user-stats"> <view class="user-stats">
<text class="amount">¥ {{ user.totalAmount }}</text> <text class="amount">¥ {{ user.totalAmount }}</text>
<text class="device-count">{{ user.deviceCount }}</text> <text class="device-count">{{ user.deviceNum }}</text>
</view> </view>
</view> </view>
</view> </view>
@ -87,6 +112,12 @@ export default {
searchKeyword: '', searchKeyword: '',
users: [], users: [],
loading: false, loading: false,
showFilterPanel: false,
filterParams: {
beginTime: '',
endTime: '',
name: '',
},
} }
}, },
onLoad() { onLoad() {
@ -102,7 +133,7 @@ export default {
return this.users return this.users
} }
return this.users.filter(user => return this.users.filter(user =>
user.username.toLowerCase().includes(this.searchKeyword.toLowerCase()) user.nickName.toLowerCase().includes(this.searchKeyword.toLowerCase())
) )
}, },
}, },
@ -111,12 +142,34 @@ export default {
async fetchUserList() { async fetchUserList() {
this.loading = true this.loading = true
try { try {
const response = await getUserList() //
const params = {}
if (this.filterParams.beginTime) {
params.beginTime = this.filterParams.beginTime + ' 00:00:00'
}
if (this.filterParams.endTime) {
params.endTime = this.filterParams.endTime + ' 23:59:59'
}
if (this.filterParams.name) {
params.name = this.filterParams.name
}
const response = await getUserList(params)
if (response.code === 200 && response.data && Array.isArray(response.data)) { if (response.code === 200 && response.data && Array.isArray(response.data)) {
// // API
this.users = response.data.map(user => ({ this.users = response.data.map(user => ({
...user, ...user,
isExpanded: false // isExpanded: false, //
// ordersdevices
devices: user.orders
? user.orders.map(order => ({
type: order.typeName || '未知设备',
amount: order.amount || 0,
rentDate: this.formatDate(order.leaseTime),
period: order.suitName || '未知周期',
expiryDate: this.formatDate(order.expirationTime),
}))
: [],
})) }))
console.log('用户列表获取成功:', this.users) console.log('用户列表获取成功:', this.users)
} }
@ -131,16 +184,44 @@ export default {
} }
}, },
//
formatDate(dateString) {
if (!dateString) return '未知'
const date = new Date(dateString)
return `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(2, '0')}.${String(date.getDate()).padStart(2, '0')}`
},
onSearch() { onSearch() {
// computed // computed
}, },
showFilter() { showFilter() {
// this.showFilterPanel = !this.showFilterPanel
uni.showToast({
title: '筛选功能开发中',
icon: 'none',
})
}, },
onBeginTimeChange(e) {
this.filterParams.beginTime = e.detail.value
},
onEndTimeChange(e) {
this.filterParams.endTime = e.detail.value
},
resetFilter() {
this.filterParams = {
beginTime: '',
endTime: '',
name: '',
}
this.searchKeyword = ''
},
applyFilter() {
this.filterParams.name = this.searchKeyword
this.showFilterPanel = false
this.fetchUserList()
},
toggleExpand(index) { toggleExpand(index) {
this.users[index].isExpanded = !this.users[index].isExpanded this.users[index].isExpanded = !this.users[index].isExpanded
}, },
@ -191,8 +272,12 @@ export default {
} }
@keyframes spin { @keyframes spin {
0% { transform: rotate(0deg); } 0% {
100% { transform: rotate(360deg); } transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
} }
.search-filter-section { .search-filter-section {
@ -236,6 +321,122 @@ export default {
} }
} }
.filter-panel {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: white;
padding: 20rpx;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
z-index: 1000;
}
.filter-item {
display: flex;
align-items: center;
margin-bottom: 15rpx;
}
.filter-label {
font-size: 28rpx;
color: #333;
margin-right: 20rpx;
}
.picker-input {
flex: 1;
font-size: 28rpx;
color: #333;
border: 1rpx solid #eee;
border-radius: 8rpx;
padding: 10rpx 20rpx;
background: #f8f8f8;
}
.filter-actions {
display: flex;
justify-content: space-around;
margin-top: 20rpx;
}
.filter-reset,
.filter-confirm {
flex: 1;
margin: 0 10rpx;
padding: 15rpx 20rpx;
font-size: 28rpx;
border-radius: 8rpx;
border: none;
background: #ff6b35;
color: white;
font-weight: 500;
}
.filter-reset {
background: #f0f0f0;
color: #333;
}
.filter-confirm {
background: #ff6b35;
}
.filter-panel {
background: white;
padding: 20rpx;
border-top: 1rpx solid #eee;
margin-bottom: 20rpx;
}
.filter-item {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
.filter-label {
width: 120rpx;
font-size: 28rpx;
color: #333;
}
.picker-input {
flex: 1;
font-size: 28rpx;
color: #333;
border: 1rpx solid #ddd;
border-radius: 8rpx;
padding: 15rpx 20rpx;
background: #f8f8f8;
}
.filter-actions {
display: flex;
gap: 20rpx;
margin-top: 20rpx;
}
.filter-reset,
.filter-confirm {
flex: 1;
padding: 20rpx;
font-size: 28rpx;
border-radius: 8rpx;
border: none;
text-align: center;
}
.filter-reset {
background: #f0f0f0;
color: #333;
}
.filter-confirm {
background: #ff6b35;
color: white;
}
.user-list { .user-list {
padding: 0 20rpx; padding: 0 20rpx;
} }