迁移用户名片弹窗脚本
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
* - mobile-drawer.js:处理移动端抽屉、房间列表和在线名单。
|
* - mobile-drawer.js:处理移动端抽屉、房间列表和在线名单。
|
||||||
* - marriage-status.js:处理婚姻状态弹窗、已婚列表、接受拒绝和离婚申请。
|
* - marriage-status.js:处理婚姻状态弹窗、已婚列表、接受拒绝和离婚申请。
|
||||||
* - toolbar.js:处理工具栏按钮和功能快捷入口。
|
* - toolbar.js:处理工具栏按钮和功能快捷入口。
|
||||||
|
* - user-card.js:提供用户名片弹窗 Alpine 组件和管理/礼物操作。
|
||||||
* - user-target-actions.js:处理点击用户名切换私聊目标和打开名片。
|
* - user-target-actions.js:处理点击用户名切换私聊目标和打开名片。
|
||||||
* - welcome-menu.js:处理欢迎菜单交互。
|
* - welcome-menu.js:处理欢迎菜单交互。
|
||||||
* - admin-menu.js:处理聊天室管理菜单交互。
|
* - admin-menu.js:处理聊天室管理菜单交互。
|
||||||
@@ -93,6 +94,7 @@ export {
|
|||||||
tryDivorce,
|
tryDivorce,
|
||||||
} from "./chat-room/marriage-status.js";
|
} from "./chat-room/marriage-status.js";
|
||||||
export { bindToolbarControls, runFeatureShortcut, runToolbarAction } from "./chat-room/toolbar.js";
|
export { bindToolbarControls, runFeatureShortcut, runToolbarAction } from "./chat-room/toolbar.js";
|
||||||
|
export { bindUserCardControls, userCardComponent } from "./chat-room/user-card.js";
|
||||||
export { bindUserTargetActions, openUserCard, switchTarget } from "./chat-room/user-target-actions.js";
|
export { bindUserTargetActions, openUserCard, switchTarget } from "./chat-room/user-target-actions.js";
|
||||||
export { bindWelcomeMenuControls } from "./chat-room/welcome-menu.js";
|
export { bindWelcomeMenuControls } from "./chat-room/welcome-menu.js";
|
||||||
export { bindAdminMenuControls } from "./chat-room/admin-menu.js";
|
export { bindAdminMenuControls } from "./chat-room/admin-menu.js";
|
||||||
@@ -263,6 +265,7 @@ import {
|
|||||||
tryDivorce,
|
tryDivorce,
|
||||||
} from "./chat-room/marriage-status.js";
|
} from "./chat-room/marriage-status.js";
|
||||||
import { bindToolbarControls, runFeatureShortcut, runToolbarAction } from "./chat-room/toolbar.js";
|
import { bindToolbarControls, runFeatureShortcut, runToolbarAction } from "./chat-room/toolbar.js";
|
||||||
|
import { bindUserCardControls, userCardComponent } from "./chat-room/user-card.js";
|
||||||
import { bindUserTargetActions, openUserCard, switchTarget } from "./chat-room/user-target-actions.js";
|
import { bindUserTargetActions, openUserCard, switchTarget } from "./chat-room/user-target-actions.js";
|
||||||
import { bindWelcomeMenuControls } from "./chat-room/welcome-menu.js";
|
import { bindWelcomeMenuControls } from "./chat-room/welcome-menu.js";
|
||||||
import { bindAdminMenuControls } from "./chat-room/admin-menu.js";
|
import { bindAdminMenuControls } from "./chat-room/admin-menu.js";
|
||||||
@@ -441,6 +444,8 @@ if (typeof window !== "undefined") {
|
|||||||
bindToolbarControls,
|
bindToolbarControls,
|
||||||
runFeatureShortcut,
|
runFeatureShortcut,
|
||||||
runToolbarAction,
|
runToolbarAction,
|
||||||
|
bindUserCardControls,
|
||||||
|
userCardComponent,
|
||||||
bindUserTargetActions,
|
bindUserTargetActions,
|
||||||
openUserCard,
|
openUserCard,
|
||||||
switchTarget,
|
switchTarget,
|
||||||
@@ -626,6 +631,7 @@ if (typeof window !== "undefined") {
|
|||||||
window.slotPanel = slotPanel;
|
window.slotPanel = slotPanel;
|
||||||
window.runFeatureShortcut = runFeatureShortcut;
|
window.runFeatureShortcut = runFeatureShortcut;
|
||||||
window.runToolbarAction = runToolbarAction;
|
window.runToolbarAction = runToolbarAction;
|
||||||
|
window.userCardComponent = userCardComponent;
|
||||||
window.buildHolidayClaimActionButton = buildHolidayClaimActionButton;
|
window.buildHolidayClaimActionButton = buildHolidayClaimActionButton;
|
||||||
window.buildHolidaySystemMessage = buildHolidaySystemMessage;
|
window.buildHolidaySystemMessage = buildHolidaySystemMessage;
|
||||||
window.holidayEventModal = holidayEventModal;
|
window.holidayEventModal = holidayEventModal;
|
||||||
@@ -731,6 +737,7 @@ if (typeof window !== "undefined") {
|
|||||||
bindFriendPanelControls();
|
bindFriendPanelControls();
|
||||||
bindFriendNotificationControls();
|
bindFriendNotificationControls();
|
||||||
bindToolbarControls();
|
bindToolbarControls();
|
||||||
|
bindUserCardControls();
|
||||||
bindUserTargetActions();
|
bindUserTargetActions();
|
||||||
bindAdminMenuControls();
|
bindAdminMenuControls();
|
||||||
bindBaccaratPanelControls();
|
bindBaccaratPanelControls();
|
||||||
|
|||||||
@@ -0,0 +1,587 @@
|
|||||||
|
// 用户名片弹窗 Alpine 组件,负责资料查看、好友、礼物、管理和职务快捷操作。
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取用户弹窗容器上的礼物初始数据。
|
||||||
|
*
|
||||||
|
* @returns {{gifts: Array<Record<string, any>>, defaultGiftId: number}}
|
||||||
|
*/
|
||||||
|
function userCardGiftDefaults() {
|
||||||
|
const container = document.getElementById("user-modal-container");
|
||||||
|
let gifts = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
gifts = JSON.parse(atob(container?.dataset.giftsBase64 || ""));
|
||||||
|
} catch (error) {
|
||||||
|
gifts = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
gifts,
|
||||||
|
defaultGiftId: Number(container?.dataset.defaultGiftId || 0),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建用户名片弹窗 Alpine 组件。
|
||||||
|
*
|
||||||
|
* @returns {Record<string, any>}
|
||||||
|
*/
|
||||||
|
export function userCardComponent() {
|
||||||
|
const giftDefaults = userCardGiftDefaults();
|
||||||
|
|
||||||
|
return {
|
||||||
|
showUserModal: false,
|
||||||
|
showOriginalLightbox: false,
|
||||||
|
userInfo: {
|
||||||
|
position_history: []
|
||||||
|
},
|
||||||
|
isMuting: false,
|
||||||
|
muteDuration: 5,
|
||||||
|
showWhispers: false,
|
||||||
|
whisperList: [],
|
||||||
|
showAnnounce: false,
|
||||||
|
announceText: '',
|
||||||
|
is_friend: false, // 当前用户是否已将对方加为好友
|
||||||
|
friendLoading: false, // 好友操作加载状态
|
||||||
|
gifts: giftDefaults.gifts,
|
||||||
|
selectedGiftId: giftDefaults.defaultGiftId,
|
||||||
|
giftCount: 1,
|
||||||
|
sendingGift: false,
|
||||||
|
|
||||||
|
// 职务奖励金币
|
||||||
|
rewardAmount: 0,
|
||||||
|
sendingReward: false,
|
||||||
|
showRewardPanel: false,
|
||||||
|
|
||||||
|
// 任命相关
|
||||||
|
showAppointPanel: false,
|
||||||
|
appointPositions: [],
|
||||||
|
selectedPositionId: null,
|
||||||
|
appointRemark: '',
|
||||||
|
appointLoading: false,
|
||||||
|
|
||||||
|
// 折叠状态
|
||||||
|
showAdminView: false, // 管理员视野
|
||||||
|
showPositionHistory: false, // 职务履历
|
||||||
|
showAdminPanel: false, // 管理操作(管理操作+职务操作合并)
|
||||||
|
|
||||||
|
// 婚姻状态
|
||||||
|
targetMarriage: null, // 对方婚姻状态 { status, partner_name, marriage_id }
|
||||||
|
marriageLoading: false,
|
||||||
|
mySex: window.chatContext?.userSex ?? '', // 当前用户性别(用于求婚异性判断)
|
||||||
|
|
||||||
|
// 自定义弹窗:直接代理到全局 window.chatDialog
|
||||||
|
$alert: (...args) => window.chatDialog.alert(...args),
|
||||||
|
$confirm: (...args) => window.chatDialog.confirm(...args),
|
||||||
|
$prompt: (...args) => window.chatDialog.prompt(...args),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断当前操作者是否拥有指定的职务权限码。
|
||||||
|
*/
|
||||||
|
hasPositionPermission(permissionCode) {
|
||||||
|
return Boolean(window.chatContext?.positionPermissionMap?.[permissionCode]);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断目标用户职务是否严格低于当前操作者。
|
||||||
|
*
|
||||||
|
* 规则:
|
||||||
|
* 1. 先比较部门位阶 rank
|
||||||
|
* 2. 部门相同再比较职务位阶 rank
|
||||||
|
* 3. 对方没有在职职务时,视为可处理
|
||||||
|
* 4. id=1 站长始终可处理
|
||||||
|
*/
|
||||||
|
canManageTargetByDuty() {
|
||||||
|
if (window.chatContext?.isSiteOwner) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetDepartmentRank = Number(this.userInfo.department_rank || 0);
|
||||||
|
const targetPositionRank = Number(this.userInfo.position_rank || 0);
|
||||||
|
|
||||||
|
if (targetDepartmentRank <= 0 && targetPositionRank <= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const operatorDepartmentRank = Number(window.chatContext?.operatorDepartmentRank || 0);
|
||||||
|
const operatorPositionRank = Number(window.chatContext?.operatorPositionRank || 0);
|
||||||
|
|
||||||
|
if (operatorDepartmentRank > targetDepartmentRank) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operatorDepartmentRank < targetDepartmentRank) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return operatorPositionRank >= targetPositionRank;
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 切换好友关系(加好友 / 删好友) */
|
||||||
|
async toggleFriend() {
|
||||||
|
if (this.friendLoading) return;
|
||||||
|
this.friendLoading = true;
|
||||||
|
const username = this.userInfo.username;
|
||||||
|
const roomId = window.chatContext.roomId;
|
||||||
|
const removing = this.is_friend;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let res;
|
||||||
|
if (removing) {
|
||||||
|
// 删除好友
|
||||||
|
res = await fetch(`/friend/${encodeURIComponent(username)}/remove`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: this._headers(),
|
||||||
|
body: JSON.stringify({
|
||||||
|
room_id: roomId
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 添加好友
|
||||||
|
res = await fetch(`/friend/${encodeURIComponent(username)}/add`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: this._headers(),
|
||||||
|
body: JSON.stringify({
|
||||||
|
room_id: roomId
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const data = await res.json();
|
||||||
|
const ok = data.status === 'success';
|
||||||
|
this.$alert(
|
||||||
|
data.message,
|
||||||
|
ok ? (removing ? '已删除好友' : '添加成功 🎉') : '操作失败',
|
||||||
|
ok ? (removing ? '#6b7280' : '#16a34a') : '#cc4444'
|
||||||
|
);
|
||||||
|
if (ok) {
|
||||||
|
this.is_friend = !this.is_friend;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.$alert('网络异常', '错误', '#cc4444');
|
||||||
|
}
|
||||||
|
this.friendLoading = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
async handleConfirmDivorce(marriageId) {
|
||||||
|
// 等待后端接口实现,当前先略
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 发起协议离婚(先拉惩罚配置,再弹专属全屏确认弹窗) */
|
||||||
|
async doDivorce(marriageId) {
|
||||||
|
if (!marriageId) return;
|
||||||
|
this.showUserModal = false;
|
||||||
|
|
||||||
|
// 从后台实时拉取最新惩罚配置
|
||||||
|
let divorceConfig = {
|
||||||
|
mutual_charm_penalty: 0,
|
||||||
|
forced_charm_penalty: 0,
|
||||||
|
mutual_cooldown_days: 0,
|
||||||
|
forced_cooldown_days: 0
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const cfgRes = await fetch(window.chatContext.marriage.divorceConfigUrl, {
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (cfgRes.ok) divorceConfig = await cfgRes.json();
|
||||||
|
} catch (e) {
|
||||||
|
/* 网络异常则使用默认值 */
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开专属离婚确认弹窗
|
||||||
|
const modal = document.getElementById('divorce-confirm-modal');
|
||||||
|
if (modal && window.Alpine) {
|
||||||
|
window.Alpine.$data(modal).open(marriageId, divorceConfig);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/** 获取用户资料 */
|
||||||
|
async fetchUser(username) {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/user/' + encodeURIComponent(username), {
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const errorData = await res.json().catch(() => ({}));
|
||||||
|
console.error('Failed to fetch user:', errorData.message || res.statusText);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.status === 'success') {
|
||||||
|
this.userInfo = data.data;
|
||||||
|
this.showPositionHistory = false;
|
||||||
|
|
||||||
|
// 加载好友状态(仅对非自己的用户查询)
|
||||||
|
if (data.data.username !== window.chatContext.username) {
|
||||||
|
fetch(`/friend/${encodeURIComponent(data.data.username)}/status`, {
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
}
|
||||||
|
}).then(r => r.json()).then(s => {
|
||||||
|
this.is_friend = s.is_friend ?? false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载对方婚姻状态
|
||||||
|
this.targetMarriage = null;
|
||||||
|
this.marriageLoading = true;
|
||||||
|
fetch(`/marriage/target?username=${encodeURIComponent(data.data.username)}`, {
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
}
|
||||||
|
}).then(r => r.json()).then(m => {
|
||||||
|
this.targetMarriage = m.marriage ?? null;
|
||||||
|
}).catch(() => {
|
||||||
|
}).finally(() => {
|
||||||
|
this.marriageLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.showUserModal = true;
|
||||||
|
this.isMuting = false;
|
||||||
|
this.showWhispers = false;
|
||||||
|
this.whisperList = [];
|
||||||
|
this.showAppointPanel = false;
|
||||||
|
this.selectedPositionId = null;
|
||||||
|
this.appointRemark = '';
|
||||||
|
// 有职务的操作人预加载可用职务列表
|
||||||
|
if (window.chatContext?.hasPosition) {
|
||||||
|
this._loadPositions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error fetching user:', e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 加载可任命职务列表 */
|
||||||
|
async _loadPositions() {
|
||||||
|
if (!window.chatContext?.appointPositionsUrl) return;
|
||||||
|
try {
|
||||||
|
const res = await fetch(window.chatContext.appointPositionsUrl, {
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.status === 'success') {
|
||||||
|
this.appointPositions = data.positions;
|
||||||
|
if (this.appointPositions.length > 0) {
|
||||||
|
this.selectedPositionId = this.appointPositions[0].id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
/* 静默失败 */
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 快速任命 */
|
||||||
|
async doAppoint() {
|
||||||
|
if (this.appointLoading || !this.selectedPositionId) return;
|
||||||
|
this.appointLoading = true;
|
||||||
|
try {
|
||||||
|
const res = await fetch(window.chatContext.appointUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: this._headers(),
|
||||||
|
body: JSON.stringify({
|
||||||
|
username: this.userInfo.username,
|
||||||
|
position_id: this.selectedPositionId,
|
||||||
|
remark: this.appointRemark.trim() || null,
|
||||||
|
room_id: window.chatContext.roomId ?? null,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
const ok = data.status === 'success';
|
||||||
|
if (ok) {
|
||||||
|
this.showUserModal = false;
|
||||||
|
} else {
|
||||||
|
this.$alert(data.message, '操作失败', '#cc4444');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.$alert('网络异常,请稍后重试', '错误', '#cc4444');
|
||||||
|
}
|
||||||
|
this.appointLoading = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 快速撤销 */
|
||||||
|
async doRevoke() {
|
||||||
|
const ok = await this.$confirm(
|
||||||
|
'确定要撤销 「' + this.userInfo.username + '」 的职务吗?撤销后将不再拥有相关权限。',
|
||||||
|
'撤销职务'
|
||||||
|
);
|
||||||
|
if (!ok) return;
|
||||||
|
this.appointLoading = true;
|
||||||
|
try {
|
||||||
|
const res = await fetch(window.chatContext.revokeUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: this._headers(),
|
||||||
|
body: JSON.stringify({
|
||||||
|
username: this.userInfo.username,
|
||||||
|
remark: '聊天室快速撤销',
|
||||||
|
room_id: window.chatContext.roomId ?? null,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
const revOk = data.status === 'success';
|
||||||
|
if (revOk) {
|
||||||
|
this.showUserModal = false;
|
||||||
|
} else {
|
||||||
|
this.$alert(data.message, '操作失败', '#cc4444');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.$alert('网络异常', '错误', '#cc4444');
|
||||||
|
}
|
||||||
|
this.appointLoading = false;
|
||||||
|
},
|
||||||
|
/** 踢出用户 */
|
||||||
|
async kickUser() {
|
||||||
|
const reason = await this.$prompt('踢出原因(可留空):', '违反聊天室规则', '踢出用户', '#cc4444');
|
||||||
|
if (reason === null) return;
|
||||||
|
try {
|
||||||
|
const res = await fetch('/command/kick', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: this._headers(),
|
||||||
|
body: JSON.stringify({
|
||||||
|
username: this.userInfo.username,
|
||||||
|
room_id: window.chatContext.roomId,
|
||||||
|
reason: reason || '违反聊天室规则'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.status === 'success') {
|
||||||
|
this.showUserModal = false;
|
||||||
|
} else {
|
||||||
|
this.$alert('操作失败:' + data.message, '操作失败', '#cc4444');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.$alert('网络异常', '错误', '#cc4444');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 禁言用户 */
|
||||||
|
async muteUser() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/command/mute', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: this._headers(),
|
||||||
|
body: JSON.stringify({
|
||||||
|
username: this.userInfo.username,
|
||||||
|
room_id: window.chatContext.roomId,
|
||||||
|
duration: this.muteDuration
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.status === 'success') {
|
||||||
|
this.showUserModal = false;
|
||||||
|
} else {
|
||||||
|
this.$alert('操作失败:' + data.message, '操作失败', '#cc4444');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.$alert('网络异常', '错误', '#cc4444');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 警告用户 */
|
||||||
|
async warnUser() {
|
||||||
|
const reason = await this.$prompt('警告原因:', '请注意言行', '警告用户', '#f59e0b');
|
||||||
|
if (reason === null) return;
|
||||||
|
try {
|
||||||
|
const res = await fetch('/command/warn', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: this._headers(),
|
||||||
|
body: JSON.stringify({
|
||||||
|
username: this.userInfo.username,
|
||||||
|
room_id: window.chatContext.roomId,
|
||||||
|
reason: reason || '请注意言行'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.status === 'success') {
|
||||||
|
this.showUserModal = false;
|
||||||
|
} else {
|
||||||
|
this.$alert('操作失败:' + data.message, '操作失败', '#cc4444');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.$alert('网络异常', '错误', '#cc4444');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 冻结用户 */
|
||||||
|
async freezeUser() {
|
||||||
|
const confirmed = await this.$confirm(
|
||||||
|
'确定要冻结 ' + this.userInfo.username + ' 的账号吗?冻结后将无法登录!',
|
||||||
|
'冻结账号',
|
||||||
|
'#cc4444'
|
||||||
|
);
|
||||||
|
if (!confirmed) return;
|
||||||
|
const reason = await this.$prompt('冻结原因:', '严重违规', '填写原因', '#cc4444');
|
||||||
|
if (reason === null) return;
|
||||||
|
try {
|
||||||
|
const res = await fetch('/command/freeze', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: this._headers(),
|
||||||
|
body: JSON.stringify({
|
||||||
|
username: this.userInfo.username,
|
||||||
|
room_id: window.chatContext.roomId,
|
||||||
|
reason: reason || '严重违规'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.status === 'success') {
|
||||||
|
this.showUserModal = false;
|
||||||
|
} else {
|
||||||
|
this.$alert('操作失败:' + data.message, '操作失败', '#cc4444');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.$alert('网络异常', '错误', '#cc4444');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 查看私信记录 */
|
||||||
|
async loadWhispers() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/command/whispers/' + encodeURIComponent(this.userInfo.username), {
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.status === 'success') {
|
||||||
|
this.whisperList = data.messages;
|
||||||
|
this.showWhispers = true;
|
||||||
|
} else {
|
||||||
|
this.$alert(data.message, '操作失败', '#cc4444');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.$alert('网络异常', '错误', '#cc4444');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 发送全服公告 */
|
||||||
|
async sendAnnounce() {
|
||||||
|
if (!this.announceText.trim()) return;
|
||||||
|
try {
|
||||||
|
const res = await fetch('/command/announce', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: this._headers(),
|
||||||
|
body: JSON.stringify({
|
||||||
|
content: this.announceText,
|
||||||
|
room_id: window.chatContext.roomId,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.status === 'success') {
|
||||||
|
this.announceText = '';
|
||||||
|
this.showAnnounce = false;
|
||||||
|
} else {
|
||||||
|
this.$alert(data.message, '操作失败', '#cc4444');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.$alert('网络异常', '错误', '#cc4444');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 送礼物 */
|
||||||
|
async sendGift() {
|
||||||
|
if (this.sendingGift || !this.selectedGiftId) return;
|
||||||
|
this.sendingGift = true;
|
||||||
|
try {
|
||||||
|
const res = await fetch('/gift/flower', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: this._headers(),
|
||||||
|
body: JSON.stringify({
|
||||||
|
to_user: this.userInfo.username,
|
||||||
|
room_id: window.chatContext.roomId,
|
||||||
|
gift_id: this.selectedGiftId,
|
||||||
|
count: this.giftCount
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
this.$alert(data.message, data.status === 'success' ? '送礼成功 🎁' : '操作失败',
|
||||||
|
data.status === 'success' ? '#e11d48' : '#cc4444');
|
||||||
|
if (data.status === 'success') {
|
||||||
|
this.showUserModal = false;
|
||||||
|
this.giftCount = 1;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.$alert('网络异常', '错误', '#cc4444');
|
||||||
|
}
|
||||||
|
this.sendingGift = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 职务奖励:向用户发放金币(凭空产生,记入履职记录) */
|
||||||
|
async sendReward() {
|
||||||
|
if (this.sendingReward) return;
|
||||||
|
const maxOnce = window.chatContext?.myMaxReward ?? 0;
|
||||||
|
const amount = parseInt(this.rewardAmount, 10);
|
||||||
|
if (!amount || amount <= 0) {
|
||||||
|
this.$alert('请输入有效的奖励金额', '提示', '#f59e0b');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 0 = 禁止(前端按钮不显示,此处二次保护)
|
||||||
|
if (maxOnce === 0) {
|
||||||
|
this.$alert('你的职务没有奖励发放权限', '无权限', '#cc4444');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// -1 = 不限,跳过上限校验;正整数 = 有具体上限
|
||||||
|
if (maxOnce > 0 && amount > maxOnce) {
|
||||||
|
this.$alert(`单次奖励上限为 ${maxOnce} 金币`, '超出上限', '#cc4444');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.sendingReward = true;
|
||||||
|
try {
|
||||||
|
const res = await fetch(window.chatContext.rewardUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: this._headers(),
|
||||||
|
body: JSON.stringify({
|
||||||
|
username: this.userInfo.username,
|
||||||
|
room_id: window.chatContext.roomId,
|
||||||
|
amount,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
const ok = data.status === 'success';
|
||||||
|
this.$alert(data.message, ok ? '奖励发放成功 🎉' : '操作失败',
|
||||||
|
ok ? '#d97706' : '#cc4444');
|
||||||
|
if (ok) {
|
||||||
|
this.showRewardPanel = false;
|
||||||
|
this.rewardAmount = 0;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.$alert('网络异常,请稍后重试', '错误', '#cc4444');
|
||||||
|
}
|
||||||
|
this.sendingReward = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 通用请求头 */
|
||||||
|
_headers() {
|
||||||
|
return {
|
||||||
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 挂载用户名片组件全局名称,兼容 Blade 的 x-data。
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
export function bindUserCardControls() {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.userCardComponent = userCardComponent;
|
||||||
|
}
|
||||||
@@ -17,564 +17,13 @@
|
|||||||
{{-- ═══════════ 用户名片弹窗 (Alpine.js) ═══════════ --}}
|
{{-- ═══════════ 用户名片弹窗 (Alpine.js) ═══════════ --}}
|
||||||
@php $gifts = \App\Models\Gift::activeList(); @endphp
|
@php $gifts = \App\Models\Gift::activeList(); @endphp
|
||||||
|
|
||||||
<script>
|
|
||||||
/**
|
|
||||||
* 礼物数据注入(避免 JSON 破坏 x-data 属性解析)
|
|
||||||
*/
|
|
||||||
window.__gifts = {!! Js::from($gifts) !!};
|
|
||||||
window.__defaultGiftId = {{ $gifts->first()?->id ?? 0 }};
|
|
||||||
|
|
||||||
/**
|
{{-- 用户名片弹窗组件脚本已迁移到 resources/js/chat-room/user-card.js --}}
|
||||||
* 用户名片弹窗 Alpine.js 组件定义
|
|
||||||
* 提取到 script 标签避免 HTML 属性中的引号冲突
|
|
||||||
*/
|
|
||||||
function userCardComponent() {
|
|
||||||
return {
|
|
||||||
showUserModal: false,
|
|
||||||
showOriginalLightbox: false,
|
|
||||||
userInfo: {
|
|
||||||
position_history: []
|
|
||||||
},
|
|
||||||
isMuting: false,
|
|
||||||
muteDuration: 5,
|
|
||||||
showWhispers: false,
|
|
||||||
whisperList: [],
|
|
||||||
showAnnounce: false,
|
|
||||||
announceText: '',
|
|
||||||
is_friend: false, // 当前用户是否已将对方加为好友
|
|
||||||
friendLoading: false, // 好友操作加载状态
|
|
||||||
gifts: window.__gifts || [],
|
|
||||||
selectedGiftId: window.__defaultGiftId || 0,
|
|
||||||
giftCount: 1,
|
|
||||||
sendingGift: false,
|
|
||||||
|
|
||||||
// 职务奖励金币
|
<div id="user-modal-container"
|
||||||
rewardAmount: 0,
|
x-data="userCardComponent()"
|
||||||
sendingReward: false,
|
data-gifts-base64="{{ base64_encode($gifts->toJson()) }}"
|
||||||
showRewardPanel: false,
|
data-default-gift-id="{{ $gifts->first()?->id ?? 0 }}">
|
||||||
|
|
||||||
// 任命相关
|
|
||||||
showAppointPanel: false,
|
|
||||||
appointPositions: [],
|
|
||||||
selectedPositionId: null,
|
|
||||||
appointRemark: '',
|
|
||||||
appointLoading: false,
|
|
||||||
|
|
||||||
// 折叠状态
|
|
||||||
showAdminView: false, // 管理员视野
|
|
||||||
showPositionHistory: false, // 职务履历
|
|
||||||
showAdminPanel: false, // 管理操作(管理操作+职务操作合并)
|
|
||||||
|
|
||||||
// 婚姻状态
|
|
||||||
targetMarriage: null, // 对方婚姻状态 { status, partner_name, marriage_id }
|
|
||||||
marriageLoading: false,
|
|
||||||
mySex: window.chatContext?.userSex ?? '', // 当前用户性别(用于求婚异性判断)
|
|
||||||
|
|
||||||
// 自定义弹窗:直接代理到全局 window.chatDialog
|
|
||||||
$alert: (...args) => window.chatDialog.alert(...args),
|
|
||||||
$confirm: (...args) => window.chatDialog.confirm(...args),
|
|
||||||
$prompt: (...args) => window.chatDialog.prompt(...args),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断当前操作者是否拥有指定的职务权限码。
|
|
||||||
*/
|
|
||||||
hasPositionPermission(permissionCode) {
|
|
||||||
return Boolean(window.chatContext?.positionPermissionMap?.[permissionCode]);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断目标用户职务是否严格低于当前操作者。
|
|
||||||
*
|
|
||||||
* 规则:
|
|
||||||
* 1. 先比较部门位阶 rank
|
|
||||||
* 2. 部门相同再比较职务位阶 rank
|
|
||||||
* 3. 对方没有在职职务时,视为可处理
|
|
||||||
* 4. id=1 站长始终可处理
|
|
||||||
*/
|
|
||||||
canManageTargetByDuty() {
|
|
||||||
if (window.chatContext?.isSiteOwner) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const targetDepartmentRank = Number(this.userInfo.department_rank || 0);
|
|
||||||
const targetPositionRank = Number(this.userInfo.position_rank || 0);
|
|
||||||
|
|
||||||
if (targetDepartmentRank <= 0 && targetPositionRank <= 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const operatorDepartmentRank = Number(window.chatContext?.operatorDepartmentRank || 0);
|
|
||||||
const operatorPositionRank = Number(window.chatContext?.operatorPositionRank || 0);
|
|
||||||
|
|
||||||
if (operatorDepartmentRank > targetDepartmentRank) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operatorDepartmentRank < targetDepartmentRank) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return operatorPositionRank >= targetPositionRank;
|
|
||||||
},
|
|
||||||
|
|
||||||
/** 切换好友关系(加好友 / 删好友) */
|
|
||||||
async toggleFriend() {
|
|
||||||
if (this.friendLoading) return;
|
|
||||||
this.friendLoading = true;
|
|
||||||
const username = this.userInfo.username;
|
|
||||||
const roomId = window.chatContext.roomId;
|
|
||||||
const removing = this.is_friend;
|
|
||||||
|
|
||||||
try {
|
|
||||||
let res;
|
|
||||||
if (removing) {
|
|
||||||
// 删除好友
|
|
||||||
res = await fetch(`/friend/${encodeURIComponent(username)}/remove`, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: this._headers(),
|
|
||||||
body: JSON.stringify({
|
|
||||||
room_id: roomId
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 添加好友
|
|
||||||
res = await fetch(`/friend/${encodeURIComponent(username)}/add`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: this._headers(),
|
|
||||||
body: JSON.stringify({
|
|
||||||
room_id: roomId
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const data = await res.json();
|
|
||||||
const ok = data.status === 'success';
|
|
||||||
this.$alert(
|
|
||||||
data.message,
|
|
||||||
ok ? (removing ? '已删除好友' : '添加成功 🎉') : '操作失败',
|
|
||||||
ok ? (removing ? '#6b7280' : '#16a34a') : '#cc4444'
|
|
||||||
);
|
|
||||||
if (ok) {
|
|
||||||
this.is_friend = !this.is_friend;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.$alert('网络异常', '错误', '#cc4444');
|
|
||||||
}
|
|
||||||
this.friendLoading = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
async handleConfirmDivorce(marriageId) {
|
|
||||||
// 等待后端接口实现,当前先略
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
|
|
||||||
/** 发起协议离婚(先拉惩罚配置,再弹专属全屏确认弹窗) */
|
|
||||||
async doDivorce(marriageId) {
|
|
||||||
if (!marriageId) return;
|
|
||||||
this.showUserModal = false;
|
|
||||||
|
|
||||||
// 从后台实时拉取最新惩罚配置
|
|
||||||
let divorceConfig = {
|
|
||||||
mutual_charm_penalty: 0,
|
|
||||||
forced_charm_penalty: 0,
|
|
||||||
mutual_cooldown_days: 0,
|
|
||||||
forced_cooldown_days: 0
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
const cfgRes = await fetch(window.chatContext.marriage.divorceConfigUrl, {
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'X-Requested-With': 'XMLHttpRequest'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (cfgRes.ok) divorceConfig = await cfgRes.json();
|
|
||||||
} catch (e) {
|
|
||||||
/* 网络异常则使用默认值 */
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打开专属离婚确认弹窗
|
|
||||||
const modal = document.getElementById('divorce-confirm-modal');
|
|
||||||
if (modal && window.Alpine) {
|
|
||||||
window.Alpine.$data(modal).open(marriageId, divorceConfig);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
/** 获取用户资料 */
|
|
||||||
async fetchUser(username) {
|
|
||||||
try {
|
|
||||||
const res = await fetch('/user/' + encodeURIComponent(username), {
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'X-Requested-With': 'XMLHttpRequest'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
const errorData = await res.json().catch(() => ({}));
|
|
||||||
console.error('Failed to fetch user:', errorData.message || res.statusText);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
if (data.status === 'success') {
|
|
||||||
this.userInfo = data.data;
|
|
||||||
this.showPositionHistory = false;
|
|
||||||
|
|
||||||
// 加载好友状态(仅对非自己的用户查询)
|
|
||||||
if (data.data.username !== window.chatContext.username) {
|
|
||||||
fetch(`/friend/${encodeURIComponent(data.data.username)}/status`, {
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'X-Requested-With': 'XMLHttpRequest'
|
|
||||||
}
|
|
||||||
}).then(r => r.json()).then(s => {
|
|
||||||
this.is_friend = s.is_friend ?? false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 加载对方婚姻状态
|
|
||||||
this.targetMarriage = null;
|
|
||||||
this.marriageLoading = true;
|
|
||||||
fetch(`/marriage/target?username=${encodeURIComponent(data.data.username)}`, {
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'X-Requested-With': 'XMLHttpRequest'
|
|
||||||
}
|
|
||||||
}).then(r => r.json()).then(m => {
|
|
||||||
this.targetMarriage = m.marriage ?? null;
|
|
||||||
}).catch(() => {
|
|
||||||
}).finally(() => {
|
|
||||||
this.marriageLoading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.showUserModal = true;
|
|
||||||
this.isMuting = false;
|
|
||||||
this.showWhispers = false;
|
|
||||||
this.whisperList = [];
|
|
||||||
this.showAppointPanel = false;
|
|
||||||
this.selectedPositionId = null;
|
|
||||||
this.appointRemark = '';
|
|
||||||
// 有职务的操作人预加载可用职务列表
|
|
||||||
if (window.chatContext?.hasPosition) {
|
|
||||||
this._loadPositions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Error fetching user:', e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/** 加载可任命职务列表 */
|
|
||||||
async _loadPositions() {
|
|
||||||
if (!window.chatContext?.appointPositionsUrl) return;
|
|
||||||
try {
|
|
||||||
const res = await fetch(window.chatContext.appointPositionsUrl, {
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const data = await res.json();
|
|
||||||
if (data.status === 'success') {
|
|
||||||
this.appointPositions = data.positions;
|
|
||||||
if (this.appointPositions.length > 0) {
|
|
||||||
this.selectedPositionId = this.appointPositions[0].id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
/* 静默失败 */
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/** 快速任命 */
|
|
||||||
async doAppoint() {
|
|
||||||
if (this.appointLoading || !this.selectedPositionId) return;
|
|
||||||
this.appointLoading = true;
|
|
||||||
try {
|
|
||||||
const res = await fetch(window.chatContext.appointUrl, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: this._headers(),
|
|
||||||
body: JSON.stringify({
|
|
||||||
username: this.userInfo.username,
|
|
||||||
position_id: this.selectedPositionId,
|
|
||||||
remark: this.appointRemark.trim() || null,
|
|
||||||
room_id: window.chatContext.roomId ?? null,
|
|
||||||
})
|
|
||||||
});
|
|
||||||
const data = await res.json();
|
|
||||||
const ok = data.status === 'success';
|
|
||||||
if (ok) {
|
|
||||||
this.showUserModal = false;
|
|
||||||
} else {
|
|
||||||
this.$alert(data.message, '操作失败', '#cc4444');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.$alert('网络异常,请稍后重试', '错误', '#cc4444');
|
|
||||||
}
|
|
||||||
this.appointLoading = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
/** 快速撤销 */
|
|
||||||
async doRevoke() {
|
|
||||||
const ok = await this.$confirm(
|
|
||||||
'确定要撤销 「' + this.userInfo.username + '」 的职务吗?撤销后将不再拥有相关权限。',
|
|
||||||
'撤销职务'
|
|
||||||
);
|
|
||||||
if (!ok) return;
|
|
||||||
this.appointLoading = true;
|
|
||||||
try {
|
|
||||||
const res = await fetch(window.chatContext.revokeUrl, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: this._headers(),
|
|
||||||
body: JSON.stringify({
|
|
||||||
username: this.userInfo.username,
|
|
||||||
remark: '聊天室快速撤销',
|
|
||||||
room_id: window.chatContext.roomId ?? null,
|
|
||||||
})
|
|
||||||
});
|
|
||||||
const data = await res.json();
|
|
||||||
const revOk = data.status === 'success';
|
|
||||||
if (revOk) {
|
|
||||||
this.showUserModal = false;
|
|
||||||
} else {
|
|
||||||
this.$alert(data.message, '操作失败', '#cc4444');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.$alert('网络异常', '错误', '#cc4444');
|
|
||||||
}
|
|
||||||
this.appointLoading = false;
|
|
||||||
},
|
|
||||||
/** 踢出用户 */
|
|
||||||
async kickUser() {
|
|
||||||
const reason = await this.$prompt('踢出原因(可留空):', '违反聊天室规则', '踢出用户', '#cc4444');
|
|
||||||
if (reason === null) return;
|
|
||||||
try {
|
|
||||||
const res = await fetch('/command/kick', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: this._headers(),
|
|
||||||
body: JSON.stringify({
|
|
||||||
username: this.userInfo.username,
|
|
||||||
room_id: window.chatContext.roomId,
|
|
||||||
reason: reason || '违反聊天室规则'
|
|
||||||
})
|
|
||||||
});
|
|
||||||
const data = await res.json();
|
|
||||||
if (data.status === 'success') {
|
|
||||||
this.showUserModal = false;
|
|
||||||
} else {
|
|
||||||
this.$alert('操作失败:' + data.message, '操作失败', '#cc4444');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.$alert('网络异常', '错误', '#cc4444');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/** 禁言用户 */
|
|
||||||
async muteUser() {
|
|
||||||
try {
|
|
||||||
const res = await fetch('/command/mute', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: this._headers(),
|
|
||||||
body: JSON.stringify({
|
|
||||||
username: this.userInfo.username,
|
|
||||||
room_id: window.chatContext.roomId,
|
|
||||||
duration: this.muteDuration
|
|
||||||
})
|
|
||||||
});
|
|
||||||
const data = await res.json();
|
|
||||||
if (data.status === 'success') {
|
|
||||||
this.showUserModal = false;
|
|
||||||
} else {
|
|
||||||
this.$alert('操作失败:' + data.message, '操作失败', '#cc4444');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.$alert('网络异常', '错误', '#cc4444');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/** 警告用户 */
|
|
||||||
async warnUser() {
|
|
||||||
const reason = await this.$prompt('警告原因:', '请注意言行', '警告用户', '#f59e0b');
|
|
||||||
if (reason === null) return;
|
|
||||||
try {
|
|
||||||
const res = await fetch('/command/warn', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: this._headers(),
|
|
||||||
body: JSON.stringify({
|
|
||||||
username: this.userInfo.username,
|
|
||||||
room_id: window.chatContext.roomId,
|
|
||||||
reason: reason || '请注意言行'
|
|
||||||
})
|
|
||||||
});
|
|
||||||
const data = await res.json();
|
|
||||||
if (data.status === 'success') {
|
|
||||||
this.showUserModal = false;
|
|
||||||
} else {
|
|
||||||
this.$alert('操作失败:' + data.message, '操作失败', '#cc4444');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.$alert('网络异常', '错误', '#cc4444');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/** 冻结用户 */
|
|
||||||
async freezeUser() {
|
|
||||||
const confirmed = await this.$confirm(
|
|
||||||
'确定要冻结 ' + this.userInfo.username + ' 的账号吗?冻结后将无法登录!',
|
|
||||||
'冻结账号',
|
|
||||||
'#cc4444'
|
|
||||||
);
|
|
||||||
if (!confirmed) return;
|
|
||||||
const reason = await this.$prompt('冻结原因:', '严重违规', '填写原因', '#cc4444');
|
|
||||||
if (reason === null) return;
|
|
||||||
try {
|
|
||||||
const res = await fetch('/command/freeze', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: this._headers(),
|
|
||||||
body: JSON.stringify({
|
|
||||||
username: this.userInfo.username,
|
|
||||||
room_id: window.chatContext.roomId,
|
|
||||||
reason: reason || '严重违规'
|
|
||||||
})
|
|
||||||
});
|
|
||||||
const data = await res.json();
|
|
||||||
if (data.status === 'success') {
|
|
||||||
this.showUserModal = false;
|
|
||||||
} else {
|
|
||||||
this.$alert('操作失败:' + data.message, '操作失败', '#cc4444');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.$alert('网络异常', '错误', '#cc4444');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/** 查看私信记录 */
|
|
||||||
async loadWhispers() {
|
|
||||||
try {
|
|
||||||
const res = await fetch('/command/whispers/' + encodeURIComponent(this.userInfo.username), {
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const data = await res.json();
|
|
||||||
if (data.status === 'success') {
|
|
||||||
this.whisperList = data.messages;
|
|
||||||
this.showWhispers = true;
|
|
||||||
} else {
|
|
||||||
this.$alert(data.message, '操作失败', '#cc4444');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.$alert('网络异常', '错误', '#cc4444');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/** 发送全服公告 */
|
|
||||||
async sendAnnounce() {
|
|
||||||
if (!this.announceText.trim()) return;
|
|
||||||
try {
|
|
||||||
const res = await fetch('/command/announce', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: this._headers(),
|
|
||||||
body: JSON.stringify({
|
|
||||||
content: this.announceText,
|
|
||||||
room_id: window.chatContext.roomId,
|
|
||||||
})
|
|
||||||
});
|
|
||||||
const data = await res.json();
|
|
||||||
if (data.status === 'success') {
|
|
||||||
this.announceText = '';
|
|
||||||
this.showAnnounce = false;
|
|
||||||
} else {
|
|
||||||
this.$alert(data.message, '操作失败', '#cc4444');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.$alert('网络异常', '错误', '#cc4444');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/** 送礼物 */
|
|
||||||
async sendGift() {
|
|
||||||
if (this.sendingGift || !this.selectedGiftId) return;
|
|
||||||
this.sendingGift = true;
|
|
||||||
try {
|
|
||||||
const res = await fetch('/gift/flower', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: this._headers(),
|
|
||||||
body: JSON.stringify({
|
|
||||||
to_user: this.userInfo.username,
|
|
||||||
room_id: window.chatContext.roomId,
|
|
||||||
gift_id: this.selectedGiftId,
|
|
||||||
count: this.giftCount
|
|
||||||
})
|
|
||||||
});
|
|
||||||
const data = await res.json();
|
|
||||||
this.$alert(data.message, data.status === 'success' ? '送礼成功 🎁' : '操作失败',
|
|
||||||
data.status === 'success' ? '#e11d48' : '#cc4444');
|
|
||||||
if (data.status === 'success') {
|
|
||||||
this.showUserModal = false;
|
|
||||||
this.giftCount = 1;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.$alert('网络异常', '错误', '#cc4444');
|
|
||||||
}
|
|
||||||
this.sendingGift = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
/** 职务奖励:向用户发放金币(凭空产生,记入履职记录) */
|
|
||||||
async sendReward() {
|
|
||||||
if (this.sendingReward) return;
|
|
||||||
const maxOnce = window.chatContext?.myMaxReward ?? 0;
|
|
||||||
const amount = parseInt(this.rewardAmount, 10);
|
|
||||||
if (!amount || amount <= 0) {
|
|
||||||
this.$alert('请输入有效的奖励金额', '提示', '#f59e0b');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 0 = 禁止(前端按钮不显示,此处二次保护)
|
|
||||||
if (maxOnce === 0) {
|
|
||||||
this.$alert('你的职务没有奖励发放权限', '无权限', '#cc4444');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// -1 = 不限,跳过上限校验;正整数 = 有具体上限
|
|
||||||
if (maxOnce > 0 && amount > maxOnce) {
|
|
||||||
this.$alert(`单次奖励上限为 ${maxOnce} 金币`, '超出上限', '#cc4444');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.sendingReward = true;
|
|
||||||
try {
|
|
||||||
const res = await fetch(window.chatContext.rewardUrl, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: this._headers(),
|
|
||||||
body: JSON.stringify({
|
|
||||||
username: this.userInfo.username,
|
|
||||||
room_id: window.chatContext.roomId,
|
|
||||||
amount,
|
|
||||||
})
|
|
||||||
});
|
|
||||||
const data = await res.json();
|
|
||||||
const ok = data.status === 'success';
|
|
||||||
this.$alert(data.message, ok ? '奖励发放成功 🎉' : '操作失败',
|
|
||||||
ok ? '#d97706' : '#cc4444');
|
|
||||||
if (ok) {
|
|
||||||
this.showRewardPanel = false;
|
|
||||||
this.rewardAmount = 0;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.$alert('网络异常,请稍后重试', '错误', '#cc4444');
|
|
||||||
}
|
|
||||||
this.sendingReward = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
/** 通用请求头 */
|
|
||||||
_headers() {
|
|
||||||
return {
|
|
||||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Accept': 'application/json'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div id="user-modal-container" x-data="userCardComponent()">
|
|
||||||
<div x-show="showUserModal" style="display: none;" class="modal-overlay" x-on:click.self="showUserModal = false">
|
<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-card" x-transition>
|
||||||
{{-- 弹窗头部 --}}
|
{{-- 弹窗头部 --}}
|
||||||
|
|||||||
Reference in New Issue
Block a user