后端部分 (Go)
- 首先创建文章相关的数据模型
package model
import (
“gorm.io/gorm”
)
// Article 文章主表
type Article struct {
gorm.Model
Status uint8 json:"status" gorm:"default:1"
// 状态:0-禁用 1-启用
Sort int json:"sort" gorm:"default:0"
// 排序
AuthorId uint json:"authorId"
// 作者ID
CategoryId uint json:"categoryId"
// 分类ID
Thumbnail string json:"thumbnail"
// 缩略图
// 关联
Translations []ArticleTranslation `json:"translations"`
Category ArticleCategory `json:"category"`
}
// ArticleTranslation 文章翻译表
type ArticleTranslation struct {
gorm.Model
ArticleId uint json:"articleId"
Lang string json:"lang" gorm:"size:5"
// 语言代码 如:zh-CN, en-US
Title string json:"title"
// 标题
Description string json:"description"
// 描述
Content string json:"content" gorm:"type:text"
// 内容
Keywords string json:"keywords"
// SEO关键词
Slug string json:"slug" gorm:"uniqueIndex"
// URL友好的标题
}
// ArticleCategory 文章分类
type ArticleCategory struct {
gorm.Model
ParentId uint json:"parentId"
Status uint8 json:"status" gorm:"default:1"
Sort int json:"sort" gorm:"default:0"
Translations []ArticleCategoryTranslation json:"translations"
}
// ArticleCategoryTranslation 分类翻译表
type ArticleCategoryTranslation struct {
gorm.Model
CategoryId uint json:"categoryId"
Lang string json:"lang" gorm:"size:5"
Name string json:"name"
Description string json:"description"
Slug string json:"slug" gorm:"uniqueIndex"
}
2. 创建服务层
package service
import (
“dagisku-server/app/model”
“dagisku-server/global”
)
type ArticleService struct{}
type ArticleListRequest struct {
Page int json:"page" form:"page"
PageSize int json:"pageSize" form:"pageSize"
Lang string json:"lang" form:"lang"
Status *uint8 json:"status" form:"status"
CategoryId *uint json:"categoryId" form:"categoryId"
}
func (s *ArticleService) GetList(req ArticleListRequest) (list []model.Article, total int64, err error) {
limit := req.PageSize
offset := req.PageSize * (req.Page - 1)
db := global.DB.Model(&model.Article{})
// 构建查询条件
if req.Status != nil {
db = db.Where("status = ?", *req.Status)
}
if req.CategoryId != nil {
db = db.Where("category_id = ?", *req.CategoryId)
}
// 预加载翻译数据
db = db.Preload("Translations", "lang = ?", req.Lang)
db = db.Preload("Category").Preload("Category.Translations", "lang = ?", req.Lang)
err = db.Count(&total).Error
if err != nil {
return
}
err = db.Order("sort desc, id desc").Limit(limit).Offset(offset).Find(&list).Error
return
}
func (s *ArticleService) Create(article *model.Article) error {
return global.DB.Create(article).Error
}
func (s *ArticleService) Update(article *model.Article) error {
return global.DB.Save(article).Error
}
func (s *ArticleService) Delete(id uint) error {
return global.DB.Delete(&model.Article{}, id).Error
}
3. 创建控制器
package article
import (
“dagisku-server/app/model”
“dagisku-server/app/service”
“dagisku-server/utils/response”
“github.com/gin-gonic/gin”
)
type ArticleApi struct{}
// List 获取文章列表
func (api *ArticleApi) List(c *gin.Context) {
var req service.ArticleListRequest
if err := c.ShouldBindQuery(&req); err != nil {
response.FailWithMessage(err.Error(), c)
return
}
if req.Page == 0 {
req.Page = 1
}
if req.PageSize == 0 {
req.PageSize = 10
}
service := service.ArticleService{}
list, total, err := service.GetList(req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
response.OkWithData(gin.H{
"list": list,
"total": total,
}, c)
}
// Create 创建文章
func (api *ArticleApi) Create(c *gin.Context) {
var article model.Article
if err := c.ShouldBindJSON(&article); err != nil {
response.FailWithMessage(err.Error(), c)
return
}
service := service.ArticleService{}
if err := service.Create(&article); err != nil {
response.FailWithMessage(err.Error(), c)
return
}
response.Ok(c)
}
// Update 更新文章
func (api *ArticleApi) Update(c *gin.Context) {
var article model.Article
if err := c.ShouldBindJSON(&article); err != nil {
response.FailWithMessage(err.Error(), c)
return
}
service := service.ArticleService{}
if err := service.Update(&article); err != nil {
response.FailWithMessage(err.Error(), c)
return
}
response.Ok(c)
}
// Delete 删除文章
func (api *ArticleApi) Delete(c *gin.Context) {
id := c.Param(“id”)
service := service.ArticleService{}
if err := service.Delete(uint(id)); err != nil {
response.FailWithMessage(err.Error(), c)
return
}
response.Ok(c)
}
4. 注册路由
// … existing code …
type ApiGroup struct {
LoginApi
system.DBApi
system.JwtApi
// … other existing APIs …
article.ArticleApi
article.ArticleCategoryApi
}
// … existing code …
func InitAdminRouter(Router *gin.RouterGroup) {
adminRouter := Router.Group(“admin”)
// ... existing routes ...
// 文章相关路由
articleRouter := adminRouter.Group("article")
{
articleRouter.GET("list", articleApi.List)
articleRouter.POST("create", articleApi.Create)
articleRouter.PUT("update", articleApi.Update)
articleRouter.DELETE("delete/:id", articleApi.Delete)
}
}
前端部分 (Nuxt 3)
- 创建API请求
import { useFetch } from ‘#app’
export const useArticleApi = () => {
const config = useRuntimeConfig()
const baseURL = config.public.apiBase
const getList = async (params: {
page: number
pageSize: number
lang: string
status?: number
categoryId?: number
}) => {
return await useFetch(‘/admin/article/list’, {
baseURL,
method: ‘GET’,
params
})
}
const create = async (data: any) => {
return await useFetch(‘/admin/article/create’, {
baseURL,
method: ‘POST’,
body: data
})
}
const update = async (data: any) => {
return await useFetch(‘/admin/article/update’, {
baseURL,
method: ‘PUT’,
body: data
})
}
const remove = async (id: number) => {
return await useFetch(/admin/article/delete/${id}
, {
baseURL,
method: ‘DELETE’
})
}
return {
getList,
create,
update,
remove
}
}
2. 创建文章列表页面
- 创建文章表单组件
<el-form
ref=“formRef”
:model=“form”
:rules=“rules”
label-width=“100px”
<el-tabs v-model="activeLang">
<el-tab-pane
v-for="lang in languages"
:key="lang.value"
:label="lang.label"
:name="lang.value"
>
<el-form-item
:prop="`translations.${getTransIndex(lang.value)}.title`"
label="标题"
>
<el-input
v-model="getTranslation(lang.value).title"
placeholder="请输入标题"
/>
</el-form-item>
<el-form-item
:prop="`translations.${getTransIndex(lang.value)}.description`"
label="描述"
>
<el-input
type="textarea"
v-model="getTranslation(lang.value).description"
placeholder="请输入描述"
/>
</el-form-item>
<el-form-item
:prop="`translations.${getTransIndex(lang.value)}.content`"
label="内容"
>
<editor
v-model="getTranslation(lang.value).content"
:height="400"
/>
</el-form-item>
</el-tab-pane>
</el-tabs>
<el-form-item label="分类" prop="categoryId">
<el-select v-model="form.categoryId">
<el-option
v-for="item in categories"
:key="item.id"
:label="item.translations[0].name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="缩略图" prop="thumbnail">
<upload v-model="form.thumbnail" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-switch
v-model="form.status"
:active-value="1"
:inactive-value="0"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSubmit">保存</el-button>
<el-button @click="$emit('cancel')">取消</el-button>
</el-form-item>
- 多语言配置
import { createI18n } from ‘vue-i18n’
export default defineNuxtPlugin(({ vueApp }) => {
const i18n = createI18n({
legacy: false,
globalInjection: true,
locale: ‘zh-CN’,
messages: {
‘zh-CN’: {
article: {
title: ‘文章管理’,
list: ‘文章列表’,
add: ‘新增文章’,
edit: ‘编辑文章’,
// … 其他翻译
}
},
‘en-US’: {
article: {
title: ‘Article Management’,
list: ‘Article List’,
add: ‘New Article’,
edit: ‘Edit Article’,
// … 其他翻译
}
}
}
})
vueApp.use(i18n)
})
5. 前端路由配置
export default defineNuxtRouteMiddleware((to) => {
const token = useCookie(‘token’)
if (!token.value && to.path.startsWith(‘/admin’)) {
return navigateTo(‘/login’)
}
})
这个实现包含了:
- 后端完整的CRUD接口
- 多语言支持(中英文)
- 富文本编辑器支持
- 图片上传功能
- 分类管理
- 权限控制