fix: 成长档案 images JSON 格式化、排序与卡片排版
- 后端:GrowthRecordResponse.images 改为 List<String>,Mapper 解析 JSON - 后端:TeacherGrowthController 返回 GrowthRecordResponse 统一 images 格式 - 后端:分页按 updatedAt、createdAt 倒序排序 - 前端:教师/学校端成长档案卡片封面与排版修复(cover 约束、flex 布局) Made-with: Cursor
This commit is contained in:
parent
ac8e07c784
commit
7d659e87c8
@ -182,10 +182,10 @@
|
||||
<a-form-item label="图片">
|
||||
<a-upload
|
||||
v-model:file-list="fileList"
|
||||
:action="uploadUrl"
|
||||
:headers="uploadHeaders"
|
||||
:custom-request="handleCustomUpload"
|
||||
list-type="picture-card"
|
||||
:max-count="9"
|
||||
accept="image/*"
|
||||
@change="handleUploadChange"
|
||||
>
|
||||
<div v-if="fileList.length < 9">
|
||||
@ -273,6 +273,7 @@ import {
|
||||
} from '@ant-design/icons-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import type { FormInstance } from 'ant-design-vue';
|
||||
import { fileApi, validateFileType } from '@/api/file';
|
||||
|
||||
const loading = ref(false);
|
||||
const submitting = ref(false);
|
||||
@ -404,10 +405,41 @@ const handleModalOk = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleUploadChange = (info: any) => {
|
||||
if (info.file.status === 'done') {
|
||||
formState.images.push(info.file.response.url);
|
||||
/** OSS 直传:自定义上传 */
|
||||
const handleCustomUpload = async (options: any) => {
|
||||
const { file, onSuccess, onError, onProgress } = options;
|
||||
const uploadFile = file instanceof File ? file : (file?.originFileObj ?? file);
|
||||
|
||||
const isImage = uploadFile.type?.startsWith('image/');
|
||||
if (!isImage) {
|
||||
message.error('只能上传图片文件');
|
||||
onError?.(new Error('只能上传图片'));
|
||||
return;
|
||||
}
|
||||
|
||||
const validation = validateFileType(uploadFile, 'POSTER');
|
||||
if (!validation.valid) {
|
||||
message.error(validation.error);
|
||||
onError?.(new Error(validation.error));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await fileApi.uploadFile(uploadFile, 'resource', {
|
||||
onProgress: (percent) => onProgress?.({ percent }),
|
||||
});
|
||||
onSuccess?.({ url: result.filePath });
|
||||
} catch (err: any) {
|
||||
const msg = err?.message || '上传失败';
|
||||
message.error(msg);
|
||||
onError?.(new Error(msg));
|
||||
}
|
||||
};
|
||||
|
||||
const handleUploadChange = (info: any) => {
|
||||
formState.images = (info.fileList || [])
|
||||
.filter((f: any) => f.response?.url)
|
||||
.map((f: any) => f.response.url);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
@ -528,6 +560,8 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.record-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
@ -542,20 +576,30 @@ onMounted(() => {
|
||||
|
||||
.card-cover {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
height: 160px;
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
}
|
||||
|
||||
.cover-image {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
}
|
||||
|
||||
.cover-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.image-count {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
right: 8px;
|
||||
z-index: 2;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
@ -581,6 +625,7 @@ onMounted(() => {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
z-index: 2;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 11px;
|
||||
@ -605,7 +650,11 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.card-body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.record-title {
|
||||
@ -639,16 +688,24 @@ onMounted(() => {
|
||||
font-size: 13px;
|
||||
color: #636E72;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
margin: 0 0 12px 0;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
border-top: 1px solid #F0F0F0;
|
||||
background: #FAFAFA;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
|
||||
@ -24,24 +24,14 @@
|
||||
<!-- 操作栏 -->
|
||||
<div class="action-bar">
|
||||
<div class="filters">
|
||||
<a-select
|
||||
v-model:value="filters.classId"
|
||||
placeholder="选择班级"
|
||||
allow-clear
|
||||
style="width: 150px;"
|
||||
@change="handleFilter"
|
||||
>
|
||||
<a-select v-model:value="filters.classId" placeholder="选择班级" allow-clear style="width: 150px;"
|
||||
@change="handleFilter">
|
||||
<a-select-option v-for="cls in myClasses" :key="cls.id" :value="cls.id">
|
||||
{{ cls.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-input-search
|
||||
v-model:value="filters.keyword"
|
||||
placeholder="搜索标题"
|
||||
style="width: 200px;"
|
||||
@search="handleFilter"
|
||||
allow-clear
|
||||
/>
|
||||
<a-input-search v-model:value="filters.keyword" placeholder="搜索标题" style="width: 200px;" @search="handleFilter"
|
||||
allow-clear />
|
||||
</div>
|
||||
<a-button type="primary" class="add-btn" @click="showAddModal">
|
||||
<PlusOutlined class="btn-icon" />
|
||||
@ -51,14 +41,10 @@
|
||||
|
||||
<!-- 档案卡片网格 -->
|
||||
<div class="record-grid" v-if="!loading && records.length > 0">
|
||||
<div
|
||||
v-for="record in records"
|
||||
:key="record.id"
|
||||
class="record-card"
|
||||
>
|
||||
<div v-for="record in records" :key="record.id" class="record-card">
|
||||
<div class="card-cover">
|
||||
<div v-if="record.images?.length" class="cover-image">
|
||||
<img :src="getImageUrl(record.images[0])" alt="cover" />
|
||||
<div v-if="record.images?.length" class="cover-image ">
|
||||
<img :src="getImageUrl(record.images[0])" class="pos-absolute !object-contain " />
|
||||
<div class="image-count" v-if="record.images.length > 1">
|
||||
<CameraOutlined /> {{ record.images.length }}
|
||||
</div>
|
||||
@ -88,7 +74,8 @@
|
||||
<span>{{ formatDate(record.recordDate) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="record-content">{{ record.content?.substring(0, 80) }}{{ record.content?.length > 80 ? '...' : '' }}</p>
|
||||
<p class="record-content">{{ record.content?.substring(0, 80) }}{{ record.content?.length > 80 ? '...' : '' }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<a-button type="link" size="small" @click="handleView(record)">
|
||||
@ -124,12 +111,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 添加/编辑档案弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="modalVisible"
|
||||
width="700px"
|
||||
@ok="handleModalOk"
|
||||
:confirm-loading="submitting"
|
||||
>
|
||||
<a-modal v-model:open="modalVisible" width="700px" @ok="handleModalOk" :confirm-loading="submitting">
|
||||
<template #title>
|
||||
<span class="modal-title">
|
||||
<EditOutlined v-if="isEdit" class="modal-title-icon" />
|
||||
@ -137,20 +119,10 @@
|
||||
{{ isEdit ? ' 编辑档案' : ' 添加档案' }}
|
||||
</span>
|
||||
</template>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formState"
|
||||
:rules="rules"
|
||||
:label-col="{ span: 4 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<a-form ref="formRef" :model="formState" :rules="rules" :label-col="{ span: 4 }" :wrapper-col="{ span: 18 }">
|
||||
<a-form-item label="学生" name="studentId" v-if="!isEdit">
|
||||
<a-select
|
||||
v-model:value="formState.studentId"
|
||||
placeholder="请选择学生"
|
||||
show-search
|
||||
:filter-option="filterStudentOption"
|
||||
>
|
||||
<a-select v-model:value="formState.studentId" placeholder="请选择学生" show-search
|
||||
:filter-option="filterStudentOption">
|
||||
<a-select-option v-for="student in students" :key="student.id" :value="student.id">
|
||||
{{ student.name }} - {{ student.className }}
|
||||
</a-select-option>
|
||||
@ -158,36 +130,26 @@
|
||||
</a-form-item>
|
||||
<a-form-item label="档案类型" name="recordType" v-if="!isEdit">
|
||||
<a-radio-group v-model:value="formState.recordType">
|
||||
<a-radio value="STUDENT"><UserOutlined class="radio-icon" /> 个人档案</a-radio>
|
||||
<a-radio value="CLASS"><TeamOutlined class="radio-icon" /> 班级档案</a-radio>
|
||||
<a-radio value="STUDENT">
|
||||
<UserOutlined class="radio-icon" /> 个人档案
|
||||
</a-radio>
|
||||
<a-radio value="CLASS">
|
||||
<TeamOutlined class="radio-icon" /> 班级档案
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="标题" name="title">
|
||||
<a-input v-model:value="formState.title" placeholder="请输入档案标题" />
|
||||
</a-form-item>
|
||||
<a-form-item label="记录日期" name="recordDate">
|
||||
<a-date-picker
|
||||
v-model:value="formState.recordDateValue"
|
||||
style="width: 100%;"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
<a-date-picker v-model:value="formState.recordDateValue" style="width: 100%;" value-format="YYYY-MM-DD" />
|
||||
</a-form-item>
|
||||
<a-form-item label="内容" name="content">
|
||||
<a-textarea
|
||||
v-model:value="formState.content"
|
||||
placeholder="请输入档案内容"
|
||||
:rows="4"
|
||||
/>
|
||||
<a-textarea v-model:value="formState.content" placeholder="请输入档案内容" :rows="4" />
|
||||
</a-form-item>
|
||||
<a-form-item label="图片">
|
||||
<a-upload
|
||||
v-model:file-list="fileList"
|
||||
:action="uploadUrl"
|
||||
:headers="uploadHeaders"
|
||||
list-type="picture-card"
|
||||
:max-count="9"
|
||||
@change="handleUploadChange"
|
||||
>
|
||||
<a-upload v-model:file-list="fileList" :custom-request="handleCustomUpload" list-type="picture-card"
|
||||
:max-count="9" accept="image/*" @change="handleUploadChange">
|
||||
<div v-if="fileList.length < 9">
|
||||
<PlusOutlined />
|
||||
<div style="margin-top: 8px">上传</div>
|
||||
@ -198,11 +160,7 @@
|
||||
</a-modal>
|
||||
|
||||
<!-- 查看档案详情弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="detailModalVisible"
|
||||
width="700px"
|
||||
:footer="null"
|
||||
>
|
||||
<a-modal v-model:open="detailModalVisible" width="700px" :footer="null">
|
||||
<template #title>
|
||||
<span class="modal-title">
|
||||
<CameraOutlined class="modal-title-icon" />
|
||||
@ -231,12 +189,8 @@
|
||||
|
||||
<div v-if="currentRecord.images?.length" class="image-gallery">
|
||||
<a-image-preview-group>
|
||||
<a-image
|
||||
v-for="(img, index) in currentRecord.images"
|
||||
:key="index"
|
||||
:src="getImageUrl(img)"
|
||||
style="width: 100px; height: 100px; object-fit: cover; margin-right: 8px; border-radius: 8px;"
|
||||
/>
|
||||
<a-image v-for="(img, index) in currentRecord.images" :key="index" :src="getImageUrl(img)"
|
||||
style="width: 100px; height: 100px; object-fit: cover; margin-right: 8px; border-radius: 8px;" />
|
||||
</a-image-preview-group>
|
||||
</div>
|
||||
</div>
|
||||
@ -245,14 +199,9 @@
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-wrapper" v-if="records.length > 0">
|
||||
<a-pagination
|
||||
v-model:current="pagination.current"
|
||||
v-model:pageSize="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
:show-size-changer="true"
|
||||
:show-total="(total: number) => `共 ${total} 条`"
|
||||
@change="handlePageChange"
|
||||
/>
|
||||
<a-pagination v-model:current="pagination.current" v-model:pageSize="pagination.pageSize"
|
||||
:total="pagination.total" :show-size-changer="true" :show-total="(total: number) => `共 ${total} 条`"
|
||||
@change="handlePageChange" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -283,6 +232,7 @@ import {
|
||||
type UpdateGrowthRecordDto,
|
||||
} from '@/api/growth';
|
||||
import { getTeacherClasses, getTeacherStudents } from '@/api/teacher';
|
||||
import { fileApi, validateFileType } from '@/api/file';
|
||||
|
||||
const loading = ref(false);
|
||||
const submitting = ref(false);
|
||||
@ -309,9 +259,6 @@ const records = ref<GrowthRecord[]>([]);
|
||||
const currentRecord = ref<GrowthRecord | null>(null);
|
||||
const fileList = ref<any[]>([]);
|
||||
|
||||
const uploadUrl = '/api/upload';
|
||||
const uploadHeaders = {};
|
||||
|
||||
const formState = reactive<CreateGrowthRecordDto & { recordDateValue?: string }>({
|
||||
studentId: undefined as any,
|
||||
classId: undefined,
|
||||
@ -476,10 +423,41 @@ const handleModalOk = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleUploadChange = (info: any) => {
|
||||
if (info.file.status === 'done') {
|
||||
formState.images.push(info.file.response.url);
|
||||
/** OSS 直传:自定义上传 */
|
||||
const handleCustomUpload = async (options: any) => {
|
||||
const { file, onSuccess, onError, onProgress } = options;
|
||||
const uploadFile = file instanceof File ? file : (file?.originFileObj ?? file);
|
||||
|
||||
const isImage = uploadFile.type?.startsWith('image/');
|
||||
if (!isImage) {
|
||||
message.error('只能上传图片文件');
|
||||
onError?.(new Error('只能上传图片'));
|
||||
return;
|
||||
}
|
||||
|
||||
const validation = validateFileType(uploadFile, 'POSTER');
|
||||
if (!validation.valid) {
|
||||
message.error(validation.error);
|
||||
onError?.(new Error(validation.error));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await fileApi.uploadFile(uploadFile, 'resource', {
|
||||
onProgress: (percent) => onProgress?.({ percent }),
|
||||
});
|
||||
onSuccess?.({ url: result.filePath });
|
||||
} catch (err: any) {
|
||||
const msg = err?.message || '上传失败';
|
||||
message.error(msg);
|
||||
onError?.(new Error(msg));
|
||||
}
|
||||
};
|
||||
|
||||
const handleUploadChange = (info: any) => {
|
||||
formState.images = (info.fileList || [])
|
||||
.filter((f: any) => f.response?.url)
|
||||
.map((f: any) => f.response.url);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
@ -602,6 +580,8 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.record-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
@ -616,20 +596,30 @@ onMounted(() => {
|
||||
|
||||
.card-cover {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
height: 160px;
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
}
|
||||
|
||||
.cover-image {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
}
|
||||
|
||||
.cover-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.image-count {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
right: 8px;
|
||||
z-index: 2;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
@ -655,6 +645,7 @@ onMounted(() => {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
z-index: 2;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 11px;
|
||||
@ -679,7 +670,11 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.card-body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.record-title {
|
||||
@ -713,16 +708,24 @@ onMounted(() => {
|
||||
font-size: 13px;
|
||||
color: #636E72;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
margin: 0 0 12px 0;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
border-top: 1px solid #F0F0F0;
|
||||
background: #FAFAFA;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
|
||||
@ -3,6 +3,7 @@ package com.reading.platform.common.mapper;
|
||||
import com.reading.platform.dto.response.GrowthRecordResponse;
|
||||
import com.reading.platform.entity.GrowthRecord;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.List;
|
||||
@ -16,8 +17,9 @@ public interface GrowthRecordMapper {
|
||||
GrowthRecordMapper INSTANCE = Mappers.getMapper(GrowthRecordMapper.class);
|
||||
|
||||
/**
|
||||
* Entity 转 Response
|
||||
* Entity 转 Response(images 从 JSON 字符串解析为 List)
|
||||
*/
|
||||
@Mapping(target = "images", expression = "java(java.util.Arrays.asList(com.reading.platform.common.util.JsonUtils.parseStringArray(entity.getImages())))")
|
||||
GrowthRecordResponse toVO(GrowthRecord entity);
|
||||
|
||||
/**
|
||||
@ -26,7 +28,8 @@ public interface GrowthRecordMapper {
|
||||
List<GrowthRecordResponse> toVO(List<GrowthRecord> entities);
|
||||
|
||||
/**
|
||||
* Response 转 Entity(用于创建/更新时)
|
||||
* Response 转 Entity(images 从 List 转为 JSON 字符串)
|
||||
*/
|
||||
@Mapping(target = "images", expression = "java(vo.getImages() != null ? com.reading.platform.common.util.JsonUtils.toJson(vo.getImages()) : null)")
|
||||
GrowthRecord toEntity(GrowthRecordResponse vo);
|
||||
}
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
package com.reading.platform.controller.teacher;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.reading.platform.common.mapper.GrowthRecordMapper;
|
||||
import com.reading.platform.common.response.PageResult;
|
||||
import com.reading.platform.common.response.Result;
|
||||
import com.reading.platform.common.security.SecurityUtils;
|
||||
import com.reading.platform.dto.request.GrowthRecordCreateRequest;
|
||||
import com.reading.platform.dto.request.GrowthRecordUpdateRequest;
|
||||
import com.reading.platform.dto.response.GrowthRecordResponse;
|
||||
import com.reading.platform.entity.GrowthRecord;
|
||||
import com.reading.platform.service.GrowthRecordService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@ -14,6 +16,8 @@ import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Tag(name = "教师端 - 成长记录", description = "教师端成长记录 API")
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/teacher/growth-records")
|
||||
@ -21,37 +25,42 @@ import org.springframework.web.bind.annotation.*;
|
||||
public class TeacherGrowthController {
|
||||
|
||||
private final GrowthRecordService growthRecordService;
|
||||
private final GrowthRecordMapper growthRecordMapper;
|
||||
|
||||
@Operation(summary = "创建成长记录")
|
||||
@PostMapping
|
||||
public Result<GrowthRecord> createGrowthRecord(@Valid @RequestBody GrowthRecordCreateRequest request) {
|
||||
public Result<GrowthRecordResponse> createGrowthRecord(@Valid @RequestBody GrowthRecordCreateRequest request) {
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
Long userId = SecurityUtils.getCurrentUserId();
|
||||
return Result.success(growthRecordService.createGrowthRecord(tenantId, userId, "teacher", request));
|
||||
GrowthRecord record = growthRecordService.createGrowthRecord(tenantId, userId, "teacher", request);
|
||||
return Result.success(growthRecordMapper.toVO(record));
|
||||
}
|
||||
|
||||
@Operation(summary = "更新成长记录")
|
||||
@PutMapping("/{id}")
|
||||
public Result<GrowthRecord> updateGrowthRecord(@PathVariable Long id, @RequestBody GrowthRecordUpdateRequest request) {
|
||||
return Result.success(growthRecordService.updateGrowthRecord(id, request));
|
||||
public Result<GrowthRecordResponse> updateGrowthRecord(@PathVariable Long id, @RequestBody GrowthRecordUpdateRequest request) {
|
||||
GrowthRecord record = growthRecordService.updateGrowthRecord(id, request);
|
||||
return Result.success(growthRecordMapper.toVO(record));
|
||||
}
|
||||
|
||||
@Operation(summary = "根据 ID 获取成长记录")
|
||||
@GetMapping("/{id}")
|
||||
public Result<GrowthRecord> getGrowthRecord(@PathVariable Long id) {
|
||||
return Result.success(growthRecordService.getGrowthRecordById(id));
|
||||
public Result<GrowthRecordResponse> getGrowthRecord(@PathVariable Long id) {
|
||||
GrowthRecord record = growthRecordService.getGrowthRecordById(id);
|
||||
return Result.success(growthRecordMapper.toVO(record));
|
||||
}
|
||||
|
||||
@Operation(summary = "获取成长记录分页列表")
|
||||
@GetMapping
|
||||
public Result<PageResult<GrowthRecord>> getGrowthRecordPage(
|
||||
public Result<PageResult<GrowthRecordResponse>> getGrowthRecordPage(
|
||||
@RequestParam(required = false, defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(required = false, defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(required = false) Long studentId,
|
||||
@RequestParam(required = false) String type) {
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
Page<GrowthRecord> page = growthRecordService.getGrowthRecordPage(tenantId, pageNum, pageSize, studentId, type);
|
||||
return Result.success(PageResult.of(page));
|
||||
List<GrowthRecordResponse> voList = growthRecordMapper.toVO(page.getRecords());
|
||||
return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize()));
|
||||
}
|
||||
|
||||
@Operation(summary = "删除成长记录")
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.reading.platform.dto.request;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
@ -17,7 +18,8 @@ public class GrowthRecordCreateRequest {
|
||||
private Long studentId;
|
||||
|
||||
@NotBlank(message = "类型不能为空")
|
||||
@Schema(description = "类型:reading-阅读,behavior-行为,achievement-成就,milestone-里程碑")
|
||||
@JsonAlias("recordType")
|
||||
@Schema(description = "类型:reading-阅读,behavior-行为,achievement-成就,milestone-里程碑,STUDENT-学生记录")
|
||||
private String type;
|
||||
|
||||
@NotBlank(message = "标题不能为空")
|
||||
@ -27,8 +29,8 @@ public class GrowthRecordCreateRequest {
|
||||
@Schema(description = "内容")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "图片(JSON 数组)")
|
||||
private String images;
|
||||
@Schema(description = "图片 URL 列表")
|
||||
private List<String> images;
|
||||
|
||||
@Schema(description = "记录日期")
|
||||
private LocalDate recordDate;
|
||||
|
||||
@ -19,8 +19,8 @@ public class GrowthRecordUpdateRequest {
|
||||
@Schema(description = "内容")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "图片(JSON 数组)")
|
||||
private String images;
|
||||
@Schema(description = "图片 URL 列表")
|
||||
private List<String> images;
|
||||
|
||||
@Schema(description = "记录日期")
|
||||
private LocalDate recordDate;
|
||||
|
||||
@ -6,6 +6,7 @@ import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 成长记录响应
|
||||
@ -34,8 +35,8 @@ public class GrowthRecordResponse {
|
||||
@Schema(description = "内容")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "图片")
|
||||
private String images;
|
||||
@Schema(description = "图片URL列表")
|
||||
private List<String> images;
|
||||
|
||||
@Schema(description = "记录人 ID")
|
||||
private Long recordedBy;
|
||||
|
||||
@ -39,7 +39,13 @@ public class GrowthRecordServiceImpl extends ServiceImpl<GrowthRecordMapper, Gro
|
||||
record.setType(request.getType());
|
||||
record.setTitle(request.getTitle());
|
||||
record.setContent(request.getContent());
|
||||
record.setImages(request.getImages());
|
||||
if (request.getImages() != null) {
|
||||
try {
|
||||
record.setImages(objectMapper.writeValueAsString(request.getImages()));
|
||||
} catch (JsonProcessingException e) {
|
||||
record.setImages("[]");
|
||||
}
|
||||
}
|
||||
record.setRecordedBy(recorderId);
|
||||
record.setRecorderRole(recorderRole);
|
||||
record.setRecordDate(request.getRecordDate() != null ? request.getRecordDate() : LocalDate.now());
|
||||
@ -72,7 +78,11 @@ public class GrowthRecordServiceImpl extends ServiceImpl<GrowthRecordMapper, Gro
|
||||
record.setContent(request.getContent());
|
||||
}
|
||||
if (request.getImages() != null) {
|
||||
record.setImages(request.getImages());
|
||||
try {
|
||||
record.setImages(objectMapper.writeValueAsString(request.getImages()));
|
||||
} catch (JsonProcessingException e) {
|
||||
record.setImages("[]");
|
||||
}
|
||||
}
|
||||
if (request.getRecordDate() != null) {
|
||||
record.setRecordDate(request.getRecordDate());
|
||||
@ -127,7 +137,8 @@ public class GrowthRecordServiceImpl extends ServiceImpl<GrowthRecordMapper, Gro
|
||||
if (StringUtils.hasText(type)) {
|
||||
wrapper.eq(GrowthRecord::getType, type);
|
||||
}
|
||||
wrapper.orderByDesc(GrowthRecord::getRecordDate);
|
||||
// 按最新修改时间、最新创建时间倒序
|
||||
wrapper.orderByDesc(GrowthRecord::getUpdatedAt).orderByDesc(GrowthRecord::getCreatedAt);
|
||||
|
||||
return growthRecordMapper.selectPage(page, wrapper);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user