Merge branch 'master' of http://192.168.195.14:3000/outsource/mi-assessment
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
commit
9c25b4ca35
46
server/MiAssessment/scripts/add_planner_booking_fields.sql
Normal file
46
server/MiAssessment/scripts/add_planner_booking_fields.sql
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
-- =============================================
|
||||
-- 规划预约表新增字段:所在地区、学校、学情
|
||||
-- =============================================
|
||||
|
||||
-- 省份
|
||||
IF NOT EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'[dbo].[planner_bookings]') AND name = 'Province')
|
||||
BEGIN
|
||||
ALTER TABLE [dbo].[planner_bookings] ADD [Province] NVARCHAR(50) NULL;
|
||||
PRINT N'Column Province added to planner_bookings';
|
||||
END
|
||||
GO
|
||||
|
||||
-- 城市
|
||||
IF NOT EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'[dbo].[planner_bookings]') AND name = 'City')
|
||||
BEGIN
|
||||
ALTER TABLE [dbo].[planner_bookings] ADD [City] NVARCHAR(50) NULL;
|
||||
PRINT N'Column City added to planner_bookings';
|
||||
END
|
||||
GO
|
||||
|
||||
-- 区县
|
||||
IF NOT EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'[dbo].[planner_bookings]') AND name = 'District')
|
||||
BEGIN
|
||||
ALTER TABLE [dbo].[planner_bookings] ADD [District] NVARCHAR(50) NULL;
|
||||
PRINT N'Column District added to planner_bookings';
|
||||
END
|
||||
GO
|
||||
|
||||
-- 学校
|
||||
IF NOT EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'[dbo].[planner_bookings]') AND name = 'School')
|
||||
BEGIN
|
||||
ALTER TABLE [dbo].[planner_bookings] ADD [School] NVARCHAR(100) NULL;
|
||||
PRINT N'Column School added to planner_bookings';
|
||||
END
|
||||
GO
|
||||
|
||||
-- 学情
|
||||
IF NOT EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'[dbo].[planner_bookings]') AND name = 'StudyInfo')
|
||||
BEGIN
|
||||
ALTER TABLE [dbo].[planner_bookings] ADD [StudyInfo] NVARCHAR(1000) NULL;
|
||||
PRINT N'Column StudyInfo added to planner_bookings';
|
||||
END
|
||||
GO
|
||||
|
||||
PRINT N'planner_bookings 新字段添加完成';
|
||||
GO
|
||||
|
|
@ -472,6 +472,13 @@ BEGIN
|
|||
[ScoreBiology] INT NULL, -- 生物成绩
|
||||
[ScoreGeography] INT NULL, -- 地理成绩
|
||||
[ScorePolitics] INT NULL, -- 政治成绩
|
||||
[FamilyAtmosphere] NVARCHAR(500) NULL, -- 家庭氛围
|
||||
[Expectation] NVARCHAR(500) NULL, -- 期望
|
||||
[Province] NVARCHAR(50) NULL, -- 省份
|
||||
[City] NVARCHAR(50) NULL, -- 城市
|
||||
[District] NVARCHAR(50) NULL, -- 区县
|
||||
[School] NVARCHAR(100) NULL, -- 学校
|
||||
[StudyInfo] NVARCHAR(1000) NULL, -- 学情
|
||||
[Status] INT NOT NULL DEFAULT 1, -- 状态:1待联系 2已联系 3已完成 4已取消
|
||||
[CreateTime] DATETIME2 NOT NULL DEFAULT GETDATE(),
|
||||
[UpdateTime] DATETIME2 NOT NULL DEFAULT GETDATE(),
|
||||
|
|
|
|||
|
|
@ -126,6 +126,36 @@ public class PlannerBooking
|
|||
[MaxLength(500)]
|
||||
public string? Expectation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 省份
|
||||
/// </summary>
|
||||
[MaxLength(50)]
|
||||
public string? Province { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 城市
|
||||
/// </summary>
|
||||
[MaxLength(50)]
|
||||
public string? City { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 区县
|
||||
/// </summary>
|
||||
[MaxLength(50)]
|
||||
public string? District { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 学校
|
||||
/// </summary>
|
||||
[MaxLength(100)]
|
||||
public string? School { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 学情
|
||||
/// </summary>
|
||||
[MaxLength(1000)]
|
||||
public string? StudyInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态:1待联系 2联系中 3已完成 4已取消
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,11 @@ public class AssessmentRecordDto
|
|||
/// </summary>
|
||||
public long UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户UID
|
||||
/// </summary>
|
||||
public string? UserUid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户昵称
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ namespace MiAssessment.Admin.Business.Models.AssessmentRecord;
|
|||
public class AssessmentRecordQueryRequest : PagedRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户ID
|
||||
/// 用户UID
|
||||
/// </summary>
|
||||
public long? UserId { get; set; }
|
||||
public string? Uid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 测评类型ID
|
||||
|
|
|
|||
|
|
@ -94,4 +94,29 @@ public class BookingDetailDto : BookingDto
|
|||
/// 期望
|
||||
/// </summary>
|
||||
public string? Expectation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 省份
|
||||
/// </summary>
|
||||
public string? Province { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 城市
|
||||
/// </summary>
|
||||
public string? City { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 区县
|
||||
/// </summary>
|
||||
public string? District { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 学校
|
||||
/// </summary>
|
||||
public string? School { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 学情
|
||||
/// </summary>
|
||||
public string? StudyInfo { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ public class AssessmentRecordService : IAssessmentRecordService
|
|||
{
|
||||
Id = r.Id,
|
||||
UserId = r.UserId,
|
||||
UserUid = r.User != null ? r.User.Uid : null,
|
||||
UserNickname = r.User != null ? r.User.Nickname : null,
|
||||
OrderId = r.OrderId,
|
||||
OrderNo = r.Order != null ? r.Order.OrderNo : null,
|
||||
|
|
@ -693,10 +694,10 @@ public class AssessmentRecordService : IAssessmentRecordService
|
|||
IQueryable<AssessmentRecord> query,
|
||||
AssessmentRecordQueryRequest request)
|
||||
{
|
||||
// 按用户ID筛选
|
||||
if (request.UserId.HasValue)
|
||||
// 按用户UID筛选
|
||||
if (!string.IsNullOrWhiteSpace(request.Uid))
|
||||
{
|
||||
query = query.Where(r => r.UserId == request.UserId.Value);
|
||||
query = query.Where(r => r.User != null && r.User.Uid == request.Uid);
|
||||
}
|
||||
|
||||
// 按测评类型ID筛选
|
||||
|
|
|
|||
|
|
@ -402,6 +402,11 @@ public class PlannerService : IPlannerService
|
|||
ScorePolitics = booking.ScorePolitics,
|
||||
FamilyAtmosphere = booking.FamilyAtmosphere,
|
||||
Expectation = booking.Expectation,
|
||||
Province = booking.Province,
|
||||
City = booking.City,
|
||||
District = booking.District,
|
||||
School = booking.School,
|
||||
StudyInfo = booking.StudyInfo,
|
||||
Status = booking.Status,
|
||||
StatusName = GetBookingStatusName(booking.Status),
|
||||
OrderAmount = booking.Order?.Amount,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import type { PagedRequest, PagedResult } from '@/types/common'
|
|||
export interface AssessmentRecordItem {
|
||||
id: number
|
||||
userId: number
|
||||
userUid: string | null
|
||||
userNickname: string | null
|
||||
orderId: number
|
||||
orderNo: string | null
|
||||
|
|
@ -92,7 +93,7 @@ export interface AssessmentReport extends AssessmentRecordItem {
|
|||
|
||||
/** 测评记录查询参数 */
|
||||
export interface AssessmentRecordQuery extends PagedRequest {
|
||||
userId?: number
|
||||
uid?: string
|
||||
assessmentTypeId?: number
|
||||
status?: number
|
||||
startDate?: string
|
||||
|
|
|
|||
|
|
@ -23,16 +23,16 @@
|
|||
<!-- 搜索表单 -->
|
||||
<el-card class="search-card">
|
||||
<el-form :model="queryParams" inline>
|
||||
<el-form-item label="用户ID">
|
||||
<el-form-item label="用户UID">
|
||||
<el-input
|
||||
v-model="queryParams.userId"
|
||||
placeholder="请输入用户ID"
|
||||
v-model="queryParams.uid"
|
||||
placeholder="请输入用户UID"
|
||||
clearable
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 160px">
|
||||
<el-option label="待支付" :value="0" />
|
||||
<el-option label="待测评" :value="1" />
|
||||
<el-option label="测评中" :value="2" />
|
||||
|
|
@ -40,6 +40,7 @@
|
|||
<el-option label="已完成" :value="4" />
|
||||
<el-option label="生成失败" :value="5" />
|
||||
<el-option label="数据已就绪" :value="6" />
|
||||
<el-option label="需重测" :value="7" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间">
|
||||
|
|
@ -72,10 +73,10 @@
|
|||
<el-table :data="state.tableData" row-key="id" stripe @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="50" />
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column label="用户" min-width="120">
|
||||
<el-table-column label="用户" min-width="140">
|
||||
<template #default="{ row }">
|
||||
<div>{{ row.userNickname || '-' }}</div>
|
||||
<div class="sub-text">ID: {{ row.userId }}</div>
|
||||
<div class="sub-text">UID: {{ row.userUid || '-' }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="被测评人" min-width="120">
|
||||
|
|
@ -452,7 +453,7 @@ const dateRange = ref<[string, string] | null>(null)
|
|||
const queryParams = reactive({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
userId: '',
|
||||
uid: '',
|
||||
status: undefined as number | undefined,
|
||||
startDate: undefined as string | undefined,
|
||||
endDate: undefined as string | undefined
|
||||
|
|
@ -551,8 +552,8 @@ async function loadRecordList() {
|
|||
page: queryParams.page,
|
||||
pageSize: queryParams.pageSize
|
||||
}
|
||||
if (queryParams.userId) {
|
||||
params.userId = Number(queryParams.userId)
|
||||
if (queryParams.uid) {
|
||||
params.uid = queryParams.uid
|
||||
}
|
||||
if (queryParams.status !== undefined) {
|
||||
params.status = queryParams.status
|
||||
|
|
@ -641,7 +642,7 @@ function handleSearch() {
|
|||
}
|
||||
|
||||
function handleReset() {
|
||||
queryParams.userId = ''
|
||||
queryParams.uid = ''
|
||||
queryParams.status = undefined
|
||||
queryParams.startDate = undefined
|
||||
queryParams.endDate = undefined
|
||||
|
|
@ -816,8 +817,8 @@ async function handleExport() {
|
|||
page: 1,
|
||||
pageSize: 10000
|
||||
}
|
||||
if (queryParams.userId) {
|
||||
params.userId = Number(queryParams.userId)
|
||||
if (queryParams.uid) {
|
||||
params.uid = queryParams.uid
|
||||
}
|
||||
if (queryParams.status !== undefined) {
|
||||
params.status = queryParams.status
|
||||
|
|
|
|||
|
|
@ -44,9 +44,9 @@
|
|||
<el-image
|
||||
v-if="row.configValue"
|
||||
:src="row.configValue"
|
||||
:preview-src-list="[row.configValue]"
|
||||
fit="contain"
|
||||
style="width: 60px; height: 60px;"
|
||||
style="width: 60px; height: 60px; cursor: pointer;"
|
||||
@click="handlePreviewImage(row.configValue)"
|
||||
/>
|
||||
<span v-else class="config-value" style="color: #909399; font-style: italic;">点击编辑上传图片</span>
|
||||
</template>
|
||||
|
|
@ -166,6 +166,17 @@
|
|||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<!-- 图片预览对话框 -->
|
||||
<el-dialog
|
||||
v-model="state.previewVisible"
|
||||
title="图片预览"
|
||||
width="500px"
|
||||
:append-to-body="true"
|
||||
>
|
||||
<div style="text-align: center;">
|
||||
<el-image :src="state.previewUrl" fit="contain" style="max-width: 100%; max-height: 60vh;" />
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -201,6 +212,8 @@ interface ConfigPageState {
|
|||
imageValue: string
|
||||
saving: boolean
|
||||
validationError: string
|
||||
previewVisible: boolean
|
||||
previewUrl: string
|
||||
}
|
||||
|
||||
// 富文本配置键列表
|
||||
|
|
@ -229,7 +242,9 @@ const state = reactive<ConfigPageState>({
|
|||
imageDialogVisible: false,
|
||||
imageValue: '',
|
||||
saving: false,
|
||||
validationError: ''
|
||||
validationError: '',
|
||||
previewVisible: false,
|
||||
previewUrl: ''
|
||||
})
|
||||
|
||||
// ============ Helper Functions ============
|
||||
|
|
@ -464,6 +479,14 @@ function handleImageCancel() {
|
|||
state.imageValue = ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览图片
|
||||
*/
|
||||
function handlePreviewImage(url: string) {
|
||||
state.previewUrl = url
|
||||
state.previewVisible = true
|
||||
}
|
||||
|
||||
async function handleImageSave() {
|
||||
if (!state.editingItem) return
|
||||
|
||||
|
|
|
|||
|
|
@ -115,6 +115,9 @@
|
|||
<el-descriptions-item label="学生姓名">{{ detailData.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学生年级">{{ detailData.gradeName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">{{ detailData.phone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所在地区">{{ [detailData.province, detailData.city, detailData.district].filter(Boolean).join(' ') || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学校">{{ detailData.school || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学情">{{ detailData.studyInfo || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业名称">{{ detailData.majorName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="语文成绩">{{ detailData.scoreChinese ?? '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="数学成绩">{{ detailData.scoreMath ?? '-' }}</el-descriptions-item>
|
||||
|
|
|
|||
|
|
@ -413,7 +413,7 @@ public class AssessmentController : ControllerBase
|
|||
/// <remarks>
|
||||
/// GET /api/assessment/getPendingRecord?typeId=1
|
||||
///
|
||||
/// 查询当前用户状态为待测评或测评中的最新测评记录,用于断点续答。
|
||||
/// 查询当前用户状态为待测评、测评中、生成失败或需重测的最新测评记录,用于断点续答和失败重测。
|
||||
/// 需要用户登录认证。
|
||||
/// </remarks>
|
||||
/// <param name="typeId">测评类型ID</param>
|
||||
|
|
@ -446,4 +446,55 @@ public class AssessmentController : ControllerBase
|
|||
return ApiResponse<PendingRecordDto>.Fail("查询进行中的测评记录失败");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重新测评(生成失败或需重测时免费重测)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// POST /api/assessment/retest
|
||||
///
|
||||
/// 将状态为生成失败(5)或需重测(7)的测评记录重置为待测评(1),
|
||||
/// 清除旧的答案、结果和结论数据,允许用户免费重新答题。
|
||||
/// 需要用户登录认证。
|
||||
/// </remarks>
|
||||
/// <param name="request">重测请求</param>
|
||||
/// <returns>重测结果</returns>
|
||||
[HttpPost("retest")]
|
||||
[Authorize]
|
||||
[ProducesResponseType(typeof(ApiResponse<bool>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ApiResponse<bool>), StatusCodes.Status401Unauthorized)]
|
||||
public async Task<ApiResponse<bool>> Retest([FromBody] RetestRequest request)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return ApiResponse<bool>.Unauthorized();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (request == null || request.RecordId <= 0)
|
||||
{
|
||||
return ApiResponse<bool>.Fail("测评记录ID无效");
|
||||
}
|
||||
|
||||
var result = await _assessmentService.RetestAsync(userId.Value, request.RecordId);
|
||||
return ApiResponse<bool>.Success(result);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Unauthorized retest, userId: {UserId}, recordId: {RecordId}", userId, request?.RecordId);
|
||||
return ApiResponse<bool>.Fail("无权限访问该测评记录", -1);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Invalid retest, userId: {UserId}, recordId: {RecordId}", userId, request?.RecordId);
|
||||
return ApiResponse<bool>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to retest, userId: {UserId}, recordId: {RecordId}", userId, request?.RecordId);
|
||||
return ApiResponse<bool>.Fail("重新测评失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,11 +116,23 @@ public interface IAssessmentService
|
|||
/// 获取用户进行中的测评记录
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 查询当前用户状态为待测评(1)或测评中(2)的最新测评记录。
|
||||
/// 用于页面加载时检测是否有未完成的测评,支持断点续答。
|
||||
/// 查询当前用户状态为待测评(1)、测评中(2)、生成失败(5)或需重测(7)的最新测评记录。
|
||||
/// 用于页面加载时检测是否有未完成的测评,支持断点续答和失败重测。
|
||||
/// </remarks>
|
||||
/// <param name="userId">当前用户ID</param>
|
||||
/// <param name="typeId">测评类型ID</param>
|
||||
/// <returns>进行中的测评记录,如果没有则返回null</returns>
|
||||
Task<PendingRecordDto?> GetPendingRecordAsync(long userId, long typeId);
|
||||
|
||||
/// <summary>
|
||||
/// 重新测评(生成失败或需重测时免费重测)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 将状态为生成失败(5)或需重测(7)的测评记录重置为待测评(1),
|
||||
/// 清除旧的答案、结果和结论数据,允许用户免费重新答题。
|
||||
/// </remarks>
|
||||
/// <param name="userId">当前用户ID</param>
|
||||
/// <param name="recordId">测评记录ID</param>
|
||||
/// <returns>重置是否成功</returns>
|
||||
Task<bool> RetestAsync(long userId, long recordId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -695,7 +695,7 @@ public class AssessmentService : IAssessmentService
|
|||
.AsNoTracking()
|
||||
.Where(r => r.UserId == userId
|
||||
&& r.AssessmentTypeId == typeId
|
||||
&& (r.Status == 1 || r.Status == 2 || r.Status == 7)
|
||||
&& (r.Status == 1 || r.Status == 2 || r.Status == 5 || r.Status == 7)
|
||||
&& !r.IsDeleted)
|
||||
.OrderByDescending(r => r.CreateTime)
|
||||
.Select(r => new PendingRecordDto
|
||||
|
|
@ -724,4 +724,81 @@ public class AssessmentService : IAssessmentService
|
|||
|
||||
return record;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> RetestAsync(long userId, long recordId)
|
||||
{
|
||||
_logger.LogInformation("重新测评请求,userId: {UserId}, recordId: {RecordId}", userId, recordId);
|
||||
|
||||
var record = await _dbContext.AssessmentRecords
|
||||
.Where(r => r.Id == recordId && !r.IsDeleted)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (record == null)
|
||||
{
|
||||
_logger.LogWarning("测评记录不存在,recordId: {RecordId}", recordId);
|
||||
throw new InvalidOperationException("测评记录不存在");
|
||||
}
|
||||
|
||||
if (record.UserId != userId)
|
||||
{
|
||||
_logger.LogWarning("测评记录不属于当前用户,userId: {UserId}, recordUserId: {RecordUserId}", userId, record.UserId);
|
||||
throw new UnauthorizedAccessException("无权限访问该测评记录");
|
||||
}
|
||||
|
||||
// 只有生成失败(5)和需重测(7)状态才允许重测
|
||||
if (record.Status != 5 && record.Status != 7)
|
||||
{
|
||||
_logger.LogWarning("测评记录状态不允许重测,status: {Status}, recordId: {RecordId}", record.Status, recordId);
|
||||
throw new InvalidOperationException("当前测评状态不允许重新测评");
|
||||
}
|
||||
|
||||
using var transaction = await _dbContext.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
// 清除旧答案
|
||||
var oldAnswers = await _dbContext.AssessmentAnswers
|
||||
.Where(a => a.RecordId == recordId)
|
||||
.ToListAsync();
|
||||
if (oldAnswers.Any())
|
||||
{
|
||||
_dbContext.AssessmentAnswers.RemoveRange(oldAnswers);
|
||||
}
|
||||
|
||||
// 清除旧结果
|
||||
var oldResults = await _dbContext.AssessmentResults
|
||||
.Where(r => r.RecordId == recordId)
|
||||
.ToListAsync();
|
||||
if (oldResults.Any())
|
||||
{
|
||||
_dbContext.AssessmentResults.RemoveRange(oldResults);
|
||||
}
|
||||
|
||||
// 清除旧结论
|
||||
var oldConclusions = await _dbContext.AssessmentRecordConclusions
|
||||
.Where(c => c.RecordId == recordId)
|
||||
.ToListAsync();
|
||||
if (oldConclusions.Any())
|
||||
{
|
||||
_dbContext.AssessmentRecordConclusions.RemoveRange(oldConclusions);
|
||||
}
|
||||
|
||||
// 重置状态为待测评
|
||||
record.Status = 1;
|
||||
record.SubmitTime = null;
|
||||
record.UpdateTime = DateTime.Now;
|
||||
|
||||
await _dbContext.SaveChangesAsync();
|
||||
await transaction.CommitAsync();
|
||||
|
||||
_logger.LogInformation("重新测评重置成功,recordId: {RecordId}, userId: {UserId}", recordId, userId);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await transaction.RollbackAsync();
|
||||
_logger.LogError(ex, "重新测评重置失败,recordId: {RecordId}", recordId);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -681,6 +681,11 @@ public class OrderService : IOrderService
|
|||
ScorePolitics = request.PlannerInfo.ScorePolitics,
|
||||
FamilyAtmosphere = request.PlannerInfo.FamilyAtmosphere,
|
||||
Expectation = request.PlannerInfo.Expectation,
|
||||
Province = request.PlannerInfo.Province,
|
||||
City = request.PlannerInfo.City,
|
||||
District = request.PlannerInfo.District,
|
||||
School = request.PlannerInfo.School,
|
||||
StudyInfo = request.PlannerInfo.StudyInfo,
|
||||
Status = 1, // 待确认
|
||||
CreateTime = now,
|
||||
UpdateTime = now,
|
||||
|
|
|
|||
|
|
@ -126,6 +126,36 @@ public class PlannerBooking
|
|||
[MaxLength(500)]
|
||||
public string? Expectation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 省份
|
||||
/// </summary>
|
||||
[MaxLength(50)]
|
||||
public string? Province { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 城市
|
||||
/// </summary>
|
||||
[MaxLength(50)]
|
||||
public string? City { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 区县
|
||||
/// </summary>
|
||||
[MaxLength(50)]
|
||||
public string? District { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 学校
|
||||
/// </summary>
|
||||
[MaxLength(100)]
|
||||
public string? School { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 学情
|
||||
/// </summary>
|
||||
[MaxLength(1000)]
|
||||
public string? StudyInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态:1待联系 2联系中 3已完成 4已取消
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
namespace MiAssessment.Model.Models.Assessment;
|
||||
|
||||
/// <summary>
|
||||
/// 重新测评请求
|
||||
/// </summary>
|
||||
public class RetestRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 测评记录ID
|
||||
/// </summary>
|
||||
public long RecordId { get; set; }
|
||||
}
|
||||
|
|
@ -157,6 +157,31 @@ public class PlannerInfoDto
|
|||
/// </summary>
|
||||
public string? Expectation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 省份
|
||||
/// </summary>
|
||||
public string? Province { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 城市
|
||||
/// </summary>
|
||||
public string? City { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 区县
|
||||
/// </summary>
|
||||
public string? District { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 学校
|
||||
/// </summary>
|
||||
public string? School { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 学情
|
||||
/// </summary>
|
||||
public string? StudyInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -89,6 +89,15 @@ export function getPendingRecord(typeId) {
|
|||
return get('/assessment/getPendingRecord', { typeId })
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新测评(生成失败或需重测时免费重测)
|
||||
* @param {number} recordId - 测评记录ID
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export function retestRecord(recordId) {
|
||||
return post('/assessment/retest', { recordId })
|
||||
}
|
||||
|
||||
export default {
|
||||
getIntro,
|
||||
getQuestionList,
|
||||
|
|
@ -98,5 +107,6 @@ export default {
|
|||
verifyInviteCode,
|
||||
getHistoryList,
|
||||
getScoreOptions,
|
||||
getPendingRecord
|
||||
getPendingRecord,
|
||||
retestRecord
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { ref, computed, onMounted } from 'vue'
|
|||
import { onShow, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
|
||||
import { useUserStore } from '@/store/user.js'
|
||||
import { useAuth } from '@/composables/useAuth.js'
|
||||
import { getHistoryList } from '@/api/assessment.js'
|
||||
import { getHistoryList, retestRecord } from '@/api/assessment.js'
|
||||
import Empty from '@/components/Empty/index.vue'
|
||||
import Loading from '@/components/Loading/index.vue'
|
||||
|
||||
|
|
@ -124,7 +124,7 @@ onReachBottom(() => {
|
|||
/**
|
||||
* 查看测评结果
|
||||
*/
|
||||
function viewResult(record) {
|
||||
async function viewResult(record) {
|
||||
// 已完成 - 查看报告
|
||||
if (record.status === ASSESSMENT_STATUS.COMPLETED) {
|
||||
uni.navigateTo({
|
||||
|
|
@ -158,23 +158,42 @@ function viewResult(record) {
|
|||
return
|
||||
}
|
||||
|
||||
// 需重测 - 跳转到测评首页重新开始
|
||||
// 需重测 - 调用重测接口后跳转答题页(免费重测)
|
||||
if (record.status === ASSESSMENT_STATUS.NEED_RETEST) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/assessment/info/index?typeId=${record.assessmentTypeId || 1}`
|
||||
})
|
||||
await handleRetestFromHistory(record)
|
||||
return
|
||||
}
|
||||
|
||||
// 生成失败 - 跳转到测评首页重新开始
|
||||
// 生成失败 - 调用重测接口后跳转答题页(免费重测)
|
||||
if (record.status === ASSESSMENT_STATUS.FAILED) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/assessment/info/index?typeId=${record.assessmentTypeId || 1}`
|
||||
})
|
||||
await handleRetestFromHistory(record)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从往期测评列表发起重测(免费)
|
||||
*/
|
||||
async function handleRetestFromHistory(record) {
|
||||
try {
|
||||
uni.showLoading({ title: '重置中...' })
|
||||
const res = await retestRecord(record.id)
|
||||
uni.hideLoading()
|
||||
|
||||
if (res && res.code === 0) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/assessment/questions/index?typeId=${record.assessmentTypeId || 1}&recordId=${record.id}`
|
||||
})
|
||||
} else {
|
||||
uni.showToast({ title: res?.message || '重置失败,请重试', icon: 'none' })
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading()
|
||||
console.error('重测重置失败:', error)
|
||||
uni.showToast({ title: '重置失败,请重试', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面显示时检查登录状态并加载数据
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@
|
|||
import {
|
||||
getIntro,
|
||||
verifyInviteCode,
|
||||
getPendingRecord
|
||||
getPendingRecord,
|
||||
retestRecord
|
||||
} from '@/api/assessment.js'
|
||||
import {
|
||||
createOrder
|
||||
|
|
@ -203,11 +204,26 @@
|
|||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* 弹窗提示文案(根据记录状态区分)
|
||||
*/
|
||||
const pendingPopupMessage = computed(() => {
|
||||
if (!pendingRecord.value) return ''
|
||||
const s = pendingRecord.value.status
|
||||
if (s === 5) return '您有一份生成失败的测评记录,可免费重新测评'
|
||||
if (s === 7) return '您有一份需要重新测评的记录,可免费重新测评'
|
||||
return '您有一份未完成的测评记录,是否继续?'
|
||||
})
|
||||
|
||||
/**
|
||||
* 底部按钮文案
|
||||
*/
|
||||
const payBtnText = computed(() => {
|
||||
if (isContinueMode.value) return '继续测评'
|
||||
if (isContinueMode.value) {
|
||||
const s = pendingRecord.value?.status
|
||||
if (s === 5 || s === 7) return '免费重新测评'
|
||||
return '继续测评'
|
||||
}
|
||||
return `支付¥${introData.value.price || 20}元开始测评`
|
||||
})
|
||||
|
||||
|
|
@ -329,8 +345,28 @@
|
|||
|
||||
// 继续测评模式:直接跳转到答题页
|
||||
if (isContinueMode.value && pendingRecord.value) {
|
||||
const r = pendingRecord.value
|
||||
|
||||
// 生成失败(5)或需重测(7)的记录,先调重测接口重置状态
|
||||
if (r.status === 5 || r.status === 7) {
|
||||
try {
|
||||
uni.showLoading({ title: '重置中...' })
|
||||
const res = await retestRecord(r.recordId)
|
||||
uni.hideLoading()
|
||||
if (!res || res.code !== 0) {
|
||||
uni.showToast({ title: res?.message || '重置失败,请重试', icon: 'none' })
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading()
|
||||
console.error('重测重置失败:', error)
|
||||
uni.showToast({ title: '重置失败,请重试', icon: 'none' })
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
uni.redirectTo({
|
||||
url: `/pages/assessment/questions/index?typeId=${typeId.value}&recordId=${pendingRecord.value.recordId}`
|
||||
url: `/pages/assessment/questions/index?typeId=${typeId.value}&recordId=${r.recordId}`
|
||||
})
|
||||
return
|
||||
}
|
||||
|
|
@ -613,7 +649,7 @@
|
|||
</view>
|
||||
<view class="popup-body">
|
||||
<view class="pending-msg">
|
||||
您有一份未完成的测评记录,是否继续?
|
||||
{{ pendingPopupMessage }}
|
||||
</view>
|
||||
<view class="pending-info">
|
||||
<text>姓名:{{ pendingRecord?.name }}</text>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { useUserStore } from '@/store/user.js'
|
||||
import { getResultStatus } from '@/api/assessment.js'
|
||||
import { getResultStatus, retestRecord } from '@/api/assessment.js'
|
||||
import Navbar from '@/components/Navbar/index.vue'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
|
@ -125,19 +125,37 @@ async function checkStatus() {
|
|||
}
|
||||
}
|
||||
|
||||
// 重测加载状态
|
||||
const retestLoading = ref(false)
|
||||
|
||||
/**
|
||||
* 重新测试
|
||||
* 重新测试(调用重测接口,免费重新答题)
|
||||
*/
|
||||
function handleRetest() {
|
||||
// 跳转到测评首页重新开始
|
||||
if (typeId.value) {
|
||||
uni.redirectTo({
|
||||
url: `/pages/assessment/info/index?typeId=${typeId.value}`
|
||||
})
|
||||
} else {
|
||||
uni.switchTab({
|
||||
url: '/pages/index/index'
|
||||
async function handleRetest() {
|
||||
if (retestLoading.value) return
|
||||
retestLoading.value = true
|
||||
|
||||
try {
|
||||
const res = await retestRecord(recordId.value)
|
||||
if (res && res.code === 0) {
|
||||
// 重置成功,直接跳转答题页
|
||||
uni.redirectTo({
|
||||
url: `/pages/assessment/questions/index?typeId=${typeId.value}&recordId=${recordId.value}`
|
||||
})
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res?.message || '重新测评失败,请稍后重试',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('重新测评失败:', error)
|
||||
uni.showToast({
|
||||
title: '重新测评失败,请稍后重试',
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
retestLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -191,8 +209,8 @@ onUnmounted(() => {
|
|||
|
||||
<!-- 重新测试按钮 -->
|
||||
<view v-if="needRetest" class="bottom-action">
|
||||
<view class="retest-btn" @click="handleRetest">
|
||||
<text>重新测试</text>
|
||||
<view class="retest-btn" :class="{ 'btn-loading': retestLoading }" @click="handleRetest">
|
||||
<text>{{ retestLoading ? '重置中...' : '重新测试' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
|
@ -301,5 +319,10 @@ onUnmounted(() => {
|
|||
&:active {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
&.btn-loading {
|
||||
opacity: 0.7;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -112,13 +112,24 @@
|
|||
</scroll-view>
|
||||
|
||||
<!-- 客服二维码弹窗 -->
|
||||
<Popup
|
||||
:visible="showQrPopup"
|
||||
:imageUrl="qrcodeUrl"
|
||||
title="扫码咨询"
|
||||
:content="qrcodeUrl ? '' : '暂未配置客服二维码'"
|
||||
@close="showQrPopup = false"
|
||||
/>
|
||||
<view v-if="showQrPopup" class="popup-mask" @click="showQrPopup = false">
|
||||
<view class="contact-popup" @click.stop>
|
||||
<view class="contact-popup-header">
|
||||
<text class="contact-popup-title">联系我们</text>
|
||||
<text class="contact-popup-close" @click="showQrPopup = false">✕</text>
|
||||
</view>
|
||||
<view class="contact-popup-body">
|
||||
<image
|
||||
v-if="qrcodeUrl"
|
||||
class="contact-qrcode"
|
||||
:src="qrcodeUrl"
|
||||
mode="widthFix"
|
||||
@click="handlePreviewQrcode"
|
||||
/>
|
||||
<text v-if="!qrcodeUrl" class="contact-tip">暂未配置客服二维码</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
|
@ -130,7 +141,6 @@ import { useNavbar } from '@/composables/useNavbar.js'
|
|||
import { getBannerList, getNavigationList } from '@/api/home.js'
|
||||
import { getContactInfo } from '@/api/system.js'
|
||||
import Loading from '@/components/Loading/index.vue'
|
||||
import Popup from '@/components/Popup/index.vue'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const { statusBarHeight, navbarHeight, totalNavbarHeight } = useNavbar()
|
||||
|
|
@ -186,6 +196,17 @@ async function loadMoreList() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览二维码图片
|
||||
*/
|
||||
function handlePreviewQrcode() {
|
||||
if (!qrcodeUrl.value) return
|
||||
uni.previewImage({
|
||||
urls: [qrcodeUrl.value],
|
||||
current: qrcodeUrl.value
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载客服二维码URL
|
||||
*/
|
||||
|
|
@ -477,4 +498,60 @@ onMounted(() => {
|
|||
height: 40rpx;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
// 联系我们弹窗
|
||||
.popup-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.contact-popup {
|
||||
width: 90%;
|
||||
background-color: $bg-white;
|
||||
border-radius: $border-radius-xl;
|
||||
overflow: hidden;
|
||||
|
||||
.contact-popup-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: $spacing-xl $spacing-xl 0;
|
||||
|
||||
.contact-popup-title {
|
||||
font-size: $font-size-lg;
|
||||
font-weight: $font-weight-bold;
|
||||
color: $text-color;
|
||||
}
|
||||
|
||||
.contact-popup-close {
|
||||
font-size: $font-size-xl;
|
||||
color: $text-placeholder;
|
||||
}
|
||||
}
|
||||
|
||||
.contact-popup-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: $spacing-xl;
|
||||
|
||||
.contact-qrcode {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.contact-tip {
|
||||
margin-top: $spacing-lg;
|
||||
font-size: $font-size-sm;
|
||||
color: $text-secondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -109,10 +109,9 @@
|
|||
<image
|
||||
class="contact-qrcode"
|
||||
:src="contactQrcodeUrl"
|
||||
mode="aspectFit"
|
||||
mode="widthFix"
|
||||
@click="handleSaveQrcode"
|
||||
/>
|
||||
<text class="contact-tip">长按识别或点击预览保存二维码</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
|
@ -523,7 +522,7 @@ onMounted(() => {
|
|||
|
||||
// 联系我们弹窗
|
||||
.contact-popup {
|
||||
width: 560rpx;
|
||||
width: 90%;
|
||||
background-color: $bg-white;
|
||||
border-radius: $border-radius-xl;
|
||||
overflow: hidden;
|
||||
|
|
@ -554,16 +553,9 @@ onMounted(() => {
|
|||
padding: $spacing-xl;
|
||||
|
||||
.contact-qrcode {
|
||||
width: 400rpx;
|
||||
height: 400rpx;
|
||||
width: 100%;
|
||||
border-radius: $border-radius-md;
|
||||
}
|
||||
|
||||
.contact-tip {
|
||||
margin-top: $spacing-lg;
|
||||
font-size: $font-size-sm;
|
||||
color: $text-placeholder;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { onShow, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
|
|||
import { useUserStore } from '@/store/user.js'
|
||||
import { useAuth } from '@/composables/useAuth.js'
|
||||
import { getOrderList } from '@/api/order.js'
|
||||
import { retestRecord } from '@/api/assessment.js'
|
||||
import Empty from '@/components/Empty/index.vue'
|
||||
import Loading from '@/components/Loading/index.vue'
|
||||
|
||||
|
|
@ -58,7 +59,7 @@ function getDisplayStatus(order) {
|
|||
|
||||
// 已支付/已完成的订单,根据测评记录状态显示
|
||||
if (assessmentStatus) {
|
||||
const map = { 1: '待测评', 2: '测评中', 3: '测评生成中', 4: '已测评', 5: '生成失败', 7: '待测评' }
|
||||
const map = { 1: '待测评', 2: '测评中', 3: '测评生成中', 4: '已测评', 5: '生成失败', 7: '需重测' }
|
||||
return map[assessmentStatus] || order.statusText || '已支付'
|
||||
}
|
||||
|
||||
|
|
@ -86,7 +87,7 @@ function getStatusClass(order) {
|
|||
const dst = getDisplayStatus(order)
|
||||
if (dst === '已测评' || dst === '待测评' || dst === '已完成') return 'status-green'
|
||||
if (dst === '测评生成中' || dst === '退款中' || dst === '已退款' || dst === '报告生成中') return 'status-red'
|
||||
if (dst === '待联系' || dst === '联系中') return 'status-orange'
|
||||
if (dst === '待联系' || dst === '联系中' || dst === '需重测' || dst === '生成失败') return 'status-orange'
|
||||
return 'status-gray'
|
||||
}
|
||||
|
||||
|
|
@ -171,13 +172,27 @@ function viewResult(order) {
|
|||
/**
|
||||
* 开始测评
|
||||
*/
|
||||
function startAssessment(order) {
|
||||
async function startAssessment(order) {
|
||||
const dst = getDisplayStatus(order)
|
||||
if (dst === '生成失败') {
|
||||
// 生成失败:跳转到测评首页重新开始
|
||||
uni.navigateTo({
|
||||
url: `/pages/assessment/info/index?typeId=${order.productId || 1}`
|
||||
})
|
||||
if (dst === '生成失败' || dst === '需重测') {
|
||||
// 生成失败或需重测:调用重测接口后跳转答题页(免费重测)
|
||||
try {
|
||||
uni.showLoading({ title: '重置中...' })
|
||||
const res = await retestRecord(order.assessmentRecordId)
|
||||
uni.hideLoading()
|
||||
|
||||
if (res && res.code === 0) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/assessment/questions/index?typeId=${order.productId || 1}&recordId=${order.assessmentRecordId}`
|
||||
})
|
||||
} else {
|
||||
uni.showToast({ title: res?.message || '重置失败,请重试', icon: 'none' })
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading()
|
||||
console.error('重测重置失败:', error)
|
||||
uni.showToast({ title: '重置失败,请重试', icon: 'none' })
|
||||
}
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: `/pages/assessment/questions/index?recordId=${order.assessmentRecordId}`
|
||||
|
|
@ -197,7 +212,7 @@ function showViewResultBtn(order) {
|
|||
*/
|
||||
function showStartBtn(order) {
|
||||
const dst = getDisplayStatus(order)
|
||||
return dst === '待测评' || dst === '生成失败'
|
||||
return dst === '待测评' || dst === '生成失败' || dst === '需重测'
|
||||
}
|
||||
|
||||
onShow(() => {
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ const timeOptions = [
|
|||
// 表单数据
|
||||
const formData = ref({
|
||||
name: '', phone: '', grade: '',
|
||||
province: '', city: '', district: '',
|
||||
school: '', studyInfo: '',
|
||||
majorName: '', familyAtmosphere: '', expectation: ''
|
||||
})
|
||||
|
||||
|
|
@ -114,10 +116,12 @@ const showSuccessPopup = ref(false)
|
|||
* 表单是否填写完整
|
||||
*/
|
||||
const isFormComplete = computed(() => {
|
||||
if (!selectedDate.value || !selectedTime.value) return false
|
||||
if (!formData.value.name.trim()) return false
|
||||
if (!formData.value.phone.trim()) return false
|
||||
if (!formData.value.grade) return false
|
||||
if (!formData.value.province) return false
|
||||
if (!formData.value.school.trim()) return false
|
||||
if (!formData.value.studyInfo.trim()) return false
|
||||
if (showScores.value) {
|
||||
if (!scores.value.chinese.trim() || !scores.value.math.trim() || !scores.value.english.trim()) return false
|
||||
}
|
||||
|
|
@ -211,13 +215,20 @@ function onGradeChange(e) {
|
|||
formData.value.majorName = ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 省市区选择
|
||||
*/
|
||||
function onRegionChange(e) {
|
||||
const value = e.detail.value
|
||||
formData.value.province = value[0] || ''
|
||||
formData.value.city = value[1] || ''
|
||||
formData.value.district = value[2] || ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证表单
|
||||
*/
|
||||
function validateForm() {
|
||||
if (!selectedDate.value || !selectedTime.value) {
|
||||
uni.showToast({ title: '请选择规划时间', icon: 'none' }); return false
|
||||
}
|
||||
if (!formData.value.name.trim()) {
|
||||
uni.showToast({ title: '请输入姓名', icon: 'none' }); return false
|
||||
}
|
||||
|
|
@ -227,6 +238,15 @@ function validateForm() {
|
|||
if (!formData.value.grade) {
|
||||
uni.showToast({ title: '请选择所在年级', icon: 'none' }); return false
|
||||
}
|
||||
if (!formData.value.province) {
|
||||
uni.showToast({ title: '请选择所在地区', icon: 'none' }); return false
|
||||
}
|
||||
if (!formData.value.school.trim()) {
|
||||
uni.showToast({ title: '请输入学校', icon: 'none' }); return false
|
||||
}
|
||||
if (!formData.value.studyInfo.trim()) {
|
||||
uni.showToast({ title: '请输入学情', icon: 'none' }); return false
|
||||
}
|
||||
if (showScores.value && (!scores.value.chinese.trim() || !scores.value.math.trim() || !scores.value.english.trim())) {
|
||||
uni.showToast({ title: '请填写必填科目成绩', icon: 'none' }); return false
|
||||
}
|
||||
|
|
@ -278,8 +298,13 @@ async function handleSubmit() {
|
|||
plannerInfo: {
|
||||
name: formData.value.name,
|
||||
phone: formData.value.phone,
|
||||
bookDateTime: `${selectedDate.value} ${selectedTime.value}`,
|
||||
bookDateTime: selectedDate.value && selectedTime.value ? `${selectedDate.value} ${selectedTime.value}` : null,
|
||||
grade: gradeMap[formData.value.grade] || 0,
|
||||
province: formData.value.province,
|
||||
city: formData.value.city,
|
||||
district: formData.value.district,
|
||||
school: formData.value.school,
|
||||
studyInfo: formData.value.studyInfo,
|
||||
majorName: showMajor.value ? formData.value.majorName : null,
|
||||
...scoreFields,
|
||||
familyAtmosphere: formData.value.familyAtmosphere,
|
||||
|
|
@ -317,9 +342,6 @@ onLoad(async (options) => {
|
|||
plannerId.value = Number(options.plannerId) || 0
|
||||
plannerName.value = decodeURIComponent(options.plannerName || '')
|
||||
userStore.restoreFromStorage()
|
||||
if (userStore.isLoggedIn && userStore.phone) {
|
||||
formData.value.phone = userStore.phone
|
||||
}
|
||||
await loadPlannerDetail()
|
||||
pageLoading.value = false
|
||||
})
|
||||
|
|
@ -330,22 +352,11 @@ onLoad(async (options) => {
|
|||
<view class="page-content">
|
||||
<!-- 主卡片 -->
|
||||
<view class="main-card">
|
||||
<!-- 规划时间 -->
|
||||
<view class="section-block">
|
||||
<!-- 规划时间(隐藏) -->
|
||||
<!-- <view class="section-block">
|
||||
<view class="section-title">我的规划时间</view>
|
||||
<picker
|
||||
mode="date"
|
||||
:start="getStartDate()"
|
||||
:end="getEndDate()"
|
||||
@change="onDateChange"
|
||||
>
|
||||
<view class="datetime-picker">
|
||||
<text class="picker-label">规划时间</text>
|
||||
<text v-if="!dateTimeDisplay" class="picker-placeholder">请选择 〉</text>
|
||||
<text v-else class="picker-value">{{ dateTimeDisplay }} 〉</text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
...
|
||||
</view> -->
|
||||
|
||||
<!-- 个人信息 -->
|
||||
<view class="section-block">
|
||||
|
|
@ -374,6 +385,36 @@ onLoad(async (options) => {
|
|||
</picker>
|
||||
</view>
|
||||
|
||||
<!-- 所在地区 -->
|
||||
<view class="field-group">
|
||||
<view class="field-label"><text class="required">*</text><text>所在地区</text></view>
|
||||
<picker mode="region" @change="onRegionChange">
|
||||
<view class="field-select">
|
||||
<text v-if="formData.province">{{ formData.province }} {{ formData.city }} {{ formData.district }}</text>
|
||||
<text v-else class="placeholder-text">请选择</text>
|
||||
<text class="select-arrow">∨</text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<!-- 学校 -->
|
||||
<view class="field-group">
|
||||
<view class="field-label"><text class="required">*</text><text>学校</text></view>
|
||||
<input class="field-input" type="text" placeholder="请输入" v-model="formData.school" maxlength="50" />
|
||||
</view>
|
||||
|
||||
<!-- 学情 -->
|
||||
<view class="field-group">
|
||||
<view class="field-label"><text class="required">*</text><text>学情</text></view>
|
||||
<textarea
|
||||
class="field-textarea"
|
||||
placeholder="请输入包括学科水平、学习状态、心态等方面的情况"
|
||||
v-model="formData.studyInfo"
|
||||
maxlength="500"
|
||||
:auto-height="false"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 各科最近成绩(小学/初中/高中) -->
|
||||
<view v-if="showScores" class="scores-section">
|
||||
<view class="section-subtitle">各科最近成绩</view>
|
||||
|
|
@ -544,6 +585,18 @@ onLoad(async (options) => {
|
|||
color: $text-color;
|
||||
}
|
||||
|
||||
.field-textarea {
|
||||
width: 100%;
|
||||
min-height: 180rpx;
|
||||
background-color: $bg-gray;
|
||||
border-radius: $border-radius-sm;
|
||||
padding: $spacing-lg;
|
||||
font-size: $font-size-md;
|
||||
color: $text-color;
|
||||
box-sizing: border-box;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.field-select {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user