收紧输入渲染与后台配置权限
This commit is contained in:
@@ -17,27 +17,39 @@
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="mx-6 mt-4 p-3 bg-amber-50 border border-amber-200 rounded-lg text-amber-800 text-sm">
|
||||
通用系统参数页仅维护低敏公共配置;SMTP、VIP 支付、微信机器人、AI 机器人等站长专属敏感项已迁移到各自独立页面。
|
||||
</div>
|
||||
|
||||
<div class="p-6">
|
||||
<form action="{{ route('admin.system.update') }}" method="POST">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<div class="space-y-6 max-w-2xl">
|
||||
@foreach ($params as $alias => $body)
|
||||
@forelse ($params as $alias => $body)
|
||||
@php
|
||||
$fieldValue = (string) $body;
|
||||
$shouldUseTextarea = strlen($fieldValue) > 50 || str_contains($fieldValue, "\n") || str_contains($fieldValue, '<');
|
||||
@endphp
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-gray-700 mb-2">
|
||||
{{ $descriptions[$alias] ?? $alias }}
|
||||
<span class="text-gray-400 font-normal ml-2">[{{ $alias }}]</span>
|
||||
</label>
|
||||
@if (strlen($body) > 50 || str_contains($body, "\n") || str_contains($body, '<'))
|
||||
@if ($shouldUseTextarea)
|
||||
<textarea name="{{ $alias }}" rows="4"
|
||||
class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500 p-2.5 bg-gray-50 border whitespace-pre-wrap">{{ $body }}</textarea>
|
||||
class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500 p-2.5 bg-gray-50 border whitespace-pre-wrap">{{ $fieldValue }}</textarea>
|
||||
@else
|
||||
<input type="text" name="{{ $alias }}" value="{{ $body }}"
|
||||
<input type="text" name="{{ $alias }}" value="{{ $fieldValue }}"
|
||||
class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500 p-2.5 bg-gray-50 border">
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
@empty
|
||||
<div class="rounded-lg border border-dashed border-gray-300 bg-gray-50 px-4 py-6 text-sm text-gray-500">
|
||||
当前没有可在通用系统页维护的公共参数,请前往对应专属配置页处理敏感模块参数。
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<div class="mt-8 pt-6 border-t flex space-x-3">
|
||||
|
||||
@@ -108,6 +108,18 @@
|
||||
|
||||
{{-- ── 手机端抽屉控制脚本 ── --}}
|
||||
<script>
|
||||
/**
|
||||
* 对手机端抽屉中的动态文本做 HTML 转义,避免直接拼入 innerHTML。
|
||||
*
|
||||
* @param {string} text
|
||||
* @returns {string}
|
||||
*/
|
||||
function escapeMobileDrawerHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前打开的抽屉名称:'toolbar' | 'users' | null
|
||||
*
|
||||
@@ -205,7 +217,7 @@
|
||||
const users = Object.keys(onlineUsers);
|
||||
container.innerHTML = users.length
|
||||
? users.filter(u => !keyword || u.toLowerCase().includes(keyword))
|
||||
.map(u => `<div class="user-item" style="padding:5px 8px;font-size:12px;border-bottom:1px solid #eee;">${u}</div>`).join('')
|
||||
.map(u => `<div class="user-item" style="padding:5px 8px;font-size:12px;border-bottom:1px solid #eee;">${escapeMobileDrawerHtml(u)}</div>`).join('')
|
||||
: '<div style="text-align:center;color:#aaa;padding:20px;font-size:12px;">暂无用户</div>';
|
||||
}
|
||||
|
||||
@@ -232,25 +244,34 @@
|
||||
return;
|
||||
}
|
||||
const currentRoomId = window.chatContext?.roomId;
|
||||
container.innerHTML = data.rooms.map(room => {
|
||||
const isCurrent = room.id === currentRoomId;
|
||||
const roomRows = data.rooms.map(room => {
|
||||
const roomId = Number.parseInt(room.id, 10);
|
||||
if (!Number.isInteger(roomId)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const isCurrent = roomId === currentRoomId;
|
||||
const bg = isCurrent ? '#ecf4ff' : '#fff';
|
||||
const nameColor = isCurrent ? '#336699' : (room.door_open ? '#444' : '#bbb');
|
||||
const badge = room.online > 0
|
||||
? `<span style="background:#e8f5e9;color:#2e7d32;border-radius:8px;padding:0 6px;font-size:10px;font-weight:bold;">${room.online}人</span>`
|
||||
const safeRoomName = escapeMobileDrawerHtml(String(room.name ?? ''));
|
||||
const safeOnlineCount = Math.max(Number.parseInt(room.online, 10) || 0, 0);
|
||||
const badge = safeOnlineCount > 0
|
||||
? `<span style="background:#e8f5e9;color:#2e7d32;border-radius:8px;padding:0 6px;font-size:10px;font-weight:bold;">${safeOnlineCount}人</span>`
|
||||
: `<span style="background:#f5f5f5;color:#bbb;border-radius:8px;padding:0 6px;font-size:10px;">空</span>`;
|
||||
const currentTag = isCurrent ? `<span style="font-size:9px;color:#7090b0;margin-left:3px;">当前</span>` : '';
|
||||
const clickAttr = isCurrent ? '' : `onclick="location.href='/room/${room.id}'"`;
|
||||
const clickAttr = isCurrent ? '' : `onclick="location.href='/room/${roomId}'"`;
|
||||
|
||||
return `<div ${clickAttr}
|
||||
style="display:flex;align-items:center;justify-content:space-between;
|
||||
padding:6px 10px;border-bottom:1px solid #eef2f8;background:${bg};
|
||||
cursor:${isCurrent ? 'default' : 'pointer'};">
|
||||
<span style="color:${nameColor};font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;margin-right:6px;">
|
||||
${room.name}${currentTag}
|
||||
${safeRoomName}${currentTag}
|
||||
</span>${badge}
|
||||
</div>`;
|
||||
}).join('');
|
||||
}).filter(Boolean).join('');
|
||||
|
||||
container.innerHTML = roomRows || '<div style="text-align:center;color:#bbb;padding:16px;font-size:11px;">暂无房间</div>';
|
||||
})
|
||||
.catch(() => {
|
||||
container.innerHTML = '<div style="text-align:center;color:#f00;padding:10px;font-size:11px;">加载失败</div>';
|
||||
|
||||
@@ -556,19 +556,26 @@
|
||||
'<div style="text-align:center;color:#bbb;padding:16px 0;font-size:11px;">暂无房间</div>';
|
||||
return;
|
||||
}
|
||||
container.innerHTML = data.rooms.map(room => {
|
||||
const isCurrent = room.id === _currentRoomId;
|
||||
const roomRows = data.rooms.map(room => {
|
||||
const roomId = Number.parseInt(room.id, 10);
|
||||
if (!Number.isInteger(roomId)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const isCurrent = roomId === _currentRoomId;
|
||||
const closed = !room.door_open;
|
||||
const safeRoomName = escapeHtml(String(room.name ?? ''));
|
||||
const safeOnlineCount = Math.max(Number.parseInt(room.online, 10) || 0, 0);
|
||||
const bg = isCurrent ? '#ecf4ff' : '#fff';
|
||||
const border = isCurrent ? '#aac5f0' : '#e0eaf5';
|
||||
const nameColor = isCurrent ? '#336699' : (closed ? '#bbb' : '#444');
|
||||
const badge = room.online > 0 ?
|
||||
`<span style="background:#e8f5e9;color:#2e7d32;border-radius:8px;padding:0 5px;font-size:10px;font-weight:bold;white-space:nowrap;flex-shrink:0;">${room.online} 人</span>` :
|
||||
const badge = safeOnlineCount > 0 ?
|
||||
`<span style="background:#e8f5e9;color:#2e7d32;border-radius:8px;padding:0 5px;font-size:10px;font-weight:bold;white-space:nowrap;flex-shrink:0;">${safeOnlineCount} 人</span>` :
|
||||
`<span style="background:#f5f5f5;color:#bbb;border-radius:8px;padding:0 5px;font-size:10px;white-space:nowrap;flex-shrink:0;">空</span>`;
|
||||
const currentTag = isCurrent ?
|
||||
`<span style="font-size:9px;color:#336699;opacity:.7;margin-left:3px;">当前</span>` :
|
||||
'';
|
||||
const clickHandler = isCurrent ? '' : `onclick="location.href='/room/${room.id}'"`;
|
||||
const clickHandler = isCurrent ? '' : `onclick="location.href='/room/${roomId}'"`;
|
||||
|
||||
return `<div ${clickHandler}
|
||||
style="display:flex;align-items:center;justify-content:space-between;
|
||||
@@ -579,11 +586,14 @@
|
||||
onmouseover="if(${!isCurrent}) this.style.background='#ddeeff';"
|
||||
onmouseout="this.style.background='${bg}';">
|
||||
<span style="color:${nameColor};font-size:11px;font-weight:${isCurrent?'bold':'normal'};overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;margin-right:4px;">
|
||||
${room.name}${currentTag}
|
||||
${safeRoomName}${currentTag}
|
||||
</span>
|
||||
${badge}
|
||||
</div>`;
|
||||
}).join('');
|
||||
}).filter(Boolean).join('');
|
||||
|
||||
container.innerHTML = roomRows ||
|
||||
'<div style="text-align:center;color:#bbb;padding:16px 0;font-size:11px;">暂无房间</div>';
|
||||
})
|
||||
.catch(() => {
|
||||
container.innerHTML =
|
||||
@@ -1030,7 +1040,7 @@
|
||||
// 生成自然语序的动作串:情绪型=[人][着/地]对[目标][verb]:;动作型=[人][了][目标],[verb]:
|
||||
const buildActionStr = (action, fromHtml, toHtml, verb = '说') => {
|
||||
const info = actionTextMap[action];
|
||||
if (!info) return `${fromHtml}对${toHtml}${action}${verb}:`;
|
||||
if (!info) return `${fromHtml}对${toHtml}${escapeHtml(String(action || ''))}${verb}:`;
|
||||
if (info.type === 'emotion') return `${fromHtml}${info.word}对${toHtml}${verb}:`;
|
||||
return `${fromHtml}${info.word}${toHtml},${verb}:`;
|
||||
};
|
||||
@@ -1437,6 +1447,7 @@
|
||||
.listen('ScreenCleared', (e) => {
|
||||
console.log('收到全员清屏事件:', e);
|
||||
const operator = e.operator;
|
||||
const safeOperator = escapeHtml(String(operator || ''));
|
||||
|
||||
// 清除公聊窗口所有消息
|
||||
const say1 = document.getElementById('chat-messages-container');
|
||||
@@ -1462,7 +1473,7 @@
|
||||
now.getMinutes().toString().padStart(2, '0') + ':' +
|
||||
now.getSeconds().toString().padStart(2, '0');
|
||||
sysDiv.innerHTML =
|
||||
`<span style="color: #dc2626; font-weight: bold;">🧹 管理员 <b>${operator}</b> 已执行全员清屏</span><span class="msg-time">(${timeStr})</span>`;
|
||||
`<span style="color: #dc2626; font-weight: bold;">🧹 管理员 <b>${safeOperator}</b> 已执行全员清屏</span><span class="msg-time">(${timeStr})</span>`;
|
||||
if (say1) {
|
||||
say1.appendChild(sysDiv);
|
||||
say1.scrollTop = say1.scrollHeight;
|
||||
@@ -1494,6 +1505,9 @@
|
||||
const timeStr = now.getHours().toString().padStart(2, '0') + ':' +
|
||||
now.getMinutes().toString().padStart(2, '0') + ':' +
|
||||
now.getSeconds().toString().padStart(2, '0');
|
||||
const safeVersion = e.safe_version ?? escapeHtml(String(e.version ?? ''));
|
||||
const safeTitle = e.safe_title ?? escapeHtml(String(e.title ?? ''));
|
||||
const safeUrl = escapeHtml(normalizeSafeChatUrl(e.url, '{{ route('changelog.index') }}'));
|
||||
|
||||
const sysDiv = document.createElement('div');
|
||||
sysDiv.className = 'msg-line';
|
||||
@@ -1501,8 +1515,8 @@
|
||||
sysDiv.style.cssText =
|
||||
'background: #fffbeb; border-left: 3px solid #d97706; border-radius: 4px; padding: 5px 10px; margin: 3px 0;';
|
||||
sysDiv.innerHTML = `<span style="color: #b45309; font-weight: bold;">
|
||||
📋 【版本更新】v${e.version} · ${e.title}
|
||||
<a href="${e.url}" target="_blank" rel="noopener"
|
||||
📋 【版本更新】v${safeVersion} · ${safeTitle}
|
||||
<a href="${safeUrl}" target="_blank" rel="noopener"
|
||||
style="color: #7c3aed; text-decoration: underline; margin-left: 8px; font-size: 0.85em;">
|
||||
查看详情 →
|
||||
</a>
|
||||
@@ -2405,4 +2419,24 @@
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* 规整广播携带的链接,只允许当前站点的 http(s) 地址进入 innerHTML。
|
||||
*/
|
||||
function normalizeSafeChatUrl(url, fallback) {
|
||||
try {
|
||||
const parsedUrl = new URL(url || fallback, window.location.origin);
|
||||
if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
if (parsedUrl.origin !== window.location.origin) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return parsedUrl.toString();
|
||||
} catch (error) {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user