新增钉钉考勤数据统计页面
This commit is contained in:
@@ -765,6 +765,44 @@ export function memberOrder(params) {
|
||||
});
|
||||
}
|
||||
|
||||
// 考勤打卡数据 配置获取
|
||||
export function attendanceConfig(params) {
|
||||
return request({
|
||||
url: `${Market_BaseUrl}/admin/attendance/config`,
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
// 考勤打卡数据 修改配置
|
||||
export function attendanceConfigPut(data) {
|
||||
return request({
|
||||
url: `${Market_BaseUrl}/admin/attendance/config`,
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
// 考勤打卡数据 列表
|
||||
export function attendanceList(params) {
|
||||
return request({
|
||||
url: `${Market_BaseUrl}/admin/attendance`,
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
// 考勤打卡数据 用户打卡详情
|
||||
export function attendanceDetail(params) {
|
||||
return request({
|
||||
url: `${Market_BaseUrl}/admin/attendance/detail`,
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
BIN
src/assets/dingding_img1.png
Normal file
BIN
src/assets/dingding_img1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
BIN
src/assets/dingding_img2.png
Normal file
BIN
src/assets/dingding_img2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 135 KiB |
BIN
src/assets/dingding_img3.png
Normal file
BIN
src/assets/dingding_img3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 220 KiB |
146
src/views/dingding/components/detailDialog.vue
Normal file
146
src/views/dingding/components/detailDialog.vue
Normal file
@@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<el-dialog title="查看详情" width="700px" v-model="visible" @closed="closedHandle">
|
||||
<div class="content" v-loading="loading">
|
||||
<div class="top">
|
||||
<div class="row">
|
||||
<div class="item">
|
||||
姓名:{{ detail.attendanceSummary.name }}
|
||||
</div>
|
||||
<div class="item">
|
||||
休息天数:{{ detail.attendanceSummary.restDays || '-' }}
|
||||
</div>
|
||||
<div class="item">
|
||||
出勤天数:{{ detail.attendanceSummary.attendDays || '-' }}
|
||||
</div>
|
||||
<div class="item">
|
||||
工作时长(分钟):{{ detail.attendanceSummary.workDuration || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" style="margin-top: 30px;">
|
||||
<div class="item">
|
||||
迟到次数:{{ detail.attendanceSummary.lateCount || '-' }}
|
||||
</div>
|
||||
<div class="item">
|
||||
迟到时长(分钟):{{ detail.attendanceSummary.lateDuration || '-' }}
|
||||
</div>
|
||||
<div class="item">
|
||||
旷工天数:{{ detail.attendanceSummary.absenceDays || '-' }}
|
||||
</div>
|
||||
<div class="item">
|
||||
备注:无
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btm">
|
||||
<div class="btm_wrap">
|
||||
<div class="item t1">日期</div>
|
||||
<div class="item t1" v-for="(item, index) in detail.weekList" :key="index">{{ item.week }}</div>
|
||||
</div>
|
||||
<div class="btm_wrap">
|
||||
<div class="item t2">
|
||||
<span style="opacity: 0;">1</span>
|
||||
</div>
|
||||
<div class="item t2" v-for="(item, index) in detail.statusList">{{ item }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { attendanceDetail } from '@/api/coupon/index'
|
||||
|
||||
const visible = ref(false)
|
||||
|
||||
const detail = ref({
|
||||
weekList: [],
|
||||
statusList: [],
|
||||
attendanceSummary: {}
|
||||
})
|
||||
|
||||
function closedHandle() {
|
||||
detail.value.weekList = []
|
||||
detail.value.statusList = []
|
||||
detail.value.attendanceSummary = {}
|
||||
}
|
||||
|
||||
const row = ref(null)
|
||||
|
||||
function show(obj) {
|
||||
visible.value = true
|
||||
row.value = { ...obj }
|
||||
attendanceDetailAjax()
|
||||
}
|
||||
|
||||
// 获取用户打卡详情
|
||||
const loading = ref(false)
|
||||
async function attendanceDetailAjax() {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await attendanceDetail({
|
||||
userId: row.value.userId
|
||||
})
|
||||
detail.value = res
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
setTimeout(() => {
|
||||
loading.value = false
|
||||
}, 500);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
show
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.content {
|
||||
.top {
|
||||
.row {
|
||||
display: flex;
|
||||
|
||||
.item {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btm {
|
||||
--height: 100px;
|
||||
width: 100%;
|
||||
height: calc(var(--height) + 20px);
|
||||
overflow-x: auto;
|
||||
margin-top: 14px;
|
||||
|
||||
|
||||
.btm_wrap {
|
||||
white-space: nowrap;
|
||||
height: calc(var(--height) / 2);
|
||||
}
|
||||
|
||||
.item {
|
||||
display: inline-block;
|
||||
width: calc(var(--height));
|
||||
height: calc(var(--height) / 2);
|
||||
line-height: calc(var(--height) / 2);
|
||||
font-size: 14px;
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
padding-left: 14px;
|
||||
border-bottom: 1px solid #ececec;
|
||||
|
||||
&.t1 {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
&.t2 {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
149
src/views/dingding/components/settingDialog.vue
Normal file
149
src/views/dingding/components/settingDialog.vue
Normal file
@@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<el-dialog title="应用配置" width="600px" top="4vh" v-model="visible">
|
||||
<div class="content">
|
||||
<div class="row">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="120" label-position="right">
|
||||
<el-form-item label="Client ID" prop="dingAppKey">
|
||||
<el-input placeholder="请输入" v-model="form.dingAppKey" style="width: 300px;"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Client Secret" prop="dingAppSecret">
|
||||
<el-input placeholder="请输入" v-model="form.dingAppSecret" style="width: 300px;"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="title">如何获取Client ID和Client Secret</div>
|
||||
<div class="t">1. 登录钉钉开发者后台。</div>
|
||||
<div class="img">
|
||||
<el-image :src="img1" style="width: 268px; height: auto" :preview-src-list="imgList"
|
||||
:initial-index="0"></el-image>
|
||||
</div>
|
||||
<div class="t">2.进入已创建的打卡应用的详情页</div>
|
||||
<div class="img">
|
||||
<el-image :src="img2" style="width: 268px; height: auto" :preview-src-list="imgList"
|
||||
:initial-index="1"></el-image>
|
||||
</div>
|
||||
<div class="t">3.在详情页找到《凭证与基础信息》,再复制如下红框的两个内容,复制粘贴过来即可</div>
|
||||
<div class="img"><el-image :src="img3" style="width: 268px; height: auto" :preview-src-list="imgList"
|
||||
:initial-index="2"></el-image></div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitHandle" :loading="loading"> 确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { attendanceConfig, attendanceConfigPut } from '@/api/coupon/index'
|
||||
import img1 from '@/assets/dingding_img1.png'
|
||||
import img2 from '@/assets/dingding_img2.png'
|
||||
import img3 from '@/assets/dingding_img3.png'
|
||||
|
||||
const imgList = ref([
|
||||
img1,
|
||||
img2,
|
||||
img3
|
||||
])
|
||||
|
||||
const visible = ref(false)
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const formRef = ref(null)
|
||||
|
||||
const form = ref({
|
||||
dingAppKey: '',
|
||||
dingAppSecret: ''
|
||||
})
|
||||
|
||||
const rules = {
|
||||
dingAppKey: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入Client ID',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
dingAppSecret: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入Client Secret',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const emits = defineEmits(['success'])
|
||||
|
||||
// 提交保存
|
||||
function submitHandle() {
|
||||
formRef.value.validate(async vaild => {
|
||||
try {
|
||||
if (vaild) {
|
||||
loading.value = true
|
||||
await attendanceConfigPut(form.value)
|
||||
ElNotification({
|
||||
type: 'success',
|
||||
title: '注意',
|
||||
message: '保存成功'
|
||||
})
|
||||
visible.value = false
|
||||
emits('success')
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
setTimeout(() => {
|
||||
loading.value = false
|
||||
}, 500);
|
||||
})
|
||||
}
|
||||
|
||||
function show() {
|
||||
visible.value = true
|
||||
attendanceConfigAjax()
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
show
|
||||
})
|
||||
|
||||
// 获取配置
|
||||
async function attendanceConfigAjax() {
|
||||
try {
|
||||
const res = await attendanceConfig()
|
||||
form.value = res
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.content {
|
||||
.row {
|
||||
border-bottom: 1px solid #ececec;
|
||||
|
||||
.title {
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
padding: 14px 0;
|
||||
}
|
||||
|
||||
.t {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
padding-bottom: 14px;
|
||||
}
|
||||
|
||||
.img {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
159
src/views/dingding/index.vue
Normal file
159
src/views/dingding/index.vue
Normal file
@@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<div class="gyq_container">
|
||||
<div class="gyq_content">
|
||||
<div class="row">
|
||||
<el-form :model="queryForm" inline>
|
||||
<el-form-item>
|
||||
<el-date-picker style="width: 300px" v-model="times" type="daterange" range-separator="至"
|
||||
start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD" :disabled-date="disabledDate"
|
||||
@change="selectTimeChange"></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="queryForm.name" :maxlength="20" placeholder="请输入姓名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" :loading="tableData.loading" @click="searchHandle">搜索</el-button>
|
||||
<el-button icon="Refresh" :loading="tableData.loading" @click="resetHandle">重置</el-button>
|
||||
<el-button plain type="primary" @click="settingDialogRef.show()">应用配置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="row">
|
||||
<el-table :data="tableData.list" v-loading="tableData.loading" stripe border>
|
||||
<el-table-column label="姓名" prop="name"></el-table-column>
|
||||
<el-table-column label="出勤天数" prop="attendDays"></el-table-column>
|
||||
<el-table-column label="休息天数" prop="restDays"></el-table-column>
|
||||
<el-table-column label="工作时长(分钟)" prop="workDuration"></el-table-column>
|
||||
<el-table-column label="迟到次数" prop="lateCount"></el-table-column>
|
||||
<el-table-column label="迟到时长(分钟)" prop="lateDuration"></el-table-column>
|
||||
<el-table-column label="旷工天数" prop="absenceDays"></el-table-column>
|
||||
<!-- <el-table-column label="备注" prop="name"></el-table-column> -->
|
||||
<el-table-column label="操作" width="120">
|
||||
<template v-slot="scope">
|
||||
<el-button link type="primary" @click="detailDialogRef.show(scope.row)">查看详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<!-- <div class="row" style="margin-top: 14px;">
|
||||
<el-pagination v-model:current-page="tableData.page" v-model:page-size="tableData.size"
|
||||
:page-sizes="[10, 20, 50, 100, 500]" background layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="tableData.total" @size-change="handleSizeChange" @current-change="handleCurrentChange" />
|
||||
</div> -->
|
||||
</div>
|
||||
<settingDialog ref="settingDialogRef" @success="searchHandle" />
|
||||
<detailDialog ref="detailDialogRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import dayjs from 'dayjs'
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { attendanceList } from '@/api/coupon/index'
|
||||
import settingDialog from './components/settingDialog.vue'
|
||||
import detailDialog from './components/detailDialog.vue'
|
||||
|
||||
const settingDialogRef = ref(null)
|
||||
const detailDialogRef = ref(null)
|
||||
|
||||
const queryForm = reactive({
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
name: ''
|
||||
})
|
||||
|
||||
// 选择日期
|
||||
const times = ref([])
|
||||
|
||||
// 禁止选择最近一个月之外的日期(只能选择最近一个月内的日期)
|
||||
function disabledDate(date) {
|
||||
const d = dayjs(date)
|
||||
const start = dayjs().subtract(1, 'month').startOf('day')
|
||||
const end = dayjs().endOf('day')
|
||||
return d.isBefore(start) || d.isAfter(end)
|
||||
}
|
||||
|
||||
function selectTimeChange(e) {
|
||||
console.log(e);
|
||||
|
||||
if (e && e.length === 2) {
|
||||
// 确保范围在允许区间内
|
||||
const start = dayjs().subtract(1, 'month').startOf('day')
|
||||
const end = dayjs().endOf('day')
|
||||
const s = dayjs(e[0])
|
||||
const ed = dayjs(e[1])
|
||||
const finalStart = s.isBefore(start) ? start : s
|
||||
const finalEnd = ed.isAfter(end) ? end : ed
|
||||
queryForm.startTime = finalStart.format('YYYY-MM-DD 00:00:00')
|
||||
queryForm.endTime = finalEnd.format('YYYY-MM-DD 23:59:59')
|
||||
} else {
|
||||
queryForm.startTime = ''
|
||||
queryForm.endTime = ''
|
||||
}
|
||||
}
|
||||
|
||||
function searchHandle() {
|
||||
tableData.page = 1
|
||||
getTableData()
|
||||
}
|
||||
|
||||
function resetHandle() {
|
||||
times.value = []
|
||||
queryForm.startTime = ''
|
||||
queryForm.endTime = ''
|
||||
queryForm.name = ''
|
||||
|
||||
searchHandle()
|
||||
}
|
||||
|
||||
const tableData = reactive({
|
||||
loading: false,
|
||||
page: 1,
|
||||
size: 10,
|
||||
total: 0,
|
||||
list: [],
|
||||
})
|
||||
|
||||
// 分页大小发生变化
|
||||
function handleSizeChange(e) {
|
||||
tableData.size = e;
|
||||
getTableData();
|
||||
}
|
||||
|
||||
// 分页发生变化
|
||||
function handleCurrentChange(e) {
|
||||
tableData.page = e;
|
||||
getTableData();
|
||||
}
|
||||
|
||||
|
||||
// 活动考勤数据
|
||||
async function getTableData() {
|
||||
try {
|
||||
tableData.loading = true
|
||||
const res = await attendanceList(queryForm)
|
||||
tableData.list = res
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
setTimeout(() => {
|
||||
tableData.loading = false
|
||||
}, 500);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getTableData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.gyq_container {
|
||||
padding: 14px;
|
||||
|
||||
.gyq_content {
|
||||
padding: 14px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user