feat: align subscription and profile settings pages with V21 design system #28
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user