文件
mashangyoujiu/Application/Retail/View/Xcx/fence_manager.html

829 行
29 KiB
HTML

<!--[1.8.50]-->
<!-- 引入Vue.js -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.min.js"></script>
<!-- 引入Element UI -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入jQuery -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
<!-- 引入腾讯地图组件 -->
<script charset="utf-8" type="text/javascript" src="/Public/js/get-position-map-tencent.js?v=1.0.2"></script>
<style type="text/css">
.fence-container {
padding: 20px;
}
.fence-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.fence-table {
margin-bottom: 20px;
}
.fence-status {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
.fence-status.active {
background-color: #e7f5e7;
color: #67c23a;
}
.fence-status.inactive {
background-color: #fef0f0;
color: #f56c6c;
}
.fence-type-tag {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
background-color: #f0f9ff;
color: #409eff;
}
.map-container {
border: 1px solid #dcdfe6;
border-radius: 4px;
overflow: hidden;
}
.drawing-tools {
padding: 10px;
background-color: #f5f7fa;
border-bottom: 1px solid #dcdfe6;
}
.drawing-tools .el-button {
margin-right: 10px;
}
.fence-form .el-form-item {
margin-bottom: 20px;
}
.coordinates-display {
max-height: 200px;
overflow-y: auto;
background-color: #f9f9f9;
padding: 10px;
border-radius: 4px;
font-family: monospace;
font-size: 12px;
}
</style>
<div id="fenceManager" class="fence-container">
<!-- 页面标题和操作按钮 -->
<div class="fence-header">
<h2>电子围栏管理</h2>
<el-button type="primary" @click="showAddDialog">新增围栏</el-button>
</div>
<!-- 围栏列表 -->
<div class="fence-table">
<el-table :data="fenceList" border stripe>
<el-table-column prop="id" label="ID" width="80"></el-table-column>
<el-table-column prop="fence_name" label="围栏名称" min-width="150"></el-table-column>
<el-table-column prop="fence_type" label="围栏类型" width="100">
<template slot-scope="scope">
<span class="fence-type-tag">
{{ scope.row.fence_type == 1 ? '多边形' : '圆形' }}
</span>
</template>
</el-table-column>
<el-table-column prop="is_active" label="状态" width="80">
<template slot-scope="scope">
<span :class="['fence-status', scope.row.is_active == 1 ? 'active' : 'inactive']">
{{ scope.row.is_active == 1 ? '启用' : '禁用' }}
</span>
</template>
</el-table-column>
<el-table-column prop="create_time" label="创建时间" width="180">
<template slot-scope="scope">
{{ formatTime(scope.row.create_time) }}
</template>
</el-table-column>
<el-table-column label="操作" width="200">
<template slot-scope="scope">
<el-button size="mini" @click="editFence(scope.row)">编辑</el-button>
<el-button size="mini" type="warning" @click="toggleFenceStatus(scope.row)">
{{ scope.row.is_active == 1 ? '禁用' : '启用' }}
</el-button>
<el-button size="mini" type="danger" @click="deleteFence(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pagination.page"
:page-sizes="[10, 20, 50, 100]"
:page-size="pagination.size"
layout="total, sizes, prev, pager, next, jumper"
:total="pagination.total">
</el-pagination>
</div>
<!-- 新增/编辑围栏对话框 -->
<el-dialog
:title="dialogTitle"
:visible.sync="dialogVisible"
width="80%"
:close-on-click-modal="false"
@close="resetForm">
<el-form :model="fenceForm" :rules="fenceRules" ref="fenceForm" label-width="100px" class="fence-form">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="围栏名称" prop="fence_name">
<el-input v-model="fenceForm.fence_name" placeholder="请输入围栏名称"></el-input>
</el-form-item>
<el-form-item label="围栏类型" prop="fence_type">
<el-radio-group v-model="fenceForm.fence_type" @change="onFenceTypeChange">
<el-radio :label="1">多边形围栏</el-radio>
<el-radio :label="2">圆形围栏</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="是否启用" prop="is_active">
<el-switch v-model="fenceForm.is_active" :active-value="1" :inactive-value="0"></el-switch>
</el-form-item>
<el-form-item label="坐标数据" v-if="fenceForm.fence_data">
<div class="coordinates-display">
{{ formatCoordinates(fenceForm.fence_data) }}
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<!-- 地图绘制区域 -->
<div class="map-container">
<div class="drawing-tools">
<el-button
size="small"
:type="drawingMode === 'polygon' ? 'primary' : 'default'"
@click="startDrawing('polygon')"
:disabled="fenceForm.fence_type !== 1">
绘制多边形
</el-button>
<el-button
size="small"
:type="drawingMode === 'circle' ? 'primary' : 'default'"
@click="startDrawing('circle')"
:disabled="fenceForm.fence_type !== 2">
绘制圆形
</el-button>
<el-button size="small" @click="clearDrawing">清除绘制</el-button>
<el-button size="small" type="success" @click="completeDrawing" :disabled="!currentDrawing">完成绘制</el-button>
</div>
<div ref="fenceMap" style="height: 400px; width: 100%;"></div>
</div>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveFence" :loading="saving">保存</el-button>
</div>
</el-dialog>
</div>
<script type="text/javascript">
var fenceManager = new Vue({
el: "#fenceManager",
data() {
return {
// 围栏列表数据
fenceList: [],
// 分页数据
pagination: {
page: 1,
size: 10,
total: 0
},
// 对话框控制
dialogVisible: false,
dialogTitle: '新增围栏',
saving: false,
// 围栏表单数据
fenceForm: {
id: 0,
fence_name: '',
fence_type: 1, // 1=多边形,2=圆形
fence_data: '',
is_active: 1
},
// 表单验证规则
fenceRules: {
fence_name: [
{ required: true, message: '请输入围栏名称', trigger: 'blur' },
{ min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
],
fence_type: [
{ required: true, message: '请选择围栏类型', trigger: 'change' }
]
},
// 地图相关
fenceMap: null,
drawingMode: '', // 'polygon' | 'circle' | ''
currentDrawing: null, // 当前绘制的图形
polygonPoints: [], // 多边形点集合
circleCenter: null, // 圆心
circleRadius: 0, // 半径
// 门店信息
storeId: 0,
merchantId: 0
}
},
methods: {
// 初始化数据
async init() {
await this.getStoreInfo();
await this.loadFenceList();
},
// 获取门店信息
async getStoreInfo() {
try {
// 这里需要根据实际情况获取门店ID和商户ID
// 可以从URL参数或全局变量中获取
this.storeId = window.storeId || 1;
this.merchantId = window.merchantId || 1;
} catch (error) {
console.error('获取门店信息失败:', error);
}
},
// 加载围栏列表
async loadFenceList() {
try {
const response = await this.request('/Retail/Xcx/getFenceList', {
store_id: this.storeId,
page: this.pagination.page,
size: this.pagination.size
});
if (response.code === 200) {
this.fenceList = response.data.list || [];
this.pagination.total = response.data.total || 0;
} else {
this.$message.error(response.msg || '加载围栏列表失败');
}
} catch (error) {
console.error('加载围栏列表失败:', error);
this.$message.error('加载围栏列表失败');
}
},
// 显示新增对话框
showAddDialog() {
this.dialogTitle = '新增围栏';
this.dialogVisible = true;
this.resetForm();
this.$nextTick(() => {
this.initFenceMap();
});
},
// 编辑围栏
editFence(fence) {
this.dialogTitle = '编辑围栏';
this.fenceForm = { ...fence };
this.dialogVisible = true;
this.$nextTick(() => {
this.initFenceMap();
this.loadFenceOnMap(fence.fence_data);
});
},
// 切换围栏状态
async toggleFenceStatus(fence) {
try {
const response = await this.request('/Retail/Xcx/updateFenceStatus', {
id: fence.id,
is_active: fence.is_active == 1 ? 0 : 1
});
if (response.code === 200) {
this.$message.success('状态更新成功');
this.loadFenceList();
} else {
this.$message.error(response.msg || '状态更新失败');
}
} catch (error) {
console.error('状态更新失败:', error);
this.$message.error('状态更新失败');
}
},
// 删除围栏
deleteFence(fence) {
this.$confirm('确定要删除这个围栏吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const response = await this.request('/Retail/Xcx/deleteFence', {
id: fence.id
});
if (response.code === 200) {
this.$message.success('删除成功');
this.loadFenceList();
} else {
this.$message.error(response.msg || '删除失败');
}
} catch (error) {
console.error('删除失败:', error);
this.$message.error('删除失败');
}
});
},
// 保存围栏
saveFence() {
this.$refs.fenceForm.validate(async (valid) => {
if (!valid) return;
if (!this.fenceForm.fence_data) {
this.$message.error('请先绘制围栏区域');
return;
}
this.saving = true;
try {
const url = this.fenceForm.id ? '/Retail/Xcx/updateFence' : '/Retail/Xcx/addFence';
const data = {
...this.fenceForm,
store_id: this.storeId,
merchant_id: this.merchantId
};
const response = await this.request(url, data);
if (response.code === 200) {
this.$message.success('保存成功');
this.dialogVisible = false;
this.loadFenceList();
} else {
this.$message.error(response.msg || '保存失败');
}
} catch (error) {
console.error('保存失败:', error);
this.$message.error('保存失败');
} finally {
this.saving = false;
}
});
},
// 重置表单
resetForm() {
this.fenceForm = {
id: 0,
fence_name: '',
fence_type: 1,
fence_data: '',
is_active: 1
};
this.clearDrawing();
if (this.$refs.fenceForm) {
this.$refs.fenceForm.resetFields();
}
},
// 围栏类型改变
onFenceTypeChange() {
this.clearDrawing();
},
// 初始化围栏地图
async initFenceMap() {
if (this.fenceMap) {
return;
}
try {
// 等待腾讯地图API加载
await this.waitForTMapLoad();
// 创建地图实例
this.fenceMap = new TMap.Map(this.$refs.fenceMap, {
center: new TMap.LatLng(39.915, 116.404),
zoom: 15,
viewMode: '2D',
mapStyleId: 'style1'
});
console.log('围栏地图初始化成功');
} catch (error) {
console.error('围栏地图初始化失败:', error);
this.$message.error('地图初始化失败');
}
},
// 等待腾讯地图API加载
waitForTMapLoad() {
return new Promise((resolve, reject) => {
if (window.TMap) {
resolve();
return;
}
let checkCount = 0;
const checkInterval = setInterval(() => {
checkCount++;
if (window.TMap) {
clearInterval(checkInterval);
resolve();
} else if (checkCount > 50) {
clearInterval(checkInterval);
reject(new Error('腾讯地图API加载超时'));
}
}, 100);
});
},
// 开始绘制
startDrawing(mode) {
if (!this.fenceMap) {
this.$message.error('地图未初始化');
return;
}
this.clearDrawing();
this.drawingMode = mode;
if (mode === 'polygon') {
this.startPolygonDrawing();
} else if (mode === 'circle') {
this.startCircleDrawing();
}
},
// 开始多边形绘制
startPolygonDrawing() {
this.polygonPoints = [];
this.$message.info('点击地图绘制多边形,双击完成绘制');
// 监听地图点击事件
this.fenceMap.on('click', this.onPolygonClick);
this.fenceMap.on('dblclick', this.onPolygonComplete);
},
// 多边形点击事件
onPolygonClick(evt) {
if (this.drawingMode !== 'polygon') return;
const latLng = evt.latLng;
this.polygonPoints.push([latLng.lng, latLng.lat]);
// 绘制临时点
this.drawTempPoints();
},
// 完成多边形绘制
onPolygonComplete() {
if (this.drawingMode !== 'polygon' || this.polygonPoints.length < 3) {
this.$message.error('多边形至少需要3个点');
return;
}
this.drawPolygon();
this.fenceMap.off('click', this.onPolygonClick);
this.fenceMap.off('dblclick', this.onPolygonComplete);
},
// 开始圆形绘制
startCircleDrawing() {
this.$message.info('点击地图设置圆心,拖拽设置半径');
this.fenceMap.on('click', this.onCircleClick);
},
// 圆形点击事件
onCircleClick(evt) {
if (this.drawingMode !== 'circle') return;
const latLng = evt.latLng;
this.circleCenter = [latLng.lng, latLng.lat];
// 开始拖拽设置半径
this.fenceMap.off('click', this.onCircleClick);
this.fenceMap.on('mousemove', this.onCircleMouseMove);
this.fenceMap.on('click', this.onCircleRadiusSet);
},
// 圆形鼠标移动事件
onCircleMouseMove(evt) {
if (!this.circleCenter) return;
const latLng = evt.latLng;
const distance = this.calculateDistance(
this.circleCenter[1], this.circleCenter[0],
latLng.lat, latLng.lng
);
this.circleRadius = distance;
this.drawTempCircle();
},
// 设置圆形半径
onCircleRadiusSet() {
if (!this.circleCenter || this.circleRadius <= 0) {
this.$message.error('请设置有效的圆形区域');
return;
}
this.drawCircle();
this.fenceMap.off('mousemove', this.onCircleMouseMove);
this.fenceMap.off('click', this.onCircleRadiusSet);
},
// 绘制多边形
drawPolygon() {
if (this.currentDrawing) {
this.currentDrawing.setMap(null);
}
const paths = this.polygonPoints.map(point => new TMap.LatLng(point[1], point[0]));
this.currentDrawing = new TMap.MultiPolygon({
map: this.fenceMap,
styles: {
'polygon-style': new TMap.PolygonStyle({
color: 'rgba(255, 0, 0, 0.3)',
strokeColor: '#ff0000',
strokeWidth: 2
})
},
geometries: [{
id: 'fence-polygon',
styleId: 'polygon-style',
paths: [paths]
}]
});
this.fenceForm.fence_data = JSON.stringify({
type: 'polygon',
coordinates: [this.polygonPoints]
});
this.$message.success('多边形绘制完成');
},
// 绘制圆形
drawCircle() {
if (this.currentDrawing) {
this.currentDrawing.setMap(null);
}
this.currentDrawing = new TMap.MultiCircle({
map: this.fenceMap,
styles: {
'circle-style': new TMap.CircleStyle({
color: 'rgba(0, 0, 255, 0.3)',
strokeColor: '#0000ff',
strokeWidth: 2
})
},
geometries: [{
id: 'fence-circle',
styleId: 'circle-style',
center: new TMap.LatLng(this.circleCenter[1], this.circleCenter[0]),
radius: this.circleRadius
}]
});
this.fenceForm.fence_data = JSON.stringify({
type: 'circle',
center: this.circleCenter,
radius: this.circleRadius
});
this.$message.success('圆形绘制完成');
},
// 绘制临时点
drawTempPoints() {
// 清除之前的临时点
if (this.tempPoints) {
this.tempPoints.setMap(null);
}
const geometries = this.polygonPoints.map((point, index) => ({
id: `temp-point-${index}`,
styleId: 'point-style',
position: new TMap.LatLng(point[1], point[0])
}));
this.tempPoints = new TMap.MultiMarker({
map: this.fenceMap,
styles: {
'point-style': new TMap.MarkerStyle({
width: 8,
height: 8,
anchor: { x: 4, y: 4 },
color: '#ff0000'
})
},
geometries: geometries
});
},
// 绘制临时圆形
drawTempCircle() {
if (this.tempCircle) {
this.tempCircle.setMap(null);
}
this.tempCircle = new TMap.MultiCircle({
map: this.fenceMap,
styles: {
'temp-circle-style': new TMap.CircleStyle({
color: 'rgba(0, 0, 255, 0.1)',
strokeColor: '#0000ff',
strokeWidth: 1,
strokeDashArray: [5, 5]
})
},
geometries: [{
id: 'temp-circle',
styleId: 'temp-circle-style',
center: new TMap.LatLng(this.circleCenter[1], this.circleCenter[0]),
radius: this.circleRadius
}]
});
},
// 清除绘制
clearDrawing() {
this.drawingMode = '';
this.polygonPoints = [];
this.circleCenter = null;
this.circleRadius = 0;
if (this.currentDrawing) {
this.currentDrawing.setMap(null);
this.currentDrawing = null;
}
if (this.tempPoints) {
this.tempPoints.setMap(null);
this.tempPoints = null;
}
if (this.tempCircle) {
this.tempCircle.setMap(null);
this.tempCircle = null;
}
// 移除所有事件监听
if (this.fenceMap) {
this.fenceMap.off('click', this.onPolygonClick);
this.fenceMap.off('dblclick', this.onPolygonComplete);
this.fenceMap.off('click', this.onCircleClick);
this.fenceMap.off('mousemove', this.onCircleMouseMove);
this.fenceMap.off('click', this.onCircleRadiusSet);
}
},
// 完成绘制
completeDrawing() {
if (this.drawingMode === 'polygon' && this.polygonPoints.length >= 3) {
this.onPolygonComplete();
} else if (this.drawingMode === 'circle' && this.circleCenter && this.circleRadius > 0) {
this.drawCircle();
} else {
this.$message.error('请先完成围栏绘制');
}
},
// 在地图上加载围栏
loadFenceOnMap(fenceData) {
if (!fenceData || !this.fenceMap) return;
try {
const data = JSON.parse(fenceData);
if (data.type === 'polygon') {
this.polygonPoints = data.coordinates[0];
this.drawPolygon();
} else if (data.type === 'circle') {
this.circleCenter = data.center;
this.circleRadius = data.radius;
this.drawCircle();
}
} catch (error) {
console.error('加载围栏数据失败:', error);
}
},
// 计算两点间距离(米)
calculateDistance(lat1, lng1, lat2, lng2) {
const R = 6371000; // 地球半径(米)
const dLat = (lat2 - lat1) * Math.PI / 180;
const dLng = (lng2 - lng1) * Math.PI / 180;
const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
Math.sin(dLng/2) * Math.sin(dLng/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return R * c;
},
// 格式化坐标数据显示
formatCoordinates(fenceData) {
if (!fenceData) return '';
try {
const data = JSON.parse(fenceData);
if (data.type === 'polygon') {
return `多边形坐标点 (${data.coordinates[0].length}个点):\n` +
data.coordinates[0].map((point, index) =>
`${index + 1}. 经度: ${point[0].toFixed(6)}, 纬度: ${point[1].toFixed(6)}`
).join('\n');
} else if (data.type === 'circle') {
return `圆形围栏:\n` +
`圆心: 经度: ${data.center[0].toFixed(6)}, 纬度: ${data.center[1].toFixed(6)}\n` +
`半径: ${data.radius.toFixed(2)} 米`;
}
} catch (error) {
return '坐标数据格式错误';
}
return '';
},
// 格式化时间
formatTime(timestamp) {
if (!timestamp) return '';
const date = new Date(timestamp * 1000);
return date.toLocaleString('zh-CN');
},
// 分页处理
handleSizeChange(size) {
this.pagination.size = size;
this.pagination.page = 1;
this.loadFenceList();
},
handleCurrentChange(page) {
this.pagination.page = page;
this.loadFenceList();
},
// HTTP请求封装
async request(url, data = {}) {
return new Promise((resolve, reject) => {
$.ajax({
url: url,
type: 'POST',
data: data,
dataType: 'json',
success: function(response) {
resolve(response);
},
error: function(xhr, status, error) {
reject(error);
}
});
});
}
},
mounted() {
this.init();
},
beforeDestroy() {
// 清理地图资源
if (this.fenceMap) {
this.fenceMap.destroy();
}
}
});
</script>