useCalendar.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. import { ref, computed, watch } from 'vue';
  2. import { getPlanStudyRecord, getStudentPracticeRecord } from '@/api/modules/study';
  3. import { useUserStore } from '@/store/userStore';
  4. // @ts-ignore
  5. import CalendarUtil from '@/uni_modules/uni-calendar/components/uni-calendar/util.js';
  6. import { PracticeRecord } from '@/types/study';
  7. export interface PracticeStatistics {
  8. list: PracticeData[],
  9. rate: number,
  10. studyDays: number,
  11. total: number,
  12. }
  13. export interface PracticeData {
  14. date: string;
  15. info: number;
  16. questionNum: number;
  17. }
  18. export function useCalendar() {
  19. const calendarUtil = new CalendarUtil();
  20. const { isStudent } = storeToRefs(useUserStore());
  21. const globalRecordId = ref(0);
  22. const selected = ref<PracticeData[]>([]);
  23. const statistics = ref<PracticeStatistics>({
  24. list: [],
  25. rate: 0,
  26. studyDays: 0,
  27. total: 0
  28. });
  29. const currentWeekNumber = ref(1); // 用于外部数据计算
  30. const calendarWeekNumber = ref(1); // 专门用于日历组件显示
  31. const currentDate = ref(new Date());
  32. const displayMode = ref<'year' | 'month'>('year');
  33. const loading = ref(false);
  34. // 计算当前周范围
  35. const currentWeekRange = computed(() => {
  36. return calendarUtil.getCurrentWeekRange(currentDate.value, currentWeekNumber.value);
  37. });
  38. // 计算当前月份范围
  39. const currentMonthRange = computed(() => {
  40. return calendarUtil.getCurrentMonthRange(currentDate.value);
  41. });
  42. // 计算是否可以切换到上一周/月/年
  43. const canGoPrev = computed(() => {
  44. const today = new Date();
  45. const current = currentDate.value;
  46. if (displayMode.value === 'year') {
  47. return current.getFullYear() > EARLIEST_YEAR.value;
  48. } else {
  49. // 月份模式:支持跨年,只要不是最早年份1月就可以往前切换
  50. return !(current.getFullYear() === EARLIEST_YEAR.value && current.getMonth() === 0);
  51. }
  52. });
  53. // 计算是否可以切换到下一周/月/年(不能超过当前日期)
  54. const canGoNext = computed(() => {
  55. const today = new Date();
  56. const current = currentDate.value;
  57. if (displayMode.value === 'year') {
  58. return current.getFullYear() < today.getFullYear();
  59. } else {
  60. // 月份模式,不能超过当前月份
  61. return current.getFullYear() < today.getFullYear() ||
  62. (current.getFullYear() === today.getFullYear() && current.getMonth() < today.getMonth());
  63. }
  64. });
  65. // 3. 确保返回的数据格式符合 PracticeData[] 接口
  66. // 4. 统计数据会从返回的练习数据中自动计算,无需额外处理
  67. // ========================================================
  68. const fetchPracticeData = async (year: number, month: number): Promise<PracticeStatistics> => {
  69. let practiceData: PracticeStatistics = {
  70. list: [],
  71. rate: 0,
  72. studyDays: 0,
  73. total: 0
  74. };
  75. let resData: PracticeRecord = {
  76. list: [],
  77. rate: 0,
  78. studyDays: 0,
  79. total: 0
  80. };
  81. if (isStudent.value) {
  82. const { data } = await getPlanStudyRecord({
  83. year,
  84. month
  85. });
  86. resData = data;
  87. } else {
  88. // 老师查看学生刷题记录
  89. const { data } = await getStudentPracticeRecord({
  90. recordId: globalRecordId.value,
  91. year,
  92. month
  93. });
  94. resData = data;
  95. }
  96. if (resData && Array.isArray(resData.list)) {
  97. practiceData.list = resData.list.map(item => ({
  98. date: item.date,
  99. info: item.rate ? Number(item.rate) : 0,
  100. questionNum: item.study ? Number(item.study) : 0
  101. })).sort((a, b) => a.date.localeCompare(b.date));
  102. }
  103. practiceData.rate = resData.rate ? Number(resData.rate) : 0;
  104. practiceData.studyDays = resData.studyDays ? Number(resData.studyDays) : 0;
  105. practiceData.total = resData.total ? Number(resData.total) : 0;
  106. return practiceData;
  107. };
  108. // 更新日历数据 - 统一的数据更新方法
  109. const updateCalendarData = async (year: number, month: number) => {
  110. if (month !== 0) {
  111. displayMode.value = 'month';
  112. currentDate.value = new Date(year, month - 1, 1);
  113. } else {
  114. displayMode.value = 'year';
  115. currentDate.value = new Date(year, month, 1);
  116. }
  117. loading.value = true;
  118. try {
  119. // 请求数据
  120. const practiceData = await fetchPracticeData(year, month);
  121. // 更新状态
  122. selected.value = practiceData.list
  123. statistics.value = practiceData;
  124. } finally {
  125. loading.value = false;
  126. }
  127. };
  128. // 切换到上一个月
  129. const goToPrevMonth = async () => {
  130. if (!canGoPrev.value) return;
  131. const currentMonth = currentDate.value.getMonth();
  132. const targetMonth = currentMonth === 0 ? 12 : currentMonth; // 如果当前是1月,目标月份是12月
  133. const targetYear = currentMonth === 0 ? currentDate.value.getFullYear() - 1 : currentDate.value.getFullYear();
  134. const prevDate = new Date(targetYear, targetMonth - 1, 1);
  135. currentDate.value = prevDate;
  136. await updateCalendarData(targetYear, targetMonth);
  137. };
  138. // 切换到下一个月
  139. const goToNextMonth = async () => {
  140. if (!canGoNext.value) return;
  141. const currentMonth = currentDate.value.getMonth();
  142. const targetMonth = currentMonth === 11 ? 1 : currentMonth + 2; // 如果当前是12月,目标月份是1月
  143. const targetYear = currentMonth === 11 ? currentDate.value.getFullYear() + 1 : currentDate.value.getFullYear();
  144. const nextDate = new Date(targetYear, targetMonth - 1, 1);
  145. currentDate.value = nextDate;
  146. await updateCalendarData(targetYear, targetMonth);
  147. };
  148. // 根据年份跳转
  149. const goToYear = async (year: number) => {
  150. await updateCalendarData(year, 0);
  151. };
  152. // 根据年份和月份跳转
  153. const goToYearMonth = async (year: number, month: number) => {
  154. await updateCalendarData(year, month);
  155. };
  156. // 生成可以选择的年份列表,默认去年+今年
  157. const yearList = computed(() => {
  158. const today = new Date();
  159. const currentYear = today.getFullYear();
  160. return [
  161. {
  162. label: currentYear - 1,
  163. value: currentYear - 1
  164. },
  165. {
  166. label: currentYear,
  167. value: currentYear
  168. }
  169. ];
  170. })
  171. // 配置变量:最早可访问的年份
  172. const EARLIEST_YEAR = computed(() => {
  173. return yearList.value[0].value;
  174. });
  175. // 获取当前年份
  176. const currentYear = computed(() => currentDate.value.getFullYear());
  177. // 获取当前月份
  178. const currentMonth = computed(() => currentDate.value.getMonth() + 1);
  179. // 初始化下拉菜单数据
  180. const initializeFormData = () => {
  181. const today = new Date();
  182. const currentYearToday = today.getFullYear();
  183. return {
  184. year: currentYearToday.toString(),
  185. month: '', // 默认不选择月份
  186. };
  187. };
  188. // 初始化数据 - 默认按年份模式初始化
  189. const init = async (recordId?: number) => {
  190. const today = new Date();
  191. if (recordId) {
  192. globalRecordId.value = recordId;
  193. }
  194. await updateCalendarData(today.getFullYear(), 0);
  195. };
  196. return {
  197. // 数据
  198. calendarUtil,
  199. selected,
  200. statistics,
  201. currentWeekNumber,
  202. calendarWeekNumber,
  203. currentDate,
  204. displayMode,
  205. currentWeekRange,
  206. currentMonthRange,
  207. // 状态
  208. canGoPrev,
  209. canGoNext,
  210. loading,
  211. // 计算属性
  212. currentYear,
  213. currentMonth,
  214. // 配置
  215. EARLIEST_YEAR,
  216. yearList,
  217. // 方法
  218. updateCalendarData,
  219. goToPrevMonth,
  220. goToNextMonth,
  221. goToYear,
  222. goToYearMonth,
  223. initializeFormData,
  224. init
  225. };
  226. }