mohのAI正在绞尽脑汁想思路ING···
mohのAI摘要
mohのAI-Lite

前端篇

文件上传组件

上传附件 返回附件的url
属性值

uploadMode: 可以指定上传模式,默认为file; 其中可选 file || video || drag || image

1
2
3
4
5
6
7
8
9
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="封面" name="workCoverUrl">
<xn-upload
v-model:value="formData.workCoverUrl"
uploadMode="image"
uploadText="上传封面"
/>
</a-form-item>
</a-col>
附件上传获取附件返回的详情信息
注意

下面是获取附件上传返回的详情信息 如文件上传名称、文件格式、文件大小等信息 通过@onSuccessful的函数进行赋值
注意: :uploadNumber=1 是指定文件上传数量

1
2
3
4
5
6
7
8
<a-form-item label="附件上传">
<xn-upload
v-model:value="fileData"
uploadResultCategory="array"
:uploadNumber=1
@onSuccessful="handleUploadSuccess"
/>
</a-form-item>
1
2
3
4
5
6
7
8
9
// 上传成功后的处理
const handleUploadSuccess = (data) => {
if (data && data.length > 0) {
formData.value.fileUrl = data[0].response?.data || data[0].url || ''
formData.value.fileName = data[0].name || ''
formData.value.fileType = data[0].name ? data[0].name.split('.').pop() : ''
formData.value.fileSize = data[0].size ? formatFileSize(data[0].size) : ''
}
}

下拉框

字典类型的下拉框
属性值

注意: EDU_LEVEL 替换成对应的字典值

1
import tool from '@/utils/tool'
1
2
3
const gradeLevelNameOptions = ref([]);

const gradeLevelNameOptions = tool.dictList('EDU_LEVEL')
1
2
3
4
<a-select 
v-model:value="searchFormState.gradeLevelName"
placeholder="请选择学段"
:options="gradeLevelNameOptions" />

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<a-col :xs="8" :sm="8" :md="8" :lg="8" :xl="8">
<a-form-item label="学段" name="gradeLevelName">
<a-select v-model:value="formData.gradeLevelName" placeholder="请选择学段" :options="gradeLevelNameOptions" allow-clear />
</a-form-item>
</a-col>
<a-col :xs="8" :sm="8" :md="8" :lg="8" :xl="8">
<a-form-item label="年级" name="gradeName">
<a-select v-model:value="formData.gradeName" placeholder="请选择年级" :options="gradeNameOptions" allow-clear />
</a-form-item>
</a-col>
<a-col :xs="8" :sm="8" :md="8" :lg="8" :xl="8">
<a-form-item label="学科" name="subjectName">
<a-select v-model:value="formData.subjectName" placeholder="请选择学科" :options="subjectNameOptions" allow-clear />
</a-form-item>
</a-col>
1
2
3
4
5
6
7
8
9
10
11

// 字典选项
const gradeLevelNameOptions = ref([])
const gradeNameOptions = ref([])
const subjectNameOptions = ref([])

const initOptions = () => {
gradeLevelNameOptions.value = tool.dictList('EDU_LEVEL')
gradeNameOptions.value = tool.dictList('YEAR_PERIOD')
subjectNameOptions.value = tool.dictList('SUBJECT')
}
自定义key和value的下拉框
属性值

这里就是将后端返回的值都赋值给了itemTypeData 后面通过:field-names属性指定key和value进行复制
这里的 @change="handleItemTypeChange" 是来监听下拉框的change事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="作品类型" name="itemTypeId">
<a-select
v-model:value="formData.itemTypeId"
show-search
style="width: 100%"
placeholder="请选择作品类型"
allow-clear
:options="itemTypeData"
:field-names="{ value: 'itemTypeId', label: 'itemTypeName' }"
@change="handleItemTypeChange"
>
</a-select>
</a-form-item>
</a-col>

1
2
3
4
5
6
7
8
9
10
// 加载作品类型数据
const loadItemTypeData = () => {
trfrontProjectApi.itemTypeListByProjectId({ projectId: projectId.value }).then((res) => {
console.log('作品类型数据:', res)
itemTypeData.value = res || []
}).catch((error) => {
console.error('获取作品类型失败:', error)
itemTypeData.value = []
})
}
1
2
3
4
5
6
7
// 处理作品类型选择变化
const handleItemTypeChange = (value) => {
const selectedItem = itemTypeData.value.find(item => item.itemTypeId === value)
if (selectedItem) {
formData.value.itemTypeName = selectedItem.itemTypeName
}
}

