925 lines
33 KiB
Vue
Raw Normal View History

2025-06-26 11:24:11 +08:00
<template>
<div class="space-y-6">
<!-- 页面头部 -->
<div class="flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold text-gray-900">用户管理</h1>
<p class="mt-1 text-sm text-gray-600">管理系统中的所有用户账户</p>
</div>
<div class="flex space-x-3">
<button
@click="exportUsers"
class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50"
>
导出数据
</button>
<button
@click="createUser"
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700"
>
添加用户
</button>
</div>
</div>
<!-- 统计卡片 -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center">
<span class="text-white text-sm font-semibold"></span>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">总用户数</dt>
<dd class="text-lg font-medium text-gray-900">{{ userStats.total }}</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-green-500 rounded-full flex items-center justify-center">
<span class="text-white text-sm font-semibold"></span>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">译员数量</dt>
<dd class="text-lg font-medium text-gray-900">{{ userStats.interpreters }}</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-purple-500 rounded-full flex items-center justify-center">
<span class="text-white text-sm font-semibold"></span>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">管理员数量</dt>
<dd class="text-lg font-medium text-gray-900">{{ userStats.admins }}</dd>
</dl>
</div>
</div>
</div>
</div>
</div>
<!-- 搜索和过滤 -->
<div class="bg-white shadow rounded-lg p-6">
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">搜索用户</label>
<input
v-model="searchQuery"
type="text"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="搜索姓名、邮箱或手机号"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">角色筛选</label>
<select
v-model="selectedRole"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="">全部角色</option>
<option value="admin">管理员</option>
<option value="customer">客户</option>
<option value="interpreter">翻译员</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">状态筛选</label>
<select
v-model="selectedStatus"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="">全部状态</option>
<option value="active">活跃</option>
<option value="inactive">非活跃</option>
<option value="suspended">已暂停</option>
</select>
</div>
<div class="flex items-end">
<button
@click="resetFilters"
class="w-full px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 border border-gray-300 rounded-md hover:bg-gray-200"
>
重置筛选
</button>
</div>
</div>
</div>
<!-- 用户列表 -->
<div class="bg-white shadow overflow-hidden sm:rounded-md">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
用户
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
角色
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
状态
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
余额
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
注册时间
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
操作
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tr v-for="user in paginatedUsers" :key="user.id">
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="flex-shrink-0 h-10 w-10">
<div class="h-10 w-10 rounded-full bg-gray-300 flex items-center justify-center">
<span class="text-sm font-medium text-gray-700">
{{ user.full_name?.charAt(0) || '用' }}
</span>
</div>
</div>
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">{{ user.full_name || '未设置' }}</div>
<div class="text-sm text-gray-500">{{ user.email }}</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full"
:class="{
'bg-purple-100 text-purple-800': user.role === 'admin',
'bg-green-100 text-green-800': user.role === 'customer',
'bg-blue-100 text-blue-800': user.role === 'interpreter'
}">
{{ getRoleText(user.role) }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full"
:class="getStatusClass(user.status)">
{{ getStatusText(user.status) }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
¥{{ user.credits?.toFixed(2) || '0.00' }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{{ formatDate(user.created_at) }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<button
@click="editUser(user)"
class="text-blue-600 hover:text-blue-900 mr-3"
>
编辑
</button>
<button
@click="deleteUser(user)"
class="text-red-600 hover:text-red-900"
>
删除
</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 分页组件 -->
<div class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6" v-if="totalPages > 1">
<div class="flex-1 flex justify-between sm:hidden">
<button
@click="goToPage(currentPage - 1)"
:disabled="currentPage <= 1"
class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
>
上一页
</button>
<button
@click="goToPage(currentPage + 1)"
:disabled="currentPage >= totalPages"
class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
>
下一页
</button>
</div>
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
<div>
<p class="text-sm text-gray-700">
显示第 <span class="font-medium">{{ (currentPage - 1) * 20 + 1 }}</span>
<span class="font-medium">{{ Math.min(currentPage * 20, filteredUsers.length) }}</span>
<span class="font-medium">{{ filteredUsers.length }}</span> 条记录
</p>
</div>
<div>
<nav class="relative z-0 inline-flex rounded-md shadow-sm -space-x-px" aria-label="Pagination">
<button
@click="goToPage(currentPage - 1)"
:disabled="currentPage <= 1"
class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
>
<span class="sr-only">上一页</span>
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd" />
</svg>
</button>
<template v-for="page in Math.min(totalPages, 7)" :key="page">
<button
@click="goToPage(page)"
:class="[
page === currentPage
? 'z-10 bg-blue-50 border-blue-500 text-blue-600'
: 'bg-white border-gray-300 text-gray-500 hover:bg-gray-50',
'relative inline-flex items-center px-4 py-2 border text-sm font-medium'
]"
>
{{ page }}
</button>
</template>
<button
@click="goToPage(currentPage + 1)"
:disabled="currentPage >= totalPages"
class="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
>
<span class="sr-only">下一页</span>
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
</svg>
</button>
</nav>
</div>
</div>
</div>
</div>
</div>
<!-- 添加用户模态框 -->
<div v-if="showCreateModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
<div class="relative top-20 mx-auto p-5 border w-11/12 md:w-3/4 lg:w-1/2 shadow-lg rounded-md bg-white">
<div class="mt-3">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-medium text-gray-900">添加新用户</h3>
<button @click="showCreateModal = false" class="text-gray-400 hover:text-gray-600">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<form @submit.prevent="submitCreateUser" class="space-y-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- 基本信息 -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">邮箱地址 *</label>
<input
v-model="newUser.email"
type="email"
required
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="请输入邮箱地址"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">密码 *</label>
<input
v-model="newUser.password"
type="password"
required
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="请输入密码"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">姓名 *</label>
<input
v-model="newUser.full_name"
type="text"
required
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="请输入姓名"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">手机号</label>
<input
v-model="newUser.phone"
type="tel"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="请输入手机号"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">角色 *</label>
<select
v-model="newUser.role"
required
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="">请选择角色</option>
<option value="admin">管理员</option>
<option value="customer">客户</option>
<option value="interpreter">翻译员</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">初始余额</label>
<input
v-model.number="newUser.credits"
type="number"
min="0"
step="0.01"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="0.00"
/>
</div>
</div>
<!-- 翻译员专用字段 -->
<div v-if="newUser.role === 'interpreter'" class="space-y-4 border-t pt-4">
<h4 class="text-md font-medium text-gray-900">翻译员专业信息</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">公司</label>
<input
v-model="newUser.company"
type="text"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="所属公司"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">部门</label>
<input
v-model="newUser.department"
type="text"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="所属部门"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">时薪/小时</label>
<input
v-model.number="newUser.hourly_rate"
type="number"
min="0"
step="0.01"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="100.00"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">时区</label>
<select
v-model="newUser.timezone"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="UTC+8">北京时间 (UTC+8)</option>
<option value="UTC">协调世界时 (UTC)</option>
<option value="UTC-5">美国东部时间 (UTC-5)</option>
<option value="UTC-8">美国西部时间 (UTC-8)</option>
</select>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">专业领域</label>
<div class="grid grid-cols-2 md:grid-cols-4 gap-2">
<label v-for="spec in specializationOptions" :key="spec" class="flex items-center">
<input
type="checkbox"
:value="spec"
v-model="newUser.specializations"
class="mr-2 text-blue-600"
/>
<span class="text-sm text-gray-700">{{ spec }}</span>
</label>
</div>
</div>
</div>
<!-- 按钮 -->
<div class="flex justify-end space-x-3 pt-4">
<button
type="button"
@click="showCreateModal = false"
class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50"
>
取消
</button>
<button
type="submit"
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700"
>
创建用户
</button>
</div>
</form>
</div>
</div>
</div>
<!-- 编辑用户模态框 -->
<div v-if="showEditModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
<div class="relative top-20 mx-auto p-5 border w-11/12 md:w-3/4 lg:w-1/2 shadow-lg rounded-md bg-white">
<div class="mt-3">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-medium text-gray-900">编辑用户</h3>
<button @click="showEditModal = false" class="text-gray-400 hover:text-gray-600">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<form @submit.prevent="submitUpdateUser" class="space-y-4" v-if="editingUser">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">邮箱地址</label>
<input
v-model="editingUser.email"
type="email"
disabled
class="w-full px-3 py-2 border border-gray-300 rounded-md bg-gray-50 text-gray-500"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">姓名 *</label>
<input
v-model="editingUser.full_name"
type="text"
required
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">手机号</label>
<input
v-model="editingUser.phone"
type="tel"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">角色 *</label>
<select
v-model="editingUser.role"
required
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="admin">管理员</option>
<option value="customer">客户</option>
<option value="interpreter">翻译员</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">余额</label>
<input
v-model.number="editingUser.credits"
type="number"
min="0"
step="0.01"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">状态</label>
<select
v-model="editingUser.status"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="active">活跃</option>
<option value="inactive">非活跃</option>
<option value="suspended">已暂停</option>
</select>
</div>
</div>
<div class="flex justify-end space-x-3 pt-4">
<button
type="button"
@click="showEditModal = false"
class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50"
>
取消
</button>
<button
type="submit"
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700"
>
更新用户
</button>
</div>
</form>
</div>
</div>
</div>
</template>
<script setup>
// 页面元数据 - 使用管理员认证和默认布局
definePageMeta({
middleware: 'admin-auth',
layout: 'default' // 明确指定使用默认布局
})
// 页面标题
useHead({
title: '用户管理 - 翻译管理系统'
})
// 路由
const router = useRouter()
// 导入Supabase数据操作
const { getProfiles } = useSupabaseData()
// 响应式数据
const users = ref([])
const loading = ref(false)
const searchQuery = ref('')
const selectedRole = ref('')
const selectedStatus = ref('')
const currentPage = ref(1)
const totalPages = ref(1)
const showCreateModal = ref(false)
const showEditModal = ref(false)
const editingUser = ref(null)
const allUsers = ref([]) // 存储所有用户数据用于筛选
// 用户统计数据
const userStats = computed(() => {
const total = allUsers.value.length
const interpreters = allUsers.value.filter(user => user.role === 'interpreter').length
const admins = allUsers.value.filter(user => user.role === 'admin').length
const customers = allUsers.value.filter(user => user.role === 'customer').length
return {
total,
interpreters,
admins,
customers
}
})
// 计算属性:过滤后的用户列表
const filteredUsers = computed(() => {
let filtered = [...allUsers.value]
// 搜索筛选
if (searchQuery.value) {
const query = searchQuery.value.toLowerCase()
filtered = filtered.filter(user =>
user.full_name?.toLowerCase().includes(query) ||
user.email?.toLowerCase().includes(query) ||
user.phone?.includes(query)
)
}
// 角色筛选
if (selectedRole.value) {
filtered = filtered.filter(user => user.role === selectedRole.value)
}
// 状态筛选
if (selectedStatus.value) {
filtered = filtered.filter(user => user.status === selectedStatus.value)
}
return filtered
})
// 计算属性:分页后的用户列表
const paginatedUsers = computed(() => {
const pageSize = 20
totalPages.value = Math.ceil(filteredUsers.value.length / pageSize)
const startIndex = (currentPage.value - 1) * pageSize
const endIndex = startIndex + pageSize
return filteredUsers.value.slice(startIndex, endIndex)
})
// 新用户表单数据
const newUser = ref({
email: '',
password: '',
full_name: '',
role: '',
phone: '',
company: '',
department: '',
specializations: [],
hourly_rate: null,
timezone: 'UTC'
})
// 角色选项
const roleOptions = [
{ value: '', label: '所有角色' },
{ value: 'admin', label: '管理员' },
{ value: 'customer', label: '客户' },
{ value: 'interpreter', label: '翻译员' }
]
// 状态选项
const statusOptions = [
{ value: '', label: '所有状态' },
{ value: 'active', label: '活跃' },
{ value: 'inactive', label: '非活跃' },
{ value: 'suspended', label: '已暂停' }
]
// 专业领域选项
const specializationOptions = [
'医疗翻译', '法律翻译', '技术翻译', '商务翻译',
'学术翻译', '金融翻译', '手语翻译', '会议翻译'
]
// 获取用户列表
const fetchUsers = async () => {
loading.value = true
try {
console.log('开始获取用户数据...')
// 临时使用模拟数据避免Supabase连接问题
const mockUsers = [
{
id: '1',
email: 'admin@example.com',
full_name: '系统管理员',
phone: '13800138000',
role: 'admin',
credits: 1000,
status: 'active',
created_at: new Date().toISOString(),
is_enterprise: false
},
{
id: '2',
email: 'translator1@example.com',
full_name: '李译员',
phone: '13800138001',
role: 'interpreter',
credits: 500,
status: 'active',
created_at: new Date(Date.now() - 86400000).toISOString(),
is_enterprise: false
},
{
id: '3',
email: 'customer1@example.com',
full_name: '张客户',
phone: '13800138002',
role: 'customer',
credits: 200,
status: 'active',
created_at: new Date(Date.now() - 172800000).toISOString(),
is_enterprise: false
},
{
id: '4',
email: 'translator2@example.com',
full_name: '王译员',
phone: '13800138003',
role: 'interpreter',
credits: 750,
status: 'inactive',
created_at: new Date(Date.now() - 259200000).toISOString(),
is_enterprise: false
},
{
id: '5',
email: 'customer2@example.com',
full_name: '陈客户',
phone: '13800138004',
role: 'customer',
credits: 150,
status: 'suspended',
created_at: new Date(Date.now() - 345600000).toISOString(),
is_enterprise: true
}
]
allUsers.value = mockUsers
console.log('用户数据加载成功:', allUsers.value.length, '个用户')
// 正式版本应该使用:
// const profilesData = await getProfiles()
// allUsers.value = profilesData || []
} catch (error) {
console.error('获取用户列表失败:', error)
allUsers.value = []
} finally {
loading.value = false
}
}
// 重置筛选条件
const resetFilters = () => {
searchQuery.value = ''
selectedRole.value = ''
selectedStatus.value = ''
currentPage.value = 1
}
// 筛选用户数据(保留原函数但不再需要)
const filterUsers = () => {
// 这个函数现在由计算属性 filteredUsers 和 paginatedUsers 处理
// 保留空函数以防其他地方调用
}
// 提交创建用户表单
const submitCreateUser = async () => {
try {
console.log('创建新用户:', newUser.value)
// 这里应该调用Supabase的用户创建API
// 暂时使用模拟数据添加到列表中
const newUserData = {
id: Date.now().toString(),
...newUser.value,
credits: newUser.value.credits || 0,
status: 'active',
created_at: new Date().toISOString(),
is_enterprise: false
}
allUsers.value.push(newUserData)
showCreateModal.value = false
resetNewUserForm()
alert('用户创建成功!')
} catch (error) {
console.error('创建用户失败:', error)
alert('创建用户失败,请重试')
}
}
// 创建用户按钮处理
const createUser = () => {
showCreateModal.value = true
}
// 编辑用户
const editUser = (user) => {
editingUser.value = { ...user }
showEditModal.value = true
}
// 提交更新用户表单
const submitUpdateUser = async () => {
try {
console.log('更新用户:', editingUser.value)
// 这里应该调用Supabase的用户更新API
// 暂时更新本地数据
const index = allUsers.value.findIndex(u => u.id === editingUser.value.id)
if (index !== -1) {
allUsers.value[index] = { ...editingUser.value }
}
showEditModal.value = false
editingUser.value = null
alert('用户更新成功!')
} catch (error) {
console.error('更新用户失败:', error)
alert('更新用户失败,请重试')
}
}
// 删除用户
const deleteUser = async (userId) => {
if (confirm('确定要删除此用户吗?此操作将禁用用户账户。')) {
try {
// 注意这里需要使用Supabase的用户删除API
// 暂时保留原API调用后续可以改为直接使用Supabase
await $fetch('/api/admin/users', {
method: 'DELETE',
body: { userId }
})
await fetchUsers()
// 显示成功提示
} catch (error) {
console.error('删除用户失败:', error)
// 显示错误提示
}
}
}
// 重置新用户表单
const resetNewUserForm = () => {
newUser.value = {
email: '',
password: '',
full_name: '',
role: '',
phone: '',
company: '',
department: '',
specializations: [],
hourly_rate: null,
timezone: 'UTC',
credits: 0
}
}
// 格式化日期
const formatDate = (dateString) => {
if (!dateString) return '从未登录'
return new Date(dateString).toLocaleString('zh-CN')
}
// 获取状态显示文本
const getStatusText = (status) => {
const statusMap = {
'suspended': '已暂停',
'inactive': '非活跃',
'active': '活跃'
}
return statusMap[status] || '未知状态'
}
// 获取状态样式类
const getStatusClass = (status) => {
const classMap = {
'suspended': 'bg-red-100 text-red-800',
'active': 'bg-green-100 text-green-800',
'inactive': 'bg-yellow-100 text-yellow-800'
}
return classMap[status] || 'bg-gray-100 text-gray-800'
}
// 获取角色显示文本
const getRoleText = (role) => {
const roleMap = {
admin: '管理员',
customer: '客户',
interpreter: '翻译员'
}
return roleMap[role] || role
}
// 监听搜索和筛选变化
watch([searchQuery, selectedRole, selectedStatus], () => {
currentPage.value = 1 // 重置到第一页
})
// 分页处理
const goToPage = (page) => {
currentPage.value = page
}
// 页面挂载时获取数据
onMounted(() => {
fetchUsers()
})
// 导出用户数据
const exportUsers = () => {
alert('导出功能开发中...')
}
</script>