调试frontend
This commit is contained in:
@@ -16,14 +16,11 @@ export function getFrontMenus() {
|
|||||||
* @param {Object} frontMenuData 前端导航数据
|
* @param {Object} frontMenuData 前端导航数据
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
export function createFrontMenu(formData, options = {}) {
|
export function createFrontMenu(formData) {
|
||||||
return request({
|
return request({
|
||||||
url: "/backend/createfrontmenu",
|
url: "/backend/frontmenus",
|
||||||
method: "post",
|
method: "post",
|
||||||
data: formData,
|
data: formData,
|
||||||
headers: {
|
|
||||||
"Content-Type": "multipart/form-data"
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +32,7 @@ export function createFrontMenu(formData, options = {}) {
|
|||||||
*/
|
*/
|
||||||
export function editFrontMenu(id, frontMenuData) {
|
export function editFrontMenu(id, frontMenuData) {
|
||||||
return request({
|
return request({
|
||||||
url: `/backend/editfrontmenu/${id}`,
|
url: `/backend/frontmenus/${id}`,
|
||||||
method: "post",
|
method: "post",
|
||||||
data: frontMenuData,
|
data: frontMenuData,
|
||||||
});
|
});
|
||||||
@@ -48,7 +45,7 @@ export function editFrontMenu(id, frontMenuData) {
|
|||||||
*/
|
*/
|
||||||
export function deleteFrontMenu(id) {
|
export function deleteFrontMenu(id) {
|
||||||
return request({
|
return request({
|
||||||
url: `/backend/deletefrontmenu/${id}`,
|
url: `/backend/frontmenus/${id}`,
|
||||||
method: "delete",
|
method: "delete",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 错误提示 -->
|
<!-- 错误提示 -->
|
||||||
<div v-else-if="hasError" class="error-container">
|
<div v-else-if="hasError && list.length === 0" class="error-container">
|
||||||
<el-icon class="error-icon"><Warning /></el-icon>
|
<el-icon class="error-icon"><Warning /></el-icon>
|
||||||
<div class="error-text">{{ errorMsg }}</div>
|
<div class="error-text">{{ errorMsg }}</div>
|
||||||
<el-button size="small" @click="fetchMenus">重新加载</el-button>
|
<el-button size="small" @click="fetchMenus">重新加载</el-button>
|
||||||
@@ -289,13 +289,40 @@ const processMenus = (menus) => {
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const list = computed(() => {
|
const fixedCmsMenu = {
|
||||||
const menuData = menuStore.menus;
|
id: -200,
|
||||||
if (!menuData || menuData.length === 0) {
|
path: "/apps/cms",
|
||||||
return [];
|
title: "文章中心",
|
||||||
}
|
icon: "Document",
|
||||||
|
order: -200,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: -201,
|
||||||
|
path: "/apps/cms/articles",
|
||||||
|
title: "文章管理",
|
||||||
|
icon: "Document",
|
||||||
|
order: 1,
|
||||||
|
children: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: -202,
|
||||||
|
path: "/apps/cms/articles/category",
|
||||||
|
title: "文章分类",
|
||||||
|
icon: "Folder",
|
||||||
|
order: 2,
|
||||||
|
children: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
const allMenus = processMenus(menuData);
|
const mergeFixedCmsMenu = (menus) => {
|
||||||
|
const hasCmsMenu = menus.some((menu) => menu.path === fixedCmsMenu.path);
|
||||||
|
return hasCmsMenu ? menus : [fixedCmsMenu, ...menus];
|
||||||
|
};
|
||||||
|
|
||||||
|
const list = computed(() => {
|
||||||
|
const menuData = menuStore.menus || [];
|
||||||
|
const allMenus = processMenus(mergeFixedCmsMenu(menuData));
|
||||||
|
|
||||||
const sortMenusRecursively = (menus) => {
|
const sortMenusRecursively = (menus) => {
|
||||||
menus.forEach((menu) => {
|
menus.forEach((menu) => {
|
||||||
|
|||||||
@@ -3,6 +3,19 @@ import { convertMenusToRoutes } from "./dynamicRoutes";
|
|||||||
|
|
||||||
// 静态子路由:需要在 Main 框架内显示的页面
|
// 静态子路由:需要在 Main 框架内显示的页面
|
||||||
const staticMainChildren = [
|
const staticMainChildren = [
|
||||||
|
// CMS 文章中心是系统内置功能,不依赖数据库菜单配置。
|
||||||
|
{
|
||||||
|
path: "/apps/cms/articles",
|
||||||
|
name: "CmsArticles",
|
||||||
|
component: () => import("@/views/apps/cms/articles/index.vue"),
|
||||||
|
meta: { requiresAuth: true, title: "文章管理", modulePath: "/apps/cms" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/apps/cms/articles/category",
|
||||||
|
name: "CmsArticleCategories",
|
||||||
|
component: () => import("@/views/apps/cms/articles/category.vue"),
|
||||||
|
meta: { requiresAuth: true, title: "文章分类", modulePath: "/apps/cms" }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/user/userProfile",
|
path: "/user/userProfile",
|
||||||
name: "userProfile",
|
name: "userProfile",
|
||||||
@@ -90,6 +103,8 @@ export async function loadAndAddDynamicRoutes() {
|
|||||||
routesLoadingPromise = null;
|
routesLoadingPromise = null;
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
} else {
|
} else {
|
||||||
|
// 即使权限菜单为空,也要注册内置文章中心路由。
|
||||||
|
addDynamicRoutes([]);
|
||||||
dynamicRoutesAdded = true;
|
dynamicRoutesAdded = true;
|
||||||
routesLoadingPromise = null;
|
routesLoadingPromise = null;
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
|||||||
@@ -156,6 +156,11 @@ function toggleExpand(category) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleDelete(category) {
|
function handleDelete(category) {
|
||||||
|
if (Number(category.tid) === 0 || category.is_global === true) {
|
||||||
|
ElMessage.warning("全局分类不可删除");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ElMessageBox.confirm(`确定要删除分类\"${category.label}\"吗?`, "提示", {
|
ElMessageBox.confirm(`确定要删除分类\"${category.label}\"吗?`, "提示", {
|
||||||
confirmButtonText: "确定",
|
confirmButtonText: "确定",
|
||||||
cancelButtonText: "取消",
|
cancelButtonText: "取消",
|
||||||
|
|||||||
@@ -53,9 +53,18 @@
|
|||||||
<el-button type="text" title="添加子分类" @click.stop="$emit('add-child', item)">
|
<el-button type="text" title="添加子分类" @click.stop="$emit('add-child', item)">
|
||||||
<el-icon><Plus /></el-icon>
|
<el-icon><Plus /></el-icon>
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="text" class="danger" title="删除" @click.stop="$emit('delete', item)">
|
<el-button
|
||||||
|
v-if="item.tid !== 0"
|
||||||
|
type="text"
|
||||||
|
class="danger"
|
||||||
|
title="删除"
|
||||||
|
@click.stop="$emit('delete', item)"
|
||||||
|
>
|
||||||
<el-icon><Delete /></el-icon>
|
<el-icon><Delete /></el-icon>
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-tag v-else type="info" size="small" title="全局分类不可删除">
|
||||||
|
全局
|
||||||
|
</el-tag>
|
||||||
</el-button-group>
|
</el-button-group>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,18 +2,18 @@ import service from '@/utils/request'
|
|||||||
|
|
||||||
// 获取前端导航
|
// 获取前端导航
|
||||||
export const getHeadMenu = async () => {
|
export const getHeadMenu = async () => {
|
||||||
const response = await service.get('index/headmenu')
|
const response = await service.get('/api/index/headmenu')
|
||||||
return response.data
|
return response.data
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据路径获取单页内容
|
// 根据路径获取单页内容
|
||||||
export const getOnePageByPath = async (path: string) => {
|
export const getOnePageByPath = async (path: string) => {
|
||||||
const response = await service.get(`index/onepage/${encodeURIComponent(path)}`)
|
const response = await service.get(`/api/index/onepage/${encodeURIComponent(path)}`)
|
||||||
return response.data
|
return response.data
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取前端底部数据
|
// 获取前端底部数据
|
||||||
export const getFooterData = async () => {
|
export const getFooterData = async () => {
|
||||||
const response = await service.get('index/footerdata')
|
const response = await service.get('/api/index/footerdata')
|
||||||
return response.data
|
return response.data
|
||||||
}
|
}
|
||||||
@@ -1,52 +1,41 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router';
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import { constantRoute, registerDynamicRoutes } from './routes';
|
import { getHeadMenu } from '@/api/index'
|
||||||
|
import { constantRoute, registerDynamicRoutes } from './routes'
|
||||||
|
|
||||||
// 先创建路由,但不立即匹配
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
routes: constantRoute,
|
routes: constantRoute,
|
||||||
// 延迟路由匹配,等待动态路由注册完成
|
|
||||||
strict: false,
|
strict: false,
|
||||||
});
|
})
|
||||||
|
|
||||||
// 在应用启动时加载菜单并注册路由
|
let routesLoaded = false
|
||||||
let routesLoaded = false;
|
let routesLoadingPromise: Promise<void> | null = null
|
||||||
let routesLoadingPromise: Promise<void> | null = null;
|
|
||||||
|
|
||||||
|
// 固定路由先可用,再尝试加载后台菜单补充自定义页面。
|
||||||
export async function loadAndRegisterRoutes() {
|
export async function loadAndRegisterRoutes() {
|
||||||
// 如果已经加载过,直接返回
|
if (routesLoaded) return
|
||||||
if (routesLoaded) {
|
if (routesLoadingPromise) return routesLoadingPromise
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果正在加载,等待加载完成
|
routesLoadingPromise = getHeadMenu()
|
||||||
if (routesLoadingPromise) {
|
.then((response) => {
|
||||||
return routesLoadingPromise;
|
if (response?.code === 200 && Array.isArray(response.data)) {
|
||||||
}
|
registerDynamicRoutes(router, response.data)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
// 后台菜单不可用时不影响固定文章路由和官网页面。
|
||||||
|
console.error('加载前端菜单失败:', error)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
routesLoaded = true
|
||||||
|
})
|
||||||
|
|
||||||
return routesLoadingPromise;
|
return routesLoadingPromise
|
||||||
}
|
}
|
||||||
|
|
||||||
// 路由守卫:确保路由已加载
|
router.beforeEach(async (_to, _from, next) => {
|
||||||
router.beforeEach(async (to, _from, next) => {
|
await loadAndRegisterRoutes()
|
||||||
// 如果路由还未加载,先加载路由
|
next()
|
||||||
if (!routesLoaded) {
|
})
|
||||||
|
|
||||||
// 等待路由加载完成
|
|
||||||
await routesLoadingPromise;
|
|
||||||
|
|
||||||
// 路由加载完成后,如果当前路径不匹配,尝试重新匹配
|
|
||||||
const matched = router.resolve(to.path).matched;
|
|
||||||
if (matched.length === 0 && to.path !== '/404') {
|
|
||||||
// 路由已加载但当前路径不匹配,可能是通配符路由
|
|
||||||
// 继续导航,让通配符路由处理
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 路由已加载,继续导航
|
export default router
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
|
||||||
|
|||||||
+99
-104
@@ -1,124 +1,119 @@
|
|||||||
import { createComponentLoader } from '@/utils/pathResolver'
|
import { createComponentLoader } from '@/utils/pathResolver'
|
||||||
|
|
||||||
// 对外暴漏配置路由,常量路由
|
// 对外暴露配置路由。核心业务路由必须固定存在,不依赖后台菜单配置。
|
||||||
export const constantRoute = [
|
export const constantRoute = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
component: () => import('@/views/home/index.vue'),
|
component: () => import('@/views/home/index.vue'),
|
||||||
name: '首页',
|
name: '首页',
|
||||||
meta: {
|
meta: { title: '首页', hidden: false },
|
||||||
title: '首页',
|
},
|
||||||
hidden: false,
|
{
|
||||||
},
|
path: '/solution',
|
||||||
},
|
component: () => import('@/views/solution/index.vue'),
|
||||||
{
|
name: '解决方案',
|
||||||
path: '/404',
|
meta: { title: '解决方案', hidden: false },
|
||||||
component: () => import('@/views/404/index.vue'),
|
},
|
||||||
name: '404',
|
{
|
||||||
meta: {
|
path: '/solution/solution',
|
||||||
title: '404',
|
component: () => import('@/views/solutions/solution/index.vue'),
|
||||||
hidden: true,
|
name: '行业解决方案',
|
||||||
icon: 'DocumentDelete',
|
meta: { title: '行业解决方案', hidden: true },
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
]
|
path: '/solution/successCases',
|
||||||
|
component: () => import('@/views/solutions/successCases/index.vue'),
|
||||||
|
name: '成功案例',
|
||||||
|
meta: { title: '成功案例', hidden: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/newsCenter',
|
||||||
|
component: () => import('@/views/newsCenter/news/index.vue'),
|
||||||
|
name: '新闻中心',
|
||||||
|
meta: { title: '新闻中心', hidden: false },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/newsCenter/news',
|
||||||
|
component: () => import('@/views/newsCenter/news/index.vue'),
|
||||||
|
name: '站点新闻',
|
||||||
|
meta: { title: '站点新闻', hidden: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/newsCenter/announcement',
|
||||||
|
component: () => import('@/views/newsCenter/announcement/index.vue'),
|
||||||
|
name: '站点公告',
|
||||||
|
meta: { title: '站点公告', hidden: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/newsCenter/technologyCenter',
|
||||||
|
component: () => import('@/views/newsCenter/technologyCenter/index.vue'),
|
||||||
|
name: '技术中心',
|
||||||
|
meta: { title: '技术中心', hidden: true },
|
||||||
|
},
|
||||||
|
// 文章详情路由固定注册,文章发布后无需先在前台菜单中配置路由。
|
||||||
|
{
|
||||||
|
path: '/newsCenter/companyNews/detail/:id',
|
||||||
|
component: () => import('@/views/components/articleDetail.vue'),
|
||||||
|
name: '公司新闻详情',
|
||||||
|
meta: { title: '公司新闻详情', hidden: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/newsCenter/kingdeeNews/detail/:id',
|
||||||
|
component: () => import('@/views/components/articleDetail.vue'),
|
||||||
|
name: '金蝶新闻详情',
|
||||||
|
meta: { title: '金蝶新闻详情', hidden: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/404',
|
||||||
|
component: () => import('@/views/404/index.vue'),
|
||||||
|
name: '404',
|
||||||
|
meta: { title: '404', hidden: true, icon: 'DocumentDelete' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/:pathMatch(.*)*',
|
||||||
|
component: () => import('@/views/onepage/index.vue'),
|
||||||
|
name: 'OnePage',
|
||||||
|
meta: { title: '单页', hidden: true },
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
// 动态路由注册函数
|
// 动态路由注册函数:后台菜单只负责补充自定义页面,不覆盖固定业务路由。
|
||||||
export function registerDynamicRoutes(router: any, menus: any[]) {
|
export function registerDynamicRoutes(router: any, menus: any[]) {
|
||||||
if (!menus || menus.length === 0) return
|
if (!menus || menus.length === 0) return
|
||||||
|
|
||||||
// 统一处理菜单路径大小写
|
const normalizeMenuPaths = (items: any[]): any[] => items.map((menu) => ({
|
||||||
const normalizeMenuPaths = (menus: any[]) => {
|
...menu,
|
||||||
return menus.map(menu => {
|
path: menu.path?.replace(/\/newscenter\//g, '/newsCenter/'),
|
||||||
const normalizedMenu = { ...menu }
|
children: menu.children?.length ? normalizeMenuPaths(menu.children) : menu.children,
|
||||||
// 将路径中的 /newscenter/ 替换为 /newsCenter/
|
}))
|
||||||
if (normalizedMenu.path) {
|
|
||||||
normalizedMenu.path = normalizedMenu.path.replace(/\/newscenter\//g, '/newsCenter/')
|
|
||||||
}
|
|
||||||
// 递归处理子菜单
|
|
||||||
if (normalizedMenu.children && normalizedMenu.children.length > 0) {
|
|
||||||
normalizedMenu.children = normalizeMenuPaths(normalizedMenu.children)
|
|
||||||
}
|
|
||||||
return normalizedMenu
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 先规范化菜单路径
|
|
||||||
const normalizedMenus = normalizeMenuPaths(menus)
|
|
||||||
|
|
||||||
// 收集所有需要注册的路由
|
|
||||||
const routesToAdd: any[] = []
|
const routesToAdd: any[] = []
|
||||||
|
|
||||||
// 递归处理菜单,收集路由
|
|
||||||
const processMenu = (menu: any) => {
|
const processMenu = (menu: any) => {
|
||||||
// type 2: 页面 - 根据 component_path 加载组件
|
|
||||||
if (menu.type === 2 && menu.path && menu.component_path) {
|
if (menu.type === 2 && menu.path && menu.component_path) {
|
||||||
const path = menu.path
|
const path = menu.path
|
||||||
// 检查路由是否已存在
|
if (!router.getRoutes().some((route: any) => route.path === path)) {
|
||||||
const existingRoute = router.getRoutes().find((r: any) => r.path === path)
|
routesToAdd.push({
|
||||||
if (!existingRoute) {
|
path,
|
||||||
try {
|
name: `menu_${menu.id}`,
|
||||||
// 使用 pathResolver 来解析 component_path
|
component: createComponentLoader(menu.component_path),
|
||||||
// component_path 可能的格式:
|
meta: { title: menu.title, menuId: menu.id, menuType: menu.type },
|
||||||
// 1. /views/newsCenter/companyNews/index.vue
|
})
|
||||||
// 2. views/newsCenter/companyNews/index.vue
|
|
||||||
// 3. newsCenter/companyNews/index.vue
|
|
||||||
const componentLoader = createComponentLoader(menu.component_path)
|
|
||||||
|
|
||||||
if (!componentLoader) {
|
|
||||||
console.error(`Failed to create component loader for: ${menu.component_path}`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
routesToAdd.push({
|
|
||||||
path: path,
|
|
||||||
name: `menu_${menu.id}`,
|
|
||||||
component: componentLoader,
|
|
||||||
meta: {
|
|
||||||
title: menu.title,
|
|
||||||
menuId: menu.id,
|
|
||||||
menuType: menu.type,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to prepare route for path ${path}, component_path: ${menu.component_path}`, error)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// type 4: 单页 - 使用通配符路由处理,不需要单独注册
|
menu.children?.forEach(processMenu)
|
||||||
|
|
||||||
// 递归处理子菜单
|
|
||||||
if (menu.children && menu.children.length > 0) {
|
|
||||||
menu.children.forEach((child: any) => processMenu(child))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理所有菜单
|
normalizeMenuPaths(menus).forEach(processMenu)
|
||||||
normalizedMenus.forEach((menu) => processMenu(menu))
|
|
||||||
|
|
||||||
// 先移除通配符路由(如果存在),以便重新添加
|
// 通配符路由必须最后注册,否则会抢先匹配后台新增的自定义页面。
|
||||||
const catchAllRoute = router.getRoutes().find((r: any) => r.path === '/:pathMatch(.*)*')
|
const catchAll = router.getRoutes().find((route: any) => route.name === 'OnePage')
|
||||||
if (catchAllRoute) {
|
if (catchAll) router.removeRoute('OnePage')
|
||||||
router.removeRoute('OnePage')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册 type 2 的路由(必须在通配符路由之前)
|
routesToAdd.forEach((route) => router.addRoute(route))
|
||||||
routesToAdd.forEach((route) => {
|
router.addRoute({
|
||||||
router.addRoute(route)
|
path: '/:pathMatch(.*)*',
|
||||||
|
component: () => import('@/views/onepage/index.vue'),
|
||||||
|
name: 'OnePage',
|
||||||
|
meta: { title: '单页', hidden: true },
|
||||||
})
|
})
|
||||||
|
|
||||||
// 最后添加通配符路由(用于单页和其他未匹配的路径)
|
|
||||||
const catchAllExists = router.getRoutes().find((r: any) => r.path === '/:pathMatch(.*)*')
|
|
||||||
if (!catchAllExists) {
|
|
||||||
router.addRoute({
|
|
||||||
path: '/:pathMatch(.*)*',
|
|
||||||
component: () => import('@/views/onepage/index.vue'),
|
|
||||||
name: 'OnePage',
|
|
||||||
meta: {
|
|
||||||
title: '单页',
|
|
||||||
hidden: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2,13 +2,17 @@ import axios from 'axios'
|
|||||||
|
|
||||||
// 创建axios实例
|
// 创建axios实例
|
||||||
const service = axios.create({
|
const service = axios.create({
|
||||||
baseURL: import.meta.env.VITE_API_BASE_URL,
|
baseURL: '/',
|
||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 请求拦截器
|
// 请求拦截器
|
||||||
service.interceptors.request.use(
|
service.interceptors.request.use(
|
||||||
(config) => {
|
(config) => {
|
||||||
|
const devTenantId = sessionStorage.getItem('dev_tenant_id')
|
||||||
|
if (devTenantId) {
|
||||||
|
config.headers['X-Tenant-ID'] = encodeURIComponent(devTenantId)
|
||||||
|
}
|
||||||
return config
|
return config
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
@@ -16,10 +20,10 @@ service.interceptors.request.use(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// 响应拦截器 - 返回 response.data
|
// 响应拦截器 - 保留完整 axios 响应,由 api 层统一取 response.data(即 {code, data, msg})
|
||||||
service.interceptors.response.use(
|
service.interceptors.response.use(
|
||||||
(response) => {
|
(response) => {
|
||||||
return response.data
|
return response
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
console.error('API请求错误:', error)
|
console.error('API请求错误:', error)
|
||||||
|
|||||||
@@ -301,12 +301,36 @@
|
|||||||
<div class="search-icon" @click="showSearch = true">
|
<div class="search-icon" @click="showSearch = true">
|
||||||
<i class="fa-solid fa-magnifying-glass"></i>
|
<i class="fa-solid fa-magnifying-glass"></i>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 开发环境租户切换按钮 -->
|
||||||
|
<div v-if="isDev" class="tenant-switcher" @click="showTenantModal = true">
|
||||||
|
<i class="fa-solid fa-gear"></i>
|
||||||
|
</div>
|
||||||
<div class="language-switcher" @click="showLanguage = true">
|
<div class="language-switcher" @click="showLanguage = true">
|
||||||
<span>中文</span>
|
<span>中文</span>
|
||||||
<i class="fa-solid fa-caret-down"></i>
|
<i class="fa-solid fa-caret-down"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 租户切换弹窗 -->
|
||||||
|
<div
|
||||||
|
class="modal"
|
||||||
|
:class="{ active: showTenantModal }"
|
||||||
|
@click.self="showTenantModal = false"
|
||||||
|
>
|
||||||
|
<div class="modal-content">
|
||||||
|
<h3>切换测试租户</h3>
|
||||||
|
<div class="search-box">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="请输入租户ID..."
|
||||||
|
v-model="devTenantId"
|
||||||
|
/>
|
||||||
|
<button @click="saveTenant">保存</button>
|
||||||
|
</div>
|
||||||
|
<button class="close-btn" @click="showTenantModal = false">×</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 搜索弹窗 -->
|
<!-- 搜索弹窗 -->
|
||||||
<div
|
<div
|
||||||
class="modal"
|
class="modal"
|
||||||
@@ -390,33 +414,62 @@ interface MenuItem {
|
|||||||
// 菜单数据
|
// 菜单数据
|
||||||
const menuItems = ref<MenuItem[]>([])
|
const menuItems = ref<MenuItem[]>([])
|
||||||
|
|
||||||
// 统一处理菜单路径大小写
|
// 统一处理菜单路径大小写,并递归处理子菜单。
|
||||||
const normalizeMenuPaths = (menus: MenuItem[]): MenuItem[] => {
|
const normalizeMenuPaths = (menus: MenuItem[]): MenuItem[] => {
|
||||||
return menus.map((menu: MenuItem) => {
|
return menus.map((menu) => ({
|
||||||
const normalizedMenu = { ...menu }
|
...menu,
|
||||||
// 将路径中的 /newscenter/ 替换为 /newsCenter/
|
path: menu.path?.replace(/\/newscenter\//g, '/newsCenter/'),
|
||||||
if (normalizedMenu.path) {
|
children: menu.children?.length ? normalizeMenuPaths(menu.children) : menu.children,
|
||||||
normalizedMenu.path = normalizedMenu.path.replace(/\/newscenter\//g, '/newsCenter/')
|
}))
|
||||||
}
|
}
|
||||||
// 递归处理子菜单
|
|
||||||
if (normalizedMenu.children && normalizedMenu.children.length > 0) {
|
// 后台菜单可配置,但文章中心是官网核心功能,必须始终存在。
|
||||||
normalizedMenu.children = normalizeMenuPaths(normalizedMenu.children)
|
const fixedNewsMenu: MenuChild = {
|
||||||
}
|
id: -101,
|
||||||
return normalizedMenu
|
title: '新闻中心',
|
||||||
})
|
path: '/newsCenter/news',
|
||||||
|
type: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixedArticleMenu: MenuItem = {
|
||||||
|
id: -100,
|
||||||
|
title: '文章中心',
|
||||||
|
path: '/newsCenter',
|
||||||
|
type: 1,
|
||||||
|
children: [fixedNewsMenu],
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergeMenuItems = (menus: MenuItem[]): MenuItem[] => {
|
||||||
|
const normalizedMenus = normalizeMenuPaths(menus)
|
||||||
|
const articleMenu = normalizedMenus.find(
|
||||||
|
(menu) => menu.path === fixedArticleMenu.path || menu.title === fixedArticleMenu.title,
|
||||||
|
)
|
||||||
|
if (!articleMenu) return [fixedArticleMenu, ...normalizedMenus]
|
||||||
|
|
||||||
|
const hasNewsCenter = articleMenu.children?.some(
|
||||||
|
(child) => child.path === '/newsCenter/news' || child.title === '新闻中心',
|
||||||
|
)
|
||||||
|
if (!hasNewsCenter) {
|
||||||
|
articleMenu.children = [
|
||||||
|
...(articleMenu.children || []),
|
||||||
|
fixedNewsMenu,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return normalizedMenus
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载菜单数据
|
// 加载菜单数据
|
||||||
const loadMenu = async () => {
|
const loadMenu = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await getHeadMenu()
|
const response = await getHeadMenu()
|
||||||
if (response.code === 200) {
|
if (response.code === 200 && Array.isArray(response.data)) {
|
||||||
menuItems.value = normalizeMenuPaths(response.data)
|
menuItems.value = mergeMenuItems(response.data)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载菜单失败:', error)
|
console.error('加载菜单失败:', error)
|
||||||
menuItems.value = []
|
|
||||||
}
|
}
|
||||||
|
menuItems.value = [fixedArticleMenu]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 拼接接口地址(用于 logo 等静态资源)
|
// 拼接接口地址(用于 logo 等静态资源)
|
||||||
@@ -436,9 +489,18 @@ const activeMenu = ref<number | null>(null)
|
|||||||
const activeCategory = ref<number>(0)
|
const activeCategory = ref<number>(0)
|
||||||
const showSearch = ref(false)
|
const showSearch = ref(false)
|
||||||
const showLanguage = ref(false)
|
const showLanguage = ref(false)
|
||||||
|
const showTenantModal = ref(false)
|
||||||
|
const devTenantId = ref(sessionStorage.getItem('dev_tenant_id') || '')
|
||||||
|
const isDev = import.meta.env.DEV
|
||||||
const searchQuery = ref('')
|
const searchQuery = ref('')
|
||||||
const currentLanguage = ref('zh')
|
const currentLanguage = ref('zh')
|
||||||
const isScrolled = ref(false)
|
const isScrolled = ref(false)
|
||||||
|
|
||||||
|
const saveTenant = () => {
|
||||||
|
sessionStorage.setItem('dev_tenant_id', devTenantId.value)
|
||||||
|
showTenantModal.value = false
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
const footerData = ref<any | null>(null)
|
const footerData = ref<any | null>(null)
|
||||||
let closeTimer: ReturnType<typeof setTimeout> | null = null
|
let closeTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
|
|
||||||
@@ -834,6 +896,8 @@ onUnmounted(() => {
|
|||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
color: #333; /* 显式设置字体颜色为深色 */
|
||||||
|
background-color: #fff; /* 显式设置背景为白色 */
|
||||||
transition: border-color 0.3s ease;
|
transition: border-color 0.3s ease;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ const formatDate = (date: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const goToDetail = (id: number) => {
|
const goToDetail = (id: number) => {
|
||||||
router.push(`/newscenter/companyNews/detail/${id}`)
|
router.push(`/newsCenter/companyNews/detail/${id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载更多数据
|
// 加载更多数据
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
|
|||||||
target: env.VITE_API_BASE_URL, // 后端 API 服务器地址
|
target: env.VITE_API_BASE_URL, // 后端 API 服务器地址
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/api/, '')
|
rewrite: (path) => path.replace(/^\/api/, '')
|
||||||
|
},
|
||||||
|
'/index': {
|
||||||
|
target: env.VITE_API_BASE_URL,
|
||||||
|
changeOrigin: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 配置静态资源服务
|
// 配置静态资源服务
|
||||||
@@ -91,4 +95,4 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
|
|||||||
exclude: []
|
exclude: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
+1087
-1037
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,154 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"server/models"
|
||||||
|
"server/pkg/jwtutil"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
beego "github.com/beego/beego/v2/server/web"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BackendMenuFrontController struct {
|
||||||
|
beego.Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *BackendMenuFrontController) checkAuth() (uint64, bool) {
|
||||||
|
authHeader := c.Ctx.Request.Header.Get("Authorization")
|
||||||
|
if authHeader == "" {
|
||||||
|
c.Data["json"] = map[string]interface{}{"code": 401, "msg": "未提供认证信息"}
|
||||||
|
_ = c.ServeJSON()
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
authParts := strings.SplitN(authHeader, " ", 2)
|
||||||
|
if len(authParts) != 2 || authParts[0] != "Bearer" {
|
||||||
|
c.Data["json"] = map[string]interface{}{"code": 401, "msg": "认证信息格式错误"}
|
||||||
|
_ = c.ServeJSON()
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
claims, err := jwtutil.ParseToken(authParts[1])
|
||||||
|
if err != nil {
|
||||||
|
c.Data["json"] = map[string]interface{}{"code": 401, "msg": "token无效"}
|
||||||
|
_ = c.ServeJSON()
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return uint64(claims.TenantId), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *BackendMenuFrontController) List() {
|
||||||
|
tid, ok := c.checkAuth()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var menus []models.BackendMenuFront
|
||||||
|
_, err := models.Orm.QueryTable("yz_backend_menu_front").
|
||||||
|
Filter("tenant_id", tid).
|
||||||
|
Filter("delete_time__isnull", true).
|
||||||
|
OrderBy("sort").
|
||||||
|
All(&menus)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "查询失败"}
|
||||||
|
} else {
|
||||||
|
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": menus}
|
||||||
|
}
|
||||||
|
_ = c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *BackendMenuFrontController) Create() {
|
||||||
|
tid, ok := c.checkAuth()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var menu models.BackendMenuFront
|
||||||
|
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &menu); err != nil {
|
||||||
|
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"}
|
||||||
|
_ = c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.TenantID = tid
|
||||||
|
menu.CreateTime = nil // 由orm自动处理
|
||||||
|
_, err := models.Orm.Insert(&menu)
|
||||||
|
if err != nil {
|
||||||
|
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "创建失败"}
|
||||||
|
} else {
|
||||||
|
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "创建成功"}
|
||||||
|
}
|
||||||
|
_ = c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *BackendMenuFrontController) Update() {
|
||||||
|
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"}
|
||||||
|
_ = c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tid, ok := c.checkAuth()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var menu models.BackendMenuFront
|
||||||
|
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &menu); err != nil {
|
||||||
|
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"}
|
||||||
|
_ = c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
num, err := models.Orm.QueryTable("yz_backend_menu_front").
|
||||||
|
Filter("id", id).
|
||||||
|
Filter("tenant_id", tid).
|
||||||
|
Update(map[string]interface{}{
|
||||||
|
"pid": menu.Pid,
|
||||||
|
"title": menu.Title,
|
||||||
|
"path": menu.Path,
|
||||||
|
"component_path": menu.ComponentPath,
|
||||||
|
"icon": menu.Icon,
|
||||||
|
"sort": menu.Sort,
|
||||||
|
"status": menu.Status,
|
||||||
|
"is_visible": menu.IsVisible,
|
||||||
|
"type": menu.Type,
|
||||||
|
"permission": menu.Permission,
|
||||||
|
"update_time": time.Now(),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil || num == 0 {
|
||||||
|
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "更新失败"}
|
||||||
|
} else {
|
||||||
|
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"}
|
||||||
|
}
|
||||||
|
_ = c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *BackendMenuFrontController) Delete() {
|
||||||
|
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"}
|
||||||
|
_ = c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tid, ok := c.checkAuth()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
num, err := models.Orm.QueryTable("yz_backend_menu_front").
|
||||||
|
Filter("id", id).
|
||||||
|
Filter("tenant_id", tid).
|
||||||
|
Update(map[string]interface{}{"delete_time": time.Now()})
|
||||||
|
|
||||||
|
if err != nil || num == 0 {
|
||||||
|
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "删除失败"}
|
||||||
|
} else {
|
||||||
|
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"}
|
||||||
|
}
|
||||||
|
_ = c.ServeJSON()
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -37,8 +39,54 @@ func (c *IndexPortalController) getTidByHost() uint64 {
|
|||||||
return 1 // 默认租户
|
return 1 // 默认租户
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHeadMenu GET /index/headmenu
|
// 获取后端菜单数据(支持开发环境租户切换)
|
||||||
func (c *IndexPortalController) GetHeadMenu() {
|
func (c *IndexPortalController) GetHeadMenu() {
|
||||||
|
// DEV 环境下支持通过 X-Tenant-ID 切换租户
|
||||||
|
runmode, _ := beego.AppConfig.String("runmode")
|
||||||
|
tenantName := c.Ctx.Request.Header.Get("X-Tenant-ID")
|
||||||
|
|
||||||
|
// 解码
|
||||||
|
if decoded, err := url.QueryUnescape(tenantName); err == nil {
|
||||||
|
tenantName = decoded
|
||||||
|
}
|
||||||
|
|
||||||
|
// 强制打印,确保你能从终端看到真实值
|
||||||
|
fmt.Printf("[DEBUG] runmode: %s, Decoded TenantName: %s\n", runmode, tenantName)
|
||||||
|
|
||||||
|
if runmode == "dev" && tenantName != "" {
|
||||||
|
// 查找租户 ID - 支持 name 或 short_name
|
||||||
|
var tenant models.SystemTenant
|
||||||
|
// 使用 OR 逻辑查询 name 或 short_name
|
||||||
|
cond := orm.NewCondition()
|
||||||
|
cond = cond.Or("tenant_name", tenantName).Or("tenant_short_name", tenantName)
|
||||||
|
|
||||||
|
err := models.Orm.QueryTable(new(models.SystemTenant)).
|
||||||
|
SetCond(cond).
|
||||||
|
One(&tenant)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("[DEBUG] QueryTenant Error: %v, Looking for: %s\n", err, tenantName)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("[DEBUG] Found Tenant ID: %d\n", tenant.ID)
|
||||||
|
// 查询该租户的自定义菜单
|
||||||
|
var menus []models.BackendMenuFront
|
||||||
|
_, err = models.Orm.QueryTable("yz_backend_menu_front").
|
||||||
|
Filter("tenant_id", tenant.ID).
|
||||||
|
Filter("delete_time__isnull", true).
|
||||||
|
OrderBy("sort").
|
||||||
|
All(&menus)
|
||||||
|
if err == nil && len(menus) > 0 {
|
||||||
|
// 转换数据格式供前端使用
|
||||||
|
menuData := buildFrontMenuTree(menus, 0)
|
||||||
|
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": menuData}
|
||||||
|
_ = c.ServeJSON()
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
fmt.Printf("[DEBUG] No menus found for tenant: %d\n", tenant.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 前台的静态导航菜单结构
|
// 前台的静态导航菜单结构
|
||||||
menuData := []map[string]interface{}{
|
menuData := []map[string]interface{}{
|
||||||
{
|
{
|
||||||
@@ -103,6 +151,27 @@ func (c *IndexPortalController) GetHeadMenu() {
|
|||||||
_ = c.ServeJSON()
|
_ = c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 辅助构建树结构
|
||||||
|
func buildFrontMenuTree(menus []models.BackendMenuFront, pid int64) []map[string]interface{} {
|
||||||
|
var tree []map[string]interface{}
|
||||||
|
for _, m := range menus {
|
||||||
|
if int64(m.Pid) == pid {
|
||||||
|
node := map[string]interface{}{
|
||||||
|
"id": m.ID,
|
||||||
|
"title": m.Title,
|
||||||
|
"path": m.Path,
|
||||||
|
"type": m.Type,
|
||||||
|
}
|
||||||
|
children := buildFrontMenuTree(menus, int64(m.ID))
|
||||||
|
if len(children) > 0 {
|
||||||
|
node["children"] = children
|
||||||
|
}
|
||||||
|
tree = append(tree, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tree
|
||||||
|
}
|
||||||
|
|
||||||
// GetFooterData GET /index/footerdata
|
// GetFooterData GET /index/footerdata
|
||||||
func (c *IndexPortalController) GetFooterData() {
|
func (c *IndexPortalController) GetFooterData() {
|
||||||
tid := c.getTidByHost()
|
tid := c.getTidByHost()
|
||||||
@@ -296,7 +365,7 @@ func (c *IndexPortalController) GetCompanyNewsDetail() {
|
|||||||
c.getArticleDetail()
|
c.getArticleDetail()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKingdeeNewsDetail GET /index/kingdeenews/detail/:id
|
// GetKingdeeNewsDetail GET /index/kingdeeNews/detail/:id
|
||||||
func (c *IndexPortalController) GetKingdeeNewsDetail() {
|
func (c *IndexPortalController) GetKingdeeNewsDetail() {
|
||||||
c.getArticleDetail()
|
c.getArticleDetail()
|
||||||
}
|
}
|
||||||
@@ -393,4 +462,4 @@ func (c *IndexPortalController) GetOnePageByPath() {
|
|||||||
"data": nil,
|
"data": nil,
|
||||||
}
|
}
|
||||||
_ = c.ServeJSON()
|
_ = c.ServeJSON()
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/beego/beego/v2/client/orm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BackendMenuFront struct {
|
||||||
|
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||||
|
TenantID uint64 `orm:"column(tenant_id)" json:"tenant_id"`
|
||||||
|
Pid int64 `orm:"column(pid)" json:"pid"`
|
||||||
|
Title string `orm:"column(title)" json:"title"`
|
||||||
|
Path string `orm:"column(path)" json:"path"`
|
||||||
|
ComponentPath string `orm:"column(component_path)" json:"component_path"`
|
||||||
|
Icon string `orm:"column(icon)" json:"icon"`
|
||||||
|
Sort int `orm:"column(sort)" json:"sort"`
|
||||||
|
Status int8 `orm:"column(status)" json:"status"`
|
||||||
|
IsVisible int8 `orm:"column(is_visible)" json:"is_visible"`
|
||||||
|
Type int8 `orm:"column(type)" json:"type"`
|
||||||
|
Permission string `orm:"column(permission)" json:"permission"`
|
||||||
|
CreateTime *time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||||
|
UpdateTime *time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"`
|
||||||
|
DeleteTime *time.Time `orm:"column(delete_time);null;type(datetime)" json:"delete_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BackendMenuFront) TableName() string {
|
||||||
|
return "yz_backend_menu_front"
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
orm.RegisterModel(new(BackendMenuFront))
|
||||||
|
}
|
||||||
+200
-159
@@ -1,159 +1,200 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/beego/beego/v2/client/orm"
|
"github.com/beego/beego/v2/client/orm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmsArticleCategory CMS 文章分类 yz_cms_article_category
|
// CmsArticleCategory CMS 文章分类 yz_cms_article_category
|
||||||
type CmsArticleCategory struct {
|
type CmsArticleCategory struct {
|
||||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||||
Tid uint64 `orm:"column(tid);default(0)" json:"tid"`
|
Tid uint64 `orm:"column(tid);default(0)" json:"tid"`
|
||||||
Cid uint64 `orm:"column(cid);default(0)" json:"cid"`
|
Cid uint64 `orm:"column(cid);default(0)" json:"cid"`
|
||||||
Name string `orm:"column(name);size(100)" json:"name"`
|
Name string `orm:"column(name);size(100)" json:"name"`
|
||||||
Image string `orm:"column(image);size(500);default()" json:"image"`
|
Image string `orm:"column(image);size(500);default()" json:"image"`
|
||||||
Desc string `orm:"column(desc);size(500);default()" json:"desc"`
|
Desc string `orm:"column(desc);size(500);default()" json:"desc"`
|
||||||
Sort int `orm:"column(sort);default(0)" json:"sort"`
|
Sort int `orm:"column(sort);default(0)" json:"sort"`
|
||||||
Status int8 `orm:"column(status);default(1)" json:"status"`
|
Status int8 `orm:"column(status);default(1)" json:"status"`
|
||||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||||
UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"`
|
UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"`
|
||||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *CmsArticleCategory) TableName() string {
|
func (m *CmsArticleCategory) TableName() string {
|
||||||
return "yz_cms_article_category"
|
return "yz_cms_article_category"
|
||||||
}
|
}
|
||||||
|
|
||||||
// CmsArticle CMS 文章 yz_cms_article
|
// CmsArticle CMS 文章 yz_cms_article
|
||||||
type CmsArticle struct {
|
type CmsArticle struct {
|
||||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||||
Tid uint64 `orm:"column(tid);default(0)" json:"tid"`
|
Tid uint64 `orm:"column(tid);default(0)" json:"tid"`
|
||||||
Title string `orm:"column(title);size(255)" json:"title"`
|
Title string `orm:"column(title);size(255)" json:"title"`
|
||||||
Author string `orm:"column(author);size(100);default()" json:"author"`
|
Author string `orm:"column(author);size(100);default()" json:"author"`
|
||||||
CateID uint64 `orm:"column(cate_id);default(0)" json:"cate_id"`
|
CateID uint64 `orm:"column(cate_id);default(0)" json:"cate_id"`
|
||||||
Content string `orm:"column(content);type(mediumtext);null" json:"content"`
|
Content string `orm:"column(content);type(mediumtext);null" json:"content"`
|
||||||
Desc string `orm:"column(desc);size(500);default()" json:"desc"`
|
Desc string `orm:"column(desc);size(500);default()" json:"desc"`
|
||||||
Image string `orm:"column(image);size(500);default()" json:"image"`
|
Image string `orm:"column(image);size(500);default()" json:"image"`
|
||||||
IsTrans int8 `orm:"column(is_trans);default(0)" json:"is_trans"`
|
IsTrans int8 `orm:"column(is_trans);default(0)" json:"is_trans"`
|
||||||
TransURL *string `orm:"column(transurl);size(500);null" json:"transurl"`
|
TransURL *string `orm:"column(transurl);size(500);null" json:"transurl"`
|
||||||
Status int8 `orm:"column(status);default(0)" json:"status"`
|
Status int8 `orm:"column(status);default(0)" json:"status"`
|
||||||
Top int8 `orm:"column(top);default(0)" json:"top"`
|
Top int8 `orm:"column(top);default(0)" json:"top"`
|
||||||
Recommend int8 `orm:"column(recommend);default(0)" json:"recommend"`
|
Recommend int8 `orm:"column(recommend);default(0)" json:"recommend"`
|
||||||
Views int `orm:"column(views);default(0)" json:"views"`
|
Views int `orm:"column(views);default(0)" json:"views"`
|
||||||
Likes int `orm:"column(likes);default(0)" json:"likes"`
|
Likes int `orm:"column(likes);default(0)" json:"likes"`
|
||||||
PublisherID *uint64 `orm:"column(publisher_id);null" json:"publisher_id"`
|
PublisherID *uint64 `orm:"column(publisher_id);null" json:"publisher_id"`
|
||||||
PublishTime *time.Time `orm:"column(publish_time);type(datetime);null" json:"publish_time"`
|
PublishTime *time.Time `orm:"column(publish_time);type(datetime);null" json:"publish_time"`
|
||||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||||
UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"`
|
UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"`
|
||||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *CmsArticle) TableName() string {
|
func (m *CmsArticle) TableName() string {
|
||||||
return "yz_cms_article"
|
return "yz_cms_article"
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmsArticleTablesOnce sync.Once
|
var cmsArticleTablesOnce sync.Once
|
||||||
|
|
||||||
// EnsureCmsArticleTables 首次使用时自动建表(若不存在)。
|
// EnsureCmsArticleTables 首次使用时自动建表(若不存在)。
|
||||||
func EnsureCmsArticleTables() error {
|
func EnsureCmsArticleTables() error {
|
||||||
var err error
|
var err error
|
||||||
cmsArticleTablesOnce.Do(func() {
|
cmsArticleTablesOnce.Do(func() {
|
||||||
_, err = Orm.Raw(`
|
_, err = Orm.Raw(`
|
||||||
CREATE TABLE IF NOT EXISTS yz_cms_article_category (
|
CREATE TABLE IF NOT EXISTS yz_cms_article_category (
|
||||||
id bigint unsigned NOT NULL AUTO_INCREMENT,
|
id bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||||
tid bigint unsigned NOT NULL DEFAULT 0,
|
tid bigint unsigned NOT NULL DEFAULT 0,
|
||||||
cid bigint unsigned NOT NULL DEFAULT 0,
|
cid bigint unsigned NOT NULL DEFAULT 0,
|
||||||
name varchar(100) NOT NULL DEFAULT '',
|
name varchar(100) NOT NULL DEFAULT '',
|
||||||
image varchar(500) NOT NULL DEFAULT '',
|
image varchar(500) NOT NULL DEFAULT '',
|
||||||
` + "`desc`" + ` varchar(500) NOT NULL DEFAULT '',
|
` + "`desc`" + ` varchar(500) NOT NULL DEFAULT '',
|
||||||
sort int NOT NULL DEFAULT 0,
|
sort int NOT NULL DEFAULT 0,
|
||||||
status tinyint NOT NULL DEFAULT 1,
|
status tinyint NOT NULL DEFAULT 1,
|
||||||
create_time datetime NOT NULL,
|
create_time datetime NOT NULL,
|
||||||
update_time datetime DEFAULT NULL,
|
update_time datetime DEFAULT NULL,
|
||||||
delete_time datetime DEFAULT NULL,
|
delete_time datetime DEFAULT NULL,
|
||||||
PRIMARY KEY (id),
|
PRIMARY KEY (id),
|
||||||
KEY idx_tid_cid (tid, cid)
|
KEY idx_tid_cid (tid, cid)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`).Exec()
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`).Exec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, err = Orm.Raw(`
|
_, err = Orm.Raw(`
|
||||||
CREATE TABLE IF NOT EXISTS yz_cms_article (
|
CREATE TABLE IF NOT EXISTS yz_cms_article (
|
||||||
id bigint unsigned NOT NULL AUTO_INCREMENT,
|
id bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||||
tid bigint unsigned NOT NULL DEFAULT 0,
|
tid bigint unsigned NOT NULL DEFAULT 0,
|
||||||
title varchar(255) NOT NULL DEFAULT '',
|
title varchar(255) NOT NULL DEFAULT '',
|
||||||
author varchar(100) NOT NULL DEFAULT '',
|
author varchar(100) NOT NULL DEFAULT '',
|
||||||
cate_id bigint unsigned NOT NULL DEFAULT 0,
|
cate_id bigint unsigned NOT NULL DEFAULT 0,
|
||||||
content mediumtext,
|
content mediumtext,
|
||||||
` + "`desc`" + ` varchar(500) NOT NULL DEFAULT '',
|
` + "`desc`" + ` varchar(500) NOT NULL DEFAULT '',
|
||||||
image varchar(500) NOT NULL DEFAULT '',
|
image varchar(500) NOT NULL DEFAULT '',
|
||||||
is_trans tinyint NOT NULL DEFAULT 0,
|
is_trans tinyint NOT NULL DEFAULT 0,
|
||||||
transurl varchar(500) DEFAULT NULL,
|
transurl varchar(500) DEFAULT NULL,
|
||||||
status tinyint NOT NULL DEFAULT 0,
|
status tinyint NOT NULL DEFAULT 0,
|
||||||
top tinyint NOT NULL DEFAULT 0,
|
top tinyint NOT NULL DEFAULT 0,
|
||||||
recommend tinyint NOT NULL DEFAULT 0,
|
recommend tinyint NOT NULL DEFAULT 0,
|
||||||
views int NOT NULL DEFAULT 0,
|
views int NOT NULL DEFAULT 0,
|
||||||
likes int NOT NULL DEFAULT 0,
|
likes int NOT NULL DEFAULT 0,
|
||||||
publisher_id bigint unsigned DEFAULT NULL,
|
publisher_id bigint unsigned DEFAULT NULL,
|
||||||
publish_time datetime DEFAULT NULL,
|
publish_time datetime DEFAULT NULL,
|
||||||
create_time datetime NOT NULL,
|
create_time datetime NOT NULL,
|
||||||
update_time datetime DEFAULT NULL,
|
update_time datetime DEFAULT NULL,
|
||||||
delete_time datetime DEFAULT NULL,
|
delete_time datetime DEFAULT NULL,
|
||||||
PRIMARY KEY (id),
|
PRIMARY KEY (id),
|
||||||
KEY idx_tid_status (tid, status),
|
KEY idx_tid_status (tid, status),
|
||||||
KEY idx_cate_id (cate_id)
|
KEY idx_cate_id (cate_id)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`).Exec()
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`).Exec()
|
||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func CmsCategoryNameMap(tid uint64, ids []uint64) map[uint64]string {
|
func EnsureCmsArticleDefaultCategories() error {
|
||||||
out := make(map[uint64]string)
|
// 只初始化两级全局分类:文章中心(顶级)和新闻中心(文章中心的子分类)。
|
||||||
if len(ids) == 0 {
|
// 不在启动时创建其它业务分类,后续分类由管理员按需新增。
|
||||||
return out
|
defaults := []struct {
|
||||||
}
|
name string
|
||||||
var rows []CmsArticleCategory
|
cid uint64
|
||||||
_, _ = Orm.QueryTable(new(CmsArticleCategory)).
|
sort int
|
||||||
Filter("tid", tid).
|
}{
|
||||||
Filter("id__in", ids).
|
{name: "文章中心", cid: 0, sort: 1},
|
||||||
Filter("delete_time__isnull", true).
|
{name: "新闻中心", cid: 1, sort: 2},
|
||||||
All(&rows, "ID", "Name")
|
}
|
||||||
for _, r := range rows {
|
|
||||||
out[r.ID] = r.Name
|
for _, item := range defaults {
|
||||||
}
|
count, err := Orm.QueryTable(new(CmsArticleCategory)).
|
||||||
return out
|
Filter("tid", 0).
|
||||||
}
|
Filter("name", item.name).
|
||||||
|
Filter("cid", item.cid).
|
||||||
func CmsFormatTime(t *time.Time) string {
|
Filter("delete_time__isnull", true).
|
||||||
if t == nil {
|
Count()
|
||||||
return ""
|
if err != nil {
|
||||||
}
|
return err
|
||||||
return t.Format("2006-01-02 15:04:05")
|
}
|
||||||
}
|
if count > 0 {
|
||||||
|
continue
|
||||||
func CmsSimilarArticles(tid uint64, title string, limit int) ([]orm.Params, error) {
|
}
|
||||||
if limit <= 0 {
|
now := time.Now()
|
||||||
limit = 5
|
_, err = Orm.Insert(&CmsArticleCategory{
|
||||||
}
|
Tid: 0,
|
||||||
var rows []CmsArticle
|
Cid: item.cid,
|
||||||
_, err := Orm.QueryTable(new(CmsArticle)).
|
Name: item.name,
|
||||||
Filter("tid", tid).
|
Sort: item.sort,
|
||||||
Filter("delete_time__isnull", true).
|
Status: 1,
|
||||||
Filter("title__icontains", title).
|
CreateTime: now,
|
||||||
Limit(limit).
|
})
|
||||||
All(&rows, "ID", "Title")
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return nil, err
|
}
|
||||||
}
|
}
|
||||||
out := make([]orm.Params, 0, len(rows))
|
return nil
|
||||||
for _, r := range rows {
|
}
|
||||||
out = append(out, orm.Params{
|
|
||||||
"id": r.ID,
|
func CmsCategoryNameMap(tid uint64, ids []uint64) map[uint64]string {
|
||||||
"title": r.Title,
|
out := make(map[uint64]string)
|
||||||
"similarity": 80,
|
if len(ids) == 0 {
|
||||||
})
|
return out
|
||||||
}
|
}
|
||||||
return out, nil
|
var rows []CmsArticleCategory
|
||||||
}
|
qs := Orm.QueryTable(new(CmsArticleCategory)).
|
||||||
|
Filter("id__in", ids).
|
||||||
|
Filter("delete_time__isnull", true)
|
||||||
|
cond := orm.NewCondition().Or("tid", 0).Or("tid", tid)
|
||||||
|
_, _ = qs.SetCond(cond).All(&rows, "ID", "Name")
|
||||||
|
for _, r := range rows {
|
||||||
|
out[r.ID] = r.Name
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func CmsFormatTime(t *time.Time) string {
|
||||||
|
if t == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return t.Format("2006-01-02 15:04:05")
|
||||||
|
}
|
||||||
|
|
||||||
|
func CmsSimilarArticles(tid uint64, title string, limit int) ([]orm.Params, error) {
|
||||||
|
if limit <= 0 {
|
||||||
|
limit = 5
|
||||||
|
}
|
||||||
|
var rows []CmsArticle
|
||||||
|
_, err := Orm.QueryTable(new(CmsArticle)).
|
||||||
|
Filter("tid", tid).
|
||||||
|
Filter("delete_time__isnull", true).
|
||||||
|
Filter("title__icontains", title).
|
||||||
|
Limit(limit).
|
||||||
|
All(&rows, "ID", "Title")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out := make([]orm.Params, 0, len(rows))
|
||||||
|
for _, r := range rows {
|
||||||
|
out = append(out, orm.Params{
|
||||||
|
"id": r.ID,
|
||||||
|
"title": r.Title,
|
||||||
|
"similarity": 80,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ func RegisterAuthRoutes() {
|
|||||||
|
|
||||||
// 菜单接口
|
// 菜单接口
|
||||||
beego.Router("/backend/menu/:id", &controllers.BackendMenuController{}, "get:GetBackendMenu")
|
beego.Router("/backend/menu/:id", &controllers.BackendMenuController{}, "get:GetBackendMenu")
|
||||||
beego.Router("/backend/allmenu", &controllers.BackendMenuController{}, "get:GetAllBackendMenus")
|
// 前端菜单接口
|
||||||
|
beego.Router("/backend/frontmenus", &controllers.BackendMenuFrontController{}, "get:List;post:Create")
|
||||||
|
beego.Router("/backend/frontmenus/:id", &controllers.BackendMenuFrontController{}, "post:Update;delete:Delete")
|
||||||
|
|
||||||
// 操作日志(yz_system_operation_log)
|
// 操作日志(yz_system_operation_log)
|
||||||
beego.Router("/backend/operationLogs", &controllers.BackendOperationLogController{}, "get:List")
|
beego.Router("/backend/operationLogs", &controllers.BackendOperationLogController{}, "get:List")
|
||||||
|
|||||||
Reference in New Issue
Block a user