<template>
|
<el-dialog v-model="dialogVisible"
|
:title="dialogTitle"
|
width="700px"
|
:close-on-click-modal="false">
|
<el-form ref="formRef"
|
:model="form"
|
:rules="rules"
|
label-width="120px"
|
class="mt8">
|
<!-- 部门选择 -->
|
<el-form-item label="部门"
|
prop="sysDeptId">
|
<el-tree-select v-model="form.sysDeptId"
|
:data="deptOptions"
|
:props="{ value: 'id', label: 'label', children: 'children' }"
|
value-key="id"
|
placeholder="请选择部门"
|
check-strictly
|
style="width: 100%"
|
:disabled="operationType === 'view'" />
|
</el-form-item>
|
<!-- 地点信息 -->
|
<!-- <el-form-item label="地点名称"
|
prop="locationName">
|
<el-input v-model="form.locationName"
|
placeholder="请输入地点名称"
|
:disabled="operationType === 'view'" />
|
</el-form-item> -->
|
<!-- 打卡范围 -->
|
<el-form-item label="打卡范围(m)"
|
prop="radius">
|
<el-input-number v-model="form.radius"
|
:min="10"
|
:max="1000"
|
:step="10"
|
placeholder="请输入打卡范围"
|
:disabled="operationType === 'view'" />
|
</el-form-item>
|
<!-- 高德地图选择 -->
|
<el-form-item label="打卡位置"
|
prop="longitude">
|
<div class="map-container">
|
<div class="map-header"
|
style="margin-bottom: 10px">
|
<el-button @click="getCurrentLocation">
|
<el-icon>
|
<Position />
|
</el-icon>
|
当前位置
|
</el-button>
|
<span style="margin-left: 10px; color: #909399;font-size: 12px;">点击地图选择位置</span>
|
</div>
|
<div id="map-container"
|
class="map"
|
ref="mapContainer"></div>
|
<div class="coordinates-info mt10">
|
<el-input v-model="form.longitude"
|
readonly
|
placeholder="经度"
|
style="width: 140px; margin-right: 10px" />
|
<el-input v-model="form.latitude"
|
readonly
|
placeholder="纬度"
|
style="width: 140px; margin-right: 10px" />
|
<!-- <el-input v-model="form.locationName"
|
placeholder="地点名称"
|
style="width: calc(100% - 290px)" /> -->
|
</div>
|
</div>
|
</el-form-item>
|
<el-form-item label="地点名称"
|
prop="locationName">
|
<el-input v-model="form.locationName"
|
:disabled="operationType === 'view'"
|
placeholder="请输入地点名称" />
|
</el-form-item>
|
<!-- 上下班时间 -->
|
<el-form-item label="上班时间"
|
prop="startAt">
|
<el-time-picker v-model="form.startAt"
|
format="HH:mm"
|
value-format="HH:mm"
|
placeholder="请选择上班时间"
|
:disabled="operationType === 'view'" />
|
</el-form-item>
|
<el-form-item label="下班时间"
|
prop="endAt">
|
<el-time-picker v-model="form.endAt"
|
format="HH:mm"
|
value-format="HH:mm"
|
placeholder="请选择下班时间"
|
:disabled="operationType === 'view'" />
|
</el-form-item>
|
</el-form>
|
<template #footer>
|
<span class="dialog-footer">
|
<el-button @click="dialogVisible = false">取消</el-button>
|
<el-button type="primary"
|
@click="submitForm"
|
v-if="operationType !== 'view'">
|
确定
|
</el-button>
|
</span>
|
</template>
|
</el-dialog>
|
</template>
|
|
<script setup>
|
import { ref, reactive, computed, watch, onMounted, nextTick } from "vue";
|
import { ElMessage } from "element-plus";
|
import { Position } from "@element-plus/icons-vue";
|
import { deptTreeSelect } from "@/api/system/user.js";
|
import { addAttendanceRule } from "@/api/personnelManagement/attendanceRules.js";
|
|
const props = defineProps({
|
modelValue: {
|
type: Boolean,
|
default: false,
|
},
|
operationType: {
|
type: String,
|
default: "add",
|
},
|
row: {
|
type: Object,
|
default: () => ({}),
|
},
|
});
|
|
const emit = defineEmits(["update:modelValue", "close"]);
|
|
const dialogVisible = computed({
|
get: () => props.modelValue,
|
set: val => emit("update:modelValue", val),
|
});
|
|
const dialogTitle = computed(() => {
|
if (props.operationType === "add") return "新增打卡规则";
|
if (props.operationType === "edit") return "编辑打卡规则";
|
return "查看打卡规则";
|
});
|
|
// 表单数据
|
const formRef = ref();
|
const form = reactive({
|
id: "",
|
sysDeptId: "",
|
locationName: "",
|
longitude: "",
|
latitude: "",
|
radius: 100,
|
startAt: "09:00",
|
endAt: "18:00",
|
});
|
|
// 表单验证规则
|
const rules = {
|
sysDeptId: [{ required: true, message: "请选择部门", trigger: "change" }],
|
locationName: [
|
{ required: true, message: "请输入地点名称", trigger: "blur" },
|
],
|
longitude: [{ required: true, message: "请选择打卡位置", trigger: "blur" }],
|
latitude: [{ required: true, message: "请选择打卡位置", trigger: "blur" }],
|
radius: [{ required: true, message: "请输入打卡范围", trigger: "blur" }],
|
startAt: [{ required: true, message: "请选择上班时间", trigger: "change" }],
|
endAt: [{ required: true, message: "请选择下班时间", trigger: "change" }],
|
};
|
|
// 部门选项
|
const deptOptions = ref([]);
|
|
// 地图相关
|
const mapContainer = ref(null);
|
let map = null;
|
let marker = null;
|
let circle = null;
|
|
// 获取部门列表
|
const fetchDeptOptions = () => {
|
deptTreeSelect().then(response => {
|
deptOptions.value = filterDisabledDept(
|
JSON.parse(JSON.stringify(response.data))
|
);
|
});
|
};
|
|
// 过滤禁用的部门
|
const filterDisabledDept = deptList => {
|
return deptList.filter(dept => {
|
if (dept.disabled) {
|
return false;
|
}
|
if (dept.children && dept.children.length) {
|
dept.children = filterDisabledDept(dept.children);
|
}
|
return true;
|
});
|
};
|
|
// 初始化地图
|
const initMap = () => {
|
nextTick(() => {
|
if (window.AMap && mapContainer.value) {
|
// 初始化地图
|
map = new window.AMap.Map(mapContainer.value, {
|
zoom: 16,
|
center: [116.397428, 39.90923], // 默认北京
|
});
|
|
// 添加控件
|
window.AMap.plugin(["AMap.ToolBar", "AMap.Scale"], function () {
|
map.addControl(new window.AMap.ToolBar());
|
map.addControl(new window.AMap.Scale());
|
});
|
|
// 添加标记
|
marker = new window.AMap.Marker({
|
position: [116.397428, 39.90923],
|
draggable: true,
|
cursor: "move",
|
title: "拖拽定位",
|
});
|
map.add(marker);
|
|
// 添加圆形范围
|
circle = new window.AMap.Circle({
|
center: [116.397428, 39.90923],
|
radius: form.radius,
|
strokeColor: "#3366FF",
|
strokeOpacity: 0.8,
|
strokeWeight: 2,
|
fillColor: "#3366FF",
|
fillOpacity: 0.2,
|
});
|
map.add(circle);
|
|
// 监听标记拖拽
|
marker.on("dragend", e => {
|
const position = e.lnglat;
|
const lng = position.getLng();
|
const lat = position.getLat();
|
form.longitude = lng;
|
form.latitude = lat;
|
updateCircle(position);
|
});
|
|
// 监听标记拖拽开始
|
marker.on("dragstart", () => {
|
map.setDefaultCursor("move");
|
});
|
|
// 监听标记拖拽结束
|
marker.on("dragend", () => {
|
map.setDefaultCursor("default");
|
});
|
|
// 监听地图点击
|
map.on("click", e => {
|
const position = e.lnglat;
|
const lng = position.getLng();
|
const lat = position.getLat();
|
form.longitude = lng;
|
form.latitude = lat;
|
updateMarker(position);
|
updateCircle(position);
|
});
|
|
// 尝试获取当前位置并设置为地图中心
|
if (navigator.geolocation && !form.longitude && !form.latitude) {
|
navigator.geolocation.getCurrentPosition(
|
position => {
|
console.log("获取到当前位置:", position);
|
const { longitude, latitude } = position.coords;
|
const currentPosition = [longitude, latitude];
|
map.setCenter(currentPosition);
|
updateMarker(currentPosition);
|
updateCircle(currentPosition);
|
form.longitude = longitude;
|
form.latitude = latitude;
|
},
|
error => {
|
console.log("获取位置失败,使用默认位置");
|
}
|
);
|
} else if (form.longitude && form.latitude) {
|
// 如果有数据,设置到地图
|
const position = [form.longitude, form.latitude];
|
map.setCenter(position);
|
updateMarker(position);
|
updateCircle(position);
|
}
|
}
|
});
|
};
|
|
// 更新标记位置
|
const updateMarker = position => {
|
if (marker) {
|
marker.setPosition(position);
|
}
|
};
|
|
// 更新圆形范围
|
const updateCircle = position => {
|
if (circle) {
|
circle.setCenter(position);
|
circle.setRadius(form.radius);
|
}
|
};
|
|
// 获取当前位置
|
const getCurrentLocation = () => {
|
if (navigator.geolocation) {
|
navigator.geolocation.getCurrentPosition(
|
position => {
|
const { longitude, latitude } = position.coords;
|
form.longitude = longitude;
|
form.latitude = latitude;
|
if (map) {
|
map.setCenter([longitude, latitude]);
|
updateMarker([longitude, latitude]);
|
updateCircle([longitude, latitude]);
|
}
|
|
// 逆地理编码获取地址
|
if (window.AMap) {
|
// 加载Geocoder插件
|
window.AMap.plugin("AMap.Geocoder", function () {
|
const geocoder = new window.AMap.Geocoder();
|
geocoder.getAddress([longitude, latitude], (status, result) => {
|
if (status === "complete" && result.regeocode) {
|
form.locationName = result.regeocode.formattedAddress;
|
}
|
});
|
});
|
}
|
},
|
error => {
|
ElMessage.error("获取位置失败,请手动选择");
|
}
|
);
|
} else {
|
ElMessage.error("浏览器不支持地理定位");
|
}
|
};
|
|
// 监听半径变化
|
watch(
|
() => form.radius,
|
newValue => {
|
if (circle) {
|
circle.setRadius(newValue);
|
}
|
}
|
);
|
|
// 监听弹窗显示
|
watch(
|
() => dialogVisible.value,
|
newValue => {
|
if (newValue) {
|
// 重置表单
|
Object.assign(form, {
|
id: "",
|
sysDeptId: "",
|
locationName: "",
|
longitude: "",
|
latitude: "",
|
radius: 100,
|
startAt: "09:00",
|
endAt: "18:00",
|
});
|
|
// 如果是编辑或查看,填充数据
|
if (props.operationType !== "add" && props.row.id) {
|
// 处理时间格式,确保是HH:mm格式
|
const rowData = { ...props.row };
|
if (rowData.startAt && rowData.startAt.includes(":")) {
|
rowData.startAt = rowData.startAt.split(":").slice(0, 2).join(":");
|
}
|
if (rowData.endAt && rowData.endAt.includes(":")) {
|
rowData.endAt = rowData.endAt.split(":").slice(0, 2).join(":");
|
}
|
Object.assign(form, rowData);
|
}
|
|
// 初始化地图
|
setTimeout(() => {
|
initMap();
|
}, 100);
|
}
|
}
|
);
|
|
// 提交表单
|
const submitForm = () => {
|
formRef.value.validate(valid => {
|
if (valid) {
|
const submitData = {
|
...form,
|
// 转换时间格式,确保只保留时分部分
|
startAt: form.startAt
|
? `${form.startAt.split(":").slice(0, 2).join(":")}`
|
: null,
|
endAt: form.endAt
|
? `${form.endAt.split(":").slice(0, 2).join(":")}`
|
: null,
|
};
|
|
if (props.operationType === "add") {
|
addAttendanceRule(submitData).then(() => {
|
ElMessage.success("新增成功");
|
emit("close");
|
});
|
} else if (props.operationType === "edit") {
|
addAttendanceRule(submitData).then(() => {
|
ElMessage.success("更新成功");
|
emit("close");
|
});
|
}
|
}
|
});
|
};
|
|
// 初始化
|
onMounted(() => {
|
fetchDeptOptions();
|
});
|
</script>
|
|
<style scoped lang="scss">
|
.map-container {
|
width: 100%;
|
}
|
|
.map {
|
width: 100%;
|
height: 400px;
|
border: 1px solid #e4e7ed;
|
}
|
|
.coordinates-info {
|
display: flex;
|
gap: 10px;
|
}
|
|
.coordinates-display {
|
padding: 10px;
|
background-color: #f5f7fa;
|
border-radius: 4px;
|
}
|
|
.mt10 {
|
margin-top: 10px;
|
}
|
|
.mt8 {
|
margin-top: 8px;
|
}
|
</style>
|