@@ -1409,38 +1390,7 @@ function paymentPaidAt(record: AdminPayment) {
-
-
-
支付与退款流水
- 共 {{ paymentRecords.length }} 条
-
-
-
-
-
-
- {{ paymentBizTypeLabel(record.biz_type) }} · {{ moneyCent(record.amount_cent) }}
-
-
- {{ paymentStatusLabel(record.status) }}
-
-
-
- 流水号:{{ record.payment_no }}
- 用户:{{ record.user_phone || record.user_id }}
- 渠道:{{ record.provider || '-' }}
- 商户:{{ record.merchant_id || '-' }}
- 三方单号:{{ record.third_order_id || '-' }}
- 渠道单号:{{ record.provider_order_id || '-' }}
- 创建:{{ formatDateTime(record.created_at) }}
- 完成:{{ formatDateTime(paymentPaidAt(record), '-') }}
-
-
- {{ record.error_code }} {{ record.error_message }}
-
-
-
-
+
@@ -1754,323 +1704,4 @@ function paymentPaidAt(record: AdminPayment) {
-
+
diff --git a/frontend/src/features/admin/views/AdminPickupDetailView.css b/frontend/src/features/admin/views/AdminPickupDetailView.css
new file mode 100644
index 0000000..2dbd2d1
--- /dev/null
+++ b/frontend/src/features/admin/views/AdminPickupDetailView.css
@@ -0,0 +1,209 @@
+.pickup-metrics {
+ margin-bottom: 20px;
+}
+
+.pickup-metrics .metric-card strong {
+ font-size: 20px;
+ line-height: 1.25;
+ overflow-wrap: anywhere;
+}
+
+.metric-card small {
+ display: block;
+ margin-top: 8px;
+ color: #8f9bba;
+ font-size: 12px;
+ line-height: 1.4;
+ word-break: break-word;
+}
+
+.form-hint {
+ margin-top: 4px;
+ color: #64748b;
+ font-size: 12px;
+ line-height: 1.5;
+}
+
+.adjustment-table {
+ width: 100%;
+}
+
+.pickup-detail-grid {
+ display: grid;
+ grid-template-columns: minmax(0, 1.1fr) minmax(360px, 0.9fr);
+ gap: 20px;
+ margin-bottom: 28px;
+}
+
+.detail-panel {
+ min-width: 0;
+}
+
+.pickup-wide-panel {
+ grid-column: 1 / -1;
+}
+
+.panel-heading {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 12px;
+ margin-bottom: 16px;
+}
+
+.panel-heading h2 {
+ margin: 0;
+}
+
+.panel-subtitle {
+ color: #8f9bba;
+ font-size: 13px;
+ line-height: 1.6;
+ text-align: right;
+}
+
+.detail-list {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 12px 18px;
+ margin: 0;
+}
+
+.detail-list div {
+ min-width: 0;
+ border-top: 1px solid #f0f2f5;
+ padding-top: 10px;
+}
+
+.detail-list .wide {
+ grid-column: 1 / -1;
+}
+
+.detail-list dt {
+ margin-bottom: 5px;
+ color: #8f9bba;
+ font-size: 12px;
+ font-weight: 600;
+}
+
+.detail-list dd {
+ margin: 0;
+ color: #1b2559;
+ font-size: 14px;
+ font-weight: 700;
+ line-height: 1.5;
+ overflow-wrap: anywhere;
+ white-space: pre-wrap;
+}
+
+.source-detail {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 6px 8px;
+}
+
+.money-list {
+ display: grid;
+ gap: 8px;
+}
+
+.money-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ border-top: 1px solid #f0f2f5;
+ padding-top: 9px;
+ color: #52616f;
+ font-size: 13px;
+}
+
+.money-row strong {
+ color: #1b2559;
+ font-size: 15px;
+}
+
+.money-row.is-subtotal {
+ border-top-color: #d8e2f0;
+ color: #1b2559;
+ font-weight: 700;
+}
+
+.money-row.is-profit strong {
+ color: #0f766e;
+}
+
+.money-row.is-total {
+ margin-top: 4px;
+ border-top-color: #bfdbfe;
+ color: #1b2559;
+ font-weight: 800;
+}
+
+.money-row.is-total strong {
+ font-size: 18px;
+}
+
+.snapshot-detail-list {
+ margin-bottom: 14px;
+}
+
+.snapshot-tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-bottom: 14px;
+}
+
+.snapshot-resources {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
+ gap: 10px;
+ margin-bottom: 14px;
+}
+
+.resource-item {
+ display: grid;
+ gap: 4px;
+ border: 1px solid #eef2f7;
+ border-radius: 8px;
+ background: #fbfcff;
+ padding: 10px 12px;
+}
+
+.resource-item strong {
+ color: #1b2559;
+ font-size: 13px;
+}
+
+.resource-item span {
+ color: #52616f;
+ font-size: 12px;
+}
+
+.snapshot-json {
+ overflow-x: auto;
+ max-height: 360px;
+ margin: 0;
+ border-radius: 8px;
+ background: #0f172a;
+ color: #dbeafe;
+ padding: 14px;
+ font-size: 12px;
+ line-height: 1.6;
+}
+
+@media (max-width: 960px) {
+ .pickup-detail-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .pickup-wide-panel {
+ grid-column: auto;
+ }
+
+ .detail-list {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/frontend/src/features/admin/views/AdminPickupDetailView.vue b/frontend/src/features/admin/views/AdminPickupDetailView.vue
index 0d0a373..159b46d 100644
--- a/frontend/src/features/admin/views/AdminPickupDetailView.vue
+++ b/frontend/src/features/admin/views/AdminPickupDetailView.vue
@@ -697,214 +697,4 @@ function readSnapshotResources(summary: Record | null): Snapsho
-
+
diff --git a/frontend/src/features/admin/views/AdminPickupView.css b/frontend/src/features/admin/views/AdminPickupView.css
new file mode 100644
index 0000000..639fda6
--- /dev/null
+++ b/frontend/src/features/admin/views/AdminPickupView.css
@@ -0,0 +1,120 @@
+.form-hint {
+ margin-top: 4px;
+ font-size: 12px;
+ color: #64748b;
+}
+.pickup-filter-bar {
+ margin-bottom: 16px;
+}
+
+.pickup-filter-bar :deep(.el-form) {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 10px 16px;
+}
+
+.pickup-filter-bar :deep(.el-form-item) {
+ margin: 0;
+}
+
+.pickup-filter-bar :deep(.el-form-item__label) {
+ padding-right: 7px;
+ color: #52616f;
+ font-size: 13px;
+}
+
+.pickup-filter-bar :deep(.el-select) {
+ width: 128px;
+}
+
+.pickup-filter-bar :deep(.el-form-item:nth-child(3) .el-select) {
+ width: 150px;
+}
+
+.pickup-filter-bar :deep(.el-form-item:nth-child(4) .el-select) {
+ width: 175px;
+}
+
+.pickup-keyword-filter :deep(.el-input) {
+ width: 280px;
+}
+
+.pickup-filter-actions :deep(.el-form-item__content) {
+ gap: 8px;
+}
+
+@media (max-width: 900px) {
+ .pickup-filter-bar :deep(.el-form) {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ .pickup-filter-bar :deep(.el-form-item),
+ .pickup-filter-bar :deep(.el-select),
+ .pickup-keyword-filter :deep(.el-input) {
+ width: 100%;
+ }
+
+ .pickup-filter-bar :deep(.el-form-item__content) {
+ min-width: 0;
+ flex: 1;
+ }
+}
+
+@media (max-width: 600px) {
+ .pickup-filter-bar :deep(.el-form) {
+ grid-template-columns: minmax(0, 1fr);
+ }
+}
+.text-muted {
+ color: #94a3b8;
+}
+.price-snapshot {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px 12px;
+ color: #475569;
+ font-size: 13px;
+ line-height: 1.6;
+}
+.source-cell {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 3px;
+}
+.source-cell small {
+ color: #64748b;
+}
+.listing-option {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ width: 100%;
+}
+.listing-option-title {
+ min-width: 0;
+ flex: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.listing-option-price {
+ flex: none;
+ color: #64748b;
+ font-variant-numeric: tabular-nums;
+}
+.selected-source {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 8px 12px;
+ color: #475569;
+ font-size: 13px;
+}
+.action-buttons {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+}
diff --git a/frontend/src/features/admin/views/AdminPickupView.vue b/frontend/src/features/admin/views/AdminPickupView.vue
index ef6d8fd..2ee53db 100644
--- a/frontend/src/features/admin/views/AdminPickupView.vue
+++ b/frontend/src/features/admin/views/AdminPickupView.vue
@@ -902,125 +902,4 @@ function listingOptionLabel(item: AvailableListing) {
-
+
diff --git a/frontend/src/features/admin/views/AdminQrCodePoolView.css b/frontend/src/features/admin/views/AdminQrCodePoolView.css
new file mode 100644
index 0000000..4e40066
--- /dev/null
+++ b/frontend/src/features/admin/views/AdminQrCodePoolView.css
@@ -0,0 +1,448 @@
+.qrcode-pool-view {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.stats-cards {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 16px;
+}
+
+.stat-card {
+ text-align: center;
+}
+
+.toolbar-card,
+.table-card {
+ border-radius: 10px;
+}
+
+.table-card :deep(.el-card__body) {
+ padding: 20px;
+}
+
+.stat-value {
+ font-size: 28px;
+ font-weight: 600;
+ line-height: 1.2;
+}
+
+.stat-value.success {
+ color: var(--el-color-success);
+}
+.stat-value.primary {
+ color: var(--el-color-primary);
+}
+.stat-value.danger {
+ color: var(--el-color-danger);
+}
+
+.stat-label {
+ margin-top: 6px;
+ color: var(--el-text-color-secondary);
+ font-size: 13px;
+}
+
+.toolbar {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 16px;
+}
+
+.filter-area,
+.action-area {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+}
+
+.filter-area {
+ flex: 1 1 auto;
+ flex-wrap: wrap;
+}
+
+.action-area {
+ flex: 0 0 auto;
+ flex-wrap: wrap;
+ justify-content: flex-end;
+}
+
+.keyword-filter {
+ width: 240px;
+}
+
+.compact-filter {
+ width: 128px;
+}
+
+.date-range-filter {
+ width: 330px;
+}
+
+.qrcode-table {
+ border-radius: 10px;
+ box-shadow: none;
+ padding: 0;
+}
+
+.qrcode-table :deep(.el-table__inner-wrapper) {
+ min-width: 0;
+}
+
+.qrcode-table :deep(.el-table__header th) {
+ height: 42px;
+ background: #f8f9fb;
+}
+
+.qrcode-table :deep(.el-table__cell) {
+ padding: 10px 0;
+}
+
+.qrcode-table :deep(.cell) {
+ padding: 0 14px;
+}
+
+.qrcode-table :deep(.el-table__fixed-right) {
+ box-shadow: -8px 0 12px rgba(31, 45, 61, 0.04);
+}
+
+.qrcode-table :deep(.el-table__header),
+.qrcode-table :deep(.el-table__body) {
+ width: 100% !important;
+}
+
+.qrcode-table :deep(.el-table__header-wrapper),
+.qrcode-table :deep(.el-table__body-wrapper) {
+ overflow-x: auto;
+}
+
+.qrcode-table :deep(.selection-col) {
+ width: 4%;
+}
+
+.qrcode-table :deep(.qr-col) {
+ width: 8%;
+}
+
+.qrcode-table :deep(.status-col) {
+ width: 8%;
+}
+
+.qrcode-table :deep(.name-col) {
+ width: 17%;
+}
+
+.qrcode-table :deep(.note-col) {
+ width: 17%;
+}
+
+.qrcode-table :deep(.group-col) {
+ width: 17%;
+}
+
+.qrcode-table :deep(.rename-col) {
+ width: 8%;
+}
+
+.qrcode-table :deep(.time-col) {
+ width: 12%;
+}
+
+.qrcode-table :deep(.action-col) {
+ width: 10%;
+}
+
+.qrcode-thumb {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 64px;
+ height: 64px;
+ border: 1px solid #eef1f5;
+ border-radius: 8px;
+ background: #ffffff;
+}
+
+.note-cell {
+ color: #303846;
+ font-weight: 500;
+}
+
+.time-stack {
+ display: inline-flex;
+ flex-direction: column;
+ gap: 2px;
+ color: #5f6b7a;
+ font-size: 12px;
+ line-height: 18px;
+ text-align: left;
+ white-space: nowrap;
+}
+
+.copy-cell-button {
+ display: inline-flex;
+ max-width: 100%;
+ align-items: center;
+ gap: 4px;
+ padding: 0;
+ border: 0;
+ background: transparent;
+ color: #303846;
+ cursor: pointer;
+ font: inherit;
+ font-size: 13px;
+ font-weight: 500;
+ line-height: 20px;
+ text-align: left;
+}
+
+.copy-cell-button span {
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.copy-cell-button .el-icon {
+ flex: 0 0 auto;
+ color: var(--el-color-primary);
+ font-size: 14px;
+}
+
+.copy-cell-button:disabled {
+ color: #a8b0bd;
+ cursor: default;
+}
+
+.table-actions {
+ display: flex;
+ justify-content: center;
+ gap: 6px;
+ white-space: nowrap;
+}
+
+.table-actions :deep(.el-button) {
+ margin-left: 0;
+ padding: 4px 6px;
+}
+
+@media (max-width: 1280px) {
+ .toolbar {
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .action-area {
+ justify-content: flex-start;
+ }
+}
+
+@media (max-width: 720px) {
+ .keyword-filter,
+ .compact-filter,
+ .date-range-filter {
+ width: 100%;
+ }
+
+ .filter-area,
+ .action-area {
+ align-items: stretch;
+ }
+}
+
+.upload-preview {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
+}
+
+.batch-upload-area {
+ width: 100%;
+}
+
+.qrcode-upload-form :deep(.el-form-item__label) {
+ align-items: center;
+ padding-right: 18px;
+ line-height: 32px;
+ white-space: nowrap;
+ word-break: keep-all;
+}
+
+.qrcode-upload-form :deep(.el-form-item) {
+ margin-bottom: 18px;
+}
+
+.qrcode-upload-form :deep(.el-form-item__content) {
+ min-width: 0;
+}
+
+.batch-upload-area :deep(.el-upload) {
+ width: 100%;
+}
+
+.batch-upload-area :deep(.el-upload-dragger) {
+ width: 100%;
+ padding: 12px 18px;
+}
+
+.batch-upload-area :deep(.el-icon--upload) {
+ margin-bottom: 4px;
+ font-size: 28px;
+ line-height: 1;
+}
+
+.batch-upload-area :deep(.el-upload__text) {
+ line-height: 20px;
+}
+
+.upload-tip {
+ color: var(--el-text-color-secondary);
+ font-size: 12px;
+ line-height: 18px;
+}
+
+.uploaded-list {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
+ gap: 12px;
+ margin-top: 10px;
+ max-height: 320px;
+ overflow-y: auto;
+ padding: 10px;
+ border: 1px solid #eef1f5;
+ border-radius: 8px;
+ background: #fafbfc;
+}
+
+.uploaded-item {
+ min-width: 0;
+ padding: 10px;
+ border: 1px solid #e7ebf2;
+ border-radius: 8px;
+ background: #ffffff;
+}
+
+.uploaded-main {
+ display: flex;
+ min-width: 0;
+ align-items: center;
+ gap: 10px;
+}
+
+.uploaded-thumb {
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 60px;
+ height: 60px;
+ flex: 0 0 auto;
+ border: 1px solid #e7ebf2;
+ border-radius: 8px;
+ background: #ffffff;
+}
+
+.uploaded-local-preview {
+ display: block;
+ width: 52px;
+ height: 52px;
+ border-radius: 6px;
+ object-fit: cover;
+}
+
+.uploaded-remove {
+ position: absolute;
+ top: -7px;
+ right: -7px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 18px;
+ height: 18px;
+ padding: 0;
+ border: 1px solid #ffffff;
+ border-radius: 50%;
+ background: var(--el-color-danger);
+ color: #ffffff;
+ cursor: pointer;
+ font-size: 14px;
+ line-height: 1;
+}
+
+.uploaded-remove:disabled {
+ cursor: not-allowed;
+ opacity: 0.45;
+}
+
+.uploaded-name {
+ overflow: hidden;
+ color: var(--el-text-color-secondary);
+ font-size: 12px;
+ line-height: 16px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.uploaded-meta {
+ display: flex;
+ min-width: 0;
+ flex: 1;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 6px;
+}
+
+.uploaded-meta .el-tag {
+ height: 20px;
+}
+
+.uploaded-group-editor {
+ margin-top: 8px;
+}
+
+.expired-tag {
+ color: var(--el-color-danger);
+}
+
+.edit-image-area {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.edit-image-preview {
+ flex: 0 0 auto;
+ width: 88px;
+ height: 88px;
+ overflow: hidden;
+ border: 1px solid #e7ebf2;
+ border-radius: 8px;
+ background: #fafbfc;
+}
+
+.edit-image-preview :deep(.el-image),
+.edit-image-preview :deep(img) {
+ display: block;
+ width: 100%;
+ height: 100%;
+}
+
+.edit-image-empty {
+ display: grid;
+ width: 100%;
+ height: 100%;
+ place-items: center;
+ color: var(--el-text-color-secondary);
+ font-size: 12px;
+}
+
+.expire-editor {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ width: 100%;
+}
diff --git a/frontend/src/features/admin/views/AdminQrCodePoolView.vue b/frontend/src/features/admin/views/AdminQrCodePoolView.vue
index d53a026..ef78aac 100644
--- a/frontend/src/features/admin/views/AdminQrCodePoolView.vue
+++ b/frontend/src/features/admin/views/AdminQrCodePoolView.vue
@@ -979,453 +979,4 @@ onMounted(reloadAll)
-
+
diff --git a/frontend/src/features/admin/views/AdminSystemConfigsView.css b/frontend/src/features/admin/views/AdminSystemConfigsView.css
new file mode 100644
index 0000000..99f3d4c
--- /dev/null
+++ b/frontend/src/features/admin/views/AdminSystemConfigsView.css
@@ -0,0 +1,89 @@
+.publish-config-panel {
+ display: grid;
+ gap: 18px;
+ margin-bottom: 18px;
+ padding: 20px;
+ border: 1px solid #e5e7eb;
+ border-radius: 8px;
+ background: #fff;
+}
+
+.publish-config-main {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 16px;
+}
+
+.panel-actions {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.publish-config-main h2 {
+ margin: 4px 0 6px;
+ color: #111827;
+ font-size: 20px;
+}
+
+.publish-config-main span {
+ color: #6b7280;
+ font-size: 13px;
+}
+
+.publish-stat-grid {
+ display: grid;
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+ gap: 10px;
+}
+
+.welcome-config-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 12px;
+}
+
+.home-stat-grid .publish-stat strong {
+ font-size: 18px;
+}
+
+.publish-stat {
+ display: grid;
+ gap: 4px;
+ padding: 12px;
+ border-radius: 8px;
+ background: #f8fafc;
+}
+
+.publish-stat strong {
+ color: #1477ff;
+ font-size: 22px;
+ line-height: 1;
+}
+
+.publish-stat span,
+.config-value {
+ color: #4b5563;
+ font-size: 13px;
+ overflow-wrap: anywhere;
+}
+
+/* 仅实体主按钮提升文字清晰度,其他按钮保持组件默认配色。 */
+:deep(.el-button--primary:not(.is-plain):not(.is-dashed):not(.is-text):not(.is-link)) {
+ font-weight: 700;
+}
+
+:deep(.el-button--primary:not(.is-plain):not(.is-dashed):not(.is-text):not(.is-link) span) {
+ color: #ffffff !important;
+ text-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
+ font-weight: 700;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+@media (max-width: 1100px) {
+ .publish-stat-grid {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ }
+}
diff --git a/frontend/src/features/admin/views/AdminSystemConfigsView.vue b/frontend/src/features/admin/views/AdminSystemConfigsView.vue
index 31a1021..efcaf4b 100644
--- a/frontend/src/features/admin/views/AdminSystemConfigsView.vue
+++ b/frontend/src/features/admin/views/AdminSystemConfigsView.vue
@@ -965,94 +965,4 @@ function formatConfigValue(row: SystemConfig) {
-
+
diff --git a/frontend/src/features/admin/views/AdminUsersView.css b/frontend/src/features/admin/views/AdminUsersView.css
new file mode 100644
index 0000000..c51733e
--- /dev/null
+++ b/frontend/src/features/admin/views/AdminUsersView.css
@@ -0,0 +1,149 @@
+.user-filter-bar {
+ margin-bottom: 16px;
+ padding: 16px 16px 0;
+ border-radius: 8px;
+ background: #fff;
+}
+
+.user-filter-bar :deep(.el-form-item) {
+ margin-bottom: 16px;
+}
+
+.balance-available {
+ color: #0f172a;
+ font-variant-numeric: tabular-nums;
+ font-weight: 700;
+}
+
+.balance-frozen {
+ color: #94a3b8;
+ font-variant-numeric: tabular-nums;
+ font-weight: 600;
+}
+
+.balance-frozen.is-positive {
+ color: #c2410c;
+}
+
+.quota-cell {
+ display: grid;
+ gap: 2px;
+}
+
+.quota-cell strong {
+ color: #0f172a;
+ font-variant-numeric: tabular-nums;
+}
+
+.quota-cell span {
+ color: #64748b;
+ font-size: 11px;
+ line-height: 1.3;
+}
+
+.table-actions {
+ display: inline-flex;
+ flex-wrap: nowrap;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 2px;
+ white-space: nowrap;
+}
+
+.table-actions :deep(.el-button) {
+ margin: 0;
+ height: 28px;
+ padding: 0 8px;
+ font-weight: 600;
+}
+
+.table-actions :deep(.el-button + .el-button) {
+ margin-left: 0;
+}
+
+.table-actions :deep(.el-button.is-text) {
+ border-radius: 6px;
+}
+
+.table-actions :deep(.el-button.is-text:hover) {
+ background: #f1f5f9;
+}
+
+.wallet-dialog {
+ display: grid;
+ gap: 14px;
+}
+
+.quota-summary {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 8px;
+ padding: 12px;
+ border: 1px solid #e5e7eb;
+ border-radius: 8px;
+ background: #f8fafc;
+ color: #64748b;
+ font-size: 13px;
+}
+
+.quota-summary strong {
+ color: #0f172a;
+}
+
+.growth-dialog {
+ gap: 16px;
+}
+
+.growth-current {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 12px 14px;
+ border: 1px solid #e5e7eb;
+ border-radius: 8px;
+ background: #f8fafc;
+ color: #64748b;
+}
+
+.growth-current strong {
+ color: #0f172a;
+ font-size: 22px;
+ font-variant-numeric: tabular-nums;
+}
+
+.wallet-balance-cards {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 10px;
+}
+
+.wallet-balance-cards > div {
+ display: grid;
+ gap: 6px;
+ padding: 12px 14px;
+ border: 1px solid #e5e7eb;
+ border-radius: 8px;
+ background: #f8fafc;
+}
+
+.wallet-balance-cards span {
+ color: #64748b;
+ font-size: 13px;
+ font-weight: 700;
+}
+
+.wallet-balance-cards strong {
+ color: #0f172a;
+ font-size: 22px;
+ line-height: 1.1;
+ font-variant-numeric: tabular-nums;
+}
+
+.full-control {
+ width: 100%;
+}
+
+.dialog-body {
+ display: grid;
+ gap: 12px;
+}
diff --git a/frontend/src/features/admin/views/AdminUsersView.vue b/frontend/src/features/admin/views/AdminUsersView.vue
index 5674c42..c47740d 100644
--- a/frontend/src/features/admin/views/AdminUsersView.vue
+++ b/frontend/src/features/admin/views/AdminUsersView.vue
@@ -755,154 +755,4 @@ function moneyCent(value: number | string | undefined) {
-
+
diff --git a/frontend/src/features/auth/views/MobileProfileView.css b/frontend/src/features/auth/views/MobileProfileView.css
new file mode 100644
index 0000000..0f2c930
--- /dev/null
+++ b/frontend/src/features/auth/views/MobileProfileView.css
@@ -0,0 +1,665 @@
+.mobile-profile-shell {
+ min-height: 100dvh;
+ background: #f6f8fa;
+ color: #1a202c;
+ padding: 0 0 calc(64px + env(safe-area-inset-bottom));
+}
+
+/* ========== Hero Header ========== */
+.profile-hero {
+ background: #ffffff;
+ padding: 20px 16px 20px;
+ border-bottom: 1px solid rgba(243, 244, 246, 0.8);
+}
+
+.hero-top-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+}
+
+.app-title {
+ font-size: 16px;
+ font-weight: 700;
+ color: #111827;
+ letter-spacing: 0.5px;
+}
+
+.hero-setting-btn {
+ display: grid;
+ width: 34px;
+ height: 34px;
+ place-items: center;
+ border: none;
+ background: #f3f4f6;
+ border-radius: 999px;
+ cursor: pointer;
+ transition: background 0.2s;
+}
+
+.hero-setting-btn:active {
+ background: #e5e7eb;
+}
+
+.profile-hero-content {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+}
+
+.avatar-circle {
+ display: grid;
+ width: 60px;
+ height: 60px;
+ place-items: center;
+ border-radius: 999px;
+ overflow: hidden;
+ background: #eaf2ff;
+ color: #1477ff;
+ font-size: 24px;
+ font-weight: 900;
+ border: 1px solid #e5e7eb;
+}
+
+.avatar-circle img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.hero-user {
+ flex: 1;
+ min-width: 0;
+}
+
+.name-row {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ flex-wrap: wrap;
+}
+
+.user-nickname {
+ margin: 0;
+ font-size: 18px;
+ font-weight: 800;
+ color: #111827;
+}
+
+.verified-tag,
+.unverified-tag {
+ font-size: 10px !important;
+ height: 18px;
+ padding: 0 6px;
+ font-weight: 700;
+ border: none;
+}
+
+.user-meta {
+ margin: 4px 0 0;
+ color: #6b7280;
+ font-size: 12px;
+}
+
+.growth-card {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 10px;
+ margin: 12px 16px 0;
+}
+
+.growth-card > div {
+ display: grid;
+ gap: 4px;
+ min-width: 0;
+ padding: 14px;
+ border: 1px solid rgba(243, 244, 246, 0.9);
+ border-radius: 16px;
+ background: #ffffff;
+ box-shadow:
+ 0 4px 16px rgba(0, 0, 0, 0.02),
+ 0 1px 4px rgba(0, 0, 0, 0.02);
+}
+
+.growth-card span {
+ color: #9ca3af;
+ font-size: 11px;
+ font-weight: 700;
+}
+
+.growth-card strong {
+ color: #7c3aed;
+ font-size: 20px;
+ line-height: 1.2;
+ font-weight: 900;
+}
+
+/* ========== Balance Card ========== */
+.balance-card {
+ margin: 12px 16px 14px;
+ padding: 16px;
+ border-radius: 16px;
+ background: #ffffff;
+ box-shadow:
+ 0 4px 16px rgba(0, 0, 0, 0.02),
+ 0 1px 4px rgba(0, 0, 0, 0.02);
+ border: 1px solid rgba(243, 244, 246, 0.9);
+}
+
+.balance-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.balance-info {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.balance-label {
+ color: #9ca3af;
+ font-size: 11px;
+ font-weight: 600;
+}
+
+.balance-value {
+ font-size: 26px;
+ font-weight: 800;
+ font-family: Inter, system-ui, sans-serif;
+ color: #111827;
+}
+
+.withdraw-btn {
+ background: transparent;
+ border: 1px solid #1477ff;
+ color: #1477ff;
+ font-weight: 700;
+ font-size: 12px;
+ height: 28px;
+ padding: 0 16px;
+ border-radius: 999px;
+ cursor: pointer;
+ transition:
+ background 0.15s,
+ color 0.15s;
+}
+
+.withdraw-btn:active {
+ background: #1477ff;
+ color: #ffffff;
+}
+
+.balance-footer {
+ margin-top: 12px;
+ padding-top: 10px;
+ border-top: 1px solid #f3f4f6;
+}
+
+.detail-btn {
+ background: transparent;
+ border: none;
+ color: #1477ff;
+ font-weight: 700;
+ font-size: 11px;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ cursor: pointer;
+ padding: 0;
+}
+
+/* ========== Menu Cards ========== */
+.menu-card {
+ margin: 0 16px 14px;
+ background: #ffffff;
+ border-radius: 16px;
+ padding: 16px;
+ box-shadow:
+ 0 4px 16px rgba(0, 0, 0, 0.02),
+ 0 1px 4px rgba(0, 0, 0, 0.02);
+ border: 1px solid rgba(243, 244, 246, 0.9);
+}
+
+.menu-card.cell-card {
+ padding: 0;
+ overflow: hidden;
+}
+
+.menu-card .card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 14px;
+}
+
+.menu-card .card-header h3 {
+ margin: 0;
+ font-size: 14px;
+ font-weight: 800;
+ color: #111827;
+}
+
+.see-all {
+ font-size: 11px;
+ color: #6b7280;
+ display: flex;
+ align-items: center;
+ gap: 2px;
+ cursor: pointer;
+}
+
+/* Grid Menu Layout */
+.grid-menu {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.grid-menu.col-4 .grid-item {
+ flex: 0 0 25%;
+}
+
+.grid-menu.col-5 .grid-item {
+ flex: 0 0 20%;
+}
+
+.grid-menu.col-3 .grid-item {
+ flex: 0 0 33.33%;
+}
+
+.grid-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 6px;
+ cursor: pointer;
+}
+
+.grid-item:active .icon-wrap {
+ transform: scale(0.92);
+}
+
+.icon-wrap {
+ display: grid;
+ width: 44px;
+ height: 44px;
+ place-items: center;
+ border-radius: 12px;
+ transition: transform 0.15s ease;
+}
+
+/* Vibrant pastel colors for menu icon wraps */
+.icon-wrap.warning {
+ color: #f59e0b;
+ background: rgba(245, 158, 11, 0.08);
+}
+.icon-wrap.info {
+ color: #2563eb;
+ background: rgba(37, 99, 235, 0.08);
+}
+.icon-wrap.primary {
+ color: #1477ff;
+ background: rgba(20, 119, 255, 0.08);
+}
+.icon-wrap.success {
+ color: #10b981;
+ background: rgba(16, 185, 129, 0.08);
+}
+.icon-wrap.orange {
+ color: #ff5f00;
+ background: rgba(255, 95, 0, 0.08);
+}
+.icon-wrap.purple {
+ color: #8b5cf6;
+ background: rgba(139, 92, 246, 0.08);
+}
+.icon-wrap.teal {
+ color: #0d9488;
+ background: rgba(13, 148, 136, 0.08);
+}
+
+.grid-item span {
+ font-size: 11px;
+ color: #374151;
+ font-weight: 600;
+}
+
+/* Vant Cell Adjustments */
+:deep(.van-cell-group) {
+ background: transparent;
+}
+:deep(.van-cell) {
+ padding: 14px 16px;
+ font-size: 14px;
+}
+.verified-color {
+ color: #10b981;
+ font-weight: 600;
+}
+.unverified-color {
+ color: #9ca3af;
+}
+.unread-cell-value {
+ color: #ef4444;
+ font-weight: 700;
+}
+
+/* 收支明细 Popup */
+.ledgers-popup {
+ display: flex;
+ flex-direction: column;
+}
+.popup-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px;
+ border-bottom: 1px solid #f3f4f6;
+ flex-shrink: 0;
+}
+.popup-header h3 {
+ margin: 0;
+ font-size: 16px;
+ font-weight: 800;
+}
+.popup-close {
+ border: none;
+ background: transparent;
+ font-size: 16px;
+ color: #9ca3af;
+ cursor: pointer;
+}
+.popup-body {
+ flex: 1;
+ overflow-y: auto;
+ padding: 0 16px;
+}
+.ledger-list {
+ display: flex;
+ flex-direction: column;
+}
+.ledger-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 14px 0;
+ border-bottom: 1px solid #f3f4f6;
+}
+.ledger-item:last-child {
+ border-bottom: none;
+}
+.ledger-left {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+.ledger-remark {
+ font-size: 14px;
+ color: #1f2937;
+ font-weight: 600;
+}
+.ledger-time {
+ font-size: 11px;
+ color: #9ca3af;
+}
+.ledger-right {
+ font-size: 15px;
+ font-weight: 800;
+}
+.in-color {
+ color: #10b981;
+}
+.out-color {
+ color: #ef4444;
+}
+
+/* 租后须知 Popup */
+.notice-popup {
+ display: flex;
+ flex-direction: column;
+}
+.notice-content {
+ padding: 16px 0;
+ color: #374151;
+ font-size: 14px;
+ line-height: 1.8;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+.center-loading {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 40px 0;
+}
+
+/* ========== 设置面板 ========== */
+.settings-panel {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ background: #f5f6f8;
+}
+
+.settings-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px;
+ background: #fff;
+ border-bottom: 1px solid #eee;
+}
+
+.settings-header h2 {
+ margin: 0;
+ font-size: 17px;
+ font-weight: 700;
+}
+
+.settings-close {
+ display: grid;
+ width: 32px;
+ height: 32px;
+ place-items: center;
+ border: none;
+ background: none;
+ color: #999;
+ cursor: pointer;
+}
+
+.settings-group {
+ margin-top: 12px;
+}
+
+.settings-group-title {
+ margin: 0 0 4px 16px;
+ font-size: 13px;
+ font-weight: 600;
+ color: #999;
+}
+
+.settings-group :deep(.van-cell) {
+ font-size: 15px;
+}
+
+.settings-logout {
+ margin: auto 16px 24px;
+ padding-top: 20px;
+}
+
+/* ========== 资料修改 ========== */
+.profile-editor {
+ padding: 16px 16px max(18px, env(safe-area-inset-bottom));
+ background: #fff;
+}
+
+.profile-editor-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.profile-editor-header h2 {
+ margin: 0;
+ color: #17233d;
+ font-size: 18px;
+ font-weight: 800;
+}
+
+.profile-editor-header button {
+ display: grid;
+ width: 34px;
+ height: 34px;
+ place-items: center;
+ border: none;
+ border-radius: 12px;
+ background: #f4f6f8;
+ color: #64748b;
+}
+
+.profile-preview {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin: 18px 0;
+ padding: 14px;
+ border-radius: 14px;
+ background: #f7faff;
+}
+
+.profile-preview-avatar {
+ display: grid;
+ width: 52px;
+ height: 52px;
+ flex: 0 0 auto;
+ place-items: center;
+ overflow: hidden;
+ border-radius: 16px;
+ background: #1477ff;
+ color: #fff;
+ font-size: 22px;
+ font-weight: 900;
+}
+
+.profile-preview-avatar img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.profile-preview-avatar.clickable {
+ cursor: pointer;
+ position: relative;
+}
+
+.avatar-upload-overlay {
+ position: absolute;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.4);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #ffffff;
+ opacity: 0;
+ transition: opacity 0.2s ease;
+}
+
+.profile-preview-avatar.clickable:hover .avatar-upload-overlay,
+.profile-preview-avatar.clickable:active .avatar-upload-overlay {
+ opacity: 1;
+}
+
+.avatar-loading {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(255, 255, 255, 0.8);
+}
+
+.preview-info {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 4px;
+}
+
+.preview-info strong {
+ display: block;
+ max-width: 230px;
+ overflow: hidden;
+ color: #17233d;
+ font-size: 16px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.preview-info p {
+ margin: 0;
+ color: #7b8798;
+ font-size: 12px;
+}
+
+.upload-btn {
+ margin-top: 4px;
+ font-weight: 600;
+}
+
+.editor-field {
+ display: block;
+ margin-bottom: 12px;
+}
+
+.password-hint {
+ margin: 8px 0 14px;
+ color: #8b9cb5;
+ font-size: 12px;
+ line-height: 1.5;
+}
+
+.editor-field span {
+ display: block;
+ margin-bottom: 6px;
+ color: #697386;
+ font-size: 13px;
+ font-weight: 700;
+}
+
+.editor-field input {
+ box-sizing: border-box;
+ width: 100%;
+ height: 46px;
+ border: 1px solid #e1e7ef;
+ border-radius: 12px;
+ background: #fff;
+ color: #17233d;
+ font-size: 14px;
+ outline: none;
+ padding: 0 12px;
+}
+
+.editor-field input:focus {
+ border-color: #1477ff;
+ box-shadow: 0 0 0 3px rgba(20, 119, 255, 0.1);
+}
+
+.profile-save-btn {
+ height: 44px;
+ margin-top: 6px;
+ background: #1477ff;
+ border-color: transparent;
+ font-weight: 800;
+}
+
+/* ========== 响应式适配 ========== */
+@media (min-width: 520px) {
+ .mobile-profile-shell {
+ max-width: 430px;
+ margin: 0 auto;
+ box-shadow: 0 0 0 1px rgba(23, 35, 61, 0.08);
+ }
+}
diff --git a/frontend/src/features/auth/views/MobileProfileView.vue b/frontend/src/features/auth/views/MobileProfileView.vue
index bc178c0..856cdb9 100644
--- a/frontend/src/features/auth/views/MobileProfileView.vue
+++ b/frontend/src/features/auth/views/MobileProfileView.vue
@@ -691,670 +691,4 @@ function resolveAvatarURL(url: string | undefined | null) {
-
+
diff --git a/frontend/src/features/financial/__tests__/financialApi.spec.ts b/frontend/src/features/financial/__tests__/financialApi.spec.ts
new file mode 100644
index 0000000..549e9e8
--- /dev/null
+++ b/frontend/src/features/financial/__tests__/financialApi.spec.ts
@@ -0,0 +1,93 @@
+import { describe, expect, it, vi } from 'vitest'
+
+const apiClient = vi.hoisted(() => ({
+ get: vi.fn(),
+ post: vi.fn(),
+ put: vi.fn(),
+ delete: vi.fn(),
+}))
+
+vi.mock('@/shared/api/client', () => ({ apiClient }))
+
+import { fetchWalletBalance, fetchWalletLedger } from '@/features/wallet/api/wallet'
+import {
+ cancelWithdrawal,
+ createPaymentAccount,
+ createWithdrawal,
+ deletePaymentAccount,
+ fetchPaymentAccount,
+ fetchPaymentAccounts,
+ fetchWithdrawal,
+ fetchWithdrawals,
+ setDefaultPaymentAccount,
+ updatePaymentAccount,
+} from '@/features/wallet/api/withdrawal'
+import { fetchAdminPayments } from '@/features/admin/api/adminPayments'
+
+describe('financial API clients', () => {
+ it('fetches wallet balance and paginated ledger with server data', async () => {
+ apiClient.get
+ .mockResolvedValueOnce({ data: { data: { available_balance_cent: 100 } } })
+ .mockResolvedValueOnce({ data: { data: { items: [], total: 0, page: 2, page_size: 10 } } })
+
+ await expect(fetchWalletBalance()).resolves.toEqual({ available_balance_cent: 100 })
+ await expect(fetchWalletLedger(2, 10)).resolves.toEqual({
+ items: [],
+ total: 0,
+ page: 2,
+ page_size: 10,
+ })
+ expect(apiClient.get).toHaveBeenNthCalledWith(2, '/wallet/ledger', {
+ params: { page: 2, page_size: 10 },
+ })
+ })
+
+ it('supports payment-account CRUD and default selection', async () => {
+ const account = { id: 7, account_type: 'alipay' }
+ apiClient.get
+ .mockResolvedValueOnce({ data: { data: { items: [account], total: 1 } } })
+ .mockResolvedValueOnce({ data: { data: account } })
+ apiClient.post.mockResolvedValueOnce({ data: { data: account } })
+ apiClient.put.mockResolvedValueOnce({ data: { data: account } })
+ apiClient.delete.mockResolvedValueOnce({ data: { data: { deleted: true } } })
+ apiClient.post.mockResolvedValueOnce({ data: { data: { updated: true } } })
+
+ await expect(fetchPaymentAccounts()).resolves.toMatchObject({ items: [account], total: 1 })
+ await expect(fetchPaymentAccount(7)).resolves.toEqual(account)
+ await expect(
+ createPaymentAccount({ account_type: 'alipay', account_name: 'A', account_no: 'x' })
+ ).resolves.toEqual(account)
+ await expect(updatePaymentAccount(7, { is_default: true })).resolves.toEqual(account)
+ await expect(deletePaymentAccount(7)).resolves.toEqual({ deleted: true })
+ await expect(setDefaultPaymentAccount(7)).resolves.toEqual({ updated: true })
+ })
+
+ it('supports withdrawal lifecycle API calls', async () => {
+ const withdrawal = { id: 9, status: 'pending' }
+ apiClient.post
+ .mockResolvedValueOnce({ data: { data: withdrawal } })
+ .mockResolvedValueOnce({ data: { data: { cancelled: true } } })
+ apiClient.get
+ .mockResolvedValueOnce({ data: { data: { items: [withdrawal], total: 1 } } })
+ .mockResolvedValueOnce({ data: { data: withdrawal } })
+
+ await expect(createWithdrawal({ payment_account_id: 7, amount_cent: 1000 })).resolves.toEqual(
+ withdrawal
+ )
+ await expect(fetchWithdrawals(1, 20)).resolves.toMatchObject({ items: [withdrawal] })
+ await expect(fetchWithdrawal(9)).resolves.toEqual(withdrawal)
+ await expect(cancelWithdrawal(9)).resolves.toEqual({ cancelled: true })
+ })
+
+ it('filters empty admin-payment query values and normalizes pagination', async () => {
+ apiClient.get.mockResolvedValueOnce({ data: { data: undefined } })
+
+ await expect(
+ fetchAdminPayments({ status: '', provider: 'mock', page: 3, silent: true })
+ ).resolves.toEqual({ items: [], total: 0, page: 3, page_size: 20 })
+ expect(apiClient.get).toHaveBeenLastCalledWith('/admin/payments', {
+ params: { provider: 'mock', page: 3 },
+ silent: true,
+ })
+ })
+})
diff --git a/frontend/src/features/listings/views/ListingDetailView.css b/frontend/src/features/listings/views/ListingDetailView.css
new file mode 100644
index 0000000..acae137
--- /dev/null
+++ b/frontend/src/features/listings/views/ListingDetailView.css
@@ -0,0 +1,680 @@
+.detail-back-bar {
+ /* 吸顶固定:始终显示在顶部导航栏(64px)下方,滚动时不跟随页面移走 */
+ position: sticky;
+ top: 78px;
+ z-index: 20;
+ display: flex;
+ align-items: center;
+ margin-bottom: 14px;
+}
+
+.detail-back-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ height: 36px;
+ padding: 0 14px;
+ border: 1px solid #eef1f5;
+ border-radius: 10px;
+ background: #ffffff;
+ color: #475569;
+ font-size: 13px;
+ font-weight: 700;
+ cursor: pointer;
+ box-shadow: 0 2px 8px rgba(23, 35, 61, 0.08);
+ transition:
+ border-color 0.2s ease,
+ color 0.2s ease,
+ background 0.2s ease,
+ box-shadow 0.2s ease;
+}
+
+.detail-back-btn:hover {
+ border-color: #ff6a00;
+ background: #fff7ed;
+ color: #ff6a00;
+ box-shadow: 0 4px 12px rgba(255, 106, 0, 0.12);
+}
+
+.pc-detail-layout {
+ grid-template-columns: minmax(0, 1fr) 420px;
+ align-items: start;
+}
+
+.pc-detail-main {
+ overflow: hidden;
+}
+
+.detail-hero-card {
+ position: relative;
+ display: grid;
+ height: 340px;
+ overflow: hidden;
+ background: #111827;
+ color: #ffffff;
+}
+
+.detail-hero-card img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.detail-hero-card > span {
+ display: grid;
+ height: 340px;
+ place-items: center;
+ font-size: 32px;
+ font-weight: 900;
+}
+
+.detail-hero-card::after {
+ position: absolute;
+ inset: 0;
+ content: '';
+ background:
+ linear-gradient(180deg, rgba(15, 23, 42, 0.06), rgba(15, 23, 42, 0.58)),
+ linear-gradient(90deg, rgba(15, 23, 42, 0.76), rgba(15, 23, 42, 0.08) 62%);
+}
+
+.detail-hero-overlay {
+ position: absolute;
+ inset: auto 0 0;
+ z-index: 1;
+ max-width: 860px;
+ padding: 28px;
+}
+
+.detail-tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-bottom: 14px;
+}
+
+.detail-tags span {
+ display: inline-flex;
+ align-items: center;
+ min-height: 30px;
+ padding: 0 10px;
+ border: 1px solid rgba(255, 255, 255, 0.34);
+ border-radius: 999px;
+ background: rgba(255, 255, 255, 0.14);
+ color: #ffffff;
+ font-size: 13px;
+ font-weight: 900;
+}
+
+.detail-code-chip {
+ width: fit-content;
+ margin-top: 10px;
+ padding: 5px 10px;
+ border: 1px solid rgba(147, 197, 253, 0.6);
+ border-radius: 999px;
+ background: rgba(239, 246, 255, 0.92);
+ color: #2563eb;
+ font-size: 12px;
+ font-weight: 900;
+ cursor: pointer;
+}
+
+.detail-favorite-chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ width: fit-content;
+ margin-top: 8px;
+ padding: 7px 12px;
+ border: 1px solid rgba(255, 106, 0, 0.42);
+ border-radius: 999px;
+ background: rgba(255, 106, 0, 0.18);
+ color: #fff7ed;
+ font-size: 12px;
+ font-weight: 900;
+ cursor: pointer;
+}
+
+.detail-hero-overlay h1 {
+ margin: 0;
+ color: #ffffff;
+ font-size: 30px;
+ line-height: 1.25;
+}
+
+.detail-hero-overlay p {
+ margin: 8px 0 0;
+ color: rgba(255, 255, 255, 0.82);
+ font-size: 16px;
+ font-weight: 800;
+}
+
+.detail-body {
+ display: grid;
+ gap: 18px;
+ padding: 24px;
+}
+
+.detail-summary-row {
+ display: grid;
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+ gap: 14px;
+}
+
+.detail-metric,
+.detail-section {
+ border: 1px solid #edf0f4;
+ border-radius: 16px;
+ background: #ffffff;
+}
+
+.detail-metric {
+ padding: 18px;
+ background: #f8fafc;
+}
+
+.detail-metric span {
+ display: block;
+ color: #8b9cb5;
+ font-size: 13px;
+ font-weight: 900;
+}
+
+.detail-metric strong {
+ display: block;
+ margin-top: 8px;
+ color: #17233d;
+ font-size: 28px;
+ line-height: 1.1;
+}
+
+.detail-metric.is-coin strong {
+ color: #1477ff;
+}
+
+.detail-metric.is-price strong {
+ color: #ef4444;
+}
+
+.detail-section {
+ padding: 22px;
+}
+
+.detail-section-head {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+ margin-bottom: 16px;
+}
+
+.detail-section-head h2 {
+ margin: 0;
+ color: #17233d;
+ font-size: 20px;
+}
+
+.detail-section-head span {
+ color: #8b9cb5;
+ font-size: 13px;
+ font-weight: 900;
+}
+
+.detail-chip-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.detail-chip-row span {
+ display: inline-flex;
+ align-items: center;
+ min-height: 32px;
+ padding: 0 10px;
+ border: 1px solid rgba(255, 106, 0, 0.28);
+ border-radius: 8px;
+ background: #fff7ed;
+ color: #ea580c;
+ font-size: 13px;
+ font-weight: 900;
+}
+
+.detail-info-grid {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 12px;
+ margin: 18px 0 0;
+}
+
+.detail-info-grid div {
+ min-width: 0;
+ border-radius: 12px;
+ background: #f8fafc;
+ padding: 14px;
+}
+
+.detail-info-grid dt {
+ color: #8b9cb5;
+ font-size: 12px;
+ font-weight: 900;
+}
+
+.detail-info-grid dd {
+ margin: 8px 0 0;
+ overflow-wrap: anywhere;
+ color: #17233d;
+ font-size: 15px;
+ font-weight: 900;
+}
+
+.detail-resource-grid {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 12px;
+}
+
+.detail-resource-card {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto;
+ gap: 8px 12px;
+ border-radius: 12px;
+ background: #f8fafc;
+ padding: 14px;
+}
+
+.detail-resource-card span {
+ min-width: 0;
+ color: #475569;
+ font-size: 14px;
+ font-weight: 900;
+}
+
+.detail-resource-card strong {
+ color: #17233d;
+ font-size: 18px;
+}
+
+.detail-resource-card em {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ grid-column: 1 / -1;
+ font-size: 12px;
+ font-style: normal;
+ font-weight: 900;
+}
+
+.detail-resource-card em b {
+ color: #ff6a00;
+}
+
+.detail-resource-card em small {
+ color: #17233d;
+ font-size: 13px;
+ font-weight: 900;
+}
+
+.skin-groups {
+ display: grid;
+ gap: 16px;
+}
+
+.skin-group h3 {
+ margin: 0 0 10px;
+ color: #475569;
+ font-size: 15px;
+}
+
+.detail-description {
+ margin: 0;
+ color: #52616f;
+ font-size: 15px;
+ line-height: 1.8;
+}
+
+.detail-screenshot-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 14px;
+}
+
+.detail-screenshot {
+ margin: 0;
+}
+
+.detail-screenshot-image {
+ width: 100%;
+ aspect-ratio: 16 / 10;
+ overflow: hidden;
+ border-radius: 12px;
+ border: 1px solid #edf0f4;
+ background: #f8fafc;
+ cursor: zoom-in;
+}
+
+.detail-screenshot-image :deep(.el-image__inner) {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.detail-screenshot figcaption {
+ margin-top: 8px;
+ color: #64748b;
+ font-size: 13px;
+ font-weight: 800;
+ text-align: center;
+}
+
+.pc-order-card {
+ max-width: none;
+ border-radius: 20px;
+ padding: 24px;
+ overflow: hidden;
+}
+
+.order-card-head {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 18px;
+ margin-bottom: 10px;
+}
+
+.order-card-head h2 {
+ margin: 4px 0 0;
+ color: #17233d;
+ font-size: 22px;
+ line-height: 1.2;
+}
+
+.order-eyebrow {
+ display: inline-flex;
+ align-items: center;
+ min-height: 24px;
+ border-radius: 999px;
+ background: #f1f5f9;
+ padding: 0 10px;
+ color: #64748b;
+ font-size: 12px;
+ font-weight: 900;
+}
+
+.order-state {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 58px;
+ min-height: 28px;
+ border-radius: 999px;
+ background: #ecfdf5;
+ color: #16a34a;
+ font-size: 12px;
+ font-weight: 900;
+ white-space: nowrap;
+}
+
+.order-state.is-busy {
+ background: #fff7ed;
+ color: #ea580c;
+}
+
+.order-form {
+ display: grid;
+ gap: 14px;
+}
+
+.order-safe-text {
+ margin-bottom: 18px;
+ color: #7a89a5;
+ font-size: 15px;
+ font-weight: 700;
+}
+
+.order-total-box {
+ position: relative;
+ display: grid;
+ gap: 12px;
+ margin-bottom: 0;
+ border: 1px solid rgba(245, 158, 11, 0.2);
+ background:
+ linear-gradient(135deg, rgba(255, 251, 235, 0.96), rgba(255, 247, 237, 0.92)), #fffbeb;
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.86);
+}
+
+.order-total-head {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 16px;
+ padding-bottom: 14px;
+ border-bottom: 1px solid rgba(217, 119, 6, 0.14);
+}
+
+.order-total-head span {
+ color: #9a5a16;
+ font-size: 14px;
+ font-weight: 900;
+}
+
+.order-total-head strong {
+ margin: 0;
+ color: #ef4444;
+ font-size: 34px;
+ line-height: 1;
+ text-align: right;
+}
+
+.order-price-breakdown {
+ display: grid;
+ gap: 10px;
+ padding: 0;
+}
+
+.order-price-breakdown div {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+}
+
+.order-price-breakdown span,
+.order-deposit-row span {
+ color: #8a5a12;
+ font-size: 13px;
+ font-weight: 900;
+}
+
+.order-price-breakdown strong,
+.order-deposit-row strong {
+ margin: 0;
+ color: #17233d;
+ font-size: 16px;
+ line-height: 1.2;
+}
+
+.order-deposit-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ padding-top: 12px;
+ border-top: 1px solid rgba(217, 119, 6, 0.14);
+}
+
+.order-deposit-row strong {
+ color: #92400e;
+ font-size: 18px;
+}
+
+.order-check-list {
+ display: grid;
+ gap: 10px;
+ margin: 0;
+}
+
+.order-check-list div {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto;
+ align-items: center;
+ gap: 16px;
+ min-height: 44px;
+ border: 1px solid #eef2f7;
+ border-radius: 12px;
+ background: #f8fafc;
+ padding: 0 14px;
+}
+
+.order-check-list dt {
+ color: #8b9cb5;
+ font-size: 13px;
+ font-weight: 900;
+}
+
+.order-check-list dd {
+ margin: 0;
+ color: #17233d;
+ font-size: 14px;
+ font-weight: 900;
+ text-align: right;
+ overflow-wrap: anywhere;
+}
+
+.full-control {
+ width: 100%;
+}
+
+.order-action-group {
+ display: grid;
+ gap: 10px;
+ padding-top: 2px;
+}
+
+.order-primary-btn {
+ min-height: 48px;
+ border: 0;
+ background: #ff8a00;
+ color: #ffffff;
+ font-size: 15px;
+ font-weight: 900;
+ box-shadow: 0 12px 24px rgba(255, 106, 0, 0.24);
+}
+
+.order-primary-btn:hover,
+.order-primary-btn:focus {
+ background: #ff7a00;
+ color: #ffffff;
+}
+
+.order-primary-btn.is-disabled,
+.order-primary-btn.is-disabled:hover {
+ background: #f3a94a;
+ color: rgba(255, 255, 255, 0.86);
+ box-shadow: none;
+}
+
+.favorite-order-btn {
+ min-height: 46px;
+ margin-left: 0;
+ border-color: #ff6a00;
+ background: #ffffff;
+ color: #ff6a00;
+ font-size: 15px;
+ font-weight: 900;
+}
+
+.favorite-order-btn:hover,
+.favorite-order-btn:focus {
+ border-color: #ff6a00;
+ background: #fff7ed;
+ color: #ea580c;
+}
+
+.agreement-dialog-body {
+ display: grid;
+ gap: 14px;
+}
+
+.agreement-tip {
+ margin: 0;
+ color: #64748b;
+ font-size: 14px;
+ font-weight: 700;
+}
+
+.agreement-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 14px;
+}
+
+.agreement-panel {
+ display: grid;
+ gap: 10px;
+ min-width: 0;
+}
+
+.agreement-panel h3 {
+ margin: 0;
+ color: #17233d;
+ font-size: 16px;
+ font-weight: 900;
+}
+
+.agreement-content {
+ height: 260px;
+ overflow: auto;
+ border: 1px solid #d8dee9;
+ border-radius: 8px;
+ background: #f8fafc;
+ padding: 14px;
+ color: #1f2937;
+ font-size: 13px;
+ font-weight: 700;
+ line-height: 1.75;
+ white-space: pre-wrap;
+}
+
+.agreement-read-hint {
+ color: #f97316;
+ font-size: 12px;
+ font-weight: 800;
+}
+
+@media (max-width: 1180px) {
+ .pc-detail-layout,
+ .detail-screenshot-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .detail-summary-row,
+ .detail-info-grid,
+ .detail-resource-grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ .detail-hero-card,
+ .detail-hero-card img,
+ .detail-hero-card > span {
+ height: 300px;
+ }
+
+ .detail-hero-overlay {
+ padding: 24px;
+ }
+
+ .detail-hero-overlay h1 {
+ font-size: 26px;
+ }
+}
+
+@media (max-width: 720px) {
+ .detail-summary-row,
+ .detail-info-grid,
+ .detail-resource-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .agreement-grid {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/frontend/src/features/listings/views/ListingDetailView.vue b/frontend/src/features/listings/views/ListingDetailView.vue
index eb34b75..5b4edf6 100644
--- a/frontend/src/features/listings/views/ListingDetailView.vue
+++ b/frontend/src/features/listings/views/ListingDetailView.vue
@@ -630,685 +630,4 @@ function goBack() {
-
+
diff --git a/frontend/src/features/listings/views/MobileListingDetailView.css b/frontend/src/features/listings/views/MobileListingDetailView.css
new file mode 100644
index 0000000..0fe21ed
--- /dev/null
+++ b/frontend/src/features/listings/views/MobileListingDetailView.css
@@ -0,0 +1,485 @@
+.mobile-detail {
+ min-height: 100dvh;
+ background: #f5f7fa;
+ padding-bottom: calc(80px + env(safe-area-inset-bottom));
+}
+
+/* ========== 顶部导航 ========== */
+.page-header {
+ position: sticky;
+ top: 0;
+ z-index: 10;
+ display: flex;
+ align-items: center;
+ height: 48px;
+ padding: 0 12px;
+ background: #fff;
+ border-bottom: 1px solid #eee;
+}
+
+.page-header h1 {
+ flex: 1;
+ margin: 0;
+ font-size: 17px;
+ font-weight: 700;
+ text-align: center;
+}
+
+.back-btn,
+.favorite-header-btn {
+ display: grid;
+ width: 36px;
+ height: 36px;
+ place-items: center;
+ border: none;
+ background: none;
+ color: #333;
+ cursor: pointer;
+}
+
+.favorite-header-btn {
+ color: #ff6a00;
+}
+
+.header-spacer {
+ width: 36px;
+}
+
+.center-loading {
+ display: flex;
+ justify-content: center;
+ padding: 60px 0;
+}
+
+/* ========== 防骗提示 ========== */
+.fraud-tip {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 8px 16px;
+ background: #fff8e1;
+ font-size: 11px;
+ color: #e65100;
+ line-height: 1.4;
+}
+
+/* ========== 封面图 ========== */
+.cover-area {
+ position: relative;
+ width: 100%;
+ aspect-ratio: 16 / 9;
+ background: #e8e8e8;
+ overflow: hidden;
+}
+
+.cover-img {
+ width: 100%;
+ height: 100%;
+ cursor: zoom-in;
+}
+
+.cover-img :deep(.el-image__inner) {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.cover-placeholder {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ color: #999;
+ font-size: 12px;
+}
+
+.cover-count {
+ position: absolute;
+ right: 10px;
+ bottom: 10px;
+ padding: 2px 8px;
+ border-radius: 8px;
+ background: rgba(0, 0, 0, 0.5);
+ color: #fff;
+ font-size: 11px;
+}
+
+/* ========== 信息卡片 ========== */
+.info-card {
+ margin: 10px 12px;
+ padding: 14px;
+ background: #fff;
+ border-radius: 12px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
+}
+
+.info-tag-row {
+ display: flex;
+ gap: 6px;
+ margin-bottom: 8px;
+}
+
+.mobile-code-chip {
+ margin-bottom: 8px;
+ padding: 4px 9px;
+ border: 1px solid #dbeafe;
+ border-radius: 999px;
+ background: #eff6ff;
+ color: #2563eb;
+ font-size: 12px;
+ font-weight: 800;
+}
+
+.mobile-favorite-chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ margin-left: 6px;
+ margin-bottom: 8px;
+ padding: 4px 9px;
+ border: 1px solid #fed7aa;
+ border-radius: 999px;
+ background: #fff7ed;
+ color: #ff6a00;
+ font-size: 12px;
+ font-weight: 800;
+}
+
+.detail-title {
+ margin: 0 0 6px;
+ font-size: 20px;
+ font-weight: 700;
+ color: #1a1a1a;
+ line-height: 1.35;
+}
+
+.detail-desc {
+ margin: 0;
+ font-size: 13px;
+ color: #666;
+ line-height: 1.5;
+}
+
+/* ========== 资产指标 ========== */
+.metric-row {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 8px;
+ padding: 0 12px;
+}
+
+.metric-item {
+ background: #fff;
+ border-radius: 10px;
+ padding: 10px 4px;
+ text-align: center;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
+}
+
+.metric-label {
+ display: block;
+ font-size: 11px;
+ color: #999;
+ margin-bottom: 4px;
+}
+
+.metric-value {
+ display: block;
+ overflow: hidden;
+ font-size: 15px;
+ color: #1a1a1a;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.metric-value.coin {
+ color: #1477ff;
+}
+
+.metric-value.price {
+ color: #ff5f00;
+}
+
+/* ========== 账号资料 ========== */
+.info-line {
+ display: flex;
+ justify-content: space-between;
+ padding: 8px 0;
+ border-bottom: 1px solid #f5f5f5;
+ font-size: 13px;
+}
+
+.info-line:last-child {
+ border-bottom: none;
+}
+
+.info-label {
+ color: #999;
+}
+
+.info-text {
+ color: #333;
+ font-weight: 500;
+}
+
+/* ========== 截图列表 ========== */
+.card-subtitle {
+ margin: 0 0 10px;
+ font-size: 14px;
+ font-weight: 700;
+}
+
+.detail-chip-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 7px;
+ margin-bottom: 10px;
+}
+
+.detail-chip-row span {
+ display: inline-flex;
+ align-items: center;
+ min-height: 28px;
+ border: 1px solid #ff8a1f;
+ border-radius: 5px;
+ color: #ff7900;
+ padding: 0 7px;
+ font-size: 12px;
+ font-weight: 800;
+}
+
+.resource-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 8px;
+}
+
+.resource-pill {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto;
+ gap: 4px 8px;
+ border-radius: 8px;
+ background: #f7f9fc;
+ padding: 10px;
+}
+
+.resource-pill span {
+ min-width: 0;
+ color: #5f6b7a;
+ font-size: 12px;
+ font-weight: 700;
+}
+
+.resource-pill strong {
+ color: #17233d;
+ font-size: 14px;
+}
+
+.resource-pill em {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 6px;
+ grid-column: 1 / -1;
+ font-size: 11px;
+ font-style: normal;
+ font-weight: 800;
+}
+
+.resource-pill em b {
+ color: #ff7900;
+}
+
+.resource-pill em small {
+ min-width: 0;
+ color: #17233d;
+ font-size: 11px;
+ font-weight: 800;
+ text-align: right;
+}
+
+.skin-detail-group + .skin-detail-group {
+ margin-top: 12px;
+}
+
+.skin-detail-group p {
+ margin: 0 0 8px;
+ color: #5f6b7a;
+ font-size: 12px;
+ font-weight: 900;
+}
+
+.screenshot-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 8px;
+}
+
+.screenshot-item {
+ margin: 0;
+}
+
+.screenshot-thumb {
+ width: 100%;
+ aspect-ratio: 4 / 3;
+ overflow: hidden;
+ border-radius: 8px;
+ background: #f8fafc;
+ cursor: pointer;
+}
+
+.screenshot-thumb :deep(.el-image__inner) {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.screenshot-item figcaption {
+ margin-top: 4px;
+ color: #6b7280;
+ font-size: 11px;
+ font-weight: 700;
+ text-align: center;
+}
+
+/* ========== 底部留白 ========== */
+.bottom-spacer {
+ height: 20px;
+}
+
+/* ========== 底部下单栏 ========== */
+.order-bar {
+ position: fixed;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 100;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 8px 12px calc(8px + env(safe-area-inset-bottom));
+ background: #fff;
+ border-top: 1px solid #eee;
+ box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.04);
+}
+
+.order-bar-left {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.order-price {
+ display: flex;
+ align-items: baseline;
+ gap: 4px;
+}
+
+.price-label {
+ font-size: 11px;
+ color: #999;
+}
+
+.price-amount {
+ font-size: 18px;
+ font-weight: 900;
+ color: #ff5f00;
+}
+
+.order-price-detail {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px 8px;
+ color: #8a5a12;
+ font-size: 10px;
+ font-weight: 700;
+ line-height: 1.3;
+}
+
+.order-btn {
+ flex-shrink: 0;
+ padding: 0 20px;
+ height: 40px;
+ font-size: 14px;
+ font-weight: 700;
+ background: #ff6a00 !important;
+ border: none !important;
+}
+
+.agreement-popup {
+ max-height: 92vh;
+}
+
+.agreement-popup-body {
+ display: grid;
+ gap: 12px;
+ max-height: 92vh;
+ overflow: auto;
+ padding: 20px 16px calc(16px + env(safe-area-inset-bottom));
+}
+
+.agreement-popup-body h3 {
+ margin: 0;
+ color: #17233d;
+ font-size: 18px;
+ font-weight: 900;
+}
+
+.agreement-popup-body > p {
+ margin: 0;
+ color: #6b7280;
+ font-size: 13px;
+ font-weight: 700;
+}
+
+.agreement-panel {
+ display: grid;
+ gap: 8px;
+}
+
+.agreement-panel > strong {
+ color: #17233d;
+ font-size: 14px;
+}
+
+.agreement-content {
+ height: 190px;
+ overflow: auto;
+ border: 1px solid #e5e7eb;
+ border-radius: 8px;
+ background: #f8fafc;
+ padding: 12px;
+ color: #1f2937;
+ font-size: 12px;
+ font-weight: 700;
+ line-height: 1.7;
+ white-space: pre-wrap;
+}
+
+.agreement-read-hint {
+ color: #ff6a00;
+ font-size: 12px;
+ font-weight: 800;
+}
+
+.agreement-confirm-btn {
+ margin-top: 4px;
+ background: #ff6a00 !important;
+ border: none !important;
+ font-weight: 800;
+}
+
+/* ========== 空状态 ========== */
+.empty-state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
+ padding: 80px 0;
+ color: #999;
+ font-size: 14px;
+}
diff --git a/frontend/src/features/listings/views/MobileListingDetailView.vue b/frontend/src/features/listings/views/MobileListingDetailView.vue
index 5ab074a..12bf339 100644
--- a/frontend/src/features/listings/views/MobileListingDetailView.vue
+++ b/frontend/src/features/listings/views/MobileListingDetailView.vue
@@ -571,490 +571,4 @@ function handleToggleFavorite() {
-
+
diff --git a/frontend/src/features/orders/composables/__tests__/useOrderPaymentCashier.spec.ts b/frontend/src/features/orders/composables/__tests__/useOrderPaymentCashier.spec.ts
new file mode 100644
index 0000000..549d251
--- /dev/null
+++ b/frontend/src/features/orders/composables/__tests__/useOrderPaymentCashier.spec.ts
@@ -0,0 +1,103 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+
+vi.mock('qrcode', () => ({
+ default: { toDataURL: vi.fn().mockResolvedValue('data:image/png;base64,qr') },
+}))
+
+import type { PaymentOrder } from '@/features/orders/api/orders'
+import {
+ paymentPayURL,
+ useOrderPaymentCashier,
+} from '@/features/orders/composables/useOrderPaymentCashier'
+
+function payment(overrides: Partial
= {}): PaymentOrder {
+ return {
+ id: 1,
+ payment_no: 'PAY1',
+ order_id: 2,
+ order_no: 'ORD1',
+ provider: 'mock',
+ third_order_id: 'THIRD1',
+ provider_order_id: '',
+ pay_way: 'ZFBZF',
+ jspay_flag: '2',
+ amount_cent: 100,
+ status: 'paying',
+ paid: false,
+ created_at: '',
+ updated_at: '',
+ ...overrides,
+ }
+}
+
+function options() {
+ return {
+ notifySuccess: vi.fn(),
+ notifyError: vi.fn(),
+ notifyInfo: vi.fn(),
+ notifyFallback: vi.fn(),
+ queryPayment: vi.fn(),
+ }
+}
+
+describe('useOrderPaymentCashier', () => {
+ beforeEach(() => vi.clearAllMocks())
+
+ it('selects the first available payment URL', () => {
+ expect(paymentPayURL(payment({ jspay_url: '', td_code: 'https://td' }))).toBe('https://td')
+ expect(paymentPayURL(payment({ jspay_info: 'https://info' }))).toBe('https://info')
+ expect(paymentPayURL(null)).toBe('')
+ })
+
+ it('handles an already-paid payment and invokes the callback', async () => {
+ const current = options()
+ const onPaid = vi.fn()
+ const cashier = useOrderPaymentCashier(current)
+
+ await cashier.openPaymentCashier(payment({ paid: true }), onPaid)
+
+ expect(current.notifySuccess).toHaveBeenCalledWith('支付成功,正在进入群消息')
+ expect(onPaid).toHaveBeenCalledOnce()
+ expect(cashier.paymentPopupVisible.value).toBe(false)
+ })
+
+ it('uses fallback feedback for non-http payment URLs', async () => {
+ const current = options()
+ const cashier = useOrderPaymentCashier(current)
+
+ await cashier.openPaymentCashier(payment({ jspay_url: 'alipay://pay' }))
+
+ expect(current.notifyFallback).toHaveBeenCalledWith('alipay://pay')
+ })
+
+ it('renders a QR code, reports pending status, then handles paid polling result', async () => {
+ const current = options()
+ current.queryPayment
+ .mockResolvedValueOnce(payment({ jspay_url: 'https://pay', paid: false }))
+ .mockResolvedValueOnce(payment({ paid: true }))
+ const onPaid = vi.fn()
+ const cashier = useOrderPaymentCashier({ ...current, pollIntervalMs: 60_000 })
+
+ await cashier.openPaymentCashier(payment({ jspay_url: 'https://pay' }), onPaid)
+ expect(cashier.paymentQRCodeURL.value).toContain('data:image')
+ await cashier.refreshPaymentStatus()
+ expect(current.notifyInfo).toHaveBeenCalledWith('支付暂未完成')
+ await cashier.refreshPaymentStatus()
+ expect(current.notifySuccess).toHaveBeenCalled()
+ expect(onPaid).toHaveBeenCalledOnce()
+ cashier.stopPaymentPolling()
+ })
+
+ it('reports QR rendering errors without leaving the generating state set', async () => {
+ const current = options()
+ const cashier = useOrderPaymentCashier(current)
+ const qrcode = await import('qrcode')
+ vi.mocked(qrcode.default.toDataURL).mockRejectedValueOnce(new Error('render failed'))
+
+ await cashier.openPaymentCashier(payment({ jspay_url: 'https://pay' }))
+
+ expect(current.notifyError).toHaveBeenCalledWith('二维码生成失败')
+ expect(cashier.qrGenerating.value).toBe(false)
+ cashier.stopPaymentPolling()
+ })
+})
diff --git a/frontend/src/features/orders/views/MobileOrderDetailView.css b/frontend/src/features/orders/views/MobileOrderDetailView.css
new file mode 100644
index 0000000..5869495
--- /dev/null
+++ b/frontend/src/features/orders/views/MobileOrderDetailView.css
@@ -0,0 +1,259 @@
+.mobile-order-detail-view {
+ min-height: 100dvh;
+ background: #f5f7fa;
+ padding-bottom: 40px;
+}
+
+/* ========== Header ========== */
+.page-header {
+ position: sticky;
+ top: 0;
+ z-index: 10;
+ display: flex;
+ align-items: center;
+ height: 48px;
+ padding: 0 12px;
+ background: #fff;
+ border-bottom: 1px solid #eee;
+}
+
+.page-header h1 {
+ flex: 1;
+ margin: 0;
+ font-size: 17px;
+ font-weight: 700;
+ text-align: center;
+}
+
+.back-btn,
+.chat-btn {
+ display: grid;
+ width: 36px;
+ height: 36px;
+ place-items: center;
+ border: none;
+ background: none;
+ color: #333;
+ cursor: pointer;
+}
+
+.chat-btn:disabled {
+ color: #a1a1aa;
+}
+
+.header-spacer {
+ width: 36px;
+}
+
+.center-loading {
+ display: flex;
+ justify-content: center;
+ padding: 60px 0;
+}
+
+/* ========== Card layout ========== */
+.card-section {
+ margin: 12px;
+ background: #fff;
+ border-radius: 12px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
+ overflow: hidden;
+ padding: 14px;
+}
+
+.info-card {
+ padding: 16px;
+}
+
+.card-header-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 10px;
+}
+
+.order-no {
+ font-size: 12px;
+ color: #999;
+ font-family: monospace;
+}
+
+.listing-code-chip {
+ margin-bottom: 10px;
+ padding: 4px 9px;
+ border: 1px solid #dbeafe;
+ border-radius: 999px;
+ background: #eff6ff;
+ color: #2563eb;
+ font-size: 12px;
+ font-weight: 800;
+}
+
+.order-title {
+ font-size: 18px;
+ font-weight: 700;
+ color: #1a1a1a;
+ margin: 0 0 14px 0;
+}
+
+.order-meta-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 8px;
+ background: #f8f9fa;
+ border-radius: 8px;
+ padding: 10px;
+}
+
+.meta-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.meta-label {
+ font-size: 11px;
+ color: #8c96a0;
+ margin-bottom: 4px;
+}
+
+.meta-value {
+ font-size: 13px;
+ color: #2c3e50;
+ font-weight: 600;
+}
+
+.meta-value.accent {
+ color: #ff5f00;
+ font-weight: 700;
+}
+
+.meta-value.income {
+ color: #16a34a;
+ font-weight: 700;
+}
+
+.meta-note {
+ margin-top: 2px;
+ font-size: 10px;
+ color: #2563eb;
+}
+
+.action-card {
+ padding: 16px;
+}
+
+.section-title {
+ font-size: 15px;
+ font-weight: 700;
+ color: #2c3e50;
+ margin: 0 0 8px 0;
+}
+
+.action-hint {
+ font-size: 12px;
+ color: #7f8c8d;
+ line-height: 1.5;
+ margin: 0 0 14px 0;
+}
+
+.action-buttons {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.action-field {
+ background: #f8fafc;
+ border-radius: 8px;
+ margin-bottom: 12px;
+ overflow: hidden;
+}
+
+.custom-inline-input {
+ border: none;
+ background: transparent;
+ outline: none;
+ /* iOS Safari:font-size < 16px 时聚焦会自动放大页面 */
+ font-size: 16px;
+ flex: 1;
+}
+
+.no-snapshot-hint {
+ text-align: center;
+ padding: 10px;
+ color: #cbd5e0;
+ font-size: 13px;
+}
+
+/* ========== Popups ========== */
+.mobile-popup-form {
+ max-height: 85%;
+ padding-bottom: env(safe-area-inset-bottom);
+}
+
+.popup-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px;
+ border-bottom: 1px solid #edf2f7;
+}
+
+.popup-header h3 {
+ margin: 0;
+ font-size: 16px;
+ font-weight: 700;
+}
+
+.popup-close {
+ border: none;
+ background: transparent;
+ font-size: 16px;
+ color: #a0aec0;
+ cursor: pointer;
+}
+
+.popup-body {
+ padding: 16px;
+ overflow-y: auto;
+}
+
+.form-hint-info {
+ font-size: 12px;
+ color: #718096;
+ line-height: 1.5;
+ margin: 0 0 12px 0;
+}
+
+.popup-field {
+ background: #f7fafc;
+ border-radius: 8px;
+ margin-bottom: 12px;
+}
+
+.evidence-upload-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-size: 13px;
+ color: #4a5568;
+ margin-bottom: 16px;
+}
+
+.popup-submit {
+ margin-top: 10px;
+}
+
+.custom-select {
+ border: none;
+ background: transparent;
+ width: 100%;
+ font-size: 14px;
+ color: #2d3748;
+ outline: none;
+}
+
+.empty-wrap {
+ padding: 60px 0;
+}
diff --git a/frontend/src/features/orders/views/MobileOrderDetailView.vue b/frontend/src/features/orders/views/MobileOrderDetailView.vue
index e92194f..a3d1e62 100644
--- a/frontend/src/features/orders/views/MobileOrderDetailView.vue
+++ b/frontend/src/features/orders/views/MobileOrderDetailView.vue
@@ -660,264 +660,4 @@ async function copyListingCode() {
-
+
diff --git a/frontend/src/features/orders/views/OrderDetailView.css b/frontend/src/features/orders/views/OrderDetailView.css
new file mode 100644
index 0000000..b66e5cf
--- /dev/null
+++ b/frontend/src/features/orders/views/OrderDetailView.css
@@ -0,0 +1,773 @@
+.order-detail-page {
+ max-width: 1280px;
+ margin: 0 auto;
+}
+
+.page-header {
+ margin-bottom: 18px;
+}
+
+.page-header.centered {
+ max-width: 940px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.header-top {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 16px;
+}
+
+.header-info {
+ flex: 1;
+}
+
+.header-info h1 {
+ margin: 4px 0 0;
+ font-size: 26px;
+ line-height: 1.25;
+}
+
+.order-meta {
+ color: #64748b;
+ margin: 6px 0 10px;
+}
+
+.content-layout {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 280px;
+ gap: 16px;
+ align-items: start;
+}
+
+.content-layout.centered {
+ grid-template-columns: minmax(0, 1fr);
+ max-width: 940px;
+ margin: 0 auto;
+}
+
+.main-content {
+ min-width: 0;
+}
+
+.action-sidebar {
+ position: sticky;
+ top: 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.sidebar-card {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ padding: 14px;
+ background: #ffffff;
+ border: 1px solid #e5e7eb;
+ border-radius: 8px;
+ box-shadow: 0 1px 4px rgba(15, 23, 42, 0.05);
+}
+
+.sidebar-card.highlight-card {
+ background: #f8fbff;
+ border-color: #3b82f6;
+}
+
+.sidebar-title {
+ font-size: 14px;
+ font-weight: 700;
+ color: #1f2937;
+ margin: 0;
+}
+
+.sidebar-hint {
+ margin: 0;
+ font-size: 12px;
+ color: #64748b;
+ line-height: 1.45;
+}
+
+.sidebar-card .el-button {
+ width: 100%;
+}
+
+.support-dispute-icon {
+ display: grid;
+ flex: none;
+ width: 32px;
+ height: 32px;
+ place-items: center;
+ border-radius: 8px;
+ background: #fff1d6;
+ color: #d97706;
+ font-size: 18px;
+}
+
+.support-dispute-copy {
+ display: grid;
+ gap: 2px;
+ min-width: 0;
+ flex: 1;
+}
+
+.support-dispute-copy strong {
+ color: #1f2937;
+ font-size: 14px;
+ line-height: 1.3;
+}
+
+.support-dispute-copy span {
+ color: #7c5a16;
+ font-size: 12px;
+ line-height: 1.45;
+}
+
+.order-progress-section {
+ margin-bottom: 12px;
+ padding: 16px;
+ background: #ffffff;
+ border: 1px solid #e5e7eb;
+ border-radius: 8px;
+}
+
+.detail-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+ gap: 10px;
+ margin-bottom: 12px;
+}
+
+.metric-card {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ min-height: 86px;
+ padding: 14px;
+ border: 1px solid #e5e7eb;
+ border-radius: 8px;
+ background: #ffffff;
+ transition: all 0.2s;
+}
+
+.metric-card:hover {
+ border-color: #cbd5e1;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
+}
+
+.metric-card.primary {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ border: none;
+ color: #ffffff;
+}
+
+.metric-card.primary .metric-label {
+ color: rgba(255, 255, 255, 0.9);
+}
+
+.metric-card.primary .metric-value {
+ color: #ffffff;
+}
+
+.metric-card.highlight {
+ background: linear-gradient(135deg, #ff6a00 0%, #ee0979 100%);
+ border: none;
+ color: #ffffff;
+}
+
+.metric-card.highlight .metric-label {
+ color: rgba(255, 255, 255, 0.9);
+}
+
+.metric-card.highlight .metric-value {
+ color: #ffffff;
+}
+
+.metric-card.income {
+ background: #f0fdf4;
+ border-color: #bbf7d0;
+}
+
+.metric-card.income .metric-label {
+ color: #15803d;
+}
+
+.metric-card.income .metric-value {
+ color: #16a34a;
+}
+
+.metric-label {
+ font-size: 12px;
+ color: #64748b;
+ font-weight: 500;
+}
+
+.metric-value {
+ font-size: 20px;
+ font-weight: 700;
+ color: #1f2937;
+ line-height: 1.2;
+}
+
+.metric-value.amount {
+ color: #ff6a00;
+}
+
+.metric-note {
+ font-size: 12px;
+ color: #2563eb;
+}
+
+.info-section {
+ margin-bottom: 16px;
+}
+
+.info-card {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 16px;
+ padding: 14px 16px;
+ background: #ffffff;
+ border: 1px solid #e5e7eb;
+ border-radius: 8px;
+}
+
+.info-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ padding: 0;
+}
+
+.info-label {
+ font-size: 13px;
+ color: #64748b;
+ font-weight: 500;
+}
+
+.info-value {
+ font-size: 13px;
+ color: #1f2937;
+ font-weight: 600;
+ text-align: right;
+}
+
+.action-card {
+ margin-top: 12px;
+ padding: 14px;
+ background: #ffffff;
+ border: 1px solid #e5e7eb;
+ border-radius: 8px;
+}
+
+.action-card.warning {
+ background: #fffbeb;
+ border-color: #fde68a;
+}
+
+.action-message {
+ margin: 0 0 12px 0;
+ color: #1f2937;
+ font-size: 14px;
+}
+
+.action-buttons {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 12px;
+}
+
+.section-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 10px;
+}
+
+.section-header.compact span {
+ color: #64748b;
+ font-size: 12px;
+}
+
+.section-header h2 {
+ font-size: 18px;
+ font-weight: 700;
+ color: #1f2937;
+ margin: 0;
+}
+
+.form-section {
+ margin-bottom: 20px;
+}
+
+.form-card {
+ display: grid;
+ gap: 12px;
+ padding: 16px;
+ background: #ffffff;
+ border: 1px solid #e5e7eb;
+ border-radius: 8px;
+}
+
+.handoff-action-section {
+ margin-bottom: 14px;
+}
+
+.handoff-action-card {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 160px;
+ gap: 12px;
+ align-items: stretch;
+ padding: 16px;
+ border: 1px solid #bfdbfe;
+ border-radius: 8px;
+ background: linear-gradient(180deg, #f8fbff 0%, #eff6ff 100%);
+}
+
+.handoff-action-card .el-button {
+ height: 100%;
+ min-height: 96px;
+}
+
+.handoff-confirm-strip {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 14px;
+ padding: 16px;
+ border: 1px solid #bfdbfe;
+ border-radius: 8px;
+ background: linear-gradient(180deg, #f8fbff 0%, #eff6ff 100%);
+}
+
+.handoff-confirm-strip div {
+ display: grid;
+ gap: 4px;
+ flex: 1;
+ min-width: 0;
+}
+
+.handoff-confirm-strip strong {
+ color: #1f2937;
+ font-size: 14px;
+}
+
+.handoff-confirm-strip span {
+ color: #475569;
+ font-size: 12px;
+ line-height: 1.45;
+}
+
+.secondary-actions {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
+ gap: 10px;
+ margin-top: 12px;
+}
+
+.secondary-action-card {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 12px 14px;
+ border-radius: 8px;
+}
+
+.secondary-action-card > div {
+ display: grid;
+ gap: 2px;
+ min-width: 0;
+ flex: 1;
+}
+
+.secondary-action-card strong {
+ color: #1f2937;
+ font-size: 13px;
+ line-height: 1.3;
+}
+
+.secondary-action-card span {
+ color: #64748b;
+ font-size: 12px;
+ line-height: 1.45;
+}
+
+.secondary-action-card.danger {
+ border: 1px solid #fecaca;
+ background: #fff7f7;
+}
+
+.secondary-action-card.support {
+ border: 1px solid #f8d99b;
+ background: #fffaf0;
+}
+
+.form-hint {
+ margin: 0;
+ padding: 10px 12px;
+ background: #f8fafc;
+ border-left: 3px solid #3b82f6;
+ color: #475569;
+ font-size: 13px;
+ line-height: 1.5;
+ border-radius: 4px;
+}
+
+.section-divider {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin: 24px 0;
+ position: relative;
+}
+
+.section-divider::before,
+.section-divider::after {
+ content: '';
+ flex: 1;
+ height: 1px;
+ background: #e5e7eb;
+}
+
+.section-divider span {
+ padding: 0 16px;
+ color: #9ca3af;
+ font-size: 14px;
+ font-weight: 500;
+}
+
+.section-divider-mini {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin: 8px 0;
+ position: relative;
+}
+
+.section-divider-mini::before,
+.section-divider-mini::after {
+ content: '';
+ flex: 1;
+ height: 1px;
+ background: #e5e7eb;
+}
+
+.section-divider-mini span {
+ padding: 0 12px;
+ color: #9ca3af;
+ font-size: 12px;
+ font-weight: 500;
+}
+
+.sub-title {
+ font-size: 15px;
+ font-weight: 600;
+ color: #374151;
+ margin: 0 0 6px 0;
+}
+
+.form-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
+ gap: 12px;
+}
+
+.field-hint {
+ display: block;
+ margin-top: 6px;
+ color: #6b7280;
+ font-size: 12px;
+}
+
+.dispute-dialog-body {
+ display: grid;
+ gap: 12px;
+}
+
+.dispute-dialog-tip {
+ display: flex;
+ gap: 10px;
+ align-items: center;
+ padding: 10px 12px;
+ border: 1px solid #f8d99b;
+ border-radius: 8px;
+ background: #fffaf0;
+}
+
+.dispute-dialog-tip p {
+ margin: 0;
+ color: #7c5a16;
+ font-size: 13px;
+ line-height: 1.5;
+}
+
+.dispute-dialog-footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+}
+
+.upload-line {
+ padding: 12px;
+ background: #f9fafb;
+ border: 1px dashed #cbd5e1;
+ border-radius: 6px;
+}
+
+.upload-line input[type='file'] {
+ width: 100%;
+ font-size: 14px;
+}
+
+:global(.dispute-dialog) {
+ border-radius: 12px;
+}
+
+:global(.dispute-dialog .el-dialog__body) {
+ padding: 14px 20px 8px;
+}
+
+:global(.dispute-dialog .el-dialog__footer) {
+ padding: 12px 20px 18px;
+}
+
+.pay-dialog-body {
+ display: grid;
+ gap: 20px;
+}
+
+.pay-way-tip {
+ margin: 0 0 16px 0;
+ color: #64748b;
+ font-size: 14px;
+}
+
+.pay-way-options {
+ display: grid;
+ gap: 12px;
+}
+
+.pay-way-option {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ padding: 16px 18px;
+ background: #ffffff;
+ border: 2px solid #e5e7eb;
+ border-radius: 12px;
+ cursor: pointer;
+ text-align: left;
+ transition: all 0.18s ease;
+}
+
+.pay-way-option:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08);
+}
+
+.pay-way-icon {
+ display: grid;
+ place-items: center;
+ width: 48px;
+ height: 48px;
+ border-radius: 12px;
+ color: #ffffff;
+ flex-shrink: 0;
+}
+
+.pay-way-option.wechat:hover {
+ border-color: #07c160;
+}
+
+.pay-way-option.wechat .pay-way-icon {
+ background: #07c160;
+}
+
+.pay-way-option.alipay:hover {
+ border-color: #1677ff;
+}
+
+.pay-way-option.alipay .pay-way-icon {
+ background: #1677ff;
+}
+
+.pay-way-text {
+ display: flex;
+ flex-direction: column;
+ gap: 3px;
+}
+
+.pay-way-text strong {
+ font-size: 16px;
+ font-weight: 600;
+ color: #1f2937;
+}
+
+.pay-way-text small {
+ font-size: 13px;
+ color: #94a3b8;
+}
+
+:global(.pay-way-dialog) {
+ border-radius: 16px;
+}
+
+:global(.pay-way-dialog .el-dialog__header) {
+ padding: 20px 24px 0;
+}
+
+:global(.pay-way-dialog .el-dialog__body) {
+ padding: 16px 24px 24px;
+}
+
+.pay-summary {
+ padding: 16px 20px;
+ background: #f8fafc;
+ border-radius: 8px;
+}
+
+.pay-summary-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.pay-summary-row span {
+ color: #64748b;
+ font-size: 14px;
+}
+
+.pay-summary-row strong {
+ color: #111a44;
+ font-size: 28px;
+ font-weight: 700;
+}
+
+.pay-qr-section {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 20px;
+ padding: 24px;
+ background: #f8fafc;
+ border-radius: 12px;
+}
+
+.pay-qr-box {
+ width: 240px;
+ height: 240px;
+ display: grid;
+ place-items: center;
+ border: 1px solid #e2e8f0;
+ border-radius: 12px;
+ background: #ffffff;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+}
+
+.pay-qr-box img {
+ width: 220px;
+ height: 220px;
+ display: block;
+}
+
+.pay-instructions {
+ text-align: center;
+}
+
+.pay-instructions h3 {
+ margin: 0 0 8px 0;
+ color: #111a44;
+ font-size: 18px;
+ font-weight: 600;
+}
+
+.pay-instructions p {
+ margin: 0;
+ color: #64748b;
+ font-size: 14px;
+ line-height: 1.7;
+}
+
+.pay-hint {
+ margin: 0;
+ padding: 16px;
+ background: #f8fafc;
+ color: #64748b;
+ font-size: 14px;
+ text-align: center;
+ border-radius: 8px;
+}
+
+.pay-dialog-footer {
+ display: flex;
+ justify-content: center;
+ padding-top: 8px;
+}
+
+:global(.order-pay-dialog) {
+ border-radius: 16px;
+}
+
+:global(.order-pay-dialog .el-dialog__header) {
+ padding: 20px 24px;
+ border-bottom: 1px solid #e5e7eb;
+}
+
+:global(.order-pay-dialog .el-dialog__body) {
+ padding: 24px;
+}
+
+:global(.order-pay-dialog .el-dialog__footer) {
+ padding: 16px 24px;
+ border-top: 1px solid #e5e7eb;
+}
+
+@media (max-width: 768px) {
+ .content-layout {
+ grid-template-columns: 1fr;
+ }
+
+ .action-sidebar {
+ position: static;
+ order: -1;
+ }
+
+ .header-top {
+ flex-direction: column;
+ }
+
+ .detail-grid {
+ grid-template-columns: repeat(2, 1fr);
+ }
+
+ .form-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .handoff-action-card,
+ .handoff-confirm-strip,
+ .secondary-action-card {
+ grid-template-columns: 1fr;
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .handoff-action-card .el-button {
+ min-height: 40px;
+ }
+
+ .action-buttons {
+ flex-direction: column;
+ }
+
+ .action-buttons .el-button {
+ width: 100%;
+ }
+
+ .pay-qr-section {
+ padding: 16px;
+ }
+}
diff --git a/frontend/src/features/orders/views/OrderDetailView.vue b/frontend/src/features/orders/views/OrderDetailView.vue
index 93d5c8f..9a5c690 100644
--- a/frontend/src/features/orders/views/OrderDetailView.vue
+++ b/frontend/src/features/orders/views/OrderDetailView.vue
@@ -823,778 +823,4 @@ watch([() => route.query.focus, order, loading], () => {
-
+
diff --git a/frontend/src/features/orders/views/OrdersView.css b/frontend/src/features/orders/views/OrdersView.css
new file mode 100644
index 0000000..0952e8b
--- /dev/null
+++ b/frontend/src/features/orders/views/OrdersView.css
@@ -0,0 +1,396 @@
+.orders-toolbar {
+ margin-top: 24px;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.search-input {
+ max-width: 400px;
+}
+
+.order-tabs {
+ margin-top: 16px;
+ border: 1px solid #e4e7ed;
+ border-radius: 8px;
+ background: #ffffff;
+ padding: 0 16px;
+}
+
+.order-tabs :deep(.el-tabs__header) {
+ margin: 0;
+}
+
+.order-tabs :deep(.el-tabs__nav-wrap::after) {
+ height: 0;
+}
+
+.tab-label {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.tab-count {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 20px;
+ height: 20px;
+ padding: 0 6px;
+ border-radius: 10px;
+ background: #ff6a00;
+ color: #ffffff;
+ font-size: 12px;
+ font-weight: 600;
+}
+
+.order-tabs :deep(.is-active) .tab-count {
+ background: #ffffff;
+ color: #ff6a00;
+}
+
+.orders-container {
+ margin-top: 24px;
+}
+
+.empty-state {
+ margin-top: 60px;
+}
+
+.orders-table {
+ display: block;
+}
+
+.mobile-cards {
+ display: none;
+}
+
+.biz-tag {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 40px;
+ padding: 2px 8px;
+ border-radius: 999px;
+ font-size: 12px;
+ font-weight: 700;
+}
+
+.biz-tag.rental {
+ color: #1477ff;
+ background: rgba(20, 119, 255, 0.1);
+}
+
+.biz-tag.crash {
+ color: #ff6a00;
+ background: rgba(255, 106, 0, 0.1);
+}
+
+.order-no-cell {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.order-no-text {
+ font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
+ font-size: 13px;
+ color: #4a5568;
+}
+
+.listing-code-line {
+ display: inline-flex;
+ width: fit-content;
+ align-items: center;
+ gap: 5px;
+ margin-top: 10px;
+ padding: 4px 9px;
+ border: 1px solid #dbeafe;
+ border-radius: 999px;
+ background: #eff6ff;
+ color: #2563eb;
+ font-size: 12px;
+ font-weight: 800;
+ cursor: pointer;
+}
+
+.listing-code-line.compact {
+ margin-top: 0;
+ margin-bottom: 4px;
+}
+
+.copy-icon {
+ cursor: pointer;
+ color: #9ca3af;
+ font-size: 14px;
+ transition: color 0.2s;
+}
+
+.copy-icon:hover {
+ color: #ff6a00;
+}
+
+.amount-cell {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.amount-value {
+ font-weight: 600;
+ color: #1f2937;
+}
+
+.amount-label {
+ font-size: 12px;
+ color: #9ca3af;
+}
+
+.amount-sub {
+ font-size: 12px;
+ color: #10b981;
+ font-weight: 600;
+}
+
+.status-cell {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.countdown-badge {
+ display: inline-block;
+ padding: 2px 6px;
+ border-radius: 4px;
+ background: #fef3c7;
+ color: #d97706;
+ font-size: 11px;
+ font-weight: 500;
+}
+
+.action-buttons {
+ display: flex;
+ gap: 8px;
+}
+
+.muted {
+ color: #9ca3af;
+ font-size: 13px;
+}
+
+.crash-product-cell,
+.rental-product-cell {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ min-width: 0;
+}
+
+.crash-product-cell {
+ flex-direction: row;
+ align-items: center;
+ gap: 12px;
+}
+
+.crash-cover {
+ width: 48px;
+ height: 48px;
+ border-radius: 10px;
+ overflow: hidden;
+ background: #f0f3f8;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #a0aec0;
+ font-size: 12px;
+ flex-shrink: 0;
+}
+
+.crash-cover.lg {
+ width: 64px;
+ height: 64px;
+}
+
+.crash-cover img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.crash-product-info {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ min-width: 0;
+}
+
+.crash-product-info strong,
+.rental-title {
+ font-size: 14px;
+ color: #1f2937;
+}
+
+.crash-product-info span {
+ font-size: 12px;
+ color: #8a94a6;
+}
+
+.crash-status {
+ display: inline-flex;
+ padding: 2px 8px;
+ border-radius: 999px;
+ font-size: 12px;
+ font-weight: 700;
+ background: #f1f5f9;
+ color: #64748b;
+}
+
+.crash-status.pending_payment {
+ background: #fff7ed;
+ color: #ff6a00;
+}
+
+.crash-status.paid {
+ background: #ecfdf5;
+ color: #059669;
+}
+
+.crash-status.receiving {
+ background: #fff7ed;
+ color: #d97706;
+}
+
+.crash-status.completed {
+ background: #eff6ff;
+ color: #2563eb;
+}
+
+.crash-status.cancelled,
+.crash-status.refunded {
+ background: #f8fafc;
+ color: #94a3b8;
+}
+
+.crash-card-body {
+ display: grid;
+ grid-template-columns: 64px 1fr;
+ gap: 12px;
+}
+
+.order-card {
+ border: 1px solid #e5e7eb;
+ border-radius: 12px;
+ padding: 16px;
+ background: #ffffff;
+ cursor: pointer;
+ transition: all 0.2s;
+ margin-bottom: 12px;
+}
+
+.order-card:hover {
+ border-color: #ff6a00;
+ box-shadow: 0 4px 12px rgba(255, 106, 0, 0.1);
+}
+
+.card-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 12px;
+}
+
+.order-no-row {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ min-width: 0;
+}
+
+.card-title {
+ font-size: 16px;
+ font-weight: 600;
+ color: #1f2937;
+ margin-bottom: 12px;
+}
+
+.card-meta {
+ display: grid;
+ gap: 8px;
+ margin-bottom: 12px;
+ padding-bottom: 12px;
+ border-bottom: 1px solid #f3f4f6;
+}
+
+.meta-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.meta-label {
+ font-size: 13px;
+ color: #6b7280;
+}
+
+.meta-value {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ font-size: 14px;
+ color: #374151;
+ font-weight: 500;
+}
+
+.meta-value em {
+ font-style: normal;
+ font-size: 12px;
+ color: #2563eb;
+}
+
+.meta-value.amount {
+ color: #ff6a00;
+ font-weight: 600;
+ font-size: 15px;
+}
+
+.card-footer {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.status-info {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.status-label {
+ font-size: 14px;
+ font-weight: 500;
+ color: #1f2937;
+}
+
+@media (max-width: 768px) {
+ .orders-table {
+ display: none;
+ }
+
+ .mobile-cards {
+ display: block;
+ }
+
+ .search-input {
+ max-width: 100%;
+ }
+
+ .order-tabs {
+ padding: 0 8px;
+ }
+
+ .order-tabs :deep(.el-tabs__item) {
+ padding: 0 12px;
+ font-size: 14px;
+ }
+}
diff --git a/frontend/src/features/orders/views/OrdersView.vue b/frontend/src/features/orders/views/OrdersView.vue
index ca41b6d..4fec8ce 100644
--- a/frontend/src/features/orders/views/OrdersView.vue
+++ b/frontend/src/features/orders/views/OrdersView.vue
@@ -586,401 +586,4 @@ function bizTypeLabel(bizType: BizType) {
-
+
diff --git a/frontend/src/features/wallet/views/MobileWithdrawalView.css b/frontend/src/features/wallet/views/MobileWithdrawalView.css
new file mode 100644
index 0000000..2e36b65
--- /dev/null
+++ b/frontend/src/features/wallet/views/MobileWithdrawalView.css
@@ -0,0 +1,484 @@
+.mobile-withdrawal {
+ --van-primary-color: #ff6a00;
+ --van-button-primary-background: #ff6a00;
+ --van-button-primary-border-color: #ff6a00;
+ --van-radio-checked-icon-color: #ff6a00;
+ --van-checkbox-checked-icon-color: #ff6a00;
+ --van-tag-warning-color: #ff6a00;
+ min-height: 100vh;
+ padding: 12px 14px 28px;
+ background: #fff7ed;
+}
+
+.page-header {
+ display: grid;
+ grid-template-columns: 40px 1fr 40px;
+ align-items: center;
+ height: 44px;
+ margin-bottom: 12px;
+}
+
+.page-header h1 {
+ margin: 0;
+ text-align: center;
+ font-size: 17px;
+ color: #101828;
+}
+
+.back-btn,
+.refresh-btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 36px;
+ height: 36px;
+ border: 0;
+ border-radius: 18px;
+ background: #fff;
+ color: #1f2937;
+}
+
+.refresh-btn {
+ justify-self: end;
+}
+
+.balance-panel {
+ display: grid;
+ gap: 6px;
+ padding: 18px;
+ border-radius: 8px;
+ background: linear-gradient(135deg, #ff8a1f 0%, #ff5a00 100%);
+ color: #fff;
+ box-shadow: 0 14px 28px rgba(255, 106, 0, 0.22);
+}
+
+.balance-panel span,
+.balance-panel small {
+ color: rgba(255, 255, 255, 0.72);
+ font-size: 12px;
+}
+
+.balance-panel strong {
+ font-size: 31px;
+ line-height: 1.1;
+}
+
+.withdraw-card,
+.records-card {
+ margin-top: 12px;
+ padding: 14px;
+ border-radius: 8px;
+ background: #fff;
+}
+
+.section-title {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ margin-bottom: 12px;
+}
+
+.section-title h2 {
+ margin: 0;
+ font-size: 16px;
+ color: #101828;
+}
+
+.section-title button {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ border: 0;
+ background: transparent;
+ color: #ff6a00;
+ font-size: 13px;
+}
+
+.section-title button:disabled {
+ color: #98a2b3;
+}
+
+.section-title span {
+ color: #98a2b3;
+ font-size: 12px;
+}
+
+.center-loading {
+ display: flex;
+ justify-content: center;
+ padding: 28px 0;
+}
+
+.empty-state {
+ display: grid;
+ justify-items: center;
+ gap: 10px;
+ padding: 24px 0;
+ color: #98a2b3;
+}
+
+.empty-state p {
+ margin: 0;
+}
+
+.account-list {
+ display: grid;
+ gap: 8px;
+ margin-bottom: 12px;
+}
+
+.account-option {
+ display: grid;
+ gap: 8px;
+ padding: 10px;
+ border: 1px solid #eaecf0;
+ border-radius: 8px;
+ background: #fff;
+}
+
+.account-option.active {
+ border-color: #ff6a00;
+ background: #fff7ed;
+}
+
+.account-select {
+ display: grid;
+ grid-template-columns: 34px 1fr auto 20px;
+ align-items: center;
+ gap: 8px;
+ width: 100%;
+ min-height: 44px;
+ padding: 0;
+ border: 0;
+ background: transparent;
+ text-align: left;
+}
+
+.account-icon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 34px;
+ height: 34px;
+ border-radius: 8px;
+ background: #fff3e8;
+ color: #ff6a00;
+}
+
+.account-main {
+ display: grid;
+ gap: 3px;
+ min-width: 0;
+}
+
+.account-main strong {
+ overflow: hidden;
+ color: #101828;
+ font-size: 14px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.account-main small {
+ overflow: hidden;
+ color: #667085;
+ font-size: 12px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.account-actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: 8px;
+ padding-top: 8px;
+ border-top: 1px solid #f2f4f7;
+}
+
+.account-actions button {
+ border: 0;
+ background: transparent;
+ color: #ff6a00;
+ font-size: 12px;
+}
+
+.account-actions button.danger {
+ color: #b42318;
+}
+
+.account-actions button:disabled {
+ color: #98a2b3;
+}
+
+:deep(.van-cell) {
+ padding-right: 0;
+ padding-left: 0;
+}
+
+.amount-meta,
+.amount-preview {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
+ margin: 10px 0;
+ color: #667085;
+ font-size: 12px;
+}
+
+.amount-meta button {
+ border: 0;
+ background: transparent;
+ color: #ff6a00;
+}
+
+.amount-preview {
+ padding: 10px 12px;
+ border-radius: 8px;
+ background: #f8fafc;
+}
+
+.amount-preview strong {
+ color: #ff6a00;
+ font-size: 18px;
+}
+
+.submit-btn {
+ margin-top: 14px;
+}
+
+.record-list {
+ display: grid;
+ gap: 10px;
+}
+
+.record-item {
+ padding: 12px;
+ border: 1px solid #eaecf0;
+ border-radius: 8px;
+ background: #fff;
+}
+
+.record-top,
+.record-meta {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 10px;
+}
+
+.record-top strong {
+ display: block;
+ color: #101828;
+ font-size: 18px;
+}
+
+.record-top small,
+.record-meta {
+ color: #98a2b3;
+ font-size: 12px;
+}
+
+.record-meta {
+ margin-top: 8px;
+}
+
+.record-remark {
+ margin: 10px 0 0;
+ color: #b42318;
+ font-size: 12px;
+}
+
+.cancel-btn {
+ margin-top: 10px;
+ padding: 6px 10px;
+ border: 1px solid #fecdca;
+ border-radius: 999px;
+ background: #fff;
+ color: #b42318;
+ font-size: 12px;
+}
+
+.account-popup {
+ max-height: 86vh;
+}
+
+.popup-body {
+ padding: 20px 16px 24px;
+}
+
+.popup-body h2 {
+ margin: 0 0 16px;
+ font-size: 17px;
+ color: #101828;
+}
+
+.delete-account-btn {
+ margin-top: 10px;
+}
+
+.type-tabs {
+ gap: 8px 12px;
+ flex-wrap: wrap;
+ margin-bottom: 6px;
+}
+
+.type-tabs :deep(.van-radio) {
+ margin-right: 0;
+}
+
+.type-tabs :deep(.van-radio__label) {
+ color: #1f2937;
+ font-size: 15px;
+}
+
+.type-label {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+}
+
+.recommend-badge {
+ display: inline-flex;
+ align-items: center;
+ height: 17px;
+ padding: 0 6px;
+ border-radius: 999px;
+ background: #fff7ed;
+ color: #c2410c;
+ font-size: 11px;
+ font-weight: 700;
+ line-height: 1;
+}
+
+.type-tip {
+ margin: 0 0 12px;
+ color: #8a5a13;
+ font-size: 12px;
+ line-height: 1.5;
+}
+
+.account-tip {
+ margin: 12px 0;
+ padding: 10px 12px;
+ border-radius: 8px;
+ background: #fffbeb;
+ color: #92400e;
+ font-size: 12px;
+ line-height: 1.6;
+}
+
+.receive-qr-tip {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 108px;
+ gap: 10px;
+ align-items: center;
+ margin: 12px 0;
+ padding: 10px;
+ border: 1px solid #fed7aa;
+ border-radius: 10px;
+ background: #fff7ed;
+ color: #92400e;
+}
+
+.receive-qr-tip strong {
+ display: block;
+ margin-bottom: 5px;
+ color: #c2410c;
+ font-size: 13px;
+}
+
+.receive-qr-tip p {
+ margin: 0;
+ font-size: 12px;
+ line-height: 1.55;
+}
+
+.receive-qr-tip img {
+ display: block;
+ width: 108px;
+ border: 1px solid #fde6c8;
+ border-radius: 8px;
+ background: #ffffff;
+}
+
+.certificate-section {
+ margin: 14px 0;
+}
+
+.certificate-title {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 10px;
+ color: #1f2937;
+ font-size: 14px;
+ font-weight: 600;
+}
+
+.certificate-title small {
+ color: #98a2b3;
+ font-size: 12px;
+ font-weight: 400;
+}
+
+.certificate-grid {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 10px;
+}
+
+.certificate-item,
+.certificate-upload {
+ position: relative;
+ overflow: hidden;
+ aspect-ratio: 1;
+ border-radius: 8px;
+ background: #fff7ed;
+}
+
+.certificate-item {
+ border: 1px solid #fed7aa;
+}
+
+.certificate-item img {
+ display: block;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.certificate-remove {
+ position: absolute;
+ top: 6px;
+ right: 6px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 22px;
+ height: 22px;
+ border: 0;
+ border-radius: 50%;
+ background: rgba(15, 23, 42, 0.72);
+ color: #fff;
+}
+
+.certificate-upload {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ border: 1px dashed #ff9f43;
+ color: #ff6a00;
+ font-size: 12px;
+}
+
+.certificate-upload input {
+ display: none;
+}
+
+.certificate-upload span {
+ line-height: 1;
+}
diff --git a/frontend/src/features/wallet/views/MobileWithdrawalView.vue b/frontend/src/features/wallet/views/MobileWithdrawalView.vue
index a419f23..24b3b90 100644
--- a/frontend/src/features/wallet/views/MobileWithdrawalView.vue
+++ b/frontend/src/features/wallet/views/MobileWithdrawalView.vue
@@ -664,489 +664,4 @@ function statusType(status: string): 'primary' | 'success' | 'danger' | 'warning
-
+
diff --git a/frontend/src/layouts/AdminLayout.css b/frontend/src/layouts/AdminLayout.css
new file mode 100644
index 0000000..7cf5377
--- /dev/null
+++ b/frontend/src/layouts/AdminLayout.css
@@ -0,0 +1,373 @@
+.admin-shell {
+ display: flex;
+ height: 100vh;
+ min-height: 100vh;
+ background-color: #f5f7fa;
+ overflow: hidden;
+}
+
+.admin-sidebar {
+ width: 220px;
+ height: 100vh;
+ flex: none;
+ background-color: #304156;
+ transition: width 0.3s ease;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+.admin-shell--collapsed .admin-sidebar {
+ width: 64px;
+}
+
+.admin-sidebar-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px;
+ border-bottom: 1px solid #3d4e5f;
+}
+
+.admin-brand {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ text-decoration: none;
+ color: #fff;
+ overflow: hidden;
+}
+
+.brand-mark {
+ width: 32px;
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: #409eff;
+ border-radius: 4px;
+ font-size: 18px;
+ font-weight: 700;
+ flex-shrink: 0;
+}
+
+.brand-text {
+ font-size: 16px;
+ font-weight: 600;
+ white-space: nowrap;
+}
+
+.collapse-btn {
+ color: #bfcbd9;
+ cursor: pointer;
+ font-size: 20px;
+ flex-shrink: 0;
+}
+
+.collapse-btn:hover {
+ color: #fff;
+}
+
+.admin-menu {
+ --el-menu-bg-color: transparent;
+ --el-menu-text-color: #bfcbd9;
+ --el-menu-active-color: #409eff;
+ --el-menu-hover-bg-color: #263445;
+
+ flex: 1;
+ min-height: 0;
+ border-right: none;
+ background-color: transparent;
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+.admin-sidebar-footer {
+ display: grid;
+ flex: none;
+ gap: 10px;
+ padding: 12px;
+ border-top: 1px solid #3d4e5f;
+ background: rgba(16, 24, 39, 0.18);
+}
+
+.admin-shell--collapsed .admin-sidebar-footer {
+ padding: 10px 8px;
+}
+
+.sidebar-notification {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ width: 100%;
+ min-height: 32px;
+ padding: 0 10px;
+ border: 1px solid rgba(64, 158, 255, 0.35);
+ border-radius: 8px;
+ color: #d7dfec;
+ text-decoration: none;
+ transition:
+ background-color 0.2s,
+ border-color 0.2s;
+}
+
+.sidebar-notification:hover,
+.sidebar-notification.router-link-active {
+ background-color: rgba(64, 158, 255, 0.14);
+ border-color: rgba(64, 158, 255, 0.8);
+ color: #fff;
+}
+
+.admin-shell--collapsed .sidebar-notification {
+ padding: 0;
+}
+
+.sidebar-notification-icon {
+ position: relative;
+ display: grid;
+ width: 20px;
+ height: 20px;
+ place-items: center;
+}
+
+.sidebar-notification-text {
+ font-size: 13px;
+ font-weight: 700;
+}
+
+.sidebar-badge {
+ position: absolute;
+ top: -8px;
+ right: -13px;
+ min-width: 16px;
+ height: 16px;
+ padding: 0 4px;
+ border: 2px solid #304156;
+ border-radius: 999px;
+ background: #ef4444;
+ color: #fff;
+ font-size: 10px;
+ font-style: normal;
+ font-weight: 800;
+ line-height: 12px;
+ text-align: center;
+ box-sizing: border-box;
+}
+
+.sidebar-status {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 7px;
+ width: 100%;
+ min-height: 30px;
+ padding: 0 10px;
+ border: 1px solid;
+ border-radius: 8px;
+ color: #d7dfec;
+ cursor: pointer;
+ transition:
+ background-color 0.2s,
+ border-color 0.2s;
+}
+
+.sidebar-status:hover {
+ background-color: rgba(255, 255, 255, 0.08);
+}
+
+.admin-shell--collapsed .sidebar-status {
+ padding: 0;
+}
+
+.sidebar-user {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ min-width: 0;
+ width: 100%;
+ padding: 8px;
+ border-radius: 10px;
+ cursor: pointer;
+ transition: background-color 0.2s;
+}
+
+.sidebar-user:hover {
+ background-color: rgba(255, 255, 255, 0.08);
+}
+
+.admin-shell--collapsed .sidebar-user {
+ justify-content: center;
+ padding: 8px 0;
+}
+
+.sidebar-user-text {
+ display: grid;
+ min-width: 0;
+ gap: 2px;
+}
+
+.sidebar-user-text strong {
+ overflow: hidden;
+ color: #fff;
+ font-size: 13px;
+ line-height: 18px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.sidebar-user-text small {
+ overflow: hidden;
+ color: #8fa0ba;
+ font-size: 12px;
+ line-height: 16px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.admin-menu:not(.el-menu--collapse) {
+ width: 100%;
+}
+
+:deep(.el-menu) {
+ background-color: transparent;
+}
+
+:deep(.el-sub-menu__title) {
+ color: #bfcbd9;
+}
+
+:deep(.el-sub-menu__title:hover) {
+ background-color: #263445;
+}
+
+:deep(.el-sub-menu.is-active > .el-sub-menu__title) {
+ color: #409eff;
+}
+
+:deep(.el-menu-item) {
+ color: #bfcbd9;
+}
+
+:deep(.el-menu-item:hover) {
+ background-color: #263445;
+}
+
+:deep(.el-menu-item.is-active) {
+ color: #409eff;
+ background-color: #263445;
+}
+
+:deep(.el-menu--inline) {
+ background-color: #263445;
+}
+
+:deep(.el-menu--inline .el-menu-item) {
+ height: 46px;
+ background-color: #263445;
+ font-size: 14px;
+}
+
+:deep(.el-menu--inline .el-menu-item:hover),
+:deep(.el-menu--inline .el-menu-item.is-active) {
+ background-color: #1f2d3d;
+}
+
+.menu-item-label {
+ min-width: 0;
+}
+
+.menu-item-badge {
+ margin-left: auto;
+ min-width: 18px;
+ height: 18px;
+ padding: 0 5px;
+ border-radius: 9px;
+ background: #f43f5e;
+ color: #ffffff;
+ font-size: 11px;
+ font-style: normal;
+ font-weight: 700;
+ line-height: 18px;
+ text-align: center;
+}
+
+:deep(.el-menu--collapse .menu-item-badge) {
+ position: absolute;
+ top: 6px;
+ right: 5px;
+ min-width: 8px;
+ width: 8px;
+ height: 8px;
+ padding: 0;
+ border: 2px solid #304156;
+ border-radius: 50%;
+ color: transparent;
+ font-size: 0;
+ line-height: 0;
+}
+
+:global(.admin-menu-popper) {
+ --el-menu-bg-color: #304156;
+ --el-menu-text-color: #bfcbd9;
+ --el-menu-active-color: #409eff;
+ --el-menu-hover-bg-color: #263445;
+}
+
+:global(.admin-menu-popper .el-menu) {
+ border-right: none;
+ background-color: #304156;
+}
+
+:global(.admin-menu-popper .el-menu-item) {
+ color: #bfcbd9;
+}
+
+:global(.admin-menu-popper .el-menu-item:hover),
+:global(.admin-menu-popper .el-menu-item.is-active) {
+ color: #409eff;
+ background-color: #263445;
+}
+
+.admin-workspace {
+ flex: 1;
+ min-width: 0;
+ min-height: 0;
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+.status-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ display: inline-block;
+}
+
+.status-text {
+ font-size: 13px;
+ color: inherit;
+ font-weight: 500;
+}
+
+:deep(.el-dropdown-menu__item .status-dot) {
+ margin-right: 8px;
+}
+
+.admin-main {
+ flex: 1;
+ min-height: 0;
+ padding: 18px 24px 24px;
+ overflow-y: auto;
+}
+
+:global(.force-password-dialog) {
+ max-width: calc(100vw - 32px);
+}
+
+.force-password-form {
+ display: grid;
+ gap: 2px;
+}
diff --git a/frontend/src/layouts/AdminLayout.vue b/frontend/src/layouts/AdminLayout.vue
index 5b6a348..4019d54 100644
--- a/frontend/src/layouts/AdminLayout.vue
+++ b/frontend/src/layouts/AdminLayout.vue
@@ -542,378 +542,4 @@ async function handleForcedPasswordChange() {