修改管理后台
This commit is contained in:
251
backend/migrations/20250118000000_system_management.js
Normal file
251
backend/migrations/20250118000000_system_management.js
Normal file
@@ -0,0 +1,251 @@
|
||||
/**
|
||||
* 系统管理功能数据库迁移
|
||||
* @file 20250118000000_system_management.js
|
||||
* @description 创建系统配置和菜单权限管理表
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
const transaction = await queryInterface.sequelize.transaction();
|
||||
|
||||
try {
|
||||
// 创建系统配置表
|
||||
await queryInterface.createTable('system_configs', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
comment: '配置ID'
|
||||
},
|
||||
config_key: {
|
||||
type: Sequelize.STRING(100),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '配置键名'
|
||||
},
|
||||
config_value: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '配置值'
|
||||
},
|
||||
config_type: {
|
||||
type: Sequelize.ENUM('string', 'number', 'boolean', 'json', 'array'),
|
||||
allowNull: false,
|
||||
defaultValue: 'string',
|
||||
comment: '配置类型'
|
||||
},
|
||||
category: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
defaultValue: 'general',
|
||||
comment: '配置分类'
|
||||
},
|
||||
description: {
|
||||
type: Sequelize.STRING(255),
|
||||
allowNull: true,
|
||||
comment: '配置描述'
|
||||
},
|
||||
is_public: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
comment: '是否公开(前端可访问)'
|
||||
},
|
||||
is_editable: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true,
|
||||
comment: '是否可编辑'
|
||||
},
|
||||
sort_order: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '排序顺序'
|
||||
},
|
||||
updated_by: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '最后更新人ID'
|
||||
},
|
||||
created_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
|
||||
},
|
||||
updated_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')
|
||||
}
|
||||
}, {
|
||||
transaction,
|
||||
comment: '系统配置表'
|
||||
});
|
||||
|
||||
// 创建菜单权限表
|
||||
await queryInterface.createTable('menu_permissions', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
comment: '权限ID'
|
||||
},
|
||||
menu_key: {
|
||||
type: Sequelize.STRING(100),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '菜单标识'
|
||||
},
|
||||
menu_name: {
|
||||
type: Sequelize.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '菜单名称'
|
||||
},
|
||||
menu_path: {
|
||||
type: Sequelize.STRING(200),
|
||||
allowNull: true,
|
||||
comment: '菜单路径'
|
||||
},
|
||||
parent_id: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '父菜单ID'
|
||||
},
|
||||
menu_type: {
|
||||
type: Sequelize.ENUM('page', 'button', 'api'),
|
||||
allowNull: false,
|
||||
defaultValue: 'page',
|
||||
comment: '菜单类型'
|
||||
},
|
||||
required_roles: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '所需角色(JSON数组)'
|
||||
},
|
||||
required_permissions: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '所需权限(JSON数组)'
|
||||
},
|
||||
icon: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: true,
|
||||
comment: '菜单图标'
|
||||
},
|
||||
sort_order: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '排序顺序'
|
||||
},
|
||||
is_visible: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true,
|
||||
comment: '是否可见'
|
||||
},
|
||||
is_enabled: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true,
|
||||
comment: '是否启用'
|
||||
},
|
||||
description: {
|
||||
type: Sequelize.STRING(255),
|
||||
allowNull: true,
|
||||
comment: '菜单描述'
|
||||
},
|
||||
created_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
|
||||
},
|
||||
updated_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')
|
||||
}
|
||||
}, {
|
||||
transaction,
|
||||
comment: '菜单权限表'
|
||||
});
|
||||
|
||||
// 添加索引
|
||||
await queryInterface.addIndex('system_configs', ['config_key'], {
|
||||
unique: true,
|
||||
transaction
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('system_configs', ['category'], {
|
||||
transaction
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('system_configs', ['is_public'], {
|
||||
transaction
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('menu_permissions', ['menu_key'], {
|
||||
unique: true,
|
||||
transaction
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('menu_permissions', ['parent_id'], {
|
||||
transaction
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('menu_permissions', ['menu_type'], {
|
||||
transaction
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('menu_permissions', ['sort_order'], {
|
||||
transaction
|
||||
});
|
||||
|
||||
// 添加外键约束
|
||||
await queryInterface.addConstraint('menu_permissions', {
|
||||
fields: ['parent_id'],
|
||||
type: 'foreign key',
|
||||
name: 'fk_menu_permissions_parent_id',
|
||||
references: {
|
||||
table: 'menu_permissions',
|
||||
field: 'id'
|
||||
},
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
transaction
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
console.log('系统管理表创建成功');
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
console.error('系统管理表创建失败:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
const transaction = await queryInterface.sequelize.transaction();
|
||||
|
||||
try {
|
||||
// 删除外键约束
|
||||
await queryInterface.removeConstraint('menu_permissions', 'fk_menu_permissions_parent_id', {
|
||||
transaction
|
||||
});
|
||||
|
||||
// 删除表
|
||||
await queryInterface.dropTable('menu_permissions', { transaction });
|
||||
await queryInterface.dropTable('system_configs', { transaction });
|
||||
|
||||
await transaction.commit();
|
||||
console.log('系统管理表删除成功');
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
console.error('系统管理表删除失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
157
backend/migrations/20250118000001_electronic_fence.js
Normal file
157
backend/migrations/20250118000001_electronic_fence.js
Normal file
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* 电子围栏表迁移
|
||||
* 创建电子围栏相关表结构
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
// 创建电子围栏表
|
||||
await queryInterface.createTable('electronic_fences', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
comment: '围栏ID'
|
||||
},
|
||||
name: {
|
||||
type: Sequelize.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '围栏名称'
|
||||
},
|
||||
type: {
|
||||
type: Sequelize.ENUM('collector', 'grazing', 'safety'),
|
||||
allowNull: false,
|
||||
defaultValue: 'collector',
|
||||
comment: '围栏类型: collector-采集器电子围栏, grazing-放牧围栏, safety-安全围栏'
|
||||
},
|
||||
description: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '围栏描述'
|
||||
},
|
||||
coordinates: {
|
||||
type: Sequelize.JSON,
|
||||
allowNull: false,
|
||||
comment: '围栏坐标点数组'
|
||||
},
|
||||
center_lng: {
|
||||
type: Sequelize.DECIMAL(10, 7),
|
||||
allowNull: false,
|
||||
comment: '围栏中心经度'
|
||||
},
|
||||
center_lat: {
|
||||
type: Sequelize.DECIMAL(10, 7),
|
||||
allowNull: false,
|
||||
comment: '围栏中心纬度'
|
||||
},
|
||||
area: {
|
||||
type: Sequelize.DECIMAL(10, 4),
|
||||
allowNull: true,
|
||||
comment: '围栏面积(平方米)'
|
||||
},
|
||||
grazing_status: {
|
||||
type: Sequelize.ENUM('grazing', 'not_grazing'),
|
||||
allowNull: false,
|
||||
defaultValue: 'not_grazing',
|
||||
comment: '放牧状态: grazing-放牧中, not_grazing-未放牧'
|
||||
},
|
||||
inside_count: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '安全区域内动物数量'
|
||||
},
|
||||
outside_count: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '安全区域外动物数量'
|
||||
},
|
||||
is_active: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true,
|
||||
comment: '是否启用'
|
||||
},
|
||||
farm_id: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '关联农场ID',
|
||||
references: {
|
||||
model: 'farms',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL'
|
||||
},
|
||||
created_by: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '创建人ID',
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL'
|
||||
},
|
||||
updated_by: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '更新人ID',
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL'
|
||||
},
|
||||
created_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.NOW,
|
||||
comment: '创建时间'
|
||||
},
|
||||
updated_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.NOW,
|
||||
comment: '更新时间'
|
||||
}
|
||||
}, {
|
||||
comment: '电子围栏表',
|
||||
charset: 'utf8mb4',
|
||||
collate: 'utf8mb4_unicode_ci'
|
||||
});
|
||||
|
||||
// 创建索引
|
||||
await queryInterface.addIndex('electronic_fences', ['name'], {
|
||||
name: 'idx_fence_name'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('electronic_fences', ['type'], {
|
||||
name: 'idx_fence_type'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('electronic_fences', ['center_lng', 'center_lat'], {
|
||||
name: 'idx_fence_center'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('electronic_fences', ['is_active'], {
|
||||
name: 'idx_fence_active'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('electronic_fences', ['farm_id'], {
|
||||
name: 'idx_fence_farm_id'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('electronic_fences', ['grazing_status'], {
|
||||
name: 'idx_fence_grazing_status'
|
||||
});
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
// 删除表
|
||||
await queryInterface.dropTable('electronic_fences');
|
||||
}
|
||||
};
|
||||
124
backend/migrations/20250118000002_electronic_fence_points.js
Normal file
124
backend/migrations/20250118000002_electronic_fence_points.js
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* 电子围栏坐标点数据表迁移
|
||||
* 用于存储围栏绘制过程中用户选定的经纬度坐标点
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
// 创建电子围栏坐标点表
|
||||
await queryInterface.createTable('electronic_fence_points', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
comment: '主键ID'
|
||||
},
|
||||
fence_id: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '关联的围栏ID',
|
||||
references: {
|
||||
model: 'electronic_fences',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE'
|
||||
},
|
||||
point_order: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '坐标点在围栏中的顺序(从0开始)'
|
||||
},
|
||||
longitude: {
|
||||
type: Sequelize.DECIMAL(10, 7),
|
||||
allowNull: false,
|
||||
comment: '经度'
|
||||
},
|
||||
latitude: {
|
||||
type: Sequelize.DECIMAL(10, 7),
|
||||
allowNull: false,
|
||||
comment: '纬度'
|
||||
},
|
||||
point_type: {
|
||||
type: Sequelize.ENUM('corner', 'control', 'marker'),
|
||||
allowNull: false,
|
||||
defaultValue: 'corner',
|
||||
comment: '坐标点类型:corner-拐角点,control-控制点,marker-标记点'
|
||||
},
|
||||
description: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '坐标点描述信息'
|
||||
},
|
||||
is_active: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true,
|
||||
comment: '是否激活'
|
||||
},
|
||||
created_by: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '创建人ID',
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL'
|
||||
},
|
||||
updated_by: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '更新人ID',
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL'
|
||||
},
|
||||
created_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.NOW,
|
||||
comment: '创建时间'
|
||||
},
|
||||
updated_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.NOW,
|
||||
comment: '更新时间'
|
||||
}
|
||||
});
|
||||
|
||||
// 添加索引
|
||||
await queryInterface.addIndex('electronic_fence_points', ['fence_id'], {
|
||||
name: 'idx_fence_points_fence_id'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('electronic_fence_points', ['fence_id', 'point_order'], {
|
||||
name: 'idx_fence_points_fence_order'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('electronic_fence_points', ['longitude', 'latitude'], {
|
||||
name: 'idx_fence_points_coordinates'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('electronic_fence_points', ['point_type'], {
|
||||
name: 'idx_fence_points_type'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('electronic_fence_points', ['is_active'], {
|
||||
name: 'idx_fence_points_active'
|
||||
});
|
||||
|
||||
console.log('✅ 电子围栏坐标点表创建成功');
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
// 删除表
|
||||
await queryInterface.dropTable('electronic_fence_points');
|
||||
console.log('✅ 电子围栏坐标点表删除成功');
|
||||
}
|
||||
};
|
||||
115
backend/migrations/20250118000003_create_pens_table.js
Normal file
115
backend/migrations/20250118000003_create_pens_table.js
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* 创建栏舍表迁移
|
||||
* @file 20250118000003_create_pens_table.js
|
||||
* @description 创建栏舍管理表
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('pens', {
|
||||
id: {
|
||||
type: Sequelize.BIGINT,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
comment: '栏舍ID'
|
||||
},
|
||||
name: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '栏舍名称'
|
||||
},
|
||||
animal_type: {
|
||||
type: Sequelize.ENUM('马', '牛', '羊', '家禽', '猪'),
|
||||
allowNull: false,
|
||||
comment: '动物类型'
|
||||
},
|
||||
pen_type: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: true,
|
||||
comment: '栏舍类型'
|
||||
},
|
||||
responsible: {
|
||||
type: Sequelize.STRING(20),
|
||||
allowNull: false,
|
||||
comment: '负责人'
|
||||
},
|
||||
capacity: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 1,
|
||||
comment: '容量'
|
||||
},
|
||||
status: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true,
|
||||
comment: '状态:true-开启,false-关闭'
|
||||
},
|
||||
description: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '备注信息'
|
||||
},
|
||||
farm_id: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'farms',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL',
|
||||
comment: '所属农场ID'
|
||||
},
|
||||
creator: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
defaultValue: 'admin',
|
||||
comment: '创建人'
|
||||
},
|
||||
created_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.NOW,
|
||||
comment: '创建时间'
|
||||
},
|
||||
updated_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.NOW,
|
||||
comment: '更新时间'
|
||||
}
|
||||
}, {
|
||||
comment: '栏舍管理表',
|
||||
charset: 'utf8mb4',
|
||||
collate: 'utf8mb4_unicode_ci'
|
||||
});
|
||||
|
||||
// 添加索引
|
||||
await queryInterface.addIndex('pens', ['name'], {
|
||||
name: 'idx_pens_name'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('pens', ['animal_type'], {
|
||||
name: 'idx_pens_animal_type'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('pens', ['farm_id'], {
|
||||
name: 'idx_pens_farm_id'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('pens', ['status'], {
|
||||
name: 'idx_pens_status'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('pens', ['created_at'], {
|
||||
name: 'idx_pens_created_at'
|
||||
});
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
await queryInterface.dropTable('pens');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 更新栏舍动物类型枚举值
|
||||
* 将中文枚举值改为英文枚举值
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
const transaction = await queryInterface.sequelize.transaction();
|
||||
|
||||
try {
|
||||
// 首先更新现有数据
|
||||
await queryInterface.sequelize.query(`
|
||||
UPDATE pens SET animal_type = 'horse' WHERE animal_type = '马';
|
||||
`, { transaction });
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
UPDATE pens SET animal_type = 'cattle' WHERE animal_type = '牛';
|
||||
`, { transaction });
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
UPDATE pens SET animal_type = 'sheep' WHERE animal_type = '羊';
|
||||
`, { transaction });
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
UPDATE pens SET animal_type = 'poultry' WHERE animal_type = '家禽';
|
||||
`, { transaction });
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
UPDATE pens SET animal_type = 'pig' WHERE animal_type = '猪';
|
||||
`, { transaction });
|
||||
|
||||
// 修改枚举类型
|
||||
await queryInterface.changeColumn('pens', 'animal_type', {
|
||||
type: Sequelize.ENUM('horse', 'cattle', 'sheep', 'poultry', 'pig'),
|
||||
allowNull: false,
|
||||
comment: '动物类型'
|
||||
}, { transaction });
|
||||
|
||||
await transaction.commit();
|
||||
console.log('栏舍动物类型枚举值更新成功');
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
console.error('栏舍动物类型枚举值更新失败:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
const transaction = await queryInterface.sequelize.transaction();
|
||||
|
||||
try {
|
||||
// 恢复中文枚举值
|
||||
await queryInterface.sequelize.query(`
|
||||
UPDATE pens SET animal_type = '马' WHERE animal_type = 'horse';
|
||||
`, { transaction });
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
UPDATE pens SET animal_type = '牛' WHERE animal_type = 'cattle';
|
||||
`, { transaction });
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
UPDATE pens SET animal_type = '羊' WHERE animal_type = 'sheep';
|
||||
`, { transaction });
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
UPDATE pens SET animal_type = '家禽' WHERE animal_type = 'poultry';
|
||||
`, { transaction });
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
UPDATE pens SET animal_type = '猪' WHERE animal_type = 'pig';
|
||||
`, { transaction });
|
||||
|
||||
// 恢复原始枚举类型
|
||||
await queryInterface.changeColumn('pens', 'animal_type', {
|
||||
type: Sequelize.ENUM('马', '牛', '羊', '家禽', '猪'),
|
||||
allowNull: false,
|
||||
comment: '动物类型'
|
||||
}, { transaction });
|
||||
|
||||
await transaction.commit();
|
||||
console.log('栏舍动物类型枚举值回滚成功');
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
console.error('栏舍动物类型枚举值回滚失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
106
backend/migrations/20250118000005_create_cattle_pens_table.js
Normal file
106
backend/migrations/20250118000005_create_cattle_pens_table.js
Normal file
@@ -0,0 +1,106 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.createTable('cattle_pens', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false
|
||||
},
|
||||
name: {
|
||||
type: Sequelize.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '栏舍名称'
|
||||
},
|
||||
code: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '栏舍编号'
|
||||
},
|
||||
type: {
|
||||
type: Sequelize.ENUM('育成栏', '产房', '配种栏', '隔离栏', '治疗栏'),
|
||||
allowNull: false,
|
||||
comment: '栏舍类型'
|
||||
},
|
||||
capacity: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '栏舍容量'
|
||||
},
|
||||
current_count: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '当前牛只数量'
|
||||
},
|
||||
area: {
|
||||
type: Sequelize.DECIMAL(10, 2),
|
||||
allowNull: false,
|
||||
comment: '面积(平方米)'
|
||||
},
|
||||
location: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '位置描述'
|
||||
},
|
||||
status: {
|
||||
type: Sequelize.ENUM('启用', '停用'),
|
||||
allowNull: false,
|
||||
defaultValue: '启用',
|
||||
comment: '状态'
|
||||
},
|
||||
remark: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '备注'
|
||||
},
|
||||
farm_id: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'farms',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL',
|
||||
comment: '所属农场ID'
|
||||
},
|
||||
created_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
|
||||
},
|
||||
updated_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')
|
||||
}
|
||||
}, {
|
||||
engine: 'InnoDB',
|
||||
charset: 'utf8mb4',
|
||||
collate: 'utf8mb4_unicode_ci',
|
||||
comment: '栏舍设置表'
|
||||
});
|
||||
|
||||
// 添加索引
|
||||
await queryInterface.addIndex('cattle_pens', ['code'], {
|
||||
name: 'idx_cattle_pens_code',
|
||||
unique: true
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('cattle_pens', ['farm_id'], {
|
||||
name: 'idx_cattle_pens_farm_id'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('cattle_pens', ['status'], {
|
||||
name: 'idx_cattle_pens_status'
|
||||
});
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.dropTable('cattle_pens');
|
||||
}
|
||||
};
|
||||
120
backend/migrations/20250118000006_create_cattle_batches_table.js
Normal file
120
backend/migrations/20250118000006_create_cattle_batches_table.js
Normal file
@@ -0,0 +1,120 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.createTable('cattle_batches', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false
|
||||
},
|
||||
name: {
|
||||
type: Sequelize.STRING(200),
|
||||
allowNull: false,
|
||||
comment: '批次名称'
|
||||
},
|
||||
code: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '批次编号'
|
||||
},
|
||||
type: {
|
||||
type: Sequelize.ENUM('育成批次', '繁殖批次', '育肥批次', '隔离批次', '治疗批次'),
|
||||
allowNull: false,
|
||||
comment: '批次类型'
|
||||
},
|
||||
start_date: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
comment: '开始日期'
|
||||
},
|
||||
expected_end_date: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true,
|
||||
comment: '预计结束日期'
|
||||
},
|
||||
actual_end_date: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true,
|
||||
comment: '实际结束日期'
|
||||
},
|
||||
target_count: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '目标牛只数量'
|
||||
},
|
||||
current_count: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '当前牛只数量'
|
||||
},
|
||||
manager: {
|
||||
type: Sequelize.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '负责人'
|
||||
},
|
||||
status: {
|
||||
type: Sequelize.ENUM('进行中', '已完成', '已暂停'),
|
||||
allowNull: false,
|
||||
defaultValue: '进行中',
|
||||
comment: '状态'
|
||||
},
|
||||
remark: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '备注'
|
||||
},
|
||||
farm_id: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'farms',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL',
|
||||
comment: '所属农场ID'
|
||||
},
|
||||
created_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
|
||||
},
|
||||
updated_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')
|
||||
}
|
||||
}, {
|
||||
engine: 'InnoDB',
|
||||
charset: 'utf8mb4',
|
||||
collate: 'utf8mb4_unicode_ci',
|
||||
comment: '批次设置表'
|
||||
});
|
||||
|
||||
// 添加索引
|
||||
await queryInterface.addIndex('cattle_batches', ['code'], {
|
||||
name: 'idx_cattle_batches_code',
|
||||
unique: true
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('cattle_batches', ['farm_id'], {
|
||||
name: 'idx_cattle_batches_farm_id'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('cattle_batches', ['status'], {
|
||||
name: 'idx_cattle_batches_status'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('cattle_batches', ['type'], {
|
||||
name: 'idx_cattle_batches_type'
|
||||
});
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.dropTable('cattle_batches');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.createTable('cattle_batch_animals', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false
|
||||
},
|
||||
batch_id: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'cattle_batches',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE',
|
||||
comment: '批次ID'
|
||||
},
|
||||
animal_id: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'animals',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE',
|
||||
comment: '动物ID'
|
||||
},
|
||||
added_date: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
comment: '添加日期'
|
||||
},
|
||||
added_by: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL',
|
||||
comment: '添加人ID'
|
||||
},
|
||||
created_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
|
||||
},
|
||||
updated_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')
|
||||
}
|
||||
}, {
|
||||
engine: 'InnoDB',
|
||||
charset: 'utf8mb4',
|
||||
collate: 'utf8mb4_unicode_ci',
|
||||
comment: '批次牛只关联表'
|
||||
});
|
||||
|
||||
// 添加索引
|
||||
await queryInterface.addIndex('cattle_batch_animals', ['batch_id'], {
|
||||
name: 'idx_cattle_batch_animals_batch_id'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('cattle_batch_animals', ['animal_id'], {
|
||||
name: 'idx_cattle_batch_animals_animal_id'
|
||||
});
|
||||
|
||||
// 添加唯一约束,防止重复添加
|
||||
await queryInterface.addIndex('cattle_batch_animals', ['batch_id', 'animal_id'], {
|
||||
name: 'idx_cattle_batch_animals_unique',
|
||||
unique: true
|
||||
});
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.dropTable('cattle_batch_animals');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,135 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.createTable('cattle_transfer_records', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false
|
||||
},
|
||||
record_id: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '记录编号'
|
||||
},
|
||||
animal_id: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'animals',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE',
|
||||
comment: '动物ID'
|
||||
},
|
||||
from_pen_id: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'cattle_pens',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE',
|
||||
comment: '转出栏舍ID'
|
||||
},
|
||||
to_pen_id: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'cattle_pens',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE',
|
||||
comment: '转入栏舍ID'
|
||||
},
|
||||
transfer_date: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
comment: '转栏日期'
|
||||
},
|
||||
reason: {
|
||||
type: Sequelize.ENUM('正常调栏', '疾病治疗', '配种需要', '产房准备', '隔离观察', '其他'),
|
||||
allowNull: false,
|
||||
comment: '转栏原因'
|
||||
},
|
||||
operator: {
|
||||
type: Sequelize.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '操作人员'
|
||||
},
|
||||
status: {
|
||||
type: Sequelize.ENUM('已完成', '进行中'),
|
||||
allowNull: false,
|
||||
defaultValue: '已完成',
|
||||
comment: '状态'
|
||||
},
|
||||
remark: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '备注'
|
||||
},
|
||||
farm_id: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'farms',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL',
|
||||
comment: '所属农场ID'
|
||||
},
|
||||
created_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
|
||||
},
|
||||
updated_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')
|
||||
}
|
||||
}, {
|
||||
engine: 'InnoDB',
|
||||
charset: 'utf8mb4',
|
||||
collate: 'utf8mb4_unicode_ci',
|
||||
comment: '转栏记录表'
|
||||
});
|
||||
|
||||
// 添加索引
|
||||
await queryInterface.addIndex('cattle_transfer_records', ['record_id'], {
|
||||
name: 'idx_cattle_transfer_records_record_id',
|
||||
unique: true
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('cattle_transfer_records', ['animal_id'], {
|
||||
name: 'idx_cattle_transfer_records_animal_id'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('cattle_transfer_records', ['from_pen_id'], {
|
||||
name: 'idx_cattle_transfer_records_from_pen_id'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('cattle_transfer_records', ['to_pen_id'], {
|
||||
name: 'idx_cattle_transfer_records_to_pen_id'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('cattle_transfer_records', ['transfer_date'], {
|
||||
name: 'idx_cattle_transfer_records_transfer_date'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('cattle_transfer_records', ['status'], {
|
||||
name: 'idx_cattle_transfer_records_status'
|
||||
});
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.dropTable('cattle_transfer_records');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.createTable('cattle_exit_records', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false
|
||||
},
|
||||
record_id: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '记录编号'
|
||||
},
|
||||
animal_id: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'animals',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE',
|
||||
comment: '动物ID'
|
||||
},
|
||||
exit_date: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
comment: '离栏日期'
|
||||
},
|
||||
exit_reason: {
|
||||
type: Sequelize.ENUM('出售', '死亡', '淘汰', '转场', '其他'),
|
||||
allowNull: false,
|
||||
comment: '离栏原因'
|
||||
},
|
||||
original_pen_id: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'cattle_pens',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE',
|
||||
comment: '原栏舍ID'
|
||||
},
|
||||
destination: {
|
||||
type: Sequelize.STRING(200),
|
||||
allowNull: false,
|
||||
comment: '去向'
|
||||
},
|
||||
disposal_method: {
|
||||
type: Sequelize.ENUM('屠宰', '转售', '掩埋', '焚烧', '其他'),
|
||||
allowNull: false,
|
||||
comment: '处理方式'
|
||||
},
|
||||
handler: {
|
||||
type: Sequelize.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '处理人员'
|
||||
},
|
||||
status: {
|
||||
type: Sequelize.ENUM('已确认', '待确认', '已取消'),
|
||||
allowNull: false,
|
||||
defaultValue: '待确认',
|
||||
comment: '状态'
|
||||
},
|
||||
remark: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '备注'
|
||||
},
|
||||
farm_id: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'farms',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL',
|
||||
comment: '所属农场ID'
|
||||
},
|
||||
created_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
|
||||
},
|
||||
updated_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')
|
||||
}
|
||||
}, {
|
||||
engine: 'InnoDB',
|
||||
charset: 'utf8mb4',
|
||||
collate: 'utf8mb4_unicode_ci',
|
||||
comment: '离栏记录表'
|
||||
});
|
||||
|
||||
// 添加索引
|
||||
await queryInterface.addIndex('cattle_exit_records', ['record_id'], {
|
||||
name: 'idx_cattle_exit_records_record_id',
|
||||
unique: true
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('cattle_exit_records', ['animal_id'], {
|
||||
name: 'idx_cattle_exit_records_animal_id'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('cattle_exit_records', ['original_pen_id'], {
|
||||
name: 'idx_cattle_exit_records_original_pen_id'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('cattle_exit_records', ['exit_date'], {
|
||||
name: 'idx_cattle_exit_records_exit_date'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('cattle_exit_records', ['exit_reason'], {
|
||||
name: 'idx_cattle_exit_records_exit_reason'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('cattle_exit_records', ['status'], {
|
||||
name: 'idx_cattle_exit_records_status'
|
||||
});
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.dropTable('cattle_exit_records');
|
||||
}
|
||||
};
|
||||
153
backend/migrations/20250118000010_create_operation_logs_table.js
Normal file
153
backend/migrations/20250118000010_create_operation_logs_table.js
Normal file
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* 创建操作日志表迁移
|
||||
* @file 20250118000010_create_operation_logs_table.js
|
||||
* @description 创建操作日志表,用于记录系统用户的操作记录
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.createTable('operation_logs', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
comment: '主键ID'
|
||||
},
|
||||
user_id: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '操作用户ID'
|
||||
},
|
||||
username: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '操作用户名'
|
||||
},
|
||||
user_role: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '操作用户角色'
|
||||
},
|
||||
operation_type: {
|
||||
type: Sequelize.ENUM('CREATE', 'UPDATE', 'DELETE'),
|
||||
allowNull: false,
|
||||
comment: '操作类型:CREATE-新增,UPDATE-编辑,DELETE-删除'
|
||||
},
|
||||
module_name: {
|
||||
type: Sequelize.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '操作模块名称'
|
||||
},
|
||||
table_name: {
|
||||
type: Sequelize.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '操作的数据表名'
|
||||
},
|
||||
record_id: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '操作的记录ID'
|
||||
},
|
||||
operation_desc: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: false,
|
||||
comment: '操作描述'
|
||||
},
|
||||
old_data: {
|
||||
type: Sequelize.JSON,
|
||||
allowNull: true,
|
||||
comment: '操作前的数据(编辑和删除时记录)'
|
||||
},
|
||||
new_data: {
|
||||
type: Sequelize.JSON,
|
||||
allowNull: true,
|
||||
comment: '操作后的数据(新增和编辑时记录)'
|
||||
},
|
||||
ip_address: {
|
||||
type: Sequelize.STRING(45),
|
||||
allowNull: true,
|
||||
comment: '操作IP地址'
|
||||
},
|
||||
user_agent: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '用户代理信息'
|
||||
},
|
||||
request_url: {
|
||||
type: Sequelize.STRING(500),
|
||||
allowNull: true,
|
||||
comment: '请求URL'
|
||||
},
|
||||
request_method: {
|
||||
type: Sequelize.STRING(10),
|
||||
allowNull: true,
|
||||
comment: '请求方法'
|
||||
},
|
||||
response_status: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '响应状态码'
|
||||
},
|
||||
execution_time: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '执行时间(毫秒)'
|
||||
},
|
||||
error_message: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '错误信息(如果有)'
|
||||
},
|
||||
created_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.NOW,
|
||||
comment: '创建时间'
|
||||
},
|
||||
updated_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.NOW,
|
||||
comment: '更新时间'
|
||||
}
|
||||
}, {
|
||||
comment: '操作日志表',
|
||||
charset: 'utf8mb4',
|
||||
collate: 'utf8mb4_unicode_ci'
|
||||
});
|
||||
|
||||
// 创建索引
|
||||
await queryInterface.addIndex('operation_logs', ['user_id'], {
|
||||
name: 'idx_operation_logs_user_id'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('operation_logs', ['operation_type'], {
|
||||
name: 'idx_operation_logs_operation_type'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('operation_logs', ['module_name'], {
|
||||
name: 'idx_operation_logs_module_name'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('operation_logs', ['table_name'], {
|
||||
name: 'idx_operation_logs_table_name'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('operation_logs', ['created_at'], {
|
||||
name: 'idx_operation_logs_created_at'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('operation_logs', ['user_id', 'operation_type'], {
|
||||
name: 'idx_operation_logs_user_operation'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('operation_logs', ['module_name', 'operation_type'], {
|
||||
name: 'idx_operation_logs_module_operation'
|
||||
});
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.dropTable('operation_logs');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 更新操作日志表操作类型枚举
|
||||
* @file 20250118000011_update_operation_logs_enum.js
|
||||
* @description 添加更多操作类型到操作日志表
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
try {
|
||||
console.log('开始更新操作日志表操作类型枚举...');
|
||||
|
||||
// 修改操作类型枚举
|
||||
await queryInterface.changeColumn('operation_logs', 'operation_type', {
|
||||
type: Sequelize.ENUM(
|
||||
'CREATE',
|
||||
'UPDATE',
|
||||
'DELETE',
|
||||
'READ',
|
||||
'LOGIN',
|
||||
'LOGOUT',
|
||||
'EXPORT',
|
||||
'IMPORT',
|
||||
'BATCH_DELETE',
|
||||
'BATCH_UPDATE'
|
||||
),
|
||||
allowNull: false,
|
||||
comment: '操作类型:CREATE-新增,UPDATE-编辑,DELETE-删除,READ-查看,LOGIN-登录,LOGOUT-登出,EXPORT-导出,IMPORT-导入,BATCH_DELETE-批量删除,BATCH_UPDATE-批量更新'
|
||||
});
|
||||
|
||||
console.log('✅ 操作日志表操作类型枚举更新成功');
|
||||
} catch (error) {
|
||||
console.error('❌ 更新操作日志表操作类型枚举失败:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
try {
|
||||
console.log('开始回滚操作日志表操作类型枚举...');
|
||||
|
||||
// 回滚到原始枚举
|
||||
await queryInterface.changeColumn('operation_logs', 'operation_type', {
|
||||
type: Sequelize.ENUM('CREATE', 'UPDATE', 'DELETE'),
|
||||
allowNull: false,
|
||||
comment: '操作类型:CREATE-新增,UPDATE-编辑,DELETE-删除'
|
||||
});
|
||||
|
||||
console.log('✅ 操作日志表操作类型枚举回滚成功');
|
||||
} catch (error) {
|
||||
console.error('❌ 回滚操作日志表操作类型枚举失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user