|
@@ -11,7 +11,13 @@
|
|
<div class="content-wrapper">
|
|
<div class="content-wrapper">
|
|
<!-- 左侧菜单 -->
|
|
<!-- 左侧菜单 -->
|
|
<div class="sidebar">
|
|
<div class="sidebar">
|
|
- <div class="menu-list">
|
|
|
|
|
|
+ <div v-if="loading" class="loading-container">
|
|
|
|
+ <div class="loading-text">加载中...</div>
|
|
|
|
+ </div>
|
|
|
|
+ <div v-else-if="menuItems.length === 0" class="empty-container">
|
|
|
|
+ <div class="empty-text">暂无案例数据</div>
|
|
|
|
+ </div>
|
|
|
|
+ <div v-else class="menu-list">
|
|
<div v-for="item in menuItems" :key="item.id" class="menu-item"
|
|
<div v-for="item in menuItems" :key="item.id" class="menu-item"
|
|
:class="{ 'has-children': item.children && item.children.length > 0 }">
|
|
:class="{ 'has-children': item.children && item.children.length > 0 }">
|
|
<!-- 一级菜单 -->
|
|
<!-- 一级菜单 -->
|
|
@@ -20,7 +26,7 @@
|
|
'expanded': expandedMenus.includes(item.id)
|
|
'expanded': expandedMenus.includes(item.id)
|
|
}" @click="handleMenuClick(item)">
|
|
}" @click="handleMenuClick(item)">
|
|
<span>{{ item.title }}</span>
|
|
<span>{{ item.title }}</span>
|
|
- <img src="@assets/arrow-down.svg" v-if="item.children && item.children.length > 0" class="expand-icon"
|
|
|
|
|
|
+ <img src="@assets/arrow-down.svg" v-if="item.children && item.children.length > 0" class="expand-icon"
|
|
:class="{ 'expanded': expandedMenus.includes(item.id) }" alt="" srcset="">
|
|
:class="{ 'expanded': expandedMenus.includes(item.id) }" alt="" srcset="">
|
|
<!-- <i v-if="item.children && item.children.length > 0" class="expand-icon"
|
|
<!-- <i v-if="item.children && item.children.length > 0" class="expand-icon"
|
|
:class="{ 'expanded': expandedMenus.includes(item.id) }">▼</i> -->
|
|
:class="{ 'expanded': expandedMenus.includes(item.id) }">▼</i> -->
|
|
@@ -72,6 +78,7 @@
|
|
import { ref, computed, onMounted, watch } from 'vue'
|
|
import { ref, computed, onMounted, watch } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import DefaultLayout from '@/layouts/DefaultLayout.vue'
|
|
import DefaultLayout from '@/layouts/DefaultLayout.vue'
|
|
|
|
+import { getProjectClassifyTree, getProjectList } from '@/api/modules/home'
|
|
|
|
|
|
export default {
|
|
export default {
|
|
name: 'CaseDetailsPage',
|
|
name: 'CaseDetailsPage',
|
|
@@ -85,188 +92,86 @@ export default {
|
|
const currentCaseId = ref(null)
|
|
const currentCaseId = ref(null)
|
|
const expandedMenus = ref([])
|
|
const expandedMenus = ref([])
|
|
const searchKeyword = ref('')
|
|
const searchKeyword = ref('')
|
|
|
|
+ const loading = ref(false)
|
|
|
|
+ const isUpdatingRoute = ref(false) // 标记是否正在更新路由
|
|
|
|
|
|
- // 案例菜单数据
|
|
|
|
- const menuItems = ref([
|
|
|
|
- {
|
|
|
|
- id: 'smart-agriculture',
|
|
|
|
- title: '智慧农业(种植+养殖)',
|
|
|
|
- children: [
|
|
|
|
- {
|
|
|
|
- id: 1,
|
|
|
|
- title: '智慧农业综合管理平台',
|
|
|
|
- content: `
|
|
|
|
- <p>智慧农业综合管理平台采用先进的物联网技术、大数据分析技术,为农业生产提供全面、精准的管理服务。通过实时监测土壤、气候、作物生长状态等关键指标,实现精准农业管理,提高农业生产效率和产品质量。</p>
|
|
|
|
-
|
|
|
|
- <h3>一、关键技术</h3>
|
|
|
|
- <p><strong>1. 物联网技术:</strong>通过在农田中部署各类传感器设备,实时监测温度、湿度、土壤养分等关键参数,并通过无线网络将数据传输至云端管理平台,为精准农业管理提供数据支撑。</p>
|
|
|
|
-
|
|
|
|
- <p><strong>2. 大数据分析:</strong>收集并分析海量农业数据,整合各类农业生产要素信息,通过大数据挖掘技术发现农业生产规律,为农业决策提供科学依据。</p>
|
|
|
|
-
|
|
|
|
- <p><strong>3. 人工智能技术:</strong>利用机器学习、深度学习等AI技术,对农作物生长状态进行智能识别与分析,实现病虫害早期预警、产量预测等功能,提升农业生产的智能化水平。</p>
|
|
|
|
-
|
|
|
|
- <p><strong>4. 精准农业设备:</strong>整合各类人工、智能农机设备的数字化改造,实现农田管理的自动化、精准化,自动化、智能人机协作,提高人机协作效率。</p>
|
|
|
|
-
|
|
|
|
- <h3>二、应用案例</h3>
|
|
|
|
- <p><strong>1. 无人机巡检:</strong>无人机可以定期巡检农田监测农作物生长状态,特别是对病虫害、农药残留等,实现精确农业巡检和监测,大幅提升农业巡检效率。</p>
|
|
|
|
-
|
|
|
|
- <p><strong>2. 智慧温室大棚:</strong>结合物联网技术和智能控制系统,实现温室大棚的自动化管理,智能控制温度、湿度、光照等环境参数,智慧农业综合管理平台可以实时监控温室大棚的运行状态,特别是对于水肥一体化管理,可以大幅提升农业生产效率10%。</p>
|
|
|
|
-
|
|
|
|
- <p><strong>3. 智能灌溉大棚:</strong>通过智能灌溉、施肥、光照、水肥平衡,为作物提供最佳的生长环境,智能灌溉大棚可以节约用水34%,农药使用量减少10%至20%。</p>
|
|
|
|
-
|
|
|
|
- <h3>三、产品介绍</h3>
|
|
|
|
- <img src="/src/assets/smart-agriculture-dashboard.png" alt="智慧农业综合管理平台界面" style="width: 100%; margin: 20px 0;" />
|
|
|
|
- `
|
|
|
|
- }
|
|
|
|
- ]
|
|
|
|
- },
|
|
|
|
- {
|
|
|
|
- id: 'smart-parking',
|
|
|
|
- title: '智慧停车',
|
|
|
|
- content: `
|
|
|
|
- <p>智慧停车系统通过物联网技术和智能化管理,为城市停车难题提供有效解决方案。系统集成了车位检测、智能引导、移动支付等功能,大幅提升停车效率和用户体验。</p>
|
|
|
|
-
|
|
|
|
- <h3>系统特点</h3>
|
|
|
|
- <p>1. 实时车位监测与引导</p>
|
|
|
|
- <p>2. 移动端预约与支付</p>
|
|
|
|
- <p>3. 数据分析与优化建议</p>
|
|
|
|
- <p>4. 违停自动识别与处理</p>
|
|
|
|
- `
|
|
|
|
- },
|
|
|
|
- {
|
|
|
|
- id: 'data-service',
|
|
|
|
- title: '数据服务系统',
|
|
|
|
- content: `
|
|
|
|
- <p>数据服务系统为政府部门和企业提供数据整合、分析和可视化服务,支持科学决策和管理优化。通过大数据技术,实现数据的深度挖掘和价值发现。</p>
|
|
|
|
-
|
|
|
|
- <h3>核心功能</h3>
|
|
|
|
- <p>1. 多源数据整合与清洗</p>
|
|
|
|
- <p>2. 实时数据分析与挖掘</p>
|
|
|
|
- <p>3. 可视化报表与仪表盘</p>
|
|
|
|
- <p>4. 预测分析与决策支持</p>
|
|
|
|
- `
|
|
|
|
- },
|
|
|
|
- {
|
|
|
|
- id: 'scenic-tour',
|
|
|
|
- title: '景区一码游系统',
|
|
|
|
- content: `
|
|
|
|
- <p>景区一码游系统通过移动互联网技术,为游客提供便捷的景区服务,包括门票预订、导览服务、设施查询等功能,全面提升旅游体验。</p>
|
|
|
|
-
|
|
|
|
- <h3>主要功能</h3>
|
|
|
|
- <p>1. 在线门票预订与验证</p>
|
|
|
|
- <p>2. 智能导览与路线推荐</p>
|
|
|
|
- <p>3. 景点介绍与AR体验</p>
|
|
|
|
- <p>4. 服务设施查询与预约</p>
|
|
|
|
- `
|
|
|
|
- },
|
|
|
|
- {
|
|
|
|
- id: 'smart-forest',
|
|
|
|
- title: '智慧林场',
|
|
|
|
- content: `
|
|
|
|
- <p>智慧林场管理系统通过物联网和大数据技术,实现森林资源的智能监管和保护。系统提供森林健康监测、病虫害预警、资源管理等功能。</p>
|
|
|
|
-
|
|
|
|
- <h3>系统优势</h3>
|
|
|
|
- <p>1. 森林环境实时监测</p>
|
|
|
|
- <p>2. 病虫害智能预警</p>
|
|
|
|
- <p>3. 资源动态管理</p>
|
|
|
|
- <p>4. 生态效益评估</p>
|
|
|
|
- `
|
|
|
|
- },
|
|
|
|
- {
|
|
|
|
- id: 'forest-fire',
|
|
|
|
- title: '森林防火',
|
|
|
|
- content: `
|
|
|
|
- <p>森林防火系统利用先进的监测技术和预警系统,实现森林火灾的早期发现和快速响应。通过多维度监测和智能分析,有效降低火灾风险。</p>
|
|
|
|
-
|
|
|
|
- <h3>技术特点</h3>
|
|
|
|
- <p>1. 多传感器融合监测</p>
|
|
|
|
- <p>2. 智能火险等级评估</p>
|
|
|
|
- <p>3. 快速预警与响应</p>
|
|
|
|
- <p>4. 应急指挥调度</p>
|
|
|
|
- `
|
|
|
|
- },
|
|
|
|
- {
|
|
|
|
- id: 'digital-factory',
|
|
|
|
- title: '数字工厂',
|
|
|
|
- children: [
|
|
|
|
- {
|
|
|
|
- id: 2,
|
|
|
|
- title: '智能制造执行系统',
|
|
|
|
- content: `
|
|
|
|
- <p>智能制造执行系统(MES)是面向制造企业车间执行层的生产信息化管理系统,为企业提供包括制造数据管理、计划排程管理、生产调度管理、库存管理、质量管理、人力资源管理、工作中心/设备管理、工具工装管理、采购管理、成本管理、项目看板管理、生产过程控制、底层数据集成分析、上层数据接口等管理模块。</p>
|
|
|
|
-
|
|
|
|
- <h3>系统特点</h3>
|
|
|
|
- <p>1. 实时数据采集与监控</p>
|
|
|
|
- <p>2. 智能排产与调度优化</p>
|
|
|
|
- <p>3. 质量全程追溯管理</p>
|
|
|
|
- <p>4. 设备状态实时监控</p>
|
|
|
|
- `
|
|
|
|
- },
|
|
|
|
- {
|
|
|
|
- id: 3,
|
|
|
|
- title: '工业物联网平台',
|
|
|
|
- content: `
|
|
|
|
- <p>工业物联网平台通过连接工厂内的各种设备、传感器和系统,实现数据的实时采集、传输和分析,为企业提供全面的数字化解决方案。</p>
|
|
|
|
-
|
|
|
|
- <h3>核心功能</h3>
|
|
|
|
- <p>1. 设备连接与数据采集</p>
|
|
|
|
- <p>2. 实时监控与预警</p>
|
|
|
|
- <p>3. 数据分析与优化建议</p>
|
|
|
|
- <p>4. 远程运维与管理</p>
|
|
|
|
- `
|
|
|
|
- }
|
|
|
|
- ]
|
|
|
|
- },
|
|
|
|
- {
|
|
|
|
- id: 'smart-elderly',
|
|
|
|
- title: '智慧养老',
|
|
|
|
- children: [
|
|
|
|
- {
|
|
|
|
- id: 4,
|
|
|
|
- title: '居家养老服务平台',
|
|
|
|
- content: `
|
|
|
|
- <p>居家养老服务平台是专为老年人及其家庭提供的综合性智慧养老解决方案,通过物联网、大数据、人工智能等技术,为老年人提供安全、便捷、舒适的居家养老服务。</p>
|
|
|
|
-
|
|
|
|
- <h3>主要功能</h3>
|
|
|
|
- <p>1. 健康监测与管理</p>
|
|
|
|
- <p>2. 紧急救助服务</p>
|
|
|
|
- <p>3. 生活服务预约</p>
|
|
|
|
- <p>4. 家属远程关怀</p>
|
|
|
|
- `
|
|
|
|
- }
|
|
|
|
- ]
|
|
|
|
- },
|
|
|
|
- {
|
|
|
|
- id: 'smart-security',
|
|
|
|
- title: '智慧安防',
|
|
|
|
- content: `
|
|
|
|
- <p>智慧安防系统集成视频监控、入侵检测、门禁控制等多种安防技术,通过人工智能和大数据分析,实现智能化安全管理和风险预警。</p>
|
|
|
|
-
|
|
|
|
- <h3>核心技术</h3>
|
|
|
|
- <p>1. 智能视频分析</p>
|
|
|
|
- <p>2. 人脸识别与行为分析</p>
|
|
|
|
- <p>3. 多维度风险评估</p>
|
|
|
|
- <p>4. 应急联动响应</p>
|
|
|
|
- `
|
|
|
|
- },
|
|
|
|
- {
|
|
|
|
- id: 'other-cases',
|
|
|
|
- title: '其他案例',
|
|
|
|
- content: `
|
|
|
|
- <p>我们还为各行各业提供了多样化的智慧解决方案,包括智慧教育、智慧医疗、智慧交通等领域,累计服务客户500+,覆盖全国多个省市。</p>
|
|
|
|
-
|
|
|
|
- <h3>服务领域</h3>
|
|
|
|
- <p>1. 智慧教育解决方案</p>
|
|
|
|
- <p>2. 智慧医疗管理系统</p>
|
|
|
|
- <p>3. 智慧交通监控平台</p>
|
|
|
|
- <p>4. 智慧社区服务系统</p>
|
|
|
|
- `
|
|
|
|
- }
|
|
|
|
- ])
|
|
|
|
|
|
+ // 案例菜单数据 - 从API获取
|
|
|
|
+ const menuItems = ref([])
|
|
|
|
|
|
// 当前选中的案例
|
|
// 当前选中的案例
|
|
const currentCase = ref(null)
|
|
const currentCase = ref(null)
|
|
|
|
|
|
|
|
+ // 获取左侧分类树数据
|
|
|
|
+ const fetchMenuData = async () => {
|
|
|
|
+ try {
|
|
|
|
+ loading.value = true
|
|
|
|
+ const response = await getProjectClassifyTree({
|
|
|
|
+ classifyType: 2 // 2:行业案例
|
|
|
|
+ })
|
|
|
|
+ console.log('左侧分类树数据', response.rows)
|
|
|
|
+
|
|
|
|
+ if (response.rows && response.rows.length > 0) {
|
|
|
|
+ // 转换API数据格式为组件需要的格式
|
|
|
|
+ menuItems.value = response.rows.map(item => {
|
|
|
|
+ // 如果有children,使用children数据
|
|
|
|
+ if (item.children && item.children.length > 0) {
|
|
|
|
+ return {
|
|
|
|
+ id: item.id,
|
|
|
|
+ title: item.classifyName || item.name,
|
|
|
|
+ children: item.children.map(child => ({
|
|
|
|
+ id: child.id,
|
|
|
|
+ title: child.projectName || child.name,
|
|
|
|
+ projectType: child.projectType,
|
|
|
|
+ // 标记这个项目需要通过API获取详细内容
|
|
|
|
+ needFetchDetail: true
|
|
|
|
+ }))
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ // 如果没有children,该分类本身就是一个可点击的项目
|
|
|
|
+ return {
|
|
|
|
+ id: item.id,
|
|
|
|
+ title: item.classifyName || item.name,
|
|
|
|
+ classifyId: item.id, // 使用分类ID作为classifyId
|
|
|
|
+ // 标记需要通过classifyId获取该分类下的项目列表
|
|
|
|
+ needFetchByClassify: true,
|
|
|
|
+ children: []
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('获取分类数据失败:', error)
|
|
|
|
+ menuItems.value = []
|
|
|
|
+ } finally {
|
|
|
|
+ loading.value = false
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 获取案例详情数据(通过ID)
|
|
|
|
+ const fetchCaseDetail = async (projectId) => {
|
|
|
|
+ try {
|
|
|
|
+ const response = await getProjectList({
|
|
|
|
+ projectType: 2, // 2:行业案例
|
|
|
|
+ id: projectId,
|
|
|
|
+ })
|
|
|
|
+ console.log('response.rows', response.rows)
|
|
|
|
+ if (response.rows && response.rows.length > 0) {
|
|
|
|
+ const caseData = response.rows[0]
|
|
|
|
+ console.log('caseData', caseData)
|
|
|
|
+ return {
|
|
|
|
+ id: caseData.id,
|
|
|
|
+ title: caseData.projectName || '未命名案例',
|
|
|
|
+ content: caseData.projectDescribe || caseData.projectProfile || '<p>暂无详细内容</p>',
|
|
|
|
+ image: caseData.projectImage
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return null
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('获取案例详情失败:', error)
|
|
|
|
+ return null
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
// 搜索过滤后的菜单项
|
|
// 搜索过滤后的菜单项
|
|
const filteredMenuItems = computed(() => {
|
|
const filteredMenuItems = computed(() => {
|
|
if (!searchKeyword.value) {
|
|
if (!searchKeyword.value) {
|
|
@@ -289,8 +194,8 @@ export default {
|
|
if (menu.children && menu.children.length > 0) {
|
|
if (menu.children && menu.children.length > 0) {
|
|
// 有子菜单的情况,添加所有子项
|
|
// 有子菜单的情况,添加所有子项
|
|
cases.push(...menu.children)
|
|
cases.push(...menu.children)
|
|
- } else if (menu.content) {
|
|
|
|
- // 没有子菜单但有内容的情况,添加菜单项本身
|
|
|
|
|
|
+ } else if (menu.needFetchByClassify || menu.content) {
|
|
|
|
+ // 没有子菜单但需要获取数据或有内容的情况,添加菜单项本身
|
|
cases.push(menu)
|
|
cases.push(menu)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
@@ -321,12 +226,14 @@ export default {
|
|
return menu.children.some(child => child.id === currentCaseId.value)
|
|
return menu.children.some(child => child.id === currentCaseId.value)
|
|
} else {
|
|
} else {
|
|
// 没有子菜单的情况:检查自身是否被选中
|
|
// 没有子菜单的情况:检查自身是否被选中
|
|
|
|
+ // 对于needFetchByClassify的情况,可能当前选中的是该分类下的某个项目
|
|
return menu.id === currentCaseId.value
|
|
return menu.id === currentCaseId.value
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// 处理菜单点击
|
|
// 处理菜单点击
|
|
const handleMenuClick = (menu) => {
|
|
const handleMenuClick = (menu) => {
|
|
|
|
+ console.log('处理菜单点击', menu)
|
|
if (menu.children && menu.children.length > 0) {
|
|
if (menu.children && menu.children.length > 0) {
|
|
// 有子菜单的情况:切换展开/收起状态
|
|
// 有子菜单的情况:切换展开/收起状态
|
|
const index = expandedMenus.value.indexOf(menu.id)
|
|
const index = expandedMenus.value.indexOf(menu.id)
|
|
@@ -336,23 +243,103 @@ export default {
|
|
expandedMenus.value.push(menu.id)
|
|
expandedMenus.value.push(menu.id)
|
|
}
|
|
}
|
|
} else {
|
|
} else {
|
|
- // 没有子菜单的情况:直接选中该菜单项(如果它有内容的话)
|
|
|
|
- if (menu.content) {
|
|
|
|
- selectCase(menu)
|
|
|
|
- }
|
|
|
|
|
|
+ // 没有子菜单的情况:直接选中该菜单项
|
|
|
|
+ selectCase(menu)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// 选择案例
|
|
// 选择案例
|
|
- const selectCase = (caseItem) => {
|
|
|
|
|
|
+ const selectCase = async (caseItem, skipRouteUpdate = false) => {
|
|
|
|
+ console.log('选择案例', caseItem)
|
|
|
|
+
|
|
|
|
+ // 如果当前已经是这个案例,直接返回
|
|
|
|
+ if (currentCaseId.value === caseItem.id) {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
currentCaseId.value = caseItem.id
|
|
currentCaseId.value = caseItem.id
|
|
- currentCase.value = caseItem
|
|
|
|
|
|
|
|
- // 更新URL
|
|
|
|
- router.replace({
|
|
|
|
- name: 'Casesdetails',
|
|
|
|
- query: { id: caseItem.id }
|
|
|
|
- })
|
|
|
|
|
|
+ try {
|
|
|
|
+ // 如果需要通过classifyId获取该分类下的项目列表
|
|
|
|
+ if (caseItem.needFetchByClassify && caseItem.classifyId) {
|
|
|
|
+ const response = await getProjectList({
|
|
|
|
+ projectType: 2, // 2:行业案例
|
|
|
|
+ classifyId: caseItem.classifyId
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ if (response.rows && response.rows.length > 0) {
|
|
|
|
+ // 取第一个项目作为默认显示内容,但保持分类ID作为当前案例ID
|
|
|
|
+ const caseData = response.rows[0]
|
|
|
|
+ currentCase.value = {
|
|
|
|
+ id: caseItem.id, // 使用分类ID,而不是项目ID
|
|
|
|
+ title: caseData.projectName || caseItem.title,
|
|
|
|
+ content: caseData.projectDescribe || caseData.projectProfile || '<p>暂无详细内容</p>',
|
|
|
|
+ image: caseData.projectImage
|
|
|
|
+ }
|
|
|
|
+ // 保持分类ID作为当前案例ID,不要被项目ID覆盖
|
|
|
|
+ // currentCaseId.value 已经在函数开始时设置为 caseItem.id
|
|
|
|
+ } else {
|
|
|
|
+ // 如果API没有返回数据,显示分类信息
|
|
|
|
+ currentCase.value = {
|
|
|
|
+ id: caseItem.id,
|
|
|
|
+ title: caseItem.title,
|
|
|
|
+ content: '<p>该分类下暂无案例</p>'
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ // 如果需要获取详细内容(从左侧菜单点击的项目)
|
|
|
|
+ else if (caseItem.needFetchDetail) {
|
|
|
|
+ const response = await getProjectList({
|
|
|
|
+ projectType: 2, // 2:行业案例
|
|
|
|
+ id: caseItem.id
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ if (response.rows && response.rows.length > 0) {
|
|
|
|
+ const caseData = response.rows[0]
|
|
|
|
+ currentCase.value = {
|
|
|
|
+ id: caseData.id,
|
|
|
|
+ title: caseData.projectName || caseItem.title,
|
|
|
|
+ content: caseData.projectDescribe || caseData.projectProfile || '<p>暂无详细内容</p>',
|
|
|
|
+ image: caseData.projectImage
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ // 如果API没有返回数据,使用基本信息
|
|
|
|
+ currentCase.value = {
|
|
|
|
+ id: caseItem.id,
|
|
|
|
+ title: caseItem.title,
|
|
|
|
+ content: '<p>暂无详细内容</p>'
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ // 直接使用传入的案例数据
|
|
|
|
+ currentCase.value = caseItem
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('获取案例数据失败:', error)
|
|
|
|
+ currentCase.value = {
|
|
|
|
+ id: caseItem.id,
|
|
|
|
+ title: caseItem.title,
|
|
|
|
+ content: '<p>获取内容失败,请稍后重试</p>'
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 更新URL(除非明确跳过)
|
|
|
|
+ if (!skipRouteUpdate) {
|
|
|
|
+ isUpdatingRoute.value = true
|
|
|
|
+ console.log('开始更新路由到ID:', currentCaseId.value)
|
|
|
|
+
|
|
|
|
+ await router.replace({
|
|
|
|
+ name: 'Casesdetails',
|
|
|
|
+ query: { id: currentCaseId.value }
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ console.log('路由更新完成')
|
|
|
|
+ // 延迟重置标志,确保路由更新完成
|
|
|
|
+ setTimeout(() => {
|
|
|
|
+ isUpdatingRoute.value = false
|
|
|
|
+ console.log('路由更新标志重置')
|
|
|
|
+ }, 200) // 增加延迟时间
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
// 搜索处理
|
|
// 搜索处理
|
|
@@ -389,8 +376,16 @@ export default {
|
|
}
|
|
}
|
|
|
|
|
|
// 根据ID查找并高亮案例
|
|
// 根据ID查找并高亮案例
|
|
- const findAndHighlightCase = (caseId) => {
|
|
|
|
|
|
+ const findAndHighlightCase = async (caseId, skipRouteUpdate = false) => {
|
|
const id = parseInt(caseId) || caseId // 支持字符串ID
|
|
const id = parseInt(caseId) || caseId // 支持字符串ID
|
|
|
|
+ console.log('查找并高亮案例', { caseId, id, skipRouteUpdate })
|
|
|
|
+
|
|
|
|
+ // 如果当前已经是这个案例,直接返回
|
|
|
|
+ if (currentCaseId.value === id) {
|
|
|
|
+ console.log('当前已经是这个案例,直接返回')
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
let foundCase = null
|
|
let foundCase = null
|
|
let parentMenuId = null
|
|
let parentMenuId = null
|
|
|
|
|
|
@@ -404,37 +399,64 @@ export default {
|
|
parentMenuId = menu.id
|
|
parentMenuId = menu.id
|
|
break
|
|
break
|
|
}
|
|
}
|
|
- } else if (menu.id === id && menu.content) {
|
|
|
|
- // 在一级菜单中查找(没有子菜单但有内容)
|
|
|
|
|
|
+ } else if (menu.id === id) {
|
|
|
|
+ // 在一级菜单中查找
|
|
foundCase = menu
|
|
foundCase = menu
|
|
break
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (foundCase) {
|
|
if (foundCase) {
|
|
|
|
+ console.log('找到案例', foundCase)
|
|
// 展开父菜单(如果有的话)
|
|
// 展开父菜单(如果有的话)
|
|
if (parentMenuId && !expandedMenus.value.includes(parentMenuId)) {
|
|
if (parentMenuId && !expandedMenus.value.includes(parentMenuId)) {
|
|
expandedMenus.value.push(parentMenuId)
|
|
expandedMenus.value.push(parentMenuId)
|
|
}
|
|
}
|
|
|
|
|
|
// 选中案例
|
|
// 选中案例
|
|
- currentCaseId.value = foundCase.id
|
|
|
|
- currentCase.value = foundCase
|
|
|
|
|
|
+ await selectCase(foundCase, skipRouteUpdate)
|
|
|
|
+ } else {
|
|
|
|
+ console.log('在菜单中没找到案例,尝试直接获取详情')
|
|
|
|
+ // 如果在菜单中没找到,可能是直接通过URL访问,尝试直接获取详情
|
|
|
|
+ const caseDetail = await fetchCaseDetail(id)
|
|
|
|
+ if (caseDetail) {
|
|
|
|
+ currentCaseId.value = id
|
|
|
|
+ currentCase.value = caseDetail
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// 监听路由变化
|
|
// 监听路由变化
|
|
- watch(() => route.query.id, (newId) => {
|
|
|
|
- if (newId) {
|
|
|
|
- findAndHighlightCase(newId)
|
|
|
|
|
|
+ watch(() => route.query.id, async (newId, oldId) => {
|
|
|
|
+ console.log('路由变化监听', { newId, oldId, currentCaseId: currentCaseId.value, isUpdatingRoute: isUpdatingRoute.value })
|
|
|
|
+
|
|
|
|
+ // 如果正在更新路由,跳过处理
|
|
|
|
+ if (isUpdatingRoute.value) {
|
|
|
|
+ console.log('正在更新路由,跳过处理')
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 如果新ID和当前案例ID相同,跳过处理
|
|
|
|
+ if (newId && parseInt(newId) === currentCaseId.value) {
|
|
|
|
+ console.log('新ID和当前案例ID相同,跳过处理')
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 只有当ID真正发生变化且菜单数据已加载时才执行
|
|
|
|
+ if (newId && newId !== oldId && menuItems.value.length > 0) {
|
|
|
|
+ console.log('执行路由变化处理')
|
|
|
|
+ await findAndHighlightCase(newId, true) // 跳过路由更新,因为这是由路由变化触发的
|
|
}
|
|
}
|
|
- }, { immediate: true })
|
|
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ onMounted(async () => {
|
|
|
|
+ // 首先获取菜单数据
|
|
|
|
+ await fetchMenuData()
|
|
|
|
|
|
- onMounted(() => {
|
|
|
|
// 初始化时根据URL参数加载案例
|
|
// 初始化时根据URL参数加载案例
|
|
const caseId = route.query.id
|
|
const caseId = route.query.id
|
|
if (caseId) {
|
|
if (caseId) {
|
|
- findAndHighlightCase(caseId)
|
|
|
|
|
|
+ await findAndHighlightCase(caseId)
|
|
} else {
|
|
} else {
|
|
// 默认选择第一个案例并展开其父菜单
|
|
// 默认选择第一个案例并展开其父菜单
|
|
const firstCase = flattenedCases.value[0]
|
|
const firstCase = flattenedCases.value[0]
|
|
@@ -448,7 +470,7 @@ export default {
|
|
break
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- selectCase(firstCase)
|
|
|
|
|
|
+ await selectCase(firstCase)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
})
|
|
@@ -460,6 +482,8 @@ export default {
|
|
filteredMenuItems,
|
|
filteredMenuItems,
|
|
expandedMenus,
|
|
expandedMenus,
|
|
searchKeyword,
|
|
searchKeyword,
|
|
|
|
+ loading,
|
|
|
|
+ isUpdatingRoute,
|
|
prevCase,
|
|
prevCase,
|
|
nextCase,
|
|
nextCase,
|
|
isMenuActive,
|
|
isMenuActive,
|
|
@@ -467,7 +491,9 @@ export default {
|
|
selectCase,
|
|
selectCase,
|
|
handleSearch,
|
|
handleSearch,
|
|
goToPrevCase,
|
|
goToPrevCase,
|
|
- goToNextCase
|
|
|
|
|
|
+ goToNextCase,
|
|
|
|
+ fetchMenuData,
|
|
|
|
+ fetchCaseDetail
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -488,7 +514,8 @@ export default {
|
|
padding: 40px 0 80px;
|
|
padding: 40px 0 80px;
|
|
background: #F5F9FF;
|
|
background: #F5F9FF;
|
|
position: relative;
|
|
position: relative;
|
|
- &::before{
|
|
|
|
|
|
+
|
|
|
|
+ &::before {
|
|
position: absolute;
|
|
position: absolute;
|
|
content: '';
|
|
content: '';
|
|
left: 0;
|
|
left: 0;
|
|
@@ -517,6 +544,20 @@ export default {
|
|
position: sticky;
|
|
position: sticky;
|
|
top: 20px;
|
|
top: 20px;
|
|
|
|
|
|
|
|
+ .loading-container,
|
|
|
|
+ .empty-container {
|
|
|
|
+ padding: 40px 20px;
|
|
|
|
+ text-align: center;
|
|
|
|
+ background: #fff;
|
|
|
|
+ border-radius: 8px;
|
|
|
|
+
|
|
|
|
+ .loading-text,
|
|
|
|
+ .empty-text {
|
|
|
|
+ color: #666;
|
|
|
|
+ font-size: 14px;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
.menu-list {
|
|
.menu-list {
|
|
.menu-item {
|
|
.menu-item {
|
|
margin-bottom: 0;
|
|
margin-bottom: 0;
|
|
@@ -542,7 +583,7 @@ export default {
|
|
&.active {
|
|
&.active {
|
|
border-color: #4a90e2;
|
|
border-color: #4a90e2;
|
|
color: #0054FF;
|
|
color: #0054FF;
|
|
- background: linear-gradient(to right,#CAE0FF,#E4EFFF);
|
|
|
|
|
|
+ background: linear-gradient(to right, #CAE0FF, #E4EFFF);
|
|
}
|
|
}
|
|
|
|
|
|
.expand-icon {
|
|
.expand-icon {
|
|
@@ -585,7 +626,7 @@ export default {
|
|
&.active {
|
|
&.active {
|
|
border-color: #0054FF;
|
|
border-color: #0054FF;
|
|
color: #4a90e2;
|
|
color: #4a90e2;
|
|
- background: linear-gradient(to right,#CAE0FF,#E4EFFF);
|
|
|
|
|
|
+ background: linear-gradient(to right, #CAE0FF, #E4EFFF);
|
|
font-weight: 500;
|
|
font-weight: 500;
|
|
}
|
|
}
|
|
}
|
|
}
|