Files
chatroom/resources/views/chat/partials/user-actions.blade.php
T
2026-04-25 19:00:41 +08:00

707 lines
48 KiB
PHP
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.
{{--
文件功能:用户交互全局函数 + 名片弹窗组件
包含:
1. switchTarget() 单击用户名切换聊天目标
2. openUserCard() 双击用户名打开名片弹窗
3. 用户名片弹窗 Alpine.js 组件(资料查看 + 送花 + 管理操作)
scripts.blade.php frame.blade.php 中抽取,保持代码职责清晰。
@author ChatRoom Laravel
@version 1.0.0
--}}
{{-- 全局 switchTarget/openUserCard 已迁移到 resources/js/chat-room/user-target-actions.js --}}
{{-- ═══════════ 用户名片弹窗 (Alpine.js) ═══════════ --}}
@php $gifts = \App\Models\Gift::activeList(); @endphp
{{-- 用户名片弹窗组件脚本已迁移到 resources/js/chat-room/user-card.js --}}
<div id="user-modal-container"
x-data="userCardComponent()"
data-gifts-base64="{{ base64_encode($gifts->toJson()) }}"
data-default-gift-id="{{ $gifts->first()?->id ?? 0 }}">
<div x-show="showUserModal" style="display: none;" class="modal-overlay" x-on:click.self="showUserModal = false">
<div class="modal-card" x-transition>
{{-- 弹窗头部 --}}
<div class="modal-header">
<h3 x-text="'用户名片 · ' + userInfo.username"></h3>
<button class="modal-close" x-on:click="showUserModal = false">&times;</button>
</div>
{{-- 弹窗内容 --}}
<div class="modal-body">
<div class="profile-row">
<img class="profile-avatar" x-show="userInfo.headface"
:src="(userInfo.headface || '1.gif').startsWith('storage/') ? '/' + (userInfo.headface || '1.gif') : '/images/headface/' + (userInfo.headface || '1.gif')"
x-on:error="$el.style.display='none'"
style="cursor: pointer; transition: transform 0.2s;"
x-on:click="if(userInfo.headface) showOriginalLightbox = true"
title="点击查看大图"
onmouseover="this.style.transform='scale(1.05)'"
onmouseout="this.style.transform='scale(1)'">
<div class="profile-info">
<h4>
<span x-text="userInfo.username"></span>
<span class="level-badge" x-text="'LV.' + userInfo.user_level"></span>
<span class="sex-badge"
x-text="userInfo.sex === '男' ? '♂' : (userInfo.sex === '女' ? '♀' : '')"
:style="userInfo.sex === '男' ? 'color: blue' : (userInfo.sex === '女' ?
'color: deeppink' : '')"></span>
</h4>
{{-- VIP + 在职职务:合并成一个胶囊,保证同一行 --}}
<div x-show="userInfo.vip?.Name || userInfo.position_name"
style="margin-top: 6px;">
<span style="display: inline-flex;align-items: center;gap: 6px;padding: 2px 10px;
border-radius: 999px;white-space: nowrap;width: fit-content;max-width: 100%;
font-size: 11px;font-weight: bold;background: #faf5ff;border: 1px solid #d8b4fe;color: #7c3aed;">
{{-- VIP --}}
<template x-if="userInfo.vip?.Name">
<span style="display: inline-flex; align-items: center; gap: 4px; color: #ea580c;">
<span x-text="userInfo.vip?.Icon || '👑'" style="font-size: 12px;"></span>
<span x-text="userInfo.vip?.Name"></span>
</span>
</template>
{{-- 分隔符:只有 VIP 和职务都存在时显示 --}}
<template x-if="userInfo.vip?.Name && userInfo.position_name">
<span style="color: #c4b5fd;">|</span>
</template>
{{-- 职务 --}}
<template x-if="userInfo.position_name">
<span style="display: inline-flex; align-items: center; gap: 4px; color: #7c3aed;">
<span x-text="userInfo.position_icon || '🎖️'" style="font-size: 12px;"></span>
<span
x-text="(userInfo.department_name ? userInfo.department_name + ' · ' : '') + userInfo.position_name"></span>
</span>
</template>
</span>
</div>
<div x-show="userInfo.sign_in?.streak_days || userInfo.sign_in?.identity"
style="margin-top: 6px;">
<span style="display: inline-flex;align-items: center;gap: 6px;padding: 2px 10px;
border-radius: 999px;white-space: nowrap;width: fit-content;max-width: 100%;
font-size: 11px;font-weight: bold;background: #f0fdfa;border: 1px solid #99f6e4;"
:style="'color:' + (userInfo.sign_in?.identity?.color || '#0f766e')">
<span x-text="userInfo.sign_in?.identity?.icon || '✅'"></span>
<span x-text="'连续签到 ' + (userInfo.sign_in?.streak_days || 0) + ' 天'"></span>
<template x-if="userInfo.sign_in?.identity?.label">
<span x-text="'· ' + userInfo.sign_in.identity.label"></span>
</template>
</span>
</div>
<div style="font-size: 11px; color: #999; margin-top: 4px;">
加入: <span x-text="userInfo.created_at"></span>
</div>
</div>
</div>
{{-- 详细信息区:外层 x-show 控制显隐,内层单独写 flex 避免被 Alpine 覆盖 --}}
<div x-show="userInfo.exp_num !== undefined" style="margin-top: 12px;">
<div style="display: flex; flex-direction: row; gap: 8px;">
<!-- 经验 -->
<div
style="flex: 1; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; padding: 6px 0; display: flex; flex-direction: column; align-items: center; justify-content: center;">
<span style="color: #64748b; font-size: 11px; margin-bottom: 2px;">经验</span>
<span x-text="userInfo.exp_num"
style="font-weight: 700; color: #4f46e5; font-size: 14px;"></span>
</div>
<!-- 金币 -->
<div
style="flex: 1; background: #fdfae8; border: 1px solid #fef08a; border-radius: 6px; padding: 6px 0; display: flex; flex-direction: column; align-items: center; justify-content: center;">
<span style="color: #b45309; font-size: 11px; margin-bottom: 2px;">金币</span>
<span x-text="userInfo.jjb"
style="font-weight: 700; color: #d97706; font-size: 14px;"></span>
</div>
<!-- 存款 -->
<div
style="flex: 1; background: #ecfdf5; border: 1px solid #a7f3d0; border-radius: 6px; padding: 6px 0; display: flex; flex-direction: column; align-items: center; justify-content: center;">
<span style="color: #047857; font-size: 11px; margin-bottom: 2px;">存款</span>
<span x-text="userInfo.bank_jjb"
style="font-weight: 700; color: #059669; font-size: 14px;"></span>
</div>
<!-- 魅力 -->
<div
style="flex: 1; background: #fdf2f8; border: 1px solid #fbcfe8; border-radius: 6px; padding: 6px 0; display: flex; flex-direction: column; align-items: center; justify-content: center;">
<span style="color: #be185d; font-size: 11px; margin-bottom: 2px;">魅力</span>
<span x-text="userInfo.meili"
style="font-weight: 700; color: #db2777; font-size: 14px;"></span>
</div>
</div>
</div>
{{-- 邀请贡献卡片 (仅查看自己资料时显示) --}}
<div x-show="userInfo.username === window.chatContext.username && userInfo.id !== undefined"
style="margin-top: 12px; border-radius: 8px; overflow: hidden; border: 1px dashed #c7d2fe;">
<div
style="background: #e0e7ff; padding: 6px 10px; font-size: 11px; font-weight: bold; color: #4338ca; display: flex; align-items: center; justify-content: space-between;">
<span>🤝 我的邀请推广</span>
<span style="font-size: 10px;">累计邀请:<span x-text="userInfo.invitees_count"
style="color: #ef4444; font-size: 12px;"></span> </span>
</div>
<div
style="padding: 8px 10px; background: #f8fafc; font-size: 11px; display: flex; flex-direction: column; gap: 6px;">
<span style="color: #64748b;">复制下方专属链接,邀请好友注册聊天室:</span>
<div style="display: flex; gap: 6px; align-items: center;">
<input type="text" readonly :value="window.location.origin + '/' + userInfo.id"
style="flex: 1; border: 1px solid #cbd5e1; border-radius: 4px; padding: 4px 6px; font-size: 11px; background: #fff; color: #334155;">
<button
x-on:click="navigator.clipboard.writeText(window.location.origin + '/' + userInfo.id); $alert('邀请链接已复制到剪贴板', '复制成功', '#22c55e');"
style="background: #4f46e5; color: white; border: none; border-radius: 4px; padding: 4px 8px; font-size: 11px; cursor: pointer; font-weight: bold;">
复制
</button>
</div>
</div>
</div>
{{-- 管理员可见区域 (IP 归属地) --}}
<div x-show="userInfo.last_ip !== undefined"
style="margin-top: 8px; border-radius: 8px; overflow: hidden;">
{{-- 可点击标题 --}}
<div x-on:click="showAdminView = !showAdminView"
style="display: flex; align-items: center; justify-content: space-between; padding: 6px 10px;
background: #fee2e2; border: 1px dashed #fca5a5; border-radius: 8px;
cursor: pointer; font-size: 11px; color: #991b1b; font-weight: bold; user-select: none;">
<span>🛡️ 管理员视野</span>
<span x-text="showAdminView ? '▲' : '▼'" style="font-size: 10px; opacity: 0.6;"></span>
</div>
{{-- 折叠内容 --}}
<div x-show="showAdminView" x-transition
style="display: none; padding: 8px 10px; background: #fff5f5; border: 1px dashed #fca5a5;
border-top: none; border-radius: 0 0 8px 8px; font-size: 11px; color: #991b1b;">
<div style="display: flex; flex-direction: column; gap: 3px;">
<div><span style="opacity: 0.8;">首次IP</span><span
x-text="userInfo.first_ip || '无'"></span>
</div>
<div><span style="opacity: 0.8;">上次IP</span><span
x-text="userInfo.last_ip || '无'"></span>
</div>
<div><span style="opacity: 0.8;">本次IP</span><span
x-text="userInfo.login_ip || '无'"></span>
</div>
<div><span style="opacity: 0.8;">归属地:</span><span
x-text="userInfo.location || '未知'"></span>
</div>
</div>
</div>
</div>
<div class="profile-detail" x-text="userInfo.sign || '这家伙很懒,什么也没留下'"
style="margin-top: 12px;"></div>
{{-- 职务履历时间轴(有任职记录才显示,可折叠) --}}
<div x-show="userInfo.position_history && userInfo.position_history.length > 0"
style="display: none; margin-top: 10px; border-top: 1px solid #f0f0f0; padding-top: 8px;">
{{-- 可点击标题 --}}
<div x-on:click="showPositionHistory = !showPositionHistory"
style="display: flex; align-items: center; justify-content: space-between;
cursor: pointer; font-size: 11px; font-weight: bold; color: #7c3aed;
margin-bottom: 4px; user-select: none;">
<span>🎖️ 职务履历 <span style="font-weight: normal; font-size: 10px; color: #9ca3af;"
x-text="'' + (userInfo.position_history?.length ?? 0) + ' 条)'"></span></span>
<span x-text="showPositionHistory ? '▲' : '▼'" style="font-size: 10px; opacity:0.5;"></span>
</div>
{{-- 折叠内容 --}}
<div x-show="showPositionHistory" x-transition style="display: none;">
<template x-for="(h, idx) in userInfo.position_history" :key="idx">
<div style="display: flex; gap: 10px; margin-bottom: 8px; position: relative;">
{{-- 线 --}}
<div
style="display: flex; flex-direction: column; align-items: center; width: 18px; flex-shrink: 0;">
<div
style="width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; margin-top: 2px;"
:style="h.is_active ? 'background: #7c3aed; box-shadow: 0 0 0 3px #ede9fe;' :
'background: #d1d5db;'">
</div>
<template x-if="idx < (userInfo.position_history?.length ?? 0) - 1">
<div style="width: 1px; flex: 1; background: #e5e7eb; margin-top: 2px;"></div>
</template>
</div>
{{-- 内容 --}}
<div style="flex: 1; font-size: 11px; padding-bottom: 4px;">
<div style="font-weight: bold; color: #374151;">
<span x-text="h.position_icon" style="margin-right: 2px;"></span>
<span
x-text="(h.department_name ? h.department_name + ' · ' : '') + h.position_name"></span>
<span x-show="h.is_active"
style="display: inline-block; margin-left: 4px; padding: 0 5px; background: #ede9fe; color: #7c3aed; border-radius: 10px; font-size: 10px;">在职中</span>
</div>
<div style="color: #9ca3af; font-size: 10px; margin-top: 2px;">
<span x-text="h.appointed_at"></span>
<span> </span>
<span x-text="h.is_active ? '至今' : (h.revoked_at || '')"></span>
<span style="margin-left: 4px;" x-text="'(' + h.duration_days + ' 天)'"></span>
</div>
</div>
</div>
</template>
</div>
</div>
</div>
{{-- 操作按钮区:加好友 + 送礼物 + 送金币(有职务且有奖励权限时显示) --}}
<div x-show="userInfo.username !== window.chatContext.username">
<div class="modal-actions" style="margin-bottom: 0; display: flex; gap: 6px;">
{{-- 加好友 / 删好友 --}}
<button x-on:click="toggleFriend()" :disabled="friendLoading"
:style="is_friend
?
'background: #f1f5f9; color: #6b7280; border: 1px solid #d1d5db;' :
'background: linear-gradient(135deg,#16a34a,#22c55e); color:#fff; border:none;'"
style="flex:1; padding: 7px 10px; border-radius: 5px; font-size: 12px;
cursor: pointer; font-weight: bold; transition: opacity .15s;"
x-text="friendLoading ? '处理中…' : (is_friend ? '✅ 好友' : ' 加好友')"></button>
{{-- 送礼物按钮 --}}
<button class="btn-whisper" style="flex:1;"
x-on:click="toggleGiftPanel()">
🎁 送礼物
</button>
{{-- 赠送金币按钮:所有人均可使用,自己金币转给对方 --}}
<button
style="flex:1; padding: 7px 10px; border-radius: 5px; font-size: 12px; font-weight: bold; cursor: pointer;
background: linear-gradient(135deg,#f59e0b,#d97706); color:#fff; border:none;"
x-on:click="toggleGiftGoldPanel()">
💰 赠金币
</button>
{{-- 求婚按钮:对方未婚 + 双方均已设置性别 + 异性 --}}
<button
x-show="!marriageLoading
&& (!targetMarriage || targetMarriage.status === 'none')
&& userInfo.sex
&& mySex
&& userInfo.sex !== mySex"
style="flex:1; padding: 7px 10px; border-radius: 5px; font-size: 12px; font-weight: bold; cursor: pointer;
background: linear-gradient(135deg,#f43f5e,#ec4899); color:#fff; border:none;"
x-on:click="showUserModal = false; openProposeModal(userInfo.username)">
💍 求婚
</button>
{{-- 自己未设置性别时的提示(让用户去完善资料) --}}
<div x-show="!marriageLoading
&& (!targetMarriage || targetMarriage.status === 'none')
&& userInfo.sex
&& !mySex
&& userInfo.sex !== mySex"
style="flex:1; display:flex; align-items:center; justify-content:center;
padding:7px 10px; border-radius:5px; font-size:11px;
background:#f8fafc; border:1px dashed #cbd5e1; color:#94a3b8; font-weight:bold; cursor:default;"
title="请到个人资料页设置您的性别后即可求婚">
💍 请先设置性别
</div>
{{-- 对方已婚时显示提示(非伴侣) --}}
<div
x-show="!marriageLoading && targetMarriage && targetMarriage.status === 'married' && !targetMarriage.is_my_partner"
:title="'与 ' + (targetMarriage?.partner_name || '—') + ' 已婚'"
style="flex:1; display:flex; align-items:center; justify-content:center;
padding:7px 10px; border-radius:5px; font-size:12px; background:#fff1f2;
border:1px solid #fecdd3; color:#f43f5e; font-weight:bold; white-space:nowrap;">
💑 已婚
</div>
{{-- 如果对方是自己的伴侣,显示离婚按钮 --}}
<button
x-show="!marriageLoading && targetMarriage && targetMarriage.status === 'married' && targetMarriage.is_my_partner"
style="flex:1; padding: 7px 10px; border-radius: 5px; font-size: 12px; font-weight: bold; cursor: pointer;
background: #64748b; color:#fff; border:none;"
x-on:click="doDivorce(targetMarriage.marriage_id)">
💔 协议离婚
</button>
</div>
{{-- 内联赠送金币面板 --}}
<div x-show="showGiftGoldPanel" x-transition
style="display: none; padding: 12px 16px; background: #fffbeb; border-top: 1px solid #fde68a;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:8px;">
<span style="font-size:13px; color:#92400e; font-weight:bold;">💰 赠送金币给 <span
x-text="userInfo.username"></span></span>
<button x-on:click="showGiftGoldPanel = false"
style="background:none; border:none; color:#92400e; cursor:pointer; font-size:18px; line-height:1; opacity:.6;">
×
</button>
</div>
<div style="font-size:11px; color:#b45309; margin-bottom:8px;">您当前余额:<b
x-text="window.chatContext.myGold ?? '—'"></b> 金币
</div>
<div style="display:flex; gap:8px; align-items:center;">
<input type="number" x-model.number="giftGoldAmount" min="1" max="999999"
placeholder="输入金额"
style="flex:1; height:36px; padding:0 10px; border:1px solid #fbbf24; border-radius:6px; font-size:13px; color:#334155;">
<button
x-on:click="sendGiftGold()"
:disabled="giftGoldSending"
style="height:36px; padding:0 16px; background:linear-gradient(135deg,#f59e0b,#d97706); color:#fff;
border:none; border-radius:6px; font-size:13px; font-weight:bold; cursor:pointer; white-space:nowrap;"
:style="giftGoldSending ? 'opacity:.6; cursor:not-allowed;' : ''"
x-text="giftGoldSending ? '赠送中…' : '💝 确认赠送'"></button>
</div>
</div>
{{-- 内联礼物面板 --}}
<div x-show="showGiftPanel" x-transition
style="display: none;
padding: 12px 16px; background: #fff; border-top: 1px solid #f1f5f9;">
<div
style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<span style="font-size: 13px; color: #334155; font-weight: bold;">🎁 选择礼物</span>
<button x-on:click="showGiftPanel = false"
style="background: none; border: none; color: #94a3b8; cursor: pointer; font-size: 18px; line-height: 1;">
×
</button>
</div>
{{-- 礼物选择列表 --}}
<div
style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; margin-bottom: 12px; max-height: 180px; overflow-y: auto;">
<template x-for="g in gifts" :key="g.id">
<div x-on:click="selectedGiftId = g.id"
:style="selectedGiftId === g.id ?
'border-color: #f43f5e; background: #fff1f2; box-shadow: 0 4px 12px rgba(244,63,94,0.15);' :
'border-color: #e2e8f0; background: #fff;'"
style="border: 2px solid; padding: 8px 4px; border-radius: 8px; text-align: center; cursor: pointer; transition: all 0.15s;">
<img :src="'/images/gifts/' + g.image"
style="width: 36px; height: 36px; object-fit: contain; margin-bottom: 4px;"
:alt="g.name">
<div
style="font-size: 11px; color: #1e293b; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;"
x-text="g.name"></div>
<div style="font-size: 10px; color: #e11d48;" x-text="g.cost + ' 💰'"></div>
</div>
</template>
</div>
{{-- 数量 + 送出 --}}
<div style="display: flex; gap: 8px; align-items: center;">
<select x-model.number="giftCount"
style="width: 70px; height: 36px; padding: 0 8px; border: 1px solid #cbd5e1; border-radius: 6px; font-size: 13px; color: #334155;">
<option value="1">1 </option>
<option value="5">5 </option>
<option value="10">10 </option>
<option value="66">66 </option>
<option value="99">99 </option>
<option value="520">520 </option>
</select>
<button x-on:click="sendGift(); showGiftPanel = false;" :disabled="sendingGift"
style="flex:1; height: 36px; background: linear-gradient(135deg,#f43f5e,#be123c); color:#fff;
border: none; border-radius: 6px; font-size: 14px; font-weight: bold; cursor: pointer;"
:style="sendingGift ? 'opacity:0.7; cursor:not-allowed;' : ''">
<span x-text="sendingGift ? '正在送出...' : '💝 确认赠送'"></span>
</button>
</div>
</div>
</div>
{{-- 管理操作 + 职务操作 合并折叠区 --}}
@php
$canWarnUser = Auth::id() === 1 || (($roomPermissionMap[\App\Support\PositionPermissionRegistry::USER_WARN] ?? false) === true);
$canKickUser = Auth::id() === 1 || (($roomPermissionMap[\App\Support\PositionPermissionRegistry::USER_KICK] ?? false) === true);
$canMuteUser = Auth::id() === 1 || (($roomPermissionMap[\App\Support\PositionPermissionRegistry::USER_MUTE] ?? false) === true);
$canFreezeUser = Auth::id() === 1 || (($roomPermissionMap[\App\Support\PositionPermissionRegistry::USER_FREEZE] ?? false) === true);
$canRewardUser = Auth::id() === 1 || (($roomPermissionMap[\App\Support\PositionPermissionRegistry::ROOM_REWARD] ?? false) === true);
$hasUserModerationPermission = $canWarnUser || $canKickUser || $canMuteUser || $canFreezeUser;
$hasPositionActions = Auth::user()->activePosition || $myLevel >= $superLevel;
@endphp
@if ($hasUserModerationPermission || $hasPositionActions)
<div style="padding: 0 16px 12px;" x-show="userInfo.username !== window.chatContext.username">
{{-- 折叠标题 --}}
<div x-on:click="showAdminPanel = !showAdminPanel"
style="display: flex; align-items: center; justify-content: space-between;
padding: 6px 10px; background: #fef2f2; border: 1px solid #fecaca;
border-radius: 6px; cursor: pointer; user-select: none;">
<span style="font-size: 11px; color: #c00; font-weight: bold;">🔧 管理操作</span>
<span x-text="showAdminPanel ? '▲' : '▼'"
style="font-size: 10px; color: #c00; opacity: 0.6;"></span>
</div>
{{-- 折叠内容 --}}
<div x-show="showAdminPanel" x-transition style="display: none; margin-top: 6px;">
@if ($hasUserModerationPermission)
<div x-show="canManageTargetByDuty()">
<div style="font-size: 10px; color: #9ca3af; margin-bottom: 4px; padding-left: 2px;">
管理员操作
</div>
<div style="display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 8px;">
@if ($canWarnUser)
<button
style="flex:1; padding: 5px; border-radius: 4px; font-size: 11px; background: #fef3c7; border: 1px solid #f59e0b; cursor: pointer;"
x-on:click="warnUser()">⚠️ 警告
</button>
@endif
@if ($canKickUser)
<button
style="flex:1; padding: 5px; border-radius: 4px; font-size: 11px; background: #fee2e2; border: 1px solid #ef4444; cursor: pointer;"
x-on:click="kickUser()">🚫 踢出
</button>
@endif
@if ($canMuteUser)
<button
style="flex:1; padding: 5px; border-radius: 4px; font-size: 11px; background: #e0e7ff; border: 1px solid #6366f1; cursor: pointer;"
x-on:click="isMuting = !isMuting">🔇 禁言
</button>
@endif
@if ($canFreezeUser)
<button
style="flex:1; padding: 5px; border-radius: 4px; font-size: 11px; background: #dbeafe; border: 1px solid #3b82f6; cursor: pointer;"
x-on:click="freezeUser()">🧊 冻结
</button>
@endif
{{-- 职务奖励金币(凭空产生),仅有明确奖励权限且 max_reward != 0 的人可见 --}}
@if ($canRewardUser)
<button x-show="window.chatContext?.myMaxReward !== 0"
style="flex:1; padding: 5px; border-radius: 4px; font-size: 11px; background: #fef3c7; border: 1px solid #f59e0b; cursor: pointer;"
x-on:click="openRewardModal(userInfo.username)">💰 奖励金币
</button>
@endif
</div>
</div>
@endif
@if ($hasPositionActions)
<div>
<div style="font-size: 10px; color: #9ca3af; margin-bottom: 4px; padding-left: 2px;">
职务操作
</div>
<div style="display: flex; gap: 6px; flex-wrap: wrap;">
<template x-if="!userInfo.position_name">
<button x-on:click="showAppointPanel = !showAppointPanel"
style="flex:1; padding: 5px; border-radius: 4px; font-size: 11px; background: #f3e8ff; border: 1px solid #a855f7; cursor: pointer;">
任命职务
</button>
</template>
<template x-if="userInfo.position_name">
<button x-on:click="doRevoke()" :disabled="appointLoading"
style="flex:1; padding: 5px; border-radius: 4px; font-size: 11px; background: #fef9c3; border: 1px solid #eab308; cursor: pointer;">
🔧
撤销职务
</button>
</template>
</div>
<div x-show="showAppointPanel" x-transition
style="display:none; margin-top:8px; padding:10px; background:#faf5ff; border:1px solid #d8b4fe; border-radius:6px;">
<div style="font-size:11px; color:#7c3aed; margin-bottom:6px;">选择职务:</div>
<select x-model.number="selectedPositionId"
style="width:100%; padding:4px; border:1px solid #c4b5fd; border-radius:4px; font-size:11px; margin-bottom:6px;">
<template x-for="p in appointPositions" :key="p.id">
<option :value="p.id"
x-text="(p.icon?p.icon+' ':'')+p.department+' · '+p.name"></option>
</template>
</select>
<input type="text" x-model="appointRemark" placeholder="备注(如任命原因)"
style="width:100%; padding:4px; border:1px solid #c4b5fd; border-radius:4px; font-size:11px; box-sizing:border-box; margin-bottom:6px;">
<div style="display:flex; gap:6px;">
<button x-on:click="doAppoint()"
:disabled="appointLoading || !selectedPositionId"
style="flex:1; padding:5px; background:#7c3aed; color:#fff; border:none; border-radius:4px; font-size:11px; cursor:pointer;">
<span x-text="appointLoading?'处理中...':'✅ 确认任命'"></span>
</button>
<button x-on:click="showAppointPanel=false"
style="padding:5px 10px; background:#fff; border:1px solid #ccc; border-radius:4px; font-size:11px; cursor:pointer;">
取消
</button>
</div>
</div>
</div>
@endif
</div>{{-- /折叠内容 --}}
{{-- 禁言输入表单 --}}
<div x-show="isMuting" style="display:none; margin-top:6px;">
<div style="display:flex; gap:6px; align-items:center;">
<input type="number" x-model="muteDuration" min="1" max="1440"
placeholder="分钟"
style="width:60px; padding:4px; border:1px solid #ccc; border-radius:3px; font-size:11px;">
<span style="font-size:11px; color:#b86e00;">分钟</span>
<button x-on:click="muteUser()"
style="padding:4px 12px; background:#6366f1; color:#fff; border:none; border-radius:3px; font-size:11px; cursor:pointer;">
执行
</button>
</div>
</div>
</div>
@endif
{{-- 私信记录展示区(管理员查看) --}}
<div x-show="showWhispers"
style="display: none; padding: 0 16px 12px; max-height: 200px; overflow-y: auto;">
<div style="font-size: 11px; color: #666; margin-bottom: 4px;"
x-text="'最近 ' + whisperList.length + ' 条悄悄话:'"></div>
<template x-for="w in whisperList" :key="w.id">
<div style="font-size: 11px; padding: 3px 0; border-bottom: 1px solid #f0f0f0;">
<span style="color: #6366f1;" x-text="w.from_user"></span>
<span style="color: #059669;" x-text="w.to_user"></span>
<span x-text="w.content"></span>
<span style="color: #aaa; font-size: 10px;" x-text="'(' + w.sent_at + ')'"></span>
</div>
</template>
<div x-show="whisperList.length === 0" style="font-size: 11px; color: #aaa;">暂无悄悄话记录</div>
</div>
</div>
</div>
{{-- 头像原图全屏大图预览灯箱 --}}
<template x-if="userInfo">
<div x-show="showOriginalLightbox"
style="display: none; z-index: 10000; background: rgba(0,0,0,0.85); backdrop-filter: blur(5px);"
class="modal-overlay" x-on:click.self="showOriginalLightbox = false" x-transition.opacity>
<div
style="position: absolute; top: 20px; right: 26px; color: rgba(255,255,255,0.7); font-size: 36px; cursor: pointer; transition: color 0.2s;"
x-on:click="showOriginalLightbox = false" onmouseover="this.style.color='white'"
onmouseout="this.style.color='rgba(255,255,255,0.7)'">&times;
</div>
<img
:src="userInfo.headface_original ? userInfo.headface_original : ((userInfo.headface || '1.gif').startsWith('storage/') ? '/' + (userInfo.headface || '1.gif') : '/images/headface/' + (userInfo.headface || '1.gif'))"
style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); max-width: 90vw; max-height: 90vh; object-fit: contain; border-radius: 8px; box-shadow: 0 10px 40px rgba(0,0,0,0.5);">
</div>
</template>
</div>
{{-- ═══════════ 奖励金币独立弹窗 ═══════════ --}}
<div id="reward-modal-container" x-data="rewardModal()">
<div x-show="show" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,.55); z-index:9900;"
x-on:click.self="show = false">
<div x-show="show"
style="display:none; position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);
width:440px; max-width:95vw;
background: linear-gradient(135deg, #f59e0b, #ea580c, #c2410c);
border-radius: 20px; padding: 28px 36px;
box-shadow: 0 20px 60px rgba(0,0,0,0.4);
border: 2px solid rgba(255,255,255,0.25); backdrop-filter: blur(8px);
text-align: center; color: #fff;">
{{-- 顶部图标与标题 --}}
<div style="font-size:44px; margin-bottom:8px; text-shadow:0 4px 8px rgba(0,0,0,0.2);">💰</div>
<div style="color:#fde68a; font-size:13px; font-weight:bold; letter-spacing:3px; margin-bottom:12px;">
══ 发放奖励金币 ══
</div>
<div
style="color:white; font-size:20px; font-weight:900; text-shadow:0 2px 8px rgba(0,0,0,0.3); margin-bottom:20px;">
发给:<span x-text="targetUsername"></span>
</div>
{{-- 额度四格 --}}
<div x-show="loading" style="color:rgba(255,255,255,0.8); font-size:12px; padding:12px 0;">
加载额度信息…
</div>
<div x-show="!loading">
<div style="display:grid; grid-template-columns:repeat(4,1fr); gap:8px; margin-bottom:20px;">
<div
style="background:rgba(255,255,255,0.15); border:1px solid rgba(255,255,255,0.2); border-radius:10px; padding:10px 4px;">
<div style="font-size:10px; color:rgba(255,255,255,0.7); margin-bottom:4px;">单次上限</div>
<div style="font-size:13px; font-weight:bold; color:#fff;" x-text="fmt(quota.max_once)"></div>
</div>
<div
style="background:rgba(255,255,255,0.15); border:1px solid rgba(255,255,255,0.2); border-radius:10px; padding:10px 4px;">
<div style="font-size:10px; color:rgba(255,255,255,0.7); margin-bottom:4px;">单日上限</div>
<div style="font-size:13px; font-weight:bold; color:#fff;"
x-text="fmt(quota.daily_limit)"></div>
</div>
<div
style="background:rgba(20,184,166,0.25); border:1px solid rgba(255,255,255,0.3); border-radius:10px; padding:10px 4px;">
<div style="font-size:10px; color:#a7f3d0; margin-bottom:4px;">今日已发</div>
<div style="font-size:13px; font-weight:bold; color:#fff;"
x-text="quota.today_sent.toLocaleString()"></div>
</div>
<div
style="background:rgba(20,184,166,0.25); border:1px solid rgba(255,255,255,0.3); border-radius:10px; padding:10px 4px;">
<div style="font-size:10px; color:#a7f3d0; margin-bottom:4px;">剩余额度</div>
<div style="font-size:13px; font-weight:bold; color:#fff;"
x-text="fmt(quota.daily_remaining)"></div>
</div>
</div>
</div>
{{-- 输入区 --}}
<div style="margin-bottom:18px;">
<input type="number" x-model.number="amount"
:placeholder="quota.max_once ? '最多 ' + quota.max_once + ' 金币' : '请输入发放金额'"
:max="quota.max_once || 999999" min="1" x-on:keydown.enter="send()"
style="width:100%; padding:14px; border:none; border-radius:12px;
background:rgba(255,255,255,0.95); font-size:16px; color:#9a3412;
font-weight:bold; text-align:center; outline:none;
box-shadow:inset 0 2px 6px rgba(0,0,0,0.1);">
<div style="margin-top:10px; font-size:11px; color:rgba(255,255,255,0.6);">
金币凭空产生并直接发放给对方,本操作记入你的履职记录。
</div>
</div>
{{-- 操作按钮 --}}
<div style="display:grid; grid-template-columns:minmax(0,1.35fr) minmax(0,0.85fr); gap:12px; width:min(100%, 360px); margin:0 auto 22px;">
<button x-on:click="send()"
style="background: linear-gradient(135deg, #fef08a, #facc15 48%, #f59e0b); color:#7c2d12;
border:2px solid rgba(255,255,255,0.5); border-radius:14px;
padding:14px 22px; min-height:56px; font-size:18px; font-weight:900; letter-spacing:1px; cursor:pointer;
box-shadow: 0 14px 28px rgba(124,45,18,0.35), inset 0 1px 0 rgba(255,255,255,0.45);
transition:transform .12s, box-shadow .12s, opacity .12s, filter .12s;"
:style="(sending || !amount) ? 'opacity: 0.45; cursor: not-allowed; box-shadow: none; filter: grayscale(0.15);' : ''"
:disabled="sending || !amount"
onmouseover="if(!this.disabled) { this.style.transform='translateY(-3px) scale(1.01)'; this.style.boxShadow='0 18px 34px rgba(124,45,18,0.42), inset 0 1px 0 rgba(255,255,255,0.55)'; }"
onmouseout="if(!this.disabled) { this.style.transform=''; this.style.boxShadow='0 14px 28px rgba(124,45,18,0.35), inset 0 1px 0 rgba(255,255,255,0.45)'; }"
onmousedown="if(!this.disabled) this.style.transform='translateY(1px)'"
onmouseup="if(!this.disabled) { this.style.transform=''; this.style.boxShadow='0 14px 28px rgba(124,45,18,0.35), inset 0 1px 0 rgba(255,255,255,0.45)'; }">
<span x-text="sending ? '⏳ 发放中…' : '💰 立即发放'"></span>
</button>
<button x-on:click="show = false"
style="background:rgba(124,45,18,0.22); color:rgba(255,255,255,0.96); border:1px solid rgba(255,255,255,0.16); border-radius:14px;
padding:12px 18px; min-height:56px; font-size:15px; font-weight:800; cursor:pointer;
box-shadow:0 10px 20px rgba(0,0,0,0.18); transition:background .15s, transform .12s, opacity .12s;"
onmouseover="this.style.background='rgba(124,45,18,0.34)';this.style.transform='translateY(-1px)'"
onmouseout="this.style.background='rgba(124,45,18,0.22)';this.style.transform=''"
onmousedown="this.style.transform='translateY(1px)'"
onmouseup="this.style.transform=''">
取消
</button>
</div>
{{-- 最近 10 条记录 --}}
<div style="background:rgba(0,0,0,0.15); border-radius:12px; padding:12px 14px; text-align:left;">
<div
style="color:rgba(255,255,255,0.7); font-size:11px; font-weight:bold; margin-bottom:8px; display:flex; align-items:center; justify-content:space-between;">
<span>📋 最近发放记录</span>
<span x-show="loading">加载中…</span>
</div>
<div x-show="!loading && quota.recent_rewards.length === 0"
style="font-size:12px; color:rgba(255,255,255,0.5); text-align:center; padding:10px 0;">
暂无发放记录
</div>
<div x-show="!loading"
style="max-height:120px; overflow-y:auto; display:flex; flex-direction:column; gap:6px; padding-right:4px;">
<template x-for="(r, i) in quota.recent_rewards" :key="i">
<div style="display:flex; align-items:center; justify-content:space-between;
padding:6px 10px; border-radius:8px; background:rgba(255,255,255,0.1);">
<span style="font-size:12px; color:#fff;" x-text="r.target"></span>
<span style="font-size:12px; font-weight:bold; color:#fde68a;"
x-text="'+' + r.amount.toLocaleString()"></span>
<span style="font-size:10px; color:rgba(255,255,255,0.5);" x-text="r.created_at"></span>
</div>
</template>
</div>
</div>
</div>
</div>
</div>
{{-- openRewardModal 已迁移到 resources/js/chat-room/reward-modal.js --}}
{{-- ═══════════ 好友系统通知监听 ═══════════ --}}
{{-- 好友通知与 BannerNotification 监听已迁移到 resources/js/chat-room/friend-notifications.js --}}