首次提交
This commit is contained in:
+125
-3
@@ -13,11 +13,132 @@ import { VitePWA } from 'vite-plugin-pwa'
|
||||
import { defineConfig } from 'vite'
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
// Local File Storage Plugin
|
||||
const localFileStoragePlugin = () => {
|
||||
return {
|
||||
name: 'local-file-storage',
|
||||
configureServer(server) {
|
||||
const dataDir = path.resolve(process.cwd(), 'data');
|
||||
if (!fs.existsSync(dataDir)) {
|
||||
fs.mkdirSync(dataDir, { recursive: true });
|
||||
}
|
||||
|
||||
server.middlewares.use('/api/ppt', async (req, res, next) => {
|
||||
const url = new URL(req.url, `http://${req.headers.host}`);
|
||||
|
||||
// Handle CORS
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (url.pathname === '/check' || url.pathname.endsWith('/check')) {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
// In dev mode on node, we just mock it. Real logic is in start-server.ps1
|
||||
res.end(JSON.stringify({ success: true, hasPpt: false }));
|
||||
return;
|
||||
}
|
||||
|
||||
if (url.pathname === '/control' || url.pathname.endsWith('/control')) {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({ success: true }));
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
server.middlewares.use('/api/local-file', async (req, res, next) => {
|
||||
const url = new URL(req.url, `http://${req.headers.host}`);
|
||||
const key = url.searchParams.get('key');
|
||||
|
||||
// Handle CORS
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (url.pathname === '/save' && req.method === 'POST') {
|
||||
let body = '';
|
||||
req.on('data', chunk => body += chunk);
|
||||
req.on('end', () => {
|
||||
try {
|
||||
const { key, data } = JSON.parse(body);
|
||||
if (!key || !data) throw new Error('Missing key or data');
|
||||
|
||||
// Sanitize key to prevent path traversal
|
||||
const safeKey = key.replace(/[^a-zA-Z0-9_-]/g, '_');
|
||||
const filePath = path.join(dataDir, `${safeKey}.json`);
|
||||
|
||||
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({ success: true }));
|
||||
} catch (e) {
|
||||
res.statusCode = 500;
|
||||
res.end(JSON.stringify({ success: false, error: e.message }));
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (url.pathname === '/load' && req.method === 'GET') {
|
||||
if (!key) {
|
||||
res.statusCode = 400;
|
||||
res.end(JSON.stringify({ success: false, error: 'Missing key' }));
|
||||
return;
|
||||
}
|
||||
|
||||
const safeKey = key.replace(/[^a-zA-Z0-9_-]/g, '_');
|
||||
const filePath = path.join(dataDir, `${safeKey}.json`);
|
||||
|
||||
if (fs.existsSync(filePath)) {
|
||||
const data = fs.readFileSync(filePath, 'utf-8');
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({ success: true, data: JSON.parse(data) }));
|
||||
} else {
|
||||
res.statusCode = 404;
|
||||
res.end(JSON.stringify({ success: false, error: 'Not found' }));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (url.pathname === '/keys' && req.method === 'GET') {
|
||||
try {
|
||||
const files = fs.readdirSync(dataDir);
|
||||
const keys = files
|
||||
.filter(f => f.endsWith('.json'))
|
||||
.map(f => f.replace('.json', ''));
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({ success: true, keys }));
|
||||
} catch (e) {
|
||||
res.statusCode = 500;
|
||||
res.end(JSON.stringify({ success: false, error: e.message }));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
plugins: [
|
||||
localFileStoragePlugin(),
|
||||
VueRouter(),
|
||||
vueDevTools(),
|
||||
Layouts(),
|
||||
@@ -168,7 +289,7 @@ export default defineConfig({
|
||||
google: {
|
||||
families: [{
|
||||
name: 'Roboto',
|
||||
styles: 'wght@100;300;400;500;700;900',
|
||||
styles: 'wght@300;400;500;700',
|
||||
}],
|
||||
},
|
||||
}),
|
||||
@@ -208,8 +329,6 @@ export default defineConfig({
|
||||
'vendor-vue': ['vue', 'vue-router', 'pinia'],
|
||||
// UI 框架
|
||||
'vendor-vuetify': ['vuetify'],
|
||||
// 监控(异步加载,独立 chunk)
|
||||
'vendor-sentry': ['@sentry/vue'],
|
||||
// 实时通信
|
||||
'vendor-socket': ['socket.io-client'],
|
||||
// 通用工具库
|
||||
@@ -220,6 +339,9 @@ export default defineConfig({
|
||||
},
|
||||
server: {
|
||||
port: 3031,
|
||||
watch: {
|
||||
ignored: ['**/data/**', path.resolve(process.cwd(), 'data', '**')]
|
||||
}
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
|
||||
Reference in New Issue
Block a user