feat:首页对接
This commit is contained in:
parent
d9c455d946
commit
3bd9e1fac3
35
src/api/modules/menu.ts
Normal file
35
src/api/modules/menu.ts
Normal file
@ -0,0 +1,35 @@
|
||||
// 菜单相关API
|
||||
import { ApiRequest } from '../request'
|
||||
import type { ApiResponse } from '../types'
|
||||
|
||||
// 菜单项接口定义
|
||||
export interface MenuItem {
|
||||
id: string
|
||||
name: string
|
||||
path: string
|
||||
type: string
|
||||
icon: string | null
|
||||
parentId: string | null
|
||||
sortOrder: number
|
||||
izVisible: number
|
||||
permissionKey: string | null
|
||||
}
|
||||
|
||||
// 菜单API类
|
||||
export class MenuApi {
|
||||
/**
|
||||
* 获取首页菜单
|
||||
*/
|
||||
static async getIndexMenus(): Promise<ApiResponse<MenuItem[]>> {
|
||||
return await ApiRequest.get('/aiol/aiolMenu/getIndexMenus')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取学生菜单
|
||||
*/
|
||||
static async getStudentMenus(): Promise<ApiResponse<MenuItem[]>> {
|
||||
return await ApiRequest.get('/aiol/aiolMenu/getStudentMenus')
|
||||
}
|
||||
}
|
||||
|
||||
export default MenuApi
|
186
src/components/layout/DynamicNavigation.vue
Normal file
186
src/components/layout/DynamicNavigation.vue
Normal file
@ -0,0 +1,186 @@
|
||||
<template>
|
||||
<div class="dynamic-navigation">
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="menuStore.loading" class="loading">
|
||||
<n-spin size="small" />
|
||||
<span>加载菜单中...</span>
|
||||
</div>
|
||||
|
||||
<!-- 错误状态 -->
|
||||
<div v-else-if="menuStore.error" class="error">
|
||||
<n-alert type="error" :title="menuStore.error" />
|
||||
<n-button @click="handleRetry" size="small" type="primary">
|
||||
重试
|
||||
</n-button>
|
||||
</div>
|
||||
|
||||
<!-- 菜单列表 -->
|
||||
<nav v-else class="nav-menu">
|
||||
<router-link
|
||||
v-for="menu in menuStore.navMenus"
|
||||
:key="menu.id"
|
||||
:to="menu.path"
|
||||
class="nav-item"
|
||||
:class="{ active: isCurrentRoute(menu.path) }"
|
||||
>
|
||||
<n-icon v-if="menu.icon" :component="getIcon(menu.icon)" />
|
||||
<span>{{ menu.name }}</span>
|
||||
</router-link>
|
||||
</nav>
|
||||
|
||||
<!-- 调试信息(开发环境) -->
|
||||
<div v-if="isDev" class="debug-info">
|
||||
<n-collapse>
|
||||
<n-collapse-item title="菜单调试信息" name="debug">
|
||||
<pre>{{ JSON.stringify(menuStore.indexMenus, null, 2) }}</pre>
|
||||
</n-collapse-item>
|
||||
</n-collapse>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import {
|
||||
NSpin,
|
||||
NAlert,
|
||||
NButton,
|
||||
NIcon,
|
||||
NCollapse,
|
||||
NCollapseItem
|
||||
} from 'naive-ui'
|
||||
import {
|
||||
HomeOutlined,
|
||||
BookOutlined,
|
||||
TeamOutlined,
|
||||
FileTextOutlined,
|
||||
CalendarOutlined,
|
||||
RobotOutlined
|
||||
} from '@vicons/antd'
|
||||
import { useMenuStore } from '@/stores/menu'
|
||||
|
||||
// 图标映射
|
||||
const iconMap: Record<string, any> = {
|
||||
home: HomeOutlined,
|
||||
book: BookOutlined,
|
||||
team: TeamOutlined,
|
||||
resource: FileTextOutlined,
|
||||
activity: CalendarOutlined,
|
||||
ai: RobotOutlined,
|
||||
}
|
||||
|
||||
const menuStore = useMenuStore()
|
||||
const route = useRoute()
|
||||
|
||||
// 是否为开发环境
|
||||
const isDev = computed(() => import.meta.env.DEV)
|
||||
|
||||
// 获取图标组件
|
||||
const getIcon = (iconName: string) => {
|
||||
return iconMap[iconName] || HomeOutlined
|
||||
}
|
||||
|
||||
// 检查是否为当前路由
|
||||
const isCurrentRoute = (path: string) => {
|
||||
return route.path === path
|
||||
}
|
||||
|
||||
// 重试加载菜单
|
||||
const handleRetry = async () => {
|
||||
await menuStore.fetchIndexMenus()
|
||||
}
|
||||
|
||||
// 组件挂载时加载菜单
|
||||
onMounted(async () => {
|
||||
if (menuStore.indexMenus.length === 0) {
|
||||
await menuStore.fetchIndexMenus()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dynamic-navigation {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 16px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.error {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
background-color: #f5f5f5;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.debug-info {
|
||||
margin-top: 16px;
|
||||
padding: 16px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.debug-info pre {
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.nav-menu {
|
||||
flex-direction: row;
|
||||
overflow-x: auto;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
flex-shrink: 0;
|
||||
min-width: 80px;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.nav-item span {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
106
src/stores/menu.ts
Normal file
106
src/stores/menu.ts
Normal file
@ -0,0 +1,106 @@
|
||||
// 菜单状态管理
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import type { MenuItem } from '@/api/modules/menu'
|
||||
import { MenuApi } from '@/api/modules/menu'
|
||||
import { generateNavMenus } from '@/utils/routeUtils'
|
||||
|
||||
export const useMenuStore = defineStore('menu', () => {
|
||||
// 状态
|
||||
const indexMenus = ref<MenuItem[]>([])
|
||||
const studentMenus = ref<MenuItem[]>([])
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
// 计算属性
|
||||
const navMenus = computed(() => generateNavMenus(indexMenus.value))
|
||||
|
||||
const visibleIndexMenus = computed(() =>
|
||||
indexMenus.value
|
||||
.filter(menu => menu.izVisible === 1)
|
||||
.sort((a, b) => b.sortOrder - a.sortOrder)
|
||||
)
|
||||
|
||||
const visibleStudentMenus = computed(() =>
|
||||
studentMenus.value
|
||||
.filter(menu => menu.izVisible === 1)
|
||||
.sort((a, b) => b.sortOrder - a.sortOrder)
|
||||
)
|
||||
|
||||
// 方法
|
||||
const fetchIndexMenus = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
|
||||
const response = await MenuApi.getIndexMenus()
|
||||
if (response.code === 200) {
|
||||
indexMenus.value = response.data || []
|
||||
console.log('✅ 首页菜单加载成功:', indexMenus.value)
|
||||
} else {
|
||||
throw new Error(response.message || '获取首页菜单失败')
|
||||
}
|
||||
} catch (err: any) {
|
||||
error.value = err.message || '获取首页菜单失败'
|
||||
console.error('❌ 获取首页菜单失败:', err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const fetchStudentMenus = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
|
||||
const response = await MenuApi.getStudentMenus()
|
||||
if (response.code === 200) {
|
||||
studentMenus.value = response.data || []
|
||||
console.log('✅ 学生菜单加载成功:', studentMenus.value)
|
||||
} else {
|
||||
throw new Error(response.message || '获取学生菜单失败')
|
||||
}
|
||||
} catch (err: any) {
|
||||
error.value = err.message || '获取学生菜单失败'
|
||||
console.error('❌ 获取学生菜单失败:', err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const getMenuByPath = (path: string, type: 'index' | 'student' = 'index'): MenuItem | undefined => {
|
||||
const menus = type === 'index' ? indexMenus.value : studentMenus.value
|
||||
return menus.find(menu => menu.path === path)
|
||||
}
|
||||
|
||||
const isRouteVisible = (path: string, type: 'index' | 'student' = 'index'): boolean => {
|
||||
const menu = getMenuByPath(path, type)
|
||||
return menu ? menu.izVisible === 1 : false
|
||||
}
|
||||
|
||||
const clearMenus = () => {
|
||||
indexMenus.value = []
|
||||
studentMenus.value = []
|
||||
error.value = null
|
||||
}
|
||||
|
||||
return {
|
||||
// 状态
|
||||
indexMenus,
|
||||
studentMenus,
|
||||
loading,
|
||||
error,
|
||||
|
||||
// 计算属性
|
||||
navMenus,
|
||||
visibleIndexMenus,
|
||||
visibleStudentMenus,
|
||||
|
||||
// 方法
|
||||
fetchIndexMenus,
|
||||
fetchStudentMenus,
|
||||
getMenuByPath,
|
||||
isRouteVisible,
|
||||
clearMenus,
|
||||
}
|
||||
})
|
114
src/utils/routeUtils.ts
Normal file
114
src/utils/routeUtils.ts
Normal file
@ -0,0 +1,114 @@
|
||||
// 路由工具函数
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import type { MenuItem } from '@/api/modules/menu'
|
||||
|
||||
// 组件映射表 - 将路径映射到对应的组件
|
||||
const componentMap: Record<string, () => Promise<any>> = {
|
||||
'/': () => import('@/views/Home.vue'),
|
||||
'/courses': () => import('@/views/Courses.vue'),
|
||||
'/special-training': () => import('@/views/SpecialTraining.vue'),
|
||||
'/faculty': () => import('@/views/Faculty.vue'),
|
||||
'/resources': () => import('@/views/Resources.vue'),
|
||||
'/activities': () => import('@/views/Activities.vue'),
|
||||
'/ai/app': () => import('@/views/Ai/AiAppList-NaiveUI.vue'),
|
||||
}
|
||||
|
||||
// 路由名称映射表 - 将路径映射到路由名称
|
||||
const routeNameMap: Record<string, string> = {
|
||||
'/': 'Home',
|
||||
'/courses': 'Courses',
|
||||
'/special-training': 'SpecialTraining',
|
||||
'/faculty': 'Faculty',
|
||||
'/resources': 'Resources',
|
||||
'/activities': 'Activities',
|
||||
'/ai/app': 'AiAppList',
|
||||
}
|
||||
|
||||
// 路由标题映射表 - 将路径映射到页面标题
|
||||
const routeTitleMap: Record<string, string> = {
|
||||
'/': '首页',
|
||||
'/courses': '课程列表',
|
||||
'/special-training': '专题训练',
|
||||
'/faculty': '师资力量',
|
||||
'/resources': '精选资源',
|
||||
'/activities': '全部活动',
|
||||
'/ai/app': 'AI应用管理',
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据菜单数据生成路由配置
|
||||
* @param menus 菜单数据数组
|
||||
* @returns 路由配置数组
|
||||
*/
|
||||
export function generateRoutesFromMenus(menus: MenuItem[]): RouteRecordRaw[] {
|
||||
const routes: RouteRecordRaw[] = []
|
||||
|
||||
// 过滤可见的菜单项并按排序字段排序
|
||||
const visibleMenus = menus
|
||||
.filter(menu => menu.izVisible === 1)
|
||||
.sort((a, b) => b.sortOrder - a.sortOrder) // 降序排列
|
||||
|
||||
for (const menu of visibleMenus) {
|
||||
const { path, name } = menu
|
||||
|
||||
// 检查路径是否有对应的组件
|
||||
if (!componentMap[path]) {
|
||||
console.warn(`路径 ${path} 没有对应的组件映射,跳过生成路由`)
|
||||
continue
|
||||
}
|
||||
|
||||
const route: RouteRecordRaw = {
|
||||
path,
|
||||
name: routeNameMap[path] || name,
|
||||
component: componentMap[path],
|
||||
meta: {
|
||||
title: routeTitleMap[path] || name,
|
||||
menuId: menu.id,
|
||||
sortOrder: menu.sortOrder,
|
||||
permissionKey: menu.permissionKey,
|
||||
}
|
||||
}
|
||||
|
||||
routes.push(route)
|
||||
}
|
||||
|
||||
return routes
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成导航菜单数据
|
||||
* @param menus 菜单数据数组
|
||||
* @returns 导航菜单数组
|
||||
*/
|
||||
export function generateNavMenus(menus: MenuItem[]) {
|
||||
return menus
|
||||
.filter(menu => menu.izVisible === 1)
|
||||
.sort((a, b) => b.sortOrder - a.sortOrder)
|
||||
.map(menu => ({
|
||||
id: menu.id,
|
||||
name: menu.name,
|
||||
path: menu.path,
|
||||
icon: menu.icon,
|
||||
sortOrder: menu.sortOrder,
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查路由是否存在于菜单中
|
||||
* @param path 路由路径
|
||||
* @param menus 菜单数据数组
|
||||
* @returns 是否存在
|
||||
*/
|
||||
export function isRouteInMenus(path: string, menus: MenuItem[]): boolean {
|
||||
return menus.some(menu => menu.path === path && menu.izVisible === 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据路径获取菜单项
|
||||
* @param path 路由路径
|
||||
* @param menus 菜单数据数组
|
||||
* @returns 菜单项或undefined
|
||||
*/
|
||||
export function getMenuByPath(path: string, menus: MenuItem[]): MenuItem | undefined {
|
||||
return menus.find(menu => menu.path === path)
|
||||
}
|
320
src/views/AiModelTest.vue
Normal file
320
src/views/AiModelTest.vue
Normal file
@ -0,0 +1,320 @@
|
||||
<template>
|
||||
<div class="ai-model-test">
|
||||
<n-card title="AI模型字典API测试">
|
||||
<n-space vertical size="large">
|
||||
<!-- 操作按钮 -->
|
||||
<n-card title="操作" size="small">
|
||||
<n-space>
|
||||
<n-button
|
||||
@click="handleLoadModels"
|
||||
:loading="loading"
|
||||
type="primary"
|
||||
>
|
||||
加载AI模型列表
|
||||
</n-button>
|
||||
<n-button
|
||||
@click="handleDirectRequest"
|
||||
:loading="loading"
|
||||
type="info"
|
||||
>
|
||||
直接请求测试
|
||||
</n-button>
|
||||
<n-button @click="handleClearModels">
|
||||
清空数据
|
||||
</n-button>
|
||||
</n-space>
|
||||
</n-card>
|
||||
|
||||
<!-- 状态信息 -->
|
||||
<n-card title="状态信息" size="small">
|
||||
<n-descriptions :column="2" bordered>
|
||||
<n-descriptions-item label="加载状态">
|
||||
<n-tag :type="loading ? 'warning' : 'success'">
|
||||
{{ loading ? '加载中' : '已完成' }}
|
||||
</n-tag>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="错误信息">
|
||||
<n-tag v-if="error" type="error">
|
||||
{{ error }}
|
||||
</n-tag>
|
||||
<n-tag v-else type="success">无错误</n-tag>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="模型数量">
|
||||
{{ modelList.length }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="API地址">
|
||||
<n-code>/sys/dict/getDictItems/airag_model where model_type = 'LLM',name,id</n-code>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="用户Token">
|
||||
<n-tag v-if="userStore.token" type="success">已登录</n-tag>
|
||||
<n-tag v-else type="warning">未登录</n-tag>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="Base URL">
|
||||
<n-code>{{ baseURL }}</n-code>
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
|
||||
<!-- 模型列表 -->
|
||||
<n-card title="AI模型列表" size="small">
|
||||
<n-data-table
|
||||
:columns="modelColumns"
|
||||
:data="modelList"
|
||||
:pagination="false"
|
||||
size="small"
|
||||
/>
|
||||
</n-card>
|
||||
|
||||
<!-- 选择器测试 -->
|
||||
<n-card title="选择器测试" size="small">
|
||||
<n-form-item label="选择AI模型">
|
||||
<n-select
|
||||
v-model:value="selectedModel"
|
||||
placeholder="请选择AI模型"
|
||||
:options="modelOptions"
|
||||
:loading="loading"
|
||||
@update:value="handleModelSelect"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="当前选择">
|
||||
<n-tag v-if="selectedModel" type="info">
|
||||
{{ getSelectedModelText() }}
|
||||
</n-tag>
|
||||
<span v-else>未选择</span>
|
||||
</n-form-item>
|
||||
</n-card>
|
||||
|
||||
<!-- 原始数据 -->
|
||||
<n-card title="原始API响应数据" size="small">
|
||||
<n-code
|
||||
:code="JSON.stringify(rawData, null, 2)"
|
||||
language="json"
|
||||
show-line-numbers
|
||||
/>
|
||||
</n-card>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, h } from 'vue'
|
||||
import {
|
||||
NCard,
|
||||
NSpace,
|
||||
NDescriptions,
|
||||
NDescriptionsItem,
|
||||
NTag,
|
||||
NButton,
|
||||
NDataTable,
|
||||
NCode,
|
||||
NFormItem,
|
||||
NSelect,
|
||||
useMessage
|
||||
} from 'naive-ui'
|
||||
import type { DataTableColumns } from 'naive-ui'
|
||||
import { SystemApi, type DictItem } from '@/api'
|
||||
import axios from 'axios'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
const message = useMessage()
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 状态
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
const modelList = ref<DictItem[]>([])
|
||||
const rawData = ref<any>(null)
|
||||
const selectedModel = ref<string>('')
|
||||
|
||||
// 计算属性
|
||||
const baseURL = computed(() => import.meta.env.VITE_API_BASE_URL || '/jeecgboot')
|
||||
|
||||
// 计算属性
|
||||
const modelOptions = computed(() =>
|
||||
modelList.value.map(item => ({
|
||||
label: item.text,
|
||||
value: item.value
|
||||
}))
|
||||
)
|
||||
|
||||
// 表格列定义
|
||||
const modelColumns: DataTableColumns<DictItem> = [
|
||||
{
|
||||
title: 'Value',
|
||||
key: 'value',
|
||||
width: 200,
|
||||
render: (row) => {
|
||||
return h('code', {
|
||||
style: 'background: #f5f5f5; padding: 2px 4px; border-radius: 3px;'
|
||||
}, row.value)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Text',
|
||||
key: 'text',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: 'Label',
|
||||
key: 'label',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: 'Title',
|
||||
key: 'title',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: 'Color',
|
||||
key: 'color',
|
||||
width: 100,
|
||||
render: (row) => {
|
||||
return row.color ? h(NTag, { type: 'info' }, () => row.color) : '无'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
// 加载AI模型列表
|
||||
const handleLoadModels = async () => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
console.log('🚀 开始加载AI模型列表...')
|
||||
console.log('🔍 当前时间戳:', Date.now())
|
||||
console.log('🔍 请求URL:', '/sys/dict/getDictItems/airag_model%20where%20model_type%20=%20\'LLM\',name,id')
|
||||
|
||||
const response = await SystemApi.getAiModelDict()
|
||||
|
||||
console.log('📦 完整API响应:', response)
|
||||
console.log('📦 响应数据:', response.data)
|
||||
|
||||
rawData.value = response
|
||||
|
||||
if (response.code === 200 || response.code === 0) {
|
||||
modelList.value = response.data || []
|
||||
message.success(`加载成功,共${modelList.value.length}个模型`)
|
||||
console.log('✅ AI模型列表加载成功:', modelList.value)
|
||||
} else {
|
||||
throw new Error(response.message || '获取模型列表失败')
|
||||
}
|
||||
} catch (err: any) {
|
||||
error.value = err.message || '加载失败'
|
||||
message.error(error.value || '未知错误')
|
||||
console.error('❌ 加载AI模型列表失败:', err)
|
||||
console.error('❌ 错误详情:', {
|
||||
message: err.message,
|
||||
response: err.response,
|
||||
config: err.config,
|
||||
stack: err.stack
|
||||
})
|
||||
|
||||
// 保存错误信息到rawData以便查看
|
||||
rawData.value = {
|
||||
error: err.message,
|
||||
response: err.response?.data,
|
||||
config: err.config
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 直接请求测试
|
||||
const handleDirectRequest = async () => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
console.log('🚀 开始直接请求测试...')
|
||||
|
||||
const baseURL = import.meta.env.VITE_API_BASE_URL || '/jeecgboot'
|
||||
const timestamp = Date.now()
|
||||
const token = userStore.token || localStorage.getItem('X-Access-Token') || ''
|
||||
|
||||
const url = `${baseURL}/sys/dict/getDictItems/airag_model%20where%20model_type%20=%20'LLM',name,id?_t=${timestamp}`
|
||||
|
||||
console.log('🔍 请求信息:', {
|
||||
url,
|
||||
timestamp,
|
||||
token: token ? '***' : '无',
|
||||
baseURL
|
||||
})
|
||||
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Request-Time': timestamp.toString(),
|
||||
'timestamp': timestamp.toString(),
|
||||
'X-Timestamp': timestamp.toString(),
|
||||
}
|
||||
|
||||
if (token) {
|
||||
headers['X-Access-Token'] = token
|
||||
}
|
||||
|
||||
console.log('🔍 请求头:', headers)
|
||||
|
||||
const response = await axios.get(url, { headers })
|
||||
|
||||
console.log('📦 直接请求响应:', response)
|
||||
rawData.value = response.data
|
||||
|
||||
if (response.data.code === 200 || response.data.code === 0) {
|
||||
modelList.value = response.data.result || response.data.data || []
|
||||
message.success(`直接请求成功,共${modelList.value.length}个模型`)
|
||||
} else {
|
||||
throw new Error(response.data.message || '直接请求失败')
|
||||
}
|
||||
} catch (err: any) {
|
||||
error.value = err.message || '直接请求失败'
|
||||
message.error(error.value || '未知错误')
|
||||
console.error('❌ 直接请求失败:', err)
|
||||
|
||||
rawData.value = {
|
||||
error: err.message,
|
||||
response: err.response?.data,
|
||||
status: err.response?.status,
|
||||
headers: err.response?.headers
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 清空数据
|
||||
const handleClearModels = () => {
|
||||
modelList.value = []
|
||||
rawData.value = null
|
||||
selectedModel.value = ''
|
||||
error.value = null
|
||||
message.info('数据已清空')
|
||||
}
|
||||
|
||||
// 模型选择
|
||||
const handleModelSelect = (value: string) => {
|
||||
const model = modelList.value.find(item => item.value === value)
|
||||
if (model) {
|
||||
message.info(`已选择: ${model.text}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取选择的模型文本
|
||||
const getSelectedModelText = () => {
|
||||
const model = modelList.value.find(item => item.value === selectedModel.value)
|
||||
return model ? model.text : selectedModel.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ai-model-test {
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
:deep(.n-code) {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
218
src/views/MenuTest.vue
Normal file
218
src/views/MenuTest.vue
Normal file
@ -0,0 +1,218 @@
|
||||
<template>
|
||||
<div class="menu-test">
|
||||
<n-card title="动态菜单路由测试">
|
||||
<n-space vertical size="large">
|
||||
<!-- 菜单状态 -->
|
||||
<n-card title="菜单状态" size="small">
|
||||
<n-descriptions :column="2" bordered>
|
||||
<n-descriptions-item label="加载状态">
|
||||
<n-tag :type="menuStore.loading ? 'warning' : 'success'">
|
||||
{{ menuStore.loading ? '加载中' : '已加载' }}
|
||||
</n-tag>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="错误信息">
|
||||
<n-tag v-if="menuStore.error" type="error">
|
||||
{{ menuStore.error }}
|
||||
</n-tag>
|
||||
<n-tag v-else type="success">无错误</n-tag>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="首页菜单数量">
|
||||
{{ menuStore.indexMenus.length }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="可见菜单数量">
|
||||
{{ menuStore.visibleIndexMenus.length }}
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<n-card title="操作" size="small">
|
||||
<n-space>
|
||||
<n-button
|
||||
@click="handleRefreshMenus"
|
||||
:loading="menuStore.loading"
|
||||
type="primary"
|
||||
>
|
||||
刷新菜单
|
||||
</n-button>
|
||||
<n-button @click="handleClearMenus">
|
||||
清空菜单
|
||||
</n-button>
|
||||
<n-button @click="handleTestRoute">
|
||||
测试路由跳转
|
||||
</n-button>
|
||||
</n-space>
|
||||
</n-card>
|
||||
|
||||
<!-- 菜单列表 -->
|
||||
<n-card title="菜单列表" size="small">
|
||||
<n-data-table
|
||||
:columns="menuColumns"
|
||||
:data="menuStore.visibleIndexMenus"
|
||||
:pagination="false"
|
||||
size="small"
|
||||
/>
|
||||
</n-card>
|
||||
|
||||
<!-- 路由信息 -->
|
||||
<n-card title="当前路由信息" size="small">
|
||||
<n-descriptions :column="1" bordered>
|
||||
<n-descriptions-item label="路径">
|
||||
{{ $route.path }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="名称">
|
||||
{{ $route.name }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="标题">
|
||||
{{ $route.meta.title }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="菜单ID">
|
||||
{{ $route.meta.menuId || '无' }}
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
|
||||
<!-- 动态导航组件 -->
|
||||
<n-card title="动态导航组件" size="small">
|
||||
<DynamicNavigation />
|
||||
</n-card>
|
||||
|
||||
<!-- 原始数据 -->
|
||||
<n-card title="原始菜单数据" size="small">
|
||||
<n-code
|
||||
:code="JSON.stringify(menuStore.indexMenus, null, 2)"
|
||||
language="json"
|
||||
show-line-numbers
|
||||
/>
|
||||
</n-card>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import {
|
||||
NCard,
|
||||
NSpace,
|
||||
NDescriptions,
|
||||
NDescriptionsItem,
|
||||
NTag,
|
||||
NButton,
|
||||
NDataTable,
|
||||
NCode,
|
||||
useMessage
|
||||
} from 'naive-ui'
|
||||
import type { DataTableColumns } from 'naive-ui'
|
||||
import { useMenuStore } from '@/stores/menu'
|
||||
import DynamicNavigation from '@/components/layout/DynamicNavigation.vue'
|
||||
import type { MenuItem } from '@/api/modules/menu'
|
||||
|
||||
const menuStore = useMenuStore()
|
||||
const router = useRouter()
|
||||
const message = useMessage()
|
||||
|
||||
// 表格列定义
|
||||
const menuColumns: DataTableColumns<MenuItem> = [
|
||||
{
|
||||
title: 'ID',
|
||||
key: 'id',
|
||||
width: 60,
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
key: 'name',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '路径',
|
||||
key: 'path',
|
||||
width: 150,
|
||||
render: (row) => {
|
||||
return h('code', { style: 'background: #f5f5f5; padding: 2px 4px; border-radius: 3px;' }, row.path)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
key: 'type',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
key: 'sortOrder',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '可见',
|
||||
key: 'izVisible',
|
||||
width: 80,
|
||||
render: (row) => {
|
||||
return h(NTag, {
|
||||
type: row.izVisible === 1 ? 'success' : 'error'
|
||||
}, () => row.izVisible === 1 ? '是' : '否')
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 100,
|
||||
render: (row) => {
|
||||
return h(NButton, {
|
||||
size: 'small',
|
||||
type: 'primary',
|
||||
onClick: () => handleNavigate(row.path)
|
||||
}, () => '跳转')
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
// 刷新菜单
|
||||
const handleRefreshMenus = async () => {
|
||||
try {
|
||||
await menuStore.fetchIndexMenus()
|
||||
message.success('菜单刷新成功')
|
||||
} catch (error) {
|
||||
message.error('菜单刷新失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 清空菜单
|
||||
const handleClearMenus = () => {
|
||||
menuStore.clearMenus()
|
||||
message.info('菜单已清空')
|
||||
}
|
||||
|
||||
// 测试路由跳转
|
||||
const handleTestRoute = () => {
|
||||
const menus = menuStore.visibleIndexMenus
|
||||
if (menus.length > 0) {
|
||||
const randomMenu = menus[Math.floor(Math.random() * menus.length)]
|
||||
handleNavigate(randomMenu.path)
|
||||
} else {
|
||||
message.warning('没有可用的菜单项')
|
||||
}
|
||||
}
|
||||
|
||||
// 导航到指定路径
|
||||
const handleNavigate = (path: string) => {
|
||||
router.push(path).then(() => {
|
||||
message.success(`已跳转到: ${path}`)
|
||||
}).catch((error) => {
|
||||
message.error(`跳转失败: ${error.message}`)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.menu-test {
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
:deep(.n-code) {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
Loading…
x
Reference in New Issue
Block a user