+
+
+
+
+
+
+
+ 最多50个字符
+
+
+
+
+
+
@@ -216,70 +518,118 @@ const deleteProject = async (projectSlug: string, projectName: string) => {
}
.project-actions {
- display: flex;
- gap: 10px;
- margin-top: 10px;
+ margin-top: 15px;
+ padding-top: 15px;
+ border-top: 1px solid #e8e8e8;
}
-.visit-button {
- flex: 1;
+.actions-container {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ flex-wrap: wrap;
+}
+
+.action-section {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+}
+
+.action-divider {
+ width: 1px;
+ height: 24px;
+ background: #ddd;
+ flex-shrink: 0;
+}
+
+.action-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 6px 12px;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+ font-weight: 500;
+ font-size: 13px;
+ transition: all 0.2s ease;
+ text-decoration: none;
+ white-space: nowrap;
+}
+
+.action-btn:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+}
+
+.btn-icon {
+ font-size: 14px;
+ line-height: 1;
+}
+
+.btn-text {
+ font-size: 13px;
+}
+
+/* 主要操作按钮 */
+.visit-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
- text-decoration: none;
- padding: 8px 12px;
- border-radius: 6px;
- text-align: center;
- font-weight: 500;
- transition: all 0.3s ease;
- font-size: 13px;
}
-.visit-button:hover {
- transform: translateY(-1px);
- box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
+.visit-btn:hover {
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
-.copy-button,
-.delete-button {
- color: white;
- border: none;
- padding: 8px 12px;
- border-radius: 6px;
- cursor: pointer;
- font-weight: 500;
- transition: all 0.3s ease;
- font-size: 13px;
-}
-
-.copy-button {
+.copy-btn {
background: #6c757d;
-}
-
-.copy-button:hover {
- background: #5a6268;
- transform: translateY(-1px);
-}
-
-.delete-button {
- background: #dc3545;
-}
-
-.delete-button:hover {
- background: #c82333;
- transform: translateY(-1px);
- box-shadow: 0 4px 12px rgba(220, 53, 69, 0.3);
-}
-
-.activate-button,
-.deactivate-button {
color: white;
- border: none;
- padding: 8px 12px;
- border-radius: 6px;
- cursor: pointer;
- font-weight: 500;
- transition: all 0.3s ease;
- font-size: 13px;
+}
+
+.copy-btn:hover {
+ background: #5a6268;
+}
+
+/* 管理操作按钮 */
+.edit-btn {
+ background: #17a2b8;
+ color: white;
+}
+
+.edit-btn:hover {
+ background: #138496;
+ box-shadow: 0 4px 12px rgba(23, 162, 184, 0.3);
+}
+
+.activate-btn {
+ background: #28a745;
+ color: white;
+}
+
+.activate-btn:hover {
+ background: #218838;
+ box-shadow: 0 4px 12px rgba(40, 167, 69, 0.3);
+}
+
+.deactivate-btn {
+ background: #ffc107;
+ color: #212529;
+}
+
+.deactivate-btn:hover {
+ background: #e0a800;
+ box-shadow: 0 4px 12px rgba(255, 193, 7, 0.3);
+}
+
+.delete-btn {
+ background: #dc3545;
+ color: white;
+}
+
+.delete-btn:hover {
+ background: #c82333;
+ box-shadow: 0 4px 12px rgba(220, 53, 69, 0.3);
}
.activate-button {
@@ -303,21 +653,333 @@ const deleteProject = async (projectSlug: string, projectName: string) => {
box-shadow: 0 4px 12px rgba(255, 193, 7, 0.3);
}
+/* 编辑对话框样式 */
+.edit-modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 1000;
+}
+
+.edit-modal {
+ background: white;
+ border-radius: 10px;
+ width: 90%;
+ max-width: 500px;
+ max-height: 90vh;
+ overflow-y: auto;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
+}
+
+.edit-modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 20px;
+ border-bottom: 1px solid #e0e0e0;
+}
+
+.edit-modal-header h3 {
+ margin: 0;
+ color: #333;
+ font-size: 1.3em;
+}
+
+.close-button {
+ background: none;
+ border: none;
+ font-size: 24px;
+ cursor: pointer;
+ color: #666;
+ padding: 0;
+ width: 30px;
+ height: 30px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ transition: all 0.3s ease;
+}
+
+.close-button:hover {
+ background: #f0f0f0;
+ color: #333;
+}
+
+.edit-modal-body {
+ padding: 20px;
+}
+
+.edit-modal-body .form-group {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ margin-bottom: 20px;
+}
+
+.edit-modal-body .form-group label {
+ font-weight: 500;
+ color: #555;
+}
+
+.edit-modal-body .form-input {
+ padding: 10px;
+ border: 1px solid #ddd;
+ border-radius: 5px;
+ font-size: 14px;
+ transition: border-color 0.3s ease;
+}
+
+.edit-modal-body .form-input:focus {
+ outline: none;
+ border-color: #667eea;
+ box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);
+}
+
+.edit-modal-body .form-input:disabled {
+ background-color: #f5f5f5;
+ cursor: not-allowed;
+}
+
+/* 文件上传区域 */
+.file-upload-area {
+ position: relative;
+ border: 2px dashed #ddd;
+ border-radius: 8px;
+ padding: 24px;
+ background: #fafafa;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ min-height: 120px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.file-upload-area:hover {
+ border-color: #667eea;
+ background: #f5f7ff;
+}
+
+.file-upload-area.dragging {
+ border-color: #667eea;
+ background: #eef2ff;
+ transform: scale(1.02);
+}
+
+.file-upload-area.has-file {
+ border-color: #28a745;
+ background: #f0f9f4;
+ cursor: default;
+}
+
+.file-input-hidden {
+ position: absolute;
+ width: 0;
+ height: 0;
+ opacity: 0;
+ pointer-events: none;
+}
+
+.upload-placeholder {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 12px;
+ text-align: center;
+}
+
+.upload-icon {
+ font-size: 48px;
+ opacity: 0.6;
+}
+
+.upload-text {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.upload-main-text {
+ font-weight: 500;
+ color: #333;
+ font-size: 14px;
+}
+
+.upload-hint {
+ font-size: 12px;
+ color: #999;
+}
+
+.file-preview {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ padding: 8px;
+ background: white;
+ border-radius: 6px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+}
+
+.file-info {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ flex: 1;
+ min-width: 0;
+}
+
+.file-icon {
+ font-size: 32px;
+ flex-shrink: 0;
+}
+
+.file-details {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.file-name {
+ font-weight: 500;
+ color: #333;
+ font-size: 14px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.file-size {
+ font-size: 12px;
+ color: #666;
+}
+
+.remove-file-btn {
+ flex-shrink: 0;
+ width: 28px;
+ height: 28px;
+ border: none;
+ background: #dc3545;
+ color: white;
+ border-radius: 50%;
+ cursor: pointer;
+ font-size: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.2s ease;
+ padding: 0;
+}
+
+.remove-file-btn:hover:not(:disabled) {
+ background: #c82333;
+ transform: scale(1.1);
+}
+
+.remove-file-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.edit-modal-body .input-hint {
+ color: #666;
+ font-size: 12px;
+}
+
+.edit-modal-footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+ padding: 20px;
+ border-top: 1px solid #e0e0e0;
+}
+
+.cancel-button,
+.save-button {
+ padding: 10px 20px;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+ font-weight: 500;
+ font-size: 14px;
+ transition: all 0.3s ease;
+}
+
+.cancel-button {
+ background: #6c757d;
+ color: white;
+}
+
+.cancel-button:hover:not(:disabled) {
+ background: #5a6268;
+ transform: translateY(-1px);
+}
+
+.save-button {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+}
+
+.save-button:hover:not(:disabled) {
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
+}
+
+.cancel-button:disabled,
+.save-button:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+ transform: none;
+}
+
/* 手机端响应式设计 */
@media (max-width: 768px) {
- .project-actions {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 8px;
+ .actions-container {
+ flex-direction: column;
+ align-items: stretch;
+ gap: 10px;
}
-
- .visit-button,
- .copy-button,
- .delete-button,
- .activate-button,
- .deactivate-button {
+
+ .action-section {
+ flex-wrap: wrap;
+ gap: 6px;
+ }
+
+ .action-divider {
+ display: none;
+ }
+
+ .action-btn {
+ flex: 1;
+ min-width: 0;
+ justify-content: center;
+ padding: 8px 10px;
font-size: 12px;
- padding: 6px 8px;
+ }
+
+ .btn-text {
+ font-size: 12px;
+ }
+
+ .edit-modal {
+ width: 95%;
+ margin: 10px;
+ }
+
+ .edit-modal-header,
+ .edit-modal-body,
+ .edit-modal-footer {
+ padding: 15px;
}
}
diff --git a/src/services/api.ts b/src/services/api.ts
index 022987f..15ec6a1 100644
--- a/src/services/api.ts
+++ b/src/services/api.ts
@@ -1,4 +1,4 @@
-import type { Project, UploadResponse, ToggleResponse } from "../types"
+import type { Project, UploadResponse, ToggleResponse, ProjectDetail, UpdateProjectResponse } from "../types"
const getApiBase = () => {
if (window.location.hostname !== 'localhost') {
@@ -57,6 +57,25 @@ export const api = {
method: "PATCH",
}),
+ // 获取项目详情
+ getProjectDetail: (slug: string): Promise
=>
+ request(`/projects/${slug}`),
+
+ // 更新项目
+ updateProject: (slug: string, formData: FormData): Promise => {
+ const API_BASE = getApiBase()
+ return fetch(`${API_BASE}/projects/${slug}`, {
+ method: "PUT",
+ body: formData,
+ }).then(async (response) => {
+ const data = await response.json()
+ if (!response.ok) {
+ throw new Error(data.error || "请求失败")
+ }
+ return data
+ })
+ },
+
// 删除项目
deleteProject: (slug: string): Promise<{ message: string }> =>
request<{ message: string }>(`/projects/${slug}`, {
diff --git a/src/types/index.ts b/src/types/index.ts
index c33e82e..d946b2a 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -22,3 +22,25 @@ export interface ToggleResponse {
message: string
isActive: boolean
}
+
+// 项目文件类型
+export interface ProjectFile {
+ id: number
+ filename: string
+ originalName: string
+ content: string
+ size: number
+ projectId: number
+ uploadedAt: string
+}
+
+// 项目详情类型(包含文件)
+export interface ProjectDetail extends Project {
+ files: ProjectFile[]
+}
+
+// 更新项目响应类型
+export interface UpdateProjectResponse {
+ message: string
+ project: Project
+}