diff --git a/client/src/components/InquiryModal.vue b/client/src/components/InquiryModal.vue
index 22c63a6..d1172ed 100644
--- a/client/src/components/InquiryModal.vue
+++ b/client/src/components/InquiryModal.vue
@@ -6,7 +6,7 @@
- Let's build
something
unreal.
+ 共创
无限
可能
告诉我你的构想。
@@ -36,7 +36,10 @@
placeholder="Name"
v-model="formData.name"
>
-
+
-
+
@@ -57,7 +63,10 @@
placeholder="Contact"
v-model="formData.contact"
>
-
+
@@ -73,7 +82,7 @@
class="peer hidden"
v-model="formData.budget"
>
- 10k - 50k
+ 1w - 5w
@@ -106,7 +115,10 @@
v-model="formData.description"
@input="autoResizeTextarea"
>
-
+
diff --git a/client/src/components/admin/AdminLayout.vue b/client/src/components/admin/AdminLayout.vue
index c517e4c..4092960 100644
--- a/client/src/components/admin/AdminLayout.vue
+++ b/client/src/components/admin/AdminLayout.vue
@@ -45,10 +45,22 @@
- �
+ 👤
关于页面
+
+
+ 💬
+ 客户评价
+
+
+
+
+ 🤝
+ 合作伙伴
+
+
💻
diff --git a/client/src/pages/Services.vue b/client/src/pages/Services.vue
index b64a072..640f345 100644
--- a/client/src/pages/Services.vue
+++ b/client/src/pages/Services.vue
@@ -157,19 +157,12 @@
+
+
\ No newline at end of file
diff --git a/client/src/pages/admin/Partners.vue b/client/src/pages/admin/Partners.vue
new file mode 100644
index 0000000..31a7a1e
--- /dev/null
+++ b/client/src/pages/admin/Partners.vue
@@ -0,0 +1,232 @@
+
+
+
合作伙伴管理
+
+
+
+
+
+
+
+
+
+
+
+ | ID |
+ Logo |
+ 名称 |
+ 网站 |
+ 描述 |
+ 创建时间 |
+ 操作 |
+
+
+
+
+ | {{ p.id }} |
+
+
+ -
+ |
+ {{ p.name }} |
+
+ 链接
+ -
+ |
+
+ {{ p.description && p.description.length > 20 ? p.description.substring(0, 20) + '...' : (p.description || '-') }}
+ |
+ {{ new Date(p.createdAt).toLocaleDateString() }} |
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/pages/admin/TestimonialForm.vue b/client/src/pages/admin/TestimonialForm.vue
new file mode 100644
index 0000000..50f4581
--- /dev/null
+++ b/client/src/pages/admin/TestimonialForm.vue
@@ -0,0 +1,205 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/pages/admin/Testimonials.vue b/client/src/pages/admin/Testimonials.vue
new file mode 100644
index 0000000..8322591
--- /dev/null
+++ b/client/src/pages/admin/Testimonials.vue
@@ -0,0 +1,226 @@
+
+
+
客户评价管理
+
+
+
+
+
+
+
+
+
+
+
+ | ID |
+ 作者 |
+ 角色 |
+ 内容 |
+ 评分 |
+ 创建时间 |
+ 操作 |
+
+
+
+
+ | {{ t.id }} |
+ {{ t.author }} |
+ {{ t.role }} |
+
+ {{ t.content.length > 30 ? t.content.substring(0, 30) + '...' : t.content }}
+ |
+ {{ t.rating }} |
+ {{ new Date(t.createdAt).toLocaleDateString() }} |
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/router.ts b/client/src/router.ts
index e1827b1..bac9d7e 100644
--- a/client/src/router.ts
+++ b/client/src/router.ts
@@ -58,6 +58,16 @@ const routes = [
{ path: 'about/create', name: 'admin-about-create', component: () => import('./pages/admin/AboutForm.vue') },
{ path: 'about/edit/:id', name: 'admin-about-edit', component: () => import('./pages/admin/AboutForm.vue') },
+ // 客户评价管理
+ { path: 'testimonials', name: 'admin-testimonials', component: () => import('./pages/admin/Testimonials.vue') },
+ { path: 'testimonials/create', name: 'admin-testimonials-create', component: () => import('./pages/admin/TestimonialForm.vue') },
+ { path: 'testimonials/:id/edit', name: 'admin-testimonials-edit', component: () => import('./pages/admin/TestimonialForm.vue') },
+
+ // 合作伙伴管理
+ { path: 'partners', name: 'admin-partners', component: () => import('./pages/admin/Partners.vue') },
+ { path: 'partners/create', name: 'admin-partners-create', component: () => import('./pages/admin/PartnerForm.vue') },
+ { path: 'partners/:id/edit', name: 'admin-partners-edit', component: () => import('./pages/admin/PartnerForm.vue') },
+
// 系统配置
{ path: 'settings', name: 'admin-settings', component: () => import('./pages/admin/Settings.vue') },
diff --git a/client/src/services/api.ts b/client/src/services/api.ts
index e326bb6..afabb13 100644
--- a/client/src/services/api.ts
+++ b/client/src/services/api.ts
@@ -961,3 +961,156 @@ export const deleteAboutProfile = async (id: number): Promise => {
throw error
}
}
+
+// 客户评价相关类型
+export interface Testimonial {
+ id: number
+ content: string
+ author: string
+ role: string
+ avatar: string
+ rating: number
+ createdAt: string
+ updatedAt: string
+}
+
+// 合作伙伴相关类型
+export interface Partner {
+ id: number
+ name: string
+ logo: string
+ description: string
+ website: string
+ createdAt: string
+ updatedAt: string
+}
+
+// 客户评价API
+export const fetchTestimonials = async (): Promise => {
+ try {
+ const response = await fetch(`${API_BASE}/testimonials`)
+ if (!response.ok) {
+ const errorData = await response.json()
+ throw new Error(errorData.error || '获取客户评价失败')
+ }
+ return await response.json()
+ } catch (error) {
+ console.error('Fetch testimonials error:', error)
+ throw error
+ }
+}
+
+export const createTestimonial = async (data: Omit): Promise => {
+ try {
+ const response = await fetch(`${API_BASE}/admin/testimonials`, {
+ method: 'POST',
+ headers: getAuthHeaders(),
+ body: JSON.stringify(data)
+ })
+ if (!response.ok) {
+ const errorData = await response.json()
+ throw new Error(errorData.error || '创建客户评价失败')
+ }
+ } catch (error) {
+ console.error('Create testimonial error:', error)
+ throw error
+ }
+}
+
+export const updateTestimonial = async (id: number, data: Omit): Promise => {
+ try {
+ const response = await fetch(`${API_BASE}/admin/testimonials/${id}`, {
+ method: 'PUT',
+ headers: getAuthHeaders(),
+ body: JSON.stringify(data)
+ })
+ if (!response.ok) {
+ const errorData = await response.json()
+ throw new Error(errorData.error || '更新客户评价失败')
+ }
+ } catch (error) {
+ console.error('Update testimonial error:', error)
+ throw error
+ }
+}
+
+export const deleteTestimonial = async (id: number): Promise => {
+ try {
+ const response = await fetch(`${API_BASE}/admin/testimonials/${id}`, {
+ method: 'DELETE',
+ headers: getAuthHeaders()
+ })
+ if (!response.ok) {
+ const errorData = await response.json()
+ throw new Error(errorData.error || '删除客户评价失败')
+ }
+ } catch (error) {
+ console.error('Delete testimonial error:', error)
+ throw error
+ }
+}
+
+// 合作伙伴API
+export const fetchPartners = async (): Promise => {
+ try {
+ const response = await fetch(`${API_BASE}/partners`)
+ if (!response.ok) {
+ const errorData = await response.json()
+ throw new Error(errorData.error || '获取合作伙伴失败')
+ }
+ return await response.json()
+ } catch (error) {
+ console.error('Fetch partners error:', error)
+ throw error
+ }
+}
+
+export const createPartner = async (data: Omit): Promise => {
+ try {
+ const response = await fetch(`${API_BASE}/admin/partners`, {
+ method: 'POST',
+ headers: getAuthHeaders(),
+ body: JSON.stringify(data)
+ })
+ if (!response.ok) {
+ const errorData = await response.json()
+ throw new Error(errorData.error || '创建合作伙伴失败')
+ }
+ } catch (error) {
+ console.error('Create partner error:', error)
+ throw error
+ }
+}
+
+export const updatePartner = async (id: number, data: Omit): Promise => {
+ try {
+ const response = await fetch(`${API_BASE}/admin/partners/${id}`, {
+ method: 'PUT',
+ headers: getAuthHeaders(),
+ body: JSON.stringify(data)
+ })
+ if (!response.ok) {
+ const errorData = await response.json()
+ throw new Error(errorData.error || '更新合作伙伴失败')
+ }
+ } catch (error) {
+ console.error('Update partner error:', error)
+ throw error
+ }
+}
+
+export const deletePartner = async (id: number): Promise => {
+ try {
+ const response = await fetch(`${API_BASE}/admin/partners/${id}`, {
+ method: 'DELETE',
+ headers: getAuthHeaders()
+ })
+ if (!response.ok) {
+ const errorData = await response.json()
+ throw new Error(errorData.error || '删除合作伙伴失败')
+ }
+ } catch (error) {
+ console.error('Delete partner error:', error)
+ throw error
+ }
+}
diff --git a/server/handlers/services.go b/server/handlers/services.go
new file mode 100644
index 0000000..ecf854a
--- /dev/null
+++ b/server/handlers/services.go
@@ -0,0 +1,144 @@
+package handlers
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/niangaodev/art-code/models"
+ "github.com/niangaodev/art-code/repositories"
+)
+
+// GetTestimonials 获取客户评价
+func GetTestimonials(c *gin.Context) {
+ testimonials, err := repositories.GetTestimonials()
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch testimonials"})
+ return
+ }
+ c.JSON(http.StatusOK, testimonials)
+}
+
+// GetPartners 获取合作伙伴
+func GetPartners(c *gin.Context) {
+ partners, err := repositories.GetPartners()
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch partners"})
+ return
+ }
+ c.JSON(http.StatusOK, partners)
+}
+
+// AdminCreateTestimonial 创建客户评价
+func AdminCreateTestimonial(c *gin.Context) {
+ var t models.Testimonial
+ if err := c.ShouldBindJSON(&t); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
+ return
+ }
+
+ if err := repositories.CreateTestimonial(&t); err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create testimonial"})
+ return
+ }
+
+ c.JSON(http.StatusOK, gin.H{"message": "Testimonial created successfully"})
+}
+
+// AdminUpdateTestimonial 更新客户评价
+func AdminUpdateTestimonial(c *gin.Context) {
+ idStr := c.Param("id")
+ var id uint
+ if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
+ return
+ }
+
+ var t models.Testimonial
+ if err := c.ShouldBindJSON(&t); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
+ return
+ }
+ t.ID = id
+
+ if err := repositories.UpdateTestimonial(&t); err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update testimonial"})
+ return
+ }
+
+ c.JSON(http.StatusOK, gin.H{"message": "Testimonial updated successfully"})
+}
+
+// AdminDeleteTestimonial 删除客户评价
+func AdminDeleteTestimonial(c *gin.Context) {
+ idStr := c.Param("id")
+ var id uint
+ if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
+ return
+ }
+
+ if err := repositories.DeleteTestimonial(id); err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete testimonial"})
+ return
+ }
+
+ c.JSON(http.StatusOK, gin.H{"message": "Testimonial deleted successfully"})
+}
+
+// AdminCreatePartner 创建合作伙伴
+func AdminCreatePartner(c *gin.Context) {
+ var p models.Partner
+ if err := c.ShouldBindJSON(&p); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
+ return
+ }
+
+ if err := repositories.CreatePartner(&p); err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create partner"})
+ return
+ }
+
+ c.JSON(http.StatusOK, gin.H{"message": "Partner created successfully"})
+}
+
+// AdminUpdatePartner 更新合作伙伴
+func AdminUpdatePartner(c *gin.Context) {
+ idStr := c.Param("id")
+ var id uint
+ if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
+ return
+ }
+
+ var p models.Partner
+ if err := c.ShouldBindJSON(&p); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
+ return
+ }
+ p.ID = id
+
+ if err := repositories.UpdatePartner(&p); err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update partner"})
+ return
+ }
+
+ c.JSON(http.StatusOK, gin.H{"message": "Partner updated successfully"})
+}
+
+// AdminDeletePartner 删除合作伙伴
+func AdminDeletePartner(c *gin.Context) {
+ idStr := c.Param("id")
+ var id uint
+ if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
+ return
+ }
+
+ if err := repositories.DeletePartner(id); err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete partner"})
+ return
+ }
+
+ c.JSON(http.StatusOK, gin.H{"message": "Partner deleted successfully"})
+}
diff --git a/server/main.go b/server/main.go
index 78984ed..1085913 100644
--- a/server/main.go
+++ b/server/main.go
@@ -77,6 +77,10 @@ func main() {
// 关于页面路由
api.GET("/about", handlers.GetAboutProfile)
+
+ // 合作页面路由
+ api.GET("/testimonials", handlers.GetTestimonials)
+ api.GET("/partners", handlers.GetPartners)
}
// 管理员API路由组
@@ -152,6 +156,16 @@ func main() {
authAdmin.POST("/about", middleware.PermissionMiddleware("settings", "create"), handlers.AdminCreateAboutProfile)
authAdmin.PUT("/about/:id", middleware.PermissionMiddleware("settings", "update"), handlers.AdminUpdateAboutProfile)
authAdmin.DELETE("/about/:id", middleware.PermissionMiddleware("settings", "delete"), handlers.AdminDeleteAboutProfile)
+
+ // 客户评价管理 (复用 settings 权限)
+ authAdmin.POST("/testimonials", middleware.PermissionMiddleware("settings", "create"), handlers.AdminCreateTestimonial)
+ authAdmin.PUT("/testimonials/:id", middleware.PermissionMiddleware("settings", "update"), handlers.AdminUpdateTestimonial)
+ authAdmin.DELETE("/testimonials/:id", middleware.PermissionMiddleware("settings", "delete"), handlers.AdminDeleteTestimonial)
+
+ // 合作伙伴管理 (复用 settings 权限)
+ authAdmin.POST("/partners", middleware.PermissionMiddleware("settings", "create"), handlers.AdminCreatePartner)
+ authAdmin.PUT("/partners/:id", middleware.PermissionMiddleware("settings", "update"), handlers.AdminUpdatePartner)
+ authAdmin.DELETE("/partners/:id", middleware.PermissionMiddleware("settings", "delete"), handlers.AdminDeletePartner)
}
}
diff --git a/server/models/services.go b/server/models/services.go
new file mode 100644
index 0000000..8675be3
--- /dev/null
+++ b/server/models/services.go
@@ -0,0 +1,30 @@
+package models
+
+import (
+ "time"
+)
+
+// Testimonial 客户评价模型
+type Testimonial struct {
+ ID uint `json:"id"`
+ Name string `json:"name"`
+ Role string `json:"role"`
+ Content string `json:"content"`
+ Avatar string `json:"avatar"`
+ Rating uint8 `json:"rating"`
+ SortOrder uint `json:"sortOrder"`
+ CreatedAt time.Time `json:"createdAt"`
+ UpdatedAt time.Time `json:"updatedAt"`
+}
+
+// Partner 合作伙伴模型
+type Partner struct {
+ ID uint `json:"id"`
+ Name string `json:"name"`
+ Logo string `json:"logo"`
+ Description string `json:"description"`
+ URL string `json:"url"`
+ SortOrder uint `json:"sortOrder"`
+ CreatedAt time.Time `json:"createdAt"`
+ UpdatedAt time.Time `json:"updatedAt"`
+}
diff --git a/server/repositories/services_repository.go b/server/repositories/services_repository.go
new file mode 100644
index 0000000..8ca3fe1
--- /dev/null
+++ b/server/repositories/services_repository.go
@@ -0,0 +1,183 @@
+package repositories
+
+import (
+ "log"
+
+ "github.com/niangaodev/art-code/config"
+ "github.com/niangaodev/art-code/models"
+)
+
+// GetTestimonials 获取所有客户评价
+func GetTestimonials() ([]models.Testimonial, error) {
+ query := "SELECT id, name, role, content, avatar, rating, sort_order, created_at, updated_at FROM testimonials ORDER BY sort_order ASC, created_at DESC"
+ rows, err := config.DB.Query(query)
+ if err != nil {
+ log.Printf("Error querying testimonials: %v", err)
+ return nil, err
+ }
+ defer rows.Close()
+
+ var testimonials []models.Testimonial
+ for rows.Next() {
+ var t models.Testimonial
+ if err := rows.Scan(
+ &t.ID,
+ &t.Name,
+ &t.Role,
+ &t.Content,
+ &t.Avatar,
+ &t.Rating,
+ &t.SortOrder,
+ &t.CreatedAt,
+ &t.UpdatedAt,
+ ); err != nil {
+ log.Printf("Error scanning testimonial: %v", err)
+ continue
+ }
+ testimonials = append(testimonials, t)
+ }
+
+ return testimonials, nil
+}
+
+// CreateTestimonial 创建客户评价
+func CreateTestimonial(t *models.Testimonial) error {
+ query := `
+ INSERT INTO testimonials (name, role, content, avatar, rating, sort_order, created_at, updated_at)
+ VALUES (?, ?, ?, ?, ?, ?, NOW(), NOW())
+ `
+ _, err := config.DB.Exec(
+ query,
+ t.Name,
+ t.Role,
+ t.Content,
+ t.Avatar,
+ t.Rating,
+ t.SortOrder,
+ )
+ if err != nil {
+ log.Printf("Error creating testimonial: %v", err)
+ return err
+ }
+ return nil
+}
+
+// UpdateTestimonial 更新客户评价
+func UpdateTestimonial(t *models.Testimonial) error {
+ query := `
+ UPDATE testimonials SET name = ?, role = ?, content = ?, avatar = ?, rating = ?, sort_order = ?, updated_at = NOW()
+ WHERE id = ?
+ `
+ _, err := config.DB.Exec(
+ query,
+ t.Name,
+ t.Role,
+ t.Content,
+ t.Avatar,
+ t.Rating,
+ t.SortOrder,
+ t.ID,
+ )
+ if err != nil {
+ log.Printf("Error updating testimonial: %v", err)
+ return err
+ }
+ return nil
+}
+
+// DeleteTestimonial 删除客户评价
+func DeleteTestimonial(id uint) error {
+ query := "DELETE FROM testimonials WHERE id = ?"
+ _, err := config.DB.Exec(query, id)
+ if err != nil {
+ log.Printf("Error deleting testimonial: %v", err)
+ return err
+ }
+ return nil
+}
+
+// GetPartners 获取所有合作伙伴
+func GetPartners() ([]models.Partner, error) {
+ query := "SELECT id, name, logo, description, url, sort_order, created_at, updated_at FROM partners ORDER BY sort_order ASC, created_at DESC"
+ rows, err := config.DB.Query(query)
+ if err != nil {
+ log.Printf("Error querying partners: %v", err)
+ return nil, err
+ }
+ defer rows.Close()
+
+ var partners []models.Partner
+ for rows.Next() {
+ var p models.Partner
+ if err := rows.Scan(
+ &p.ID,
+ &p.Name,
+ &p.Logo,
+ &p.Description,
+ &p.URL,
+ &p.SortOrder,
+ &p.CreatedAt,
+ &p.UpdatedAt,
+ ); err != nil {
+ log.Printf("Error scanning partner: %v", err)
+ continue
+ }
+ partners = append(partners, p)
+ }
+
+ return partners, nil
+}
+
+// CreatePartner 创建合作伙伴
+func CreatePartner(p *models.Partner) error {
+ query := `
+ INSERT INTO partners (name, logo, description, url, sort_order, created_at, updated_at)
+ VALUES (?, ?, ?, ?, ?, NOW(), NOW())
+ `
+ _, err := config.DB.Exec(
+ query,
+ p.Name,
+ p.Logo,
+ p.Description,
+ p.URL,
+ p.SortOrder,
+ )
+ if err != nil {
+ log.Printf("Error creating partner: %v", err)
+ return err
+ }
+ return nil
+}
+
+// UpdatePartner 更新合作伙伴
+func UpdatePartner(p *models.Partner) error {
+ query := `
+ UPDATE partners SET name = ?, logo = ?, description = ?, url = ?, sort_order = ?, updated_at = NOW()
+ WHERE id = ?
+ `
+ _, err := config.DB.Exec(
+ query,
+ p.Name,
+ p.Logo,
+ p.Description,
+ p.URL,
+ p.SortOrder,
+ p.ID,
+ )
+ if err != nil {
+ log.Printf("Error updating partner: %v", err)
+ return err
+ }
+ return nil
+}
+
+// DeletePartner 删除合作伙伴
+func DeletePartner(id uint) error {
+ query := "DELETE FROM partners WHERE id = ?"
+ _, err := config.DB.Exec(query, id)
+ if err != nil {
+ log.Printf("Error deleting partner: %v", err)
+ return err
+ }
+ return nil
+}
diff --git a/server/scripts/run_services_migration.go b/server/scripts/run_services_migration.go
new file mode 100644
index 0000000..0a92f60
--- /dev/null
+++ b/server/scripts/run_services_migration.go
@@ -0,0 +1,35 @@
+package main
+
+import (
+ "io/ioutil"
+ "log"
+ "strings"
+
+ "github.com/niangaodev/art-code/config"
+)
+
+func main() {
+ config.InitDB()
+ defer config.CloseDB()
+
+ // Read SQL file
+ content, err := ioutil.ReadFile("scripts/create_services_tables.sql")
+ if err != nil {
+ log.Fatalf("Failed to read SQL file: %v", err)
+ }
+
+ requests := strings.Split(string(content), ";")
+ for _, request := range requests {
+ request = strings.TrimSpace(request)
+ if request == "" {
+ continue
+ }
+ _, err := config.DB.Exec(request)
+ if err != nil {
+ log.Printf("Error executing query: %s\nError: %v", request, err)
+ } else {
+ log.Println("Executed query successfully")
+ }
+ }
+ log.Println("Services tables migration completed.")
+}