Commit 1e2ed63c authored by Administrator's avatar Administrator
Browse files

Checkpoint 2026-07-02 before using Minmax

parent b8cdc054
......@@ -29,4 +29,14 @@ coverage
*.tsbuildinfo
.qoder*
\ No newline at end of file
.qoder*
CLAUDE.md
reasonix.toml
swagger*
desktop*.json
clipboard*
*.xlsx
*.yaml
暑假日程表.txt
import-assignments.cjs
\ No newline at end of file
......@@ -8,6 +8,9 @@
"name": "student-app-frontend",
"version": "0.0.0",
"dependencies": {
"@fullcalendar/daygrid": "^6.1.21",
"@fullcalendar/interaction": "^6.1.21",
"@fullcalendar/vue3": "^6.1.21",
"@mdi/font": "^7.4.47",
"axios": "^1.11.0",
"pinia": "^3.0.3",
......@@ -23,6 +26,7 @@
"eslint": "^9.31.0",
"eslint-plugin-vue": "~10.3.0",
"globals": "^16.3.0",
"js-yaml": "^5.2.0",
"prettier": "3.6.2",
"vite": "^7.0.6",
"vite-plugin-vue-devtools": "^8.0.0"
......@@ -1089,6 +1093,29 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@eslint/eslintrc/node_modules/js-yaml": {
"version": "4.3.0",
"resolved": "https://repo.huaweicloud.com/repository/npm/js-yaml/-/js-yaml-4.3.0.tgz",
"integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/nodeca"
}
],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/@eslint/js": {
"version": "9.33.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.33.0.tgz",
......@@ -1126,6 +1153,44 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@fullcalendar/core": {
"version": "6.1.21",
"resolved": "https://repo.huaweicloud.com/repository/npm/@fullcalendar/core/-/core-6.1.21.tgz",
"integrity": "sha512-t3u/+sqh3Iq7TWtUnVLcGDUE6OWZh0UD3c04bI/l7lSLAgAKr3kngBmhHiQD1QXpwC8ZN5iNqG7a7gOVixhSKQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"preact": "~10.12.1"
}
},
"node_modules/@fullcalendar/daygrid": {
"version": "6.1.21",
"resolved": "https://repo.huaweicloud.com/repository/npm/@fullcalendar/daygrid/-/daygrid-6.1.21.tgz",
"integrity": "sha512-QYb1y40RGYLlOxKpYWg8O+7njEnKnFG8Tt7qjnubJGR35s1phQg67E+81y2TyAbbm59p2JFOCXGDk9t6KDujIA==",
"license": "MIT",
"peerDependencies": {
"@fullcalendar/core": "~6.1.21"
}
},
"node_modules/@fullcalendar/interaction": {
"version": "6.1.21",
"resolved": "https://repo.huaweicloud.com/repository/npm/@fullcalendar/interaction/-/interaction-6.1.21.tgz",
"integrity": "sha512-WPYpqtljDWmU0Xm2cOtFrLlocgxv7cgkOppj34Q6OUUat8a6Cnd6kYo2JR+irP223PE5lBYHFNp1qh7SIpJc0w==",
"license": "MIT",
"peerDependencies": {
"@fullcalendar/core": "~6.1.21"
}
},
"node_modules/@fullcalendar/vue3": {
"version": "6.1.21",
"resolved": "https://repo.huaweicloud.com/repository/npm/@fullcalendar/vue3/-/vue3-6.1.21.tgz",
"integrity": "sha512-OGt6WSC+/zz/ej6a0KfIBNl7BYuGchpZU49SsedYyv3WZWbghAE+D8YD6nhH1ia/I4p5Gcsv/nEXgEkT/I8aYQ==",
"license": "MIT",
"peerDependencies": {
"@fullcalendar/core": "~6.1.21",
"vue": "^3.0.11"
}
},
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
......@@ -3238,16 +3303,26 @@
"license": "MIT"
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"version": "5.2.0",
"resolved": "https://repo.huaweicloud.com/repository/npm/js-yaml/-/js-yaml-5.2.0.tgz",
"integrity": "sha512-YeLUMlvR4Ou1B119LIaM0r65JvbOBooJDc9yEu0dClb/uSC5P4FrLU8OCCz/HXWvtPoIrR0dRzABTjo1sTN9Bw==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/nodeca"
}
],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
"js-yaml": "bin/js-yaml.mjs"
}
},
"node_modules/jsesc": {
......@@ -3728,6 +3803,17 @@
"node": ">=4"
}
},
"node_modules/preact": {
"version": "10.12.1",
"resolved": "https://repo.huaweicloud.com/repository/npm/preact/-/preact-10.12.1.tgz",
"integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==",
"license": "MIT",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
......
......@@ -14,6 +14,9 @@
"format": "prettier --write src/"
},
"dependencies": {
"@fullcalendar/daygrid": "^6.1.21",
"@fullcalendar/interaction": "^6.1.21",
"@fullcalendar/vue3": "^6.1.21",
"@mdi/font": "^7.4.47",
"axios": "^1.11.0",
"pinia": "^3.0.3",
......@@ -29,6 +32,7 @@
"eslint": "^9.31.0",
"eslint-plugin-vue": "~10.3.0",
"globals": "^16.3.0",
"js-yaml": "^5.2.0",
"prettier": "3.6.2",
"vite": "^7.0.6",
"vite-plugin-vue-devtools": "^8.0.0"
......
......@@ -6,7 +6,8 @@ export const login = async (credentials) => {
const response = await apiClient.post(apiEndpoints.AUTH.LOGIN, credentials)
return response.data
} catch (error) {
throw new Error(error?.response?.data?.detail || error?.response?.data?.message || error.message || 'Login failed')
// 保留原始 error.response,以便调用方(如 auth store)能访问 error.response.data.detail
throw error
}
}
......@@ -16,6 +17,7 @@ export const refreshAccessToken = async (refreshToken) => {
const response = await apiClient.post(apiEndpoints.AUTH.REFRESH, { refresh: refreshToken })
return response.data
} catch (error) {
throw new Error(error?.response?.data?.detail || error?.message || 'Failed to refresh token')
// 保留原始 error.response,以便调用方能访问 error.response.data.detail
throw error
}
}
......@@ -77,6 +77,7 @@ export const getTasks = async (params = {}) => {
if (params.student_id) queryParams.student_id = params.student_id
if (params.subject_id) queryParams.subject_id = params.subject_id
if (params.term_id) queryParams.term_id = params.term_id
if (params.story_id) queryParams.story_id = params.story_id
console.log('Tasks API request params:', queryParams)
......
<template>
<v-footer app color="primary" dark :elevation="2">
<v-col class="text-center white--text">
<v-footer app color="primary" :elevation="2">
<v-col class="text-center text-white">
&copy; 2025 Student Management App
</v-col>
</v-footer>
......
......@@ -22,6 +22,7 @@ export default {
subject: 'Subject',
student: 'Student',
term: 'Term',
story: 'Story',
startDate: 'Start Date',
endDate: 'End Date',
completionPercent: 'Completion',
......@@ -43,9 +44,11 @@ export default {
allStudents: 'All Students',
allSubjects: 'All Subjects',
allTerms: 'All Terms',
allStories: 'No Story',
apply: 'Apply Filters',
clear: 'Clear Filters',
selectStudentFirst: 'Please select a student first',
selectTermFirst: 'Please select a term first',
expand: 'Expand Filter Panel',
collapse: 'Collapse Filter Panel',
expandToReselect: 'Click to Expand and Reselect'
......
......@@ -22,6 +22,7 @@ export default {
subject: '学科',
student: '学生',
term: '学期',
story: '系列任务',
startDate: '开始日期',
endDate: '截止日期',
completionPercent: '完成度',
......@@ -43,9 +44,11 @@ export default {
allStudents: '所有学生',
allSubjects: '所有学科',
allTerms: '所有学期',
allStories: '无系列',
apply: '应用筛选',
clear: '清除筛选',
selectStudentFirst: '请先选择学生',
selectTermFirst: '请先选择学期',
expand: '展开筛选面板',
collapse: '收起筛选面板',
expandToReselect: '点击展开重新选择'
......
import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import { isLanAccess } from '@/utils/lanAccess'
import HomeView from '../views/HomeView.vue'
import LoginView from '../views/LoginView.vue'
import ProfileView from '../views/ProfileView.vue'
......@@ -146,6 +147,12 @@ const router = createRouter({
// 路由守卫
router.beforeEach(async (to, from, next) => {
// 内网访问跳过认证
if (isLanAccess()) {
next()
return
}
const authStore = useAuthStore()
const isAuthed = authStore.isAuthenticated
......
......@@ -3,6 +3,7 @@ import { defineStore } from 'pinia'
import { useRouter } from 'vue-router'
import apiClient from '@/api/index.js'
import { login as authLogin, refreshAccessToken as authRefreshToken } from '@/api/authService.js'
import { isLanAccess } from '@/utils/lanAccess'
export const useAuthStore = defineStore('auth', () => {
const router = useRouter()
......@@ -120,7 +121,11 @@ export const useAuthStore = defineStore('auth', () => {
}
if (originalRequest._retry) {
// 已重试过,仍然 401,直接登出
// 已重试过,仍然 401
// 内网访问时:读操作(GET)跳过,写操作(POST/PATCH/DELETE)要求登录
if (isLanAccess() && originalRequest.method?.toUpperCase() === 'GET') {
return Promise.reject(error)
}
logout()
return Promise.reject(error)
}
......@@ -132,6 +137,10 @@ export const useAuthStore = defineStore('auth', () => {
originalRequest.headers.Authorization = `Bearer ${accessToken.value}`
return apiClient(originalRequest)
} catch (e) {
// 内网访问时:读操作(GET)跳过,写操作要求登录
if (isLanAccess() && originalRequest.method?.toUpperCase() === 'GET') {
return Promise.reject(error)
}
logout()
return Promise.reject(e)
}
......
/**
* LAN 内网访问配置
*
* 在这些 CIDR 范围内访问时,跳过前端认证检查。
* 需要后端配合去掉 API 认证。
*/
const ALLOWED_CIDRS = [
'192.168.1.0/24',
'192.168.2.0/24',
]
// 将 IP 地址转换为 32 位整数
const ipToInt = (ip) => {
const parts = ip.split('.').map(Number)
return ((parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3]) >>> 0
}
// 检查 IP 是否在 CIDR 范围内
const ipInCidr = (ip, cidr) => {
const [rangeIp, bits] = cidr.split('/')
const mask = ~(2 ** (32 - parseInt(bits)) - 1) >>> 0
return (ipToInt(ip) & mask) === (ipToInt(rangeIp) & mask)
}
/**
* 判断当前访问是否来自允许的内网地址
*/
export const isLanAccess = () => {
if (typeof window === 'undefined') return false
const hostname = window.location.hostname
// localhost 始终放行
if (hostname === 'localhost' || hostname === '127.0.0.1') return true
// 检查是否在允许的 CIDR 范围内
return ALLOWED_CIDRS.some(cidr => ipInCidr(hostname, cidr))
}
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment