增加考勤管理页面

This commit is contained in:
BianLzhaoMin 2025-11-27 13:49:17 +08:00
parent 0ab6513828
commit c0c800bbc7
4 changed files with 799 additions and 0 deletions

View File

@ -0,0 +1,80 @@
import request from '@/utils/request'
// 获取工程和考勤机树形数据
export function getProjectAndMacTreeAPI() {
return request({
url: '/system/attMacManage/tree',
method: 'get',
})
}
// 根据考勤机编号获取人员列表
export function getPersonListByMacNoAPI(params) {
return request({
url: '/system/attMacManage/personList',
method: 'get',
params,
})
}
// 获取所有人员列表
export function getAllPersonListAPI(params) {
return request({
url: '/system/attMacManage/allPersonList',
method: 'get',
params,
})
}
// 删除人员
export function deletePersonAPI(id) {
return request({
url: '/system/attMacManage/deletePerson/' + id,
method: 'post',
})
}
// 批量删除人员
export function batchDeletePersonAPI(ids) {
return request({
url: '/system/attMacManage/batchDeletePerson',
method: 'post',
data: { ids },
})
}
// 下发
export function sendDownAPI(data) {
return request({
url: '/system/attMacManage/sendDown',
method: 'post',
data,
})
}
// 配置
export function configAPI(data) {
return request({
url: '/system/attMacManage/config',
method: 'post',
data,
})
}
// 刷新
export function refreshAPI(macNo) {
return request({
url: '/system/attMacManage/refresh',
method: 'post',
data: { macNo },
})
}
// 重启
export function restartAPI(macNo) {
return request({
url: '/system/attMacManage/restart',
method: 'post',
data: { macNo },
})
}

View File

@ -0,0 +1,159 @@
<template>
<el-card class="tree-card" shadow="hover">
<div slot="header" class="card-header">
<span>工程考勤机</span>
</div>
<div class="tree-container">
<div class="head-container">
<el-input
v-model="filterText"
placeholder="请输入关键字"
clearable
size="small"
prefix-icon="el-icon-search"
style="margin-bottom: 20px"
/>
</div>
<el-tree
:data="treeData"
:props="defaultProps"
:expand-on-click-node="false"
:filter-node-method="filterNode"
ref="tree"
node-key="id"
default-expand-all
highlight-current
@node-click="handleNodeClick"
>
<span class="custom-tree-node" slot-scope="{ node }">
<span>{{ node.label }}</span>
</span>
</el-tree>
</div>
</el-card>
</template>
<script>
import { getProjectAndMacTreeAPI } from '@/api/system/attMacManage.js'
export default {
name: 'LeftTree',
data() {
return {
filterText: '',
treeData: [],
defaultProps: {
children: 'children',
label: 'label',
},
}
},
watch: {
filterText(val) {
this.$refs.tree.filter(val)
},
},
created() {
this.getTreeData()
},
methods: {
//
async getTreeData() {
try {
const res = await getProjectAndMacTreeAPI()
if (res.code === 200) {
this.treeData = res.data || []
}
} catch (error) {
console.error('获取树形数据失败:', error)
// 使
this.treeData = [
{
id: '1',
label: '工程1',
level: 1,
children: [
{
id: '1-1',
label: '考勤机001',
macNo: 'MAC001',
level: 2,
},
{
id: '1-2',
label: '考勤机002',
macNo: 'MAC002',
level: 2,
},
],
},
{
id: '2',
label: '工程2',
level: 1,
children: [
{
id: '2-1',
label: '考勤机003',
macNo: 'MAC003',
level: 2,
},
],
},
]
}
},
//
filterNode(value, data) {
if (!value) return true
return data.label.indexOf(value) !== -1
},
//
handleNodeClick(data) {
this.$emit('node-click', data)
},
},
}
</script>
<style scoped lang="scss">
.tree-card {
height: calc(100vh - 120px);
display: flex;
flex-direction: column;
::v-deep .el-card__body {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
padding: 20px;
}
.card-header {
font-weight: 600;
font-size: 16px;
}
}
.tree-container {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
.head-container {
margin-bottom: 10px;
}
}
::v-deep .el-tree {
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
}
}
</style>

View File

