This commit is contained in:
2026-07-28 13:59:16 +08:00 Unverified
parent be842cc1d5
commit 9a75589282
13 changed files with 1119 additions and 43 deletions
+1 -1
View File
@@ -60,7 +60,7 @@
"ExpireMinutes": 60 "ExpireMinutes": 60
}, },
"Cors": { "Cors": {
"Origins": [] "Origins": ["capacitor://localhost", "http://localhost"]
}, },
"OfficialDocuments": { "OfficialDocuments": {
"InstitutionName": "明序大学", "InstitutionName": "明序大学",
+5
View File
@@ -1,3 +1,8 @@
# Capacitor
/android
/ios
.env.capacitor
# Logs # Logs
logs logs
*.log *.log
+13
View File
@@ -0,0 +1,13 @@
import type { CapacitorConfig } from '@capacitor/cli'
const config: CapacitorConfig = {
appId: 'edu.mingxu.jiaowu',
appName: '明序教务',
webDir: 'dist',
bundledWebRuntime: false,
android: {
allowMixedContent: true,
},
}
export default config
+1 -1
View File
@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#152550" /> <meta name="theme-color" content="#152550" />
<meta name="description" content="明序大学教务管理系统" /> <meta name="description" content="明序大学教务管理系统" />
<title>明序教务管理系统</title> <title>明序教务管理系统</title>
+1021 -3
View File
File diff suppressed because it is too large Load Diff
+11 -1
View File
@@ -6,9 +6,18 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vue-tsc -b && vite build", "build": "vue-tsc -b && vite build",
"preview": "vite preview" "build:capacitor": "vue-tsc -b && vite build --mode capacitor",
"preview": "vite preview",
"cap:sync": "npx cap sync",
"cap:open:android": "npx cap open android",
"cap:open:ios": "npx cap open ios",
"cap:run:android": "npx cap run android",
"cap:run:ios": "npx cap run ios"
}, },
"dependencies": { "dependencies": {
"@capacitor/android": "^8.4.2",
"@capacitor/core": "^8.4.2",
"@capacitor/ios": "^8.4.2",
"@ckeditor/ckeditor5-vue": "^8.2.0", "@ckeditor/ckeditor5-vue": "^8.2.0",
"@element-plus/icons-vue": "^2.3.2", "@element-plus/icons-vue": "^2.3.2",
"axios": "^1.18.1", "axios": "^1.18.1",
@@ -24,6 +33,7 @@
"vue-router": "^4.6.4" "vue-router": "^4.6.4"
}, },
"devDependencies": { "devDependencies": {
"@capacitor/cli": "^8.4.2",
"@types/node": "^24.13.2", "@types/node": "^24.13.2",
"@types/qrcode": "1.5.6", "@types/qrcode": "1.5.6",
"@vitejs/plugin-vue": "^6.0.7", "@vitejs/plugin-vue": "^6.0.7",
+2 -1
View File
@@ -1,4 +1,5 @@
import axios from 'axios' import axios from 'axios'
import { goLogin } from '../utils/navigate'
const http = axios.create({ const http = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL ?? '/api', baseURL: import.meta.env.VITE_API_BASE_URL ?? '/api',
@@ -17,7 +18,7 @@ http.interceptors.response.use(
if (error.response?.status === 401 && !error.config?.url?.endsWith('/auth/login')) { if (error.response?.status === 401 && !error.config?.url?.endsWith('/auth/login')) {
localStorage.removeItem('jiaowu_token') localStorage.removeItem('jiaowu_token')
localStorage.removeItem('jiaowu_user') localStorage.removeItem('jiaowu_user')
window.location.assign(`/login?redirect=${encodeURIComponent(location.pathname)}`) goLogin(location.pathname + location.search + location.hash)
} }
return Promise.reject(error) return Promise.reject(error)
}, },
+6 -4
View File
@@ -3,8 +3,10 @@ import { createPinia } from 'pinia'
import './style.css' import './style.css'
import App from './App.vue' import App from './App.vue'
import router from './router' import router from './router'
import { setRouter } from './utils/navigate'
createApp(App) const app = createApp(App)
.use(createPinia()) app.use(createPinia())
.use(router) app.use(router)
.mount('#app') setRouter(router)
app.mount('#app')
+4 -2
View File
@@ -1,9 +1,11 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router'
import { useAuthStore } from '../stores/auth' import { useAuthStore } from '../stores/auth'
import AdminLayout from '../layouts/AdminLayout.vue' import AdminLayout from '../layouts/AdminLayout.vue'
const router = createRouter({ const router = createRouter({
history: createWebHistory(), history: import.meta.env.VITE_ROUTER_MODE === 'hash'
? createWebHashHistory()
: createWebHistory(),
routes: [ routes: [
{ {
path: '/login', path: '/login',
+17
View File
@@ -0,0 +1,17 @@
import type { Router } from 'vue-router'
let router: Router | null = null
export function setRouter(r: Router): void {
router = r
}
export function goLogin(redirect: string): void {
if (router) {
router.push({ name: 'login', query: { redirect } })
} else {
window.location.assign(
`/?redirect=${encodeURIComponent(redirect)}#/login`,
)
}
}
+2 -1
View File
@@ -314,8 +314,9 @@ async function showQrCode() {
ElMessage.warning('未取得签到码,请刷新考勤表后重试。') ElMessage.warning('未取得签到码,请刷新考勤表后重试。')
return return
} }
const publicBase = import.meta.env.VITE_PUBLIC_BASE_URL ?? window.location.origin
qrCheckInUrl.value = qrCheckInUrl.value =
`${window.location.origin}/my-attendance?token=${encodeURIComponent(sheet.checkInToken)}` `${publicBase}/my-attendance?token=${encodeURIComponent(sheet.checkInToken)}`
try { try {
qrDataUrl.value = await QRCode.toDataURL(qrCheckInUrl.value, { qrDataUrl.value = await QRCode.toDataURL(qrCheckInUrl.value, {
width: 360, width: 360,
+2 -1
View File
@@ -610,9 +610,10 @@ function openCalendarClient() {
function normalizeCalendarSubscription(value: any) { function normalizeCalendarSubscription(value: any) {
if (!value?.feedPath) return value if (!value?.feedPath) return value
const configuredBase = String(http.defaults.baseURL ?? '/api') const configuredBase = String(http.defaults.baseURL ?? '/api')
const publicBase = import.meta.env.VITE_PUBLIC_BASE_URL ?? window.location.origin
const apiBase = new URL( const apiBase = new URL(
configuredBase.endsWith('/') ? configuredBase : `${configuredBase}/`, configuredBase.endsWith('/') ? configuredBase : `${configuredBase}/`,
window.location.origin, publicBase,
) )
const feedUrl = new URL(value.feedPath, apiBase).toString() const feedUrl = new URL(value.feedPath, apiBase).toString()
return { return {
+34 -28
View File
@@ -1,37 +1,43 @@
import { defineConfig } from 'vite' import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite' import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite' import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig(({ mode }) => {
plugins: [ const env = loadEnv(mode, process.cwd(), '')
vue(), const isCapacitor = env.VITE_ROUTER_MODE === 'hash'
AutoImport({
resolvers: [ElementPlusResolver()], return {
dts: 'src/auto-imports.d.ts', plugins: [
}), vue(),
Components({ AutoImport({
resolvers: [ElementPlusResolver({ importStyle: 'css', directives: true })], resolvers: [ElementPlusResolver()],
dts: 'src/components.d.ts', dts: 'src/auto-imports.d.ts',
}), }),
], Components({
build: { resolvers: [ElementPlusResolver({ importStyle: 'css', directives: true })],
outDir: '../src/Jiaowu.Api/wwwroot', dts: 'src/components.d.ts',
emptyOutDir: true, }),
}, ],
server: { base: isCapacitor ? './' : '/',
port: 5173, build: {
proxy: { outDir: isCapacitor ? 'dist' : '../src/Jiaowu.Api/wwwroot',
'/api': { emptyOutDir: true,
target: 'http://localhost:5255', },
changeOrigin: true, server: {
}, port: 5173,
'/health': { proxy: {
target: 'http://localhost:5255', '/api': {
changeOrigin: true, target: 'http://localhost:5255',
changeOrigin: true,
},
'/health': {
target: 'http://localhost:5255',
changeOrigin: true,
},
}, },
}, },
}, }
}) })