心理咨询预约移动端
This commit is contained in:
1 parent
f256e580dc
commit
69b73a9373
7 files changed
+2752
-3
No files matched your search
+17
-1
@@ -431,7 +431,23 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation
|
||||
slot.put("availableCount", totalDetailSlots - bookedCount); // 可用数量
|
||||
slot.put("isExpired", isExpired); // 是否已过期
|
||||
|
||||
// TODO: 添加咨询师姓名等信息
|
||||
// 查询咨询师姓名
|
||||
if (schedule.getCounselorId() != null) {
|
||||
try {
|
||||
String sql = "SELECT nick_name FROM sys_user WHERE user_id = ?";
|
||||
List<Map<String, Object>> result = jdbcTemplate.queryForList(sql, schedule.getCounselorId());
|
||||
if (!result.isEmpty()) {
|
||||
slot.put("counselorName", result.get(0).get("nick_name"));
|
||||
} else {
|
||||
slot.put("counselorName", null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("查询咨询师姓名失败, counselorId: {}, error: {}", schedule.getCounselorId(), e.getMessage());
|
||||
slot.put("counselorName", null);
|
||||
}
|
||||
} else {
|
||||
slot.put("counselorName", null);
|
||||
}
|
||||
|
||||
availableSlots.add(slot);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
import { request } from '@/common/request';
|
||||
|
||||
/**
|
||||
* 心理健康咨询预约 API
|
||||
* 移动端API接口 - 基于web端功能移植
|
||||
*/
|
||||
|
||||
// ===== 预约时段管理 API =====
|
||||
|
||||
/**
|
||||
* 获取可预约时段列表(用于日历视图)
|
||||
* @param {string} startDate 开始日期 YYYY-MM-DD
|
||||
* @param {string} endDate 结束日期 YYYY-MM-DD
|
||||
*/
|
||||
export const getAvailableTimeSlots = (startDate, endDate) => {
|
||||
return request({
|
||||
url: '/xljk/consultation/appointment/available-slots',
|
||||
method: 'GET',
|
||||
params: { startDate, endDate }
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取可用的详细时段列表(30分钟一个时段)
|
||||
* @param {string} date 日期 YYYY-MM-DD
|
||||
* @param {string} timeSlot 时段 如 "09:00-12:00"
|
||||
*/
|
||||
export const getAvailableDetailSlots = (date, timeSlot) => {
|
||||
return request({
|
||||
url: '/xljk/consultation/appointment/available-detail-slots',
|
||||
method: 'GET',
|
||||
params: { date, timeSlot }
|
||||
});
|
||||
};
|
||||
|
||||
// ===== 预约记录管理 API =====
|
||||
|
||||
/**
|
||||
* 学生提交预约
|
||||
* @param {object} data 预约数据
|
||||
*/
|
||||
export const submitAppointment = (data) => {
|
||||
return request({
|
||||
url: '/xljk/consultation/appointment/submit',
|
||||
method: 'POST',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取我的预约记录(学生用)
|
||||
* @param {string|number} studentId 学生ID
|
||||
* @param {boolean} forceRefresh 是否强制刷新
|
||||
*/
|
||||
export const getMyAppointments = (studentId, forceRefresh = false) => {
|
||||
let url = `/xljk/consultation/appointment/my-appointments/${studentId}`;
|
||||
|
||||
// 强制添加时间戳参数,确保绕过浏览器缓存
|
||||
url += `?_t=${new Date().getTime()}`;
|
||||
|
||||
// 如果需要额外强制刷新
|
||||
if (forceRefresh) {
|
||||
url += `&force=true`;
|
||||
}
|
||||
|
||||
return request({
|
||||
url,
|
||||
method: 'GET',
|
||||
header: {
|
||||
// 禁止所有缓存
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
'Pragma': 'no-cache',
|
||||
'Expires': '0'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询预约记录详细信息
|
||||
* @param {string|number} id 预约记录ID
|
||||
*/
|
||||
export const getAppointmentDetail = (id) => {
|
||||
return request({
|
||||
url: `/xljk/consultation/appointment/${id}`,
|
||||
method: 'GET'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 取消预约(更新状态为已取消,不删除记录)
|
||||
* @param {string|number} id 预约记录ID
|
||||
*/
|
||||
export const cancelAppointment = (id) => {
|
||||
return request({
|
||||
url: `/xljk/consultation/appointment`,
|
||||
method: 'PUT',
|
||||
data: {
|
||||
id: id,
|
||||
status: 2 // 已取消状态
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新预约信息(通用方法)
|
||||
* @param {object} data 更新数据,必须包含id字段
|
||||
*/
|
||||
export const updateAppointment = (data) => {
|
||||
return request({
|
||||
url: `/xljk/consultation/appointment`,
|
||||
method: 'PUT',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// ===== 选项数据 API =====
|
||||
|
||||
/**
|
||||
* 获取咨询师选项
|
||||
*/
|
||||
export const getCounselorOptions = () => {
|
||||
return request({
|
||||
url: '/xljk/consultation/schedule/options/counselors',
|
||||
method: 'GET'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取学生信息(通过学号)
|
||||
* @param {string} studentNo 学号
|
||||
*/
|
||||
export const getStudentByNo = (studentNo) => {
|
||||
return request({
|
||||
url: `/xljk/consultation/student/${studentNo}`,
|
||||
method: 'GET'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取院系选项
|
||||
*/
|
||||
export const getCollegeOptions = () => {
|
||||
return request({
|
||||
url: '/xljk/consultation/appointment/options/colleges',
|
||||
method: 'GET'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取专业选项
|
||||
* @param {string} collegeName 院系名称(可选)
|
||||
*/
|
||||
export const getMajorOptions = (collegeName) => {
|
||||
return request({
|
||||
url: '/xljk/consultation/appointment/options/majors',
|
||||
method: 'GET',
|
||||
params: { collegeName }
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取班级选项
|
||||
* @param {string} collegeName 院系名称(可选)
|
||||
* @param {string} majorName 专业名称(可选)
|
||||
*/
|
||||
export const getClassOptions = (collegeName, majorName) => {
|
||||
return request({
|
||||
url: '/xljk/consultation/appointment/options/classes',
|
||||
method: 'GET',
|
||||
params: { collegeName, majorName }
|
||||
});
|
||||
};
|
||||
|
||||
// ===== 移动端专用辅助方法 =====
|
||||
|
||||
/**
|
||||
* 格式化日期为 YYYY-MM-DD
|
||||
* @param {Date} date 日期对象
|
||||
*/
|
||||
export const formatDate = (date) => {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取指定日期的周日期范围
|
||||
* @param {Date} date 指定日期
|
||||
* @returns {object} { startDate, endDate, weekDates }
|
||||
*/
|
||||
export const getWeekDateRange = (date = new Date()) => {
|
||||
const currentDate = new Date(date);
|
||||
const currentDay = currentDate.getDay(); // 0=Sunday, 1=Monday, ...
|
||||
|
||||
// 计算本周一的日期
|
||||
const monday = new Date(currentDate);
|
||||
monday.setDate(currentDate.getDate() - currentDay + (currentDay === 0 ? -6 : 1));
|
||||
|
||||
// 生成一周的日期数组
|
||||
const weekDates = [];
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const weekDate = new Date(monday);
|
||||
weekDate.setDate(monday.getDate() + i);
|
||||
weekDates.push({
|
||||
date: formatDate(weekDate),
|
||||
dayName: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'][i],
|
||||
dayNumber: weekDate.getDate(),
|
||||
isToday: formatDate(weekDate) === formatDate(new Date())
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
startDate: formatDate(monday),
|
||||
endDate: formatDate(new Date(monday.getTime() + 6 * 24 * 60 * 60 * 1000)),
|
||||
weekDates
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查预约限制
|
||||
* @param {string|number} studentId 学生ID
|
||||
*/
|
||||
export const checkAppointmentLimitation = async (studentId) => {
|
||||
try {
|
||||
// 这里可能需要导入noshow相关的API,暂时使用简单的实现
|
||||
return request({
|
||||
url: `/xljk/consultation/appointment/check-limitation/${studentId}`,
|
||||
method: 'GET'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('检查预约限制失败', error);
|
||||
// 检查失败不阻止预约
|
||||
return { code: 200, data: { limited: false } };
|
||||
}
|
||||
};
|
||||
+25
-1
@@ -302,7 +302,31 @@
|
||||
"navigationBarBackgroundColor": "#0051d9",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/xljk/reservation/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "心理咨询预约",
|
||||
"navigationBarBackgroundColor": "#0051d9",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/xljk/my-appointments/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的预约",
|
||||
"navigationBarBackgroundColor": "#0051d9",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/xljk/reservation/appointment-form",
|
||||
"style": {
|
||||
"navigationBarTitleText": "预约咨询",
|
||||
"navigationBarBackgroundColor": "#0051d9",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
|
||||
@@ -262,6 +262,20 @@
|
||||
/>
|
||||
</template>
|
||||
</ServiceItem>
|
||||
|
||||
<ServiceItem
|
||||
v-if="hasPermi(['student:counsel:reservation'])"
|
||||
title="心理咨询预约"
|
||||
@click="gotoPsychologicalReservation"
|
||||
>
|
||||
<template #icon>
|
||||
<image
|
||||
src="/static/00113.png"
|
||||
mode="aspectFit"
|
||||
style="width: 24px; height: 24px"
|
||||
/>
|
||||
</template>
|
||||
</ServiceItem>
|
||||
</GridLayout>
|
||||
</Card>
|
||||
|
||||
@@ -710,7 +724,8 @@ export default {
|
||||
this.hasPermi(['student:dormhygiene:listapp']) ||
|
||||
this.hasPermi(['student:workstudy:list']) ||
|
||||
this.hasPermi(['student:violation:listapp']) ||
|
||||
this.hasPermi(['student:assessment:applyapp'])
|
||||
this.hasPermi(['student:assessment:applyapp']) ||
|
||||
this.hasPermi(['student:counsel:reservation'])
|
||||
);
|
||||
},
|
||||
|
||||
@@ -966,6 +981,13 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
// 跳转到心理咨询预约页面
|
||||
gotoPsychologicalReservation() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/xljk/reservation/index',
|
||||
});
|
||||
},
|
||||
|
||||
// 跳转到修改密码页面
|
||||
gotoResetPassword() {
|
||||
uni.navigateTo({
|
||||
|
||||
@@ -0,0 +1,737 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<!-- 导航栏 -->
|
||||
<u-navbar
|
||||
title="我的预约"
|
||||
:border="false"
|
||||
bg-color="#ffffff"
|
||||
title-color="#333333"
|
||||
@leftClick="goBack"
|
||||
>
|
||||
<template #left>
|
||||
<u-icon name="arrow-left" size="22" color="#333333"></u-icon>
|
||||
</template>
|
||||
</u-navbar>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<scroll-view class="main-content" scroll-y enhanced>
|
||||
<!-- 筛选器 -->
|
||||
<view class="filter-section">
|
||||
<view class="filter-tabs">
|
||||
<view
|
||||
v-for="(tab, index) in filterTabs"
|
||||
:key="index"
|
||||
class="filter-tab"
|
||||
:class="{ active: activeFilterIndex === index }"
|
||||
@click="switchFilter(index)"
|
||||
>
|
||||
<text class="tab-text">{{ tab.label }}</text>
|
||||
<!-- 只有待咨询标签显示圆形标记 -->
|
||||
<view v-if="tab.value === '0' && tab.count > 0" class="tab-badge-circle"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view v-if="loading" class="loading-container">
|
||||
<u-loading-icon mode="circle"></u-loading-icon>
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 预约列表 -->
|
||||
<view v-else-if="appointmentList.length > 0" class="appointment-list">
|
||||
<view
|
||||
v-for="appointment in appointmentList"
|
||||
:key="appointment.id"
|
||||
class="appointment-card"
|
||||
@click="viewAppointmentDetail(appointment)"
|
||||
>
|
||||
<!-- 预约状态 -->
|
||||
<view class="appointment-header">
|
||||
<view class="appointment-date">
|
||||
<text class="date-text">{{ appointment.appointmentDate }}</text>
|
||||
<text class="time-text">{{ appointment.appointmentTime }}</text>
|
||||
</view>
|
||||
<view class="appointment-status">
|
||||
<u-tag
|
||||
:text="getStatusText(appointment.status)"
|
||||
:type="getStatusType(appointment.status)"
|
||||
size="mini"
|
||||
></u-tag>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 咨询师信息 -->
|
||||
<view class="counselor-section">
|
||||
<view class="counselor-avatar">
|
||||
<u-avatar
|
||||
:src="appointment.counselorAvatar"
|
||||
size="40"
|
||||
:text="appointment.counselorName ? appointment.counselorName.charAt(0) : '咨'"
|
||||
></u-avatar>
|
||||
</view>
|
||||
<view class="counselor-info">
|
||||
<text class="counselor-name">{{ appointment.counselorName || '未知咨询师' }}</text>
|
||||
<text class="appointment-reason">{{ appointment.reason || '心理咨询' }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 取消按钮 -->
|
||||
<view v-if="appointment.status === '0'" class="inline-action">
|
||||
<button
|
||||
class="cancel-button-inline"
|
||||
@click.stop="handleCancelClick(appointment)"
|
||||
:disabled="isCancelButtonDisabled(appointment.id)"
|
||||
>
|
||||
{{ getCancelButtonText(appointment.id) }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-else class="empty-container">
|
||||
<u-empty
|
||||
mode="data"
|
||||
:text="getEmptyText()"
|
||||
textColor="#909399"
|
||||
iconColor="#c8c9cc"
|
||||
></u-empty>
|
||||
<u-button
|
||||
type="primary"
|
||||
size="normal"
|
||||
@click="goToReservation"
|
||||
style="margin-top: 40rpx;"
|
||||
>
|
||||
立即预约
|
||||
</u-button>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getMyAppointments, cancelAppointment } from '@/api/xljk'
|
||||
|
||||
export default {
|
||||
name: 'MyAppointments',
|
||||
data() {
|
||||
return {
|
||||
// 筛选相关
|
||||
filterTabs: [
|
||||
{ label: '全部', value: 'all', count: 0 },
|
||||
{ label: '待咨询', value: '0', count: 0 },
|
||||
{ label: '已完成', value: '1', count: 0 },
|
||||
{ label: '已取消', value: '2', count: 0 },
|
||||
{ label: '未到访', value: '3', count: 0 }
|
||||
],
|
||||
activeFilterIndex: 0,
|
||||
|
||||
// 列表相关
|
||||
appointmentList: [],
|
||||
allAppointments: [], // 存储所有预约数据用于筛选
|
||||
loading: false,
|
||||
|
||||
// 取消预约相关
|
||||
selectedAppointment: null,
|
||||
cancelLoading: false,
|
||||
cancelingAppointmentId: null // 正在取消的预约ID
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
// 初始化时重置取消相关状态
|
||||
this.resetCancelState()
|
||||
this.loadAppointments()
|
||||
},
|
||||
onShow() {
|
||||
// 页面显示时刷新数据,因为可能从预约页面返回
|
||||
// 同时重置取消相关状态,防止从其他页面返回时状态异常
|
||||
this.resetCancelState()
|
||||
this.loadAppointments()
|
||||
},
|
||||
methods: {
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack()
|
||||
},
|
||||
|
||||
// 加载预约数据
|
||||
async loadAppointments() {
|
||||
this.loading = true
|
||||
|
||||
try {
|
||||
// 正确获取用户信息 - 使用专门的获取方法
|
||||
const { userStorage } = await import('@/common/storage')
|
||||
const userInfo = userStorage.getUserInfo()
|
||||
|
||||
console.log('🔍 [我的预约] 获取到的完整用户信息:', userInfo)
|
||||
|
||||
// 从正确的路径获取用户ID
|
||||
let studentId = null
|
||||
if (userInfo && userInfo.user) {
|
||||
studentId = userInfo.user.userId
|
||||
}
|
||||
|
||||
console.log('🔍 [我的预约] 解析到的学生ID:', studentId)
|
||||
|
||||
if (!studentId) {
|
||||
uni.showToast({
|
||||
title: '请先登录',
|
||||
icon: 'error'
|
||||
})
|
||||
|
||||
// 跳转到登录页面
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/index'
|
||||
})
|
||||
}, 1500)
|
||||
return
|
||||
}
|
||||
|
||||
// 调用API获取我的预约数据
|
||||
const response = await getMyAppointments(studentId)
|
||||
|
||||
console.log('🔍 [我的预约] API响应数据:', response)
|
||||
|
||||
if (response.code === 200 && response.data) {
|
||||
console.log('🔍 [我的预约] 原始数据列表:', response.data)
|
||||
|
||||
// 打印每条数据的status字段类型和值
|
||||
response.data.forEach((item, index) => {
|
||||
console.log(`🔍 [我的预约] 数据${index}: ID=${item.appointmentId}, status=${item.status} (${typeof item.status})`)
|
||||
})
|
||||
// 转换API数据格式
|
||||
const appointments = response.data.map(item => {
|
||||
return {
|
||||
id: item.id, // 修复:使用 id 而不是 appointmentId
|
||||
appointmentDate: item.appointmentDate,
|
||||
appointmentTime: item.appointmentDetailTime || item.appointmentTime,
|
||||
counselorName: item.counselorName,
|
||||
counselorAvatar: '',
|
||||
reason: item.reason,
|
||||
status: String(item.status), // 转换为字符串,便于筛选比较
|
||||
originalData: item
|
||||
}
|
||||
})
|
||||
|
||||
this.allAppointments = appointments
|
||||
console.log('🔍 [我的预约] 转换后的数据:', appointments)
|
||||
this.updateFilterCounts()
|
||||
this.filterAppointments()
|
||||
} else {
|
||||
this.allAppointments = []
|
||||
this.appointmentList = []
|
||||
this.updateFilterCounts()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载预约数据失败:', error)
|
||||
uni.showToast({
|
||||
title: '加载失败,请检查网络连接',
|
||||
icon: 'error'
|
||||
})
|
||||
this.allAppointments = []
|
||||
this.appointmentList = []
|
||||
this.updateFilterCounts()
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 更新筛选标签数量
|
||||
updateFilterCounts() {
|
||||
console.log('🔍 [我的预约] 更新筛选标签数量,总数据:', this.allAppointments.length)
|
||||
|
||||
// 统计各状态数量
|
||||
const status0Count = this.allAppointments.filter(item => item.status === '0').length
|
||||
const status1Count = this.allAppointments.filter(item => item.status === '1').length
|
||||
const status2Count = this.allAppointments.filter(item => item.status === '2').length
|
||||
const status3Count = this.allAppointments.filter(item => item.status === '3').length
|
||||
|
||||
console.log('🔍 [我的预约] 状态统计: 待咨询(0)=', status0Count, ', 已完成(1)=', status1Count, ', 已取消(2)=', status2Count, ', 未到访(3)=', status3Count)
|
||||
|
||||
this.filterTabs[0].count = this.allAppointments.length // 全部
|
||||
this.filterTabs[1].count = status0Count // 待咨询
|
||||
this.filterTabs[2].count = status1Count // 已完成
|
||||
this.filterTabs[3].count = status2Count // 已取消
|
||||
this.filterTabs[4].count = status3Count // 未到访
|
||||
},
|
||||
|
||||
// 切换筛选器
|
||||
switchFilter(index) {
|
||||
this.activeFilterIndex = index
|
||||
this.filterAppointments()
|
||||
},
|
||||
|
||||
// 筛选预约数据
|
||||
filterAppointments() {
|
||||
const currentFilter = this.filterTabs[this.activeFilterIndex]
|
||||
|
||||
console.log('🔍 [我的预约] 开始筛选,当前筛选条件:', currentFilter)
|
||||
|
||||
if (currentFilter.value === 'all') {
|
||||
this.appointmentList = [...this.allAppointments]
|
||||
} else {
|
||||
this.appointmentList = this.allAppointments.filter(item => item.status === currentFilter.value)
|
||||
}
|
||||
|
||||
console.log('🔍 [我的预约] 筛选结果数量:', this.appointmentList.length)
|
||||
console.log('🔍 [我的预约] 筛选结果详情:', this.appointmentList)
|
||||
},
|
||||
|
||||
// 获取状态文本
|
||||
getStatusText(status) {
|
||||
const statusMap = {
|
||||
'0': '待咨询',
|
||||
'1': '已完成',
|
||||
'2': '已取消',
|
||||
'3': '未到访',
|
||||
// 兼容英文状态值
|
||||
'pending': '待咨询',
|
||||
'completed': '已完成',
|
||||
'cancelled': '已取消',
|
||||
'expired': '已过期'
|
||||
}
|
||||
return statusMap[status] || '未知状态'
|
||||
},
|
||||
|
||||
// 获取状态类型(用于标签颜色)
|
||||
getStatusType(status) {
|
||||
const typeMap = {
|
||||
'0': 'warning',
|
||||
'1': 'success',
|
||||
'2': 'primary', // 已取消状态使用蓝色
|
||||
'3': 'info', // 未到访状态使用灰色
|
||||
// 兼容英文状态值
|
||||
'pending': 'warning',
|
||||
'completed': 'success',
|
||||
'cancelled': 'primary', // 已取消状态使用蓝色
|
||||
'expired': 'info'
|
||||
}
|
||||
return typeMap[status] || 'info'
|
||||
},
|
||||
|
||||
// 获取空状态文本
|
||||
getEmptyText() {
|
||||
const currentFilter = this.filterTabs[this.activeFilterIndex]
|
||||
const textMap = {
|
||||
'all': '暂无预约记录',
|
||||
'0': '暂无待咨询预约',
|
||||
'1': '暂无已完成预约',
|
||||
'2': '暂无已取消预约',
|
||||
'3': '暂无未到访预约'
|
||||
}
|
||||
return textMap[currentFilter.value] || '暂无数据'
|
||||
},
|
||||
|
||||
// 查看预约详情
|
||||
viewAppointmentDetail(appointment) {
|
||||
// 这里可以跳转到预约详情页面,或者显示详情弹窗
|
||||
console.log('查看预约详情:', appointment)
|
||||
// uni.navigateTo({
|
||||
// url: `/pages/xljk/appointment-detail/index?id=${appointment.id}`
|
||||
// })
|
||||
},
|
||||
|
||||
// 处理取消按钮点击
|
||||
handleCancelClick(appointment) {
|
||||
console.log('🔄 [取消预约] 按钮点击处理:', {
|
||||
appointmentId: appointment.id,
|
||||
appointmentStatus: appointment.status,
|
||||
当前状态: {
|
||||
cancelLoading: this.cancelLoading,
|
||||
cancelingAppointmentId: this.cancelingAppointmentId,
|
||||
isDisabled: this.isCancelButtonDisabled(appointment.id)
|
||||
}
|
||||
})
|
||||
|
||||
// 调用取消预约方法
|
||||
this.cancelAppointment(appointment)
|
||||
},
|
||||
|
||||
// 取消预约
|
||||
cancelAppointment(appointment) {
|
||||
console.log('点击取消预约按钮,预约信息:', appointment)
|
||||
|
||||
if (appointment.status !== '0') {
|
||||
uni.showToast({
|
||||
title: '只能取消待咨询的预约',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否已经在取消中
|
||||
if (this.cancelingAppointmentId === appointment.id) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log('使用原生弹窗显示取消确认')
|
||||
this.selectedAppointment = appointment
|
||||
|
||||
// 使用原生确认弹窗
|
||||
uni.showModal({
|
||||
title: '确认取消',
|
||||
content: '确定要取消这次预约吗?取消后将无法恢复。',
|
||||
confirmText: '确认取消',
|
||||
cancelText: '再想想',
|
||||
confirmColor: '#ff4757',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
console.log('用户确认取消')
|
||||
this.confirmCancel()
|
||||
} else if (res.cancel) {
|
||||
console.log('用户取消了取消操作')
|
||||
this.closeCancelModal()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 确认取消预约
|
||||
async confirmCancel() {
|
||||
if (!this.selectedAppointment || this.cancelLoading) return
|
||||
|
||||
const appointmentId = this.selectedAppointment.id
|
||||
this.cancelLoading = true
|
||||
this.cancelingAppointmentId = appointmentId // 标记正在取消的预约
|
||||
|
||||
console.log('🔄 [取消预约] 开始处理:', {
|
||||
appointmentId,
|
||||
当前状态: {
|
||||
cancelLoading: this.cancelLoading,
|
||||
cancelingAppointmentId: this.cancelingAppointmentId
|
||||
}
|
||||
})
|
||||
|
||||
// 强制超时重置机制,防止状态一直不重置
|
||||
const forceResetTimeout = setTimeout(() => {
|
||||
console.log('⚠️ [取消预约] 强制超时重置状态')
|
||||
this.cancelLoading = false
|
||||
this.cancelingAppointmentId = null
|
||||
uni.showToast({
|
||||
title: '操作超时,已重置',
|
||||
icon: 'none'
|
||||
})
|
||||
}, 45000) // 45秒强制重置
|
||||
|
||||
try {
|
||||
if (!appointmentId) {
|
||||
throw new Error('预约ID为空')
|
||||
}
|
||||
|
||||
console.log('📡 [取消预约] 发送API请求...')
|
||||
|
||||
// 添加超时控制,手机端网络可能较慢
|
||||
const timeoutPromise = new Promise((_, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(new Error('请求超时,请重试'))
|
||||
}, 30000) // 30秒超时
|
||||
})
|
||||
|
||||
const apiPromise = cancelAppointment(appointmentId)
|
||||
const response = await Promise.race([apiPromise, timeoutPromise])
|
||||
|
||||
console.log('📡 [取消预约] API响应:', response)
|
||||
|
||||
if (response && response.code === 200) {
|
||||
console.log('✅ [取消预约] 取消成功')
|
||||
uni.showToast({
|
||||
title: '取消成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
// 立即更新本地数据,避免重新请求
|
||||
const appointmentIndex = this.allAppointments.findIndex(item => item.id === appointmentId)
|
||||
if (appointmentIndex !== -1) {
|
||||
// 更新状态为已取消
|
||||
this.allAppointments[appointmentIndex].status = '2'
|
||||
console.log('📊 [取消预约] 本地数据已更新:', this.allAppointments[appointmentIndex])
|
||||
|
||||
// 立即刷新筛选和计数
|
||||
this.updateFilterCounts()
|
||||
this.filterAppointments()
|
||||
}
|
||||
|
||||
this.closeCancelModal()
|
||||
// 注释掉重新加载,使用本地更新提升用户体验
|
||||
// this.loadAppointments()
|
||||
} else {
|
||||
const errorMsg = response?.msg || '取消失败'
|
||||
console.log('❌ [取消预约] 业务错误:', errorMsg)
|
||||
uni.showToast({
|
||||
title: errorMsg,
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ [取消预约] 请求失败:', error)
|
||||
|
||||
let errorMessage = '取消失败,请检查网络连接'
|
||||
if (error.message === '请求超时,请重试') {
|
||||
errorMessage = '请求超时,请重试'
|
||||
} else if (error.errMsg && error.errMsg.includes('timeout')) {
|
||||
errorMessage = '网络超时,请检查网络连接'
|
||||
} else if (error.errMsg && error.errMsg.includes('fail')) {
|
||||
errorMessage = '网络连接失败,请检查网络'
|
||||
}
|
||||
|
||||
uni.showToast({
|
||||
title: errorMessage,
|
||||
icon: 'error'
|
||||
})
|
||||
} finally {
|
||||
console.log('🔄 [取消预约] 清理状态')
|
||||
clearTimeout(forceResetTimeout) // 清除强制重置定时器
|
||||
this.cancelLoading = false
|
||||
this.cancelingAppointmentId = null // 重置正在取消的预约ID
|
||||
}
|
||||
},
|
||||
|
||||
// 重置取消预约状态
|
||||
closeCancelModal() {
|
||||
console.log('🔄 [取消预约] 重置所有状态:', {
|
||||
之前状态: {
|
||||
selectedAppointment: this.selectedAppointment?.id,
|
||||
cancelLoading: this.cancelLoading,
|
||||
cancelingAppointmentId: this.cancelingAppointmentId
|
||||
}
|
||||
})
|
||||
|
||||
this.selectedAppointment = null
|
||||
this.cancelLoading = false // 重置加载状态
|
||||
this.cancelingAppointmentId = null // 重置正在取消的预约ID
|
||||
|
||||
console.log('🔄 [取消预约] 状态重置完成:', {
|
||||
当前状态: {
|
||||
selectedAppointment: this.selectedAppointment,
|
||||
cancelLoading: this.cancelLoading,
|
||||
cancelingAppointmentId: this.cancelingAppointmentId
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
|
||||
// 跳转到预约页面
|
||||
goToReservation() {
|
||||
uni.navigateBack()
|
||||
},
|
||||
|
||||
// 检查特定预约是否正在取消
|
||||
isAppointmentCanceling(appointmentId) {
|
||||
return this.cancelingAppointmentId === appointmentId
|
||||
},
|
||||
|
||||
// 获取取消按钮文本
|
||||
getCancelButtonText(appointmentId) {
|
||||
return this.cancelingAppointmentId === appointmentId ? '取消中...' : '取消预约'
|
||||
},
|
||||
|
||||
// 检查取消按钮是否禁用
|
||||
isCancelButtonDisabled(appointmentId) {
|
||||
return this.cancelingAppointmentId === appointmentId
|
||||
},
|
||||
|
||||
// 重置取消相关状态
|
||||
resetCancelState() {
|
||||
console.log('🔄 [取消预约] 重置取消相关状态')
|
||||
this.selectedAppointment = null
|
||||
this.cancelLoading = false
|
||||
this.cancelingAppointmentId = null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
height: calc(100vh - 88rpx); // 减去导航栏高度
|
||||
padding-top: 20rpx;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
background: #ffffff;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.filter-tabs {
|
||||
display: flex;
|
||||
padding: 20rpx;
|
||||
|
||||
.filter-tab {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
padding: 20rpx 10rpx;
|
||||
border-radius: 12rpx;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&.active {
|
||||
background: rgba(24, 144, 255, 0.1);
|
||||
|
||||
.tab-text {
|
||||
color: #1890ff;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.tab-badge-circle {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
background: #ff4d4f;
|
||||
border-radius: 50%;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 20rpx;
|
||||
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.appointment-list {
|
||||
padding: 0 20rpx;
|
||||
|
||||
.appointment-card {
|
||||
background: #ffffff;
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
background: rgba(24, 144, 255, 0.02);
|
||||
}
|
||||
|
||||
.appointment-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
.appointment-date {
|
||||
.date-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
display: block;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.time-text {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.counselor-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
.counselor-avatar {
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.counselor-info {
|
||||
flex: 1;
|
||||
|
||||
.counselor-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
display: block;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.appointment-reason {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.inline-action {
|
||||
.cancel-button-inline {
|
||||
background-color: #ff4757;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 12rpx;
|
||||
padding: 12rpx 20rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
min-width: 120rpx;
|
||||
|
||||
&:disabled {
|
||||
background-color: #cccccc;
|
||||
color: #999999;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&:active:not(:disabled) {
|
||||
background-color: #ff3742;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.empty-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 20rpx;
|
||||
}
|
||||
|
||||
.cancel-modal-content {
|
||||
padding: 20rpx 0;
|
||||
|
||||
.cancel-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,708 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<!-- 自定义导航栏 -->
|
||||
<view class="custom-navbar">
|
||||
<view class="navbar-left" @click="goBack">
|
||||
<u-icon name="arrow-left" size="20" color="#ffffff"></u-icon>
|
||||
</view>
|
||||
<text class="navbar-title">预约咨询</text>
|
||||
<view class="navbar-right"></view>
|
||||
</view>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<scroll-view class="main-content" scroll-y enhanced :show-scrollbar="false">
|
||||
<!-- 预约信息确认 -->
|
||||
<view class="confirm-section">
|
||||
<view class="section-title">
|
||||
<u-icon name="calendar" size="16" color="#3c9cff"></u-icon>
|
||||
<text class="title-text">预约信息确认</text>
|
||||
</view>
|
||||
|
||||
<view class="confirm-card">
|
||||
<view class="confirm-item">
|
||||
<text class="confirm-label">预约日期:</text>
|
||||
<text class="confirm-value">{{ formattedDate }}</text>
|
||||
</view>
|
||||
<view class="confirm-item">
|
||||
<text class="confirm-label">预约时段:</text>
|
||||
<text class="confirm-value">{{ appointmentData.timeRange }}</text>
|
||||
</view>
|
||||
<view class="confirm-item">
|
||||
<text class="confirm-label">咨询师:</text>
|
||||
<text class="confirm-value">{{ appointmentData.counselorName }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 个人信息 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">
|
||||
<u-icon name="account" size="16" color="#3c9cff"></u-icon>
|
||||
<text class="title-text">个人信息</text>
|
||||
</view>
|
||||
|
||||
<view class="form-card">
|
||||
<view class="form-item">
|
||||
<text class="form-label">姓名:</text>
|
||||
<text class="form-value">{{ userInfo.nickName || userInfo.userName || '未设置' }}</text>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">学号:</text>
|
||||
<text class="form-value">{{ userInfo.userName || '未设置' }}</text>
|
||||
</view>
|
||||
<!-- 院系、专业、班级信息 -->
|
||||
<view class="form-item">
|
||||
<text class="form-label">院系:</text>
|
||||
<u-picker
|
||||
:columns="[collegeOptions]"
|
||||
keyName="label"
|
||||
v-model="collegeIndex"
|
||||
:show="showCollegePicker"
|
||||
@confirm="onCollegeConfirm"
|
||||
@close="showCollegePicker = false"
|
||||
></u-picker>
|
||||
<view class="picker-input" @click="showCollegePicker = true">
|
||||
<text class="picker-text" :class="{ placeholder: !form.collegeName }">
|
||||
{{ form.collegeName || '请选择院系' }}
|
||||
</text>
|
||||
<u-icon name="arrow-right" size="14" color="#c8c9cc"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="form-label">专业:</text>
|
||||
<u-picker
|
||||
:columns="[majorOptions]"
|
||||
keyName="label"
|
||||
v-model="majorIndex"
|
||||
:show="showMajorPicker"
|
||||
@confirm="onMajorConfirm"
|
||||
@close="showMajorPicker = false"
|
||||
></u-picker>
|
||||
<view class="picker-input" @click="handleMajorPickerClick">
|
||||
<text class="picker-text" :class="{ placeholder: !form.majorName }">
|
||||
{{ form.majorName || '请选择专业' }}
|
||||
</text>
|
||||
<u-icon name="arrow-right" size="14" color="#c8c9cc"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="form-label">班级:</text>
|
||||
<u-picker
|
||||
:columns="[classOptions]"
|
||||
keyName="label"
|
||||
v-model="classIndex"
|
||||
:show="showClassPicker"
|
||||
@confirm="onClassConfirm"
|
||||
@close="showClassPicker = false"
|
||||
></u-picker>
|
||||
<view class="picker-input" @click="handleClassPickerClick">
|
||||
<text class="picker-text" :class="{ placeholder: !form.className }">
|
||||
{{ form.className || '请选择班级' }}
|
||||
</text>
|
||||
<u-icon name="arrow-right" size="14" color="#c8c9cc"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 咨询原因 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">
|
||||
<u-icon name="chat" size="16" color="#3c9cff"></u-icon>
|
||||
<text class="title-text">咨询原因</text>
|
||||
<text class="required-mark">*</text>
|
||||
</view>
|
||||
|
||||
<view class="form-card">
|
||||
<u-input
|
||||
v-model="form.reason"
|
||||
type="textarea"
|
||||
placeholder="请详细描述您希望咨询的问题或困扰,以便咨询师更好地了解您的需求"
|
||||
:maxlength="500"
|
||||
:show-word-limit="true"
|
||||
:auto-height="true"
|
||||
:custom-style="{
|
||||
minHeight: '120px',
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none'
|
||||
}"
|
||||
></u-input>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 温馨提示 -->
|
||||
<view class="tips-section">
|
||||
<view class="section-title">
|
||||
<u-icon name="info-circle" size="16" color="#ff9900"></u-icon>
|
||||
<text class="title-text">温馨提示</text>
|
||||
</view>
|
||||
|
||||
<view class="tips-card">
|
||||
<view class="tip-item">
|
||||
<text class="tip-number">1.</text>
|
||||
<text class="tip-text">请提前5-10分钟到达咨询室,迟到可能影响咨询效果</text>
|
||||
</view>
|
||||
<view class="tip-item">
|
||||
<text class="tip-number">2.</text>
|
||||
<text class="tip-text">咨询内容严格保密,请放心倾述您的困扰</text>
|
||||
</view>
|
||||
<view class="tip-item">
|
||||
<text class="tip-number">3.</text>
|
||||
<text class="tip-text">如需取消预约,请提前24小时操作</text>
|
||||
</view>
|
||||
<view class="tip-item">
|
||||
<text class="tip-number">4.</text>
|
||||
<text class="tip-text">连续3次未到访将被限制预约功能</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部间距 -->
|
||||
<view class="bottom-spacer"></view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部提交按钮 -->
|
||||
<view class="submit-container">
|
||||
<u-button
|
||||
text="提交预约"
|
||||
type="primary"
|
||||
:loading="submitting"
|
||||
:disabled="!canSubmit"
|
||||
@click="submitAppointment"
|
||||
:custom-style="{
|
||||
width: '100%',
|
||||
height: '50px',
|
||||
fontSize: '18px',
|
||||
fontWeight: 'bold'
|
||||
}"
|
||||
></u-button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
submitAppointment,
|
||||
getCollegeOptions,
|
||||
getMajorOptions,
|
||||
getClassOptions,
|
||||
checkAppointmentLimitation
|
||||
} from '@/api/xljk';
|
||||
import { userStorage } from '@/common/storage';
|
||||
|
||||
export default {
|
||||
name: 'AppointmentForm',
|
||||
data() {
|
||||
return {
|
||||
// 用户信息
|
||||
userInfo: {},
|
||||
|
||||
// 预约数据
|
||||
appointmentData: {
|
||||
date: '',
|
||||
scheduleId: '',
|
||||
counselorId: '',
|
||||
timeRange: '',
|
||||
counselorName: ''
|
||||
},
|
||||
|
||||
// 表单数据
|
||||
form: {
|
||||
collegeName: '',
|
||||
majorName: '',
|
||||
className: '',
|
||||
reason: ''
|
||||
},
|
||||
|
||||
// 选项数据
|
||||
collegeOptions: [],
|
||||
majorOptions: [],
|
||||
classOptions: [],
|
||||
|
||||
// 选择器状态
|
||||
showCollegePicker: false,
|
||||
showMajorPicker: false,
|
||||
showClassPicker: false,
|
||||
|
||||
// 选择器索引
|
||||
collegeIndex: [0],
|
||||
majorIndex: [0],
|
||||
classIndex: [0],
|
||||
|
||||
// 提交状态
|
||||
submitting: false
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
formattedDate() {
|
||||
if (!this.appointmentData.date) return '';
|
||||
const date = new Date(this.appointmentData.date);
|
||||
const month = date.getMonth() + 1;
|
||||
const day = date.getDate();
|
||||
const weekDay = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][date.getDay()];
|
||||
return `${month}月${day}日 ${weekDay}`;
|
||||
},
|
||||
|
||||
canSubmit() {
|
||||
return this.form.reason.trim().length > 0 &&
|
||||
this.form.collegeName &&
|
||||
this.form.majorName &&
|
||||
this.form.className;
|
||||
}
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
console.log('预约表单页面加载,参数:', options);
|
||||
this.checkLoginStatus();
|
||||
this.parseParams(options);
|
||||
this.loadOptions();
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 检查登录状态
|
||||
checkLoginStatus() {
|
||||
if (!userStorage.isLoggedIn()) {
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/index',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
const userStorage_data = userStorage.getUserInfo();
|
||||
if (userStorage_data && userStorage_data.user) {
|
||||
this.userInfo = userStorage_data.user;
|
||||
}
|
||||
|
||||
console.log('用户信息:', this.userInfo);
|
||||
return true;
|
||||
},
|
||||
|
||||
// 解析页面参数
|
||||
parseParams(options) {
|
||||
this.appointmentData = {
|
||||
date: options.date || '',
|
||||
scheduleId: options.scheduleId || '',
|
||||
counselorId: options.counselorId || '',
|
||||
timeRange: options.timeRange || '',
|
||||
counselorName: decodeURIComponent(options.counselorName || '')
|
||||
};
|
||||
|
||||
console.log('预约数据:', this.appointmentData);
|
||||
},
|
||||
|
||||
// 加载选项数据
|
||||
async loadOptions() {
|
||||
try {
|
||||
// 加载院系选项
|
||||
const collegeRes = await getCollegeOptions();
|
||||
if (collegeRes.code === 200 && collegeRes.data) {
|
||||
this.collegeOptions = collegeRes.data.map(item => ({
|
||||
label: item.label || item.name,
|
||||
value: item.value || item.name
|
||||
}));
|
||||
}
|
||||
|
||||
// 加载所有专业选项
|
||||
const majorRes = await getMajorOptions();
|
||||
if (majorRes.code === 200 && majorRes.data) {
|
||||
this.majorOptions = majorRes.data.map(item => ({
|
||||
label: item.label || item.name,
|
||||
value: item.value || item.name
|
||||
}));
|
||||
}
|
||||
|
||||
// 加载所有班级选项
|
||||
const classRes = await getClassOptions();
|
||||
if (classRes.code === 200 && classRes.data) {
|
||||
this.classOptions = classRes.data.map(item => ({
|
||||
label: item.label || item.name,
|
||||
value: item.value || item.name
|
||||
}));
|
||||
}
|
||||
|
||||
console.log('选项数据加载完成:', {
|
||||
colleges: this.collegeOptions.length,
|
||||
majors: this.majorOptions.length,
|
||||
classes: this.classOptions.length
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('加载选项数据失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 院系选择确认
|
||||
async onCollegeConfirm(e) {
|
||||
console.log('院系选择:', e);
|
||||
const index = e.value[0];
|
||||
const selectedCollege = this.collegeOptions[index];
|
||||
|
||||
if (selectedCollege) {
|
||||
this.form.collegeName = selectedCollege.value;
|
||||
this.collegeIndex = [index];
|
||||
|
||||
// 清空专业和班级
|
||||
this.form.majorName = '';
|
||||
this.form.className = '';
|
||||
this.majorIndex = [0];
|
||||
this.classIndex = [0];
|
||||
|
||||
// 重新加载专业选项
|
||||
try {
|
||||
const majorRes = await getMajorOptions(selectedCollege.value);
|
||||
if (majorRes.code === 200 && majorRes.data) {
|
||||
this.majorOptions = majorRes.data.map(item => ({
|
||||
label: item.label || item.name,
|
||||
value: item.value || item.name
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载专业选项失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
this.showCollegePicker = false;
|
||||
},
|
||||
|
||||
// 专业选择确认
|
||||
async onMajorConfirm(e) {
|
||||
console.log('专业选择:', e);
|
||||
const index = e.value[0];
|
||||
const selectedMajor = this.majorOptions[index];
|
||||
|
||||
if (selectedMajor) {
|
||||
this.form.majorName = selectedMajor.value;
|
||||
this.majorIndex = [index];
|
||||
|
||||
// 清空班级
|
||||
this.form.className = '';
|
||||
this.classIndex = [0];
|
||||
|
||||
// 重新加载班级选项
|
||||
try {
|
||||
const classRes = await getClassOptions(this.form.collegeName, selectedMajor.value);
|
||||
if (classRes.code === 200 && classRes.data) {
|
||||
this.classOptions = classRes.data.map(item => ({
|
||||
label: item.label || item.name,
|
||||
value: item.value || item.name
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载班级选项失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
this.showMajorPicker = false;
|
||||
},
|
||||
|
||||
// 班级选择确认
|
||||
onClassConfirm(e) {
|
||||
console.log('班级选择:', e);
|
||||
const index = e.value[0];
|
||||
const selectedClass = this.classOptions[index];
|
||||
|
||||
if (selectedClass) {
|
||||
this.form.className = selectedClass.value;
|
||||
this.classIndex = [index];
|
||||
}
|
||||
|
||||
this.showClassPicker = false;
|
||||
},
|
||||
|
||||
// 处理专业选择器点击
|
||||
handleMajorPickerClick() {
|
||||
if (!this.form.collegeName) {
|
||||
uni.showToast({
|
||||
title: '请先选择院系',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.showMajorPicker = true;
|
||||
},
|
||||
|
||||
// 处理班级选择器点击
|
||||
handleClassPickerClick() {
|
||||
if (!this.form.collegeName) {
|
||||
uni.showToast({
|
||||
title: '请先选择院系',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!this.form.majorName) {
|
||||
uni.showToast({
|
||||
title: '请先选择专业',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.showClassPicker = true;
|
||||
},
|
||||
|
||||
// 提交预约
|
||||
async submitAppointment() {
|
||||
if (!this.canSubmit) {
|
||||
uni.showToast({
|
||||
title: '请完善预约信息',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.submitting = true;
|
||||
|
||||
try {
|
||||
// 检查预约限制
|
||||
if (this.userInfo.userId) {
|
||||
const limitationCheck = await checkAppointmentLimitation(this.userInfo.userId);
|
||||
if (limitationCheck.code === 200 && limitationCheck.data && limitationCheck.data.limited) {
|
||||
uni.showToast({
|
||||
title: `您暂时无法预约,限制将于 ${limitationCheck.data.expireDateStr} 解除`,
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 构建提交数据
|
||||
const submitData = {
|
||||
studentId: this.userInfo.userId,
|
||||
studentName: this.userInfo.nickName || this.userInfo.userName,
|
||||
studentNo: this.userInfo.userName,
|
||||
collegeName: this.form.collegeName,
|
||||
majorName: this.form.majorName,
|
||||
className: this.form.className,
|
||||
scheduleId: this.appointmentData.scheduleId,
|
||||
counselorId: this.appointmentData.counselorId,
|
||||
counselorName: this.appointmentData.counselorName,
|
||||
appointmentDate: this.appointmentData.date,
|
||||
appointmentTime: this.appointmentData.timeRange,
|
||||
appointmentDetailTime: this.appointmentData.timeRange,
|
||||
reason: this.form.reason.trim()
|
||||
};
|
||||
|
||||
console.log('提交预约数据:', submitData);
|
||||
|
||||
const response = await submitAppointment(submitData);
|
||||
|
||||
if (response.code === 200) {
|
||||
uni.showToast({
|
||||
title: '预约成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
// 延迟跳转,让用户看到成功提示
|
||||
setTimeout(() => {
|
||||
uni.navigateBack({
|
||||
delta: 2 // 返回到主页面
|
||||
});
|
||||
}, 1500);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: response.msg || '预约失败',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交预约失败:', error);
|
||||
uni.showToast({
|
||||
title: '网络错误,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
this.submitting = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.custom-navbar {
|
||||
background: #1890ff;
|
||||
padding: calc(var(--status-bar-height) + 12px) 16px 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.navbar-left, .navbar-right {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.navbar-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
|
||||
/* 通用样式 */
|
||||
.confirm-section, .form-section, .tips-section {
|
||||
margin: 16px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.required-mark {
|
||||
color: #f56c6c;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.confirm-card, .form-card, .tips-card {
|
||||
background-color: #ffffff;
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 确认信息 */
|
||||
.confirm-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.confirm-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.confirm-label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
margin-right: 8px;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.confirm-value {
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 表单样式 */
|
||||
.form-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.form-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
margin-right: 12px;
|
||||
min-width: 60px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.form-value {
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.picker-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex: 1;
|
||||
padding: 8px 12px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 12px;
|
||||
min-height: 24px;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.picker-text.placeholder {
|
||||
color: #c8c9cc;
|
||||
}
|
||||
|
||||
/* 温馨提示 */
|
||||
.tip-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.tip-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.tip-number {
|
||||
font-size: 14px;
|
||||
color: #ff9900;
|
||||
margin-right: 8px;
|
||||
font-weight: 500;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 底部提交按钮 */
|
||||
.submit-container {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: #ffffff;
|
||||
padding: 16px;
|
||||
border-top: 1px solid #eee;
|
||||
padding-bottom: calc(16px + env(safe-area-inset-bottom));
|
||||
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.bottom-spacer {
|
||||
height: 20px;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user