update
This commit is contained in:
@@ -5,15 +5,13 @@ import { useProjects } from "./composables/useProjects"
|
|||||||
|
|
||||||
const { projects, fetchProjects } = useProjects()
|
const { projects, fetchProjects } = useProjects()
|
||||||
|
|
||||||
const getApiBase = () => {
|
function getApiBase() {
|
||||||
if (window.location.hostname !== "localhost") {
|
if (window.location.hostname !== "localhost") {
|
||||||
return "/api"
|
return `${window.location.protocol}//${window.location.host}/api`
|
||||||
}
|
}
|
||||||
return "http://localhost:3000/api"
|
return "http://localhost:3000/api"
|
||||||
}
|
}
|
||||||
|
|
||||||
const API_BASE = getApiBase()
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchProjects()
|
fetchProjects()
|
||||||
})
|
})
|
||||||
@@ -23,7 +21,7 @@ onMounted(() => {
|
|||||||
<div class="app">
|
<div class="app">
|
||||||
<ProjectManager
|
<ProjectManager
|
||||||
:projects="projects"
|
:projects="projects"
|
||||||
:api-base="API_BASE"
|
:api-base="getApiBase()"
|
||||||
@project-uploaded="fetchProjects"
|
@project-uploaded="fetchProjects"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,11 +24,27 @@ const fileInput = ref<HTMLInputElement>()
|
|||||||
|
|
||||||
const handleFileSelect = () => {
|
const handleFileSelect = () => {
|
||||||
const file = fileInput.value?.files?.[0]
|
const file = fileInput.value?.files?.[0]
|
||||||
if (file && file.name.endsWith(".html")) {
|
if (!file) return
|
||||||
uploadProject(file)
|
|
||||||
} else {
|
// 文件类型验证
|
||||||
|
if (!file.name.endsWith(".html")) {
|
||||||
showMessage("请选择HTML文件", "error")
|
showMessage("请选择HTML文件", "error")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 文件大小验证 (5MB)
|
||||||
|
const maxSize = 5 * 1024 * 1024
|
||||||
|
if (file.size > maxSize) {
|
||||||
|
showMessage("文件大小不能超过5MB", "error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.size === 0) {
|
||||||
|
showMessage("文件不能为空", "error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadProject(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
const uploadProject = async (file: File) => {
|
const uploadProject = async (file: File) => {
|
||||||
@@ -37,6 +53,11 @@ const uploadProject = async (file: File) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (projectName.value.length > 50) {
|
||||||
|
showMessage("项目名称不能超过50个字符", "error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
isUploading.value = true
|
isUploading.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -129,7 +150,10 @@ const deleteProject = async (projectSlug: string, projectName: string) => {
|
|||||||
id="projectName"
|
id="projectName"
|
||||||
placeholder="请输入项目名称"
|
placeholder="请输入项目名称"
|
||||||
class="form-input"
|
class="form-input"
|
||||||
|
maxlength="50"
|
||||||
|
:disabled="isUploading"
|
||||||
/>
|
/>
|
||||||
|
<small class="input-hint">最多50个字符</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="file-upload">
|
<div class="file-upload">
|
||||||
@@ -139,6 +163,7 @@ const deleteProject = async (projectSlug: string, projectName: string) => {
|
|||||||
accept=".html"
|
accept=".html"
|
||||||
@change="handleFileSelect"
|
@change="handleFileSelect"
|
||||||
style="display: none"
|
style="display: none"
|
||||||
|
:disabled="isUploading"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
@click="fileInput?.click()"
|
@click="fileInput?.click()"
|
||||||
@@ -294,13 +319,25 @@ const deleteProject = async (projectSlug: string, projectName: string) => {
|
|||||||
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);
|
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-input:disabled {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-hint {
|
||||||
|
color: #666;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 4px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.upload-button {
|
.upload-button {
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 15px 30px;
|
padding: 10px 20px;
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
border-radius: 25px;
|
border-radius: 20px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@@ -468,11 +505,12 @@ const deleteProject = async (projectSlug: string, projectName: string) => {
|
|||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
color: white;
|
color: white;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
padding: 10px 15px;
|
padding: 8px 12px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.visit-button:hover {
|
.visit-button:hover {
|
||||||
@@ -484,11 +522,12 @@ const deleteProject = async (projectSlug: string, projectName: string) => {
|
|||||||
.delete-button {
|
.delete-button {
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 10px 15px;
|
padding: 8px 12px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copy-button {
|
.copy-button {
|
||||||
@@ -514,11 +553,12 @@ const deleteProject = async (projectSlug: string, projectName: string) => {
|
|||||||
.deactivate-button {
|
.deactivate-button {
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 10px 15px;
|
padding: 8px 12px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.activate-button {
|
.activate-button {
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import type { Project, UploadResponse, ToggleResponse } from '../types'
|
import type { Project, UploadResponse, ToggleResponse } from "../types"
|
||||||
|
|
||||||
// 动态获取API基础URL
|
|
||||||
const getApiBase = () => {
|
const getApiBase = () => {
|
||||||
// 在Docker环境中,前端通过Caddy代理访问后端
|
|
||||||
if (window.location.hostname !== 'localhost') {
|
if (window.location.hostname !== 'localhost') {
|
||||||
return '/api'
|
return `${window.location.protocol}//${window.location.host}/api`
|
||||||
}
|
}
|
||||||
// 开发环境直接访问后端
|
// 开发环境直接访问后端
|
||||||
return 'http://localhost:3000/api'
|
return 'http://localhost:3000/api'
|
||||||
@@ -15,7 +13,7 @@ async function request<T>(url: string, options: RequestInit = {}): Promise<T> {
|
|||||||
const API_BASE = getApiBase()
|
const API_BASE = getApiBase()
|
||||||
const response = await fetch(`${API_BASE}${url}`, {
|
const response = await fetch(`${API_BASE}${url}`, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
"Content-Type": "application/json",
|
||||||
...options.headers,
|
...options.headers,
|
||||||
},
|
},
|
||||||
...options,
|
...options,
|
||||||
@@ -24,7 +22,7 @@ async function request<T>(url: string, options: RequestInit = {}): Promise<T> {
|
|||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(data.error || '请求失败')
|
throw new Error(data.error || "请求失败")
|
||||||
}
|
}
|
||||||
|
|
||||||
return data
|
return data
|
||||||
@@ -33,25 +31,32 @@ async function request<T>(url: string, options: RequestInit = {}): Promise<T> {
|
|||||||
// API方法
|
// API方法
|
||||||
export const api = {
|
export const api = {
|
||||||
// 获取项目列表
|
// 获取项目列表
|
||||||
getProjects: (): Promise<Project[]> => request<Project[]>('/projects'),
|
getProjects: (): Promise<Project[]> => request<Project[]>("/projects"),
|
||||||
|
|
||||||
// 上传项目
|
// 上传项目
|
||||||
uploadProject: (formData: FormData): Promise<UploadResponse> =>
|
uploadProject: (formData: FormData): Promise<UploadResponse> => {
|
||||||
request<UploadResponse>('/upload', {
|
const API_BASE = getApiBase()
|
||||||
method: 'POST',
|
return fetch(`${API_BASE}/upload`, {
|
||||||
|
method: "POST",
|
||||||
body: formData,
|
body: formData,
|
||||||
headers: {}, // 让浏览器自动设置Content-Type
|
}).then(async (response) => {
|
||||||
}),
|
const data = await response.json()
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(data.error || "请求失败")
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
// 切换项目状态
|
// 切换项目状态
|
||||||
toggleProject: (slug: string): Promise<ToggleResponse> =>
|
toggleProject: (slug: string): Promise<ToggleResponse> =>
|
||||||
request<ToggleResponse>(`/projects/${slug}/toggle`, {
|
request<ToggleResponse>(`/projects/${slug}/toggle`, {
|
||||||
method: 'PATCH',
|
method: "PATCH",
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// 删除项目
|
// 删除项目
|
||||||
deleteProject: (slug: string): Promise<{ message: string }> =>
|
deleteProject: (slug: string): Promise<{ message: string }> =>
|
||||||
request<{ message: string }>(`/projects/${slug}`, {
|
request<{ message: string }>(`/projects/${slug}`, {
|
||||||
method: 'DELETE',
|
method: "DELETE",
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user