| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |