Add Dashboard video publishing helper
This commit is contained in:
parent
e430ecf2ef
commit
c7395f6f57
@ -6292,6 +6292,75 @@ body::after {
|
||||
border-radius: 0 0 14px 14px;
|
||||
}
|
||||
|
||||
/* ========== Video Helper (视频助手) ========== */
|
||||
.video-helper-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
.video-helper-card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 10px;
|
||||
padding: 14px 16px;
|
||||
min-width: 0;
|
||||
}
|
||||
.video-helper-card:hover {
|
||||
border-color: rgba(79,140,255,0.25);
|
||||
}
|
||||
.video-helper-card-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.video-helper-title-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
min-width: 0;
|
||||
}
|
||||
.video-helper-platform {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
width: fit-content;
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
background: rgba(79,140,255,0.1);
|
||||
color: var(--accent-blue);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.video-helper-title {
|
||||
color: var(--text-primary);
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.video-helper-note {
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.video-helper-content {
|
||||
margin: 0;
|
||||
padding: 10px 12px;
|
||||
min-height: 54px;
|
||||
max-height: 260px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-secondary);
|
||||
font-family: inherit;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* ========== Art Development ========== */
|
||||
.artdev-module .module-body {
|
||||
padding: 0;
|
||||
|
||||
@ -863,6 +863,7 @@
|
||||
<!-- Sub-tabs -->
|
||||
<div class="sub-tabs" id="sns-sub-tabs">
|
||||
<button class="sub-tab" data-sub="tools">常用工具</button>
|
||||
<button class="sub-tab" data-sub="video">视频助手</button>
|
||||
<button class="sub-tab" data-sub="xiaoheihe">小黑盒</button>
|
||||
<button class="sub-tab active" data-sub="steam">Steam商店</button>
|
||||
<button class="sub-tab" data-sub="bilibili">Bilibili</button>
|
||||
@ -889,6 +890,20 @@
|
||||
<div id="qr-list"></div>
|
||||
</div>
|
||||
|
||||
<!-- Sub-panel: 视频助手 -->
|
||||
<div id="sub-sns-video" class="sub-panel">
|
||||
<div class="sns-header-bar">
|
||||
<div class="sns-header-left">
|
||||
<span class="sns-header-title">视频助手</span>
|
||||
<span class="sns-header-count" id="video-helper-count">加载中...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<input type="text" id="video-helper-search" class="search-input" placeholder="搜索平台、标题或标签...">
|
||||
</div>
|
||||
<div id="video-helper-list"></div>
|
||||
</div>
|
||||
|
||||
<!-- Sub-panel: 小黑盒 -->
|
||||
<div id="sub-sns-xiaoheihe" class="sub-panel">
|
||||
<div class="sns-header-bar">
|
||||
@ -1398,6 +1413,7 @@
|
||||
<script src="js/sns.js"></script>
|
||||
<script src="js/community_monitor.js"></script>
|
||||
<script src="js/quick_replies.js"></script>
|
||||
<script src="js/video_helper.js"></script>
|
||||
<script src="js/form_helper.js"></script>
|
||||
<script src="js/art_dev.js"></script>
|
||||
<script src="js/email_processing.js"></script>
|
||||
|
||||
@ -44,9 +44,11 @@ function snsInit() {
|
||||
const targetPanel = document.getElementById('sub-sns-' + sub);
|
||||
if (targetPanel) targetPanel.classList.add('active');
|
||||
|
||||
// tools 子标签走常用回复模块,其它走 SNS 平台数据
|
||||
// tools/video 子标签走静态工具模块,其它走 SNS 平台数据
|
||||
if (sub === 'tools') {
|
||||
if (typeof quickRepliesInit === 'function') quickRepliesInit();
|
||||
} else if (sub === 'video') {
|
||||
if (typeof videoHelperInit === 'function') videoHelperInit();
|
||||
} else {
|
||||
currentSnsPlatform = sub;
|
||||
snsLoadPlatform(currentSnsPlatform);
|
||||
|
||||
267
Tools/Dashboard/js/video_helper.js
Normal file
267
Tools/Dashboard/js/video_helper.js
Normal file
@ -0,0 +1,267 @@
|
||||
/* ============================================================
|
||||
TH1 Dashboard - SNS Assistant / Video Helper
|
||||
Static copy blocks for publishing trailers and platform tags.
|
||||
============================================================ */
|
||||
|
||||
const videoHelperState = {
|
||||
initialized: false,
|
||||
items: [
|
||||
{
|
||||
id: 'bilibili-tags-hakurei-summer',
|
||||
platform: 'Bilibili',
|
||||
title: 'Bilibili Tag - 博丽帝国夏促宣传片',
|
||||
note: 'B站投稿标签,优先覆盖东方、策略、Steam、游戏宣传片。',
|
||||
content: [
|
||||
'帝国幻想乡',
|
||||
'TOHOTOPIA',
|
||||
'东方Project',
|
||||
'东方同人游戏',
|
||||
'博丽灵梦',
|
||||
'博丽帝国',
|
||||
'策略游戏',
|
||||
'战棋游戏',
|
||||
'4X游戏',
|
||||
'独立游戏',
|
||||
'Steam游戏',
|
||||
'Steam夏促'
|
||||
].join(' ')
|
||||
},
|
||||
{
|
||||
id: 'youtube-tags-hakurei-summer',
|
||||
platform: 'YouTube',
|
||||
title: 'YouTube Tags - Hakurei Empire Summer Sale Trailer',
|
||||
note: 'YouTube 标签栏使用逗号分隔,偏英文搜索。',
|
||||
content: [
|
||||
'TOHOTOPIA',
|
||||
'Tohotopia',
|
||||
'Touhou',
|
||||
'Touhou Project',
|
||||
'Touhou fangame',
|
||||
'Touhou fan game',
|
||||
'Hakurei Reimu',
|
||||
'Reimu Hakurei',
|
||||
'Hakurei Empire',
|
||||
'Sumireko Usami',
|
||||
'Kasen Ibaraki',
|
||||
'Aunn Komano',
|
||||
'Suika Ibuki',
|
||||
'strategy game',
|
||||
'4X strategy',
|
||||
'turn based strategy',
|
||||
'indie game',
|
||||
'Steam game',
|
||||
'Steam Summer Sale',
|
||||
'free update',
|
||||
'new faction',
|
||||
'Polytopia'
|
||||
].join(', ')
|
||||
},
|
||||
{
|
||||
id: 'twitter-tags-hakurei-summer',
|
||||
platform: 'X / Twitter',
|
||||
title: 'X/Twitter Hashtags - 英文主推',
|
||||
note: '主推文建议 2-4 个 hashtag,不要塞太多。',
|
||||
content: '#TOHOTOPIA #Touhou #IndieGame #SteamSummerSale'
|
||||
},
|
||||
{
|
||||
id: 'twitter-tags-alt',
|
||||
platform: 'X / Twitter',
|
||||
title: 'X/Twitter Hashtags - 策略玩家向',
|
||||
note: '当推文更强调玩法、4X、回合制时使用。',
|
||||
content: '#TOHOTOPIA #TurnBasedStrategy #4XStrategy #IndieGame'
|
||||
},
|
||||
{
|
||||
id: 'youtube-title-hakurei',
|
||||
platform: 'YouTube',
|
||||
title: 'YouTube Title - 英文标题',
|
||||
note: '适合英文版宣传片。',
|
||||
content: 'Hakurei Empire is Here! Free Faction Update & Steam Summer Sale | TOHOTOPIA'
|
||||
},
|
||||
{
|
||||
id: 'bilibili-title-hakurei',
|
||||
platform: 'Bilibili',
|
||||
title: 'Bilibili Title - 中文标题',
|
||||
note: '延续“参赛确认”系列感。',
|
||||
content: '博丽帝国参赛确认!新阵营免费更新,Steam夏促进行中!'
|
||||
},
|
||||
{
|
||||
id: 'twitter-post-hakurei',
|
||||
platform: 'X / Twitter',
|
||||
title: 'X/Twitter Post - 英文主推文',
|
||||
note: '主推文建议原生上传视频,Steam 链接放回复或正文末尾。',
|
||||
content: [
|
||||
'Free Update: Hakurei Empire is Here!',
|
||||
'',
|
||||
'TOHOTOPIA is 40% off during the Steam Summer Sale.',
|
||||
'Build your Gensokyo empire with Reimu, Sumireko, Kasen, Aunn, and Suika.',
|
||||
'',
|
||||
'#TOHOTOPIA #Touhou #IndieGame #SteamSummerSale'
|
||||
].join('\n')
|
||||
},
|
||||
{
|
||||
id: 'steam-link-reply',
|
||||
platform: 'X / Twitter',
|
||||
title: 'X/Twitter Reply - Steam 链接回复',
|
||||
note: '主推文不放外链时,第一条回复放这个。',
|
||||
content: [
|
||||
'Play TOHOTOPIA on Steam:',
|
||||
'https://store.steampowered.com/app/3774440/TOHOTOPIA/'
|
||||
].join('\n')
|
||||
},
|
||||
{
|
||||
id: 'bilibili-description-hakurei',
|
||||
platform: 'Bilibili',
|
||||
title: 'Bilibili Description - 中文简介',
|
||||
note: '发布中文版本视频时可直接改价格/群号后使用。',
|
||||
content: [
|
||||
'大家好,这里是《帝国幻想乡~Tohotopia》开发者 天火人雪糕!',
|
||||
'',
|
||||
'《帝国幻想乡~Tohotopia》新阵营【博丽帝国】现已免费更新!',
|
||||
'Steam 夏促同步进行中,欢迎加入愿望单 / 入手体验~',
|
||||
'',
|
||||
'--------------------------------',
|
||||
'本视频是【博丽帝国】阵营宣传片!',
|
||||
'',
|
||||
'博丽帝国阵营——幻想乡的巫女、守护者与异变专家们正式参赛。',
|
||||
'灵梦、堇子、华扇、阿吽、萃香将以全新的英雄与技能组合登场,在棋盘上展开一场神社势力的大会战!',
|
||||
'',
|
||||
'【King职阶英雄】博丽灵梦',
|
||||
'【Queen职阶英雄】宇佐见堇子',
|
||||
'【Bishop职阶英雄】茨木华扇',
|
||||
'【Knight职阶英雄】高丽野阿吽',
|
||||
'【Rook职阶英雄】伊吹萃香',
|
||||
'',
|
||||
'Steam商店页:https://store.steampowered.com/app/3774440/TOHOTOPIA/',
|
||||
'玩家群:请替换为群号',
|
||||
'',
|
||||
'如果你喜欢文明、Polytopia、战棋、东方同人策略游戏,欢迎关注我们~'
|
||||
].join('\n')
|
||||
},
|
||||
{
|
||||
id: 'youtube-description-hakurei',
|
||||
platform: 'YouTube',
|
||||
title: 'YouTube Description - English Description',
|
||||
note: '适合英文版 YouTube 视频简介。',
|
||||
content: [
|
||||
'The Hakurei Empire is now available as a free update in TOHOTOPIA!',
|
||||
'',
|
||||
'A new faction joins the tournament with Reimu Hakurei, Sumireko Usami, Kasen Ibaraki, Aunn Komano, and Suika Ibuki. Build your empire, explore the board, and turn the battlefield into a very Gensokyo-style strategy game.',
|
||||
'',
|
||||
'Steam Summer Sale is live now.',
|
||||
'',
|
||||
'Steam page:',
|
||||
'https://store.steampowered.com/app/3774440/TOHOTOPIA/',
|
||||
'',
|
||||
'#TOHOTOPIA #Touhou #IndieGame #SteamSummerSale'
|
||||
].join('\n')
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
function videoHelperInit() {
|
||||
if (!videoHelperState.initialized) {
|
||||
videoHelperState.initialized = true;
|
||||
const searchInput = document.getElementById('video-helper-search');
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', videoHelperRender);
|
||||
}
|
||||
}
|
||||
videoHelperRender();
|
||||
}
|
||||
|
||||
function videoHelperRender() {
|
||||
const container = document.getElementById('video-helper-list');
|
||||
const countEl = document.getElementById('video-helper-count');
|
||||
if (!container) return;
|
||||
|
||||
const searchInput = document.getElementById('video-helper-search');
|
||||
const term = searchInput ? searchInput.value.toLowerCase().trim() : '';
|
||||
|
||||
let items = videoHelperState.items.slice();
|
||||
if (term) {
|
||||
items = items.filter(item => {
|
||||
const haystack = `${item.platform} ${item.title} ${item.note} ${item.content}`.toLowerCase();
|
||||
return haystack.includes(term);
|
||||
});
|
||||
}
|
||||
|
||||
if (countEl) {
|
||||
countEl.textContent = `${items.length} / ${videoHelperState.items.length} 条`;
|
||||
}
|
||||
|
||||
if (items.length === 0) {
|
||||
container.innerHTML = '<div class="sns-empty">没有匹配的视频发布文案</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = `<div class="video-helper-grid">${items.map(videoHelperRenderCard).join('')}</div>`;
|
||||
}
|
||||
|
||||
function videoHelperRenderCard(item) {
|
||||
return `<div class="video-helper-card" data-id="${videoHelperEscHtml(item.id)}">
|
||||
<div class="video-helper-card-header">
|
||||
<div class="video-helper-title-wrap">
|
||||
<span class="video-helper-platform">${videoHelperEscHtml(item.platform)}</span>
|
||||
<span class="video-helper-title">${videoHelperEscHtml(item.title)}</span>
|
||||
</div>
|
||||
<button class="qr-copy-btn" onclick="videoHelperCopy('${videoHelperEscAttr(item.id)}')" title="复制纯文本">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
|
||||
复制
|
||||
</button>
|
||||
</div>
|
||||
<div class="video-helper-note">${videoHelperEscHtml(item.note || '')}</div>
|
||||
<pre class="video-helper-content">${videoHelperEscHtml(item.content || '')}</pre>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
async function videoHelperCopy(id) {
|
||||
const item = videoHelperState.items.find(entry => entry.id === id);
|
||||
if (!item) return;
|
||||
|
||||
try {
|
||||
await videoHelperCopyText(item.content || '');
|
||||
videoHelperToast('已复制到剪贴板', 'success');
|
||||
} catch (e) {
|
||||
console.error('Video helper copy failed:', e);
|
||||
videoHelperToast('复制失败: ' + e.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function videoHelperCopyText(text) {
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
await navigator.clipboard.writeText(text);
|
||||
return;
|
||||
}
|
||||
|
||||
const ta = document.createElement('textarea');
|
||||
ta.value = text;
|
||||
ta.style.position = 'fixed';
|
||||
ta.style.opacity = '0';
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(ta);
|
||||
}
|
||||
|
||||
function videoHelperEscHtml(str) {
|
||||
if (str === undefined || str === null) return '';
|
||||
const div = document.createElement('div');
|
||||
div.textContent = String(str);
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function videoHelperEscAttr(str) {
|
||||
return String(str || '').replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
||||
}
|
||||
|
||||
function videoHelperToast(message, type) {
|
||||
if (typeof showToast === 'function') {
|
||||
showToast(message, type || 'success');
|
||||
return;
|
||||
}
|
||||
if (typeof qrToast === 'function') {
|
||||
qrToast(message, type || 'success');
|
||||
return;
|
||||
}
|
||||
alert(message);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user