@@ -0,0 +1,44 @@ | |||
import request from '@/utils/request' | |||
// 查询预警结果列表 | |||
export function queryList(query) { | |||
return request({ | |||
url: '/dk/attendanceGroup/list', | |||
method: 'get', | |||
params: query | |||
}) | |||
} | |||
// 查询预警结果详细 | |||
export function getOperationWarnresult(id) { | |||
return request({ | |||
url: '/dk/attendanceGroup/' + id, | |||
method: 'get' | |||
}) | |||
} | |||
// 新增预警结果 | |||
export function add(data) { | |||
return request({ | |||
url: '/dk/attendanceGroup/add', | |||
method: 'post', | |||
data: data | |||
}) | |||
} | |||
// 修改预警结果 | |||
export function updateOperationWarnresult(data) { | |||
return request({ | |||
url: '/system/operationWarnresult', | |||
method: 'put', | |||
data: data | |||
}) | |||
} | |||
// 删除预警结果 | |||
export function delOperationWarnresult(id) { | |||
return request({ | |||
url: '/system/operationWarnresult/' + id, | |||
method: 'delete' | |||
}) | |||
} |
@@ -0,0 +1,44 @@ | |||
import request from '@/utils/request' | |||
// 查询预警结果列表 | |||
export function queryList(query) { | |||
return request({ | |||
url: '/dk/config/list', | |||
method: 'get', | |||
params: query | |||
}) | |||
} | |||
// 查询预警结果详细 | |||
export function getOperationWarnresult(id) { | |||
return request({ | |||
url: '/system/operationWarnresult/' + id, | |||
method: 'get' | |||
}) | |||
} | |||
// 新增预警结果 | |||
export function addConfig(data) { | |||
return request({ | |||
url: '/dk/config/add', | |||
method: 'post', | |||
data: data | |||
}) | |||
} | |||
// 修改预警结果 | |||
export function updateConfig(data) { | |||
return request({ | |||
url: '/dk/config/updateConfig', | |||
method: 'put', | |||
data: data | |||
}) | |||
} | |||
// 删除 | |||
export function deleteConfig(id) { | |||
return request({ | |||
url: '/dk/config/deleteConfig/' + id, | |||
method: 'delete' | |||
}) | |||
} |
@@ -0,0 +1,44 @@ | |||
import request from '@/utils/request' | |||
// 查询预警结果列表 | |||
export function queryList(query) { | |||
return request({ | |||
url: '/dk/record/list', | |||
method: 'get', | |||
params: query | |||
}) | |||
} | |||
// 查询预警结果详细 | |||
export function getOperationWarnresult(id) { | |||
return request({ | |||
url: '/system/operationWarnresult/' + id, | |||
method: 'get' | |||
}) | |||
} | |||
// 新增预警结果 | |||
export function addOperationWarnresult(data) { | |||
return request({ | |||
url: '/system/operationWarnresult', | |||
method: 'post', | |||
data: data | |||
}) | |||
} | |||
// 修改预警结果 | |||
export function updateOperationWarnresult(data) { | |||
return request({ | |||
url: '/system/operationWarnresult', | |||
method: 'put', | |||
data: data | |||
}) | |||
} | |||
// 删除预警结果 | |||
export function delOperationWarnresult(id) { | |||
return request({ | |||
url: '/system/operationWarnresult/' + id, | |||
method: 'delete' | |||
}) | |||
} |
@@ -0,0 +1,179 @@ | |||
<template> | |||
<div class="map-container"> | |||
<div ref="mapContainer" class="map" :style="{ height: mapHeight }"></div> | |||
<div class="map-controls"> | |||
<div class="control-item"> | |||
<label>经度:</label> | |||
<input v-model="longitude" type="text" placeholder="请输入经度" /> | |||
</div> | |||
<div class="control-item"> | |||
<label>纬度:</label> | |||
<input v-model="latitude" type="text" placeholder="请输入纬度" /> | |||
</div> | |||
<button @click="updateMap">更新位置</button> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
export default { | |||
name: 'MapComponent', | |||
data() { | |||
return { | |||
map: null, | |||
marker: null, | |||
longitude: '116.397428', // 默认经度(北京) | |||
latitude: '39.90923', // 默认纬度(北京) | |||
mapHeight: '400px', | |||
amapKey: '你的高德地图API Key' // 替换为你的API Key | |||
}; | |||
}, | |||
mounted() { | |||
this.loadMapScript(); | |||
}, | |||
methods: { | |||
// 加载高德地图脚本 | |||
loadMapScript() { | |||
if (window.AMap) { | |||
this.initMap(); | |||
return; | |||
} | |||
const script = document.createElement('script'); | |||
script.src = `https://webapi.amap.com/maps?v=2.0&key=${this.amapKey}&callback=onAMapLoaded`; | |||
script.async = true; | |||
document.body.appendChild(script); | |||
// 定义地图加载完成后的回调 | |||
window.onAMapLoaded = () => { | |||
this.initMap(); | |||
}; | |||
}, | |||
// 初始化地图 | |||
initMap() { | |||
this.map = new window.AMap.Map(this.$refs.mapContainer, { | |||
zoom: 13, // 初始缩放级别 | |||
center: [this.longitude, this.latitude], // 初始中心点 | |||
resizeEnable: true | |||
}); | |||
// 添加默认标记 | |||
this.addMarker(); | |||
// 添加地图控件 | |||
this.map.addControl(new window.AMap.ToolBar()); | |||
this.map.addControl(new window.AMap.Zoom()); | |||
}, | |||
// 添加标记点 | |||
addMarker() { | |||
// 如果已有标记,则移除 | |||
if (this.marker) { | |||
this.marker.setMap(null); | |||
} | |||
// 创建新标记 | |||
this.marker = new window.AMap.Marker({ | |||
position: [this.longitude, this.latitude], | |||
map: this.map, | |||
title: '当前位置', | |||
draggable: true, // 允许拖动 | |||
animation: 'AMAP_ANIMATION_DROP' // 标记动画 | |||
}); | |||
// 添加信息窗口 | |||
const infoWindow = new window.AMap.InfoWindow({ | |||
content: `<p>坐标: ${this.latitude}, ${this.longitude}</p>`, | |||
offset: new window.AMap.Pixel(0, -30) | |||
}); | |||
// 点击标记显示信息窗口 | |||
this.marker.on('click', () => { | |||
infoWindow.open(this.map, this.marker.getPosition()); | |||
}); | |||
// 拖动结束更新坐标 | |||
this.marker.on('dragend', (e) => { | |||
const position = e.lnglat; | |||
this.longitude = position.lng; | |||
this.latitude = position.lat; | |||
}); | |||
// 地图居中到标记位置 | |||
this.map.setCenter([this.longitude, this.latitude]); | |||
}, | |||
// 更新地图位置 | |||
updateMap() { | |||
if (!this.map) return; | |||
// 验证经纬度 | |||
const lng = parseFloat(this.longitude); | |||
const lat = parseFloat(this.latitude); | |||
if (isNaN(lng) || isNaN(lat)) { | |||
alert('请输入有效的经纬度数值'); | |||
return; | |||
} | |||
this.addMarker(); | |||
} | |||
}, | |||
beforeDestroy() { | |||
// 销毁地图实例 | |||
if (this.map) { | |||
this.map.destroy(); | |||
} | |||
} | |||
}; | |||
</script> | |||
<style scoped> | |||
.map-container { | |||
margin: 20px; | |||
} | |||
.map { | |||
width: 100%; | |||
border: 1px solid #ddd; | |||
border-radius: 4px; | |||
margin-bottom: 10px; | |||
} | |||
.map-controls { | |||
display: flex; | |||
align-items: center; | |||
gap: 10px; | |||
} | |||
.control-item { | |||
display: flex; | |||
align-items: center; | |||
gap: 5px; | |||
} | |||
label { | |||
font-weight: bold; | |||
} | |||
input { | |||
padding: 5px; | |||
border: 1px solid #ddd; | |||
border-radius: 4px; | |||
width: 120px; | |||
} | |||
button { | |||
padding: 6px 12px; | |||
background-color: #409eff; | |||
color: white; | |||
border: none; | |||
border-radius: 4px; | |||
cursor: pointer; | |||
} | |||
button:hover { | |||
background-color: #3a8ee6; | |||
} | |||
</style> |
@@ -0,0 +1,630 @@ | |||
<template> | |||
<div class="attendance-group-container"> | |||
<div class="page-header"> | |||
<el-row> | |||
<el-col :span=24> | |||
<h1>考勤组配置</h1> | |||
<p>设置考勤规则、关联员工和打卡区域</p> | |||
</el-col> | |||
</el-row> | |||
</div> | |||
<div class="main-content"> | |||
<el-row :gutter="20"> | |||
<el-col :sm="24" :md="6"> | |||
<el-table :data="attendanceGroupData"> | |||
<el-table-column label="考勤组" align="center" prop="name" lable-size="100px"></el-table-column> | |||
<el-table-column label="描述" align="center" prop="description" ></el-table-column> | |||
<el-table-column label="考勤时间" align="center" prop="workDays" ></el-table-column> | |||
</el-table> | |||
</el-col> | |||
<el-col :sm="24" :md="18"> | |||
<div class="card-header"> | |||
<el-card class="group-form-card"> | |||
<template #header> | |||
<div class="card-header"> | |||
<h3>{{ isEditMode ? '编辑考勤组' : '新建考勤组' }}</h3> | |||
</div> | |||
</template> | |||
<el-form :model="attendanceGroup" :rules="rules" ref="groupForm" label-width="120px"> | |||
<!-- 基本信息 --> | |||
<el-form-item label="考勤组名称" prop="name"> | |||
<el-input v-model="attendanceGroup.name" placeholder="请输入考勤组名称"></el-input> | |||
</el-form-item> | |||
<el-form-item label="描述" prop="description"> | |||
<el-input v-model="attendanceGroup.description" placeholder="请输入描述信息"></el-input> | |||
</el-form-item> | |||
<!-- 考勤时间设置 --> | |||
<el-form-item label="工作日设置"> | |||
<el-checkbox-group v-model="attendanceGroup.workDays"> | |||
<el-checkbox v-for="day in workDays" :key="day.value" :label="day.value">{{ day.label }}</el-checkbox> | |||
</el-checkbox-group> | |||
</el-form-item> | |||
<el-form-item label="上班时间" prop="workStartTime"> | |||
<el-time-picker | |||
v-model="attendanceGroup.workStartTime" | |||
format="HH:mm" | |||
value-format="HH:mm" | |||
placeholder="选择上班时间" | |||
></el-time-picker> | |||
</el-form-item> | |||
<el-form-item label="迟到浮动" prop="lateRange"> | |||
<el-col :span="12"> | |||
<el-switch | |||
v-model="attendanceGroup.allowLate" | |||
active-text="允许" | |||
inactive-text="不允许" | |||
@change="handleLateChange" | |||
></el-switch> | |||
</el-col> | |||
<el-col :span="12" v-if="attendanceGroup.allowLate"> | |||
<el-slider | |||
v-model="attendanceGroup.lateRange" | |||
:min="1" | |||
:max="30" | |||
:step="1" | |||
show-tooltip | |||
@change="validateLateRange" | |||
></el-slider> | |||
<div class="slider-value">{{ attendanceGroup.lateRange }}分钟</div> | |||
</el-col> | |||
</el-form-item> | |||
<el-form-item label="下班时间" prop="workEndTime"> | |||
<el-time-picker | |||
v-model="attendanceGroup.workEndTime" | |||
format="HH:mm" | |||
value-format="HH:mm" | |||
placeholder="选择下班时间" | |||
></el-time-picker> | |||
</el-form-item> | |||
<el-form-item label="早退浮动" prop="earlyRange"> | |||
<el-col :span="12"> | |||
<el-switch | |||
v-model="attendanceGroup.allowEarly" | |||
active-text="允许" | |||
inactive-text="不允许" | |||
@change="handleEarlyChange" | |||
></el-switch> | |||
</el-col> | |||
<el-col :span="12" v-if="attendanceGroup.allowEarly"> | |||
<el-slider | |||
v-model="attendanceGroup.earlyRange" | |||
:min="1" | |||
:max="30" | |||
:step="1" | |||
show-tooltip | |||
@change="validateEarlyRange" | |||
></el-slider> | |||
<div class="slider-value">{{ attendanceGroup.earlyRange }}分钟</div> | |||
</el-col> | |||
</el-form-item> | |||
<!-- 打卡配置 --> | |||
<el-form-item label="打卡区域" prop="areaId"> | |||
<el-select | |||
v-model="attendanceGroup.areaId" | |||
placeholder="请选择打卡区域" | |||
> | |||
<el-option | |||
v-for="area in checkInAreas" | |||
:key="area.id" | |||
:label="area.name" | |||
:value="area.id" | |||
></el-option> | |||
</el-select> | |||
</el-form-item> | |||
<!-- <el-form-item label="打卡方式" prop="checkInType"> | |||
<el-radio-group v-model="attendanceGroup.checkInType"> | |||
<el-radio label="location">位置打卡</el-radio> | |||
<el-radio label="qr">二维码打卡</el-radio> | |||
<el-radio label="both">两者都可</el-radio> | |||
</el-radio-group> | |||
</el-form-item> --> | |||
<!-- 关联员工 --> | |||
<el-form-item label="关联员工"> | |||
<el-button | |||
type="primary" | |||
size="small" | |||
@click="openUserSelectionDialog" | |||
> | |||
<i class="el-icon-plus"></i> 选择员工 | |||
</el-button> | |||
<el-table | |||
:data="attendanceGroup.members" | |||
style="width: 100%; margin-top: 20px" | |||
empty-text="暂无关联员工" | |||
> | |||
<el-table-column prop="name" label="姓名"></el-table-column> | |||
<el-table-column prop="employeeId" label="工号"></el-table-column> | |||
<el-table-column prop="department" label="部门"></el-table-column> | |||
<el-table-column prop="position" label="职位"></el-table-column> | |||
<el-table-column label="操作" width="80"> | |||
<template #default="scope"> | |||
<el-button | |||
type="danger" | |||
size="mini" | |||
@click="removeMember(scope.row)" | |||
> | |||
<i class="el-icon-delete"></i> | |||
</el-button> | |||
</template> | |||
</el-table-column> | |||
</el-table> | |||
</el-form-item> | |||
<!-- 操作按钮 --> | |||
<el-form-item> | |||
<el-button type="primary" @click="saveGroup">保存</el-button> | |||
<el-button @click="cancelEdit">取消</el-button> | |||
</el-form-item> | |||
</el-form> | |||
</el-card> | |||
</div> | |||
</el-col> | |||
</el-row> | |||
</div> | |||
<!-- 员工选择对话框 --> | |||
<el-dialog | |||
title="选择员工" | |||
:visible.sync="userSelectionDialogVisible" | |||
width="60%" | |||
:before-close="handleCloseUserDialog" | |||
> | |||
<!-- <template #content> | |||
<div class="search-container"> | |||
<el-input | |||
v-model="userSearchKeyword" | |||
placeholder="搜索员工" | |||
prefix-icon="el-icon-search" | |||
@keyup.enter="searchUsers" | |||
></el-input> | |||
<el-button @click="searchUsers">搜索</el-button> | |||
</div> | |||
<el-table | |||
:data="filteredUsers" | |||
@selection-change="handleUserSelectionChange" | |||
> | |||
<el-table-column type="selection" width="55"></el-table-column> | |||
<el-table-column prop="name" label="姓名"></el-table-column> | |||
<el-table-column prop="employeeId" label="工号"></el-table-column> | |||
<el-table-column prop="department" label="部门"></el-table-column> | |||
<el-table-column prop="position" label="职位"></el-table-column> | |||
</el-table> | |||
</template> --> | |||
<template #footer> | |||
<el-button @click="userSelectionDialogVisible = false">取消</el-button> | |||
<el-button type="primary" @click="confirmUserSelection">确定</el-button> | |||
</template> | |||
<div class="search-container"> | |||
<el-input | |||
v-model="userSearchKeyword" | |||
placeholder="搜索员工" | |||
prefix-icon="el-icon-search" | |||
@keyup.enter="searchUsers" | |||
></el-input> | |||
<el-button @click="searchUsers">搜索</el-button> | |||
</div> | |||
<el-table | |||
:data="filteredUsers" | |||
@selection-change="handleUserSelectionChange" | |||
> | |||
<el-table-column type="selection" width="55"></el-table-column> | |||
<el-table-column prop="name" label="姓名"></el-table-column> | |||
<el-table-column prop="employeeId" label="工号"></el-table-column> | |||
<el-table-column prop="department" label="部门"></el-table-column> | |||
<el-table-column prop="position" label="职位"></el-table-column> | |||
</el-table> | |||
</el-dialog> | |||
</div> | |||
</template> | |||
<script> | |||
import { | |||
add, | |||
getOperationWarnresult, | |||
delOperationWarnresult, | |||
addOperationWarnresult, | |||
updateOperationWarnresult | |||
} from '@/api/daka/attendance-group' | |||
export default { | |||
name: 'AttendanceGroup', | |||
data() { | |||
return { | |||
// 考勤组数据 | |||
attendanceGroup: { | |||
id: null, | |||
name: '', | |||
description: '', | |||
workDays: ['mon', 'tue', 'wed', 'thu', 'fri'], | |||
workStartTime: '09:00', | |||
workEndTime: '18:00', | |||
allowLate: true, | |||
lateRange: 15, | |||
allowEarly: true, | |||
earlyRange: 15, | |||
areaId: null, | |||
checkInType: 'location', | |||
members: [] | |||
}, | |||
// 工作日选项 | |||
workDays: [ | |||
{ value: 'mon', label: '周一' }, | |||
{ value: 'tue', label: '周二' }, | |||
{ value: 'wed', label: '周三' }, | |||
{ value: 'thu', label: '周四' }, | |||
{ value: 'fri', label: '周五' }, | |||
{ value: 'sat', label: '周六' }, | |||
{ value: 'sun', label: '周日' } | |||
], | |||
// 员工列表 | |||
users: [], | |||
// 打卡区域列表 | |||
checkInAreas: [], | |||
// 搜索关键词 | |||
userSearchKeyword: '', | |||
// 选中的用户 | |||
selectedUsers: [], | |||
// 对话框状态 | |||
userSelectionDialogVisible: false, | |||
// 表单验证规则 | |||
rules: { | |||
name: [ | |||
{ required: true, message: '请输入考勤组名称', trigger: 'blur' } | |||
], | |||
workStartTime: [ | |||
{ required: true, message: '请选择上班时间', trigger: 'change' } | |||
], | |||
workEndTime: [ | |||
{ required: true, message: '请选择下班时间', trigger: 'change' } | |||
], | |||
areaId: [ | |||
{ required: true, message: '请选择打卡区域', trigger: 'change' } | |||
] | |||
}, | |||
// 编辑模式 | |||
isEditMode: false, | |||
// 提示信息 | |||
successMessage: '', | |||
successMessageVisible: false, | |||
attendanceGroupData: [{name: 'sssss', description: 'dddd', workDays: '[mon, tue, wed, thu, fri]'}] | |||
}; | |||
}, | |||
computed: { | |||
// 过滤后的用户列表 | |||
filteredUsers() { | |||
debugger | |||
// 获取不在当前考勤组中的用户 | |||
const groupMemberIds = this.attendanceGroup.members.map(member => member.id); | |||
const availableUsers = this.users.filter(user => | |||
!groupMemberIds.includes(user.id) | |||
); | |||
// 应用搜索过滤 | |||
if (!this.userSearchKeyword) return availableUsers; | |||
const keyword = this.userSearchKeyword.toLowerCase(); | |||
return availableUsers.filter(user => | |||
user.name.toLowerCase().includes(keyword) || | |||
user.employeeId.toLowerCase().includes(keyword) || | |||
user.department.toLowerCase().includes(keyword) || | |||
user.position.toLowerCase().includes(keyword) | |||
); | |||
} | |||
}, | |||
mounted() { | |||
// 加载模拟数据 | |||
this.loadMockData(); | |||
// 默认进入新建模式 | |||
this.resetForm(); | |||
}, | |||
methods: { | |||
// 加载模拟数据 | |||
loadMockData() { | |||
// 模拟员工数据 | |||
this.users = [ | |||
{ id: 1, name: '张三', employeeId: 'EMP001', department: '研发部', position: '高级工程师' }, | |||
{ id: 2, name: '李四', employeeId: 'EMP002', department: '研发部', position: '产品经理' }, | |||
{ id: 3, name: '王五', employeeId: 'EMP003', department: '销售部', position: '销售代表' }, | |||
{ id: 4, name: '赵六', employeeId: 'EMP004', department: '销售部', position: '销售经理' }, | |||
{ id: 5, name: '钱七', employeeId: 'EMP005', department: '财务部', position: '会计' }, | |||
{ id: 6, name: '孙八', employeeId: 'EMP006', department: '财务部', position: '财务总监' }, | |||
{ id: 7, name: '周九', employeeId: 'EMP007', department: '人力资源', position: 'HR专员' }, | |||
{ id: 8, name: '吴十', employeeId: 'EMP008', department: '人力资源', position: 'HR经理' }, | |||
{ id: 9, name: '郑十一', employeeId: 'EMP009', department: '市场部', position: '市场专员' }, | |||
{ id: 10, name: '王十二', employeeId: 'EMP010', department: '市场部', position: '市场总监' } | |||
]; | |||
// 模拟打卡区域数据 | |||
this.checkInAreas = [ | |||
{ id: 1, name: '总部办公楼', lng: 116.404, lat: 39.915, radius: 300, enabled: true }, | |||
{ id: 2, name: '销售办公室', lng: 116.414, lat: 39.905, radius: 200, enabled: true }, | |||
{ id: 3, name: '南区仓库', lng: 116.394, lat: 39.925, radius: 500, enabled: true } | |||
]; | |||
}, | |||
// 重置表单 | |||
resetForm() { | |||
this.attendanceGroup = { | |||
id: null, | |||
name: '', | |||
description: '', | |||
workDays: ['mon', 'tue', 'wed', 'thu', 'fri'], | |||
workStartTime: '09:00', | |||
workEndTime: '18:00', | |||
allowLate: true, | |||
lateRange: 15, | |||
allowEarly: true, | |||
earlyRange: 15, | |||
areaId: null, | |||
checkInType: 'location', | |||
members: [] | |||
}; | |||
this.isEditMode = false; | |||
}, | |||
// 打开编辑模式(示例:编辑第一个考勤组) | |||
openEditMode() { | |||
// 实际项目中,这里应该接收考勤组ID并从API获取数据 | |||
// 这里仅作示例,使用模拟数据 | |||
const exampleGroup = { | |||
id: 1, | |||
name: '研发部考勤组', | |||
description: '研发部门日常考勤规则', | |||
workDays: ['mon', 'tue', 'wed', 'thu', 'fri'], | |||
workStartTime: '09:00', | |||
workEndTime: '18:00', | |||
allowLate: true, | |||
lateRange: 15, | |||
allowEarly: true, | |||
earlyRange: 15, | |||
areaId: 1, | |||
checkInType: 'location', | |||
members: this.users.slice(0, 2) | |||
}; | |||
this.attendanceGroup = { ...exampleGroup }; | |||
this.isEditMode = true; | |||
}, | |||
// 保存考勤组 | |||
saveGroup() { | |||
this.$refs.groupForm.validate(valid => { | |||
if (valid) { | |||
// 这里应该调用API保存数据 | |||
// 示例代码仅做演示 | |||
this.attendanceGroup.workDaysList = this.attendanceGroup.workDays | |||
if (this.isEditMode) { | |||
console.log('更新考勤组:', this.attendanceGroup); | |||
this.showSuccessMessage('考勤组更新成功'); | |||
} else { | |||
console.log('创建考勤组:', this.attendanceGroup); | |||
this.showSuccessMessage('考勤组创建成功'); | |||
add(this.attendanceGroup).then((res) => { | |||
console.log(res) | |||
}) | |||
} | |||
// 重置表单 | |||
this.resetForm(); | |||
} else { | |||
console.log('表单验证失败'); | |||
return false; | |||
} | |||
}); | |||
}, | |||
// 取消编辑 | |||
cancelEdit() { | |||
this.resetForm(); | |||
}, | |||
// 打开员工选择对话框 | |||
openUserSelectionDialog() { | |||
this.userSelectionDialogVisible = true; | |||
this.selectedUsers = []; | |||
}, | |||
// 处理员工选择变化 | |||
handleUserSelectionChange(selection) { | |||
this.selectedUsers = selection; | |||
}, | |||
// 搜索用户 | |||
searchUsers() { | |||
// 搜索逻辑已在computed属性中实现 | |||
// 这里只需触发重新计算 | |||
}, | |||
// 确认选择员工 | |||
confirmUserSelection() { | |||
if (this.selectedUsers.length === 0) { | |||
this.$message.warning('请选择员工'); | |||
return; | |||
} | |||
// 添加选中的员工到考勤组 | |||
this.attendanceGroup.members = [ | |||
...this.attendanceGroup.members, | |||
...this.selectedUsers | |||
]; | |||
this.userSelectionDialogVisible = false; | |||
}, | |||
// 处理关闭员工对话框 | |||
handleCloseUserDialog(done) { | |||
if (this.selectedUsers.length > 0) { | |||
this.$confirm('确定要取消选择吗?', '提示', { | |||
type: 'warning' | |||
}).then(() => { | |||
done(); | |||
}).catch(() => { | |||
// 取消关闭 | |||
}); | |||
} else { | |||
done(); | |||
} | |||
}, | |||
// 移除员工 | |||
removeMember(member) { | |||
this.$confirm('确定要移除该员工吗?', '提示', { | |||
type: 'warning' | |||
}).then(() => { | |||
this.attendanceGroup.members = this.attendanceGroup.members.filter( | |||
m => m.id !== member.id | |||
); | |||
}).catch(() => { | |||
// 取消操作 | |||
}); | |||
}, | |||
// 处理迟到开关变化 | |||
handleLateChange(value) { | |||
if (!value) { | |||
this.attendanceGroup.lateRange = 0; | |||
} | |||
}, | |||
// 处理早退开关变化 | |||
handleEarlyChange(value) { | |||
if (!value) { | |||
this.attendanceGroup.earlyRange = 0; | |||
} | |||
}, | |||
// 验证迟到浮动范围 | |||
validateLateRange(value) { | |||
if (value > 30) { | |||
this.attendanceGroup.lateRange = 30; | |||
this.$message.warning('迟到浮动范围最大为30分钟'); | |||
} | |||
}, | |||
// 验证早退浮动范围 | |||
validateEarlyRange(value) { | |||
if (value > 30) { | |||
this.attendanceGroup.earlyRange = 30; | |||
this.$message.warning('早退浮动范围最大为30分钟'); | |||
} | |||
}, | |||
// 显示成功消息 | |||
showSuccessMessage(message) { | |||
this.successMessage = message; | |||
this.successMessageVisible = true; | |||
// 3秒后自动关闭 | |||
setTimeout(() => { | |||
this.successMessageVisible = false; | |||
}, 3000); | |||
} | |||
} | |||
}; | |||
</script> | |||
<style scoped> | |||
.equal-width .left, .equal-width .right { | |||
flex: 1; | |||
} | |||
.attendance-group-container { | |||
padding: 20px; | |||
} | |||
.page-header { | |||
margin-bottom: 30px; | |||
} | |||
.page-header h1 { | |||
font-size: 24px; | |||
font-weight: 600; | |||
margin-bottom: 10px; | |||
} | |||
.page-header p { | |||
color: #606266; | |||
} | |||
.main-content { | |||
display: flex; | |||
max-width: 800px; | |||
margin: 0 auto; | |||
} | |||
.group-form-card { | |||
border-radius: 8px; | |||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); | |||
} | |||
.card-header { | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
} | |||
.search-container { | |||
display: flex; | |||
gap: 10px; | |||
margin-bottom: 20px; | |||
} | |||
.slider-value { | |||
text-align: center; | |||
margin-top: 10px; | |||
color: #606266; | |||
} | |||
/* 表单验证错误提示样式 */ | |||
.el-form-item__error { | |||
padding-top: 4px; | |||
} | |||
/* 表格样式优化 */ | |||
.el-table { | |||
border-radius: 4px; | |||
} | |||
.el-table th { | |||
background-color: #f5f7fa; | |||
} | |||
/* 按钮样式优化 */ | |||
.el-button { | |||
transition: all 0.2s; | |||
} | |||
.el-button:hover { | |||
transform: translateY(-1px); | |||
} | |||
/* 消息提示样式 */ | |||
.el-message { | |||
top: 80px; | |||
} | |||
</style> |
@@ -0,0 +1,782 @@ | |||
<template> | |||
<div class="attendance-area-config"> | |||
<div class="page-header"> | |||
<h1>打卡区域配置</h1> | |||
<p>配置打卡区域,包括地点名称、经纬度和打卡半径</p> | |||
</div> | |||
<div class="content-wrapper"> | |||
<!-- 区域列表 --> | |||
<div class="area-list"> | |||
<div class="list-header"> | |||
<h2>区域列表</h2> | |||
<!-- <button @click="showAddModal = true" class="btn-add"> | |||
<i class="fa fa-plus"></i> 添加区域 | |||
</button> --> | |||
</div> | |||
<div class="area-table"> | |||
<table> | |||
<thead> | |||
<tr> | |||
<th>序号</th> | |||
<th>地点名称</th> | |||
<th>经纬度</th> | |||
<th>打卡半径(米)</th> | |||
<th>状态</th> | |||
<th>操作</th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
<tr v-for="(area, index) in areaList" :key="area.id"> | |||
<td>{{ index + 1 }}</td> | |||
<td>{{ area.name }}</td> | |||
<td>{{ area.lng }}, {{ area.lat }}</td> | |||
<td>{{ area.radius }}</td> | |||
<td> | |||
<span :class="area.enableStatus == '0' ? 'status-enabled' : 'status-disabled'"> | |||
{{ area.enableStatus == '0' ? '已启用' : '启用' }} | |||
</span> | |||
</td> | |||
<td> | |||
<button @click="editArea(area)" class="btn-edit"> | |||
<i class="fa fa-pencil"></i> 编辑 | |||
</button> | |||
<button | |||
@click="toggleAreaStatus(area)" | |||
class="btn-status" | |||
:disabled="area.enableStatus =='0'" | |||
> | |||
<i class="fa fa-toggle-on"></i> {{ area.enableStatus == '0' ? '已启用' : '启用' }} | |||
</button> | |||
<button | |||
@click="deleteArea(area)" | |||
class="btn-delete" | |||
:disabled="area.enableStatus == '0'" | |||
> | |||
<i class="fa fa-trash"></i> 删除 | |||
</button> | |||
</td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
<div v-if="areaList.length === 0" class="empty-state"> | |||
<i class="fa fa-map-o"></i> | |||
<p>暂无打卡区域,请点击上方"添加区域"按钮创建</p> | |||
</div> | |||
</div> | |||
</div> | |||
<!-- 地图选点区域 --> | |||
<div class="map-section"> | |||
<h2>地图选点</h2> | |||
<div class="map-controls"> | |||
<div class="control-group"> | |||
<label>地点名称:</label> | |||
<input v-model="formData.name" type="text" placeholder="请输入地点名称" /> | |||
</div> | |||
<div class="control-group"> | |||
<label>经度:</label> | |||
<input v-model="formData.lng" type="text" placeholder="请输入经度" /> | |||
</div> | |||
<div class="control-group"> | |||
<label>纬度:</label> | |||
<input v-model="formData.lat" type="text" placeholder="请输入纬度" /> | |||
</div> | |||
<div class="control-group"> | |||
<label>打卡半径(米):</label> | |||
<input v-model.number="formData.radius" type="number" min="10" max="5000" step="10" /> | |||
</div> | |||
<div class="radius-slider"> | |||
<label>调整半径:</label> | |||
<input | |||
type="range" | |||
min="10" | |||
max="5000" | |||
step="10" | |||
v-model.number="formData.radius" | |||
/> | |||
<span>{{ formData.radius }}米</span> | |||
</div> | |||
</div> | |||
<!-- <div ref="mapContainer" class="map" :style="{ height: mapHeight }"></div> --> | |||
<div class="map-actions"> | |||
<button @click="clearMapSelection" class="btn-clear"> | |||
<i class="fa fa-eraser"></i> 清除选择 | |||
</button> | |||
<button | |||
@click="saveArea" | |||
class="btn-save" | |||
:disabled="!isFormValid" | |||
> | |||
<i class="fa fa-save"></i> 保存区域 | |||
</button> | |||
</div> | |||
</div> | |||
</div> | |||
<!-- 确认删除模态框 --> | |||
<div v-if="showDeleteModal" class="modal-overlay"> | |||
<div class="modal-content"> | |||
<h3>确认删除</h3> | |||
<p>确定要删除 "{{ currentDeletingArea.name }}" 区域吗?</p> | |||
<div class="modal-actions"> | |||
<button @click="showDeleteModal = false" class="btn-cancel">取消</button> | |||
<button @click="confirmDeleteArea" class="btn-confirm">确认删除</button> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import { | |||
queryList, | |||
addConfig, | |||
deleteConfig, | |||
updateConfig | |||
} from '@/api/daka/daka-config' | |||
export default { | |||
name: 'AttendanceAreaConfig', | |||
data() { | |||
return { | |||
// 地图相关 | |||
map: null, | |||
marker: null, | |||
circle: null, | |||
mapHeight: '400px', | |||
baiduMapKey: '你的百度地图API Key', // 替换为你的API Key | |||
// 表单数据 | |||
formData: { | |||
id: null, | |||
name: '', | |||
lng: '116.404', // 默认经度(北京天安门) | |||
lat: '39.915', // 默认纬度(北京天安门) | |||
radius: 500, // 默认半径500米 | |||
enabled: false | |||
}, | |||
// 区域列表 | |||
areaList: [], | |||
// 模态框状态 | |||
showDeleteModal: false, | |||
currentDeletingArea: null | |||
}; | |||
}, | |||
computed: { | |||
// 表单验证 | |||
isFormValid() { | |||
return ( | |||
this.formData.name.trim() !== '' && | |||
!isNaN(parseFloat(this.formData.lng)) && | |||
!isNaN(parseFloat(this.formData.lat)) && | |||
this.formData.radius >= 10 && | |||
this.formData.radius <= 5000 | |||
); | |||
} | |||
}, | |||
mounted() { | |||
// 加载模拟数据 | |||
this.loadMockData(); | |||
// 加载地图 | |||
this.loadMapScript(); | |||
}, | |||
methods: { | |||
// 加载模拟数据 | |||
loadMockData() { | |||
// 这里应该从API获取数据,现在使用模拟数据 | |||
this.areaList = [ | |||
{ | |||
id: 1, | |||
name: '总部办公楼', | |||
lng: 116.404, | |||
lat: 39.915, | |||
radius: 300, | |||
enabled: true | |||
}, | |||
{ | |||
id: 2, | |||
name: '研发中心', | |||
lng: 116.414, | |||
lat: 39.905, | |||
radius: 200, | |||
enabled: false | |||
}, | |||
{ | |||
id: 3, | |||
name: '南区仓库', | |||
lng: 116.394, | |||
lat: 39.925, | |||
radius: 500, | |||
enabled: false | |||
} | |||
]; | |||
queryList().then((response) => { | |||
console.log(response) | |||
this.areaList = response | |||
}) | |||
}, | |||
// 加载百度地图脚本 | |||
loadMapScript() { | |||
if (window.BMapGL) { | |||
this.initMap(); | |||
return; | |||
} | |||
const script = document.createElement('script'); | |||
script.src = `https://api.map.baidu.com/api?type=webgl&v=1.0&ak=${this.baiduMapKey}`; | |||
script.onload = () => { | |||
this.initMap(); | |||
}; | |||
document.body.appendChild(script); | |||
}, | |||
// 初始化地图 | |||
initMap() { | |||
// 创建地图实例 | |||
this.map = new window.BMapGL.Map(this.$refs.mapContainer); | |||
// 设置中心点和缩放级别 | |||
const point = new window.BMapGL.Point(this.formData.lng, this.formData.lat); | |||
this.map.centerAndZoom(point, 15); | |||
// 启用滚轮缩放 | |||
this.map.enableScrollWheelZoom(true); | |||
// 添加常用控件 | |||
this.map.addControl(new window.BMapGL.NavigationControl()); | |||
this.map.addControl(new window.BMapGL.ScaleControl()); | |||
// 添加地图点击事件 | |||
this.map.addEventListener('click', (e) => { | |||
this.formData.lng = e.latlng.lng; | |||
this.formData.lat = e.latlng.lat; | |||
this.updateMapOverlay(); | |||
}); | |||
// 初始化地图覆盖物 | |||
this.updateMapOverlay(); | |||
}, | |||
// 更新地图覆盖物(标记点和圆形区域) | |||
updateMapOverlay() { | |||
if (!this.map) return; | |||
// 清除旧的覆盖物 | |||
if (this.marker) { | |||
this.map.removeOverlay(this.marker); | |||
} | |||
if (this.circle) { | |||
this.map.removeOverlay(this.circle); | |||
} | |||
// 创建新的标记点 | |||
const point = new window.BMapGL.Point(this.formData.lng, this.formData.lat); | |||
this.marker = new window.BMapGL.Marker(point, { | |||
icon: new window.BMapGL.Icon( | |||
'https://api.map.baidu.com/library/MassMarkers/1.2/src/images/m0.png', | |||
new window.BMapGL.Size(23, 25) | |||
) | |||
}); | |||
// 创建圆形区域 | |||
this.circle = new window.BMapGL.Circle(point, this.formData.radius, { | |||
strokeColor: '#409eff', | |||
strokeWeight: 2, | |||
strokeOpacity: 0.8, | |||
fillColor: '#409eff', | |||
fillOpacity: 0.2 | |||
}); | |||
// 添加覆盖物到地图 | |||
this.map.addOverlay(this.marker); | |||
this.map.addOverlay(this.circle); | |||
// 地图居中到标记位置 | |||
this.map.panTo(point); | |||
}, | |||
// 清除地图选择 | |||
clearMapSelection() { | |||
this.formData = { | |||
id: null, | |||
name: '', | |||
lng: '116.404', | |||
lat: '39.915', | |||
radius: 500, | |||
enabled: false | |||
}; | |||
if (this.map) { | |||
this.updateMapOverlay(); | |||
} | |||
}, | |||
// 保存区域 | |||
saveArea() { | |||
if (!this.isFormValid) return; | |||
const newArea = { | |||
id: this.formData.id || Date.now(), // 如果是新建,使用时间戳作为ID | |||
name: this.formData.name.trim(), | |||
lng: parseFloat(this.formData.lng), | |||
lat: parseFloat(this.formData.lat), | |||
radius: this.formData.radius, | |||
enabled: this.formData.enabled | |||
}; | |||
if (this.formData.id) { | |||
// 更新现有区域 | |||
const index = this.areaList.findIndex(area => area.id === this.formData.id); | |||
if (index !== -1) { | |||
this.areaList.splice(index, 1, newArea); | |||
} | |||
} else { | |||
// 添加新区域 | |||
this.areaList.push(newArea); | |||
} | |||
addConfig(this.formData).then((res)=>{ | |||
console.log(this.formData) | |||
console.log(res) | |||
}) | |||
// 重置表单 | |||
this.clearMapSelection(); | |||
}, | |||
// 编辑区域 | |||
editArea(area) { | |||
// 复制对象,避免直接修改原始数据 | |||
this.formData = { ...area }; | |||
// 更新地图显示 | |||
if (this.map) { | |||
this.updateMapOverlay(); | |||
} | |||
}, | |||
// 切换区域状态 | |||
toggleAreaStatus(area) { | |||
if (area.enableStatus == '0') { | |||
// 已启用的区域不允许禁用 | |||
alert('已启用的区域不允许禁用'); | |||
return; | |||
} | |||
// 启用区域 | |||
const index = this.areaList.findIndex(a => a.id === area.id); | |||
if (index !== -1) { | |||
this.areaList[index].enableStatus = '0'; | |||
} | |||
area.enableStatus = '0' | |||
updateConfig(area).then((res) => { | |||
console.log(res) | |||
}) | |||
}, | |||
// 删除区域 | |||
deleteArea(area) { | |||
if (area.enabled == '0') { | |||
// 已启用的区域不允许删除 | |||
alert('已启用的区域不允许删除'); | |||
return; | |||
} | |||
this.currentDeletingArea = area; | |||
this.showDeleteModal = true; | |||
}, | |||
// 确认删除区域 | |||
confirmDeleteArea() { | |||
if (!this.currentDeletingArea) return; | |||
this.areaList = this.areaList.filter( | |||
area => area.id !== this.currentDeletingArea.id | |||
); | |||
console.log(this.currentDeletingArea) | |||
deleteConfig(this.currentDeletingArea.id).then((res)=> { | |||
console.log(res) | |||
}) | |||
this.currentDeletingArea = null; | |||
this.showDeleteModal = false; | |||
} | |||
}, | |||
beforeDestroy() { | |||
// 销毁地图实例 | |||
if (this.map) { | |||
this.map.clearOverlays(); | |||
this.map.removeEventListener('click'); | |||
this.map = null; | |||
} | |||
} | |||
}; | |||
</script> | |||
<style scoped> | |||
.attendance-area-config { | |||
padding: 20px; | |||
font-family: Arial, sans-serif; | |||
max-width: 1200px; | |||
margin: 0 auto; | |||
} | |||
.page-header { | |||
text-align: center; | |||
margin-bottom: 30px; | |||
} | |||
.page-header h1 { | |||
color: #333; | |||
margin-bottom: 10px; | |||
} | |||
.page-header p { | |||
color: #666; | |||
} | |||
.content-wrapper { | |||
display: flex; | |||
gap: 20px; | |||
} | |||
.area-list { | |||
flex: 1; | |||
min-width: 400px; | |||
} | |||
.list-header { | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
margin-bottom: 15px; | |||
} | |||
.list-header h2 { | |||
margin: 0; | |||
color: #333; | |||
} | |||
.btn-add { | |||
background-color: #409eff; | |||
color: white; | |||
border: none; | |||
padding: 8px 12px; | |||
border-radius: 4px; | |||
cursor: pointer; | |||
transition: background-color 0.3s; | |||
} | |||
.btn-add:hover { | |||
background-color: #3a8ee6; | |||
} | |||
.area-table { | |||
border: 1px solid #ddd; | |||
border-radius: 4px; | |||
overflow: hidden; | |||
} | |||
.area-table table { | |||
width: 100%; | |||
border-collapse: collapse; | |||
} | |||
.area-table th, | |||
.area-table td { | |||
padding: 10px; | |||
text-align: left; | |||
border-bottom: 1px solid #eee; | |||
} | |||
.area-table th { | |||
background-color: #f5f7fa; | |||
color: #606266; | |||
font-weight: bold; | |||
} | |||
.area-table tr:last-child td { | |||
border-bottom: none; | |||
} | |||
.area-table tr:hover { | |||
background-color: #fafafa; | |||
} | |||
.status-enabled { | |||
display: inline-block; | |||
padding: 2px 6px; | |||
background-color: #e6f7ff; | |||
color: #1890ff; | |||
border-radius: 3px; | |||
font-size: 12px; | |||
} | |||
.status-disabled { | |||
display: inline-block; | |||
padding: 2px 6px; | |||
background-color: #f5f5f5; | |||
color: #999; | |||
border-radius: 3px; | |||
font-size: 12px; | |||
} | |||
.btn-edit, | |||
.btn-status, | |||
.btn-delete { | |||
padding: 5px 10px; | |||
margin-right: 5px; | |||
border: none; | |||
border-radius: 4px; | |||
cursor: pointer; | |||
transition: background-color 0.3s; | |||
font-size: 12px; | |||
} | |||
.btn-edit { | |||
background-color: #e6f7ff; | |||
color: #1890ff; | |||
} | |||
.btn-edit:hover { | |||
background-color: #d6efff; | |||
} | |||
.btn-status { | |||
background-color: #f6ffed; | |||
color: #52c41a; | |||
} | |||
.btn-status:hover { | |||
background-color: #e8ffd9; | |||
} | |||
.btn-status:disabled { | |||
background-color: #f5f5f5; | |||
color: #999; | |||
cursor: not-allowed; | |||
} | |||
.btn-delete { | |||
background-color: #fff2f0; | |||
color: #ff4d4f; | |||
} | |||
.btn-delete:hover { | |||
background-color: #ffe5e3; | |||
} | |||
.btn-delete:disabled { | |||
background-color: #f5f5f5; | |||
color: #999; | |||
cursor: not-allowed; | |||
} | |||
.empty-state { | |||
text-align: center; | |||
padding: 50px 20px; | |||
color: #999; | |||
} | |||
.empty-state i { | |||
font-size: 40px; | |||
margin-bottom: 10px; | |||
display: block; | |||
} | |||
.map-section { | |||
flex: 1; | |||
} | |||
.map-section h2 { | |||
margin-top: 0; | |||
color: #333; | |||
} | |||
.map-controls { | |||
display: flex; | |||
flex-wrap: wrap; | |||
gap: 15px; | |||
margin-bottom: 15px; | |||
} | |||
.control-group { | |||
display: flex; | |||
align-items: center; | |||
gap: 5px; | |||
min-width: 220px; | |||
} | |||
.control-group label { | |||
font-weight: bold; | |||
min-width: 80px; | |||
} | |||
.control-group input { | |||
flex: 1; | |||
padding: 6px 10px; | |||
border: 1px solid #ddd; | |||
border-radius: 4px; | |||
} | |||
.radius-slider { | |||
width: 100%; | |||
display: flex; | |||
align-items: center; | |||
gap: 10px; | |||
} | |||
.radius-slider input[type="range"] { | |||
flex: 1; | |||
} | |||
.map { | |||
border: 1px solid #ddd; | |||
border-radius: 4px; | |||
margin-bottom: 15px; | |||
} | |||
.map-actions { | |||
display: flex; | |||
gap: 10px; | |||
} | |||
.btn-clear, | |||
.btn-save { | |||
padding: 8px 15px; | |||
border: none; | |||
border-radius: 4px; | |||
cursor: pointer; | |||
transition: background-color 0.3s; | |||
display: flex; | |||
align-items: center; | |||
gap: 5px; | |||
} | |||
.btn-clear { | |||
background-color: #f5f5f5; | |||
color: #666; | |||
} | |||
.btn-clear:hover { | |||
background-color: #eee; | |||
} | |||
.btn-save { | |||
background-color: #409eff; | |||
color: white; | |||
} | |||
.btn-save:hover { | |||
background-color: #3a8ee6; | |||
} | |||
.btn-save:disabled { | |||
background-color: #e6e6e6; | |||
color: #999; | |||
cursor: not-allowed; | |||
} | |||
.modal-overlay { | |||
position: fixed; | |||
top: 0; | |||
left: 0; | |||
width: 100%; | |||
height: 100%; | |||
background-color: rgba(0, 0, 0, 0.5); | |||
display: flex; | |||
justify-content: center; | |||
align-items: center; | |||
z-index: 1000; | |||
} | |||
.modal-content { | |||
background-color: white; | |||
border-radius: 4px; | |||
padding: 20px; | |||
width: 300px; | |||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); | |||
} | |||
.modal-content h3 { | |||
margin-top: 0; | |||
color: #333; | |||
} | |||
.modal-actions { | |||
display: flex; | |||
justify-content: flex-end; | |||
gap: 10px; | |||
margin-top: 20px; | |||
} | |||
.btn-cancel, | |||
.btn-confirm { | |||
padding: 8px 15px; | |||
border: none; | |||
border-radius: 4px; | |||
cursor: pointer; | |||
transition: background-color 0.3s; | |||
} | |||
.btn-cancel { | |||
background-color: #f5f5f5; | |||
color: #666; | |||
} | |||
.btn-cancel:hover { | |||
background-color: #eee; | |||
} | |||
.btn-confirm { | |||
background-color: #409eff; | |||
color: white; | |||
} | |||
.btn-confirm:hover { | |||
background-color: #3a8ee6; | |||
} | |||
/* 响应式设计 */ | |||
@media (max-width: 900px) { | |||
.content-wrapper { | |||
flex-direction: column; | |||
} | |||
.area-list, | |||
.map-section { | |||
min-width: auto; | |||
} | |||
} | |||
@media (max-width: 500px) { | |||
.control-group { | |||
min-width: 100%; | |||
} | |||
.area-table th, | |||
.area-table td { | |||
padding: 8px 5px; | |||
font-size: 14px; | |||
} | |||
.btn-edit, | |||
.btn-status, | |||
.btn-delete { | |||
padding: 4px 6px; | |||
margin-right: 2px; | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,456 @@ | |||
<template> | |||
<div class="app-container"> | |||
<el-form | |||
v-show="showSearch" | |||
ref="queryForm" | |||
:model="queryParams" | |||
size="small" | |||
:inline="true" | |||
class="app-container--search" | |||
> | |||
<el-form-item label="员工姓名" prop="sysUserName"> | |||
<el-input | |||
v-model="queryParams.sysUserName" | |||
placeholder="请输入员工姓名" | |||
clearable | |||
@keyup.enter.native="handleQuery" | |||
/> | |||
</el-form-item> | |||
<el-form-item label="打卡状态" prop="checkInStatus"> | |||
<el-select | |||
v-model="queryParams.checkInStatus" | |||
placeholder="请选择打卡状态" | |||
clearable | |||
> | |||
<el-option | |||
v-for="dict in daka_check_in_status" | |||
:key="dict.value" | |||
:label="dict.label" | |||
:value="dict.value" | |||
/> | |||
</el-select> | |||
</el-form-item> | |||
<el-form-item> | |||
<el-button | |||
type="primary" | |||
icon="el-icon-search" | |||
size="mini" | |||
@click="handleQuery" | |||
>搜索</el-button> | |||
<el-button | |||
icon="el-icon-refresh" | |||
size="mini" | |||
@click="resetQuery" | |||
>重置</el-button> | |||
</el-form-item> | |||
</el-form> | |||
<el-row :gutter="10" class="mb8"> | |||
<!-- <el-col :span="1.5"> | |||
<el-button | |||
v-hasPermi="['system:operationWarnresult:remove']" | |||
type="danger" | |||
plain | |||
icon="el-icon-delete" | |||
size="mini" | |||
:disabled="multiple" | |||
@click="handleDelete" | |||
>删除</el-button> | |||
</el-col> --> | |||
<el-col :span="1.5"> | |||
<el-button | |||
v-hasPermi="['system:operationWarnresult:export']" | |||
type="warning" | |||
plain | |||
icon="el-icon-download" | |||
size="mini" | |||
@click="handleExport" | |||
>导出</el-button> | |||
</el-col> | |||
<el-radio-group v-model="queryParams.exportRange" class="ml-4"> | |||
<el-radio label="day" size="large">按日</el-radio> | |||
<el-radio label="month" size="large">按月</el-radio> | |||
</el-radio-group> | |||
<right-toolbar :show-search.sync="showSearch" @queryTable="getList" /> | |||
</el-row> | |||
<el-table | |||
v-loading="loading" | |||
:data="resultList" | |||
@selection-change="handleSelectionChange" | |||
> | |||
<!-- <el-table-column type="selection" width="55" align="center" /> --> | |||
<el-table-column v-if="false" label="主键ID" align="center" prop="id" /> | |||
<el-table-column | |||
label="员工姓名" | |||
align="center" | |||
prop="sysUserName" | |||
width="180" | |||
> | |||
</el-table-column> | |||
<el-table-column label="打卡时间" align="center" prop="checkInTime"> | |||
</el-table-column> | |||
<el-table-column | |||
width="150" | |||
label="打卡状态" | |||
align="center" | |||
prop="checkInStatus" | |||
> | |||
<template slot-scope="scope"> | |||
<span | |||
v-if="scope.row.checkInStatus == '0'" | |||
style=" | |||
display: inline-flex; | |||
align-items: center; | |||
justify-content: center; | |||
text-align: center; | |||
" | |||
> | |||
正常 | |||
</span> | |||
<span | |||
v-if="scope.row.checkInStatus == '1'" | |||
style=" | |||
display: inline-flex; | |||
align-items: center; | |||
justify-content: center; | |||
text-align: center; | |||
" | |||
> | |||
迟到 | |||
</span> | |||
<span | |||
v-if="scope.row.checkInStatus == '2'" | |||
style=" | |||
display: inline-flex; | |||
align-items: center; | |||
justify-content: center; | |||
text-align: center; | |||
" | |||
> | |||
缺卡 | |||
</span> | |||
</template> | |||
</el-table-column> | |||
<!-- <el-table-column | |||
label="操作" | |||
align="center" | |||
width="220" | |||
class-name="small-padding fixed-width" | |||
> | |||
<template slot-scope="scope"> | |||
<el-button | |||
v-if="scope.row.status == 0" | |||
v-hasPermi="['system:operationWarnresult:edit']" | |||
size="mini" | |||
type="text" | |||
icon="el-icon-circle-check" | |||
@click="handleDispose(scope.row)" | |||
>处理</el-button> | |||
<span | |||
v-else | |||
class="text-info" | |||
style="margin-right: 10px" | |||
>已处理</span> | |||
<el-button | |||
size="mini" | |||
type="text" | |||
icon="el-icon-camera" | |||
@click="handleView(scope.row)" | |||
>快照</el-button> | |||
<el-button | |||
v-hasPermi="['system:operationWarnresult:remove']" | |||
size="mini" | |||
type="text" | |||
icon="el-icon-delete" | |||
@click="handleDelete(scope.row)" | |||
>删除</el-button> | |||
</template> | |||
</el-table-column> --> | |||
</el-table> | |||
<pagination | |||
v-show="total > 0" | |||
:total="total" | |||
:page.sync="queryParams.pageNum" | |||
:limit.sync="queryParams.pageSize" | |||
@pagination="getList" | |||
/> | |||
<!-- 添加或修改对话框 --> | |||
<el-dialog :title="title" :visible.sync="open" width="960px" append-to-body> | |||
<div class="warn-result" v-html="warnResult" /> | |||
</el-dialog> | |||
</div> | |||
</template> | |||
<script> | |||
import { | |||
queryList, | |||
delOperationWarnresult, | |||
addOperationWarnresult, | |||
updateOperationWarnresult | |||
} from '@/api/daka/daka-record' | |||
import map from '@/views/components/daka/map'; | |||
export default { | |||
name: 'dakarecord', | |||
dicts: [ | |||
'zs_operation_warn_types', | |||
'zs_operation_status', | |||
'zs_operation_compare_status', | |||
'zs_operation_platform', | |||
'daka_check_in_status' | |||
], | |||
data() { | |||
return { | |||
// 按钮loading | |||
buttonLoading: false, | |||
// 遮罩层 | |||
loading: true, | |||
// 选中数组 | |||
ids: [], | |||
// 非单个禁用 | |||
single: true, | |||
// 非多个禁用 | |||
multiple: true, | |||
// 显示搜索条件 | |||
showSearch: true, | |||
// 总条数 | |||
total: 0, | |||
// 表格数据 | |||
resultList: [], | |||
// 弹出层标题 | |||
title: '', | |||
// 是否显示弹出层 | |||
open: false, | |||
// 图片快照时间范围 | |||
daterangeWarnTime: [], | |||
// 查询参数 | |||
queryParams: { | |||
pageNum: 1, | |||
pageSize: 10, | |||
sysUserName: undefined, | |||
checkInStatus: undefined, | |||
exportRange: undefined, | |||
}, | |||
// 表单参数 | |||
form: {}, | |||
// 结果 | |||
warnResult: '', | |||
// 表单校验 | |||
rules: { | |||
snapshotUrl: [ | |||
{ required: true, message: '图片快照不能为空', trigger: 'blur' } | |||
] | |||
}, | |||
daka_check_in_status: [{label: '正常', value: '0'},{label: '迟到', value: '1'},{label: '缺卡', value: '2'}] | |||
} | |||
}, | |||
created() { | |||
this.getList() | |||
}, | |||
methods: { | |||
/** 查询结果列表 */ | |||
getList() { | |||
this.loading = true | |||
queryList(this.queryParams).then((response) => { | |||
this.resultList = response.rows | |||
this.total = response.total | |||
this.loading = false | |||
}) | |||
}, | |||
// 取消按钮 | |||
cancel() { | |||
this.open = false | |||
this.reset() | |||
}, | |||
// 表单重置 | |||
reset() { | |||
this.form = { | |||
id: undefined, | |||
warnTime: undefined, | |||
goodsSkuName: undefined, | |||
goodsSkuSn: undefined, | |||
initPrice: undefined, | |||
curPrice: undefined, | |||
priceChangeRatio: undefined, | |||
warnTypes: undefined, | |||
warnContent: undefined, | |||
status: undefined, | |||
snapshotUrl: undefined | |||
} | |||
this.resetForm('form') | |||
}, | |||
/** 处理按钮操作 */ | |||
handleDispose(row) { | |||
const _html = ` | |||
<p class="warn-result-text">预警商品:<a href="${row.goodsSkuUrl}" target="_blank">${row.goodsSkuName}</a></p> | |||
<p class="warn-result-text">预警价格:${row.curPrice}</p> | |||
<p class="warn-result-text">预警时间:${row.warnTime}</p> | |||
<p class="warn-result-text">预警平台:${row.platform}</p> | |||
<p class="warn-result-text">预警备注:${row.remark}</p> | |||
<p class="warn-result-text">预警快照:<a href="${row.snapshotUrl}" target="_blank">点击查看</a></p> | |||
<p class="warn-result-tips">注:如果点击“稍后处理”,则预警结果将不会被处理,预警状态将不会被修改。如果点击“立即处理”,则当前商品基准价格将会被修改为预警价格,价格监控按照新的基准价格进行监控。</p> | |||
` | |||
this.$confirm(_html, '提示', { | |||
confirmButtonText: '立即处理', | |||
cancelButtonText: '稍后处理', | |||
dangerouslyUseHTMLString: true, | |||
closeOnClickModal: false, | |||
closeOnPressEscape: false, | |||
showClose: false, | |||
customClass: 'warn-result-message' | |||
}).then(() => { | |||
this.loading = true | |||
row.status = 1 | |||
updateOperationWarnresult(row) | |||
.then((response) => { | |||
this.$modal.msgSuccess('处理成功') | |||
this.open = false | |||
this.getList() | |||
}) | |||
.finally(() => { | |||
this.loading = false | |||
}) | |||
}) | |||
}, | |||
/** 搜索按钮操作 */ | |||
handleQuery() { | |||
this.queryParams.pageNum = 1 | |||
this.getList() | |||
}, | |||
/** 重置按钮操作 */ | |||
resetQuery() { | |||
this.daterangeWarnTime = [] | |||
this.resetForm('queryForm') | |||
this.handleQuery() | |||
}, | |||
// 多选框选中数据 | |||
handleSelectionChange(selection) { | |||
this.ids = selection.map((item) => item.id) | |||
this.single = selection.length !== 1 | |||
this.multiple = !selection.length | |||
}, | |||
handleView(row) { | |||
// 将状态设置为已处理 | |||
this.open = true | |||
this.title = '预警结果' | |||
this.warnResult = row.warnContent | |||
}, | |||
/** 提交按钮 */ | |||
submitForm() { | |||
this.$refs['form'].validate((valid) => { | |||
if (valid) { | |||
this.buttonLoading = true | |||
if (this.form.id != null) { | |||
updateOperationWarnresult(this.form) | |||
.then((response) => { | |||
this.$modal.msgSuccess('修改成功') | |||
this.open = false | |||
this.getList() | |||
}) | |||
.finally(() => { | |||
this.buttonLoading = false | |||
}) | |||
} else { | |||
addOperationWarnresult(this.form) | |||
.then((response) => { | |||
this.$modal.msgSuccess('新增成功') | |||
this.open = false | |||
this.getList() | |||
}) | |||
.finally(() => { | |||
this.buttonLoading = false | |||
}) | |||
} | |||
} | |||
}) | |||
}, | |||
/** 删除按钮操作 */ | |||
handleDelete(row) { | |||
const ids = row.id || this.ids | |||
this.$modal | |||
.confirm('是否确认删除预警结果编号为"' + ids + '"的数据项?') | |||
.then(() => { | |||
this.loading = true | |||
return delOperationWarnresult(ids) | |||
}) | |||
.then(() => { | |||
this.loading = false | |||
this.getList() | |||
this.$modal.msgSuccess('删除成功') | |||
}) | |||
.catch(() => {}) | |||
.finally(() => { | |||
this.loading = false | |||
}) | |||
}, | |||
/** 导出按钮操作 */ | |||
handleExport() { | |||
this.download( | |||
'dk/record/export', | |||
{ | |||
...this.queryParams | |||
}, | |||
`result_${new Date().getTime()}.xlsx` | |||
) | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss"> | |||
.warn-result table { | |||
width: 100% !important; | |||
} | |||
.warn-result table td a { | |||
display: none; | |||
} | |||
.warn-result table img { | |||
width: 100% !important; | |||
} | |||
.warn-result-message { | |||
.warn-result-text { | |||
font-size: 14px; | |||
color: #303133; | |||
a { | |||
color: #409eff; | |||
text-decoration: underline; | |||
} | |||
} | |||
.warn-result-tips { | |||
font-size: 12px; | |||
color: #999; | |||
margin-top: 10px; | |||
} | |||
} | |||
</style> | |||
<style lang="scss" scoped> | |||
.goods--info { | |||
display: flex; | |||
flex-direction: column; | |||
justify-content: center; | |||
text-align: left; | |||
.el-link { | |||
font-size: 14px; | |||
display: -webkit-box; | |||
-webkit-line-clamp: 2; | |||
line-clamp: 2; | |||
-webkit-box-orient: vertical; | |||
overflow: hidden; | |||
} | |||
em { | |||
font-size: 12px; | |||
color: #999; | |||
font-style: normal; | |||
} | |||
} | |||
</style> |