其他

Ant Design Vue Modal 防止误关闭配置
使用场景

在表单编辑场景中,用户填写表单时,误点击模态框外部区域可能导致、表单意外关闭、已填写数据丢失、用户需要重新输入、影响用户体验 因此需要阻止模态框外部区域点击关闭模态框

属性: :maskClosable="false" 添加这个属性即可

1
2
3
4
5
6
7
<a-modal
v-model:open="open"
title="编辑教师信息"
:width="800"
:footer="null"
:maskClosable="false" <!-- 关键属性 -->
>
路由参数的接收和路由页面的跳转
使用场景

在开发过程中,经常需要进行页面之间的跳转以及页面之间传递数据,例如从列表页点击”编辑”按钮跳转到详情页并携带id,
或者在详情页获取url中的参数来请求数据等场景

基础引入
1
2
3
4
import { useRoute, useRouter } from "vue-router";

const route = useRoute() // 用于获取当前路由信息(接收参数)
const router = useRouter() // 用于进行路由跳转(发送参数)
路由跳转

方式1:path 跳转 + query 传参(URL可见参数)

1
2
3
4
5
6
7
8
9
10
// 跳转并传递参数(参数会显示在 URL 中)
router.push({
path: '/targetPage',
query: {
id: '123',
name: '张三',
type: 'edit'
}
})
// URL 效果: /targetPage?id=123&name=张三&type=edit

方式2:name 跳转 + params 传参(URL不可见参数)

1
2
3
4
5
6
7
8
9
// 跳转并传递参数(参数不会显示在 URL 中,需配合动态路由使用)
router.push({
name: 'TargetPageName',
params: {
id: '123'
}
})
// 注意:使用 params 时,需要在路由配置中定义动态路径如:/targetPage/:id

方式3:字符串简写形式

1
2
3
4
5
// 简单跳转
router.push('/home')

// 带 query 参数的简写
router.push('/detail?id=123&name=测试')
路由接收(获取参数)

接收 query 参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { useRoute } from "vue-router";

const route = useRoute()

// 获取单个参数
const id = route.query.id
const name = route.query.name

// 在 onMounted 中使用
onMounted(() => {
if (route.query.id) {
// 根据id请求详情数据
loadDetailData(route.query.id)
}
})

接收 params 参数

1
2
3
4
5
6
7
import { useRoute } from "vue-router";

const route = useRoute()

// 获取 params 参数
const id = route.params.id

后端篇

将实体类列表转换为map列表

属性值

通过BeanUtil.beanToMap(实体类) 转换为map的形式

1
2
3
4
5
6
7
8
public List<Map<String, Object>> itemTypeListByProjectId(String projectId) {
List<Map<String, Object>> result = new ArrayList<>();
List<TrcProjectItemType> list = trcProjectItemTypeService.list(new LambdaQueryWrapper<TrcProjectItemType>().eq(TrcProjectItemType::getProjectId, projectId));
for (TrcProjectItemType project : list) {
result.add(BeanUtil.beanToMap(project));
}
return result;
}

获取当前登录人的userId

1
2
SaBaseLoginUser loginUser = StpLoginUserUtil.getLoginUser();
String userId = loginUser.getId();

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
public class TeacherMessageUtil {
@Resource
private AuthApi authApi;

public String getTeacherId() {
SaBaseLoginUser loginUser = StpLoginUserUtil.getLoginUser();
String userId = loginUser.getId();

AuthThirdUserVo authThirdUserVo = authApi.queryByUserId(userId);
if(ObjectUtil.isEmpty(authThirdUserVo)){
return null;
}
String teacherId = authThirdUserVo.getThirdId();
return teacherId;
}
}

跨模块调用:实体类与Map转换的解决方案

碎碎念

