1753 lines
71 KiB
Plaintext
1753 lines
71 KiB
Plaintext
<!doctype html>
|
||
<html lang="zh-CN">
|
||
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>作品广场 - 灵丌AI - 001CODE</title>
|
||
<style>
|
||
:root {
|
||
--bg: #f0f4f9; --ink: #0f172a; --muted: #64748b;
|
||
--ai-cyan: #00CCFF; --ai-accent: #00CCFF; --line: #e2e8f0;
|
||
--nav-h: 56px;
|
||
}
|
||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||
html, body { min-height: 100%; }
|
||
body {
|
||
background: var(--bg); color: var(--ink);
|
||
font-family: "Noto Sans SC", "PingFang SC", system-ui, -apple-system, "Segoe UI", Arial, sans-serif;
|
||
}
|
||
|
||
.cd-nav {
|
||
position: sticky; top: 0; z-index: 50; height: var(--nav-h);
|
||
background: #fff; border-bottom: 1px solid var(--line);
|
||
display: flex; align-items: center; padding: 0 16px; gap: 12px;
|
||
}
|
||
.cd-nav__back {
|
||
display: inline-flex; align-items: center; gap: 8px;
|
||
padding: 8px 16px 8px 10px; border-radius: 12px;
|
||
border: 1.5px solid #7dd3fc;
|
||
background: linear-gradient(135deg, #f0fbff, #f0fbff);
|
||
text-decoration: none; color: #0003FF; font-size: 14px; font-weight: 700;
|
||
transition: all 0.2s; flex-shrink: 0;
|
||
box-shadow: 0 1px 3px rgba(0, 3, 255, 0.12);
|
||
}
|
||
.cd-nav__back svg {
|
||
flex-shrink: 0; width: 18px; height: 18px;
|
||
padding: 5px; box-sizing: content-box;
|
||
border-radius: 8px; background: #fff;
|
||
border: 1px solid #bae6fd; color: #0003FF;
|
||
}
|
||
.cd-nav__back:hover {
|
||
border-color: #60a5fa;
|
||
background: linear-gradient(135deg, #e0f7ff, #e0f7ff);
|
||
color: #0003FF;
|
||
box-shadow: 0 2px 8px rgba(0, 3, 255, 0.18);
|
||
}
|
||
.cd-nav__back:hover svg { border-color: #7dd3fc; background: #f0fbff; }
|
||
.cd-nav__logo {
|
||
display: inline-flex; align-items: center; justify-content: center;
|
||
flex-shrink: 0; height: 48px; width: 48px; text-decoration: none;
|
||
transition: transform 0.2s, opacity 0.2s;
|
||
}
|
||
.cd-nav__logo img {
|
||
height: 48px; width: 48px; object-fit: contain; display: block;
|
||
}
|
||
.cd-nav__logo:hover { transform: scale(1.08); opacity: 0.92; }
|
||
.cd-nav__course {
|
||
flex: 1; min-width: 0;
|
||
font-size: 15px; font-weight: 700; color: #0b1a4a;
|
||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||
}
|
||
.cd-nav__right {
|
||
display: flex; align-items: center; gap: 8px; flex-shrink: 0; margin-left: auto;
|
||
}
|
||
.cd-nav__btn {
|
||
display: inline-flex; align-items: center; gap: 6px;
|
||
padding: 7px 12px; border-radius: 18px; border: 1px solid var(--line);
|
||
background: #f8fafc; color: #475569; font-size: 12px; font-weight: 600;
|
||
text-decoration: none; cursor: pointer; font-family: inherit;
|
||
transition: all 0.2s; white-space: nowrap;
|
||
}
|
||
.cd-nav__btn:hover, .cd-nav__btn.is-active {
|
||
border-color: #7dd3fc; background: #f0fbff; color: #0003FF;
|
||
}
|
||
.cd-nav__user {
|
||
display: none; align-items: center; gap: 8px;
|
||
padding: 3px 10px 3px 3px; border-radius: 22px;
|
||
border: 1px solid var(--line); background: #fff;
|
||
color: inherit; max-width: 180px;
|
||
}
|
||
.cd-nav__avatar {
|
||
width: 28px; height: 28px; border-radius: 50%; flex-shrink: 0;
|
||
background: linear-gradient(135deg, #e0f7ff, #7dd3fc);
|
||
display: flex; align-items: center; justify-content: center; font-size: 14px;
|
||
overflow: hidden;
|
||
}
|
||
.cd-nav__avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||
.cd-nav__user-meta { min-width: 0; line-height: 1.2; }
|
||
.cd-nav__user-name {
|
||
font-size: 12px; font-weight: 600; color: #0b1a4a;
|
||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 80px;
|
||
}
|
||
.cd-nav__user-score { font-size: 10px; color: #0003FF; font-weight: 500; text-decoration: none; }
|
||
.cd-nav__user-score:hover { text-decoration: underline; }
|
||
.cd-nav__login {
|
||
display: none; padding: 7px 14px; border-radius: 18px; border: none;
|
||
background: linear-gradient(90deg, rgba(0, 204, 255, 1) 0%, rgba(0, 3, 255, 1) 100%);
|
||
color: #fff; font-size: 12px; font-weight: 600; text-decoration: none;
|
||
}
|
||
|
||
.page { max-width: 1200px; margin: 0 auto; padding: 28px 24px 48px; }
|
||
.page-hero {
|
||
display: flex; align-items: flex-start; justify-content: space-between;
|
||
gap: 16px; margin-bottom: 24px; flex-wrap: wrap;
|
||
}
|
||
.page-hero__title { font-size: 28px; font-weight: 800; color: #0b1a4a; margin-bottom: 8px; }
|
||
.page-hero__sub { font-size: 14px; color: var(--muted); line-height: 1.6; }
|
||
.scope-tabs {
|
||
display: inline-flex; align-items: center; gap: 6px;
|
||
padding: 4px; border-radius: 12px; background: #fff; border: 1px solid var(--line);
|
||
flex-shrink: 0;
|
||
}
|
||
.scope-tab {
|
||
padding: 8px 14px; border-radius: 9px; border: none;
|
||
background: transparent; color: #64748b; font-size: 13px; font-weight: 700;
|
||
cursor: pointer; font-family: inherit; transition: all 0.2s; white-space: nowrap;
|
||
}
|
||
.scope-tab:hover { color: #0003FF; }
|
||
.scope-tab.active {
|
||
background: linear-gradient(135deg, rgba(0,204,255,0.14), rgba(0,3,255,0.1));
|
||
color: #0003FF;
|
||
}
|
||
|
||
.filter-row {
|
||
display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 22px;
|
||
}
|
||
.filter-chip {
|
||
padding: 8px 14px; border-radius: 999px; border: 1.5px solid #e2e8f0;
|
||
background: #fff; color: #475569; font-size: 13px; font-weight: 600;
|
||
cursor: pointer; font-family: inherit; transition: all 0.2s;
|
||
}
|
||
.filter-chip:hover { border-color: #7dd3fc; color: #0003FF; }
|
||
.filter-chip.active {
|
||
border-color: #0003FF; color: #0003FF;
|
||
background: linear-gradient(135deg, rgba(0,204,255,0.12), rgba(0,3,255,0.08));
|
||
}
|
||
|
||
.works-grid {
|
||
display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px;
|
||
}
|
||
.work-card {
|
||
display: flex; flex-direction: column;
|
||
border-radius: 14px; overflow: hidden; border: 1px solid #e8ecf1;
|
||
background: #fff; transition: all 0.25s; cursor: pointer; text-align: left;
|
||
font-family: inherit; padding: 0; width: 100%; height: 100%;
|
||
}
|
||
.work-card:hover {
|
||
transform: translateY(-3px); box-shadow: 0 10px 28px rgba(0,3,255,0.12);
|
||
}
|
||
.work-card__media {
|
||
width: 100%; aspect-ratio: 4/3; object-fit: cover; display: block; flex-shrink: 0;
|
||
background: linear-gradient(135deg, #e0f7ff, #7dd3fc);
|
||
}
|
||
.work-card__media-wrap {
|
||
position: relative; width: 100%; aspect-ratio: 4/3; flex-shrink: 0;
|
||
background: linear-gradient(135deg, #e0f7ff, #7dd3fc); overflow: hidden;
|
||
}
|
||
.work-card__media-wrap .work-card__media {
|
||
width: 100%; height: 100%; aspect-ratio: auto;
|
||
}
|
||
.work-card__media-wrap video.work-card__media {
|
||
background: #0f172a; object-fit: cover;
|
||
}
|
||
.work-card__play {
|
||
position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
|
||
width: 44px; height: 44px; border-radius: 50%;
|
||
background: rgba(15, 23, 42, 0.55); color: #fff;
|
||
display: flex; align-items: center; justify-content: center;
|
||
pointer-events: none; backdrop-filter: blur(2px);
|
||
}
|
||
.work-card__play svg { width: 18px; height: 18px; margin-left: 2px; }
|
||
|
||
.preview-modal {
|
||
display: none; position: fixed; inset: 0; z-index: 300;
|
||
background: rgba(15, 23, 42, 0.72); align-items: center; justify-content: center;
|
||
padding: 24px; box-sizing: border-box;
|
||
}
|
||
.preview-modal.is-open { display: flex; }
|
||
.preview-modal__panel {
|
||
position: relative; width: min(920px, 100%); max-height: calc(100vh - 48px);
|
||
background: #0f172a; border-radius: 16px; overflow: hidden;
|
||
box-shadow: 0 20px 60px rgba(0,0,0,0.35);
|
||
display: flex; flex-direction: column;
|
||
}
|
||
.preview-modal__head {
|
||
display: flex; align-items: center; justify-content: space-between; gap: 12px;
|
||
padding: 14px 16px; background: #fff; border-bottom: 1px solid var(--line);
|
||
}
|
||
.preview-modal__title {
|
||
font-size: 15px; font-weight: 700; color: #0b1a4a;
|
||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||
}
|
||
.preview-modal__close {
|
||
flex-shrink: 0; width: 32px; height: 32px; border-radius: 8px;
|
||
border: 1px solid #e2e8f0; background: #f8fafc; color: #475569;
|
||
cursor: pointer; font-size: 18px; line-height: 1; font-family: inherit;
|
||
}
|
||
.preview-modal__close:hover { border-color: #7dd3fc; color: #0003FF; }
|
||
.preview-modal__body {
|
||
flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center;
|
||
padding: 12px; background: #0b1220;
|
||
}
|
||
.preview-modal__body img,
|
||
.preview-modal__body video {
|
||
max-width: 100%; max-height: calc(100vh - 160px);
|
||
border-radius: 8px; display: block;
|
||
}
|
||
.preview-modal__body img { object-fit: contain; background: transparent; }
|
||
.preview-modal__body video { width: 100%; background: #000; }
|
||
.preview-modal__empty {
|
||
color: #94a3b8; font-size: 14px; font-weight: 600; padding: 40px 16px; text-align: center;
|
||
}
|
||
|
||
/* 音频 / 语音 / 音乐播放器(类音乐 App) */
|
||
.audio-player {
|
||
width: min(380px, 100%);
|
||
border-radius: 24px;
|
||
overflow: hidden;
|
||
background: #fff;
|
||
box-shadow: 0 24px 56px rgba(15, 23, 42, 0.32);
|
||
padding: 28px 24px 24px;
|
||
box-sizing: border-box;
|
||
color: #0b1a4a;
|
||
}
|
||
.audio-player__art {
|
||
position: relative;
|
||
width: 168px; height: 168px;
|
||
margin: 0 auto 22px;
|
||
border-radius: 50%;
|
||
background:
|
||
radial-gradient(circle at 35% 30%, rgba(255,255,255,0.55), transparent 42%),
|
||
linear-gradient(145deg, #00CCFF, #0003FF 55%, #4f46e5);
|
||
box-shadow:
|
||
0 0 0 10px rgba(0, 3, 255, 0.08),
|
||
0 18px 36px rgba(0, 3, 255, 0.35);
|
||
display: flex; align-items: center; justify-content: center;
|
||
transition: transform 0.35s ease;
|
||
}
|
||
.audio-player.is-playing .audio-player__art {
|
||
animation: audioDiscSpin 8s linear infinite;
|
||
}
|
||
@keyframes audioDiscSpin {
|
||
from { transform: rotate(0deg); }
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
.audio-player__art-hole {
|
||
width: 28px; height: 28px; border-radius: 50%;
|
||
background: #fff;
|
||
box-shadow: inset 0 0 0 6px rgba(0, 3, 255, 0.18);
|
||
}
|
||
.audio-player__art-ring {
|
||
position: absolute; inset: 18px; border-radius: 50%;
|
||
border: 1px solid rgba(255,255,255,0.28);
|
||
pointer-events: none;
|
||
}
|
||
.audio-player__meta { text-align: center; margin-bottom: 18px; }
|
||
.audio-player__badge {
|
||
display: inline-flex; align-items: center;
|
||
padding: 3px 10px; border-radius: 999px; margin-bottom: 10px;
|
||
background: rgba(0,3,255,0.1); color: #0003FF;
|
||
font-size: 11px; font-weight: 700;
|
||
}
|
||
.audio-player__title {
|
||
font-size: 18px; font-weight: 800; color: #0b1a4a; line-height: 1.35;
|
||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||
}
|
||
.audio-player__sub {
|
||
margin-top: 6px; color: #64748b; font-size: 13px; font-weight: 500;
|
||
}
|
||
.audio-player__wave {
|
||
display: flex; align-items: flex-end; justify-content: space-between;
|
||
height: 44px; width: 100%;
|
||
margin: 0 0 14px; padding: 0 2px; box-sizing: border-box;
|
||
}
|
||
.audio-player__wave span {
|
||
flex: 0 0 auto; width: 3px; min-width: 3px; max-width: 3px;
|
||
min-height: 4px; height: 4px; border-radius: 999px;
|
||
background: #7dd3fc;
|
||
transform-origin: bottom center;
|
||
}
|
||
.audio-player.is-playing .audio-player__wave span {
|
||
background: linear-gradient(90deg, rgba(0, 204, 255, 1) 0%, rgba(0, 3, 255, 1) 100%);
|
||
}
|
||
.audio-player__progress-wrap { margin-bottom: 8px; }
|
||
.audio-player__progress {
|
||
-webkit-appearance: none; appearance: none;
|
||
width: 100%; height: 6px; border-radius: 999px;
|
||
background: #e0f7ff; outline: none; cursor: pointer;
|
||
}
|
||
.audio-player__progress::-webkit-slider-thumb {
|
||
-webkit-appearance: none; appearance: none;
|
||
width: 14px; height: 14px; border-radius: 50%;
|
||
background: #0003FF; border: 2px solid #fff;
|
||
box-shadow: 0 2px 8px rgba(0, 3, 255, 0.45);
|
||
cursor: pointer;
|
||
}
|
||
.audio-player__progress::-moz-range-thumb {
|
||
width: 14px; height: 14px; border-radius: 50%;
|
||
background: #0003FF; border: 2px solid #fff;
|
||
box-shadow: 0 2px 8px rgba(0, 3, 255, 0.45);
|
||
cursor: pointer;
|
||
}
|
||
.audio-player__time {
|
||
display: flex; justify-content: space-between;
|
||
font-size: 11px; color: #94a3b8; font-weight: 600;
|
||
margin-bottom: 16px;
|
||
}
|
||
.audio-player__controls {
|
||
display: flex; align-items: center; justify-content: center; gap: 18px;
|
||
}
|
||
.audio-player__btn {
|
||
width: 44px; height: 44px; border-radius: 50%;
|
||
border: 1px solid #e2e8f0; background: #f8fafc;
|
||
color: #475569; cursor: pointer; display: inline-flex;
|
||
align-items: center; justify-content: center;
|
||
font-family: inherit; transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.18s ease, border-color 0.18s ease, color 0.18s ease;
|
||
}
|
||
.audio-player__btn:hover { border-color: #7dd3fc; color: #0003FF; background: #f0fbff; }
|
||
.audio-player__btn--play {
|
||
width: 64px; height: 64px;
|
||
border: none;
|
||
background: linear-gradient(90deg, rgba(0, 204, 255, 1) 0%, rgba(0, 3, 255, 1) 100%);
|
||
color: #fff;
|
||
box-shadow: 0 10px 24px rgba(0, 3, 255, 0.4);
|
||
}
|
||
/* 覆盖通用 .audio-player__btn:hover,避免白底白图标 */
|
||
.audio-player__btn.audio-player__btn--play:hover {
|
||
border: none;
|
||
background: linear-gradient(135deg, #b56af8, #7476f4);
|
||
color: #fff;
|
||
filter: none;
|
||
transform: translateY(-2px) scale(1.05);
|
||
box-shadow:
|
||
0 14px 30px rgba(0, 3, 255, 0.5),
|
||
0 0 0 6px rgba(0, 204, 255, 0.16);
|
||
}
|
||
.audio-player__btn.audio-player__btn--play:active {
|
||
transform: translateY(0) scale(0.98);
|
||
box-shadow: 0 8px 18px rgba(0, 3, 255, 0.38);
|
||
}
|
||
.audio-player__btn svg { width: 22px; height: 22px; display: block; }
|
||
.audio-player__btn--play svg { width: 28px; height: 28px; margin-left: 2px; fill: currentColor; }
|
||
.audio-player__btn--play.is-playing svg { margin-left: 0; }
|
||
.audio-player audio { display: none; }
|
||
|
||
/* 绘本预览阅读器 */
|
||
.book-reader {
|
||
width: 100%; max-width: 720px; max-height: calc(100vh - 140px);
|
||
display: flex; flex-direction: column; gap: 12px;
|
||
background: #fff; border-radius: 14px; overflow: hidden;
|
||
}
|
||
.book-reader__stage {
|
||
position: relative; flex: 1; min-height: 0;
|
||
display: flex; align-items: center; justify-content: center;
|
||
background: linear-gradient(180deg, #f8fafc 0%, #eef2ff 100%);
|
||
padding: 16px 56px;
|
||
}
|
||
.book-reader__img {
|
||
max-width: 100%; max-height: min(52vh, 420px);
|
||
border-radius: 10px; object-fit: contain; display: block;
|
||
box-shadow: 0 8px 28px rgba(15, 23, 42, 0.12);
|
||
}
|
||
.book-reader__placeholder {
|
||
color: #94a3b8; font-size: 14px; font-weight: 600; padding: 48px 16px; text-align: center;
|
||
}
|
||
.book-reader__nav {
|
||
position: absolute; top: 50%; transform: translateY(-50%);
|
||
width: 40px; height: 40px; border-radius: 50%;
|
||
border: 1px solid #e2e8f0; background: rgba(255,255,255,0.95);
|
||
color: #475569; cursor: pointer; font-size: 22px; line-height: 1;
|
||
display: flex; align-items: center; justify-content: center;
|
||
box-shadow: 0 4px 14px rgba(15, 23, 42, 0.08);
|
||
font-family: inherit;
|
||
}
|
||
.book-reader__nav:hover { border-color: #7dd3fc; color: #0003FF; }
|
||
.book-reader__nav:disabled { opacity: 0.35; cursor: default; }
|
||
.book-reader__nav--prev { left: 10px; }
|
||
.book-reader__nav--next { right: 10px; }
|
||
.book-reader__footer {
|
||
padding: 0 18px 16px; display: flex; flex-direction: column; gap: 8px;
|
||
}
|
||
.book-reader__page-no {
|
||
font-size: 12px; font-weight: 700; color: #0003FF;
|
||
}
|
||
.book-reader__text {
|
||
font-size: 14px; line-height: 1.7; color: #0b1a4a;
|
||
max-height: 7.2em; overflow: auto;
|
||
}
|
||
.book-reader__dots {
|
||
display: flex; justify-content: center; gap: 6px; flex-wrap: wrap; padding-bottom: 2px;
|
||
}
|
||
.book-reader__dot {
|
||
width: 8px; height: 8px; border-radius: 50%; border: none; padding: 0;
|
||
background: #d8b4fe; cursor: pointer; opacity: 0.45;
|
||
}
|
||
.book-reader__dot.is-active { opacity: 1; background: #0003FF; transform: scale(1.15); }
|
||
.work-card__body {
|
||
display: flex; flex-direction: column; flex: 1; min-height: 0;
|
||
padding: 12px 14px 12px;
|
||
}
|
||
.work-card__type {
|
||
display: inline-flex; align-self: flex-start; padding: 2px 8px; border-radius: 6px; margin-bottom: 8px;
|
||
font-size: 11px; font-weight: 600; background: rgba(0,3,255,0.08); color: #0003FF;
|
||
}
|
||
.work-card__name {
|
||
font-size: 14px; font-weight: 700; color: #0b1a4a; margin-bottom: 4px;
|
||
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
|
||
overflow: hidden; line-height: 1.4; min-height: 2.8em;
|
||
}
|
||
.work-card__meta { font-size: 12px; color: #94a3b8; }
|
||
.work-card__author {
|
||
font-size: 12px; color: #64748b; margin-top: 4px;
|
||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||
}
|
||
.work-card__meta-row { margin-top: 2px; }
|
||
|
||
.empty-state {
|
||
text-align: center; padding: 64px 20px; color: #94a3b8;
|
||
background: #fff; border-radius: 16px; border: 1px dashed #e2e8f0;
|
||
}
|
||
.empty-state__title { font-size: 16px; font-weight: 700; color: #0b1a4a; margin-bottom: 8px; }
|
||
.empty-state__sub { font-size: 13px; line-height: 1.6; margin-bottom: 18px; }
|
||
.empty-state__btn {
|
||
display: inline-flex; padding: 10px 20px; border-radius: 10px; border: none;
|
||
background: linear-gradient(90deg, rgba(0, 204, 255, 1) 0%, rgba(0, 3, 255, 1) 100%); color: #fff;
|
||
font-size: 13px; font-weight: 700; text-decoration: none; cursor: pointer; font-family: inherit;
|
||
}
|
||
.work-card__footer {
|
||
display: flex; align-items: center; gap: 8px;
|
||
margin-top: auto; padding-top: 12px;
|
||
border-top: 1px solid #f1f5f9;
|
||
font-size: 11px; color: #94a3b8;
|
||
}
|
||
.work-card__publish {
|
||
display: inline-flex; align-items: center; justify-content: center;
|
||
padding: 5px 10px; border-radius: 8px; border: 1px solid rgba(0,3,255,0.25);
|
||
background: #f0fbff; color: #0003FF; cursor: pointer;
|
||
font-family: inherit; font-size: 11px; font-weight: 600;
|
||
transition: all 0.15s; flex-shrink: 0;
|
||
}
|
||
.work-card__publish:hover {
|
||
background: linear-gradient(90deg, rgba(0, 204, 255, 1) 0%, rgba(0, 3, 255, 1) 100%);
|
||
border-color: transparent; color: #fff;
|
||
}
|
||
.work-card__publish:disabled { opacity: 0.55; cursor: wait; }
|
||
.work-card__published {
|
||
display: inline-flex; align-items: center; justify-content: center;
|
||
padding: 5px 10px; border-radius: 8px;
|
||
background: #f1f5f9; color: #94a3b8;
|
||
font-size: 11px; font-weight: 600; flex-shrink: 0;
|
||
}
|
||
.work-card__delete {
|
||
display: inline-flex; align-items: center; justify-content: center;
|
||
margin-left: auto; padding: 5px 10px; border-radius: 8px; border: 1px solid #fecaca;
|
||
background: #fff; color: #dc2626; cursor: pointer;
|
||
font-family: inherit; font-size: 11px; font-weight: 600;
|
||
transition: all 0.15s; flex-shrink: 0;
|
||
}
|
||
.work-card__delete:hover { background: #fef2f2; border-color: #f87171; }
|
||
.work-card__delete:disabled { opacity: 0.55; cursor: wait; }
|
||
.toast {
|
||
position: fixed; top: 18px; left: 50%; transform: translateX(-50%) translateY(-12px);
|
||
z-index: 200; padding: 10px 16px; border-radius: 10px;
|
||
background: rgba(15, 23, 42, 0.92); color: #fff;
|
||
font-size: 13px; font-weight: 600; pointer-events: none;
|
||
opacity: 0; transition: opacity 0.2s, transform 0.2s;
|
||
max-width: calc(100vw - 32px); box-shadow: 0 8px 24px rgba(15,23,42,0.18);
|
||
}
|
||
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
|
||
.toast.is-error { background: rgba(185, 28, 28, 0.94); }
|
||
.pager {
|
||
display: flex; align-items: center; justify-content: center; gap: 12px;
|
||
margin-top: 28px;
|
||
}
|
||
.pager__btn {
|
||
padding: 8px 16px; border-radius: 10px; border: 1px solid #e2e8f0;
|
||
background: #fff; color: #475569; font-size: 13px; font-weight: 600;
|
||
cursor: pointer; font-family: inherit;
|
||
}
|
||
.pager__btn:hover:not(:disabled) { border-color: #7dd3fc; color: #0003FF; }
|
||
.pager__btn:disabled { opacity: 0.45; cursor: not-allowed; }
|
||
.pager__info { font-size: 13px; color: #94a3b8; font-weight: 600; }
|
||
.loading-box {
|
||
grid-column: 1 / -1; text-align: center; padding: 48px 16px; color: #64748b; font-size: 14px;
|
||
}
|
||
.loading-spin {
|
||
width: 36px; height: 36px; margin: 0 auto 12px; border-radius: 50%;
|
||
border: 3px solid #e0f7ff; border-top-color: #0003FF;
|
||
animation: spin 0.8s linear infinite;
|
||
}
|
||
@keyframes spin { to { transform: rotate(360deg); } }
|
||
|
||
@media (max-width: 640px) {
|
||
.page { padding: 20px 16px 40px; }
|
||
.page-hero__title { font-size: 22px; }
|
||
.cd-nav__user-meta { display: none; }
|
||
}
|
||
</style>
|
||
</head>
|
||
|
||
<body>
|
||
<header class="cd-nav">
|
||
<a class="cd-nav__back" href="/ai.html" title="返回" onclick="if (document.referrer) { history.back(); return false; }">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
|
||
<path d="M19 12H5M12 19l-7-7 7-7"/>
|
||
</svg>
|
||
返回
|
||
</a>
|
||
<a class="cd-nav__logo" href="/ai.html" title="灵丌AI">
|
||
<img src="../images/aigc/logo.png" alt="灵丌AI" />
|
||
</a>
|
||
<div class="cd-nav__course">作品广场</div>
|
||
<div class="cd-nav__right">
|
||
<a class="cd-nav__btn is-active" href="/works-plaza.html">作品广场</a>
|
||
<button type="button" class="cd-nav__btn" id="navMineBtn">我的作品</button>
|
||
<a class="cd-nav__login" id="navLoginBtn" href="/login.html">请登录</a>
|
||
<div class="cd-nav__user" id="navUser">
|
||
<div class="cd-nav__avatar" id="navAvatar"><img src="../images/aigc/ai_programming_avatar_01.png" alt="头像" /></div>
|
||
<div class="cd-nav__user-meta">
|
||
<div class="cd-nav__user-name" id="navUserName">加载中</div>
|
||
<a class="cd-nav__user-score" href="/ai-points.html">积分 <strong id="navScore">--</strong></a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="page">
|
||
<div class="page-hero">
|
||
<div>
|
||
<h1 class="page-hero__title" id="pageTitle">作品广场</h1>
|
||
<p class="page-hero__sub" id="pageSub">看看大家用 AI 创作了什么,发现灵感、互相学习</p>
|
||
</div>
|
||
<div class="scope-tabs" role="tablist" aria-label="作品范围">
|
||
<button type="button" class="scope-tab active" id="scopePlaza" data-scope="plaza">广场</button>
|
||
<button type="button" class="scope-tab" id="scopeMine" data-scope="mine">我的作品</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="filter-row" id="filterRow">
|
||
<button type="button" class="filter-chip active" data-type="">全部</button>
|
||
<button type="button" class="filter-chip" data-type="image">图片</button>
|
||
<button type="button" class="filter-chip" data-type="book">绘本</button>
|
||
<button type="button" class="filter-chip" data-type="video">视频</button>
|
||
<button type="button" class="filter-chip" data-type="tts">语音</button>
|
||
<button type="button" class="filter-chip" data-type="music">音乐</button>
|
||
</div>
|
||
|
||
<div class="works-grid" id="worksGrid"></div>
|
||
<div class="pager" id="pager" style="display:none;">
|
||
<button type="button" class="pager__btn" id="pagerPrev">上一页</button>
|
||
<span class="pager__info" id="pagerInfo">1 / 1</span>
|
||
<button type="button" class="pager__btn" id="pagerNext">下一页</button>
|
||
</div>
|
||
</div>
|
||
<div class="toast" id="toast" role="status" aria-live="polite"></div>
|
||
<div class="preview-modal" id="previewModal" aria-hidden="true">
|
||
<div class="preview-modal__panel" role="dialog" aria-modal="true" aria-labelledby="previewModalTitle">
|
||
<div class="preview-modal__head">
|
||
<div class="preview-modal__title" id="previewModalTitle">作品预览</div>
|
||
<button type="button" class="preview-modal__close" id="previewModalClose" aria-label="关闭">×</button>
|
||
</div>
|
||
<div class="preview-modal__body" id="previewModalBody"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
var currentFilter = '';
|
||
var currentPage = 1;
|
||
var pageSize = 20;
|
||
var totalCount = 0;
|
||
var loading = false;
|
||
var worksCache = [];
|
||
var deletingIds = {};
|
||
var publishingIds = {};
|
||
var toastTimer = null;
|
||
var listScope = 'plaza'; // plaza | mine
|
||
|
||
function resolveBaseUrl() {
|
||
return (window.CONFIG && window.CONFIG.DataServerBaseUrl) ||
|
||
"<%= htmlWebpackPlugin.options.dataServerBaseUrl %>";
|
||
}
|
||
function authHeaders() {
|
||
var headers = { 'Content-Type': 'application/json' };
|
||
var token = localStorage.getItem('access_token');
|
||
if (token) headers.Authorization = 'Bearer ' + token;
|
||
return headers;
|
||
}
|
||
function escapeHtml(str) {
|
||
return String(str == null ? '' : str)
|
||
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||
.replace(/"/g, '"').replace(/'/g, ''');
|
||
}
|
||
/** 读取 ai.html 缓存头像,仅展示 */
|
||
function applyNavAvatar(avatarEl) {
|
||
var el = avatarEl || document.getElementById('navAvatar');
|
||
if (!el) return;
|
||
var n = 1;
|
||
try {
|
||
var parsed = parseInt(localStorage.getItem('ai_programming_avatar'), 10);
|
||
if (parsed >= 1 && parsed <= 12) n = parsed;
|
||
} catch (e) {}
|
||
var pad = n < 10 ? ('0' + n) : String(n);
|
||
var src = '../images/aigc/ai_programming_avatar_' + pad + '.png';
|
||
var img = el.querySelector('img');
|
||
if (!img) {
|
||
img = document.createElement('img');
|
||
img.alt = '头像';
|
||
el.textContent = '';
|
||
el.appendChild(img);
|
||
}
|
||
img.src = src;
|
||
}
|
||
function pickApiData(result) {
|
||
if (!result || typeof result !== 'object') return {};
|
||
if (result.data && typeof result.data === 'object' && !Array.isArray(result.data)) return result.data;
|
||
if (result.obj && typeof result.obj === 'object' && !Array.isArray(result.obj)) return result.obj;
|
||
return result;
|
||
}
|
||
function formatTime(iso) {
|
||
if (!iso) return '';
|
||
try {
|
||
var d = new Date(iso);
|
||
if (isNaN(d.getTime())) return '';
|
||
var m = d.getMonth() + 1;
|
||
var day = d.getDate();
|
||
return d.getFullYear() + '/' + m + '/' + day;
|
||
} catch (e) { return ''; }
|
||
}
|
||
function placeholderThumb(type) {
|
||
var map = {
|
||
text: '../images/aigc/icon-ai-wenben.png',
|
||
image: '../images/aigc/icon-ai-huitu.png',
|
||
book: '../images/aigc/icon-ai-huiben.png',
|
||
video: '../images/aigc/icon-ai-shiping.png',
|
||
audio: '../images/aigc/icon-ai-yuying.png',
|
||
tts: '../images/aigc/icon-ai-yuying.png',
|
||
music: '../images/aigc/icon-ai-music.png'
|
||
};
|
||
return map[type] || '../images/aigc/icon-ai-huitu.png';
|
||
}
|
||
|
||
function getWorkContent(item) {
|
||
if (!item) return '';
|
||
var content = item.content;
|
||
if (content == null) return '';
|
||
if (typeof content === 'string') return content.trim();
|
||
return '';
|
||
}
|
||
|
||
function parseBookPages(content) {
|
||
if (content == null || content === '') return [];
|
||
var pages = content;
|
||
if (typeof content === 'string') {
|
||
var trimmed = content.trim();
|
||
if (!trimmed) return [];
|
||
if (trimmed.charAt(0) !== '[' && trimmed.charAt(0) !== '{') return [];
|
||
try {
|
||
pages = JSON.parse(trimmed);
|
||
} catch (e) {
|
||
return [];
|
||
}
|
||
}
|
||
if (pages && !Array.isArray(pages) && Array.isArray(pages.pages)) {
|
||
pages = pages.pages;
|
||
}
|
||
if (!Array.isArray(pages)) return [];
|
||
return pages.map(function(p) {
|
||
if (!p || typeof p !== 'object') return null;
|
||
var image = p.image_url || p.url || p.content || '';
|
||
var text = p.text || p.desc || p.description || '';
|
||
if (!image && !text) return null;
|
||
return { image_url: String(image || ''), text: String(text || '') };
|
||
}).filter(Boolean);
|
||
}
|
||
|
||
function isBookWorkType(type) {
|
||
var t = String(type || '').toLowerCase();
|
||
return t === 'book' || t === 'huiben' || t === 'picture_book' || t === 'picturebook';
|
||
}
|
||
|
||
function isVideoUrl(url) {
|
||
return !!url && /\.(mp4|webm|mov|m4v)(\?|$)/i.test(url);
|
||
}
|
||
|
||
function isImageUrl(url) {
|
||
return !!url && /\.(png|jpe?g|webp|gif|bmp)(\?|$)/i.test(url);
|
||
}
|
||
|
||
function isAudioUrl(url) {
|
||
return !!url && /\.(mp3|wav|ogg|m4a|aac)(\?|$)/i.test(url);
|
||
}
|
||
|
||
function resolveMediaKind(item) {
|
||
var type = String((item && item.work_type) || '').toLowerCase();
|
||
var bookPages = parseBookPages(item && item.content);
|
||
// 绘本优先:work_type 为 book,或 content 是 [{image_url,text}, ...]
|
||
if (isBookWorkType(type) || bookPages.length) return 'book';
|
||
var content = getWorkContent(item);
|
||
if (type === 'video' || isVideoUrl(content)) return 'video';
|
||
if (type === 'image' || isImageUrl(content)) return 'image';
|
||
if (type === 'audio' || type === 'tts' || type === 'music' || isAudioUrl(content)) return 'audio';
|
||
if (type === 'text') return 'text';
|
||
// 无明确类型时,按 content URL 推断;OSS 结果常无扩展名,偏视频
|
||
if (content && /^https?:\/\//i.test(content) && /dashscope|aliyuncs|\.oss-/i.test(content)) {
|
||
return 'video';
|
||
}
|
||
return type || 'unknown';
|
||
}
|
||
|
||
function resolveBookCover(item) {
|
||
var pages = parseBookPages(item && item.content);
|
||
if (pages.length) return pages[0].image_url || '';
|
||
return '';
|
||
}
|
||
|
||
function resolveThumb(item) {
|
||
// 封面统一优先使用 thumbnail_url(视频除外,见 renderCardMedia)
|
||
if (item && item.thumbnail_url) return item.thumbnail_url;
|
||
var kind = resolveMediaKind(item);
|
||
var content = getWorkContent(item);
|
||
if (kind === 'image' && content) return content;
|
||
if (kind === 'book') {
|
||
var cover = resolveBookCover(item);
|
||
if (cover) return cover;
|
||
}
|
||
return placeholderThumb(item && item.work_type);
|
||
}
|
||
|
||
function resolveVideoSrc(item) {
|
||
var content = getWorkContent(item);
|
||
if (content && /^https?:\/\//i.test(content)) return content;
|
||
if (content && isVideoUrl(content)) return content;
|
||
return '';
|
||
}
|
||
|
||
function renderCardMedia(item) {
|
||
var kind = resolveMediaKind(item);
|
||
var thumb = resolveThumb(item);
|
||
var playIcon =
|
||
'<span class="work-card__play" aria-hidden="true">' +
|
||
'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>' +
|
||
'</span>';
|
||
// 视频:一律用视频元素展示第一帧(后端不返回预览图)
|
||
if (kind === 'video') {
|
||
var videoSrc = resolveVideoSrc(item);
|
||
if (videoSrc) {
|
||
return '<div class="work-card__media-wrap">' +
|
||
'<video class="work-card__media" src="' + escapeHtml(videoSrc) +
|
||
'" muted playsinline preload="metadata" data-video-cover="1"></video>' +
|
||
playIcon +
|
||
'</div>';
|
||
}
|
||
return '<div class="work-card__media-wrap">' +
|
||
'<img class="work-card__media" src="' + escapeHtml(placeholderThumb('video')) + '" alt="" />' +
|
||
playIcon +
|
||
'</div>';
|
||
}
|
||
return '<div class="work-card__media-wrap">' +
|
||
'<img class="work-card__media" src="' + escapeHtml(thumb) + '" alt="" onerror="this.src=\'' + placeholderThumb(item.work_type) + '\'" />' +
|
||
(kind === 'audio' ? playIcon : '') +
|
||
'</div>';
|
||
}
|
||
|
||
/** 强制解码视频第一帧作为封面(部分浏览器 currentTime=0 会黑屏) */
|
||
function hydrateVideoCovers(root) {
|
||
var scope = root || document;
|
||
var videos = scope.querySelectorAll('video.work-card__media[data-video-cover="1"]');
|
||
Array.prototype.forEach.call(videos, function(video) {
|
||
if (video.getAttribute('data-cover-bound') === '1') return;
|
||
video.setAttribute('data-cover-bound', '1');
|
||
|
||
var fallbackToPlaceholder = function() {
|
||
var wrap = video.parentElement;
|
||
if (!wrap) return;
|
||
var img = document.createElement('img');
|
||
img.className = 'work-card__media';
|
||
img.alt = '';
|
||
img.src = placeholderThumb('video');
|
||
try { wrap.replaceChild(img, video); } catch (e) {}
|
||
};
|
||
|
||
var seekFirstFrame = function() {
|
||
try {
|
||
if (!isFinite(video.duration) || video.duration <= 0) {
|
||
video.currentTime = 0.1;
|
||
return;
|
||
}
|
||
video.currentTime = Math.min(0.1, Math.max(0.01, video.duration * 0.01));
|
||
} catch (e) {}
|
||
};
|
||
|
||
video.addEventListener('loadeddata', seekFirstFrame);
|
||
video.addEventListener('loadedmetadata', seekFirstFrame);
|
||
video.addEventListener('seeked', function() {
|
||
try { video.pause(); } catch (e) {}
|
||
});
|
||
video.addEventListener('error', fallbackToPlaceholder);
|
||
|
||
if (video.readyState >= 1) seekFirstFrame();
|
||
});
|
||
}
|
||
|
||
function closePreviewModal() {
|
||
stopPlazaWaveVisual();
|
||
var modal = document.getElementById('previewModal');
|
||
var body = document.getElementById('previewModalBody');
|
||
if (!modal) return;
|
||
var video = body && body.querySelector('video');
|
||
var audio = body && body.querySelector('audio');
|
||
if (video) {
|
||
try { video.pause(); } catch (e) {}
|
||
}
|
||
if (audio) {
|
||
try { audio.pause(); } catch (e) {}
|
||
}
|
||
bookPreviewPages = [];
|
||
bookPreviewIndex = 0;
|
||
if (body) body.innerHTML = '';
|
||
modal.classList.remove('is-open');
|
||
modal.setAttribute('aria-hidden', 'true');
|
||
}
|
||
|
||
var bookPreviewPages = [];
|
||
var bookPreviewIndex = 0;
|
||
var PLAZA_WAVE_BAR_COUNT = 20;
|
||
var plazaWave = {
|
||
bars: [],
|
||
raf: 0,
|
||
audioCtx: null,
|
||
analyser: null,
|
||
source: null,
|
||
dataArray: null,
|
||
boundAudio: null,
|
||
useAnalyser: false,
|
||
silentFrames: 0
|
||
};
|
||
|
||
function getAudioTypeLabel(item) {
|
||
var t = String((item && item.work_type) || '').toLowerCase();
|
||
if (t === 'music') return '音乐';
|
||
if (t === 'tts' || t === 'voice') return '语音';
|
||
if (t === 'audio') return '音频';
|
||
return (item && item.work_type_display) || '音频';
|
||
}
|
||
|
||
function ensurePlazaWaveBars(el) {
|
||
if (!el) return [];
|
||
if (el.children.length !== PLAZA_WAVE_BAR_COUNT) {
|
||
var html = '';
|
||
for (var i = 0; i < PLAZA_WAVE_BAR_COUNT; i++) {
|
||
html += '<span style="flex:0 0 auto;width:3px;min-width:3px;max-width:3px;"></span>';
|
||
}
|
||
el.innerHTML = html;
|
||
}
|
||
plazaWave.bars = Array.prototype.slice.call(el.children);
|
||
return plazaWave.bars;
|
||
}
|
||
|
||
function setPlazaWaveIdle() {
|
||
var bars = plazaWave.bars || [];
|
||
for (var i = 0; i < bars.length; i++) bars[i].style.height = '4px';
|
||
}
|
||
|
||
function stopPlazaWaveVisual() {
|
||
if (plazaWave.raf) {
|
||
cancelAnimationFrame(plazaWave.raf);
|
||
plazaWave.raf = 0;
|
||
}
|
||
plazaWave.silentFrames = 0;
|
||
var player = document.getElementById('plazaAudioPlayer');
|
||
if (player) player.classList.remove('is-playing');
|
||
setPlazaWaveIdle();
|
||
}
|
||
|
||
function paintPlazaAnalyserWave() {
|
||
var bars = plazaWave.bars;
|
||
var data = plazaWave.dataArray;
|
||
if (!bars.length || !data || !plazaWave.analyser) return 0;
|
||
plazaWave.analyser.getByteFrequencyData(data);
|
||
var step = Math.max(1, Math.floor(data.length / bars.length));
|
||
var sum = 0;
|
||
for (var i = 0; i < bars.length; i++) {
|
||
var v = data[i * step] || 0;
|
||
sum += v;
|
||
var boost = 0.55 + 0.45 * Math.sin((i / Math.max(1, bars.length - 1)) * Math.PI);
|
||
bars[i].style.height = Math.max(8, Math.min(100, (v / 255) * 100 * boost + 8)) + '%';
|
||
}
|
||
return sum / Math.max(1, bars.length);
|
||
}
|
||
|
||
function paintPlazaFallbackWave(now) {
|
||
var bars = plazaWave.bars;
|
||
if (!bars.length) return;
|
||
var t = (now || performance.now()) / 1000;
|
||
for (var i = 0; i < bars.length; i++) {
|
||
var n = i / Math.max(1, bars.length - 1);
|
||
var envelope = Math.sin(n * Math.PI);
|
||
var wobble =
|
||
Math.sin(t * 7.2 + i * 0.55) * 0.35 +
|
||
Math.sin(t * 11.5 + i * 0.9) * 0.25 +
|
||
Math.sin(t * 3.4 + i * 0.2) * 0.15;
|
||
bars[i].style.height = Math.max(8, Math.min(100, 12 + envelope * 58 + (wobble + 0.5) * 28 * envelope)) + '%';
|
||
}
|
||
}
|
||
|
||
function plazaWaveTick(now) {
|
||
var audioEl = document.getElementById('plazaPreviewAudio');
|
||
if (!audioEl || audioEl.paused || audioEl.ended) {
|
||
stopPlazaWaveVisual();
|
||
return;
|
||
}
|
||
if (plazaWave.useAnalyser) {
|
||
var avg = paintPlazaAnalyserWave();
|
||
if (avg < 1.5) {
|
||
plazaWave.silentFrames += 1;
|
||
if (plazaWave.silentFrames > 12) {
|
||
plazaWave.useAnalyser = false;
|
||
paintPlazaFallbackWave(now);
|
||
}
|
||
} else {
|
||
plazaWave.silentFrames = 0;
|
||
}
|
||
} else {
|
||
paintPlazaFallbackWave(now);
|
||
}
|
||
plazaWave.raf = requestAnimationFrame(plazaWaveTick);
|
||
}
|
||
|
||
function ensurePlazaAudioGraph(audioEl) {
|
||
if (!audioEl) return false;
|
||
// 每次打开新 audio 节点需重建图(旧 source 不可复用)
|
||
if (plazaWave.source && plazaWave.boundAudio === audioEl) return !!plazaWave.analyser;
|
||
try {
|
||
if (plazaWave.source) {
|
||
try { plazaWave.source.disconnect(); } catch (e) {}
|
||
plazaWave.source = null;
|
||
plazaWave.analyser = null;
|
||
}
|
||
var Ctx = window.AudioContext || window.webkitAudioContext;
|
||
if (!Ctx) return false;
|
||
plazaWave.audioCtx = plazaWave.audioCtx || new Ctx();
|
||
plazaWave.analyser = plazaWave.audioCtx.createAnalyser();
|
||
plazaWave.analyser.fftSize = 128;
|
||
plazaWave.analyser.smoothingTimeConstant = 0.78;
|
||
plazaWave.source = plazaWave.audioCtx.createMediaElementSource(audioEl);
|
||
plazaWave.source.connect(plazaWave.analyser);
|
||
plazaWave.analyser.connect(plazaWave.audioCtx.destination);
|
||
plazaWave.dataArray = new Uint8Array(plazaWave.analyser.frequencyBinCount);
|
||
plazaWave.boundAudio = audioEl;
|
||
return true;
|
||
} catch (err) {
|
||
console.warn('[works-plaza] analyser init failed', err);
|
||
plazaWave.analyser = null;
|
||
plazaWave.source = null;
|
||
plazaWave.boundAudio = null;
|
||
return false;
|
||
}
|
||
}
|
||
|
||
function startPlazaWaveVisual() {
|
||
var audioEl = document.getElementById('plazaPreviewAudio');
|
||
var player = document.getElementById('plazaAudioPlayer');
|
||
var waveEl = document.getElementById('plazaAudioWave');
|
||
if (!audioEl || !player || !waveEl) return;
|
||
ensurePlazaWaveBars(waveEl);
|
||
player.classList.add('is-playing');
|
||
plazaWave.silentFrames = 0;
|
||
if (plazaWave.audioCtx && plazaWave.audioCtx.state === 'suspended') {
|
||
plazaWave.audioCtx.resume().catch(function() {});
|
||
}
|
||
plazaWave.useAnalyser = ensurePlazaAudioGraph(audioEl);
|
||
if (plazaWave.raf) cancelAnimationFrame(plazaWave.raf);
|
||
plazaWave.raf = requestAnimationFrame(plazaWaveTick);
|
||
}
|
||
|
||
function bindPlazaAudioPlayerEvents(audioEl) {
|
||
if (!audioEl || audioEl.getAttribute('data-wave-bound') === '1') return;
|
||
audioEl.setAttribute('data-wave-bound', '1');
|
||
audioEl.addEventListener('play', function() {
|
||
startPlazaWaveVisual();
|
||
updatePlazaPlayButton(true);
|
||
});
|
||
audioEl.addEventListener('playing', function() {
|
||
startPlazaWaveVisual();
|
||
updatePlazaPlayButton(true);
|
||
});
|
||
audioEl.addEventListener('pause', function() {
|
||
stopPlazaWaveVisual();
|
||
updatePlazaPlayButton(false);
|
||
});
|
||
audioEl.addEventListener('ended', function() {
|
||
stopPlazaWaveVisual();
|
||
updatePlazaPlayButton(false);
|
||
var progress = document.getElementById('plazaAudioProgress');
|
||
if (progress) progress.value = '0';
|
||
var cur = document.getElementById('plazaAudioCurrent');
|
||
if (cur) cur.textContent = '0:00';
|
||
});
|
||
audioEl.addEventListener('emptied', function() {
|
||
stopPlazaWaveVisual();
|
||
updatePlazaPlayButton(false);
|
||
});
|
||
audioEl.addEventListener('timeupdate', syncPlazaAudioProgress);
|
||
audioEl.addEventListener('loadedmetadata', syncPlazaAudioProgress);
|
||
}
|
||
|
||
function formatAudioTime(sec) {
|
||
if (!isFinite(sec) || sec < 0) return '0:00';
|
||
var s = Math.floor(sec);
|
||
var m = Math.floor(s / 60);
|
||
var r = s % 60;
|
||
return m + ':' + (r < 10 ? '0' + r : r);
|
||
}
|
||
|
||
function syncPlazaAudioProgress() {
|
||
var audioEl = document.getElementById('plazaPreviewAudio');
|
||
var progress = document.getElementById('plazaAudioProgress');
|
||
var cur = document.getElementById('plazaAudioCurrent');
|
||
var dur = document.getElementById('plazaAudioDuration');
|
||
if (!audioEl) return;
|
||
var duration = isFinite(audioEl.duration) ? audioEl.duration : 0;
|
||
var current = isFinite(audioEl.currentTime) ? audioEl.currentTime : 0;
|
||
if (progress) {
|
||
progress.max = duration > 0 ? String(duration) : '0';
|
||
progress.value = String(current);
|
||
var pct = duration > 0 ? (current / duration) * 100 : 0;
|
||
progress.style.background =
|
||
'linear-gradient(90deg, #00CCFF 0%, #0003FF ' + pct + '%, #e0f7ff ' + pct + '%)';
|
||
}
|
||
if (cur) cur.textContent = formatAudioTime(current);
|
||
if (dur) dur.textContent = formatAudioTime(duration);
|
||
}
|
||
|
||
function updatePlazaPlayButton(playing) {
|
||
var btn = document.getElementById('plazaAudioPlayBtn');
|
||
if (!btn) return;
|
||
btn.classList.toggle('is-playing', !!playing);
|
||
btn.setAttribute('aria-label', playing ? '暂停' : '播放');
|
||
btn.innerHTML = playing
|
||
? '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M6 5h4v14H6zm8 0h4v14h-4z"/></svg>'
|
||
: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>';
|
||
}
|
||
|
||
function togglePlazaAudioPlay() {
|
||
var audioEl = document.getElementById('plazaPreviewAudio');
|
||
if (!audioEl) return;
|
||
if (audioEl.paused) {
|
||
var p = audioEl.play();
|
||
if (p && typeof p.catch === 'function') p.catch(function() {});
|
||
} else {
|
||
audioEl.pause();
|
||
}
|
||
}
|
||
|
||
function seekPlazaAudio(deltaSec) {
|
||
var audioEl = document.getElementById('plazaPreviewAudio');
|
||
if (!audioEl) return;
|
||
var next = (audioEl.currentTime || 0) + deltaSec;
|
||
var duration = isFinite(audioEl.duration) ? audioEl.duration : 0;
|
||
if (duration > 0) next = Math.max(0, Math.min(duration, next));
|
||
else next = Math.max(0, next);
|
||
try { audioEl.currentTime = next; } catch (e) {}
|
||
syncPlazaAudioProgress();
|
||
}
|
||
|
||
function onPlazaProgressInput(e) {
|
||
var audioEl = document.getElementById('plazaPreviewAudio');
|
||
var progress = e && e.target;
|
||
if (!audioEl || !progress) return;
|
||
var val = Number(progress.value);
|
||
if (!isFinite(val)) return;
|
||
try { audioEl.currentTime = val; } catch (err) {}
|
||
syncPlazaAudioProgress();
|
||
}
|
||
|
||
function renderAudioPlayer(item, content) {
|
||
var label = getAudioTypeLabel(item);
|
||
var title = (item && item.title) || '未命名作品';
|
||
var author = (item && item.username) || '';
|
||
return '<div class="audio-player" id="plazaAudioPlayer">' +
|
||
'<div class="audio-player__art" aria-hidden="true">' +
|
||
'<div class="audio-player__art-ring"></div>' +
|
||
'<div class="audio-player__art-hole"></div>' +
|
||
'</div>' +
|
||
'<div class="audio-player__meta">' +
|
||
'<div class="audio-player__badge">' + escapeHtml(label) + '</div>' +
|
||
'<div class="audio-player__title" title="' + escapeHtml(title) + '">' + escapeHtml(title) + '</div>' +
|
||
'<div class="audio-player__sub">' + (author ? ('@' + escapeHtml(author)) : '灵丌AI 作品') + '</div>' +
|
||
'</div>' +
|
||
'<div class="audio-player__wave" id="plazaAudioWave"></div>' +
|
||
'<div class="audio-player__progress-wrap">' +
|
||
'<input class="audio-player__progress" id="plazaAudioProgress" type="range" min="0" max="0" value="0" step="0.01" />' +
|
||
'</div>' +
|
||
'<div class="audio-player__time">' +
|
||
'<span id="plazaAudioCurrent">0:00</span>' +
|
||
'<span id="plazaAudioDuration">0:00</span>' +
|
||
'</div>' +
|
||
'<div class="audio-player__controls">' +
|
||
'<button type="button" class="audio-player__btn" id="plazaAudioRewind" aria-label="快退10秒">' +
|
||
'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M11.99 5V1l-5 5 5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6h-2c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"/></svg>' +
|
||
'</button>' +
|
||
'<button type="button" class="audio-player__btn audio-player__btn--play" id="plazaAudioPlayBtn" aria-label="播放">' +
|
||
'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>' +
|
||
'</button>' +
|
||
'<button type="button" class="audio-player__btn" id="plazaAudioForward" aria-label="快进10秒">' +
|
||
'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 5V1l5 5-5 5V7c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6h2c0 4.42-3.58 8-8 8s-8-3.58-8-8 3.58-8 8-8z"/></svg>' +
|
||
'</button>' +
|
||
'</div>' +
|
||
'<audio id="plazaPreviewAudio" src="' + escapeHtml(content) + '" playsinline preload="metadata"></audio>' +
|
||
'</div>';
|
||
}
|
||
|
||
function openAudioPlayer(item, content) {
|
||
var body = document.getElementById('previewModalBody');
|
||
if (!body || !content) return;
|
||
stopPlazaWaveVisual();
|
||
plazaWave.source = null;
|
||
plazaWave.analyser = null;
|
||
plazaWave.boundAudio = null;
|
||
body.innerHTML = renderAudioPlayer(item, content);
|
||
|
||
var waveEl = document.getElementById('plazaAudioWave');
|
||
var audioEl = document.getElementById('plazaPreviewAudio');
|
||
var playBtn = document.getElementById('plazaAudioPlayBtn');
|
||
var rewindBtn = document.getElementById('plazaAudioRewind');
|
||
var forwardBtn = document.getElementById('plazaAudioForward');
|
||
var progress = document.getElementById('plazaAudioProgress');
|
||
|
||
ensurePlazaWaveBars(waveEl);
|
||
setPlazaWaveIdle();
|
||
updatePlazaPlayButton(false);
|
||
bindPlazaAudioPlayerEvents(audioEl);
|
||
|
||
if (playBtn) playBtn.addEventListener('click', togglePlazaAudioPlay);
|
||
if (rewindBtn) rewindBtn.addEventListener('click', function() { seekPlazaAudio(-10); });
|
||
if (forwardBtn) forwardBtn.addEventListener('click', function() { seekPlazaAudio(10); });
|
||
if (progress) {
|
||
progress.addEventListener('input', onPlazaProgressInput);
|
||
progress.addEventListener('change', onPlazaProgressInput);
|
||
}
|
||
|
||
try { audioEl.crossOrigin = 'anonymous'; } catch (e) {}
|
||
audioEl.onerror = function() {
|
||
try {
|
||
audioEl.onerror = null;
|
||
audioEl.removeAttribute('crossorigin');
|
||
audioEl.crossOrigin = null;
|
||
audioEl.src = content;
|
||
} catch (e2) {}
|
||
};
|
||
|
||
// 打开后自动播放
|
||
var playPromise = audioEl.play();
|
||
if (playPromise && typeof playPromise.catch === 'function') {
|
||
playPromise.catch(function() {
|
||
updatePlazaPlayButton(false);
|
||
});
|
||
}
|
||
}
|
||
|
||
function renderBookReaderPage() {
|
||
var body = document.getElementById('previewModalBody');
|
||
if (!body || !bookPreviewPages.length) return;
|
||
var total = bookPreviewPages.length;
|
||
var idx = Math.max(0, Math.min(bookPreviewIndex, total - 1));
|
||
bookPreviewIndex = idx;
|
||
var page = bookPreviewPages[idx] || {};
|
||
var img = page.image_url || '';
|
||
var text = page.text || '';
|
||
var imgHtml = img
|
||
? '<img class="book-reader__img" src="' + escapeHtml(img) + '" alt="第' + (idx + 1) + '页" />'
|
||
: '<div class="book-reader__placeholder">本页暂无插图</div>';
|
||
var dots = bookPreviewPages.map(function(_, i) {
|
||
return '<button type="button" class="book-reader__dot' + (i === idx ? ' is-active' : '') +
|
||
'" data-book-page="' + i + '" aria-label="第' + (i + 1) + '页"></button>';
|
||
}).join('');
|
||
|
||
body.innerHTML =
|
||
'<div class="book-reader">' +
|
||
'<div class="book-reader__stage">' +
|
||
'<button type="button" class="book-reader__nav book-reader__nav--prev" data-book-nav="prev"' +
|
||
(idx <= 0 ? ' disabled' : '') + ' aria-label="上一页">‹</button>' +
|
||
imgHtml +
|
||
'<button type="button" class="book-reader__nav book-reader__nav--next" data-book-nav="next"' +
|
||
(idx >= total - 1 ? ' disabled' : '') + ' aria-label="下一页">›</button>' +
|
||
'</div>' +
|
||
'<div class="book-reader__footer">' +
|
||
'<div class="book-reader__page-no">第 ' + (idx + 1) + ' / ' + total + ' 页</div>' +
|
||
'<div class="book-reader__text">' + escapeHtml(text || '(本页暂无文字)') + '</div>' +
|
||
'<div class="book-reader__dots">' + dots + '</div>' +
|
||
'</div>' +
|
||
'</div>';
|
||
}
|
||
|
||
function openBookPreview(item) {
|
||
bookPreviewPages = parseBookPages(item && item.content);
|
||
bookPreviewIndex = 0;
|
||
var body = document.getElementById('previewModalBody');
|
||
if (!bookPreviewPages.length) {
|
||
if (body) body.innerHTML = '<div class="preview-modal__empty">暂无可预览的绘本内容</div>';
|
||
return;
|
||
}
|
||
renderBookReaderPage();
|
||
}
|
||
|
||
function openWorkPreview(item) {
|
||
if (!item) return;
|
||
var modal = document.getElementById('previewModal');
|
||
var body = document.getElementById('previewModalBody');
|
||
var titleEl = document.getElementById('previewModalTitle');
|
||
if (!modal || !body) return;
|
||
|
||
var kind = resolveMediaKind(item);
|
||
var content = getWorkContent(item);
|
||
var type = String((item && item.work_type) || kind || '').toLowerCase();
|
||
if (titleEl) titleEl.textContent = item.title || item.work_type_display || '作品预览';
|
||
|
||
// 无可用媒体(如图片/内容缺失)时,跳转到对应创作页
|
||
var hasBook = kind === 'book' && parseBookPages(item && item.content).length > 0;
|
||
var hasMedia = !!(
|
||
hasBook ||
|
||
(kind === 'video' && content) ||
|
||
(kind === 'image' && content) ||
|
||
(kind === 'audio' && content) ||
|
||
(kind === 'text' && content) ||
|
||
(content && /^https?:\/\//i.test(content))
|
||
);
|
||
if (!hasMedia) {
|
||
location.href = createToolUrlByType(type || currentFilter);
|
||
return;
|
||
}
|
||
|
||
if (kind === 'book') {
|
||
openBookPreview(item);
|
||
} else if (kind === 'video' && content) {
|
||
body.innerHTML = '<video src="' + escapeHtml(content) + '" controls autoplay playsinline preload="metadata"></video>';
|
||
} else if (kind === 'image' && content) {
|
||
body.innerHTML = '<img src="' + escapeHtml(content) + '" alt="' + escapeHtml(item.title || '作品预览') + '" />';
|
||
} else if (kind === 'audio' && content) {
|
||
openAudioPlayer(item, content);
|
||
} else if (kind === 'text' && content) {
|
||
body.innerHTML =
|
||
'<div style="width:100%;max-height:calc(100vh - 160px);overflow:auto;padding:20px;background:#fff;border-radius:8px;color:#0b1a4a;line-height:1.7;white-space:pre-wrap;">' +
|
||
escapeHtml(content) +
|
||
'</div>';
|
||
} else if (content && /^https?:\/\//i.test(content)) {
|
||
// 未知类型但有 URL:按图片尝试,失败由浏览器处理
|
||
if (isVideoUrl(content)) {
|
||
body.innerHTML = '<video src="' + escapeHtml(content) + '" controls autoplay playsinline></video>';
|
||
} else {
|
||
body.innerHTML = '<img src="' + escapeHtml(content) + '" alt="作品预览" />';
|
||
}
|
||
} else {
|
||
location.href = createToolUrlByType(type || currentFilter);
|
||
return;
|
||
}
|
||
|
||
modal.classList.add('is-open');
|
||
modal.setAttribute('aria-hidden', 'false');
|
||
}
|
||
function totalPages() {
|
||
return Math.max(1, Math.ceil((Number(totalCount) || 0) / pageSize));
|
||
}
|
||
|
||
function isLoggedIn() {
|
||
return !!localStorage.getItem('access_token');
|
||
}
|
||
|
||
function goLogin() {
|
||
try { localStorage.setItem('returnUrl', location.pathname + location.search); } catch (e) {}
|
||
location.href = '/login.html';
|
||
}
|
||
|
||
function showToast(msg, type) {
|
||
var el = document.getElementById('toast');
|
||
if (!el) return;
|
||
el.textContent = msg || '';
|
||
el.classList.toggle('is-error', type === 'error');
|
||
el.classList.add('show');
|
||
if (toastTimer) clearTimeout(toastTimer);
|
||
toastTimer = setTimeout(function() { el.classList.remove('show'); }, 2000);
|
||
}
|
||
|
||
function updateScopeUi() {
|
||
var isMine = listScope === 'mine';
|
||
var titleEl = document.getElementById('pageTitle');
|
||
var subEl = document.getElementById('pageSub');
|
||
var plazaTab = document.getElementById('scopePlaza');
|
||
var mineTab = document.getElementById('scopeMine');
|
||
var navPlaza = document.querySelector('.cd-nav__btn[href="/works-plaza.html"]');
|
||
var navMine = document.getElementById('navMineBtn');
|
||
if (titleEl) titleEl.textContent = isMine ? '我的作品' : '作品广场';
|
||
if (subEl) {
|
||
subEl.textContent = isMine
|
||
? '查看你已发布的作品,支持按类型筛选'
|
||
: '看看大家用 AI 创作了什么,发现灵感、互相学习';
|
||
}
|
||
if (plazaTab) plazaTab.classList.toggle('active', !isMine);
|
||
if (mineTab) mineTab.classList.toggle('active', isMine);
|
||
if (navPlaza) navPlaza.classList.toggle('is-active', !isMine);
|
||
if (navMine) navMine.classList.toggle('is-active', isMine);
|
||
document.title = (isMine ? '我的作品' : '作品广场') + ' - 灵丌AI - 001CODE';
|
||
}
|
||
|
||
function setListScope(scope, opts) {
|
||
var next = scope === 'mine' ? 'mine' : 'plaza';
|
||
if (next === 'mine' && !isLoggedIn()) {
|
||
showToast('请先登录后查看我的作品', 'error');
|
||
setTimeout(goLogin, 600);
|
||
return;
|
||
}
|
||
if (listScope === next && !(opts && opts.forceReload)) return;
|
||
listScope = next;
|
||
currentPage = 1;
|
||
updateScopeUi();
|
||
loadWorks();
|
||
}
|
||
|
||
function isPublicWork(w) {
|
||
if (!w) return false;
|
||
var v = w.is_public;
|
||
return v === true || v === 1 || v === '1' || String(v).toLowerCase() === 'true';
|
||
}
|
||
|
||
async function publishWork(workId, btn) {
|
||
var id = Number(workId);
|
||
if (!id || publishingIds[id]) return;
|
||
if (!isLoggedIn()) {
|
||
showToast('请先登录后再发布', 'error');
|
||
setTimeout(goLogin, 600);
|
||
return;
|
||
}
|
||
if (!window.confirm('确定将作品发布到广场吗?发布后所有人可见。')) return;
|
||
|
||
publishingIds[id] = true;
|
||
if (btn) {
|
||
btn.disabled = true;
|
||
btn.textContent = '发布中…';
|
||
}
|
||
|
||
try {
|
||
var res = await fetch(resolveBaseUrl() + '/api/ai-explorer/works/publish/', {
|
||
method: 'POST',
|
||
headers: authHeaders(),
|
||
body: JSON.stringify({ id: id })
|
||
});
|
||
var result = await res.json().catch(function() { return null; });
|
||
if (res.status === 401 || res.status === 403) {
|
||
showToast('登录已失效,请重新登录', 'error');
|
||
setTimeout(goLogin, 600);
|
||
return;
|
||
}
|
||
if (!res.ok || (result && result.success === false)) {
|
||
throw new Error((result && (result.message || result.detail)) || ('HTTP ' + res.status));
|
||
}
|
||
|
||
for (var i = 0; i < worksCache.length; i++) {
|
||
if (Number(worksCache[i].id) === id) {
|
||
worksCache[i].is_public = true;
|
||
worksCache[i].status = 'published';
|
||
worksCache[i].status_display = '已发布';
|
||
break;
|
||
}
|
||
}
|
||
showToast('发布成功,在通过审核后即可广场展示', 'success');
|
||
renderWorks();
|
||
} catch (err) {
|
||
console.error('[ai-works] publish failed', err);
|
||
showToast((err && err.message) || '发布失败,请稍后重试', 'error');
|
||
if (btn) {
|
||
btn.disabled = false;
|
||
btn.textContent = '发布';
|
||
}
|
||
} finally {
|
||
delete publishingIds[id];
|
||
}
|
||
}
|
||
|
||
async function deleteWork(workId, btn) {
|
||
var id = Number(workId);
|
||
if (!id || deletingIds[id]) return;
|
||
if (!isLoggedIn()) {
|
||
showToast('请先登录后再删除', 'error');
|
||
setTimeout(goLogin, 600);
|
||
return;
|
||
}
|
||
if (!window.confirm('确定删除这个作品吗?删除后不可恢复。')) return;
|
||
|
||
deletingIds[id] = true;
|
||
if (btn) btn.disabled = true;
|
||
|
||
try {
|
||
var res = await fetch(resolveBaseUrl() + '/api/ai-explorer/works/delete/', {
|
||
method: 'POST',
|
||
headers: authHeaders(),
|
||
body: JSON.stringify({ work_id: id })
|
||
});
|
||
var result = await res.json().catch(function() { return null; });
|
||
if (res.status === 401 || res.status === 403) {
|
||
showToast('登录已失效,请重新登录', 'error');
|
||
setTimeout(goLogin, 600);
|
||
return;
|
||
}
|
||
if (!res.ok || (result && result.success === false)) {
|
||
throw new Error((result && (result.message || result.detail)) || ('HTTP ' + res.status));
|
||
}
|
||
|
||
worksCache = worksCache.filter(function(item) { return Number(item.id) !== id; });
|
||
totalCount = Math.max(0, (Number(totalCount) || 0) - 1);
|
||
showToast((result && result.message) || '删除成功');
|
||
|
||
if (!worksCache.length && currentPage > 1 && totalCount > 0) {
|
||
currentPage -= 1;
|
||
loadWorks();
|
||
return;
|
||
}
|
||
if (!worksCache.length && totalCount > 0) {
|
||
loadWorks();
|
||
return;
|
||
}
|
||
renderWorks();
|
||
} catch (err) {
|
||
console.error('[ai-works] delete failed', err);
|
||
showToast((err && err.message) || '删除失败,请稍后重试', 'error');
|
||
if (btn) btn.disabled = false;
|
||
} finally {
|
||
delete deletingIds[id];
|
||
}
|
||
}
|
||
|
||
function renderLoading() {
|
||
document.getElementById('worksGrid').innerHTML =
|
||
'<div class="loading-box"><div class="loading-spin"></div>作品加载中...</div>';
|
||
document.getElementById('pager').style.display = 'none';
|
||
}
|
||
|
||
function renderError(msg) {
|
||
document.getElementById('worksGrid').innerHTML =
|
||
'<div class="empty-state" style="grid-column:1/-1;">' +
|
||
'<div class="empty-state__title">加载失败</div>' +
|
||
'<div class="empty-state__sub">' + escapeHtml(msg || '请稍后重试') + '</div>' +
|
||
'<button type="button" class="empty-state__btn" onclick="loadWorks()">重新加载</button>' +
|
||
'</div>';
|
||
document.getElementById('pager').style.display = 'none';
|
||
}
|
||
|
||
function renderPager() {
|
||
var pager = document.getElementById('pager');
|
||
var pages = totalPages();
|
||
if (!worksCache.length && totalCount === 0) {
|
||
pager.style.display = 'none';
|
||
return;
|
||
}
|
||
pager.style.display = 'flex';
|
||
document.getElementById('pagerInfo').textContent = currentPage + ' / ' + pages + ' · 共 ' + totalCount + ' 件';
|
||
document.getElementById('pagerPrev').disabled = currentPage <= 1 || loading;
|
||
document.getElementById('pagerNext').disabled = currentPage >= pages || loading;
|
||
}
|
||
|
||
function createToolUrlByType(type) {
|
||
var t = String(type || '').toLowerCase();
|
||
if (t === 'image' || t === 'img' || t === 'draw') return '/ai-draw.html';
|
||
if (t === 'book' || t === 'huiben' || t === 'picture_book' || t === 'picturebook') return '/ai-huiben.html';
|
||
if (t === 'video') return '/ai-video.html';
|
||
if (t === 'tts' || t === 'voice' || t === 'audio') return '/ai-voice.html';
|
||
if (t === 'music') return '/ai-music.html';
|
||
if (t === 'text') return '/ai-text.html';
|
||
return '/ai.html';
|
||
}
|
||
|
||
function createToolLabelByType(type) {
|
||
var t = String(type || '').toLowerCase();
|
||
if (t === 'image' || t === 'img' || t === 'draw') return '去 AI 绘图';
|
||
if (t === 'book' || t === 'huiben' || t === 'picture_book' || t === 'picturebook') return '去 AI 绘本';
|
||
if (t === 'video') return '去 AI 视频';
|
||
if (t === 'tts' || t === 'voice' || t === 'audio') return '去 AI 语音';
|
||
if (t === 'music') return '去 AI 音乐';
|
||
if (t === 'text') return '去 AI 文本';
|
||
return '去创作';
|
||
}
|
||
|
||
function renderWorks() {
|
||
var grid = document.getElementById('worksGrid');
|
||
if (!worksCache.length) {
|
||
var createHref = createToolUrlByType(currentFilter);
|
||
var createLabel = createToolLabelByType(currentFilter);
|
||
grid.innerHTML =
|
||
'<div class="empty-state" style="grid-column:1/-1;">' +
|
||
'<div class="empty-state__title">' + (listScope === 'mine' ? '还没有已发布作品' : '暂无公开作品') + '</div>' +
|
||
'<div class="empty-state__sub">' +
|
||
(listScope === 'mine'
|
||
? '去创作工具箱生成作品并发布后,会显示在这里'
|
||
: '换个分类看看,或去创作工具箱生成并发布作品') +
|
||
'</div>' +
|
||
'<a class="empty-state__btn" href="' + createHref + '">' + createLabel + '</a>' +
|
||
'</div>';
|
||
renderPager();
|
||
return;
|
||
}
|
||
grid.innerHTML = worksCache.map(function(w) {
|
||
var typeLabel = w.work_type_display || w.work_type || '作品';
|
||
var title = w.title || '未命名作品';
|
||
var author = w.username || '匿名';
|
||
var time = formatTime(w.created_at);
|
||
var meta = (w.model_name ? w.model_name + ' · ' : '') + time;
|
||
var publishBtn = isPublicWork(w)
|
||
? '<span class="work-card__published">已发布</span>'
|
||
: ('<button type="button" class="work-card__publish" data-work-id="' + escapeHtml(w.id) + '" title="发布到广场">发布</button>');
|
||
var footerHtml = listScope === 'mine'
|
||
? ('<div class="work-card__footer">' +
|
||
publishBtn +
|
||
'<button type="button" class="work-card__delete" data-work-id="' + escapeHtml(w.id) + '" title="删除作品">删除</button>' +
|
||
'</div>')
|
||
: '';
|
||
return '<div class="work-card" data-id="' + escapeHtml(w.id) + '">' +
|
||
renderCardMedia(w) +
|
||
'<div class="work-card__body">' +
|
||
'<span class="work-card__type">' + escapeHtml(typeLabel) + '</span>' +
|
||
'<div class="work-card__name">' + escapeHtml(title) + '</div>' +
|
||
'<div class="work-card__author">@' + escapeHtml(author) + '</div>' +
|
||
'<div class="work-card__meta work-card__meta-row">' + escapeHtml(meta) + '</div>' +
|
||
footerHtml +
|
||
'</div>' +
|
||
'</div>';
|
||
}).join('');
|
||
hydrateVideoCovers(grid);
|
||
renderPager();
|
||
}
|
||
|
||
async function loadWorks() {
|
||
if (loading) return;
|
||
if (listScope === 'mine' && !isLoggedIn()) {
|
||
listScope = 'plaza';
|
||
updateScopeUi();
|
||
}
|
||
loading = true;
|
||
renderLoading();
|
||
try {
|
||
var payload = {
|
||
page: currentPage,
|
||
page_size: pageSize
|
||
};
|
||
if (currentFilter) payload.work_type = currentFilter;
|
||
|
||
var endpoint = listScope === 'mine'
|
||
? '/api/ai-explorer/works/mine/'
|
||
: '/api/ai-explorer/works/list/';
|
||
|
||
var res = await fetch(resolveBaseUrl() + endpoint, {
|
||
method: 'POST',
|
||
headers: authHeaders(),
|
||
body: JSON.stringify(payload)
|
||
});
|
||
var result = await res.json().catch(function() { return null; });
|
||
if (res.status === 401 || res.status === 403) {
|
||
showToast('登录已失效,请重新登录', 'error');
|
||
setTimeout(goLogin, 600);
|
||
return;
|
||
}
|
||
if (!res.ok || (result && result.success === false)) {
|
||
throw new Error((result && (result.message || result.detail)) || ('HTTP ' + res.status));
|
||
}
|
||
var data = pickApiData(result);
|
||
totalCount = Number(data.count) || 0;
|
||
worksCache = Array.isArray(data.results) ? data.results : [];
|
||
var pages = totalPages();
|
||
if (currentPage > pages) {
|
||
currentPage = pages;
|
||
loading = false;
|
||
return loadWorks();
|
||
}
|
||
renderWorks();
|
||
} catch (err) {
|
||
console.error('[ai-works] list failed', err);
|
||
renderError((err && err.message) || '作品列表加载失败');
|
||
} finally {
|
||
loading = false;
|
||
renderPager();
|
||
}
|
||
}
|
||
|
||
document.getElementById('worksGrid').addEventListener('click', function(e) {
|
||
var publishBtn = e.target.closest('.work-card__publish');
|
||
if (publishBtn) {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
publishWork(publishBtn.getAttribute('data-work-id'), publishBtn);
|
||
return;
|
||
}
|
||
var deleteBtn = e.target.closest('.work-card__delete');
|
||
if (deleteBtn) {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
deleteWork(deleteBtn.getAttribute('data-work-id'), deleteBtn);
|
||
return;
|
||
}
|
||
var card = e.target.closest('.work-card');
|
||
if (!card) return;
|
||
var id = card.getAttribute('data-id');
|
||
var item = worksCache.find(function(w) { return String(w.id) === String(id); });
|
||
if (item) openWorkPreview(item);
|
||
});
|
||
|
||
(function initPreviewModal() {
|
||
var modal = document.getElementById('previewModal');
|
||
var closeBtn = document.getElementById('previewModalClose');
|
||
var body = document.getElementById('previewModalBody');
|
||
if (closeBtn) closeBtn.addEventListener('click', closePreviewModal);
|
||
if (modal) {
|
||
modal.addEventListener('click', function(e) {
|
||
if (e.target === modal) closePreviewModal();
|
||
});
|
||
}
|
||
if (body) {
|
||
body.addEventListener('click', function(e) {
|
||
var navBtn = e.target.closest('[data-book-nav]');
|
||
if (navBtn && !navBtn.disabled) {
|
||
var dir = navBtn.getAttribute('data-book-nav');
|
||
if (dir === 'prev' && bookPreviewIndex > 0) {
|
||
bookPreviewIndex -= 1;
|
||
renderBookReaderPage();
|
||
} else if (dir === 'next' && bookPreviewIndex < bookPreviewPages.length - 1) {
|
||
bookPreviewIndex += 1;
|
||
renderBookReaderPage();
|
||
}
|
||
return;
|
||
}
|
||
var dot = e.target.closest('[data-book-page]');
|
||
if (dot) {
|
||
var pageIdx = Number(dot.getAttribute('data-book-page'));
|
||
if (!isNaN(pageIdx) && pageIdx !== bookPreviewIndex) {
|
||
bookPreviewIndex = pageIdx;
|
||
renderBookReaderPage();
|
||
}
|
||
}
|
||
});
|
||
}
|
||
document.addEventListener('keydown', function(e) {
|
||
if (e.key === 'Escape') {
|
||
closePreviewModal();
|
||
return;
|
||
}
|
||
if (!modal || !modal.classList.contains('is-open') || !bookPreviewPages.length) return;
|
||
if (e.key === 'ArrowLeft' && bookPreviewIndex > 0) {
|
||
bookPreviewIndex -= 1;
|
||
renderBookReaderPage();
|
||
} else if (e.key === 'ArrowRight' && bookPreviewIndex < bookPreviewPages.length - 1) {
|
||
bookPreviewIndex += 1;
|
||
renderBookReaderPage();
|
||
}
|
||
});
|
||
})();
|
||
|
||
document.getElementById('filterRow').addEventListener('click', function(e) {
|
||
var btn = e.target.closest('.filter-chip');
|
||
if (!btn || loading) return;
|
||
currentFilter = btn.getAttribute('data-type') || '';
|
||
currentPage = 1;
|
||
document.querySelectorAll('.filter-chip').forEach(function(c) {
|
||
c.classList.toggle('active', c === btn);
|
||
});
|
||
loadWorks();
|
||
});
|
||
|
||
document.querySelector('.scope-tabs').addEventListener('click', function(e) {
|
||
var tab = e.target.closest('.scope-tab');
|
||
if (!tab || loading) return;
|
||
setListScope(tab.getAttribute('data-scope') || 'plaza');
|
||
});
|
||
|
||
document.getElementById('navMineBtn').addEventListener('click', function() {
|
||
if (loading) return;
|
||
setListScope('mine');
|
||
});
|
||
|
||
document.querySelector('.cd-nav__btn[href="/works-plaza.html"]').addEventListener('click', function(e) {
|
||
if (listScope === 'plaza') return;
|
||
e.preventDefault();
|
||
setListScope('plaza');
|
||
});
|
||
|
||
document.getElementById('pagerPrev').addEventListener('click', function() {
|
||
if (currentPage <= 1 || loading) return;
|
||
currentPage -= 1;
|
||
loadWorks();
|
||
});
|
||
document.getElementById('pagerNext').addEventListener('click', function() {
|
||
if (currentPage >= totalPages() || loading) return;
|
||
currentPage += 1;
|
||
loadWorks();
|
||
});
|
||
|
||
(function initNavUser() {
|
||
var loginBtn = document.getElementById('navLoginBtn');
|
||
var userBox = document.getElementById('navUser');
|
||
var token = localStorage.getItem('access_token');
|
||
if (!token) {
|
||
loginBtn.style.display = 'inline-flex';
|
||
userBox.style.display = 'none';
|
||
return;
|
||
}
|
||
loginBtn.style.display = 'none';
|
||
userBox.style.display = 'inline-flex';
|
||
document.getElementById('navUserName').textContent = localStorage.getItem('username') || '用户';
|
||
applyNavAvatar(document.getElementById('navAvatar'));
|
||
try {
|
||
var cachedPoints = localStorage.getItem('ai_points_balance');
|
||
document.getElementById('navScore').textContent = (cachedPoints != null && cachedPoints !== '') ? cachedPoints : '--';
|
||
} catch (e) {}
|
||
|
||
fetch(resolveBaseUrl() + '/api/ai-explorer/userinfo/', {
|
||
method: 'POST',
|
||
headers: authHeaders(),
|
||
body: JSON.stringify({})
|
||
}).then(function(res) {
|
||
return res.json().catch(function() { return null; }).then(function(result) {
|
||
return { status: res.status, ok: res.ok, result: result };
|
||
});
|
||
}).then(function(payload) {
|
||
if (payload.status === 403 || payload.status === 401) {
|
||
showToast('登录失效,请重新登录', 'error');
|
||
try {
|
||
localStorage.removeItem('access_token');
|
||
localStorage.removeItem('refresh_token');
|
||
} catch (e) {}
|
||
setTimeout(function() { goLogin(); }, 2000);
|
||
return;
|
||
}
|
||
if (!payload.ok || !payload.result || payload.result.success === false) {
|
||
var errMsg = (payload.result && (payload.result.message || payload.result.detail || payload.result.error)) ||
|
||
('获取用户信息失败' + (payload.status ? '(' + payload.status + ')' : ''));
|
||
showToast(errMsg, 'error');
|
||
return;
|
||
}
|
||
var data = (payload.result && payload.result.data) || payload.result || {};
|
||
var name = data.full_name || data.username || localStorage.getItem('username') || '用户';
|
||
document.getElementById('navUserName').textContent = name;
|
||
applyNavAvatar(document.getElementById('navAvatar'));
|
||
var score = data.score != null ? data.score : (data.points != null ? data.points : null);
|
||
var scoreEl = document.getElementById('navScore');
|
||
if (scoreEl && score != null) scoreEl.textContent = String(score);
|
||
}).catch(function(err) {
|
||
showToast((err && err.message) || '获取用户信息失败,请稍后重试', 'error');
|
||
});
|
||
})();
|
||
|
||
updateScopeUi();
|
||
loadWorks();
|
||
</script>
|
||
</body>
|
||
|
||
</html>
|