添加了更多功能

This commit is contained in:
2026-06-24 22:44:14 +08:00
parent ff7173aa68
commit 56af6ab269
11 changed files with 1416 additions and 359 deletions

239
db.js
View File

@@ -39,9 +39,10 @@ async function initDB() {
id INT NOT NULL AUTO_INCREMENT COMMENT '用户ID (主键)',
username VARCHAR(50) NOT NULL COMMENT '用户名 (登录账号)',
password VARCHAR(255) NOT NULL COMMENT '登录密码 (应存储加密后的值)',
real_name VARCHAR(50) DEFAULT NULL COMMENT '真实姓名',
role VARCHAR(20) NOT NULL DEFAULT 'user' COMMENT '角色: admin-管理员, user-普通用户',
status TINYINT(1) NOT NULL DEFAULT 1 COMMENT '账号状态: 0-禁用, 1-启用',
is_active TINYINT(1) NOT NULL DEFAULT 1 COMMENT '账号状态: 0-禁用, 1-启用',
role_id INT DEFAULT NULL COMMENT '角色ID (关联roles表)',
employee_id INT DEFAULT NULL COMMENT '员工ID (关联employees表)',
department VARCHAR(100) DEFAULT NULL COMMENT '所属部门 (从employees同步的冗余字段)',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (id),
@@ -152,20 +153,228 @@ async function initDB() {
`)
console.log('[init] 数据表初始化完成')
// 2.3 插 admin仅当不存在时
const [rows] = await pool.query(
'SELECT id FROM users WHERE username = ?',
['admin']
)
if (rows.length === 0) {
const hash = await bcrypt.hash('123456', 10)
// ============ 2.3 RBAC 权限相关表 ============
await pool.query(`
CREATE TABLE IF NOT EXISTS roles (
id INT NOT NULL AUTO_INCREMENT COMMENT '角色ID (主键)',
name VARCHAR(50) NOT NULL COMMENT '角色标识 (英文)',
description VARCHAR(200) DEFAULT NULL COMMENT '角色描述 (中文)',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (id),
UNIQUE KEY uk_roles_name (name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表'
`)
await pool.query(`
CREATE TABLE IF NOT EXISTS permissions (
id INT NOT NULL AUTO_INCREMENT COMMENT '权限ID (主键)',
name VARCHAR(100) NOT NULL COMMENT '权限标识 (resource:action)',
description VARCHAR(200) DEFAULT NULL COMMENT '权限描述 (中文)',
resource VARCHAR(50) NOT NULL COMMENT '资源名称',
action VARCHAR(50) NOT NULL COMMENT '操作类型: read/create/update/delete',
PRIMARY KEY (id),
UNIQUE KEY uk_permissions_name (name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限表'
`)
await pool.query(`
CREATE TABLE IF NOT EXISTS role_permissions (
role_id INT NOT NULL COMMENT '角色ID',
permission_id INT NOT NULL COMMENT '权限ID',
PRIMARY KEY (role_id, permission_id),
KEY idx_rp_permission_id (permission_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色-权限关联表'
`)
await pool.query(`
CREATE TABLE IF NOT EXISTS suppliers (
id INT NOT NULL AUTO_INCREMENT COMMENT '供应商ID (主键)',
name VARCHAR(200) NOT NULL COMMENT '供应商名称',
contact VARCHAR(100) DEFAULT NULL COMMENT '联系人',
phone VARCHAR(50) DEFAULT NULL COMMENT '联系电话',
address VARCHAR(500) DEFAULT NULL COMMENT '地址',
type VARCHAR(50) DEFAULT NULL COMMENT '类型: 原材料/包装/设备',
content TEXT DEFAULT NULL COMMENT '备注说明',
status TINYINT(1) NOT NULL DEFAULT 1 COMMENT '状态: 0-停用, 1-正常',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (id),
KEY idx_suppliers_name (name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='供应商信息表'
`)
// ============ 2.4 旧表迁移(兼容已有数据库) ============
// users 表:删除 real_name、role新增 is_active、role_id、employee_id、department
try { await pool.query(`ALTER TABLE users DROP COLUMN real_name`) } catch {}
try { await pool.query(`ALTER TABLE users DROP COLUMN role`) } catch {}
try { await pool.query(`ALTER TABLE users CHANGE COLUMN status is_active TINYINT(1) NOT NULL DEFAULT 1 COMMENT '账号状态: 0-禁用, 1-启用'`) } catch {}
try { await pool.query(`ALTER TABLE users ADD COLUMN role_id INT DEFAULT NULL COMMENT '角色ID' AFTER is_active`) } catch {}
try { await pool.query(`ALTER TABLE users ADD COLUMN employee_id INT DEFAULT NULL COMMENT '员工ID' AFTER role_id`) } catch {}
try { await pool.query(`ALTER TABLE users ADD COLUMN department VARCHAR(100) DEFAULT NULL COMMENT '所属部门' AFTER employee_id`) } catch {}
// customers 表:新增 responsible_user_id
try { await pool.query(`ALTER TABLE customers ADD COLUMN responsible_user_id INT DEFAULT NULL COMMENT '负责人用户ID'`) } catch {}
// contracts 表:新增 type
try { await pool.query(`ALTER TABLE contracts ADD COLUMN type VARCHAR(20) DEFAULT 'franchise' COMMENT '合同类型: franchise-加盟合同, supply-采购合同'`) } catch {}
// contracts 表:新增 responsible_user_id用于数据范围隔离
try { await pool.query(`ALTER TABLE contracts ADD COLUMN responsible_user_id INT DEFAULT NULL COMMENT '负责人用户ID'`) } catch {}
// after_sales 表:新增 responsible_user_id用于数据范围隔离
try { await pool.query(`ALTER TABLE after_sales ADD COLUMN responsible_user_id INT DEFAULT NULL COMMENT '负责人用户ID'`) } catch {}
// ============ 2.5 种子数据 ============
await seedRolesAndPermissions()
await seedDefaultUsers()
}
// ============ 3. 种子数据:角色与权限 ============
async function seedRolesAndPermissions() {
const [roles] = await pool.query('SELECT id FROM roles LIMIT 1')
if (roles.length > 0) return // 已初始化过,跳过
const roleList = [
{ name: 'admin', description: '系统管理员' },
{ name: 'general_manager', description: '总经理' },
{ name: 'franchise_manager', description: '招商经理' },
{ name: 'operations_manager', description: '运营经理' },
{ name: 'procurement_manager', description: '采购经理' },
{ name: 'finance', description: '财务人员' },
]
for (const r of roleList) {
await pool.query('INSERT INTO roles (name, description) VALUES (?, ?)', [r.name, r.description])
}
console.log('[init] 已初始化 6 个角色')
const permissionList = [
{ name: 'customer:read', description: '查看加盟商', resource: 'customer', action: 'read' },
{ name: 'customer:create', description: '新增加盟商', resource: 'customer', action: 'create' },
{ name: 'customer:update', description: '修改加盟商', resource: 'customer', action: 'update' },
{ name: 'customer:delete', description: '删除加盟商', resource: 'customer', action: 'delete' },
{ name: 'contract:read', description: '查看合同', resource: 'contract', action: 'read' },
{ name: 'contract:create', description: '新增合同', resource: 'contract', action: 'create' },
{ name: 'contract:update', description: '修改合同', resource: 'contract', action: 'update' },
{ name: 'contract:delete', description: '删除合同', resource: 'contract', action: 'delete' },
{ name: 'after_sale:read', description: '查看售后', resource: 'after_sale', action: 'read' },
{ name: 'after_sale:create',description: '新增售后', resource: 'after_sale', action: 'create' },
{ name: 'after_sale:update',description: '修改售后', resource: 'after_sale', action: 'update' },
{ name: 'after_sale:delete',description: '删除售后', resource: 'after_sale', action: 'delete' },
{ name: 'product:read', description: '查看产品原料', resource: 'product', action: 'read' },
{ name: 'product:create', description: '新增产品原料', resource: 'product', action: 'create' },
{ name: 'product:update', description: '修改产品原料', resource: 'product', action: 'update' },
{ name: 'product:delete', description: '删除产品原料', resource: 'product', action: 'delete' },
{ name: 'supplier:read', description: '查看供应商', resource: 'supplier', action: 'read' },
{ name: 'supplier:create', description: '新增供应商', resource: 'supplier', action: 'create' },
{ name: 'supplier:update', description: '修改供应商', resource: 'supplier', action: 'update' },
{ name: 'supplier:delete', description: '删除供应商', resource: 'supplier', action: 'delete' },
{ name: 'employee:read', description: '查看员工', resource: 'employee', action: 'read' },
{ name: 'employee:create', description: '新增员工', resource: 'employee', action: 'create' },
{ name: 'employee:update', description: '修改员工', resource: 'employee', action: 'update' },
{ name: 'employee:delete', description: '删除员工', resource: 'employee', action: 'delete' },
{ name: 'user:manage', description: '管理用户账号', resource: 'user', action: 'manage' },
]
for (const p of permissionList) {
await pool.query(
'INSERT INTO users (username, password, real_name, role) VALUES (?, ?, ?, ?)',
['admin', hash, '超级管理员', 'admin']
'INSERT INTO permissions (name, description, resource, action) VALUES (?, ?, ?, ?)',
[p.name, p.description, p.resource, p.action]
)
console.log('[init] 已创建默认账号 admin / 123456')
} else {
console.log('[init] admin 已存在,跳过')
}
console.log('[init] 已初始化 25 个权限')
// 角色-权限映射
const allPerms = permissionList.map(p => p.name)
const rolePermMap = {
admin: allPerms, // 管理员拥有所有权限
general_manager: [
'customer:read', 'contract:read', 'after_sale:read',
'product:read', 'supplier:read', 'employee:read',
],
franchise_manager: [
'customer:read', 'customer:create', 'customer:update', 'customer:delete',
'contract:read', 'contract:create', 'contract:update', 'contract:delete',
'employee:read',
],
operations_manager: [
'customer:read', 'customer:update',
'contract:read',
'after_sale:read', 'after_sale:create', 'after_sale:update', 'after_sale:delete',
'employee:read',
],
procurement_manager: [
'contract:read', 'contract:create', 'contract:update', 'contract:delete',
'product:read', 'product:create', 'product:update', 'product:delete',
'supplier:read', 'supplier:create', 'supplier:update', 'supplier:delete',
'employee:read',
],
finance: [
'customer:read', 'contract:read', 'after_sale:read', 'employee:read',
],
}
const [roleRows] = await pool.query('SELECT id, name FROM roles')
const [permRows] = await pool.query('SELECT id, name FROM permissions')
const roleIdMap = Object.fromEntries(roleRows.map(r => [r.name, r.id]))
const permIdMap = Object.fromEntries(permRows.map(p => [p.name, p.id]))
for (const [roleName, permNames] of Object.entries(rolePermMap)) {
for (const permName of permNames) {
await pool.query(
'INSERT IGNORE INTO role_permissions (role_id, permission_id) VALUES (?, ?)',
[roleIdMap[roleName], permIdMap[permName]]
)
}
}
console.log('[init] 已初始化角色-权限映射')
}
// ============ 4. 种子数据:默认用户 ============
async function seedDefaultUsers() {
const hash = await bcrypt.hash('123456', 10)
const [roleRows] = await pool.query('SELECT id, name FROM roles')
const roleIdMap = Object.fromEntries(roleRows.map(r => [r.name, r.id]))
const seedEmployees = [
{ name: '张超', gender: '男', age: 42, education: '硕士', department: '总经理办公室', entry_date: '2015-03-01', position: '总经理', salary: 50000, phone: '13800001001', email: 'zhangchao@mixue.com' },
{ name: '李明', gender: '男', age: 35, education: '本科', department: '招商部', entry_date: '2018-06-15', position: '招商经理', salary: 18000, phone: '13800001002', email: 'liming@mixue.com' },
{ name: '王丽', gender: '女', age: 33, education: '本科', department: '运营部', entry_date: '2019-01-10', position: '运营经理', salary: 18000, phone: '13800001003', email: 'wangli@mixue.com' },
{ name: '赵强', gender: '男', age: 36, education: '本科', department: '采购部', entry_date: '2017-09-20', position: '采购经理', salary: 18000, phone: '13800001004', email: 'zhaoqiang@mixue.com' },
{ name: '陈芳', gender: '女', age: 30, education: '本科', department: '财务部', entry_date: '2020-03-05', position: '财务主管', salary: 15000, phone: '13800001005', email: 'chenfang@mixue.com' },
{ name: '系统管理员', gender: '男', age: 28, education: '本科', department: '信息技术部', entry_date: '2022-07-01', position: '系统管理员', salary: 12000, phone: '13800001006', email: 'admin@mixue.com' },
]
const seedUsers = [
{ username: 'zhangchao', roleName: 'general_manager' },
{ username: 'liming', roleName: 'franchise_manager' },
{ username: 'wangli', roleName: 'operations_manager' },
{ username: 'zhaoqiang', roleName: 'procurement_manager' },
{ username: 'chenfang', roleName: 'finance' },
{ username: 'admin', roleName: 'admin' },
]
for (let i = 0; i < seedUsers.length; i++) {
const emp = seedEmployees[i]
const usr = seedUsers[i]
// 逐个检查用户是否存在(不依赖全局检查,避免部分用户被误删后跳过全部重建)
const [userCheck] = await pool.query('SELECT id FROM users WHERE username = ?', [usr.username])
if (userCheck.length > 0) continue
// 先插入 employees
const [empResult] = await pool.query(
`INSERT INTO employees (name, gender, age, education, department, entry_date, position, salary, phone, email, status)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)`,
[emp.name, emp.gender, emp.age, emp.education, emp.department, emp.entry_date, emp.position, emp.salary, emp.phone, emp.email]
)
const empId = empResult.insertId
// 再插入 users关联 employee 和 role
await pool.query(
`INSERT INTO users (username, password, is_active, role_id, employee_id, department)
VALUES (?, ?, 1, ?, ?, ?)`,
[usr.username, hash, roleIdMap[usr.roleName], empId, emp.department]
)
console.log(`[init] 已创建用户 ${usr.username} (${emp.name} - ${emp.department})`)
}
}