@ -0,0 +1,294 @@
<template>
<el-card class="table-card" shadow="hover">
<div slot="header" class="card-header">
<span>人员列表</span>
</div>
<div class="table-container">
<TableModel
:formLabel="formLabel"
:columnsList="columnsList"
:request-api="requestApi"
:showOperation="true"
:isSelectShow="true"
:sendParams="sendParams"
ref="tableRef"
>
<template slot="btn">
<el-button
size="mini"
type="danger"
icon="el-icon-delete"
:disabled="selectedData.length === 0"
@click="handleBatchDelete"
>
批量删除
</el-button>
<el-button
size="mini"
type="primary"
icon="el-icon-download"
@click="handleSendDown"
>
下发
</el-button>
<el-button
size="mini"
type="primary"
icon="el-icon-setting"
@click="handleConfig"
>
配置
</el-button>
<el-button
size="mini"
type="success"
icon="el-icon-refresh"
@click="handleRefresh"
>
刷新
</el-button>
<el-button
size="mini"
type="warning"
icon="el-icon-switch-button"
@click="handleRestart"
>
重启
</el-button>
</template>
<template slot="handle" slot-scope="{ data }">
<el-button
size="mini"
type="danger"
icon="el-icon-delete"
@click="handleDelete(data)"
>
删除
</el-button>
</template>
</TableModel>
</div>
</el-card>
</template>
<script>
import TableModel from '@/components/TableModel'
import {
getPersonListByMacNoAPI,
getAllPersonListAPI,
deletePersonAPI,
batchDeletePersonAPI,
refreshAPI,
restartAPI,
} from '@/api/system/attMacManage.js'
export default {
name: 'RightTable',
components: {
TableModel,
},
props: {
macNo: {
type: String,
default: '',
},
},
data() {
return {
//
formLabel: [
{
f_label: '姓名',
f_model: 'name',
f_type: 'ipt',
f_width: '180px',
},
{
f_label: '身份证号',
f_model: 'idNumber',
f_type: 'ipt',
f_width: '180px',
},
],
//
columnsList: [
{
t_label: '姓名',
t_props: 'name',
},
{
t_label: '身份证号',
t_props: 'idNumber',
},
{
t_label: '手机号',
t_props: 'phone',
},
{
t_label: '工种',
t_props: 'postName',
},
{
t_label: '考勤机编号',
t_props: 'macNo',
},
{
t_label: '绑定时间',
t_props: 'bindTime',
t_width: '180',
},
],
}
},
computed: {
// 使API
requestApi() {
return this.macNo ? getPersonListByMacNoAPI : getAllPersonListAPI
},
//
sendParams() {
return {
macNo: this.macNo || '',
}
},
//
selectedData() {
return this.$refs.tableRef?.selectedData || []
},
},
watch: {
macNo: {
handler() {
//
this.$nextTick(() => {
if (this.$refs.tableRef) {
this.$refs.tableRef.getTableList()
}
})
},
immediate: true,
},
},
methods: {
//
getTableList() {
if (this.$refs.tableRef) {
this.$refs.tableRef.getTableList()
}
},
//
handleDelete(row) {
this.$modal
.confirm('确定要删除该人员吗?')
.then(() => {
deletePersonAPI(row.id)
.then((res) => {
if (res.code === 200) {
this.$modal.msgSuccess('删除成功')
this.getTableList()
this.$emit('refresh-tree')
}
})
.catch(() => {})
})
.catch(() => {})
},
//
handleBatchDelete() {
if (this.selectedData.length === 0) {
this.$modal.msgWarning('请选择要删除的数据')
return
}
this.$modal
.confirm(
'确定要删除选中的 ' +
this.selectedData.length +
' 条数据吗?',
)
.then(() => {
const ids = this.selectedData.map((item) => item.id)
batchDeletePersonAPI(ids)
.then((res) => {
if (res.code === 200) {
this.$modal.msgSuccess('批量删除成功')
this.getTableList()
this.$emit('refresh-tree')
}
})
.catch(() => {})
})
.catch(() => {})
},
//
handleSendDown() {
this.$parent.openSendDownDialog()
},
//
handleConfig() {
this.$parent.openConfigDialog()
},
//
handleRefresh() {
if (!this.macNo) {
this.$modal.msgWarning('请先选择考勤机')
return
}
refreshAPI(this.macNo)
.then((res) => {
if (res.code === 200) {
this.$modal.msgSuccess('刷新成功')
this.getTableList()
}
})
.catch(() => {})
},
//
handleRestart() {
if (!this.macNo) {
this.$modal.msgWarning('请先选择考勤机')
return
}
this.$modal
.confirm('确定要重启该考勤机吗?')
.then(() => {
restartAPI(this.macNo)
.then((res) => {
if (res.code === 200) {
this.$modal.msgSuccess('重启成功')
}
})
.catch(() => {})
})
.catch(() => {})
},
},
}
</script>
<style scoped lang="scss">
.table-card {
height: calc(100vh - 120px);
display: flex;
flex-direction: column;
::v-deep .el-card__body {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
padding: 20px;
}
.card-header {
font-weight: 600;
font-size: 16px;
}
}
.table-container {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
}
</style>

View File

