更新若干事项
This commit is contained in:
1 parent
68cdaf38fa
commit
9898962836
30 files changed
+7715
No files matched your search
@@ -0,0 +1,314 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:model-value="modelValue"
|
||||
title="班次管理"
|
||||
width="900px"
|
||||
destroy-on-close
|
||||
@update:model-value="emit('update:modelValue', $event)"
|
||||
@open="loadShifts"
|
||||
>
|
||||
<div class="shift-toolbar">
|
||||
<div class="shift-tip">班次用于定义上下班时间与休息时段,可被排班方案和临时调配复用。</div>
|
||||
<el-button type="primary" :icon="Plus" @click="openCreate">新增班次</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="shifts" v-loading="loading" size="small" max-height="320">
|
||||
<el-table-column label="班次" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<span class="shift-dot" :style="{ background: row.color }" />
|
||||
<span>{{ row.name }}</span>
|
||||
<el-tag v-if="row.is_default === 1" size="small" type="success" class="ml6">默认</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="short_name" label="简称" width="90" />
|
||||
<el-table-column label="上班时间" width="150">
|
||||
<template #default="{ row }">{{ row.work_start }} - {{ row.work_end }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="休息时段" width="140">
|
||||
<template #default="{ row }">
|
||||
{{ row.rest_start ? `${row.rest_start} - ${row.rest_end}` : "无" }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="工时" width="80">
|
||||
<template #default="{ row }">{{ row.work_hours }}h</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 1 ? 'success' : 'info'" size="small">
|
||||
{{ row.status === 1 ? "启用" : "停用" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="130" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="openEdit(row)">编辑</el-button>
|
||||
<el-button link type="danger" @click="removeShift(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #empty><el-empty description="还没有班次,请先新增一个" :image-size="60" /></template>
|
||||
</el-table>
|
||||
|
||||
<div v-if="formVisible" class="shift-form">
|
||||
<div class="form-title">{{ editingID ? "编辑班次" : "新增班次" }}</div>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="92px">
|
||||
<div class="form-grid">
|
||||
<el-form-item label="班次名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="如:早班 / 晚班 / 行政班" maxlength="50" />
|
||||
</el-form-item>
|
||||
<el-form-item label="简称">
|
||||
<el-input v-model="form.short_name" placeholder="看板色块上的短文本" maxlength="20" />
|
||||
</el-form-item>
|
||||
<el-form-item label="上班时间" prop="work_start">
|
||||
<el-time-picker v-model="form.work_start" value-format="HH:mm" format="HH:mm" placeholder="上班" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="下班时间" prop="work_end">
|
||||
<el-time-picker v-model="form.work_end" value-format="HH:mm" format="HH:mm" placeholder="下班" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="休息开始">
|
||||
<el-time-picker v-model="form.rest_start" value-format="HH:mm" format="HH:mm" placeholder="选填" clearable style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="休息结束">
|
||||
<el-time-picker v-model="form.rest_end" value-format="HH:mm" format="HH:mm" placeholder="选填" clearable style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="标识颜色">
|
||||
<el-color-picker v-model="form.color" :predefine="COLORS" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序">
|
||||
<el-input-number v-model="form.sort" :min="0" :max="999" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="默认班次">
|
||||
<el-switch v-model="form.is_default" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio :value="1">启用</el-radio>
|
||||
<el-radio :value="0">停用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" class="span-2">
|
||||
<el-input v-model="form.remark" maxlength="255" placeholder="选填" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<span class="hours-tip">自动计算工时:{{ computedHours }} 小时</span>
|
||||
<el-button @click="formVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="saveShift">保存班次</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { createShift, deleteShift, getShiftList, updateShift } from '@/api/oaScheduling'
|
||||
|
||||
defineProps({ modelValue: Boolean })
|
||||
const emit = defineEmits(['update:modelValue', 'changed'])
|
||||
|
||||
const COLORS = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#9254DE', '#13C2C2']
|
||||
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const shifts = ref([])
|
||||
const formVisible = ref(false)
|
||||
const editingID = ref(0)
|
||||
const formRef = ref(null)
|
||||
|
||||
const form = reactive({
|
||||
name: '',
|
||||
short_name: '',
|
||||
color: '#409EFF',
|
||||
work_start: '09:00',
|
||||
work_end: '18:00',
|
||||
rest_start: '12:00',
|
||||
rest_end: '13:00',
|
||||
is_default: false,
|
||||
status: 1,
|
||||
sort: 0,
|
||||
remark: ''
|
||||
})
|
||||
|
||||
const rules = {
|
||||
name: [{ required: true, message: '请输入班次名称', trigger: 'blur' }],
|
||||
work_start: [{ required: true, message: '请选择上班时间', trigger: 'change' }],
|
||||
work_end: [{ required: true, message: '请选择下班时间', trigger: 'change' }]
|
||||
}
|
||||
|
||||
function unwrap(res) {
|
||||
if (res && typeof res === 'object' && 'code' in res) {
|
||||
if (Number(res.code) !== 200) throw new Error(res.msg || '请求失败')
|
||||
return res.data
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
const computedHours = computed(() => {
|
||||
const toMinutes = (value) => {
|
||||
if (!value) return null
|
||||
const [hour, minute] = String(value).split(':').map(Number)
|
||||
if (Number.isNaN(hour) || Number.isNaN(minute)) return null
|
||||
return hour * 60 + minute
|
||||
}
|
||||
let start = toMinutes(form.work_start)
|
||||
let end = toMinutes(form.work_end)
|
||||
if (start === null || end === null) return 0
|
||||
if (end <= start) end += 24 * 60
|
||||
let span = end - start
|
||||
let restStart = toMinutes(form.rest_start)
|
||||
let restEnd = toMinutes(form.rest_end)
|
||||
if (restStart !== null && restEnd !== null) {
|
||||
if (restEnd <= restStart) restEnd += 24 * 60
|
||||
const overlap = Math.max(0, Math.min(restEnd, end) - Math.max(restStart, start))
|
||||
span -= overlap
|
||||
}
|
||||
return Math.max(0, Math.round((span / 60) * 100) / 100)
|
||||
})
|
||||
|
||||
async function loadShifts() {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = unwrap(await getShiftList()) || {}
|
||||
shifts.value = data.list || []
|
||||
} catch (error) {
|
||||
ElMessage.error(error.message || '加载班次失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
editingID.value = 0
|
||||
Object.assign(form, {
|
||||
name: '', short_name: '', color: '#409EFF',
|
||||
work_start: '09:00', work_end: '18:00', rest_start: '12:00', rest_end: '13:00',
|
||||
is_default: false, status: 1, sort: shifts.value.length, remark: ''
|
||||
})
|
||||
formVisible.value = true
|
||||
}
|
||||
|
||||
function openEdit(row) {
|
||||
editingID.value = row.id
|
||||
Object.assign(form, {
|
||||
name: row.name,
|
||||
short_name: row.short_name || '',
|
||||
color: row.color || '#409EFF',
|
||||
work_start: row.work_start,
|
||||
work_end: row.work_end,
|
||||
rest_start: row.rest_start || '',
|
||||
rest_end: row.rest_end || '',
|
||||
is_default: row.is_default === 1,
|
||||
status: row.status,
|
||||
sort: row.sort || 0,
|
||||
remark: row.remark || ''
|
||||
})
|
||||
formVisible.value = true
|
||||
}
|
||||
|
||||
async function saveShift() {
|
||||
if (!formRef.value) return
|
||||
const valid = await formRef.value.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
if (form.rest_start && !form.rest_end) {
|
||||
ElMessage.warning('请选择休息结束时间')
|
||||
return
|
||||
}
|
||||
if (!form.rest_start && form.rest_end) {
|
||||
ElMessage.warning('请选择休息开始时间')
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
const payload = { ...form, work_hours: computedHours.value }
|
||||
if (editingID.value) {
|
||||
unwrap(await updateShift(editingID.value, payload))
|
||||
ElMessage.success('班次已更新')
|
||||
} else {
|
||||
unwrap(await createShift(payload))
|
||||
ElMessage.success('班次已创建')
|
||||
}
|
||||
formVisible.value = false
|
||||
await loadShifts()
|
||||
emit('changed')
|
||||
} catch (error) {
|
||||
ElMessage.error(error.message || '保存班次失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function removeShift(row) {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确认删除班次「${row.name}」?已被排班使用的班次不能删除。`, '删除班次', {
|
||||
type: 'warning'
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
try {
|
||||
unwrap(await deleteShift(row.id))
|
||||
ElMessage.success('班次已删除')
|
||||
await loadShifts()
|
||||
emit('changed')
|
||||
} catch (error) {
|
||||
ElMessage.error(error.message || '删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ loadShifts })
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.shift-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.shift-tip {
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
}
|
||||
.shift-dot {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
margin-right: 6px;
|
||||
}
|
||||
.ml6 {
|
||||
margin-left: 6px;
|
||||
}
|
||||
.shift-form {
|
||||
margin-top: 16px;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
background: #f7f9fc;
|
||||
border: 1px solid #ebeef5;
|
||||
}
|
||||
.form-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 0 16px;
|
||||
}
|
||||
.span-2 {
|
||||
grid-column: span 3;
|
||||
}
|
||||
.form-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
.hours-tip {
|
||||
margin-right: auto;
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user