| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- 页颿 é¢åæä½æé® --> |
| | | <div class="page-header"> |
| | | <div class="title">çæ¬¡é
ç½®</div> |
| | | <div class="actions"> |
| | | <el-button type="primary" |
| | | @click="openForm('add')"> |
| | | <el-icon> |
| | | <Plus /> |
| | | </el-icon> |
| | | æ°å¢ç次 |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | <!-- æ¥è¯¢æ¡ä»¶ --> |
| | | <!-- <el-form :model="searchForm" |
| | | :inline="true" |
| | | class="search-form mb16"> |
| | | <el-form-item label="é¨é¨ï¼" |
| | | prop="countId"> |
| | | <el-tree-select v-model="searchForm.countId" |
| | | :data="deptOptions" |
| | | :props="{ value: 'id', label: 'label', children: 'children' }" |
| | | value-key="id" |
| | | placeholder="è¯·éæ©é¨é¨" |
| | | check-strictly |
| | | style="width: 200px" /> |
| | | </el-form-item> |
| | | <el-form-item label="å°ç¹ï¼" |
| | | prop="locationName"> |
| | | <el-input v-model="searchForm.locationName" |
| | | placeholder="请è¾å
¥å°ç¹åç§°" |
| | | clearable |
| | | style="width: 200px" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" |
| | | @click="fetchData"> |
| | | <el-icon> |
| | | <Search /> |
| | | </el-icon> |
| | | æç´¢ |
| | | </el-button> |
| | | <el-button @click="resetSearch"> |
| | | <el-icon> |
| | | <Refresh /> |
| | | </el-icon> |
| | | éç½® |
| | | </el-button> |
| | | </el-form-item> |
| | | </el-form> --> |
| | | <!-- çæ¬¡å表 --> |
| | | <el-card shadow="never" |
| | | class="mb16"> |
| | | <el-table :data="tableData" |
| | | border |
| | | v-loading="tableLoading" |
| | | height="calc(100vh - 18.5em)" |
| | | style="width: 100%" |
| | | row-key="id"> |
| | | <el-table-column type="index" |
| | | label="åºå·" |
| | | width="60" |
| | | align="center" /> |
| | | <el-table-column label="é¨é¨"> |
| | | <template #default="scope"> |
| | | {{ getDeptNameById(scope.row.sysDeptId) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="çæ¬¡"> |
| | | <template #default="scope"> |
| | | {{ getShiftNameByValue(scope.row.shift) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="locationName" |
| | | label="å°ç¹åç§°" /> |
| | | <el-table-column prop="longitude" |
| | | label="ç»åº¦" /> |
| | | <el-table-column prop="latitude" |
| | | label="纬度" /> |
| | | <el-table-column prop="radius" |
| | | label="æå¡èå´(m)" /> |
| | | <el-table-column prop="startAt" |
| | | label="ä¸çæ¶é´"> |
| | | <template #default="scope"> |
| | | {{ scope.row.startAt }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="endAt" |
| | | label="ä¸çæ¶é´"> |
| | | <template #default="scope"> |
| | | {{ scope.row.endAt }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" |
| | | width="180" |
| | | fixed="right" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" |
| | | size="small" |
| | | link |
| | | @click="openForm('edit', scope.row)">ç¼è¾</el-button> |
| | | <el-button type="danger" |
| | | size="small" |
| | | link |
| | | @click="handleDelete(scope.row.id)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <pagination :total="page.total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | @pagination="paginationChange" |
| | | class="mt10" /> |
| | | </el-card> |
| | | <!-- æ°å¢/ç¼è¾çæ¬¡å¼¹çª --> |
| | | <rule-form ref="ruleFormRef" |
| | | v-model="dialogVisible" |
| | | :operation-type="operationType" |
| | | :row="currentRow" |
| | | @close="dialogVisible = false; fetchData()" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { |
| | | Plus, |
| | | Edit, |
| | | Delete, |
| | | Search, |
| | | Refresh, |
| | | ArrowLeft, |
| | | } from "@element-plus/icons-vue"; |
| | | import Pagination from "@/components/Pagination/index.vue"; |
| | | import RuleForm from "./components/form.vue"; |
| | | import { deptTreeSelect } from "@/api/system/user.js"; |
| | | import { |
| | | getAttendanceRules, |
| | | deleteAttendanceRule, |
| | | } from "@/api/personnelManagement/attendanceRules.js"; |
| | | import { useDict } from "@/utils/dict"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | // è¡¨æ ¼æ°æ® |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | |
| | | // å页忰 |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | // æ¥è¯¢è¡¨å |
| | | const searchForm = reactive({ |
| | | countId: "", |
| | | locationName: "", |
| | | }); |
| | | |
| | | // é¨é¨é项 |
| | | const deptOptions = ref([]); |
| | | // è·åçæ¬¡åå
¸å¼ |
| | | const { shifts_list } = useDict("shifts_list"); |
| | | |
| | | // å¼¹çªæ§å¶ |
| | | const dialogVisible = ref(false); |
| | | const operationType = ref("add"); |
| | | const currentRow = ref({}); |
| | | const ruleFormRef = ref(); |
| | | |
| | | // æ ¼å¼åæ¶é´ |
| | | const formatTime = timestamp => { |
| | | if (!timestamp) return ""; |
| | | const date = new Date(timestamp); |
| | | return `${String(date.getHours()).padStart(2, "0")}:${String( |
| | | date.getMinutes() |
| | | ).padStart(2, "0")}`; |
| | | }; |
| | | |
| | | // æ ¹æ®ç次å¼è·åçæ¬¡åç§° |
| | | const getShiftNameByValue = value => { |
| | | if (!value) return ""; |
| | | const shift = shifts_list.value.find(item => item.value === value); |
| | | return shift ? shift.label : value; |
| | | }; |
| | | |
| | | // è·åé¨é¨å表 |
| | | 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; |
| | | }); |
| | | }; |
| | | |
| | | // æ ¹æ®é¨é¨IDæ¥æ¾é¨é¨åç§° |
| | | const getDeptNameById = (deptId, deptList = deptOptions.value) => { |
| | | for (const dept of deptList) { |
| | | if (dept.id === deptId) { |
| | | return dept.label; |
| | | } |
| | | if (dept.children && dept.children.length) { |
| | | const name = getDeptNameById(deptId, dept.children); |
| | | if (name) { |
| | | return name; |
| | | } |
| | | } |
| | | } |
| | | return ""; |
| | | }; |
| | | |
| | | // æ¥è¯¢ç次å表 |
| | | const fetchData = () => { |
| | | tableLoading.value = true; |
| | | getAttendanceRules({ ...page, ...searchForm }) |
| | | .then(res => { |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | // å页忴 |
| | | const paginationChange = pagination => { |
| | | page.current = pagination.page; |
| | | page.size = pagination.limit; |
| | | fetchData(); |
| | | }; |
| | | |
| | | // éç½®æç´¢ |
| | | const resetSearch = () => { |
| | | searchForm.countId = ""; |
| | | searchForm.locationName = ""; |
| | | fetchData(); |
| | | }; |
| | | |
| | | // æå¼è¡¨å |
| | | const openForm = (type, row = {}) => { |
| | | operationType.value = type; |
| | | currentRow.value = row; |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // å é¤ç次 |
| | | const handleDelete = id => { |
| | | ElMessageBox.confirm("ç¡®å®è¦å é¤è¿æ¡ç次åï¼", "å é¤ç¡®è®¤", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | deleteAttendanceRule([id]).then(() => { |
| | | ElMessage.success("å 餿å"); |
| | | fetchData(); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | // åæ¶å é¤ |
| | | }); |
| | | }; |
| | | |
| | | // åå§å |
| | | onMounted(() => { |
| | | fetchDeptOptions(); |
| | | fetchData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .page-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | |
| | | .title { |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | } |
| | | |
| | | .mb16 { |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .mt10 { |
| | | margin-top: 10px; |
| | | } |
| | | </style> |