界面优化
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
18631081161 2026-04-07 19:58:30 +08:00
parent 5f46609ece
commit 34030cf2ea
18 changed files with 390 additions and 61 deletions

View 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

View File

@ -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(),

View File

@ -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>

View File

@ -15,6 +15,11 @@ public class AssessmentRecordDto
/// </summary>
public long UserId { get; set; }
/// <summary>
/// 用户UID
/// </summary>
public string? UserUid { get; set; }
/// <summary>
/// 用户昵称
/// </summary>

View File

@ -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

View File

@ -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; }
}

View File

@ -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筛选

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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>

View File

@ -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>

View File

@ -124,7 +124,7 @@ onReachBottom(() => {
/**
* 查看测评结果
*/
function viewResult(record) {
async function viewResult(record) {
// -
if (record.status === ASSESSMENT_STATUS.COMPLETED) {
uni.navigateTo({

View File

@ -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>

View File

@ -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>

View File

@ -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,
@ -330,22 +355,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 +388,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 +588,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;