新苗调整
This commit is contained in:
parent
8bdde206b0
commit
2b1dad9e29
@ -55,12 +55,21 @@ export const dqpkApi = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 单日的排课课表
|
* 单日的排课课表(家长端:仅 yfzc_pkkb,不叠加调换课/代课)
|
||||||
*/
|
*/
|
||||||
export const drpkkbApi = async (params: any) => {
|
export const drpkkbApi = async (params: any) => {
|
||||||
return await get("/mobile/jz/pkkb/drpkkb", params);
|
return await get("/mobile/jz/pkkb/drpkkb", params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 某周每日排课课表(家长端:仅 yfzc_pkkb,不叠加调换课/代课) */
|
||||||
|
export const drpkkbWeekApi = async (params: {
|
||||||
|
bjId: string;
|
||||||
|
xqId: string;
|
||||||
|
zc: number;
|
||||||
|
}) => {
|
||||||
|
return await get("/mobile/jz/pkkb/drpkkbWeek", params);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询学生考试场次
|
* 查询学生考试场次
|
||||||
*/
|
*/
|
||||||
@ -125,6 +134,13 @@ export const jzXsQjActivitiHistoryApi = async (params: any) => {
|
|||||||
return await get("/activiti/history/historicFlow", params);
|
return await get("/activiti/history/historicFlow", params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学期列表(下拉选择,走 findPage 数据权限)
|
||||||
|
*/
|
||||||
|
export const xqFindPageForSelectApi = async () => {
|
||||||
|
return await get("/api/xq/findPage", { page: 1, rows: 9999, status: "A" });
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据课程ID查询作品执行数据(家长端)
|
* 根据课程ID查询作品执行数据(家长端)
|
||||||
*/
|
*/
|
||||||
@ -134,6 +150,7 @@ export const zpzxFindByKcParamsApi = async (params: {
|
|||||||
njmcId?: string;
|
njmcId?: string;
|
||||||
bjId?: string;
|
bjId?: string;
|
||||||
xsId?: string;
|
xsId?: string;
|
||||||
|
xqId?: string;
|
||||||
}) => {
|
}) => {
|
||||||
return await get("/api/zpzx/findByKcParams", params);
|
return await get("/api/zpzx/findByKcParams", params);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -65,12 +65,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, reactive, computed, onMounted, nextTick } from "vue";
|
import { ref, computed, onMounted, nextTick } from "vue";
|
||||||
import { dqpkApi, drpkkbApi } from "@/api/base/server";
|
import { dqpkApi, drpkkbWeekApi } from "@/api/base/server";
|
||||||
import { useUserStore } from "@/store/modules/user";
|
import { useUserStore } from "@/store/modules/user";
|
||||||
import { useCommonStore } from "@/store/modules/common";
|
|
||||||
const { getCurXs } = useUserStore();
|
const { getCurXs } = useUserStore();
|
||||||
const { getCacheList }= useCommonStore();
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import "dayjs/locale/zh-cn";
|
import "dayjs/locale/zh-cn";
|
||||||
import weekOfYear from "dayjs/plugin/weekOfYear";
|
import weekOfYear from "dayjs/plugin/weekOfYear";
|
||||||
@ -80,122 +78,197 @@ dayjs.locale("zh-cn");
|
|||||||
dayjs.extend(weekOfYear);
|
dayjs.extend(weekOfYear);
|
||||||
dayjs.extend(isoWeek);
|
dayjs.extend(isoWeek);
|
||||||
|
|
||||||
let dqZc = 0;
|
let xqId = "";
|
||||||
let xqId = '';
|
|
||||||
|
|
||||||
// 这是当年的第几周
|
|
||||||
const dnDjz = ref(dayjs().isoWeek());
|
const dnDjz = ref(dayjs().isoWeek());
|
||||||
|
|
||||||
// 当前选中周次
|
|
||||||
const curZc = ref<any>({});
|
const curZc = ref<any>({});
|
||||||
// 当前选中日期序号
|
const curRqIndex = ref(0);
|
||||||
const curRqIndex = ref(0)
|
const zcList = ref<any[]>([]);
|
||||||
// 周次列表
|
|
||||||
const zcList = ref<any>([])
|
|
||||||
// 日期列表
|
|
||||||
const rqList = computed(() => {
|
const rqList = computed(() => {
|
||||||
if (!curZc.value || !curZc.value.drList || !curZc.value.drList.length) {
|
if (!curZc.value?.drList?.length) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return curZc.value.drList.filter((dr: any) => {
|
return filterDrList(curZc.value.drList);
|
||||||
return (!dr.holiday && dr.zj <= 5) || (dr.holiday && dr.holiday.type != 2);
|
|
||||||
});
|
});
|
||||||
});
|
const sjList = ref<any[]>([]);
|
||||||
// 时间列表
|
/** 当前周整周课表缓存,key 为 YYYY-MM-DD */
|
||||||
const sjList = ref<any>([])
|
const weekDaySjMap = ref<Record<string, any[]>>({});
|
||||||
|
const weekCacheZcDjz = ref<number | null>(null);
|
||||||
|
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
|
|
||||||
// 周选择相关
|
|
||||||
const showWeekPicker = ref(false);
|
const showWeekPicker = ref(false);
|
||||||
const weekPopup = ref<any>(null);
|
const weekPopup = ref<any>(null);
|
||||||
|
const targetScrollTop = ref(0);
|
||||||
|
|
||||||
const targetScrollTop = ref(0); // 新增:用于绑定 scroll-top
|
function filterDrList(drList: any[]) {
|
||||||
|
if (!drList?.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return drList.filter((dr: any) => {
|
||||||
|
return (!dr.holiday && dr.zj <= 5) || (dr.holiday && dr.holiday.type != 2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function dayRqToYmd(day: any): string {
|
||||||
|
if (!day) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (day.rq != null && day.rq !== "") {
|
||||||
|
return dayjs(day.rq).format("YYYY-MM-DD");
|
||||||
|
}
|
||||||
|
const rqNum = day.rqNum;
|
||||||
|
if (rqNum != null && rqNum !== "") {
|
||||||
|
const s = String(rqNum);
|
||||||
|
if (s.length === 8) {
|
||||||
|
return `${s.slice(0, 4)}-${s.slice(4, 6)}-${s.slice(6, 8)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDateInWeekRawDrList(zc: any, ymd: string): boolean {
|
||||||
|
if (!ymd || !zc?.drList?.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (zc.drList as any[]).some((dr) => dayRqToYmd(dr) === ymd);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findZcIndexByCalendarDate(list: any[], ymd: string): number {
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
if (isDateInWeekRawDrList(list[i], ymd)) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findRqListIndexForCalendarToday(drs: any[]): number {
|
||||||
|
const today = dayjs().format("YYYY-MM-DD");
|
||||||
|
return drs.findIndex((d) => dayRqToYmd(d) === today);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定位当前周 + 今天。
|
||||||
|
* 1. 按校历日期找含「今天」的周(含周末,不用过滤后的列表)
|
||||||
|
* 2. 否则按 dnDjz(ISO 周)匹配「当前周」标签
|
||||||
|
* 3. 不用缓存里可能过期的 result.zc
|
||||||
|
* 4. 若今天在本周校历内但不在可展示日(周末/放假)→ 下一周第一个可展示日
|
||||||
|
*/
|
||||||
|
function resolveInitialWeekAndDayIndex(list: any[]): { zcIdx: number; dayIdx: number } {
|
||||||
|
if (!list.length) {
|
||||||
|
return { zcIdx: 0, dayIdx: 0 };
|
||||||
|
}
|
||||||
|
const today = dayjs().format("YYYY-MM-DD");
|
||||||
|
|
||||||
|
let zcIdx = findZcIndexByCalendarDate(list, today);
|
||||||
|
if (zcIdx < 0) {
|
||||||
|
const isoWeekNum = dayjs().isoWeek();
|
||||||
|
zcIdx = list.findIndex((z: any) => z.dnDjz === isoWeekNum);
|
||||||
|
}
|
||||||
|
if (zcIdx < 0) {
|
||||||
|
zcIdx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let drs = filterDrList(list[zcIdx]?.drList || []);
|
||||||
|
let dayIdx = findRqListIndexForCalendarToday(drs);
|
||||||
|
|
||||||
|
if (dayIdx < 0 && isDateInWeekRawDrList(list[zcIdx], today)) {
|
||||||
|
const nextZcIdx = zcIdx + 1;
|
||||||
|
if (nextZcIdx < list.length) {
|
||||||
|
zcIdx = nextZcIdx;
|
||||||
|
drs = filterDrList(list[zcIdx]?.drList || []);
|
||||||
|
}
|
||||||
|
dayIdx = 0;
|
||||||
|
} else if (dayIdx < 0) {
|
||||||
|
dayIdx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { zcIdx, dayIdx };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadWeekSchedule(zc: any) {
|
||||||
|
weekDaySjMap.value = {};
|
||||||
|
weekCacheZcDjz.value = null;
|
||||||
|
sjList.value = [];
|
||||||
|
const bjId = getCurXs?.bjId;
|
||||||
|
if (!bjId || !xqId || !zc?.djz) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
const res: any = await drpkkbWeekApi({
|
||||||
|
bjId: String(bjId),
|
||||||
|
xqId,
|
||||||
|
zc: zc.djz,
|
||||||
|
});
|
||||||
|
weekCacheZcDjz.value = zc.djz;
|
||||||
|
if (res?.resultCode === 1 && Array.isArray(res.result)) {
|
||||||
|
for (const day of res.result) {
|
||||||
|
const key = day.rq ? dayjs(day.rq).format("YYYY-MM-DD") : "";
|
||||||
|
if (key) {
|
||||||
|
weekDaySjMap.value[key] = Array.isArray(day.sjList) ? day.sjList : [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("加载周课表失败:", error);
|
||||||
|
uni.showToast({ title: "加载课表失败", icon: "none" });
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyDaySchedule(index: number) {
|
||||||
|
curRqIndex.value = index;
|
||||||
|
if (!rqList.value.length) {
|
||||||
|
sjList.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const safeIdx = Math.max(0, Math.min(index, rqList.value.length - 1));
|
||||||
|
curRqIndex.value = safeIdx;
|
||||||
|
const rqKey = dayRqToYmd(rqList.value[safeIdx]);
|
||||||
|
sjList.value = weekDaySjMap.value[rqKey] || [];
|
||||||
|
}
|
||||||
|
|
||||||
// 打开周选择弹窗
|
|
||||||
const openWeekPicker = async () => {
|
const openWeekPicker = async () => {
|
||||||
showWeekPicker.value = true;
|
showWeekPicker.value = true;
|
||||||
// 打开弹窗
|
|
||||||
await nextTick();
|
await nextTick();
|
||||||
if (weekPopup.value) {
|
if (weekPopup.value) {
|
||||||
weekPopup.value.open("bottom");
|
weekPopup.value.open("bottom");
|
||||||
} else {
|
|
||||||
console.error("Week popup ref is not available.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭周选择弹窗
|
|
||||||
const closeWeekPicker = () => {
|
|
||||||
if (weekPopup.value) {
|
|
||||||
weekPopup.value.close();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 弹窗状态变化
|
const closeWeekPicker = () => {
|
||||||
|
weekPopup.value?.close();
|
||||||
|
};
|
||||||
|
|
||||||
const popupChange = (e: { show: boolean }) => {
|
const popupChange = (e: { show: boolean }) => {
|
||||||
if (!e.show) {
|
if (!e.show) {
|
||||||
showWeekPicker.value = false;
|
showWeekPicker.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 选择周
|
const selectWeek = async (zc: any) => {
|
||||||
const selectWeek = (zc: any) => {
|
if (curZc.value?.djz === zc?.djz) {
|
||||||
if (curZc.value && zc && zc.djz === curZc.value.djz) {
|
|
||||||
// 如果点击的是当前已选中的周,则只关闭弹窗
|
|
||||||
closeWeekPicker();
|
closeWeekPicker();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 复制
|
|
||||||
Object.assign(curZc.value, zc);
|
Object.assign(curZc.value, zc);
|
||||||
|
|
||||||
// 关闭弹窗
|
|
||||||
closeWeekPicker();
|
closeWeekPicker();
|
||||||
|
await loadWeekSchedule(zc);
|
||||||
// 选中第一天
|
const drs = filterDrList(zc.drList || []);
|
||||||
selectDay(0);
|
const todayIdx = findRqListIndexForCalendarToday(drs);
|
||||||
|
applyDaySchedule(todayIdx >= 0 ? todayIdx : 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 选择日期
|
|
||||||
const selectDay = (index: number) => {
|
const selectDay = (index: number) => {
|
||||||
curRqIndex.value = index;
|
if (weekCacheZcDjz.value !== curZc.value?.djz) {
|
||||||
if (!rqList.value.length) {
|
void loadWeekSchedule(curZc.value).then(() => applyDaySchedule(index));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const params = {
|
applyDaySchedule(index);
|
||||||
jsId: "",
|
|
||||||
bjId: getCurXs.bjId,
|
|
||||||
xqId: xqId,
|
|
||||||
zc: curZc.value.djz,
|
|
||||||
rq: rqList.value[index].rq, // 用于查询代课信息
|
|
||||||
zj: rqList.value[index].zj,
|
|
||||||
txDate: null
|
|
||||||
};
|
|
||||||
if (params.zj > 5) {
|
|
||||||
const holiday = rqList.value[index].holiday || {};
|
|
||||||
if (holiday && holiday.type === 3 && holiday.txDate) {
|
|
||||||
params.txDate = holiday.txDate;
|
|
||||||
const txZc = rqList.value[index].txZc;
|
|
||||||
if (txZc) {
|
|
||||||
params.zc = txZc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
drpkkbApi(params).then(res => {
|
|
||||||
// 根据接口返回的result判断是否已报名
|
|
||||||
if (res && res.resultCode === 1) {
|
|
||||||
sjList.value = res.result;
|
|
||||||
} else {
|
|
||||||
// 接口调用成功但返回错误
|
|
||||||
console.warn("检查报名状态接口返回错误:", res);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
// 接口调用失败
|
|
||||||
console.error("调用检查报名状态接口失败:", error);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 判断课程颜色
|
|
||||||
const getCourseColorClass = (subject: string | undefined): string => {
|
const getCourseColorClass = (subject: string | undefined): string => {
|
||||||
if (!subject) return "";
|
if (!subject) return "";
|
||||||
if (subject.includes("语文")) return "color-lang";
|
if (subject.includes("语文")) return "color-lang";
|
||||||
@ -205,33 +278,23 @@ const getCourseColorClass = (subject: string | undefined): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 获取当前排课设置
|
try {
|
||||||
const res = await getCacheList(dqpkApi, "dqPkSz");
|
// 不用 getCacheList:缓存里的 result.zc 会过期,导致误定位到旧周次
|
||||||
const result = res.result;
|
const res: any = await dqpkApi();
|
||||||
dqZc = res.result.zc;
|
const result = res?.result ?? res;
|
||||||
xqId = res.result.xq.id;
|
if (!result?.xq?.id) {
|
||||||
zcList.value = result.zcList;
|
return;
|
||||||
|
}
|
||||||
|
xqId = result.xq.id;
|
||||||
|
zcList.value = result.zcList || [];
|
||||||
sjList.value = [];
|
sjList.value = [];
|
||||||
|
|
||||||
// 按当前日期定位到对应的周次和星期几
|
const { zcIdx, dayIdx } = resolveInitialWeekAndDayIndex(zcList.value);
|
||||||
const today = dayjs();
|
Object.assign(curZc.value, zcList.value[zcIdx] || {});
|
||||||
const currentWeekday = today.day(); // 0-6,0是周日
|
await loadWeekSchedule(curZc.value);
|
||||||
const currentWeekdayIndex = currentWeekday === 0 ? 6 : currentWeekday - 1; // 转换为1-7,1是周一
|
applyDaySchedule(dayIdx);
|
||||||
|
} catch (error) {
|
||||||
// 查找当前周次
|
console.error("初始化课表失败:", error);
|
||||||
let zc = zcList.value.find((item:any) => item.djz === dqZc);
|
|
||||||
if (!zc) {
|
|
||||||
zc = zcList.value[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 选择当前周次
|
|
||||||
selectWeek(zc);
|
|
||||||
|
|
||||||
// 自动选择当前星期几
|
|
||||||
if (rqList.value && rqList.value.length > 0) {
|
|
||||||
// 确保索引在有效范围内
|
|
||||||
const targetIndex = Math.min(currentWeekdayIndex, rqList.value.length - 1);
|
|
||||||
selectDay(targetIndex);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
<!-- 顶部蓝色背景(与教师端一致) -->
|
<!-- 顶部蓝色背景(与教师端一致) -->
|
||||||
<view class="blue-header">
|
<view class="blue-header">
|
||||||
<view class="header-content">
|
<view class="header-content">
|
||||||
<text class="exam-title">{{ kscc.ksmc || '考试场次' }}</text>
|
<text class="exam-title">{{ kscc.ksmc || '学情场次' }}</text>
|
||||||
<view class="student-info-section">
|
<view class="student-info-section">
|
||||||
<text class="student-name">{{ curXs.xm || curXs.name || '学生姓名' }}</text>
|
<text class="student-name">{{ curXs.xm || curXs.name || '学生姓名' }}</text>
|
||||||
<text class="student-class" v-if="curXs.njmc || curXs.bjmc">
|
<text class="student-class" v-if="curXs.njmc || curXs.bjmc">
|
||||||
@ -45,11 +45,11 @@
|
|||||||
:class="{ active: activeTab === 'scores' }"
|
:class="{ active: activeTab === 'scores' }"
|
||||||
@click="switchTab('scores')"
|
@click="switchTab('scores')"
|
||||||
>
|
>
|
||||||
<text>学科成绩</text>
|
<text>学科等级</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 学科成绩视图(与教师端布局一致,分数按 sfXsFs 显示) -->
|
<!-- 学科等级视图(与教师端布局一致,分数按 sfXsFs 显示) -->
|
||||||
<view class="score-view" v-if="activeTab === 'scores'">
|
<view class="score-view" v-if="activeTab === 'scores'">
|
||||||
<view v-if="ksccKscjList.length > 0" class="score-content">
|
<view v-if="ksccKscjList.length > 0" class="score-content">
|
||||||
<view
|
<view
|
||||||
@ -152,7 +152,7 @@ onLoad((options?: Record<string, string>) => {
|
|||||||
}
|
}
|
||||||
kscc.value = {
|
kscc.value = {
|
||||||
id: kid,
|
id: kid,
|
||||||
ksmc: name || "考试场次",
|
ksmc: name || "学情场次",
|
||||||
};
|
};
|
||||||
if (q.njmcId != null && String(q.njmcId).trim()) {
|
if (q.njmcId != null && String(q.njmcId).trim()) {
|
||||||
try {
|
try {
|
||||||
@ -198,16 +198,16 @@ const radarData = {
|
|||||||
|
|
||||||
// 所有科目列表
|
// 所有科目列表
|
||||||
const sysKmList = ref<any>([]);
|
const sysKmList = ref<any>([]);
|
||||||
// 当前考试科目列表
|
// 当前学情科目列表
|
||||||
const ksccKmList = ref<any>([]);
|
const ksccKmList = ref<any>([]);
|
||||||
// 科目卷面分总数
|
// 科目卷面分总数
|
||||||
const totalKmFs = ref<number>(0);
|
const totalKmFs = ref<number>(0);
|
||||||
|
|
||||||
// 科目等级列表
|
// 科目等级列表
|
||||||
const kmDjList = ref<any>([]);
|
const kmDjList = ref<any>([]);
|
||||||
// 考试场次等级列表
|
// 学情场次等级列表
|
||||||
const djList = ref<any>([]);
|
const djList = ref<any>([]);
|
||||||
// 考试场次成绩列表
|
// 学情场次成绩列表
|
||||||
const ksccKscjList = ref<any>([]);
|
const ksccKscjList = ref<any>([]);
|
||||||
|
|
||||||
// 绘制雷达图
|
// 绘制雷达图
|
||||||
@ -361,7 +361,7 @@ function resolveOverallKsDj(sumScaled: number, examFullMarksScaled: number): any
|
|||||||
return pickBestDjMatch(ksDjRows, scoreForMatch) || {};
|
return pickBestDjMatch(ksDjRows, scoreForMatch) || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化考试等级列表(与教师端 kspj/detail 一致)
|
// 初始化学情等级列表(与教师端 kspj/detail 一致)
|
||||||
const initKsdj = () => {
|
const initKsdj = () => {
|
||||||
djList.value.forEach((ksdj: any) => {
|
djList.value.forEach((ksdj: any) => {
|
||||||
ksdj.zdf = floatToInt(ksdj.zdf, fsScale);
|
ksdj.zdf = floatToInt(ksdj.zdf, fsScale);
|
||||||
@ -417,7 +417,7 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
const ksccId = kscc.value?.id;
|
const ksccId = kscc.value?.id;
|
||||||
if (!ksccId) {
|
if (!ksccId) {
|
||||||
uni.showToast({ title: "请先选择考试场次", icon: "none" });
|
uni.showToast({ title: "请先选择学情场次", icon: "none" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const res = await xsKscjApi({
|
const res = await xsKscjApi({
|
||||||
@ -640,7 +640,7 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 学科成绩视图(与教师端一致) */
|
/* 学科等级视图(与教师端一致) */
|
||||||
.score-view {
|
.score-view {
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="page">
|
<view class="page">
|
||||||
<view class="head-tip">请选择考试场次</view>
|
<view class="head-tip">请选择学情场次</view>
|
||||||
<scroll-view scroll-y class="scroll">
|
<scroll-view scroll-y class="scroll">
|
||||||
<view v-if="loading" class="state">加载中...</view>
|
<view v-if="loading" class="state">加载中...</view>
|
||||||
<view v-else-if="!treeData.length" class="state">暂无考试场次数据</view>
|
<view v-else-if="!treeData.length" class="state">暂无学情场次数据</view>
|
||||||
<view v-else class="sections">
|
<view v-else class="sections">
|
||||||
<view v-for="xq in treeData" :key="xqKey(xq)" class="section">
|
<view v-for="xq in treeData" :key="xqKey(xq)" class="section">
|
||||||
<text class="section-title">{{ xq.title || xq.label || "学期" }}</text>
|
<text class="section-title">{{ xq.title || xq.label || "学期" }}</text>
|
||||||
@ -16,7 +16,7 @@
|
|||||||
<text class="kscc-name">{{ c.title || c.ksmc || "—" }}</text>
|
<text class="kscc-name">{{ c.title || c.ksmc || "—" }}</text>
|
||||||
<text class="arrow">›</text>
|
<text class="arrow">›</text>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="!(xq.children || []).length" class="kscc-empty">该学期下暂无场次</view>
|
<view v-if="!(xq.children || []).length" class="kscc-empty">该学期下暂无学情场次</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
@ -68,7 +68,7 @@ function goDetail(xq: any, c: any) {
|
|||||||
const xqTitle = String(xq?.title ?? xq?.label ?? "").trim();
|
const xqTitle = String(xq?.title ?? xq?.label ?? "").trim();
|
||||||
const ksTitle = String(c?.title ?? c?.ksmc ?? "").trim();
|
const ksTitle = String(c?.title ?? c?.ksmc ?? "").trim();
|
||||||
const idEnc = encodeURIComponent(String(ksccId));
|
const idEnc = encodeURIComponent(String(ksccId));
|
||||||
const ksEnc = encodeURIComponent(ksTitle || "考试场次");
|
const ksEnc = encodeURIComponent(ksTitle || "学情场次");
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/base/grades/detail?ksccId=${idEnc}&ksmc=${ksEnc}`,
|
url: `/pages/base/grades/detail?ksccId=${idEnc}&ksmc=${ksEnc}`,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -67,15 +67,18 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, watch, reactive } from "vue";
|
import { ref, computed, onMounted, watch, reactive } from "vue";
|
||||||
|
import { onShow } from "@dcloudio/uni-app";
|
||||||
import XsPicker from "@/pages/base/components/XsPicker/index.vue"
|
import XsPicker from "@/pages/base/components/XsPicker/index.vue"
|
||||||
import { getNoticeListApi } from "@/api/base/notice";
|
import { getNoticeListApi } from "@/api/base/notice";
|
||||||
import { getMobileMenuApi } from "@/api/system/menu";
|
import { getMobileMenuApi } from "@/api/system/menu";
|
||||||
|
import { getPermissionChangeTimeApi } from "@/api/system/config";
|
||||||
import { useUserStore } from "@/store/modules/user";
|
import { useUserStore } from "@/store/modules/user";
|
||||||
import { useDataStore } from "@/store/modules/data";
|
import { useDataStore } from "@/store/modules/data";
|
||||||
import { useMenuStore } from "@/store/modules/menu";
|
import { useMenuStore } from "@/store/modules/menu";
|
||||||
import { type MobileMenuTreeNode } from "@/api/system/menu";
|
import { type MobileMenuTreeNode } from "@/api/system/menu";
|
||||||
import { PageUtils } from "@/utils/pageUtil";
|
import { PageUtils } from "@/utils/pageUtil";
|
||||||
import { useDebounce } from "@/utils/debounce";
|
import { useDebounce } from "@/utils/debounce";
|
||||||
|
import { resolveJzdUserId } from "@/utils/menuCache";
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const { getCurXs } = userStore;
|
const { getCurXs } = userStore;
|
||||||
@ -218,23 +221,38 @@ const getArticleList = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
async function loadHomeMenu() {
|
||||||
setGlobal({ from: 'home' });
|
const userId = resolveJzdUserId(userStore.getUser);
|
||||||
|
if (!userId || !userStore.getToken) {
|
||||||
|
menuItems.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (menuStore.isMenuCacheForUser(userId)) {
|
||||||
|
menuItems.value = flattenMenuToItems(menuStore.getMobileMenu);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 菜单由 launchPage 加载并写入 menuStore,此处读缓存
|
|
||||||
const cachedMenu = menuStore.getMobileMenu;
|
|
||||||
if (cachedMenu?.length > 0) {
|
|
||||||
menuItems.value = flattenMenuToItems(cachedMenu);
|
|
||||||
} else if (userStore.getToken) {
|
|
||||||
// 首次注册登录场景:launchPage 可能因 token 未就绪返回空菜单,此处补拉
|
|
||||||
try {
|
try {
|
||||||
|
const timeRes = await getPermissionChangeTimeApi();
|
||||||
|
const serverChangeTime = String((timeRes as any)?.result ?? "");
|
||||||
|
if (serverChangeTime) userStore.setChangeTime(serverChangeTime);
|
||||||
|
|
||||||
const r = await getMobileMenuApi();
|
const r = await getMobileMenuApi();
|
||||||
if (r?.result && Array.isArray(r.result) && r.result.length > 0) {
|
if (r?.result && Array.isArray(r.result) && r.result.length > 0) {
|
||||||
menuStore.setMobileMenu(r.result);
|
menuStore.setMobileMenu(r.result, userId);
|
||||||
menuItems.value = flattenMenuToItems(r.result);
|
menuItems.value = flattenMenuToItems(r.result);
|
||||||
|
} else {
|
||||||
|
menuItems.value = [];
|
||||||
}
|
}
|
||||||
} catch (_e) {}
|
} catch (_e) {
|
||||||
|
menuItems.value = [];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
setGlobal({ from: 'home' });
|
||||||
|
await loadHomeMenu();
|
||||||
|
|
||||||
// 确保有学生年级信息才获取通知公告
|
// 确保有学生年级信息才获取通知公告
|
||||||
if (curXs.value && curXs.value.njmcId) {
|
if (curXs.value && curXs.value.njmcId) {
|
||||||
@ -250,6 +268,13 @@ onMounted(async () => {
|
|||||||
await checkSubscribeStatus();
|
await checkSubscribeStatus();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onShow(() => {
|
||||||
|
const userId = resolveJzdUserId(userStore.getUser);
|
||||||
|
if (!menuStore.isMenuCacheForUser(userId)) {
|
||||||
|
void loadHomeMenu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 检查关注状态
|
// 检查关注状态
|
||||||
const checkSubscribeStatus = async () => {
|
const checkSubscribeStatus = async () => {
|
||||||
const userInfo = userStore.getUser;
|
const userInfo = userStore.getUser;
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
<view class="banner-title-wrapper">
|
<view class="banner-title-wrapper">
|
||||||
<text class="banner-icon">{{ activeTab === 0 ? '📝' : '📚' }}</text>
|
<text class="banner-icon">{{ activeTab === 0 ? '📝' : '📚' }}</text>
|
||||||
<view class="banner-text">
|
<view class="banner-text">
|
||||||
<text class="banner-title">{{ activeTab === 0 ? '作品任务' : '作品资源' }}</text>
|
<text class="banner-title">{{ activeTab === 0 ? '作品任务' : '课程资源' }}</text>
|
||||||
<text class="banner-subtitle">{{
|
<text class="banner-subtitle">{{
|
||||||
activeTab === 0 ? '独立项目选择 · 学习成长' : '课程资料 · 在线查看'
|
activeTab === 0 ? '独立项目选择 · 学习成长' : '课程资料 · 在线查看'
|
||||||
}}</text>
|
}}</text>
|
||||||
@ -29,18 +29,45 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<view class="header-panel">
|
||||||
|
<view class="filter-bar">
|
||||||
|
<view class="filter-item">
|
||||||
|
<uni-data-select
|
||||||
|
v-model="searchForm.xnmc"
|
||||||
|
:localdata="xnmcList"
|
||||||
|
placeholder="选择学年"
|
||||||
|
@change="handleXnChange"
|
||||||
|
class="filter-select"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="filter-item">
|
||||||
|
<uni-data-select
|
||||||
|
v-model="searchForm.xqId"
|
||||||
|
:localdata="xqList"
|
||||||
|
placeholder="选择学期"
|
||||||
|
@change="handleXqChange"
|
||||||
|
class="filter-select"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
<view class="tab-bar">
|
<view class="tab-bar">
|
||||||
<view class="tab-item" :class="{ active: activeTab === 0 }" @click="activeTab = 0">
|
<view class="tab-item" :class="{ active: activeTab === 0 }" @click="activeTab = 0">
|
||||||
<text class="tab-text">作品任务</text>
|
<text class="tab-text">作品任务</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="tab-item" :class="{ active: activeTab === 1 }" @click="activeTab = 1">
|
<view class="tab-item" :class="{ active: activeTab === 1 }" @click="activeTab = 1">
|
||||||
<text class="tab-text">作品资源</text>
|
<text class="tab-text">课程资源</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 任务列表 -->
|
<!-- 任务列表 -->
|
||||||
<scroll-view scroll-y class="list-scroll-view">
|
<scroll-view scroll-y class="list-scroll-view" :enable-back-to-top="true">
|
||||||
<view v-if="isLoading && taskList.length === 0" class="loading-indicator">
|
<view v-if="!xqIdsForApi" class="empty-state empty-state--filter">
|
||||||
|
<view class="empty-icon">📅</view>
|
||||||
|
<text class="empty-text">请先选择学年,并选择具体学期或「全部学期」</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-else-if="isLoading && taskList.length === 0" class="loading-indicator">
|
||||||
<view class="loading-spinner"></view>
|
<view class="loading-spinner"></view>
|
||||||
<text class="loading-text">加载中...</text>
|
<text class="loading-text">加载中...</text>
|
||||||
</view>
|
</view>
|
||||||
@ -97,7 +124,7 @@
|
|||||||
|
|
||||||
<view v-else class="empty-state">
|
<view v-else class="empty-state">
|
||||||
<view class="empty-icon">📭</view>
|
<view class="empty-icon">📭</view>
|
||||||
<text class="empty-text">{{ activeTab === 0 ? '暂无作品任务' : '暂无作品资源' }}</text>
|
<text class="empty-text">{{ activeTab === 0 ? '暂无作品任务' : '暂无课程资源' }}</text>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
@ -105,12 +132,41 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, onUnmounted } from "vue";
|
import { ref, reactive, computed, onMounted, onUnmounted } from "vue";
|
||||||
import { onShow, onLoad } from "@dcloudio/uni-app";
|
import { onShow, onLoad } from "@dcloudio/uni-app";
|
||||||
import { useUserStore } from "@/store/modules/user";
|
import { useUserStore } from "@/store/modules/user";
|
||||||
|
import { useCommonStore } from "@/store/modules/common";
|
||||||
import { zpzxFindByKcParamsApi } from "@/api/base/server";
|
import { zpzxFindByKcParamsApi } from "@/api/base/server";
|
||||||
|
import { getPersistedDqXqAndZc } from "@/utils/xszpPersistXq";
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const commonStore = useCommonStore();
|
||||||
|
|
||||||
|
const searchForm = reactive({
|
||||||
|
xnmc: "",
|
||||||
|
xqId: "",
|
||||||
|
});
|
||||||
|
const xnmcList = ref<Array<{ value: string; text: string }>>([]);
|
||||||
|
const allXqList = ref<Array<{ id: string; xqmc: string; xnmc: string }>>([]);
|
||||||
|
const xqList = ref<Array<{ value: string; text: string }>>([]);
|
||||||
|
const currentXqId = ref("");
|
||||||
|
const xqListReady = ref(false);
|
||||||
|
|
||||||
|
/** 传给接口的学期 id:单选为单个 id;选「全部学期」为当前学年下所有学期 id 逗号拼接 */
|
||||||
|
const xqIdsForApi = computed(() => {
|
||||||
|
if (searchForm.xqId) {
|
||||||
|
return searchForm.xqId;
|
||||||
|
}
|
||||||
|
const year = searchForm.xnmc;
|
||||||
|
if (!year) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return allXqList.value
|
||||||
|
.filter((it) => it.xnmc === year)
|
||||||
|
.map((it) => String(it.id))
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(",");
|
||||||
|
});
|
||||||
|
|
||||||
interface TaskItem {
|
interface TaskItem {
|
||||||
id: string;
|
id: string;
|
||||||
@ -190,18 +246,101 @@ onLoad(async (options) => {
|
|||||||
|
|
||||||
// openId 异步登录时 onShow 往往在 isLoginReady 仍为 false 时已执行过,需在 onLoad 末尾补一次加载;
|
// openId 异步登录时 onShow 往往在 isLoginReady 仍为 false 时已执行过,需在 onLoad 末尾补一次加载;
|
||||||
// 普通进入只由 onShow 拉取,避免与 onLoad 重复请求
|
// 普通进入只由 onShow 拉取,避免与 onLoad 重复请求
|
||||||
if (options && options.openId) {
|
if (options && options.openId && xqListReady.value) {
|
||||||
|
loadTaskList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadXqList();
|
||||||
|
xqListReady.value = true;
|
||||||
|
if (isLoginReady.value) {
|
||||||
loadTaskList();
|
loadTaskList();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
// 只有在登录准备就绪后才加载数据
|
// 只有在登录准备就绪且学期列表已初始化后才加载数据
|
||||||
if (isLoginReady.value) {
|
if (isLoginReady.value && xqListReady.value) {
|
||||||
loadTaskList();
|
loadTaskList();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function handleXnChange() {
|
||||||
|
refreshXqListByYear();
|
||||||
|
loadTaskList();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleXqChange() {
|
||||||
|
loadTaskList();
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshXqListByYear() {
|
||||||
|
const year = searchForm.xnmc;
|
||||||
|
const list = allXqList.value
|
||||||
|
.filter((item) => item.xnmc === year)
|
||||||
|
.map((item) => ({ value: item.id, text: item.xqmc }));
|
||||||
|
xqList.value = [{ value: "", text: "全部学期" }, ...list];
|
||||||
|
const curId = currentXqId.value;
|
||||||
|
if (curId && list.some((x) => String(x.value) === String(curId))) {
|
||||||
|
searchForm.xqId = curId;
|
||||||
|
} else {
|
||||||
|
searchForm.xqId = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadXqList = async () => {
|
||||||
|
try {
|
||||||
|
const response: any = await commonStore.getXqListForSelect();
|
||||||
|
const data =
|
||||||
|
response?.rows ||
|
||||||
|
response?.result ||
|
||||||
|
response?.data ||
|
||||||
|
(Array.isArray(response) ? response : []);
|
||||||
|
const dq = await getPersistedDqXqAndZc(commonStore);
|
||||||
|
const dqXqId = dq?.xqId != null ? String(dq.xqId) : "";
|
||||||
|
const cur =
|
||||||
|
data.find((item: any) => String(item?.id) === dqXqId) ||
|
||||||
|
data.find((item: any) => String(item?.dqxq) === "是");
|
||||||
|
currentXqId.value = cur?.id != null ? String(cur.id) : "";
|
||||||
|
const curXnmc = cur?.xnmc != null ? String(cur.xnmc).trim() : "";
|
||||||
|
|
||||||
|
allXqList.value = data.map((item: any) => ({
|
||||||
|
id: item.id,
|
||||||
|
xqmc: item.xqmc || item.name,
|
||||||
|
xnmc: item.xnmc || "",
|
||||||
|
}));
|
||||||
|
const yearSet = new Set<string>();
|
||||||
|
allXqList.value.forEach((item) => {
|
||||||
|
if (item.xnmc) yearSet.add(item.xnmc);
|
||||||
|
});
|
||||||
|
xnmcList.value = Array.from(yearSet).map((y) => ({ value: y, text: y }));
|
||||||
|
|
||||||
|
if (xnmcList.value.length > 0) {
|
||||||
|
if (curXnmc && xnmcList.value.some((x) => x.value === curXnmc)) {
|
||||||
|
searchForm.xnmc = curXnmc;
|
||||||
|
} else {
|
||||||
|
searchForm.xnmc = xnmcList.value[0].value;
|
||||||
|
}
|
||||||
|
refreshXqListByYear();
|
||||||
|
} else {
|
||||||
|
xqList.value = allXqList.value.map((item) => ({
|
||||||
|
value: item.id,
|
||||||
|
text: item.xqmc,
|
||||||
|
}));
|
||||||
|
if (currentXqId.value) searchForm.xqId = currentXqId.value;
|
||||||
|
else if (xqList.value.length)
|
||||||
|
searchForm.xqId = String(xqList.value[0].value);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
xnmcList.value = [];
|
||||||
|
allXqList.value = [];
|
||||||
|
xqList.value = [];
|
||||||
|
currentXqId.value = "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 监听刷新事件
|
// 监听刷新事件
|
||||||
uni.$on('refreshTaskList', () => {
|
uni.$on('refreshTaskList', () => {
|
||||||
console.log('收到刷新任务列表事件');
|
console.log('收到刷新任务列表事件');
|
||||||
@ -215,7 +354,11 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
const loadTaskList = async () => {
|
const loadTaskList = async () => {
|
||||||
// 如果登录还未准备就绪,不执行加载
|
// 如果登录还未准备就绪,不执行加载
|
||||||
if (!isLoginReady.value) {
|
if (!isLoginReady.value || !xqListReady.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!xqIdsForApi.value) {
|
||||||
|
taskList.value = [];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,6 +382,9 @@ const loadTaskList = async () => {
|
|||||||
if (kcId.value) {
|
if (kcId.value) {
|
||||||
params.kcId = kcId.value;
|
params.kcId = kcId.value;
|
||||||
}
|
}
|
||||||
|
if (xqIdsForApi.value) {
|
||||||
|
params.xqId = xqIdsForApi.value;
|
||||||
|
}
|
||||||
|
|
||||||
const response = await zpzxFindByKcParamsApi(params);
|
const response = await zpzxFindByKcParamsApi(params);
|
||||||
|
|
||||||
@ -320,7 +466,7 @@ const viewTaskDetail = (task: TaskItem) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 作品资源(资料查看)详情 */
|
/** 课程资源(资料查看)详情 */
|
||||||
const viewResourceDetail = (task: TaskItem) => {
|
const viewResourceDetail = (task: TaskItem) => {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.append('zpId', task.zpId);
|
params.append('zpId', task.zpId);
|
||||||
@ -384,14 +530,16 @@ const formatTime = (timeStr?: string) => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: linear-gradient(180deg, #f0f5ff 0%, #f5f7fa 100%);
|
min-height: 100vh;
|
||||||
|
background: #eef2f8;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 页面横幅样式
|
|
||||||
.page-banner {
|
.page-banner {
|
||||||
background: linear-gradient(135deg, #4e73df 0%, #2e59d9 100%);
|
flex-shrink: 0;
|
||||||
padding: 20px 16px;
|
background: linear-gradient(145deg, #3d5fc4 0%, #2e59d9 55%, #2547b8 100%);
|
||||||
box-shadow: 0 4px 12px rgba(78, 115, 223, 0.3);
|
padding: 18px 16px 16px;
|
||||||
|
box-shadow: 0 4px 12px rgba(46, 89, 217, 0.2);
|
||||||
|
|
||||||
.banner-content {
|
.banner-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -402,89 +550,137 @@ const formatTime = (timeStr?: string) => {
|
|||||||
.banner-title-wrapper {
|
.banner-title-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 10px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.banner-icon {
|
.banner-icon {
|
||||||
font-size: 32px;
|
font-size: 28px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.banner-text {
|
.banner-text {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.banner-title {
|
.banner-title {
|
||||||
font-size: 24px;
|
font-size: 20px;
|
||||||
font-weight: bold;
|
font-weight: 700;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
line-height: 1.2;
|
line-height: 1.25;
|
||||||
}
|
}
|
||||||
|
|
||||||
.banner-subtitle {
|
.banner-subtitle {
|
||||||
font-size: 13px;
|
font-size: 12px;
|
||||||
color: rgba(255, 255, 255, 0.9);
|
color: rgba(255, 255, 255, 0.88);
|
||||||
letter-spacing: 1px;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-count {
|
.task-count {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: rgba(255, 255, 255, 0.2);
|
flex-shrink: 0;
|
||||||
backdrop-filter: blur(10px);
|
background: rgba(255, 255, 255, 0.18);
|
||||||
padding: 10px 16px;
|
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||||
border-radius: 12px;
|
padding: 8px 14px;
|
||||||
min-width: 60px;
|
border-radius: 14px;
|
||||||
|
min-width: 56px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.count-number {
|
.count-number {
|
||||||
font-size: 24px;
|
font-size: 22px;
|
||||||
font-weight: bold;
|
font-weight: 700;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.count-label {
|
.count-label {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: rgba(255, 255, 255, 0.9);
|
color: rgba(255, 255, 255, 0.92);
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-panel {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 12px 12px 10px;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
border-bottom: 1px solid #e9ecef;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 1px solid #eef1f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-item {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-item :deep(.uni-stat__select) {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-item :deep(.uni-select) {
|
||||||
|
border: 1px solid #e2e8f0 !important;
|
||||||
|
border-radius: 10px !important;
|
||||||
|
background: #f8fafc !important;
|
||||||
|
min-height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-item :deep(.uni-select__input-text) {
|
||||||
|
font-size: 13px !important;
|
||||||
|
color: #334155 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.tab-bar {
|
.tab-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
background: #ffffff;
|
background: #f1f5f9;
|
||||||
margin: 0 16px;
|
border-radius: 10px;
|
||||||
margin-top: -10px;
|
padding: 3px;
|
||||||
margin-bottom: 12px;
|
gap: 3px;
|
||||||
border-radius: 12px;
|
|
||||||
padding: 4px;
|
|
||||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
|
||||||
position: relative;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-item {
|
.tab-item {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 10px 0;
|
padding: 9px 0;
|
||||||
border-radius: 10px;
|
border-radius: 9px;
|
||||||
transition: background 0.2s ease, color 0.2s ease;
|
transition: background 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
|
||||||
.tab-text {
|
.tab-text {
|
||||||
font-size: 15px;
|
font-size: 14px;
|
||||||
color: #666;
|
color: #64748b;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background: linear-gradient(135deg, #4e73df 0%, #2e59d9 100%);
|
background: #ffffff;
|
||||||
|
box-shadow: 0 2px 8px rgba(46, 89, 217, 0.15);
|
||||||
|
|
||||||
.tab-text {
|
.tab-text {
|
||||||
color: #ffffff;
|
color: #2e59d9;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -492,7 +688,10 @@ const formatTime = (timeStr?: string) => {
|
|||||||
|
|
||||||
.list-scroll-view {
|
.list-scroll-view {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 0;
|
min-height: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 12px 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-indicator {
|
.loading-indicator {
|
||||||
@ -500,21 +699,21 @@ const formatTime = (timeStr?: string) => {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 40px 0;
|
padding: 48px 0;
|
||||||
|
|
||||||
.loading-spinner {
|
.loading-spinner {
|
||||||
width: 40px;
|
width: 36px;
|
||||||
height: 40px;
|
height: 36px;
|
||||||
border: 3px solid #f3f3f3;
|
border: 3px solid #e2e8f0;
|
||||||
border-top: 3px solid #409eff;
|
border-top: 3px solid #4e73df;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
animation: spin 1s linear infinite;
|
animation: spin 1s linear infinite;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-text {
|
.loading-text {
|
||||||
color: #999;
|
color: #94a3b8;
|
||||||
font-size: 14px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -524,26 +723,23 @@ const formatTime = (timeStr?: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.task-card {
|
.task-card {
|
||||||
background: linear-gradient(135deg, #ffffff 0%, #fafbfc 100%);
|
background: #ffffff;
|
||||||
border-radius: 16px;
|
border-radius: 14px;
|
||||||
margin: 0 0 16px 0;
|
margin: 0 0 12px;
|
||||||
padding: 16px;
|
padding: 14px 16px;
|
||||||
box-shadow:
|
border: 1px solid #e8edf5;
|
||||||
0 2px 16px rgba(0, 0, 0, 0.08),
|
box-shadow: 0 4px 14px rgba(15, 23, 42, 0.06);
|
||||||
0 0 0 1px rgba(78, 115, 223, 0.1);
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: visible;
|
overflow: hidden;
|
||||||
|
|
||||||
&--resource {
|
&--resource {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
transform: translateY(2px) scale(0.98);
|
transform: scale(0.99);
|
||||||
box-shadow:
|
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.08);
|
||||||
0 1px 8px rgba(0, 0, 0, 0.12),
|
|
||||||
0 0 0 1px rgba(78, 115, 223, 0.15);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -565,9 +761,9 @@ const formatTime = (timeStr?: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.task-title {
|
.task-title {
|
||||||
font-size: 16px;
|
font-size: 15px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #333;
|
color: #1e293b;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -634,39 +830,37 @@ const formatTime = (timeStr?: string) => {
|
|||||||
|
|
||||||
.card-footer {
|
.card-footer {
|
||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
border-top: 1px solid #f0f0f0;
|
border-top: 1px solid #f1f5f9;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 10px;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
.action-btn {
|
.action-btn {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 36px;
|
height: 34px;
|
||||||
line-height: 36px;
|
line-height: 34px;
|
||||||
border-radius: 8px;
|
border-radius: 10px;
|
||||||
font-size: 14px;
|
font-size: 13px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
border: none;
|
border: none;
|
||||||
transition: all 0.3s;
|
transition: opacity 0.2s;
|
||||||
|
|
||||||
&.challenge-btn {
|
&.challenge-btn {
|
||||||
background: linear-gradient(135deg, #4e73df 0%, #2e59d9 100%);
|
background: linear-gradient(135deg, #4e73df 0%, #2e59d9 100%);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background: linear-gradient(135deg, #2e59d9 0%, #1e3fa8 100%);
|
opacity: 0.88;
|
||||||
transform: scale(0.98);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.detail-btn {
|
&.detail-btn {
|
||||||
background: #f5f7fa;
|
background: #f1f5f9;
|
||||||
color: #4e73df;
|
color: #2e59d9;
|
||||||
border: 1px solid #e0e6ed;
|
border: 1px solid #e2e8f0;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background: #e8ecf1;
|
opacity: 0.88;
|
||||||
transform: scale(0.98);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -677,18 +871,29 @@ const formatTime = (timeStr?: string) => {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 80px 20px;
|
padding: 56px 20px;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 14px;
|
||||||
|
border: 1px dashed #dbe3ef;
|
||||||
|
|
||||||
|
&--filter {
|
||||||
|
padding: 40px 20px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border-color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
.empty-icon {
|
.empty-icon {
|
||||||
font-size: 64px;
|
font-size: 48px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 12px;
|
||||||
opacity: 0.6;
|
opacity: 0.75;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-text {
|
.empty-text {
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
color: #4a5568;
|
color: #64748b;
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import { checkOpenId } from "@/api/system/login";
|
|||||||
import { getMobileMenuApi } from "@/api/system/menu";
|
import { getMobileMenuApi } from "@/api/system/menu";
|
||||||
import { getPermissionChangeTimeApi } from "@/api/system/config";
|
import { getPermissionChangeTimeApi } from "@/api/system/config";
|
||||||
import { PageUtils } from "@/utils/pageUtil";
|
import { PageUtils } from "@/utils/pageUtil";
|
||||||
|
import { resolveJzdUserId } from "@/utils/menuCache";
|
||||||
|
|
||||||
const dataStore = useDataStore();
|
const dataStore = useDataStore();
|
||||||
const { setGlobal } = dataStore;
|
const { setGlobal } = dataStore;
|
||||||
@ -33,6 +34,37 @@ const toLogin = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function syncMobileMenu(userId: string, savedChangeTime: string) {
|
||||||
|
if (!userId) return;
|
||||||
|
|
||||||
|
let needFetchMenu = true;
|
||||||
|
let serverChangeTime = "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const timeRes = await getPermissionChangeTimeApi();
|
||||||
|
serverChangeTime = String((timeRes as any)?.result ?? "");
|
||||||
|
needFetchMenu = menuStore.shouldRefreshMenu(
|
||||||
|
userId,
|
||||||
|
serverChangeTime,
|
||||||
|
savedChangeTime
|
||||||
|
);
|
||||||
|
if (serverChangeTime) userStore.setChangeTime(serverChangeTime);
|
||||||
|
} catch (_e) {
|
||||||
|
needFetchMenu = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!needFetchMenu && menuStore.isMenuCacheForUser(userId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const r = await getMobileMenuApi();
|
||||||
|
if (r?.result && Array.isArray(r.result) && r.result.length > 0) {
|
||||||
|
menuStore.setMobileMenu(r.result, userId);
|
||||||
|
}
|
||||||
|
} catch (_e) {}
|
||||||
|
}
|
||||||
|
|
||||||
// 重构一下初始化的数据
|
// 重构一下初始化的数据
|
||||||
const initGlobalData = (data: any) => {
|
const initGlobalData = (data: any) => {
|
||||||
let gData = { ...(data || {}) };
|
let gData = { ...(data || {}) };
|
||||||
@ -71,36 +103,11 @@ const initGlobalData = (data: any) => {
|
|||||||
onLoad(async (data: any) => {
|
onLoad(async (data: any) => {
|
||||||
const gData = initGlobalData(data);
|
const gData = initGlobalData(data);
|
||||||
|
|
||||||
// 从登录页跳转:已登录,按权限变更时间判断是否拉取菜单后进首页
|
// 从登录页跳转:已登录,按权限变更时间 + userId 判断是否拉取菜单后进首页
|
||||||
if (gData.fromLogin === "1") {
|
if (gData.fromLogin === "1") {
|
||||||
let needFetchMenu = true;
|
const userId = resolveJzdUserId(userStore.getUser);
|
||||||
try {
|
const savedChangeTime = userStore.getChangeTime || "";
|
||||||
const timeRes = await getPermissionChangeTimeApi();
|
await syncMobileMenu(userId, savedChangeTime);
|
||||||
const serverChangeTime = String((timeRes as any)?.result ?? "");
|
|
||||||
const localChangeTime = userStore.getChangeTime || "";
|
|
||||||
const localMenu = menuStore.getMobileMenu || [];
|
|
||||||
needFetchMenu =
|
|
||||||
serverChangeTime !== localChangeTime || !localMenu?.length;
|
|
||||||
console.log("[launchPage] fromLogin", {
|
|
||||||
timeResRaw: JSON.stringify(timeRes),
|
|
||||||
serverChangeTime,
|
|
||||||
localChangeTime,
|
|
||||||
localMenuLength: localMenu?.length ?? 0,
|
|
||||||
needFetchMenu,
|
|
||||||
});
|
|
||||||
if (serverChangeTime) userStore.setChangeTime(serverChangeTime);
|
|
||||||
} catch (_e) {
|
|
||||||
needFetchMenu = true;
|
|
||||||
console.warn("[launchPage] fromLogin getPermissionChangeTime 异常", _e);
|
|
||||||
}
|
|
||||||
if (needFetchMenu) {
|
|
||||||
try {
|
|
||||||
const r = await getMobileMenuApi();
|
|
||||||
if (r?.result && Array.isArray(r.result) && r.result.length > 0) {
|
|
||||||
menuStore.setMobileMenu(r.result);
|
|
||||||
}
|
|
||||||
} catch (_e) {}
|
|
||||||
}
|
|
||||||
PageUtils.toHome(gData.lxId, gData.action);
|
PageUtils.toHome(gData.lxId, gData.action);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -110,40 +117,37 @@ onLoad(async (data: any) => {
|
|||||||
const res = await checkOpenId({ openId: gData.openId, appCode: "JZ" });
|
const res = await checkOpenId({ openId: gData.openId, appCode: "JZ" });
|
||||||
|
|
||||||
if (res.resultCode == 1 && res.result) {
|
if (res.resultCode == 1 && res.result) {
|
||||||
// 在 afterLoginAction 之前保存:afterLoginAction 会调用 logout 清空 changeTime 和 menuStore
|
const loginUserId = resolveJzdUserId(res.result);
|
||||||
const savedChangeTime = userStore.getChangeTime || "";
|
const savedChangeTime = userStore.getChangeTime || "";
|
||||||
const savedMenu = menuStore.getMobileMenu || [];
|
// 仅当缓存属于同一用户时才保留,避免换账号或 H5 同域 token 混用恢复错菜单
|
||||||
|
const canRestoreCache = menuStore.isMenuCacheForUser(loginUserId);
|
||||||
|
const savedMenu = canRestoreCache ? [...menuStore.getMobileMenu] : [];
|
||||||
|
|
||||||
userStore.afterLoginAction(res.result);
|
userStore.afterLoginAction(res.result);
|
||||||
|
|
||||||
// 判断是否需要拉取菜单:时间戳不一致或本地无菜单则重新请求
|
|
||||||
let needFetchMenu = true;
|
let needFetchMenu = true;
|
||||||
try {
|
try {
|
||||||
const timeRes = await getPermissionChangeTimeApi();
|
const timeRes = await getPermissionChangeTimeApi();
|
||||||
const serverChangeTime = String((timeRes as any)?.result ?? "");
|
const serverChangeTime = String((timeRes as any)?.result ?? "");
|
||||||
needFetchMenu =
|
needFetchMenu = menuStore.shouldRefreshMenu(
|
||||||
serverChangeTime !== savedChangeTime || !savedMenu?.length;
|
loginUserId,
|
||||||
console.log("[launchPage] openId", {
|
|
||||||
timeResRaw: JSON.stringify(timeRes),
|
|
||||||
serverChangeTime,
|
serverChangeTime,
|
||||||
savedChangeTime,
|
savedChangeTime
|
||||||
savedMenuLength: savedMenu?.length ?? 0,
|
);
|
||||||
needFetchMenu,
|
|
||||||
});
|
|
||||||
if (serverChangeTime) userStore.setChangeTime(serverChangeTime);
|
if (serverChangeTime) userStore.setChangeTime(serverChangeTime);
|
||||||
} catch (_e) {
|
} catch (_e) {
|
||||||
needFetchMenu = true;
|
needFetchMenu = true;
|
||||||
console.warn("[launchPage] openId getPermissionChangeTime 异常", _e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needFetchMenu) {
|
if (needFetchMenu) {
|
||||||
try {
|
try {
|
||||||
const r = await getMobileMenuApi();
|
const r = await getMobileMenuApi();
|
||||||
if (r?.result && Array.isArray(r.result) && r.result.length > 0) {
|
if (r?.result && Array.isArray(r.result) && r.result.length > 0) {
|
||||||
menuStore.setMobileMenu(r.result);
|
menuStore.setMobileMenu(r.result, loginUserId);
|
||||||
}
|
}
|
||||||
} catch (_e) {}
|
} catch (_e) {}
|
||||||
} else {
|
} else if (savedMenu.length > 0) {
|
||||||
menuStore.setMobileMenu(savedMenu); // 恢复被 logout 清空的菜单
|
menuStore.setMobileMenu(savedMenu, loginUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
PageUtils.toHome(gData.lxId, gData.action);
|
PageUtils.toHome(gData.lxId, gData.action);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { jsFindByIdApi } from "@/api/base/jsApi";
|
import { jsFindByIdApi } from "@/api/base/jsApi";
|
||||||
import { xkkclxFindAllApi, getXkkcDetailByIdApi } from "@/api/base/xkApi";
|
import { xkkclxFindAllApi, getXkkcDetailByIdApi } from "@/api/base/xkApi";
|
||||||
import { clear } from "console";
|
import { xqFindPageForSelectApi, gzlGetDqXqAndZcApi } from "@/api/base/server";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -155,6 +155,20 @@ export const useCommonStore = defineStore({
|
|||||||
}
|
}
|
||||||
return Promise.resolve(this.data.xkkc[id]);
|
return Promise.resolve(this.data.xkkc[id]);
|
||||||
},
|
},
|
||||||
|
/** 学期列表(findPage,供下拉选择,Pinia 持久化缓存) */
|
||||||
|
async getXqListForSelect(): Promise<any> {
|
||||||
|
return this.getCacheList(xqFindPageForSelectApi, "xqFindPage");
|
||||||
|
},
|
||||||
|
/** 当前学期与周次(Pinia 持久化,供学年学期筛选默认项) */
|
||||||
|
async getDqXqAndZc(): Promise<any> {
|
||||||
|
if (!this.data.dqXqAndZc) {
|
||||||
|
const res = await gzlGetDqXqAndZcApi();
|
||||||
|
if (res && res.resultCode === 1) {
|
||||||
|
this.data.dqXqAndZc = res.result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.resolve(this.data.dqXqAndZc);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
persist: {
|
persist: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|||||||
@ -6,24 +6,53 @@ export const useMenuStore = defineStore({
|
|||||||
state: () => ({
|
state: () => ({
|
||||||
/** 树形菜单数据,持久化到 localStorage key: app-Menu-jzd(家长端,与教师端 app-Menu-jsd 区分,避免同域下菜单互相覆盖) */
|
/** 树形菜单数据,持久化到 localStorage key: app-Menu-jzd(家长端,与教师端 app-Menu-jsd 区分,避免同域下菜单互相覆盖) */
|
||||||
mobileMenu: [] as MobileMenuTreeNode[],
|
mobileMenu: [] as MobileMenuTreeNode[],
|
||||||
|
/** 菜单所属用户 ID,换账号时与当前用户不一致则丢弃缓存 */
|
||||||
|
menuUserId: "" as string,
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
getMobileMenu(): MobileMenuTreeNode[] {
|
getMobileMenu(): MobileMenuTreeNode[] {
|
||||||
return this.mobileMenu || [];
|
return this.mobileMenu || [];
|
||||||
},
|
},
|
||||||
|
getMenuUserId(): string {
|
||||||
|
return this.menuUserId || "";
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
/** 清空菜单 */
|
/** 清空菜单 */
|
||||||
clearMenu() {
|
clearMenu() {
|
||||||
this.mobileMenu = [];
|
this.mobileMenu = [];
|
||||||
|
this.menuUserId = "";
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 设置菜单:拉取菜单时先清空再写入
|
* 设置菜单:拉取菜单时先清空再写入
|
||||||
* @param menu 树形菜单数据
|
* @param menu 树形菜单数据
|
||||||
|
* @param userId 当前登录用户 ID,用于缓存归属校验
|
||||||
*/
|
*/
|
||||||
setMobileMenu(menu: MobileMenuTreeNode[]) {
|
setMobileMenu(menu: MobileMenuTreeNode[], userId?: string | number) {
|
||||||
this.clearMenu();
|
this.clearMenu();
|
||||||
this.mobileMenu = menu && Array.isArray(menu) ? [...menu] : [];
|
this.mobileMenu = menu && Array.isArray(menu) ? [...menu] : [];
|
||||||
|
if (userId != null && userId !== "") {
|
||||||
|
this.menuUserId = String(userId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/** 缓存是否属于指定用户且非空 */
|
||||||
|
isMenuCacheForUser(userId: string | number | undefined | null): boolean {
|
||||||
|
if (userId == null || userId === "" || !this.menuUserId || !this.mobileMenu?.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return String(userId) === String(this.menuUserId);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 是否应重新拉取菜单:换用户、无缓存、或全局权限变更时间不一致
|
||||||
|
*/
|
||||||
|
shouldRefreshMenu(
|
||||||
|
userId: string | number | undefined | null,
|
||||||
|
serverChangeTime: string,
|
||||||
|
localChangeTime: string
|
||||||
|
): boolean {
|
||||||
|
if (!this.isMenuCacheForUser(userId)) return true;
|
||||||
|
if (serverChangeTime && serverChangeTime !== (localChangeTime || "")) return true;
|
||||||
|
return false;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
persist: {
|
persist: {
|
||||||
|
|||||||
7
src/utils/menuCache.ts
Normal file
7
src/utils/menuCache.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
/** 从家长端登录用户信息解析 userId */
|
||||||
|
export function resolveJzdUserId(user: unknown): string {
|
||||||
|
if (!user || typeof user !== "object") return "";
|
||||||
|
const u = user as Record<string, unknown>;
|
||||||
|
const id = u.userId ?? u.id;
|
||||||
|
return id != null && id !== "" ? String(id) : "";
|
||||||
|
}
|
||||||
8
src/utils/xszpPersistXq.ts
Normal file
8
src/utils/xszpPersistXq.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import type { useCommonStore } from "@/store/modules/common";
|
||||||
|
|
||||||
|
type CommonStore = ReturnType<typeof useCommonStore>;
|
||||||
|
|
||||||
|
/** Pinia 持久化:common.data.dqXqAndZc */
|
||||||
|
export async function getPersistedDqXqAndZc(store: CommonStore): Promise<any> {
|
||||||
|
return (await store.getDqXqAndZc()) ?? {};
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user