128 lines
4.5 KiB
JavaScript
128 lines
4.5 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)), '..')
|
|
const appsettingsPath = resolve(webRoot, '..', 'src', 'Jiaowu.Api', 'appsettings.json')
|
|
const branding = JSON.parse(readFileSync(appsettingsPath, 'utf8')).Branding
|
|
|
|
if (!branding?.BrandName?.trim() || !branding?.ShortName?.trim()) {
|
|
throw new Error('appsettings.json 中缺少 Branding:BrandName 或 Branding:ShortName。')
|
|
}
|
|
|
|
function escapeXml(value) {
|
|
return String(value)
|
|
.replaceAll('&', '&')
|
|
.replaceAll('<', '<')
|
|
.replaceAll('>', '>')
|
|
.replaceAll('"', '"')
|
|
.replaceAll("'", ''')
|
|
}
|
|
|
|
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')
|
|
})
|
|
|
|
const stringsPath = resolve(androidRoot, 'app', 'src', 'main', 'res', 'values', 'strings.xml')
|
|
updateFile(stringsPath, content => content
|
|
.replaceAll('{{BRAND_NAME}}', escapeXml(branding.BrandName.trim()))
|
|
.replaceAll('{{SHORT_NAME}}', escapeXml(branding.ShortName.trim())))
|
|
|
|
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('、')} 的扫码、定位和原生桌面体验。`)
|