|
|
|
|
@@ -38,8 +38,8 @@
|
|
|
|
|
<el-button size="mini" type="success" icon="el-icon-plus" @click="addShiftDetail">{{ $t(key('add_shift')) }}</el-button>
|
|
|
|
|
<el-table :data="shiftsData" border style="width:100%;margin-top:10px">
|
|
|
|
|
<el-table-column :label="$t(key('shift_name'))"><template slot-scope="scope"><el-input v-model="shiftsData[scope.$index].name" :placeholder="$t(key('enter_shift_name'))" /></template></el-table-column>
|
|
|
|
|
<el-table-column :label="$t(key('shift_start_time'))" width="190"><template slot-scope="scope"><el-time-picker v-model="shiftsData[scope.$index].start_time" format="HH:mm" value-format="HH:mm" :placeholder="$t(key('select_shift_start_time'))" /></template></el-table-column>
|
|
|
|
|
<el-table-column :label="$t(key('shift_end_time'))" width="190"><template slot-scope="scope"><el-time-picker v-model="shiftsData[scope.$index].finish_time" format="HH:mm" value-format="HH:mm" :placeholder="$t(key('select_shift_end_time'))" /></template></el-table-column>
|
|
|
|
|
<el-table-column :label="$t(key('shift_start_time'))" width="190"><template slot-scope="scope"><el-time-picker v-model="shiftsData[scope.$index].start_time" format="HH:mm" value-format="HH:mm" :placeholder="$t(key('select_shift_start_time'))" @change="val => changeShiftTime(val, scope.$index, 'start_time')" /></template></el-table-column>
|
|
|
|
|
<el-table-column :label="$t(key('shift_end_time'))" width="190"><template slot-scope="scope"><el-time-picker v-model="shiftsData[scope.$index].finish_time" format="HH:mm" value-format="HH:mm" :placeholder="$t(key('select_shift_end_time'))" @change="val => changeShiftTime(val, scope.$index, 'finish_time')" /></template></el-table-column>
|
|
|
|
|
<el-table-column :label="$t(key('production_team_binding'))"><template slot-scope="scope"><el-select v-model="shiftsData[scope.$index].production_team_id" clearable filterable :placeholder="$t(key('please_select'))" @change="val => changeProductionTeam(val, scope.$index)"><el-option v-for="item in selectableTeams" :key="item.id" :label="item.name" :value="item.id" /></el-select></template></el-table-column>
|
|
|
|
|
<el-table-column :label="$t(key('operation'))" width="120"><template slot-scope="scope"><el-button type="danger" size="mini" icon="el-icon-delete" @click="shiftsData.splice(scope.$index, 1)">{{ $t(key('delete')) }}</el-button></template></el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
@@ -49,7 +49,7 @@
|
|
|
|
|
<el-dialog :title="$t(key('shift_plan_data_import'))" :visible.sync="importVisible" width="80%" :close-on-click-modal="false">
|
|
|
|
|
<el-alert :title="$t(key('upload_file_alert_title'))" :description="$t(key('upload_file_alert_description'))" :closable="false" type="warning" />
|
|
|
|
|
<el-upload action="" :multiple="false" :auto-upload="false" :show-file-list="true" :file-list="importFileList" accept=".xls,.xlsx" :on-change="onImportFileChange"><el-button slot="trigger" size="mini" type="success">{{ $t(key('select_file')) }}</el-button><el-button style="margin-left:10px" size="mini" type="primary" :loading="importLoading" @click.stop="downloadTemplate">{{ $t(key('download_template')) }}</el-button></el-upload>
|
|
|
|
|
<el-table :data="importRows" height="330" border style="margin-top:12px" v-loading="importTableLoading"><el-table-column prop="name" :label="$t(key('shift_plan_name'))" /><el-table-column prop="code" :label="$t(key('shift_plan_code'))" /><el-table-column prop="start_time" :label="$t(key('start_time'))" /><el-table-column prop="finish_time" :label="$t(key('end_time'))" /><el-table-column prop="status" :label="$t(key('status'))" /><el-table-column prop="shift_name" :label="$t(key('shift_name'))" /></el-table>
|
|
|
|
|
<el-table :data="importRows" height="330" border style="margin-top:12px" v-loading="importTableLoading"><el-table-column prop="name" :label="$t(key('shift_plan_name'))" /><el-table-column prop="code" :label="$t(key('shift_plan_code'))" /><el-table-column prop="start_time" :label="$t(key('start_time'))" /><el-table-column prop="finish_time" :label="$t(key('end_time'))" /><el-table-column prop="production_team_ids" :label="$t(key('production_team'))" /><el-table-column prop="cycle_length" :label="$t(key('rotation_count'))" /><el-table-column prop="cycle_unit" :label="$t(key('rotation_unit'))" /><el-table-column prop="weekly_rest_days" :label="$t(key('rest_day_setting'))" /><el-table-column prop="status" :label="$t(key('status'))" /><el-table-column prop="shift_name" :label="$t(key('shift_name'))" /><el-table-column prop="shift_start_time" :label="$t(key('shift_start_time'))" /><el-table-column prop="shift_finish_time" :label="$t(key('shift_end_time'))" /><el-table-column prop="production_team_id" :label="$t(key('production_team_binding'))" /></el-table>
|
|
|
|
|
<span slot="footer"><el-button @click="importVisible = false">{{ $t(key('cancel')) }}</el-button><el-button type="primary" @click="submitImport">{{ $t(key('confirm')) }}</el-button></span>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
</d2-container>
|
|
|
|
|
@@ -122,7 +122,12 @@ export default {
|
|
|
|
|
methods: {
|
|
|
|
|
defaultFormData () { return { name: '', code: '', time_range: '', status: 1, remark: '', weekly_rest_days: [6, 7], cycle: { unit: 'day', length: 0 }, productionTeamIds: [] } },
|
|
|
|
|
async loadTeams () { const res = await getTeamAll({}); this.teamOptions = (res && res.data) || res || [] },
|
|
|
|
|
async fetchData () { this.loading = true; try { const res = await getShiftList({ ...this.search, page_no: this.pagination.current, page_size: this.pagination.size }); const { list, total } = readPageData(res); this.tableData = list; this.pagination.total = total } finally { this.loading = false } },
|
|
|
|
|
buildSearchParams () {
|
|
|
|
|
const params = { ...this.search }
|
|
|
|
|
if (!Array.isArray(params.create_time) || !params.create_time.length) delete params.create_time
|
|
|
|
|
return params
|
|
|
|
|
},
|
|
|
|
|
async fetchData () { this.loading = true; try { const res = await getShiftList({ ...this.buildSearchParams(), page_no: this.pagination.current, page_size: this.pagination.size }); const { list, total } = readPageData(res); this.tableData = list; this.pagination.total = total } finally { this.loading = false } },
|
|
|
|
|
onSearch () { this.pagination.current = 1; this.fetchData() },
|
|
|
|
|
onReset () { this.search = { name: '', code: '', create_time: '' }; this.pagination.current = 1; this.fetchData() },
|
|
|
|
|
onPageChange (page) { this.pagination.current = page.current; this.pagination.size = page.size; this.fetchData() },
|
|
|
|
|
@@ -130,16 +135,47 @@ export default {
|
|
|
|
|
openEdit (row) { this.handleType = 'edit'; this.dialogTitle = this.key('edit_shift_plan'); this.editId = row.id; this.formData = { name: row.name, code: row.code, time_range: [row.start_time, row.finish_time], status: Number(row.status), remark: row.remark || '', weekly_rest_days: safeJson(row.weekly_rest_days, []), cycle: safeJson(row.cycle, { unit: 'day', length: 0 }), productionTeamIds: safeJson(row.production_team_ids, []) }; this.shiftsData = safeJson(row.shifts, []); this.dialogVisible = true },
|
|
|
|
|
addShiftDetail () { this.shiftsData.push({ name: '', start_time: '', finish_time: '', production_team_id: '' }) },
|
|
|
|
|
changeProductionTeam (value, index) { if (!value) return; if (this.shiftsData.some((item, i) => i !== index && item.production_team_id === value)) { this.shiftsData[index].production_team_id = ''; this.$message.warning(this.$t(this.key('production_team_can_only_bind_one_shift'))) } },
|
|
|
|
|
changeShiftTime (value, index, field) {
|
|
|
|
|
if (!value) {
|
|
|
|
|
this.$message.warning(this.$t(this.key('please_select_valid_time')))
|
|
|
|
|
this.shiftsData[index][field] = ''
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
const current = this.shiftsData[index]
|
|
|
|
|
if (!current.start_time || !current.finish_time) return
|
|
|
|
|
if (this.hasShiftTimeConflict(current, index)) {
|
|
|
|
|
this.$message.error(this.$t(this.key('shift_time_conflict')))
|
|
|
|
|
this.shiftsData[index][field] = ''
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
toMinute (value) {
|
|
|
|
|
const [hour, minute] = String(value || '').split(':').map(Number)
|
|
|
|
|
return hour * 60 + minute
|
|
|
|
|
},
|
|
|
|
|
getShiftRanges (shift) {
|
|
|
|
|
const start = this.toMinute(shift.start_time)
|
|
|
|
|
const finish = this.toMinute(shift.finish_time)
|
|
|
|
|
if (finish < start) return [[start, 24 * 60], [0, finish]]
|
|
|
|
|
return [[start, finish]]
|
|
|
|
|
},
|
|
|
|
|
hasShiftTimeConflict (current, currentIndex) {
|
|
|
|
|
const currentRanges = this.getShiftRanges(current)
|
|
|
|
|
return this.shiftsData.some((item, index) => {
|
|
|
|
|
if (index === currentIndex || !item.start_time || !item.finish_time) return false
|
|
|
|
|
const ranges = this.getShiftRanges(item)
|
|
|
|
|
return currentRanges.some(([start, finish]) => ranges.some(([itemStart, itemFinish]) => start < itemFinish && itemStart < finish))
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
validateShifts () { for (let i = 0; i < this.shiftsData.length; i++) { const item = this.shiftsData[i]; if (!item.name) return this.$t(this.key('please_enter_shift_name_row')) + (i + 1); if (!item.start_time) return this.$t(this.key('please_select_shift_start_time_row')) + (i + 1); if (!item.finish_time) return this.$t(this.key('please_select_shift_end_time_row')) + (i + 1) } return '' },
|
|
|
|
|
submitDialog () { this.$refs.form.validate(async valid => { if (!valid) return; const error = this.validateShifts(); if (error) { this.$message.error(error); return } this.submitting = true; try { const payload = { ...this.formData, start_time: this.formData.time_range[0], finish_time: this.formData.time_range[1], is_shift: Number(this.formData.cycle.length) > 0 ? 1 : 0, rest_enabled: this.formData.weekly_rest_days.length !== 7 ? 1 : 0, shiftsData: JSON.stringify(this.shiftsData) }; if (this.handleType === 'create') await createShift(payload); else await editShift({ ...payload, id: this.editId }); this.$message.success(this.$t(this.key('operation_successful'))); this.closeDialog(); this.fetchData() } finally { this.submitting = false } }) },
|
|
|
|
|
submitDialog () { this.$refs.form.validate(async valid => { if (!valid) return; const error = this.validateShifts(); if (error) { this.$message.error(error); return } if (this.shiftsData.some((item, index) => this.hasShiftTimeConflict(item, index))) { this.$message.error(this.$t(this.key('shift_time_conflict'))); return } this.submitting = true; try { const payload = { ...this.formData, start_time: this.formData.time_range[0], finish_time: this.formData.time_range[1], is_shift: Number(this.formData.cycle.length) > 0 ? 1 : 0, rest_enabled: this.formData.weekly_rest_days.length !== 7 ? 1 : 0, shiftsData: JSON.stringify(this.shiftsData) }; if (this.handleType === 'create') await createShift(payload); else await editShift({ ...payload, id: this.editId }); this.$message.success(this.$t(this.key('operation_successful'))); this.closeDialog(); this.fetchData() } finally { this.submitting = false } }) },
|
|
|
|
|
closeDialog () { this.dialogVisible = false; this.formData = this.defaultFormData(); this.shiftsData = []; this.editId = '' },
|
|
|
|
|
async handleDelete (row) { const cancelled = await this.$confirmAction({ message: this.key('delete_department_confirm_message'), title: this.key('prompt'), confirmButtonText: this.key('confirm'), cancelButtonText: this.key('cancel') }, () => deleteShift({ id: [row.id] })); if (cancelled) return; this.$message.success(this.$t(this.key('operation_successful'))); this.fetchData() },
|
|
|
|
|
async handleBatchDelete () { if (!this.selectedRows.length) { this.$message.error(this.$t(this.key('please_select_table_data'))); return } const cancelled = await this.$confirmAction({ message: this.key('batch_delete_confirm_message'), title: this.key('prompt'), confirmButtonText: this.key('confirm'), cancelButtonText: this.key('cancel') }, () => deleteShift({ id: this.selectedRows.map(item => item.id) })); if (cancelled) return; this.$message.success(this.$t(this.key('operation_successful'))); this.fetchData() },
|
|
|
|
|
openImport () { this.importFileList = []; this.importRows = []; this.importVisible = true },
|
|
|
|
|
async downloadTemplate () { this.importLoading = true; try { const res = await getShiftImportTemplate({}); downloadRename(res, 'xlsx', this.$t(this.key('shift_plan_data_import_template'))) } finally { this.importLoading = false } },
|
|
|
|
|
async onImportFileChange (file) { if (!file || !/\.(xls|xlsx)$/i.test(file.name)) { this.$message.error(this.$t(this.key('upload_format_error'))); return } this.importFileList = [file]; this.importTableLoading = true; try { const rows = await readExcel(file.raw); this.importRows = rows.map(row => ({ name: row[this.$t(this.key('shift_plan_name'))], code: row[this.$t(this.key('shift_plan_code'))], start_time: row[this.$t(this.key('start_time'))], finish_time: row[this.$t(this.key('end_time'))], status: row[this.$t(this.key('status'))], shift_name: row[this.$t(this.key('shift_name'))], shifts: { name: row[this.$t(this.key('shift_name'))], start_time: row[this.$t(this.key('shift_start_time'))], finish_time: row[this.$t(this.key('shift_end_time'))], production_team_id: row[this.$t(this.key('production_team_binding'))] } })) } finally { this.importTableLoading = false } },
|
|
|
|
|
async onImportFileChange (file) { if (!file || !/\.(xls|xlsx)$/i.test(file.name)) { this.$message.error(this.$t(this.key('upload_format_error'))); return } this.importFileList = [file]; this.importTableLoading = true; try { const rows = await readExcel(file.raw); this.importRows = rows.map(row => { const shift = { name: row[this.$t(this.key('shift_name'))], start_time: row[this.$t(this.key('shift_start_time'))], finish_time: row[this.$t(this.key('shift_end_time'))], production_team_id: row[this.$t(this.key('production_team_binding'))] }; return { name: row[this.$t(this.key('shift_plan_name'))], code: row[this.$t(this.key('shift_plan_code'))], start_time: row[this.$t(this.key('start_time'))], finish_time: row[this.$t(this.key('end_time'))], production_team_ids: row[this.$t(this.key('production_team'))], cycle: { length: row[this.$t(this.key('rotation_count'))], unit: row[this.$t(this.key('rotation_unit'))] }, cycle_length: row[this.$t(this.key('rotation_count'))], cycle_unit: row[this.$t(this.key('rotation_unit'))], weekly_rest_days: row[this.$t(this.key('rest_day_setting'))], status: row[this.$t(this.key('status'))], shift_name: shift.name, shift_start_time: shift.start_time, shift_finish_time: shift.finish_time, production_team_id: shift.production_team_id, shifts: shift } }) } finally { this.importTableLoading = false } },
|
|
|
|
|
async submitImport () { if (!this.importRows.length) { this.$message.error(this.$t(this.key('please_import_department_data'))); return } await importShiftData({ import_data: JSON.stringify(this.importRows) }); this.$message.success(this.$t(this.key('operation_successful'))); this.importVisible = false; this.fetchData() },
|
|
|
|
|
async handleExport () { const cancelled = await this.$confirmAction({ message: this.key('export_confirm_message'), title: this.key('prompt'), confirmButtonText: this.key('confirm'), cancelButtonText: this.key('cancel') }, () => exportShiftTask({ ...this.search, action: 'download' })); if (cancelled) return; this.$message.success(this.$t(this.key('download_task_created'))) }
|
|
|
|
|
async handleExport () { const cancelled = await this.$confirmAction({ message: this.key('export_confirm_message'), title: this.key('prompt'), confirmButtonText: this.key('confirm'), cancelButtonText: this.key('cancel') }, () => exportShiftTask({ ...this.buildSearchParams(), action: 'download' })); if (cancelled) return; this.$message.success(this.$t(this.key('download_task_created'))) }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|