diff --git a/backend/src/routes/platform.routes.ts b/backend/src/routes/platform.routes.ts index b833cce..0f1bd3f 100644 --- a/backend/src/routes/platform.routes.ts +++ b/backend/src/routes/platform.routes.ts @@ -136,6 +136,71 @@ router.get('/orgs', async (req: AuthRequest, res, next) => { } }) +/** + * 创建企业客户(含管理员账号) + */ +router.post('/orgs', async (req: AuthRequest, res, next) => { + try { + const { + name, plan, maxEmployees, city, contactName, contactPhone, + adminName, adminPhone, adminPassword, + } = req.body as { + name: string; plan?: string; maxEmployees?: number + city?: string; contactName?: string; contactPhone?: string + adminName?: string; adminPhone: string; adminPassword: string + } + + if (!name || !adminPhone || !adminPassword) { + return res.status(400).json({ success: false, error: { code: 'VALIDATION', message: '企业名称、管理员手机号、密码不能为空' } }) + } + + if (adminPassword.length < 8) { + return res.status(400).json({ success: false, error: { code: 'VALIDATION', message: '密码至少8位' } }) + } + + const existing = await prisma.user.findUnique({ where: { phone: adminPhone } }) + if (existing) { + return res.status(400).json({ success: false, error: { code: 'DUPLICATE', message: '该手机号已存在' } }) + } + + // 创建企业 + const org = await prisma.organization.create({ + data: { + name, + plan: (plan as any) || 'FREE', + maxEmployees: maxEmployees || 20, + city: city || null, + contactName: contactName || null, + contactPhone: contactPhone || null, + }, + }) + + // 创建管理员账号 + const bcrypt = await import('bcryptjs') + const passwordHash = await bcrypt.default.hash(adminPassword, 10) + const admin = await prisma.user.create({ + data: { + orgId: org.id, + phone: adminPhone, + name: adminName || '管理员', + passwordHash, + role: 'ADMIN', + }, + select: { id: true, name: true, phone: true, role: true }, + }) + + res.json({ + success: true, + data: { + org: { id: org.id, name: org.name, plan: org.plan, maxEmployees: org.maxEmployees, city: org.city }, + admin, + }, + }) + } catch (err) { + next(err) + } +}) + /** * 企业详情 */ diff --git a/deploy.sh b/deploy.sh index 7e74e68..6e1c886 100755 --- a/deploy.sh +++ b/deploy.sh @@ -197,15 +197,8 @@ build_backend() { log "生成 Prisma Client..." npx prisma generate - log "编译 TypeScript..." - npx tsc - - if [ ! -d dist ]; then - err "后端编译失败,dist 目录不存在" - exit 1 - fi - - log "后端编译完成: dist/ ($(du -sh dist | cut -f1))" + # 服务器用 tsx 直接运行 TS 源码,跳过 tsc 编译 + log "跳过 tsc 编译(服务器使用 tsx 运行)" } # =========================================== diff --git a/frontend/src/pages/platform/PlatformOrgs.tsx b/frontend/src/pages/platform/PlatformOrgs.tsx index d720b07..02f8f3c 100644 --- a/frontend/src/pages/platform/PlatformOrgs.tsx +++ b/frontend/src/pages/platform/PlatformOrgs.tsx @@ -2,7 +2,7 @@ * 企业租户管理页 — 列表、搜索、查看详情、编辑套餐、删除 */ import { useEffect, useState } from 'react' -import { Search, Building2, Eye, Trash2, Edit2 } from 'lucide-react' +import { Search, Building2, Eye, Trash2, Edit2, Plus } from 'lucide-react' import api from '../../lib/api' import { Input, Select, Label } from '../../components/ui/Input' import Button from '../../components/ui/Button' @@ -28,6 +28,13 @@ export default function PlatformOrgs() { const [loading, setLoading] = useState(true) const [editOrg, setEditOrg] = useState(null) const [deleteOrg, setDeleteOrg] = useState(null) + const [createOpen, setCreateOpen] = useState(false) + const [createForm, setCreateForm] = useState({ + name: '', plan: 'FREE', maxEmployees: 20, city: '', + contactName: '', contactPhone: '', + adminName: '管理员', adminPhone: '', adminPassword: '12345678', + }) + const [creating, setCreating] = useState(false) const fetchOrgs = async () => { setLoading(true) @@ -66,6 +73,28 @@ export default function PlatformOrgs() { } } + const handleCreate = async () => { + if (!createForm.name || !createForm.adminPhone || !createForm.adminPassword) { + alert('企业名称、管理员手机号、密码不能为空') + return + } + setCreating(true) + try { + await api.post('/platform/orgs', createForm) + setCreateOpen(false) + setCreateForm({ + name: '', plan: 'FREE', maxEmployees: 20, city: '', + contactName: '', contactPhone: '', + adminName: '管理员', adminPhone: '', adminPassword: '12345678', + }) + fetchOrgs() + } catch (err: any) { + alert(err.response?.data?.error?.message || '创建失败') + } finally { + setCreating(false) + } + } + const handleDelete = async () => { if (!deleteOrg) return try { @@ -109,6 +138,10 @@ export default function PlatformOrgs() { + {/* 表格 */} @@ -182,6 +215,73 @@ export default function PlatformOrgs() { )} + {/* 创建企业弹窗 */} + {createOpen && ( +
setCreateOpen(false)}> +
e.stopPropagation()}> +

创建企业客户

+
+
+

企业信息

+
+
+ + setCreateForm({ ...createForm, name: e.target.value })} /> +
+
+ + +
+
+ + setCreateForm({ ...createForm, maxEmployees: parseInt(e.target.value) || 0 })} /> +
+
+ + setCreateForm({ ...createForm, city: e.target.value })} /> +
+
+ + setCreateForm({ ...createForm, contactName: e.target.value })} /> +
+
+ + setCreateForm({ ...createForm, contactPhone: e.target.value })} /> +
+
+
+
+

管理员账号

+
+
+ + setCreateForm({ ...createForm, adminName: e.target.value })} /> +
+
+ + setCreateForm({ ...createForm, adminPhone: e.target.value })} /> +
+
+ + setCreateForm({ ...createForm, adminPassword: e.target.value })} /> +
+
+
+
+ + +
+
+
+
+ )} + {/* 编辑弹窗 */} {editOrg && (
setEditOrg(null)}>