feat: 一键部署脚本 + Dashboard按钮移至标题下方 + 修复演示模式退出

This commit is contained in:
selfrelease
2026-08-01 23:35:49 +08:00
parent df89bcab7f
commit cdf100206a
3 changed files with 156 additions and 3 deletions
Executable
+145
View File
@@ -0,0 +1,145 @@
#!/usr/bin/env bash
set -euo pipefail
# ═══════════════════════════════════════════════════════════
# 全国记者站管理系统 — 一键部署脚本
# 用法: ./deploy.sh
# 功能: 拉取最新代码 → 安装依赖 → 数据库迁移 → 构建 → 重启服务
# ═══════════════════════════════════════════════════════════
# ── 配置 ──────────────────────────────────────────────────
DEPLOY_DIR="/var/www/rsms"
GIT_REMOTE="origin"
BRANCH="main"
API_PORT="${API_PORT:-8787}"
PM2_NAME="rsms-server"
NGINX_SITE="r.all8ai.top"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log() { echo -e "${GREEN}[deploy]${NC} $1"; }
warn() { echo -e "${YELLOW}[warn]${NC} $1"; }
err() { echo -e "${RED}[error]${NC} $1"; exit 1; }
# ── 前置检查 ──────────────────────────────────────────────
check_deps() {
log "检查依赖..."
command -v git >/dev/null || err "git 未安装"
command -v node >/dev/null || err "node 未安装"
command -v npm >/dev/null || err "npm 未安装"
command -v nginx >/dev/null || err "nginx 未安装"
command -v pm2 >/dev/null || err "pm2 未安装 (运行: npm install -g pm2)"
NODE_MAJOR=$(node -v | sed 's/v\([0-9]*\).*/\1/')
[ "$NODE_MAJOR" -ge 22 ] || err "Node.js 版本需 >= 22 (当前: $(node -v))"
log "依赖检查通过: node $(node -v), npm $(npm -v)"
}
# ── 拉取最新代码 ──────────────────────────────────────────
pull_code() {
log "拉取最新代码..."
cd "$DEPLOY_DIR"
git fetch "$GIT_REMOTE" "$BRANCH"
git reset --hard "$GIT_REMOTE/$BRANCH"
log "代码已更新到 $(git log --oneline -1)"
}
# ── 安装依赖 ──────────────────────────────────────────────
install_deps() {
log "安装依赖..."
npm install --production=false
log "依赖安装完成"
}
# ── 数据库迁移 ────────────────────────────────────────────
run_migrations() {
log "执行数据库迁移..."
npm run db:migrate
log "数据库迁移完成"
}
# ── 构建前端 ──────────────────────────────────────────────
build_frontend() {
log "构建前端..."
npm run build
log "前端构建完成: $(ls dist/assets/*.js | wc -l) 个 JS 文件"
}
# ── 重启后端服务 ──────────────────────────────────────────
restart_server() {
log "重启后端服务 (pm2: $PM2_NAME)..."
pm2 delete "$PM2_NAME" 2>/dev/null || true
pm2 start npm --name "$PM2_NAME" -- run server:start
pm2 save
sleep 2
# 健康检查
local health
health=$(curl -s "http://127.0.0.1:${API_PORT}/api/health" 2>/dev/null || echo "FAIL")
if echo "$health" | grep -q '"status":"ok"'; then
log "后端服务健康检查通过"
else
warn "后端健康检查异常: $health"
warn "查看日志: pm2 logs $PM2_NAME --lines 20"
fi
}
# ── 重载 nginx ────────────────────────────────────────────
reload_nginx() {
log "检查 nginx 配置..."
nginx -t 2>&1 || err "nginx 配置检查失败"
nginx -s reload
log "nginx 已重载"
}
# ── 验证站点 ──────────────────────────────────────────────
verify_site() {
log "验证站点..."
local http_code
http_code=$(curl -s -o /dev/null -w "%{http_code}" "http://${NGINX_SITE}/" 2>/dev/null || echo "000")
if [ "$http_code" = "200" ]; then
log "站点验证通过: http://${NGINX_SITE} → 200"
else
warn "站点返回: $http_code (可能需要检查 DNS 或 nginx 配置)"
fi
# HTTPS 检查(如果证书存在)
if [ -d "/etc/letsencrypt/live/${NGINX_SITE}" ]; then
local https_code
https_code=$(curl -s -o /dev/null -w "%{http_code}" "https://${NGINX_SITE}/" 2>/dev/null || echo "000")
log "HTTPS 验证: https://${NGINX_SITE}$https_code"
fi
}
# ── 主流程 ────────────────────────────────────────────────
main() {
echo ""
echo "══════════════════════════════════════════════════"
echo " 全国记者站管理系统 — 一键部署"
echo "══════════════════════════════════════════════════"
echo ""
check_deps
pull_code
install_deps
run_migrations
build_frontend
restart_server
reload_nginx
verify_site
echo ""
log "部署完成!"
echo " 站点: https://${NGINX_SITE}"
echo " API: https://${NGINX_SITE}/api/health"
echo " 日志: pm2 logs ${PM2_NAME}"
echo " 状态: pm2 list"
echo ""
}
main "$@"
+6
View File
@@ -19,6 +19,12 @@ export function TopBar({ pageTitle, onMenuToggle }: TopBarProps) {
const handleLogout = () => {
if (isAuthenticated) {
logout()
} else {
// 演示模式:清除 auth_role 退出到登录页
localStorage.removeItem('auth_role')
localStorage.removeItem('auth_name')
localStorage.removeItem('auth_station')
window.location.reload()
}
}
+5 -3
View File
@@ -139,13 +139,15 @@ export function Dashboard({ records, onNavigate, onCreate, onSelect }: Dashboard
<span>{role === 'reporter' ? '这是你的个人工作概况。' : role === 'station' ? '这是本站今日运行概况。' : '这里是全国记者站今日运行概况。'}</span>
</div>
</div>
{role !== 'headquarters' && (
</div>
{role !== 'headquarters' && (
<div style={{ marginBottom: 16 }}>
<button className="primary-button" onClick={onCreate}>
<Plus size={18} />
</button>
)}
</div>
</div>
)}
<div className="metric-grid">
<div onClick={() => setDrillDown('pending')} style={{ cursor: 'pointer' }}>