Feat/commercial saas polish #12

Merged
stringadmin merged 6 commits from feat/commercial-saas-polish into master 2026-06-04 10:09:15 +00:00
4 changed files with 434 additions and 101 deletions
Showing only changes of commit b08a7918da - Show all commits
+97 -95
View File
@@ -362,109 +362,111 @@ function ScriptTokensPage() {
<div className="script-eval-v5-page"> <div className="script-eval-v5-page">
{/* Left Panel */} {/* Left Panel */}
<aside className="script-eval-v5-left"> <aside className="script-eval-v5-left">
<div className="script-eval-v5-lp-section"> <div className="script-eval-v5-left-main">
<div className="script-eval-v5-lp-label"></div> <div className="script-eval-v5-lp-section">
<div <div className="script-eval-v5-lp-label"></div>
className="script-eval-v5-upload-zone" <div
role="button" className="script-eval-v5-upload-zone"
tabIndex={0} role="button"
onClick={() => fileInputRef.current?.click()} tabIndex={0}
onKeyDown={uploadKeyDown} onClick={() => fileInputRef.current?.click()}
> onKeyDown={uploadKeyDown}
{uploadedFile ? ( >
<div className="script-eval-v5-upload-done is-show"> {uploadedFile ? (
<CheckCircleFilled /> <div className="script-eval-v5-upload-done is-show">
<span className="script-eval-v5-uf-meta"> <CheckCircleFilled />
<span className="script-eval-v5-uf-name">{uploadedFile.name}</span> <span className="script-eval-v5-uf-meta">
<span className="script-eval-v5-uf-size">{formatFileSize(uploadedFile.size)}</span> <span className="script-eval-v5-uf-name">{uploadedFile.name}</span>
</span> <span className="script-eval-v5-uf-size">{formatFileSize(uploadedFile.size)}</span>
<span className="script-eval-v5-uf-re" onClick={(e) => { e.stopPropagation(); handleReset(); }}> </span>
<span className="script-eval-v5-uf-re" onClick={(e) => { e.stopPropagation(); handleReset(); }}>
</span>
</div> </span>
) : ( </div>
<> ) : (
<div className="script-eval-v5-upload-icon"><UploadOutlined /></div> <>
<div className="script-eval-v5-upload-text"></div> <div className="script-eval-v5-upload-icon"><UploadOutlined /></div>
<button type="button" className="script-eval-v5-upload-btn" onClick={(e) => { e.stopPropagation(); fileInputRef.current?.click(); }}> <div className="script-eval-v5-upload-text"></div>
<UploadOutlined /> <button type="button" className="script-eval-v5-upload-btn" onClick={(e) => { e.stopPropagation(); fileInputRef.current?.click(); }}>
</button> <UploadOutlined />
<div className="script-eval-v5-upload-hint">{TEXT_FILE_HINT}</div> </button>
</> <div className="script-eval-v5-upload-hint">{TEXT_FILE_HINT}</div>
)} </>
)}
</div>
<input ref={fileInputRef} type="file" accept={TEXT_FILE_ACCEPT} style={{ display: "none" }} onChange={handleFileUpload} />
</div> </div>
<input ref={fileInputRef} type="file" accept={TEXT_FILE_ACCEPT} style={{ display: "none" }} onChange={handleFileUpload} />
</div>
<div className="script-eval-v5-lp-section"> <div className="script-eval-v5-lp-section">
<div className="script-eval-v5-lp-label">AI </div> <div className="script-eval-v5-lp-label">AI </div>
<div className="script-eval-v5-info-grid"> <div className="script-eval-v5-info-grid">
{!result ? ( {!result ? (
<div className="script-eval-v5-info-empty"></div> <div className="script-eval-v5-info-empty"></div>
) : ( ) : (
<> <>
<div className="script-eval-v5-info-item"> <div className="script-eval-v5-info-item">
<span className="script-eval-v5-info-key"></span> <span className="script-eval-v5-info-key"></span>
<span className="script-eval-v5-info-val"><span className="script-eval-v5-info-tag">{result.totalScore} · {grade}</span></span> <span className="script-eval-v5-info-val"><span className="script-eval-v5-info-tag">{result.totalScore} · {grade}</span></span>
</div> </div>
<div className="script-eval-v5-info-item"> <div className="script-eval-v5-info-item">
<span className="script-eval-v5-info-key"></span> <span className="script-eval-v5-info-key"></span>
<span className="script-eval-v5-info-val">{script.length} </span> <span className="script-eval-v5-info-val">{script.length} </span>
</div> </div>
<div className="script-eval-v5-info-item"> <div className="script-eval-v5-info-item">
<span className="script-eval-v5-info-key"></span> <span className="script-eval-v5-info-key"></span>
<span className="script-eval-v5-info-val">{new Date().toLocaleDateString("zh-CN")}</span> <span className="script-eval-v5-info-val">{new Date().toLocaleDateString("zh-CN")}</span>
</div> </div>
<div className="script-eval-v5-info-item"> <div className="script-eval-v5-info-item">
<span className="script-eval-v5-info-key"></span> <span className="script-eval-v5-info-key"></span>
<span className="script-eval-v5-info-val">{beatPct}%</span> <span className="script-eval-v5-info-val">{beatPct}%</span>
</div> </div>
</> </>
)} )}
</div>
</div> </div>
</div>
<div className="script-eval-v5-lp-section is-fill"> <div className="script-eval-v5-lp-section is-fill">
<div className="script-eval-v5-lp-label"></div> <div className="script-eval-v5-lp-label"></div>
<div className="script-eval-v5-history-list"> <div className="script-eval-v5-history-list">
{!session ? ( {!session ? (
<div className="script-eval-v5-history-empty"></div> <div className="script-eval-v5-history-empty"></div>
) : history.length === 0 ? ( ) : history.length === 0 ? (
<div className="script-eval-v5-history-empty"></div> <div className="script-eval-v5-history-empty"></div>
) : ( ) : (
history.map((item, i) => ( history.map((item, i) => (
<div key={i} className={`script-eval-v5-history-item${i === 0 ? " is-active" : ""}`}> <div key={i} className={`script-eval-v5-history-item${i === 0 ? " is-active" : ""}`}>
<div className="script-eval-v5-hi-left"> <div className="script-eval-v5-hi-left">
<div className="script-eval-v5-hi-name">{item.name}</div> <div className="script-eval-v5-hi-name">{item.name}</div>
<div className="script-eval-v5-hi-date">{item.date}</div> <div className="script-eval-v5-hi-date">{item.date}</div>
<div className="script-eval-v5-hi-bar"> <div className="script-eval-v5-hi-bar">
<div className="script-eval-v5-hi-bar-fill" style={{ width: `${Math.min(92, (item.score / 100) * 100)}%` }} /> <div className="script-eval-v5-hi-bar-fill" style={{ width: `${Math.min(92, (item.score / 100) * 100)}%` }} />
</div>
</div>
<div className="script-eval-v5-hi-right">
<div className={`script-eval-v5-hi-score${item.score >= 90 ? " is-green" : ""}`}>{item.score}</div>
<div className="script-eval-v5-hi-grade">{item.grade}</div>
</div> </div>
</div> </div>
<div className="script-eval-v5-hi-right"> ))
<div className={`script-eval-v5-hi-score${item.score >= 90 ? " is-green" : ""}`}>{item.score}</div> )}
<div className="script-eval-v5-hi-grade">{item.grade}</div> </div>
</div>
</div>
))
)}
</div> </div>
</div>
<div className="script-eval-v5-lp-bottom"> <div className="script-eval-v5-lp-bottom">
<button <button
type="button" type="button"
className="script-eval-v5-eval-btn" className="script-eval-v5-eval-btn"
disabled={loading || !hasContent} disabled={loading || !hasContent}
onClick={() => void handleEvaluate()} onClick={() => void handleEvaluate()}
> >
{loading ? <LoadingOutlined /> : <ThunderboltOutlined />} {loading ? <LoadingOutlined /> : <ThunderboltOutlined />}
<span>{loading ? "评测中..." : "开始评测"}</span> <span>{loading ? "评测中..." : "开始评测"}</span>
</button> </button>
<button type="button" className="script-eval-v5-export-btn" disabled={!result} onClick={handleExportMarkdown}> <button type="button" className="script-eval-v5-export-btn" disabled={!result} onClick={handleExportMarkdown}>
<DownloadOutlined /> <DownloadOutlined />
<span></span> <span></span>
</button> </button>
</div>
</div> </div>
</aside> </aside>
@@ -277,9 +277,8 @@ function TokenUsagePage({
) : null} ) : null}
<section className="management-metric-cards" aria-label="关键指标"> <section className="management-metric-cards" aria-label="关键指标">
{metricCards.map((card, index) => ( {metricCards.map((card) => (
<article key={card.key} className={`management-metric-card is-${card.tone}`}> <article key={card.key} className={`management-metric-card is-${card.tone}`}>
<span className="management-metric-card__index">{String(index + 1).padStart(2, "0")}</span>
<span className="management-metric-card__label">{card.label}</span> <span className="management-metric-card__label">{card.label}</span>
<strong className="management-metric-card__value">{card.value}</strong> <strong className="management-metric-card__value">{card.value}</strong>
<span className="management-metric-card__hint">{card.hint}</span> <span className="management-metric-card__hint">{card.hint}</span>
+42 -4
View File
@@ -8008,9 +8008,8 @@
} }
.product-clone-page[data-tool="clone"] .clone-ai-logo { .product-clone-page[data-tool="clone"] .clone-ai-logo {
position: sticky; position: static;
top: 0; z-index: auto;
z-index: 3;
margin: -18px -18px 2px; margin: -18px -18px 2px;
padding: 16px 18px 14px; padding: 16px 18px 14px;
border-bottom-color: var(--ecm-line); border-bottom-color: var(--ecm-line);
@@ -8513,7 +8512,7 @@
} }
.product-clone-page[data-tool="clone"] .clone-ai-logo { .product-clone-page[data-tool="clone"] .clone-ai-logo {
margin: -14px -14px 0; margin: 0;
padding: 14px 54px 12px 14px; padding: 14px 54px 12px 14px;
} }
@@ -8792,3 +8791,42 @@
padding-top: 14px; padding-top: 14px;
} }
} }
/* Mobile clone header alignment: keep the tool title in normal flow, but attach it to the top nav rhythm. */
@media (max-width: 900px) {
.product-clone-page[data-tool="clone"] {
padding-top: 59px;
}
.product-clone-page[data-tool="clone"] > .product-clone-shell {
min-height: calc(100% - 59px);
}
.product-clone-page[data-tool="clone"] .clone-ai-panel {
padding-top: 0;
}
.product-clone-page[data-tool="clone"] .clone-ai-logo {
margin: 0 -18px 2px;
}
}
@media (max-width: 620px) {
.product-clone-page[data-tool="clone"] .clone-ai-panel {
padding: 0 14px 14px;
}
.product-clone-page[data-tool="clone"] .clone-ai-logo {
margin: 0 -14px 0;
}
}
@media (max-width: 480px) {
.product-clone-page[data-tool="clone"] {
padding-top: 59px;
}
.product-clone-page[data-tool="clone"] > .product-clone-shell {
min-height: calc(100% - 59px);
}
}
+294
View File
@@ -3421,3 +3421,297 @@
font-size: 13px; font-size: 13px;
} }
} }
/* Script review left panel overflow guard: keep actions available while history remains scrollable. */
.script-eval-v5-left {
overflow: hidden;
}
.script-eval-v5-left-main {
display: flex;
flex: 1 1 auto;
flex-direction: column;
min-height: 0;
overflow-x: hidden;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: rgb(0 255 136 / 35%) transparent;
}
.script-eval-v5-left-main::-webkit-scrollbar,
.script-eval-v5-history-list::-webkit-scrollbar {
width: 6px;
}
.script-eval-v5-left-main::-webkit-scrollbar-track,
.script-eval-v5-history-list::-webkit-scrollbar-track {
background: transparent;
}
.script-eval-v5-left-main::-webkit-scrollbar-thumb,
.script-eval-v5-history-list::-webkit-scrollbar-thumb {
border-radius: 999px;
background: rgb(0 255 136 / 28%);
}
.script-eval-v5-left-main .script-eval-v5-lp-section.is-fill {
flex: 0 0 auto;
min-height: 210px;
}
.script-eval-v5-left-main .script-eval-v5-history-list {
min-height: 128px;
max-height: clamp(160px, 28vh, 300px);
overflow-y: auto;
}
.script-eval-v5-lp-bottom {
position: static;
z-index: auto;
flex-shrink: 0;
margin-top: 0;
}
@media (max-height: 820px) and (min-width: 901px) {
.script-eval-v5-left-main .script-eval-v5-lp-section.is-fill {
flex-basis: auto;
min-height: 190px;
}
.script-eval-v5-left-main .script-eval-v5-history-list {
min-height: 118px;
max-height: clamp(142px, 23vh, 220px);
}
}
@media (max-width: 900px) {
.script-eval-v5-left-main {
overscroll-behavior: contain;
}
}
@media (max-width: 680px) {
.script-eval-v5-left {
overflow: visible;
}
.script-eval-v5-left-main {
flex: 0 0 auto;
overflow: visible;
}
.script-eval-v5-left-main .script-eval-v5-lp-section.is-fill {
min-height: 224px;
}
.script-eval-v5-left-main .script-eval-v5-history-list {
min-height: 132px;
max-height: min(260px, 42vh);
}
.script-eval-v5-history-empty {
min-height: 118px;
}
}
/* Final commercial polish for the script scoring workspace. */
.script-eval-v5 {
background:
radial-gradient(circle at 12% 0%, rgb(0 255 136 / 5%), transparent 28%),
linear-gradient(180deg, #0d1010 0%, #090b0b 100%);
}
.script-eval-v5-page {
background:
linear-gradient(90deg, rgb(0 255 136 / 4%), transparent 24%),
linear-gradient(180deg, rgb(255 255 255 / 1.8%), transparent 180px);
}
.script-eval-v5-left {
background:
linear-gradient(180deg, rgb(255 255 255 / 4%), transparent 180px),
linear-gradient(90deg, rgb(0 255 136 / 4%), transparent 32%),
var(--v5-panel);
}
.script-eval-v5-left-main {
scroll-padding-block: 18px;
}
.script-eval-v5-left-main .script-eval-v5-lp-section {
flex-shrink: 0;
padding-inline: 22px;
background:
linear-gradient(180deg, rgb(255 255 255 / 1.8%), transparent 80px);
}
.script-eval-v5-left-main .script-eval-v5-lp-section + .script-eval-v5-lp-section {
box-shadow: inset 0 1px 0 rgb(255 255 255 / 2.5%);
}
.script-eval-v5-lp-label {
color: #91a09b;
}
.script-eval-v5-upload-zone {
display: grid;
place-items: center;
overflow: hidden;
isolation: isolate;
}
.script-eval-v5-upload-zone::after {
content: "";
position: absolute;
inset: 1px;
z-index: -1;
border-radius: inherit;
background:
radial-gradient(circle at 50% 18%, rgb(0 255 136 / 11%), transparent 38%),
linear-gradient(180deg, rgb(255 255 255 / 2%), transparent 60%);
opacity: 0.78;
pointer-events: none;
}
.script-eval-v5-upload-zone:focus-visible {
outline: 2px solid rgb(0 255 136 / 42%);
outline-offset: 3px;
}
.script-eval-v5.is-ready .script-eval-v5-upload-zone,
.script-eval-v5.is-complete .script-eval-v5-upload-zone {
border-color: rgb(0 255 136 / 28%);
background:
linear-gradient(180deg, rgb(0 255 136 / 8%), rgb(255 255 255 / 2.5%)),
rgb(255 255 255 / 2.8%);
}
.script-eval-v5-upload-done {
width: min(100%, 320px);
padding: 14px 14px;
box-shadow: inset 0 1px 0 rgb(255 255 255 / 8%);
}
.script-eval-v5-info-grid {
display: grid;
grid-template-columns: 1fr;
}
.script-eval-v5-info-item {
min-height: 42px;
box-shadow: inset 0 1px 0 rgb(255 255 255 / 3%);
}
.script-eval-v5-info-empty,
.script-eval-v5-history-empty {
color: #82918c;
background:
linear-gradient(180deg, rgb(255 255 255 / 3.2%), rgb(255 255 255 / 1.8%));
}
.script-eval-v5-left-main .script-eval-v5-lp-section.is-fill {
background:
linear-gradient(180deg, rgb(0 255 136 / 3.4%), transparent 92px),
linear-gradient(180deg, rgb(255 255 255 / 1.8%), transparent);
}
.script-eval-v5-history-list {
padding: 2px 8px 2px 0;
}
.script-eval-v5-history-item {
min-height: 68px;
box-shadow: inset 0 1px 0 rgb(255 255 255 / 3%);
}
.script-eval-v5-lp-bottom {
padding: 18px 22px 22px;
background:
linear-gradient(180deg, rgb(255 255 255 / 2.2%), transparent 60px),
#111414;
box-shadow: inset 0 1px 0 rgb(255 255 255 / 3.5%);
}
.script-eval-v5-export-btn {
border-color: rgb(255 255 255 / 7%);
background:
linear-gradient(180deg, rgb(255 255 255 / 3.5%), rgb(255 255 255 / 1.8%)),
#111414;
color: #7f8d88;
}
.script-eval-v5-export-btn:not(:disabled):hover {
border-color: rgb(0 255 136 / 22%);
color: #c7d5d0;
background:
linear-gradient(180deg, rgb(0 255 136 / 8%), rgb(255 255 255 / 2%)),
#111414;
}
.script-eval-v5-eval-btn:disabled,
.script-eval-v5-export-btn:disabled {
opacity: 0.48;
cursor: not-allowed;
}
.script-eval-v5-right-topbar {
backdrop-filter: blur(14px);
background:
linear-gradient(180deg, rgb(18 22 21 / 92%), rgb(12 14 14 / 88%));
}
.script-eval-v5-right-content:not(.is-report) {
background:
radial-gradient(circle at 50% 43%, rgb(0 255 136 / 5%), transparent 32%),
linear-gradient(180deg, transparent, rgb(0 0 0 / 12%));
}
.script-eval-v5-upload-card-title {
color: #f0fff8;
}
.script-eval-v5-upload-card-desc {
max-width: 540px;
color: #96a5a0;
}
.script-eval-v5-statusbar {
background:
linear-gradient(180deg, rgb(17 20 20 / 84%), rgb(10 12 12 / 92%));
}
@media (max-height: 760px) and (min-width: 901px) {
.script-eval-v5-left-main .script-eval-v5-lp-section {
padding-block: 12px;
}
.script-eval-v5-upload-zone {
min-height: 156px;
}
.script-eval-v5-left-main .script-eval-v5-lp-section.is-fill {
min-height: 176px;
}
.script-eval-v5-left-main .script-eval-v5-history-list {
min-height: 110px;
}
}
@media (max-width: 680px) {
.script-eval-v5-left-main .script-eval-v5-lp-section {
padding-inline: 16px;
}
.script-eval-v5-upload-zone {
min-height: 164px;
}
.script-eval-v5-lp-bottom {
padding: 14px 16px 18px;
}
.script-eval-v5-right-content:not(.is-report) {
padding-top: 22px;
}
}