更新
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"total": 0,
|
"total": 1,
|
||||||
"byTemplate": {
|
"byTemplate": {
|
||||||
"lobster-birth": 1
|
"lobster-birth": 2
|
||||||
},
|
},
|
||||||
"updatedAt": "2026-03-17T09:44:46.005Z"
|
"updatedAt": "2026-03-18T03:21:37.604Z"
|
||||||
}
|
}
|
||||||
685
src/index.css
685
src/index.css
@@ -41,6 +41,10 @@ textarea {
|
|||||||
font: inherit;
|
font: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
display: block;
|
display: block;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
@@ -62,13 +66,6 @@ code {
|
|||||||
linear-gradient(180deg, rgba(255, 255, 255, 0.58), rgba(255, 255, 255, 0.32));
|
linear-gradient(180deg, rgba(255, 255, 255, 0.58), rgba(255, 255, 255, 0.32));
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-shell--empty {
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
background: #111827;
|
|
||||||
color: #f8fafc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page {
|
.page {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 1440px;
|
max-width: 1440px;
|
||||||
@@ -79,65 +76,6 @@ code {
|
|||||||
gap: 20px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.studio-header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
justify-content: space-between;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.72);
|
|
||||||
border-radius: 28px;
|
|
||||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.96), rgba(244, 247, 255, 0.88));
|
|
||||||
padding: 22px 24px;
|
|
||||||
box-shadow: var(--shadow-soft);
|
|
||||||
backdrop-filter: blur(18px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.studio-kicker,
|
|
||||||
.section-kicker {
|
|
||||||
margin: 0 0 8px;
|
|
||||||
color: var(--page-accent);
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0.18em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.studio-header h1,
|
|
||||||
.panel-head h2 {
|
|
||||||
margin: 0;
|
|
||||||
color: var(--text-main);
|
|
||||||
font-weight: 800;
|
|
||||||
letter-spacing: -0.03em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.studio-header h1 {
|
|
||||||
font-size: clamp(28px, 4vw, 42px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.studio-copy,
|
|
||||||
.panel-head p {
|
|
||||||
margin: 10px 0 0;
|
|
||||||
color: var(--text-soft);
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.studio-summary {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.studio-summary span {
|
|
||||||
border: 1px solid rgba(91, 108, 255, 0.16);
|
|
||||||
border-radius: 999px;
|
|
||||||
background: rgba(91, 108, 255, 0.08);
|
|
||||||
padding: 10px 14px;
|
|
||||||
color: var(--page-accent);
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel {
|
.panel {
|
||||||
border: 1px solid var(--panel-border);
|
border: 1px solid var(--panel-border);
|
||||||
border-radius: 28px;
|
border-radius: 28px;
|
||||||
@@ -168,7 +106,7 @@ code {
|
|||||||
|
|
||||||
.panel-head--inline {
|
.panel-head--inline {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
@@ -178,7 +116,31 @@ code {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.panel-head h2 {
|
.panel-head h2 {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text-main);
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-strip__summary {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-strip__summary span {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 28px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(91, 108, 255, 0.08);
|
||||||
|
padding: 0 12px;
|
||||||
|
color: var(--page-accent);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel--templates {
|
.panel--templates {
|
||||||
@@ -186,13 +148,22 @@ code {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.template-strip {
|
.template-strip {
|
||||||
display: grid;
|
|
||||||
gap: 16px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
gap: 14px;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
scroll-snap-type: x proximity;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-strip::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.template-card {
|
.template-card {
|
||||||
|
flex: 0 0 min(280px, 84vw);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid rgba(148, 163, 184, 0.24);
|
border: 1px solid rgba(148, 163, 184, 0.24);
|
||||||
border-radius: 24px;
|
border-radius: 24px;
|
||||||
@@ -200,6 +171,7 @@ code {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
scroll-snap-align: start;
|
||||||
transition:
|
transition:
|
||||||
transform 0.22s ease,
|
transform 0.22s ease,
|
||||||
box-shadow 0.22s ease,
|
box-shadow 0.22s ease,
|
||||||
@@ -219,7 +191,6 @@ code {
|
|||||||
|
|
||||||
.template-card__preview {
|
.template-card__preview {
|
||||||
position: relative;
|
position: relative;
|
||||||
aspect-ratio: 16 / 5;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: linear-gradient(135deg, #e9eeff, #f8faff);
|
background: linear-gradient(135deg, #e9eeff, #f8faff);
|
||||||
}
|
}
|
||||||
@@ -232,17 +203,18 @@ code {
|
|||||||
background: linear-gradient(180deg, transparent, rgba(15, 23, 42, 0.16));
|
background: linear-gradient(180deg, transparent, rgba(15, 23, 42, 0.16));
|
||||||
}
|
}
|
||||||
|
|
||||||
.template-card__preview img {
|
.template-card__preview--component {
|
||||||
width: 100%;
|
display: grid;
|
||||||
height: 100%;
|
place-items: center;
|
||||||
object-fit: cover;
|
padding: 16px;
|
||||||
|
background: linear-gradient(135deg, #e9eeff, #f8faff);
|
||||||
}
|
}
|
||||||
|
|
||||||
.template-card__body {
|
.template-card__body {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 14px;
|
gap: 12px;
|
||||||
padding: 14px 16px 16px;
|
padding: 14px 16px 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,6 +246,50 @@ code {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.template-card__preview-shell {
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-strip__footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-strip__dots {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-strip__dot {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(91, 108, 255, 0.18);
|
||||||
|
padding: 0;
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(91, 108, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-strip__dot.is-active {
|
||||||
|
width: 24px;
|
||||||
|
background: linear-gradient(135deg, var(--page-accent), #7f8dff);
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-strip__position {
|
||||||
|
color: var(--text-soft);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
.workspace--studio {
|
.workspace--studio {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
@@ -290,6 +306,10 @@ code {
|
|||||||
gap: 14px;
|
gap: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-grid--mobile {
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.field {
|
.field {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
@@ -353,29 +373,16 @@ code {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-status {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-status span {
|
|
||||||
border-radius: 999px;
|
|
||||||
background: rgba(91, 108, 255, 0.08);
|
|
||||||
padding: 8px 12px;
|
|
||||||
color: var(--page-accent);
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-frame {
|
.preview-frame {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
overflow: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-frame--studio {
|
.preview-frame--studio {
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
border-radius: 26px;
|
border-radius: 26px;
|
||||||
background:
|
background:
|
||||||
radial-gradient(circle at top left, rgba(91, 108, 255, 0.08), transparent 26%),
|
radial-gradient(circle at top left, rgba(91, 108, 255, 0.08), transparent 26%),
|
||||||
@@ -384,88 +391,72 @@ code {
|
|||||||
box-shadow: inset 0 0 0 1px rgba(148, 163, 184, 0.14);
|
box-shadow: inset 0 0 0 1px rgba(148, 163, 184, 0.14);
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-canvas {
|
|
||||||
position: relative;
|
|
||||||
margin: 0 auto;
|
|
||||||
overflow: hidden;
|
|
||||||
border-radius: 24px;
|
|
||||||
background: #f9fafb;
|
|
||||||
box-shadow: 0 24px 44px rgba(64, 78, 150, 0.18);
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-background,
|
|
||||||
.preview-text,
|
|
||||||
.preview-qr,
|
|
||||||
.preview-stats {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-background {
|
|
||||||
inset: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-text {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-qr {
|
|
||||||
border-radius: 14px;
|
|
||||||
background: rgba(255, 255, 255, 0.92);
|
|
||||||
padding: 8px;
|
|
||||||
box-shadow: 0 14px 26px rgba(15, 23, 42, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-stats {
|
|
||||||
border-radius: 999px;
|
|
||||||
background: rgba(255, 255, 255, 0.92);
|
|
||||||
padding: 12px 20px;
|
|
||||||
color: var(--page-rose);
|
|
||||||
font-weight: 800;
|
|
||||||
line-height: 1;
|
|
||||||
box-shadow: 0 12px 24px rgba(15, 23, 42, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-row {
|
.download-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: right;
|
justify-content: flex-end;
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.download-row button {
|
.download-row button,
|
||||||
min-width: 148px;
|
.mobile-preview-actions__primary,
|
||||||
border: 0;
|
.mobile-preview-actions__secondary,
|
||||||
|
.mobile-form-sheet__back,
|
||||||
|
.mobile-form-sheet__close,
|
||||||
|
.mobile-form-sheet__done {
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
cursor: pointer;
|
||||||
|
transition:
|
||||||
|
transform 0.2s ease,
|
||||||
|
box-shadow 0.2s ease,
|
||||||
|
filter 0.2s ease,
|
||||||
|
background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-row button,
|
||||||
|
.mobile-preview-actions__primary,
|
||||||
|
.mobile-form-sheet__done {
|
||||||
|
min-width: 148px;
|
||||||
background: linear-gradient(135deg, var(--page-accent), var(--page-rose));
|
background: linear-gradient(135deg, var(--page-accent), var(--page-rose));
|
||||||
padding: 12px 24px;
|
padding: 12px 24px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 800;
|
|
||||||
letter-spacing: 0.02em;
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: 0 14px 30px rgba(91, 108, 255, 0.24);
|
box-shadow: 0 14px 30px rgba(91, 108, 255, 0.24);
|
||||||
transition:
|
|
||||||
transform 0.2s ease,
|
|
||||||
box-shadow 0.2s ease,
|
|
||||||
filter 0.2s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.download-row button:hover:enabled {
|
.mobile-preview-actions__secondary,
|
||||||
|
.mobile-form-sheet__back,
|
||||||
|
.mobile-form-sheet__close {
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
color: var(--text-main);
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-row button:hover:enabled,
|
||||||
|
.mobile-preview-actions__primary:hover:enabled,
|
||||||
|
.mobile-preview-actions__secondary:hover,
|
||||||
|
.mobile-form-sheet__back:hover,
|
||||||
|
.mobile-form-sheet__close:hover,
|
||||||
|
.mobile-form-sheet__done:hover {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 18px 34px rgba(91, 108, 255, 0.3);
|
box-shadow: 0 18px 34px rgba(91, 108, 255, 0.24);
|
||||||
filter: saturate(1.05);
|
filter: saturate(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.download-row button:disabled {
|
.download-row button:disabled,
|
||||||
|
.mobile-preview-actions__primary:disabled,
|
||||||
|
.mobile-form-sheet__done:disabled {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
transform: none;
|
transform: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mobile-preview-actions {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.error-box {
|
.error-box {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
@@ -476,128 +467,11 @@ code {
|
|||||||
padding: 12px 14px;
|
padding: 12px 14px;
|
||||||
color: #b91c1c;
|
color: #b91c1c;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 900px) {
|
.mobile-form-sheet {
|
||||||
.studio-header {
|
display: none;
|
||||||
flex-direction: row;
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-head--inline {
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.template-strip {
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1080px) {
|
|
||||||
.workspace--studio {
|
|
||||||
grid-template-columns: 300px minmax(0, 1fr);
|
|
||||||
align-items: start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-frame {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-canvas {
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
border-radius: 24px;
|
|
||||||
background: #dbe4ff;
|
|
||||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.45);
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-canvas--tx {
|
|
||||||
background: linear-gradient(180deg, #edf3ff 0%, #dbe7ff 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-background {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: fill;
|
|
||||||
user-select: none;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-background--cover {
|
|
||||||
background-position: center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-text {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 2;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-text--tx {
|
|
||||||
text-shadow: 0 4px 18px rgba(5, 21, 66, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-text--tx-count {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 3;
|
|
||||||
white-space: nowrap;
|
|
||||||
word-break: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-text--layer {
|
|
||||||
z-index: 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-qr {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 2;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-qr--tx {
|
|
||||||
border-radius: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-stats {
|
|
||||||
margin-top: 14px;
|
|
||||||
display: inline-flex;
|
|
||||||
align-self: flex-end;
|
|
||||||
border: 1px solid rgba(99, 102, 241, 0.18);
|
|
||||||
border-radius: 999px;
|
|
||||||
background: rgba(255, 255, 255, 0.92);
|
|
||||||
padding: 10px 14px;
|
|
||||||
color: #ef4444;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 800;
|
|
||||||
}
|
|
||||||
|
|
||||||
.template-card__preview--component {
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
aspect-ratio: auto;
|
|
||||||
padding: 18px;
|
|
||||||
background: linear-gradient(135deg, #e9eeff, #f8faff);
|
|
||||||
}
|
|
||||||
|
|
||||||
.template-card__preview-shell {
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-frame--studio {
|
|
||||||
overflow: auto;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.export-stage {
|
.export-stage {
|
||||||
@@ -608,3 +482,272 @@ code {
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (min-width: 900px) {
|
||||||
|
.panel-head--inline {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-strip {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
overflow: visible;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-card {
|
||||||
|
flex-basis: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1080px) {
|
||||||
|
.workspace--studio {
|
||||||
|
grid-template-columns: 300px minmax(0, 1fr);
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.app-shell {
|
||||||
|
padding: 14px 12px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page--studio {
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
border-radius: 22px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-head {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-head h2 {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel--templates {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-card {
|
||||||
|
flex: 0 0 72vw;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-card__preview--component {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-card__body {
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 12px 14px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-card__title {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-card__tag {
|
||||||
|
padding: 6px 10px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-strip__summary {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-strip__summary span:last-child {
|
||||||
|
color: #7c89c0;
|
||||||
|
background: rgba(124, 137, 192, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-strip {
|
||||||
|
padding-right: 18vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-strip__footer {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel--fields {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-frame--studio {
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-row {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-preview-actions {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 16px;
|
||||||
|
padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-preview-actions__primary,
|
||||||
|
.mobile-preview-actions__secondary {
|
||||||
|
min-height: 52px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 14px 18px;
|
||||||
|
font-size: 16px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-box {
|
||||||
|
border-radius: 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-form-sheet {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 60;
|
||||||
|
display: block;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-form-sheet__backdrop {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(15, 23, 42, 0.38);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.22s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-form-sheet__panel {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: linear-gradient(180deg, #f9fbff 0%, #eef3ff 100%);
|
||||||
|
transform: translateY(100%);
|
||||||
|
transition: transform 0.22s ease;
|
||||||
|
box-shadow: 0 -18px 40px rgba(15, 23, 42, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-form-sheet.is-open {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-form-sheet.is-open .mobile-form-sheet__backdrop {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-form-sheet.is-open .mobile-form-sheet__panel {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-form-sheet__header {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 18px 16px 14px;
|
||||||
|
border-bottom: 1px solid rgba(148, 163, 184, 0.2);
|
||||||
|
background: rgba(255, 255, 255, 0.86);
|
||||||
|
backdrop-filter: blur(18px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-form-sheet__header h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 800;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-form-sheet__back,
|
||||||
|
.mobile-form-sheet__close {
|
||||||
|
min-height: 40px;
|
||||||
|
padding: 0 14px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-form-sheet__body {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 16px 16px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-grid--mobile .field {
|
||||||
|
gap: 10px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-grid--mobile .field input,
|
||||||
|
.form-grid--mobile .field textarea {
|
||||||
|
min-height: 52px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-grid--mobile .field textarea {
|
||||||
|
min-height: 112px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-form-sheet__footer {
|
||||||
|
padding: 14px 16px calc(env(safe-area-inset-bottom, 0px) + 18px);
|
||||||
|
border-top: 1px solid rgba(148, 163, 184, 0.2);
|
||||||
|
background: rgba(255, 255, 255, 0.94);
|
||||||
|
backdrop-filter: blur(18px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-form-sheet__done {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 52px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 479px) {
|
||||||
|
.app-shell {
|
||||||
|
padding-inline: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel,
|
||||||
|
.panel--templates {
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-card {
|
||||||
|
flex-basis: 82vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-strip {
|
||||||
|
padding-right: 20vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-strip__summary {
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-strip__summary span,
|
||||||
|
.template-strip__position {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-frame--studio {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-preview-actions {
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-form-sheet__header,
|
||||||
|
.mobile-form-sheet__body,
|
||||||
|
.mobile-form-sheet__footer {
|
||||||
|
padding-inline: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export const certificateTemplates: CertificateTemplateDefinition[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "lobster-birth-tx",
|
id: "lobster-birth-tx",
|
||||||
name: "小龙虾出生证明腾讯云版",
|
name: "小龙虾出生证明",
|
||||||
width: 1576,
|
width: 1576,
|
||||||
height: 1080,
|
height: 1080,
|
||||||
Component: LobsterBirthTxTemplate,
|
Component: LobsterBirthTxTemplate,
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ const emptyStats: StatsResponse = {
|
|||||||
updatedAt: null,
|
updatedAt: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const previewWidth = 760;
|
const desktopPreviewWidth = 760;
|
||||||
|
const mobileBreakpoint = 768;
|
||||||
const statsUnavailableMessage =
|
const statsUnavailableMessage =
|
||||||
"统计服务未连接,当前数量不会持久化。请同时运行 npm run dev 和 npm run dev:server,或直接运行 npm run dev:all。";
|
"统计服务未连接,当前数量不会持久化。请同时运行 npm run dev 和 npm run dev:server,或直接运行 npm run dev:all。";
|
||||||
const statsIncrementFailedMessage =
|
const statsIncrementFailedMessage =
|
||||||
@@ -59,6 +60,94 @@ function TemplateThumbnail({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function useIsMobile(breakpoint: number) {
|
||||||
|
const [isMobile, setIsMobile] = useState(() => {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return window.innerWidth < breakpoint;
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mediaQuery = window.matchMedia(`(max-width: ${breakpoint - 1}px)`);
|
||||||
|
const sync = () => setIsMobile(mediaQuery.matches);
|
||||||
|
|
||||||
|
sync();
|
||||||
|
mediaQuery.addEventListener("change", sync);
|
||||||
|
|
||||||
|
return () => mediaQuery.removeEventListener("change", sync);
|
||||||
|
}, [breakpoint]);
|
||||||
|
|
||||||
|
return isMobile;
|
||||||
|
}
|
||||||
|
|
||||||
|
function useContainerWidth<T extends HTMLElement>() {
|
||||||
|
const ref = useRef<T | null>(null);
|
||||||
|
const [width, setWidth] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const node = ref.current;
|
||||||
|
|
||||||
|
if (!node) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateWidth = () => {
|
||||||
|
setWidth(node.clientWidth);
|
||||||
|
};
|
||||||
|
|
||||||
|
updateWidth();
|
||||||
|
|
||||||
|
const observer = new ResizeObserver(() => {
|
||||||
|
updateWidth();
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(node);
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return [ref, width] as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
function CertificateFormFields({
|
||||||
|
formData,
|
||||||
|
onChange,
|
||||||
|
className = "form-grid",
|
||||||
|
}: {
|
||||||
|
formData: CertificateFormData;
|
||||||
|
onChange: (key: CertificateFieldKey) => (event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
{certificateFieldOrder.map((key) => (
|
||||||
|
<label key={key} className="field">
|
||||||
|
<span>{certificateFieldLabels[key]}</span>
|
||||||
|
{key === "familyAddress" || key === "birthAddress" ? (
|
||||||
|
<textarea
|
||||||
|
rows={3}
|
||||||
|
value={formData[key]}
|
||||||
|
onChange={onChange(key)}
|
||||||
|
placeholder={`请输入${certificateFieldLabels[key]}`}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<input
|
||||||
|
value={formData[key]}
|
||||||
|
onChange={onChange(key)}
|
||||||
|
placeholder={`请输入${certificateFieldLabels[key]}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function CertificateGenerator() {
|
export function CertificateGenerator() {
|
||||||
const [selectedTemplateId, setSelectedTemplateId] = useState(
|
const [selectedTemplateId, setSelectedTemplateId] = useState(
|
||||||
certificateTemplates[0]?.id ?? "",
|
certificateTemplates[0]?.id ?? "",
|
||||||
@@ -68,7 +157,10 @@ export function CertificateGenerator() {
|
|||||||
const [isExporting, setIsExporting] = useState(false);
|
const [isExporting, setIsExporting] = useState(false);
|
||||||
const [errorMessage, setErrorMessage] = useState("");
|
const [errorMessage, setErrorMessage] = useState("");
|
||||||
const [statsMessage, setStatsMessage] = useState("");
|
const [statsMessage, setStatsMessage] = useState("");
|
||||||
|
const [isMobileFormOpen, setIsMobileFormOpen] = useState(false);
|
||||||
const exportRef = useRef<HTMLDivElement>(null);
|
const exportRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [previewMeasureRef, previewContainerWidth] = useContainerWidth<HTMLDivElement>();
|
||||||
|
const isMobile = useIsMobile(mobileBreakpoint);
|
||||||
|
|
||||||
const selectedTemplate = useMemo(
|
const selectedTemplate = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -80,6 +172,20 @@ export function CertificateGenerator() {
|
|||||||
const qrSource = useMemo(() => getQrSource(), []);
|
const qrSource = useMemo(() => getQrSource(), []);
|
||||||
const currentCount = stats.total + 1;
|
const currentCount = stats.total + 1;
|
||||||
const SelectedTemplateComponent = selectedTemplate.Component;
|
const SelectedTemplateComponent = selectedTemplate.Component;
|
||||||
|
const selectedTemplateIndex = Math.max(
|
||||||
|
certificateTemplates.findIndex((template) => template.id === selectedTemplate.id),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const previewScale = useMemo(() => {
|
||||||
|
const fallbackWidth = isMobile ? 320 : desktopPreviewWidth;
|
||||||
|
const availableWidth = previewContainerWidth || fallbackWidth;
|
||||||
|
const targetWidth = isMobile
|
||||||
|
? Math.max(availableWidth, 280)
|
||||||
|
: Math.min(availableWidth, desktopPreviewWidth);
|
||||||
|
|
||||||
|
return Math.min(targetWidth / selectedTemplate.width, 1);
|
||||||
|
}, [isMobile, previewContainerWidth, selectedTemplate.width]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let active = true;
|
let active = true;
|
||||||
@@ -100,6 +206,29 @@ export function CertificateGenerator() {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isMobile) {
|
||||||
|
setIsMobileFormOpen(false);
|
||||||
|
}
|
||||||
|
}, [isMobile]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof document === "undefined") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { body } = document;
|
||||||
|
const previousOverflow = body.style.overflow;
|
||||||
|
|
||||||
|
if (isMobile && isMobileFormOpen) {
|
||||||
|
body.style.overflow = "hidden";
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
body.style.overflow = previousOverflow;
|
||||||
|
};
|
||||||
|
}, [isMobile, isMobileFormOpen]);
|
||||||
|
|
||||||
const handleChange =
|
const handleChange =
|
||||||
(key: CertificateFieldKey) =>
|
(key: CertificateFieldKey) =>
|
||||||
(event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
(event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||||
@@ -154,6 +283,9 @@ export function CertificateGenerator() {
|
|||||||
<div>
|
<div>
|
||||||
<h2>模板切换</h2>
|
<h2>模板切换</h2>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="template-strip__summary">
|
||||||
|
<span>{`共 ${certificateTemplates.length} 个模板`}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="template-strip">
|
<div className="template-strip">
|
||||||
{certificateTemplates.map((template) => {
|
{certificateTemplates.map((template) => {
|
||||||
@@ -177,7 +309,6 @@ export function CertificateGenerator() {
|
|||||||
<div className="template-card__body">
|
<div className="template-card__body">
|
||||||
<div>
|
<div>
|
||||||
<p className="template-card__title">{template.name}</p>
|
<p className="template-card__title">{template.name}</p>
|
||||||
<p className="template-card__meta">{template.width} x {template.height}</p>
|
|
||||||
</div>
|
</div>
|
||||||
<span className="template-card__tag">{active ? "当前模板" : "点击切换"}</span>
|
<span className="template-card__tag">{active ? "当前模板" : "点击切换"}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -185,6 +316,19 @@ export function CertificateGenerator() {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="template-strip__footer">
|
||||||
|
<div className="template-strip__dots" aria-label="模板位置">
|
||||||
|
{certificateTemplates.map((template, index) => (
|
||||||
|
<button
|
||||||
|
key={template.id}
|
||||||
|
type="button"
|
||||||
|
className={`template-strip__dot ${index === selectedTemplateIndex ? "is-active" : ""}`}
|
||||||
|
onClick={() => setSelectedTemplateId(template.id)}
|
||||||
|
aria-label={`切换到模板 ${index + 1}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="workspace workspace--studio">
|
<section className="workspace workspace--studio">
|
||||||
@@ -192,27 +336,7 @@ export function CertificateGenerator() {
|
|||||||
<div className="panel-head">
|
<div className="panel-head">
|
||||||
<h2>龙虾信息填写</h2>
|
<h2>龙虾信息填写</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-grid">
|
<CertificateFormFields formData={formData} onChange={handleChange} />
|
||||||
{certificateFieldOrder.map((key) => (
|
|
||||||
<label key={key} className="field">
|
|
||||||
<span>{certificateFieldLabels[key]}</span>
|
|
||||||
{key === "familyAddress" || key === "birthAddress" ? (
|
|
||||||
<textarea
|
|
||||||
rows={2}
|
|
||||||
value={formData[key]}
|
|
||||||
onChange={handleChange(key)}
|
|
||||||
placeholder={`请输入${certificateFieldLabels[key]}`}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<input
|
|
||||||
value={formData[key]}
|
|
||||||
onChange={handleChange(key)}
|
|
||||||
placeholder={`请输入${certificateFieldLabels[key]}`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</label>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{customTemplateUploadEnabled ? (
|
{customTemplateUploadEnabled ? (
|
||||||
<div className="panel-note">
|
<div className="panel-note">
|
||||||
@@ -231,11 +355,11 @@ export function CertificateGenerator() {
|
|||||||
{errorMessage ? <div className="error-box">{errorMessage}</div> : null}
|
{errorMessage ? <div className="error-box">{errorMessage}</div> : null}
|
||||||
{statsMessage ? <div className="error-box">{statsMessage}</div> : null}
|
{statsMessage ? <div className="error-box">{statsMessage}</div> : null}
|
||||||
|
|
||||||
<div className="preview-frame preview-frame--studio">
|
<div ref={previewMeasureRef} className="preview-frame preview-frame--studio">
|
||||||
<TemplateScaleFrame
|
<TemplateScaleFrame
|
||||||
width={selectedTemplate.width}
|
width={selectedTemplate.width}
|
||||||
height={selectedTemplate.height}
|
height={selectedTemplate.height}
|
||||||
scale={previewWidth / selectedTemplate.width}
|
scale={previewScale}
|
||||||
>
|
>
|
||||||
<SelectedTemplateComponent
|
<SelectedTemplateComponent
|
||||||
formData={formData}
|
formData={formData}
|
||||||
@@ -250,9 +374,48 @@ export function CertificateGenerator() {
|
|||||||
{isExporting ? "导出中..." : "下载图片"}
|
{isExporting ? "导出中..." : "下载图片"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="mobile-preview-actions">
|
||||||
|
<button type="button" className="mobile-preview-actions__primary" onClick={handleExport} disabled={isExporting}>
|
||||||
|
{isExporting ? "导出中..." : "下载图片"}
|
||||||
|
</button>
|
||||||
|
<button type="button" className="mobile-preview-actions__secondary" onClick={() => setIsMobileFormOpen(true)}>
|
||||||
|
填写信息
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{isMobile ? (
|
||||||
|
<div className={`mobile-form-sheet ${isMobileFormOpen ? "is-open" : ""}`} aria-hidden={!isMobileFormOpen}>
|
||||||
|
<div className="mobile-form-sheet__backdrop" onClick={() => setIsMobileFormOpen(false)} />
|
||||||
|
<div className="mobile-form-sheet__panel" role="dialog" aria-modal="true" aria-labelledby="mobile-form-title">
|
||||||
|
<div className="mobile-form-sheet__header">
|
||||||
|
<button type="button" className="mobile-form-sheet__back" onClick={() => setIsMobileFormOpen(false)}>
|
||||||
|
返回预览
|
||||||
|
</button>
|
||||||
|
<h2 id="mobile-form-title">填写龙虾信息</h2>
|
||||||
|
<button type="button" className="mobile-form-sheet__close" onClick={() => setIsMobileFormOpen(false)} aria-label="关闭表单">
|
||||||
|
关闭
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="mobile-form-sheet__body">
|
||||||
|
<CertificateFormFields formData={formData} onChange={handleChange} className="form-grid form-grid--mobile" />
|
||||||
|
{customTemplateUploadEnabled ? (
|
||||||
|
<div className="panel-note">
|
||||||
|
已开启 <code>VITE_ENABLE_CUSTOM_TEMPLATE_UPLOAD</code>,当前版本仅预留入口。
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<div className="mobile-form-sheet__footer">
|
||||||
|
<button type="button" className="mobile-form-sheet__done" onClick={() => setIsMobileFormOpen(false)}>
|
||||||
|
返回预览
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<div className="export-stage" aria-hidden="true">
|
<div className="export-stage" aria-hidden="true">
|
||||||
<div ref={exportRef}>
|
<div ref={exportRef}>
|
||||||
<SelectedTemplateComponent
|
<SelectedTemplateComponent
|
||||||
|
|||||||
Reference in New Issue
Block a user