mirror of
https://github.com/patdelphi/suanming.git
synced 2026-02-27 21:23:12 +08:00
fix: optimize mobile display for qimen nine-grid layout
- Improve responsive design for mobile devices - Adjust grid container max-width for different screen sizes - Optimize font sizes for better readability on small screens - Reduce gaps and padding for mobile optimization - Ensure all nine grid cells display completely on mobile
This commit is contained in:
@@ -83,7 +83,7 @@ app.use(cors({
|
|||||||
const allowedOrigins = [
|
const allowedOrigins = [
|
||||||
'http://localhost:5173',
|
'http://localhost:5173',
|
||||||
'http://localhost:4173',
|
'http://localhost:4173',
|
||||||
process.env.CORS_ORIGIN
|
...(process.env.CORS_ORIGIN ? process.env.CORS_ORIGIN.split(',') : [])
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
|
|
||||||
// 允许所有.koyeb.app域名
|
// 允许所有.koyeb.app域名
|
||||||
|
|||||||
232
server/index.cjs.bak
Normal file
232
server/index.cjs.bak
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const cors = require('cors');
|
||||||
|
const helmet = require('helmet');
|
||||||
|
const path = require('path');
|
||||||
|
const { dbManager } = require('./database/index.cjs');
|
||||||
|
|
||||||
|
// 导入路由
|
||||||
|
const authRoutes = require('./routes/auth.cjs');
|
||||||
|
const analysisRoutes = require('./routes/analysis.cjs');
|
||||||
|
const historyRoutes = require('./routes/history.cjs');
|
||||||
|
const profileRoutes = require('./routes/profile.cjs');
|
||||||
|
const downloadRoutes = require('./routes/download.cjs');
|
||||||
|
const aiInterpretationRoutes = require('./routes/aiInterpretation.cjs');
|
||||||
|
const qimenRoutes = require('./routes/qimen.cjs');
|
||||||
|
|
||||||
|
// 导入中间件
|
||||||
|
const { errorHandler } = require('./middleware/errorHandler.cjs');
|
||||||
|
const { requestLogger } = require('./middleware/logger.cjs');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const PORT = process.env.PORT || 3001;
|
||||||
|
|
||||||
|
// 初始化数据库
|
||||||
|
try {
|
||||||
|
dbManager.init();
|
||||||
|
console.log('数据库连接成功');
|
||||||
|
|
||||||
|
// 在生产环境中,确保管理员用户存在
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
const db = dbManager.getDatabase();
|
||||||
|
const adminExists = db.prepare('SELECT id FROM users WHERE email = ?').get('admin@localhost');
|
||||||
|
|
||||||
|
if (!adminExists) {
|
||||||
|
const bcrypt = require('bcryptjs');
|
||||||
|
const adminPassword = bcrypt.hashSync('admin123', 12);
|
||||||
|
|
||||||
|
// 创建管理员用户
|
||||||
|
const insertAdmin = db.prepare(
|
||||||
|
'INSERT INTO users (email, password_hash) VALUES (?, ?)'
|
||||||
|
);
|
||||||
|
const adminResult = insertAdmin.run('admin@localhost', adminPassword);
|
||||||
|
|
||||||
|
// 创建管理员档案
|
||||||
|
const insertAdminProfile = db.prepare(
|
||||||
|
'INSERT INTO user_profiles (user_id, full_name, username) VALUES (?, ?, ?)'
|
||||||
|
);
|
||||||
|
insertAdminProfile.run(adminResult.lastInsertRowid, '系统管理员', 'admin');
|
||||||
|
|
||||||
|
console.log('✅ 管理员用户创建成功');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('数据库连接失败:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 安全中间件
|
||||||
|
app.use(helmet({
|
||||||
|
contentSecurityPolicy: {
|
||||||
|
directives: {
|
||||||
|
defaultSrc: ["'self'"],
|
||||||
|
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||||
|
scriptSrc: ["'self'"],
|
||||||
|
imgSrc: ["'self'", "data:", "https:"],
|
||||||
|
connectSrc: [
|
||||||
|
"'self'",
|
||||||
|
"https://api.openai.com",
|
||||||
|
"https://open.bigmodel.cn",
|
||||||
|
"https://*.openai.azure.com",
|
||||||
|
"https://api.anthropic.com",
|
||||||
|
"https://generativelanguage.googleapis.com"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
crossOriginEmbedderPolicy: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
// CORS配置
|
||||||
|
app.use(cors({
|
||||||
|
origin: process.env.NODE_ENV === 'production'
|
||||||
|
? (origin, callback) => {
|
||||||
|
// 生产环境的严格检查
|
||||||
|
const allowedOrigins = [
|
||||||
|
'http://localhost:5173',
|
||||||
|
'http://localhost:4173',
|
||||||
|
process.env.CORS_ORIGIN
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
// 允许所有.koyeb.app域名
|
||||||
|
if (!origin || origin.endsWith('.koyeb.app') || allowedOrigins.includes(origin)) {
|
||||||
|
callback(null, true);
|
||||||
|
} else {
|
||||||
|
callback(new Error('Not allowed by CORS'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: true, // 开发环境允许所有域名
|
||||||
|
credentials: true,
|
||||||
|
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||||
|
allowedHeaders: ['Content-Type', 'Authorization']
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 基础中间件
|
||||||
|
app.use(express.json({ limit: '10mb' }));
|
||||||
|
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
||||||
|
app.use(requestLogger);
|
||||||
|
|
||||||
|
// 健康检查端点
|
||||||
|
app.get('/health', (req, res) => {
|
||||||
|
res.json({
|
||||||
|
status: 'ok',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
database: 'connected'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// API健康检查端点(用于Koyeb监控)
|
||||||
|
app.get('/api/health', (req, res) => {
|
||||||
|
res.status(200).json({
|
||||||
|
status: 'healthy',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
uptime: process.uptime(),
|
||||||
|
database: 'connected'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// API路由
|
||||||
|
app.use('/api/auth', authRoutes);
|
||||||
|
app.use('/api/analysis', analysisRoutes);
|
||||||
|
app.use('/api/history', historyRoutes);
|
||||||
|
app.use('/api/profile', profileRoutes);
|
||||||
|
app.use('/api/download', downloadRoutes);
|
||||||
|
app.use('/api/ai-interpretation', aiInterpretationRoutes);
|
||||||
|
app.use('/api/qimen', qimenRoutes);
|
||||||
|
|
||||||
|
// 静态文件服务 (用于生产环境)
|
||||||
|
// 强制在 Koyeb 部署时启用静态文件服务
|
||||||
|
const isProduction = process.env.NODE_ENV === 'production' || process.env.PORT === '8000';
|
||||||
|
console.log('当前环境:', process.env.NODE_ENV);
|
||||||
|
console.log('端口:', process.env.PORT);
|
||||||
|
console.log('是否为生产环境:', isProduction);
|
||||||
|
|
||||||
|
if (isProduction) {
|
||||||
|
const distPath = path.join(__dirname, '../dist');
|
||||||
|
const indexPath = path.join(distPath, 'index.html');
|
||||||
|
|
||||||
|
console.log('静态文件目录:', distPath);
|
||||||
|
console.log('index.html路径:', indexPath);
|
||||||
|
|
||||||
|
// 检查文件是否存在
|
||||||
|
const fs = require('fs');
|
||||||
|
console.log('dist目录存在:', fs.existsSync(distPath));
|
||||||
|
console.log('index.html存在:', fs.existsSync(indexPath));
|
||||||
|
|
||||||
|
if (fs.existsSync(distPath)) {
|
||||||
|
console.log('dist目录内容:', fs.readdirSync(distPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
app.use(express.static(distPath));
|
||||||
|
|
||||||
|
// SPA路由处理 - 只处理非API请求
|
||||||
|
app.get('*', (req, res, next) => {
|
||||||
|
if (req.path.startsWith('/api')) {
|
||||||
|
return next(); // 让后续的404处理器处理API请求
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs.existsSync(indexPath)) {
|
||||||
|
res.sendFile(indexPath);
|
||||||
|
} else {
|
||||||
|
console.error('index.html文件不存在:', indexPath);
|
||||||
|
res.status(404).json({
|
||||||
|
error: {
|
||||||
|
code: 'STATIC_FILE_NOT_FOUND',
|
||||||
|
message: '静态文件不存在,请检查构建过程'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 404处理
|
||||||
|
app.use('*', (req, res) => {
|
||||||
|
res.status(404).json({
|
||||||
|
error: {
|
||||||
|
code: 'NOT_FOUND',
|
||||||
|
message: '请求的资源不存在'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 错误处理中间件
|
||||||
|
app.use(errorHandler);
|
||||||
|
|
||||||
|
// 启动服务器
|
||||||
|
const server = app.listen(PORT, '0.0.0.0', () => {
|
||||||
|
console.log(`🚀 服务器运行在 http://0.0.0.0:${PORT}`);
|
||||||
|
console.log(`📊 数据库文件: ${path.resolve('./numerology.db')}`);
|
||||||
|
console.log(`🌍 环境: ${process.env.NODE_ENV || 'development'}`);
|
||||||
|
console.log(`🏥 健康检查: http://0.0.0.0:${PORT}/api/health`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 优雅关闭
|
||||||
|
process.on('SIGTERM', () => {
|
||||||
|
console.log('收到SIGTERM信号,开始优雅关闭...');
|
||||||
|
server.close(() => {
|
||||||
|
console.log('HTTP服务器已关闭');
|
||||||
|
dbManager.close();
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('SIGINT', () => {
|
||||||
|
console.log('收到SIGINT信号,开始优雅关闭...');
|
||||||
|
server.close(() => {
|
||||||
|
console.log('HTTP服务器已关闭');
|
||||||
|
dbManager.close();
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 未捕获异常处理
|
||||||
|
process.on('uncaughtException', (error) => {
|
||||||
|
console.error('未捕获的异常:', error);
|
||||||
|
dbManager.close();
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (reason, promise) => {
|
||||||
|
console.error('未处理的Promise拒绝:', reason);
|
||||||
|
console.error('Promise:', promise);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = app;
|
||||||
@@ -362,8 +362,8 @@ const CompleteQimenAnalysis: React.FC<QimenAnalysisProps> = ({ analysis, classNa
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 专业九宫格奇门盘 */}
|
{/* 专业九宫格奇门盘 */}
|
||||||
<div className="w-full max-w-sm mx-auto p-4">
|
<div className="w-full max-w-xs sm:max-w-sm md:max-w-md mx-auto p-2 sm:p-4">
|
||||||
<div className="grid grid-cols-3 gap-3 w-full">
|
<div className="grid grid-cols-3 gap-1 sm:gap-2 md:gap-3 w-full">
|
||||||
{gridPositions.map((row, rowIndex) =>
|
{gridPositions.map((row, rowIndex) =>
|
||||||
row.map((palaceIndex, colIndex) => {
|
row.map((palaceIndex, colIndex) => {
|
||||||
const palace = qimenPan.dipan[palaceIndex];
|
const palace = qimenPan.dipan[palaceIndex];
|
||||||
@@ -374,21 +374,22 @@ const CompleteQimenAnalysis: React.FC<QimenAnalysisProps> = ({ analysis, classNa
|
|||||||
<div
|
<div
|
||||||
key={`${rowIndex}-${colIndex}`}
|
key={`${rowIndex}-${colIndex}`}
|
||||||
className={cn(
|
className={cn(
|
||||||
'aspect-square border-2 relative flex flex-col justify-between p-1',
|
'aspect-square border-2 relative flex flex-col justify-between',
|
||||||
'min-h-[80px] min-w-[80px]',
|
'min-h-[70px] min-w-[70px] sm:min-h-[80px] sm:min-w-[80px] md:min-h-[90px] md:min-w-[90px]',
|
||||||
|
'p-0.5 sm:p-1 md:p-2',
|
||||||
colorClass,
|
colorClass,
|
||||||
isCenter && 'border-4 border-yellow-500 bg-yellow-200'
|
isCenter && 'border-4 border-yellow-500 bg-yellow-200'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/* 顶部行:宫位信息 */}
|
{/* 顶部行:宫位信息 */}
|
||||||
<div className="flex justify-between items-start text-xs leading-none">
|
<div className="flex justify-between items-start text-[10px] sm:text-xs leading-none">
|
||||||
<div className="text-red-800 font-bold">
|
<div className="text-red-800 font-bold">
|
||||||
<div>{palaceNames[palaceIndex]}</div>
|
<div className="text-[10px] sm:text-xs">{palaceNames[palaceIndex]}</div>
|
||||||
<div className="text-gray-600">{palaceNumbers[palaceIndex]}</div>
|
<div className="text-gray-600 text-[8px] sm:text-[10px]">{palaceNumbers[palaceIndex]}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="text-blue-700 font-medium">{palaceElements[palaceIndex]}</div>
|
<div className="text-blue-700 font-medium text-[10px] sm:text-xs">{palaceElements[palaceIndex]}</div>
|
||||||
<div className="text-gray-700">{palaceZhi[palaceIndex]}</div>
|
<div className="text-gray-700 text-[8px] sm:text-[10px]">{palaceZhi[palaceIndex]}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -396,21 +397,21 @@ const CompleteQimenAnalysis: React.FC<QimenAnalysisProps> = ({ analysis, classNa
|
|||||||
<div className="flex-1 flex flex-col justify-center items-center">
|
<div className="flex-1 flex flex-col justify-center items-center">
|
||||||
{/* 天干 - 最大最显眼 */}
|
{/* 天干 - 最大最显眼 */}
|
||||||
{palace?.gan && (
|
{palace?.gan && (
|
||||||
<div className="text-black font-bold text-lg sm:text-xl md:text-2xl">
|
<div className="text-black font-bold text-sm sm:text-lg md:text-xl">
|
||||||
{palace.gan}
|
{palace.gan}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 九星 */}
|
{/* 九星 */}
|
||||||
{palace?.star && (
|
{palace?.star && (
|
||||||
<div className="text-blue-700 font-bold text-xs">
|
<div className="text-blue-700 font-bold text-[8px] sm:text-xs">
|
||||||
{palace.star}
|
{palace.star}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 底部行:门神信息 */}
|
{/* 底部行:门神信息 */}
|
||||||
<div className="flex justify-between items-end text-xs font-bold">
|
<div className="flex justify-between items-end text-[8px] sm:text-xs font-bold">
|
||||||
{/* 左下角:八门 */}
|
{/* 左下角:八门 */}
|
||||||
<div>
|
<div>
|
||||||
{palace?.door && (
|
{palace?.door && (
|
||||||
|
|||||||
Reference in New Issue
Block a user