• 设为首页
  • 收藏本站
  • 积分充值
  • VIP赞助
  • 手机版
  • 微博
  • 微信
    微信公众号 添加方式:
    1:搜索微信号(888888
    2:扫描左侧二维码
  • 快捷导航
    福建二哥 门户 查看主题

    golang+vue打造高效多语言博客系统的完整指南

    发布者: 福建二哥 | 发布时间: 2025-8-14 10:57| 查看数: 60| 评论数: 0|帖子模式

    后端部分 (Go)


    1. 首先创建文章相关的数据模型
    1. package model

    2. import (
    3.         "gorm.io/gorm"
    4. )

    5. // Article 文章主表
    6. type Article struct {
    7.         gorm.Model
    8.         Status     uint8  `json:"status" gorm:"default:1"` // 状态:0-禁用 1-启用
    9.         Sort       int    `json:"sort" gorm:"default:0"`   // 排序
    10.         AuthorId   uint   `json:"authorId"`                // 作者ID
    11.         CategoryId uint   `json:"categoryId"`              // 分类ID
    12.         Thumbnail  string `json:"thumbnail"`               // 缩略图
    13.        
    14.         // 关联
    15.         Translations []ArticleTranslation `json:"translations"`
    16.         Category     ArticleCategory      `json:"category"`
    17. }

    18. // ArticleTranslation 文章翻译表
    19. type ArticleTranslation struct {
    20.         gorm.Model
    21.         ArticleId   uint   `json:"articleId"`
    22.         Lang        string `json:"lang" gorm:"size:5"`        // 语言代码 如:zh-CN, en-US
    23.         Title       string `json:"title"`                     // 标题
    24.         Description string `json:"description"`               // 描述
    25.         Content     string `json:"content" gorm:"type:text"`  // 内容
    26.         Keywords    string `json:"keywords"`                  // SEO关键词
    27.         Slug        string `json:"slug" gorm:"uniqueIndex"`   // URL友好的标题
    28. }

    29. // ArticleCategory 文章分类
    30. type ArticleCategory struct {
    31.         gorm.Model
    32.         ParentId     uint   `json:"parentId"`
    33.         Status       uint8  `json:"status" gorm:"default:1"`
    34.         Sort         int    `json:"sort" gorm:"default:0"`
    35.         Translations []ArticleCategoryTranslation `json:"translations"`
    36. }

    37. // ArticleCategoryTranslation 分类翻译表
    38. type ArticleCategoryTranslation struct {
    39.         gorm.Model
    40.         CategoryId  uint   `json:"categoryId"`
    41.         Lang        string `json:"lang" gorm:"size:5"`
    42.         Name        string `json:"name"`
    43.         Description string `json:"description"`
    44.         Slug        string `json:"slug" gorm:"uniqueIndex"`
    45. }
    复制代码
    2. 创建服务层
    1. package service

    2. import (
    3.         "dagisku-server/app/model"
    4.         "dagisku-server/global"
    5. )

    6. type ArticleService struct{}

    7. type ArticleListRequest struct {
    8.         Page     int    `json:"page" form:"page"`
    9.         PageSize int    `json:"pageSize" form:"pageSize"`
    10.         Lang     string `json:"lang" form:"lang"`
    11.         Status   *uint8 `json:"status" form:"status"`
    12.         CategoryId *uint `json:"categoryId" form:"categoryId"`
    13. }

    14. func (s *ArticleService) GetList(req ArticleListRequest) (list []model.Article, total int64, err error) {
    15.         limit := req.PageSize
    16.         offset := req.PageSize * (req.Page - 1)
    17.        
    18.         db := global.DB.Model(&model.Article{})
    19.        
    20.         // 构建查询条件
    21.         if req.Status != nil {
    22.                 db = db.Where("status = ?", *req.Status)
    23.         }
    24.         if req.CategoryId != nil {
    25.                 db = db.Where("category_id = ?", *req.CategoryId)
    26.         }
    27.        
    28.         // 预加载翻译数据
    29.         db = db.Preload("Translations", "lang = ?", req.Lang)
    30.         db = db.Preload("Category").Preload("Category.Translations", "lang = ?", req.Lang)
    31.        
    32.         err = db.Count(&total).Error
    33.         if err != nil {
    34.                 return
    35.         }
    36.        
    37.         err = db.Order("sort desc, id desc").Limit(limit).Offset(offset).Find(&list).Error
    38.         return
    39. }

    40. func (s *ArticleService) Create(article *model.Article) error {
    41.         return global.DB.Create(article).Error
    42. }

    43. func (s *ArticleService) Update(article *model.Article) error {
    44.         return global.DB.Save(article).Error
    45. }

    46. func (s *ArticleService) Delete(id uint) error {
    47.         return global.DB.Delete(&model.Article{}, id).Error
    48. }
    复制代码
    3. 创建控制器
    1. package article

    2. import (
    3.         "dagisku-server/app/model"
    4.         "dagisku-server/app/service"
    5.         "dagisku-server/utils/response"
    6.         "github.com/gin-gonic/gin"
    7. )

    8. type ArticleApi struct{}

    9. // List 获取文章列表
    10. func (api *ArticleApi) List(c *gin.Context) {
    11.         var req service.ArticleListRequest
    12.         if err := c.ShouldBindQuery(&req); err != nil {
    13.                 response.FailWithMessage(err.Error(), c)
    14.                 return
    15.         }
    16.        
    17.         if req.Page == 0 {
    18.                 req.Page = 1
    19.         }
    20.         if req.PageSize == 0 {
    21.                 req.PageSize = 10
    22.         }
    23.        
    24.         service := service.ArticleService{}
    25.         list, total, err := service.GetList(req)
    26.         if err != nil {
    27.                 response.FailWithMessage(err.Error(), c)
    28.                 return
    29.         }
    30.        
    31.         response.OkWithData(gin.H{
    32.                 "list": list,
    33.                 "total": total,
    34.         }, c)
    35. }

    36. // Create 创建文章
    37. func (api *ArticleApi) Create(c *gin.Context) {
    38.         var article model.Article
    39.         if err := c.ShouldBindJSON(&article); err != nil {
    40.                 response.FailWithMessage(err.Error(), c)
    41.                 return
    42.         }
    43.        
    44.         service := service.ArticleService{}
    45.         if err := service.Create(&article); err != nil {
    46.                 response.FailWithMessage(err.Error(), c)
    47.                 return
    48.         }
    49.        
    50.         response.Ok(c)
    51. }

    52. // Update 更新文章
    53. func (api *ArticleApi) Update(c *gin.Context) {
    54.         var article model.Article
    55.         if err := c.ShouldBindJSON(&article); err != nil {
    56.                 response.FailWithMessage(err.Error(), c)
    57.                 return
    58.         }
    59.        
    60.         service := service.ArticleService{}
    61.         if err := service.Update(&article); err != nil {
    62.                 response.FailWithMessage(err.Error(), c)
    63.                 return
    64.         }
    65.        
    66.         response.Ok(c)
    67. }

    68. // Delete 删除文章
    69. func (api *ArticleApi) Delete(c *gin.Context) {
    70.         id := c.Param("id")
    71.         service := service.ArticleService{}
    72.         if err := service.Delete(uint(id)); err != nil {
    73.                 response.FailWithMessage(err.Error(), c)
    74.                 return
    75.         }
    76.        
    77.         response.Ok(c)
    78. }
    复制代码
    4. 注册路由
    1. // ... existing code ...

    2. type ApiGroup struct {
    3.         LoginApi
    4.         system.DBApi
    5.         system.JwtApi
    6.         // ... other existing APIs ...
    7.         article.ArticleApi
    8.         article.ArticleCategoryApi
    9. }

    10. // ... existing code ...
    复制代码
    1. func InitAdminRouter(Router *gin.RouterGroup) {
    2.         adminRouter := Router.Group("admin")
    3.        
    4.         // ... existing routes ...
    5.        
    6.         // 文章相关路由
    7.         articleRouter := adminRouter.Group("article")
    8.         {
    9.                 articleRouter.GET("list", articleApi.List)
    10.                 articleRouter.POST("create", articleApi.Create)
    11.                 articleRouter.PUT("update", articleApi.Update)
    12.                 articleRouter.DELETE("delete/:id", articleApi.Delete)
    13.         }
    14. }
    复制代码
    前端部分 (Nuxt 3)


    1. 创建API请求
    1. import { useFetch } from '#app'

    2. export const useArticleApi = () => {
    3.   const config = useRuntimeConfig()
    4.   const baseURL = config.public.apiBase
    5.   
    6.   const getList = async (params: {
    7.     page: number
    8.     pageSize: number
    9.     lang: string
    10.     status?: number
    11.     categoryId?: number
    12.   }) => {
    13.     return await useFetch('/admin/article/list', {
    14.       baseURL,
    15.       method: 'GET',
    16.       params
    17.     })
    18.   }
    19.   
    20.   const create = async (data: any) => {
    21.     return await useFetch('/admin/article/create', {
    22.       baseURL,
    23.       method: 'POST',
    24.       body: data
    25.     })
    26.   }
    27.   
    28.   const update = async (data: any) => {
    29.     return await useFetch('/admin/article/update', {
    30.       baseURL,
    31.       method: 'PUT',
    32.       body: data
    33.     })
    34.   }
    35.   
    36.   const remove = async (id: number) => {
    37.     return await useFetch(`/admin/article/delete/${id}`, {
    38.       baseURL,
    39.       method: 'DELETE'
    40.     })
    41.   }
    42.   
    43.   return {
    44.     getList,
    45.     create,
    46.     update,
    47.     remove
    48.   }
    49. }
    复制代码
    2. 创建文章列表页面
    1. <template>
    2.   <div>
    3.     <el-card>
    4.       <!-- 搜索栏 -->
    5.       <el-form :inline="true" :model="searchForm">
    6.         <el-form-item>
    7.           <el-select v-model="searchForm.lang" placeholder="选择语言">
    8.             <el-option label="中文" value="zh-CN" />
    9.             <el-option label="English" value="en-US" />
    10.           </el-select>
    11.         </el-form-item>
    12.         <el-form-item>
    13.           <el-button type="primary" @click="loadData">搜索</el-button>
    14.           <el-button @click="handleAdd">新增</el-button>
    15.         </el-form-item>
    16.       </el-form>
    17.       
    18.       <!-- 数据表格 -->
    19.       <el-table :data="tableData" v-loading="loading">
    20.         <el-table-column prop="id" label="ID" width="80" />
    21.         <el-table-column label="标题">
    22.           <template #default="{ row }">
    23.             {{ row.translations?.[0]?.title }}
    24.           </template>
    25.         </el-table-column>
    26.         <el-table-column label="分类">
    27.           <template #default="{ row }">
    28.             {{ row.category?.translations?.[0]?.name }}
    29.           </template>
    30.         </el-table-column>
    31.         <el-table-column prop="status" label="状态">
    32.           <template #default="{ row }">
    33.             <el-tag :type="row.status === 1 ? 'success' : 'info'">
    34.               {{ row.status === 1 ? '启用' : '禁用' }}
    35.             </el-tag>
    36.           </template>
    37.         </el-table-column>
    38.         <el-table-column label="操作" width="200">
    39.           <template #default="{ row }">
    40.             <el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
    41.             <el-button type="danger" link @click="handleDelete(row)">删除</el-button>
    42.           </template>
    43.         </el-table-column>
    44.       </el-table>
    45.       
    46.       <!-- 分页 -->
    47.       <div class="pagination-container">
    48.         <el-pagination
    49.           v-model:current-page="page"
    50.           v-model:page-size="pageSize"
    51.           :total="total"
    52.           @current-change="loadData"
    53.         />
    54.       </div>
    55.     </el-card>
    56.    
    57.     <!-- 编辑弹窗 -->
    58.     <el-dialog
    59.       :title="dialogTitle"
    60.       v-model="dialogVisible"
    61.       width="800px"
    62.     >
    63.       <article-form
    64.         v-if="dialogVisible"
    65.         :form-data="formData"
    66.         @submit="handleSubmit"
    67.         @cancel="dialogVisible = false"
    68.       />
    69.     </el-dialog>
    70.   </div>
    71. </template>

    72. <script setup lang="ts">
    73. const { getList, remove } = useArticleApi()

    74. // 状态定义
    75. const searchForm = ref({
    76.   lang: 'zh-CN'
    77. })
    78. const loading = ref(false)
    79. const tableData = ref([])
    80. const page = ref(1)
    81. const pageSize = ref(10)
    82. const total = ref(0)
    83. const dialogVisible = ref(false)
    84. const dialogTitle = ref('')
    85. const formData = ref<any>({})

    86. // 加载数据
    87. const loadData = async () => {
    88.   loading.value = true
    89.   try {
    90.     const { data } = await getList({
    91.       page: page.value,
    92.       pageSize: pageSize.value,
    93.       lang: searchForm.value.lang
    94.     })
    95.     tableData.value = data.value.list
    96.     total.value = data.value.total
    97.   } finally {
    98.     loading.value = false
    99.   }
    100. }

    101. // 处理新增
    102. const handleAdd = () => {
    103.   formData.value = {
    104.     status: 1,
    105.     translations: [{ lang: searchForm.value.lang }]
    106.   }
    107.   dialogTitle.value = '新增文章'
    108.   dialogVisible.value = true
    109. }

    110. // 处理编辑
    111. const handleEdit = (row: any) => {
    112.   formData.value = { ...row }
    113.   dialogTitle.value = '编辑文章'
    114.   dialogVisible.value = true
    115. }

    116. // 处理删除
    117. const handleDelete = async (row: any) => {
    118.   try {
    119.     await ElMessageBox.confirm('确认删除该文章吗?')
    120.     await remove(row.id)
    121.     ElMessage.success('删除成功')
    122.     loadData()
    123.   } catch (err) {
    124.     // 取消删除时不显示错误
    125.     if (err !== 'cancel') {
    126.       ElMessage.error('删除失败')
    127.     }
    128.   }
    129. }

    130. // 处理表单提交
    131. const handleSubmit = async (data: any) => {
    132.   try {
    133.     if (data.id) {
    134.       await update(data)
    135.     } else {
    136.       await create(data)
    137.     }
    138.     ElMessage.success('保存成功')
    139.     dialogVisible.value = false
    140.     loadData()
    141.   } catch (err) {
    142.     ElMessage.error('保存失败')
    143.   }
    144. }

    145. // 初始加载
    146. onMounted(() => {
    147.   loadData()
    148. })
    149. </script>

    150. <style scoped>
    151. .pagination-container {
    152.   margin-top: 20px;
    153.   text-align: right;
    154. }
    155. </style>
    复制代码
    3. 创建文章表单组件
    1. <template>
    2.   <el-form
    3.     ref="formRef"
    4.     :model="form"
    5.     :rules="rules"
    6.     label-width="100px"
    7.   >
    8.     <el-tabs v-model="activeLang">
    9.       <el-tab-pane
    10.         v-for="lang in languages"
    11.         :key="lang.value"
    12.         :label="lang.label"
    13.         :name="lang.value"
    14.       >
    15.         <el-form-item
    16.           :prop="`translations.${getTransIndex(lang.value)}.title`"
    17.           label="标题"
    18.         >
    19.           <el-input
    20.             v-model="getTranslation(lang.value).title"
    21.             placeholder="请输入标题"
    22.           />
    23.         </el-form-item>
    24.         
    25.         <el-form-item
    26.           :prop="`translations.${getTransIndex(lang.value)}.description`"
    27.           label="描述"
    28.         >
    29.           <el-input
    30.             type="textarea"
    31.             v-model="getTranslation(lang.value).description"
    32.             placeholder="请输入描述"
    33.           />
    34.         </el-form-item>
    35.         
    36.         <el-form-item
    37.           :prop="`translations.${getTransIndex(lang.value)}.content`"
    38.           label="内容"
    39.         >
    40.           <editor
    41.             v-model="getTranslation(lang.value).content"
    42.             :height="400"
    43.           />
    44.         </el-form-item>
    45.       </el-tab-pane>
    46.     </el-tabs>
    47.    
    48.     <el-form-item label="分类" prop="categoryId">
    49.       <el-select v-model="form.categoryId">
    50.         <el-option
    51.           v-for="item in categories"
    52.           :key="item.id"
    53.           :label="item.translations[0].name"
    54.           :value="item.id"
    55.         />
    56.       </el-select>
    57.     </el-form-item>
    58.    
    59.     <el-form-item label="缩略图" prop="thumbnail">
    60.       <upload v-model="form.thumbnail" />
    61.     </el-form-item>
    62.    
    63.     <el-form-item label="状态" prop="status">
    64.       <el-switch
    65.         v-model="form.status"
    66.         :active-value="1"
    67.         :inactive-value="0"
    68.       />
    69.     </el-form-item>
    70.    
    71.     <el-form-item>
    72.       <el-button type="primary" @click="handleSubmit">保存</el-button>
    73.       <el-button @click="$emit('cancel')">取消</el-button>
    74.     </el-form-item>
    75.   </el-form>
    76. </template>

    77. <script setup lang="ts">
    78. const props = defineProps<{
    79.   formData: any
    80. }>()

    81. const emit = defineEmits(['submit', 'cancel'])

    82. // 表单数据
    83. const form = ref({
    84.   ...props.formData,
    85.   translations: props.formData.translations || []
    86. })

    87. // 语言配置
    88. const languages = [
    89.   { label: '中文', value: 'zh-CN' },
    90.   { label: 'English', value: 'en-US' }
    91. ]
    92. const activeLang = ref(languages[0].value)

    93. // 获取翻译索引
    94. const getTransIndex = (lang: string) => {
    95.   return form.value.translations.findIndex((t: any) => t.lang === lang)
    96. }

    97. // 获取或创建翻译
    98. const getTranslation = (lang: string) => {
    99.   let trans = form.value.translations.find((t: any) => t.lang === lang)
    100.   if (!trans) {
    101.     trans = { lang }
    102.     form.value.translations.push(trans)
    103.   }
    104.   return trans
    105. }

    106. // 表单验证规则
    107. const rules = {
    108.   categoryId: [{ required: true, message: '请选择分类' }],
    109.   translations: {
    110.     title: [{ required: true, message: '请输入标题' }],
    111.     content: [{ required: true, message: '请输入内容' }]
    112.   }
    113. }

    114. // 提交表单
    115. const formRef = ref()
    116. const handleSubmit = async () => {
    117.   if (!formRef.value) return
    118.   await formRef.value.validate()
    119.   emit('submit', form.value)
    120. }
    121. </script>
    复制代码
    4. 多语言配置
    1. import { createI18n } from 'vue-i18n'

    2. export default defineNuxtPlugin(({ vueApp }) => {
    3.   const i18n = createI18n({
    4.     legacy: false,
    5.     globalInjection: true,
    6.     locale: 'zh-CN',
    7.     messages: {
    8.       'zh-CN': {
    9.         article: {
    10.           title: '文章管理',
    11.           list: '文章列表',
    12.           add: '新增文章',
    13.           edit: '编辑文章',
    14.           // ... 其他翻译
    15.         }
    16.       },
    17.       'en-US': {
    18.         article: {
    19.           title: 'Article Management',
    20.           list: 'Article List',
    21.           add: 'New Article',
    22.           edit: 'Edit Article',
    23.           // ... 其他翻译
    24.         }
    25.       }
    26.     }
    27.   })

    28.   vueApp.use(i18n)
    29. })
    复制代码
    5. 前端路由配置
    1. export default defineNuxtRouteMiddleware((to) => {
    2.   const token = useCookie('token')
    3.   
    4.   if (!token.value && to.path.startsWith('/admin')) {
    5.     return navigateTo('/login')
    6.   }
    7. })
    复制代码
    这个实现包含了:

    • 后端完整的CRUD接口
    • 多语言支持(中英文)
    • 富文本编辑器支持
    • 图片上传功能
    • 分类管理
    • 权限控制
    以上就是golang+vue打造高效多语言博客系统的完整指南的详细内容,更多关于go vue多语言博客的资料请关注脚本之家其它相关文章!

    来源:互联网
    免责声明:如果侵犯了您的权益,请联系站长(1277306191@qq.com),我们会及时删除侵权内容,谢谢合作!

    最新评论

    浏览过的版块

    QQ Archiver 手机版 小黑屋 福建二哥 ( 闽ICP备2022004717号|闽公网安备35052402000345号 )

    Powered by Discuz! X3.5 © 2001-2023

    快速回复 返回顶部 返回列表