zhxy-jsd/src/pages/base/mine/index.vue
2025-08-27 22:25:20 +08:00

787 lines
19 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="mine-page">
<!-- 1. 顶部 Header - 复制自service页面 -->
<view class="header-section">
<view class="header-gradient"></view>
<!-- 退出按钮 -->
<view class="logout-btn" @click="handleLogout">
<text class="logout-text">退出</text>
</view>
<!-- 老师信息 -->
<view class="teacher-info">
<view class="teacher-avatar">
<image
class="avatar-image"
:src="teacherData.avatar || '/static/base/default-avatar.png'"
mode="aspectFill"
></image>
</view>
<view class="teacher-details">
<view class="teacher-name">{{ teacherData.name }}</view>
<view class="teacher-position">{{ teacherPosition }}</view>
<view class="teacher-class">{{ teacherData.className }}</view>
</view>
</view>
<!-- 统计信息 - 暂时隐藏 -->
<!-- <view class="stats-info">
<view class="stat-item">
<text class="stat-label">积分</text>
<text class="stat-value">{{ teacherData.score }}</text>
</view>
<view class="stat-divider">|</view>
<view class="stat-item">
<text class="stat-label">工作量</text>
<text class="stat-value">{{ teacherData.workload }}课时</text>
</view>
</view> -->
<!-- 介绍文字 -->
<view class="teacher-intro">
{{ teacherData.introduction }}
</view>
</view>
<!-- 2. 日程管理区域 -->
<view class="main-content">
<!-- 近期日程标题 -->
<view class="schedule-header">
<text class="schedule-title">近期日程</text>
<view class="schedule-link" @click="goToSchedule">
<text class="link-text">查看日程</text>
<text class="link-arrow">></text>
</view>
</view>
<!-- 日期选择器 -->
<view class="date-selector">
<view class="date-container">
<view
class="date-item"
v-for="(date, index) in weekDates"
:key="index"
:class="{ active: selectedDateIndex === index }"
@click="selectDate(index)"
>
<text class="date-day">{{ date.day }}</text>
<text class="date-number">{{ date.number }}</text>
<view v-if="date.hasEvent" class="date-dot"></view>
</view>
</view>
</view>
<!-- 日程列表 -->
<scroll-view class="schedule-list" scroll-y="true" show-scrollbar="false" enhanced="true">
<view class="schedule-content">
<view
class="schedule-item"
v-for="(item, index) in getCurrentSchedule"
:key="index"
>
<view class="time-column">
<text class="start-time">{{ item.startTime }}</text>
<text class="end-time" v-if="item.endTime">{{ item.endTime }}</text>
</view>
<view class="content-column">
<view class="event-header">
<view
class="event-tag"
:class="item.type"
v-if="item.tag"
>
<text class="tag-text">{{ item.tag }}</text>
</view>
<text class="event-title">{{ item.title }}</text>
</view>
<view class="event-details" v-if="item.details">
<view class="event-location" v-if="item.location">
<text class="location-icon">📍</text>
<text class="location-text">{{ item.location }}</text>
</view>
<view class="event-description" v-if="item.description">
<text class="description-icon">📝</text>
<text class="description-text">{{ item.description }}</text>
</view>
</view>
</view>
</view>
<!-- 无日程时的提示 -->
<view class="no-schedule" v-if="getCurrentSchedule.length === 0">
<text class="no-schedule-text">暂无日程安排</text>
</view>
</view>
</scroll-view>
</view>
</view>
</template>
<script lang="ts" setup>
import { useUserStore } from "@/store/modules/user";
import { useCommonStore } from "@/store/modules/common";
import { imagUrl } from "@/utils";
import { reactive, ref, computed, onMounted } from "vue";
// 定义老师数据接口
interface TeacherData {
name: string;
position: string;
className: string;
score: number;
workload: number;
introduction: string;
avatar?: string;
}
// 日程数据接口
interface ScheduleItem {
startTime: string;
endTime?: string;
title: string;
type: string;
tag?: string;
location?: string;
description?: string;
details?: boolean;
}
// 日期数据接口
interface DateItem {
day: string;
number: string;
hasEvent: boolean;
date: string;
}
const { logout, getUser, getJs } = useUserStore();
const { getZwListByLx } = useCommonStore();
// 职务标签
const dzZwLabel = ref<string>("");
const qtZwLabel = ref<string>("");
// 老师数据
const teacherData = reactive<TeacherData>({
name: getJs.jsxm || getUser.loginName,
position: "", // 将在初始化时设置
className: getJs.njz || "",
score: 88, // 默认值,后续可以从接口获取
workload: 40, // 默认值,后续可以从接口获取
introduction: getJs.introduction || "北冥有鱼,其名为鲲。鲲之大,不知其几千里也。",
avatar: imagUrl(getUser.profilePhoto),
});
// 计算属性:动态获取职务信息
const teacherPosition = computed(() => {
const positions = [];
if (dzZwLabel.value) positions.push(dzZwLabel.value);
if (qtZwLabel.value) positions.push(qtZwLabel.value);
return positions.join('、') || '暂无职务信息';
});
// 退出登录
const handleLogout = () => {
uni.showModal({
title: "确认退出",
content: "确定要退出登录吗?",
success: (res) => {
if (res.confirm) {
logout();
uni.reLaunch({
url: "/pages/system/login/login",
});
}
},
});
};
// 日程相关数据
const selectedDateIndex = ref(0); // 默认选中第一天
// 生成一周的日期
const weekDates = ref<DateItem[]>([]);
// 生成空数据的函数
const generateScheduleData = () => {
const today = new Date();
const currentDay = today.getDay();
const mondayOffset = currentDay === 0 ? -6 : 1 - currentDay;
const startOfWeek = new Date(today);
startOfWeek.setDate(today.getDate() + mondayOffset);
const data: Record<string, ScheduleItem[]> = {};
// 生成一周的日期,但不包含任何日程数据
for (let i = 0; i < 7; i++) {
const date = new Date(startOfWeek);
date.setDate(startOfWeek.getDate() + i);
const dateStr = date.getDate().toString();
// 所有日期都没有日程安排
data[dateStr] = [];
}
return data;
};
// 日程数据
const scheduleData = reactive<Record<string, ScheduleItem[]>>(generateScheduleData());
// 获取当前选中日期的日程
const getCurrentSchedule = computed(() => {
const currentDate = weekDates.value[selectedDateIndex.value];
if (!currentDate) return [];
return scheduleData[currentDate.number] || [];
});
// 初始化职务信息
const initPositionInfo = async () => {
try {
let dzZw: string[] = [];
let qtZw: string[] = [];
// 解析党政职务
if (getJs.dzzw && typeof getJs.dzzw === "string") {
dzZw = getJs.dzzw.split(",");
}
// 解析其他职务
if (getJs.qtzw && typeof getJs.qtzw === "string") {
qtZw = getJs.qtzw.split(",");
}
// 获取党政职务名称
if (dzZw && dzZw.length > 0) {
const res = await getZwListByLx({ zwlx: "党政职务" });
dzZwLabel.value = dzZw
.map((zwId: string) => {
const zw = res.result.find((zw: any) => zwId === zw.id);
return zw ? zw.zwmc : "";
})
.filter(Boolean)
.join(", ");
}
// 获取其他职务名称
if (qtZw && qtZw.length > 0) {
const res = await getZwListByLx({ zwlx: "其他职务" });
qtZwLabel.value = qtZw
.map((zwId: string) => {
const zw = res.result.find((zw: any) => zwId === zw.id);
return zw ? zw.zwmc : "";
})
.filter(Boolean)
.join(", ");
}
console.log("职务信息初始化完成:", { dzZwLabel: dzZwLabel.value, qtZwLabel: qtZwLabel.value });
} catch (error) {
console.error("初始化职务信息失败:", error);
}
};
// 初始化日期
const initDates = () => {
const days = ['一', '二', '三', '四', '五', '六', '日'];
const today = new Date();
const currentDay = today.getDay(); // 0-60是周日
// 计算本周的开始日期(周一)
const startOfWeek = new Date(today);
const mondayOffset = currentDay === 0 ? -6 : 1 - currentDay; // 如果是周日往前推6天否则推到周一
startOfWeek.setDate(today.getDate() + mondayOffset);
weekDates.value = [];
for (let i = 0; i < 7; i++) {
const date = new Date(startOfWeek);
date.setDate(startOfWeek.getDate() + i);
const dateStr = date.getDate().toString();
const hasEvent = scheduleData[dateStr] && scheduleData[dateStr].length > 0;
weekDates.value.push({
day: days[i],
number: dateStr,
hasEvent,
date: date.toISOString().split('T')[0]
});
}
};
// 选择日期
const selectDate = (index: number) => {
selectedDateIndex.value = index;
};
// 跳转到日程页面
const goToSchedule = () => {
uni.navigateTo({
url: '/pages/view/schedule/index'
});
};
onMounted(async () => {
// 初始化职务信息
await initPositionInfo();
// 初始化日期
initDates();
// 默认选中今天
const today = new Date();
const currentDay = today.getDay();
const todayIndex = currentDay === 0 ? 6 : currentDay - 1; // 周日是6其他是day-1
selectedDateIndex.value = todayIndex;
});
</script>
<style scoped lang="scss">
.mine-page {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
position: relative;
}
// 顶部 Header - 复制自service页面
.header-section {
position: relative;
background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 50%, #ec4899 100%);
color: #ffffff;
overflow: hidden;
padding: 40rpx 30rpx 30rpx;
height: auto;
min-height: 270rpx;
.header-gradient {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
45deg,
rgba(255, 255, 255, 0.1) 0%,
rgba(255, 255, 255, 0.05) 100%
);
z-index: 1;
}
.logout-btn {
position: absolute;
top: 40rpx;
right: 30rpx;
z-index: 3;
background: rgba(255, 255, 255, 0.2);
border-radius: 20rpx;
padding: 10rpx 20rpx;
border: 1px solid rgba(255, 255, 255, 0.3);
backdrop-filter: blur(10px);
.logout-text {
color: #ffffff;
font-size: 14px;
font-weight: 500;
}
}
.teacher-info {
display: flex;
align-items: center;
margin-top: 20rpx;
z-index: 2;
position: relative;
.teacher-avatar {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
overflow: hidden;
border: 3px solid rgba(255, 255, 255, 0.3);
margin-right: 20rpx;
.avatar-image {
width: 100%;
height: 100%;
}
}
.teacher-details {
flex: 1;
.teacher-name {
font-size: 20px;
font-weight: bold;
color: #ffffff;
margin-bottom: 5rpx;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
.teacher-position {
font-size: 14px;
color: rgba(255, 255, 255, 0.9);
margin-bottom: 5rpx;
}
.teacher-class {
font-size: 12px;
color: rgba(255, 255, 255, 0.8);
}
}
}
.stats-info {
display: flex;
align-items: center;
justify-content: center;
margin-top: 20rpx;
z-index: 2;
position: relative;
.stat-item {
display: flex;
align-items: center;
.stat-label {
font-size: 14px;
color: rgba(255, 255, 255, 0.9);
}
.stat-value {
font-size: 14px;
color: #ffffff;
font-weight: 600;
}
}
.stat-divider {
margin: 0 30rpx;
color: rgba(255, 255, 255, 0.6);
font-size: 14px;
}
}
.teacher-intro {
text-align: center;
font-size: 12px;
color: rgba(255, 255, 255, 0.8);
margin-top: 20rpx;
line-height: 1.4;
z-index: 2;
position: relative;
}
}
// 主要内容区域
.main-content {
box-sizing: border-box;
padding: 20px 12px 0px 12px; // 减少左右padding
position: relative;
background: linear-gradient(
to bottom,
rgba(255, 255, 255, 0.95) 0%,
rgba(248, 250, 252, 0.98) 100%
);
border-radius: 20px 20px 0 0;
margin-top: -20px;
z-index: 3;
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.1);
height: calc(100vh - 340rpx); // 固定高度
display: flex;
flex-direction: column;
overflow: hidden; // 防止内容溢出
}
// 日程标题
.schedule-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
flex-shrink: 0; // 不压缩
.schedule-title {
font-size: 17px;
font-weight: 600;
color: #1f2937;
}
.schedule-link {
display: flex;
align-items: center;
cursor: pointer;
.link-text {
font-size: 13px;
color: #6b7280;
margin-right: 4px;
}
.link-arrow {
font-size: 13px;
color: #6b7280;
}
}
}
// 日期选择器
.date-selector {
margin-bottom: 15px;
background: #ffffff;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
flex-shrink: 0; // 不压缩
margin-left: 0;
margin-right: 0;
.date-container {
display: flex;
padding: 8px 4px;
justify-content: space-around; // 使用space-around确保更好的分布
width: 100%; // 占满宽度
box-sizing: border-box;
}
.date-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 6px 2px;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
flex: 1; // 平均分配空间
max-width: calc((100% - 32px) / 7); // 减去padding后平均分配
min-width: 0; // 允许压缩
.date-day {
font-size: 10px;
color: #6b7280;
margin-bottom: 2px;
white-space: nowrap; // 防止换行
text-align: center;
}
.date-number {
font-size: 14px;
font-weight: 600;
color: #374151;
white-space: nowrap; // 防止换行
text-align: center;
}
.date-dot {
position: absolute;
bottom: 1px;
width: 3px;
height: 3px;
background: #10b981;
border-radius: 50%;
}
&.active {
background: #10b981;
color: #ffffff;
transform: scale(1.02); // 减少放大倍数,避免超出
z-index: 1; // 确保选中项在最上层
margin: 0 -1px; // 轻微负边距补偿放大效果
.date-day,
.date-number {
color: #ffffff;
}
.date-dot {
background: #ffffff;
}
}
}
}
// 日程列表
.schedule-list {
flex: 1; // 占据剩余空间
height: 0; // 配合flex使用确保滚动正常
padding-bottom: 20px;
.schedule-content {
padding-bottom: 35px; // 底部留白
}
.schedule-item {
display: flex;
padding: 14px 0;
border-bottom: 1px solid #f3f4f6;
&:last-child {
border-bottom: none;
}
.time-column {
width: 60px;
flex-shrink: 0;
margin-right: 15px;
.start-time {
display: block;
font-size: 15px;
font-weight: 600;
color: #374151;
line-height: 1.2;
}
.end-time {
display: block;
font-size: 13px;
color: #6b7280;
margin-top: 2px;
line-height: 1.2;
}
}
.content-column {
flex: 1;
min-width: 0; // 允许内容压缩
.event-header {
display: flex;
align-items: center;
margin-bottom: 8px;
.event-tag {
display: inline-flex;
align-items: center;
padding: 3px 8px;
border-radius: 12px;
margin-right: 8px;
flex-shrink: 0; // 标签不压缩
&.meeting {
background: #10b981;
}
&.class {
background: #3b82f6;
}
&.tutoring {
background: #f59e0b;
}
&.preparation {
background: #8b5cf6;
}
&.counseling {
background: #ef4444;
}
.tag-text {
font-size: 11px;
color: #ffffff;
font-weight: 500;
white-space: nowrap;
}
}
.event-title {
font-size: 15px;
font-weight: 500;
color: #1f2937;
flex: 1;
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; // 单行显示,超出省略
}
}
.event-details {
.event-location,
.event-description {
display: flex;
align-items: center;
margin-bottom: 4px;
.location-icon,
.description-icon {
font-size: 13px;
margin-right: 4px;
flex-shrink: 0;
}
.location-text,
.description-text {
font-size: 13px;
color: #6b7280;
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; // 单行显示,超出省略
}
}
}
}
}
.no-schedule {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
min-height: 200px;
.no-schedule-text {
font-size: 15px;
color: #9ca3af;
}
}
}
// 动画定义
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
// 小屏幕适配
@media (max-width: 375px) {
.main-content {
padding: 20px 8px 0px 8px; // 进一步减少padding
}
.date-selector {
.date-container {
padding: 8px 2px; // 减少内边距
}
.date-item {
padding: 5px 1px; // 减少项目内边距
.date-day {
font-size: 9px; // 稍微减小字体
}
.date-number {
font-size: 13px; // 稍微减小字体
}
&.active {
transform: scale(1.01); // 进一步减少放大倍数
margin: 0; // 移除负边距
}
}
}
}
</style>