feat(news): 支持新闻中心多站点隔离功能
新增站点编码配置,支持新闻分类与文章按站点隔离。主要变更包括: - 数据库表增加 site_code 字段及索引 - 后台管理界面支持按站点筛选 - 前台接口支持通过查询参数或请求头指定站点 - 新增站点配置与解析逻辑
This commit is contained in:
4
client/env.d.ts
vendored
4
client/env.d.ts
vendored
@@ -1,5 +1,9 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_PORTAL_SITE_CODE?: string
|
||||
}
|
||||
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import request from '@/utils/request'
|
||||
import { portalSiteQueryParams } from '@/config/portalSite'
|
||||
|
||||
export const portalApi = {
|
||||
getCompanyInfo: () => request.get('/portal/company'),
|
||||
@@ -17,9 +18,12 @@ export const portalApi = {
|
||||
getCaseCategories: () => request.get('/portal/case/category'),
|
||||
getCases: (params?: any) => request.get('/portal/case', { params }),
|
||||
getCaseById: (id: number) => request.get(`/portal/case/${id}`),
|
||||
getNewsCategories: () => request.get('/portal/news/category'),
|
||||
getNewsList: (params?: any) => request.get('/portal/news', { params }),
|
||||
getNewsById: (id: number) => request.get(`/portal/news/${id}`),
|
||||
getNewsCategories: () =>
|
||||
request.get('/portal/news/category', { params: { ...portalSiteQueryParams() } }),
|
||||
getNewsList: (params?: any) =>
|
||||
request.get('/portal/news', { params: { ...portalSiteQueryParams(), ...params } }),
|
||||
getNewsById: (id: number) =>
|
||||
request.get(`/portal/news/${id}`, { params: { ...portalSiteQueryParams() } }),
|
||||
}
|
||||
|
||||
export const adminApi = {
|
||||
@@ -85,11 +89,12 @@ export const adminApi = {
|
||||
addCaseCategory: (data: any) => request.post('/admin/case/category', data),
|
||||
deleteCaseCategory: (id: number) => request.delete(`/admin/case/category/${id}`),
|
||||
|
||||
getNewsList: (params?: any) => request.get('/admin/news', { params }),
|
||||
getNewsList: (params?: any) => request.get('/admin/news', { params: { ...params } }),
|
||||
addNews: (data: any) => request.post('/admin/news', data),
|
||||
updateNews: (data: any) => request.put('/admin/news', data),
|
||||
deleteNews: (id: number) => request.delete(`/admin/news/${id}`),
|
||||
getNewsCategories: () => request.get('/admin/news/category'),
|
||||
getNewsCategories: (params?: any) =>
|
||||
request.get('/admin/news/category', { params: { ...params } }),
|
||||
addNewsCategory: (data: any) => request.post('/admin/news/category', data),
|
||||
deleteNewsCategory: (id: number) => request.delete(`/admin/news/category/${id}`),
|
||||
|
||||
|
||||
8
client/src/config/portalSite.ts
Normal file
8
client/src/config/portalSite.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/** 前台新闻等接口按站点隔离;与后端 app.portal.allowed-site-codes 一致。不设则走后端默认 site-code。 */
|
||||
export function portalSiteQueryParams(): Record<string, string> {
|
||||
const code = import.meta.env.VITE_PORTAL_SITE_CODE as string | undefined
|
||||
if (code && String(code).trim() !== '') {
|
||||
return { siteCode: String(code).trim().toLowerCase() }
|
||||
}
|
||||
return {}
|
||||
}
|
||||
@@ -2,10 +2,19 @@
|
||||
<div class="admin-crud-page">
|
||||
<div class="page-header">
|
||||
<h2>新闻管理</h2>
|
||||
<el-button type="primary" @click="openDialog()">新增新闻</el-button>
|
||||
<div class="page-header-filters">
|
||||
<span style="margin-right:8px">站点</span>
|
||||
<el-select v-model="filterSiteCode" placeholder="全部站点" clearable style="width:180px" @change="loadList">
|
||||
<el-option label="全部站点" value="" />
|
||||
<el-option label="主站 wuhansaga" value="wuhansaga" />
|
||||
<el-option label="第二站点 saga-secondary" value="saga-secondary" />
|
||||
</el-select>
|
||||
<el-button type="primary" style="margin-left:12px" @click="openDialog()">新增新闻</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="list" border stripe v-loading="loading">
|
||||
<el-table-column prop="newsId" label="ID" width="80" />
|
||||
<el-table-column prop="siteCode" label="站点" width="130" />
|
||||
<el-table-column label="封面" width="140">
|
||||
<template #default="{ row }">
|
||||
<el-image v-if="row.coverImage" :src="uploadPublicUrl(row.coverImage)" style="width:100px;height:50px;" fit="cover" />
|
||||
@@ -31,7 +40,17 @@
|
||||
<el-form :model="form" label-width="120px">
|
||||
<el-form-item label="标题(中)"><el-input v-model="form.titleZh" /></el-form-item>
|
||||
<el-form-item label="标题(英)"><el-input v-model="form.titleEn" /></el-form-item>
|
||||
<el-form-item label="分类ID"><el-input-number v-model="form.categoryId" :min="1" /></el-form-item>
|
||||
<el-form-item label="站点">
|
||||
<el-select v-model="form.siteCode" placeholder="请选择" style="width:100%" @change="onFormSiteChange">
|
||||
<el-option label="主站 wuhansaga" value="wuhansaga" />
|
||||
<el-option label="第二站点 saga-secondary" value="saga-secondary" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="分类">
|
||||
<el-select v-model="form.categoryId" placeholder="请选择分类" style="width:100%">
|
||||
<el-option v-for="cat in categoryOptions" :key="cat.newsCategoryId" :label="cat.nameZh" :value="cat.newsCategoryId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="摘要(中)"><el-input v-model="form.excerptZh" type="textarea" :rows="2" /></el-form-item>
|
||||
<el-form-item label="摘要(英)"><el-input v-model="form.excerptEn" type="textarea" :rows="2" /></el-form-item>
|
||||
<el-form-item label="内容(中)"><el-input v-model="form.contentZh" type="textarea" :rows="6" /></el-form-item>
|
||||
@@ -59,14 +78,37 @@ import { uploadPublicUrl } from '@/utils/uploadUrl'
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const list = ref<any[]>([])
|
||||
const categoryOptions = ref<any[]>([])
|
||||
const filterSiteCode = ref('')
|
||||
const dialogVisible = ref(false)
|
||||
const editingId = ref<number | null>(null)
|
||||
const form = ref<any>({})
|
||||
|
||||
async function loadCategoriesForForm() {
|
||||
const site = form.value.siteCode
|
||||
if (!site) {
|
||||
categoryOptions.value = []
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await adminApi.getNewsCategories({ siteCode: site })
|
||||
categoryOptions.value = res.data ?? []
|
||||
} catch {
|
||||
categoryOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
function onFormSiteChange() {
|
||||
form.value.categoryId = undefined
|
||||
loadCategoriesForForm()
|
||||
}
|
||||
|
||||
async function loadList() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await adminApi.getNewsList()
|
||||
const params: Record<string, string> = {}
|
||||
if (filterSiteCode.value) params.siteCode = filterSiteCode.value
|
||||
const res = await adminApi.getNewsList(params)
|
||||
list.value = res.data ?? []
|
||||
} catch {
|
||||
list.value = []
|
||||
@@ -76,9 +118,16 @@ async function loadList() {
|
||||
}
|
||||
|
||||
function openDialog(row?: any) {
|
||||
if (row) { editingId.value = row.newsId; form.value = { ...row } }
|
||||
else { editingId.value = null; form.value = { sortOrder: 0, isPublished: 0, isFeatured: 0, categoryId: 1 } }
|
||||
if (row) {
|
||||
editingId.value = row.newsId
|
||||
form.value = { ...row }
|
||||
} else {
|
||||
editingId.value = null
|
||||
const defaultSite = filterSiteCode.value || 'wuhansaga'
|
||||
form.value = { sortOrder: 0, isPublished: 0, isFeatured: 0, siteCode: defaultSite, categoryId: undefined }
|
||||
}
|
||||
dialogVisible.value = true
|
||||
loadCategoriesForForm()
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
@@ -100,5 +149,5 @@ onMounted(loadList)
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.admin-crud-page { .page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; h2 { font-size: 20px; color: @gray-800; } } }
|
||||
.admin-crud-page { .page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; flex-wrap: wrap; gap: 12px; h2 { font-size: 20px; color: @gray-800; } .page-header-filters { display: flex; align-items: center; flex-wrap: wrap; } } }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user