zhxy-jzd/src/pages/base/home/index.vue
ywyonui 1d1b63df25 Merge branch 'master' of http://119.29.194.155:8894/zwq/zhxy-jzd
# Conflicts:
#	src/pages/base/gzs/index.vue
#	src/pages/base/gzs/jc.vue
#	src/pages/base/gzs/xkXqk.vue
2025-09-02 21:59:16 +08:00

714 lines
18 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";
import { PageUtils } from "@/utils/pageUtil";
const { getCurXs } = useUserStore();
const { setData, getAppCode, setGlobal } = 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/qj/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/xk/xqk",
permissionKey: "school-xqk", // 兴趣课权限编码
},
{
title: "俱乐部",
icon: "/static/base/home/contacts-book-3-line.png",
path: "/pages/base/xk/jlb",
permissionKey: "school-jlb", // 俱乐部权限编码
},
{
title: "就餐详情",
icon: "/static/base/home/contacts-book-3-line.png",
path: "/pages/base/jc/index",
permissionKey: "school-jcjf",
},
{
title: "兴趣课选课",
icon: "/static/base/home/file-text-line.png",
path: "/pages/base/gzs/index",
permissionKey: "school-xqkxk", // 兴趣课选课权限编码
lxId: '962488654',
},
{
title: "俱乐部选课",
icon: "/static/base/home/contacts-book-3-line.png",
path: "/pages/base/gzs/index",
permissionKey: "school-jlbxk", // 俱乐部选课权限编码
lxId: '816059832',
},
{
title: "就餐缴费",
icon: "/static/base/home/contacts-book-3-line.png",
path: "/pages/base/gzs/index",
permissionKey: "school-jcjf",
lxId: 'JC',
},
]);
// 通知公告数据
const announcements = ref<any>([])
// 当前选中的学生
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) {
if (!item.lxId) {
uni.navigateTo({
url: item.path,
});
} else {
setGlobal({ lxId: item.lxId });
PageUtils.toHome(item.lxId);
}
}
}
// 切换学生
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>