Преглед изворни кода

考勤功能-补卡功能开发

zhonghuizhen пре 1 година
родитељ
комит
194b9794cc

+ 111 - 0
src/api/business/attendance.js

@@ -0,0 +1,111 @@
+import request from '@/utils/request'
+import { BUSINESS_BASE_URL } from '@/api/baseUrl'
+import { save } from '@/api/platform/message/innerMessage'
+
+/**
+ * 考勤明细-打卡
+ * @param {*} params
+ */
+export function attendanceDetailClockIn (params) {
+    return request({
+        url: BUSINESS_BASE_URL() + '/employee/attendanceDetail/clockIn',
+        method: 'post',
+        params
+    })
+}
+
+/**
+ * 考勤明细-获取
+ * @param {*} params
+ */
+export function getAttendanceDetail (params) {
+    return request({
+        url: BUSINESS_BASE_URL() + '/employee/attendanceDetail/get',
+        method: 'get',
+        params
+    })
+}
+
+/**
+ * 考勤明细-查询
+ * @param {*} params
+ */
+export function queryAttendanceDetail (data) {
+    return request({
+        url: BUSINESS_BASE_URL() + '/employee/attendanceDetail/query',
+        method: 'post',
+        data
+    })
+}
+
+/**
+ * 考勤明细-保存
+ * @param {*} params
+ */
+export function saveAttendanceDetail (data) {
+    return request({
+        url: BUSINESS_BASE_URL() + '/employee/attendanceDetail/save',
+        method: 'post',
+        data
+    })
+}
+
+/**
+ * 考勤明细-删除
+ * @param {*} params
+ */
+export function removeAttendanceDetail (params) {
+    return request({
+        url: BUSINESS_BASE_URL() + '/employee/attendanceDetail/remove',
+        method: 'post',
+        params
+    })
+}
+
+/**
+ * 补卡记录-查询
+ * @param {*} params
+ */
+export function queryAttendanceReissue (data) {
+    return request({
+        url: BUSINESS_BASE_URL() + '/employee/attendanceReissue/query',
+        method: 'post',
+        data
+    })
+}
+
+/**
+ * 补卡记录-保存
+ * @param {*} params
+ */
+export function saveAttendanceReissue (data) {
+    return request({
+        url: BUSINESS_BASE_URL() + '/employee/attendanceReissue/save',
+        method: 'post',
+        data
+    })
+}
+
+/**
+ * 补卡记录-删除
+ * @param {*} params
+ */
+export function removeAttendanceReissue (params) {
+    return request({
+        url: BUSINESS_BASE_URL() + '/employee/attendanceReissue/remove',
+        method: 'post',
+        params
+    })
+}
+
+/**
+ * 补卡记录-获取
+ * @param {*} params
+ */
+export function getAttendanceReissue (params) {
+    return request({
+        url: BUSINESS_BASE_URL() + '/employee/attendanceReissue/get',
+        method: 'get',
+        params
+    })
+}

+ 204 - 0
src/views/business/attendance/makeUPVerify.vue

