Mars Developer 51f8d95bf9 first commit
2025-06-26 11:24:11 +08:00

925 lines
33 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>
<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>