feat: align subscription and profile settings pages with V21 design system #28

Merged
xiaoxia merged 1 commits from feature/v21-subscription-profile into develop 2026-06-26 22:13:56 +08:00
15 changed files with 1448 additions and 119 deletions
@@ -0,0 +1,359 @@
/* V21 业务组件统一样式 */
/* ==================== 按钮 ==================== */
.xx-primary-btn {
background: linear-gradient(135deg, #6366f1, #4f46e5) !important;
color: white !important;
border: none !important;
border-radius: 14px !important;
padding: 10px 20px !important;
font-weight: 600 !important;
box-shadow: 0 14px 26px rgba(79,70,229,0.22) !important;
transition: all 0.2s !important;
cursor: pointer;
height: auto !important;
}
.xx-primary-btn:hover {
box-shadow: 0 18px 34px rgba(79,70,229,0.28) !important;
transform: translateY(-1px);
}
.xx-ghost-btn {
background: transparent !important;
color: #4f46e5 !important;
border: 2px solid #4f46e5 !important;
border-radius: 14px !important;
padding: 8px 18px !important;
font-weight: 600 !important;
transition: all 0.2s !important;
cursor: pointer;
height: auto !important;
}
.xx-ghost-btn:hover {
background: #eef2ff !important;
}
/* ==================== 卡片 ==================== */
.xx-card {
background: rgba(255,255,255,0.94);
border: 1px solid rgba(226,232,240,0.95);
border-radius: 22px;
box-shadow: 0 24px 70px rgba(15,23,42,0.09);
padding: 24px;
margin-bottom: 20px;
transition: all 0.25s;
}
.xx-card:hover {
box-shadow: 0 26px 64px rgba(15,23,42,0.14);
transform: translateY(-2px);
}
/* ==================== 页面结构 ==================== */
.xx-page {
max-width: 1200px;
margin: 0 auto;
padding: 24px;
}
.xx-page-head {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 18px;
margin-bottom: 28px;
}
.xx-page-head h2 {
font-size: 26px;
font-weight: 850;
color: #0f172a;
margin: 0 0 8px;
}
.xx-page-head p {
font-size: 14px;
color: #64748b;
margin: 0;
}
/* ==================== 表格样式 ==================== */
.xx-table-card {
background: rgba(255,255,255,0.94);
border: 1px solid rgba(226,232,240,0.95);
border-radius: 22px;
box-shadow: 0 24px 70px rgba(15,23,42,0.09);
padding: 20px;
overflow: hidden;
}
/* 表格包装器 */
.xx-table-wrapper {
border-radius: 16px;
overflow: hidden;
}
/* ==================== 标签/Tag ==================== */
.xx-tag {
padding: 4px 12px;
border-radius: 10px;
font-size: 13px;
font-weight: 500;
}
.xx-tag-indigo {
background: #eef2ff;
color: #4f46e5;
border: 1px solid #c7d2fe;
}
.xx-tag-success {
background: #f0fdf4;
color: #16a34a;
border: 1px solid #bbf7d0;
}
.xx-tag-warning {
background: #fffbeb;
color: #d97706;
border: 1px solid #fde68a;
}
.xx-tag-error {
background: #fef2f2;
color: #dc2626;
border: 1px solid #fecaca;
}
/* ==================== 搜索栏 ==================== */
.xx-search-bar {
margin-bottom: 20px;
}
.xx-search-input {
width: 100%;
padding: 12px 18px;
border: 2px solid #e2e8f0;
border-radius: 14px;
font-size: 14px;
background: white;
transition: all 0.2s;
outline: none;
}
.xx-search-input:focus {
border-color: #4f46e5;
box-shadow: 0 0 0 4px rgba(79,70,229,0.1);
}
/* ==================== Modal ==================== */
.xx-modal .ant-modal-content {
border-radius: 22px;
padding: 24px;
}
.xx-modal .ant-modal-header {
border-radius: 22px 22px 0 0;
padding: 20px 24px;
border-bottom: 1px solid #e2e8f0;
}
.xx-modal .ant-modal-title {
font-size: 18px;
font-weight: 700;
color: #0f172a;
}
.xx-modal .ant-modal-footer {
border-top: 1px solid #e2e8f0;
padding: 16px 24px;
}
/* ==================== 空状态 ==================== */
.xx-empty-state {
text-align: center;
padding: 48px 24px;
color: #64748b;
}
.xx-empty-state-icon {
font-size: 48px;
margin-bottom: 16px;
}
/* ==================== 网格布局 ==================== */
.xx-grid-2 {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
}
.xx-grid-3 {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
.xx-grid-4 {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
}
@media (max-width: 768px) {
.xx-grid-2, .xx-grid-3, .xx-grid-4 {
grid-template-columns: 1fr;
}
}
/* ==================== 配额展示 ==================== */
.xx-quota-item {
padding: 20px;
background: white;
border: 1px solid #e2e8f0;
border-radius: 16px;
transition: all 0.2s;
}
.xx-quota-item:hover {
border-color: #4f46e5;
box-shadow: 0 8px 24px rgba(79,70,229,0.1);
}
/* ==================== 进度条 ==================== */
.xx-progress {
margin-top: 12px;
}
/* ==================== Ant Design 覆盖样式 ==================== */
/* Table overrides */
.ant-table-wrapper .ant-table-thead > tr > th {
background: #f8fafc !important;
font-weight: 700 !important;
color: #0f172a !important;
border-bottom: 2px solid #e2e8f0 !important;
padding: 14px 16px !important;
}
.ant-table-wrapper .ant-table-tbody > tr > td {
padding: 14px 16px !important;
border-bottom: 1px solid #f1f5f9 !important;
}
.ant-table-wrapper .ant-table-tbody > tr:hover > td {
background: #fafbfc !important;
}
/* Card overrides */
.ant-card {
border-radius: 22px !important;
border: 1px solid rgba(226,232,240,0.95) !important;
}
.ant-card-head {
border-bottom: 1px solid #e2e8f0 !important;
min-height: 52px !important;
padding: 0 24px !important;
}
.ant-card-head-title {
font-weight: 700 !important;
font-size: 16px !important;
color: #0f172a !important;
}
.ant-card-body {
padding: 20px 24px !important;
}
/* Modal overrides */
.ant-modal-content {
border-radius: 22px !important;
overflow: hidden;
}
.ant-modal-header {
padding: 20px 24px !important;
background: white !important;
}
.ant-modal-title {
font-weight: 700 !important;
font-size: 18px !important;
color: #0f172a !important;
}
.ant-modal-body {
padding: 24px !important;
}
.ant-modal-footer {
padding: 16px 24px !important;
}
/* Button overrides */
.ant-btn-primary {
background: linear-gradient(135deg, #6366f1, #4f46e5) !important;
border: none !important;
border-radius: 14px !important;
box-shadow: 0 14px 26px rgba(79,70,229,0.22) !important;
height: auto !important;
padding: 10px 20px !important;
font-weight: 600 !important;
}
.ant-btn-primary:hover {
background: linear-gradient(135deg, #6366f1, #4f46e5) !important;
box-shadow: 0 18px 34px rgba(79,70,229,0.28) !important;
transform: translateY(-1px);
}
/* Tag overrides */
.ant-tag {
border-radius: 10px !important;
padding: 4px 12px !important;
font-weight: 500 !important;
}
/* Select overrides */
.ant-select-selector {
border-radius: 14px !important;
border-color: #e2e8f0 !important;
}
.ant-select:not(.ant-select-disabled):hover .ant-select-selector {
border-color: #4f46e5 !important;
}
.ant-select-focused .ant-select-selector {
border-color: #4f46e5 !important;
box-shadow: 0 0 0 3px rgba(79,70,229,0.1) !important;
}
/* Input overrides */
.ant-input {
border-radius: 14px !important;
border-color: #e2e8f0 !important;
padding: 10px 14px !important;
}
.ant-input:hover {
border-color: #4f46e5 !important;
}
.ant-input:focus {
border-color: #4f46e5 !important;
box-shadow: 0 0 0 3px rgba(79,70,229,0.1) !important;
}
/* Progress overrides */
.ant-progress-inner {
background: #f1f5f9 !important;
border-radius: 10px !important;
}
.ant-progress-bg {
border-radius: 10px !important;
}
+517
View File
@@ -0,0 +1,517 @@
/* V21 Admin 页面样式 */
/* 页面容器 */
.dashboard-page,
.analytics-page,
.user-management-page,
.log-viewer-page,
.system-monitor-page,
.admin-coming-soon-page {
padding: 32px;
max-width: 1400px;
margin: 0 auto;
}
/* 页面头部 */
.xx-page-head {
background: rgba(255, 255, 255, 0.94);
border: 1px solid rgba(226, 232, 240, 0.95);
border-radius: 28px;
box-shadow: 0 24px 70px rgba(15, 23, 42, 0.09);
padding: 32px 40px;
margin-bottom: 32px;
display: flex;
justify-content: space-between;
align-items: center;
}
.xx-page-head-content {
display: flex;
flex-direction: column;
gap: 8px;
}
.xx-page-head h2 {
font-size: 28px;
font-weight: 900;
color: var(--slate, #0f172a);
margin: 0;
letter-spacing: -0.02em;
}
.xx-page-head p {
font-size: 15px;
color: var(--muted, #64748b);
margin: 0;
}
.xx-page-head-actions {
display: flex;
gap: 12px;
align-items: center;
}
/* 简化页面头部(无操作按钮) */
.xx-page-head-simple {
background: rgba(255, 255, 255, 0.94);
border: 1px solid rgba(226, 232, 240, 0.95);
border-radius: 28px;
box-shadow: 0 24px 70px rgba(15, 23, 42, 0.09);
padding: 32px 40px;
margin-bottom: 32px;
}
.xx-page-head-simple h2 {
font-size: 28px;
font-weight: 900;
color: var(--slate, #0f172a);
margin: 0 0 8px;
letter-spacing: -0.02em;
}
.xx-page-head-simple p {
font-size: 15px;
color: var(--muted, #64748b);
margin: 0;
}
/* V21 卡片 */
.xx-card {
background: rgba(255, 255, 255, 0.94);
border: 1px solid rgba(226, 232, 240, 0.95);
border-radius: 22px;
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.06);
padding: 24px;
transition: all 0.3s;
}
.xx-card:hover {
box-shadow: 0 16px 40px rgba(15, 23, 42, 0.08);
}
.xx-card .ant-card-head {
border-bottom: 1px solid rgba(226, 232, 240, 0.8);
padding: 20px 24px;
}
.xx-card .ant-card-head-title {
font-weight: 800;
font-size: 17px;
color: var(--slate, #0f172a);
}
.xx-card .ant-card-body {
padding: 24px;
}
/* 统计卡片网格 - 4列 */
.xx-grid-4 {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 24px;
margin-bottom: 32px;
}
@media (max-width: 1200px) {
.xx-grid-4 {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.xx-grid-4 {
grid-template-columns: 1fr;
}
}
/* 统计卡片 */
.xx-stat-card {
background: rgba(255, 255, 255, 0.94);
border: 1px solid rgba(226, 232, 240, 0.95);
border-radius: 22px;
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.06);
padding: 24px;
transition: all 0.3s;
}
.xx-stat-card:hover {
transform: translateY(-2px);
box-shadow: 0 16px 40px rgba(15, 23, 42, 0.08);
}
.xx-stat-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16px;
}
.xx-stat-card-icon {
width: 48px;
height: 48px;
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
}
.xx-stat-card-icon.primary {
background: linear-gradient(135deg, #6366f1, #4f46e5);
color: white;
}
.xx-stat-card-icon.success {
background: linear-gradient(135deg, #34d399, #10b981);
color: white;
}
.xx-stat-card-icon.warning {
background: linear-gradient(135deg, #fbbf24, #f59e0b);
color: white;
}
.xx-stat-card-icon.purple {
background: linear-gradient(135deg, #a78bfa, #8b5cf6);
color: white;
}
.xx-stat-card-icon.info {
background: linear-gradient(135deg, #60a5fa, #3b82f6);
color: white;
}
.xx-stat-card-icon.orange {
background: linear-gradient(135deg, #fb923c, #f97316);
color: white;
}
.xx-stat-card-label {
font-size: 14px;
color: var(--muted, #64748b);
font-weight: 500;
margin-bottom: 8px;
}
.xx-stat-card-value {
font-size: 32px;
font-weight: 900;
color: var(--slate, #0f172a);
line-height: 1.2;
letter-spacing: -0.02em;
}
.xx-stat-card-value.primary {
color: var(--indigo, #4f46e5);
}
.xx-stat-card-value.success {
color: var(--green, #10b981);
}
.xx-stat-card-value.warning {
color: var(--amber, #f59e0b);
}
.xx-stat-card-value.purple {
color: #8b5cf6;
}
.xx-stat-card-growth {
font-size: 13px;
color: var(--green, #10b981);
font-weight: 600;
margin-top: 8px;
display: flex;
align-items: center;
gap: 4px;
}
/* 数据表格 */
.xx-table-wrapper {
background: rgba(255, 255, 255, 0.94);
border: 1px solid rgba(226, 232, 240, 0.95);
border-radius: 22px;
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.06);
overflow: hidden;
}
.xx-table-wrapper .ant-table {
background: transparent;
}
.xx-table-wrapper .ant-table-thead > tr > th {
background: rgba(248, 250, 252, 0.8);
font-weight: 800;
font-size: 13px;
color: var(--slate, #0f172a);
text-transform: uppercase;
letter-spacing: 0.05em;
padding: 16px 20px;
}
.xx-table-wrapper .ant-table-tbody > tr > td {
padding: 16px 20px;
font-size: 14px;
}
.xx-table-wrapper .ant-table-tbody > tr:hover > td {
background: rgba(79, 70, 229, 0.03);
}
/* V21 按钮 */
.xx-primary-btn {
background: linear-gradient(135deg, #6366f1, #4f46e5) !important;
color: white !important;
border: none !important;
border-radius: 14px !important;
font-weight: 700 !important;
box-shadow: 0 8px 20px rgba(79, 70, 229, 0.25) !important;
transition: all 0.2s !important;
}
.xx-primary-btn:hover {
box-shadow: 0 12px 28px rgba(79, 70, 229, 0.3) !important;
transform: translateY(-1px);
}
.xx-ghost-btn {
background: white !important;
border: 1px solid rgba(226, 232, 240, 0.95) !important;
color: var(--slate, #0f172a) !important;
border-radius: 14px !important;
font-weight: 600 !important;
transition: all 0.2s !important;
}
.xx-ghost-btn:hover {
border-color: var(--indigo, #4f46e5) !important;
color: var(--indigo, #4f46e5) !important;
}
/* V21 输入框 */
.xx-search-input {
border-radius: 14px !important;
border: 1px solid rgba(226, 232, 240, 0.95) !important;
padding: 8px 16px !important;
}
.xx-search-input:hover,
.xx-search-input:focus {
border-color: var(--indigo, #4f46e5) !important;
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1) !important;
}
/* V21 Select */
.xx-select {
border-radius: 14px !important;
}
.xx-select:hover,
.xx-select:focus {
border-color: var(--indigo, #4f46e5) !important;
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1) !important;
}
/* V21 Tag */
.xx-tag {
border-radius: 8px !important;
font-weight: 600 !important;
font-size: 12px !important;
padding: 4px 10px !important;
}
.xx-tag.info {
background: rgba(59, 130, 246, 0.1) !important;
color: #3b82f6 !important;
border: 1px solid rgba(59, 130, 246, 0.2) !important;
}
.xx-tag.success {
background: rgba(16, 185, 129, 0.1) !important;
color: #10b981 !important;
border: 1px solid rgba(16, 185, 129, 0.2) !important;
}
.xx-tag.warning {
background: rgba(245, 158, 11, 0.1) !important;
color: #f59e0b !important;
border: 1px solid rgba(245, 158, 11, 0.2) !important;
}
.xx-tag.error {
background: rgba(239, 68, 68, 0.1) !important;
color: #ef4444 !important;
border: 1px solid rgba(239, 68, 68, 0.2) !important;
}
.xx-tag.debug {
background: rgba(100, 116, 139, 0.1) !important;
color: #64748b !important;
border: 1px solid rgba(100, 116, 139, 0.2) !important;
}
/* V21 Progress */
.xx-progress-primary .ant-progress-circle .ant-progress-text {
color: var(--indigo, #4f46e5) !important;
font-weight: 700 !important;
}
.xx-progress-success .ant-progress-circle .ant-progress-text {
color: var(--green, #10b981) !important;
font-weight: 700 !important;
}
.xx-progress-warning .ant-progress-circle .ant-progress-text {
color: var(--amber, #f59e0b) !important;
font-weight: 700 !important;
}
/* 图表容器 */
.xx-chart-container {
background: rgba(255, 255, 255, 0.94);
border: 1px solid rgba(226, 232, 240, 0.95);
border-radius: 22px;
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.06);
padding: 24px;
}
.xx-chart-container .ant-card-head {
border-bottom: 1px solid rgba(226, 232, 240, 0.8);
padding: 20px 24px;
}
.xx-chart-container .ant-card-head-title {
font-weight: 800;
font-size: 17px;
color: var(--slate, #0f172a);
}
/* 筛选器区域 */
.xx-filter-bar {
background: rgba(255, 255, 255, 0.94);
border: 1px solid rgba(226, 232, 240, 0.95);
border-radius: 22px;
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.06);
padding: 20px 24px;
margin-bottom: 24px;
display: flex;
flex-wrap: wrap;
gap: 16px;
align-items: center;
}
/* 资源监控卡片 */
.xx-resource-card {
background: rgba(255, 255, 255, 0.94);
border: 1px solid rgba(226, 232, 240, 0.95);
border-radius: 22px;
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.06);
padding: 24px;
text-align: center;
}
.xx-resource-card-icon {
width: 64px;
height: 64px;
border-radius: 18px;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 16px;
font-size: 28px;
}
.xx-resource-card-label {
font-size: 14px;
color: var(--muted, #64748b);
font-weight: 600;
margin-bottom: 16px;
}
/* 日期选择器 */
.xx-date-picker {
border-radius: 14px !important;
}
/* Drawer */
.xx-drawer .ant-drawer-header {
border-bottom: 1px solid rgba(226, 232, 240, 0.8);
padding: 20px 24px;
}
.xx-drawer .ant-drawer-title {
font-weight: 800;
font-size: 18px;
color: var(--slate, #0f172a);
}
/* 日志详情 */
.xx-log-detail {
padding: 4px 0;
}
.xx-log-detail-label {
font-size: 13px;
font-weight: 700;
color: var(--slate, #0f172a);
margin-bottom: 4px;
}
.xx-log-detail-value {
font-size: 14px;
color: var(--muted, #64748b);
background: rgba(248, 250, 252, 0.8);
padding: 12px;
border-radius: 10px;
margin-bottom: 16px;
}
.xx-log-detail-code {
font-family: 'JetBrains Mono', 'Fira Code', monospace;
background: rgba(248, 250, 252, 0.8);
padding: 12px;
border-radius: 10px;
white-space: pre-wrap;
word-break: break-all;
}
/* Result 页面居中 */
.xx-result-center {
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
}
.xx-result-center .ant-result {
padding: 48px;
}
/* 刷新时间显示 */
.xx-refresh-time {
font-size: 13px;
color: var(--muted, #64748b);
margin-right: 12px;
}
/* 图表配色覆盖 */
.recharts-text {
fill: #64748b !important;
font-size: 12px !important;
}
.recharts-cartesian-grid-horizontal line,
.recharts-cartesian-grid-vertical line {
stroke: rgba(226, 232, 240, 0.8) !important;
}
/* 图表 Legend */
.recharts-legend-wrapper {
padding-top: 16px !important;
}
.recharts-legend-item-text {
color: #64748b !important;
font-size: 13px !important;
}
+91 -11
View File
@@ -1,18 +1,98 @@
.forgot-password-container {
/* V21 忘记密码页 - 与 Login 保持一致 */
.xx-auth-page {
display: grid;
place-items: center;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: linear-gradient(135deg, rgba(79, 70, 229, 0.08), rgba(16, 185, 129, 0.06));
padding: 24px;
}
.forgot-password-card {
width: 400px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
.xx-auth-card {
background: rgba(255, 255, 255, 0.94);
border: 1px solid #e2e8f0;
border-radius: 28px;
box-shadow: 0 24px 70px rgba(15, 23, 42, 0.09);
padding: 40px;
width: 100%;
max-width: 400px;
}
.forgot-password-card .ant-card-head-title {
.xx-auth-header {
text-align: center;
margin-bottom: 32px;
}
.xx-logo-big {
font-size: 64px;
margin-bottom: 16px;
}
.xx-auth-header h1 {
font-size: 28px;
margin: 0 0 8px;
color: #0f172a;
font-weight: 900;
}
.xx-auth-header p {
color: #64748b;
margin: 0;
font-size: 14px;
}
.xx-auth-footer {
text-align: center;
font-size: 14px;
color: #64748b;
margin-top: 20px;
}
.xx-auth-footer a {
color: #4f46e5;
text-decoration: none;
font-weight: 850;
}
.xx-auth-footer a:hover {
color: #4338ca;
}
/* Form 样式覆盖 */
.ant-form-item-label > label {
font-weight: 850;
color: #0f172a;
font-size: 14px;
}
.ant-input,
.ant-input-password input {
background: white;
border: 1px solid #e2e8f0;
border-radius: 12px;
}
.ant-input:focus,
.ant-input-password input:focus {
border-color: #4f46e5;
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
}
/* Result 成功页样式 */
.xx-result-page {
display: grid;
place-items: center;
min-height: 100vh;
background: linear-gradient(135deg, rgba(79, 70, 229, 0.08), rgba(16, 185, 129, 0.06));
padding: 24px;
}
.xx-result-card {
background: rgba(255, 255, 255, 0.94);
border: 1px solid #e2e8f0;
border-radius: 28px;
box-shadow: 0 24px 70px rgba(15, 23, 42, 0.09);
padding: 48px 40px;
width: 100%;
max-width: 400px;
text-align: center;
font-size: 24px;
font-weight: 600;
}
+31 -20
View File
@@ -1,8 +1,8 @@
/**
* 忘记密码页面
* 忘记密码页面 - V21 完全对标
*/
import React, { useState } from 'react';
import { Form, Input, Button, Card, message, Result } from 'antd';
import { Form, Input, Button, Result, message } from 'antd';
import { MailOutlined } from '@ant-design/icons';
import { Link } from 'react-router-dom';
import { useMutation } from '@tanstack/react-query';
@@ -30,8 +30,8 @@ const ForgotPassword: React.FC = () => {
if (emailSent) {
return (
<div className="forgot-password-container">
<Card className="forgot-password-card">
<div className="xx-result-page">
<div className="xx-result-card">
<Result
status="success"
title="重置邮件已发送"
@@ -42,45 +42,56 @@ const ForgotPassword: React.FC = () => {
</Link>,
]}
/>
</Card>
</div>
</div>
);
}
return (
<div className="forgot-password-container">
<Card className="forgot-password-card" title="重置密码">
<p style={{ marginBottom: '24px', color: '#666' }}>
<div className="xx-auth-page">
<div className="xx-auth-card">
<div className="xx-auth-header">
<div className="xx-logo-big">🦐</div>
<h1></h1>
<p></p>
</div>
<p style={{ marginBottom: '24px', color: '#64748b', fontSize: '14px', textAlign: 'center' }}>
</p>
<Form form={form} name="forgot-password" onFinish={onFinish} size="large">
<Form form={form} name="forgot-password" onFinish={onFinish} layout="vertical">
<Form.Item
name="email"
label="邮箱"
rules={[
{ required: true, message: '请输入邮箱' },
{ type: 'email', message: '请输入有效的邮箱地址' },
]}
>
<Input prefix={<MailOutlined />} placeholder="邮箱" />
<Input
prefix={<MailOutlined />}
placeholder="name@example.com"
size="large"
/>
</Form.Item>
<Form.Item>
<Button
type="primary"
htmlType="submit"
loading={resetMutation.isPending}
block
<button
type="submit"
className="btn primary"
style={{ width: '100%' }}
disabled={resetMutation.isPending}
>
</Button>
{resetMutation.isPending ? '发送中...' : '发送重置邮件'}
</button>
</Form.Item>
<div style={{ textAlign: 'center' }}>
<div className="xx-auth-footer">
<Link to="/login"></Link>
</div>
</Form>
</Card>
</div>
</div>
);
};
+80 -11
View File
@@ -1,18 +1,87 @@
.register-container {
/* V21 注册页 - 与 Login 保持一致 */
.xx-auth-page {
display: grid;
place-items: center;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: linear-gradient(135deg, rgba(79, 70, 229, 0.08), rgba(16, 185, 129, 0.06));
padding: 24px;
}
.register-card {
width: 400px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
.xx-auth-card {
background: rgba(255, 255, 255, 0.94);
border: 1px solid #e2e8f0;
border-radius: 28px;
box-shadow: 0 24px 70px rgba(15, 23, 42, 0.09);
padding: 40px;
width: 100%;
max-width: 400px;
}
.register-card .ant-card-head-title {
.xx-auth-header {
text-align: center;
font-size: 24px;
font-weight: 600;
margin-bottom: 32px;
}
.xx-logo-big {
font-size: 64px;
margin-bottom: 16px;
}
.xx-auth-header h1 {
font-size: 28px;
margin: 0 0 8px;
color: #0f172a;
font-weight: 900;
}
.xx-auth-header p {
color: #64748b;
margin: 0;
font-size: 14px;
}
.xx-auth-footer {
text-align: center;
font-size: 14px;
color: #64748b;
margin-top: 20px;
}
.xx-auth-footer a {
color: #4f46e5;
text-decoration: none;
font-weight: 850;
}
.xx-auth-footer a:hover {
color: #4338ca;
}
/* Form 样式覆盖 */
.ant-form-item-label > label {
font-weight: 850;
color: #0f172a;
font-size: 14px;
}
.ant-input,
.ant-input-password input {
background: white;
border: 1px solid #e2e8f0;
border-radius: 12px;
}
.ant-input:focus,
.ant-input-password input:focus {
border-color: #4f46e5;
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
}
.ant-checkbox-wrapper {
font-size: 14px;
color: #64748b;
}
.ant-checkbox-inner {
border-radius: 6px;
}
+31 -17
View File
@@ -1,8 +1,8 @@
/**
* 注册页面
* 注册页面 - V21 完全对标
*/
import React from 'react';
import { Form, Input, Button, Card, message } from 'antd';
import { Form, Input, message } from 'antd';
import { UserOutlined, LockOutlined, MailOutlined } from '@ant-design/icons';
import { Link } from 'react-router-dom';
import { useRegister } from '@/hooks/useAuth';
@@ -34,17 +34,24 @@ const Register: React.FC = () => {
};
return (
<div className="register-container">
<Card className="register-card" title="注册小虾 SaaS">
<div className="xx-auth-page">
<div className="xx-auth-card">
<div className="xx-auth-header">
<div className="xx-logo-big">🦐</div>
<h1></h1>
<p></p>
</div>
<Form
form={form}
name="register"
onFinish={onFinish}
autoComplete="off"
size="large"
layout="vertical"
>
<Form.Item
name="email"
label="邮箱"
rules={[
{ required: true, message: '请输入邮箱' },
{ type: 'email', message: '请输入有效的邮箱地址' },
@@ -52,12 +59,14 @@ const Register: React.FC = () => {
>
<Input
prefix={<MailOutlined />}
placeholder="邮箱"
placeholder="name@example.com"
size="large"
/>
</Form.Item>
<Form.Item
name="username"
label="用户名"
rules={[
{ required: true, message: '请输入用户名' },
{ min: 3, message: '用户名至少 3 个字符' },
@@ -71,11 +80,13 @@ const Register: React.FC = () => {
<Input
prefix={<UserOutlined />}
placeholder="用户名"
size="large"
/>
</Form.Item>
<Form.Item
name="password"
label="密码"
rules={[
{ required: true, message: '请输入密码' },
{ min: 8, message: '密码至少 8 个字符' },
@@ -87,12 +98,14 @@ const Register: React.FC = () => {
>
<Input.Password
prefix={<LockOutlined />}
placeholder="密码"
placeholder="•••••••••"
size="large"
/>
</Form.Item>
<Form.Item
name="confirmPassword"
label="确认密码"
dependencies={['password']}
rules={[
{ required: true, message: '请确认密码' },
@@ -108,26 +121,27 @@ const Register: React.FC = () => {
>
<Input.Password
prefix={<LockOutlined />}
placeholder="确认密码"
placeholder="•••••••••"
size="large"
/>
</Form.Item>
<Form.Item>
<Button
type="primary"
htmlType="submit"
loading={registerMutation.isPending}
block
<button
type="submit"
className="btn primary"
style={{ width: '100%' }}
disabled={registerMutation.isPending}
>
</Button>
{registerMutation.isPending ? '注册中...' : '注册'}
</button>
</Form.Item>
<div style={{ textAlign: 'center' }}>
<div className="xx-auth-footer">
<Link to="/login"></Link>
</div>
</Form>
</Card>
</div>
</div>
);
};
+91 -11
View File
@@ -1,18 +1,98 @@
.reset-password-container {
/* V21 重置密码页 - 与 Login 保持一致 */
.xx-auth-page {
display: grid;
place-items: center;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: linear-gradient(135deg, rgba(79, 70, 229, 0.08), rgba(16, 185, 129, 0.06));
padding: 24px;
}
.reset-password-card {
width: 400px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
.xx-auth-card {
background: rgba(255, 255, 255, 0.94);
border: 1px solid #e2e8f0;
border-radius: 28px;
box-shadow: 0 24px 70px rgba(15, 23, 42, 0.09);
padding: 40px;
width: 100%;
max-width: 400px;
}
.reset-password-card .ant-card-head-title {
.xx-auth-header {
text-align: center;
margin-bottom: 32px;
}
.xx-logo-big {
font-size: 64px;
margin-bottom: 16px;
}
.xx-auth-header h1 {
font-size: 28px;
margin: 0 0 8px;
color: #0f172a;
font-weight: 900;
}
.xx-auth-header p {
color: #64748b;
margin: 0;
font-size: 14px;
}
.xx-auth-footer {
text-align: center;
font-size: 14px;
color: #64748b;
margin-top: 20px;
}
.xx-auth-footer a {
color: #4f46e5;
text-decoration: none;
font-weight: 850;
}
.xx-auth-footer a:hover {
color: #4338ca;
}
/* Form 样式覆盖 */
.ant-form-item-label > label {
font-weight: 850;
color: #0f172a;
font-size: 14px;
}
.ant-input,
.ant-input-password input {
background: white;
border: 1px solid #e2e8f0;
border-radius: 12px;
}
.ant-input:focus,
.ant-input-password input:focus {
border-color: #4f46e5;
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
}
/* Result 结果页样式 */
.xx-result-page {
display: grid;
place-items: center;
min-height: 100vh;
background: linear-gradient(135deg, rgba(79, 70, 229, 0.08), rgba(16, 185, 129, 0.06));
padding: 24px;
}
.xx-result-card {
background: rgba(255, 255, 255, 0.94);
border: 1px solid #e2e8f0;
border-radius: 28px;
box-shadow: 0 24px 70px rgba(15, 23, 42, 0.09);
padding: 48px 40px;
width: 100%;
max-width: 400px;
text-align: center;
font-size: 24px;
font-weight: 600;
}
+10 -9
View File
@@ -2,8 +2,9 @@
* 账号安全设置页面
*/
import React from 'react';
import { Card, Form, Input, Button, Alert } from 'antd';
import { Form, Input, Button, Alert } from 'antd';
import { LockOutlined, MailOutlined } from '@ant-design/icons';
import './ProfileSettings.css';
const AccountSecurity: React.FC = () => {
const [passwordForm] = Form.useForm();
@@ -15,14 +16,13 @@ const AccountSecurity: React.FC = () => {
return (
<div style={{ padding: '24px' }}>
<h1></h1>
<Card title="修改密码" style={{ marginBottom: 24 }}>
<div className="xx-settings-page">
<div className="xx-settings-card">
<h3></h3>
<Alert
type="info"
showIcon
style={{ marginBottom: 16 }}
style={{ marginBottom: 20 }}
message="账号安全设置暂未开放"
description="当前后端尚未提供登录态修改密码/邮箱接口,避免假成功,入口暂时禁用。"
/>
@@ -71,9 +71,10 @@ const AccountSecurity: React.FC = () => {
</Button>
</Form.Item>
</Form>
</Card>
</div>
<Card title="修改邮箱">
<div className="xx-settings-card">
<h3></h3>
<Form form={emailForm} layout="vertical" onFinish={onEmailSubmit}>
<Form.Item
name="newEmail"
@@ -100,7 +101,7 @@ const AccountSecurity: React.FC = () => {
</Button>
</Form.Item>
</Form>
</Card>
</div>
</div>
);
};
@@ -2,7 +2,8 @@
* 通知设置页面
*/
import React from 'react';
import { Card, Form, Switch, Button, Divider, Alert } from 'antd';
import { Form, Switch, Button, Divider, Alert } from 'antd';
import './ProfileSettings.css';
const NotificationSettings: React.FC = () => {
@@ -12,13 +13,12 @@ const NotificationSettings: React.FC = () => {
return (
<div style={{ padding: '24px' }}>
<h1></h1>
<Card>
<div className="xx-settings-page">
<div className="xx-settings-card">
<Alert
type="info"
showIcon
style={{ marginBottom: 16 }}
style={{ marginBottom: 20 }}
message="通知偏好暂未开放"
description="当前后端尚未提供通知偏好保存接口,避免假成功,保存入口暂时禁用。"
/>
@@ -33,7 +33,7 @@ const NotificationSettings: React.FC = () => {
marketingEmails: false,
}}
>
<h3></h3>
<h3 className="xx-section-title"></h3>
<Form.Item
name="emailNotifications"
label="启用邮件通知"
@@ -44,7 +44,7 @@ const NotificationSettings: React.FC = () => {
<Divider />
<h3></h3>
<h3 className="xx-section-title"></h3>
<Form.Item
name="inviteNotifications"
label="工作空间邀请通知"
@@ -71,7 +71,7 @@ const NotificationSettings: React.FC = () => {
<Divider />
<h3></h3>
<h3 className="xx-section-title"></h3>
<Form.Item
name="marketingEmails"
label="接收产品更新和优惠信息"
@@ -86,7 +86,7 @@ const NotificationSettings: React.FC = () => {
</Button>
</Form.Item>
</Form>
</Card>
</div>
</div>
);
};
@@ -0,0 +1,179 @@
/**
* V21 个人设置页面样式
*/
/* 页面容器 */
.xx-settings-page {
max-width: 900px;
margin: 0 auto;
padding: 48px 24px;
}
/* 页面头部 */
.xx-settings-head {
background: rgba(255,255,255,0.94);
border: 1px solid rgba(226,232,240,0.95);
border-radius: 28px;
box-shadow: 0 24px 70px rgba(15,23,42,0.09);
padding: 32px 36px;
margin-bottom: 28px;
}
.xx-settings-head h1 {
font-size: 32px;
font-weight: 900;
color: var(--slate, #0f172a);
margin: 0 0 8px;
letter-spacing: -0.02em;
}
.xx-settings-head p {
font-size: 16px;
color: var(--muted, #64748b);
margin: 0;
}
/* 内容卡片 */
.xx-settings-card {
background: rgba(255,255,255,0.94);
border: 1px solid rgba(226,232,240,0.95);
border-radius: 28px;
box-shadow: 0 24px 70px rgba(15,23,42,0.09);
padding: 32px 36px;
margin-bottom: 28px;
transition: all 0.25s;
}
.xx-settings-card:hover {
box-shadow: 0 26px 64px rgba(15,23,42,0.14);
transform: translateY(-2px);
}
/* 卡片标题 */
.xx-settings-card h3 {
font-size: 20px;
font-weight: 900;
color: var(--slate, #0f172a);
margin: 0 0 20px;
padding-bottom: 16px;
border-bottom: 1px solid var(--line, #e2e8f0);
}
/* 结果页面容器 */
.xx-result-page {
background: rgba(255,255,255,0.94);
border: 1px solid rgba(226,232,240,0.95);
border-radius: 28px;
box-shadow: 0 24px 70px rgba(15,23,42,0.09);
padding: 60px 40px;
margin-bottom: 28px;
text-align: center;
transition: all 0.25s;
}
.xx-result-page:hover {
box-shadow: 0 26px 64px rgba(15,23,42,0.14);
transform: translateY(-2px);
}
/* 按钮容器 */
.xx-btn-group {
margin-top: 24px;
display: flex;
gap: 12px;
justify-content: center;
flex-wrap: wrap;
}
/* 表单区块标题 */
.xx-section-title {
font-size: 18px;
font-weight: 700;
color: var(--slate, #0f172a);
margin: 24px 0 16px;
}
.xx-section-title:first-child {
margin-top: 0;
}
/* Ant Design Form 适配 */
.ant-form-item {
margin-bottom: 20px;
}
.ant-form-item-label > label {
font-weight: 600;
color: var(--slate, #0f172a);
}
.ant-input,
.ant-input-affix-wrapper {
border-radius: 12px !important;
border-color: var(--line, #e2e8f0) !important;
}
.ant-input:hover,
.ant-input-affix-wrapper:hover {
border-color: var(--indigo, #4f46e5) !important;
}
.ant-input:focus,
.ant-input-affix-wrapper:focus,
.ant-input-affix-wrapper-focused {
border-color: var(--indigo, #4f46e5) !important;
box-shadow: 0 0 0 2px rgba(79,70,229,0.1) !important;
}
/* Switch 样式 */
.ant-switch {
background: var(--line, #e2e8f0);
}
.ant-switch-checked {
background: linear-gradient(135deg, #6366f1, #4f46e5) !important;
}
/* Alert 样式 */
.ant-alert {
border-radius: 14px;
padding: 16px 20px;
margin-bottom: 20px;
}
.ant-alert-info {
background: var(--indigo-soft, #eef2ff);
border: 1px solid rgba(79,70,229,0.2);
}
.ant-alert-message {
font-weight: 600;
color: var(--slate, #0f172a);
}
.ant-alert-description {
color: var(--muted, #64748b);
}
/* Result 组件样式 */
.ant-result {
padding: 0;
}
.ant-result-title {
font-size: 24px;
font-weight: 800;
color: var(--slate, #0f172a);
}
.ant-result-subtitle {
font-size: 15px;
color: var(--muted, #64748b);
margin-top: 8px;
}
/* Divider */
.ant-divider {
border-color: var(--line, #e2e8f0);
margin: 24px 0;
}
@@ -1,16 +1,17 @@
import React from 'react';
import { Card, Result } from 'antd';
import { Result } from 'antd';
import './ProfileSettings.css';
const SessionManagement: React.FC = () => {
return (
<div>
<Card title="会话管理">
<div className="xx-settings-page">
<div className="xx-result-page">
<Result
status="info"
title="会话管理暂未开放"
subTitle="当前版本未接入会话列表、设备管理和远程登出后端服务,因此不展示模拟设备,也不会伪造登出操作。"
/>
</Card>
</div>
</div>
);
};
+7 -5
View File
@@ -2,8 +2,9 @@
* 个人设置页面
*/
import React from 'react';
import { Card, Form, Input, Button, Alert } from 'antd';
import { Form, Input, Button, Alert } from 'antd';
import { useAuthStore } from '@/store/authStore';
import './ProfileSettings.css';
const Settings: React.FC = () => {
const user = useAuthStore((state) => state.user);
@@ -13,12 +14,13 @@ const Settings: React.FC = () => {
return (
<div style={{ padding: '24px' }}>
<Card title="个人信息">
<div className="xx-settings-page">
<div className="xx-settings-card">
<h3></h3>
<Alert
type="info"
showIcon
style={{ marginBottom: 16 }}
style={{ marginBottom: 20 }}
message="个人资料编辑暂未开放"
description="当前仅展示登录用户信息,资料修改接口接入后再开放保存。"
/>
@@ -50,7 +52,7 @@ const Settings: React.FC = () => {
</Button>
</Form.Item>
</Form>
</Card>
</div>
</div>
);
};
+5 -4
View File
@@ -1,13 +1,14 @@
import React from 'react';
import { Button, Card, Result } from 'antd';
import { Button, Result } from 'antd';
import { useNavigate } from 'react-router-dom';
import '../profile/ProfileSettings.css';
const Billing: React.FC = () => {
const navigate = useNavigate();
return (
<div style={{ padding: '24px' }}>
<Card>
<div className="xx-settings-page">
<div className="xx-result-page">
<Result
status="info"
title="账单管理暂未开放"
@@ -18,7 +19,7 @@ const Billing: React.FC = () => {
</Button>,
]}
/>
</Card>
</div>
</div>
);
};
@@ -1,14 +1,15 @@
import React from 'react';
import { Button, Card, Result } from 'antd';
import { Button, Result } from 'antd';
import { useNavigate, useParams } from 'react-router-dom';
import '../profile/ProfileSettings.css';
const UpgradeSubscription: React.FC = () => {
const { workspaceId } = useParams<{ workspaceId: string }>();
const navigate = useNavigate();
return (
<div style={{ padding: '24px' }}>
<Card>
<div className="xx-settings-page">
<div className="xx-result-page">
<Result
status="info"
title="订阅升级暂未开放"
@@ -24,7 +25,7 @@ const UpgradeSubscription: React.FC = () => {
) : null,
]}
/>
</Card>
</div>
</div>
);
};
@@ -1,5 +1,5 @@
/**
* 工作空间详情页面
* 工作空间详情页面 - V21 UI
*/
import React, { useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
@@ -11,6 +11,7 @@ import { createProject, getProjects } from '@/api/projects';
import MemberList from '@/components/business/MemberList';
import InviteMemberModal from '@/components/business/InviteMemberModal';
import PermissionMatrix from '@/components/business/PermissionMatrix';
import '@/components/business/business.css';
const WorkspaceDetail: React.FC = () => {
const { id } = useParams<{ id: string }>();
@@ -50,20 +51,20 @@ const WorkspaceDetail: React.FC = () => {
});
};
if (isLoading) return <div>...</div>;
if (!workspace) return <div></div>;
if (isLoading) return <div className="xx-page"><div className="xx-card" style={{textAlign: 'center', padding: '40px'}}>...</div></div>;
if (!workspace) return <div className="xx-page"><div className="xx-card" style={{textAlign: 'center', padding: '40px'}}></div></div>;
const items = [
{
key: 'overview',
label: '概览',
children: (
<div>
<div className="xx-workspace-overview">
<Card
title="工作空间信息"
style={{ marginBottom: 16 }}
className="xx-card"
extra={
<Button type="primary" onClick={() => setCreateProjectOpen(true)}>
<Button type="primary" className="xx-primary-btn" onClick={() => setCreateProjectOpen(true)}>
</Button>
}
@@ -80,7 +81,7 @@ const WorkspaceDetail: React.FC = () => {
</Descriptions>
</Card>
<Card title="项目" style={{ marginBottom: 16 }}>
<Card title="项目列表" className="xx-card">
{projectsQuery.isError && (
<Alert type="error" showIcon message="项目列表加载失败" style={{ marginBottom: 16 }} />
)}
@@ -131,18 +132,25 @@ const WorkspaceDetail: React.FC = () => {
</>
),
children: (
<div>
<div style={{ marginBottom: 16 }}>
<div className="xx-members-section">
<div className="xx-page-head" style={{marginBottom: 20}}>
<div>
<h3 style={{margin: 0, fontSize: 18, fontWeight: 700, color: '#0f172a'}}></h3>
<p style={{margin: '4px 0 0', fontSize: 14, color: '#64748b'}}>访</p>
</div>
<Button
type="primary"
className="xx-primary-btn"
icon={<PlusOutlined />}
onClick={() => setInviteModalOpen(true)}
>
</Button>
</div>
<MemberList workspaceId={id!} />
<div style={{ marginTop: 24 }}>
<div className="xx-card">
<MemberList workspaceId={id!} />
</div>
<div style={{marginTop: 24}}>
<PermissionMatrix />
</div>
</div>
@@ -151,13 +159,18 @@ const WorkspaceDetail: React.FC = () => {
{
key: 'settings',
label: '设置',
children: <div></div>,
children: <div className="xx-card"><p style={{color: '#64748b'}}></p></div>,
},
];
return (
<div>
<h1>{workspace.name}</h1>
<div className="xx-page">
<div className="xx-page-head">
<div>
<h1 style={{margin: 0, fontSize: 28, fontWeight: 850, color: '#0f172a'}}>{workspace.name}</h1>
<p style={{margin: '8px 0 0', fontSize: 14, color: '#64748b'}}></p>
</div>
</div>
<Tabs items={items} />
<Modal
title="创建项目"
@@ -167,6 +180,7 @@ const WorkspaceDetail: React.FC = () => {
confirmLoading={createProjectMutation.isPending}
onOk={handleCreateProject}
onCancel={() => setCreateProjectOpen(false)}
className="xx-modal"
>
<Form form={projectForm} layout="vertical" preserve={false}>
<Form.Item