@@ -0,0 +1,204 @@
+<template>
+    <div class="main-container">
+        <ibps-crud
+            ref="crud"
+            :display-field="title"
+            :height="height"
+            :data="listData"
+            :toolbars="listConfig.toolbars"
+            :search-form="listConfig.searchForm"
+            :pk-key="pkKey"
+            :columns="listConfig.columns"
+            :row-handle="listConfig.rowHandle"
+            :pagination="pagination"
+            :loading="loading"
+            @action-event="handleAction"
+            @sort-change="handleSortChange"
+            @pagination-change="handlePaginationChange"
+            @row-dblclick="handleRowDblclick"
+        >
+        </ibps-crud>
+    </div>
+</template>
+
+<script>
+import ActionUtils from '@/utils/action'
+import FixHeight from '@/mixins/height'
+import IbpsExport from '@/plugins/export'
+import color from '@/store/modules/ibps/modules/color'
+
+export default {
+    mixins: [FixHeight],
+    data () {
+        const { userList = [], deptList = [] } = this.$store.getters || {}
+        const userOption = userList.map(item => ({ label: item.userName, value: item.userId }))
+        const deptOption = deptList.map(item => ({ label: item.positionName, value: item.positionId }))
+        return {
+            userOption,
+            deptOption,
+            title: '补卡审核',
+            pkKey: 'id_', // 主键对应数据库字段
+            loading: true,
+            height: document.clientHeight,
+            listData: [],
+            pagination: {
+                currentPage: 1,
+                limit: 20
+            },
+            sorts: {},
+            listConfig: {
+                toolbars: [
+                    { key: 'search', icon: 'ibps-icon-search', label: '查询', type: 'primary' }
+                ],
+                searchForm: {
+                    labelWidth: 100,
+                    forms: [
+                        { prop: 'Q^bian_zhi_ren_^S', label: '申请人', fieldType: 'select', options: userOption },
+                        { prop: ['Q^bian_zhi_shi_jian^DL', 'Q^bian_zhi_shi_jian^DG'], label: '申请时间', fieldType: 'daterange' },
+                        { prop: 'Q^zhuang_tai_^SL', label: '状态', fieldType: 'select', options: [{ value: '待审核', label: '待审核' }, { value: '未通过', label: '未通过' }, { value: '已通过', label: '已通过' }] },
+                        { prop: ['Q^bu_ka_ri_qi_^DL', 'Q^bu_ka_ri_qi_^DG'], label: '补卡日期', fieldType: 'daterange' },
+                        { prop: 'Q^bu_ka_ban_ci_^SL', label: '补卡班次' },
+                        { prop: 'Q^bu_ka_shi_you_^SL', label: '补卡事由' }
+                    ]
+                },
+                columns: [
+                    { prop: 'bianZhiRen', label: '申请人', tags: userOption, width: 80 },
+                    { prop: 'bianZhiShiJian', label: '申请时间', dateFormat: 'yyyy-MM-dd HH:mm', sortable: 'custom', width: 140 },
+                    // { prop: 'shenHeRen', label: '审核人', tags: userOption, dataType: 'stringArray', separator: ',', minWidth: 100 },
+                    // { prop: 'shenHeShiJian', label: '审核时间', dateFormat: 'yyyy-MM-dd HH:mm', sortable: 'custom', width: 140 },
+                    { prop: 'zhuangTai', label: '状态', width: 90 },
+                    { prop: 'buKaRiQi', label: '补卡日期', dateFormat: 'yyyy-MM-dd', sortable: 'custom', width: 80 },
+                    { prop: 'buKaShiJian', label: '补卡时间', dateFormat: 'HH:mm', sortable: 'custom', width: 80 },
+                    { prop: 'buKaBanCi', label: '补卡班次', width: 80 },
+                    { prop: 'buKaShiYou', label: '补卡事由', width: 300 },
+                    { prop: 'fuJian', label: '说明附件', width: 150 }
+                ],
+                rowHandle: {
+                    effect: 'default',
+                    // effect: 'display',
+                    actions: [
+                        { key: 'detail', label: '详情', type: 'primary', icon: 'ibps-icon-list-alt' }
+                    ]
+                }
+            }
+        }
+    },
+    computed: {
+    },
+    created () {
+        this.loadData()
+    },
+    methods: {
+        // 加载数据
+        loadData () {
+            this.loading = true
+            const sql = this.getSearchSql()
+            this.$common.request('sql', sql).then(res => {
+                this.listData = res.variables.data
+                // 做部门和姓名处理
+                this.listData.forEach(item => {
+                    item.userName = this.getUserLabel(item.yong_hu_id_)
+                    item.deptName = this.getDeptLabel(item.bu_men_)
+                })
+                // this.pagination.total = res.totalCount
+            }).finally(() => {
+                this.loading = false
+            })
+        },
+        getUserLabel (userId) {
+            const user = this.userOption.find(item => item.value === userId)
+            return user ? user.label : ''
+        },
+        getDeptLabel (positionId) {
+            const dept = this.deptOption.find(item => item.value === positionId)
+            return dept ? dept.label : ''
+        },
+        /**
+         * 获取格式化参数
+         */
+        getSearchFormData () {
+            const { first, second } = this.$store.getters.level || {}
+            const searchParam = this.$refs['crud'] ? this.$refs['crud'].getSearcFormData() : {}
+            searchParam['Q^di_dian_^S'] = second || first
+            return ActionUtils.formatParams(searchParam, this.pagination, this.sorts)
+        },
+        getSearchSql () {
+            let sql = `select * FROM t_attendance_reissue`
+            const params = this.getSearchFormData()
+            // 定义操作符映射
+            const operatorMap = {
+                'S': '=',
+                'SL': 'LIKE',
+                'DG': '<=',
+                'DL': '>='
+            }
+            // 如果有查询条件,构建 WHERE 子句
+            if (params.parameters && params.parameters.length > 0) {
+                const conditions = []
+                params.parameters.forEach(item => {
+                    const { key, value } = item
+                    const parts = key.split('^') // 格式: Q^field^operator
+                    if (parts.length === 3 && parts[0] === 'Q') {
+                        const field = parts[1] // 字段名
+                        const operatorKey = parts[2] // 操作符(S/SL/DG/DL)
+                        const operator = operatorMap[operatorKey]
+                        if (operator) {
+                            let condition
+                            if (operator === 'LIKE') {
+                                condition = `${field} LIKE '%${value}%'` // LIKE 模糊查询
+                            } else {
+                                condition = `${field} ${operator} '${value}'` // 其他操作符(=, <=, >=)
+                            }
+                            conditions.push(condition)
+                        }
+                    }
+                })
+
+                if (conditions.length > 0) {
+                    sql += ' WHERE ' + conditions.join(' AND ')
+                }
+            }
+            // 添加分页
+            sql += ` LIMIT ${this.pagination.limit} OFFSET ${(this.pagination.currentPage - 1) * this.pagination.limit}`
+            return sql
+        },
+        // 分页/排序处理
+        handlePaginationChange (page) {
+            ActionUtils.setPagination(this.pagination, page)
+            this.loadData()
+        },
+        handleSortChange (sort) {
+            ActionUtils.setSorts(this.sorts, sort)
+            this.loadData()
+        },
+
+        // 操作处理
+        handleAction (command, _, selection) {
+            switch (command) {
+                case 'search':
+                    ActionUtils.setFirstPagination(this.pagination)
+                    this.loadData()
+                    break
+            }
+        },
+        addData () {
+
+        }
+    }
+}
+</script>
+
+<style scoped>
+.main-container {
+    padding: 20px;
+    background: #fff;
+    border-radius: 4px;
+    box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
+}
+
+::v-deep .el-table {
+    .el-table__column[prop="bu_men_"] { min-width: 200px; }
+    .el-table__column[prop="pal_ban_ming_chen"] { min-width: 180px; }
+    .el-tag { margin: 2px; }
+}
+</style>

+ 328 - 0
src/views/business/attendance/makeUpEdit.vue

@@ -0,0 +1,328 @@
+<template>
+    <el-dialog
+        v-loading="loading"
+        :visible.sync="dialogVisible"
+        :close-on-click-modal="false"
+        :close-on-press-escape="false"
+        :show-close="false"
+        append-to-body
+        class="dialog makeup-dialog"
+        top="5vh"
+        width="600px"
+        :title="title"
+        @open="loadData"
+        @close="closeDialog"
+    >
+        <el-form
+            ref="makeupForm"
+            :model="formData"
+            :rules="rules"
+            label-width="100px"
+            label-position="right"
+            class="makeup-form"
+            @submit.native.prevent
+        >
+
+            <!-- 补卡日期 -->
+            <el-form-item label="补卡日期" prop="buKaRiQi">
+                <el-date-picker
+                    v-model="formData.buKaRiQi"
+                    type="date"
+                    placeholder="选择日期"
+                    value-format="yyyy-MM-dd"
+                    :picker-options="buKaRiQiPickerOptions"
+                    :disabled="isFromDetail"
+                    @change="handleBuKaRiQiChange"
+                />
+            </el-form-item>
+            <!-- 补卡班次 -->
+            <el-form-item label="补卡班次" prop="buKaBanCi">
+                <el-select
+                    v-model="formData.buKaBanCi"
+                    placeholder="请选择班次"
+                    :disabled="isFromDetail"
+                    @focus="loadBuKaBanCiOptions"
+                >
+                    <el-option
+                        v-for="buKaBanCi in buKaBanCiOptions"
+                        :key="buKaBanCi.value"
+                        :label="buKaBanCi.label"
+                        :value="buKaBanCi.value"
+                    />
+                </el-select>
+            </el-form-item>
+            <!-- 补卡时间 -->
+            <el-form-item label="补卡时间" prop="buKaShiJian">
+                <el-time-picker
+                    v-model="formData.buKaShiJian"
+                    value-format="HH:mm"
+                    placeholder="选择时间"
+                />
+            </el-form-item>
+            <!-- 补卡事由 -->
+            <el-form-item label="补卡事由" prop="buKaShiYou">
+                <el-input
+                    v-model="formData.buKaShiYou"
+                    type="textarea"
+                    :rows="3"
+                    placeholder="请输入补卡原因"
+                    maxlength="200"
+                    show-word-limit
+                />
+            </el-form-item>
+            <!-- 说明附件 -->
+            <el-form-item label="说明附件">
+                <ibps-attachment
+                    v-model="formData.fuJian"
+                    :download="true"
+                    multiple
+                    accept="*"
+                    :readonly="readonly"
+                    style="width:100%"
+                />
+            </el-form-item>
+        </el-form>
+        <div slot="footer" class="el-dialog--center">
+            <ibps-toolbar :actions="toolbars" @action-event="handleFormAction" />
+        </div>
+    </el-dialog>
+</template>
+<script>
+import { queryAttendanceDetail, saveAttendanceReissue, saveAttendanceDetail } from '@/api/business/attendance'
+import IbpsAttachment from '@/business/platform/file/attachment/selector'
+import ActionUtils from '@/utils/action'
+export default {
+    components: {
+        IbpsAttachment
+    },
+    name: 'MakeUpEdit',
+    props: {
+        visible: Boolean,
+        params: {
+            type: Object,
+            default: () => ({})
+        }
+    },
+    data () {
+        return {
+            loading: false,
+            submitting: false,
+            dialogVisible: this.visible,
+            title: '补卡申请',
+            isFromDetail: false,
+            formData: {
+                buKaRiQi: '',
+                buKaBanCi: '',
+                buKaShiJian: '',
+                buKaShiYou: '',
+                fuJian: ''
+            },
+            buKaBanCiOptions: [],
+            fileList: [],
+            yichangdata: [],
+            rules: {
+                buKaRiQi: [{ required: true, message: '请选择补卡日期', trigger: 'change' }],
+                buKaBanCi: [{ required: true, message: '请选择补卡班次', trigger: 'change' }],
+                buKaShiJian: [{ required: true, message: '请选择补卡时间', trigger: 'change' }],
+                buKaShiYou: [{ required: true, message: '请输入补卡事由', trigger: 'blur' }]
+            },
+            toolbars: [
+                { key: 'save', icon: 'ibps-icon-save', label: '提交', type: 'success', visible: !this.readonly },
+                // { key: 'tempSave', icon: 'ibps-icon-save', label: '暂存', type: 'primary', visible: !this.readonly },
+                { key: 'cancel', icon: 'el-icon-close', label: '关闭', type: 'danger' }
+            ],
+            buKaRiQiPickerOptions: {
+                disabledDate (time) {
+                    // 禁用今天之后的日期
+                    return time.getTime() > Date.now() - 24 * 60 * 60 * 1000
+                }
+            }
+        }
+    },
+
+    watch: {
+        visible (val) {
+            this.dialogVisible = val
+        }
+    },
+    mounted () {
+        this.initFromParams()
+    },
+    methods: {
+        /**
+         * 异常考勤查询参数
+         */
+        getSearchFormData () {
+            const paramjson = this.$refs['crud'] ? this.$refs['crud'].getSearcFormData() : {}
+            const { first, second } = this.$store.getters.level || {}
+            paramjson['Q^di_dian_^S'] = (second || first)
+            paramjson['Q^ri_qi_^S'] = this.formData.buKaRiQi
+            paramjson['Q^yong_hu_id_^S'] = this.$store.getters.userId
+            return ActionUtils.formatParams(
+                paramjson,
+                this.pagination,
+                this.sorts
+            )
+        },
+        // 查询补卡日期下的异常数据
+        handleBuKaRiQiChange (buKaRiQi) {
+            queryAttendanceDetail(this.getSearchFormData()).then(res => {
+                // ActionUtils.handleListData(this, res.data)
+                this.yichangdata = res.data.dataResult.filter(item => item.kaoQinZhuangTa === '异常')
+                if (this.yichangdata.length === 0) {
+                    this.$message.warning('该日期没有异常班次!')
+                    return
+                } else {
+                    const buKaBanCiArr = []
+                    this.yichangdata.forEach(element => {
+                        if (element.zhuangTai1 !== '正常') { // 上班异常
+                            buKaBanCiArr.push({ label: element.banCiBieMing + '-' + '上班', value: element.banCiBieMing + '-' + '上班', id: element.id })
+                        }
+                        if (element.zhuangTai2 !== '正常') {
+                            buKaBanCiArr.push({ label: element.banCiBieMing + '-' + '下班', value: element.banCiBieMing + '-' + '下班', id: element.id })
+                        }
+                    })
+                    this.buKaBanCiOptions = buKaBanCiArr
+                    if (this.buKaBanCiOptions.length === 1) { // 只有一个异常班次时自动带出
+                        this.formData.buKaBanCi = this.buKaBanCiOptions[0].value
+                        this.formData.buKaShiJian = this.buKaBanCiOptions[0].value.includes('上班') ? (this.yichangdata[0].banCiKaiShi.split(' ')[1]) : (this.yichangdata[0].banCiJieShu.split(' ')[1])
+                    }
+                }
+            }).catch(() => {
+            })
+        },
+        initFromParams () {
+            if (this.params.source === 'buKaBanCiDetail') {
+                this.isFromDetail = true
+                this.formData.buKaRiQi = this.defaultbuKaRiQi
+                this.formData.buKaBanCi = this.defaultbuKaBanCi
+            }
+        },
+        async loadBuKaBanCiOptions () {
+            if (!this.formData.buKaRiQi) {
+                this.$message.warning('请先选择补卡日期')
+                return
+            }
+        },
+        handleFormAction ({ key }) {
+            switch (key) {
+                case 'save':
+                    this.handleSave(key)
+                    break
+                // case 'tempSave':
+                    // this.handleSave(key)
+                    // break
+                case 'cancel':
+                    this.handleCancel()
+                    break
+                default:
+                    break
+            }
+        },
+        handleSave (key) {
+            const self = this
+            this.$refs.makeupForm.validate((valid) => {
+                if (!valid) {
+                    return self.$message.warning('请完善表单必填项信息!')
+                }
+                const { first, second } = self.$store.getters.level || {}
+                const { buKaRiQi, buKaBanCi, buKaShiJian, buKaShiYou, fuJian } = self.formData || {}
+
+                // 补卡关联的考勤数据
+                const updateObj = self.buKaBanCiOptions.filter(obj => obj.value === self.formData.buKaBanCi)
+                const updateId = updateObj[0].id
+                let updateData = self.yichangdata.filter(obj => obj.id === updateId)
+
+                // 获得补卡审批人
+                const sql = `select USER_ID_ FROM t_schedule_detail WHERE id_ = '${updateData[0].paiBanJiLuId}'`
+                self.$common.request('sql', sql).then((res) => {
+                    const submitData =
+                    {
+                        banCiZhuangTai: '',
+                        bianZhiRen: self.$store.getters.userId,
+                        bianZhiShiJian: self.$common.getDateNow(),
+                        buKaBanCi: buKaBanCi,
+                        buKaRiQi: buKaRiQi,
+                        buKaShiJian: buKaShiJian,
+                        buKaShiYou: buKaShiYou,
+                        createBy: '',
+                        createTime: '',
+                        dataStatus: '',
+                        dbType: '',
+                        diDian: second || first,
+                        dsAlias: '',
+                        fuJian: fuJian,
+                        id: self.params.id,
+                        ip: '',
+                        kaoQinId: updateId,
+                        kuaiZhao: '',
+                        name: '',
+                        paiBanId: updateData.paiBanId,
+                        paiBanJiLuId: updateData.paiBanJiLuId,
+                        pk: self.params.pk,
+                        shenHeRen: res.variables.data[0].USER_ID_ || '',
+                        shenHeShiJian: '',
+                        shenHeYiJian: '',
+                        tenantId: '',
+                        type: '',
+                        updateBy: '',
+                        updateTime: '',
+                        zhuangTai: '待审核'
+                    }
+
+                    // 提交数据
+                    saveAttendanceReissue(submitData).then((res) => {
+                        // 更新考勤表
+                        this.updateAttendanceDetail(submitData, updateObj, updateData)
+                    })
+                })
+            })
+        },
+        updateAttendanceDetail (submitData, updateObj, updateData) {
+            const self = this
+            if (updateObj.value.includes('上班')) { // 更新上班数据
+                updateData.zhuangTai1 = '正常'
+                updateData.daKaShiJian1 = submitData.buKaShiJian
+            } else { // 更新下班数据
+                updateData.zhuangTai2 = '正常'
+                updateData.daKaShiJian2 = submitData.buKaShiJian
+            }
+            saveAttendanceDetail(updateData).then(async (res) => {
+                self.$message.success(`补卡成功`)
+                self.closeDialog()
+            })
+        },
+        handleCancel () {
+            this.closeDialog()
+        },
+        closeDialog () {
+            this.$emit('close', false)
+            this.$emit('closeBuKaDialog', 'buka')
+        }
+    }
+}
+</script>
+<style lang="scss" scoped>
+  .makeup-dialog {
+        ::v-deep {
+            .el-dialog {
+                min-width: 1024px;
+                &__header {
+                    padding: 15px 20px 16px;
+                }
+            }
+        }
+        .makeup-form {
+            padding: 20px;
+            background: #f5f5f5;
+            border-radius: 4px;
+            overflow: hidden;
+            min-Height: 400px;
+            height: 60vh;
+            .operate-btn {
+                text-align: right;
+                margin-bottom: 5px;
+            }
+        }
+    }
+</style>

+ 233 - 0
src/views/business/attendance/makeUpRecords.vue

@@ -0,0 +1,233 @@
+<template>
+    <div class="main-container">
+        <ibps-crud
+            ref="crud"
+            :display-field="title"
+            :height="height"
+            :data="listData"
+            :toolbars="listConfig.toolbars"
+            :search-form="listConfig.searchForm"
+            :pk-key="pkKey"
+            :columns="listConfig.columns"
+            :row-handle="listConfig.rowHandle"
+            :pagination="pagination"
+            :loading="loading"
+            @action-event="handleAction"
+            @sort-change="handleSortChange"
+            @pagination-change="handlePaginationChange"
+            @row-dblclick="handleRowDblclick"
+        >
+        </ibps-crud>
+        <makeUpEdit
+            v-if="showMakeUpEdit"
+            :visible.sync="showMakeUpEdit"
+            :params="params"
+            :readonly="readonly"
+            @refresh="loadData"
+            @close="() => showMakeUpEdit = false"
+        />
+    </div>
+</template>
+
+<script>
+import ActionUtils from '@/utils/action'
+import FixHeight from '@/mixins/height'
+import IbpsExport from '@/plugins/export'
+import color from '@/store/modules/ibps/modules/color'
+
+export default {
+    components: {
+        makeUpEdit: () => import('./makeUpEdit')
+    },
+    mixins: [FixHeight],
+    data () {
+        const { userList = [], deptList = [] } = this.$store.getters || {}
+        const userOption = userList.map(item => ({ label: item.userName, value: item.userId }))
+        const deptOption = deptList.map(item => ({ label: item.positionName, value: item.positionId }))
+        return {
+            userOption,
+            deptOption,
+            title: '补卡记录',
+            pkKey: 'id_', // 主键对应数据库字段
+            loading: true,
+            height: document.clientHeight,
+            listData: [],
+            pagination: {
+                currentPage: 1,
+                limit: 20
+            },
+            showMakeUpEdit: false,
+            params: {},
+            sorts: {},
+            listConfig: {
+                toolbars: [
+                    { key: 'search', icon: 'ibps-icon-search', label: '查询', type: 'primary' },
+                    { key: 'add', icon: 'ibps-icon-plus', label: '申请', type: 'success' }
+                ],
+                searchForm: {
+                    labelWidth: 100,
+                    forms: [
+                        { prop: 'Q^bian_zhi_ren_^S', label: '申请人', fieldType: 'select', options: userOption },
+                        { prop: ['Q^bian_zhi_shi_jian^DL', 'Q^bian_zhi_shi_jian^DG'], label: '申请时间', fieldType: 'daterange' },
+                        { prop: 'Q^zhuang_tai_^SL', label: '状态', fieldType: 'select', options: [{ value: '待审核', label: '待审核' }, { value: '未通过', label: '未通过' }, { value: '已通过', label: '已通过' }] },
+                        { prop: ['Q^bu_ka_ri_qi_^DL', 'Q^bu_ka_ri_qi_^DG'], label: '补卡日期', fieldType: 'daterange' },
+                        { prop: 'Q^bu_ka_ban_ci_^SL', label: '补卡班次' },
+                        { prop: 'Q^bu_ka_shi_you_^SL', label: '补卡事由' }
+                    ]
+                },
+                columns: [
+                    { prop: 'bianZhiRen', label: '申请人', tags: userOption, width: 80 },
+                    { prop: 'bianZhiShiJian', label: '申请时间', dateFormat: 'yyyy-MM-dd HH:mm', sortable: 'custom', width: 140 },
+                    // { prop: 'shenHeRen', label: '审核人', tags: userOption, dataType: 'stringArray', separator: ',', minWidth: 100 },
+                    // { prop: 'shenHeShiJian', label: '审核时间', dateFormat: 'yyyy-MM-dd HH:mm', sortable: 'custom', width: 140 },
+                    { prop: 'zhuangTai', label: '状态', width: 90 },
+                    { prop: 'buKaRiQi', label: '补卡日期', dateFormat: 'yyyy-MM-dd', sortable: 'custom', width: 80 },
+                    { prop: 'buKaShiJian', label: '补卡时间', dateFormat: 'HH:mm', sortable: 'custom', width: 80 },
+                    { prop: 'buKaBanCi', label: '补卡班次', width: 80 },
+                    { prop: 'buKaShiYou', label: '补卡事由', width: 300 },
+                    { prop: 'fuJian', label: '说明附件', width: 150 }
+                ],
+                rowHandle: {
+                    effect: 'default',
+                    // effect: 'display',
+                    actions: [
+                        { key: 'detail', label: '详情', type: 'primary', icon: 'ibps-icon-list-alt' }
+                    ]
+                }
+            }
+        }
+    },
+    computed: {
+    },
+    created () {
+        this.loadData()
+    },
+    methods: {
+        // 加载数据
+        loadData () {
+            this.loading = true
+            const sql = this.getSearchSql()
+            this.$common.request('sql', sql).then(res => {
+                this.listData = res.variables.data
+                // 做部门和姓名处理
+                this.listData.forEach(item => {
+                    item.userName = this.getUserLabel(item.yong_hu_id_)
+                    item.deptName = this.getDeptLabel(item.bu_men_)
+                })
+                // this.pagination.total = res.totalCount
+            }).finally(() => {
+                this.loading = false
+            })
+        },
+        getUserLabel (userId) {
+            const user = this.userOption.find(item => item.value === userId)
+            return user ? user.label : ''
+        },
+        getDeptLabel (positionId) {
+            const dept = this.deptOption.find(item => item.value === positionId)
+            return dept ? dept.label : ''
+        },
+        /**
+         * 获取格式化参数
+         */
+        getSearchFormData () {
+            const { first, second } = this.$store.getters.level || {}
+            const searchParam = this.$refs['crud'] ? this.$refs['crud'].getSearcFormData() : {}
+            searchParam['Q^di_dian_^S'] = second || first
+            return ActionUtils.formatParams(searchParam, this.pagination, this.sorts)
+        },
+        getSearchSql () {
+            let sql = `select * FROM t_attendance_reissue`
+            const params = this.getSearchFormData()
+            // 定义操作符映射
+            const operatorMap = {
+                'S': '=',
+                'SL': 'LIKE',
+                'DG': '<=',
+                'DL': '>='
+            }
+            // 如果有查询条件,构建 WHERE 子句
+            if (params.parameters && params.parameters.length > 0) {
+                const conditions = []
+                params.parameters.forEach(item => {
+                    const { key, value } = item
+                    const parts = key.split('^') // 格式: Q^field^operator
+                    if (parts.length === 3 && parts[0] === 'Q') {
+                        const field = parts[1] // 字段名
+                        const operatorKey = parts[2] // 操作符(S/SL/DG/DL)
+                        const operator = operatorMap[operatorKey]
+                        if (operator) {
+                            let condition
+                            if (operator === 'LIKE') {
+                                condition = `${field} LIKE '%${value}%'` // LIKE 模糊查询
+                            } else {
+                                condition = `${field} ${operator} '${value}'` // 其他操作符(=, <=, >=)
+                            }
+                            conditions.push(condition)
+                        }
+                    }
+                })
+
+                if (conditions.length > 0) {
+                    sql += ' WHERE ' + conditions.join(' AND ')
+                }
+            }
+            // 添加分页
+            sql += ` LIMIT ${this.pagination.limit} OFFSET ${(this.pagination.currentPage - 1) * this.pagination.limit}`
+            return sql
+        },
+        // 分页/排序处理
+        handlePaginationChange (page) {
+            ActionUtils.setPagination(this.pagination, page)
+            this.loadData()
+        },
+        handleSortChange (sort) {
+            ActionUtils.setSorts(this.sorts, sort)
+            this.loadData()
+        },
+
+        // 操作处理
+        handleAction (command, _, selection, data) {
+            switch (command) {
+                case 'search':
+                    ActionUtils.setFirstPagination(this.pagination)
+                    this.loadData()
+                    break
+                case 'edit':
+                    this.handleEdit(command, data)
+                    break
+                case 'add':
+                    this.handleEdit(command, {})
+                    break
+            }
+        },
+        /**
+         * 处理编辑
+         */
+        async handleEdit (key, { id, scheduleId }) {
+            this.params = {
+                id,
+                scheduleId,
+                action: key === 'detail' ? 'view' : 'edit'
+            }
+            this.readonly = key === 'detail'
+            this.showMakeUpEdit = true
+        }
+    }
+}
+</script>
+
+<style scoped>
+.main-container {
+    padding: 20px;
+    background: #fff;
+    border-radius: 4px;
+    box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
+}
+
+::v-deep .el-table {
+    .el-table__column[prop="bu_men_"] { min-width: 200px; }
+    .el-table__column[prop="pal_ban_ming_chen"] { min-width: 180px; }
+    .el-tag { margin: 2px; }
+}
+</style>

+ 22 - 11
src/views/system/dashboard/components/util.js

@@ -761,7 +761,7 @@ export function buildComponent (name, column, preview, vm) {
                     const timeDifference = endTime - startTime
                     return timeDifference / (1000 * 60)
                 },
-                // 打卡处理逻辑
+                // 首页打卡处理逻辑
                 handleClockFromTab (todaySchedule) {
                     const today = this.$common.getDateNow()
                     // 当天仅有一个班次
@@ -776,7 +776,6 @@ export function buildComponent (name, column, preview, vm) {
                             const filtered = this.scheduleData.filter(item => item.start === today && item.alias === currentSchedule)
                             scheduleArr = scheduleArr.concat(filtered)
                         }
-                        debugger
                         const h = this.$createElement
                         const self = this
                         this.$msgbox({
@@ -787,7 +786,7 @@ export function buildComponent (name, column, preview, vm) {
                                     model: {
                                         value: self.tempSelectedValue,
                                         callback: (value) => {
-                                            self.tempSelectedValue = value;
+                                            self.tempSelectedValue = value
                                         }
                                     }
                                 }, scheduleArr.map(schedule =>
@@ -806,24 +805,24 @@ export function buildComponent (name, column, preview, vm) {
                             beforeClose: (action, instance, done) => {
                                 if (action === 'confirm') {
                                     if (!this.tempSelectedValue) {
-                                        this.$message.warning('请选择一个班次');
-                                        return false;
+                                        this.$message.warning('请选择一个班次')
+                                        return false
                                     }
-                                    done();
+                                    done()
                                 } else {
-                                    done();
+                                    done()
                                 }
                             }
                         }).then(() => {
                             const scheduleObj = this.scheduleData.find(item => 
                                 item.start === today && item.alias === this.tempSelectedValue
-                            );
+                            )
                             if (scheduleObj?.attendance) {
-                                this.handleClock(scheduleObj.attendance);
+                                this.handleClock(scheduleObj.attendance)
                             }
                         }).catch(() => {
                             // 用户取消操作
-                        });
+                        })
                     }
                 },
                 showMySchedule () {
@@ -845,12 +844,24 @@ export function buildComponent (name, column, preview, vm) {
                         headerToolbar: { // 日历头部按钮位置
                             // left: 'prev,next today',
                             // start: '',
-                            right: 'prev,next today',
+                            right: 'customButton prev,next today',
                             left: '',
                             center: 'title'
                             // right: 'dayGridMonth,timeGridWeek,timeGridDay'
                             // end: 'prev,next,today,month,agendaWeek,agendaDay,listWeek'
                         },
+                        customButtons: {
+                            customButton: { // 定义自定义按钮
+                                text: '补卡',
+                                click: (param) => {
+                                    // 打开补卡申请弹窗
+                                    this.$emit(
+                                        'open',
+                                        'buka'
+                                    )
+                                }
+                            }
+                        },
                         // events: this.scheduleData, // 排班数组
                         eventClick: this.handleScheduleEventClick, // 排班点击信息展示
                         scheduleShift: this.scheduleShift,

+ 5 - 2
src/views/system/homepage/components/banciDialog.vue

@@ -53,10 +53,10 @@
                     <div class="dakaBox">
                         <div>
                             <span>上班:</span> <span v-html="getAttendanceInfo(banci.attendance, 1)" />
-                            <button v-if="banci && banci.attendance && banci.attendance.zhuang_tai_1_!= '正常'" class="clock-btn"> 补卡 </button>
+                            <button v-if="banci && banci.attendance && banci.attendance.zhuang_tai_1_!= '正常'" class="clock-btn" @click="bukaFun"> 补卡 </button>
                         </div>
                         <div><span>下班:</span> <span v-html="getAttendanceInfo(banci.attendance, 2)" />
-                            <button v-if="banci && banci.attendance && banci.attendance.zhuang_tai_2_!= '正常'" class="clock-btn"> 补卡 </button>
+                            <button v-if="banci && banci.attendance && banci.attendance.zhuang_tai_2_!= '正常'" class="clock-btn" @click="bukaFun"> 补卡 </button>
                         </div>
                     </div>
                 </div>
@@ -115,6 +115,9 @@ export default {
         },
         closeDialog () {
             this.$emit('closeBanciDialog', 'banci')
+        },
+        bukaFun () {
+            this.$emit('open', 'buka')
         }
     }
 }

+ 21 - 2
src/views/system/homepage/index.vue

@@ -138,6 +138,12 @@
             @closeBanciDialog="handleClose"
             @open="handleOpen"
         />
+        <makeUpEdit
+            :params="bukaInfo"
+            :visible="makeUpEditVisible"
+            @closeBuKaDialog="handleClose"
+            @open="handleOpen"
+        />
     </ibps-container>
 </template>
 
@@ -165,6 +171,7 @@ import { markReadCalendar } from '@/api/detection/newHomeApi'
 // 日程提醒弹窗组件
 import CalendarAlert from '@/views/system/dashboard/components/calendar-alert.vue'
 import mySchedule from './components/mySchedule.vue'
+import makeUpEdit from '@/views/business/attendance/makeUpEdit.vue'
 
 const _import = require('@/utils/util.import.' + process.env.NODE_ENV)
 export default {
@@ -179,7 +186,8 @@ export default {
         ScheduleAdd,
         CalendarAlert,
         mySchedule,
-        banciDialog
+        banciDialog,
+        makeUpEdit: makeUpEdit
     },
     data () {
         return {
@@ -237,7 +245,9 @@ export default {
             calendarIds: [], // 日程 id 数组
             scheduleConfig: {},
             banciInfo: {},
-            banciDialogVisible: false
+            banciDialogVisible: false,
+            bukaInfo: {},
+            makeUpEditVisible: false
         }
     },
     computed: {
@@ -656,6 +666,9 @@ export default {
                 case 'banci':
                     this.banciDialogVisible = false
                     break
+                case 'buka':
+                    this.makeUpEditVisible = false
+                    break
                 default:
                     break
             }
@@ -686,6 +699,12 @@ export default {
                     this.banciDialogVisible = true
                     break
                 }
+                case 'buka':
+                {
+                    this.bukaInfo = dateArr
+                    this.makeUpEditVisible = true
+                    break
+                }
                 default:
                     break
             }