2026-06-15 20:46:48 +08:00
|
|
|
|
// db.js —— 只做两件事:导出连接池;启动时建库/建表/插 admin
|
|
|
|
|
|
require('dotenv').config() //自动寻找.env文件,并把里面的键值对全部导入进来
|
|
|
|
|
|
const mysql = require('mysql2/promise')
|
|
|
|
|
|
const bcrypt = require('bcryptjs') //用来给密码做哈希加密
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 1. 连接池 ============
|
|
|
|
|
|
// 业务代码里用 pool.query() / pool.execute(),会自动从池里取/还连接
|
|
|
|
|
|
const pool = mysql.createPool({
|
|
|
|
|
|
host: process.env.DB_HOST,
|
|
|
|
|
|
port: Number(process.env.DB_PORT) || 3306,
|
|
|
|
|
|
user: process.env.DB_USER,
|
|
|
|
|
|
password: process.env.DB_PASSWORD,
|
|
|
|
|
|
database: process.env.DB_NAME,
|
|
|
|
|
|
waitForConnections: true, // 池里没连接时排队等
|
|
|
|
|
|
connectionLimit: 10, // 最多 10 个连接
|
|
|
|
|
|
decimalNumbers: true,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 2. 启动时初始化 ============
|
|
|
|
|
|
// 思路:先用一个"无 database"的连接建库,再回到 pool 建表、插 admin
|
|
|
|
|
|
async function initDB() {
|
|
|
|
|
|
const { DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME } = process.env
|
|
|
|
|
|
|
|
|
|
|
|
// 2.1 建库(如果不存在)
|
|
|
|
|
|
const conn = await mysql.createConnection({
|
|
|
|
|
|
host: DB_HOST,
|
|
|
|
|
|
port: Number(DB_PORT) || 3306,
|
|
|
|
|
|
user: DB_USER,
|
|
|
|
|
|
password: DB_PASSWORD,
|
|
|
|
|
|
})
|
|
|
|
|
|
await conn.query(
|
|
|
|
|
|
`CREATE DATABASE IF NOT EXISTS \`${DB_NAME}\` DEFAULT CHARACTER SET utf8mb4`
|
|
|
|
|
|
)
|
|
|
|
|
|
await conn.end()
|
|
|
|
|
|
|
2026-06-22 20:48:29 +08:00
|
|
|
|
// 2.2 建表(如果不存在)
|
2026-06-15 20:46:48 +08:00
|
|
|
|
await pool.query(`
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS users (
|
2026-06-22 20:48:29 +08:00
|
|
|
|
id INT NOT NULL AUTO_INCREMENT COMMENT '用户ID (主键)',
|
|
|
|
|
|
username VARCHAR(50) NOT NULL COMMENT '用户名 (登录账号)',
|
|
|
|
|
|
password VARCHAR(255) NOT NULL COMMENT '登录密码 (应存储加密后的值)',
|
2026-06-24 22:44:14 +08:00
|
|
|
|
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同步的冗余字段)',
|
2026-06-22 20:48:29 +08:00
|
|
|
|
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),
|
|
|
|
|
|
UNIQUE KEY uk_username (username)
|
|
|
|
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表'
|
2026-06-15 20:46:48 +08:00
|
|
|
|
`)
|
|
|
|
|
|
|
2026-06-22 20:48:29 +08:00
|
|
|
|
await pool.query(`
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS customers (
|
|
|
|
|
|
id INT NOT NULL AUTO_INCREMENT COMMENT '加盟商ID (主键)',
|
|
|
|
|
|
name VARCHAR(100) NOT NULL COMMENT '加盟商姓名',
|
|
|
|
|
|
phone VARCHAR(20) DEFAULT NULL COMMENT '联系电话',
|
|
|
|
|
|
province VARCHAR(50) DEFAULT NULL COMMENT '省',
|
|
|
|
|
|
city VARCHAR(50) DEFAULT NULL COMMENT '市',
|
|
|
|
|
|
district VARCHAR(50) DEFAULT NULL COMMENT '区',
|
|
|
|
|
|
address VARCHAR(200) DEFAULT NULL COMMENT '详细地址',
|
|
|
|
|
|
customer_type VARCHAR(20) DEFAULT 'Normal' COMMENT '客户类型: VIP-地区总代理, Normal-普通代理',
|
|
|
|
|
|
email VARCHAR(100) DEFAULT NULL COMMENT '电子邮箱',
|
|
|
|
|
|
remark TEXT DEFAULT NULL COMMENT '备注信息',
|
|
|
|
|
|
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_customers_name (name),
|
|
|
|
|
|
KEY idx_customers_phone (phone)
|
|
|
|
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='加盟商信息表'
|
|
|
|
|
|
`)
|
|
|
|
|
|
// 确保 customer_type 列存在(兼容旧表)
|
|
|
|
|
|
try { await pool.query(`ALTER TABLE customers ADD COLUMN customer_type VARCHAR(20) DEFAULT 'Normal' COMMENT '客户类型: VIP-地区总代理, Normal-普通代理' AFTER address`) } catch {}
|
|
|
|
|
|
await pool.query(`
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS employees (
|
|
|
|
|
|
id INT NOT NULL AUTO_INCREMENT COMMENT '员工ID (主键)',
|
|
|
|
|
|
name VARCHAR(100) NOT NULL COMMENT '员工姓名',
|
|
|
|
|
|
gender VARCHAR(4) DEFAULT NULL COMMENT '性别: 男/女',
|
|
|
|
|
|
age INT DEFAULT NULL COMMENT '年龄',
|
|
|
|
|
|
education VARCHAR(50) DEFAULT NULL COMMENT '学历: 高中/专科/本科/硕士/博士',
|
|
|
|
|
|
department VARCHAR(100) DEFAULT NULL COMMENT '所属部门',
|
|
|
|
|
|
entry_date DATE DEFAULT NULL COMMENT '入职时间',
|
|
|
|
|
|
position VARCHAR(100) DEFAULT NULL COMMENT '职务/岗位',
|
|
|
|
|
|
salary DECIMAL(10,2) DEFAULT NULL COMMENT '工资金额',
|
|
|
|
|
|
phone VARCHAR(20) DEFAULT NULL COMMENT '联系电话',
|
|
|
|
|
|
email VARCHAR(100) DEFAULT NULL COMMENT '电子邮箱',
|
|
|
|
|
|
status TINYINT(1) NOT NULL DEFAULT 1 COMMENT '在职状态: 0-离职, 1-在职',
|
|
|
|
|
|
remark TEXT DEFAULT NULL COMMENT '备注',
|
|
|
|
|
|
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_employees_name (name),
|
|
|
|
|
|
KEY idx_employees_department (department)
|
|
|
|
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工信息表'
|
|
|
|
|
|
`)
|
|
|
|
|
|
await pool.query(`
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS products (
|
|
|
|
|
|
id INT NOT NULL AUTO_INCREMENT COMMENT '产品ID (主键)',
|
|
|
|
|
|
name VARCHAR(200) NOT NULL COMMENT '产品名称',
|
|
|
|
|
|
type VARCHAR(100) DEFAULT NULL COMMENT '产品类型/分类',
|
|
|
|
|
|
quantity INT NOT NULL DEFAULT 0 COMMENT '产品库存数量',
|
|
|
|
|
|
price DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '产品单价',
|
|
|
|
|
|
unit VARCHAR(20) DEFAULT '件' COMMENT '计量单位',
|
|
|
|
|
|
specification VARCHAR(200) DEFAULT NULL COMMENT '产品规格/型号',
|
|
|
|
|
|
supplier VARCHAR(200) DEFAULT NULL COMMENT '供应商',
|
|
|
|
|
|
remark TEXT DEFAULT NULL COMMENT '备注',
|
|
|
|
|
|
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_products_name (name),
|
|
|
|
|
|
KEY idx_products_type (type)
|
|
|
|
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='产品信息表'
|
|
|
|
|
|
`)
|
|
|
|
|
|
// contracts 依赖 customers 和 employees,after_sales 同样依赖
|
|
|
|
|
|
await pool.query(`
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS contracts (
|
|
|
|
|
|
id INT NOT NULL AUTO_INCREMENT COMMENT '合同ID (主键)',
|
|
|
|
|
|
customer_id INT NOT NULL COMMENT '客户ID (关联客户表)',
|
|
|
|
|
|
contract_name VARCHAR(200) NOT NULL COMMENT '合同名称',
|
|
|
|
|
|
contract_no VARCHAR(100) DEFAULT NULL COMMENT '合同编号',
|
|
|
|
|
|
contract_content TEXT DEFAULT NULL COMMENT '合同内容/条款',
|
|
|
|
|
|
amount DECIMAL(12,2) DEFAULT NULL COMMENT '合同金额',
|
|
|
|
|
|
effective_date DATE DEFAULT NULL COMMENT '合同生效日期',
|
|
|
|
|
|
expiry_date DATE DEFAULT NULL COMMENT '合同有效期 (截止日期)',
|
|
|
|
|
|
employee_id INT DEFAULT NULL COMMENT '业务员ID (关联员工表)',
|
|
|
|
|
|
status VARCHAR(20) NOT NULL DEFAULT '生效' COMMENT '合同状态: 草稿/生效/完成/作废',
|
|
|
|
|
|
remark TEXT DEFAULT NULL COMMENT '备注',
|
|
|
|
|
|
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_contracts_customer_id (customer_id),
|
|
|
|
|
|
KEY idx_contracts_employee_id (employee_id),
|
|
|
|
|
|
KEY idx_contracts_effective_date (effective_date)
|
|
|
|
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='合同信息表'
|
|
|
|
|
|
`)
|
|
|
|
|
|
await pool.query(`
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS after_sales (
|
|
|
|
|
|
id INT NOT NULL AUTO_INCREMENT COMMENT '售后记录ID (主键)',
|
|
|
|
|
|
customer_id INT NOT NULL COMMENT '客户ID (关联客户表)',
|
|
|
|
|
|
feedback TEXT NOT NULL COMMENT '客户反馈意见/售后内容',
|
|
|
|
|
|
employee_id INT DEFAULT NULL COMMENT '处理业务员ID (关联员工表)',
|
|
|
|
|
|
handle_method TEXT DEFAULT NULL COMMENT '处理方式/解决方案',
|
|
|
|
|
|
handle_status VARCHAR(20) NOT NULL DEFAULT '待处理' COMMENT '处理状态: 待处理/处理中/已完成',
|
|
|
|
|
|
service_date DATE DEFAULT NULL COMMENT '售后日期',
|
|
|
|
|
|
remark TEXT DEFAULT NULL COMMENT '备注',
|
|
|
|
|
|
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_after_sales_customer_id (customer_id),
|
|
|
|
|
|
KEY idx_after_sales_employee_id (employee_id),
|
|
|
|
|
|
KEY idx_after_sales_status (handle_status)
|
|
|
|
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='售后信息表'
|
|
|
|
|
|
`)
|
|
|
|
|
|
console.log('[init] 数据表初始化完成')
|
|
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
// ============ 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 permissions (name, description, resource, action) VALUES (?, ?, ?, ?)',
|
|
|
|
|
|
[p.name, p.description, p.resource, p.action]
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
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
|
2026-06-15 20:46:48 +08:00
|
|
|
|
await pool.query(
|
2026-06-24 22:44:14 +08:00
|
|
|
|
`INSERT INTO users (username, password, is_active, role_id, employee_id, department)
|
|
|
|
|
|
VALUES (?, ?, 1, ?, ?, ?)`,
|
|
|
|
|
|
[usr.username, hash, roleIdMap[usr.roleName], empId, emp.department]
|
2026-06-15 20:46:48 +08:00
|
|
|
|
)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
console.log(`[init] 已创建用户 ${usr.username} (${emp.name} - ${emp.department})`)
|
2026-06-15 20:46:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = { pool, initDB }
|