修改管理后台
This commit is contained in:
298
backend/models/ElectronicFencePoint.js
Normal file
298
backend/models/ElectronicFencePoint.js
Normal file
@@ -0,0 +1,298 @@
|
||||
/**
|
||||
* 电子围栏坐标点模型
|
||||
* 用于存储围栏绘制过程中用户选定的经纬度坐标点
|
||||
*/
|
||||
|
||||
const { DataTypes } = require('sequelize');
|
||||
const { sequelize } = require('../config/database-simple');
|
||||
const BaseModel = require('./BaseModel');
|
||||
|
||||
class ElectronicFencePoint extends BaseModel {
|
||||
// 模型属性定义
|
||||
static attributes = {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
comment: '主键ID'
|
||||
},
|
||||
fence_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '关联的围栏ID',
|
||||
references: {
|
||||
model: 'electronic_fences',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
point_order: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '坐标点在围栏中的顺序(从0开始)'
|
||||
},
|
||||
longitude: {
|
||||
type: DataTypes.DECIMAL(10, 7),
|
||||
allowNull: false,
|
||||
comment: '经度'
|
||||
},
|
||||
latitude: {
|
||||
type: DataTypes.DECIMAL(10, 7),
|
||||
allowNull: false,
|
||||
comment: '纬度'
|
||||
},
|
||||
point_type: {
|
||||
type: DataTypes.ENUM('corner', 'control', 'marker'),
|
||||
allowNull: false,
|
||||
defaultValue: 'corner',
|
||||
comment: '坐标点类型:corner-拐角点,control-控制点,marker-标记点'
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '坐标点描述信息'
|
||||
},
|
||||
is_active: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true,
|
||||
comment: '是否激活'
|
||||
},
|
||||
created_by: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '创建人ID'
|
||||
},
|
||||
updated_by: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '更新人ID'
|
||||
}
|
||||
};
|
||||
|
||||
// 模型选项
|
||||
static options = {
|
||||
tableName: 'electronic_fence_points',
|
||||
comment: '电子围栏坐标点表',
|
||||
indexes: [
|
||||
{
|
||||
fields: ['fence_id']
|
||||
},
|
||||
{
|
||||
fields: ['fence_id', 'point_order']
|
||||
},
|
||||
{
|
||||
fields: ['longitude', 'latitude']
|
||||
},
|
||||
{
|
||||
fields: ['point_type']
|
||||
},
|
||||
{
|
||||
fields: ['is_active']
|
||||
}
|
||||
],
|
||||
hooks: {
|
||||
beforeCreate: (point, options) => {
|
||||
// 创建前钩子
|
||||
if (!point.point_order && point.point_order !== 0) {
|
||||
// 如果没有指定顺序,自动计算
|
||||
return ElectronicFencePoint.count({
|
||||
where: { fence_id: point.fence_id }
|
||||
}).then(count => {
|
||||
point.point_order = count;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 实例方法
|
||||
/**
|
||||
* 获取坐标点的经纬度对象
|
||||
* @returns {Object} 包含lng和lat的对象
|
||||
*/
|
||||
getCoordinates() {
|
||||
return {
|
||||
lng: parseFloat(this.longitude),
|
||||
lat: parseFloat(this.latitude)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置坐标点
|
||||
* @param {number} lng - 经度
|
||||
* @param {number} lat - 纬度
|
||||
*/
|
||||
setCoordinates(lng, lat) {
|
||||
this.longitude = lng;
|
||||
this.latitude = lat;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为前端格式
|
||||
* @returns {Object} 前端使用的坐标点格式
|
||||
*/
|
||||
toFrontendFormat() {
|
||||
return {
|
||||
id: this.id,
|
||||
fenceId: this.fence_id,
|
||||
pointOrder: this.point_order,
|
||||
lng: parseFloat(this.longitude),
|
||||
lat: parseFloat(this.latitude),
|
||||
pointType: this.point_type,
|
||||
description: this.description,
|
||||
isActive: this.is_active,
|
||||
createdAt: this.created_at,
|
||||
updatedAt: this.updated_at
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算到另一个点的距离(米)
|
||||
* @param {ElectronicFencePoint} otherPoint - 另一个坐标点
|
||||
* @returns {number} 距离(米)
|
||||
*/
|
||||
distanceTo(otherPoint) {
|
||||
const R = 6371000; // 地球半径(米)
|
||||
const lat1 = this.latitude * Math.PI / 180;
|
||||
const lat2 = otherPoint.latitude * Math.PI / 180;
|
||||
const deltaLat = (otherPoint.latitude - this.latitude) * Math.PI / 180;
|
||||
const deltaLng = (otherPoint.longitude - this.longitude) * Math.PI / 180;
|
||||
|
||||
const a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
|
||||
Math.cos(lat1) * Math.cos(lat2) *
|
||||
Math.sin(deltaLng / 2) * Math.sin(deltaLng / 2);
|
||||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
|
||||
return R * c;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查坐标点是否在指定范围内
|
||||
* @param {number} centerLng - 中心点经度
|
||||
* @param {number} centerLat - 中心点纬度
|
||||
* @param {number} radius - 半径(米)
|
||||
* @returns {boolean} 是否在范围内
|
||||
*/
|
||||
isWithinRadius(centerLng, centerLat, radius) {
|
||||
const centerPoint = {
|
||||
longitude: centerLng,
|
||||
latitude: centerLat
|
||||
};
|
||||
return this.distanceTo(centerPoint) <= radius;
|
||||
}
|
||||
|
||||
// 静态方法
|
||||
/**
|
||||
* 根据围栏ID获取所有坐标点
|
||||
* @param {number} fenceId - 围栏ID
|
||||
* @param {Object} options - 查询选项
|
||||
* @returns {Promise<Array>} 坐标点数组
|
||||
*/
|
||||
static async getByFenceId(fenceId, options = {}) {
|
||||
const defaultOptions = {
|
||||
where: {
|
||||
fence_id: fenceId,
|
||||
is_active: true
|
||||
},
|
||||
order: [['point_order', 'ASC']]
|
||||
};
|
||||
|
||||
return this.findAll({
|
||||
...defaultOptions,
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量创建坐标点
|
||||
* @param {number} fenceId - 围栏ID
|
||||
* @param {Array} points - 坐标点数组
|
||||
* @param {Object} options - 创建选项
|
||||
* @returns {Promise<Array>} 创建的坐标点数组
|
||||
*/
|
||||
static async createPoints(fenceId, points, options = {}) {
|
||||
const pointsData = points.map((point, index) => ({
|
||||
fence_id: fenceId,
|
||||
point_order: index,
|
||||
longitude: point.lng,
|
||||
latitude: point.lat,
|
||||
point_type: point.type || 'corner',
|
||||
description: point.description || null,
|
||||
created_by: options.createdBy || null
|
||||
}));
|
||||
|
||||
return this.bulkCreate(pointsData, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新围栏的所有坐标点
|
||||
* @param {number} fenceId - 围栏ID
|
||||
* @param {Array} points - 新的坐标点数组
|
||||
* @param {Object} options - 更新选项
|
||||
* @returns {Promise<Array>} 更新后的坐标点数组
|
||||
*/
|
||||
static async updateFencePoints(fenceId, points, options = {}) {
|
||||
const transaction = options.transaction || await sequelize.transaction();
|
||||
|
||||
try {
|
||||
// 删除现有坐标点
|
||||
await this.destroy({
|
||||
where: { fence_id: fenceId },
|
||||
transaction
|
||||
});
|
||||
|
||||
// 创建新的坐标点
|
||||
const newPoints = await this.createPoints(fenceId, points, {
|
||||
...options,
|
||||
transaction
|
||||
});
|
||||
|
||||
if (!options.transaction) {
|
||||
await transaction.commit();
|
||||
}
|
||||
|
||||
return newPoints;
|
||||
} catch (error) {
|
||||
if (!options.transaction) {
|
||||
await transaction.rollback();
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取围栏的边界框
|
||||
* @param {number} fenceId - 围栏ID
|
||||
* @returns {Promise<Object>} 边界框对象
|
||||
*/
|
||||
static async getFenceBounds(fenceId) {
|
||||
const points = await this.getByFenceId(fenceId);
|
||||
|
||||
if (points.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const lngs = points.map(p => parseFloat(p.longitude));
|
||||
const lats = points.map(p => parseFloat(p.latitude));
|
||||
|
||||
return {
|
||||
minLng: Math.min(...lngs),
|
||||
maxLng: Math.max(...lngs),
|
||||
minLat: Math.min(...lats),
|
||||
maxLat: Math.max(...lats),
|
||||
center: {
|
||||
lng: (Math.min(...lngs) + Math.max(...lngs)) / 2,
|
||||
lat: (Math.min(...lats) + Math.max(...lats)) / 2
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化模型
|
||||
ElectronicFencePoint.init(ElectronicFencePoint.attributes, {
|
||||
...ElectronicFencePoint.options,
|
||||
sequelize,
|
||||
modelName: 'ElectronicFencePoint'
|
||||
});
|
||||
|
||||
module.exports = ElectronicFencePoint;
|
||||
Reference in New Issue
Block a user