v1.0.2
parent
ff2eeee5a3
commit
df96e75e7f
|
|
@ -0,0 +1,8 @@
|
||||||
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
README.md
|
||||||
|
Dockerfile
|
||||||
|
.dockerignore
|
||||||
|
docker-compose.yml
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
# 生产环境服务器 - PM2版本
|
||||||
|
FROM swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/node:18-alpine
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 复制构建产物
|
||||||
|
COPY dist ./dist
|
||||||
|
|
||||||
|
# 复制服务器文件和PM2配置
|
||||||
|
COPY server.js .
|
||||||
|
COPY ecosystem.config.json .
|
||||||
|
|
||||||
|
# 复制生产环境package.json
|
||||||
|
COPY package.prod.json package.json
|
||||||
|
|
||||||
|
# 安装生产依赖和PM2
|
||||||
|
RUN npm install --production && \
|
||||||
|
npm install -g pm2
|
||||||
|
|
||||||
|
# 创建日志目录
|
||||||
|
RUN mkdir -p logs
|
||||||
|
|
||||||
|
# 暴露端口
|
||||||
|
EXPOSE 3001
|
||||||
|
|
||||||
|
# 健康检查
|
||||||
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
||||||
|
CMD wget --quiet --tries=1 --spider http://localhost:3001/health || exit 1
|
||||||
|
|
||||||
|
# 启动PM2
|
||||||
|
CMD ["pm2-runtime", "start", "ecosystem.config.json"]
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "🚀 开始部署iMeeting前端服务(PM2模式)..."
|
||||||
|
|
||||||
|
# 检查node_modules
|
||||||
|
if [ ! -d "node_modules" ]; then
|
||||||
|
echo "📦 安装依赖..."
|
||||||
|
yarn install
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 构建前端
|
||||||
|
echo "🔨 构建前端应用..."
|
||||||
|
yarn build
|
||||||
|
|
||||||
|
# 检查构建是否成功
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "❌ 构建失败!请检查代码错误"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "dist" ]; then
|
||||||
|
echo "❌ 构建失败!dist目录未生成"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ 前端构建完成,开始Docker部署..."
|
||||||
|
|
||||||
|
# 创建日志目录
|
||||||
|
mkdir -p logs
|
||||||
|
|
||||||
|
# 停止并删除现有容器
|
||||||
|
echo "📦 停止现有容器..."
|
||||||
|
docker-compose -f docker-compose.prod.yml down
|
||||||
|
|
||||||
|
# 构建新镜像
|
||||||
|
echo "🔨 构建Docker镜像..."
|
||||||
|
docker-compose -f docker-compose.prod.yml build --no-cache
|
||||||
|
|
||||||
|
# 启动服务
|
||||||
|
echo "▶️ 启动PM2服务..."
|
||||||
|
docker-compose -f docker-compose.prod.yml up -d
|
||||||
|
|
||||||
|
# 检查服务状态
|
||||||
|
echo "🔍 检查服务状态..."
|
||||||
|
sleep 15
|
||||||
|
docker-compose -f docker-compose.prod.yml ps
|
||||||
|
|
||||||
|
# 检查PM2进程状态
|
||||||
|
echo "🔄 检查PM2进程状态..."
|
||||||
|
docker exec imeeting-frontend pm2 list
|
||||||
|
|
||||||
|
# 检查健康状态
|
||||||
|
echo "🏥 检查健康状态..."
|
||||||
|
curl -f http://localhost:3001/health && echo "✅ 前端服务健康检查通过" || echo "❌ 前端服务健康检查失败"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "🎉 部署完成!"
|
||||||
|
echo "📱 前端服务访问地址: http://localhost:3001"
|
||||||
|
echo "📊 查看日志: docker-compose -f docker-compose.prod.yml logs -f"
|
||||||
|
echo "📈 查看PM2状态: docker exec imeeting-frontend pm2 monit"
|
||||||
|
echo "📋 查看PM2进程: docker exec imeeting-frontend pm2 list"
|
||||||
|
echo "🛑 停止服务: docker-compose -f docker-compose.prod.yml down"
|
||||||
|
echo ""
|
||||||
|
echo "💡 提示:PM2模式特性:"
|
||||||
|
echo " ✅ 集群模式(2个实例)"
|
||||||
|
echo " ✅ 自动重启和故障恢复"
|
||||||
|
echo " ✅ 内存限制保护(1GB)"
|
||||||
|
echo " ✅ 详细日志管理"
|
||||||
|
echo " ✅ 进程监控和健康检查"
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
services:
|
||||||
|
imeeting-frontend:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: imeeting-frontend
|
||||||
|
ports:
|
||||||
|
- "3001:3001"
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
- PORT=3001
|
||||||
|
- BACKEND_URL=http://host.docker.internal:8001
|
||||||
|
volumes:
|
||||||
|
# 挂载日志目录到宿主机
|
||||||
|
- ./logs:/app/logs
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3001/health", "||", "exit", "1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"name": "imeeting-frontend",
|
||||||
|
"script": "server.js",
|
||||||
|
"instances": 2,
|
||||||
|
"exec_mode": "cluster",
|
||||||
|
"env": {
|
||||||
|
"NODE_ENV": "production",
|
||||||
|
"PORT": 3001
|
||||||
|
},
|
||||||
|
"error_file": "./logs/err.log",
|
||||||
|
"out_file": "./logs/out.log",
|
||||||
|
"log_file": "./logs/combined.log",
|
||||||
|
"time": true,
|
||||||
|
"log_date_format": "YYYY-MM-DD HH:mm:ss Z",
|
||||||
|
"max_memory_restart": "1G",
|
||||||
|
"restart_delay": 4000,
|
||||||
|
"autorestart": true,
|
||||||
|
"watch": false,
|
||||||
|
"max_restarts": 10,
|
||||||
|
"min_uptime": "10s"
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>会议助手</title>
|
<title>慧会议</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"name": "imeeting-frontend-server",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "commonjs",
|
||||||
|
"main": "server.js",
|
||||||
|
"dependencies": {
|
||||||
|
"express": "4.18.2",
|
||||||
|
"http-proxy-middleware": "2.0.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
const express = require("express");
|
||||||
|
const { createProxyMiddleware } = require("http-proxy-middleware");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const PORT = process.env.PORT;
|
||||||
|
const BACKEND_URL = process.env.BACKEND_URL || "http://localhost:8000";
|
||||||
|
|
||||||
|
// 健康检查
|
||||||
|
app.get('/health', (req, res) => {
|
||||||
|
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||||||
|
});
|
||||||
|
|
||||||
|
// API代理 - 需要在body parser之前
|
||||||
|
app.use("/api", createProxyMiddleware({
|
||||||
|
target: BACKEND_URL,
|
||||||
|
changeOrigin: true,
|
||||||
|
timeout: 30000,
|
||||||
|
proxyTimeout: 30000
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 上传文件代理
|
||||||
|
app.use("/uploads", createProxyMiddleware({
|
||||||
|
target: BACKEND_URL,
|
||||||
|
changeOrigin: true,
|
||||||
|
timeout: 30000,
|
||||||
|
proxyTimeout: 30000
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 设置请求体大小限制 - 放在代理之后
|
||||||
|
app.use(express.json({ limit: '50mb' }));
|
||||||
|
app.use(express.urlencoded({ limit: '50mb', extended: true }));
|
||||||
|
|
||||||
|
// 静态文件服务
|
||||||
|
app.use(express.static(path.join(__dirname, "dist"), {
|
||||||
|
maxAge: '1d',
|
||||||
|
etag: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
// SPA路由处理 - 必须放在最后
|
||||||
|
app.get("*", (req, res) => {
|
||||||
|
res.sendFile(path.join(__dirname, "dist", "index.html"));
|
||||||
|
});
|
||||||
|
|
||||||
|
// 错误处理中间件
|
||||||
|
app.use((err, req, res, next) => {
|
||||||
|
console.error('Server error:', err);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(PORT, '0.0.0.0', () => {
|
||||||
|
console.log(`iMeeting Frontend Server running on port ${PORT}`);
|
||||||
|
console.log(`Backend proxy target: ${BACKEND_URL}`);
|
||||||
|
console.log(`Environment: ${process.env.NODE_ENV}`);
|
||||||
|
});
|
||||||
|
|
@ -160,9 +160,8 @@ const HomePage = ({ onLogin }) => {
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div className="demo-info">
|
<div className="demo-info">
|
||||||
<p>测试账号:</p>
|
<p>开通账号:</p>
|
||||||
<p>用户名: user1, 密码: hashed_password_1</p>
|
<p>请联系:18980500203</p>
|
||||||
<p>用户名: user2, 密码: hashed_password_2</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1612,7 +1612,6 @@
|
||||||
min-height: 80px;
|
min-height: 80px;
|
||||||
border: 1px solid #d1d5db;
|
border: 1px solid #d1d5db;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
padding: 12px;
|
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
|
|
|
||||||
|
|
@ -260,9 +260,17 @@ const MeetingDetails = ({ user }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatTime = (seconds) => {
|
const formatTime = (seconds) => {
|
||||||
const mins = Math.floor(seconds / 60);
|
const hours = Math.floor(seconds / 3600);
|
||||||
|
const mins = Math.floor((seconds % 3600) / 60);
|
||||||
const secs = Math.floor(seconds % 60);
|
const secs = Math.floor(seconds % 60);
|
||||||
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
|
||||||
|
if (hours > 0) {
|
||||||
|
// 超过60分钟显示小时:分钟:秒格式
|
||||||
|
return `${hours}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
||||||
|
} else {
|
||||||
|
// 60分钟内显示分钟:秒格式
|
||||||
|
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePlayPause = () => {
|
const handlePlayPause = () => {
|
||||||
|
|
@ -600,19 +608,14 @@ const MeetingDetails = ({ user }) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建一个临时的React容器用于渲染Markdown
|
// 先渲染Markdown内容
|
||||||
const tempDiv = document.createElement('div');
|
const tempDiv = document.createElement('div');
|
||||||
tempDiv.style.position = 'fixed';
|
tempDiv.style.display = 'none';
|
||||||
tempDiv.style.top = '-9999px';
|
document.body.appendChild(tempDiv);
|
||||||
tempDiv.style.width = '800px';
|
|
||||||
tempDiv.style.padding = '20px';
|
|
||||||
tempDiv.style.backgroundColor = 'white';
|
|
||||||
|
|
||||||
// 导入markdown-to-html转换所需的模块
|
|
||||||
const ReactMarkdown = (await import('react-markdown')).default;
|
const ReactMarkdown = (await import('react-markdown')).default;
|
||||||
const { createRoot } = await import('react-dom/client');
|
const { createRoot } = await import('react-dom/client');
|
||||||
|
|
||||||
document.body.appendChild(tempDiv);
|
|
||||||
const root = createRoot(tempDiv);
|
const root = createRoot(tempDiv);
|
||||||
|
|
||||||
// 渲染Markdown内容并获取HTML
|
// 渲染Markdown内容并获取HTML
|
||||||
|
|
@ -624,101 +627,151 @@ const MeetingDetails = ({ user }) => {
|
||||||
children: summaryContent
|
children: summaryContent
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
setTimeout(resolve, 100); // 等待渲染完成
|
setTimeout(resolve, 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderedHTML = tempDiv.innerHTML;
|
const renderedHTML = tempDiv.innerHTML;
|
||||||
|
|
||||||
// 创建一个隐藏的HTML容器用于生成PDF
|
// 创建会议信息
|
||||||
const printContainer = document.createElement('div');
|
|
||||||
printContainer.style.position = 'fixed';
|
|
||||||
printContainer.style.top = '-9999px';
|
|
||||||
printContainer.style.width = '210mm';
|
|
||||||
printContainer.style.padding = '20mm';
|
|
||||||
printContainer.style.backgroundColor = 'white';
|
|
||||||
printContainer.style.fontFamily = 'Arial, sans-serif';
|
|
||||||
printContainer.style.fontSize = '14px';
|
|
||||||
printContainer.style.lineHeight = '1.6';
|
|
||||||
printContainer.style.color = '#333';
|
|
||||||
|
|
||||||
// 创建PDF内容的HTML,使用渲染后的Markdown内容
|
|
||||||
const meetingTime = formatDateTime(meeting.meeting_time);
|
const meetingTime = formatDateTime(meeting.meeting_time);
|
||||||
const attendeesList = meeting.attendees.map(attendee =>
|
const attendeesList = meeting.attendees.map(attendee =>
|
||||||
typeof attendee === 'string' ? attendee : attendee.caption
|
typeof attendee === 'string' ? attendee : attendee.caption
|
||||||
).join('、');
|
).join('、');
|
||||||
|
|
||||||
printContainer.innerHTML = `
|
// 创建一个隐藏的iframe用于打印
|
||||||
<div>
|
const printFrame = document.createElement('iframe');
|
||||||
<h1 style="color: #2563eb; margin-bottom: 30px; font-size: 24px; border-bottom: 2px solid #e5e7eb; padding-bottom: 10px;">
|
printFrame.style.position = 'fixed';
|
||||||
${meeting.title || '会议总结'}
|
printFrame.style.width = '0';
|
||||||
</h1>
|
printFrame.style.height = '0';
|
||||||
|
printFrame.style.border = 'none';
|
||||||
|
printFrame.style.left = '-9999px';
|
||||||
|
document.body.appendChild(printFrame);
|
||||||
|
|
||||||
|
const printDocument = printFrame.contentWindow.document;
|
||||||
|
|
||||||
|
// 使用Blob和URL来确保编码正确
|
||||||
|
const htmlContent = `<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<title>${meeting.title || '会议总结'}</title>
|
||||||
|
<style>
|
||||||
|
@charset "UTF-8";
|
||||||
|
@page { size: A4; margin: 20mm; }
|
||||||
|
body {
|
||||||
|
font-family: "PingFang SC", "Microsoft YaHei", "Hiragino Sans GB", "Heiti SC", "WenQuanYi Micro Hei", sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: #2563eb;
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
border-bottom: 2px solid #e5e7eb;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
color: #374151;
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 25px 0 15px;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
color: #1e293b;
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 20px 0 10px;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 10px 0;
|
||||||
|
color: #475569;
|
||||||
|
}
|
||||||
|
ul, ol {
|
||||||
|
margin: 10px 0;
|
||||||
|
padding-left: 25px;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
margin: 5px 0;
|
||||||
|
color: #475569;
|
||||||
|
}
|
||||||
|
strong {
|
||||||
|
color: #1e293b;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.info-section {
|
||||||
|
background: #f9fafb;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.info-section h2 {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
.content-section {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
.footer-section {
|
||||||
|
margin-top: 50px;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 1px solid #e5e7eb;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
@media print {
|
||||||
|
body { padding: 0; }
|
||||||
|
h1 { page-break-before: avoid; }
|
||||||
|
h2 { page-break-before: avoid; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>${meeting.title || '会议总结'}</h1>
|
||||||
|
<div class="info-section">
|
||||||
|
<h2>会议信息</h2>
|
||||||
|
<p><strong>会议时间:</strong>${meetingTime}</p>
|
||||||
|
<p><strong>创建人:</strong>${meeting.creator_username}</p>
|
||||||
|
<p><strong>参会人数:</strong>${meeting.attendees.length}人</p>
|
||||||
|
<p><strong>参会人员:</strong>${attendeesList}</p>
|
||||||
|
</div>
|
||||||
|
<div class="content-section">
|
||||||
|
<h2>会议摘要</h2>
|
||||||
|
${renderedHTML}
|
||||||
|
</div>
|
||||||
|
<div class="footer-section">
|
||||||
|
<p>导出时间:${new Date().toLocaleString('zh-CN')}</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
|
||||||
|
// 使用Blob创建URL以确保正确的编码
|
||||||
|
const blob = new Blob([htmlContent], { type: 'text/html; charset=UTF-8' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
// 设置iframe的src为blob URL
|
||||||
|
printFrame.src = url;
|
||||||
|
|
||||||
|
// 等待iframe加载完成
|
||||||
|
printFrame.onload = () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
// 执行打印
|
||||||
|
printFrame.contentWindow.focus();
|
||||||
|
printFrame.contentWindow.print();
|
||||||
|
|
||||||
<div style="margin-bottom: 30px; background: #f9fafb; padding: 20px; border-radius: 8px;">
|
// 清理资源
|
||||||
<h2 style="color: #374151; font-size: 16px; margin-bottom: 15px;">会议信息</h2>
|
setTimeout(() => {
|
||||||
<p style="margin: 8px 0;"><strong>会议时间:</strong>${meetingTime}</p>
|
URL.revokeObjectURL(url);
|
||||||
<p style="margin: 8px 0;"><strong>创建人:</strong>${meeting.creator_username}</p>
|
root.unmount();
|
||||||
<p style="margin: 8px 0;"><strong>参会人数:</strong>${meeting.attendees.length}人</p>
|
document.body.removeChild(tempDiv);
|
||||||
<p style="margin: 8px 0;"><strong>参会人员:</strong>${attendeesList}</p>
|
document.body.removeChild(printFrame);
|
||||||
</div>
|
}, 2000);
|
||||||
|
}, 500);
|
||||||
<div style="margin-bottom: 30px;">
|
};
|
||||||
<h2 style="color: #374151; font-size: 16px; margin-bottom: 15px;">会议摘要</h2>
|
|
||||||
<div style="line-height: 1.8;">${renderedHTML}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin-top: 50px; padding-top: 20px; border-top: 1px solid #e5e7eb; font-size: 12px; color: #6b7280;">
|
|
||||||
<p>导出时间:${new Date().toLocaleString('zh-CN')}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
document.body.appendChild(printContainer);
|
|
||||||
|
|
||||||
// 使用浏览器的打印功能生成PDF
|
|
||||||
const originalContent = document.body.innerHTML;
|
|
||||||
const originalTitle = document.title;
|
|
||||||
|
|
||||||
// 临时替换页面内容
|
|
||||||
document.body.innerHTML = printContainer.innerHTML;
|
|
||||||
document.title = `${meeting.title || '会议总结'}_${new Date().toISOString().split('T')[0]}`;
|
|
||||||
|
|
||||||
// 添加打印样式
|
|
||||||
const printStyles = document.createElement('style');
|
|
||||||
printStyles.innerHTML = `
|
|
||||||
@media print {
|
|
||||||
body { margin: 0; padding: 20px; font-family: 'Microsoft YaHei', Arial, sans-serif; }
|
|
||||||
h1 { page-break-before: avoid; }
|
|
||||||
h2 { page-break-before: avoid; }
|
|
||||||
h3 { margin-top: 1.5rem; margin-bottom: 0.75rem; color: #1e293b; }
|
|
||||||
h4 { margin-top: 1rem; margin-bottom: 0.5rem; color: #1e293b; }
|
|
||||||
p { margin-bottom: 0.75rem; color: #475569; line-height: 1.6; }
|
|
||||||
ul, ol { margin: 0.75rem 0; padding-left: 1.5rem; }
|
|
||||||
li { margin-bottom: 0.25rem; color: #475569; }
|
|
||||||
strong { color: #1e293b; font-weight: 600; }
|
|
||||||
code { background: #f1f5f9; padding: 2px 4px; border-radius: 3px; color: #dc2626; }
|
|
||||||
.page-break { page-break-before: always; }
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
document.head.appendChild(printStyles);
|
|
||||||
|
|
||||||
// 打开打印对话框
|
|
||||||
window.print();
|
|
||||||
|
|
||||||
// 清理:恢复原始内容
|
|
||||||
setTimeout(() => {
|
|
||||||
document.body.innerHTML = originalContent;
|
|
||||||
document.title = originalTitle;
|
|
||||||
document.head.removeChild(printStyles);
|
|
||||||
document.body.removeChild(printContainer);
|
|
||||||
document.body.removeChild(tempDiv);
|
|
||||||
|
|
||||||
// 重新初始化React组件(这是一个简化的处理)
|
|
||||||
window.location.reload();
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('PDF导出失败:', error);
|
console.error('PDF导出失败:', error);
|
||||||
alert('PDF导出失败,请重试。建议使用浏览器的打印功能并选择"保存为PDF"。');
|
alert('PDF导出失败,请重试。');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
server: {
|
server: {
|
||||||
host: true, // Optional: Allows the server to be accessible externally
|
host: true, // Optional: Allows the server to be accessible externally
|
||||||
port: 5173, // Optional: Specify a port if needed
|
|
||||||
allowedHosts: ['imeeting.unisspace.com'], // Add the problematic hostname here
|
allowedHosts: ['imeeting.unisspace.com'], // Add the problematic hostname here
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue