|
|
@@ -0,0 +1,523 @@
|
|
|
+<template>
|
|
|
+ <div class="overtime-apply">
|
|
|
+ <van-card class="form-card">
|
|
|
+ <template #header>
|
|
|
+ <div class="clearfix">
|
|
|
+ <span>加班申请</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <van-form ref="overtimeForm">
|
|
|
+ <!-- 加班日期 -->
|
|
|
+ <van-field
|
|
|
+ label="加班日期"
|
|
|
+ v-model="overtimeForm.overtimeDate"
|
|
|
+ readonly
|
|
|
+ clickable
|
|
|
+ placeholder="选择加班日期"
|
|
|
+ @click="showDatePicker = true"
|
|
|
+ :rules="[{ required: true, message: '请选择加班日期' }]"
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- 加班人员 -->
|
|
|
+ <van-field
|
|
|
+ label="加班人员"
|
|
|
+ v-model="overtimeForm.employees"
|
|
|
+ readonly
|
|
|
+ clickable
|
|
|
+ placeholder="请选择部门及人员"
|
|
|
+ @click="popupShow = true"
|
|
|
+ :rules="[{ required: true, message: '请选择加班人员' }]"
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- 用餐类别 -->
|
|
|
+ <van-field
|
|
|
+ label="用餐类别"
|
|
|
+ v-model="overtimeForm.mealTypeText"
|
|
|
+ readonly
|
|
|
+ clickable
|
|
|
+ placeholder="请选择用餐类别"
|
|
|
+ @click="showMealTypePicker = true"
|
|
|
+ :rules="[{ required: true, message: '请选择用餐类别' }]"
|
|
|
+ />
|
|
|
+
|
|
|
+ <van-popup v-model:show="showMealTypePicker" position="bottom" round closeable>
|
|
|
+ <div style="max-height: 80vh; overflow-y: auto; padding: 20px;">
|
|
|
+ <h3 style="font-size: 16px; margin-bottom: 15px;">选择餐型</h3>
|
|
|
+ <van-checkbox-group v-model="mealTypeIndex">
|
|
|
+ <van-checkbox
|
|
|
+ v-for="(item, index) in mealTypeColumns"
|
|
|
+ :key="index"
|
|
|
+ :name="item.value"
|
|
|
+ style="margin-bottom: 10px;"
|
|
|
+ >
|
|
|
+ {{ item.value }}
|
|
|
+ </van-checkbox>
|
|
|
+ </van-checkbox-group>
|
|
|
+
|
|
|
+ <div style="margin-top: 20px; display: flex; gap: 10px;">
|
|
|
+ <van-button type="default" block @click="showMealTypePicker = false">取消</van-button>
|
|
|
+ <van-button type="primary" block @click="onMealTypeSubmit">确定</van-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </van-popup>
|
|
|
+
|
|
|
+ <!-- 加班时段 -->
|
|
|
+ <van-field
|
|
|
+ label="加班时段"
|
|
|
+ v-model="timeTypeText"
|
|
|
+ readonly
|
|
|
+ clickable
|
|
|
+ placeholder="选择开始时间"
|
|
|
+ @click="showTimeTypePickerClick"
|
|
|
+ :rules="[{ required: true, message: '请选择加班时段' }]"
|
|
|
+ class="time-field-with-button"
|
|
|
+ >
|
|
|
+ </van-field>
|
|
|
+ <van-popup v-model:show="showTimeTypePicker" position="bottom">
|
|
|
+ <van-picker
|
|
|
+ v-model="timeTypeIndex"
|
|
|
+ :columns="timeTypeColumnsText"
|
|
|
+ @change="handleTimeTypeChange"
|
|
|
+ @confirm="handleTimeTypeConfirm"
|
|
|
+ @cancel="showTimeTypePicker = false"
|
|
|
+ />
|
|
|
+ </van-popup>
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ <!-- 工作内容 -->
|
|
|
+ <van-field
|
|
|
+ label="工作内容"
|
|
|
+ v-model="overtimeForm.workContent"
|
|
|
+ type="textarea"
|
|
|
+ :rows="4"
|
|
|
+ placeholder="请输入工作内容"
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- 提交按钮 -->
|
|
|
+ <div style="margin-top: 20px;">
|
|
|
+ <van-button type="primary" @click="submit" native-type="submit" block>提交申请</van-button>
|
|
|
+ <van-button @click="resetForm" plain block style="margin-top: 10px;">重置</van-button>
|
|
|
+ </div>
|
|
|
+ </van-form>
|
|
|
+ </van-card>
|
|
|
+
|
|
|
+
|
|
|
+ <!-- 弹出层选人 -->
|
|
|
+ <van-popup v-model="popupShow" round position="bottom" :style="{ height: '80%',background: '#F4F4F4' }" >
|
|
|
+ <ChooseSomeone ref="ChooseSomeoneOne" :groupView="this.groupViewNum" :groupViewBack="true" :peopleList="peopleList" :IsOverTime="true" :deptIdTree="deptIdTree" @ChooseSomeoneChanhe="chooseSomeoneChanhe" :peopleListId="peopleListId" ></ChooseSomeone>
|
|
|
+ </van-popup>
|
|
|
+
|
|
|
+ <!-- 加班日期 -->
|
|
|
+ <!-- 日期 -->
|
|
|
+ <van-calendar v-model="showDatePicker" ref="calendarRef" @confirm="dateOnConfirm" />
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+
|
|
|
+import axios from 'axios';
|
|
|
+import ChooseSomeone from '../../components/chooseSomeone.vue'
|
|
|
+import { Dialog, Toast } from 'vant';
|
|
|
+export default {
|
|
|
+
|
|
|
+name: 'OvertimeApply',
|
|
|
+
|
|
|
+ components: {
|
|
|
+ ChooseSomeone,
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {overtimeForm: {
|
|
|
+ overtimeDate: '', // 加班日期
|
|
|
+ employees: [], // 选中的员工ID数组
|
|
|
+ employeeIds: [], // 选中的员工ID数组
|
|
|
+ mealType: [], // 选中的用餐类别
|
|
|
+ mealTypeId: [], // 选中的用餐类别ID
|
|
|
+ timeType: '', // 选中的时间段ID
|
|
|
+ workContent: '', // 工作内容描述
|
|
|
+ startTime: '', // 加班开始时间
|
|
|
+ endTime: '', // 加班结束时间
|
|
|
+ duration: '' // 加班时长
|
|
|
+ },
|
|
|
+
|
|
|
+ // 用户信息和权限控制
|
|
|
+ user: JSON.parse(sessionStorage.getItem("user") || '{}'), // 当前登录用户信息
|
|
|
+ permissions: JSON.parse(sessionStorage.getItem("permissions") || '[]'), // 用户权限列表
|
|
|
+ hasManagePermission: false, // 是否有时间段管理权限
|
|
|
+
|
|
|
+ // 日期选择器控制
|
|
|
+ showDatePicker: false, // 日期选择器弹窗显示状态
|
|
|
+
|
|
|
+ // 部门及人员选择
|
|
|
+ popupShow: false, // 日期选择器弹窗显示状态
|
|
|
+
|
|
|
+
|
|
|
+ peopleList: [], // 可选人员列表
|
|
|
+ peopleListId: [],
|
|
|
+ deptIdTree: [],//部门及人员树状结构数据
|
|
|
+ groupViewNum:3,
|
|
|
+ peopleType:0,
|
|
|
+ //用餐类别
|
|
|
+ mealTypeText: '',
|
|
|
+ // 用餐类别选择器
|
|
|
+ showMealTypePicker: false,
|
|
|
+ // 用餐类别数据
|
|
|
+ mealTypeColumns: [
|
|
|
+ { value: '中餐', id: 0 },
|
|
|
+ { value: '晚餐', id: 1 },
|
|
|
+ { value: '夜宵', id: 2 }
|
|
|
+ ],
|
|
|
+ // 选中的用餐类别索引
|
|
|
+ mealTypeIndex: [],
|
|
|
+ //时间段
|
|
|
+ timeTypeText: '',
|
|
|
+ // 时间段选择器
|
|
|
+ showTimeTypePicker: false,
|
|
|
+ // 时间段编辑器
|
|
|
+ showTimeEditDialog: false,
|
|
|
+ // 时间段数据
|
|
|
+ timeTypeColumns: [
|
|
|
+ { text: '8:00-12:00', value: 1 },
|
|
|
+ { text: '12:00-16:00', value: 2 },
|
|
|
+ { text: '16:00-20:00', value: 3 }
|
|
|
+ ],
|
|
|
+ // 选中的时间段索引
|
|
|
+ timeTypeIndex: 0,
|
|
|
+ showTimeFormDialog: false,
|
|
|
+
|
|
|
+ // 添加编辑时间段的中转数据
|
|
|
+ timeForm: {
|
|
|
+ startTime: '',
|
|
|
+ endTime: '',
|
|
|
+ hours: ''
|
|
|
+ },
|
|
|
+
|
|
|
+ // 开始时间选择器弹窗显示状态
|
|
|
+ showStartPicker: false,
|
|
|
+
|
|
|
+ // 结束时间选择器弹窗显示状态
|
|
|
+ showEndPicker: false,
|
|
|
+
|
|
|
+ // 时间段弹窗显示状态
|
|
|
+ showTimeDialog: false,
|
|
|
+
|
|
|
+ //部门id
|
|
|
+ desId: null,
|
|
|
+
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ computed: {
|
|
|
+ timeTypeColumnsText() {
|
|
|
+ return this.timeTypeColumns.map(item => `${item.startTime} - ${item.endTime}, ${item.hours}小时`);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ this.getPeople()
|
|
|
+ this.getTimeTypeList();
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ // 监听开始时间和结束时间变化,自动计算时长
|
|
|
+ 'overtimeForm.startTime'(newVal) {
|
|
|
+ this.calculateDuration();
|
|
|
+ },
|
|
|
+ 'overtimeForm.endTime'(newVal) {
|
|
|
+ this.calculateDuration();
|
|
|
+ },
|
|
|
+ // 监听时间段表单的开始时间和结束时间变化,自动计算时长
|
|
|
+ 'timeForm.startTime'(newVal) {
|
|
|
+ this.calculateTimeFormDuration();
|
|
|
+ },
|
|
|
+ 'timeForm.endTime'(newVal) {
|
|
|
+ this.calculateTimeFormDuration();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ //时间选择相关
|
|
|
+ dateOnConfirm(date) {
|
|
|
+ this.overtimeForm.overtimeDate = this.formatDate(date);
|
|
|
+ this.showDatePicker = false;
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ formatDate(date) {
|
|
|
+ // 中国标准时间转成 YYYY-MM-DD
|
|
|
+ const year = date.getFullYear();
|
|
|
+ const month = date.getMonth() + 1;
|
|
|
+ const day = date.getDate();
|
|
|
+ return `${year}-${month < 10 ? '0' + month : month}-${day < 10 ? '0' + day : day}`;
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ // 选中人员
|
|
|
+ chooseSomeoneChanhe(item) {
|
|
|
+ console.log('当前点击的人员', item)
|
|
|
+ let arr = item.map(item => {
|
|
|
+ return item.id
|
|
|
+ })
|
|
|
+ let nameArr = item.map(item => {
|
|
|
+ return item.name
|
|
|
+ })
|
|
|
+ this.employeeIds = arr
|
|
|
+
|
|
|
+ this.overtimeForm.employeeIds = arr
|
|
|
+ this.overtimeForm.employees = nameArr
|
|
|
+ this.popupShow = false
|
|
|
+ this.$emit('flashList')
|
|
|
+ },
|
|
|
+ // 时间段选择器确认事件
|
|
|
+ handleTimeTypeConfirm() {
|
|
|
+ this.timeTypeText = this.timeTypeColumns.find(item => item.value == this.overtimeForm.timeType).text;
|
|
|
+ this.showTimeTypePicker = false;
|
|
|
+ },
|
|
|
+ // 重置表单
|
|
|
+ resetForm() {
|
|
|
+ this.overtimeForm = {
|
|
|
+ overtimeDate: '', // 加班日期
|
|
|
+ employees: [], // 选中的员工ID数组
|
|
|
+ mealType: [], // 选中的用餐类别
|
|
|
+ timeType: '', // 选中的时间段ID
|
|
|
+ workContent: '', // 工作内容描述
|
|
|
+ startTime: '', // 加班开始时间
|
|
|
+ endTime: '', // 加班结束时间
|
|
|
+ duration: '' // 加班时长
|
|
|
+ }
|
|
|
+ this.timeTypeText = ''
|
|
|
+ this.timeForm = {
|
|
|
+ startTime: '',
|
|
|
+ endTime: '',
|
|
|
+ hours: ''
|
|
|
+ }
|
|
|
+ this.mealTypeText = ''
|
|
|
+ this.mealTypeIndex = []
|
|
|
+
|
|
|
+ if (this.$refs.calendarRef && this.$refs.calendarRef.reset) {
|
|
|
+ this.$refs.calendarRef.reset();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ getPeople() {
|
|
|
+ this.$axios.post('/department/userListInMyRange', {
|
|
|
+ })
|
|
|
+ .then(res => {
|
|
|
+ if (res.code == "ok") {
|
|
|
+ let list = res.data;
|
|
|
+ let newList = JSON.parse(JSON.stringify(list))
|
|
|
+ if(newList && newList.length > 0) {
|
|
|
+ this.desId = newList[0].id
|
|
|
+ }
|
|
|
+ this.deptIdTree = list
|
|
|
+ console.log(this.peopleList)
|
|
|
+ } else {
|
|
|
+ JSON.parse()
|
|
|
+ this.$toast.clear();
|
|
|
+ this.$toast.fail(res.msg);
|
|
|
+ }
|
|
|
+ }).catch(err => { this.$toast.clear(); });
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ showTimeForm() {
|
|
|
+ // 显示添加时间段表单的逻辑
|
|
|
+ console.log("显示添加时间段表单")
|
|
|
+ },
|
|
|
+
|
|
|
+ // 用餐类别 提交选择
|
|
|
+ onMealTypeSubmit() {
|
|
|
+ this.overtimeForm.mealTypeText = this.mealTypeIndex.join(',')
|
|
|
+ this.overtimeForm.mealTypeId = this.mealTypeIndex.map(item => item.id)
|
|
|
+ this.mealTypeText = this.mealTypeIndex.join(',')
|
|
|
+ this.showMealTypePicker = false;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 加班时段选择器点击事件
|
|
|
+ showTimeTypePickerClick() {
|
|
|
+ this.showTimeTypePicker = true;
|
|
|
+ },
|
|
|
+
|
|
|
+ //时间段弹窗点击事件
|
|
|
+ handleTimeTypeChange(picker,value,index) {
|
|
|
+ console.log(value, index)
|
|
|
+ this.overtimeForm.startTime = this.timeTypeColumns[index].startTime
|
|
|
+ this.overtimeForm.endTime = this.timeTypeColumns[index].endTime
|
|
|
+ this.overtimeForm.timeType = this.timeTypeColumns[index].id
|
|
|
+ this.timeTypeText = this.overtimeForm.startTime + ' - ' + this.overtimeForm.endTime + ', ' + this.timeTypeColumns[index].hours + '小时';
|
|
|
+ this.showTimeTypePicker = false;
|
|
|
+ },
|
|
|
+
|
|
|
+ //加载时间段列表
|
|
|
+ getTimeTypeList() {
|
|
|
+ this.$axios.post('/overtime-setting/list',{
|
|
|
+ }).then(
|
|
|
+ res => {
|
|
|
+ if (res.code == "ok") {
|
|
|
+ this.timeTypeColumns = res.data
|
|
|
+ } else {
|
|
|
+
|
|
|
+ }
|
|
|
+ },
|
|
|
+ error => {
|
|
|
+ console.error('获取时间段列表失败:', error);
|
|
|
+ }
|
|
|
+ ).catch(err => { this.$toast.clear(); });
|
|
|
+ },
|
|
|
+
|
|
|
+ //时间段编辑
|
|
|
+ editTimeType(item) {
|
|
|
+ this.showTimeFormDialog = true;
|
|
|
+ this.timeForm = { ...item }; // 复制当前时间段数据到表单
|
|
|
+ this.showStartPicker = false;
|
|
|
+ this.showEndPicker = false;
|
|
|
+ },
|
|
|
+
|
|
|
+ //时间段删除
|
|
|
+ deleteTimeType(item) {
|
|
|
+ Dialog.confirm({
|
|
|
+ title: '确认删除',
|
|
|
+ message: `确定要删除该时间段吗?\n此操作不可撤销`,
|
|
|
+ messageAlign: 'left',
|
|
|
+ confirmButtonText: '删除',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ theme: 'round-button',
|
|
|
+ })
|
|
|
+ .then(() => {
|
|
|
+ // 用户点击“删除”
|
|
|
+ this.$axios.post('/overtime-setting/delete', {
|
|
|
+ id: item.id
|
|
|
+ }).then(res => {
|
|
|
+ if (res.code == "ok") {
|
|
|
+ this.getTimeTypeList()
|
|
|
+ Toast.success('时间段删除成功');
|
|
|
+ } else {
|
|
|
+ JSON.parse()
|
|
|
+ this.$toast.clear();
|
|
|
+ this.$toast.fail(res.msg);
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ // 用户点击“取消”,什么也不做
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 开始时间选择器确认事件
|
|
|
+ onStartConfirm(value) {
|
|
|
+ this.timeForm.startTime = value;
|
|
|
+ this.showStartPicker = false;
|
|
|
+ },
|
|
|
+ // 结束时间选择器确认事件
|
|
|
+ onEndConfirm(value) {
|
|
|
+ this.timeForm.endTime = value;
|
|
|
+ this.showEndPicker = false;
|
|
|
+ },
|
|
|
+
|
|
|
+ //提交申请
|
|
|
+ submit() {
|
|
|
+ if (
|
|
|
+ this.overtimeForm.overtimeDate && this.overtimeForm.overtimeDate.trim() !== '' &&
|
|
|
+ this.overtimeForm.employeeIds && this.overtimeForm.employeeIds.length > 0 &&
|
|
|
+ this.overtimeForm.mealTypeText && this.overtimeForm.mealTypeText.trim() !== '' &&
|
|
|
+ this.overtimeForm.timeType &&
|
|
|
+ this.overtimeForm.workContent && this.overtimeForm.workContent.trim() !== '' &&
|
|
|
+ this.overtimeForm.startTime && this.overtimeForm.startTime.trim() !== '' &&
|
|
|
+ this.overtimeForm.endTime && this.overtimeForm.endTime.trim() !== '' &&
|
|
|
+ this.overtimeForm.duration && this.overtimeForm.duration.trim() !== ''
|
|
|
+ ) {
|
|
|
+ // 所有必填项均已填写,继续提交
|
|
|
+ } else {
|
|
|
+ this.$toast.fail('请填写完整的加班申请信息');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 构建提交参数,映射到后端实体字段
|
|
|
+ const params = {
|
|
|
+ // applicant: 申请人 - 可以从用户信息中获取,这里暂时留空让后端处理
|
|
|
+ employeeIds: this.overtimeForm.employeeIds.join(','), // 加班员工ID列表,用逗号分隔
|
|
|
+ workDate: this.overtimeForm.overtimeDate, // 加班日期
|
|
|
+ startTime: this.overtimeForm.startTime, // 加班开始时间
|
|
|
+ endTime: this.overtimeForm.endTime, // 加班结束时间
|
|
|
+ hours: parseFloat(this.overtimeForm.duration), // 加班时长
|
|
|
+ content: this.overtimeForm.workContent, // 工作内容
|
|
|
+ timeTypeId: this.overtimeForm.timeType, // 加班时段ID
|
|
|
+ mealType: this.overtimeForm.mealTypeText, // 用餐类别,用逗号分隔
|
|
|
+ }
|
|
|
+ console.log(params)
|
|
|
+ this.$axios.post('/work-overtime/addOrUpdate', params)
|
|
|
+ .then(
|
|
|
+ res => {
|
|
|
+ if (res.code == "ok") {
|
|
|
+ console.log(res)
|
|
|
+ Toast.success('加班申请提交成功!');
|
|
|
+ this.resetForm(); // 重置表单
|
|
|
+ } else {
|
|
|
+ console.log(res)
|
|
|
+ Toast.fail(res.msg || '提交失败');
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ )
|
|
|
+ .catch(
|
|
|
+ error => {
|
|
|
+ console.log('catch 错误:', error)
|
|
|
+ Toast.fail('请求异常');
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ calculateDuration() {
|
|
|
+ if (this.overtimeForm.startTime && this.overtimeForm.endTime) {
|
|
|
+ const start = this.overtimeForm.startTime.split(':');
|
|
|
+ const end = this.overtimeForm.endTime.split(':');
|
|
|
+
|
|
|
+ const startDate = new Date();
|
|
|
+ startDate.setHours(start[0], start[1], 0);
|
|
|
+
|
|
|
+ const endDate = new Date();
|
|
|
+ endDate.setHours(end[0], end[1], 0);
|
|
|
+
|
|
|
+ // 处理跨天情况
|
|
|
+ if (endDate < startDate) {
|
|
|
+ endDate.setDate(endDate.getDate() + 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ const diff = (endDate - startDate) / (1000 * 60 * 60); // 转换为小时
|
|
|
+ this.overtimeForm.duration = diff.toFixed(2);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.overtime-apply {
|
|
|
+ padding: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.form-card {
|
|
|
+ margin-bottom: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.clearfix::after {
|
|
|
+ content: "";
|
|
|
+ display: table;
|
|
|
+ clear: both;
|
|
|
+}
|
|
|
+
|
|
|
+.van-table {
|
|
|
+ --van-table-row-height: 40px;
|
|
|
+}
|
|
|
+
|
|
|
+.time-field-with-button {
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.manage-time-btn {
|
|
|
+ position: absolute;
|
|
|
+ right: 10px;
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ z-index: 1;
|
|
|
+}
|
|
|
+</style>
|