@ -0,0 +1,266 @@
<template>
<div class="app-container">
<el-row :gutter="10">
<!-- 左侧树形组件 -->
<el-col :span="6" :xs="24">
<leftTree ref="leftTreeRef" @node-click="handleNodeClick" />
</el-col>
<!-- 右侧表格组件 -->
<el-col :span="18" :xs="24">
<rightTable
ref="rightTableRef"
:mac-no="selectedMacNo"
@refresh-tree="handleRefreshTree"
/>
</el-col>
</el-row>
<!-- 下发弹框 -->
<DialogModel
:dialogConfig="sendDownDialogConfig"
@closeDialogOuter="handleCloseSendDownDialog"
>
<template slot="outerContent">
<el-form
ref="sendDownFormRef"
:model="sendDownFormData"
:rules="sendDownFormRules"
label-width="120px"
>
<el-form-item label="下发内容:" prop="content">
<el-input
v-model="sendDownFormData.content"
type="textarea"
:autosize="{ minRows: 4, maxRows: 8 }"
placeholder="请输入下发内容"
clearable
maxlength="500"
show-word-limit
/>
</el-form-item>
</el-form>
<el-row class="dialog-footer-btn">
<el-button size="medium" @click="handleCloseSendDownDialog">
取消
</el-button>
<el-button
size="medium"
type="primary"
@click="handleSendDownSubmit"
>
确定
</el-button>
</el-row>
</template>
</DialogModel>
<!-- 配置弹框 -->
<DialogModel
:dialogConfig="configDialogConfig"
@closeDialogOuter="handleCloseConfigDialog"
>
<template slot="outerContent">
<el-form
ref="configFormRef"
:model="configFormData"
:rules="configFormRules"
label-width="120px"
>
<el-form-item label="配置项:" prop="configItem">
<el-input
v-model="configFormData.configItem"
type="textarea"
:autosize="{ minRows: 4, maxRows: 8 }"
placeholder="请输入配置项"
clearable
maxlength="500"
show-word-limit
/>
</el-form-item>
</el-form>
<el-row class="dialog-footer-btn">
<el-button size="medium" @click="handleCloseConfigDialog">
取消
</el-button>
<el-button
size="medium"
type="primary"
@click="handleConfigSubmit"
>
确定
</el-button>
</el-row>
</template>
</DialogModel>
</div>
</template>
<script>
import leftTree from './components/leftTree.vue'
import rightTable from './components/rightTable.vue'
import DialogModel from '@/components/DialogModel'
import { sendDownAPI, configAPI } from '@/api/system/attMacManage.js'
export default {
name: 'AttMacManage',
components: {
leftTree,
rightTable,
DialogModel,
},
data() {
return {
selectedMacNo: '', //
//
sendDownDialogConfig: {
outerVisible: false,
outerTitle: '下发',
outerWidth: '50%',
maxHeight: '80vh',
},
//
configDialogConfig: {
outerVisible: false,
outerTitle: '配置',
outerWidth: '50%',
maxHeight: '80vh',
},
//
sendDownFormData: {
content: '',
},
//
configFormData: {
configItem: '',
},
//
sendDownFormRules: {
content: [
{
required: true,
message: '请输入下发内容',
trigger: 'blur',
},
],
},
//
configFormRules: {
configItem: [
{
required: true,
message: '请输入配置项',
trigger: 'blur',
},
],
},
}
},
methods: {
//
handleNodeClick(data) {
if (data.level === 2) {
//
this.selectedMacNo = data.macNo || data.id
} else {
//
this.selectedMacNo = ''
}
//
this.$nextTick(() => {
if (this.$refs.rightTableRef) {
this.$refs.rightTableRef.getTableList()
}
})
},
//
handleRefreshTree() {
if (this.$refs.leftTreeRef) {
this.$refs.leftTreeRef.getTreeData()
}
},
//
openSendDownDialog() {
this.sendDownDialogConfig.outerVisible = true
this.resetSendDownForm()
},
//
handleCloseSendDownDialog() {
this.sendDownDialogConfig.outerVisible = false
this.resetSendDownForm()
},
//
resetSendDownForm() {
this.sendDownFormData = {
content: '',
}
if (this.$refs.sendDownFormRef) {
this.$refs.sendDownFormRef.resetFields()
}
},
//
handleSendDownSubmit() {
this.$refs.sendDownFormRef.validate((valid) => {
if (valid) {
const params = {
macNo: this.selectedMacNo,
content: this.sendDownFormData.content,
}
sendDownAPI(params)
.then((res) => {
if (res.code === 200) {
this.$modal.msgSuccess('下发成功')
this.handleCloseSendDownDialog()
}
})
.catch(() => {})
}
})
},
//
openConfigDialog() {
this.configDialogConfig.outerVisible = true
this.resetConfigForm()
},
//
handleCloseConfigDialog() {
this.configDialogConfig.outerVisible = false
this.resetConfigForm()
},
//
resetConfigForm() {
this.configFormData = {
configItem: '',
}
if (this.$refs.configFormRef) {
this.$refs.configFormRef.resetFields()
}
},
//
handleConfigSubmit() {
this.$refs.configFormRef.validate((valid) => {
if (valid) {
const params = {
macNo: this.selectedMacNo,
configItem: this.configFormData.configItem,
}
configAPI(params)
.then((res) => {
if (res.code === 200) {
this.$modal.msgSuccess('配置成功')
this.handleCloseConfigDialog()
}
})
.catch(() => {})
}
})
},
},
}
</script>
<style scoped lang="scss">
.dialog-footer-btn {
text-align: right;
padding-top: 20px;
}
</style>