diff --git a/src/App.vue b/src/App.vue index bae46ab..71689e7 100644 --- a/src/App.vue +++ b/src/App.vue @@ -23,10 +23,24 @@ onMounted(() => { diff --git a/src/components/ProjectCard.vue b/src/components/ProjectCard.vue index b2f7dc4..b705b21 100644 --- a/src/components/ProjectCard.vue +++ b/src/components/ProjectCard.vue @@ -19,7 +19,6 @@ const emit = defineEmits() const { showMessage } = useMessage() -// 编辑相关状态 const isEditing = ref(false) const isLoading = ref(false) const editProjectName = ref("") @@ -50,7 +49,6 @@ const toggleProjectStatus = async (projectSlug: string) => { error instanceof Error ? error.message : "状态切换失败,请检查网络连接", "error" ) - console.error("状态切换失败:", error) } } @@ -68,32 +66,24 @@ const deleteProject = async (projectSlug: string, projectName: string) => { error instanceof Error ? error.message : "删除失败,请检查网络连接", "error" ) - console.error("删除失败:", error) } } -// 打开编辑对话框 const openEditDialog = async (projectSlug: string) => { isEditing.value = true editProjectName.value = props.project.name - + try { - // 获取项目详情(包括文件内容) projectDetail.value = await api.getProjectDetail(projectSlug) - if (projectDetail.value.files.length > 0) { - // 可以在这里预填充文件内容,如果需要的话 - } } catch (error) { showMessage( error instanceof Error ? error.message : "获取项目详情失败", "error" ) - console.error("获取项目详情失败:", error) isEditing.value = false } } -// 关闭编辑对话框 const closeEditDialog = () => { isEditing.value = false editProjectName.value = "" @@ -105,47 +95,36 @@ const closeEditDialog = () => { } } -// 验证文件 const validateFile = (file: File): boolean => { - // 文件类型验证 if (!file.name.endsWith(".html")) { showMessage("请选择HTML文件", "error") return false } - - // 文件大小验证 (5MB) const maxSize = 5 * 1024 * 1024 if (file.size > maxSize) { showMessage("文件大小不能超过5MB", "error") return false } - if (file.size === 0) { showMessage("文件不能为空", "error") return false } - return true } -// 处理文件选择 const handleFileSelect = (event: Event) => { const target = event.target as HTMLInputElement const file = target.files?.[0] if (!file) return - + if (validateFile(file)) { selectedFile.value = file } else { - // 验证失败,清空选择 - if (editFileInput.value) { - editFileInput.value.value = "" - } + if (editFileInput.value) editFileInput.value.value = "" selectedFile.value = null } } -// 处理拖拽 const handleDragOver = (event: DragEvent) => { event.preventDefault() isDragging.value = true @@ -158,13 +137,12 @@ const handleDragLeave = () => { const handleDrop = (event: DragEvent) => { event.preventDefault() isDragging.value = false - + const file = event.dataTransfer?.files?.[0] if (!file) return - + if (validateFile(file)) { selectedFile.value = file - // 同步到input元素 if (editFileInput.value) { const dataTransfer = new DataTransfer() dataTransfer.items.add(file) @@ -173,38 +151,31 @@ const handleDrop = (event: DragEvent) => { } } -// 清除选择的文件 const clearSelectedFile = () => { selectedFile.value = null - if (editFileInput.value) { - editFileInput.value.value = "" - } + if (editFileInput.value) editFileInput.value.value = "" } -// 格式化文件大小 const formatFileSize = (bytes: number): string => { if (bytes === 0) return "0 B" const k = 1024 const sizes = ["B", "KB", "MB", "GB"] const i = Math.floor(Math.log(bytes) / Math.log(k)) - return Math.round(bytes / Math.pow(k, i) * 100) / 100 + " " + sizes[i] + return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i] } -// 保存编辑 const saveEdit = async () => { if (!editProjectName.value.trim()) { showMessage("请输入项目名称", "error") return } - if (editProjectName.value.length > 50) { showMessage("项目名称不能超过50个字符", "error") return } const file = selectedFile.value - - // 至少需要修改名称或文件 + if (editProjectName.value === props.project.name && !file) { showMessage("请至少修改项目名称或上传新文件", "error") return @@ -214,13 +185,10 @@ const saveEdit = async () => { try { const formData = new FormData() - - // 只有当名称改变时才添加 + if (editProjectName.value !== props.project.name) { formData.append("projectName", editProjectName.value) } - - // 如果选择了新文件,添加文件 if (file) { formData.append("file", file) } @@ -234,7 +202,6 @@ const saveEdit = async () => { error instanceof Error ? error.message : "更新失败,请检查网络连接", "error" ) - console.error("更新失败:", error) } finally { isLoading.value = false } @@ -242,744 +209,659 @@ const saveEdit = async () => { diff --git a/src/components/ProjectList.vue b/src/components/ProjectList.vue index fb278cd..079cfc8 100644 --- a/src/components/ProjectList.vue +++ b/src/components/ProjectList.vue @@ -42,192 +42,308 @@ const handleProjectUpdated = () => { diff --git a/src/components/ProjectUpload.vue b/src/components/ProjectUpload.vue index 22e81b0..965d3fd 100644 --- a/src/components/ProjectUpload.vue +++ b/src/components/ProjectUpload.vue @@ -12,41 +12,64 @@ const emit = defineEmits() const { message: uploadMessage, messageType, showMessage } = useMessage() const projectName = ref("") const isUploading = ref(false) +const isDragging = ref(false) const fileInput = ref() +const validateFile = (file: File): boolean => { + if (!file.name.endsWith(".html")) { + showMessage("请选择HTML文件", "error") + return false + } + const maxSize = 5 * 1024 * 1024 + if (file.size > maxSize) { + showMessage("文件大小不能超过5MB", "error") + return false + } + if (file.size === 0) { + showMessage("文件不能为空", "error") + return false + } + return true +} + const handleFileSelect = () => { const file = fileInput.value?.files?.[0] if (!file) return - - // 文件类型验证 - if (!file.name.endsWith(".html")) { - showMessage("请选择HTML文件", "error") - return + if (validateFile(file)) { + uploadProject(file) } - - // 文件大小验证 (5MB) - const maxSize = 5 * 1024 * 1024 - if (file.size > maxSize) { - showMessage("文件大小不能超过5MB", "error") - return +} + +const handleDragOver = (event: DragEvent) => { + event.preventDefault() + isDragging.value = true +} + +const handleDragLeave = () => { + isDragging.value = false +} + +const handleDrop = (event: DragEvent) => { + event.preventDefault() + isDragging.value = false + const file = event.dataTransfer?.files?.[0] + if (!file) return + if (validateFile(file)) { + uploadProject(file) } - - if (file.size === 0) { - showMessage("文件不能为空", "error") - return - } - - uploadProject(file) +} + +const triggerFileSelect = () => { + fileInput.value?.click() } const uploadProject = async (file: File) => { if (!projectName.value.trim()) { - showMessage("请输入项目名称", "error") + showMessage("请先输入项目名称", "error") return } - - if (projectName.value.length > 50) { - showMessage("项目名称不能超过50个字符", "error") + if (projectName.value.length > 20) { + showMessage("项目名称不能超过20个字符", "error") return } @@ -58,10 +81,7 @@ const uploadProject = async (file: File) => { formData.append("projectName", projectName.value) const result = await api.uploadProject(formData) - showMessage( - `项目 "${result.name}" 上传成功!访问地址: ${result.url}`, - "success" - ) + showMessage(`"${result.name}" 上传成功`, "success") emit("project-uploaded") resetForm() } catch (error) { @@ -69,7 +89,6 @@ const uploadProject = async (file: File) => { error instanceof Error ? error.message : "上传失败,请检查网络连接", "error" ) - console.error("上传错误:", error) } finally { isUploading.value = false } @@ -84,164 +103,269 @@ const resetForm = () => { diff --git a/src/style.css b/src/style.css index dd0fa7a..4db0cce 100644 --- a/src/style.css +++ b/src/style.css @@ -1,13 +1,69 @@ -/* 全局样式重置 */ -* { +/* Design Tokens */ +:root { + --color-primary: #2563eb; + --color-primary-hover: #1d4ed8; + --color-primary-light: #eff6ff; + --color-primary-border: #bfdbfe; + + --color-danger: #dc2626; + --color-danger-hover: #b91c1c; + --color-danger-light: #fef2f2; + --color-danger-border: #fecaca; + + --color-success: #16a34a; + --color-success-light: #f0fdf4; + --color-success-border: #bbf7d0; + + --color-warning: #d97706; + --color-warning-light: #fffbeb; + --color-warning-border: #fde68a; + + --color-gray-50: #f8fafc; + --color-gray-100: #f1f5f9; + --color-gray-200: #e2e8f0; + --color-gray-300: #cbd5e1; + --color-gray-400: #94a3b8; + --color-gray-500: #64748b; + --color-gray-600: #475569; + --color-gray-700: #334155; + --color-gray-800: #1e293b; + --color-gray-900: #0f172a; + + --color-surface: #ffffff; + --color-bg: #f1f5f9; + --color-text: #1e293b; + --color-text-muted: #64748b; + --color-border: #e2e8f0; + --color-border-focus: #2563eb; + + --radius-sm: 6px; + --radius-md: 8px; + --radius-lg: 12px; + --radius-xl: 16px; + --radius-full: 9999px; + + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -2px rgba(0, 0, 0, 0.05); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.07), 0 4px 6px -4px rgba(0, 0, 0, 0.05); + + --transition-fast: 150ms ease; + --transition-base: 200ms ease; +} + +/* Reset */ +*, *::before, *::after { box-sizing: border-box; } +/* Base */ body { margin: 0; - font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; - background-color: #f5f5f5; - color: #333; + font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", system-ui, sans-serif; + font-size: 15px; + line-height: 1.5; + color: var(--color-text); + background-color: var(--color-bg); + -webkit-font-smoothing: antialiased; } #app {