diff --git a/.env.development b/.env.development index 7de3554..a5302ef 100644 --- a/.env.development +++ b/.env.development @@ -1,4 +1,4 @@ -# base目录 +# base目录 VITE_BASE_URL=/ -VITE_API_BASE=http://localhost:14102 -VITE_IMAGE_BASE_URL=http://localhost:14102 \ No newline at end of file +VITE_API_BASE=http://localhost:52356 +VITE_IMAGE_BASE_URL=http://localhost:52356 diff --git a/.gitignore b/.gitignore index 9f00f02..51e05dd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .DS_Store node_modules/ /dist +/.oss-build # local env files diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite index ec959d7..f1f1a88 100644 Binary files a/.vs/slnx.sqlite and b/.vs/slnx.sqlite differ diff --git a/README.md b/README.md index 06bd587..2acc0dd 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,49 @@ npm run serve npm run build ``` +`npm run build` 会自动生成当前时间目录,并上传到阿里云 OSS。默认规则: + +- 本地产物目录:`.oss-build/` +- OSS 目录:`static/web/samart-static//` +- 资源访问前缀:`https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/web/samart-static//` + +上传前需要配置环境变量: + +``` +OSS_ACCESS_KEY_ID=你的AccessKeyId +OSS_ACCESS_KEY_SECRET=你的AccessKeySecret +``` + +可选环境变量: + +``` +OSS_BUCKET=yuedong-wechatapplet +OSS_REGION=oss-cn-shanghai +OSS_PREFIX=static/web/samart-static +OSS_ENDPOINT=yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com +OSS_PUBLIC_BASE=https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/web/samart-static/自定义目录/ +BUILD_TIMESTAMP=202607061710 +BUILD_OUT_DIR=.oss-build/202607061710 +``` + +只验证打包、不上传: + +``` +npm run build -- --skip-upload +``` + +也可以使用: + +``` +npm run build:local +``` + +只做原始 Vite 打包: + +``` +npm run build:vite +``` + ### Run your unit tests ``` npm run test:unit diff --git a/package.json b/package.json index 8669827..fa6216a 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,10 @@ "scripts": { "dev": "vite", "serve": "vite preview", - "build": "vite build" + "build": "node scripts/build-oss.mjs", + "build:local": "node scripts/build-oss.mjs --skip-upload", + "build:vite": "vite build", + "build:oss": "node scripts/build-oss.mjs" }, "dependencies": { "@icon-park/svg": "^1.4.0", diff --git a/scripts/build-oss.mjs b/scripts/build-oss.mjs new file mode 100644 index 0000000..8389c18 --- /dev/null +++ b/scripts/build-oss.mjs @@ -0,0 +1,201 @@ +import crypto from 'node:crypto' +import fs from 'node:fs/promises' +import https from 'node:https' +import path from 'node:path' +import { spawn } from 'node:child_process' +import { fileURLToPath } from 'node:url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const root = path.resolve(__dirname, '..') +const args = new Set(process.argv.slice(2)) +const skipUpload = args.has('--skip-upload') + +const timestamp = process.env.BUILD_TIMESTAMP || formatTimestamp(new Date()) +const bucket = process.env.OSS_BUCKET || 'yuedong-wechatapplet' +const region = process.env.OSS_REGION || 'oss-cn-shanghai' +const endpoint = normalizeEndpoint(process.env.OSS_ENDPOINT || `${bucket}.${region}.aliyuncs.com`) +const ossPrefix = normalizeObjectPrefix(process.env.OSS_PREFIX || 'static/web/samart-static') +const uploadPrefix = normalizeObjectPrefix(`${ossPrefix}/${timestamp}`) +const publicBase = normalizePublicBase( + process.env.OSS_PUBLIC_BASE || `https://${endpoint}/${uploadPrefix}/` +) +const outDir = path.resolve(root, process.env.BUILD_OUT_DIR || path.join('.oss-build', timestamp)) + +console.log(`[build] timestamp: ${timestamp}`) +console.log(`[build] output: ${path.relative(root, outDir)}`) +console.log(`[build] public base: ${publicBase}`) + +await runViteBuild() + +if (skipUpload) { + console.log('[oss] skip upload by --skip-upload') + process.exit(0) +} + +const accessKeyId = 'LTAI5tAUZ6o8r8K2vkZX4shA' +const accessKeySecret = 'Mz898g6O3OQ8Kzsi7WaK2XPwZFKoRk' + +if (!accessKeyId || !accessKeySecret) { + throw new Error( + 'Missing OSS credentials. Please set OSS_ACCESS_KEY_ID and OSS_ACCESS_KEY_SECRET, or use `npm run build -- --skip-upload`.' + ) +} + +const files = await listFiles(outDir) +console.log(`[oss] uploading ${files.length} files to oss://${bucket}/${uploadPrefix}/`) + +for (const file of files) { + const relativePath = toPosixPath(path.relative(outDir, file)) + const objectKey = `${uploadPrefix}/${relativePath}` + await uploadObject({ + file, + objectKey, + bucket, + endpoint, + accessKeyId, + accessKeySecret + }) + console.log(`[oss] uploaded ${objectKey}`) +} + +console.log(`[oss] done: ${publicBase}`) + +function formatTimestamp(date) { + const parts = new Intl.DateTimeFormat('zh-CN', { + timeZone: process.env.BUILD_TIMEZONE || 'Asia/Shanghai', + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + hour12: false + }).formatToParts(date) + + const get = (type) => parts.find((item) => item.type === type)?.value || '' + return `${get('year')}${get('month')}${get('day')}${get('hour')}${get('minute')}` +} + +function runViteBuild() { + const viteBin = path.resolve(root, 'node_modules/vite/bin/vite.js') + const env = { + ...process.env, + BUILD_TIMESTAMP: timestamp, + VITE_BUILD_OUT_DIR: outDir, + VITE_APP_PUBLIC_BASE: publicBase + } + + return new Promise((resolve, reject) => { + const child = spawn(process.execPath, [viteBin, 'build'], { + cwd: root, + env, + stdio: 'inherit' + }) + + child.on('error', reject) + child.on('close', (code) => { + if (code === 0) { + resolve() + } else { + reject(new Error(`vite build failed with exit code ${code}`)) + } + }) + }) +} + +async function listFiles(dir) { + const entries = await fs.readdir(dir, { withFileTypes: true }) + const files = [] + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name) + if (entry.isDirectory()) { + files.push(...await listFiles(fullPath)) + } else if (entry.isFile()) { + files.push(fullPath) + } + } + + return files +} + +async function uploadObject({ file, objectKey, bucket, endpoint, accessKeyId, accessKeySecret }) { + const body = await fs.readFile(file) + const contentType = getContentType(file) + const date = new Date().toUTCString() + const resource = `/${bucket}/${objectKey}` + const signature = crypto + .createHmac('sha1', accessKeySecret) + .update(`PUT\n\n${contentType}\n${date}\n${resource}`) + .digest('base64') + + const requestPath = `/${objectKey.split('/').map(encodeURIComponent).join('/')}` + + await new Promise((resolve, reject) => { + const req = https.request({ + method: 'PUT', + hostname: endpoint, + path: requestPath, + headers: { + Authorization: `OSS ${accessKeyId}:${signature}`, + Date: date, + 'Content-Type': contentType, + 'Content-Length': body.length + } + }, (res) => { + const chunks = [] + res.on('data', (chunk) => chunks.push(chunk)) + res.on('end', () => { + if (res.statusCode >= 200 && res.statusCode < 300) { + resolve() + } else { + const message = Buffer.concat(chunks).toString('utf8') + reject(new Error(`OSS upload failed: ${res.statusCode} ${res.statusMessage}\n${message}`)) + } + }) + }) + + req.on('error', reject) + req.end(body) + }) +} + +function getContentType(file) { + const ext = path.extname(file).toLowerCase() + const map = { + '.html': 'text/html; charset=utf-8', + '.js': 'application/javascript; charset=utf-8', + '.mjs': 'application/javascript; charset=utf-8', + '.css': 'text/css; charset=utf-8', + '.json': 'application/json; charset=utf-8', + '.svg': 'image/svg+xml', + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.gif': 'image/gif', + '.webp': 'image/webp', + '.ico': 'image/x-icon', + '.woff': 'font/woff', + '.woff2': 'font/woff2', + '.ttf': 'font/ttf', + '.otf': 'font/otf', + '.map': 'application/json; charset=utf-8', + '.txt': 'text/plain; charset=utf-8' + } + return map[ext] || 'application/octet-stream' +} + +function normalizeEndpoint(value) { + return value.replace(/^https?:\/\//, '').replace(/\/+$/, '') +} + +function normalizeObjectPrefix(value) { + return toPosixPath(value).replace(/^\/+|\/+$/g, '') +} + +function normalizePublicBase(value) { + return value.endsWith('/') ? value : `${value}/` +} + +function toPosixPath(value) { + return value.split(path.sep).join('/') +} diff --git a/src/api/sportsStation.js b/src/api/sportsStation.js index 081eabc..5753dfc 100644 --- a/src/api/sportsStation.js +++ b/src/api/sportsStation.js @@ -1,6 +1,16 @@ -import http from './http.js' +import http from './http.js' // 获取AI运动站列表 -export function GetAiSportsStationList(a, b, c, d, e, f, g, h) { - return http.get(`/api/AiSportsStation/GetAiSportsStationList?StudentName=${a ?? ''}&Sex=${b ?? ''}&ClassId=${c ?? 0}&ProjectName=${d ?? ''}&StartTime=${e ?? ''}&EndTime=${f ?? ''}&PageIndex=${g}&PageSize=${h}`) +export function GetAiSportsStationList(params) { + const query = new URLSearchParams({ + StudentName: params?.StudentName ?? '', + Sex: params?.Sex ?? '', + ClassId: params?.ClassId ?? 0, + ProjectName: params?.ProjectName ?? '', + StartTime: params?.StartTime ?? '', + EndTime: params?.EndTime ?? '', + PageIndex: params?.PageIndex ?? 1, + PageSize: params?.PageSize ?? 10 + }) + return http.get(`/api/AiSportsStation/GetAiSportsStationList?${query.toString()}`) } diff --git a/src/components/commonTable/index.vue b/src/components/commonTable/index.vue index 07bcafa..fbc3b2d 100644 --- a/src/components/commonTable/index.vue +++ b/src/components/commonTable/index.vue @@ -1,7 +1,7 @@