新增生产-前缫看台排班,并导入机组车台与看台排班数据
功能: - 后端新增 KtpbCtgs / KtpbDcgs / KtpbZbzs 三张表、KtpbService 与 api/prod/ktpb 接口 (车头工、挡车工、值班长分页查询与维护,挡车工按车间+班次+机组整组替换保存) - 挡车数按 F9 口径自动重算:接壤车台各计 0.5,保存时服务端重新计算并校验车台范围 - 前端新增 /prod/qianxian/ktpb 看台排班页(三页签 + 车间/班次/机组过滤 + 批量编辑弹窗) - 新增通用职工选择弹窗 EmployeeSelect(单选/多选,按部门过滤) - 生产-前缫菜单下新增「看台排班」,权限 prod:ktpb:list 数据导入: - 重新生成 biz_prod_qs_jzct.json,新增 ktpb_ctg / ktpb_dcg / ktpb_zbz 三个内嵌资源 - F9 部门 Id 经 DeptData 映射到本系统部门;职工按原始雪花 Id 关联 - 剔除软删除/脏数据:机组车台 24 条、车头工 33 条、挡车工 202 条(34 组)、值班长 7 条 - 导入幂等:机组车台按车间+机组 upsert,排班按业务唯一键去重,已维护的组不覆盖 其他:同步工作进度-12 文档与开发数据库快照
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
<script setup>
|
||||
/**
|
||||
* 职工选择弹窗(单选 / 多选)
|
||||
* 对应 WinForms 端的 F9ModalPageUserControl 职工选择页,数据源为 /api/sys/employee。
|
||||
*/
|
||||
import { ref, watch } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import request from '../../utils/request';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: Boolean,
|
||||
title: { type: String, default: '选择职工' },
|
||||
multiple: Boolean,
|
||||
depId: { type: [Number, String], default: 0 },
|
||||
width: { type: String, default: '820px' },
|
||||
});
|
||||
const emit = defineEmits(['update:modelValue', 'confirm']);
|
||||
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const rows = ref([]);
|
||||
const total = ref(0);
|
||||
const keyword = ref('');
|
||||
const pageIndex = ref(1);
|
||||
const pageSize = ref(20);
|
||||
const current = ref(null);
|
||||
const selected = ref([]);
|
||||
|
||||
watch(() => props.modelValue, async (v) => {
|
||||
visible.value = v;
|
||||
if (!v) return;
|
||||
keyword.value = '';
|
||||
pageIndex.value = 1;
|
||||
current.value = null;
|
||||
selected.value = [];
|
||||
await query();
|
||||
});
|
||||
watch(visible, (v) => emit('update:modelValue', v));
|
||||
|
||||
async function query() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await request.get('/sys/employee', {
|
||||
params: {
|
||||
depId: props.depId || 0,
|
||||
like: keyword.value,
|
||||
pageIndex: pageIndex.value,
|
||||
pageSize: pageSize.value,
|
||||
},
|
||||
});
|
||||
rows.value = res.data?.items || [];
|
||||
total.value = Number(res.data?.total || 0);
|
||||
} catch {
|
||||
rows.value = [];
|
||||
total.value = 0;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onSearch() {
|
||||
pageIndex.value = 1;
|
||||
query();
|
||||
}
|
||||
function onPageChange(p) {
|
||||
pageIndex.value = p;
|
||||
query();
|
||||
}
|
||||
function onRowClick(row) {
|
||||
if (props.multiple) return;
|
||||
current.value = row;
|
||||
}
|
||||
function onRowDblClick(row) {
|
||||
if (props.multiple) return;
|
||||
current.value = row;
|
||||
confirm();
|
||||
}
|
||||
|
||||
function confirm() {
|
||||
const picked = props.multiple ? selected.value : current.value ? [current.value] : [];
|
||||
if (!picked.length) {
|
||||
ElMessage.warning('请选择职工');
|
||||
return;
|
||||
}
|
||||
visible.value = false;
|
||||
emit('confirm', props.multiple ? picked.map((x) => ({ ...x })) : { ...picked[0] });
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="title" :width="width" append-to-body destroy-on-close>
|
||||
<div class="es-toolbar">
|
||||
<el-input
|
||||
v-model="keyword"
|
||||
placeholder="姓名 / 工号"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
@keyup.enter="onSearch"
|
||||
@clear="onSearch"
|
||||
/>
|
||||
<el-button type="primary" @click="onSearch">查询</el-button>
|
||||
<span v-if="multiple" class="es-tip">已选 {{ selected.length }} 人</span>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="rows"
|
||||
border
|
||||
size="small"
|
||||
height="400"
|
||||
highlight-current-row
|
||||
@row-click="onRowClick"
|
||||
@row-dblclick="onRowDblClick"
|
||||
@selection-change="selected = $event"
|
||||
>
|
||||
<el-table-column v-if="multiple" type="selection" width="45" />
|
||||
<el-table-column type="index" label="序号" width="55" />
|
||||
<el-table-column prop="name" label="姓名" width="110" />
|
||||
<el-table-column prop="code" label="工号" width="120" />
|
||||
<el-table-column prop="depName" label="部门" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="gangwei" label="岗位" min-width="120" show-overflow-tooltip />
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
class="es-page"
|
||||
:current-page="pageIndex"
|
||||
:page-size="pageSize"
|
||||
:total="total"
|
||||
layout="total, prev, pager, next"
|
||||
@current-change="onPageChange"
|
||||
/>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="confirm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.es-toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
|
||||
.es-tip { color: var(--el-text-color-secondary); font-size: 13px; }
|
||||
.es-page { margin-top: 10px; justify-content: flex-end; }
|
||||
</style>
|
||||
@@ -65,6 +65,7 @@ export const componentMap = {
|
||||
'prod/lsjl/index': () => import('../views/prod/lsjl/index.vue'),
|
||||
'prod/xdjy/index': () => import('../views/prod/xdjy/index.vue'),
|
||||
'prod/hbjy/index': () => import('../views/prod/hbjy/index.vue'),
|
||||
'prod/ktpb/index': () => import('../views/prod/ktpb/index.vue'),
|
||||
'notification/messages/index': () => import('../views/notification/messages/index.vue'),
|
||||
'notification/subscribe/index': () => import('../views/notification/subscribe/index.vue'),
|
||||
'notification/type/index': () => import('../views/notification/type/index.vue'),
|
||||
|
||||
@@ -0,0 +1,650 @@
|
||||
<script setup>
|
||||
/**
|
||||
* 看台排班(生产-前缫)
|
||||
* 对应 WinForms 端 WinClient\F9.Business.Prod\QS\KTPB\Page_KTPB
|
||||
* - 车头工:一个「车间+班次+机组」一条记录(2 名车头工)
|
||||
* - 挡车工:一个「车间+班次+机组」一组记录,按车台区间分派挡车工,挡车数自动计算
|
||||
* - 值班长:一个「车间+班次」可有多名值班长
|
||||
*/
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import request from '../../../utils/request';
|
||||
import EmployeeSelect from '../../../components/select/EmployeeSelect.vue';
|
||||
|
||||
const TABS = [
|
||||
{ name: 'dcg', label: '挡车工' },
|
||||
{ name: 'ctg', label: '车头工' },
|
||||
{ name: 'zbz', label: '值班长' },
|
||||
];
|
||||
const BANCI = [
|
||||
{ value: 1, label: '甲班' },
|
||||
{ value: 2, label: '乙班' },
|
||||
];
|
||||
|
||||
const tab = ref('dcg');
|
||||
const deps = ref([]);
|
||||
const jizuOptions = ref([]);
|
||||
|
||||
const filter = reactive({ depId: 0, banci: 1, jizu: 0, keyword: '' });
|
||||
|
||||
const state = reactive({
|
||||
dcg: { rows: [], total: 0, loading: false, pageIndex: 1, pageSize: 20 },
|
||||
ctg: { rows: [], total: 0, loading: false, pageIndex: 1, pageSize: 20 },
|
||||
zbz: { rows: [], total: 0, loading: false, pageIndex: 1, pageSize: 20 },
|
||||
});
|
||||
const cur = computed(() => state[tab.value]);
|
||||
|
||||
const depName = computed(() => deps.value.find((d) => d.id === filter.depId)?.name || '');
|
||||
const banciName = computed(() => BANCI.find((b) => b.value === filter.banci)?.label || '');
|
||||
|
||||
// ==================== 通用查询 ====================
|
||||
|
||||
async function loadDeps() {
|
||||
const res = await request.get('/prod/ktpb/qianxian-deps');
|
||||
deps.value = res.data || [];
|
||||
if (deps.value.length && !filter.depId) filter.depId = deps.value[0].id;
|
||||
}
|
||||
|
||||
async function loadJizu() {
|
||||
if (!filter.depId) {
|
||||
jizuOptions.value = [];
|
||||
return;
|
||||
}
|
||||
const res = await request.get('/prod/ktpb/jizu-options', { params: { depId: filter.depId } });
|
||||
jizuOptions.value = res.data || [];
|
||||
if (!jizuOptions.value.some((x) => x.jizu === filter.jizu)) {
|
||||
filter.jizu = jizuOptions.value.length ? jizuOptions.value[0].jizu : 0;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadList(resetPage = false) {
|
||||
const s = cur.value;
|
||||
if (resetPage) s.pageIndex = 1;
|
||||
s.loading = true;
|
||||
try {
|
||||
const res = await request.get(`/prod/ktpb/${tab.value}`, {
|
||||
params: {
|
||||
depId: filter.depId || 0,
|
||||
banci: filter.banci || null,
|
||||
jizu: tab.value === 'zbz' ? null : filter.jizu || null,
|
||||
like: filter.keyword,
|
||||
pageIndex: s.pageIndex,
|
||||
pageSize: s.pageSize,
|
||||
},
|
||||
});
|
||||
s.rows = res.data?.items || [];
|
||||
s.total = Number(res.data?.total || 0);
|
||||
} finally {
|
||||
s.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function onDepChange() {
|
||||
await loadJizu();
|
||||
await loadList(true);
|
||||
}
|
||||
async function onSearch() {
|
||||
await loadList(true);
|
||||
}
|
||||
async function onPageChange(p) {
|
||||
cur.value.pageIndex = p;
|
||||
await loadList();
|
||||
}
|
||||
async function onTabChange(name) {
|
||||
await loadList();
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadDeps();
|
||||
await loadJizu();
|
||||
await loadList(true);
|
||||
});
|
||||
|
||||
// ==================== 挡车工:整组批量编辑 ====================
|
||||
|
||||
const dcgDlg = reactive({
|
||||
visible: false,
|
||||
loading: false,
|
||||
saving: false,
|
||||
depId: 0,
|
||||
banci: 1,
|
||||
jizu: 0,
|
||||
chetaiStart: 0,
|
||||
chetaiEnd: 0,
|
||||
rows: [],
|
||||
});
|
||||
|
||||
const dcgDepName = computed(() => deps.value.find((d) => d.id === dcgDlg.depId)?.name || '');
|
||||
const dcgBanciName = computed(() => BANCI.find((b) => b.value === dcgDlg.banci)?.label || '');
|
||||
const dcgTotalChetai = computed(() =>
|
||||
dcgDlg.chetaiStart && dcgDlg.chetaiEnd ? dcgDlg.chetaiEnd - dcgDlg.chetaiStart + 1 : 0
|
||||
);
|
||||
const dcgAssigned = computed(() => {
|
||||
const set = new Set();
|
||||
dcgDlg.rows.forEach((r) => {
|
||||
const s = Number(r.chetaiStart) || 0;
|
||||
const e = Number(r.chetaiEnd) || 0;
|
||||
for (let i = s; i <= e; i++) set.add(i);
|
||||
});
|
||||
return set.size;
|
||||
});
|
||||
|
||||
async function openDcgGroup() {
|
||||
if (!filter.depId) return ElMessage.warning('请先选择车间');
|
||||
if (!filter.jizu) return ElMessage.warning('请先选择机组');
|
||||
|
||||
const opt = jizuOptions.value.find((x) => x.jizu === filter.jizu);
|
||||
dcgDlg.depId = filter.depId;
|
||||
dcgDlg.banci = filter.banci;
|
||||
dcgDlg.jizu = filter.jizu;
|
||||
dcgDlg.chetaiStart = opt?.chetaiStart || 0;
|
||||
dcgDlg.chetaiEnd = opt?.chetaiEnd || 0;
|
||||
dcgDlg.visible = true;
|
||||
dcgDlg.loading = true;
|
||||
try {
|
||||
const res = await request.get('/prod/ktpb/dcg/group', {
|
||||
params: { depId: dcgDlg.depId, banci: dcgDlg.banci, jizu: dcgDlg.jizu },
|
||||
});
|
||||
dcgDlg.rows = (res.data || []).map((x) => ({
|
||||
id: x.id,
|
||||
ygId: x.ygId,
|
||||
ygName: x.ygName,
|
||||
chetaiStart: x.chetaiStart,
|
||||
chetaiEnd: x.chetaiEnd,
|
||||
dangcheshu: x.dangcheshu,
|
||||
}));
|
||||
recalcDangche(dcgDlg.rows);
|
||||
} finally {
|
||||
dcgDlg.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function draftDcgRow(name, start, end) {
|
||||
dcgDlg.rows.push({ id: 0, ygId: 0, ygName: name, chetaiStart: start, chetaiEnd: end, dangcheshu: 0 });
|
||||
}
|
||||
|
||||
function appendEmployees(picked) {
|
||||
const exist = new Set(dcgDlg.rows.map((r) => String(r.ygId)));
|
||||
let next = dcgDlg.chetaiStart;
|
||||
if (dcgDlg.rows.length) {
|
||||
next = Math.max(...dcgDlg.rows.map((r) => Number(r.chetaiEnd) || 0)) + 1;
|
||||
}
|
||||
picked.forEach((e) => {
|
||||
if (exist.has(String(e.id))) return;
|
||||
if (next > dcgDlg.chetaiEnd) next = dcgDlg.chetaiEnd;
|
||||
draftDcgRow(e.name, next, next);
|
||||
exist.add(String(e.id));
|
||||
next += 1;
|
||||
});
|
||||
recalcDangche(dcgDlg.rows);
|
||||
}
|
||||
|
||||
function removeDcgRow(index) {
|
||||
dcgDlg.rows.splice(index, 1);
|
||||
recalcDangche(dcgDlg.rows);
|
||||
}
|
||||
|
||||
function clearDcgRows() {
|
||||
dcgDlg.rows = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 挡车数计算(与后端 KtpbService.RecalcDangcheshu 完全一致):
|
||||
* 单台车权重 1;若该车台被多条记录共享,且同时是某记录起点与某记录终点(接壤),权重 0.5。
|
||||
*/
|
||||
function recalcDangche(rows) {
|
||||
const counts = {};
|
||||
const starts = new Set();
|
||||
const ends = new Set();
|
||||
rows.forEach((r) => {
|
||||
const s = Number(r.chetaiStart) || 0;
|
||||
const e = Number(r.chetaiEnd) || 0;
|
||||
if (!s || e < s) return;
|
||||
starts.add(s);
|
||||
ends.add(e);
|
||||
for (let i = s; i <= e; i++) counts[i] = (counts[i] || 0) + 1;
|
||||
});
|
||||
rows.forEach((r) => {
|
||||
const s = Number(r.chetaiStart) || 0;
|
||||
const e = Number(r.chetaiEnd) || 0;
|
||||
let total = 0;
|
||||
for (let i = s; i <= e; i++) {
|
||||
let w = 1;
|
||||
if ((counts[i] || 0) > 1 && starts.has(i) && ends.has(i)) w = 0.5;
|
||||
total += w;
|
||||
}
|
||||
r.dangcheshu = Math.round(total * 100) / 100;
|
||||
});
|
||||
}
|
||||
|
||||
const empPicker = reactive({ visible: false, multiple: false, target: '' });
|
||||
|
||||
function pickEmployee(target) {
|
||||
empPicker.target = target;
|
||||
empPicker.multiple = target === 'dcg';
|
||||
empPicker.visible = true;
|
||||
}
|
||||
|
||||
function onEmployeePicked(picked) {
|
||||
if (empPicker.target === 'dcg') {
|
||||
appendEmployees(picked);
|
||||
} else if (empPicker.target === 'ctg1') {
|
||||
ctgForm.ygid1 = picked.id;
|
||||
ctgForm.ygid1Name = picked.name;
|
||||
} else if (empPicker.target === 'ctg2') {
|
||||
ctgForm.ygid2 = picked.id;
|
||||
ctgForm.ygid2Name = picked.name;
|
||||
} else if (empPicker.target === 'zbz') {
|
||||
zbzForm.ygId = picked.id;
|
||||
zbzForm.ygName = picked.name;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveDcgGroup() {
|
||||
for (const r of dcgDlg.rows) {
|
||||
if (!r.ygId) return ElMessage.warning('存在未选择挡车工的记录');
|
||||
if (!r.chetaiStart) return ElMessage.warning(`挡车工「${r.ygName}」请填写起始车台`);
|
||||
if (!r.chetaiEnd || Number(r.chetaiEnd) < Number(r.chetaiStart)) {
|
||||
return ElMessage.warning(`挡车工「${r.ygName}」结束车台不能小于起始车台`);
|
||||
}
|
||||
if (Number(r.chetaiStart) < dcgDlg.chetaiStart || Number(r.chetaiEnd) > dcgDlg.chetaiEnd) {
|
||||
return ElMessage.warning(`挡车工「${r.ygName}」车台范围必须在本机组 ${dcgDlg.chetaiStart}-${dcgDlg.chetaiEnd} 内`);
|
||||
}
|
||||
}
|
||||
recalcDangche(dcgDlg.rows);
|
||||
dcgDlg.saving = true;
|
||||
try {
|
||||
const res = await request.post('/prod/ktpb/dcg/batch', {
|
||||
depId: dcgDlg.depId,
|
||||
banci: dcgDlg.banci,
|
||||
jizu: dcgDlg.jizu,
|
||||
items: dcgDlg.rows.map((r) => ({
|
||||
ygId: r.ygId,
|
||||
ygName: r.ygName,
|
||||
chetaiStart: Number(r.chetaiStart),
|
||||
chetaiEnd: Number(r.chetaiEnd),
|
||||
dangcheshu: r.dangcheshu,
|
||||
})),
|
||||
});
|
||||
ElMessage.success(res.message || '保存成功');
|
||||
dcgDlg.visible = false;
|
||||
await loadList();
|
||||
} finally {
|
||||
dcgDlg.saving = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteDcg(row) {
|
||||
await ElMessageBox.confirm(
|
||||
`确定删除「${row.deptName} ${row.banciName}班 机组${row.jizu}」中 ${row.ygName} 的排班吗?`,
|
||||
'提示',
|
||||
{ type: 'warning' }
|
||||
);
|
||||
await request.delete(`/prod/ktpb/dcg/${row.id}`);
|
||||
ElMessage.success('删除成功');
|
||||
await loadList();
|
||||
}
|
||||
|
||||
// ==================== 车头工 ====================
|
||||
|
||||
const ctgDlg = reactive({ visible: false, saving: false, id: 0 });
|
||||
const ctgForm = reactive({
|
||||
depId: 0,
|
||||
banci: 1,
|
||||
jizu: 0,
|
||||
ygid1: 0,
|
||||
ygid1Name: '',
|
||||
ygid2: 0,
|
||||
ygid2Name: '',
|
||||
});
|
||||
|
||||
function openCtg(row) {
|
||||
ctgDlg.id = row?.id || 0;
|
||||
ctgForm.depId = row?.depId || filter.depId;
|
||||
ctgForm.banci = row?.banci || filter.banci;
|
||||
ctgForm.jizu = row?.jizu || filter.jizu;
|
||||
ctgForm.ygid1 = row?.ygid1 || 0;
|
||||
ctgForm.ygid1Name = row?.ygid1Name || '';
|
||||
ctgForm.ygid2 = row?.ygid2 || 0;
|
||||
ctgForm.ygid2Name = row?.ygid2Name || '';
|
||||
ctgDlg.visible = true;
|
||||
}
|
||||
|
||||
async function saveCtg() {
|
||||
if (!ctgForm.depId) return ElMessage.warning('请选择车间');
|
||||
if (!ctgForm.jizu) return ElMessage.warning('请选择机组');
|
||||
if (!ctgForm.ygid1) return ElMessage.warning('请选择车头工1');
|
||||
ctgDlg.saving = true;
|
||||
try {
|
||||
const payload = {
|
||||
depId: ctgForm.depId,
|
||||
banci: ctgForm.banci,
|
||||
jizu: ctgForm.jizu,
|
||||
ygid1: ctgForm.ygid1,
|
||||
ygid2: ctgForm.ygid2 || 0,
|
||||
};
|
||||
if (ctgDlg.id) {
|
||||
await request.put(`/prod/ktpb/ctg/${ctgDlg.id}`, payload);
|
||||
ElMessage.success('修改成功');
|
||||
} else {
|
||||
await request.post('/prod/ktpb/ctg', payload);
|
||||
ElMessage.success('新增成功');
|
||||
}
|
||||
ctgDlg.visible = false;
|
||||
await loadList();
|
||||
} finally {
|
||||
ctgDlg.saving = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteCtg(row) {
|
||||
await ElMessageBox.confirm(`确定删除「${row.deptName} ${row.banciName}班 机组${row.jizu}」的车头工排班吗?`, '提示', {
|
||||
type: 'warning',
|
||||
});
|
||||
await request.delete(`/prod/ktpb/ctg/${row.id}`);
|
||||
ElMessage.success('删除成功');
|
||||
await loadList();
|
||||
}
|
||||
|
||||
// ==================== 值班长 ====================
|
||||
|
||||
const zbzDlg = reactive({ visible: false, saving: false, id: 0 });
|
||||
const zbzForm = reactive({ depId: 0, banci: 1, ygId: 0, ygName: '' });
|
||||
|
||||
function openZbz(row) {
|
||||
zbzDlg.id = row?.id || 0;
|
||||
zbzForm.depId = row?.depId || filter.depId;
|
||||
zbzForm.banci = row?.banci || filter.banci;
|
||||
zbzForm.ygId = row?.ygId || 0;
|
||||
zbzForm.ygName = row?.ygName || '';
|
||||
zbzDlg.visible = true;
|
||||
}
|
||||
|
||||
async function saveZbz() {
|
||||
if (!zbzForm.depId) return ElMessage.warning('请选择车间');
|
||||
if (!zbzForm.ygId) return ElMessage.warning('请选择值班长');
|
||||
zbzDlg.saving = true;
|
||||
try {
|
||||
const payload = { depId: zbzForm.depId, banci: zbzForm.banci, ygId: zbzForm.ygId };
|
||||
if (zbzDlg.id) {
|
||||
await request.put(`/prod/ktpb/zbz/${zbzDlg.id}`, payload);
|
||||
ElMessage.success('修改成功');
|
||||
} else {
|
||||
await request.post('/prod/ktpb/zbz', payload);
|
||||
ElMessage.success('新增成功');
|
||||
}
|
||||
zbzDlg.visible = false;
|
||||
await loadList();
|
||||
} finally {
|
||||
zbzDlg.saving = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteZbz(row) {
|
||||
await ElMessageBox.confirm(`确定删除「${row.deptName} ${row.banciName}班」值班长 ${row.ygName} 的排班吗?`, '提示', {
|
||||
type: 'warning',
|
||||
});
|
||||
await request.delete(`/prod/ktpb/zbz/${row.id}`);
|
||||
ElMessage.success('删除成功');
|
||||
await loadList();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="ktpb-page">
|
||||
<el-card shadow="never">
|
||||
<el-tabs v-model="tab" @tab-change="onTabChange">
|
||||
<el-tab-pane v-for="t in TABS" :key="t.name" :label="t.label" :name="t.name" />
|
||||
</el-tabs>
|
||||
|
||||
<div class="toolbar">
|
||||
<span class="lbl">车间</span>
|
||||
<el-select v-model="filter.depId" placeholder="全部车间" clearable style="width: 160px" @change="onDepChange">
|
||||
<el-option v-for="d in deps" :key="d.id" :label="d.name" :value="d.id" />
|
||||
</el-select>
|
||||
|
||||
<span class="lbl">班次</span>
|
||||
<el-select v-model="filter.banci" style="width: 110px" @change="onSearch">
|
||||
<el-option v-for="b in BANCI" :key="b.value" :label="b.label" :value="b.value" />
|
||||
</el-select>
|
||||
|
||||
<template v-if="tab !== 'zbz'">
|
||||
<span class="lbl">机组</span>
|
||||
<el-select v-model="filter.jizu" placeholder="全部机组" clearable style="width: 130px" @change="onSearch">
|
||||
<el-option
|
||||
v-for="j in jizuOptions"
|
||||
:key="j.jizu"
|
||||
:label="`机组${j.jizu}(${j.chetaiStart}-${j.chetaiEnd})`"
|
||||
:value="j.jizu"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<el-input
|
||||
v-model="filter.keyword"
|
||||
placeholder="姓名 / 车台"
|
||||
clearable
|
||||
style="width: 180px"
|
||||
@keyup.enter="onSearch"
|
||||
@clear="onSearch"
|
||||
/>
|
||||
<el-button type="primary" @click="onSearch">查询</el-button>
|
||||
|
||||
<div class="right">
|
||||
<el-button v-if="tab === 'dcg'" type="primary" @click="openDcgGroup">排班维护(按机组)</el-button>
|
||||
<el-button v-else type="primary" @click="tab === 'ctg' ? openCtg(null) : openZbz(null)">新增</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 挡车工 -->
|
||||
<el-table v-if="tab === 'dcg'" v-loading="state.dcg.loading" :data="state.dcg.rows" border size="small">
|
||||
<el-table-column type="index" label="序号" width="55" />
|
||||
<el-table-column prop="deptName" label="车间" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="班次" width="80" align="center">
|
||||
<template #default="{ row }">{{ row.banciName }}班</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="jizu" label="机组" width="70" align="center" />
|
||||
<el-table-column prop="ygName" label="挡车工" width="110" />
|
||||
<el-table-column label="车台范围" width="110" align="center">
|
||||
<template #default="{ row }">{{ row.chetaiStart }} - {{ row.chetaiEnd }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="dangcheshu" label="挡车数" width="90" align="right" />
|
||||
<el-table-column label="操作" width="90" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="danger" @click="deleteDcg(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 车头工 -->
|
||||
<el-table v-else-if="tab === 'ctg'" v-loading="state.ctg.loading" :data="state.ctg.rows" border size="small">
|
||||
<el-table-column type="index" label="序号" width="55" />
|
||||
<el-table-column prop="deptName" label="车间" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="班次" width="80" align="center">
|
||||
<template #default="{ row }">{{ row.banciName }}班</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="jizu" label="机组" width="70" align="center" />
|
||||
<el-table-column prop="ygid1Name" label="车头工1" width="120" />
|
||||
<el-table-column prop="ygid2Name" label="车头工2" width="120" />
|
||||
<el-table-column label="操作" width="130" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="openCtg(row)">修改</el-button>
|
||||
<el-button link type="danger" @click="deleteCtg(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 值班长 -->
|
||||
<el-table v-else v-loading="state.zbz.loading" :data="state.zbz.rows" border size="small">
|
||||
<el-table-column type="index" label="序号" width="55" />
|
||||
<el-table-column prop="deptName" label="车间" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="班次" width="90" align="center">
|
||||
<template #default="{ row }">{{ row.banciName }}班</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="ygName" label="值班长" width="140" />
|
||||
<el-table-column label="操作" width="130" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="openZbz(row)">修改</el-button>
|
||||
<el-button link type="danger" @click="deleteZbz(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
class="pager"
|
||||
:current-page="cur.pageIndex"
|
||||
:page-size="cur.pageSize"
|
||||
:total="cur.total"
|
||||
layout="total, prev, pager, next, jumper"
|
||||
@current-change="onPageChange"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- ============ 挡车工整组排班 ============ -->
|
||||
<el-dialog v-model="dcgDlg.visible" title="挡车工排班维护" width="880px" destroy-on-close>
|
||||
<div class="dlg-head">
|
||||
<el-tag type="info">{{ dcgDepName }}</el-tag>
|
||||
<el-tag type="info">{{ dcgBanciName }}</el-tag>
|
||||
<el-tag type="info">机组 {{ dcgDlg.jizu }}(车台 {{ dcgDlg.chetaiStart }}-{{ dcgDlg.chetaiEnd }})</el-tag>
|
||||
<el-tag :type="dcgAssigned === dcgTotalChetai ? 'success' : 'warning'">
|
||||
已分配 {{ dcgAssigned }} / {{ dcgTotalChetai }} 台
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<div class="dlg-toolbar">
|
||||
<el-button type="primary" size="small" @click="pickEmployee('dcg')">添加挡车工</el-button>
|
||||
<el-button size="small" @click="clearDcgRows">清空</el-button>
|
||||
<span class="hint">车台结束号与下一行起始号接壤时,该车台各按 0.5 台计(挡车数自动计算)</span>
|
||||
</div>
|
||||
|
||||
<el-table v-loading="dcgDlg.loading" :data="dcgDlg.rows" border size="small" height="380">
|
||||
<el-table-column type="index" label="序号" width="55" />
|
||||
<el-table-column label="挡车工" min-width="150">
|
||||
<template #default="{ row, $index }">
|
||||
<span v-if="row.ygName">{{ row.ygName }}</span>
|
||||
<el-button v-else link type="primary" @click="pickEmployee('dcg')">选择</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="起始车台" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.chetaiStart"
|
||||
:min="dcgDlg.chetaiStart"
|
||||
:max="dcgDlg.chetaiEnd"
|
||||
:controls="false"
|
||||
size="small"
|
||||
style="width: 90px"
|
||||
@change="recalcDangche(dcgDlg.rows)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结束车台" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.chetaiEnd"
|
||||
:min="dcgDlg.chetaiStart"
|
||||
:max="dcgDlg.chetaiEnd"
|
||||
:controls="false"
|
||||
size="small"
|
||||
style="width: 90px"
|
||||
@change="recalcDangche(dcgDlg.rows)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="dangcheshu" label="挡车数" width="90" align="right" />
|
||||
<el-table-column label="操作" width="80" align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" @click="removeDcgRow($index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="dcgDlg.visible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="dcgDlg.saving" @click="saveDcgGroup">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- ============ 车头工 ============ -->
|
||||
<el-dialog v-model="ctgDlg.visible" :title="ctgDlg.id ? '修改车头工排班' : '新增车头工排班'" width="520px">
|
||||
<el-form label-width="90px">
|
||||
<el-form-item label="车间">
|
||||
<el-select v-model="ctgForm.depId" placeholder="请选择车间" style="width: 100%">
|
||||
<el-option v-for="d in deps" :key="d.id" :label="d.name" :value="d.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="班次">
|
||||
<el-select v-model="ctgForm.banci" style="width: 100%">
|
||||
<el-option v-for="b in BANCI" :key="b.value" :label="b.label" :value="b.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="机组">
|
||||
<el-input-number v-model="ctgForm.jizu" :min="1" :max="99" :controls="false" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="车头工1">
|
||||
<el-input :model-value="ctgForm.ygid1Name" readonly placeholder="点击选择">
|
||||
<template #append>
|
||||
<el-button @click="pickEmployee('ctg1')">选择</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="车头工2">
|
||||
<el-input :model-value="ctgForm.ygid2Name" readonly placeholder="点击选择(可空)">
|
||||
<template #append>
|
||||
<el-button @click="pickEmployee('ctg2')">选择</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="ctgDlg.visible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="ctgDlg.saving" @click="saveCtg">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- ============ 值班长 ============ -->
|
||||
<el-dialog v-model="zbzDlg.visible" :title="zbzDlg.id ? '修改值班长排班' : '新增值班长排班'" width="480px">
|
||||
<el-form label-width="90px">
|
||||
<el-form-item label="车间">
|
||||
<el-select v-model="zbzForm.depId" placeholder="请选择车间" style="width: 100%">
|
||||
<el-option v-for="d in deps" :key="d.id" :label="d.name" :value="d.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="班次">
|
||||
<el-select v-model="zbzForm.banci" style="width: 100%">
|
||||
<el-option v-for="b in BANCI" :key="b.value" :label="b.label" :value="b.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="值班长">
|
||||
<el-input :model-value="zbzForm.ygName" readonly placeholder="点击选择">
|
||||
<template #append>
|
||||
<el-button @click="pickEmployee('zbz')">选择</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="zbzDlg.visible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="zbzDlg.saving" @click="saveZbz">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<EmployeeSelect
|
||||
v-model="empPicker.visible"
|
||||
:multiple="empPicker.multiple"
|
||||
:dep-id="dcgDlg.visible ? dcgDlg.depId : filter.depId"
|
||||
@confirm="onEmployeePicked"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.ktpb-page :deep(.el-card__body) { padding-top: 6px; }
|
||||
.toolbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
|
||||
.toolbar .lbl { color: var(--el-text-color-regular); font-size: 13px; }
|
||||
.toolbar .right { margin-left: auto; }
|
||||
.pager { margin-top: 10px; justify-content: flex-end; }
|
||||
.dlg-head { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
|
||||
.dlg-toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
|
||||
.dlg-toolbar .hint { color: var(--el-text-color-secondary); font-size: 12px; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user