Files
tyapi-frontend/src/pages/admin/users/index.vue
2025-12-10 14:17:31 +08:00

1375 lines
40 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<ListPageLayout
title="用户管理"
subtitle="管理系统用户信息"
>
<!-- 统计信息 -->
<template #actions>
<div :class="['flex gap-4', isMobile ? 'flex-wrap justify-center' : '']">
<div :class="['stat-item', isMobile ? 'flex-1 min-w-0' : '']">
<div class="stat-value">{{ stats.total_users || 0 }}</div>
<div class="stat-label">总用户数</div>
</div>
<div :class="['stat-item', isMobile ? 'flex-1 min-w-0' : '']">
<div class="stat-value">{{ stats.certified_users || 0 }}</div>
<div class="stat-label">已认证用户</div>
</div>
<div :class="['stat-item', isMobile ? 'flex-1 min-w-0' : '']">
<div class="stat-value">{{ stats.active_users || 0 }}</div>
<div class="stat-label">活跃用户</div>
</div>
</div>
</template>
<template #filters>
<FilterSection>
<FilterItem label="搜索用户">
<el-input
v-model="filters.phone"
placeholder="输入手机号"
clearable
@input="handleSearch"
class="w-full"
/>
</FilterItem>
<FilterItem label="认证状态">
<el-select
v-model="filters.is_certified"
placeholder="选择认证状态"
clearable
@change="handleFilterChange"
class="w-full"
>
<el-option label="已认证" :value="true" />
<el-option label="未认证" :value="false" />
</el-select>
</FilterItem>
<FilterItem label="激活状态">
<el-select
v-model="filters.is_active"
placeholder="选择激活状态"
clearable
@change="handleFilterChange"
class="w-full"
>
<el-option label="已激活" :value="true" />
<el-option label="未激活" :value="false" />
</el-select>
</FilterItem>
<FilterItem label="企业名称">
<el-input
v-model="filters.company_name"
placeholder="输入企业名称"
clearable
@input="handleSearch"
class="w-full"
/>
</FilterItem>
<template #stats>
共找到 {{ total }} 个用户
</template>
<template #buttons>
<div :class="['flex gap-2', isMobile ? 'flex-wrap w-full' : '']">
<el-button :size="isMobile ? 'small' : 'default'" @click="resetFilters" :class="isMobile ? 'flex-1' : ''">
重置筛选
</el-button>
<el-button :size="isMobile ? 'small' : 'default'" type="primary" @click="loadUsers" :class="isMobile ? 'flex-1' : ''">
应用筛选
</el-button>
</div>
</template>
</FilterSection>
</template>
<template #table>
<!-- 加载状态 -->
<div v-if="loading" class="flex justify-center items-center py-12">
<el-loading size="large" />
</div>
<!-- 移动端卡片布局 -->
<div v-else-if="isMobile && users.length > 0" class="user-cards">
<div
v-for="user in users"
:key="user.id"
class="user-card"
>
<div class="card-header">
<div class="flex-1">
<div class="flex items-center gap-2 mb-1">
<span class="font-semibold text-base text-blue-600 font-mono">{{ formatPhone(user.phone) }}</span>
<el-tag
:type="user.is_active ? 'success' : 'warning'"
size="small"
>
{{ user.is_active ? '已激活' : '未激活' }}
</el-tag>
<el-tag
:type="user.is_certified ? 'success' : 'info'"
size="small"
>
{{ user.is_certified ? '已认证' : '未认证' }}
</el-tag>
</div>
<div v-if="user.username" class="text-xs text-gray-500">用户名: {{ user.username }}</div>
</div>
</div>
<div class="card-body">
<div class="card-row">
<span class="card-label">钱包余额</span>
<span class="card-value text-green-600 font-semibold">¥{{ formatMoney(user.wallet_balance || '0') }}</span>
</div>
<div v-if="user.enterprise_info" class="card-row">
<span class="card-label">企业名称</span>
<span class="card-value">{{ user.enterprise_info.company_name || '-' }}</span>
</div>
<div v-if="user.enterprise_info" class="card-row">
<span class="card-label">法人姓名</span>
<span class="card-value text-sm">{{ user.enterprise_info.legal_person_name || '-' }}</span>
</div>
<div class="card-row">
<span class="card-label">注册时间</span>
<span class="card-value text-sm">{{ formatDate(user.created_at) }} {{ formatTime(user.created_at) }}</span>
</div>
</div>
<div class="card-footer">
<div class="action-buttons">
<el-button
type="primary"
size="small"
@click="handleViewUser(user)"
class="action-btn"
>
查看详情
</el-button>
<el-button
type="warning"
size="small"
@click="handleRecharge(user)"
:disabled="!user.is_certified"
class="action-btn"
>
充值
</el-button>
<el-dropdown @command="handleMoreAction" trigger="click">
<el-button type="info" size="small" :disabled="!user.is_certified" class="action-btn">
更多<el-icon class="ml-1"><arrow-down /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :command="{ action: 'subscriptions', user }">
<el-icon><tickets /></el-icon>
订阅管理
</el-dropdown-item>
<el-dropdown-item :command="{ action: 'api_calls', user }">
<el-icon><document /></el-icon>
调用记录
</el-dropdown-item>
<el-dropdown-item :command="{ action: 'consumption', user }">
<el-icon><money /></el-icon>
消费记录
</el-dropdown-item>
<el-dropdown-item :command="{ action: 'recharge_history', user }">
<el-icon><wallet /></el-icon>
充值记录
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
</div>
<!-- 桌面端表格布局 -->
<div v-else-if="!isMobile" class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
<div class="table-container">
<el-table
:data="users"
style="width: 100%"
:header-cell-style="{
background: '#f8fafc',
color: '#475569',
fontWeight: '600',
fontSize: '14px'
}"
:cell-style="{
fontSize: '14px',
color: '#1e293b'
}"
>
<el-table-column prop="phone" label="手机号" width="140">
<template #default="{ row }">
<span class="font-mono text-sm">{{ formatPhone(row.phone) }}</span>
</template>
</el-table-column>
<el-table-column prop="username" label="用户名" width="120">
<template #default="{ row }">
<span class="text-gray-600">{{ row.username || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="is_active" label="激活状态" width="120">
<template #default="{ row }">
<el-tag
:type="row.is_active ? 'success' : 'warning'"
size="small"
>
{{ row.is_active ? '已激活' : '未激活' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="is_certified" label="认证状态" width="120">
<template #default="{ row }">
<el-tag
:type="row.is_certified ? 'success' : 'info'"
size="small"
>
{{ row.is_certified ? '已认证' : '未认证' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="wallet_balance" label="钱包余额" width="120">
<template #default="{ row }">
<span class="font-medium text-green-600">
¥{{ formatMoney(row.wallet_balance || '0') }}
</span>
</template>
</el-table-column>
<el-table-column prop="enterprise_info.company_name" label="企业信息" min-width="200">
<template #default="{ row }">
<div v-if="row.enterprise_info" class="space-y-1">
<div class="flex items-center text-gray-900">
<span class="w-20 text-gray-500">企业名</span>
<span class="font-medium">{{ row.enterprise_info.company_name || '-' }}</span>
</div>
<div class="flex items-center text-gray-900">
<span class="w-20 text-gray-500">法人姓名</span>
<span class="text-sm">{{ row.enterprise_info.legal_person_name || '-' }}</span>
</div>
</div>
<span v-else class="text-gray-400">-</span>
</template>
</el-table-column>
<el-table-column prop="created_at" label="注册时间" width="160">
<template #default="{ row }">
<div class="text-sm">
<div class="text-gray-900">{{ formatDate(row.created_at) }}</div>
<div class="text-gray-500">{{ formatTime(row.created_at) }}</div>
</div>
</template>
</el-table-column>
<el-table-column label="操作" min-width="300" fixed="right">
<template #default="{ row }">
<div class="flex items-center space-x-2">
<el-button
size="small"
type="primary"
@click="handleViewUser(row)"
>
查看详情
</el-button>
<!-- 充值按钮 -->
<el-tooltip
v-if="!row.is_certified"
content="需要企业认证后才能进行充值操作"
placement="top"
>
<el-button
size="small"
type="warning"
@click="handleRecharge(row)"
:disabled="!row.is_certified"
>
充值
</el-button>
</el-tooltip>
<el-button
v-else
size="small"
type="warning"
@click="handleRecharge(row)"
>
充值
</el-button>
<!-- 更多操作按钮 -->
<el-tooltip
v-if="!row.is_certified"
content="需要企业认证后才能查看更多操作"
placement="top"
>
<el-dropdown @command="handleMoreAction" trigger="click">
<el-button
size="small"
type="info"
:disabled="!row.is_certified"
>
更多<el-icon class="el-icon--right"><arrow-down /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :command="{ action: 'subscriptions', user: row }">
<el-icon><tickets /></el-icon>
订阅管理
</el-dropdown-item>
<el-dropdown-item :command="{ action: 'api_calls', user: row }">
<el-icon><document /></el-icon>
调用记录
</el-dropdown-item>
<el-dropdown-item :command="{ action: 'consumption', user: row }">
<el-icon><money /></el-icon>
消费记录
</el-dropdown-item>
<el-dropdown-item :command="{ action: 'recharge_history', user: row }">
<el-icon><wallet /></el-icon>
充值记录
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-tooltip>
<el-dropdown
v-else
@command="handleMoreAction"
trigger="click"
>
<el-button size="small" type="info">
更多<el-icon class="el-icon--right"><arrow-down /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :command="{ action: 'subscriptions', user: row }">
<el-icon><tickets /></el-icon>
订阅管理
</el-dropdown-item>
<el-dropdown-item :command="{ action: 'api_calls', user: row }">
<el-icon><document /></el-icon>
调用记录
</el-dropdown-item>
<el-dropdown-item :command="{ action: 'consumption', user: row }">
<el-icon><money /></el-icon>
消费记录
</el-dropdown-item>
<el-dropdown-item :command="{ action: 'recharge_history', user: row }">
<el-icon><wallet /></el-icon>
充值记录
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
</el-table-column>
</el-table>
</div>
</div>
<!-- 空状态 -->
<div v-if="!loading && users.length === 0" class="text-center py-12">
<el-empty description="暂无用户数据" />
</div>
</template>
<template #pagination>
<el-pagination
v-if="total > 0"
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="total"
:layout="isMobile ? 'prev, pager, next' : 'total, sizes, prev, pager, next, jumper'"
:small="isMobile"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</template>
<template #extra>
<!-- 用户详情弹窗 -->
<el-dialog
v-model="userDialogVisible"
title="用户详情"
:width="isMobile ? '90%' : '800px'"
class="user-dialog"
>
<div v-if="selectedUser" class="space-y-6">
<!-- 用户统计信息 -->
<div :class="['grid gap-6', isMobile ? 'grid-cols-1' : 'grid-cols-3']">
<div class="user-stat-card">
<div class="user-stat-value">{{ selectedUser.login_count || 0 }}</div>
<div class="user-stat-label">登录次数</div>
</div>
<div class="user-stat-card">
<div class="user-stat-value">{{ selectedUser.user_type === 'admin' ? '管理员' : '普通用户' }}</div>
<div class="user-stat-label">用户类型</div>
</div>
<div class="user-stat-card">
<div class="user-stat-value">¥{{ formatMoney(selectedUser.wallet_balance) }}</div>
<div class="user-stat-label">钱包余额</div>
</div>
</div>
<!-- 基本信息 -->
<div class="user-info">
<h3 class="text-lg font-semibold text-gray-900 mb-4">基本信息</h3>
<div :class="['grid gap-4', isMobile ? 'grid-cols-1' : 'grid-cols-2']">
<div class="info-item">
<span class="info-label">手机号:</span>
<span class="info-value">{{ formatPhone(selectedUser.phone) }}</span>
</div>
<div class="info-item">
<span class="info-label">用户名:</span>
<span class="info-value">{{ selectedUser.username || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">激活状态:</span>
<span class="info-value">
<el-tag :type="selectedUser.is_active ? 'success' : 'warning'" size="small">
{{ selectedUser.is_active ? '已激活' : '未激活' }}
</el-tag>
</span>
</div>
<div class="info-item">
<span class="info-label">认证状态:</span>
<span class="info-value">
<el-tag :type="selectedUser.is_certified ? 'success' : 'info'" size="small">
{{ selectedUser.is_certified ? '已认证' : '未认证' }}
</el-tag>
</span>
</div>
<div class="info-item">
<span class="info-label">注册时间:</span>
<span class="info-value">{{ formatDate(selectedUser.created_at) }}</span>
</div>
<div v-if="selectedUser.last_login_at" class="info-item">
<span class="info-label">最后登录:</span>
<span class="info-value">{{ formatDate(selectedUser.last_login_at) }}</span>
</div>
</div>
</div>
<!-- 企业信息 -->
<div v-if="selectedUser.enterprise_info" class="enterprise-info">
<h3 class="text-lg font-semibold text-gray-900 mb-4">企业信息</h3>
<div :class="['grid gap-4', isMobile ? 'grid-cols-1' : 'grid-cols-2']">
<div class="info-item">
<span class="info-label">企业名称:</span>
<span class="info-value">{{ selectedUser.enterprise_info.company_name }}</span>
</div>
<div class="info-item">
<span class="info-label">统一社会信用代码:</span>
<span class="info-value">{{ selectedUser.enterprise_info.unified_social_code }}</span>
</div>
<div class="info-item">
<span class="info-label">法定代表人:</span>
<span class="info-value">{{ selectedUser.enterprise_info.legal_person_name }}</span>
</div>
<div class="info-item">
<span class="info-label">法定代表人手机:</span>
<span class="info-value">{{ formatPhone(selectedUser.enterprise_info.legal_person_phone) }}</span>
</div>
<div class="info-item col-span-2">
<span class="info-label">企业地址:</span>
<span class="info-value">{{ selectedUser.enterprise_info.enterprise_address }}</span>
</div>
<div class="info-item">
<span class="info-label">企业邮箱:</span>
<span class="info-value">{{ selectedUser.enterprise_info.enterprise_email || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">认证时间:</span>
<span class="info-value">{{ formatDate(selectedUser.enterprise_info.created_at) }}</span>
</div>
</div>
<!-- 合同信息部分 -->
<div v-if="selectedUser.enterprise_info.contracts && selectedUser.enterprise_info.contracts.length > 0" class="contracts-section mt-6">
<h4 class="text-md font-semibold text-gray-900 mb-3">合同信息</h4>
<div class="contracts-list space-y-3">
<div
v-for="contract in selectedUser.enterprise_info.contracts"
:key="contract.id"
class="contract-item bg-gray-50 border border-gray-200 rounded-lg p-4"
>
<div class="flex items-center justify-between">
<div class="flex-1">
<div class="flex items-center space-x-3 mb-2">
<span class="text-sm font-medium text-gray-900">{{ contract.contract_name }}</span>
<el-tag size="small" type="primary">{{ contract.contract_type_name || contract.contract_type }}</el-tag>
</div>
<div class="text-sm text-gray-600">
创建时间: {{ formatDate(contract.created_at) }}
</div>
</div>
<div class="flex items-center space-x-2">
<el-button
size="small"
type="primary"
@click="handleViewContract(contract)"
>
查看合同
</el-button>
</div>
</div>
</div>
</div>
</div>
<div v-else class="text-center py-6 text-gray-500">
暂无合同信息
</div>
</div>
<!-- 未认证提示 -->
<div v-else class="text-center py-6 text-gray-500 bg-gray-50 rounded-lg">
<el-icon class="text-4xl text-gray-300 mb-2"><warning /></el-icon>
<div class="text-lg font-medium text-gray-600">该用户尚未完成企业认证</div>
<div class="text-sm text-gray-500 mt-1">完成企业认证后可查看详细信息</div>
</div>
</div>
</el-dialog>
<!-- 充值弹窗 -->
<el-dialog
v-model="rechargeDialogVisible"
title="用户充值"
:width="isMobile ? '90%' : '500px'"
class="recharge-dialog"
>
<div v-if="selectedUser" class="space-y-6">
<div class="user-info mb-4">
<div class="info-item">
<span class="info-label">用户手机:</span>
<span class="info-value">{{ formatPhone(selectedUser.phone) }}</span>
</div>
<div class="info-item">
<span class="info-label">用户类型:</span>
<span class="info-value">
<el-tag :type="selectedUser.user_type === 'admin' ? 'danger' : 'primary'" size="small">
{{ selectedUser.user_type === 'admin' ? '管理员' : '普通用户' }}
</el-tag>
</span>
</div>
</div>
<el-form
ref="rechargeFormRef"
:model="rechargeForm"
:rules="rechargeRules"
label-width="100px"
>
<el-form-item label="充值方式" prop="rechargeType">
<el-radio-group v-model="rechargeForm.rechargeType">
<el-radio label="transfer">对公转账</el-radio>
<el-radio label="gift">赠送充值</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="充值金额" prop="amount">
<el-input
v-model="rechargeForm.amount"
placeholder="请输入充值金额"
type="number"
min="0.01"
step="0.01"
>
<template #append></template>
</el-input>
</el-form-item>
<el-form-item
v-if="rechargeForm.rechargeType === 'transfer'"
label="转账订单号"
prop="transferOrderID"
>
<el-input
v-model="rechargeForm.transferOrderID"
placeholder="请输入转账订单号"
/>
</el-form-item>
<el-form-item label="备注信息" prop="notes">
<el-input
v-model="rechargeForm.notes"
type="textarea"
:rows="3"
placeholder="请输入备注信息(可选)"
maxlength="500"
show-word-limit
/>
</el-form-item>
</el-form>
</div>
<template #footer>
<div :class="['dialog-footer', isMobile ? 'flex-col' : '']">
<el-button
:class="isMobile ? 'w-full' : ''"
@click="rechargeDialogVisible = false"
>
取消
</el-button>
<el-button
type="primary"
:class="isMobile ? 'w-full' : ''"
@click="handleSubmitRecharge"
:loading="rechargeLoading"
>
确认充值
</el-button>
</div>
</template>
</el-dialog>
</template>
</ListPageLayout>
</template>
<script setup>
import { financeApi, userApi } from '@/api'
import FilterItem from '@/components/common/FilterItem.vue'
import FilterSection from '@/components/common/FilterSection.vue'
import ListPageLayout from '@/components/common/ListPageLayout.vue'
import { useMobileTable } from '@/composables/useMobileTable'
import { ArrowDown, Document, Money, Tickets, Wallet, Warning } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { useRouter } from 'vue-router'
// 获取路由实例
const router = useRouter()
// 移动端检测
const { isMobile, isTablet } = useMobileTable()
// 响应式数据
const loading = ref(false)
const users = ref([])
const total = ref(0)
const currentPage = ref(1)
const pageSize = ref(10)
const userDialogVisible = ref(false)
const rechargeDialogVisible = ref(false)
const selectedUser = ref(null)
const rechargeLoading = ref(false)
const rechargeFormRef = ref(null)
// 统计数据
const stats = ref({
total_users: 0,
active_users: 0,
certified_users: 0
})
// 筛选条件
const filters = reactive({
phone: '',
user_type: '',
is_active: null,
is_certified: null,
company_name: ''
})
// 充值表单
const rechargeForm = reactive({
rechargeType: 'transfer',
amount: '',
transferOrderID: '',
notes: ''
})
// 充值表单验证规则
const rechargeRules = {
rechargeType: [
{ required: true, message: '请选择充值方式', trigger: 'change' }
],
amount: [
{ required: true, message: '请输入充值金额', trigger: 'blur' },
{
pattern: /^[1-9]\d*(\.\d{1,2})?$/,
message: '请输入有效的金额最少0.01元)',
trigger: 'blur'
}
],
transferOrderID: [
{
required: true,
message: '请输入转账订单号',
trigger: 'blur',
validator: (rule, value, callback) => {
if (rechargeForm.rechargeType === 'transfer' && !value) {
callback(new Error('请输入转账订单号'))
} else {
callback()
}
}
}
]
}
// 搜索防抖
let searchTimer = null
// 初始化
onMounted(() => {
loadUsers()
loadStats()
})
// 加载用户列表
const loadUsers = async () => {
loading.value = true
try {
const params = {
page: currentPage.value,
page_size: pageSize.value,
...filters
}
const response = await userApi.getUserList(params)
users.value = response.data?.items || []
total.value = response.data?.total || 0
} catch (error) {
console.error('加载用户失败:', error)
ElMessage.error('加载用户失败')
} finally {
loading.value = false
}
}
// 加载统计数据
const loadStats = async () => {
try {
const response = await userApi.getUserStats()
stats.value = {
total_users: response.data?.total_users || 0,
active_users: response.data?.active_users || 0,
certified_users: response.data?.certified_users || 0
}
} catch (error) {
console.error('加载统计数据失败:', error)
ElMessage.error('加载统计数据失败')
}
}
// 格式化手机号
const formatPhone = (phone) => {
// if (!phone) return '-'
// if (phone.length < 7) return phone
// return phone.substring(0, 3) + '****' + phone.substring(phone.length - 4)
return phone
}
// 格式化日期
const formatDate = (date) => {
if (!date) return '-'
return new Date(date).toLocaleDateString('zh-CN')
}
// 格式化时间
const formatTime = (date) => {
if (!date) return '-'
return new Date(date).toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit'
})
}
// 格式化金额
const formatMoney = (amount) => {
if (!amount) return '0.00'
const num = parseFloat(amount)
if (isNaN(num)) return '0.00'
return num.toFixed(2)
}
// 处理筛选变化
const handleFilterChange = () => {
currentPage.value = 1
loadUsers()
}
// 处理搜索
const handleSearch = () => {
if (searchTimer) {
clearTimeout(searchTimer)
}
searchTimer = setTimeout(() => {
currentPage.value = 1
loadUsers()
}, 500)
}
// 重置筛选
const resetFilters = () => {
Object.keys(filters).forEach(key => {
filters[key] = key.includes('is_') ? null : ''
})
currentPage.value = 1
loadUsers()
}
// 处理分页大小变化
const handleSizeChange = (size) => {
pageSize.value = size
currentPage.value = 1
loadUsers()
}
// 处理当前页变化
const handleCurrentChange = (page) => {
currentPage.value = page
loadUsers()
}
// 查看用户详情
const handleViewUser = (user) => {
selectedUser.value = user
userDialogVisible.value = true
}
// 查看合同
const handleViewContract = (contract) => {
if (contract.contract_file_url) {
// 在新窗口中打开合同链接
window.open(contract.contract_file_url, '_blank')
} else {
ElMessage.warning('合同文件链接不可用')
}
}
// 处理充值
const handleRecharge = (user) => {
// 检查用户是否已认证
if (!user.is_certified) {
ElMessage.warning('该用户尚未完成企业认证,无法进行充值操作')
return
}
selectedUser.value = user
// 重置表单
rechargeForm.rechargeType = 'transfer'
rechargeForm.amount = ''
rechargeForm.transferOrderID = ''
rechargeForm.notes = ''
rechargeDialogVisible.value = true
}
// 提交充值
const handleSubmitRecharge = async () => {
if (!rechargeFormRef.value) return
try {
await rechargeFormRef.value.validate()
rechargeLoading.value = true
const params = {
user_id: selectedUser.value.id,
amount: rechargeForm.amount,
notes: rechargeForm.notes
}
if (rechargeForm.rechargeType === 'transfer') {
params.transfer_order_id = rechargeForm.transferOrderID
await financeApi.transferRecharge(params)
ElMessage.success('对公转账充值成功')
} else {
await financeApi.giftRecharge(params)
ElMessage.success('赠送充值成功')
}
rechargeDialogVisible.value = false
// 重新加载用户列表
loadUsers()
} catch (error) {
console.error('充值失败:', error)
ElMessage.error(error.response?.data?.message || '充值失败')
} finally {
rechargeLoading.value = false
}
}
// 处理更多操作
const handleMoreAction = (command) => {
const { action, user } = command
// 检查用户是否已认证
if (!user.is_certified) {
ElMessage.warning('该用户尚未完成企业认证,无法执行此操作')
return
}
switch (action) {
case 'subscriptions':
router.push({
name: 'AdminSubscriptions',
query: { user_id: user.id }
})
break
case 'api_calls':
router.push({
name: 'AdminUsage',
query: { user_id: user.id }
})
break
case 'consumption':
router.push({
name: 'AdminTransactions',
query: { user_id: user.id }
})
break
case 'recharge_history':
router.push({
name: 'AdminRechargeRecords',
query: { user_id: user.id }
})
break
default:
ElMessage.warning('未知操作')
}
}
</script>
<style scoped>
/* 统计项样式 */
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px 24px;
background: rgba(255, 255, 255, 0.95);
border: 1px solid rgba(226, 232, 240, 0.6);
border-radius: 12px;
min-width: 120px;
}
.stat-value {
font-size: 24px;
font-weight: 700;
color: #1e293b;
line-height: 1;
margin-bottom: 4px;
}
.stat-label {
font-size: 13px;
color: #64748b;
font-weight: 500;
}
/* 用户详情弹窗样式 */
.user-dialog :deep(.el-dialog) {
border-radius: 16px;
overflow: hidden;
}
.user-dialog :deep(.el-dialog__header) {
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
border-bottom: 1px solid rgba(226, 232, 240, 0.6);
padding: 20px 24px;
}
.user-dialog :deep(.el-dialog__title) {
font-size: 18px;
font-weight: 600;
color: #1e293b;
}
.user-dialog :deep(.el-dialog__body) {
padding: 24px;
max-height: 70vh;
overflow-y: auto;
}
/* 企业信息弹窗样式 */
.enterprise-dialog :deep(.el-dialog) {
border-radius: 16px;
overflow: hidden;
}
.enterprise-dialog :deep(.el-dialog__header) {
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
border-bottom: 1px solid rgba(226, 232, 240, 0.6);
padding: 20px 24px;
}
.enterprise-dialog :deep(.el-dialog__title) {
font-size: 18px;
font-weight: 600;
color: #1e293b;
}
.enterprise-dialog :deep(.el-dialog__body) {
padding: 24px;
}
/* 充值弹窗样式 */
.recharge-dialog :deep(.el-dialog) {
border-radius: 16px;
overflow: hidden;
}
.recharge-dialog :deep(.el-dialog__header) {
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
border-bottom: 1px solid rgba(226, 232, 240, 0.6);
padding: 20px 24px;
}
.recharge-dialog :deep(.el-dialog__title) {
font-size: 18px;
font-weight: 600;
color: #1e293b;
}
.recharge-dialog :deep(.el-dialog__body) {
padding: 24px;
}
.recharge-dialog :deep(.el-dialog__footer) {
background: rgba(248, 250, 252, 0.5);
border-top: 1px solid rgba(226, 232, 240, 0.4);
padding: 16px 24px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
}
/* 用户统计卡片 */
.user-stat-card {
background: rgba(248, 250, 252, 0.8);
border: 1px solid rgba(226, 232, 240, 0.6);
border-radius: 12px;
padding: 20px;
text-align: center;
transition: all 0.2s ease;
}
.user-stat-card:hover {
background: rgba(255, 255, 255, 0.9);
border-color: rgba(59, 130, 246, 0.3);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.user-stat-value {
font-size: 28px;
font-weight: 700;
color: #1e293b;
line-height: 1;
margin-bottom: 8px;
}
.user-stat-label {
font-size: 14px;
color: #64748b;
font-weight: 500;
}
/* 用户信息 */
.user-info {
background: rgba(248, 250, 252, 0.5);
border: 1px solid rgba(226, 232, 240, 0.4);
border-radius: 8px;
padding: 16px;
}
/* 企业信息 */
.enterprise-info {
background: rgba(248, 250, 252, 0.5);
border: 1px solid rgba(226, 232, 240, 0.4);
border-radius: 8px;
padding: 16px;
}
/* 合同信息 */
.contracts-section {
background: rgba(248, 250, 252, 0.5);
border: 1px solid rgba(226, 232, 240, 0.4);
border-radius: 8px;
padding: 16px;
}
.contract-item {
transition: all 0.2s ease;
}
.contract-item:hover {
background: rgba(255, 255, 255, 0.8);
border-color: rgba(59, 130, 246, 0.3);
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid rgba(226, 232, 240, 0.3);
}
.info-item:last-child {
border-bottom: none;
}
.info-label {
font-size: 14px;
color: #64748b;
font-weight: 500;
}
.info-value {
font-size: 14px;
color: #1e293b;
font-weight: 600;
}
/* 表格样式优化 */
:deep(.el-table) {
border-radius: 8px;
overflow: hidden;
}
:deep(.el-table th) {
background: #f8fafc !important;
border-bottom: 1px solid #e2e8f0;
}
:deep(.el-table td) {
border-bottom: 1px solid #f1f5f9;
}
:deep(.el-table tr:hover > td) {
background: #f8fafc !important;
}
/* 移动端卡片布局 */
.user-cards {
display: flex;
flex-direction: column;
gap: 12px;
}
.user-card {
background: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 16px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid #f3f4f6;
}
.card-body {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 12px;
}
.card-row {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 8px;
}
.card-label {
font-size: 12px;
color: #6b7280;
font-weight: 500;
min-width: 80px;
flex-shrink: 0;
}
.card-value {
font-size: 14px;
color: #1f2937;
text-align: right;
word-break: break-word;
flex: 1;
}
.card-footer {
padding-top: 12px;
border-top: 1px solid #f3f4f6;
}
.action-buttons {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.action-btn {
flex: 1;
min-width: 0;
}
/* 表格容器 */
.table-container {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
/* 响应式设计 */
@media (max-width: 768px) {
.stat-item {
padding: 12px 16px;
min-width: 100px;
}
.stat-value {
font-size: 20px;
}
.stat-label {
font-size: 12px;
}
.user-stat-card {
padding: 16px;
}
.user-stat-value {
font-size: 24px;
}
.user-stat-label {
font-size: 13px;
}
/* 表格在移动端优化 */
.table-container {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
:deep(.el-table) {
font-size: 12px;
min-width: 1000px;
}
:deep(.el-table th),
:deep(.el-table td) {
padding: 8px 4px;
}
:deep(.el-table .cell) {
padding: 0 4px;
word-break: break-word;
line-height: 1.4;
}
/* 分页组件在移动端优化 */
:deep(.el-pagination) {
justify-content: center;
}
:deep(.el-pagination .el-pagination__sizes) {
display: none;
}
:deep(.el-pagination .el-pagination__total) {
display: none;
}
:deep(.el-pagination .el-pagination__jump) {
display: none;
}
/* 对话框在移动端优化 */
:deep(.user-dialog .el-dialog__body),
:deep(.recharge-dialog .el-dialog__body) {
padding: 16px;
max-height: 80vh;
}
.dialog-footer {
flex-direction: column;
gap: 8px;
}
}
/* 超小屏幕进一步优化 */
@media (max-width: 480px) {
.user-card {
padding: 12px;
}
.card-header {
flex-direction: column;
gap: 8px;
}
.card-body {
gap: 6px;
}
.card-label {
font-size: 11px;
min-width: 70px;
}
.card-value {
font-size: 13px;
}
.card-row {
flex-direction: column;
align-items: flex-start;
gap: 4px;
}
.card-value {
text-align: left;
}
.action-buttons {
gap: 6px;
}
.action-btn {
font-size: 12px;
padding: 6px 8px;
}
}
/* 禁用按钮样式优化 */
:deep(.el-button.is-disabled) {
opacity: 0.6;
cursor: not-allowed;
}
:deep(.el-button.is-disabled:hover) {
opacity: 0.6;
}
/* 工具提示样式 */
:deep(.el-tooltip__popper) {
font-size: 12px;
line-height: 1.4;
}
</style>