const { defineConfig } = require('vite') const vue = require('@vitejs/plugin-vue').default const path = require('path') const { spawn } = require('child_process') // https://vitejs.dev/config/ module.exports = defineConfig({ css: { preprocessorOptions: { scss: { silenceDeprecations: ['legacy-js-api'], api: 'modern-compiler' } } }, plugins: [ vue(), // 孔夫子旧书网登录代理(避免浏览器跨域限制) { name: 'kongfz-login-proxy', configureServer(server) { server.middlewares.use('/kongfz-login', async (req, res) => { if (req.method !== 'POST') { res.setHeader('Content-Type', 'application/json') res.end(JSON.stringify({ code: 405, message: '仅支持 POST 请求' })) return } let body = '' req.on('data', chunk => { body += chunk.toString() }) req.on('end', async () => { try { const params = new URLSearchParams(body) const username = params.get('loginName') || '' const password = params.get('loginPass') || '' if (!username || !password) { res.setHeader('Content-Type', 'application/json') res.end(JSON.stringify({ code: 400, message: '请输入用户名和密码!' })) return } const https = require('https') const http = require('http') function requestWithRedirect(options, bodyData, redirectCount = 0) { return new Promise((resolve, reject) => { if (redirectCount > 5) { reject(new Error('重定向次数过多')) return } const mod = options.port === 443 ? https : http const req = mod.request(options, (response) => { if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) { const redirectUrl = new URL(response.headers.location, `https://${options.hostname}`) const redirectOpts = { hostname: redirectUrl.hostname, port: redirectUrl.port || (redirectUrl.protocol === 'https:' ? 443 : 80), path: redirectUrl.pathname + redirectUrl.search, method: 'GET', headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' }, timeout: 15000 } if (response.headers['set-cookie']) { const cookies = Array.isArray(response.headers['set-cookie']) ? response.headers['set-cookie'].join('; ') : response.headers['set-cookie'] redirectOpts.headers['Cookie'] = cookies.split(';')[0].trim() } resolve(requestWithRedirect(redirectOpts, null, redirectCount + 1)) return } let responseData = '' response.on('data', chunk => { responseData += chunk }) response.on('end', () => resolve({ statusCode: response.statusCode, headers: response.headers, body: responseData })) }) req.on('error', err => reject(new Error(`登录请求失败: ${err.message}`))) req.on('timeout', () => { req.destroy(); reject(new Error('登录请求超时')) }) if (bodyData) req.write(bodyData) req.end() }) } const formData = new URLSearchParams() formData.append('loginName', username) formData.append('loginPass', password) formData.append('returnUrl', 'http://user.kongfz.com/') const formDataStr = formData.toString() const loginResponse = await requestWithRedirect({ hostname: 'login.kongfz.com', port: 443, path: '/Pc/Login/account', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(formDataStr), 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' }, timeout: 15000 }, formDataStr) if (loginResponse.statusCode !== 200) { throw new Error(`登录失败(HTTP状态码: ${loginResponse.statusCode})`) } const setCookie = loginResponse.headers['set-cookie'] || [] const cookieStr = Array.isArray(setCookie) ? setCookie.join('; ') : setCookie || '' if (loginResponse.body.includes("window.location.href='https://login.kongfz.cn/Pc/Session/rsync")) { if (!cookieStr) throw new Error('登录成功但未获取到Cookie') if (!cookieStr.includes('PHPSESSID=')) throw new Error('登录失败: 未找到 PHPSESSID') const token = cookieStr.split('PHPSESSID=')[1].split(';')[0] const userInfoResponse = await requestWithRedirect({ hostname: 'user.kongfz.com', port: 443, path: '/User/Index/getUserInfo/', method: 'GET', headers: { 'Cookie': `PHPSESSID=${token}`, 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'application/json, text/plain, */*', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8' }, timeout: 15000 }, null) let displayName = username try { const userJson = JSON.parse(userInfoResponse.body) if (userJson.status && userJson.data) displayName = userJson.data.nickname || username } catch { } res.setHeader('Content-Type', 'application/json') res.end(JSON.stringify({ code: 200, message: '登录成功', data: { token, username: displayName, nickname: displayName } })) } else { try { const errJson = JSON.parse(loginResponse.body) if (errJson.errCode === 1001 || errJson.errCode === 1005) throw new Error('账号或密码错误') throw new Error(errJson.errInfo || '登录失败,未知错误!') } catch (parseErr) { if (parseErr instanceof SyntaxError) throw new Error('登录失败,未知错误!') throw parseErr } } } catch (error) { res.setHeader('Content-Type', 'application/json') res.end(JSON.stringify({ code: 500, message: error.message })) } }) }) } }, // 本地程序启动器(无需协议注册/脚本/额外服务,Vite 服务器直接 spawn exe) { name: 'local-launcher', configureServer(server) { server.middlewares.use('/api/local/launch-exe', (req, res) => { res.setHeader('Access-Control-Allow-Origin', '*') res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS') res.setHeader('Access-Control-Allow-Headers', 'Content-Type') if (req.method === 'OPTIONS') { res.statusCode = 204 return res.end() } if (req.method !== 'POST') { res.setHeader('Content-Type', 'application/json') return res.end(JSON.stringify({ code: 405, msg: '仅支持 POST' })) } let body = '' req.on('data', chunk => { body += chunk.toString() }) req.on('end', () => { try { const { exe_path } = JSON.parse(body) if (!exe_path) { res.setHeader('Content-Type', 'application/json') return res.end(JSON.stringify({ code: 400, msg: '缺少 exe_path' })) } const fs = require('fs') if (!fs.existsSync(exe_path)) { res.setHeader('Content-Type', 'application/json') return res.end(JSON.stringify({ code: 404, msg: `文件不存在: ${exe_path}` })) } const exeDir = path.dirname(exe_path) const proc = spawn(exe_path, [], { cwd: exeDir, detached: true, stdio: 'ignore', windowsHide: false }) proc.unref() res.setHeader('Content-Type', 'application/json') res.end(JSON.stringify({ code: 0, msg: '已启动', pid: proc.pid })) } catch (err) { res.setHeader('Content-Type', 'application/json') res.end(JSON.stringify({ code: 500, msg: err.message })) } }) }) } } ], resolve: { alias: { '@': path.resolve(__dirname, './src') } }, define: { __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false' }, server: { port: 5174, host: '0.0.0.0', historyApiFallback: true, proxy: { '/api': { // target: 'http://127.0.0.1:9090', // target: 'http://192.168.101.213:9090', target: 'https://psi.api.buzhiyushu.cn', // target: 'https://psi.api.buzhiyushu.cn', changeOrigin: true }, '/api/print': { // target: 'http://192.168.101.127:8075', target: 'https://print.buzhiyushu.cn', changeOrigin: true } } } })