1803 lines
69 KiB
HTML
1803 lines
69 KiB
HTML
<!doctype html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>产品-02C 用户工作区与作品工作台原型</title>
|
||
<style>
|
||
:root {
|
||
--bg: #f3f5f4;
|
||
--paper: #fffdf9;
|
||
--surface: #ffffff;
|
||
--surface-soft: #f8faf9;
|
||
--surface-strong: #eef3f1;
|
||
--ink: #17221f;
|
||
--muted: #50615c;
|
||
--weak: #7a8985;
|
||
--line: #d9e1df;
|
||
--line-strong: #bdcbc7;
|
||
--accent: #236f61;
|
||
--accent-deep: #174f46;
|
||
--accent-soft: #e6f3f0;
|
||
--blue: #315f89;
|
||
--blue-soft: #e8f0f7;
|
||
--gold: #80601c;
|
||
--gold-soft: #fff5dc;
|
||
--danger: #a83d35;
|
||
--danger-soft: #fff0ee;
|
||
--ok: #28744f;
|
||
--ok-soft: #e8f5ed;
|
||
--shadow: 0 14px 34px rgba(23, 34, 31, 0.07);
|
||
--radius: 8px;
|
||
--sidebar: 246px;
|
||
color-scheme: light;
|
||
}
|
||
|
||
* {
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
html {
|
||
min-width: 320px;
|
||
background: var(--bg);
|
||
color: var(--ink);
|
||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
body {
|
||
margin: 0;
|
||
}
|
||
|
||
button,
|
||
input,
|
||
select,
|
||
textarea {
|
||
font: inherit;
|
||
}
|
||
|
||
button,
|
||
select {
|
||
cursor: pointer;
|
||
}
|
||
|
||
.app {
|
||
min-height: 100vh;
|
||
display: grid;
|
||
grid-template-columns: var(--sidebar) minmax(0, 1fr);
|
||
grid-template-rows: 58px minmax(0, 1fr);
|
||
}
|
||
|
||
.topbar {
|
||
grid-column: 1 / -1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 16px;
|
||
padding: 10px 16px;
|
||
border-bottom: 1px solid var(--line);
|
||
background: rgba(255, 255, 255, 0.96);
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 30;
|
||
}
|
||
|
||
.brand,
|
||
.top-actions,
|
||
.row,
|
||
.badge-row,
|
||
.mini-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.brand-mark {
|
||
width: 34px;
|
||
height: 34px;
|
||
display: grid;
|
||
place-items: center;
|
||
border-radius: 7px;
|
||
background: var(--accent);
|
||
color: #fff;
|
||
font-weight: 900;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
.brand h1 {
|
||
margin: 0;
|
||
font-size: 15px;
|
||
line-height: 1.1;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
.brand p {
|
||
margin: 2px 0 0;
|
||
color: var(--muted);
|
||
font-size: 11px;
|
||
line-height: 1.1;
|
||
}
|
||
|
||
.avatar {
|
||
width: 29px;
|
||
height: 29px;
|
||
display: grid;
|
||
place-items: center;
|
||
border: 1px solid var(--line);
|
||
border-radius: 50%;
|
||
background: var(--surface-soft);
|
||
font-size: 12px;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.sidebar {
|
||
grid-column: 1;
|
||
grid-row: 2;
|
||
min-height: 0;
|
||
overflow: auto;
|
||
border-right: 1px solid var(--line);
|
||
background: var(--surface);
|
||
}
|
||
|
||
.nav-section {
|
||
padding: 12px;
|
||
border-bottom: 1px solid var(--line);
|
||
}
|
||
|
||
.nav-title {
|
||
margin: 0 0 8px;
|
||
color: var(--weak);
|
||
font-size: 11px;
|
||
font-weight: 850;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
.nav-button {
|
||
width: 100%;
|
||
min-height: 36px;
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) auto;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin: 0 0 4px;
|
||
padding: 7px 8px;
|
||
border: 1px solid transparent;
|
||
border-radius: 7px;
|
||
background: transparent;
|
||
color: var(--ink);
|
||
text-align: left;
|
||
}
|
||
|
||
.nav-button:hover {
|
||
border-color: var(--line);
|
||
background: var(--surface-soft);
|
||
}
|
||
|
||
.nav-button.active {
|
||
border-color: #a5d5cb;
|
||
background: var(--accent-soft);
|
||
color: var(--accent-deep);
|
||
}
|
||
|
||
.nav-name {
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
font-size: 12px;
|
||
font-weight: 750;
|
||
}
|
||
|
||
.nav-status {
|
||
width: 7px;
|
||
height: 7px;
|
||
border-radius: 999px;
|
||
background: var(--line-strong);
|
||
}
|
||
|
||
.nav-button.active .nav-status {
|
||
background: var(--accent);
|
||
}
|
||
|
||
.main {
|
||
grid-column: 2;
|
||
grid-row: 2;
|
||
min-width: 0;
|
||
min-height: 0;
|
||
overflow: auto;
|
||
padding: 16px;
|
||
}
|
||
|
||
.screen {
|
||
max-width: 1640px;
|
||
margin: 0 auto;
|
||
animation: screen-in 140ms ease-out;
|
||
}
|
||
|
||
@keyframes screen-in {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(4px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.page-head {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) auto;
|
||
align-items: start;
|
||
gap: 14px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.kicker {
|
||
color: var(--muted);
|
||
font-size: 12px;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.page-title {
|
||
margin: 3px 0 5px;
|
||
font-size: 25px;
|
||
line-height: 1.18;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
.page-desc {
|
||
margin: 0;
|
||
color: var(--muted);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.button {
|
||
min-height: 31px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 6px;
|
||
padding: 5px 10px;
|
||
border: 1px solid var(--line-strong);
|
||
border-radius: 7px;
|
||
background: var(--surface);
|
||
color: var(--ink);
|
||
font-size: 12px;
|
||
font-weight: 750;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.button:hover {
|
||
border-color: #91aaa4;
|
||
background: #f8fbfa;
|
||
}
|
||
|
||
.button.primary {
|
||
border-color: var(--accent);
|
||
background: var(--accent);
|
||
color: #fff;
|
||
}
|
||
|
||
.button.primary:hover {
|
||
background: var(--accent-deep);
|
||
}
|
||
|
||
.button.soft {
|
||
border-color: #afd8d0;
|
||
background: var(--accent-soft);
|
||
color: var(--accent-deep);
|
||
}
|
||
|
||
.button.warn {
|
||
border-color: #e5c77d;
|
||
background: var(--gold-soft);
|
||
color: var(--gold);
|
||
}
|
||
|
||
.button.danger {
|
||
border-color: #eeb9b3;
|
||
background: var(--danger-soft);
|
||
color: var(--danger);
|
||
}
|
||
|
||
.button.ghost {
|
||
border-color: transparent;
|
||
background: transparent;
|
||
color: var(--muted);
|
||
}
|
||
|
||
.button:disabled,
|
||
.nav-button:disabled {
|
||
cursor: not-allowed;
|
||
opacity: 0.54;
|
||
}
|
||
|
||
.button:focus-visible,
|
||
.nav-button:focus-visible,
|
||
input:focus-visible,
|
||
select:focus-visible,
|
||
textarea:focus-visible {
|
||
outline: 3px solid rgba(35, 111, 97, 0.18);
|
||
outline-offset: 2px;
|
||
}
|
||
|
||
.badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
max-width: 100%;
|
||
min-height: 22px;
|
||
padding: 2px 8px;
|
||
border: 1px solid var(--line);
|
||
border-radius: 999px;
|
||
background: var(--surface);
|
||
color: var(--muted);
|
||
font-size: 11px;
|
||
font-weight: 750;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.badge.ok {
|
||
border-color: #b9dfca;
|
||
background: var(--ok-soft);
|
||
color: var(--ok);
|
||
}
|
||
|
||
.badge.warn {
|
||
border-color: #e6cc89;
|
||
background: var(--gold-soft);
|
||
color: var(--gold);
|
||
}
|
||
|
||
.badge.danger {
|
||
border-color: #ecb4ae;
|
||
background: var(--danger-soft);
|
||
color: var(--danger);
|
||
}
|
||
|
||
.badge.blue {
|
||
border-color: #bfd4e6;
|
||
background: var(--blue-soft);
|
||
color: var(--blue);
|
||
}
|
||
|
||
.panel,
|
||
.filter-bar,
|
||
.metric,
|
||
.work-card,
|
||
.workspace-context,
|
||
.editor,
|
||
.chapter-pane,
|
||
.side-panel {
|
||
border: 1px solid var(--line);
|
||
border-radius: var(--radius);
|
||
background: var(--surface);
|
||
box-shadow: var(--shadow);
|
||
}
|
||
|
||
.filter-bar {
|
||
margin-bottom: 12px;
|
||
padding: 10px;
|
||
}
|
||
|
||
.filter-grid {
|
||
display: grid;
|
||
grid-template-columns: minmax(220px, 1.6fr) repeat(4, minmax(122px, 1fr)) auto;
|
||
gap: 8px;
|
||
align-items: end;
|
||
}
|
||
|
||
.control label {
|
||
display: block;
|
||
margin-bottom: 4px;
|
||
color: var(--muted);
|
||
font-size: 11px;
|
||
font-weight: 850;
|
||
}
|
||
|
||
.control input,
|
||
.control select,
|
||
.control textarea {
|
||
width: 100%;
|
||
min-height: 31px;
|
||
padding: 6px 8px;
|
||
border: 1px solid var(--line);
|
||
border-radius: 7px;
|
||
background: var(--surface-soft);
|
||
color: var(--ink);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.control textarea {
|
||
min-height: 86px;
|
||
resize: vertical;
|
||
}
|
||
|
||
.hint {
|
||
margin: 4px 0 0;
|
||
color: var(--weak);
|
||
font-size: 11px;
|
||
}
|
||
|
||
.grid-2 {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 12px;
|
||
}
|
||
|
||
.grid-3 {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
gap: 12px;
|
||
}
|
||
|
||
.grid-4 {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
gap: 10px;
|
||
}
|
||
|
||
.main-grid {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) minmax(310px, 390px);
|
||
gap: 12px;
|
||
align-items: start;
|
||
}
|
||
|
||
.panel {
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.panel-head {
|
||
min-height: 42px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 10px;
|
||
padding: 9px 11px;
|
||
border-bottom: 1px solid var(--line);
|
||
background: var(--surface-soft);
|
||
}
|
||
|
||
.panel-title {
|
||
margin: 0;
|
||
font-size: 13px;
|
||
font-weight: 850;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
.panel-body {
|
||
padding: 11px;
|
||
}
|
||
|
||
.metric {
|
||
min-height: 84px;
|
||
padding: 10px;
|
||
}
|
||
|
||
.metric-label {
|
||
margin: 0;
|
||
color: var(--muted);
|
||
font-size: 11px;
|
||
font-weight: 850;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.metric-value {
|
||
margin: 5px 0 4px;
|
||
font-size: 22px;
|
||
line-height: 1.1;
|
||
font-weight: 900;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
.metric-foot {
|
||
color: var(--weak);
|
||
font-size: 11px;
|
||
}
|
||
|
||
.works-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(260px, 1fr));
|
||
gap: 12px;
|
||
}
|
||
|
||
.work-card {
|
||
display: grid;
|
||
gap: 10px;
|
||
padding: 12px;
|
||
min-height: 176px;
|
||
}
|
||
|
||
.work-card-head {
|
||
display: grid;
|
||
grid-template-columns: 42px minmax(0, 1fr);
|
||
gap: 10px;
|
||
align-items: center;
|
||
}
|
||
|
||
.cover {
|
||
width: 42px;
|
||
height: 56px;
|
||
border-radius: 6px;
|
||
display: grid;
|
||
place-items: center;
|
||
background: linear-gradient(145deg, #295e55, #dfebdf);
|
||
color: #fff;
|
||
font-size: 12px;
|
||
font-weight: 900;
|
||
}
|
||
|
||
.work-title {
|
||
margin: 0;
|
||
font-size: 15px;
|
||
line-height: 1.25;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
.work-meta {
|
||
margin: 3px 0 0;
|
||
color: var(--muted);
|
||
font-size: 11px;
|
||
}
|
||
|
||
.stat-line {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
gap: 8px;
|
||
padding: 8px;
|
||
border-radius: 7px;
|
||
background: var(--surface-soft);
|
||
}
|
||
|
||
.stat-line strong {
|
||
display: block;
|
||
font-size: 14px;
|
||
line-height: 1.1;
|
||
}
|
||
|
||
.stat-line span {
|
||
color: var(--weak);
|
||
font-size: 10px;
|
||
}
|
||
|
||
.workspace-context {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) auto;
|
||
gap: 12px;
|
||
align-items: center;
|
||
margin-bottom: 12px;
|
||
padding: 10px 12px;
|
||
background: var(--paper);
|
||
}
|
||
|
||
.workspace-context h2 {
|
||
margin: 0;
|
||
font-size: 17px;
|
||
line-height: 1.2;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
.workspace-context p {
|
||
margin: 4px 0 0;
|
||
color: var(--muted);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.workspace-tabs {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
overflow: auto;
|
||
margin-bottom: 12px;
|
||
padding-bottom: 2px;
|
||
}
|
||
|
||
.workspace-tabs .button.active,
|
||
.workspace-secondary .button.active {
|
||
border-color: var(--accent);
|
||
background: var(--accent);
|
||
color: #fff;
|
||
}
|
||
|
||
.workspace-secondary {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
overflow: auto;
|
||
margin: -6px 0 12px;
|
||
padding-bottom: 2px;
|
||
}
|
||
|
||
.writing-grid {
|
||
display: grid;
|
||
grid-template-columns: 242px minmax(420px, 1fr) 360px;
|
||
gap: 12px;
|
||
align-items: stretch;
|
||
min-height: calc(100vh - 178px);
|
||
}
|
||
|
||
.chapter-pane,
|
||
.side-panel,
|
||
.editor {
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.chapter-list,
|
||
.record-list,
|
||
.compact-list {
|
||
display: grid;
|
||
gap: 7px;
|
||
}
|
||
|
||
.chapter-item,
|
||
.list-item {
|
||
padding: 8px;
|
||
border: 1px solid var(--line);
|
||
border-radius: 7px;
|
||
background: var(--surface);
|
||
}
|
||
|
||
.chapter-item.active {
|
||
border-color: #a5d5cb;
|
||
background: var(--accent-soft);
|
||
}
|
||
|
||
.chapter-item h4,
|
||
.list-item h4 {
|
||
margin: 0 0 4px;
|
||
font-size: 12px;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.chapter-item p,
|
||
.list-item p {
|
||
margin: 0;
|
||
color: var(--muted);
|
||
font-size: 11px;
|
||
}
|
||
|
||
.editor-toolbar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
padding: 9px 11px;
|
||
border-bottom: 1px solid var(--line);
|
||
background: var(--surface-soft);
|
||
}
|
||
|
||
.manuscript {
|
||
min-height: 515px;
|
||
padding: 24px 30px;
|
||
background: var(--paper);
|
||
color: #1a211f;
|
||
font-family: "Songti SC", "STSong", "SimSun", serif;
|
||
font-size: 18px;
|
||
line-height: 1.9;
|
||
outline: none;
|
||
overflow: auto;
|
||
}
|
||
|
||
.manuscript p {
|
||
margin: 0 0 14px;
|
||
}
|
||
|
||
.selected-text {
|
||
background: #dcece8;
|
||
border-bottom: 2px solid #72b7ab;
|
||
}
|
||
|
||
.side-panel .panel-body {
|
||
display: grid;
|
||
gap: 10px;
|
||
}
|
||
|
||
.suggestion-box {
|
||
padding: 10px;
|
||
border: 1px solid #cdded9;
|
||
border-radius: 8px;
|
||
background: #fbfefc;
|
||
}
|
||
|
||
.suggestion-text {
|
||
margin: 8px 0;
|
||
color: #202926;
|
||
font-size: 13px;
|
||
line-height: 1.65;
|
||
}
|
||
|
||
.score-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
gap: 7px;
|
||
}
|
||
|
||
.score {
|
||
padding: 8px;
|
||
border: 1px solid var(--line);
|
||
border-radius: 7px;
|
||
background: var(--surface-soft);
|
||
}
|
||
|
||
.score strong {
|
||
display: block;
|
||
font-size: 18px;
|
||
line-height: 1;
|
||
}
|
||
|
||
.score span {
|
||
color: var(--muted);
|
||
font-size: 10px;
|
||
}
|
||
|
||
.table-wrap {
|
||
overflow: auto;
|
||
}
|
||
|
||
table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
min-width: 760px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
th,
|
||
td {
|
||
padding: 9px 8px;
|
||
border-bottom: 1px solid var(--line);
|
||
text-align: left;
|
||
vertical-align: top;
|
||
}
|
||
|
||
th {
|
||
color: var(--muted);
|
||
background: var(--surface-soft);
|
||
font-size: 11px;
|
||
font-weight: 850;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
td {
|
||
color: var(--ink);
|
||
}
|
||
|
||
.form-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 9px;
|
||
}
|
||
|
||
.form-grid.three {
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
}
|
||
|
||
.choice-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
gap: 10px;
|
||
}
|
||
|
||
.choice {
|
||
padding: 11px;
|
||
border: 1px solid var(--line);
|
||
border-radius: 8px;
|
||
background: var(--surface);
|
||
}
|
||
|
||
.choice.active {
|
||
border-color: #9fd1c8;
|
||
background: var(--accent-soft);
|
||
}
|
||
|
||
.choice h3 {
|
||
margin: 0 0 6px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.choice p {
|
||
margin: 0;
|
||
color: var(--muted);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.progress {
|
||
height: 9px;
|
||
overflow: hidden;
|
||
border-radius: 999px;
|
||
background: var(--surface-strong);
|
||
}
|
||
|
||
.progress > span {
|
||
display: block;
|
||
height: 100%;
|
||
border-radius: inherit;
|
||
background: var(--accent);
|
||
}
|
||
|
||
.timeline {
|
||
display: grid;
|
||
gap: 10px;
|
||
}
|
||
|
||
.timeline-item {
|
||
display: grid;
|
||
grid-template-columns: 18px minmax(0, 1fr);
|
||
gap: 9px;
|
||
}
|
||
|
||
.timeline-dot {
|
||
width: 10px;
|
||
height: 10px;
|
||
margin-top: 5px;
|
||
border-radius: 50%;
|
||
background: var(--line-strong);
|
||
}
|
||
|
||
.timeline-dot.ok {
|
||
background: var(--ok);
|
||
}
|
||
|
||
.timeline-dot.warn {
|
||
background: var(--gold);
|
||
}
|
||
|
||
.timeline-item h4 {
|
||
margin: 0 0 2px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.timeline-item p {
|
||
margin: 0;
|
||
color: var(--muted);
|
||
font-size: 11px;
|
||
}
|
||
|
||
.diff {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 10px;
|
||
}
|
||
|
||
.diff-box {
|
||
min-height: 150px;
|
||
padding: 10px;
|
||
border: 1px solid var(--line);
|
||
border-radius: 8px;
|
||
background: var(--surface-soft);
|
||
font-size: 12px;
|
||
line-height: 1.65;
|
||
}
|
||
|
||
.danger-zone {
|
||
border-color: #edb8b2;
|
||
background: #fff8f7;
|
||
}
|
||
|
||
.empty-state {
|
||
min-height: 200px;
|
||
display: grid;
|
||
place-items: center;
|
||
padding: 24px;
|
||
border: 1px dashed var(--line-strong);
|
||
border-radius: 8px;
|
||
background: var(--surface-soft);
|
||
color: var(--muted);
|
||
text-align: center;
|
||
}
|
||
|
||
.empty-state h3 {
|
||
margin: 0 0 6px;
|
||
color: var(--ink);
|
||
font-size: 15px;
|
||
}
|
||
|
||
.empty-state p {
|
||
margin: 0 0 12px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
@media (max-width: 1220px) {
|
||
.app {
|
||
grid-template-columns: 208px minmax(0, 1fr);
|
||
}
|
||
|
||
.writing-grid {
|
||
grid-template-columns: 210px minmax(360px, 1fr);
|
||
}
|
||
|
||
.writing-grid .side-panel {
|
||
grid-column: 1 / -1;
|
||
}
|
||
|
||
.works-grid,
|
||
.grid-3,
|
||
.choice-grid {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
|
||
.filter-grid {
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
}
|
||
}
|
||
|
||
@media (max-width: 820px) {
|
||
.app {
|
||
display: block;
|
||
}
|
||
|
||
.topbar {
|
||
position: static;
|
||
}
|
||
|
||
.sidebar {
|
||
border-right: 0;
|
||
border-bottom: 1px solid var(--line);
|
||
max-height: 240px;
|
||
}
|
||
|
||
.main {
|
||
padding: 12px;
|
||
}
|
||
|
||
.page-head,
|
||
.workspace-context,
|
||
.main-grid,
|
||
.grid-2,
|
||
.grid-3,
|
||
.grid-4,
|
||
.filter-grid,
|
||
.form-grid,
|
||
.form-grid.three,
|
||
.choice-grid,
|
||
.diff,
|
||
.writing-grid,
|
||
.works-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.manuscript {
|
||
min-height: 420px;
|
||
padding: 18px;
|
||
font-size: 16px;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="app">
|
||
<header class="topbar">
|
||
<div class="brand">
|
||
<div class="brand-mark">M</div>
|
||
<div>
|
||
<h1>Muse 用户工作区</h1>
|
||
<p>我的作品 / 作品工作台 / 写作台</p>
|
||
</div>
|
||
</div>
|
||
<div class="top-actions">
|
||
<button class="button soft" data-go="work-create-import-entry">新建作品</button>
|
||
<button class="button" data-go="work-import-parse">导入旧稿</button>
|
||
<div class="avatar">林</div>
|
||
</div>
|
||
</header>
|
||
<aside class="sidebar" id="nav"></aside>
|
||
<main class="main" id="app" tabindex="-1"></main>
|
||
</div>
|
||
|
||
<script>
|
||
const pages = [
|
||
{ id: "user-my-works", name: "我的作品", group: "用户工作区", desc: "找到、创建、导入和继续写作作品" },
|
||
{ id: "work-create-import-entry", name: "新建/导入作品", group: "用户工作区", desc: "创建空作品、使用模板或上传旧稿" },
|
||
{ id: "work-workspace-shell", name: "作品工作台壳", group: "当前作品", desc: "单作品导航、状态摘要和返回规则" },
|
||
{ id: "work-writing-desk", name: "写作台", group: "当前作品", desc: "编辑正文、触发 AI 生成并处理候选" },
|
||
{ id: "work-suggestion-panel", name: "AI 候选决策面板", group: "当前作品", desc: "接受、修改后合并或丢弃候选" },
|
||
{ id: "work-planning-desk", name: "作品规划台", group: "当前作品", desc: "维护方向、大纲、世界、角色和文风" },
|
||
{ id: "work-knowledge-consistency", name: "知识与一致性", group: "当前作品", desc: "管理作品知识、草稿和一致性问题" },
|
||
{ id: "work-knowledge-draft-detail", name: "知识草稿详情", group: "当前作品", desc: "查看来源、差异、风险并做确认" },
|
||
{ id: "work-agent-association", name: "作品智能体关联", group: "当前作品设置", desc: "查看组合并替换开放槽位" },
|
||
{ id: "work-knowledge-source", name: "作品知识来源", group: "当前作品设置", desc: "绑定、解绑、降权和关闭生成使用" },
|
||
{ id: "work-import-parse", name: "导入解析", group: "当前作品流程", desc: "上传旧稿并查看解析进度" },
|
||
{ id: "work-full-parse-review", name: "全书解析审阅", group: "当前作品流程", desc: "按章节确认、驳回或重试解析结果" },
|
||
{ id: "work-export-delivery", name: "导出交付", group: "当前作品流程", desc: "选择范围和格式并下载交付物" },
|
||
{ id: "work-records-usage", name: "记录与用量", group: "当前作品", desc: "查看单作品记录、任务和 Token 使用" },
|
||
{ id: "work-settings", name: "作品设置", group: "当前作品设置", desc: "管理基础信息、归档和删除" }
|
||
];
|
||
|
||
const works = [
|
||
{ title: "雾港异闻录", type: "都市奇幻", words: "128,430", chapters: 42, edited: "今天 16:22", status: "连载中", drafts: 7, risk: "2 个来源异常", badge: "待处理" },
|
||
{ title: "第九封来信", type: "悬疑", words: "62,810", chapters: 21, edited: "昨天 23:41", status: "草稿", drafts: 3, risk: "无阻断风险", badge: "继续写" },
|
||
{ title: "群星旧约", type: "科幻", words: "211,020", chapters: 66, edited: "05-20 11:08", status: "修订", drafts: 18, risk: "最近解析部分失败", badge: "审阅" },
|
||
{ title: "南桥旧梦", type: "年代", words: "31,560", chapters: 12, edited: "05-18 09:15", status: "归档", drafts: 0, risk: "只读归档", badge: "查看" }
|
||
];
|
||
|
||
const chapters = [
|
||
{ title: "第 1 章 雾港来客", words: "3,420", state: "已保存" },
|
||
{ title: "第 2 章 码头上的黑伞", words: "3,880", state: "待确认 2" },
|
||
{ title: "第 3 章 旧钟楼", words: "2,760", state: "当前章节" },
|
||
{ title: "第 4 章 失踪的账本", words: "1,240", state: "草稿" }
|
||
];
|
||
|
||
const workspaceTabs = [
|
||
["work-writing-desk", "写作"],
|
||
["work-planning-desk", "规划"],
|
||
["work-knowledge-consistency", "知识与一致性"]
|
||
];
|
||
|
||
const workspaceSecondaryLinks = [
|
||
["work-agent-association", "智能体设置"],
|
||
["work-knowledge-source", "知识来源"],
|
||
["work-import-parse", "导入任务"],
|
||
["work-export-delivery", "导出任务"],
|
||
["work-records-usage", "记录"],
|
||
["work-settings", "设置"]
|
||
];
|
||
|
||
function go(id) {
|
||
location.hash = id;
|
||
}
|
||
|
||
function escapeHtml(value) {
|
||
return String(value).replace(/[&<>"']/g, (char) => ({
|
||
"&": "&",
|
||
"<": "<",
|
||
">": ">",
|
||
"\"": """,
|
||
"'": "'"
|
||
}[char]));
|
||
}
|
||
|
||
function badge(text, tone = "") {
|
||
return `<span class="badge ${tone}">${escapeHtml(text)}</span>`;
|
||
}
|
||
|
||
function pageHead(page, actions = "") {
|
||
return `
|
||
<div class="page-head">
|
||
<div>
|
||
<div class="kicker">${escapeHtml(page.group)}</div>
|
||
<h2 class="page-title">${escapeHtml(page.name)}</h2>
|
||
<p class="page-desc">${escapeHtml(page.desc)}</p>
|
||
</div>
|
||
<div class="row">${actions}</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function panel(title, body, actions = "") {
|
||
return `
|
||
<section class="panel">
|
||
<div class="panel-head">
|
||
<h3 class="panel-title">${title}</h3>
|
||
<div class="mini-actions">${actions}</div>
|
||
</div>
|
||
<div class="panel-body">${body}</div>
|
||
</section>
|
||
`;
|
||
}
|
||
|
||
function workspaceFrame(activeId, content, right = "") {
|
||
const tabHtml = workspaceTabs.map(([id, name]) => `
|
||
<button class="button ${id === activeId ? "active" : ""}" data-go="${id}">${name}</button>
|
||
`).join("");
|
||
const secondaryHtml = workspaceSecondaryLinks.map(([id, name]) => `
|
||
<button class="button ghost ${id === activeId ? "active" : ""}" data-go="${id}">${name}</button>
|
||
`).join("");
|
||
const context = `
|
||
<section class="workspace-context">
|
||
<div>
|
||
<h2>雾港异闻录</h2>
|
||
<p>当前章节:第 3 章 旧钟楼 · 最近保存:16:18 · 待确认知识 7 · 来源异常 2</p>
|
||
</div>
|
||
<div class="row">
|
||
${badge("正常", "ok")}
|
||
${badge("有待确认", "warn")}
|
||
<button class="button" data-go="user-my-works">返回我的作品</button>
|
||
</div>
|
||
</section>
|
||
<nav class="workspace-tabs">${tabHtml}</nav>
|
||
<nav class="workspace-secondary">${secondaryHtml}</nav>
|
||
`;
|
||
if (!right) return context + content;
|
||
return context + `<div class="main-grid"><div>${content}</div><div>${right}</div></div>`;
|
||
}
|
||
|
||
function renderNav(activeId) {
|
||
const groups = pages.reduce((acc, page) => {
|
||
acc[page.group] = acc[page.group] || [];
|
||
acc[page.group].push(page);
|
||
return acc;
|
||
}, {});
|
||
document.getElementById("nav").innerHTML = Object.entries(groups).map(([group, items]) => `
|
||
<section class="nav-section">
|
||
<h2 class="nav-title">${escapeHtml(group)}</h2>
|
||
${items.map((page) => `
|
||
<button class="nav-button ${page.id === activeId ? "active" : ""}" data-go="${page.id}">
|
||
<span class="nav-name">${escapeHtml(page.name)}</span>
|
||
<span class="nav-status"></span>
|
||
</button>
|
||
`).join("")}
|
||
</section>
|
||
`).join("");
|
||
}
|
||
|
||
function renderMyWorks(page) {
|
||
const cards = works.map((work, index) => `
|
||
<article class="work-card">
|
||
<div class="work-card-head">
|
||
<div class="cover">${index + 1}</div>
|
||
<div>
|
||
<h3 class="work-title">${escapeHtml(work.title)}</h3>
|
||
<p class="work-meta">${escapeHtml(work.type)} · ${escapeHtml(work.status)} · 最近编辑 ${escapeHtml(work.edited)}</p>
|
||
</div>
|
||
</div>
|
||
<div class="stat-line">
|
||
<div><strong>${work.words}</strong><span>字数</span></div>
|
||
<div><strong>${work.chapters}</strong><span>章节</span></div>
|
||
<div><strong>${work.drafts}</strong><span>待确认</span></div>
|
||
</div>
|
||
<div class="badge-row">
|
||
${badge(work.badge, work.badge === "继续写" ? "ok" : work.badge === "待处理" ? "warn" : "blue")}
|
||
${badge(work.risk, work.risk.includes("异常") || work.risk.includes("失败") ? "warn" : "")}
|
||
</div>
|
||
<div class="row">
|
||
<button class="button primary" data-go="work-writing-desk">继续写作</button>
|
||
<button class="button" data-go="work-export-delivery">导出</button>
|
||
<button class="button ghost" data-go="work-settings">设置</button>
|
||
</div>
|
||
</article>
|
||
`).join("");
|
||
return `
|
||
<div class="screen">
|
||
${pageHead(page, `<button class="button primary" data-go="work-create-import-entry">新建作品</button><button class="button" data-go="work-import-parse">导入作品</button>`)}
|
||
<section class="filter-bar">
|
||
<div class="filter-grid">
|
||
<div class="control"><label>作品名</label><input value="雾港" /></div>
|
||
<div class="control"><label>类型</label><select><option>全部类型</option><option>都市奇幻</option><option>悬疑</option></select></div>
|
||
<div class="control"><label>状态</label><select><option>全部状态</option><option>连载中</option><option>草稿</option><option>归档</option></select></div>
|
||
<div class="control"><label>风险</label><select><option>全部风险</option><option>有待确认</option><option>来源异常</option></select></div>
|
||
<div class="control"><label>排序</label><select><option>最近编辑优先</option><option>字数最多</option><option>章节最多</option></select></div>
|
||
<button class="button">筛选</button>
|
||
</div>
|
||
</section>
|
||
<div class="grid-4" style="margin-bottom:12px">
|
||
<div class="metric"><p class="metric-label">全部作品</p><div class="metric-value">4</div><div class="metric-foot">当前账户可访问</div></div>
|
||
<div class="metric"><p class="metric-label">待确认知识</p><div class="metric-value">28</div><div class="metric-foot">来自 3 部作品</div></div>
|
||
<div class="metric"><p class="metric-label">最近失败任务</p><div class="metric-value">2</div><div class="metric-foot">可进入对应作品处理</div></div>
|
||
<div class="metric"><p class="metric-label">本周写作</p><div class="metric-value">18,240</div><div class="metric-foot">新增字数</div></div>
|
||
</div>
|
||
<section class="works-grid">${cards}</section>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function renderCreateImport(page) {
|
||
return `
|
||
<div class="screen">
|
||
${pageHead(page, `<button class="button" data-go="user-my-works">取消</button><button class="button primary" data-go="work-writing-desk">创建并进入写作台</button>`)}
|
||
<div class="grid-2">
|
||
${panel("创建方式", `
|
||
<div class="choice-grid">
|
||
<div class="choice active"><h3>空白作品</h3><p>创建默认章节,立即进入写作台。</p></div>
|
||
<div class="choice"><h3>使用模板</h3><p>从可用模板带入题材、结构和默认章节。</p></div>
|
||
<div class="choice"><h3>导入旧稿</h3><p>上传旧文稿,稍后按章节审阅解析结果。</p></div>
|
||
</div>
|
||
`)}
|
||
${panel("文件上传", `
|
||
<div class="empty-state">
|
||
<div>
|
||
<h3>拖入旧稿或选择文件</h3>
|
||
<p>支持 txt、docx、md;上传后进入导入解析。</p>
|
||
<button class="button">选择文件</button>
|
||
</div>
|
||
</div>
|
||
`)}
|
||
</div>
|
||
<div style="height:12px"></div>
|
||
${panel("基础信息", `
|
||
<div class="form-grid three">
|
||
<div class="control"><label>作品标题</label><input value="雾港异闻录" /></div>
|
||
<div class="control"><label>类型</label><select><option>都市奇幻</option><option>悬疑</option><option>科幻</option></select></div>
|
||
<div class="control"><label>题材</label><input value="港口、旧神、家族秘密" /></div>
|
||
<div class="control"><label>目标字数</label><input value="350000" /></div>
|
||
<div class="control"><label>模板</label><select><option>长篇连载默认模板</option><option>三幕式章节模板</option></select></div>
|
||
<div class="control"><label>创作状态</label><select><option>草稿</option><option>连载中</option></select></div>
|
||
<div class="control" style="grid-column:1 / -1"><label>简介</label><textarea>一名档案修复师回到雾港,发现父亲留下的旧钟楼记录与多年前失踪案有关。</textarea></div>
|
||
</div>
|
||
`, `<button class="button">保存为稍后填写</button>`)}
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function renderWorkspaceShell(page) {
|
||
const body = `
|
||
<div class="grid-4" style="margin-bottom:12px">
|
||
<div class="metric"><p class="metric-label">当前章节</p><div class="metric-value">第 3 章</div><div class="metric-foot">旧钟楼 · 2,760 字</div></div>
|
||
<div class="metric"><p class="metric-label">保存状态</p><div class="metric-value">16:18</div><div class="metric-foot">最近保存成功</div></div>
|
||
<div class="metric"><p class="metric-label">待确认知识</p><div class="metric-value">7</div><div class="metric-foot">人物 3 · 事件 2 · 地点 2</div></div>
|
||
<div class="metric"><p class="metric-label">来源异常</p><div class="metric-value">2</div><div class="metric-foot">可在知识来源处理</div></div>
|
||
</div>
|
||
<div class="grid-3">
|
||
${panel("主工作面", `<div class="compact-list">
|
||
<div class="list-item"><h4>写作台</h4><p>编辑正文、保存文本块、处理候选。</p></div>
|
||
<div class="list-item"><h4>作品规划台</h4><p>作品方向、章节大纲、世界设定、角色关系、文风检查。</p></div>
|
||
<div class="list-item"><h4>知识与一致性</h4><p>正式知识、待确认草稿、冲突问题和来源追溯。</p></div>
|
||
</div>`)}
|
||
${panel("设置型入口", `<div class="compact-list">
|
||
<div class="list-item"><h4>作品智能体关联</h4><p>查看组合,替换开放槽位,回退默认。</p></div>
|
||
<div class="list-item"><h4>作品知识来源</h4><p>绑定、解绑、降权和关闭生成使用。</p></div>
|
||
<div class="list-item"><h4>作品设置</h4><p>基础信息、归档、恢复和删除。</p></div>
|
||
</div>`)}
|
||
${panel("流程入口", `<div class="compact-list">
|
||
<div class="list-item"><h4>导入解析</h4><p>上传旧稿,查看解析进度。</p></div>
|
||
<div class="list-item"><h4>导出交付</h4><p>选择范围和格式,下载交付物。</p></div>
|
||
<div class="list-item"><h4>记录与用量</h4><p>生成记录、任务记录、失败原因和 Token。</p></div>
|
||
</div>`)}
|
||
</div>
|
||
`;
|
||
return `<div class="screen">${pageHead(page)}${workspaceFrame(page.id, body)}</div>`;
|
||
}
|
||
|
||
function renderWritingDesk(page) {
|
||
const chapterItems = chapters.map((chapter, index) => `
|
||
<button class="chapter-item ${index === 2 ? "active" : ""}" style="text-align:left">
|
||
<h4>${escapeHtml(chapter.title)}</h4>
|
||
<p>${chapter.words} 字 · ${chapter.state}</p>
|
||
</button>
|
||
`).join("");
|
||
const content = `
|
||
<div class="writing-grid">
|
||
<section class="chapter-pane">
|
||
<div class="panel-head">
|
||
<h3 class="panel-title">章节</h3>
|
||
<button class="button">新建</button>
|
||
</div>
|
||
<div class="panel-body">
|
||
<div class="control"><label>章节搜索</label><input value="旧钟楼" /></div>
|
||
<div style="height:8px"></div>
|
||
<div class="chapter-list">${chapterItems}</div>
|
||
</div>
|
||
</section>
|
||
<section class="editor">
|
||
<div class="editor-toolbar">
|
||
<div>
|
||
<strong>第 3 章 旧钟楼</strong>
|
||
<div class="hint">章节目标:让林澈第一次发现父亲失踪与港务账本有关。</div>
|
||
</div>
|
||
<div class="row">
|
||
${badge("未保存改动", "warn")}
|
||
<button class="button primary">保存正文</button>
|
||
</div>
|
||
</div>
|
||
<div class="manuscript" contenteditable="true" aria-label="正文编辑器">
|
||
<p>旧钟楼的门比林澈记忆里更沉。铜环上浮着一层潮湿的绿锈,像某种没有完全闭合的眼睛。</p>
|
||
<p>他把钥匙插进去时,远处港口传来三声短促的汽笛。那声音穿过雨幕,落在空旷的街面上,竟和父亲录音里的背景音一模一样。</p>
|
||
<p><span class="selected-text">账本夹在钟摆后方的暗格里,封面没有署名,只用蓝墨水写着一串日期。</span>林澈盯着最末一行,忽然意识到那不是账目日期,而是失踪名单。</p>
|
||
<p>楼下有人推门。风灌进来,灯泡轻轻晃了一下。</p>
|
||
</div>
|
||
</section>
|
||
<aside class="side-panel">
|
||
<div class="panel-head">
|
||
<h3 class="panel-title">AI 候选与状态</h3>
|
||
<button class="button" data-go="work-suggestion-panel">展开</button>
|
||
</div>
|
||
<div class="panel-body">
|
||
<div class="row">
|
||
<button class="button primary">续写</button>
|
||
<button class="button">改写</button>
|
||
<button class="button">扩写</button>
|
||
<button class="button">润色</button>
|
||
<button class="button">纠错</button>
|
||
<button class="button">检查</button>
|
||
</div>
|
||
<div class="suggestion-box">
|
||
<div class="badge-row">${badge("候选可处理", "ok")}${badge("有 2 条知识草稿", "warn")}</div>
|
||
<p class="suggestion-text">“别回头。”楼梯间传来的声音压得很低,像是怕惊醒钟楼深处某个沉睡的东西。林澈的手指停在账本边缘,纸页上的日期被雨声一点点浸湿。</p>
|
||
<div class="row">
|
||
<button class="button primary">原样接受</button>
|
||
<button class="button">修改后合并</button>
|
||
<button class="button danger">丢弃</button>
|
||
</div>
|
||
</div>
|
||
<div class="score-grid">
|
||
<div class="score"><strong>86</strong><span>设定一致</span></div>
|
||
<div class="score"><strong>91</strong><span>角色声音</span></div>
|
||
<div class="score"><strong>78</strong><span>场景结构</span></div>
|
||
</div>
|
||
<div class="compact-list">
|
||
<div class="list-item"><h4>当前智能体</h4><p>悬疑续写助手 · 来源:用户替换 · 授权正常</p></div>
|
||
<div class="list-item"><h4>知识来源</h4><p>雾港世界设定、港务资料库 · 1 个来源版本变化</p></div>
|
||
<div class="list-item"><h4>版本历史</h4><p>16:18 保存 · 15:42 保存 · 14:05 候选合并</p></div>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
</div>
|
||
`;
|
||
return `<div class="screen">${pageHead(page)}${workspaceFrame(page.id, content)}</div>`;
|
||
}
|
||
|
||
function renderSuggestionPanel(page) {
|
||
const content = `
|
||
<div class="main-grid">
|
||
${panel("候选文本", `
|
||
<div class="suggestion-box">
|
||
<div class="badge-row">${badge("来源需重验", "warn")}${badge("知识草稿提取中", "warn")}${badge("直接接受已禁用", "danger")}</div>
|
||
<p class="suggestion-text">“别回头。”楼梯间传来的声音压得很低,像是怕惊醒钟楼深处某个沉睡的东西。林澈的手指停在账本边缘,纸页上的日期被雨声一点点浸湿。他知道,只要把账本合上,父亲留下的谜题就还能维持原样;可如果他继续读下去,雾港就再也不是记忆里的雾港。</p>
|
||
</div>
|
||
<div style="height:10px"></div>
|
||
<div class="row">
|
||
<button class="button primary" disabled>原样接受</button>
|
||
<button class="button">修改后合并</button>
|
||
<button class="button danger">丢弃当前建议</button>
|
||
<button class="button primary">重新生成</button>
|
||
<button class="button">复制非阻断文本</button>
|
||
</div>
|
||
`)}
|
||
<div>
|
||
${panel("创作健康度", `
|
||
<div class="score-grid">
|
||
<div class="score"><strong>86</strong><span>设定一致</span></div>
|
||
<div class="score"><strong>91</strong><span>角色声音</span></div>
|
||
<div class="score"><strong>78</strong><span>场景结构</span></div>
|
||
</div>
|
||
<div style="height:8px"></div>
|
||
<div class="compact-list">
|
||
<div class="list-item"><h4>改进建议</h4><p>候选后半段可以增加外部动作,避免停留在内心判断。</p></div>
|
||
<div class="list-item"><h4>关键风险</h4><p>账本来源版本变化,不能原样接受;需要重新生成或手动处理。</p></div>
|
||
</div>
|
||
`)}
|
||
<div style="height:12px"></div>
|
||
${panel("来源与知识状态", `
|
||
<div class="compact-list">
|
||
<div class="list-item"><h4>关联智能体</h4><p>悬疑续写助手 · 用户替换 · 授权正常</p></div>
|
||
<div class="list-item"><h4>知识来源</h4><p>雾港世界设定 v4 可生成;港务资料库 v2 来源已变化。</p></div>
|
||
<div class="list-item"><h4>知识草稿</h4><p>2 条提取中;接受正文不会自动确认知识。</p></div>
|
||
</div>
|
||
`, `<button class="button" data-go="work-knowledge-draft-detail">查看草稿</button>`)}
|
||
</div>
|
||
</div>
|
||
`;
|
||
return `<div class="screen">${pageHead(page)}${workspaceFrame(page.id, content)}</div>`;
|
||
}
|
||
|
||
function renderPlanningDesk(page) {
|
||
const outlineRows = [
|
||
["第 1 章", "林澈回到雾港", "建立悬念", "已保存"],
|
||
["第 2 章", "黑伞人出现", "引出追踪", "已保存"],
|
||
["第 3 章", "旧钟楼账本", "揭示父亲线索", "候选待确认"],
|
||
["第 4 章", "账本失窃", "制造转折", "草稿"]
|
||
].map((row) => `<tr>${row.map((cell) => `<td>${escapeHtml(cell)}</td>`).join("")}<td><button class="button">查看</button></td></tr>`).join("");
|
||
const content = `
|
||
<div class="grid-4" style="margin-bottom:12px">
|
||
<div class="metric"><p class="metric-label">作品方向</p><div class="metric-value">悬疑</div><div class="metric-foot">都市奇幻基调</div></div>
|
||
<div class="metric"><p class="metric-label">章节大纲</p><div class="metric-value">42</div><div class="metric-foot">已规划章节</div></div>
|
||
<div class="metric"><p class="metric-label">角色关系</p><div class="metric-value">18</div><div class="metric-foot">主要关系</div></div>
|
||
<div class="metric"><p class="metric-label">文风检查</p><div class="metric-value">82</div><div class="metric-foot">节奏略慢</div></div>
|
||
</div>
|
||
<div class="main-grid">
|
||
<div>
|
||
${panel("作品设定", `
|
||
<div class="form-grid">
|
||
<div class="control"><label>题材</label><input value="港口、旧神、家族秘密" /></div>
|
||
<div class="control"><label>基调</label><input value="潮湿、克制、悬疑递进" /></div>
|
||
<div class="control"><label>禁区</label><input value="不使用全知解释谜底" /></div>
|
||
<div class="control"><label>结局方向</label><input value="林澈主动继承守望者身份" /></div>
|
||
</div>
|
||
`, `<button class="button primary">保存规划项</button><button class="button">生成候选</button>`)}
|
||
<div style="height:12px"></div>
|
||
${panel("章节大纲", `<div class="table-wrap"><table><thead><tr><th>章节</th><th>摘要</th><th>章节目标</th><th>状态</th><th>操作</th></tr></thead><tbody>${outlineRows}</tbody></table></div>`)}
|
||
</div>
|
||
<div>
|
||
${panel("角色与世界", `
|
||
<div class="compact-list">
|
||
<div class="list-item"><h4>林澈</h4><p>档案修复师;动机是查清父亲失踪。</p></div>
|
||
<div class="list-item"><h4>顾南枝</h4><p>港务局档案员;知道旧账本来源。</p></div>
|
||
<div class="list-item"><h4>旧钟楼</h4><p>雾港最早的潮汐观测点;隐藏账本暗格。</p></div>
|
||
</div>
|
||
`)}
|
||
<div style="height:12px"></div>
|
||
${panel("文风检查", `
|
||
<div class="score-grid">
|
||
<div class="score"><strong>88</strong><span>作者声音</span></div>
|
||
<div class="score"><strong>76</strong><span>句式变化</span></div>
|
||
<div class="score"><strong>81</strong><span>张力节奏</span></div>
|
||
</div>
|
||
<div style="height:8px"></div>
|
||
<button class="button">发起文风检查</button>
|
||
`)}
|
||
</div>
|
||
</div>
|
||
`;
|
||
return `<div class="screen">${pageHead(page)}${workspaceFrame(page.id, content)}</div>`;
|
||
}
|
||
|
||
function renderKnowledgeConsistency(page) {
|
||
const knowledgeRows = [
|
||
["人物", "林澈", "父亲失踪后回到雾港", "第 1 章", "已确认"],
|
||
["地点", "旧钟楼", "潮汐观测点,钟摆后有暗格", "第 3 章", "待修正"],
|
||
["事件", "港务账本失踪", "与失踪名单有关", "第 3 章", "草稿待确认"],
|
||
["关系", "林澈 / 顾南枝", "互相试探的协作关系", "第 2 章", "已确认"]
|
||
].map((row) => `<tr>${row.map((cell, index) => `<td>${index === 4 ? badge(cell, cell.includes("待") ? "warn" : cell.includes("修正") ? "danger" : "ok") : escapeHtml(cell)}</td>`).join("")}<td><button class="button">查看来源</button></td></tr>`).join("");
|
||
const content = `
|
||
<div class="main-grid">
|
||
<div>
|
||
${panel("知识列表", `
|
||
<div class="filter-bar">
|
||
<div class="filter-grid" style="grid-template-columns: minmax(180px,1.5fr) repeat(3,minmax(110px,1fr)) auto">
|
||
<div class="control"><label>搜索</label><input value="旧钟楼" /></div>
|
||
<div class="control"><label>知识类型</label><select><option>全部类型</option><option>人物</option><option>地点</option><option>事件</option></select></div>
|
||
<div class="control"><label>风险</label><select><option>全部风险</option><option>有冲突</option><option>来源失效</option></select></div>
|
||
<div class="control"><label>状态</label><select><option>全部状态</option><option>待确认</option><option>已确认</option></select></div>
|
||
<button class="button">筛选</button>
|
||
</div>
|
||
</div>
|
||
<div class="table-wrap"><table><thead><tr><th>类型</th><th>对象</th><th>内容摘要</th><th>来源</th><th>状态</th><th>操作</th></tr></thead><tbody>${knowledgeRows}</tbody></table></div>
|
||
`)}
|
||
</div>
|
||
<div>
|
||
${panel("待确认草稿", `
|
||
<div class="compact-list">
|
||
<div class="list-item"><h4>旧钟楼暗格</h4><p>来源:第 3 章 · 新增地点属性 · 可确认</p><div class="row" style="margin-top:8px"><button class="button primary">确认</button><button class="button">忽略</button><button class="button" data-go="work-knowledge-draft-detail">详情</button></div></div>
|
||
<div class="list-item"><h4>账本日期</h4><p>来源版本变化,需要重新提取。</p><div class="row" style="margin-top:8px"><button class="button">重新提取</button><button class="button">忽略</button></div></div>
|
||
</div>
|
||
`, `<button class="button">发起一致性检查</button>`)}
|
||
<div style="height:12px"></div>
|
||
${panel("一致性问题", `
|
||
<div class="compact-list">
|
||
<div class="list-item"><h4>旧钟楼位置不一致</h4><p>第 1 章写在东港,第 3 章写在北港。建议定位正文后修正。</p></div>
|
||
<div class="list-item"><h4>顾南枝知情程度冲突</h4><p>第 2 章“不知道账本”,第 3 章候选暗示她见过账本。</p></div>
|
||
</div>
|
||
`)}
|
||
</div>
|
||
</div>
|
||
`;
|
||
return `<div class="screen">${pageHead(page)}${workspaceFrame(page.id, content)}</div>`;
|
||
}
|
||
|
||
function renderKnowledgeDraftDetail(page) {
|
||
const content = `
|
||
<div class="main-grid">
|
||
<div>
|
||
${panel("草稿摘要", `
|
||
<div class="badge-row">${badge("存在冲突", "warn")}${badge("来源:第 3 章 旧钟楼", "blue")}${badge("直接确认已禁用", "danger")}</div>
|
||
<div style="height:10px"></div>
|
||
<div class="diff">
|
||
<div class="diff-box"><strong>当前正式知识</strong><br />旧钟楼是雾港早期建筑,曾用于潮汐观测。位置:东港旧街。</div>
|
||
<div class="diff-box"><strong>草稿变更</strong><br />旧钟楼钟摆后存在暗格,保存港务账本。位置需要从东港旧街修正为北港斜坡街。</div>
|
||
</div>
|
||
<div style="height:10px"></div>
|
||
<div class="row">
|
||
<button class="button primary" disabled>确认</button>
|
||
<button class="button primary">修改后确认</button>
|
||
<button class="button">忽略</button>
|
||
<button class="button">重新提取</button>
|
||
</div>
|
||
`)}
|
||
</div>
|
||
<div>
|
||
${panel("来源快照", `
|
||
<div class="suggestion-box">
|
||
<p class="suggestion-text">账本夹在钟摆后方的暗格里,封面没有署名,只用蓝墨水写着一串日期。</p>
|
||
</div>
|
||
<div class="compact-list" style="margin-top:10px">
|
||
<div class="list-item"><h4>来源状态</h4><p>当前章节版本匹配,可定位正文。</p></div>
|
||
<div class="list-item"><h4>风险说明</h4><p>位置字段与已有知识冲突,不能直接确认;需要修改后确认或忽略。</p></div>
|
||
<div class="list-item"><h4>相关章节</h4><p>第 1 章 雾港来客;第 3 章 旧钟楼。</p></div>
|
||
</div>
|
||
`, `<button class="button">定位来源</button>`)}
|
||
</div>
|
||
</div>
|
||
`;
|
||
return `<div class="screen">${pageHead(page)}${workspaceFrame(page.id, content)}</div>`;
|
||
}
|
||
|
||
function renderAgentAssociation(page) {
|
||
const rows = [
|
||
["续写风格", "悬疑续写助手", "用户替换", "授权正常", "失败时回退默认", "替换"],
|
||
["角色声音建议", "角色对白检查", "系统默认", "可用", "失败时显示原因", "替换"],
|
||
["输出合规检查", "平台安全检查", "不可替换", "可用", "阻断高风险输出", "不可替换"],
|
||
["上下文授权过滤", "来源授权过滤", "不可替换", "可用", "防止越权来源进入生成", "不可替换"],
|
||
["一致性叙事检查", "长篇一致性检查", "系统默认", "可用", "失败时保留候选", "试用"]
|
||
].map((row) => `
|
||
<tr>
|
||
<td><strong>${row[0]}</strong></td>
|
||
<td>${row[1]}</td>
|
||
<td>${badge(row[2], row[2].includes("用户") ? "blue" : row[2].includes("不可") ? "warn" : "")}</td>
|
||
<td>${badge(row[3], row[3].includes("正常") || row[3] === "可用" ? "ok" : "warn")}</td>
|
||
<td>${row[4]}</td>
|
||
<td><button class="button" ${row[5] === "不可替换" ? "disabled" : ""}>${row[5]}</button></td>
|
||
</tr>
|
||
`).join("");
|
||
const content = `
|
||
${panel("当前作品智能体组合", `
|
||
<div class="grid-3">
|
||
<div class="metric"><p class="metric-label">开放槽位</p><div class="metric-value">3</div><div class="metric-foot">可替换子智能体</div></div>
|
||
<div class="metric"><p class="metric-label">已替换</p><div class="metric-value">1</div><div class="metric-foot">续写风格使用用户智能体</div></div>
|
||
<div class="metric"><p class="metric-label">不可替换</p><div class="metric-value">2</div><div class="metric-foot">安全和授权边界</div></div>
|
||
</div>
|
||
`)}
|
||
<div style="height:12px"></div>
|
||
${panel("槽位列表", `<div class="table-wrap"><table><thead><tr><th>槽位用途</th><th>当前智能体</th><th>来源</th><th>授权</th><th>失败回退</th><th>操作</th></tr></thead><tbody>${rows}</tbody></table></div>`, `<button class="button">查看授权</button><button class="button">回退默认</button>`)}
|
||
`;
|
||
return `<div class="screen">${pageHead(page)}${workspaceFrame(page.id, content)}</div>`;
|
||
}
|
||
|
||
function renderKnowledgeSource(page) {
|
||
const rows = [
|
||
["雾港世界设定", "用户知识库", "v4", "可检索、可生成", "本周 34 次", "正常"],
|
||
["港务资料库", "市场知识库", "v2", "仅检索", "本周 8 次", "版本变化"],
|
||
["民国港口法规摘录", "全局知识库", "v1", "只读参考", "本周 3 次", "授权正常"],
|
||
["旧报纸剪辑", "市场知识库", "v3", "已关闭生成", "本周 0 次", "来源下架"]
|
||
].map((row) => `
|
||
<tr>
|
||
<td><strong>${row[0]}</strong></td>
|
||
<td>${row[1]}</td>
|
||
<td>${row[2]}</td>
|
||
<td>${row[3]}</td>
|
||
<td>${row[4]}</td>
|
||
<td>${badge(row[5], row[5] === "正常" || row[5] === "授权正常" ? "ok" : "warn")}</td>
|
||
<td><div class="row"><button class="button">查看命中</button><button class="button">降权</button><button class="button danger">解绑</button></div></td>
|
||
</tr>
|
||
`).join("");
|
||
const content = `
|
||
<div class="main-grid">
|
||
${panel("当前绑定来源", `<div class="table-wrap"><table><thead><tr><th>知识库</th><th>来源类型</th><th>版本</th><th>用途</th><th>最近命中</th><th>状态</th><th>操作</th></tr></thead><tbody>${rows}</tbody></table></div>`, `<button class="button primary">绑定知识库</button>`)}
|
||
<div>
|
||
${panel("可绑定来源", `
|
||
<div class="compact-list">
|
||
<div class="list-item"><h4>人物职业资料包</h4><p>用户知识库 · 可检索 · 处理完成</p><button class="button" style="margin-top:8px">绑定</button></div>
|
||
<div class="list-item"><h4>悬疑节奏参考库</h4><p>市场知识库 · 已安装 · 可生成</p><button class="button" style="margin-top:8px">绑定</button></div>
|
||
</div>
|
||
`)}
|
||
<div style="height:12px"></div>
|
||
${panel("用途说明", `
|
||
<div class="compact-list">
|
||
<div class="list-item"><h4>可检索</h4><p>写作时可被查到并摘要展示。</p></div>
|
||
<div class="list-item"><h4>可生成</h4><p>允许进入本次生成上下文。</p></div>
|
||
<div class="list-item"><h4>关闭生成</h4><p>只做参考,不进入后续生成。</p></div>
|
||
</div>
|
||
`)}
|
||
</div>
|
||
</div>
|
||
`;
|
||
return `<div class="screen">${pageHead(page)}${workspaceFrame(page.id, content)}</div>`;
|
||
}
|
||
|
||
function renderImportParse(page) {
|
||
const content = `
|
||
<div class="main-grid">
|
||
<div>
|
||
${panel("导入文件", `
|
||
<div class="empty-state" style="min-height:150px">
|
||
<div>
|
||
<h3>雾港旧稿-前四十章.docx</h3>
|
||
<p>4.8 MB · 上传于 16:03 · 当前解析中</p>
|
||
<div class="progress"><span style="width:68%"></span></div>
|
||
<p class="hint">已识别 28 / 42 章,失败 2 章。</p>
|
||
</div>
|
||
</div>
|
||
<div style="height:10px"></div>
|
||
<div class="row"><button class="button">重新上传</button><button class="button">重试失败</button><button class="button danger">取消解析</button><button class="button" data-go="work-full-parse-review">进入审阅</button></div>
|
||
`)}
|
||
<div style="height:12px"></div>
|
||
${panel("章节识别摘要", `
|
||
<div class="grid-4">
|
||
<div class="metric"><p class="metric-label">识别章节</p><div class="metric-value">28</div><div class="metric-foot">已完成</div></div>
|
||
<div class="metric"><p class="metric-label">待处理</p><div class="metric-value">12</div><div class="metric-foot">排队中</div></div>
|
||
<div class="metric"><p class="metric-label">失败章节</p><div class="metric-value">2</div><div class="metric-foot">可重试</div></div>
|
||
<div class="metric"><p class="metric-label">待审阅</p><div class="metric-value">18</div><div class="metric-foot">章节结果</div></div>
|
||
</div>
|
||
`)}
|
||
</div>
|
||
<div>
|
||
${panel("解析进度", `
|
||
<div class="timeline">
|
||
<div class="timeline-item"><span class="timeline-dot ok"></span><div><h4>文件上传完成</h4><p>16:03 · 文件格式可用。</p></div></div>
|
||
<div class="timeline-item"><span class="timeline-dot ok"></span><div><h4>章节识别完成</h4><p>识别到 42 个章节标题。</p></div></div>
|
||
<div class="timeline-item"><span class="timeline-dot warn"></span><div><h4>内容解析进行中</h4><p>第 29 章正在处理,可稍后查看。</p></div></div>
|
||
<div class="timeline-item"><span class="timeline-dot"></span><div><h4>等待章节审阅</h4><p>解析完成后进入全书解析审阅。</p></div></div>
|
||
</div>
|
||
`)}
|
||
</div>
|
||
</div>
|
||
`;
|
||
return `<div class="screen">${pageHead(page)}${workspaceFrame(page.id, content)}</div>`;
|
||
}
|
||
|
||
function renderFullParseReview(page) {
|
||
const rows = [
|
||
["第 1 章 雾港来客", "已解析", "人物 3 · 地点 2", "低", "可确认"],
|
||
["第 2 章 码头上的黑伞", "已解析", "人物 2 · 事件 1", "中", "可确认"],
|
||
["第 3 章 旧钟楼", "待确认", "地点 1 · 事件 2", "高", "需处理"],
|
||
["第 18 章 潮汐表", "失败", "无", "未知", "可重试"]
|
||
].map((row) => {
|
||
const canConfirm = row[4] === "可确认";
|
||
return `
|
||
<tr>
|
||
<td><strong>${row[0]}</strong></td>
|
||
<td>${badge(row[1], row[1] === "失败" ? "danger" : row[1] === "待确认" ? "warn" : "ok")}</td>
|
||
<td>${row[2]}</td>
|
||
<td>${badge(row[3], row[3] === "高" ? "danger" : row[3] === "中" ? "warn" : "")}</td>
|
||
<td>${row[4]}</td>
|
||
<td><div class="row"><button class="button" ${canConfirm ? "" : "disabled"}>${canConfirm ? "确认" : "确认禁用"}</button><button class="button">驳回</button><button class="button">重试</button></div></td>
|
||
</tr>
|
||
`;
|
||
}).join("");
|
||
const content = `
|
||
${panel("章节审阅表", `<div class="table-wrap"><table><thead><tr><th>章节</th><th>解析状态</th><th>识别结果</th><th>风险</th><th>可确认性</th><th>操作</th></tr></thead><tbody>${rows}</tbody></table></div>`, `<button class="button primary">确认 2 个可确认章节</button>`)}
|
||
<div style="height:12px"></div>
|
||
<div class="grid-3">
|
||
${panel("章节详情", `<div class="suggestion-box"><p class="suggestion-text">第 3 章识别到“旧钟楼位置”与已有知识冲突,确认按钮已禁用。确认其他可确认章节只会产生待确认知识草稿,不会直接写入作品知识。</p></div>`)}
|
||
${panel("风险分布", `<div class="score-grid"><div class="score"><strong>12</strong><span>低风险</span></div><div class="score"><strong>4</strong><span>中风险</span></div><div class="score"><strong>1</strong><span>高风险</span></div></div>`)}
|
||
${panel("处理结果", `<div class="compact-list"><div class="list-item"><h4>已确认章节</h4><p>确认后进入正文和章节结果历史。</p></div><div class="list-item"><h4>知识草稿</h4><p>仍需在知识与一致性中确认。</p></div></div>`)}
|
||
</div>
|
||
`;
|
||
return `<div class="screen">${pageHead(page)}${workspaceFrame(page.id, content)}</div>`;
|
||
}
|
||
|
||
function renderExportDelivery(page) {
|
||
const content = `
|
||
<div class="main-grid">
|
||
<div>
|
||
${panel("导出配置", `
|
||
<div class="form-grid three">
|
||
<div class="control"><label>导出类型</label><select><option>正文</option><option>设定</option><option>作品知识</option></select></div>
|
||
<div class="control"><label>范围</label><select><option>全部章节</option><option>当前章节</option><option>已选章节</option></select></div>
|
||
<div class="control"><label>格式</label><select><option>docx</option><option>txt</option><option>md</option><option>zip</option></select></div>
|
||
<div class="control"><label>预计字数</label><input value="128430" /></div>
|
||
<div class="control"><label>预计大小</label><input value="2.4 MB" /></div>
|
||
<div class="control"><label>授权限制</label><input value="旧报纸剪辑不可导出" /></div>
|
||
</div>
|
||
<div style="height:10px"></div>
|
||
<div class="row"><button class="button primary">创建导出任务</button><button class="button">查看最近导出任务</button></div>
|
||
`)}
|
||
</div>
|
||
<div>
|
||
${panel("授权限制提示", `
|
||
<div class="compact-list">
|
||
<div class="list-item"><h4>市场知识库限制</h4><p>旧报纸剪辑来源已下架,不能包含在导出范围。</p></div>
|
||
<div class="list-item"><h4>第三方来源</h4><p>港务资料库仅允许内部参考,导出时只保留作品正文。</p></div>
|
||
</div>
|
||
`)}
|
||
</div>
|
||
</div>
|
||
<div style="height:12px"></div>
|
||
${panel("导出任务", `
|
||
<div class="table-wrap"><table><thead><tr><th>任务</th><th>范围</th><th>格式</th><th>状态</th><th>下载有效期</th><th>操作</th></tr></thead><tbody>
|
||
<tr><td>正文导出 16:12</td><td>全部章节</td><td>docx</td><td>${badge("完成", "ok")}</td><td>23 小时</td><td><button class="button primary">下载</button></td></tr>
|
||
<tr><td>作品知识导出 14:30</td><td>人物、地点、事件</td><td>zip</td><td>${badge("失败", "danger")}</td><td>-</td><td><button class="button">重试</button></td></tr>
|
||
<tr><td>设定导出 13:05</td><td>规划项</td><td>md</td><td>${badge("准备中", "warn")}</td><td>-</td><td><button class="button" disabled>等待</button></td></tr>
|
||
</tbody></table></div>
|
||
`)}
|
||
`;
|
||
return `<div class="screen">${pageHead(page)}${workspaceFrame(page.id, content)}</div>`;
|
||
}
|
||
|
||
function renderRecordsUsage(page) {
|
||
const rows = [
|
||
["16:20", "续写", "第 3 章", "悬疑续写助手", "候选可处理", "3,820", "已结算"],
|
||
["16:05", "一致性检查", "第 1-3 章", "长篇一致性检查", "有问题", "1,440", "已结算"],
|
||
["15:42", "保存正文", "第 3 章", "-", "成功", "0", "无消耗"],
|
||
["14:58", "规划候选", "第 4 章", "章节规划助手", "失败", "0", "已返还"]
|
||
].map((row) => `
|
||
<tr>
|
||
<td>${row[0]}</td><td>${row[1]}</td><td>${row[2]}</td><td>${row[3]}</td>
|
||
<td>${badge(row[4], row[4] === "失败" ? "danger" : row[4].includes("问题") ? "warn" : "ok")}</td>
|
||
<td>${row[5]}</td><td>${row[6]}</td>
|
||
<td><div class="row"><button class="button">详情</button><button class="button">进入章节</button></div></td>
|
||
</tr>
|
||
`).join("");
|
||
const content = `
|
||
<div class="grid-4" style="margin-bottom:12px">
|
||
<div class="metric"><p class="metric-label">今日 Token</p><div class="metric-value">8,912</div><div class="metric-foot">当前作品</div></div>
|
||
<div class="metric"><p class="metric-label">生成记录</p><div class="metric-value">16</div><div class="metric-foot">近 7 天</div></div>
|
||
<div class="metric"><p class="metric-label">失败任务</p><div class="metric-value">2</div><div class="metric-foot">可回到工作面重试</div></div>
|
||
<div class="metric"><p class="metric-label">待结算</p><div class="metric-value">1</div><div class="metric-foot">等待网关返回</div></div>
|
||
</div>
|
||
${panel("记录筛选", `
|
||
<div class="filter-grid" style="grid-template-columns: repeat(5,minmax(120px,1fr)) auto">
|
||
<div class="control"><label>时间</label><select><option>今天</option><option>近 7 天</option><option>近 30 天</option></select></div>
|
||
<div class="control"><label>动作</label><select><option>全部动作</option><option>续写</option><option>检查</option><option>规划</option></select></div>
|
||
<div class="control"><label>状态</label><select><option>全部状态</option><option>成功</option><option>失败</option><option>待结算</option></select></div>
|
||
<div class="control"><label>智能体</label><select><option>全部智能体</option><option>悬疑续写助手</option></select></div>
|
||
<div class="control"><label>来源</label><select><option>全部来源</option><option>雾港世界设定</option></select></div>
|
||
<button class="button">筛选</button>
|
||
</div>
|
||
`)}
|
||
<div style="height:12px"></div>
|
||
${panel("生成与任务记录", `<div class="table-wrap"><table><thead><tr><th>时间</th><th>动作</th><th>章节</th><th>智能体摘要</th><th>状态</th><th>Token</th><th>结算</th><th>操作</th></tr></thead><tbody>${rows}</tbody></table></div>`, `<button class="button" data-go="work-writing-desk">回到写作台</button>`)}
|
||
`;
|
||
return `<div class="screen">${pageHead(page)}${workspaceFrame(page.id, content)}</div>`;
|
||
}
|
||
|
||
function renderSettings(page) {
|
||
const content = `
|
||
<div class="main-grid">
|
||
<div>
|
||
${panel("基础信息", `
|
||
<div class="form-grid">
|
||
<div class="control"><label>标题</label><input value="雾港异闻录" /></div>
|
||
<div class="control"><label>类型</label><select><option>都市奇幻</option><option>悬疑</option></select></div>
|
||
<div class="control"><label>目标字数</label><input value="350000" /></div>
|
||
<div class="control"><label>创作状态</label><select><option>连载中</option><option>草稿</option><option>归档</option></select></div>
|
||
<div class="control" style="grid-column:1 / -1"><label>简介</label><textarea>一名档案修复师回到雾港,发现父亲留下的旧钟楼记录与多年前失踪案有关。</textarea></div>
|
||
</div>
|
||
<div style="height:10px"></div>
|
||
<button class="button primary">保存基础信息</button>
|
||
`)}
|
||
<div style="height:12px"></div>
|
||
${panel("创作偏好摘要", `
|
||
<div class="compact-list">
|
||
<div class="list-item"><h4>默认写作方向</h4><p>悬疑递进、克制描写、少解释。</p></div>
|
||
<div class="list-item"><h4>当前来源状态</h4><p>2 个来源需要处理;不会自动写入作品事实。</p></div>
|
||
<div class="list-item"><h4>最近导出</h4><p>今天 12:00 生成正文导出任务,可在导出交付页查看。</p></div>
|
||
</div>
|
||
`)}
|
||
</div>
|
||
<div>
|
||
${panel("作品状态", `
|
||
<div class="compact-list">
|
||
<div class="list-item"><h4>当前状态</h4><p>正常 · 可编辑 · 可导出。</p></div>
|
||
<div class="list-item"><h4>归档</h4><p>归档后默认列表隐藏,可从设置恢复。</p><button class="button warn" style="margin-top:8px">归档作品</button></div>
|
||
<div class="list-item"><h4>恢复</h4><p>归档作品可恢复到正常状态。</p><button class="button" style="margin-top:8px">恢复作品</button></div>
|
||
</div>
|
||
`)}
|
||
<div style="height:12px"></div>
|
||
${panel("危险操作区", `
|
||
<div class="danger-zone list-item">
|
||
<h4>删除作品</h4>
|
||
<p>删除前需要预览正文、知识、绑定、市场发布和导出建议。当前存在 1 个导出任务和 2 个来源异常,删除受限。</p>
|
||
<div class="row" style="margin-top:8px"><button class="button">预览删除影响</button><button class="button danger" disabled>删除作品</button></div>
|
||
</div>
|
||
`)}
|
||
</div>
|
||
</div>
|
||
`;
|
||
return `<div class="screen">${pageHead(page)}${workspaceFrame(page.id, content)}</div>`;
|
||
}
|
||
|
||
const renderers = {
|
||
"user-my-works": renderMyWorks,
|
||
"work-create-import-entry": renderCreateImport,
|
||
"work-workspace-shell": renderWorkspaceShell,
|
||
"work-writing-desk": renderWritingDesk,
|
||
"work-suggestion-panel": renderSuggestionPanel,
|
||
"work-planning-desk": renderPlanningDesk,
|
||
"work-knowledge-consistency": renderKnowledgeConsistency,
|
||
"work-knowledge-draft-detail": renderKnowledgeDraftDetail,
|
||
"work-agent-association": renderAgentAssociation,
|
||
"work-knowledge-source": renderKnowledgeSource,
|
||
"work-import-parse": renderImportParse,
|
||
"work-full-parse-review": renderFullParseReview,
|
||
"work-export-delivery": renderExportDelivery,
|
||
"work-records-usage": renderRecordsUsage,
|
||
"work-settings": renderSettings
|
||
};
|
||
|
||
function render() {
|
||
const requested = location.hash.replace("#", "");
|
||
const page = pages.find((item) => item.id === requested) || pages[0];
|
||
if (page.id !== requested) {
|
||
history.replaceState(null, "", `#${page.id}`);
|
||
}
|
||
renderNav(page.id);
|
||
document.getElementById("app").innerHTML = renderers[page.id](page);
|
||
document.getElementById("app").focus({ preventScroll: true });
|
||
}
|
||
|
||
document.addEventListener("click", (event) => {
|
||
const target = event.target.closest("[data-go]");
|
||
if (!target) return;
|
||
go(target.dataset.go);
|
||
});
|
||
|
||
window.addEventListener("hashchange", render);
|
||
render();
|
||
</script>
|
||
</body>
|
||
</html>
|