zhxy-jzd/src/pages/base/home/index.vue
2026-02-23 17:31:01 +08:00

604 lines
15 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="item.id ?? index"
class="grid-item" @click="handleMenuClick(item)">
<view class="grid-icon-container">
<view class="icon-background"></view>
<image :src="item.icon" class="grid-icon" @error="handleImageError" mode="aspectFit"></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, reactive } from "vue";
import XsPicker from "@/pages/base/components/XsPicker/index.vue"
import { getNoticeListApi } from "@/api/base/notice";
import { getMobileMenuApi } from "@/api/system/menu";
import { useUserStore } from "@/store/modules/user";
import { useDataStore } from "@/store/modules/data";
import { useMenuStore } from "@/store/modules/menu";
import { type MobileMenuTreeNode } from "@/api/system/menu";
import { PageUtils } from "@/utils/pageUtil";
import { useDebounce } from "@/utils/debounce";
const userStore = useUserStore();
const { getCurXs } = userStore;
const dataStore = useDataStore();
const menuStore = useMenuStore();
const { setData, getAppCode, setGlobal } = dataStore;
const { debounce } = useDebounce(2000);
/** 家长端菜单项(后端 getMobileMenuApi 动态加载) */
interface HomeMenuItem {
id?: number | string;
title: string;
icon: string;
path?: string;
permissionKey?: string;
action?: string;
lxId?: string;
}
/** 家长端特殊菜单:选课/退费/缴费需 lxId + action后端 auth_code 映射 */
const JZD_SPECIAL_MENU: Record<string, { lxId: string; action: string }> = {
"school-xqkxk": { lxId: "962488654", action: "jf" }, // 兴趣课选课
"school-jlbxk": { lxId: "816059832", action: "jf" }, // 俱乐部选课
"school-jcjf": { lxId: "JC", action: "jf" }, // 就餐报名
"school-xqk-tf": { lxId: "962488654", action: "tf" }, // 兴趣课退费
"school-jlb-tf": { lxId: "816059832", action: "tf" }, // 俱乐部退费
};
// 将菜单树扁平化为家长端结构
function flattenMenuToItems(nodes: MobileMenuTreeNode[]): HomeMenuItem[] {
const items: HomeMenuItem[] = [];
const walk = (list: MobileMenuTreeNode[]) => {
for (const node of list || []) {
if (node.pagePath) {
const icon = (node.normalCss && /^[a-zA-Z0-9_-]+$/.test(node.normalCss))
? `/static/base/home/${node.normalCss}.png`
: "/static/base/home/file-text-line.png";
const authCode = node.authCode || "";
const special = authCode ? JZD_SPECIAL_MENU[authCode] : undefined;
items.push({
id: node.id,
title: node.screenName,
icon,
path: node.pagePath,
permissionKey: authCode,
...(special && { lxId: special.lxId, action: special.action }),
});
}
if (node.children?.length) walk(node.children);
}
};
if (nodes.length === 1 && !nodes[0].pagePath && (nodes[0].children?.length ?? 0) > 0) {
walk(nodes[0].children!);
} else {
walk(nodes);
}
return items;
}
const menuItems = ref<HomeMenuItem[]>([]);
// 通知公告数据
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",
});
}
// 处理菜单点击
const handleMenuClick = debounce(async (item: any) => {
if (item.path) {
if (item.lxId) {
setGlobal({ lxId: item.lxId, action: item.action, from: 'home' });
PageUtils.toHome(item.lxId, item.action);
} else {
uni.navigateTo({
url: item.path,
});
}
}
});
// 切换学生
function switchXs(xs: any) {
getArticleList();
}
// 处理图片加载失败
function handleImageError(e: any) {
// 图片加载失败时,使用默认占位图标
const target = e.target || e.currentTarget;
if (target) {
target.src = '/static/base/home/file-text-line.png'; // 默认图标
}
}
// 跳转到详情页面
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 () => {
setGlobal({ from: 'home' });
// 菜单由 launchPage 加载并写入 menuStore此处读缓存
const cachedMenu = menuStore.getMobileMenu;
if (cachedMenu?.length > 0) {
menuItems.value = flattenMenuToItems(cachedMenu);
} else if (userStore.getToken) {
// 首次注册登录场景launchPage 可能因 token 未就绪返回空菜单,此处补拉
try {
const r = await getMobileMenuApi();
if (r?.result && Array.isArray(r.result) && r.result.length > 0) {
menuStore.setMobileMenu(r.result);
menuItems.value = flattenMenuToItems(r.result);
}
} catch (_e) {}
}
// 确保有学生年级信息才获取通知公告
if (curXs.value && curXs.value.njmcId) {
getArticleList();
} else {
setTimeout(() => {
if (curXs.value && curXs.value.njmcId) {
getArticleList();
}
}, 100);
}
await checkSubscribeStatus();
});
// 检查关注状态
const checkSubscribeStatus = async () => {
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'
});
}
}
});
};
// 监听学生信息变化,当学生信息更新时重新获取通知公告
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>