fix: 修改邮箱同步到 assets/issue + 自适应布局 + 部署后自动批量同步

- api/auth/me PUT + api/admin/users PATCH:写 LLDAP 后同步更新 assets/issue SQLite
- deploy-ai.sh:OA 部署后自动执行批量 email 同步(新服务器首次部署填充历史数据)
- scripts/sync-emails-to-sites.js:批量同步工具脚本
- 全站自适应:移除 maxWidth 硬限制,Form 用 auto-fit grid,表格 overflow-x auto
This commit is contained in:
aiyimickey 2026-05-18 17:33:41 +08:00
parent ab25541200
commit 14abdff875
6 changed files with 64 additions and 8 deletions

View File

@ -0,0 +1,37 @@
// 批量同步 LLDAP email → assets / issue 本地用户表
// 每次 OA 部署后自动执行,确保新服务器历史数据也能填充
const { exec } = require('child_process')
const { promisify } = require('util')
const e = promisify(exec)
const SITES = [
{ name: 'assets', db: process.env.ASSETS_DB_PATH || '/data/other-sites/assets/assets.db' },
{ name: 'issue', db: process.env.ISSUE_DB_PATH || '/data/other-sites/issue/issue.db' },
]
async function main() {
const r = await e(
`docker exec lldap sqlite3 /data/users.db "SELECT user_id, email FROM users WHERE email != '';"`
)
const lines = r.stdout.trim().split('\n').filter(Boolean)
let synced = 0
for (const line of lines) {
const [user, mail] = line.split('|')
const su = user.replace(/'/g, "''")
const sm = (mail || '').replace(/'/g, "''")
for (const site of SITES) {
try {
await e(
`sqlite3 "${site.db}" "UPDATE users SET email = '${sm}', updated_at = datetime('now', '+8 hours') WHERE username = '${su}';"`
)
} catch {}
}
synced++
console.log(` ${user}${mail}`)
}
console.log(`已同步 ${synced} 个用户邮箱`)
}
main().catch(err => { console.error(err.message); process.exit(1) })

View File

@ -9,10 +9,11 @@ interface LdapUser { username: string; email: string; displayName: string; creat
const s = {
wrap: { minHeight: '100vh', background: 'var(--bg)' } as React.CSSProperties,
main: { padding: '32px 40px', maxWidth: 1440 } as React.CSSProperties,
tabBar: { display: 'flex', gap: 0, borderBottom: '1px solid var(--border)', marginBottom: 32 } as React.CSSProperties,
main: { padding: 'clamp(16px, 3vw, 48px)' } as React.CSSProperties,
tabBar: { display: 'flex', gap: 0, borderBottom: '1px solid var(--border)', marginBottom: 32, overflowX: 'auto' as any, whiteSpace: 'nowrap' as any } as React.CSSProperties,
content: {} as React.CSSProperties,
grid2: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20 } as React.CSSProperties,
grid2: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: 20 } as React.CSSProperties,
tableWrap: { overflowX: 'auto' as any } as React.CSSProperties,
field: { marginBottom: 20 } as React.CSSProperties,
label: { display: 'block', fontSize: 13, fontWeight: 500, color: 'var(--text-secondary)', marginBottom: 6 } as React.CSSProperties,
input: { width: '100%', height: 44, padding: '0 14px', border: '1px solid var(--border)', borderRadius: 8, background: 'var(--bg-card)', color: 'var(--text)', fontSize: 14, outline: 'none', boxSizing: 'border-box' as any },
@ -200,7 +201,7 @@ export default function AdminUsersPage() {
{users.length === 0 ? (
<p style={{ fontSize: 13, color: 'var(--text-muted)', textAlign: 'center', padding: 40 }}>...</p>
) : (
<table style={s.table}>
<div style={s.tableWrap}><table style={s.table}>
<thead>
<tr>
<th style={s.th}></th>
@ -238,7 +239,7 @@ export default function AdminUsersPage() {
</tr>
))}
</tbody>
</table>
</table></div>
)}
</div>
)}

View File

@ -95,7 +95,7 @@ export default function RoleManager({ setResult }: Props) {
</button>
</div>
<table style={ss.table}>
<div style={{ overflowX: 'auto' }}><table style={ss.table}>
<thead>
<tr>
<th style={ss.th}></th><th style={ss.th}></th><th style={ss.th}></th><th style={ss.th}></th><th style={ss.th}></th>
@ -129,7 +129,7 @@ export default function RoleManager({ setResult }: Props) {
</tr>
))}
</tbody>
</table>
</table></div>
</div>
)
}

View File

@ -99,6 +99,15 @@ export async function PATCH(request: Request) {
{ timeout: 5000 }
)
// 同步更新 assets / issue 本地用户表
const assetsDb = process.env.ASSETS_DB_PATH || '/Users/niuniu/programs/docker/assets-ai/data/assets.db'
const issueDb = process.env.ISSUE_DB_PATH || '/Users/niuniu/programs/docker/issue-ai/data/issue.db'
for (const dbPath of [assetsDb, issueDb]) {
try {
await execAsync(`sqlite3 "${dbPath}" "UPDATE users SET email = '${safeEmail}', updated_at = datetime('now', '+8 hours') WHERE username = '${safeUser}';"`, { timeout: 3000 })
} catch {}
}
return NextResponse.json({ success: true, username, email: email || '' })
} catch (e) {
return NextResponse.json({ error: '修改失败' }, { status: 500 })

View File

@ -65,6 +65,15 @@ export async function PUT(request: Request) {
{ timeout: 5000 }
)
// 同步更新 assets / issue 本地用户表
const assetsDb = process.env.ASSETS_DB_PATH || '/Users/niuniu/programs/docker/assets-ai/data/assets.db'
const issueDb = process.env.ISSUE_DB_PATH || '/Users/niuniu/programs/docker/issue-ai/data/issue.db'
for (const dbPath of [assetsDb, issueDb]) {
try {
await execAsync(`sqlite3 "${dbPath}" "UPDATE users SET email = '${safeEmail}', updated_at = datetime('now', '+8 hours') WHERE username = '${safeUser}';"`, { timeout: 3000 })
} catch {}
}
return NextResponse.json({ success: true, email: email || '' })
} catch (e) {
const msg = e instanceof Error ? e.message : '修改失败'

View File

@ -12,7 +12,7 @@ export default function HeaderUI({ displayName, isAdmin, backLabel }: Props) {
position: 'sticky', top: 0, zIndex: 50, height: 56,
background: 'var(--bg-card)', borderBottom: '1px solid var(--border)',
}}>
<div style={{ height: '100%', padding: '0 40px', display: 'flex', alignItems: 'center', justifyContent: 'space-between', maxWidth: 1440, margin: '0 auto' }}>
<div className="header-inner" style={{ height: '100%', padding: '0 clamp(16px, 3vw, 48px)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
{/* 左侧 */}
{backLabel ? (
<a href="/" style={{ display: 'flex', alignItems: 'center', gap: 8, textDecoration: 'none', color: 'var(--text-secondary)', fontSize: 15 }}>