最近在开发教研系统过程中,由于我开发的是前台部分(snowy-plugin-trfont)模块,这时候前台数据需要获取教师的数据,而教师的数据在(snowy-pugin-trc)模块下,
因此我需要在(snowy-plugin-trc-api)模块下调用(snowy-plugin-trc)模块下的接口,这时候就设计到一个问题就是(snowy-plugin-trc-api)模块不能直接引用(snowy-pugin-trc)模块
的实体类,这时候需要有两种方式来解决?第一种就是在(snowy-plugin-trc-api)模块下定义对应的实体类来接受,第二种就是通过map来进行转换,我采用的是第二种,比较方便

核心代码

Map转换实体类(入参)

1
2
3
4
5
// 创建目标实体类对象
TrcProjectPageParam trcProjectPageParam = new TrcProjectPageParam();

// 使用BeanUtil进行属性拷贝(需保证Map的key与实体类字段名对应)
BeanUtil.copyProperties(param, trcProjectPageParam);

实体类转Map(出参)

1
2
3
4
5
6
7
// 单个实体转换
BeanUtil.beanToMap(project);

// 批量转换示例
page.getRecords().stream()
.map(BeanUtil::beanToMap)
.collect(Collectors.toList());

完整代码示例

Controller层(调用方)

1
2
3
4
5
6
7
8
9
10
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;

/**
* 获取校验比赛分页查询
*/
@GetMapping("/trfront/project/pageProject")
public CommonResult<Page<Map<String, Object>>> pageProject(@RequestParam Map<String, Object> request) {
// 调用API模块,传递Map参数
return CommonResult.data(trcProjectApi.pageProject(request));
}

API接口定义

1
2
3
4
5
6
/**
* 分页获取项目列表
* @param param 分页参数(Map格式)
* @return 项目列表(Map格式的分页数据)
*/
Page<Map<String, Object>> pageProject(Map<String, Object> param);

API实现层(关键转换逻辑)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Override
public Page<Map<String, Object>> pageProject(Map<String, Object> param) {
// 1. Map参数 → 实体类参数(入参转换)
TrcProjectPageParam trcProjectPageParam = new TrcProjectPageParam();
BeanUtil.copyProperties(param, trcProjectPageParam);

// 2. 使用实体类参数进行业务查询
Page<TrcProject> page = trcProjectService.page(trcProjectPageParam);

// 3. 准备结果集
List<Map<String, Object>> result = new ArrayList<>();

// 4. 实体类结果 → Map结果(出参转换)
for (TrcProject project : page.getRecords()) {
result.add(BeanUtil.beanToMap(project));
}

// 5. 重新构建分页对象(保持分页信息)
Page<Map<String, Object>> pageResult = new Page<>(
page.getCurrent(),
page.getSize(),
page.getTotal()
);
pageResult.setRecords(result);

return pageResult;
}

自定义sql 如何实现分页功能

Page page = CommonPageRequest.defaultPage(); 这个是snowy框架封装的方法 直接调用即可,T 为对应的数据结构

核心代码

service层

1
2
3
4
5
6
7
8
9
10
Page<T> page = CommonPageRequest.defaultPage();

@Resource
private SchoolMapper schoolMapper;

public Page<SchoolMessage> pageSchool(SchoolPageParm schoolPageParm){
Page<SchoolMessage> page = CommonPageRequest.defaultPage();
Page<SchoolMessage> schoolMessageList = schoolMapper.pageSchoolMessage(page, schoolPageParm);
}

mapper层

1
2
Page<SchoolMessage> pageSchoolMessage(@Param("page") Page<SchoolMessage> page, @Param("param") SchoolPageParm schoolPageParm);

mapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- 分页查询学校信息 -->
<select id="pageSchoolMessage" resultType="com.example.school.dto.response.SchoolMessage">
SELECT
id,
school_name AS schoolName,
address,
phone,
principal,
create_time AS createTime,
update_time AS updateTime
FROM t_school
WHERE is_deleted = 0
<if test="param.schoolName != null and param.schoolName != ''">
AND school_name LIKE CONCAT('%', #{param.schoolName}, '%')
</if>
<if test="param.address != null and param.address != ''">
AND address LIKE CONCAT('%', #{param.address}, '%')
</if>
<if test="param.principal != null and param.principal != ''">
AND principal LIKE CONCAT('%', #{param.principal}, '%')
</if>
</select>