initial
This commit is contained in:
252
src/DRS9.Dashboard.Server/wwwroot/css/app.css
Normal file
252
src/DRS9.Dashboard.Server/wwwroot/css/app.css
Normal file
@@ -0,0 +1,252 @@
|
||||
/* DRS9 Dashboard Admin Styles */
|
||||
|
||||
.page {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
padding: 1rem 0;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background-color: #f8f9fa;
|
||||
border-right: 1px solid #dee2e6;
|
||||
min-height: calc(100vh - 60px);
|
||||
}
|
||||
|
||||
.sidebar .nav-link {
|
||||
color: #495057;
|
||||
padding: 0.75rem 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.sidebar .nav-link:hover {
|
||||
color: #0d6efd;
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
|
||||
.sidebar .nav-link.active {
|
||||
color: #0d6efd;
|
||||
background-color: #e7f1ff;
|
||||
}
|
||||
|
||||
.sidebar .nav-link i {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
/* Card Styles */
|
||||
.card {
|
||||
border: none;
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Status Badges */
|
||||
.badge {
|
||||
padding: 0.5em 0.75em;
|
||||
}
|
||||
|
||||
/* Table Styles */
|
||||
.table {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.table thead th {
|
||||
border-bottom: 2px solid #dee2e6;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Form Styles */
|
||||
.form-label {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Action Buttons */
|
||||
.btn-sm {
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
/* Modal Customization */
|
||||
.modal-header {
|
||||
background-color: #f8f9fa;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
/* Stats Cards */
|
||||
.stat-card {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.stat-card.success {
|
||||
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
|
||||
}
|
||||
|
||||
.stat-card.warning {
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
}
|
||||
|
||||
.stat-card.info {
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
}
|
||||
|
||||
.stat-card h3 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.stat-card p {
|
||||
margin-bottom: 0;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Loading Spinner */
|
||||
.spinner-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
/* Device Status Indicators */
|
||||
.status-indicator {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.status-online {
|
||||
background-color: #28a745;
|
||||
}
|
||||
|
||||
.status-offline {
|
||||
background-color: #6c757d;
|
||||
}
|
||||
|
||||
.status-error {
|
||||
background-color: #dc3545;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
left: -250px;
|
||||
width: 250px;
|
||||
height: calc(100vh - 60px);
|
||||
transition: left 0.3s;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.sidebar.show {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
main {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Content Type Icons */
|
||||
.content-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.5rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.content-icon.video {
|
||||
background-color: #e3f2fd;
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
.content-icon.image {
|
||||
background-color: #f3e5f5;
|
||||
color: #7b1fa2;
|
||||
}
|
||||
|
||||
.content-icon.web {
|
||||
background-color: #e8f5e9;
|
||||
color: #388e3c;
|
||||
}
|
||||
|
||||
.content-icon.dashboard {
|
||||
background-color: #fff3e0;
|
||||
color: #f57c00;
|
||||
}
|
||||
|
||||
/* Drag and Drop */
|
||||
.draggable-item {
|
||||
cursor: move;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.draggable-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.draggable-item.dragging {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.drop-zone {
|
||||
border: 2px dashed #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
transition: border-color 0.2s, background-color 0.2s;
|
||||
}
|
||||
|
||||
.drop-zone.drag-over {
|
||||
border-color: #0d6efd;
|
||||
background-color: #e7f1ff;
|
||||
}
|
||||
|
||||
/* Toast Notifications */
|
||||
.toast-container {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 9999;
|
||||
}
|
||||
173
src/DRS9.Dashboard.Server/wwwroot/viewer.html
Normal file
173
src/DRS9.Dashboard.Server/wwwroot/viewer.html
Normal file
@@ -0,0 +1,173 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DRS9 查看器</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
background: #000;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#viewer {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#viewer iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
#viewer img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
#viewer video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.loading {
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #f44;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.info {
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
color: #fff;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="viewer">
|
||||
<div class="loading">正在加载内容...</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const API_BASE = '/api/guest';
|
||||
|
||||
// 从 URL 获取设备 ID
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const deviceId = urlParams.get('deviceId') || urlParams.get('d');
|
||||
|
||||
if (!deviceId) {
|
||||
showError('请指定设备 ID:?deviceId=1 或 ?d=1');
|
||||
} else {
|
||||
loadContent(deviceId);
|
||||
}
|
||||
|
||||
async function loadContent(deviceId) {
|
||||
try {
|
||||
// 先尝试获取播放列表
|
||||
const playlistRes = await fetch(`${API_BASE}/playlist/${deviceId}`);
|
||||
const playlistData = await playlistRes.json();
|
||||
|
||||
if (playlistData.success && playlistData.data.length > 0) {
|
||||
showPlaylist(playlistData.data, playlistData.loopMode);
|
||||
} else {
|
||||
// 回退到直接获取内容
|
||||
const contentRes = await fetch(`${API_BASE}/content/${deviceId}`);
|
||||
const contentData = await contentRes.json();
|
||||
|
||||
if (contentData.success && contentData.data.length > 0) {
|
||||
showContent(contentData.data);
|
||||
} else {
|
||||
showError('暂无内容');
|
||||
}
|
||||
}
|
||||
|
||||
// 显示设备信息
|
||||
document.getElementById('viewer').innerHTML += `
|
||||
<div class="info">设备 ${deviceId}</div>
|
||||
`;
|
||||
} catch (err) {
|
||||
showError('加载失败:' + err.message);
|
||||
}
|
||||
}
|
||||
|
||||
function showPlaylist(items, loopMode) {
|
||||
let currentIndex = 0;
|
||||
|
||||
const showItem = (index) => {
|
||||
if (index >= items.length) {
|
||||
if (loopMode === 'Loop') {
|
||||
index = 0;
|
||||
} else {
|
||||
// Once 模式,结束
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const item = items[index];
|
||||
const duration = item.duration * 1000;
|
||||
|
||||
if (item.applicationType === 'Video' || item.contentUrl.endsWith('.mp4')) {
|
||||
document.getElementById('viewer').innerHTML = `
|
||||
<video src="${item.contentUrl}" autoplay muted></video>
|
||||
`;
|
||||
const video = document.querySelector('video');
|
||||
video.onended = () => showItem(index + 1);
|
||||
} else if (item.applicationType === 'Image' || /\.(jpg|jpeg|png|gif|svg|webp)$/i.test(item.contentUrl)) {
|
||||
document.getElementById('viewer').innerHTML = `
|
||||
<img src="${item.contentUrl}" alt="${item.applicationName}">
|
||||
`;
|
||||
setTimeout(() => showItem(index + 1), duration);
|
||||
} else {
|
||||
// Web 内容
|
||||
document.getElementById('viewer').innerHTML = `
|
||||
<iframe src="${item.contentUrl}" sandbox="allow-scripts allow-same-origin allow-forms"></iframe>
|
||||
`;
|
||||
setTimeout(() => showItem(index + 1), duration);
|
||||
}
|
||||
|
||||
currentIndex = index;
|
||||
};
|
||||
|
||||
showItem(0);
|
||||
}
|
||||
|
||||
function showContent(items) {
|
||||
showPlaylist(items.map(item => ({
|
||||
applicationName: item.applicationName,
|
||||
applicationType: item.applicationType,
|
||||
contentUrl: item.contentUrl,
|
||||
duration: item.duration
|
||||
})), 'Loop');
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
document.getElementById('viewer').innerHTML = `
|
||||
<div class="error">${message}</div>
|
||||
`;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user