在这里插入图片描述
现在前端运行后显示http://localhost:5173/页面,然后重定向到login页面,在这个过程中,会出现鉴权错误,进行提醒,然后跳转到登录页面,基于以下原因,我们进行修改
/
或 /dashboard
,未登录用户会看到空白页或报错(因为未鉴权)。/login
,确保用户从登录入口进入,逻辑更清晰。/
会先加载 MainLayout
,再跳转 /dashboard
,触发权限校验错误。App.vue
中用 router.push
)。routes
配置中一目了然)。添加以下代码:
const routes = [
{
path: '/login',
name: 'Login',
component: LoginVue,
meta: { title: '登录' }
},
{
path: '/',
redirect: '/login' // 添加这行
},
{
path: '/',
component: MainLayout,
redirect: '/dashboard',
children: [
// ...其他子路由保持不变
]
}
]
在这里插入图片描述
normal_mode_count
, professional_mode_count
, education_mode_count
)。创建统计表:
CREATE TABLE mode_selection_stats (
id SERIAL PRIMARY KEY,
normal_mode_count INT DEFAULT 0,
professional_mode_count INT DEFAULT 0,
education_mode_count INT DEFAULT 0,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
初始化数据(插入一行初始记录):
INSERT INTO mode_selection_stats (normal_mode_count, professional_mode_count, education_mode_count)
VALUES (0, 0, 0);
src/main/java/com/museum/
├── pojo/ModeStats.java # 实体类
├── mapper/ModeStatsMapper.java # MyBatis Mapper
├── service/
│ ├── ModeStatsService.java # 服务接口
│ └── impl/ModeStatsServiceImpl.java # 服务实现
└── controller/ModeStatsController.java # 控制器
pom.xml
)这个之前已经添加过了,之前没有添加过这些依赖的需要配置一下
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>org.postgresqlgroupId>
<artifactId>postgresqlartifactId>
<scope>runtimescope>
dependency>
dependencies>
application.yml
)这个之前也是已经添加过了,之前没有添加过这些依赖的需要配置一下
spring:
datasource:
url: jdbc:postgresql://your-neon-host:5432/your-db?sslmode=require
username: your-username
password: your-password
jpa:
hibernate:
ddl-auto: none # 禁止自动建表
ModeStats.java
)package com.museum.pojo;
import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("mode_selection_stats") // 指定表名
public class ModeStats {
@TableId(type = IdType.AUTO) // 自增主键
private Long id;
// 普通模式选择次数
@TableField("normal_mode_count")
private Integer normalModeCount = 0;
// 专业模式选择次数
@TableField("professional_mode_count")
private Integer professionalModeCount = 0;
// 教育模式选择次数
@TableField("education_mode_count")
private Integer educationModeCount = 0;
// 最后更新时间
@TableField(value = "updated_at", fill = FieldFill.INSERT_UPDATE) // 自动填充
private LocalDateTime updatedAt;
}
ModeDto.java
)package com.museum.dto;
import lombok.Data;
@Data // Lombok 注解,自动生成 getter/setter
public class ModeDto {
private String mode; // 必须与前端 JSON 的字段名一致
}
采用DTO:
Entity
(如 ModeStats
)作为接口参数/返回值时,会暴露数据库表结构,存在安全隐患(如敏感字段泄露)。Controller
中使用 @RequestParam
或 Map
接收参数时,校验逻辑分散且难以维护@RequestBody
接收 JSON,或通过 @ModelAttribute
接收表单数据,适配不同协议ModeStatsMapper.java
)使用原子操作,实现互斥
package com.museum.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.museum.pojo.ModeStats;
import org.apache.ibatis.annotations.Update;
public interface ModeStatsMapper extends BaseMapper<ModeStats> {
// 原子操作:普通模式计数器+1
@Update("UPDATE mode_selection_stats SET normal_mode_count = normal_mode_count + 1, updated_at = NOW() WHERE id = 1")
int incrementNormalMode();
// 专业模式计数器+1
@Update("UPDATE mode_selection_stats SET professional_mode_count = professional_mode_count + 1, updated_at = NOW() WHERE id = 1")
int incrementProfessionalMode();
// 教育模式计数器+1
@Update("UPDATE mode_selection_stats SET education_mode_count = education_mode_count + 1, updated_at = NOW() WHERE id = 1")
int incrementEducationMode();
}
ModeStatsController.java
)package com.museum.controller;
import com.museum.pojo.ModeStats;
import com.museum.service.ModeStatsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/mode-stats")
public class ModeStatsController {
@Autowired
private ModeStatsService modeStatsService;
@PostMapping("/update")
public String updateModeStats(@RequestParam String mode) {
boolean success = modeStatsService.updateModeStats(mode);
return success ? "更新成功" : "无效的模式类型";
}
@GetMapping("/current")
public ModeStats getModeStats() {
return modeStatsService.getModeStats();
}
}
ModeStatsService.java
)package com.museum.service;
import com.museum.pojo.ModeStats;
public interface ModeStatsService {
// 更新模式选择次数
boolean updateModeStats(String mode);
// 获取当前统计数据
ModeStats getModeStats();
}
ModeStatsServiceImpl.java
)package com.museum.service.impl;
import com.museum.mapper.ModeStatsMapper;
import com.museum.pojo.ModeStats;
import com.museum.service.ModeStatsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class ModeStatsServiceImpl implements ModeStatsService {
@Autowired
private ModeStatsMapper modeStatsMapper;
@Override
@Transactional
public boolean updateModeStats(String mode) {
try {
switch (mode) {
case "normal":
modeStatsMapper.incrementNormalMode();
break;
case "professional":
modeStatsMapper.incrementProfessionalMode();
break;
case "education":
modeStatsMapper.incrementEducationMode();
break;
default:
return false;
}
return true;
} catch (Exception e) {
throw new RuntimeException("更新模式统计失败", e);
}
}
@Override
public ModeStats getModeStats() {
return modeStatsMapper.selectById(1L); // 默认查询id=1的记录
}
}
Service
实现类中使用 @Transactional
保证计数器更新的原子性。RuntimeException
)。modeStatsMapper.selectById(1L)
查询数据。POST /api/mode-stats/update
更新统计GET /api/mode-stats/current
获取当前数据调整 confirmSelection
方法
async confirmSelection() {
if (!this.selectedMode) return;
uni.showLoading({ title: '提交中...' });
try {
// 调用 Spring Boot 后端 API
const response = await uni.request({
url: 'https://your-springboot-domain/api/update-mode-stats',
method: 'POST',
data: { mode: this.selectedMode },
header: { 'Content-Type': 'application/json' }
});
// 本地存储并跳转
uni.setStorageSync('selectedMode', this.selectedMode);
uni.redirectTo({ url: '/pages/ai_tour_guide/ai_tour_guide' });
} catch (err) {
uni.showToast({ title: '提交失败: ' + err.message, icon: 'none' });
} finally {
uni.hideLoading();
}
}
测试接口
POST /api/update-mode-stats?mode=normal
ModeStatsController
中添加新的接口@GetMapping("/distribution")
public Map<String, Integer> getModeDistribution() {
return modeStatsService.getModeDistribution();
}
ModeStatsService
接口中添加方法Map<String, Integer> getModeDistribution();
ModeStatsServiceImpl
中实现该方法@Override
public Map<String, Integer> getModeDistribution() {
ModeStats stats = getModeStats();
Map<String, Integer> distribution = new HashMap<>();
distribution.put("普通模式", stats.getNormalModeCount());
distribution.put("专业模式", stats.getProfessionalModeCount());
distribution.put("教育模式", stats.getEducationModeCount());
return distribution;
}
statisticsApi.js
中添加新的 API 方法// 获取模式分布数据
getModeDistribution: (params) => {
return request({
url: '/api/mode-stats/distribution',
method: 'get',
params
})
}
// 获取用户模式分布
const fetchUserRoles = async () => {
loading.userRoles = true;
try {
const result = await statisticsApi.getModeDistribution(dateRangeParams.value);
// 将后端返回的数据格式转换为前端图表需要的格式
chartData.userRoles = Object.entries(result).map(([name, value]) => ({
name,
value
}));
} catch (error) {
console.error('获取用户模式分布失败', error);
ElMessage.error('获取用户模式分布失败');
} finally {
loading.userRoles = false;
}
};
<chart-card title="用户模式分布" :loading="loading.userRoles" :refreshable="true" @refresh="refreshUserRoles">
<pie-chart :data="chartData.userRoles" height="300px" :doughnut="true"
:colors="['#1e88e5', '#26a69a', '#ffc107']" />
chart-card>
后端返回的是 Map
格式(如 {"普通模式":2,"教育模式":0,"专业模式":0}
)
{
普通模式: 2,
教育模式: 0,
专业模式: 0
}
前端 pie-chart
组件需要的是 Array<{name: string, value: number}>
格式
前端 错误地进入了 catch
块,导致数据无法正确渲染到饼图
[
{ name: "普通模式", value: 2 },
{ name: "教育模式", value: 0 },
{ name: "专业模式", value: 0 }
]
修改代码进行数据处理:
// 如果错误对象本身就是数据(如API返回非200但带数据)
if (error && typeof error === "object" && !Array.isArray(error)) {
chartData.userRoles = Object.entries(error).map(([name, value]) => ({
name,
value: Number(value) || 0,
}));
console.warn("成功:", chartData.userRoles);
}