Files
Academic-Affairs-System/web/scripts/configure-capacitor.mjs
T
biss 66622ca207 智能开屏:显示“姓名+早上/中午/下午/晚上问候”,支持春节、端午、中秋、国庆等节日文案,可点击跳过:[SmartLaunchScreen.vue](E:/jiaowu/web/src/components/SmartLaunchScreen.vue)
长按 App 图标:提供“我的课表、考试安排、课堂签到、消息中心”四个入口。签到会按学生/教师角色自动分流:[shortcuts.xml](E:/jiaowu/web/native/android/app/src/main/res/xml/shortcuts.xml)
桌面小组件:新增“今日课表”和“近期考试”,展示最近同步的数据,点击可进入对应页面:[WidgetRenderer.java](E:/jiaowu/web/native/android/app/src/main/java/edu/mingxu/jiaowu/WidgetRenderer.java)
安全处理:组件只缓存课程和考试摘要,不保存登录令牌;退出账号会清空组件,隔天未刷新时不会继续展示旧的“今日课表”。
原生模板已纳入版本管理,每次 npm run cap:sync 会自动恢复到被忽略的 Android 工程:[configure-capacitor.mjs](E:/jiaowu/web/scripts/configure-capacitor.mjs)
2026-07-29 11:11:37 +08:00

108 lines
3.7 KiB
JavaScript

import {
copyFileSync,
existsSync,
mkdirSync,
readFileSync,
readdirSync,
writeFileSync,
} from 'node:fs'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
const webRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
function updateFile(path, update) {
if (!existsSync(path)) return false
const current = readFileSync(path, 'utf8')
const next = update(current)
if (next !== current) writeFileSync(path, next, 'utf8')
return true
}
function copyTemplateTree(source, destination) {
if (!existsSync(source)) {
throw new Error(`缺少原生模板目录:${source}`)
}
mkdirSync(destination, { recursive: true })
for (const entry of readdirSync(source, { withFileTypes: true })) {
const sourcePath = resolve(source, entry.name)
const destinationPath = resolve(destination, entry.name)
if (entry.isDirectory()) copyTemplateTree(sourcePath, destinationPath)
else copyFileSync(sourcePath, destinationPath)
}
}
function configureAndroid() {
const androidRoot = resolve(webRoot, 'android')
const variablesPath = resolve(webRoot, 'android', 'variables.gradle')
const templateRoot = resolve(webRoot, 'native', 'android')
const manifestPath = resolve(
androidRoot,
'app',
'src',
'main',
'AndroidManifest.xml',
)
if (!existsSync(variablesPath)) return false
copyTemplateTree(templateRoot, androidRoot)
if (!existsSync(manifestPath)) {
throw new Error('Android 原生模板未生成 AndroidManifest.xml。')
}
updateFile(variablesPath, content => {
if (!/minSdkVersion\s*=\s*\d+/.test(content)) {
throw new Error('未在 android/variables.gradle 中找到 minSdkVersion。')
}
return content.replace(/minSdkVersion\s*=\s*\d+/, 'minSdkVersion = 26')
})
updateFile(manifestPath, content => {
const declarations = [
'<uses-permission android:name="android.permission.CAMERA" />',
'<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />',
'<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />',
'<uses-feature android:name="android.hardware.camera" android:required="false" />',
'<uses-feature android:name="android.hardware.location.gps" android:required="false" />',
]
const missing = declarations.filter(declaration => !content.includes(declaration))
if (!missing.length) return content
return content.replace(
'</manifest>',
` ${missing.join('\n ')}\n</manifest>`,
)
})
return true
}
function configureIos() {
const infoPlistPath = resolve(webRoot, 'ios', 'App', 'App', 'Info.plist')
if (!existsSync(infoPlistPath)) return false
updateFile(infoPlistPath, content => {
const descriptions = [
['NSCameraUsageDescription', '用于扫描教师展示的课堂签到二维码。'],
['NSLocationWhenInUseUsageDescription', '用于在课堂签到时校验当前位置。'],
['NSLocationAlwaysAndWhenInUseUsageDescription', '用于在课堂签到时校验当前位置。'],
]
const missing = descriptions.filter(([key]) => !content.includes(`<key>${key}</key>`))
if (!missing.length) return content
const entries = missing
.map(([key, value]) => `\t<key>${key}</key>\n\t<string>${value}</string>`)
.join('\n')
return content.replace('</dict>', `${entries}\n</dict>`)
})
return true
}
const configuredPlatforms = [
configureAndroid() ? 'Android' : null,
configureIos() ? 'iOS' : null,
].filter(Boolean)
if (!configuredPlatforms.length) {
throw new Error('尚未生成 Capacitor 原生工程,请先运行 npx cap add android 或 ios。')
}
console.log(`已配置 ${configuredPlatforms.join('、')} 的扫码、定位和原生桌面体验。`)