diff --git a/src/api/base/notice.ts b/src/api/base/notice.ts new file mode 100644 index 0000000..2ab7eed --- /dev/null +++ b/src/api/base/notice.ts @@ -0,0 +1,17 @@ +import { get } from "@/utils/request"; + +// 通知公告分页查询参数接口 +export interface NoticePageParams { + page: number; + rows: number; + appCode: string; + fbfw?: string; + xqId?: string; + fbNjmcId?: string; + releaseFlag?: string; +} + +// 获取通知公告列表 +export function getNoticeListApi(params: NoticePageParams) { + return get("/api/cms/article/list", params); +} \ No newline at end of file diff --git a/src/api/system/login/index.ts b/src/api/system/login/index.ts index 704e17c..c6e7164 100644 --- a/src/api/system/login/index.ts +++ b/src/api/system/login/index.ts @@ -63,6 +63,14 @@ export const checkOpenId = async (param: { export const updateUserApi = async (param: any) => { return await post("/open/login/js/updateUser", param); }; + +export const updateSignFileApi = async (param: { userId: number; signFile: string }) => { + return await post("/api/user/updateSignFile", { + id: param.userId, + signFile: param.signFile + }); +}; + export const findJsByPhoneApi = async (param: any) => { return await get("/api/js/findJsByPhone", param); }; diff --git a/src/pages.json b/src/pages.json index dcc8157..100bf55 100644 --- a/src/pages.json +++ b/src/pages.json @@ -57,6 +57,15 @@ } } }, + { + "path": "pages/system/subscribe/index", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "关注服务号", + "enablePullDownRefresh": false, + "backgroundColor": "#f4f5f7" + } + }, { "path": "pages/system/webView/webView", "style": { @@ -242,6 +251,13 @@ "enablePullDownRefresh": false } }, + { + "path": "pages/base/course-selection/noticeclub", + "style": { + "navigationBarTitleText": "告知书", + "enablePullDownRefresh": false + } + }, { "path": "pages/base/course-selection/enrolled", "style": { @@ -254,6 +270,14 @@ "navigationBarTitleText": "未开放", "enablePullDownRefresh": false } + }, + { + "path": "pages/base/course-selection/enrollment-ended", + "style": { + "navigationBarTitleText": "选课已结束", + "enablePullDownRefresh": false, + "navigationStyle": "custom" + } } ], "globalStyle": { diff --git a/src/pages/base/components/XkCountdown/index.vue b/src/pages/base/components/XkCountdown/index.vue index 0ec2630..03f50a0 100644 --- a/src/pages/base/components/XkCountdown/index.vue +++ b/src/pages/base/components/XkCountdown/index.vue @@ -1,7 +1,6 @@ @@ -48,17 +58,20 @@ const countdownTime = reactive({ const kcStatus = ref(false); -// 倒计时标题文本 -const countdownTitle = computed(() => { - return kcStatus.value ? "距离选课结束还剩" : "距离选课开始还剩"; -}); - // 当前选中的学生 let countdownTimer: number | null = null; const remainTime = ref("00:00:00"); // 添加选课是否已结束的标记 const isEnrollmentEnded = ref(false); +// 添加选课是否已结束的标记(区分选课开始和选课结束) +const isCourseEnded = ref(false); + +// 格式化结束时间 +const formatEndTime = computed(() => { + if (!props.xk || !props.xk.xkjstime) return ''; + return dayjs(props.xk.xkjstime).format('YYYY-MM-DD'); +}); // 启动倒计时 const startCountdown = (endTimeStamp: number) => { @@ -82,26 +95,10 @@ const startCountdown = (endTimeStamp: number) => { countdownTimer = null; } - // 判断当前倒计时是选课开始还是选课结束 - if (!kcStatus.value) { - // 如果是选课开始倒计时结束,则切换到选课结束倒计时 - kcStatus.value = true; // 更新状态为选课已开始 - uni.showToast({ - title: "选课已开始", - icon: "none", - }); - // 开始选课结束倒计时 - const xkjstime = dayjs(props.xk.xkjstime).valueOf(); - startCountdown(xkjstime); - } else { - // 如果是选课结束倒计时结束 - isEnrollmentEnded.value = true; // 标记选课已结束 - emit("over"); - uni.showToast({ - title: "选课已结束", - icon: "none", - }); - } + // 选课开始倒计时结束 + isEnrollmentEnded.value = true; // 标记选课已开始,隐藏倒计时 + isCourseEnded.value = false; // 标记选课未结束 + emit("over"); return; } @@ -134,22 +131,29 @@ const changeXk = (xk: any) => { //获取选课结束时间 const xkjstime = dayjs(xk.xkjstime).valueOf(); - // 检查是否已经超过选课结束时间 - if (now > xkjstime) { - kcStatus.value = true; + // 如果当前时间 >= 选课结束时间,则显示选课已结束 + if (now >= xkjstime) { isEnrollmentEnded.value = true; + isCourseEnded.value = true; countdownTime.hours = "00"; countdownTime.minutes = "00"; countdownTime.seconds = "00"; + return; } - //判断选课是否开始 - else if (now < xkkstime) { - kcStatus.value = false; - startCountdown(xkkstime); - } else { - kcStatus.value = true; - startCountdown(xkjstime); + + // 如果当前时间 >= 选课开始时间 且 < 选课结束时间,显示报名提示 + if (now >= xkkstime && now < xkjstime) { + isEnrollmentEnded.value = true; + isCourseEnded.value = false; + countdownTime.hours = "00"; + countdownTime.minutes = "00"; + countdownTime.seconds = "00"; + return; } + + // 只有当前时间 < 选课开始时间时才显示倒计时 + kcStatus.value = false; + startCountdown(xkkstime); } // 页面卸载前清除定时器 @@ -174,38 +178,37 @@ if (props.xk && props.xk.id) { \ No newline at end of file diff --git a/src/pages/base/components/XkPicker/index.vue b/src/pages/base/components/XkPicker/index.vue index 94063ea..ad09b08 100644 --- a/src/pages/base/components/XkPicker/index.vue +++ b/src/pages/base/components/XkPicker/index.vue @@ -117,13 +117,6 @@ const loadXkList = async () => { const switchXk = (xk: any) => { curXk.value = xk; showFlag.value = false; - if (xk && xk.id) { - // 显示切换成功提示 - uni.showToast({ - title: `已切换到${xk.xkmc}`, - icon: "none", - }); - } emit("change", xk); } diff --git a/src/pages/base/components/XkkcList/index.vue b/src/pages/base/components/XkkcList/index.vue index 1ff678e..b9ab1d9 100644 --- a/src/pages/base/components/XkkcList/index.vue +++ b/src/pages/base/components/XkkcList/index.vue @@ -9,15 +9,17 @@ :class="{ selected: xkkc.isSelected }" @click="toggleSelection(xkkc)" > - {{ xkkc.kcmc }} + + {{ xkkc.kcmc }} + + + + 报名情况: {{ xkkc.hasNum || 0 }} | {{ xkkc.maxNum || 0 }} - 详情 { setCurXs(xs); showFlag.value = false; emit('change', xs); - // 这里可以添加切换学生后的其他操作,如刷新页面数据等 - uni.showToast({ - title: `已切换到${xs.xm}`, - icon: "none", - }); +} + +// 设置当前学生(无提示) +const setCurrentStudent = (xs: any) => { + setCurXs(xs); + emit('change', xs); } // 如果是bar形式,则默认打开选择器 -if (props.isBar -//&& (getCurXs === null || !getCurXs.id) -) { +if (props.isBar) { if (getUser.xsList.length > 1 ) { showPicker(); } else { - switchXs(getUser.xsList[0]); + // 只有一个学生时,直接设置但不显示提示 + setCurrentStudent(getUser.xsList[0]); } } diff --git a/src/pages/base/course-selection/club-selection.vue b/src/pages/base/course-selection/club-selection.vue index c9743bc..f074242 100644 --- a/src/pages/base/course-selection/club-selection.vue +++ b/src/pages/base/course-selection/club-selection.vue @@ -3,12 +3,15 @@ - - - - - - + + + + + + + + + @@ -35,6 +38,7 @@ import XkkcList from "@/pages/base/components/XkkcList/index.vue" import { jzXkQkjApi } from "@/api/base/server"; import { useUserStore } from "@/store/modules/user"; import { useDataStore } from "@/store/modules/data"; +import dayjs from "dayjs"; const { getCurXs, getUser } = useUserStore(); const { setData, getData } = useDataStore(); @@ -46,9 +50,31 @@ const selectedXkkcIds = ref([]); const xsFlag = ref(true); +// 检查选课是否已结束 +const checkEnrollmentStatus = (xk: any) => { + if (!xk || !xk.xkjstime) return; + + const now = new Date().getTime(); + const endTime = new Date(xk.xkjstime).getTime(); + + if (now > endTime) { + // 选课已结束,跳转到结束页面 + const courseInfo = encodeURIComponent(JSON.stringify(xk)); + uni.navigateTo({ + url: `/pages/base/course-selection/enrollment-ended?courseInfo=${courseInfo}` + }); + return true; + } + return false; +}; + // 切换选课 const switchXk = (xk: any) => { curXk.value = xk; + // 检查选课状态 + if (checkEnrollmentStatus(xk)) { + return; + } } const switchXs = (xs: any) => { @@ -57,7 +83,9 @@ const switchXs = (xs: any) => { // 选课时间结束 const xkTimeOver = (val: any) => { - console.log(val); + console.log('选课时间结束:', val); + // 选课开始时不再跳转,让用户继续选课 + // 只有在选课结束时才跳转到选课结束页面 } // 选中课程 @@ -75,6 +103,21 @@ const submit = async () => { }); return; } + + // 检查选课时间 + if (curXk.value && curXk.value.xkkstime) { + const now = dayjs().valueOf(); + const startTime = dayjs(curXk.value.xkkstime).valueOf(); + + if (now < startTime) { + uni.showToast({ + title: "选课还未开始,请耐心等待!", + icon: "none", + }); + return; + } + } + uni.showLoading({ title: "抢课中...", }); @@ -173,6 +216,36 @@ const submit = async () => { flex-direction: column; gap: 15px; + .top-row { + display: flex; + align-items: center; + + :deep(.title-section) { + width: 100%; + } + } + + .bottom-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 15px; + + // 学生选择器样式 + :deep(.xs-bar) { + flex: 1; + min-width: 0; + } + + // 倒计时组件样式 + :deep(.countdown-section) { + flex-shrink: 0; + background: rgba(255, 255, 255, 0.1); + border-radius: 6px; + padding: 6px 10px; + backdrop-filter: blur(10px); + } + } } } diff --git a/src/pages/base/course-selection/detail.vue b/src/pages/base/course-selection/detail.vue index 5895df4..2377bce 100644 --- a/src/pages/base/course-selection/detail.vue +++ b/src/pages/base/course-selection/detail.vue @@ -161,7 +161,7 @@ const courseDetail = computed(() => { location: data.kcdd || "暂无地点信息", price: data.kcje || 0, studyTime: data.studyTime || "暂无上课时间", - xkkcImg: data.xkkcImg || "/static/images/robot-course.jpg", // 默认图片 + xkkcImg: imagUrl(data.xkkcImg), // 使用imagUrl处理图片路径 }; }); diff --git a/src/pages/base/course-selection/enrollment-ended.vue b/src/pages/base/course-selection/enrollment-ended.vue new file mode 100644 index 0000000..9fd5088 --- /dev/null +++ b/src/pages/base/course-selection/enrollment-ended.vue @@ -0,0 +1,291 @@ + + + + + \ No newline at end of file diff --git a/src/pages/base/course-selection/index.vue b/src/pages/base/course-selection/index.vue index 32e04a8..1b4abaa 100644 --- a/src/pages/base/course-selection/index.vue +++ b/src/pages/base/course-selection/index.vue @@ -5,10 +5,11 @@ - - - - + + + + + @@ -35,6 +36,7 @@ import XkkcList from "@/pages/base/components/XkkcList/index.vue" import { jzXkQkjApi } from "@/api/base/server"; import { useUserStore } from "@/store/modules/user"; import { useDataStore } from "@/store/modules/data"; +import dayjs from "dayjs"; const { getCurXs, getUser } = useUserStore(); const { setData, getData } = useDataStore(); @@ -47,9 +49,31 @@ const selectedXkkcIds = ref([]); const xsFlag = ref(true); +// 检查选课是否已结束 +const checkEnrollmentStatus = (xk: any) => { + if (!xk || !xk.xkjstime) return; + + const now = new Date().getTime(); + const endTime = new Date(xk.xkjstime).getTime(); + + if (now > endTime) { + // 选课已结束,跳转到结束页面 + const courseInfo = encodeURIComponent(JSON.stringify(xk)); + uni.navigateTo({ + url: `/pages/base/course-selection/enrollment-ended?courseInfo=${courseInfo}` + }); + return true; + } + return false; +}; + // 切换选课 const switchXk = (xk: any) => { curXk.value = xk; + // 检查选课状态 + if (checkEnrollmentStatus(xk)) { + return; + } } // 切换学生 @@ -59,7 +83,12 @@ const switchXs = (xs: any) => { // 选课时间结束 const xkTimeOver = (val: any) => { - console.log(val); + console.log('选课时间结束:', val); + // 跳转到选课结束页面 + const courseInfo = encodeURIComponent(JSON.stringify(curXk.value)); + uni.navigateTo({ + url: `/pages/base/course-selection/enrollment-ended?courseInfo=${courseInfo}` + }); } // 选中课程 @@ -77,6 +106,21 @@ const submit = async () => { }); return; } + + // 检查选课时间 + if (curXk.value && curXk.value.xkkstime) { + const now = dayjs().valueOf(); + const startTime = dayjs(curXk.value.xkkstime).valueOf(); + + if (now < startTime) { + uni.showToast({ + title: "选课还未开始,请耐心等待!", + icon: "none", + }); + return; + } + } + uni.showLoading({ title: "抢课中...", }); @@ -174,6 +218,27 @@ const submit = async () => { flex-direction: column; gap: 15px; + .bottom-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 15px; + + // 学生选择器样式 + :deep(.xs-bar) { + flex: 1; + min-width: 0; + } + + // 倒计时组件样式 + :deep(.countdown-section) { + flex-shrink: 0; + background: rgba(255, 255, 255, 0.1); + border-radius: 6px; + padding: 6px 10px; + backdrop-filter: blur(10px); + } + } } } diff --git a/src/pages/base/course-selection/notice.vue b/src/pages/base/course-selection/notice.vue index 30632ab..4d4cfb4 100644 --- a/src/pages/base/course-selection/notice.vue +++ b/src/pages/base/course-selection/notice.vue @@ -25,14 +25,19 @@ + + diff --git a/src/pages/base/home/index.vue b/src/pages/base/home/index.vue index c71de15..ceae315 100644 --- a/src/pages/base/home/index.vue +++ b/src/pages/base/home/index.vue @@ -27,6 +27,7 @@ @@ -79,14 +80,48 @@ 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 = 10 * 60 * 1000; // 10分钟刷新一次(测试用) +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 () => { @@ -135,37 +170,56 @@ 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", // 俱乐部选课权限编码 }, ]); @@ -229,13 +283,21 @@ function goToDetail(notice: any) { const getArticleList = async () => { if (curXs.value && curXs.value.njmcId) { - const params = Object.assign({}, pageParams.value, { njmcId: 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' // 发布状态:有效 + }; - cmsArticlePageApi(params).then(res => { - announcements.value = res.rows; - }) - .catch((error) => { - // 接口调用失败 + getNoticeListApi(params).then(res => { + announcements.value = res.rows; + }) + .catch((error) => { + // 接口调用失败 }); } }; @@ -255,8 +317,66 @@ onMounted(async () => { // 初始化时检查是否需要刷新学生信息 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(); diff --git a/src/pages/system/launchPage/launchPage.vue b/src/pages/system/launchPage/launchPage.vue index 28b0259..8c79c74 100644 --- a/src/pages/system/launchPage/launchPage.vue +++ b/src/pages/system/launchPage/launchPage.vue @@ -16,6 +16,7 @@ import {onLoad} from "@dcloudio/uni-app"; import {useDataStore} from "@/store/modules/data"; import {useUserStore} from "@/store/modules/user"; import {checkOpenId} from "@/api/system/login"; +import {refreshPermissionCache} from "@/utils/permission"; const { setGlobal } = useDataStore(); const { afterLoginAction } = useUserStore(); @@ -36,24 +37,35 @@ function toHome(data: any) { onLoad(async (data: any) => { setGlobal(data); if (data && data.openId) { - checkOpenId({ openId: data.openId, appCode: "JZ" }) - .then(async (res) => { - if (res.resultCode == 1) { - if (res.result) { - afterLoginAction(res.result); - toHome(data); - return; + try { + const res = await checkOpenId({ openId: data.openId, appCode: "JZ" }); + + if (res.resultCode == 1 && res.result) { + // 执行登录操作 + afterLoginAction(res.result); + + // 如果有changeTime参数,更新权限缓存 + if (data.changeTime) { + const userStore = useUserStore(); + const currentPermissions = userStore.getAuth; + + if (currentPermissions && currentPermissions.length > 0) { + refreshPermissionCache(currentPermissions, data.changeTime); } } - uni.reLaunch({ - url: "/pages/system/login/login", - }); - }) - .catch((err) => { - uni.reLaunch({ - url: "/pages/system/login/login", - }); - }); + + // 直接跳转到首页,关注检查在首页进行 + toHome(data); + } else { + uni.reLaunch({ + url: "/pages/system/login/login", + }); + } + } catch (err) { + uni.reLaunch({ + url: "/pages/system/login/login", + }); + } } else { uni.reLaunch({ url: "/pages/system/login/login", diff --git a/src/pages/system/login/login.vue b/src/pages/system/login/login.vue index 3cc65e7..3d6d832 100644 --- a/src/pages/system/login/login.vue +++ b/src/pages/system/login/login.vue @@ -124,6 +124,7 @@ import { loginRegisterJzApi } from "@/api/base/server"; import { useUserStore } from "@/store/modules/user"; import { useDataStore } from "@/store/modules/data"; import {imagUrl} from "@/utils"; +import {refreshPermissionCache} from "@/utils/permission"; const dicOptions = ref([[[]]]); const dicPickerRef = ref(); @@ -264,7 +265,7 @@ function toHome() { }); } else if (getGlobal.type == 2) { uni.reLaunch({ - url: "/pages/base/course-selection/club-selection", + url: "/pages/base/course-selection/noticeclub", }); } else { uni.reLaunch({ @@ -305,6 +306,17 @@ async function submit() { hideLoading(); if (res.resultCode == 1) { afterLoginAction(res.result); + + // 如果有changeTime参数,更新权限缓存 + if (res.result && res.result.changeTime) { + const userStore = useUserStore(); + const currentPermissions = userStore.getAuth; + + if (currentPermissions && currentPermissions.length > 0) { + refreshPermissionCache(currentPermissions, res.result.changeTime); + } + } + toHome(); } else { showToast({ title: res.message || "提交失败", icon: "none" }); diff --git a/src/pages/system/subscribe/index.vue b/src/pages/system/subscribe/index.vue new file mode 100644 index 0000000..81745ba --- /dev/null +++ b/src/pages/system/subscribe/index.vue @@ -0,0 +1,194 @@ + + + + + \ No newline at end of file diff --git a/src/static/base/home/details.svg b/src/static/base/home/details.svg new file mode 100644 index 0000000..e36784f --- /dev/null +++ b/src/static/base/home/details.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/static/base/home/rdcode.jpg b/src/static/base/home/rdcode.jpg new file mode 100644 index 0000000..9950092 Binary files /dev/null and b/src/static/base/home/rdcode.jpg differ diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts index 4f7eec8..34959b5 100644 --- a/src/store/modules/user.ts +++ b/src/store/modules/user.ts @@ -15,6 +15,7 @@ interface UserState { auth: string[]; lastRefreshTime: number; // 上次刷新时间 refreshInterval: number; // 刷新间隔(毫秒) + changeTime: string; // 权限变更时间 ws: any; wsCallback: any; } @@ -32,6 +33,7 @@ export const useUserStore = defineStore({ auth: [], lastRefreshTime: 0, // 上次刷新时间 refreshInterval: 7 * 24 * 60 * 60 * 1000, // 刷新间隔(毫秒) + changeTime: '', // 权限变更时间 ws: null, wsCallback: defWsCallback }), @@ -53,6 +55,9 @@ export const useUserStore = defineStore({ }, getRefreshInterval(): number { return this.refreshInterval; + }, + getChangeTime(): string { + return this.changeTime; } }, actions: { @@ -74,6 +79,9 @@ export const useUserStore = defineStore({ setRefreshInterval(interval: number) { this.refreshInterval = interval; }, + setChangeTime(changeTime: string) { + this.changeTime = changeTime; + }, // 更新学生信息 updateStudentInfo(studentInfo: any) { this.setCurXs(studentInfo); diff --git a/src/utils/permission.ts b/src/utils/permission.ts index 9d61774..c7d5ce5 100644 --- a/src/utils/permission.ts +++ b/src/utils/permission.ts @@ -2,11 +2,258 @@ import {ISROUTERINTERCEPT} from "@/config"; import {getRouter} from "@/utils/uniapp"; import {useUserStore} from "@/store/modules/user"; -export function _auth(autd: string) { - const {getAuth} = useUserStore() - return getAuth.includes(autd); +// 权限缓存接口 +interface PermissionCache { + permissions: string[]; + timestamp: number; + userId: string; + changeTime: string; } +// 存储键名 +const PERMISSION_CACHE_KEY = 'permission_cache'; +const CHANGE_TIME_KEY = 'change_time'; + +// 存储操作 +function setStorage(key: string, value: any): void { + try { + const jsonValue = JSON.stringify(value); + uni.setStorageSync(key, jsonValue); + } catch (error) { + console.error('存储权限缓存失败:', error); + } +} + +function getStorage(key: string): any { + try { + const value = uni.getStorageSync(key); + + if (value) { + const parsedValue = JSON.parse(value); + return parsedValue; + } else { + return null; + } + } catch (error) { + console.error('获取权限缓存失败:', error); + return null; + } +} + +// 从app-user获取changeTime +export function getChangeTimeFromAppUser(): string | null { + 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到app-user +function setChangeTimeToAppUser(changeTime: string): void { + try { + const userDataStr = uni.getStorageSync('app-user'); + if (!userDataStr) return; + + const userData = typeof userDataStr === 'string' ? JSON.parse(userDataStr) : userDataStr; + userData.changeTime = changeTime; + uni.setStorageSync('app-user', JSON.stringify(userData)); + } catch (error) { + console.error('设置changeTime失败:', error); + } +} + +// 获取权限缓存 +function getPermissionCache(): PermissionCache | null { + return getStorage(PERMISSION_CACHE_KEY); +} + +// 设置权限缓存 +function setPermissionCache(permissions: string[], userId: string, changeTime?: string): void { + const cache: PermissionCache = { + permissions, + timestamp: Date.now(), + userId, + changeTime: changeTime || '' + }; + + setStorage(PERMISSION_CACHE_KEY, cache); + + if (changeTime) { + setChangeTimeToAppUser(changeTime); + } +} + +// 清除权限缓存 +function clearPermissionCache(): void { + try { + uni.removeStorageSync(PERMISSION_CACHE_KEY); + } catch (error) { + console.error('清除权限缓存失败:', error); + } +} + +// 检查缓存是否有效 +function isCacheValid(cache: PermissionCache, currentUserId: string): boolean { + if (!cache || !cache.permissions || cache.userId !== currentUserId) { + return false; + } + + // 检查缓存是否过期(7天) + const now = Date.now(); + const cacheAge = now - cache.timestamp; + const maxAge = 7 * 24 * 60 * 60 * 1000; // 7天 + + return cacheAge < maxAge; +} + +// 获取用户权限(带缓存) +function getUserPermissionsWithCache(currentChangeTime?: string): string[] { + const userStore = useUserStore(); + const currentUser = userStore.getUser; + const currentUserId = currentUser?.userId || currentUser?.id; + + if (!currentUserId) { + return userStore.getAuth; + } + + const cache = getPermissionCache(); + + if (cache && isCacheValid(cache, currentUserId)) { + if (currentChangeTime) { + const serverTime = new Date(currentChangeTime).getTime(); + const cacheTime = new Date(cache.changeTime).getTime(); + + if (serverTime > cacheTime) { + const permissions = userStore.getAuth; + if (permissions && permissions.length > 0) { + setPermissionCache(permissions, currentUserId, currentChangeTime); + } + return permissions; + } else { + return cache.permissions; + } + } else { + return cache.permissions; + } + } + + const permissions = userStore.getAuth; + if (permissions && permissions.length > 0) { + setPermissionCache(permissions, currentUserId, currentChangeTime); + } + + return permissions; +} + +// 刷新权限缓存 +export function refreshPermissionCache(permissions?: string[], changeTime?: string): void { + const userStore = useUserStore(); + const currentUser = userStore.getUser; + const currentUserId = currentUser?.userId || currentUser?.id; + + if (!currentUserId) { + return; + } + + const permissionList = permissions || userStore.getAuth; + + const currentCache = getPermissionCache(); + if (currentCache && currentCache.permissions && permissionList) { + const isSame = JSON.stringify(currentCache.permissions.sort()) === JSON.stringify(permissionList.sort()); + if (isSame && currentCache.changeTime === changeTime) { + return; + } + } + + setPermissionCache(permissionList, currentUserId, changeTime); + + if (changeTime) { + userStore.setChangeTime(changeTime); + } +} + +// 清除权限缓存(供外部调用) +export function clearPermissionCachePublic(): void { + clearPermissionCache(); +} + +// 权限检查函数 +export function _auth(autd: string, changeTime?: string) { + const permissions = getUserPermissionsWithCache(changeTime); + return permissions.includes(autd); +} + +export function hasPermission(permissionKey: string, changeTime?: string): boolean { + if (!permissionKey) return true; + const permissions = getUserPermissionsWithCache(changeTime); + // 去重处理,避免重复权限影响判断 + const uniquePermissions = [...new Set(permissions)]; + return uniquePermissions.includes(permissionKey); +} + +export function hasAnyPermission(permissionKeys: string[], changeTime?: string): boolean { + if (!permissionKeys || permissionKeys.length === 0) return true; + const permissions = getUserPermissionsWithCache(changeTime); + // 去重处理,避免重复权限影响判断 + const uniquePermissions = [...new Set(permissions)]; + return permissionKeys.some(key => uniquePermissions.includes(key)); +} + +export function hasAllPermissions(permissionKeys: string[], changeTime?: string): boolean { + if (!permissionKeys || permissionKeys.length === 0) return true; + const permissions = getUserPermissionsWithCache(changeTime); + // 去重处理,避免重复权限影响判断 + const uniquePermissions = [...new Set(permissions)]; + return permissionKeys.every(key => uniquePermissions.includes(key)); +} + +// 获取用户权限 +export function getUserPermissions(changeTime?: string): string[] { + return getUserPermissionsWithCache(changeTime); +} + +// 权限缓存管理器 +export const PermissionCacheManager = { + getCacheInfo() { + const cache = getPermissionCache(); + const changeTime = getChangeTimeFromAppUser(); + return { + cache, + changeTime, + hasCache: !!cache, + cacheAge: cache ? Date.now() - cache.timestamp : 0 + }; + }, + + debugCache() { + const info = this.getCacheInfo(); + console.log('权限缓存信息:', info); + }, + + forceRefresh() { + clearPermissionCache(); + const userStore = useUserStore(); + const permissions = userStore.getAuth; + if (permissions && permissions.length > 0) { + const currentUser = userStore.getUser; + const currentUserId = currentUser?.userId || currentUser?.id; + if (currentUserId) { + setPermissionCache(permissions, currentUserId); + } + } + }, + + clear() { + clearPermissionCache(); + } +}; + function loginPage(url: string) { uni.redirectTo({ url: "/pages/system/login/login?redirect=" + url,