-
+
{{ userStore.user?.username }}
@@ -228,6 +228,8 @@ import { ref, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'
import { ElMessage, ElMessageBox } from 'element-plus'
+import { getImageUrl } from '@/utils/config'
+
import {
Odometer,
User,
diff --git a/src/stores/user.js b/src/stores/user.js
index dc5c040..b8b2e90 100644
--- a/src/stores/user.js
+++ b/src/stores/user.js
@@ -1,188 +1,187 @@
-import { defineStore } from 'pinia'
-import { ElMessage } from 'element-plus'
+import {defineStore} from 'pinia'
+import {ElMessage} from 'element-plus'
import api from '@/utils/api'
export const useUserStore = defineStore('user', {
- state: () => ({
- user: null,
- token: localStorage.getItem('admin_token') || null,
- loading: false,
- statusCheckInterval: null
- }),
-
- getters: {
- isAuthenticated: (state) => !!state.token && !!state.user,
- isAdmin: (state) => state.user?.role === 'admin'
- },
-
- actions: {
- // 登录
- async login(credentials) {
- this.loading = true
- try {
- const response = await api.auth.login(credentials)
- const { token, user } = response.data
-
- this.token = token
- this.user = user
-
- // 存储到本地存储
- localStorage.setItem('admin_token', token)
- localStorage.setItem('admin_user', JSON.stringify(user))
-
- this.startStatusCheck() // 登录成功后开始状态检查
- ElMessage.success(`欢迎回来,${user.username}!`)
- return { success: true }
- } catch (error) {
- const message = error.response?.data?.message || '登录失败'
- ElMessage.error(message)
- return { success: false, message }
- } finally {
- this.loading = false
- }
+ state: () => ({
+ user: null,
+ token: localStorage.getItem('admin_token') || null,
+ loading: false,
+ statusCheckInterval: null
+ }),
+
+ getters: {
+ isAuthenticated: (state) => !!state.token && !!state.user,
+ isAdmin: (state) => state.user?.role === 'admin'
},
-
- // 登出
- async logout() {
- try {
- this.stopStatusCheck() // 登出时停止状态检查
-
- // 清除状态
- this.user = null
- this.token = null
-
- // 清除本地存储
- localStorage.removeItem('admin_token')
- localStorage.removeItem('admin_user')
-
- ElMessage.success('退出成功,期待您的再次光临!')
- } catch (error) {
- console.error('登出失败:', error)
- }
- },
-
- // 检查认证状态(仅在需要时验证token有效性)
- async checkAuth() {
- const token = localStorage.getItem('admin_token')
- const userStr = localStorage.getItem('admin_user')
-
- if (!token || !userStr) {
- return false
- }
-
- try {
- // 先从本地存储恢复状态
- const user = JSON.parse(userStr)
- this.token = token
- this.user = user
-
- // 可选:验证token是否仍然有效(仅在必要时调用)
- // 这里不主动验证,让具体的API调用时自然验证
- return true
- } catch (error) {
- console.error('认证检查失败:', error)
- // 清除无效的本地存储数据
- localStorage.removeItem('admin_token')
- localStorage.removeItem('admin_user')
- return false
- }
- },
-
- // 更新个人信息
- async updateProfile(profileData) {
- this.loading = true
- try {
- const response = await api.users.updateUser(this.user.id, profileData)
-
- this.user = { ...this.user, ...response.data }
- localStorage.setItem('admin_user', JSON.stringify(this.user))
-
- ElMessage.success('个人信息更新成功')
- return { success: true }
- } catch (error) {
- const message = error.response?.data?.message || '更新失败'
- ElMessage.error(message)
- return { success: false, message }
- } finally {
- this.loading = false
- }
- },
-
- // 修改密码
- async changePassword(passwordData) {
- this.loading = true
- try {
- await api.auth.changePassword(passwordData)
- ElMessage.success('密码修改成功,请重新登录')
-
- // 修改密码后需要重新登录
- setTimeout(() => {
- this.logout()
- }, 1500)
-
- return { success: true }
- } catch (error) {
- const message = error.response?.data?.message || '密码修改失败'
- ElMessage.error(message)
- return { success: false, message }
- } finally {
- this.loading = false
- }
- },
-
- // 获取用户详情
- async getUserDetails(userId) {
- try {
- const response = await api.users.getUserById(userId)
- return response.data
- } catch (error) {
- const message = error.response?.data?.message || '获取用户详情失败'
- ElMessage.error(message)
- throw error
- }
- },
-
- // 获取当前用户信息
- async fetchUserInfo() {
- try {
- const response = await api.auth.getCurrentUser()
- this.user = response.data.user
- localStorage.setItem('admin_user', JSON.stringify(this.user))
- return { success: true }
- } catch (error) {
- const message = error.response?.data?.message || '获取用户信息失败'
- console.error('获取用户信息失败:', error)
- return { success: false, message }
- }
- },
-
- // 开始状态检查
- startStatusCheck() {
- // 如果已经有定时器在运行,先清除
- if (this.statusCheckInterval) {
- clearInterval(this.statusCheckInterval)
- }
-
- // 每5分钟检查一次用户状态
- this.statusCheckInterval = setInterval(async () => {
- if (this.isAuthenticated) {
- try {
- await api.auth.getCurrentUser()
- } catch (error) {
- // 如果请求失败,说明token可能已失效或用户被拉黑
- // api拦截器会自动处理这些情况
- console.log('用户状态检查失败,可能已被拉黑或token失效')
- }
+
+ actions: {
+ // 登录
+ async login(credentials) {
+ this.loading = true
+ try {
+ const response = await api.auth.login(credentials)
+ const {token, agent} = response.data.data
+ this.token = token
+ this.user = agent
+
+ // 存储到本地存储
+ localStorage.setItem('admin_token', token)
+ localStorage.setItem('admin_user', JSON.stringify(agent))
+
+ this.startStatusCheck() // 登录成功后开始状态检查
+ ElMessage.success(`欢迎回来,${agent.realName}!`)
+ return {success: true}
+ } catch (error) {
+ const message = error.response?.data?.message || '登录失败'
+ ElMessage.error(message)
+ return {success: false, message}
+ } finally {
+ this.loading = false
+ }
+ },
+
+ // 登出
+ async logout() {
+ try {
+ this.stopStatusCheck() // 登出时停止状态检查
+
+ // 清除状态
+ this.user = null
+ this.token = null
+
+ // 清除本地存储
+ localStorage.removeItem('admin_token')
+ localStorage.removeItem('admin_user')
+
+ ElMessage.success('退出成功,期待您的再次光临!')
+ } catch (error) {
+ console.error('登出失败:', error)
+ }
+ },
+
+ // 检查认证状态(仅在需要时验证token有效性)
+ async checkAuth() {
+ const token = localStorage.getItem('admin_token')
+ const userStr = localStorage.getItem('admin_user')
+
+ if (!token || !userStr) {
+ return false
+ }
+
+ try {
+ // 先从本地存储恢复状态
+ const user = JSON.parse(userStr)
+ this.token = token
+ this.user = user
+
+ // 可选:验证token是否仍然有效(仅在必要时调用)
+ // 这里不主动验证,让具体的API调用时自然验证
+ return true
+ } catch (error) {
+ console.error('认证检查失败:', error)
+ // 清除无效的本地存储数据
+ localStorage.removeItem('admin_token')
+ localStorage.removeItem('admin_user')
+ return false
+ }
+ },
+
+ // 更新个人信息
+ async updateProfile(profileData) {
+ this.loading = true
+ try {
+ const response = await api.users.updateUser(this.user.id, profileData)
+
+ this.user = {...this.user, ...response.data}
+ localStorage.setItem('admin_user', JSON.stringify(this.user))
+
+ ElMessage.success('个人信息更新成功')
+ return {success: true}
+ } catch (error) {
+ const message = error.response?.data?.message || '更新失败'
+ ElMessage.error(message)
+ return {success: false, message}
+ } finally {
+ this.loading = false
+ }
+ },
+
+ // 修改密码
+ async changePassword(passwordData) {
+ this.loading = true
+ try {
+ await api.auth.changePassword(passwordData)
+ ElMessage.success('密码修改成功,请重新登录')
+
+ // 修改密码后需要重新登录
+ setTimeout(() => {
+ this.logout()
+ }, 1500)
+
+ return {success: true}
+ } catch (error) {
+ const message = error.response?.data?.message || '密码修改失败'
+ ElMessage.error(message)
+ return {success: false, message}
+ } finally {
+ this.loading = false
+ }
+ },
+
+ // 获取用户详情
+ async getUserDetails(userId) {
+ try {
+ const response = await api.users.getUserById(userId)
+ return response.data
+ } catch (error) {
+ const message = error.response?.data?.message || '获取用户详情失败'
+ ElMessage.error(message)
+ throw error
+ }
+ },
+
+ // 获取当前用户信息
+ async fetchUserInfo() {
+ try {
+ const response = await api.auth.getCurrentUser()
+ this.user = response.data.user
+ localStorage.setItem('admin_user', JSON.stringify(this.user))
+ return {success: true}
+ } catch (error) {
+ const message = error.response?.data?.message || '获取用户信息失败'
+ console.error('获取用户信息失败:', error)
+ return {success: false, message}
+ }
+ },
+
+ // 开始状态检查
+ startStatusCheck() {
+ // 如果已经有定时器在运行,先清除
+ if (this.statusCheckInterval) {
+ clearInterval(this.statusCheckInterval)
+ }
+
+ // 每5分钟检查一次用户状态
+ this.statusCheckInterval = setInterval(async () => {
+ if (this.isAuthenticated) {
+ try {
+ await api.auth.getCurrentUser()
+ } catch (error) {
+ // 如果请求失败,说明token可能已失效或用户被拉黑
+ // api拦截器会自动处理这些情况
+ console.log('用户状态检查失败,可能已被拉黑或token失效')
+ }
+ }
+ }, 5 * 60 * 1000) // 5分钟
+ },
+
+ // 停止状态检查
+ stopStatusCheck() {
+ if (this.statusCheckInterval) {
+ clearInterval(this.statusCheckInterval)
+ this.statusCheckInterval = null
+ }
}
- }, 5 * 60 * 1000) // 5分钟
- },
-
- // 停止状态检查
- stopStatusCheck() {
- if (this.statusCheckInterval) {
- clearInterval(this.statusCheckInterval)
- this.statusCheckInterval = null
- }
}
- }
})
\ No newline at end of file
diff --git a/src/utils/api.js b/src/utils/api.js
index be3b52c..40169e8 100644
--- a/src/utils/api.js
+++ b/src/utils/api.js
@@ -1,15 +1,15 @@
import axios from 'axios'
-import { ElMessage, ElLoading } from 'element-plus'
+import {ElMessage, ElLoading} from 'element-plus'
import NProgress from 'nprogress'
-import { apiURL } from './config.js'
+import {apiURL} from './config.js'
// 创建axios实例
const request = axios.create({
- baseURL: apiURL,
- timeout: 10000,
- headers: {
- 'Content-Type': 'application/json'
- }
+ baseURL: apiURL,
+ timeout: 10000,
+ headers: {
+ 'Content-Type': 'application/json'
+ }
})
let loadingInstance = null
@@ -18,203 +18,209 @@ let isLoggingOut = false // 防止重复登出
// 显示加载
const showLoading = () => {
- if (requestCount === 0) {
- loadingInstance = ElLoading.service({
- text: '加载中...',
- background: 'rgba(0, 0, 0, 0.7)'
- })
- }
- requestCount++
+ if (requestCount === 0) {
+ loadingInstance = ElLoading.service({
+ text: '加载中...',
+ background: 'rgba(0, 0, 0, 0.7)'
+ })
+ }
+ requestCount++
}
// 隐藏加载
const hideLoading = () => {
- requestCount--
- if (requestCount <= 0) {
- requestCount = 0
- if (loadingInstance) {
- loadingInstance.close()
- loadingInstance = null
+ requestCount--
+ if (requestCount <= 0) {
+ requestCount = 0
+ if (loadingInstance) {
+ loadingInstance.close()
+ loadingInstance = null
+ }
}
- }
}
// 请求拦截器
request.interceptors.request.use(
- (config) => {
- // 开始进度条
- NProgress.start()
-
- // 显示加载动画(除了某些不需要的请求)
- if (!config.hideLoading) {
- showLoading()
+ (config) => {
+ // 开始进度条
+ NProgress.start()
+
+ // 显示加载动画(除了某些不需要的请求)
+ if (!config.hideLoading) {
+ showLoading()
+ }
+
+ // 添加认证token
+ const token = localStorage.getItem('admin_token')
+ if (token) {
+ config.headers.Authorization = `Bearer ${token}`
+ }
+
+ return config
+ },
+ (error) => {
+ hideLoading()
+ NProgress.done()
+ return Promise.reject(error)
}
-
- // 添加认证token
- const token = localStorage.getItem('admin_token')
- if (token) {
- config.headers.Authorization = `Bearer ${token}`
- }
-
- return config
- },
- (error) => {
- hideLoading()
- NProgress.done()
- return Promise.reject(error)
- }
)
// 响应拦截器
request.interceptors.response.use(
- (response) => {
- hideLoading()
- NProgress.done()
- return response
- },
- (error) => {
- hideLoading()
- NProgress.done()
-
- const { response } = error
-
- if (response) {
- switch (response.status) {
- case 401:
- // 防止重复处理401错误
- if (!isLoggingOut) {
- isLoggingOut = true
-
- // 只在非登录页面显示错误消息
- if (window.location.pathname !== '/admin/login') {
- ElMessage.error('登录已过期,请重新登录')
+ (response) => {
+ hideLoading()
+ NProgress.done()
+ return response
+ },
+ (error) => {
+ hideLoading()
+ NProgress.done()
+
+ const {response} = error
+
+ if (response) {
+ switch (response.status) {
+ case 401:
+ // 防止重复处理401错误
+ if (!isLoggingOut) {
+ isLoggingOut = true
+
+ // 只在非登录页面显示错误消息
+ if (window.location.pathname !== '/admin/login') {
+ ElMessage.error('登录已过期,请重新登录')
+ }
+
+ // 清除本地存储
+ localStorage.removeItem('admin_token')
+ localStorage.removeItem('admin_user')
+
+ // 立即跳转到登录页,减少延迟
+ setTimeout(() => {
+ if (typeof window !== 'undefined' && window.location.pathname !== '/admin/login') {
+ window.location.href = '/admin/login'
+ }
+ // 重置标志
+ setTimeout(() => {
+ isLoggingOut = false
+ }, 1000)
+ }, 500)
+ }
+ break
+ case 403:
+ // 检查是否是用户被拉黑
+ if (response.data.code === 'USER_BLACKLISTED') {
+ // 防止重复处理拉黑错误
+ if (!isLoggingOut) {
+ isLoggingOut = true
+
+ ElMessage.error(response.data.message || '账户已被拉黑,请联系管理员')
+
+ // 清除本地存储
+ localStorage.removeItem('admin_token')
+ localStorage.removeItem('admin_user')
+
+ // 跳转到登录页
+ setTimeout(() => {
+ if (typeof window !== 'undefined' && window.location.pathname !== '/admin/login') {
+ window.location.href = '/admin/login'
+ }
+ // 重置标志
+ setTimeout(() => {
+ isLoggingOut = false
+ }, 1000)
+ }, 500)
+ }
+ } else {
+ ElMessage.error('没有权限访问此资源')
+ }
+ break
+ case 404:
+ ElMessage.error('请求的资源不存在')
+ break
+ case 422:
+ ElMessage.error(response.data.message || '请求参数错误')
+ break
+ case 429:
+ ElMessage.error('请求过于频繁,请稍后再试')
+ break
+ case 500:
+ ElMessage.error('服务器内部错误')
+ break
+ default:
+ ElMessage.error(response.data.error.message || '请求失败')
}
-
- // 清除本地存储
- localStorage.removeItem('admin_token')
- localStorage.removeItem('admin_user')
-
- // 立即跳转到登录页,减少延迟
- setTimeout(() => {
- if (typeof window !== 'undefined' && window.location.pathname !== '/admin/login') {
- window.location.href = '/admin/login'
- }
- // 重置标志
- setTimeout(() => {
- isLoggingOut = false
- }, 1000)
- }, 500)
- }
- break
- case 403:
- // 检查是否是用户被拉黑
- if (response.data.code === 'USER_BLACKLISTED') {
- // 防止重复处理拉黑错误
- if (!isLoggingOut) {
- isLoggingOut = true
-
- ElMessage.error(response.data.message || '账户已被拉黑,请联系管理员')
-
- // 清除本地存储
- localStorage.removeItem('admin_token')
- localStorage.removeItem('admin_user')
-
- // 跳转到登录页
- setTimeout(() => {
- if (typeof window !== 'undefined' && window.location.pathname !== '/admin/login') {
- window.location.href = '/admin/login'
- }
- // 重置标志
- setTimeout(() => {
- isLoggingOut = false
- }, 1000)
- }, 500)
- }
- } else {
- ElMessage.error('没有权限访问此资源')
- }
- break
- case 404:
- ElMessage.error('请求的资源不存在')
- break
- case 422:
- ElMessage.error(response.data.message || '请求参数错误')
- break
- case 429:
- ElMessage.error('请求过于频繁,请稍后再试')
- break
- case 500:
- ElMessage.error('服务器内部错误')
- break
- default:
- ElMessage.error(response.data.error.message || '请求失败')
- }
- } else {
- ElMessage.error('网络错误,请检查网络连接')
+ } else {
+ ElMessage.error('网络错误,请检查网络连接')
+ }
+
+ return Promise.reject(error)
}
-
- return Promise.reject(error)
- }
)
// API接口定义
const api = {
- // 代理认证相关
- auth: {
- login: (data) => request.post('/agents/auth/login', data),
- getCurrentUser: () => request.get('/agents/auth/me'),
- changePassword: (data) => request.put('/agents/auth/change-password', data)
- },
-
- // 代理下用户管理(只读)
- users: {
- getUsers: (params) => request.get('/agents/users', { params }),
- getUserById: (id) => request.get(`/agents/users/${id}`),
- getUserStats: () => request.get('/agents/users/stats')
- },
-
- // 代理数据统计
- dashboard: {
- getStats: () => request.get('/agents/dashboard/stats'),
- getChartData: (type) => request.get(`/agents/dashboard/charts/${type}`)
- },
+ // 代理认证相关
+ auth: {
+ login: (data) => request.post('/agents/auth/login', data),
+ getCurrentUser: () => request.get('/agents/auth/me'),
+ changePassword: (data) => request.put('/agents/auth/change-password', data)
+ },
- // 佣金管理
- commissions: {
- // 获取佣金列表
- getList: (params) => request.get('/agents/commissions', { params }),
- // 获取佣金统计
- getStats: (params) => request.get('/agents/commissions/stats', { params }),
- // 发放单个佣金
- pay: (id) => request.post(`/agents/commissions/${id}/pay`),
- // 批量发放佣金
- batchPay: (data) => request.post('/agents/commissions/batch-pay', data)
- },
+ // 代理下用户管理(只读)
+ users: {
+ getUsers: (params) => request.get('/users', {params}),
+ getUserById: (id) => request.get(`/agents/users/${id}`),
+ getUserStats: () => request.get('/agents/users/stats')
+ },
- // 转账记录管理(只读)
- transfers: {
- getTransfers: (params) => request.get('/agents/transfers', { params }),
- getTransferStats: () => request.get('/agents/transfers/stats')
- },
-
- // 文件上传
- upload: {
- uploadImage: (file) => {
- const formData = new FormData()
- formData.append('image', file)
- return request.post('/upload/image', formData, {
- headers: {
- 'Content-Type': 'multipart/form-data'
+ // 代理数据统计
+ dashboard: {
+ getStats: () => request.get('/agent/stats'),
+ getChartData: (type) => request.get(`/agents/dashboard/charts/${type}`),
+ getUserChart: (params) => request.get("/agent/user-growth-trend", {params}),
+ getCommissionTrend: () => request.get("/agent/commission-trend"),
+ getCommissionDistribution: () => request.get("/agent/commission-distribution"),
+ getRecentUsers: () => request.get("/agent/recent-users"),
+ getRecentCommissions: () => request.get("/agent/recent-commissions"),
+ },
+
+ // 佣金管理
+ commissions: {
+ // 获取佣金列表
+ getList: (params) => request.get('/agents/commissions/list', {params}),
+ // 获取佣金统计
+ getStats: (params) => request.get('/agents/commissions/stats', {params}),
+ // 发放单个佣金
+ pay: (id) => request.post(`/agents/commissions/${id}/pay`),
+ // 批量发放佣金
+ batchPay: (data) => request.post('/agents/commissions/batch-pay', data)
+ },
+
+ // 转账记录管理(只读)
+ transfers: {
+ getList: (params) => request.get('/transfers', {params}),
+ getTransfers: (params) => request.get('/agents/transfers', {params}),
+ getTransferStats: () => request.get('/agents/transfers/stats')
+ },
+
+ // 文件上传
+ upload: {
+ uploadImage: (file) => {
+ const formData = new FormData()
+ formData.append('image', file)
+ return request.post('/upload/image', formData, {
+ headers: {
+ 'Content-Type': 'multipart/form-data'
+ }
+ })
}
- })
- }
- },
-
- // 为了向后兼容,添加直接的 get、post 等方法
- get: (url, config) => request.get(url, config),
- post: (url, data, config) => request.post(url, data, config),
- put: (url, data, config) => request.put(url, data, config),
- delete: (url, config) => request.delete(url, config)
+ },
+
+ // 为了向后兼容,添加直接的 get、post 等方法
+ get: (url, config) => request.get(url, config),
+ post: (url, data, config) => request.post(url, data, config),
+ put: (url, data, config) => request.put(url, data, config),
+ delete: (url, config) => request.delete(url, config)
}
export default api
\ No newline at end of file
diff --git a/src/utils/config.js b/src/utils/config.js
index 4c7a3a7..959bbeb 100644
--- a/src/utils/config.js
+++ b/src/utils/config.js
@@ -2,8 +2,8 @@
const config = {
development: {
baseURL: 'https://minio.zrbjr.com',
- uploadURL: 'http://localhost:3002/api/upload',
- apiURL: 'http://localhost:3002/api'
+ uploadURL: 'http://192.168.1.43:3002/api/upload',
+ apiURL: 'http://192.168.1.43:3002/api'
},
production: {
baseURL: 'https://minio.zrbjr.com',
@@ -27,34 +27,31 @@ export const { baseURL, uploadURL, apiURL } = config[env]
* @returns {string} 完整的图片URL
*/
export const getImageUrl = (imagePath) => {
- const cleanBaseURL = baseURL.replace(/\/$/, '')
- if (!imagePath) return ''
- if (imagePath.startsWith('http')) return imagePath
-
- // 在开发环境下,使用代理路径
- if (env === 'development') {
- // 如果路径已经包含uploads,直接使用
- if (imagePath.startsWith('/uploads/') || imagePath.startsWith('uploads/')) {
- const cleanPath = imagePath.startsWith('/') ? imagePath : `/${imagePath}`
- return cleanPath
+ // console.log('getImageUrl called with:', imagePath)
+ if (!imagePath) return ''
+ if (imagePath.startsWith('http')) return imagePath
+ const cleanBaseURL = baseURL.replace(/\/$/, '')
+
+ // 如果图片路径以/uploads开头,直接返回原路径
+ if (imagePath.startsWith('/uploads')) {
+ // console.log('Image starts with /uploads, returning original path:', imagePath)
+ return `${cleanBaseURL}/jurongquan${imagePath}`
}
- // 否则添加uploads前缀
- return `${cleanBaseURL}${imagePath}`
- }
-
- // 生产环境下使用完整URL
-
- let cleanImagePath
-
- // 如果路径已经包含uploads,直接使用
- if (imagePath.startsWith('/uploads/') || imagePath.startsWith('uploads/')) {
- cleanImagePath = imagePath.startsWith('/') ? imagePath : `/${imagePath}`
- } else {
- // 否则添加uploads前缀
- cleanImagePath = `/uploads/${imagePath}`
- }
-
- return `${cleanBaseURL}${cleanImagePath}`
+
+ // 在开发环境下,也需要根据路径前缀处理
+ if (env === 'development') {
+ const cleanBaseURL = baseURL.replace(/\/$/, '')
+ const cleanImagePath = imagePath.startsWith('/') ? imagePath : `/${imagePath}`
+ const fullUrl = `${cleanBaseURL}${cleanImagePath}`
+ // console.log('Development environment, returning:', fullUrl)
+ return fullUrl
+ }
+
+ // 生产环境下使用完整URL
+ const cleanImagePath = imagePath.startsWith('/') ? imagePath : `/${imagePath}`
+ const fullUrl = `${cleanBaseURL}${cleanImagePath}`
+
+ return fullUrl
}
/**
diff --git a/src/utils/public_method.js b/src/utils/public_method.js
new file mode 100644
index 0000000..c274e49
--- /dev/null
+++ b/src/utils/public_method.js
@@ -0,0 +1,131 @@
+/**
+ * 验证并脱敏电话号码
+ * @param {string} phone - 需要验证和脱敏的电话号码
+ * @returns {string|boolean} - 脱敏后的电话号码,无效则返回false
+ */
+export function maskPhoneNumber(phone) {
+ // 检查是否为字符串
+ if (typeof phone !== 'string') {
+ return phone;
+ }
+
+ // 移除所有非数字字符
+ const cleaned = phone.replace(/\D/g, '');
+
+ // 验证常见的电话号码格式
+ // 支持: 11位手机号(中国大陆)、带区号的固定电话
+ const phoneRegex = /^(1[3-9]\d{9})$|^(\d{3,4}-\d{7,8})$|^(\d{3,4}\d{7,8})$/;
+
+ if (!phoneRegex.test(cleaned) && !phoneRegex.test(phone)) {
+ return phone; // 不是有效的电话号码
+ }
+
+ // 根据不同格式进行脱敏
+ if (cleaned.length === 11) {
+ // 手机号: 保留前3位和后4位,中间4位用*代替
+ return cleaned.replace(/^(\d{3})(\d{4})(\d{4})$/, '$1****$3');
+ } else if (phone.includes('-')) {
+ // 带区号的固定电话: 区号不变,号码中间用*代替
+ const [areaCode, number] = phone.split('-');
+ if (number.length <= 4) {
+ return `${areaCode}-****`;
+ }
+ return `${areaCode}-${number.substr(0, 2)}****${number.substr(-2)}`;
+ } else {
+ // 不带区号的固定电话
+ if (cleaned.length <= 4) {
+ return '****';
+ }
+ return `${cleaned.substr(0, 2)}****${cleaned.substr(-2)}`;
+ }
+}
+
+/**
+ * 姓名脱敏处理
+ * @param {string} name - 需要脱敏的姓名
+ * @returns {string} - 脱敏后的姓名
+ */
+export function maskName(name) {
+ // 检查输入是否为有效字符串
+ if (!name || typeof name !== 'string') {
+ return '';
+ }
+
+ // 去除前后空格
+ const trimmedName = name.trim();
+
+ // 检查是否为英文姓名(包含空格)
+ if (trimmedName.includes(' ')) {
+ const parts = trimmedName.split(' ').filter(part => part);
+
+ // 处理英文名:名全显,姓只显首字母
+ if (parts.length >= 2) {
+ const firstName = parts.slice(0, -1).join(' ');
+ const lastName = parts[parts.length - 1];
+ return `${firstName} ${lastName.charAt(0)}*`;
+ }
+ }
+
+ // 处理中文姓名
+ const length = trimmedName.length;
+
+ switch (length) {
+ case 1:
+ // 单字名,不脱敏
+ return trimmedName;
+ case 2:
+ // 双字名,隐藏第二个字
+ return `${trimmedName[0]}*`;
+ case 3:
+ // 三字名,隐藏中间字
+ return `${trimmedName[0]}*${trimmedName[2]}`;
+ case 4:
+ // 四字名(如复姓),隐藏中间两个字
+ return `${trimmedName[0]}**${trimmedName[3]}`;
+ default:
+ // 更长的姓名,显示首尾各两个字,中间用*代替
+ if (length > 4) {
+ return `${trimmedName.substr(0, 2)}${'*'.repeat(length - 4)}${trimmedName.substr(-2)}`;
+ }
+ }
+
+ return trimmedName;
+}
+
+/**
+ * 转换日期 yyyy-HH-mm
+ */
+export const convertToDateOnly = (dateString) => {
+ // 创建Date对象
+ const date = new Date(dateString);
+
+ // 获取年、月、日
+ const year = date.getFullYear();
+ // 月份从0开始,所以需要加1
+ const month = String(date.getMonth() + 1).padStart(2, '0');
+ const day = String(date.getDate()).padStart(2, '0');
+
+ // 拼接成YYYY-MM-DD格式
+ return `${year}-${month}-${day - 1}`;
+}
+
+/**
+ * 将ISO格式日期时间转换为yyyy-MM-dd HH:mm:ss格式
+ * @param {string} isoString - ISO格式的日期时间字符串,如2025-09-04T06:18:08.000Z
+ * @returns {string} - 转换后的日期时间字符串
+ */
+export function formatIsoToCustom(utcString) {
+ // 创建Date对象
+ const date = new Date(utcString);
+
+ // 使用UTC方法获取各时间部分,确保使用UTC时间而非本地时间
+ const year = date.getUTCFullYear();
+ const month = String(date.getUTCMonth() + 1).padStart(2, '0'); // 月份从0开始
+ const day = String(date.getUTCDate()).padStart(2, '0');
+ const hours = String(date.getUTCHours()).padStart(2, '0');
+ const minutes = String(date.getUTCMinutes()).padStart(2, '0');
+ const seconds = String(date.getUTCSeconds()).padStart(2, '0');
+
+ // 拼接成目标格式
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+}
\ No newline at end of file
diff --git a/src/views/Dashboard.vue b/src/views/Dashboard.vue
index 8c95ce0..7a5444b 100644
--- a/src/views/Dashboard.vue
+++ b/src/views/Dashboard.vue
@@ -5,23 +5,25 @@
-
+
@@ -41,15 +43,17 @@
-
+
+
+
{{ stat.value }}
{{ stat.label }}
-
-
- {{ stat.change }}
-
+
+
+
+
@@ -57,7 +61,7 @@
-
+
@@ -65,69 +69,78 @@
-
+
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -141,30 +154,29 @@
-
+
-
+
-
+
+
+
{{ user.username }}
{{ user.phone }}
{{ formatTime(user.created_at) }}
-
- 新用户
-
-
+
@@ -176,24 +188,26 @@
-
+
-
+
-
+
+
+
-
+{{ commission.amount }}
-
{{ commission.description }}
+
+{{ commission.commission_amount }}
+
{{ commission.username+'('+commission.real_name+')' }}
{{ formatTime(commission.created_at) }}
-
- {{ getCommissionStatusText(commission.status) }}
-
+
+
+
@@ -204,11 +218,11 @@
diff --git a/src/views/Users.vue b/src/views/Users.vue
index c68916e..1c6c70d 100644
--- a/src/views/Users.vue
+++ b/src/views/Users.vue
@@ -4,292 +4,234 @@
用户管理
查看我的下级用户信息
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
搜索
-
+
+
+
重置
-
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+
-
{{ stats.totalUsers }}
+
{{ stats.total_users }}
下级用户总数
-
+
+
+
-
+
-
{{ stats.activeUsers }}
+
{{ stats.active_users }}
活跃用户
-
+
+
+
-
+
-
¥{{ formatBalance(stats.totalBalance) }}
+
¥{{ formatBalance(stats.total_balance) }}
用户总余额
-
+
+
+
-
+
-
{{ stats.todayNewUsers }}
+
{{ stats.today_new_users }}
今日新增
-
+
+
+
-
+
-
-
-
-
-
-
+
+
-
-
+
+
+
+
-
+
-
{{ row.username }}
+
{{ maskPhoneNumber(row.username) }}
ID: {{ row.id }}
-
+
{{ row.real_name || '-' }}
-
-
-
- {{ row.city_name || '-' }}
-
-
-
-
-
- {{ row.district_name || '-' }}
-
-
-
-
+
+
¥{{ formatBalance(row.balance) }}
-
-
+
+
{{ formatPoints(row.points) }}
-
-
-
-
- {{ getUserStatusText(row) }}
-
-
-
-
+
- {{ formatDate(row.created_at) }}
+ {{ convertToDateOnly(row.created_at) }}
-
-
-
- {{ row.last_login_at ? formatDate(row.last_login_at) : '从未登录' }}
-
-
-
+
查看详情
-
+
-
+
-
-
+
+
+
+
- {{ selectedUser.username }}
+ {{ maskPhoneNumber(selectedUser.username) }}
{{ selectedUser.real_name || '-' }}
- {{ selectedUser.phone || '-' }}
+ {{ maskPhoneNumber(selectedUser.phone) || '-' }}
{{ selectedUser.id_card || '-' }}
- {{ selectedUser.city_name || '-' }}
- {{ selectedUser.district_name || '-' }}
¥{{ formatBalance(selectedUser.balance) }}
{{ formatPoints(selectedUser.points) }}
- {{ formatDate(selectedUser.created_at) }}
- {{ selectedUser.last_login_at ? formatDate(selectedUser.last_login_at) : '从未登录' }}
-
-
- {{ getUserStatusText(selectedUser) }}
-
-
+ {{ convertToDateOnly(selectedUser.created_at) }}
@@ -297,19 +239,21 @@
@@ -526,7 +477,7 @@ onMounted(() => {
gap: 8px;
}
-.stats-row {
+.stats-section {
margin-bottom: 20px;
}