golang+vue打造高效多語言博客系統(tǒng)的完整指南
更新時間:2025年03月24日 10:41:50 作者:程序員愛釣魚
這篇文章主要為大家詳細(xì)介紹了如何使用golang和vue打造一個高效多語言博客系統(tǒng),本文為大家附上了完整版指南,有需要的小伙伴可以參考一下
后端部分 (Go)
1. 首先創(chuàng)建文章相關(guān)的數(shù)據(jù)模型
package model
import (
"gorm.io/gorm"
)
// Article 文章主表
type Article struct {
gorm.Model
Status uint8 `json:"status" gorm:"default:1"` // 狀態(tài):0-禁用 1-啟用
Sort int `json:"sort" gorm:"default:0"` // 排序
AuthorId uint `json:"authorId"` // 作者ID
CategoryId uint `json:"categoryId"` // 分類ID
Thumbnail string `json:"thumbnail"` // 縮略圖
// 關(guān)聯(lián)
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"` // 標(biāo)題
Description string `json:"description"` // 描述
Content string `json:"content" gorm:"type:text"` // 內(nèi)容
Keywords string `json:"keywords"` // SEO關(guān)鍵詞
Slug string `json:"slug" gorm:"uniqueIndex"` // URL友好的標(biāo)題
}
// 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. 創(chuàng)建服務(wù)層
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{})
// 構(gòu)建查詢條件
if req.Status != nil {
db = db.Where("status = ?", *req.Status)
}
if req.CategoryId != nil {
db = db.Where("category_id = ?", *req.CategoryId)
}
// 預(yù)加載翻譯數(shù)據(jù)
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. 創(chuàng)建控制器
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 創(chuàng)建文章
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 ...
// 文章相關(guān)路由
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)
1. 創(chuàng)建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. 創(chuàng)建文章列表頁面
<template>
<div>
<el-card>
<!-- 搜索欄 -->
<el-form :inline="true" :model="searchForm">
<el-form-item>
<el-select v-model="searchForm.lang" placeholder="選擇語言">
<el-option label="中文" value="zh-CN" />
<el-option label="English" value="en-US" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadData">搜索</el-button>
<el-button @click="handleAdd">新增</el-button>
</el-form-item>
</el-form>
<!-- 數(shù)據(jù)表格 -->
<el-table :data="tableData" v-loading="loading">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column label="標(biāo)題">
<template #default="{ row }">
{{ row.translations?.[0]?.title }}
</template>
</el-table-column>
<el-table-column label="分類">
<template #default="{ row }">
{{ row.category?.translations?.[0]?.name }}
</template>
</el-table-column>
<el-table-column prop="status" label="狀態(tài)">
<template #default="{ row }">
<el-tag :type="row.status === 1 ? 'success' : 'info'">
{{ row.status === 1 ? '啟用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="200">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">編輯</el-button>
<el-button type="danger" link @click="handleDelete(row)">刪除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分頁 -->
<div class="pagination-container">
<el-pagination
v-model:current-page="page"
v-model:page-size="pageSize"
:total="total"
@current-change="loadData"
/>
</div>
</el-card>
<!-- 編輯彈窗 -->
<el-dialog
:title="dialogTitle"
v-model="dialogVisible"
width="800px"
>
<article-form
v-if="dialogVisible"
:form-data="formData"
@submit="handleSubmit"
@cancel="dialogVisible = false"
/>
</el-dialog>
</div>
</template>
<script setup lang="ts">
const { getList, remove } = useArticleApi()
// 狀態(tài)定義
const searchForm = ref({
lang: 'zh-CN'
})
const loading = ref(false)
const tableData = ref([])
const page = ref(1)
const pageSize = ref(10)
const total = ref(0)
const dialogVisible = ref(false)
const dialogTitle = ref('')
const formData = ref<any>({})
// 加載數(shù)據(jù)
const loadData = async () => {
loading.value = true
try {
const { data } = await getList({
page: page.value,
pageSize: pageSize.value,
lang: searchForm.value.lang
})
tableData.value = data.value.list
total.value = data.value.total
} finally {
loading.value = false
}
}
// 處理新增
const handleAdd = () => {
formData.value = {
status: 1,
translations: [{ lang: searchForm.value.lang }]
}
dialogTitle.value = '新增文章'
dialogVisible.value = true
}
// 處理編輯
const handleEdit = (row: any) => {
formData.value = { ...row }
dialogTitle.value = '編輯文章'
dialogVisible.value = true
}
// 處理刪除
const handleDelete = async (row: any) => {
try {
await ElMessageBox.confirm('確認(rèn)刪除該文章嗎?')
await remove(row.id)
ElMessage.success('刪除成功')
loadData()
} catch (err) {
// 取消刪除時不顯示錯誤
if (err !== 'cancel') {
ElMessage.error('刪除失敗')
}
}
}
// 處理表單提交
const handleSubmit = async (data: any) => {
try {
if (data.id) {
await update(data)
} else {
await create(data)
}
ElMessage.success('保存成功')
dialogVisible.value = false
loadData()
} catch (err) {
ElMessage.error('保存失敗')
}
}
// 初始加載
onMounted(() => {
loadData()
})
</script>
<style scoped>
.pagination-container {
margin-top: 20px;
text-align: right;
}
</style>
3. 創(chuàng)建文章表單組件
<template>
<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="標(biāo)題"
>
<el-input
v-model="getTranslation(lang.value).title"
placeholder="請輸入標(biāo)題"
/>
</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="內(nèi)容"
>
<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="狀態(tài)" 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>
</el-form>
</template>
<script setup lang="ts">
const props = defineProps<{
formData: any
}>()
const emit = defineEmits(['submit', 'cancel'])
// 表單數(shù)據(jù)
const form = ref({
...props.formData,
translations: props.formData.translations || []
})
// 語言配置
const languages = [
{ label: '中文', value: 'zh-CN' },
{ label: 'English', value: 'en-US' }
]
const activeLang = ref(languages[0].value)
// 獲取翻譯索引
const getTransIndex = (lang: string) => {
return form.value.translations.findIndex((t: any) => t.lang === lang)
}
// 獲取或創(chuàng)建翻譯
const getTranslation = (lang: string) => {
let trans = form.value.translations.find((t: any) => t.lang === lang)
if (!trans) {
trans = { lang }
form.value.translations.push(trans)
}
return trans
}
// 表單驗證規(guī)則
const rules = {
categoryId: [{ required: true, message: '請選擇分類' }],
translations: {
title: [{ required: true, message: '請輸入標(biāo)題' }],
content: [{ required: true, message: '請輸入內(nèi)容' }]
}
}
// 提交表單
const formRef = ref()
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate()
emit('submit', form.value)
}
</script>
4. 多語言配置
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')
}
})
這個實現(xiàn)包含了:
- 后端完整的CRUD接口
- 多語言支持(中英文)
- 富文本編輯器支持
- 圖片上傳功能
- 分類管理
- 權(quán)限控制
以上就是golang+vue打造高效多語言博客系統(tǒng)的完整指南的詳細(xì)內(nèi)容,更多關(guān)于go vue多語言博客的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
golang實現(xiàn)對docker容器心跳監(jiān)控功能
這篇文章主要介紹了golang實現(xiàn)對docker容器心跳監(jiān)控功能,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2019-09-09
Go語言中的流程控制結(jié)構(gòu)和函數(shù)詳解
這篇文章主要介紹了Go語言中的流程控制結(jié)構(gòu)和函數(shù)詳解,本文詳細(xì)講解了if、goto、for、switch等控制語句,同時對函數(shù)相關(guān)知識做了講解,需要的朋友可以參考下2014-10-10
使用Go語言創(chuàng)建靜態(tài)文件服務(wù)器問題
這篇文章主要介紹了使用Go語言創(chuàng)建靜態(tài)文件服務(wù)器,本文通過試了代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03

