zhxy-jzd/src/pages/base/home/index.vue
2025-08-04 15:23:50 +08:00

711 lines
23 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="home-page">
<view class="content-container">
<!-- 用户信息卡片 -->
<view class="user-info-card" v-if="curXs">
<view class="banner-content">
<image src="/static/base/home/2211.png" class="banner-img"></image>
<view class="banner-overlay">
</view>
</view>
<!-- 学生信息 -->
<XsPicker :is-bar="false" @change="switchXs" />
<view class="glxs-btn" @click="goToGlxs">
<u-icon name="plus" size="12" color="#fff"></u-icon>
<text>新增学生</text>
</view>
</view>
<!-- 功能菜单 -->
<view class="menu-section">
<view class="section-title">
<text class="title-text">校园服务</text>
<view class="title-line"></view>
</view>
<view class="grid-menu">
<view
v-for="(item, index) in menuItems"
:key="index"
v-show="hasPermissionDirect(item.permissionKey)"
class="grid-item"
@click="handleMenuClick(item)"
>
<view class="grid-icon-container">
<view class="icon-background"></view>
<image :src="item.icon" class="grid-icon"></image>
</view>
<text class="grid-text">{{ item.title }}</text>
</view>
</view>
</view>
<!-- 通知公告 -->
<view class="notice-section">
<view class="section-title">
<text class="title-text">通知公告</text>
<view class="title-line"></view>
</view>
<view class="notice-list">
<view
v-for="(notice, index) in announcements"
:key="index"
class="notice-item"
@click="goToDetail(notice)"
>
<view class="notice-icon">
<u-icon name="bell" size="20" color="#4A90E2"></u-icon>
</view>
<view class="notice-content">
<text class="notice-title">{{ notice.title }}</text>
<text class="notice-desc">{{ notice.description }}</text>
<view class="notice-footer">
<text class="notice-date">{{ notice.releaseTime }}</text>
<view class="notice-arrow">
<u-icon name="arrow-right" size="12" color="#C8C9CC"></u-icon>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, watch } from "vue";
import { onShow } from "@dcloudio/uni-app";
import XsPicker from "@/pages/base/components/XsPicker/index.vue"
import { cmsArticlePageApi, getUserLatestInfoApi } from "@/api/base/server";
import { getNoticeListApi } from "@/api/base/notice";
import { useUserStore } from "@/store/modules/user";
import { useDataStore } from "@/store/modules/data";
import { hasPermission } from "@/utils/permission";
const { getCurXs } = useUserStore();
const { setData, getAppCode } = useDataStore();
// 刷新相关变量
const { getLastRefreshTime, getRefreshInterval, setLastRefreshTime, updateStudentInfo, updateStudentList } = useUserStore();
const REFRESH_INTERVAL = 7 * 24 * 60 * 60 * 1000; // 1周刷新一次7天
// 获取当前changeTime
const getCurrentChangeTime = () => {
try {
const userDataStr = uni.getStorageSync('app-user');
if (!userDataStr) return null;
const userData = typeof userDataStr === 'string' ? JSON.parse(userDataStr) : userDataStr;
return userData?.changeTime || null;
} catch (error) {
console.error('获取changeTime失败:', error);
return null;
}
};
// 检查权限带changeTime
const checkPermission = (permissionKey: string) => {
const changeTime = getCurrentChangeTime();
return hasPermission(permissionKey, changeTime);
};
// 直接权限检查函数,避免缓存问题
const hasPermissionDirect = (permissionKey: string) => {
if (!permissionKey) return true;
const userStore = useUserStore();
const permissions = userStore.getAuth;
if (!permissions || permissions.length === 0) return false;
const uniquePermissions = [...new Set(permissions)];
return uniquePermissions.includes(permissionKey);
};
// 检查是否需要刷新学生信息
const checkAndRefreshStudentInfo = async () => {
const lastRefreshTime = getLastRefreshTime;
const currentTime = Date.now();
if (!lastRefreshTime || (currentTime - lastRefreshTime) > REFRESH_INTERVAL) {
await refreshStudentInfo();
setLastRefreshTime(currentTime);
}
};
// 刷新学生信息
const refreshStudentInfo = async () => {
try {
const response = await getUserLatestInfoApi();
if (response && response.result) {
// 更新学生列表
if (response.result.xsList && response.result.xsList.length > 0) {
updateStudentList(response.result.xsList);
// 更新当前学生信息(保持当前选中的学生)
const currentXsId = curXs.value?.id;
if (currentXsId) {
const currentStudent = response.result.xsList.find((xs: any) => xs.id === currentXsId);
if (currentStudent) {
updateStudentInfo(currentStudent);
} else {
// 如果当前学生不在列表中,选择第一个学生
updateStudentInfo(response.result.xsList[0]);
}
} else {
// 如果没有当前学生,选择第一个学生
updateStudentInfo(response.result.xsList[0]);
}
}
}
} catch (error) {
// 处理错误,静默失败
}
};
// 菜单项数据
const menuItems = ref([
{
title: "班级课表",
icon: "/static/base/home/book-read-line.png",
path: "/pages/base/class-schedule/index",
permissionKey: "school-bjkb", // 班级课表权限编码
},
{
title: "成绩查询",
icon: "/static/base/home/file-search-line.png",
path: "/pages/base/grades/list",
permissionKey: "school-cjcx", // 成绩查询权限编码
},
{
title: "在线请假",
icon: "/static/base/home/draft-line.png",
path: "/pages/base/leave-request/index",
permissionKey: "school-zxqj", // 在线请假权限编码
},
// TODO:需求待协商硬件对接
// {
// title: "进出校园",
// icon: "/static/base/home/file-transfer-line.png",
// path: "/pages/base/campus-access/index",
// permissionKey: "school-jcxy", // 进出校园权限编码
// },
{
title: "家校沟通",
icon: "/static/base/home/file-transfer-line.png",
path: "/pages/base/jl/index",
permissionKey: "school-jxgt", // 家校沟通权限编码
},
{
title: "兴趣课",
icon: "/static/base/home/file-text-line.png",
path: "/pages/base/interest-class/index",
permissionKey: "school-xqk", // 兴趣课权限编码
},
{
title: "俱乐部",
icon: "/static/base/home/contacts-book-3-line.png",
path: "/pages/base/club/index",
permissionKey: "school-jlb", // 俱乐部权限编码
},
{
title: "兴趣课选课",
icon: "/static/base/home/file-text-line.png",
path: "/pages/base/course-selection/notice",
permissionKey: "school-xqkxk", // 兴趣课选课权限编码
},
{
title: "俱乐部选课",
icon: "/static/base/home/contacts-book-3-line.png",
path: "/pages/base/course-selection/noticeclub",
permissionKey: "school-jlbxk", // 俱乐部选课权限编码
},
]);
// 通知公告数据
const announcements = ref<any>([
{
"id": 2142746416,
"createdTime": "2024-10-10 09:37:45",
"updatedTime": "2024-10-12 15:47:44",
"createdUserName": "管理员",
"updatedUserName": "管理员",
"title": "值周学生常规规范",
"columnId": 24,
"sort": 1,
"description": "",
"releaseTime": "2024-10-10 09:32:36",
"author": "",
"source": "",
"content": "<p style=\"text-align: center;\"><br></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px;\">1.值周前一周放假前清楚自己常规检查的位楼栋、楼层和班级;</span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px;\">2.检查时段包括早读、大课间、下午眼保健操。</span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px;\">3.检查内容。</span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px; font-family: 黑体;\"><strong>早读八看:</strong></span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px;\">⑴看是否有教师值守;</span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px;\">⑵看值守教师是否长时间玩手机;</span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px;\">⑶看黑板上的值日课程贴是否填写;</span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px;\">⑷看同学是否到班即读,有无疯玩打闹;</span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px;\">⑸看课桌椅、讲桌是否整齐、整洁;</span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px;\">⑹看红领巾佩戴(队徽)情况;</span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px;\">⑺看教室、走廊是否有垃圾;</span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px;\">⑻看黑板上是否书写(课件)有早读的内容和要求。</span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px; font-family: 黑体;\"><strong>大课间七看:</strong></span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px;\">⑴看是否认真组织室内操;</span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px;\">⑵看室外操班级是否留有学生在教室且打闹;</span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px;\">⑶看室内操是否安静、动用是否整齐;</span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px;\">⑷看是否有教师值守室内操;</span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px;\">⑸看教室、走廊地面是否有垃圾;</span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px;\">⑹看室外操班级桌面是否收拾整洁;</span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px;\">⑺看室外操班级是否关闭用电设备。</span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px; font-family: 黑体;\"><strong>眼保健操五看:</strong></span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px;\">⑴看是否按要求组织眼保健操;</span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px;\">⑵看做操时是否关灯;</span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px;\">⑶看是否有学生讲话、未做、未闭眼做操;</span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px;\">⑷看教室、走廊地面是否整洁;</span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px;\">⑸看走廊书吧是否整齐。</span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px;\">4.检查时实行蹲点检查,整个时段来回巡查。</span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px;\">5.校门口礼仪队同学要保持良好的精神状态,着装整洁,可设计个性的欢迎口号、动作、道具等,热情向师生问好;</span></p><p style=\"text-indent: 30pt; line-height: 2;\"><span style=\"color: black; font-size: 19px;\">6.常规检查同学到班检查要佩戴好绶带,进入教室要喊“报告”。</span></p>",
"columnName": "师、生值周工作要求",
"pk": 2142746416
}
])
// 当前选中的学生
let curXs = computed(() => getCurXs)
let pageParams = ref({
page: 1,
rows: 10,
appCode: getAppCode
})
const goToGlxs = () => {
uni.navigateTo({
url: "/pages/base/home/glxs",
});
}
// 处理菜单点击
function handleMenuClick(item: any) {
if (item.path) {
uni.navigateTo({
url: item.path,
});
}
}
// 切换学生
function switchXs(xs: any) {
getArticleList();
}
// 跳转到详情页面
function goToDetail(notice: any) {
setData(notice)
uni.navigateTo({
url: '/pages/base/home/detail'
})
}
const getArticleList = async () => {
if (curXs.value && curXs.value.njmcId) {
const params = {
page: pageParams.value.page,
rows: pageParams.value.rows,
appCode: getAppCode,
fbfw: 'JZ', // 发布范围:家长端
xqId: curXs.value.xqId || curXs.value.xq_id, // 学生所在学期
fbNjmcId: curXs.value.njmcId, // 学生所在年级
releaseFlag: 'A' // 发布状态:有效
};
getNoticeListApi(params).then(res => {
announcements.value = res.rows;
})
.catch((error) => {
// 接口调用失败
});
}
};
onMounted(async () => {
// 确保有学生年级信息才获取通知公告
if (curXs.value && curXs.value.njmcId) {
getArticleList();
} else {
// 如果store中没有学生信息等待一下再尝试
setTimeout(() => {
if (curXs.value && curXs.value.njmcId) {
getArticleList();
}
}, 100);
}
// 初始化时检查是否需要刷新学生信息
await checkAndRefreshStudentInfo();
// 检查权限缓存,确保权限数据是最新的
const userStore = useUserStore();
const changeTime = userStore.getChangeTime;
if (changeTime) {
// 如果有changeTime检查是否需要刷新权限
const { PermissionCacheManager } = await import('@/utils/permission');
const cacheInfo = PermissionCacheManager.getCacheInfo();
if (cacheInfo.hasCache && cacheInfo.changeTime) {
const serverTime = new Date(changeTime).getTime();
const cacheTime = new Date(cacheInfo.changeTime).getTime();
if (serverTime > cacheTime) {
// 服务器时间更新,刷新权限缓存
const { refreshPermissionCache } = await import('@/utils/permission');
const currentPermissions = userStore.getAuth;
if (currentPermissions && currentPermissions.length > 0) {
refreshPermissionCache(currentPermissions, changeTime);
}
}
}
}
// 检查用户是否已关注服务号
await checkSubscribeStatus();
});
// 检查关注状态
const checkSubscribeStatus = async () => {
const userStore = useUserStore();
const userInfo = userStore.getUser;
if (userInfo && !userInfo.subscribed) {
// 延迟显示关注提醒,避免与启动页面冲突
setTimeout(() => {
showSubscribeReminder();
}, 2000);
}
};
// 显示关注提醒
const showSubscribeReminder = () => {
uni.showModal({
title: '关注服务号',
content: '请先关注我们的服务号,以便接收重要通知',
showCancel: true,
cancelText: '稍后关注',
confirmText: '立即关注',
success: (res) => {
if (res.confirm) {
// 跳转到关注页面
uni.navigateTo({
url: '/pages/system/subscribe/index'
});
}
}
});
};
// 页面显示时检查是否需要刷新
onShow(async () => {
await checkAndRefreshStudentInfo();
});
// 监听学生信息变化,当学生信息更新时重新获取通知公告
watch(curXs, (newXs, oldXs) => {
if (newXs && newXs.njmcId) {
getArticleList();
}
}, { immediate: false });
</script>
<style lang="scss" scoped>
.home-page {
background-color: #ffffff;
min-height: 100vh;
position: relative;
}
.content-container {
padding: 15px;
position: relative;
z-index: 1;
}
/* 用户信息卡片 */
.user-info-card {
position: relative;
background: linear-gradient(135deg, #ffffff 0%, #f8faff 100%);
border-radius: 20px;
padding: 20px;
margin-bottom: 20px;
margin-top: 20px;
box-shadow: 0 12px 40px rgba(74, 144, 226, 0.25);
border: 1px solid rgba(255, 255, 255, 0.8);
overflow: hidden;
.banner-content {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
.banner-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.banner-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, rgba(74, 144, 226, 0.8) 0%, rgba(53, 122, 189, 0.9) 100%);
display: flex;
align-items: center;
justify-content: center;
}
}
.glxs-btn {
position: absolute;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
padding: 8px 16px;
background: linear-gradient(135deg, #4A90E2 0%, #357ABD 100%);
color: #ffffff;
border-top-left-radius: 20px;
border-bottom-right-radius: 20px;
font-size: 13px;
font-weight: 500;
box-shadow: 0 4px 12px rgba(74, 144, 226, 0.3);
transition: all 0.3s ease;
flex-shrink: 0;
min-width: 60px;
height: 16px;
text {
line-height: 1;
}
}
}
/* 功能菜单 */
.menu-section {
margin-bottom: 25px;
.section-title {
display: flex;
align-items: center;
margin-bottom: 20px;
padding: 0 5px;
.title-text {
font-size: 18px;
font-weight: 600;
color: #303133;
position: relative;
padding-left: 12px;
&::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 16px;
background: linear-gradient(135deg, #4A90E2 0%, #357ABD 100%);
border-radius: 2px;
}
}
.title-line {
flex: 1;
height: 1px;
background: linear-gradient(90deg, #e0e6ed 0%, transparent 100%);
margin-left: 20px;
}
}
.grid-menu {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0;
background: linear-gradient(135deg, #ffffff 0%, #f8faff 100%);
border-radius: 16px;
box-shadow: 0 8px 32px rgba(74, 144, 226, 0.2);
border: 1px solid rgba(255, 255, 255, 0.8);
.grid-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px 10px;
transition: all 0.3s ease;
border-radius: 12px;
position: relative;
&:active {
transform: scale(0.95);
background-color: rgba(74, 144, 226, 0.05);
}
.grid-icon-container {
position: relative;
width: 48px;
height: 48px;
margin-bottom: 12px;
display: flex;
align-items: center;
justify-content: center;
.icon-background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 12px;
background: linear-gradient(135deg, #f0f4ff 0%, #e6f0ff 100%);
box-shadow: 0 2px 8px rgba(74, 144, 226, 0.15);
}
.grid-icon {
position: relative;
z-index: 1;
width: 28px;
height: 28px;
object-fit: contain;
}
}
.grid-text {
font-size: 13px;
font-weight: 500;
color: #303133;
text-align: center;
line-height: 1.2;
}
}
}
}
/* 通知公告 */
.notice-section {
.section-title {
display: flex;
align-items: center;
margin-bottom: 20px;
padding: 0 5px;
.title-text {
font-size: 18px;
font-weight: 600;
color: #303133;
position: relative;
padding-left: 12px;
&::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 16px;
background: linear-gradient(135deg, #4A90E2 0%, #357ABD 100%);
border-radius: 2px;
}
}
.title-line {
flex: 1;
height: 1px;
background: linear-gradient(90deg, #e0e6ed 0%, transparent 100%);
margin-left: 20px;
}
}
.notice-list {
.notice-item {
display: flex;
align-items: flex-start;
background: linear-gradient(135deg, #ffffff 0%, #f8faff 100%);
padding: 18px;
margin-bottom: 12px;
border-radius: 16px;
box-shadow: 0 8px 24px rgba(74, 144, 226, 0.18);
border: 1px solid rgba(255, 255, 255, 0.8);
transition: all 0.3s ease;
&:active {
transform: translateY(-2px);
box-shadow: 0 12px 36px rgba(74, 144, 226, 0.25);
}
.notice-icon {
width: 50px;
height: 50px;
background: linear-gradient(135deg, #f0f4ff 0%, #e6f0ff 100%);
border-radius: 12px;
margin-right: 15px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(74, 144, 226, 0.15);
flex-shrink: 0;
}
.notice-content {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
.notice-title {
font-size: 15px;
font-weight: 600;
color: #303133;
margin-bottom: 8px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
line-height: 1.3;
}
.notice-desc {
font-size: 13px;
color: #606266;
margin-bottom: 12px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
flex: 1;
line-height: 1.4;
}
.notice-footer {
display: flex;
align-items: center;
justify-content: space-between;
.notice-date {
font-size: 12px;
color: #909399;
font-weight: 400;
line-height: 1;
}
.notice-arrow {
width: 20px;
height: 20px;
background-color: #f5f7fa;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
}
}
}
}
}
/* 响应式优化 */
@media (max-width: 375px) {
.content-container {
padding: 12px;
}
.grid-menu .grid-item {
padding: 15px 8px;
}
}
</style>