Initial commit of akmon project
This commit is contained in:
850
demo_message_system.html
Normal file
850
demo_message_system.html
Normal file
@@ -0,0 +1,850 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Supabase 消息系统演示</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
||||
padding: 40px;
|
||||
max-width: 900px;
|
||||
width: 90%;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.header p {
|
||||
color: #666;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
background: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin-bottom: 30px;
|
||||
border-left: 4px solid #007bff;
|
||||
}
|
||||
|
||||
.user-info h3 {
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.user-info .info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: 700;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #333;
|
||||
background: white;
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.role-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.role-admin { background: #dc3545; color: white; }
|
||||
.role-teacher { background: #28a745; color: white; }
|
||||
.role-student { background: #007bff; color: white; }
|
||||
|
||||
.section {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.section h3 {
|
||||
color: #333;
|
||||
margin-bottom: 15px;
|
||||
font-size: 1.3rem;
|
||||
border-bottom: 2px solid #eee;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 20px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 400;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: #0056b3;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
background: #ccc;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.btn-success { background: #28a745; }
|
||||
.btn-success:hover { background: #1e7e34; }
|
||||
|
||||
.btn-warning { background: #ffc107; color: #333; }
|
||||
.btn-warning:hover { background: #e0a800; }
|
||||
|
||||
.btn-danger { background: #dc3545; }
|
||||
.btn-danger:hover { background: #c82333; }
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group textarea,
|
||||
.form-group select {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.form-group input:focus,
|
||||
.form-group textarea:focus,
|
||||
.form-group select:focus {
|
||||
outline: none;
|
||||
border-color: #007bff;
|
||||
}
|
||||
|
||||
.results {
|
||||
background: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin-top: 20px;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.results h4 {
|
||||
color: #333;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.results pre {
|
||||
background: #2d3748;
|
||||
color: #e2e8f0;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.message-list {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.message-item {
|
||||
background: white;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin-bottom: 10px;
|
||||
border-left: 4px solid #007bff;
|
||||
}
|
||||
|
||||
.message-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.message-title {
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.message-time {
|
||||
color: #666;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
color: #555;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 3px solid #f3f3f3;
|
||||
border-top: 3px solid #3498db;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.alert {
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
background: #d1ecf1;
|
||||
color: #0c5460;
|
||||
border: 1px solid #bee5eb;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1> Supabase 消息系统</h1>
|
||||
<p>完整的角色管理与权限控制演示</p>
|
||||
</div>
|
||||
|
||||
<!-- 登录表单 -->
|
||||
<div id="login-section" class="section">
|
||||
<h3> 用户登录</h3>
|
||||
<div class="form-group">
|
||||
<label>邮箱地址</label>
|
||||
<input type="email" id="email" placeholder="请输入邮箱" value="teacher@example.com">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>密码</label>
|
||||
<input type="password" id="password" placeholder="请输入密码" value="password123">
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<button class="btn" onclick="signIn()">
|
||||
<span></span> 登录
|
||||
</button>
|
||||
<button class="btn btn-success" onclick="signUp()">
|
||||
<span></span> 注册
|
||||
</button>
|
||||
</div>
|
||||
<div class="alert alert-info">
|
||||
<strong>测试账户:</strong><br>
|
||||
教师: teacher@example.com / password123<br>
|
||||
学生: student@example.com / password123
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 用户信息 -->
|
||||
<div id="user-section" class="section hidden">
|
||||
<div class="user-info">
|
||||
<h3> 当前用户信息</h3>
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<span class="info-label">邮箱:</span>
|
||||
<span class="info-value" id="user-email">-</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">角色:</span>
|
||||
<span class="role-badge" id="user-role">-</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">用户ID:</span>
|
||||
<span class="info-value" id="user-id">-</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">部门:</span>
|
||||
<span class="info-value" id="user-department">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button class="btn btn-danger" onclick="signOut()">
|
||||
<span></span> 退出登录
|
||||
</button>
|
||||
<button class="btn btn-warning" onclick="testPermissions()">
|
||||
<span></span> 测试权限
|
||||
</button>
|
||||
<button class="btn" onclick="refreshUserInfo()">
|
||||
<span></span> 刷新信息
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 消息功能 -->
|
||||
<div id="message-section" class="section hidden">
|
||||
<h3> 消息功能</h3>
|
||||
|
||||
<!-- 发送消息 -->
|
||||
<div class="form-group">
|
||||
<label>消息标题</label>
|
||||
<input type="text" id="message-title" placeholder="请输入消息标题">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>消息内容</label>
|
||||
<textarea id="message-content" rows="3" placeholder="请输入消息内容"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>接收者类型</label>
|
||||
<select id="receiver-type">
|
||||
<option value="user">指定用户</option>
|
||||
<option value="broadcast">广播消息</option>
|
||||
<option value="group">群组消息</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>接收者ID(用户ID或群组ID)</label>
|
||||
<input type="text" id="receiver-id" placeholder="请输入接收者ID">
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button class="btn" onclick="sendMessage()">
|
||||
<span></span> 发送消息
|
||||
</button>
|
||||
<button class="btn btn-success" onclick="loadMessages()">
|
||||
<span></span> 刷新消息
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 消息列表 -->
|
||||
<div class="message-list" id="message-list">
|
||||
<h4> 消息列表</h4>
|
||||
<div id="messages-container">
|
||||
<p>点击"刷新消息"加载消息列表</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 管理员功能 -->
|
||||
<div id="admin-section" class="section hidden">
|
||||
<h3>⚙️ 管理员功能</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<label>目标用户ID</label>
|
||||
<input type="text" id="target-user-id" placeholder="请输入要管理的用户ID">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>新角色</label>
|
||||
<select id="new-role">
|
||||
<option value="student">学生</option>
|
||||
<option value="teacher">教师</option>
|
||||
<option value="admin">管理员</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button class="btn" onclick="updateUserRole()">
|
||||
<span></span> 更新用户角色
|
||||
</button>
|
||||
<button class="btn btn-success" onclick="getAllUsers()">
|
||||
<span></span> 查看所有用户
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 结果显示 -->
|
||||
<div id="results" class="results hidden">
|
||||
<h4> 操作结果</h4>
|
||||
<pre id="results-content"></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Supabase 配置 - 请替换为你的实际配置
|
||||
const SUPABASE_URL = 'YOUR_SUPABASE_URL'
|
||||
const SUPABASE_ANON_KEY = 'YOUR_SUPABASE_ANON_KEY'
|
||||
|
||||
// 如果没有配置,使用演示模式
|
||||
if (SUPABASE_URL === 'YOUR_SUPABASE_URL') {
|
||||
document.querySelector('.container').innerHTML = `
|
||||
<div class="header">
|
||||
<h1>⚠️ 配置需要</h1>
|
||||
<p>请在代码中配置你的 Supabase URL 和 API Key</p>
|
||||
</div>
|
||||
<div class="alert alert-error">
|
||||
<strong>配置步骤:</strong><br>
|
||||
1. 替换 SUPABASE_URL 为你的项目URL<br>
|
||||
2. 替换 SUPABASE_ANON_KEY 为你的匿名密钥<br>
|
||||
3. 执行 deploy_complete_system.sql 部署数据库<br>
|
||||
4. 创建测试用户账户
|
||||
</div>
|
||||
`
|
||||
return
|
||||
}
|
||||
|
||||
const supabase = supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
|
||||
let currentUser = null
|
||||
let currentRole = null
|
||||
|
||||
// 初始化
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
console.log(' 初始化消息系统演示')
|
||||
|
||||
// 检查当前用户
|
||||
const { data: { user } } = await supabase.auth.getUser()
|
||||
if (user) {
|
||||
await handleUserSignedIn(user)
|
||||
}
|
||||
|
||||
// 监听认证状态变化
|
||||
supabase.auth.onAuthStateChange(async (event, session) => {
|
||||
console.log(' 认证状态变化:', event)
|
||||
|
||||
if (event === 'SIGNED_IN' && session?.user) {
|
||||
await handleUserSignedIn(session.user)
|
||||
} else if (event === 'SIGNED_OUT') {
|
||||
handleUserSignedOut()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 用户登录处理
|
||||
async function handleUserSignedIn(user) {
|
||||
currentUser = user
|
||||
currentRole = await getUserRole(user.id)
|
||||
|
||||
// 显示/隐藏相应区域
|
||||
document.getElementById('login-section').classList.add('hidden')
|
||||
document.getElementById('user-section').classList.remove('hidden')
|
||||
document.getElementById('message-section').classList.remove('hidden')
|
||||
|
||||
if (currentRole === 'admin') {
|
||||
document.getElementById('admin-section').classList.remove('hidden')
|
||||
}
|
||||
|
||||
// 更新用户信息显示
|
||||
updateUserInfoDisplay()
|
||||
|
||||
showAlert('登录成功!', 'success')
|
||||
}
|
||||
|
||||
// 用户退出处理
|
||||
function handleUserSignedOut() {
|
||||
currentUser = null
|
||||
currentRole = null
|
||||
|
||||
// 显示/隐藏相应区域
|
||||
document.getElementById('login-section').classList.remove('hidden')
|
||||
document.getElementById('user-section').classList.add('hidden')
|
||||
document.getElementById('message-section').classList.add('hidden')
|
||||
document.getElementById('admin-section').classList.add('hidden')
|
||||
document.getElementById('results').classList.add('hidden')
|
||||
|
||||
showAlert('已退出登录', 'info')
|
||||
}
|
||||
|
||||
// 更新用户信息显示
|
||||
function updateUserInfoDisplay() {
|
||||
if (!currentUser) return
|
||||
|
||||
document.getElementById('user-email').textContent = currentUser.email
|
||||
document.getElementById('user-id').textContent = currentUser.id.substring(0, 8) + '...'
|
||||
|
||||
const roleElement = document.getElementById('user-role')
|
||||
roleElement.textContent = currentRole || 'unknown'
|
||||
roleElement.className = `role-badge role-${currentRole || 'unknown'}`
|
||||
|
||||
// 从元数据获取部门信息
|
||||
const department = currentUser.user_metadata?.department ||
|
||||
currentUser.raw_user_meta_data?.department ||
|
||||
'未设置'
|
||||
document.getElementById('user-department').textContent = department
|
||||
}
|
||||
|
||||
// 获取用户角色
|
||||
async function getUserRole(userId) {
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.rpc('get_user_role', { target_user_id: userId })
|
||||
|
||||
if (error) throw error
|
||||
return data || 'student'
|
||||
} catch (error) {
|
||||
console.error('获取用户角色失败:', error)
|
||||
return 'student'
|
||||
}
|
||||
}
|
||||
|
||||
// 登录
|
||||
async function signIn() {
|
||||
const email = document.getElementById('email').value
|
||||
const password = document.getElementById('password').value
|
||||
|
||||
if (!email || !password) {
|
||||
showAlert('请输入邮箱和密码', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const { data, error } = await supabase.auth.signInWithPassword({
|
||||
email,
|
||||
password
|
||||
})
|
||||
|
||||
if (error) throw error
|
||||
|
||||
// 用户登录成功会触发 onAuthStateChange
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error)
|
||||
showAlert(`登录失败: ${error.message}`, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
// 注册
|
||||
async function signUp() {
|
||||
const email = document.getElementById('email').value
|
||||
const password = document.getElementById('password').value
|
||||
|
||||
if (!email || !password) {
|
||||
showAlert('请输入邮箱和密码', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const { data, error } = await supabase.auth.signUp({
|
||||
email,
|
||||
password,
|
||||
options: {
|
||||
data: {
|
||||
department: 'Demo Department'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (error) throw error
|
||||
|
||||
showAlert('注册成功!请检查邮箱验证链接', 'success')
|
||||
} catch (error) {
|
||||
console.error('注册失败:', error)
|
||||
showAlert(`注册失败: ${error.message}`, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
async function signOut() {
|
||||
try {
|
||||
const { error } = await supabase.auth.signOut()
|
||||
if (error) throw error
|
||||
|
||||
// 退出成功会触发 onAuthStateChange
|
||||
} catch (error) {
|
||||
console.error('退出失败:', error)
|
||||
showAlert(`退出失败: ${error.message}`, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
// 测试权限
|
||||
async function testPermissions() {
|
||||
if (!currentUser) return
|
||||
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.rpc('test_message_permissions', { test_user_id: currentUser.id })
|
||||
|
||||
if (error) throw error
|
||||
|
||||
showResults('权限测试结果', data)
|
||||
} catch (error) {
|
||||
console.error('权限测试失败:', error)
|
||||
showAlert(`权限测试失败: ${error.message}`, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新用户信息
|
||||
async function refreshUserInfo() {
|
||||
if (!currentUser) return
|
||||
|
||||
try {
|
||||
const { data: { user } } = await supabase.auth.getUser()
|
||||
if (user) {
|
||||
currentUser = user
|
||||
currentRole = await getUserRole(user.id)
|
||||
updateUserInfoDisplay()
|
||||
showAlert('用户信息已刷新', 'success')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('刷新用户信息失败:', error)
|
||||
showAlert(`刷新失败: ${error.message}`, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
async function sendMessage() {
|
||||
const title = document.getElementById('message-title').value
|
||||
const content = document.getElementById('message-content').value
|
||||
const receiverType = document.getElementById('receiver-type').value
|
||||
const receiverId = document.getElementById('receiver-id').value
|
||||
|
||||
if (!title || !content) {
|
||||
showAlert('请输入消息标题和内容', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
if (receiverType !== 'broadcast' && !receiverId) {
|
||||
showAlert('请输入接收者ID', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取消息类型ID
|
||||
const { data: messageTypes } = await supabase
|
||||
.from('ak_message_types')
|
||||
.select('id')
|
||||
.eq('type_name', 'notification')
|
||||
.limit(1)
|
||||
|
||||
const messageTypeId = messageTypes?.[0]?.id
|
||||
|
||||
if (!messageTypeId) {
|
||||
throw new Error('未找到消息类型')
|
||||
}
|
||||
|
||||
const { data, error } = await supabase
|
||||
.rpc('send_secure_message', {
|
||||
message_type_id: messageTypeId,
|
||||
receiver_type: receiverType,
|
||||
receiver_id: receiverType === 'broadcast' ? null : receiverId,
|
||||
title,
|
||||
content,
|
||||
metadata_json: { source: 'web_demo' }
|
||||
})
|
||||
|
||||
if (error) throw error
|
||||
|
||||
showAlert('消息发送成功!', 'success')
|
||||
|
||||
// 清空表单
|
||||
document.getElementById('message-title').value = ''
|
||||
document.getElementById('message-content').value = ''
|
||||
document.getElementById('receiver-id').value = ''
|
||||
|
||||
// 刷新消息列表
|
||||
await loadMessages()
|
||||
|
||||
} catch (error) {
|
||||
console.error('发送消息失败:', error)
|
||||
showAlert(`发送消息失败: ${error.message}`, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
// 加载消息列表
|
||||
async function loadMessages() {
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('ak_messages')
|
||||
.select(`
|
||||
*,
|
||||
ak_message_types(type_name, display_name)
|
||||
`)
|
||||
.order('created_at', { ascending: false })
|
||||
.limit(10)
|
||||
|
||||
if (error) throw error
|
||||
|
||||
displayMessages(data)
|
||||
showAlert('消息列表已刷新', 'success')
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载消息失败:', error)
|
||||
showAlert(`加载消息失败: ${error.message}`, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
// 显示消息列表
|
||||
function displayMessages(messages) {
|
||||
const container = document.getElementById('messages-container')
|
||||
|
||||
if (!messages || messages.length === 0) {
|
||||
container.innerHTML = '<p>暂无消息</p>'
|
||||
return
|
||||
}
|
||||
|
||||
const messagesHtml = messages.map(msg => `
|
||||
<div class="message-item">
|
||||
<div class="message-header">
|
||||
<span class="message-title">${msg.title}</span>
|
||||
<span class="message-time">${new Date(msg.created_at).toLocaleString()}</span>
|
||||
</div>
|
||||
<div class="message-content">${msg.content}</div>
|
||||
<div style="margin-top: 10px; font-size: 0.8rem; color: #888;">
|
||||
ID: ${msg.id.substring(0, 8)}... |
|
||||
类型: ${msg.ak_message_types?.display_name || '未知'} |
|
||||
接收者: ${msg.receiver_type}
|
||||
</div>
|
||||
</div>
|
||||
`).join('')
|
||||
|
||||
container.innerHTML = messagesHtml
|
||||
}
|
||||
|
||||
// 更新用户角色(管理员功能)
|
||||
async function updateUserRole() {
|
||||
const targetUserId = document.getElementById('target-user-id').value
|
||||
const newRole = document.getElementById('new-role').value
|
||||
|
||||
if (!targetUserId) {
|
||||
showAlert('请输入目标用户ID', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.rpc('update_user_role', {
|
||||
target_user_id: targetUserId,
|
||||
new_role: newRole,
|
||||
additional_data: { department: 'Updated via Demo' }
|
||||
})
|
||||
|
||||
if (error) throw error
|
||||
|
||||
showAlert('用户角色更新成功!', 'success')
|
||||
document.getElementById('target-user-id').value = ''
|
||||
|
||||
} catch (error) {
|
||||
console.error('更新用户角色失败:', error)
|
||||
showAlert(`更新失败: ${error.message}`, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
// 获取所有用户(管理员功能)
|
||||
async function getAllUsers() {
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('user_roles_detailed')
|
||||
.select('*')
|
||||
.order('created_at', { ascending: false })
|
||||
|
||||
if (error) throw error
|
||||
|
||||
showResults('所有用户列表', data)
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取用户列表失败:', error)
|
||||
showAlert(`获取用户列表失败: ${error.message}`, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
// 显示结果
|
||||
function showResults(title, data) {
|
||||
const resultsSection = document.getElementById('results')
|
||||
const resultsContent = document.getElementById('results-content')
|
||||
|
||||
resultsSection.classList.remove('hidden')
|
||||
resultsSection.querySelector('h4').textContent = ` ${title}`
|
||||
resultsContent.textContent = JSON.stringify(data, null, 2)
|
||||
|
||||
// 滚动到结果区域
|
||||
resultsSection.scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
|
||||
// 显示提示信息
|
||||
function showAlert(message, type = 'info') {
|
||||
// 移除现有的alert
|
||||
const existingAlert = document.querySelector('.alert')
|
||||
if (existingAlert && !existingAlert.classList.contains('alert-info')) {
|
||||
existingAlert.remove()
|
||||
}
|
||||
|
||||
const alertDiv = document.createElement('div')
|
||||
alertDiv.className = `alert alert-${type}`
|
||||
alertDiv.textContent = message
|
||||
|
||||
// 插入到container的开始
|
||||
const container = document.querySelector('.container')
|
||||
container.insertBefore(alertDiv, container.firstChild)
|
||||
|
||||
// 3秒后自动移除
|
||||
setTimeout(() => {
|
||||
if (alertDiv.parentNode) {
|
||||
alertDiv.remove()
|
||||
}
|
||||
}, 3000)
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user