diff --git a/mock-server/data/orgs.js b/mock-server/data/orgs.js new file mode 100644 index 0000000..4a7d2e2 --- /dev/null +++ b/mock-server/data/orgs.js @@ -0,0 +1,38 @@ +// 机构数据以扁平列表存储,前端/mock均通过 parentId 自行组装树 +// org001 为一级机构(内置根机构),不可删除;admin 用户归属于此 +export const orgs = [ + { id: 'org001', name: '总行', parentId: null, level: 1, remark: '内置根机构,不可删除', builtin: true, createdAt: '2026-01-01 00:00:00' }, + { id: 'org002', name: '华东分行', parentId: 'org001', level: 2, remark: '', builtin: false, createdAt: '2026-01-02 00:00:00' }, + { id: 'org003', name: '华南分行', parentId: 'org001', level: 2, remark: '', builtin: false, createdAt: '2026-01-02 00:00:00' }, + { id: 'org004', name: '上海分部', parentId: 'org002', level: 3, remark: '', builtin: false, createdAt: '2026-01-03 00:00:00' } +] + +let seq = orgs.length + +export function nextOrgId() { + seq += 1 + return `org${String(seq).padStart(3, '0')}` +} + +export function buildOrgTree(list) { + const map = new Map(list.map((o) => [o.id, { ...o, children: [] }])) + const roots = [] + map.forEach((node) => { + if (node.parentId && map.has(node.parentId)) { + map.get(node.parentId).children.push(node) + } else { + roots.push(node) + } + }) + return roots +} + +export function collectDescendantIds(list, id) { + const result = [] + const children = list.filter((o) => o.parentId === id) + children.forEach((c) => { + result.push(c.id) + result.push(...collectDescendantIds(list, c.id)) + }) + return result +} diff --git a/mock-server/index.js b/mock-server/index.js index efc263e..1e1390d 100644 --- a/mock-server/index.js +++ b/mock-server/index.js @@ -2,6 +2,7 @@ import express from 'express' import cors from 'cors' import cookieParser from 'cookie-parser' import authRoutes from './routes/auth.js' +import systemRoutes from './routes/system.js' const app = express() app.use(cors({ origin: 'http://localhost:5173', credentials: true })) @@ -9,6 +10,7 @@ app.use(express.json({ limit: '10mb' })) app.use(cookieParser()) app.use('/auth', authRoutes) +app.use('/system', systemRoutes) const PORT = 8888 app.listen(PORT, () => { diff --git a/mock-server/routes/system.js b/mock-server/routes/system.js new file mode 100644 index 0000000..e3fc281 --- /dev/null +++ b/mock-server/routes/system.js @@ -0,0 +1,99 @@ +import express from 'express' +import { orgs, nextOrgId, buildOrgTree, collectDescendantIds } from '../data/orgs.js' +import { users } from '../data/users.js' +import { requireAuth } from '../middleware/requireAuth.js' + +const router = express.Router() +router.use(requireAuth) + +// ---------------- 机构管理 ---------------- + +router.get('/org/list', (req, res) => { + const { name, id } = req.query || {} + let list = orgs + if (name || id) { + const matched = orgs.filter( + (o) => (name && o.name.includes(name)) || (id && o.id.includes(id)) + ) + const idSet = new Set() + matched.forEach((m) => { + let cur = m + while (cur) { + idSet.add(cur.id) + cur = orgs.find((o) => o.id === cur.parentId) + } + }) + list = orgs.filter((o) => idSet.has(o.id)) + } + res.json({ code: 200, msg: 'ok', data: buildOrgTree(list) }) +}) + +router.post('/org', (req, res) => { + const { name, parentId, remark } = req.body || {} + if (!name) return res.json({ code: 40004, msg: '机构名称不能为空', data: null }) + if (!parentId) return res.json({ code: 40004, msg: '请选择上级机构', data: null }) + const parent = orgs.find((o) => o.id === parentId) + if (!parent) return res.json({ code: 40004, msg: '上级机构不存在', data: null }) + const duplicate = orgs.some((o) => o.parentId === parentId && o.name === name) + if (duplicate) return res.json({ code: 40005, msg: '同一上级机构下机构名称不可重复', data: null }) + + const org = { + id: nextOrgId(), + name, + parentId, + level: parent.level + 1, + remark: remark || '', + builtin: false, + createdAt: new Date().toISOString().slice(0, 19).replace('T', ' ') + } + orgs.push(org) + res.json({ code: 200, msg: 'ok', data: org }) +}) + +router.put('/org/:id', (req, res) => { + const { id } = req.params + const { name, parentId, remark } = req.body || {} + const org = orgs.find((o) => o.id === id) + if (!org) return res.json({ code: 40404, msg: '机构不存在', data: null }) + if (!name) return res.json({ code: 40004, msg: '机构名称不能为空', data: null }) + + let nextParentId = org.parentId + let level = org.level + if (org.parentId !== null) { + if (!parentId) return res.json({ code: 40004, msg: '请选择上级机构', data: null }) + if (parentId === id) return res.json({ code: 40006, msg: '上级机构不可选择自身', data: null }) + const descendantIds = collectDescendantIds(orgs, id) + if (descendantIds.includes(parentId)) { + return res.json({ code: 40006, msg: '上级机构不可选择自身下级', data: null }) + } + const parent = orgs.find((o) => o.id === parentId) + if (!parent) return res.json({ code: 40004, msg: '上级机构不存在', data: null }) + nextParentId = parentId + level = parent.level + 1 + } + + const duplicate = orgs.some((o) => o.id !== id && o.parentId === nextParentId && o.name === name) + if (duplicate) return res.json({ code: 40005, msg: '同一上级机构下机构名称不可重复', data: null }) + + org.name = name + org.parentId = nextParentId + org.level = level + org.remark = remark || '' + res.json({ code: 200, msg: 'ok', data: org }) +}) + +router.delete('/org/:id', (req, res) => { + const { id } = req.params + const index = orgs.findIndex((o) => o.id === id) + if (index === -1) return res.json({ code: 40404, msg: '机构不存在', data: null }) + const org = orgs[index] + if (org.builtin) return res.json({ code: 40007, msg: '内置一级机构不可删除', data: null }) + const hasChildren = orgs.some((o) => o.parentId === id) + if (hasChildren) return res.json({ code: 40008, msg: '该机构存在下级机构,不可删除', data: null }) + const hasUsers = users.some((u) => u.orgId === id) + if (hasUsers) return res.json({ code: 40009, msg: '该机构已关联用户,不可删除', data: null }) + orgs.splice(index, 1) + res.json({ code: 200, msg: 'ok', data: null }) +}) + +export default router diff --git a/src/api/system.js b/src/api/system.js new file mode 100644 index 0000000..452368d --- /dev/null +++ b/src/api/system.js @@ -0,0 +1,7 @@ +import request from './request' + +// ---------------- 机构管理 ---------------- +export const fetchOrgListApi = (params) => request.get('/system/org/list', { params }) +export const createOrgApi = (data) => request.post('/system/org', data) +export const updateOrgApi = (id, data) => request.put(`/system/org/${id}`, data) +export const deleteOrgApi = (id) => request.delete(`/system/org/${id}`) diff --git a/src/components/OrgTreeSelect.vue b/src/components/OrgTreeSelect.vue new file mode 100644 index 0000000..d88974d --- /dev/null +++ b/src/components/OrgTreeSelect.vue @@ -0,0 +1,54 @@ + + + diff --git a/src/components/ProTable.vue b/src/components/ProTable.vue new file mode 100644 index 0000000..4346d4b --- /dev/null +++ b/src/components/ProTable.vue @@ -0,0 +1,105 @@ + + + + + diff --git a/src/components/StatusTag.vue b/src/components/StatusTag.vue new file mode 100644 index 0000000..06b8328 --- /dev/null +++ b/src/components/StatusTag.vue @@ -0,0 +1,23 @@ + + + diff --git a/src/views/system/org/OrgList.vue b/src/views/system/org/OrgList.vue new file mode 100644 index 0000000..1a99c3f --- /dev/null +++ b/src/views/system/org/OrgList.vue @@ -0,0 +1,189 @@ + + + + +