mirror of
https://github.com/Coldsmiles/infstarweb.git
synced 2026-04-23 02:30:41 +08:00
feat: Add video embedding functionality and update facilities data structure
This commit is contained in:
@@ -453,6 +453,27 @@
|
|||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Video Embed */
|
||||||
|
.video-embed-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 56.25%; /* 16:9 aspect ratio */
|
||||||
|
margin: 12px 0 20px;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #000;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-embed-wrapper iframe {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
.no-results-message {
|
.no-results-message {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 60px;
|
padding: 60px;
|
||||||
@@ -999,6 +1020,11 @@
|
|||||||
color: #1565c0;
|
color: #1565c0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.badge-video {
|
||||||
|
background: #fce4ec;
|
||||||
|
color: #c62828;
|
||||||
|
}
|
||||||
|
|
||||||
.sortable-item .item-content {
|
.sortable-item .item-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
border: 1px solid rgba(0,0,0,0.06) !important;
|
border: 1px solid rgba(0,0,0,0.06) !important;
|
||||||
|
|||||||
@@ -23,8 +23,8 @@
|
|||||||
"content": "使用前请仔细阅读告示牌说明。指示灯熄灭前请勿离开。"
|
"content": "使用前请仔细阅读告示牌说明。指示灯熄灭前请勿离开。"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "video",
|
||||||
"content": "参考视频:BV1qPhWzdEwU"
|
"content": "BV1qPhWzdEwU"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -59,8 +59,8 @@
|
|||||||
],
|
],
|
||||||
"notes": [
|
"notes": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "video",
|
||||||
"content": "参考视频:BV1g142167tJ"
|
"content": "BV1g142167tJ"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -266,6 +266,9 @@
|
|||||||
<button type="button" class="add-item-btn" data-target="instructions" data-type="image">
|
<button type="button" class="add-item-btn" data-target="instructions" data-type="image">
|
||||||
<i class="fas fa-image"></i> 添加图片
|
<i class="fas fa-image"></i> 添加图片
|
||||||
</button>
|
</button>
|
||||||
|
<button type="button" class="add-item-btn" data-target="instructions" data-type="video">
|
||||||
|
<i class="fas fa-video"></i> 添加视频
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -278,6 +281,9 @@
|
|||||||
<button type="button" class="add-item-btn" data-target="notes" data-type="image">
|
<button type="button" class="add-item-btn" data-target="notes" data-type="image">
|
||||||
<i class="fas fa-image"></i> 添加图片
|
<i class="fas fa-image"></i> 添加图片
|
||||||
</button>
|
</button>
|
||||||
|
<button type="button" class="add-item-btn" data-target="notes" data-type="video">
|
||||||
|
<i class="fas fa-video"></i> 添加视频
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="editor-actions">
|
<div class="editor-actions">
|
||||||
|
|||||||
@@ -195,10 +195,46 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
img.src = block.content;
|
img.src = block.content;
|
||||||
img.loading = 'lazy';
|
img.loading = 'lazy';
|
||||||
container.appendChild(img);
|
container.appendChild(img);
|
||||||
|
} else if (block.type === 'video') {
|
||||||
|
const bv = parseBVNumber(block.content);
|
||||||
|
if (bv) {
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.className = 'video-embed-wrapper';
|
||||||
|
const iframe = document.createElement('iframe');
|
||||||
|
iframe.src = `https://player.bilibili.com/player.html?bvid=${bv}&autoplay=0&high_quality=1`;
|
||||||
|
iframe.allowFullscreen = true;
|
||||||
|
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-popups');
|
||||||
|
iframe.loading = 'lazy';
|
||||||
|
wrapper.appendChild(iframe);
|
||||||
|
container.appendChild(wrapper);
|
||||||
|
} else {
|
||||||
|
const p = document.createElement('p');
|
||||||
|
p.className = 'text-secondary';
|
||||||
|
p.innerText = '无效的视频 BV 号';
|
||||||
|
container.appendChild(p);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseBVNumber(input) {
|
||||||
|
if (!input) return null;
|
||||||
|
input = input.trim();
|
||||||
|
// Match BV number directly (e.g. BV1qPhWzdEwU)
|
||||||
|
const bvPattern = /^(BV[A-Za-z0-9]+)$/;
|
||||||
|
const directMatch = input.match(bvPattern);
|
||||||
|
if (directMatch) return directMatch[1];
|
||||||
|
// Match from bilibili URL (e.g. https://www.bilibili.com/video/BV1qPhWzdEwU/...)
|
||||||
|
const urlPattern = /bilibili\.com\/video\/(BV[A-Za-z0-9]+)/;
|
||||||
|
const urlMatch = input.match(urlPattern);
|
||||||
|
if (urlMatch) return urlMatch[1];
|
||||||
|
// Match from b23.tv short URL or other formats containing BV
|
||||||
|
const generalPattern = /(BV[A-Za-z0-9]{10,})/;
|
||||||
|
const generalMatch = input.match(generalPattern);
|
||||||
|
if (generalMatch) return generalMatch[1];
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
function getStatusText(status) {
|
function getStatusText(status) {
|
||||||
const map = {
|
const map = {
|
||||||
@@ -433,14 +469,20 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
div.dataset.idx = idx;
|
div.dataset.idx = idx;
|
||||||
div.dataset.listId = listId;
|
div.dataset.listId = listId;
|
||||||
|
|
||||||
const isText = item.type === 'text';
|
const typeBadgeClass = item.type === 'text' ? 'badge-text' : item.type === 'image' ? 'badge-image' : 'badge-video';
|
||||||
|
const typeBadgeLabel = item.type === 'text' ? '文字' : item.type === 'image' ? '图片' : '视频';
|
||||||
|
let contentHtml;
|
||||||
|
if (item.type === 'text') {
|
||||||
|
contentHtml = `<textarea class="item-content" rows="2" placeholder="输入文字内容...">${escapeHtml(item.content)}</textarea>`;
|
||||||
|
} else if (item.type === 'image') {
|
||||||
|
contentHtml = `<input type="text" class="item-content" placeholder="输入图片URL..." value="${escapeHtml(item.content)}">`;
|
||||||
|
} else {
|
||||||
|
contentHtml = `<input type="text" class="item-content" placeholder="BV1xxxxxxxxxx 或 bilibili 视频地址" value="${escapeHtml(item.content)}">`;
|
||||||
|
}
|
||||||
div.innerHTML = `
|
div.innerHTML = `
|
||||||
<span class="drag-handle"><i class="fas fa-grip-vertical"></i></span>
|
<span class="drag-handle"><i class="fas fa-grip-vertical"></i></span>
|
||||||
<span class="item-type-badge ${isText ? 'badge-text' : 'badge-image'}">${isText ? '文字' : '图片'}</span>
|
<span class="item-type-badge ${typeBadgeClass}">${typeBadgeLabel}</span>
|
||||||
${isText
|
${contentHtml}
|
||||||
? `<textarea class="item-content" rows="2" placeholder="输入文字内容...">${escapeHtml(item.content)}</textarea>`
|
|
||||||
: `<input type="text" class="item-content" placeholder="输入图片URL..." value="${escapeHtml(item.content)}">`
|
|
||||||
}
|
|
||||||
<button type="button" class="remove-item-btn" title="删除"><i class="fas fa-trash-alt"></i></button>
|
<button type="button" class="remove-item-btn" title="删除"><i class="fas fa-trash-alt"></i></button>
|
||||||
`;
|
`;
|
||||||
container.appendChild(div);
|
container.appendChild(div);
|
||||||
@@ -606,8 +648,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
editorInstructions.forEach(block => {
|
editorInstructions.forEach(block => {
|
||||||
if (block.type === 'text') {
|
if (block.type === 'text') {
|
||||||
html += `<p>${escapeHtml(block.content) || '<span class=\"text-secondary\">空文字</span>'}</p>`;
|
html += `<p>${escapeHtml(block.content) || '<span class=\"text-secondary\">空文字</span>'}</p>`;
|
||||||
} else {
|
} else if (block.type === 'image') {
|
||||||
html += block.content ? `<img src="${escapeHtml(block.content)}" loading="lazy">` : '<p class="text-secondary">空图片</p>';
|
html += block.content ? `<img src="${escapeHtml(block.content)}" loading="lazy">` : '<p class="text-secondary">空图片</p>';
|
||||||
|
} else if (block.type === 'video') {
|
||||||
|
html += renderVideoPreviewHtml(block.content);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -623,8 +667,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
editorNotes.forEach(block => {
|
editorNotes.forEach(block => {
|
||||||
if (block.type === 'text') {
|
if (block.type === 'text') {
|
||||||
html += `<p>${escapeHtml(block.content) || '<span class=\"text-secondary\">空文字</span>'}</p>`;
|
html += `<p>${escapeHtml(block.content) || '<span class=\"text-secondary\">空文字</span>'}</p>`;
|
||||||
} else {
|
} else if (block.type === 'image') {
|
||||||
html += block.content ? `<img src="${escapeHtml(block.content)}" loading="lazy">` : '<p class="text-secondary">空图片</p>';
|
html += block.content ? `<img src="${escapeHtml(block.content)}" loading="lazy">` : '<p class="text-secondary">空图片</p>';
|
||||||
|
} else if (block.type === 'video') {
|
||||||
|
html += renderVideoPreviewHtml(block.content);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -657,8 +703,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
z: parseInt(document.getElementById('editor-z').value) || 0
|
z: parseInt(document.getElementById('editor-z').value) || 0
|
||||||
},
|
},
|
||||||
contributors: [...editorContributors],
|
contributors: [...editorContributors],
|
||||||
instructions: editorInstructions.filter(i => i.content.trim() !== ''),
|
instructions: editorInstructions.filter(i => i.content.trim() !== '').map(i => i.type === 'video' ? { type: 'video', content: parseBVNumber(i.content) || i.content } : {...i}),
|
||||||
notes: editorNotes.filter(n => n.content.trim() !== '')
|
notes: editorNotes.filter(n => n.content.trim() !== '').map(n => n.type === 'video' ? { type: 'video', content: parseBVNumber(n.content) || n.content } : {...n})
|
||||||
};
|
};
|
||||||
|
|
||||||
const jsonStr = JSON.stringify(facilityObj, null, 4);
|
const jsonStr = JSON.stringify(facilityObj, null, 4);
|
||||||
@@ -689,6 +735,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function renderVideoPreviewHtml(content) {
|
||||||
|
const bv = parseBVNumber(content);
|
||||||
|
if (bv) {
|
||||||
|
return `<div class="video-embed-wrapper"><iframe src="https://player.bilibili.com/player.html?bvid=${bv}&autoplay=0&high_quality=1" allowfullscreen sandbox="allow-scripts allow-same-origin allow-popups" loading="lazy"></iframe></div>`;
|
||||||
|
}
|
||||||
|
return '<p class="text-secondary">请输入有效的 BV 号或 bilibili 视频地址</p>';
|
||||||
|
}
|
||||||
|
|
||||||
// --- Utility ---
|
// --- Utility ---
|
||||||
function escapeHtml(text) {
|
function escapeHtml(text) {
|
||||||
if (!text) return '';
|
if (!text) return '';
|
||||||
|
|||||||
Reference in New Issue
Block a user